chore: generate

This commit is contained in:
opencode-agent[bot]
2026-08-18 00:19:50 +00:00
parent e55a91eadd
commit cd3133038b
23 changed files with 213 additions and 226 deletions
@@ -277,8 +277,7 @@ function ProviderConnection(props: {
})
const provider = createMemo(() => ({
id: props.provider,
name:
providers.all().get(props.provider)?.name ?? controller.integration()?.name ?? props.provider,
name: providers.all().get(props.provider)?.name ?? controller.integration()?.name ?? props.provider,
}))
const methodLabel = (value?: { type?: string; label?: string }) => {
if (!value) return ""
@@ -50,7 +50,8 @@ export const DialogSelectMcp: Component = () => {
>
{(i) => {
const mcpStatus = () =>
data.location.mcp.server.list({ directory: sdk().directory })?.find((server) => server.name === i.name)?.status
data.location.mcp.server.list({ directory: sdk().directory })?.find((server) => server.name === i.name)
?.status
const status = () => mcpStatus()?.status
const statusLabel = () => {
const key = status() ? statusLabels[status() as keyof typeof statusLabels] : undefined
@@ -90,8 +90,8 @@ export const ProjectSettingsExtensions: Component = () => {
.sort()
})
const mcpEnabled = (name: string) =>
data.location.mcp.server.list({ directory: directorySDK().directory })?.find((server) => server.name === name)?.status
.status === "connected"
data.location.mcp.server.list({ directory: directorySDK().directory })?.find((server) => server.name === name)
?.status.status === "connected"
const [globalPluginList] = createResource(
() => serverSDK.connection.status() === "connected",
@@ -196,7 +196,6 @@ export const ProjectSettingsExtensions: Component = () => {
<SharedSection count={serverSkills().length}>{skillRows(serverSkills())}</SharedSection>
</div>
</TabsV2.Content>
</TabsV2>
</div>
)
@@ -63,7 +63,10 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
const [head, ...tail] = text.split(" ")
const cmd = head?.startsWith("/") ? head.slice(1) : undefined
if (cmd && input.data.location.command.list({ directory: input.draft.sessionDirectory })?.some((item) => item.name === cmd)) {
if (
cmd &&
input.data.location.command.list({ directory: input.draft.sessionDirectory })?.some((item) => item.name === cmd)
) {
setBusy()
try {
const messageID = Identifier.ascending("message")
@@ -197,8 +200,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
if (!sessionID) return Promise.resolve()
input.onAbort?.()
return serverSDK.api.session.interrupt({ sessionID })
.catch(() => {})
return serverSDK.api.session.interrupt({ sessionID }).catch(() => {})
}
const restoreCommentItems = (
@@ -181,8 +181,7 @@ export function SessionContextTab() {
{ label: "context.stats.reasoningTokens", value: () => formatter().number(ctx()?.tokens.reasoning) },
{
label: "context.stats.cacheTokens",
value: () =>
`${formatter().number(ctx()?.tokens.cache.read)} / ${formatter().number(ctx()?.tokens.cache.write)}`,
value: () => `${formatter().number(ctx()?.tokens.cache.read)} / ${formatter().number(ctx()?.tokens.cache.write)}`,
},
{ label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.intl()) },
{ label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.intl()) },
@@ -305,9 +304,7 @@ export function SessionContextTab() {
</div>
<Accordion multiple>
<For each={messages()}>
{(message) => (
<RawMessage message={message} onRendered={restoreScroll} time={formatter().time} />
)}
{(message) => <RawMessage message={message} onRendered={restoreScroll} time={formatter().time} />}
</For>
</Accordion>
</div>
@@ -27,15 +27,16 @@ export function StatusPopoverBody(props: { shown: boolean }) {
const toggleMcp = useMcpToggle(() => sdk().directory)
const mcp = () => data.location.mcp.server.list({ directory: sdk().directory }) ?? []
const mcpNames = createMemo(() => mcp().map((server) => server.name).sort((a, b) => a.localeCompare(b)))
const mcpNames = createMemo(() =>
mcp()
.map((server) => server.name)
.sort((a, b) => a.localeCompare(b)),
)
const mcpStatus = (name: string) => mcp().find((server) => server.name === name)?.status.status
const mcpConnected = createMemo(() => mcpNames().filter((name) => mcpStatus(name) === "connected").length)
const [pluginList] = createResource(
() => (props.shown ? sdk().directory : undefined),
(directory) =>
serverSDK.api.plugin
.list({ location: { directory } })
.then((result) => result.data),
(directory) => serverSDK.api.plugin.list({ location: { directory } }).then((result) => result.data),
)
const plugins = createMemo(() => (pluginList.latest ?? []).map((item) => item.id))
const pluginCount = createMemo(() => plugins().length)
+8 -10
View File
@@ -78,16 +78,14 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
scope,
normalizeDir: path.normalizeDir,
list: (dir) =>
serverSDK.api.file
.list({ path: dir, location: { directory: scope() } })
.then((x) =>
x.data.map((entry) => ({
...entry,
name: entry.path.split("/").at(-1) ?? entry.path,
absolute: `${scope()}/${entry.path}`,
ignored: false,
})),
),
serverSDK.api.file.list({ path: dir, location: { directory: scope() } }).then((x) =>
x.data.map((entry) => ({
...entry,
name: entry.path.split("/").at(-1) ?? entry.path,
absolute: `${scope()}/${entry.path}`,
ignored: false,
})),
),
onError: (message) => {
showToast({
variant: "error",
@@ -86,5 +86,4 @@ describe("query keys", () => {
{ id: "b", sandboxes: [] },
])
})
})
@@ -233,7 +233,10 @@ export function createChildStoreManager(input: {
},
get mcp() {
return Object.fromEntries(
(input.data.location.mcp.server.list({ directory }) ?? []).map((server) => [server.name, server.status]),
(input.data.location.mcp.server.list({ directory }) ?? []).map((server) => [
server.name,
server.status,
]),
)
},
get mcp_resource() {
@@ -1,9 +1,5 @@
import { describe, expect, test } from "bun:test"
import type {
AgentListOutput,
ModelListOutput,
ProviderListOutput,
} from "@opencode-ai/client/promise"
import type { AgentListOutput, ModelListOutput, ProviderListOutput } from "@opencode-ai/client/promise"
import { directoryKey, normalizeAgentList, normalizeProviderList } from "./utils"
describe("normalizeAgentList", () => {
@@ -1,8 +1,4 @@
import type {
AgentListOutput,
ModelListOutput,
ProviderListOutput,
} from "@opencode-ai/client/promise"
import type { AgentListOutput, ModelListOutput, ProviderListOutput } from "@opencode-ai/client/promise"
import type { Agent, Project, Provider, ProviderListResponse } from "@/types"
import type { Project as CurrentProject } from "@opencode-ai/client/promise"
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
+2 -7
View File
@@ -13,10 +13,7 @@ export type WorkspaceLocation = LocationContext & {
const context = createSimpleContext({
name: "Location",
init: (props: {
directory: string | Accessor<string>
workspaceID?: string | Accessor<string | undefined>
}) => {
init: (props: { directory: string | Accessor<string>; workspaceID?: string | Accessor<string | undefined> }) => {
const serverSDK = useServerSDK()
const data = useData()
const ref = createMemo(() => ({
@@ -44,9 +41,7 @@ const context = createSimpleContext({
})
})
const location = createMemo(() =>
serverSDK.ensureDirSdkContext(current()?.directory ?? ref().directory),
)
const location = createMemo(() => serverSDK.ensureDirSdkContext(current()?.directory ?? ref().directory))
return createMemo<WorkspaceLocation>(() => ({
...location(),
ref: ref(),
+2 -1
View File
@@ -26,7 +26,8 @@ export function useMcpToggle(directory?: Accessor<string | undefined>, onSuccess
} else if (server.status.status === "needs_auth" && server.integrationID) {
const integration = await serverSDK.api.integration.get({ integrationID: server.integrationID, location: ref })
const method = integration.data?.methods.find((item) => item.type === "oauth" && !item.form?.length)
if (!method || method.type !== "oauth") throw new Error(`MCP server ${name} requires an interactive authentication form`)
if (!method || method.type !== "oauth")
throw new Error(`MCP server ${name} requires an interactive authentication form`)
const attempt = await serverSDK.api.integration.oauth.connect({
integrationID: server.integrationID,
methodID: method.id,
+2 -11
View File
@@ -5,12 +5,7 @@ import { getOwner, onCleanup, untrack } from "solid-js"
import { createStore } from "solid-js/store"
import { useLanguage } from "@/context/language"
import { type ServerSDK } from "./server-sdk"
import {
bootstrapDirectory,
bootstrapGlobal,
loadGlobalConfigQuery,
loadPathQuery,
} from "./global-sync/bootstrap"
import { bootstrapDirectory, bootstrapGlobal, loadGlobalConfigQuery, loadPathQuery } from "./global-sync/bootstrap"
import { createChildStoreManager } from "./global-sync/child-store"
import { applyDirectoryEvent, applyGlobalEvent } from "./global-sync/event-reducer"
import type { ProjectMeta } from "./global-sync/types"
@@ -247,11 +242,7 @@ export function createServerSyncContextInner(serverSDK: ServerSDK, data: Data) {
const existing = children.children[key]
if (!existing) return
children.mark(key)
if (
eventType === "config.updated" ||
eventType === "agent.updated"
)
queue.push(key)
if (eventType === "config.updated" || eventType === "agent.updated") queue.push(key)
const [store, setStore] = existing
if (eventType === "worktree.updated") void bootstrap.refetch()
if (eventType !== "vcs.branch.updated")
@@ -57,17 +57,16 @@ export function createHomeSessionsController(home: HomeController) {
? homeSessionIndexKey(ServerConnection.key(conn))
: (["home", "session-index", "unselected"] as const),
enabled: !!ctx && ctx.sdk.connection.status() === "connected",
queryFn:
ctx
? async ({ signal }) => {
const index = await loadHomeSessionIndex(
(input, options) => ctx.sdk.api.session.list(input, options),
signal,
)
index.forEach(ctx.data.session.remember)
return Date.now()
}
: skipToken,
queryFn: ctx
? async ({ signal }) => {
const index = await loadHomeSessionIndex(
(input, options) => ctx.sdk.api.session.list(input, options),
signal,
)
index.forEach(ctx.data.session.remember)
return Date.now()
}
: skipToken,
retry: false,
staleTime: 30_000,
refetchOnMount: true,
@@ -15,9 +15,14 @@ export function useSessionTabAvatarState(
const ctx = serverCtx()
if (!ctx) return false
const permission = ctx.permission
return !!sessionPermissionRequest(ctx.data.session.list(), ctx.data.session.permission.list, sessionId(), (item) => {
return !permission.autoResponds(item, directory())
})
return !!sessionPermissionRequest(
ctx.data.session.list(),
ctx.data.session.permission.list,
sessionId(),
(item) => {
return !permission.autoResponds(item, directory())
},
)
})
const hasQuestions = createMemo(() => {
const data = serverCtx()?.data
@@ -88,7 +88,9 @@ export function createNewSessionWorkspaceController(input: {
)
const projectRoot = createMemo(() => currentProject()?.worktree ?? sdk().directory)
createEffect(() => {
void Promise.all([data.location.syncInfo({ directory: sdk().directory }), data.project.sync()]).catch(() => undefined)
void Promise.all([data.location.syncInfo({ directory: sdk().directory }), data.project.sync()]).catch(
() => undefined,
)
const project = currentProject()
const directories = project ? [project.worktree, ...workspaceDirectories(project)] : [sdk().directory]
directories.forEach((directory) => void data.location.vcs.sync({ directory }).catch(() => undefined))
@@ -40,7 +40,9 @@ export function createPromptInputController(input: {
model: {
selection: input.model ?? local.model,
paid: providers.paid().length > 0,
loading: (local.agent.visible() && data.location.agent.list({ directory: sdk().directory }) === undefined) || !providers.ready(),
loading:
(local.agent.visible() && data.location.agent.list({ directory: sdk().directory }) === undefined) ||
!providers.ready(),
},
session: {
id: input.sessionID(),
@@ -72,9 +72,7 @@ export function SessionComposerRegion(props: {
</div>
)}
</Show>
<div
class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none"
>
<div class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none">
{controller.handoffPrompt() || language.t("prompt.loading")}
</div>
</>
@@ -53,9 +53,9 @@ export function createSessionComposerController() {
if (!primary()) return []
const id = params.id
if (!id) return []
const assistant = data.session.message.list(id).findLast(
(message) => message.type === "assistant" && message.time.completed === undefined,
)
const assistant = data.session.message
.list(id)
.findLast((message) => message.type === "assistant" && message.time.completed === undefined)
if (assistant?.type !== "assistant") return []
return assistant.content.flatMap((part) => {
if (part.type !== "tool" || part.state.status !== "running") return []
@@ -149,14 +149,12 @@ export function createSessionComposerController() {
if (!primary()) return
const sessionID = params.id
if (!sessionID) return
await serverSDK.api.session
.background({ sessionID })
.catch((error) => {
showToast({
title: language.t("common.requestFailed"),
description: error instanceof Error ? error.message : String(error),
})
await serverSDK.api.session.background({ sessionID }).catch((error) => {
showToast({
title: language.t("common.requestFailed"),
description: error instanceof Error ? error.message : String(error),
})
})
}
const [store, setStore] = createStore({
@@ -44,7 +44,6 @@ export function createTimelineModel(input: { session: Pick<SessionController, "i
userMessages: input.session.history.userMessages,
visibleUserMessages: input.session.history.visibleUserMessages,
}
}
export function isTimelineReady(messages: Message[] | undefined, loading: boolean) {
+119 -115
View File
@@ -220,129 +220,133 @@ const setup = Effect.gen(function* () {
return { db, bus, instructions: yield* instructionBuiltIns.load(sessionID) }
})
it.effect("generates from fresh settled Session context without durable mutation", () =>
Effect.gen(function* () {
requests.length = 0
hasHttpMiddleware = false
instruction = "Initial context"
const { db, bus, instructions } = yield* setup
yield* InstructionState.prepare(db, bus, instructions, sessionID)
const existing = SessionMessage.ID.create()
yield* bus.publish(SessionEvent.InboxEnqueued, {
sessionID,
inboxID: existing,
item: { type: "user", payload: { text: "Existing durable context" }, delivery: "steer" },
})
yield* bus.publish(SessionEvent.InboxDelivered, {
sessionID,
inboxID: existing,
})
const settledAssistant = SessionMessage.ID.create()
yield* bus.publish(SessionEvent.Step.Started, {
sessionID,
assistantMessageID: settledAssistant,
agent: Agent.ID.make("build"),
model: { id: ID.make("generate-model"), providerID: Provider.ID.make("test") },
})
yield* bus.publish(SessionEvent.Text.Started, {
sessionID,
assistantMessageID: settledAssistant,
ordinal: 0,
})
yield* bus.publish(SessionEvent.Text.Ended, {
sessionID,
assistantMessageID: settledAssistant,
ordinal: 0,
text: "Settled partial answer",
})
const activeAssistant = SessionMessage.ID.create()
yield* bus.publish(SessionEvent.Step.Started, {
sessionID,
assistantMessageID: activeAssistant,
agent: Agent.ID.make("build"),
model: { id: ID.make("generate-model"), providerID: Provider.ID.make("test") },
})
yield* bus.publish(SessionEvent.Tool.Input.Started, {
sessionID,
assistantMessageID: activeAssistant,
id: "active-call",
name: "echo",
})
yield* bus.publish(SessionEvent.Tool.Input.Ended, {
sessionID,
assistantMessageID: activeAssistant,
id: "active-call",
text: "{}",
})
yield* bus.publish(SessionEvent.Tool.Called, {
sessionID,
assistantMessageID: activeAssistant,
id: "active-call",
input: {},
executed: false,
})
yield* bus.publish(SessionEvent.InboxEnqueued, {
sessionID,
inboxID: SessionMessage.ID.create(),
item: { type: "user", payload: { text: "Queued input must remain invisible" }, delivery: "queue" },
})
instruction = "Changed context"
const before = yield* durableState(db, sessionID)
const hooks = yield* PluginHooks.Service
yield* hooks.register("session", "context", (event) =>
Effect.sync(() => {
event.system = [SystemPart.make("Hooked system"), ...event.system]
if (event.tools.lookup) event.tools.lookup.description = "Hooked lookup"
}),
)
it.effect(
"generates from fresh settled Session context without durable mutation",
() =>
Effect.gen(function* () {
requests.length = 0
hasHttpMiddleware = false
instruction = "Initial context"
const { db, bus, instructions } = yield* setup
yield* InstructionState.prepare(db, bus, instructions, sessionID)
const existing = SessionMessage.ID.create()
yield* bus.publish(SessionEvent.InboxEnqueued, {
sessionID,
inboxID: existing,
item: { type: "user", payload: { text: "Existing durable context" }, delivery: "steer" },
})
yield* bus.publish(SessionEvent.InboxDelivered, {
sessionID,
inboxID: existing,
})
const settledAssistant = SessionMessage.ID.create()
yield* bus.publish(SessionEvent.Step.Started, {
sessionID,
assistantMessageID: settledAssistant,
agent: Agent.ID.make("build"),
model: { id: ID.make("generate-model"), providerID: Provider.ID.make("test") },
})
yield* bus.publish(SessionEvent.Text.Started, {
sessionID,
assistantMessageID: settledAssistant,
ordinal: 0,
})
yield* bus.publish(SessionEvent.Text.Ended, {
sessionID,
assistantMessageID: settledAssistant,
ordinal: 0,
text: "Settled partial answer",
})
const activeAssistant = SessionMessage.ID.create()
yield* bus.publish(SessionEvent.Step.Started, {
sessionID,
assistantMessageID: activeAssistant,
agent: Agent.ID.make("build"),
model: { id: ID.make("generate-model"), providerID: Provider.ID.make("test") },
})
yield* bus.publish(SessionEvent.Tool.Input.Started, {
sessionID,
assistantMessageID: activeAssistant,
id: "active-call",
name: "echo",
})
yield* bus.publish(SessionEvent.Tool.Input.Ended, {
sessionID,
assistantMessageID: activeAssistant,
id: "active-call",
text: "{}",
})
yield* bus.publish(SessionEvent.Tool.Called, {
sessionID,
assistantMessageID: activeAssistant,
id: "active-call",
input: {},
executed: false,
})
yield* bus.publish(SessionEvent.InboxEnqueued, {
sessionID,
inboxID: SessionMessage.ID.create(),
item: { type: "user", payload: { text: "Queued input must remain invisible" }, delivery: "queue" },
})
instruction = "Changed context"
const before = yield* durableState(db, sessionID)
const hooks = yield* PluginHooks.Service
yield* hooks.register("session", "context", (event) =>
Effect.sync(() => {
event.system = [SystemPart.make("Hooked system"), ...event.system]
if (event.tools.lookup) event.tools.lookup.description = "Hooked lookup"
}),
)
const generate = yield* SessionGenerate.Service
const result = yield* generate.generate({ sessionID, prompt: "Summarize privately" })
const generate = yield* SessionGenerate.Service
const result = yield* generate.generate({ sessionID, prompt: "Summarize privately" })
expect(result).toBe("Transient answer")
expect(requests).toHaveLength(1)
expect(hasHttpMiddleware).toBe(true)
expect(requests[0]?.model).toBe(model)
expect(requests[0]?.system[0]?.text).toBe("Hooked system")
expect(requests[0]?.system.map((part) => part.text)).toContain("Initial context")
expect(requests[0]?.http?.headers).toMatchObject({ "X-Session-Id": sessionID })
expect(requests[0]?.promptCacheKey).toBe(sessionID)
const instructionUpdates = requests[0]?.messages.flatMap((message) =>
message.role === "system"
? message.content.flatMap((content) => (content.type === "text" ? [content.text] : []))
: [],
)
expect(instructionUpdates).toHaveLength(1)
expect(instructionUpdates?.[0]).toContain("Changed context")
expect(instructionUpdates?.[0]).toContain("tools.captured.lookup(input: {}): Promise<string>")
expect(userTexts(requests[0])).toEqual(["Existing durable context", "Summarize privately"])
expect(
requests[0]?.messages.flatMap((message) =>
message.role === "assistant"
expect(result).toBe("Transient answer")
expect(requests).toHaveLength(1)
expect(hasHttpMiddleware).toBe(true)
expect(requests[0]?.model).toBe(model)
expect(requests[0]?.system[0]?.text).toBe("Hooked system")
expect(requests[0]?.system.map((part) => part.text)).toContain("Initial context")
expect(requests[0]?.http?.headers).toMatchObject({ "X-Session-Id": sessionID })
expect(requests[0]?.promptCacheKey).toBe(sessionID)
const instructionUpdates = requests[0]?.messages.flatMap((message) =>
message.role === "system"
? message.content.flatMap((content) => (content.type === "text" ? [content.text] : []))
: [],
),
).toEqual(["Settled partial answer"])
expect(requests[0]?.tools).toMatchObject([{ name: "lookup", description: "Hooked lookup" }])
expect(requests[0]?.toolChoice).toBeUndefined()
expect(yield* durableState(db, sessionID)).toEqual(before)
}),
)
expect(instructionUpdates).toHaveLength(1)
expect(instructionUpdates?.[0]).toContain("Changed context")
expect(instructionUpdates?.[0]).toContain("tools.captured.lookup(input: {}): Promise<string>")
expect(userTexts(requests[0])).toEqual(["Existing durable context", "Summarize privately"])
expect(
requests[0]?.messages.flatMap((message) =>
message.role === "assistant"
? message.content.flatMap((content) => (content.type === "text" ? [content.text] : []))
: [],
),
).toEqual(["Settled partial answer"])
expect(requests[0]?.tools).toMatchObject([{ name: "lookup", description: "Hooked lookup" }])
expect(requests[0]?.toolChoice).toBeUndefined()
expect(yield* durableState(db, sessionID)).toEqual(before)
}),
{ timeout: 15_000 },
)
it.effect("blocks unavailable initial instructions before generation", () =>
Effect.gen(function* () {
requests.length = 0
instruction = Instructions.unavailable
const { db } = yield* setup
const before = yield* durableState(db, sessionID)
const generate = yield* SessionGenerate.Service
it.effect(
"blocks unavailable initial instructions before generation",
() =>
Effect.gen(function* () {
requests.length = 0
instruction = Instructions.unavailable
const { db } = yield* setup
const before = yield* durableState(db, sessionID)
const generate = yield* SessionGenerate.Service
const error = yield* generate.generate({ sessionID, prompt: "Summarize privately" }).pipe(Effect.flip)
const error = yield* generate.generate({ sessionID, prompt: "Summarize privately" }).pipe(Effect.flip)
expect(error).toBeInstanceOf(Instructions.InitializationBlocked)
expect(requests).toEqual([])
expect(yield* durableState(db, sessionID)).toEqual(before)
}),
expect(error).toBeInstanceOf(Instructions.InitializationBlocked)
expect(requests).toEqual([])
expect(yield* durableState(db, sessionID)).toEqual(before)
}),
{ timeout: 15_000 },
)
+23 -21
View File
@@ -180,28 +180,30 @@ describeHg("Vcs mercurial", () => {
),
)
it.live("diffs a named branch against the default branch", () =>
withHg((directory) =>
Effect.gen(function* () {
yield* Effect.promise(async () => {
await fs.writeFile(path.join(directory, "file.txt"), "one\n")
await commitAll(directory, "initial")
})
const vcs = yield* Vcs.Service
expect(yield* vcs.diff("branch")).toEqual([])
it.live(
"diffs a named branch against the default branch",
() =>
withHg((directory) =>
Effect.gen(function* () {
yield* Effect.promise(async () => {
await fs.writeFile(path.join(directory, "file.txt"), "one\n")
await commitAll(directory, "initial")
})
const vcs = yield* Vcs.Service
expect(yield* vcs.diff("branch")).toEqual([])
yield* Effect.promise(async () => {
await hg(directory, "branch", "-q", "feature")
await fs.writeFile(path.join(directory, "file.txt"), "one\ntwo\n")
await commitAll(directory, "feature change")
})
const diff = yield* vcs.diff("branch")
expect(diff.map((item) => ({ file: item.file, status: item.status }))).toEqual([
{ file: "file.txt", status: "modified" },
])
expect(diff[0].patch).toContain("+two")
}),
),
yield* Effect.promise(async () => {
await hg(directory, "branch", "-q", "feature")
await fs.writeFile(path.join(directory, "file.txt"), "one\ntwo\n")
await commitAll(directory, "feature change")
})
const diff = yield* vcs.diff("branch")
expect(diff.map((item) => ({ file: item.file, status: item.status }))).toEqual([
{ file: "file.txt", status: "modified" },
])
expect(diff[0].patch).toContain("+two")
}),
),
15_000,
)
})