Skip to content

Commit d8d7a58

Browse files
kitlangtonaprogramq
authored andcommitted
feat(cli): auto-dispose InstanceContext after effectCmd handlers (anomalyco#25481)
1 parent 9bef88e commit d8d7a58

9 files changed

Lines changed: 47 additions & 6 deletions

File tree

packages/opencode/src/acp/agent.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,26 @@ export class Agent implements ACPAgent {
465465
return
466466
}
467467

468+
case "file.edited": {
469+
const props = event.properties
470+
const session = this.sessionManager.tryGet(props.sessionID)
471+
if (!session) return
472+
473+
if (this.config.clientCapabilities?.fs?.writeTextFile) {
474+
try {
475+
await this.connection.writeTextFile({
476+
sessionId: session.id,
477+
path: props.file,
478+
content: await Bun.file(props.file).text(),
479+
})
480+
log.info("synced file edit to ACP client", { filepath: props.file, sessionId: session.id })
481+
} catch (error) {
482+
log.error("failed to sync file edit to ACP client", { error, filepath: props.file, sessionId: session.id })
483+
}
484+
}
485+
return
486+
}
487+
468488
case "message.part.delta": {
469489
const props = event.properties
470490
const session = this.sessionManager.tryGet(props.sessionID)

packages/opencode/src/acp/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { McpServer } from "@agentclientprotocol/sdk"
1+
import type { McpServer, ClientCapabilities } from "@agentclientprotocol/sdk"
22
import type { OpencodeClient } from "@opencode-ai/sdk/v2"
33
import type { ProviderID, ModelID } from "../provider/schema"
44

@@ -21,4 +21,5 @@ export interface ACPConfig {
2121
providerID: ProviderID
2222
modelID: ModelID
2323
}
24+
clientCapabilities?: ClientCapabilities
2425
}

packages/opencode/src/cli/effect-cmd.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Argv } from "yargs"
22
import { Effect, Schema } from "effect"
33
import { AppRuntime, type AppServices } from "@/effect/app-runtime"
44
import { InstanceStore } from "@/project/instance-store"
5+
import { InstanceRef } from "@/effect/instance-ref"
56
import { cmd } from "./cmd/cmd"
67

78
/**
@@ -21,6 +22,11 @@ export const fail = (message: string, exitCode = 1) => Effect.fail(new CliError(
2122
* Effect-native CLI command builder. Wraps yargs `cmd()` so the handler body is
2223
* an `Effect` with `InstanceRef` provided and any `AppServices` yieldable.
2324
*
25+
* The handler is wrapped in `Effect.ensuring(store.dispose(ctx))` so the loaded
26+
* InstanceContext is disposed (runDisposers + IPC `server.instance.disposed`)
27+
* on every Exit — success, typed failure, defect, or interruption. Matches the
28+
* legacy `bootstrap()` finally-disposal semantics without per-handler boilerplate.
29+
*
2430
* Errors propagate to the existing top-level handler in `src/index.ts`; use
2531
* `fail("...")` for user-visible domain failures (clean exit, formatted message).
2632
*
@@ -47,6 +53,17 @@ export const effectCmd = <Args, A>(opts: {
4753
// yargs typing wraps Args in ArgumentsCamelCase<WithDoubleDash<...>>; cast at the boundary.
4854
const args = rawArgs as unknown as Args
4955
const directory = opts.directory?.(args) ?? process.cwd()
50-
await AppRuntime.runPromise(InstanceStore.Service.use((s) => s.provide({ directory }, opts.handler(args))))
56+
await AppRuntime.runPromise(
57+
InstanceStore.Service.use((store) =>
58+
store.provide(
59+
{ directory },
60+
Effect.gen(function* () {
61+
const ctx = yield* InstanceRef
62+
const body = opts.handler(args)
63+
return ctx ? yield* body.pipe(Effect.ensuring(store.dispose(ctx))) : yield* body
64+
}),
65+
),
66+
),
67+
)
5168
},
5269
})

packages/opencode/src/file/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export const Event = {
7272
"file.edited",
7373
Schema.Struct({
7474
file: Schema.String,
75+
sessionID: Schema.String,
7576
}),
7677
),
7778
}

packages/opencode/src/tool/apply_patch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ export const ApplyPatchTool = Tool.define(
249249
if (yield* format.file(edited)) {
250250
yield* Bom.syncFile(afs, edited, change.bom)
251251
}
252-
yield* bus.publish(File.Event.Edited, { file: edited })
252+
yield* bus.publish(File.Event.Edited, { file: edited, sessionID: ctx.sessionID })
253253
}
254254
}
255255

packages/opencode/src/tool/edit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export const EditTool = Tool.define(
108108
if (yield* format.file(filePath)) {
109109
contentNew = yield* Bom.syncFile(afs, filePath, desiredBom)
110110
}
111-
yield* bus.publish(File.Event.Edited, { file: filePath })
111+
yield* bus.publish(File.Event.Edited, { file: filePath, sessionID: ctx.sessionID })
112112
yield* bus.publish(FileWatcher.Event.Updated, {
113113
file: filePath,
114114
event: existed ? "change" : "add",
@@ -152,7 +152,7 @@ export const EditTool = Tool.define(
152152
if (yield* format.file(filePath)) {
153153
contentNew = yield* Bom.syncFile(afs, filePath, desiredBom)
154154
}
155-
yield* bus.publish(File.Event.Edited, { file: filePath })
155+
yield* bus.publish(File.Event.Edited, { file: filePath, sessionID: ctx.sessionID })
156156
yield* bus.publish(FileWatcher.Event.Updated, {
157157
file: filePath,
158158
event: "change",

packages/opencode/src/tool/write.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export const WriteTool = Tool.define(
6565
if (yield* format.file(filepath)) {
6666
yield* Bom.syncFile(fs, filepath, desiredBom)
6767
}
68-
yield* bus.publish(File.Event.Edited, { file: filepath })
68+
yield* bus.publish(File.Event.Edited, { file: filepath, sessionID: ctx.sessionID })
6969
yield* bus.publish(FileWatcher.Event.Updated, {
7070
file: filepath,
7171
event: exists ? "change" : "add",

packages/sdk/js/src/gen/types.gen.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ export type EventFileEdited = {
490490
type: "file.edited"
491491
properties: {
492492
file: string
493+
sessionID?: string
493494
}
494495
}
495496

packages/sdk/js/src/v2/gen/types.gen.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ export type EventFileEdited = {
237237
type: "file.edited"
238238
properties: {
239239
file: string
240+
sessionID: string
240241
}
241242
}
242243

0 commit comments

Comments
 (0)