From 4772b6a3e8c2eaeb7594503c4584fb225c75bb15 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 2 Sep 2026 14:39:02 -0400 Subject: [PATCH] refactor: rename State drafts to editors (#46876) --- packages/core/src/agent.ts | 20 +- packages/core/src/catalog.ts | 32 ++-- packages/core/src/command.ts | 10 +- packages/core/src/config/plugin/agent.ts | 14 +- packages/core/src/config/plugin/command.ts | 4 +- packages/core/src/config/plugin/compaction.ts | 4 +- packages/core/src/config/plugin/formatter.ts | 8 +- packages/core/src/config/plugin/image.ts | 4 +- .../core/src/config/plugin/instruction.ts | 8 +- .../src/config/plugin/location-watcher.ts | 4 +- packages/core/src/config/plugin/mcp.ts | 6 +- packages/core/src/config/plugin/reference.ts | 4 +- packages/core/src/config/plugin/shell.ts | 4 +- packages/core/src/config/plugin/skill.ts | 4 +- packages/core/src/config/plugin/snapshot.ts | 4 +- .../core/src/config/plugin/tool-output.ts | 4 +- .../src/filesystem/location-watcher-policy.ts | 12 +- packages/core/src/formatter.ts | 16 +- packages/core/src/image.ts | 16 +- packages/core/src/instruction-discovery.ts | 18 +- packages/core/src/integration.ts | 28 +-- packages/core/src/mcp/index.ts | 26 +-- packages/core/src/plugin/agent.ts | 14 +- packages/core/src/plugin/command.ts | 8 +- packages/core/src/plugin/host.ts | 87 ++++----- .../core/src/plugin/mcp-codemode-exclusion.ts | 4 +- packages/core/src/plugin/plan.ts | 4 +- packages/core/src/plugin/provider/azure.ts | 6 +- .../plugin/provider/cloudflare-ai-gateway.ts | 4 +- .../plugin/provider/cloudflare-workers-ai.ts | 4 +- .../src/plugin/provider/github-copilot.ts | 6 +- packages/core/src/plugin/provider/openai.ts | 6 +- packages/core/src/plugin/provider/opencode.ts | 8 +- packages/core/src/plugin/provider/xai.ts | 8 +- packages/core/src/plugin/skill.ts | 6 +- packages/core/src/plugin/vcs/git.ts | 4 +- packages/core/src/plugin/vcs/hg.ts | 4 +- packages/core/src/plugin/websearch/exa.ts | 12 +- .../core/src/plugin/websearch/firecrawl.ts | 12 +- .../core/src/plugin/websearch/parallel.ts | 12 +- packages/core/src/plugin/websearch/tavily.ts | 12 +- packages/core/src/reference.ts | 14 +- packages/core/src/session/compaction.ts | 14 +- packages/core/src/shell/select.ts | 10 +- packages/core/src/skill.ts | 16 +- packages/core/src/snapshot.ts | 10 +- packages/core/src/state.ts | 30 +-- packages/core/src/tool-output.ts | 12 +- packages/core/src/tool.ts | 28 +-- packages/core/src/tool/AGENTS.md | 8 +- packages/core/src/tool/mcp.ts | 4 +- packages/core/src/tool/plugin/edit.ts | 4 +- packages/core/src/tool/plugin/glob.ts | 4 +- packages/core/src/tool/plugin/grep.ts | 4 +- packages/core/src/tool/plugin/patch.ts | 4 +- packages/core/src/tool/plugin/question.ts | 4 +- packages/core/src/tool/plugin/read.ts | 4 +- packages/core/src/tool/plugin/shell.ts | 4 +- packages/core/src/tool/plugin/skill.ts | 4 +- packages/core/src/tool/plugin/subagent.ts | 4 +- packages/core/src/tool/plugin/webfetch.ts | 4 +- packages/core/src/tool/plugin/websearch.ts | 4 +- packages/core/src/tool/plugin/write.ts | 4 +- packages/core/src/vcs.ts | 14 +- packages/core/src/websearch.ts | 14 +- packages/core/src/wellknown/plugin.ts | 6 +- packages/core/test/catalog.test.ts | 10 +- packages/core/test/codemode.test.ts | 6 +- .../core/test/codemode/instructions.test.ts | 16 +- packages/core/test/command.test.ts | 14 +- packages/core/test/config/provider.test.ts | 16 +- packages/core/test/config/reload.test.ts | 4 +- packages/core/test/config/websearch.test.ts | 4 +- .../location-watcher-policy.test.ts | 8 +- packages/core/test/filesystem/watcher.test.ts | 2 +- packages/core/test/formatter.test.ts | 8 +- .../core/test/instruction-discovery.test.ts | 34 ++-- packages/core/test/mcp.test.ts | 60 +++--- packages/core/test/plugin.test.ts | 12 +- .../core/test/plugin/fixtures/greeting.ts | 4 +- packages/core/test/plugin/host.ts | 62 +++--- packages/core/test/plugin/models-dev.test.ts | 4 +- .../core/test/plugin/provider-azure.test.ts | 24 +-- .../plugin/provider-github-copilot.test.ts | 6 +- .../test/plugin/provider-lmstudio.test.ts | 18 +- .../core/test/plugin/provider-ollama.test.ts | 12 +- .../test/plugin/provider-opencode.test.ts | 8 +- .../core/test/plugin/provider-vllm.test.ts | 12 +- .../core/test/plugin/provider-xai.test.ts | 6 +- packages/core/test/plugin/supervisor.test.ts | 2 +- .../core/test/plugin/system-prompt.test.ts | 24 +-- packages/core/test/plugin/websearch.test.ts | 4 +- packages/core/test/pty/pty-session.test.ts | 2 +- packages/core/test/reference.test.ts | 22 +-- packages/core/test/session-generate.test.ts | 4 +- .../core/test/session-runner-recorded.test.ts | 4 +- packages/core/test/session-runner.test.ts | 22 +-- packages/core/test/skill.test.ts | 34 ++-- packages/core/test/snapshot.test.ts | 2 +- packages/core/test/state-replay.test.ts | 16 +- packages/core/test/state.test.ts | 178 +++++++++--------- packages/core/test/tool-output.test.ts | 2 +- packages/core/test/tool-read.test.ts | 12 +- packages/core/test/tool-registry.test.ts | 140 +++++++------- packages/core/test/tool-shell.test.ts | 42 ++--- packages/core/test/tool-subagent.test.ts | 10 +- packages/core/test/tool-websearch.test.ts | 4 +- packages/core/test/vcs.test.ts | 76 ++++---- packages/core/test/websearch.test.ts | 16 +- packages/plugin/src/README.md | 4 +- packages/plugin/src/effect/PLAN.md | 2 +- packages/plugin/src/effect/README.md | 2 +- packages/plugin/src/effect/agent.ts | 4 +- packages/plugin/src/effect/catalog.ts | 4 +- packages/plugin/src/effect/command.ts | 4 +- packages/plugin/src/effect/integration.ts | 4 +- packages/plugin/src/effect/mcp.ts | 4 +- packages/plugin/src/effect/reference.ts | 4 +- packages/plugin/src/effect/skill.ts | 4 +- packages/plugin/src/effect/tool.ts | 4 +- packages/plugin/src/effect/vcs.ts | 4 +- packages/plugin/src/effect/websearch.ts | 4 +- packages/plugin/src/promise/adapter.ts | 58 +++--- packages/plugin/src/promise/agent.ts | 4 +- packages/plugin/src/promise/catalog.ts | 4 +- packages/plugin/src/promise/command.ts | 4 +- packages/plugin/src/promise/integration.ts | 4 +- packages/plugin/src/promise/mcp.ts | 4 +- packages/plugin/src/promise/reference.ts | 4 +- packages/plugin/src/promise/skill.ts | 4 +- packages/plugin/src/promise/tool.ts | 4 +- packages/plugin/src/promise/vcs.ts | 4 +- packages/plugin/src/promise/websearch.ts | 4 +- packages/sdk/test/embedded.test.ts | 4 +- packages/sdk/test/instances-effect.test.ts | 8 +- .../server/test/session-instances.test.ts | 12 +- packages/server/test/vcs.test.ts | 6 +- .../src/docs/content/build/plugins/effect.mdx | 148 +++++++-------- .../src/docs/content/build/plugins/index.mdx | 138 +++++++------- 139 files changed, 1064 insertions(+), 1063 deletions(-) diff --git a/packages/core/src/agent.ts b/packages/core/src/agent.ts index fba9e7ec75e..fe22869345c 100644 --- a/packages/core/src/agent.ts +++ b/packages/core/src/agent.ts @@ -34,7 +34,7 @@ type Data = { default?: ID } -export type Draft = { +export type Editor = { list: () => readonly Info[] get: (id: ID) => Info | undefined default: (id: ID | undefined) => void @@ -42,7 +42,7 @@ export type Draft = { remove: (id: ID) => void } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly get: (id: ID) => Effect.Effect readonly resolve: (id?: ID | string) => Effect.Effect readonly select: (id?: ID | string) => Effect.Effect @@ -62,29 +62,29 @@ const layer = Layer.effect( { action: "external_directory", resource: path.join(global.tmp, "*"), effect: "allow" }, { action: "external_directory", resource: path.join(global.config, "*"), effect: "allow" }, ] - const state = State.create({ + const state = State.create({ name: "agent", initial: () => ({ agents: new Map() }), - draft: (draft) => ({ - list: () => Array.fromIterable(draft.agents.values()) as Info[], - get: (id) => draft.agents.get(id), + editor: (editor) => ({ + list: () => Array.fromIterable(editor.agents.values()) as Info[], + get: (id) => editor.agents.get(id), default: (id) => { - draft.default = id + editor.default = id }, update: (id, fn) => { const defaults = Info.default(id) const current = - draft.agents.get(id) ?? + editor.agents.get(id) ?? ({ ...defaults, permissions: [...defaults.permissions, ...permissions], } as Types.DeepMutable) - if (!draft.agents.has(id)) draft.agents.set(id, current) + if (!editor.agents.has(id)) editor.agents.set(id, current) fn(current) current.id = id }, remove: (id) => { - draft.agents.delete(id) + editor.agents.delete(id) }, }), notify: () => bus.publish(Agent.Event.Updated, {}).pipe(Effect.asVoid), diff --git a/packages/core/src/catalog.ts b/packages/core/src/catalog.ts index 07a7a634ea6..45b7c66dbdb 100644 --- a/packages/core/src/catalog.ts +++ b/packages/core/src/catalog.ts @@ -23,7 +23,7 @@ type Data = { defaultModel?: DefaultModel } -export type Draft = { +export type Editor = { provider: { list: () => readonly ProviderRecord[] get: (providerID: Provider.ID) => ProviderRecord | undefined @@ -41,7 +41,7 @@ export type Draft = { } } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly provider: { readonly get: (providerID: Provider.ID) => Effect.Effect readonly all: () => Effect.Effect @@ -82,39 +82,39 @@ const layer = Layer.effect( } satisfies Model.Info } - const state = State.create({ + const state = State.create({ name: "catalog", initial: () => ({ providers: new Map() }), - draft: (draft) => { - const result: Draft = { + editor: (editor) => { + const result: Editor = { provider: { - list: () => Array.fromIterable(draft.providers.values()) as ProviderRecord[], - get: (providerID) => draft.providers.get(providerID), + list: () => Array.fromIterable(editor.providers.values()) as ProviderRecord[], + get: (providerID) => editor.providers.get(providerID), update: (providerID, fn) => { - let current = draft.providers.get(providerID) + let current = editor.providers.get(providerID) if (!current) { current = { provider: Provider.Info.empty(providerID) as Provider.MutableInfo, models: new Map(), } - draft.providers.set(providerID, current) + editor.providers.set(providerID, current) } fn(current.provider) }, remove: (providerID) => { - draft.providers.delete(providerID) + editor.providers.delete(providerID) }, }, model: { - get: (providerID, modelID) => draft.providers.get(providerID)?.models.get(modelID), + get: (providerID, modelID) => editor.providers.get(providerID)?.models.get(modelID), update: (providerID, modelID, fn) => { - let record = draft.providers.get(providerID) + let record = editor.providers.get(providerID) if (!record) { record = { provider: Provider.Info.empty(providerID) as Provider.MutableInfo, models: new Map(), } - draft.providers.set(providerID, record) + editor.providers.set(providerID, record) } const model = record.models.get(modelID) ?? (Model.Info.default(providerID, modelID) as Model.MutableInfo) if (!record.models.has(modelID)) record.models.set(modelID, model) @@ -123,12 +123,12 @@ const layer = Layer.effect( model.providerID = providerID }, remove: (providerID, modelID) => { - draft.providers.get(providerID)?.models.delete(modelID) + editor.providers.get(providerID)?.models.delete(modelID) }, default: { - get: () => draft.defaultModel, + get: () => editor.defaultModel, set: (providerID, modelID) => { - draft.defaultModel = { providerID, modelID } + editor.defaultModel = { providerID, modelID } }, }, }, diff --git a/packages/core/src/command.ts b/packages/core/src/command.ts index b3a216464ad..9650ad477ac 100644 --- a/packages/core/src/command.ts +++ b/packages/core/src/command.ts @@ -25,7 +25,7 @@ export interface Definition { readonly execute: (input: Invocation) => Effect.Effect } -export type Draft = { +export type Editor = { add: (definition: Definition) => void } @@ -39,7 +39,7 @@ export class ExecutionError extends Schema.TaggedError()("Comman message: Schema.String, }) {} -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly get: (name: string) => Effect.Effect readonly list: () => Effect.Effect readonly execute: (input: { @@ -54,11 +54,11 @@ export const layer = Layer.effect( Service, Effect.gen(function* () { const bus = yield* Bus.Service - const state = State.create, Draft>({ + const state = State.create, Editor>({ name: "command", initial: () => new Map(), - draft: (draft) => ({ - add: (definition) => draft.set(definition.name, definition), + editor: (editor) => ({ + add: (definition) => editor.set(definition.name, definition), }), notify: () => bus.publish(Command.Event.Updated, {}).pipe(Effect.asVoid), }) diff --git a/packages/core/src/config/plugin/agent.ts b/packages/core/src/config/plugin/agent.ts index 263777876ca..b3c37a2f3da 100644 --- a/packages/core/src/config/plugin/agent.ts +++ b/packages/core/src/config/plugin/agent.ts @@ -83,27 +83,27 @@ export const Plugin = define({ Effect.forkScoped({ startImmediately: true }), ) loaded.documents = yield* load() - yield* ctx.agent.transform((draft) => { + yield* ctx.agent.transform((editor) => { const permissions = expandPermissions( loaded.documents.flatMap((document) => document.info.permissions ?? []), global.home, ) const configuredDefault = Config.latest(loaded.documents, "default_agent") - if (configuredDefault !== undefined) draft.default(Agent.ID.make(configuredDefault)) - for (const current of draft.list()) { - draft.update(current.id, (agent) => agent.permissions.push(...permissions)) + if (configuredDefault !== undefined) editor.default(Agent.ID.make(configuredDefault)) + for (const current of editor.list()) { + editor.update(current.id, (agent) => agent.permissions.push(...permissions)) } for (const document of loaded.documents) { for (const [id, item] of Object.entries(document.info.agents ?? {})) { const agentID = Agent.ID.make(id) if (item.disabled) { - draft.remove(agentID) + editor.remove(agentID) continue } - const exists = draft.get(agentID) !== undefined - draft.update(agentID, (agent) => { + const exists = editor.get(agentID) !== undefined + editor.update(agentID, (agent) => { if (!exists) agent.permissions.push(...permissions) if (item.model !== undefined) agent.model = { diff --git a/packages/core/src/config/plugin/command.ts b/packages/core/src/config/plugin/command.ts index fb82ab55703..b74eb3f702e 100644 --- a/packages/core/src/config/plugin/command.ts +++ b/packages/core/src/config/plugin/command.ts @@ -64,10 +64,10 @@ export const Plugin = define({ Effect.forkScoped({ startImmediately: true }), ) loaded.documents = yield* load() - yield* ctx.command.transform((draft) => { + yield* ctx.command.transform((editor) => { for (const document of loaded.documents) { for (const [name, command] of Object.entries(document.commands ?? {})) { - draft.add({ + editor.add({ name, description: command.description, execute: (input) => diff --git a/packages/core/src/config/plugin/compaction.ts b/packages/core/src/config/plugin/compaction.ts index 40a5a1287a8..0b7a2c60ede 100644 --- a/packages/core/src/config/plugin/compaction.ts +++ b/packages/core/src/config/plugin/compaction.ts @@ -12,10 +12,10 @@ export const Plugin = define({ const config = yield* Config.Service const compaction = yield* SessionCompaction.Service const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, compaction.reload()) - yield* compaction.transform((draft) => { + yield* compaction.transform((editor) => { for (const entry of loaded.entries) { if (entry.type !== "document" || !entry.info.compaction) continue - draft.configure({ + editor.configure({ ...(entry.info.compaction.auto === undefined ? {} : { auto: entry.info.compaction.auto }), ...(entry.info.compaction.buffer === undefined ? {} : { buffer: entry.info.compaction.buffer }), ...(entry.info.compaction.keep?.tokens === undefined ? {} : { tokens: entry.info.compaction.keep.tokens }), diff --git a/packages/core/src/config/plugin/formatter.ts b/packages/core/src/config/plugin/formatter.ts index 151be859d4e..483e8727706 100644 --- a/packages/core/src/config/plugin/formatter.ts +++ b/packages/core/src/config/plugin/formatter.ts @@ -24,7 +24,7 @@ export const Plugin = define({ const processes = yield* AppProcess.Service const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, formatter.reload()) - yield* formatter.transform((draft) => { + yield* formatter.transform((editor) => { const configured = Config.latest(loaded.entries, "formatter") if (!configured) return const builtIns = make({ @@ -35,12 +35,12 @@ export const Plugin = define({ processes, bin: global.bin, }) - builtIns.forEach(draft.set) + builtIns.forEach(editor.set) if (configured === true) return for (const [name, entry] of Object.entries(configured)) { if (entry.disabled) { - draft.remove(name) + editor.remove(name) continue } const builtIn = builtIns.find((formatter) => formatter.name === name) @@ -51,7 +51,7 @@ export const Plugin = define({ enabled: builtIn && !entry.command ? builtIn.enabled : Effect.succeed(entry.command ? [...entry.command] : false), } - draft.set(current) + editor.set(current) } }) }), diff --git a/packages/core/src/config/plugin/image.ts b/packages/core/src/config/plugin/image.ts index 5595591a9a0..ac18c8c6f0f 100644 --- a/packages/core/src/config/plugin/image.ts +++ b/packages/core/src/config/plugin/image.ts @@ -12,12 +12,12 @@ export const Plugin = define({ const config = yield* Config.Service const image = yield* Image.Service const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, image.reload()) - yield* image.transform((draft) => { + yield* image.transform((editor) => { for (const entry of loaded.entries) { if (entry.type !== "document") continue const configured = entry.info.media?.image if (!configured) continue - draft.configure({ + editor.configure({ ...(configured.auto_resize === undefined ? {} : { autoResize: configured.auto_resize }), ...(configured.max_width === undefined ? {} : { maxWidth: configured.max_width }), ...(configured.max_height === undefined ? {} : { maxHeight: configured.max_height }), diff --git a/packages/core/src/config/plugin/instruction.ts b/packages/core/src/config/plugin/instruction.ts index e0b36e614e3..df50d0a2b22 100644 --- a/packages/core/src/config/plugin/instruction.ts +++ b/packages/core/src/config/plugin/instruction.ts @@ -112,17 +112,17 @@ export const Plugin = define({ Effect.forkScoped({ startImmediately: true }), ) yield* refresh() - yield* discovery.transform((draft) => { + yield* discovery.transform((editor) => { if (loaded.current.type === "unavailable") { - draft.unavailable() + editor.unavailable() return } - for (const file of loaded.current.files) draft.add(file) + for (const file of loaded.current.files) editor.add(file) }) }).pipe( Effect.catchCause((cause) => Effect.logWarning("failed to activate instruction source", { cause }).pipe( - Effect.andThen(discovery.transform((draft) => draft.unavailable())), + Effect.andThen(discovery.transform((editor) => editor.unavailable())), Effect.asVoid, ), ), diff --git a/packages/core/src/config/plugin/location-watcher.ts b/packages/core/src/config/plugin/location-watcher.ts index 539227fe430..2db0d42985e 100644 --- a/packages/core/src/config/plugin/location-watcher.ts +++ b/packages/core/src/config/plugin/location-watcher.ts @@ -12,10 +12,10 @@ export const Plugin = define({ const config = yield* Config.Service const policy = yield* LocationWatcherPolicy.Service const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, policy.reload()) - yield* policy.transform((draft) => { + yield* policy.transform((editor) => { for (const entry of loaded.entries) { if (entry.type !== "document" || !entry.info.watcher?.ignore) continue - draft.add(entry.info.watcher.ignore) + editor.add(entry.info.watcher.ignore) } }) }), diff --git a/packages/core/src/config/plugin/mcp.ts b/packages/core/src/config/plugin/mcp.ts index 80bfe806c5d..a18632703a5 100644 --- a/packages/core/src/config/plugin/mcp.ts +++ b/packages/core/src/config/plugin/mcp.ts @@ -36,7 +36,7 @@ export const register = Effect.fn("ConfigMCPPlugin.register")(function* ( // Subscribe before the initial load so updates racing it trigger a rebuild. loaded.entries = yield* config.entries() - yield* mcp.transform((draft) => { + yield* mcp.transform((editor) => { const documents = loaded.entries.filter((entry): entry is Document => entry.type === "document") // Global timeout defaults merge in config order; each server can override them. const timeout = Object.assign( @@ -50,8 +50,8 @@ export const register = Effect.fn("ConfigMCPPlugin.register")(function* ( } } for (const [name, server] of servers) { - if (draft.get(name)) continue - draft.set(name, { ...server, timeout: { ...timeout, ...server.timeout } }) + if (editor.get(name)) continue + editor.set(name, { ...server, timeout: { ...timeout, ...server.timeout } }) } }) }) diff --git a/packages/core/src/config/plugin/reference.ts b/packages/core/src/config/plugin/reference.ts index 58065a3e722..486239b4915 100644 --- a/packages/core/src/config/plugin/reference.ts +++ b/packages/core/src/config/plugin/reference.ts @@ -19,14 +19,14 @@ export const Plugin = define({ const location = yield* Location.Service const global = yield* Global.Service const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, ctx.reference.reload()) - yield* ctx.reference.transform((draft) => { + yield* ctx.reference.transform((editor) => { for (const doc of loaded.entries.filter((entry): entry is Document => entry.type === "document")) { const directory = doc.path ? path.dirname(doc.path) : location.directory for (const [name, entry] of Object.entries(doc.info.references ?? {})) { if (!validAlias(name)) continue const description = typeof entry === "string" ? undefined : entry.description const hidden = typeof entry === "string" ? undefined : entry.hidden - draft.add( + editor.add( name, local(entry) ? Reference.LocalSource.make({ diff --git a/packages/core/src/config/plugin/shell.ts b/packages/core/src/config/plugin/shell.ts index 08a0bb55f2f..bb2f60c8bc3 100644 --- a/packages/core/src/config/plugin/shell.ts +++ b/packages/core/src/config/plugin/shell.ts @@ -12,9 +12,9 @@ export const Plugin = define({ const config = yield* Config.Service const shell = yield* ShellSelect.Service const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, shell.reload()) - yield* shell.transform((draft) => { + yield* shell.transform((editor) => { const configured = Config.latest(loaded.entries, "shell") - if (configured) draft.configure(configured) + if (configured) editor.configure(configured) }) }), }) diff --git a/packages/core/src/config/plugin/skill.ts b/packages/core/src/config/plugin/skill.ts index 2c6f6292e9e..df4c178bdfb 100644 --- a/packages/core/src/config/plugin/skill.ts +++ b/packages/core/src/config/plugin/skill.ts @@ -180,8 +180,8 @@ export const Plugin = define({ Effect.forkScoped({ startImmediately: true }), ) yield* refresh() - yield* ctx.skill.transform((draft) => { - for (const skill of loaded.skills) draft.add(skill) + yield* ctx.skill.transform((editor) => { + for (const skill of loaded.skills) editor.add(skill) }) yield* ctx.event.subscribe().pipe( Stream.filter((event) => event.type === "config.updated"), diff --git a/packages/core/src/config/plugin/snapshot.ts b/packages/core/src/config/plugin/snapshot.ts index d045dcc16b6..3ee0b24d769 100644 --- a/packages/core/src/config/plugin/snapshot.ts +++ b/packages/core/src/config/plugin/snapshot.ts @@ -12,10 +12,10 @@ export const Plugin = define({ const config = yield* Config.Service const snapshot = yield* Snapshot.Service const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, snapshot.reload()) - yield* snapshot.transform((draft) => { + yield* snapshot.transform((editor) => { const configured = Config.latest(loaded.entries, "snapshots") if (configured === undefined) return - draft.configure(configured) + editor.configure(configured) }) }), }) diff --git a/packages/core/src/config/plugin/tool-output.ts b/packages/core/src/config/plugin/tool-output.ts index c8edd581517..c565191d1b6 100644 --- a/packages/core/src/config/plugin/tool-output.ts +++ b/packages/core/src/config/plugin/tool-output.ts @@ -12,10 +12,10 @@ export const Plugin = define({ const config = yield* Config.Service const output = yield* ToolOutput.Service const loaded = yield* ConfigEntryObserver.observe(config, ctx.event, output.reload()) - yield* output.transform((draft) => { + yield* output.transform((editor) => { const configured = Config.latest(loaded.entries, "tool_output") if (!configured) return - draft.configure({ + editor.configure({ ...(configured.max_lines === undefined ? {} : { maxLines: configured.max_lines }), ...(configured.max_bytes === undefined ? {} : { maxBytes: configured.max_bytes }), }) diff --git a/packages/core/src/filesystem/location-watcher-policy.ts b/packages/core/src/filesystem/location-watcher-policy.ts index 40f20a76fd1..c700b27a5b4 100644 --- a/packages/core/src/filesystem/location-watcher-policy.ts +++ b/packages/core/src/filesystem/location-watcher-policy.ts @@ -8,12 +8,12 @@ type Data = { ignore: string[] } -export type Draft = { +export type Editor = { add: (ignore: readonly string[]) => void list: () => readonly string[] } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly current: () => readonly string[] readonly observe: ( listener: (ignore: readonly string[]) => Effect.Effect, @@ -26,12 +26,12 @@ const layer = Layer.effect( Service, Effect.gen(function* () { const listeners = new Set<(ignore: readonly string[]) => Effect.Effect>() - const state = State.create({ + const state = State.create({ name: "location-watcher-policy", initial: () => ({ ignore: [] }), - draft: (draft) => ({ - add: (ignore) => draft.ignore.push(...ignore), - list: () => draft.ignore, + editor: (editor) => ({ + add: (ignore) => editor.ignore.push(...ignore), + list: () => editor.ignore, }), // Read per listener: a reentrant transform inside an earlier listener must reach later ones. notify: () => Effect.forEach(listeners, (listener) => listener(current()), { discard: true }), diff --git a/packages/core/src/formatter.ts b/packages/core/src/formatter.ts index af17112ff16..f487f696892 100644 --- a/packages/core/src/formatter.ts +++ b/packages/core/src/formatter.ts @@ -13,12 +13,12 @@ type Data = { formatters: Info[] } -export type Draft = { +export type Editor = { set: (formatter: Info) => void remove: (name: string) => void } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly file: (filepath: string) => Effect.Effect } @@ -30,17 +30,17 @@ const layer = Layer.effect( const location = yield* Location.Service const processes = yield* AppProcess.Service const commands = new WeakMap() - const state = State.create({ + const state = State.create({ name: "formatter", initial: () => ({ formatters: [] }), - draft: (draft) => ({ + editor: (editor) => ({ set: (formatter) => { - const index = draft.formatters.findIndex((item) => item.name === formatter.name) - if (index === -1) draft.formatters.push(formatter) - else draft.formatters[index] = formatter + const index = editor.formatters.findIndex((item) => item.name === formatter.name) + if (index === -1) editor.formatters.push(formatter) + else editor.formatters[index] = formatter }, remove: (name) => { - draft.formatters = draft.formatters.filter((formatter) => formatter.name !== name) + editor.formatters = editor.formatters.filter((formatter) => formatter.name !== name) }, }), }) diff --git a/packages/core/src/image.ts b/packages/core/src/image.ts index b62c28d50be..2547b43a048 100644 --- a/packages/core/src/image.ts +++ b/packages/core/src/image.ts @@ -39,11 +39,11 @@ export type Limits = { maxBase64Bytes: number } -export type Draft = { +export type Editor = { configure: (limits: Partial) => void } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly normalize: ( resource: string, content: FileSystem.Content & { readonly encoding: "base64" }, @@ -58,7 +58,7 @@ export class Service extends Context.Service()("@opencode/Im const layer = Layer.effect( Service, Effect.gen(function* () { - const state = State.create({ + const state = State.create({ name: "image", initial: () => ({ autoResize: true, @@ -66,12 +66,12 @@ const layer = Layer.effect( maxHeight: 2_000, maxBase64Bytes: 5 * 1024 * 1024, }), - draft: (draft) => ({ + editor: (editor) => ({ configure: (limits) => { - if (limits.autoResize !== undefined) draft.autoResize = limits.autoResize - if (limits.maxWidth !== undefined) draft.maxWidth = limits.maxWidth - if (limits.maxHeight !== undefined) draft.maxHeight = limits.maxHeight - if (limits.maxBase64Bytes !== undefined) draft.maxBase64Bytes = limits.maxBase64Bytes + if (limits.autoResize !== undefined) editor.autoResize = limits.autoResize + if (limits.maxWidth !== undefined) editor.maxWidth = limits.maxWidth + if (limits.maxHeight !== undefined) editor.maxHeight = limits.maxHeight + if (limits.maxBase64Bytes !== undefined) editor.maxBase64Bytes = limits.maxBase64Bytes }, }), }) diff --git a/packages/core/src/instruction-discovery.ts b/packages/core/src/instruction-discovery.ts index d6bc4d2f9be..abab217f11c 100644 --- a/packages/core/src/instruction-discovery.ts +++ b/packages/core/src/instruction-discovery.ts @@ -25,7 +25,7 @@ export type Data = { available: boolean } -export type Draft = { +export type Editor = { list: () => readonly Types.DeepMutable[] // Map insertion order is render order: config adds global then nearest-to-farthest project files; // sibling contributors interleave by transform registration order. @@ -35,7 +35,7 @@ export type Draft = { unavailable: () => void } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { // Discovery policy lives here because internal plugins have no per-composition options channel. // Move it into plugin config once plugins can consume their own options. readonly project: boolean @@ -57,21 +57,21 @@ export const layer = (options?: Options) => Service, Effect.gen(function* () { const bus = yield* Bus.Service - const state = State.create({ + const state = State.create({ name: "instruction-discovery", initial: () => ({ files: new Map(), available: true }), - draft: (draft) => ({ - list: () => Array.from(draft.files.values()), - add: (file) => draft.files.set(file.path, new File(file) as Types.DeepMutable), + editor: (editor) => ({ + list: () => Array.from(editor.files.values()), + add: (file) => editor.files.set(file.path, new File(file) as Types.DeepMutable), update: (path, update) => { - const current = draft.files.get(AbsolutePath.make(path)) + const current = editor.files.get(AbsolutePath.make(path)) if (!current) return update(current) current.path = AbsolutePath.make(path) }, - remove: (path) => draft.files.delete(AbsolutePath.make(path)), + remove: (path) => editor.files.delete(AbsolutePath.make(path)), unavailable: () => { - draft.available = false + editor.available = false }, }), notify: () => bus.publish(Event.Updated, {}).pipe(Effect.asVoid), diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index 21a9339c16f..d73f85ac518 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -130,7 +130,7 @@ type Data = { integrations: Map } -export type Draft = { +export type Editor = { list: () => readonly Ref[] get: (id: ID) => Ref | undefined update: (id: ID, update: (integration: Types.DeepMutable) => void) => void @@ -142,7 +142,7 @@ export type Draft = { } } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { /** Registers a scoped transform over the integration registry. */ /** Returns one integration with its methods and current connections. */ readonly get: (id: ID) => Effect.Effect @@ -266,27 +266,27 @@ const layer = Layer.effect( const scope = yield* Scope.Scope const attempts = SynchronizedRef.makeUnsafe(new Map()) const commandAttempts = SynchronizedRef.makeUnsafe(new Map()) - const state = State.create({ + const state = State.create({ name: "integration", initial: () => ({ integrations: new Map() }), - draft: (draft) => ({ - list: () => Array.from(draft.integrations.values(), (entry) => entry.ref) as Ref[], - get: (id) => draft.integrations.get(id)?.ref as Ref | undefined, + editor: (editor) => ({ + list: () => Array.from(editor.integrations.values(), (entry) => entry.ref) as Ref[], + get: (id) => editor.integrations.get(id)?.ref as Ref | undefined, update: (id, update) => { - const current = draft.integrations.get(id) ?? { + const current = editor.integrations.get(id) ?? { ref: { id, name: id }, methods: [], implementations: new Map(), } - if (!draft.integrations.has(id)) draft.integrations.set(id, current) + if (!editor.integrations.has(id)) editor.integrations.set(id, current) update(current.ref) current.ref.id = id }, - remove: (id) => draft.integrations.delete(id), + remove: (id) => editor.integrations.delete(id), method: { - list: (integrationID) => (draft.integrations.get(integrationID)?.methods as Method[] | undefined) ?? [], + list: (integrationID) => (editor.integrations.get(integrationID)?.methods as Method[] | undefined) ?? [], update: (implementation) => { - const current = draft.integrations.get(implementation.integrationID) ?? { + const current = editor.integrations.get(implementation.integrationID) ?? { ref: { id: implementation.integrationID, name: implementation.integrationID, @@ -294,8 +294,8 @@ const layer = Layer.effect( methods: [], implementations: new Map>(), } - if (!draft.integrations.has(implementation.integrationID)) { - draft.integrations.set(implementation.integrationID, current) + if (!editor.integrations.has(implementation.integrationID)) { + editor.integrations.set(implementation.integrationID, current) } const index = current.methods.findIndex((method) => { if (method.type !== implementation.method.type) return false @@ -315,7 +315,7 @@ const layer = Layer.effect( } }, remove: (integrationID, method) => { - const current = draft.integrations.get(integrationID) + const current = editor.integrations.get(integrationID) if (!current) return const index = current.methods.findIndex((candidate) => { if (candidate.type !== method.type) return false diff --git a/packages/core/src/mcp/index.ts b/packages/core/src/mcp/index.ts index ef2eb6b1d19..45ad5b21447 100644 --- a/packages/core/src/mcp/index.ts +++ b/packages/core/src/mcp/index.ts @@ -133,7 +133,7 @@ type Data = { removed: Set } -export type Draft = { +export type Editor = { list: () => readonly [ServerName, Types.DeepMutable][] get: (server: ServerName | string) => Types.DeepMutable | undefined set: (server: ServerName | string, config: Mcp.ServerConfig) => void @@ -143,7 +143,7 @@ export type Draft = { const cloneConfig = (config: Mcp.ServerConfig) => structuredClone(config) as Types.DeepMutable -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly servers: () => Effect.Effect readonly add: (server: ServerName | string, config: Mcp.ServerConfig) => Effect.Effect readonly connect: (server: ServerName | string) => Effect.Effect @@ -227,12 +227,12 @@ export const layer = (options?: Options) => const scope = yield* Scope.fork(root) entry.registration = { dispose: Scope.close(scope, Exit.void) } yield* integration - .transform((draft) => { - draft.update(integrationID, (ref) => { + .transform((editor) => { + editor.update(integrationID, (ref) => { ref.name = name ref.metadata = { source: "mcp" } }) - draft.method.update({ + editor.method.update({ integrationID, method: { id: methodID, type: "oauth", label: name }, authorize: () => @@ -678,7 +678,7 @@ export const layer = (options?: Options) => Stream.runForEach((event) => Effect.sync(() => fork(reconnect(event.data.integrationID)))), ), ) - const state: State.Interface = State.create({ + const state: State.Interface = State.create({ name: "mcp", initial: () => ({ servers: new Map( @@ -688,20 +688,20 @@ export const layer = (options?: Options) => ), removed: new Set(Array.from(overrides).flatMap(([name, config]) => (config === false ? [name] : []))), }), - draft: (draft) => ({ - list: () => Array.from(draft.servers), - get: (server) => draft.servers.get(ServerName.make(server)), + editor: (editor) => ({ + list: () => Array.from(editor.servers), + get: (server) => editor.servers.get(ServerName.make(server)), set: (server, serverConfig) => { const name = ServerName.make(server) - if (draft.removed.has(name)) return - draft.servers.set(name, cloneConfig(serverConfig)) + if (editor.removed.has(name)) return + editor.servers.set(name, cloneConfig(serverConfig)) }, update: (server, update) => { - const current = draft.servers.get(ServerName.make(server)) + const current = editor.servers.get(ServerName.make(server)) if (!current) return update(current) }, - remove: (server) => draft.servers.delete(ServerName.make(server)), + remove: (server) => editor.servers.delete(ServerName.make(server)), }), notify: () => State.reconcile(root, fork, () => reconcileLock.withPermit(reconcile())), }) diff --git a/packages/core/src/plugin/agent.ts b/packages/core/src/plugin/agent.ts index ee4101f8bbd..8d16ae58a87 100644 --- a/packages/core/src/plugin/agent.ts +++ b/packages/core/src/plugin/agent.ts @@ -83,15 +83,15 @@ Rules: export const Plugin = define({ id: "opencode.agent", effect: Effect.fn(function* (ctx) { - yield* ctx.agent.transform((draft) => { - draft.update(Agent.defaultID, (item) => { + yield* ctx.agent.transform((editor) => { + editor.update(Agent.defaultID, (item) => { item.name = Agent.Name.make("Build") item.description = "The default agent. Executes tools based on configured permissions." item.mode = "primary" item.permissions.push({ action: "question", resource: "*", effect: "allow" }) }) - draft.update(Agent.ID.make("general"), (item) => { + editor.update(Agent.ID.make("general"), (item) => { item.name = Agent.Name.make("General") item.description = "General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel." @@ -102,7 +102,7 @@ export const Plugin = define({ ) }) - draft.update(Agent.ID.make("explore"), (item) => { + editor.update(Agent.ID.make("explore"), (item) => { const externalDirectories = item.permissions.filter( (rule) => rule.action === "external_directory" && rule.effect === "allow", ) @@ -130,13 +130,13 @@ export const Plugin = define({ ) }) - draft.update(Agent.ID.make("compaction"), (item) => { + editor.update(Agent.ID.make("compaction"), (item) => { item.name = Agent.Name.make("Compaction") item.mode = "primary" item.hidden = true }) - draft.update(Agent.ID.make("title"), (item) => { + editor.update(Agent.ID.make("title"), (item) => { item.name = Agent.Name.make("Title") item.mode = "primary" item.hidden = true @@ -144,7 +144,7 @@ export const Plugin = define({ item.permissions.push({ action: "*", resource: "*", effect: "deny" }) }) - draft.update(Agent.ID.make("summary"), (item) => { + editor.update(Agent.ID.make("summary"), (item) => { item.name = Agent.Name.make("Summary") item.mode = "primary" item.hidden = true diff --git a/packages/core/src/plugin/command.ts b/packages/core/src/plugin/command.ts index 0dc1616c10c..3b5df7daa0c 100644 --- a/packages/core/src/plugin/command.ts +++ b/packages/core/src/plugin/command.ts @@ -25,8 +25,8 @@ export const Plugin = define({ Effect.forkScoped({ startImmediately: true }), ) loaded.prompts = yield* mcp.prompts() - yield* ctx.command.transform((draft) => { - draft.add({ + yield* ctx.command.transform((editor) => { + editor.add({ name: "init", description: "guided AGENTS.md setup", execute: (input) => @@ -39,7 +39,7 @@ export const Plugin = define({ }) .pipe(Effect.asVoid), }) - draft.add({ + editor.add({ name: "review", description: "review changes [commit|branch|pr], defaults to uncommitted", execute: (input) => @@ -53,7 +53,7 @@ export const Plugin = define({ .pipe(Effect.asVoid), }) for (const prompt of loaded.prompts) { - draft.add({ + editor.add({ name: mcpCommandName(prompt.server, prompt.name), description: prompt.description, execute: (input) => diff --git a/packages/core/src/plugin/host.ts b/packages/core/src/plugin/host.ts index 7431b0fc52f..0d128bdcb66 100644 --- a/packages/core/src/plugin/host.ts +++ b/packages/core/src/plugin/host.ts @@ -136,13 +136,13 @@ export const make = Effect.fn("PluginHost.make")(function* ( }, reload: agents.reload, transform: (callback) => - agents.transform((draft) => { + agents.transform((editor) => { callback({ - list: () => mutable(draft.list()), - get: (id) => mutable(draft.get(Agent.ID.make(id))), - default: (id) => draft.default(id === undefined ? undefined : Agent.ID.make(id)), - update: (id, update) => draft.update(Agent.ID.make(id), update), - remove: (id) => draft.remove(Agent.ID.make(id)), + list: () => mutable(editor.list()), + get: (id) => mutable(editor.get(Agent.ID.make(id))), + default: (id) => editor.default(id === undefined ? undefined : Agent.ID.make(id)), + update: (id, update) => editor.update(Agent.ID.make(id), update), + remove: (id) => editor.remove(Agent.ID.make(id)), }) }), }, @@ -196,24 +196,25 @@ export const make = Effect.fn("PluginHost.make")(function* ( }, reload: catalog.reload, transform: (callback) => - catalog.transform((draft) => { + catalog.transform((editor) => { callback({ provider: { - list: () => mutable(draft.provider.list()), - get: (id) => mutable(draft.provider.get(Provider.ID.make(id))), - update: (id, update) => draft.provider.update(Provider.ID.make(id), update), - remove: (id) => draft.provider.remove(Provider.ID.make(id)), + list: () => mutable(editor.provider.list()), + get: (id) => mutable(editor.provider.get(Provider.ID.make(id))), + update: (id, update) => editor.provider.update(Provider.ID.make(id), update), + remove: (id) => editor.provider.remove(Provider.ID.make(id)), }, model: { get: (providerID, modelID) => - mutable(draft.model.get(Provider.ID.make(providerID), Model.ID.make(modelID))), + mutable(editor.model.get(Provider.ID.make(providerID), Model.ID.make(modelID))), update: (providerID, modelID, update) => - draft.model.update(Provider.ID.make(providerID), Model.ID.make(modelID), update), - remove: (providerID, modelID) => draft.model.remove(Provider.ID.make(providerID), Model.ID.make(modelID)), + editor.model.update(Provider.ID.make(providerID), Model.ID.make(modelID), update), + remove: (providerID, modelID) => + editor.model.remove(Provider.ID.make(providerID), Model.ID.make(modelID)), default: { - get: draft.model.default.get, + get: editor.model.default.get, set: (providerID, modelID) => - draft.model.default.set(Provider.ID.make(providerID), Model.ID.make(modelID)), + editor.model.default.set(Provider.ID.make(providerID), Model.ID.make(modelID)), }, }, }) @@ -315,17 +316,17 @@ export const make = Effect.fn("PluginHost.make")(function* ( ), }, transform: (callback) => - integration.transform((draft) => { + integration.transform((editor) => { callback({ - list: () => mutable(draft.list()), - get: (id) => mutable(draft.get(Integration.ID.make(id))), - update: (id, update) => draft.update(Integration.ID.make(id), update), - remove: (id) => draft.remove(Integration.ID.make(id)), + list: () => mutable(editor.list()), + get: (id) => mutable(editor.get(Integration.ID.make(id))), + update: (id, update) => editor.update(Integration.ID.make(id), update), + remove: (id) => editor.remove(Integration.ID.make(id)), method: { - list: (id) => draft.method.list(Integration.ID.make(id)), - update: (input) => draft.method.update(methodImplementation(input)), + list: (id) => editor.method.list(Integration.ID.make(id)), + update: (input) => editor.method.update(methodImplementation(input)), remove: (id, method) => - draft.method.remove(Integration.ID.make(id), Schema.decodeUnknownSync(Integration.Method)(method)), + editor.method.remove(Integration.ID.make(id), Schema.decodeUnknownSync(Integration.Method)(method)), }, }) }), @@ -350,13 +351,13 @@ export const make = Effect.fn("PluginHost.make")(function* ( }, reload: mcp.reload, transform: (callback) => - mcp.transform((draft) => { + mcp.transform((editor) => { callback({ - list: () => draft.list().map(([name, config]) => [name, mutable(config)]), - get: (name) => mutable(draft.get(name)), - set: (name, config) => draft.set(name, Schema.decodeUnknownSync(ServerConfig)(config)), - update: draft.update, - remove: draft.remove, + list: () => editor.list().map(([name, config]) => [name, mutable(config)]), + get: (name) => mutable(editor.get(name)), + set: (name, config) => editor.set(name, Schema.decodeUnknownSync(ServerConfig)(config)), + update: editor.update, + remove: editor.remove, }) }), }, @@ -391,11 +392,11 @@ export const make = Effect.fn("PluginHost.make")(function* ( list: () => response(reference.list()), reload: reference.reload, transform: (callback) => - reference.transform((draft) => { + reference.transform((editor) => { callback({ - add: (name, source) => draft.add(name, Schema.decodeUnknownSync(Reference.Source)(source)), - remove: draft.remove, - list: draft.list, + add: (name, source) => editor.add(name, Schema.decodeUnknownSync(Reference.Source)(source)), + remove: editor.remove, + list: editor.list, }) }), }, @@ -403,12 +404,12 @@ export const make = Effect.fn("PluginHost.make")(function* ( list: () => response(skill.list()), reload: skill.reload, transform: (callback) => - skill.transform((draft) => { + skill.transform((editor) => { callback({ - list: () => mutable(draft.list()), - add: (value) => draft.add(Schema.decodeUnknownSync(Skill.Info)(value)), - update: draft.update, - remove: draft.remove, + list: () => mutable(editor.list()), + add: (value) => editor.add(Schema.decodeUnknownSync(Skill.Info)(value)), + update: editor.update, + remove: editor.remove, }) }), }, @@ -441,18 +442,18 @@ export const make = Effect.fn("PluginHost.make")(function* ( ), reload: websearch.reload, transform: (callback) => - websearch.transform((draft) => { + websearch.transform((editor) => { callback({ add: (definition) => - draft.add({ + editor.add({ id: WebSearch.ID.make(definition.id), name: definition.name, execute: definition.execute, }), default: { - get: draft.default.get, + get: editor.default.get, set: (selection) => - draft.default.set( + editor.default.set( selection === false || selection === "random" ? selection : WebSearch.ID.make(selection), ), }, diff --git a/packages/core/src/plugin/mcp-codemode-exclusion.ts b/packages/core/src/plugin/mcp-codemode-exclusion.ts index 7dba5c7a97d..cfd3c4bd7d3 100644 --- a/packages/core/src/plugin/mcp-codemode-exclusion.ts +++ b/packages/core/src/plugin/mcp-codemode-exclusion.ts @@ -9,8 +9,8 @@ const urls = [/^https:\/\/executor\.sh\/[^/]+\/mcp$/] export const Plugin = define({ id: "opencode.mcp.codemode.exclusion", effect: Effect.fn(function* (ctx) { - yield* ctx.mcp.transform((draft) => { - for (const [, server] of draft.list()) { + yield* ctx.mcp.transform((editor) => { + for (const [, server] of editor.list()) { if (server.codemode !== undefined) continue if (server.type === "local") { if (server.command[0] === "executor" && server.command[1] === "mcp") server.codemode = false diff --git a/packages/core/src/plugin/plan.ts b/packages/core/src/plugin/plan.ts index 2404327d391..a271d03cfdf 100644 --- a/packages/core/src/plugin/plan.ts +++ b/packages/core/src/plugin/plan.ts @@ -30,8 +30,8 @@ export const Plugin = define({ const global = yield* Global.Service const directory = path.join(global.home, ".opencode", "plan") const enterReminder = enter(directory) - yield* ctx.agent.transform((draft) => { - draft.update(plan, (item) => { + yield* ctx.agent.transform((editor) => { + editor.update(plan, (item) => { item.name = Agent.Name.make("Plan") item.description = "Read-only agent for exploring the codebase and planning work before implementation." item.mode = "primary" diff --git a/packages/core/src/plugin/provider/azure.ts b/packages/core/src/plugin/provider/azure.ts index 9eb79918d94..694ea6ce29d 100644 --- a/packages/core/src/plugin/provider/azure.ts +++ b/packages/core/src/plugin/provider/azure.ts @@ -80,13 +80,13 @@ export const AzurePlugin = define({ ]) }) - yield* ctx.integration.transform((draft) => { - draft.method.update({ + yield* ctx.integration.transform((editor) => { + editor.method.update({ integrationID: Provider.ID.azure, method: { type: "key", label: "API key", form: form() }, }) if (!available) return - draft.method.update({ + editor.method.update({ integrationID: Provider.ID.azure, method: { id: methodID, diff --git a/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts b/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts index ac7cc168efd..bc8dd302594 100644 --- a/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts +++ b/packages/core/src/plugin/provider/cloudflare-ai-gateway.ts @@ -39,8 +39,8 @@ export const CloudflareAIGatewayPlugin = define({ if (gatewayId) return Form.Fields.make([accountIdForm]) return Form.Fields.make([accountIdForm, gatewayIdForm]) }) - yield* ctx.integration.transform((draft) => { - draft.method.update({ + yield* ctx.integration.transform((editor) => { + editor.method.update({ integrationID: providerID, method: { type: "key", diff --git a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts index f4c2220038e..e8600788e18 100644 --- a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts +++ b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts @@ -25,8 +25,8 @@ export const CloudflareWorkersAIPlugin = define({ }, ]) }) - yield* ctx.integration.transform((draft) => { - draft.method.update({ + yield* ctx.integration.transform((editor) => { + editor.method.update({ integrationID: providerID, method: { type: "key", diff --git a/packages/core/src/plugin/provider/github-copilot.ts b/packages/core/src/plugin/provider/github-copilot.ts index d7a60fda13d..00ec5f4541c 100644 --- a/packages/core/src/plugin/provider/github-copilot.ts +++ b/packages/core/src/plugin/provider/github-copilot.ts @@ -190,9 +190,9 @@ export const GithubCopilotPlugin = define({ ) }) - yield* ctx.integration.transform((draft) => { - draft.method.remove("github-copilot", { type: "key" }) - draft.method.update(oauth(ctx.app)) + yield* ctx.integration.transform((editor) => { + editor.method.remove("github-copilot", { type: "key" }) + editor.method.update(oauth(ctx.app)) }) yield* ctx.catalog.transform((evt) => { const item = evt.provider.get(Provider.ID.githubCopilot) diff --git a/packages/core/src/plugin/provider/openai.ts b/packages/core/src/plugin/provider/openai.ts index 930d32d02f9..d084a79408b 100644 --- a/packages/core/src/plugin/provider/openai.ts +++ b/packages/core/src/plugin/provider/openai.ts @@ -245,9 +245,9 @@ export const OpenAIPlugin = define({ : undefined }) - yield* ctx.integration.transform((draft) => { - draft.method.update(browser(ctx.app)) - draft.method.update(headless(ctx.app)) + yield* ctx.integration.transform((editor) => { + editor.method.update(browser(ctx.app)) + editor.method.update(headless(ctx.app)) }) yield* load() yield* ctx.catalog.transform((evt) => { diff --git a/packages/core/src/plugin/provider/opencode.ts b/packages/core/src/plugin/provider/opencode.ts index 0c6601f55be..cbf53ba4f9b 100644 --- a/packages/core/src/plugin/provider/opencode.ts +++ b/packages/core/src/plugin/provider/opencode.ts @@ -103,12 +103,12 @@ export const OpencodePlugin = define { - draft.update("opencode", (integration) => { + yield* ctx.integration.transform((editor) => { + editor.update("opencode", (integration) => { integration.name = "OpenCode" }) - draft.method.update(oauth(http)) - draft.method.update({ integrationID: "opencode", method: { type: "key", label: "API key (service account)" } }) + editor.method.update(oauth(http)) + editor.method.update({ integrationID: "opencode", method: { type: "key", label: "API key (service account)" } }) }) yield* load() diff --git a/packages/core/src/plugin/provider/xai.ts b/packages/core/src/plugin/provider/xai.ts index 8e9b137a761..94b0be539eb 100644 --- a/packages/core/src/plugin/provider/xai.ts +++ b/packages/core/src/plugin/provider/xai.ts @@ -88,12 +88,12 @@ export const XAIPlugin = define({ { discard: true }, ) - yield* ctx.integration.transform((draft) => { - draft.update("xai", (integration) => { + yield* ctx.integration.transform((editor) => { + editor.update("xai", (integration) => { integration.name = "xAI" }) - draft.method.update(device(ctx.app)) - draft.method.update({ integrationID: "xai", method: { type: "key", label: "Manually enter API Key" } }) + editor.method.update(device(ctx.app)) + editor.method.update({ integrationID: "xai", method: { type: "key", label: "Manually enter API Key" } }) }) yield* ctx.catalog.transform((catalog) => { const provider = catalog.provider.get(providerID) diff --git a/packages/core/src/plugin/skill.ts b/packages/core/src/plugin/skill.ts index 3f25364d22f..f65ec249951 100644 --- a/packages/core/src/plugin/skill.ts +++ b/packages/core/src/plugin/skill.ts @@ -24,8 +24,8 @@ export const Plugin = define({ id: "opencode.skill", effect: Effect.fn(function* (ctx) { const reportContent = yield* reportContentWithDiagnostics(ctx.app) - yield* ctx.skill.transform((draft) => { - draft.add( + yield* ctx.skill.transform((editor) => { + editor.add( Skill.Info.make({ id: Skill.ID.make("opencode"), name: Skill.Name.make("OpenCode"), @@ -34,7 +34,7 @@ export const Plugin = define({ content: OpencodeContent, }), ) - draft.add( + editor.add( Skill.Info.make({ id: Skill.ID.make("report"), name: Skill.Name.make("Report"), diff --git a/packages/core/src/plugin/vcs/git.ts b/packages/core/src/plugin/vcs/git.ts index efc3a968f61..dd62b34925d 100644 --- a/packages/core/src/plugin/vcs/git.ts +++ b/packages/core/src/plugin/vcs/git.ts @@ -30,8 +30,8 @@ export const Plugin = define({ worktree: location.project.directory, }) - yield* ctx.vcs.transform((draft) => { - draft.add({ + yield* ctx.vcs.transform((editor) => { + editor.add({ id: "git", name: "Git", info: () => adapter.info(), diff --git a/packages/core/src/plugin/vcs/hg.ts b/packages/core/src/plugin/vcs/hg.ts index 2e02f0e0157..0f79dbeb37f 100644 --- a/packages/core/src/plugin/vcs/hg.ts +++ b/packages/core/src/plugin/vcs/hg.ts @@ -35,8 +35,8 @@ export const Plugin = define({ worktree: location.project.directory, }) - yield* ctx.vcs.transform((draft) => { - draft.add({ + yield* ctx.vcs.transform((editor) => { + editor.add({ id: "hg", name: "Mercurial", info: () => adapter.info(), diff --git a/packages/core/src/plugin/websearch/exa.ts b/packages/core/src/plugin/websearch/exa.ts index 14090fe7008..77b380db370 100644 --- a/packages/core/src/plugin/websearch/exa.ts +++ b/packages/core/src/plugin/websearch/exa.ts @@ -26,19 +26,19 @@ export const Plugin = define({ id: "opencode.websearch.exa", effect: Effect.fn("WebSearchExa.Plugin")(function* (ctx) { const http = yield* HttpClient.HttpClient - yield* ctx.integration.transform((draft) => { - draft.update("exa", (integration) => (integration.name = "Exa")) - draft.method.update({ + yield* ctx.integration.transform((editor) => { + editor.update("exa", (integration) => (integration.name = "Exa")) + editor.method.update({ integrationID: "exa", method: { type: "key" }, }) - draft.method.update({ + editor.method.update({ integrationID: "exa", method: { type: "env", names: ["EXA_API_KEY"] }, }) }) - yield* ctx.websearch.transform((draft) => { - draft.add({ + yield* ctx.websearch.transform((editor) => { + editor.add({ id: "exa", name: "Exa", execute: (input) => diff --git a/packages/core/src/plugin/websearch/firecrawl.ts b/packages/core/src/plugin/websearch/firecrawl.ts index 0634ba5bad0..4cb56956405 100644 --- a/packages/core/src/plugin/websearch/firecrawl.ts +++ b/packages/core/src/plugin/websearch/firecrawl.ts @@ -37,19 +37,19 @@ export const Plugin = define({ id: "opencode.websearch.firecrawl", effect: Effect.fn("WebSearchFirecrawl.Plugin")(function* (ctx) { const http = yield* HttpClient.HttpClient - yield* ctx.integration.transform((draft) => { - draft.update("firecrawl", (integration) => (integration.name = "Firecrawl")) - draft.method.update({ + yield* ctx.integration.transform((editor) => { + editor.update("firecrawl", (integration) => (integration.name = "Firecrawl")) + editor.method.update({ integrationID: "firecrawl", method: { type: "key" }, }) - draft.method.update({ + editor.method.update({ integrationID: "firecrawl", method: { type: "env", names: ["FIRECRAWL_API_KEY"] }, }) }) - yield* ctx.websearch.transform((draft) => { - draft.add({ + yield* ctx.websearch.transform((editor) => { + editor.add({ id: "firecrawl", name: "Firecrawl", execute: (input) => diff --git a/packages/core/src/plugin/websearch/parallel.ts b/packages/core/src/plugin/websearch/parallel.ts index 2f485b98bff..577aafc2546 100644 --- a/packages/core/src/plugin/websearch/parallel.ts +++ b/packages/core/src/plugin/websearch/parallel.ts @@ -52,19 +52,19 @@ export const Plugin = define({ id: "opencode.websearch.parallel", effect: Effect.fn("WebSearchParallel.Plugin")(function* (ctx) { const http = yield* HttpClient.HttpClient - yield* ctx.integration.transform((draft) => { - draft.update("parallel", (integration) => (integration.name = "Parallel")) - draft.method.update({ + yield* ctx.integration.transform((editor) => { + editor.update("parallel", (integration) => (integration.name = "Parallel")) + editor.method.update({ integrationID: "parallel", method: { type: "key" }, }) - draft.method.update({ + editor.method.update({ integrationID: "parallel", method: { type: "env", names: ["PARALLEL_API_KEY"] }, }) }) - yield* ctx.websearch.transform((draft) => { - draft.add({ + yield* ctx.websearch.transform((editor) => { + editor.add({ id: "parallel", name: "Parallel", execute: (input) => diff --git a/packages/core/src/plugin/websearch/tavily.ts b/packages/core/src/plugin/websearch/tavily.ts index e833c7af490..19be1544f56 100644 --- a/packages/core/src/plugin/websearch/tavily.ts +++ b/packages/core/src/plugin/websearch/tavily.ts @@ -28,19 +28,19 @@ export const Plugin = define({ id: "opencode.websearch.tavily", effect: Effect.fn("WebSearchTavily.Plugin")(function* (ctx) { const http = yield* HttpClient.HttpClient - yield* ctx.integration.transform((draft) => { - draft.update("tavily", (integration) => (integration.name = "Tavily")) - draft.method.update({ + yield* ctx.integration.transform((editor) => { + editor.update("tavily", (integration) => (integration.name = "Tavily")) + editor.method.update({ integrationID: "tavily", method: { type: "key" }, }) - draft.method.update({ + editor.method.update({ integrationID: "tavily", method: { type: "env", names: ["TAVILY_API_KEY"] }, }) }) - yield* ctx.websearch.transform((draft) => { - draft.add({ + yield* ctx.websearch.transform((editor) => { + editor.add({ id: "tavily", name: "Tavily", execute: (input) => diff --git a/packages/core/src/reference.ts b/packages/core/src/reference.ts index 5e2eb22a75d..ba7f2980bc4 100644 --- a/packages/core/src/reference.ts +++ b/packages/core/src/reference.ts @@ -28,13 +28,13 @@ type Data = { sources: Map } -type Draft = { +type Editor = { add(name: string, source: Source): void remove(name: string): void list(): readonly [string, Source][] } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly list: () => Effect.Effect } @@ -91,13 +91,13 @@ const layer = Layer.effect( { concurrency: 4, discard: true }, ) }) - const state = State.create({ + const state = State.create({ name: "reference", initial: () => ({ sources: new Map() }), - draft: (draft) => ({ - add: (name, source) => draft.sources.set(name, source), - remove: (name) => draft.sources.delete(name), - list: () => Array.from(draft.sources), + editor: (editor) => ({ + add: (name, source) => editor.sources.set(name, source), + remove: (name) => editor.sources.delete(name), + list: () => Array.from(editor.sources), }), notify: () => Effect.gen(function* () { diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts index 1c631185056..b3ca1f522a3 100644 --- a/packages/core/src/session/compaction.ts +++ b/packages/core/src/session/compaction.ts @@ -75,7 +75,7 @@ export type Settings = { tokens: number } -export type Draft = { +export type Editor = { configure: (settings: Partial) => void } @@ -116,7 +116,7 @@ export type Outcome = | Pick | Pick -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly enabled: () => boolean readonly required: (input: RequiredInput) => boolean readonly compact: (input: AutoInput) => Effect.Effect @@ -326,14 +326,14 @@ export const layer = Layer.effect( const bus = yield* Bus.Service const llm = yield* LLMClient.Service - const state = State.create({ + const state = State.create({ name: "session-compaction", initial: () => ({ auto: true, buffer: DEFAULT_BUFFER, tokens: DEFAULT_KEEP_TOKENS }), - draft: (draft) => ({ + editor: (editor) => ({ configure: (settings) => { - if (settings.auto !== undefined) draft.auto = settings.auto - if (settings.buffer !== undefined) draft.buffer = settings.buffer - if (settings.tokens !== undefined) draft.tokens = settings.tokens + if (settings.auto !== undefined) editor.auto = settings.auto + if (settings.buffer !== undefined) editor.buffer = settings.buffer + if (settings.tokens !== undefined) editor.tokens = settings.tokens }, }), }) diff --git a/packages/core/src/shell/select.ts b/packages/core/src/shell/select.ts index 0eb7b3d2ac8..44445fd7783 100644 --- a/packages/core/src/shell/select.ts +++ b/packages/core/src/shell/select.ts @@ -37,7 +37,7 @@ type Data = { shell?: string } -export type Draft = { +export type Editor = { configure: (shell: string) => void } @@ -45,7 +45,7 @@ export type ResolveInput = { priority: "config" | "compat" } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly resolve: (input: ResolveInput) => Effect.Effect } @@ -201,12 +201,12 @@ const layer = (options?: Options) => Service, Effect.gen(function* () { const global = yield* Global.Service - const state = State.create({ + const state = State.create({ name: "shell-select", initial: () => ({}), - draft: (draft) => ({ + editor: (editor) => ({ configure: (shell) => { - draft.shell = shell + editor.shell = shell }, }), }) diff --git a/packages/core/src/skill.ts b/packages/core/src/skill.ts index 1bcae148fe1..50962ae33ee 100644 --- a/packages/core/src/skill.ts +++ b/packages/core/src/skill.ts @@ -72,14 +72,14 @@ export type Data = { skills: Map> } -export type Draft = { +export type Editor = { list: () => readonly Types.DeepMutable[] add: (skill: Info) => void update: (id: string, update: (skill: Types.DeepMutable) => void) => void remove: (id: string) => void } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly get: (id: ID) => Effect.Effect readonly list: () => Effect.Effect } @@ -91,22 +91,22 @@ const layer = Layer.effect( Effect.gen(function* () { const bus = yield* Bus.Service - const state = State.create({ + const state = State.create({ name: "skill", initial: () => ({ skills: new Map() }), - draft: (draft) => ({ - list: () => Array.from(draft.skills.values()), + editor: (editor) => ({ + list: () => Array.from(editor.skills.values()), add: (skill) => { - draft.skills.set(skill.id, { ...skill } as Types.DeepMutable) + editor.skills.set(skill.id, { ...skill } as Types.DeepMutable) }, update: (id, update) => { - const current = draft.skills.get(ID.make(id)) + const current = editor.skills.get(ID.make(id)) if (!current) return update(current) current.id = ID.make(id) }, remove: (id) => { - draft.skills.delete(ID.make(id)) + editor.skills.delete(ID.make(id)) }, }), notify: () => bus.publish(Skill.Event.Updated, {}).pipe(Effect.asVoid), diff --git a/packages/core/src/snapshot.ts b/packages/core/src/snapshot.ts index a824cf24384..25e79d29e09 100644 --- a/packages/core/src/snapshot.ts +++ b/packages/core/src/snapshot.ts @@ -36,11 +36,11 @@ export interface RestoreInput { readonly files: ReadonlyMap } -export type Draft = { +export type Editor = { configure: (enabled: boolean) => void } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { /** * Capture the current Location-scoped filesystem state as a content-addressed * tree. Returns `undefined` when snapshots are disabled, unsupported, or the @@ -77,12 +77,12 @@ const layer = Layer.effect( const global = yield* Global.Service const location = yield* Location.Service const lifetime = yield* Scope.Scope - const state = State.create<{ enabled: boolean }, Draft>({ + const state = State.create<{ enabled: boolean }, Editor>({ name: "snapshot", initial: () => ({ enabled: true }), - draft: (draft) => ({ + editor: (editor) => ({ configure: (enabled) => { - draft.enabled = enabled + editor.enabled = enabled }, }), }) diff --git a/packages/core/src/state.ts b/packages/core/src/state.ts index f47c28f0c83..4ee7d408ea1 100644 --- a/packages/core/src/state.ts +++ b/packages/core/src/state.ts @@ -5,11 +5,11 @@ import { Cause, Context, Effect, Exit, Fiber, Scope } from "effect" /** * A synchronous, replayable edit to the current domain state. * - * Domain drafts expose readable and writable state while preserving concise + * Domain editors expose readable and writable state while preserving concise * plugin/config code. Transforms synchronously rebuild derived state. */ -type TransformCallback = (draft: DraftApi) => void -export type MakeDraft = (state: State) => DraftApi +type TransformCallback = (editor: Editor) => void +export type MakeEditor = (state: State) => Editor export interface Registration { readonly dispose: Effect.Effect @@ -19,15 +19,15 @@ export interface Registration { * Registers a scoped transform. Reads rebuild by applying every registered transform in order. * Closing the owning Scope removes the transform and invalidates the current value. */ -export type Transform = ( - transform: TransformCallback, +export type Transform = ( + transform: TransformCallback, ) => Effect.Effect /** Invalidates the current value after captured inputs change and notifies like a registration would. */ export type Reload = () => Effect.Effect -export interface Transformable { - readonly transform: Transform +export interface Transformable { + readonly transform: Transform readonly reload: Reload } @@ -88,12 +88,12 @@ export const inherit = Effect.fnUntraced(function* () { return (effect: Effect.Effect) => Effect.provideService(effect, CurrentBatch, batch) }) -export interface Options { +export interface Options { readonly name?: string /** Creates the empty base value for every rebuild. */ readonly initial: () => State - /** Wraps mutable state in a domain-specific draft API. */ - readonly draft: MakeDraft + /** Wraps mutable state in a domain-specific editor API. */ + readonly editor: MakeEditor /** * Observes the freshly rebuilt value outside the read path. Every registration, disposal, or * reload notifies once it is applied; a batch coalesces them into one notification at its end. @@ -102,7 +102,7 @@ export interface Options { readonly notify?: (state: State) => Effect.Effect } -export interface Interface extends Transformable { +export interface Interface extends Transformable { /** * Rebuilds synchronously when transforms changed since the last read. Each rebuild produces a new * value and never touches earlier ones, so callers may retain what they read. @@ -110,17 +110,17 @@ export interface Interface extends Transformable { readonly get: () => State } -export function create(options: Options): Interface { +export function create(options: Options): Interface { let state = options.initial() - const transforms: { run: TransformCallback }[] = [] + const transforms: { run: TransformCallback }[] = [] let dirty = false let closed = false const get = () => { if (closed || !dirty) return state const next = options.initial() - const draft = options.draft(next) - for (const transform of transforms) transform.run(draft) + const editor = options.editor(next) + for (const transform of transforms) transform.run(editor) // Only a complete fold becomes visible; a throwing callback leaves the previous value and stays dirty. state = next dirty = false diff --git a/packages/core/src/tool-output.ts b/packages/core/src/tool-output.ts index db29cbe3560..f908cce6f38 100644 --- a/packages/core/src/tool-output.ts +++ b/packages/core/src/tool-output.ts @@ -22,11 +22,11 @@ type Limits = { maxBytes: number } -export type Draft = { +export type Editor = { configure: (limits: Partial) => void } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly truncate: (result: Result) => Effect.Effect readonly cleanup: () => Effect.Effect } @@ -51,13 +51,13 @@ const layer = Layer.effect( const fs = yield* FSUtil.Service const global = yield* Global.Service const directory = path.join(global.data, DIRECTORY) - const state = State.create({ + const state = State.create({ name: "tool-output", initial: () => ({ maxLines: MAX_LINES, maxBytes: MAX_BYTES }), - draft: (draft) => ({ + editor: (editor) => ({ configure: (limits) => { - if (limits.maxLines !== undefined) draft.maxLines = limits.maxLines - if (limits.maxBytes !== undefined) draft.maxBytes = limits.maxBytes + if (limits.maxLines !== undefined) editor.maxLines = limits.maxLines + if (limits.maxBytes !== undefined) editor.maxBytes = limits.maxBytes }, }), }) diff --git a/packages/core/src/tool.ts b/packages/core/src/tool.ts index 9fd0ceae695..1137d047849 100644 --- a/packages/core/src/tool.ts +++ b/packages/core/src/tool.ts @@ -23,7 +23,7 @@ export class RegistrationError extends Schema.TaggedError()(" message: Schema.String, }) {} -export interface Draft { +export interface Editor { readonly list: () => readonly (Tool.Info & { readonly id: string })[] readonly get: (id: string) => (Tool.Info & { readonly id: string }) | undefined readonly namespace: (namespace: Tool.Namespace) => void @@ -38,7 +38,7 @@ type Data = { errors: { kind: "tool" | "namespace"; name: string; namespace?: string; error: RegistrationError }[] } -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly snapshot: (permissions?: Permission.Ruleset) => Effect.Effect } @@ -149,35 +149,35 @@ const layer = Layer.effect( } }) - const state = State.create({ + const state = State.create({ name: "tool", initial: () => ({ tools: new Map(), namespaces: new Map(), errors: [], }), - draft: (draft) => ({ - list: () => Array.from(draft.tools.values()), - get: (id) => draft.tools.get(id), + editor: (editor) => ({ + list: () => Array.from(editor.tools.values()), + get: (id) => editor.tools.get(id), namespace: (namespace) => { const error = namespaceError(namespace.name) if (error) { - draft.errors.push({ kind: "namespace", name: namespace.name, namespace: namespace.name, error }) + editor.errors.push({ kind: "namespace", name: namespace.name, namespace: namespace.name, error }) return } - draft.namespaces.set(namespace.name, { ...namespace }) + editor.namespaces.set(namespace.name, { ...namespace }) }, add: (tool) => { const error = registrationError(tool) if (error) { - draft.errors.push({ kind: "tool", name: tool.name, namespace: tool.options?.namespace, error }) + editor.errors.push({ kind: "tool", name: tool.name, namespace: tool.options?.namespace, error }) return } const id = effectiveName(tool) - draft.tools.set(id, { ...tool, id, options: tool.options && { ...tool.options } }) + editor.tools.set(id, { ...tool, id, options: tool.options && { ...tool.options } }) }, update: (id, update) => { - const current = draft.tools.get(id) + const current = editor.tools.get(id) if (!current) return const tool = { ...current, options: current.options && { ...current.options } } update(tool) @@ -187,13 +187,13 @@ const layer = Layer.effect( tool.options = { ...tool.options, namespace: current.options?.namespace } const error = registrationError(tool) if (error) { - draft.errors.push({ kind: "tool", name: tool.name, namespace: tool.options?.namespace, error }) + editor.errors.push({ kind: "tool", name: tool.name, namespace: tool.options?.namespace, error }) return } - draft.tools.set(id, tool) + editor.tools.set(id, tool) }, remove: (id) => { - draft.tools.delete(id) + editor.tools.delete(id) }, }), notify: (value) => diff --git a/packages/core/src/tool/AGENTS.md b/packages/core/src/tool/AGENTS.md index 5b26235e0a4..4af998f8cb6 100644 --- a/packages/core/src/tool/AGENTS.md +++ b/packages/core/src/tool/AGENTS.md @@ -4,7 +4,7 @@ ## Representations -- Plugin authors get schema-derived input types at the `ToolDraft.add` boundary through `Tool`. +- Plugin authors get schema-derived input types at the `ToolEditor.add` boundary through `Tool`. - The heterogeneous Core registry deliberately erases registered definitions to `Tool.Info`. Use `any` at this internal boundary; do not replace it with `unknown`, JSON-value plumbing, casts, or compiled wrapper types solely to preserve type safety after registration. - Executors return model content and metadata alongside declared machine output. Shipped built-ins and plugin tools use the same runtime shape after registration. - `src/tool.ts` stores canonical Location registrations, derives LLM definitions, executes tools, and normalizes model content and images. @@ -30,11 +30,11 @@ Leaves own resolution, permission, and side-effect ordering. Translate only expe ## Registration -Built-ins, plugins, and MCP install tools through `Tool.Service.transform`, adding complete tool objects to the draft. A tool may provide a namespace, which flattens direct model names to `_`, and defaults into CodeMode (`codemode` defaults true; `codemode: false` keeps the tool on the provider's native tool list). +Built-ins, plugins, and MCP install tools through `Tool.Service.transform`, adding complete tool objects to the editor. A tool may provide a namespace, which flattens direct model names to `_`, and defaults into CodeMode (`codemode` defaults true; `codemode: false` keeps the tool on the provider's native tool list). -Namespace descriptions are registered once through `draft.namespace(...)`. Tool options continue to reference the namespace by string name; an unregistered namespace remains valid and simply has no namespace description. +Namespace descriptions are registered once through `editor.namespace(...)`. Tool options continue to reference the namespace by string name; an unregistered namespace remains valid and simply has no namespace description. -The service uses shared `State` to replay synchronous transforms in registration order against a fresh draft. `Tool.Service.reload()` rebuilds from captured source data without changing registration precedence. Registrations are scoped and return a real, idempotent `dispose` Effect: +The service uses shared `State` to replay synchronous transforms in registration order against a fresh editor. `Tool.Service.reload()` rebuilds from captured source data without changing registration precedence. Registrations are scoped and return a real, idempotent `dispose` Effect: - The latest valid active registration for the same effective name wins. - `update` and `remove` target effective names and do nothing for missing tools. Updates preserve the name and namespace; invalid updates leave the previous definition intact. Creating a tool requires `add`. diff --git a/packages/core/src/tool/mcp.ts b/packages/core/src/tool/mcp.ts index c09f663adcc..8bc5ec14e6b 100644 --- a/packages/core/src/tool/mcp.ts +++ b/packages/core/src/tool/mcp.ts @@ -38,10 +38,10 @@ export const layer = Layer.effect( .withPermit( Effect.gen(function* () { discovered = yield* mcp.tools() - yield* tools.transform((draft) => { + yield* tools.transform((editor) => { for (const tool of discovered) { const schema = (tool.inputSchema ?? {}) as JsonSchema.JsonSchema - draft.add({ + editor.add({ name: tool.name, options: { namespace: namespace(tool.server), codemode: tool.codemode !== false }, description: tool.description ?? "", diff --git a/packages/core/src/tool/plugin/edit.ts b/packages/core/src/tool/plugin/edit.ts index 70f4c63f07a..165cfef6cea 100644 --- a/packages/core/src/tool/plugin/edit.ts +++ b/packages/core/src/tool/plugin/edit.ts @@ -117,8 +117,8 @@ export const Plugin = { const permission = yield* Permission.Service yield* ctx.tool - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name, options: { codemode: false, permission: "edit" }, description: diff --git a/packages/core/src/tool/plugin/glob.ts b/packages/core/src/tool/plugin/glob.ts index acedde49418..b47df989292 100644 --- a/packages/core/src/tool/plugin/glob.ts +++ b/packages/core/src/tool/plugin/glob.ts @@ -52,8 +52,8 @@ export const Plugin = { const permission = yield* Permission.Service yield* ctx.tool - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name, options: { codemode: false }, description: 'Search file paths using a glob pattern (examples: "**/*.ts", "src/**/*.tsx").', diff --git a/packages/core/src/tool/plugin/grep.ts b/packages/core/src/tool/plugin/grep.ts index 08f9bc71ee9..be4e5076fbe 100644 --- a/packages/core/src/tool/plugin/grep.ts +++ b/packages/core/src/tool/plugin/grep.ts @@ -71,8 +71,8 @@ export const Plugin = { const permission = yield* Permission.Service yield* ctx.tool - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name, options: { codemode: false }, description: diff --git a/packages/core/src/tool/plugin/patch.ts b/packages/core/src/tool/plugin/patch.ts index b25b681dfc5..3efd3cc682f 100644 --- a/packages/core/src/tool/plugin/patch.ts +++ b/packages/core/src/tool/plugin/patch.ts @@ -74,8 +74,8 @@ export const Plugin = { const permission = yield* Permission.Service yield* ctx.tool - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name, options: { codemode: false, permission: "edit" }, description: DESCRIPTION, diff --git a/packages/core/src/tool/plugin/question.ts b/packages/core/src/tool/plugin/question.ts index 95b340d3ac4..21bd40b4d77 100644 --- a/packages/core/src/tool/plugin/question.ts +++ b/packages/core/src/tool/plugin/question.ts @@ -52,8 +52,8 @@ export const Plugin = { const permission = yield* Permission.Service yield* ctx.tool - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name, options: { codemode: false }, description, diff --git a/packages/core/src/tool/plugin/read.ts b/packages/core/src/tool/plugin/read.ts index 11a8e478203..b18ce6b5fad 100644 --- a/packages/core/src/tool/plugin/read.ts +++ b/packages/core/src/tool/plugin/read.ts @@ -38,8 +38,8 @@ export const Plugin = { const location = yield* Location.Service yield* ctx.tool - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name, options: { codemode: false }, description: diff --git a/packages/core/src/tool/plugin/shell.ts b/packages/core/src/tool/plugin/shell.ts index d27aab6aeca..bb1c0a44094 100644 --- a/packages/core/src/tool/plugin/shell.ts +++ b/packages/core/src/tool/plugin/shell.ts @@ -196,8 +196,8 @@ export const Plugin = { ) yield* ctx.tool - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name, options: { codemode: false }, description: description(), diff --git a/packages/core/src/tool/plugin/skill.ts b/packages/core/src/tool/plugin/skill.ts index 55fef659909..1ed68adaf8a 100644 --- a/packages/core/src/tool/plugin/skill.ts +++ b/packages/core/src/tool/plugin/skill.ts @@ -36,8 +36,8 @@ export const Plugin = { const skills = yield* Skill.Service const permission = yield* Permission.Service yield* ctx.tool - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name, options: { codemode: false }, description, diff --git a/packages/core/src/tool/plugin/subagent.ts b/packages/core/src/tool/plugin/subagent.ts index 8dcf47af511..58d245931d6 100644 --- a/packages/core/src/tool/plugin/subagent.ts +++ b/packages/core/src/tool/plugin/subagent.ts @@ -98,8 +98,8 @@ export const Plugin = { }) yield* ctx.tool - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name, options: { codemode: false }, description, diff --git a/packages/core/src/tool/plugin/webfetch.ts b/packages/core/src/tool/plugin/webfetch.ts index 9486b12273e..415b69aa388 100644 --- a/packages/core/src/tool/plugin/webfetch.ts +++ b/packages/core/src/tool/plugin/webfetch.ts @@ -107,8 +107,8 @@ export const Plugin = { const permission = yield* Permission.Service yield* ctx.tool - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name, options: { codemode: false }, description, diff --git a/packages/core/src/tool/plugin/websearch.ts b/packages/core/src/tool/plugin/websearch.ts index 78502117108..92187035447 100644 --- a/packages/core/src/tool/plugin/websearch.ts +++ b/packages/core/src/tool/plugin/websearch.ts @@ -32,8 +32,8 @@ export const Plugin = { const websearch = yield* WebSearch.Service yield* ctx.tool - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name, options: { codemode: false }, description, diff --git a/packages/core/src/tool/plugin/write.ts b/packages/core/src/tool/plugin/write.ts index 8fc55d4112e..4f4f0b2660b 100644 --- a/packages/core/src/tool/plugin/write.ts +++ b/packages/core/src/tool/plugin/write.ts @@ -53,8 +53,8 @@ export const Plugin = { const permission = yield* Permission.Service yield* ctx.tool - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name, options: { codemode: false, permission: "edit" }, description: diff --git a/packages/core/src/vcs.ts b/packages/core/src/vcs.ts index 78afb17e27f..389b5b8ec27 100644 --- a/packages/core/src/vcs.ts +++ b/packages/core/src/vcs.ts @@ -2,7 +2,7 @@ export * as Vcs from "./vcs.js" import path from "path" import { Cause, Context, Effect, FiberSet, Layer, Schema, Semaphore, Stream } from "effect" -import type { VcsDefinition, VcsDraft } from "@opencode-ai/plugin/effect/vcs" +import type { VcsDefinition, VcsEditor } from "@opencode-ai/plugin/effect/vcs" import { FileDiff } from "@opencode-ai/schema/file-diff" import { FileSystem } from "@opencode-ai/schema/filesystem" import { Base, BranchList, FileStatus, Info, Mode } from "@opencode-ai/schema/vcs" @@ -38,7 +38,7 @@ export interface Adapter { readonly diff: (mode: Mode, options?: DiffOptions) => Effect.Effect } -export interface Interface extends Adapter, State.Transformable { +export interface Interface extends Adapter, State.Transformable { readonly base: () => Effect.Effect } @@ -71,14 +71,14 @@ const layer = Layer.effect( const decodeBranches = Schema.decodeUnknownEffect(BranchList) const decodeStatus = Schema.decodeUnknownEffect(Schema.Array(FileStatus)) const decodeDiff = Schema.decodeUnknownEffect(Schema.Array(FileDiff.Info)) - const state: State.Interface = State.create({ + const state: State.Interface = State.create({ name: "vcs", initial: () => ({ providers: new Map() }), - draft: (draft) => ({ - add: (provider) => draft.providers.set(provider.id, provider), + editor: (editor) => ({ + add: (provider) => editor.providers.set(provider.id, provider), default: { - get: () => draft.selection, - set: (selection) => (draft.selection = selection), + get: () => editor.selection, + set: (selection) => (editor.selection = selection), }, }), notify: () => State.reconcile(root, fork, () => refresh()), diff --git a/packages/core/src/websearch.ts b/packages/core/src/websearch.ts index 3c5b7ee6032..dd6e8ae71b0 100644 --- a/packages/core/src/websearch.ts +++ b/packages/core/src/websearch.ts @@ -51,7 +51,7 @@ export class RequestError extends Schema.TaggedError()("WebSearch. export type Error = ProviderRequiredError | ProviderNotFoundError | DisabledError | RequestError -export interface Interface extends State.Transformable { +export interface Interface extends State.Transformable { readonly providers: () => Effect.Effect readonly default: () => Effect.Effect readonly select: (selection: Selection) => Effect.Effect @@ -65,7 +65,7 @@ type Data = { selection?: Selection } -export type Draft = { +export type Editor = { add: (provider: ProviderImplementation) => void default: { get: () => Selection | undefined @@ -79,13 +79,13 @@ const layer = Layer.effect( const bus = yield* Bus.Service const kv = yield* KV.Service const decodeResults = Schema.decodeUnknownEffect(Schema.Array(Result)) - const state = State.create({ + const state = State.create({ initial: () => ({ providers: new Map() }), - draft: (draft) => ({ - add: (provider) => draft.providers.set(provider.id, provider), + editor: (editor) => ({ + add: (provider) => editor.providers.set(provider.id, provider), default: { - get: () => draft.selection, - set: (selection) => (draft.selection = selection), + get: () => editor.selection, + set: (selection) => (editor.selection = selection), }, }), notify: () => bus.publish(WebSearch.Event.Updated, {}).pipe(Effect.asVoid), diff --git a/packages/core/src/wellknown/plugin.ts b/packages/core/src/wellknown/plugin.ts index 53f33176b53..4a1da602c08 100644 --- a/packages/core/src/wellknown/plugin.ts +++ b/packages/core/src/wellknown/plugin.ts @@ -11,13 +11,13 @@ export const Plugin = define({ const bus = yield* Bus.Service const wellknown = yield* WellKnown.Service yield* wellknown.entries().pipe(Effect.orDie) - yield* ctx.integration.transform((draft) => { + yield* ctx.integration.transform((editor) => { wellknown.snapshot().forEach((entry) => { if (!entry.manifest.auth) return - draft.update(entry.integrationID, (integration) => { + editor.update(entry.integrationID, (integration) => { integration.name = new URL(entry.origin).hostname }) - draft.method.update({ + editor.method.update({ integrationID: entry.integrationID, method: { id: "login", diff --git a/packages/core/test/catalog.test.ts b/packages/core/test/catalog.test.ts index dd407bc8220..1cf2d2c50a8 100644 --- a/packages/core/test/catalog.test.ts +++ b/packages/core/test/catalog.test.ts @@ -38,8 +38,8 @@ describe("Catalog", () => { const catalog = yield* Catalog.Service const providerID = Provider.ID.make("resolve-fixture") const modelID = Model.ID.make("fixture-model") - yield* catalog.transform((draft) => - draft.model.update(providerID, modelID, (model) => { + yield* catalog.transform((editor) => + editor.model.update(providerID, modelID, (model) => { model.package = path === "aisdk" ? Provider.aisdk("@ai-sdk/fixture") : "@opencode-ai/ai/providers/openai" model.settings = { apiKey: path === "empty-key" ? "" : "fixture-key", @@ -62,8 +62,8 @@ describe("Catalog", () => { }, ) - yield* catalog.transform((draft) => - draft.model.update(providerID, modelID, (model) => { + yield* catalog.transform((editor) => + editor.model.update(providerID, modelID, (model) => { model.limit.context = 100_000 model.capabilities.tools = false model.variants.push({ id: Model.VariantID.make("other") }) @@ -323,7 +323,7 @@ describe("Catalog", () => { const providerID = Provider.ID.make("test") const old = Model.ID.make("old") const newest = Model.ID.make("new") - const models = (catalog: Catalog.Draft) => { + const models = (catalog: Catalog.Editor) => { catalog.provider.update(providerID, () => {}) catalog.model.update(providerID, old, (model) => { model.time.released = 1000 diff --git a/packages/core/test/codemode.test.ts b/packages/core/test/codemode.test.ts index 5ff6afbadc3..28f1e1847f8 100644 --- a/packages/core/test/codemode.test.ts +++ b/packages/core/test/codemode.test.ts @@ -10,9 +10,9 @@ describe("CodeMode", () => { it.effect("owns registrations, execute, and catalog materialization", () => Effect.gen(function* () { const tools = yield* Tool.Service - yield* tools.transform((draft) => { - draft.namespace({ name: "empty", description: "No tools registered yet" }) - draft.add({ + yield* tools.transform((editor) => { + editor.namespace({ name: "empty", description: "No tools registered yet" }) + editor.add({ name: "echo", description: "Echo text", input: Schema.Struct({ text: Schema.String }), diff --git a/packages/core/test/codemode/instructions.test.ts b/packages/core/test/codemode/instructions.test.ts index 6362d7d9484..c8e847289d5 100644 --- a/packages/core/test/codemode/instructions.test.ts +++ b/packages/core/test/codemode/instructions.test.ts @@ -94,10 +94,10 @@ describe("CodeModeInstructions", () => { const tools = yield* Tool.Service const initialized = yield* Effect.scoped( Effect.gen(function* () { - yield* tools.transform((draft) => { - draft.namespace({ name: "tools", description: "Project utilities" }) - draft.add({ ...zeta, options: { namespace: "tools" } }) - draft.add({ ...alpha, options: { namespace: "tools" } }) + yield* tools.transform((editor) => { + editor.namespace({ name: "tools", description: "Project utilities" }) + editor.add({ ...zeta, options: { namespace: "tools" } }) + editor.add({ ...alpha, options: { namespace: "tools" } }) }) const snapshot = yield* tools.snapshot() return yield* readInitial(CodeModeInstructions.make(snapshot.codeModeCatalog)) @@ -105,10 +105,10 @@ describe("CodeModeInstructions", () => { ) const reordered = yield* Effect.scoped( Effect.gen(function* () { - yield* tools.transform((draft) => { - draft.namespace({ name: "tools", description: "Project utilities" }) - draft.add({ ...alpha, options: { namespace: "tools" } }) - draft.add({ ...zeta, options: { namespace: "tools" } }) + yield* tools.transform((editor) => { + editor.namespace({ name: "tools", description: "Project utilities" }) + editor.add({ ...alpha, options: { namespace: "tools" } }) + editor.add({ ...zeta, options: { namespace: "tools" } }) }) const snapshot = yield* tools.snapshot() return yield* readUpdate(CodeModeInstructions.make(snapshot.codeModeCatalog), initialized) diff --git a/packages/core/test/command.test.ts b/packages/core/test/command.test.ts index 8c43e5388ee..9cb5db7f638 100644 --- a/packages/core/test/command.test.ts +++ b/packages/core/test/command.test.ts @@ -12,8 +12,8 @@ describe("Command", () => { Effect.gen(function* () { const command = yield* Command.Service const calls: Command.Invocation[] = [] - yield* command.transform((draft) => { - draft.add({ + yield* command.transform((editor) => { + editor.add({ name: "goal", description: "Manage the session goal", execute: (input) => Effect.sync(() => calls.push(input)), @@ -36,9 +36,9 @@ describe("Command", () => { it.effect("replaces commands with later definitions", () => Effect.gen(function* () { const command = yield* Command.Service - yield* command.transform((draft) => { - draft.add({ name: "goal", description: "First", execute: () => Effect.void }) - draft.add({ name: "goal", description: "Second", execute: () => Effect.void }) + yield* command.transform((editor) => { + editor.add({ name: "goal", description: "First", execute: () => Effect.void }) + editor.add({ name: "goal", description: "Second", execute: () => Effect.void }) }) expect(yield* command.list()).toEqual([Command.Info.make({ name: "goal", description: "Second" })]) @@ -48,8 +48,8 @@ describe("Command", () => { it.effect("returns callback error messages without stack traces", () => Effect.gen(function* () { const command = yield* Command.Service - yield* command.transform((draft) => { - draft.add({ + yield* command.transform((editor) => { + editor.add({ name: "fail", execute: () => Effect.fail(new Error("command failed")), }) diff --git a/packages/core/test/config/provider.test.ts b/packages/core/test/config/provider.test.ts index 66bfa5c81ca..3db83374b5c 100644 --- a/packages/core/test/config/provider.test.ts +++ b/packages/core/test/config/provider.test.ts @@ -92,11 +92,11 @@ describe("ConfigProviderPlugin.Plugin", () => { const providerID = Provider.ID.make("custom") const inheritedID = Model.ID.make("inherited") const overriddenID = Model.ID.make("overridden") - yield* catalog.transform((draft) => { - draft.model.update(providerID, inheritedID, (model) => { + yield* catalog.transform((editor) => { + editor.model.update(providerID, inheritedID, (model) => { model.capabilities = { tools: false, input: ["text"], output: ["text"] } }) - draft.model.update(providerID, overriddenID, (model) => { + editor.model.update(providerID, overriddenID, (model) => { model.capabilities = { tools: false, input: ["text"], output: ["text"] } }) }) @@ -273,8 +273,8 @@ describe("ConfigProviderPlugin.Plugin", () => { const catalog = yield* Catalog.Service const providerID = Provider.ID.make("custom") const modelID = Model.ID.make("chat") - yield* catalog.transform((draft) => { - draft.model.update(providerID, modelID, (model) => { + yield* catalog.transform((editor) => { + editor.model.update(providerID, modelID, (model) => { model.package = "aisdk:@ai-sdk/anthropic" model.settings = { baseURL: "https://catalog.example/v1" } model.capabilities = { tools: false, input: ["audio"], output: ["audio"] } @@ -480,11 +480,11 @@ describe("ConfigProviderPlugin.Plugin", () => { }), ] - yield* catalog.transform((draft) => { - draft.provider.update(Provider.ID.anthropic, (provider) => { + yield* catalog.transform((editor) => { + editor.provider.update(Provider.ID.anthropic, (provider) => { provider.package = "aisdk:@ai-sdk/anthropic" }) - draft.model.update(Provider.ID.anthropic, modelID, (model) => { + editor.model.update(Provider.ID.anthropic, modelID, (model) => { model.variants = [{ id: Model.VariantID.make("fast"), settings: { effort: "high" } }] }) }) diff --git a/packages/core/test/config/reload.test.ts b/packages/core/test/config/reload.test.ts index e2e3379fdeb..dda3cd834c6 100644 --- a/packages/core/test/config/reload.test.ts +++ b/packages/core/test/config/reload.test.ts @@ -39,8 +39,8 @@ describe("config plugin reloads", () => { const plugins = yield* Plugin.Service const references = yield* Reference.Service const host = yield* PluginHost.make(plugins) - yield* references.transform((draft) => - draft.add( + yield* references.transform((editor) => + editor.add( "external", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/references/external") }), ), diff --git a/packages/core/test/config/websearch.test.ts b/packages/core/test/config/websearch.test.ts index 98431fa481d..d9a4519d1c3 100644 --- a/packages/core/test/config/websearch.test.ts +++ b/packages/core/test/config/websearch.test.ts @@ -20,8 +20,8 @@ describe("ConfigWebSearchPlugin.Plugin", () => { const bus = yield* Bus.Service const config = yield* Config.Test const plugins = yield* Plugin.Service - yield* websearch.transform((draft) => - draft.add({ id: WebSearch.ID.make("test"), name: "Test", execute: () => Effect.succeed([]) }), + yield* websearch.transform((editor) => + editor.add({ id: WebSearch.ID.make("test"), name: "Test", execute: () => Effect.succeed([]) }), ) yield* ConfigWebSearchPlugin.Plugin.effect(yield* PluginHost.make(plugins)) diff --git a/packages/core/test/filesystem/location-watcher-policy.test.ts b/packages/core/test/filesystem/location-watcher-policy.test.ts index 3530baf6b07..9936729d425 100644 --- a/packages/core/test/filesystem/location-watcher-policy.test.ts +++ b/packages/core/test/filesystem/location-watcher-policy.test.ts @@ -21,9 +21,9 @@ describe("LocationWatcherPolicy", () => { yield* State.batch( Effect.gen(function* () { - yield* policy.transform((draft) => draft.add(["node_modules"])) + yield* policy.transform((editor) => editor.add(["node_modules"])) expect(policy.current()).toEqual(["node_modules"]) - const overlay = yield* policy.transform((draft) => draft.add([".git"])) + const overlay = yield* policy.transform((editor) => editor.add([".git"])) expect(policy.current()).toEqual(["node_modules", ".git"]) expect(observed).toEqual([]) @@ -47,7 +47,7 @@ describe("LocationWatcherPolicy", () => { Effect.gen(function* () { if (reentered) return reentered = true - yield* policy.transform((draft) => draft.add([".git"])).pipe(Scope.provide(scope)) + yield* policy.transform((editor) => editor.add([".git"])).pipe(Scope.provide(scope)) expect(policy.current()).toEqual(["node_modules", ".git"]) }), ) @@ -58,7 +58,7 @@ describe("LocationWatcherPolicy", () => { }), ) - yield* policy.transform((draft) => draft.add(["node_modules"])) + yield* policy.transform((editor) => editor.add(["node_modules"])) expect(policy.current()).toEqual(["node_modules", ".git"]) expect(observed).toEqual([ diff --git a/packages/core/test/filesystem/watcher.test.ts b/packages/core/test/filesystem/watcher.test.ts index 0b22e15fc2e..7b73020edda 100644 --- a/packages/core/test/filesystem/watcher.test.ts +++ b/packages/core/test/filesystem/watcher.test.ts @@ -306,7 +306,7 @@ describe("LocationWatcher subscriptions", () => { layer: Layer.effectDiscard( Effect.gen(function* () { const policy = yield* LocationWatcherPolicy.Service - yield* policy.transform((draft) => draft.add([".git"])) + yield* policy.transform((editor) => editor.add([".git"])) }), ), deps: [LocationWatcherPolicy.node], diff --git a/packages/core/test/formatter.test.ts b/packages/core/test/formatter.test.ts index a499ff0945c..c9da6b68dfb 100644 --- a/packages/core/test/formatter.test.ts +++ b/packages/core/test/formatter.test.ts @@ -198,9 +198,9 @@ describe("Formatter", () => { withFormatter(false, (formatter, directory) => Effect.gen(function* () { const command = { suffix: "A" } - yield* formatter.transform((draft) => { + yield* formatter.transform((editor) => { const suffix = command.suffix - draft.set({ + editor.set({ name: "reload", extensions: [".reload"], enabled: Effect.succeed([ @@ -230,7 +230,7 @@ describe("Formatter", () => { const resolving = yield* Deferred.make() const release = yield* Deferred.make() const command = { suffix: "A" } - yield* formatter.transform((draft) => { + yield* formatter.transform((editor) => { const suffix = command.suffix const resolved = [ process.execPath, @@ -238,7 +238,7 @@ describe("Formatter", () => { `const fs = require('fs'); const file = process.argv.at(-1); fs.appendFileSync(file, '${suffix}')`, "$FILE", ] - draft.set({ + editor.set({ name: "reload-race", extensions: [".race"], enabled: diff --git a/packages/core/test/instruction-discovery.test.ts b/packages/core/test/instruction-discovery.test.ts index 1af50a4b1a1..f6c21957954 100644 --- a/packages/core/test/instruction-discovery.test.ts +++ b/packages/core/test/instruction-discovery.test.ts @@ -79,15 +79,15 @@ describe("InstructionDiscovery", () => { it.effect("stores ordered values with last-write-wins precedence", () => Effect.gen(function* () { const discovery = yield* InstructionDiscovery.Service - yield* discovery.transform((draft) => { - draft.add(file("/repo/AGENTS.md", "first")) - draft.add(file("/repo/packages/AGENTS.md", "package")) - draft.add(file("/repo/AGENTS.md", "last")) - draft.update("/repo/packages/AGENTS.md", (current) => { + yield* discovery.transform((editor) => { + editor.add(file("/repo/AGENTS.md", "first")) + editor.add(file("/repo/packages/AGENTS.md", "package")) + editor.add(file("/repo/AGENTS.md", "last")) + editor.update("/repo/packages/AGENTS.md", (current) => { current.content = "updated" current.path = AbsolutePath.make("/ignored") }) - draft.remove("/missing") + editor.remove("/missing") }) expect(yield* discovery.list()).toEqual([ @@ -100,7 +100,7 @@ describe("InstructionDiscovery", () => { it.effect("preserves admitted values while the source is unavailable", () => Effect.gen(function* () { const discovery = yield* InstructionDiscovery.Service - yield* discovery.transform((draft) => draft.unavailable()) + yield* discovery.transform((editor) => editor.unavailable()) expect( (yield* readUpdate( yield* discovery.load(), @@ -113,16 +113,16 @@ describe("InstructionDiscovery", () => { it.effect("renders granular instruction updates", () => Effect.gen(function* () { const discovery = yield* InstructionDiscovery.Service - yield* discovery.transform((draft) => { - draft.add(file("/global/AGENTS.md", "global")) - draft.add( + yield* discovery.transform((editor) => { + editor.add(file("/global/AGENTS.md", "global")) + editor.add( file("/repo/AGENTS.md", ["old", ...Array.from({ length: 20 }, (_, index) => `keep ${index}`)].join("\n")), ) }) const initial = yield* readInitial(yield* discovery.load()) - yield* discovery.transform((draft) => { - draft.update("/repo/AGENTS.md", (current) => { + yield* discovery.transform((editor) => { + editor.update("/repo/AGENTS.md", (current) => { current.content = ["new", ...Array.from({ length: 20 }, (_, index) => `keep ${index}`)].join("\n") }) }) @@ -134,9 +134,9 @@ describe("InstructionDiscovery", () => { const rewritten = state({ "core/instructions": [{ path: "/repo/AGENTS.md", content: "old one\nold two\nold three\nold four" }], }) - yield* discovery.transform((draft) => { - draft.remove("/global/AGENTS.md") - draft.update("/repo/AGENTS.md", (current) => { + yield* discovery.transform((editor) => { + editor.remove("/global/AGENTS.md") + editor.update("/repo/AGENTS.md", (current) => { current.content = "new" }) }) @@ -144,8 +144,8 @@ describe("InstructionDiscovery", () => { "The instructions changed:\nInstructions from: /repo/AGENTS.md\nnew", ) - yield* discovery.transform((draft) => { - draft.add(file("/repo/packages/AGENTS.md", "package")) + yield* discovery.transform((editor) => { + editor.add(file("/repo/packages/AGENTS.md", "package")) }) const structural = (yield* readUpdate(yield* discovery.load(), initial)).text expect(structural).toContain("The instructions from /global/AGENTS.md no longer apply.") diff --git a/packages/core/test/mcp.test.ts b/packages/core/test/mcp.test.ts index 2c1bd6f0bf8..79d6259cbd8 100644 --- a/packages/core/test/mcp.test.ts +++ b/packages/core/test/mcp.test.ts @@ -1297,8 +1297,8 @@ testEffect(Layer.empty).live( const original = JSON.stringify(entries) yield* Effect.gen(function* () { const service = yield* Mcp.Service - const check = yield* service.transform((draft) => { - expect(draft.get("resources")).toEqual({ + const check = yield* service.transform((editor) => { + expect(editor.get("resources")).toEqual({ type: "local", command: ["later"], disabled: true, @@ -1314,8 +1314,8 @@ testEffect(Layer.empty).live( } satisfies ConfigMCP.Local yield* service.add("resources", runtime) yield* service.reload() - yield* service.transform((draft) => { - expect(draft.get("resources")).toEqual(runtime) + yield* service.transform((editor) => { + expect(editor.get("resources")).toEqual(runtime) }) }).pipe( Effect.provide( @@ -1336,8 +1336,8 @@ testEffect(resourceMcpLayer(new ConfigMCP.Local({ type: "local", command: ["unus yield* Effect.scoped( Effect.gen(function* () { - yield* service.transform((draft) => { - draft.set("dynamic", { + yield* service.transform((editor) => { + editor.set("dynamic", { type: "local", command: [process.execPath, path.join(import.meta.dir, "fixture/mcp-output-schema.ts")], }) @@ -1347,12 +1347,12 @@ testEffect(resourceMcpLayer(new ConfigMCP.Local({ type: "local", command: ["unus ) expect(yield* service.tools()).toHaveLength(2) - yield* service.transform((draft) => draft.update("dynamic", (server) => (server.codemode = false))) + yield* service.transform((editor) => editor.update("dynamic", (server) => (server.codemode = false))) expect((yield* service.tools()).map((tool) => tool.codemode)).toEqual([false, false]) const settings = { disabled: true } - yield* service.transform((draft) => { - draft.update("dynamic", (server) => { + yield* service.transform((editor) => { + editor.update("dynamic", (server) => { server.disabled = settings.disabled }) }) @@ -1366,7 +1366,7 @@ testEffect(resourceMcpLayer(new ConfigMCP.Local({ type: "local", command: ["unus ) expect(yield* service.tools()).toHaveLength(2) - const removed = yield* service.transform((draft) => draft.remove("dynamic")) + const removed = yield* service.transform((editor) => editor.remove("dynamic")) expect((yield* service.servers()).some((server) => server.name === "dynamic")).toBe(false) expect(yield* service.tools()).toEqual([]) @@ -1396,14 +1396,14 @@ test("restores runtime MCP config when a transform is disposed", async () => { disabled: true, }) yield* service.add("dynamic", config) - const transformed = yield* service.transform((draft) => - draft.update("dynamic", (server) => { + const transformed = yield* service.transform((editor) => + editor.update("dynamic", (server) => { if (server.type === "remote") server.headers = { Authorization: "transformed" } }), ) let observed: string | undefined - yield* service.transform((draft) => { - const server = draft.get("dynamic") + yield* service.transform((editor) => { + const server = editor.get("dynamic") observed = server?.type === "remote" ? server.headers?.Authorization : undefined }) @@ -1432,8 +1432,8 @@ test("isolates nested configured MCP mutations and reconciles them", async () => Effect.gen(function* () { const service = yield* Mcp.Service expect(published.filter((type) => type === McpEvent.StatusChanged.type)).toHaveLength(1) - yield* service.transform((draft) => - draft.update("resources", (server) => { + yield* service.transform((editor) => + editor.update("resources", (server) => { if (server.type === "remote" && server.headers) server.headers.Authorization = "transformed" }), ) @@ -1452,16 +1452,16 @@ testEffect(Layer.empty).live("batches MCP transforms without connecting intermed const service = yield* Mcp.Service const registrations = yield* State.batch( Effect.gen(function* () { - const added = yield* service.transform((draft) => - draft.set("dynamic", { + const added = yield* service.transform((editor) => + editor.set("dynamic", { type: "remote", url: server.url, oauth: false, }), ) expect((yield* service.servers()).some((server) => server.name === "dynamic")).toBe(false) - const disabled = yield* service.transform((draft) => - draft.update("dynamic", (config) => (config.disabled = true)), + const disabled = yield* service.transform((editor) => + editor.update("dynamic", (config) => (config.disabled = true)), ) return [added, disabled] }), @@ -1587,13 +1587,13 @@ testEffect(Layer.empty).live("keeps MCP config snapshots stable during an in-fli yield* Effect.gen(function* () { const service = yield* Mcp.Service const replacing = yield* service - .transform((draft) => draft.update("resources", (config) => (config.disabled = false))) + .transform((editor) => editor.update("resources", (config) => (config.disabled = false))) .pipe(Effect.forkScoped({ startImmediately: true })) yield* Deferred.await(started) const restoring = yield* State.batch( Effect.gen(function* () { - yield* service.transform((draft) => draft.update("resources", (config) => (config.disabled = true))) + yield* service.transform((editor) => editor.update("resources", (config) => (config.disabled = true))) yield* Deferred.succeed(accepted, undefined) }), ).pipe(Effect.forkScoped({ startImmediately: true })) @@ -1657,9 +1657,9 @@ shutdownIt.effect("discards in-flight and queued MCP notifications after its lay ) const source = { url: "https://example.com/initial", added: false } yield* service - .transform((draft) => { - draft.set("fixture", { type: "remote", url: source.url, oauth: false, disabled: true }) - if (source.added) draft.set("queued", { type: "local", command: ["unused"], disabled: true }) + .transform((editor) => { + editor.set("fixture", { type: "remote", url: source.url, oauth: false, disabled: true }) + if (source.added) editor.set("queued", { type: "local", command: ["unused"], disabled: true }) }) .pipe(Scope.provide(root)) @@ -1758,8 +1758,8 @@ testEffect(Layer.empty).live("isolates invalid MCP tools and preserves plugin tr "other_lookup", "execute", ]) - const override = yield* registry.transform((draft) => { - draft.add({ + const override = yield* registry.transform((editor) => { + editor.add({ name: "search", options: { namespace: "demo", codemode: false }, description: "Override search", @@ -1768,11 +1768,11 @@ testEffect(Layer.empty).live("isolates invalid MCP tools and preserves plugin tr execute: () => Effect.succeed({ output: "override" }), }) }) - const mutation = yield* registry.transform((draft) => { - draft.update("other_lookup", (tool) => { + const mutation = yield* registry.transform((editor) => { + editor.update("other_lookup", (tool) => { tool.description += " updated" }) - draft.remove("repaired_lookup") + editor.remove("repaired_lookup") }) yield* Ref.set(catalog, [tool("demo", "y".repeat(65)), ...healthy, tool("demo", "added"), namespace]) diff --git a/packages/core/test/plugin.test.ts b/packages/core/test/plugin.test.ts index 02bb7b1f086..3c310ff15b9 100644 --- a/packages/core/test/plugin.test.ts +++ b/packages/core/test/plugin.test.ts @@ -116,7 +116,7 @@ it.effect("unloading a plugin removes its commands and runs cleanup", () => cleaned = true }), ) - yield* ctx.command.transform((draft) => draft.add({ name: "greet", execute: () => Effect.void })) + yield* ctx.command.transform((editor) => editor.add({ name: "greet", execute: () => Effect.void })) }), }, ]) @@ -141,7 +141,7 @@ it.effect("reports a failed plugin without blocking a healthy plugin", () => revision: "1", effect: (ctx) => ctx.command - .transform((draft) => draft.add({ name: "greet", execute: () => Effect.void })) + .transform((editor) => editor.add({ name: "greet", execute: () => Effect.void })) .pipe(Effect.asVoid), }, ]) @@ -166,8 +166,8 @@ it.effect("reloading a plugin replaces its command implementation", () => revision, effect: (ctx) => ctx.command - .transform((draft) => - draft.add({ + .transform((editor) => + editor.add({ name: "greet", execute: () => Effect.sync(() => { @@ -225,8 +225,8 @@ it.effect("refreshes expired OAuth credentials through the context during activa revision: "1", effect: (ctx) => Effect.gen(function* () { - yield* ctx.integration.transform((draft) => - draft.method.update({ + yield* ctx.integration.transform((editor) => + editor.method.update({ integrationID, method: { id: methodID, type: "oauth", label: "Fixture" }, authorize: () => Effect.die("unexpected authorization"), diff --git a/packages/core/test/plugin/fixtures/greeting.ts b/packages/core/test/plugin/fixtures/greeting.ts index 1dbc2928959..1c2f47c3fe9 100644 --- a/packages/core/test/plugin/fixtures/greeting.ts +++ b/packages/core/test/plugin/fixtures/greeting.ts @@ -3,8 +3,8 @@ import { Plugin } from "@opencode-ai/plugin" export default Plugin.define({ id: "greeting", async setup(ctx) { - await ctx.command.transform((draft) => - draft.add({ + await ctx.command.transform((editor) => + editor.add({ name: "greet", description: ctx.options.description, execute: async () => {}, diff --git a/packages/core/test/plugin/host.ts b/packages/core/test/plugin/host.ts index b544e0fac1d..586b23c06c9 100644 --- a/packages/core/test/plugin/host.ts +++ b/packages/core/test/plugin/host.ts @@ -193,21 +193,21 @@ export function agentHost(agent: Agent.Interface): Plugin.Context["agent"] { list: () => Effect.die("unused agent.list"), reload: agent.reload, transform: (callback) => - agent.transform((draft) => + agent.transform((editor) => callback({ - list: () => draft.list().map(agentInfo), + list: () => editor.list().map(agentInfo), get: (id) => { - const value = draft.get(Agent.ID.make(id)) + const value = editor.get(Agent.ID.make(id)) return value && agentInfo(value) }, - default: (id) => draft.default(id === undefined ? undefined : Agent.ID.make(id)), + default: (id) => editor.default(id === undefined ? undefined : Agent.ID.make(id)), update: (id, update) => - draft.update(Agent.ID.make(id), (value) => { + editor.update(Agent.ID.make(id), (value) => { const current = agentInfo(value) update(current) Object.assign(value, current, { id: Agent.ID.make(current.id) }) }), - remove: (id) => draft.remove(Agent.ID.make(id)), + remove: (id) => editor.remove(Agent.ID.make(id)), }), ), } @@ -238,16 +238,16 @@ export function catalogHost(catalog: Catalog.Interface): Plugin.Context["catalog }, reload: catalog.reload, transform: (callback) => - catalog.transform((draft) => + catalog.transform((editor) => callback({ provider: { list: () => - draft.provider.list().map((value) => ({ + editor.provider.list().map((value) => ({ provider: providerInfo(value.provider), models: new Map(Array.from(value.models, ([id, model]) => [id, modelInfo(model)])), })), get: (id) => { - const value = draft.provider.get(Provider.ID.make(id)) + const value = editor.provider.get(Provider.ID.make(id)) return ( value && { provider: providerInfo(value.provider), @@ -256,20 +256,20 @@ export function catalogHost(catalog: Catalog.Interface): Plugin.Context["catalog ) }, update: (id, update) => - draft.provider.update(Provider.ID.make(id), (value) => { + editor.provider.update(Provider.ID.make(id), (value) => { const current = providerInfo(value) update(current) Object.assign(value, current, { id: Provider.ID.make(current.id) }) }), - remove: (id) => draft.provider.remove(Provider.ID.make(id)), + remove: (id) => editor.provider.remove(Provider.ID.make(id)), }, model: { get: (providerID, modelID) => { - const value = draft.model.get(Provider.ID.make(providerID), Model.ID.make(modelID)) + const value = editor.model.get(Provider.ID.make(providerID), Model.ID.make(modelID)) return value && modelInfo(value) }, update: (providerID, modelID, update) => - draft.model.update(Provider.ID.make(providerID), Model.ID.make(modelID), (value) => { + editor.model.update(Provider.ID.make(providerID), Model.ID.make(modelID), (value) => { const current = modelInfo(value) update(current) Object.assign(value, current, { @@ -282,14 +282,14 @@ export function catalogHost(catalog: Catalog.Interface): Plugin.Context["catalog })), }) }), - remove: (providerID, modelID) => draft.model.remove(Provider.ID.make(providerID), Model.ID.make(modelID)), + remove: (providerID, modelID) => editor.model.remove(Provider.ID.make(providerID), Model.ID.make(modelID)), default: { get: () => { - const value = draft.model.default.get() + const value = editor.model.default.get() return value && { providerID: value.providerID, modelID: value.modelID } }, set: (providerID, modelID) => - draft.model.default.set(Provider.ID.make(providerID), Model.ID.make(modelID)), + editor.model.default.set(Provider.ID.make(providerID), Model.ID.make(modelID)), }, }, }), @@ -324,22 +324,22 @@ export function integrationHost(integration: Integration.Interface): Plugin.Cont ), }, transform: (callback) => - integration.transform((draft) => + integration.transform((editor) => callback({ - list: () => draft.list().map((value) => ({ id: value.id, name: value.name })), + list: () => editor.list().map((value) => ({ id: value.id, name: value.name })), get: (id) => { - const value = draft.get(Integration.ID.make(id)) + const value = editor.get(Integration.ID.make(id)) return value && { id: value.id, name: value.name } }, - update: (id, update) => draft.update(Integration.ID.make(id), update), - remove: (id) => draft.remove(Integration.ID.make(id)), + update: (id, update) => editor.update(Integration.ID.make(id), update), + remove: (id) => editor.remove(Integration.ID.make(id)), method: { - list: (id) => draft.method.list(Integration.ID.make(id)), + list: (id) => editor.method.list(Integration.ID.make(id)), update: (input) => { if ("authorize" in input) { const methodID = Integration.MethodID.make(input.method.id) const refresh = input.refresh - draft.method.update({ + editor.method.update({ integrationID: Integration.ID.make(input.integrationID), method: { ...input.method, id: methodID }, authorize: (answer) => @@ -390,14 +390,14 @@ export function integrationHost(integration: Integration.Interface): Plugin.Cont return } if (input.method.type === "env") { - draft.method.update({ + editor.method.update({ integrationID: Integration.ID.make(input.integrationID), method: input.method, }) return } if (input.method.type === "command") { - draft.method.update({ + editor.method.update({ integrationID: Integration.ID.make(input.integrationID), method: { ...input.method, @@ -406,12 +406,12 @@ export function integrationHost(integration: Integration.Interface): Plugin.Cont }) return } - draft.method.update({ + editor.method.update({ integrationID: Integration.ID.make(input.integrationID), method: input.method, }) }, - remove: (id, item) => draft.method.remove(Integration.ID.make(id), internalMethod(item)), + remove: (id, item) => editor.method.remove(Integration.ID.make(id), internalMethod(item)), }, }), ), @@ -435,18 +435,18 @@ export function webSearchHost(websearch: WebSearch.Interface): Plugin.Context["w .pipe(Effect.map((data) => ({ location, data }))), reload: websearch.reload, transform: (callback) => - websearch.transform((draft) => { + websearch.transform((editor) => { callback({ add: (definition) => - draft.add({ + editor.add({ id: WebSearch.ID.make(definition.id), name: definition.name, execute: definition.execute, }), default: { - get: draft.default.get, + get: editor.default.get, set: (selection) => - draft.default.set( + editor.default.set( selection === false || selection === "random" ? selection : WebSearch.ID.make(selection), ), }, diff --git a/packages/core/test/plugin/models-dev.test.ts b/packages/core/test/plugin/models-dev.test.ts index 9b951c65f4a..4b92064e242 100644 --- a/packages/core/test/plugin/models-dev.test.ts +++ b/packages/core/test/plugin/models-dev.test.ts @@ -76,8 +76,8 @@ describe("ModelsDevPlugin", () => { const scope = yield* Scope.make() yield* catalog - .transform((draft) => - draft.model.update(providerID, modelID, (model) => { + .transform((editor) => + editor.model.update(providerID, modelID, (model) => { model.variants ??= [] model.variants.push({ id: Model.VariantID.make("configured") }) }), diff --git a/packages/core/test/plugin/provider-azure.test.ts b/packages/core/test/plugin/provider-azure.test.ts index 4a630282754..dff14d36784 100644 --- a/packages/core/test/plugin/provider-azure.test.ts +++ b/packages/core/test/plugin/provider-azure.test.ts @@ -244,12 +244,12 @@ describe("AzurePlugin", () => { () => Effect.gen(function* () { const catalog = yield* Catalog.Service - yield* catalog.transform((draft) => { - draft.provider.update(Provider.ID.azure, (provider) => { + yield* catalog.transform((editor) => { + editor.provider.update(Provider.ID.azure, (provider) => { provider.package = Provider.aisdk("@ai-sdk/azure") }) - draft.model.update(Provider.ID.azure, Model.ID.make("gpt-5-mini"), () => {}) - draft.model.update(Provider.ID.azure, Model.ID.make("gpt-5-nano"), () => {}) + editor.model.update(Provider.ID.azure, Model.ID.make("gpt-5-mini"), () => {}) + editor.model.update(Provider.ID.azure, Model.ID.make("gpt-5-nano"), () => {}) }) yield* azureCredential yield* addPlugin() @@ -462,24 +462,24 @@ describe("AzurePlugin", () => { gateway: Model.ID.make("gateway"), nonAzure: Model.ID.make("non-azure"), } - yield* catalog.transform((draft) => { - draft.provider.update(Provider.ID.azure, (provider) => { + yield* catalog.transform((editor) => { + editor.provider.update(Provider.ID.azure, (provider) => { provider.package = Provider.aisdk("@ai-sdk/azure") }) - draft.model.update(Provider.ID.azure, models.responses, () => {}) - draft.model.update(Provider.ID.azure, models.chat, (model) => { + editor.model.update(Provider.ID.azure, models.responses, () => {}) + editor.model.update(Provider.ID.azure, models.chat, (model) => { model.settings = { useCompletionUrls: true } }) - draft.model.update(Provider.ID.azure, models.preview, (model) => { + editor.model.update(Provider.ID.azure, models.preview, (model) => { model.settings = { apiVersion: "2025-04-01-preview" } }) - draft.model.update(Provider.ID.azure, models.deploymentURL, (model) => { + editor.model.update(Provider.ID.azure, models.deploymentURL, (model) => { model.settings = { useDeploymentBasedUrls: true } }) - draft.model.update(Provider.ID.azure, models.gateway, (model) => { + editor.model.update(Provider.ID.azure, models.gateway, (model) => { model.settings = { baseURL: "https://gateway.example/azure" } }) - draft.model.update(Provider.ID.azure, models.nonAzure, (model) => { + editor.model.update(Provider.ID.azure, models.nonAzure, (model) => { model.package = Provider.aisdk("@ai-sdk/anthropic") }) }) diff --git a/packages/core/test/plugin/provider-github-copilot.test.ts b/packages/core/test/plugin/provider-github-copilot.test.ts index 1d4f5865c5a..8072986bf69 100644 --- a/packages/core/test/plugin/provider-github-copilot.test.ts +++ b/packages/core/test/plugin/provider-github-copilot.test.ts @@ -56,12 +56,12 @@ describe("GithubCopilotPlugin", () => { it.effect("removes the generic key method", () => Effect.gen(function* () { const integrations = yield* Integration.Service - yield* integrations.transform((draft) => { - draft.method.update({ + yield* integrations.transform((editor) => { + editor.method.update({ integrationID: Integration.ID.make("github-copilot"), method: { type: "key" }, }) - draft.method.update({ + editor.method.update({ integrationID: Integration.ID.make("github-copilot"), method: { type: "env", names: ["GITHUB_TOKEN"] }, }) diff --git a/packages/core/test/plugin/provider-lmstudio.test.ts b/packages/core/test/plugin/provider-lmstudio.test.ts index 38d1ef1a34c..b0cc276c5d9 100644 --- a/packages/core/test/plugin/provider-lmstudio.test.ts +++ b/packages/core/test/plugin/provider-lmstudio.test.ts @@ -289,22 +289,22 @@ describe("LMStudioPlugin", () => { const catalog = yield* Catalog.Service const integrations = yield* Integration.Service const providerID = Provider.ID.make("lmstudio") - yield* integrations.transform((draft) => { - draft.update(Integration.ID.make("lmstudio"), (integration) => { + yield* integrations.transform((editor) => { + editor.update(Integration.ID.make("lmstudio"), (integration) => { integration.name = "LMStudio" }) - draft.method.update({ + editor.method.update({ integrationID: Integration.ID.make("lmstudio"), method: { type: "env", names: ["LMSTUDIO_API_KEY"] }, }) }) - yield* catalog.transform((draft) => { - draft.provider.update(providerID, (provider) => { + yield* catalog.transform((editor) => { + editor.provider.update(providerID, (provider) => { provider.name = "LMStudio" provider.package = "aisdk:@ai-sdk/openai-compatible" provider.integrationID = Integration.ID.make("lmstudio") }) - draft.model.update(providerID, Model.ID.make("static-model"), () => {}) + editor.model.update(providerID, Model.ID.make("static-model"), () => {}) }) expect((yield* catalog.provider.available()).map((provider) => provider.id)).not.toContain(providerID) @@ -319,11 +319,11 @@ describe("LMStudioPlugin", () => { expect(yield* catalog.model.get(providerID, Model.ID.make("static-model"))).toBeUndefined() expect((yield* catalog.provider.available()).map((provider) => provider.id)).toContain(providerID) - yield* integrations.transform((draft) => { - draft.update(Integration.ID.make("lmstudio"), (integration) => { + yield* integrations.transform((editor) => { + editor.update(Integration.ID.make("lmstudio"), (integration) => { integration.name = "Configured LM Studio" }) - draft.method.update({ integrationID: Integration.ID.make("lmstudio"), method: { type: "key" } }) + editor.method.update({ integrationID: Integration.ID.make("lmstudio"), method: { type: "key" } }) }) expect((yield* catalog.provider.available()).map((provider) => provider.id)).toContain(providerID) diff --git a/packages/core/test/plugin/provider-ollama.test.ts b/packages/core/test/plugin/provider-ollama.test.ts index 1db57979df6..3ef226774a5 100644 --- a/packages/core/test/plugin/provider-ollama.test.ts +++ b/packages/core/test/plugin/provider-ollama.test.ts @@ -188,22 +188,22 @@ describe("OllamaPlugin", () => { const catalog = yield* Catalog.Service const integrations = yield* Integration.Service const providerID = Provider.ID.make("ollama") - yield* integrations.transform((draft) => { - draft.update(Integration.ID.make("ollama"), (integration) => { + yield* integrations.transform((editor) => { + editor.update(Integration.ID.make("ollama"), (integration) => { integration.name = "Ollama" }) - draft.method.update({ + editor.method.update({ integrationID: Integration.ID.make("ollama"), method: { type: "env", names: ["OLLAMA_API_KEY"] }, }) }) - yield* catalog.transform((draft) => { - draft.provider.update(providerID, (provider) => { + yield* catalog.transform((editor) => { + editor.provider.update(providerID, (provider) => { provider.name = "Ollama" provider.package = "aisdk:@ai-sdk/openai-compatible" provider.integrationID = Integration.ID.make("ollama") }) - draft.model.update(providerID, Model.ID.make("static-model"), () => {}) + editor.model.update(providerID, Model.ID.make("static-model"), () => {}) }) yield* addPlugin(server.url.origin, "5 millis") diff --git a/packages/core/test/plugin/provider-opencode.test.ts b/packages/core/test/plugin/provider-opencode.test.ts index 7338cf1a4c0..5c90f8ca503 100644 --- a/packages/core/test/plugin/provider-opencode.test.ts +++ b/packages/core/test/plugin/provider-opencode.test.ts @@ -267,12 +267,12 @@ describe("OpencodePlugin", () => { const credentials = yield* Credential.Service const catalog = yield* Catalog.Service const integrations = yield* Integration.Service - yield* catalog.transform((draft) => { - draft.provider.update(Provider.ID.openai, (provider) => { + yield* catalog.transform((editor) => { + editor.provider.update(Provider.ID.openai, (provider) => { provider.package = Provider.aisdk("@ai-sdk/openai") provider.integrationID = Integration.ID.make("openai") }) - draft.model.update(Provider.ID.openai, Model.ID.make("api-model"), (model) => { + editor.model.update(Provider.ID.openai, Model.ID.make("api-model"), (model) => { model.package = Provider.aisdk("@ai-sdk/openai") model.settings = { baseURL: "https://upstream.example/v1" } model.variants = [ @@ -284,7 +284,7 @@ describe("OpencodePlugin", () => { }, ] }) - draft.model.update(Provider.ID.make("remote"), Model.ID.make("stale"), () => {}) + editor.model.update(Provider.ID.make("remote"), Model.ID.make("stale"), () => {}) }) const initial = yield* credentials.create({ integrationID: Integration.ID.make("opencode"), diff --git a/packages/core/test/plugin/provider-vllm.test.ts b/packages/core/test/plugin/provider-vllm.test.ts index 3554e45fb4e..672252ccc27 100644 --- a/packages/core/test/plugin/provider-vllm.test.ts +++ b/packages/core/test/plugin/provider-vllm.test.ts @@ -172,23 +172,23 @@ describe("VLLMPlugin", () => { const catalog = yield* Catalog.Service const integrations = yield* Integration.Service const providerID = Provider.ID.make("vllm") - yield* integrations.transform((draft) => { - draft.update(Integration.ID.make("vllm"), (integration) => { + yield* integrations.transform((editor) => { + editor.update(Integration.ID.make("vllm"), (integration) => { integration.name = "vLLM" }) - draft.method.update({ + editor.method.update({ integrationID: Integration.ID.make("vllm"), method: { type: "env", names: ["VLLM_API_KEY"] }, }) }) - yield* catalog.transform((draft) => { - draft.provider.update(providerID, (provider) => { + yield* catalog.transform((editor) => { + editor.provider.update(providerID, (provider) => { provider.name = "vLLM" provider.package = "aisdk:@ai-sdk/openai-compatible" provider.integrationID = Integration.ID.make("vllm") provider.activation = "auto" }) - draft.model.update(providerID, Model.ID.make("static-model"), () => {}) + editor.model.update(providerID, Model.ID.make("static-model"), () => {}) }) yield* addPlugin(server.url.origin, "5 millis") diff --git a/packages/core/test/plugin/provider-xai.test.ts b/packages/core/test/plugin/provider-xai.test.ts index c2302cb30e5..40f4cd46723 100644 --- a/packages/core/test/plugin/provider-xai.test.ts +++ b/packages/core/test/plugin/provider-xai.test.ts @@ -73,11 +73,11 @@ describe("XAIPlugin", () => { Effect.gen(function* () { const catalog = yield* Catalog.Service const providerID = Provider.ID.make("xai") - yield* catalog.transform((draft) => { - draft.provider.update(providerID, (provider) => { + yield* catalog.transform((editor) => { + editor.provider.update(providerID, (provider) => { provider.package = Provider.aisdk("@ai-sdk/xai") }) - draft.model.update(providerID, Model.ID.make("grok-4.6"), () => {}) + editor.model.update(providerID, Model.ID.make("grok-4.6"), () => {}) }) yield* addPlugin() diff --git a/packages/core/test/plugin/supervisor.test.ts b/packages/core/test/plugin/supervisor.test.ts index c5a78a21a6c..81c46c3e2ed 100644 --- a/packages/core/test/plugin/supervisor.test.ts +++ b/packages/core/test/plugin/supervisor.test.ts @@ -24,7 +24,7 @@ const greeter = (command: string) => define({ id, effect: (ctx) => - ctx.command.transform((draft) => draft.add({ name: command, execute: () => Effect.void })).pipe(Effect.asVoid), + ctx.command.transform((editor) => editor.add({ name: command, execute: () => Effect.void })).pipe(Effect.asVoid), }) const instances = Layer.effect( diff --git a/packages/core/test/plugin/system-prompt.test.ts b/packages/core/test/plugin/system-prompt.test.ts index 367c3c428d0..e5d4abe0f7d 100644 --- a/packages/core/test/plugin/system-prompt.test.ts +++ b/packages/core/test/plugin/system-prompt.test.ts @@ -21,7 +21,7 @@ const fallback = SessionSystemPrompt.make([]) const makeHost = Effect.gen(function* () { const agents = yield* Agent.Service const plugins = yield* Plugin.Service - yield* agents.transform((draft) => draft.update(Agent.ID.make("build"), () => {})) + yield* agents.transform((editor) => editor.update(Agent.ID.make("build"), () => {})) return yield* PluginHost.make(plugins) }) @@ -66,9 +66,9 @@ describe("SystemPromptPlugin", () => { const catalog = yield* Catalog.Service const hooks = yield* PluginHooks.Service const pluginHost = yield* makeHost - yield* catalog.transform((draft) => { + yield* catalog.transform((editor) => { for (const id of ["gpt-5", "gpt-4.1", "gpt-5-codex"]) - draft.model.update(Provider.ID.make("test"), Model.ID.make(id), () => {}) + editor.model.update(Provider.ID.make("test"), Model.ID.make(id), () => {}) }) yield* Effect.forEach(SystemPromptPlugin.Plugins, (plugin) => plugin.effect(pluginHost), { discard: true, @@ -108,8 +108,8 @@ describe("SystemPromptPlugin", () => { const catalog = yield* Catalog.Service const hooks = yield* PluginHooks.Service const pluginHost = yield* makeHost - yield* catalog.transform((draft) => - draft.model.update(Provider.ID.make("test"), Model.ID.make("gpt-5"), () => {}), + yield* catalog.transform((editor) => + editor.model.update(Provider.ID.make("test"), Model.ID.make("gpt-5"), () => {}), ) yield* SystemPromptPlugin.OpenAIPlugin.effect(pluginHost) const event = context("gpt-5") @@ -154,8 +154,8 @@ describe("SystemPromptPlugin", () => { Effect.gen(function* () { const agents = yield* Agent.Service const hooks = yield* PluginHooks.Service - yield* agents.transform((draft) => - draft.update(Agent.ID.make("build"), (agent) => { + yield* agents.transform((editor) => + editor.update(Agent.ID.make("build"), (agent) => { agent.system = "Custom agent prompt" }), ) @@ -177,7 +177,7 @@ describe("SystemPromptPlugin", () => { const hooks = yield* PluginHooks.Service const pluginHost = yield* makeHost yield* SystemPromptPlugin.OpenAIPlugin.effect(pluginHost) - yield* agents.transform((draft) => draft.remove(Agent.ID.make("build"))) + yield* agents.transform((editor) => editor.remove(Agent.ID.make("build"))) const event = context("gpt-5") yield* hooks.trigger("session", "context", event) @@ -207,14 +207,14 @@ describe("SystemPromptPlugin", () => { const catalog = yield* Catalog.Service const hooks = yield* PluginHooks.Service const pluginHost = yield* makeHost - yield* catalog.transform((draft) => { - draft.model.update(Provider.ID.make("test"), Model.ID.make("openai-alias"), (model) => { + yield* catalog.transform((editor) => { + editor.model.update(Provider.ID.make("test"), Model.ID.make("openai-alias"), (model) => { model.modelID = Model.ID.make("gpt-5") }) - draft.model.update(Provider.ID.make("test"), Model.ID.make("gpt-5-alias"), (model) => { + editor.model.update(Provider.ID.make("test"), Model.ID.make("gpt-5-alias"), (model) => { model.modelID = Model.ID.make("custom-model") }) - draft.model.update(Provider.ID.make("test"), Model.ID.make("codex-family-alias"), (model) => { + editor.model.update(Provider.ID.make("test"), Model.ID.make("codex-family-alias"), (model) => { model.modelID = Model.ID.make("custom-deployment") model.family = Model.Family.make("gpt-codex") }) diff --git a/packages/core/test/plugin/websearch.test.ts b/packages/core/test/plugin/websearch.test.ts index bb55c7146d2..aba54aeecda 100644 --- a/packages/core/test/plugin/websearch.test.ts +++ b/packages/core/test/plugin/websearch.test.ts @@ -34,8 +34,8 @@ describe("built-in web search providers", () => { Effect.gen(function* () { const integrations = yield* Integration.Service const websearch = yield* WebSearch.Service - const registration = yield* webSearchHost(websearch).transform((draft) => { - draft.add({ + const registration = yield* webSearchHost(websearch).transform((editor) => { + editor.add({ id: "test-websearch", name: "Test Web Search", execute: (input) => Effect.succeed([{ url: "https://example.com", content: input.query, time: {} }]), diff --git a/packages/core/test/pty/pty-session.test.ts b/packages/core/test/pty/pty-session.test.ts index 06cf8fd2140..b9dfa200f35 100644 --- a/packages/core/test/pty/pty-session.test.ts +++ b/packages/core/test/pty/pty-session.test.ts @@ -212,7 +212,7 @@ describe("pty create defaults", () => { if (!configuredShell) return const pty = yield* Pty.Service const shell = yield* ShellSelect.Service - yield* shell.transform((draft) => draft.configure(configuredShell)) + yield* shell.transform((editor) => editor.configure(configuredShell)) const info = yield* Effect.acquireRelease(pty.create({ title: "configured" }), (created) => pty.remove(created.id).pipe(Effect.ignore), ) diff --git a/packages/core/test/reference.test.ts b/packages/core/test/reference.test.ts index 43df3747401..40934c8e698 100644 --- a/packages/core/test/reference.test.ts +++ b/packages/core/test/reference.test.ts @@ -61,13 +61,13 @@ describe("Reference", () => { yield* State.batch( Effect.gen(function* () { - yield* references.transform((draft) => - draft.add("docs", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/docs") })), + yield* references.transform((editor) => + editor.add("docs", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/docs") })), ) expect((yield* references.list()).map((info) => info.name)).toEqual(["docs"]) - yield* references.transform((draft) => { - draft.add( + yield* references.transform((editor) => { + editor.add( "sdk", Reference.GitSource.make({ type: "git", @@ -77,12 +77,12 @@ describe("Reference", () => { hidden: true, }), ) - draft.add("invalid", Reference.GitSource.make({ type: "git", repository: "invalid" })) - draft.add( + editor.add("invalid", Reference.GitSource.make({ type: "git", repository: "invalid" })) + editor.add( "invalid-branch", Reference.GitSource.make({ type: "git", repository: "owner/repo", branch: "../escape" }), ) - draft.add("file", Reference.GitSource.make({ type: "git", repository: "file:///docs" })) + editor.add("file", Reference.GitSource.make({ type: "git", repository: "file:///docs" })) }) const infos = yield* references.list() expect(infos.map((info) => info.name)).toEqual(["docs", "sdk"]) @@ -122,8 +122,8 @@ describe("Reference", () => { if (event.type !== Reference.Event.Updated.type || reentered) return reentered = true yield* references - .transform((draft) => - draft.add("docs", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/new") })), + .transform((editor) => + editor.add("docs", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/new") })), ) .pipe(Scope.provide(scope)) }), @@ -139,8 +139,8 @@ describe("Reference", () => { ) yield* Effect.addFinalizer(() => first.pipe(Effect.andThen(second))) - yield* references.transform((draft) => - draft.add("docs", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/old") })), + yield* references.transform((editor) => + editor.add("docs", Reference.LocalSource.make({ type: "local", path: AbsolutePath.make("/old") })), ) expect((yield* references.list()).map((info) => info.path)).toEqual([AbsolutePath.make("/new")]) diff --git a/packages/core/test/session-generate.test.ts b/packages/core/test/session-generate.test.ts index b1a588f93c1..1a7d651d81a 100644 --- a/packages/core/test/session-generate.test.ts +++ b/packages/core/test/session-generate.test.ts @@ -200,8 +200,8 @@ const setup = Effect.gen(function* () { const instructionBuiltIns = yield* InstructionBuiltIns.Service const context = yield* SessionContext.Service const store = yield* SessionStore.Service - yield* agents.transform((draft) => - draft.update(Agent.ID.make("build"), (agent) => { + yield* agents.transform((editor) => + editor.update(Agent.ID.make("build"), (agent) => { agent.mode = "primary" }), ) diff --git a/packages/core/test/session-runner-recorded.test.ts b/packages/core/test/session-runner-recorded.test.ts index 45f99799686..08a64ae014c 100644 --- a/packages/core/test/session-runner-recorded.test.ts +++ b/packages/core/test/session-runner-recorded.test.ts @@ -182,8 +182,8 @@ describe("SessionRunnerLLM recorded", () => { const agents = yield* Agent.Service const catalog = yield* Catalog.Service const hooks = yield* PluginHooks.Service - yield* agents.transform((draft) => - draft.update(Agent.ID.make("build"), (agent) => { + yield* agents.transform((editor) => + editor.update(Agent.ID.make("build"), (agent) => { agent.mode = "primary" agent.permissions.push({ action: "execute", resource: "*", effect: "deny" }) }), diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index b24c2de52ba..3a6486655a1 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -257,8 +257,8 @@ const permissionFail = { } const permission = permissionLayer() const transformTools = (registry: Tool.Interface, tools: Readonly>, options?: Tool.Options) => - registry.transform((draft) => - Object.entries(tools).forEach(([name, tool]) => draft.add({ ...tool, name, options: options ?? tool.options })), + registry.transform((editor) => + Object.entries(tools).forEach(([name, tool]) => editor.add({ ...tool, name, options: options ?? tool.options })), ) const layer = Layer.unwrap( Effect.map(RunnerState, (state) => { @@ -521,8 +521,8 @@ const setup = Effect.gen(function* () { yield* Effect.forEach(SystemPromptPlugin.Plugins, (plugin) => plugin.effect(pluginHost), { discard: true, }) - yield* agents.transform((draft) => - draft.update(Agent.ID.make("build"), (agent) => { + yield* agents.transform((editor) => + editor.update(Agent.ID.make("build"), (agent) => { agent.mode = "primary" }), ) @@ -906,8 +906,8 @@ const prepareTitleGeneration = Effect.gen(function* () { const agents = yield* Agent.Service const { db } = yield* Database.Service yield* db.update(SessionTable).set({ title: null }).where(eq(SessionTable.id, sessionID)).run().pipe(Effect.orDie) - yield* agents.transform((draft) => - draft.update(Agent.ID.make("title"), (agent) => { + yield* agents.transform((editor) => + editor.update(Agent.ID.make("title"), (agent) => { agent.mode = "primary" agent.hidden = true agent.system = "Generate a title." @@ -1700,8 +1700,8 @@ describe("SessionRunnerLLM", () => { scenario("updates selected-agent skill instructions after an agent switch", function* (s) { const agents = yield* Agent.Service - yield* agents.transform((draft) => - draft.update(Agent.ID.make("reviewer"), (agent) => { + yield* agents.transform((editor) => + editor.update(Agent.ID.make("reviewer"), (agent) => { agent.mode = "primary" }), ) @@ -2113,8 +2113,8 @@ describe("SessionRunnerLLM", () => { provider: route === AnthropicMessages.route ? "anthropic" : "openai", route, }) - yield* agents.transform((draft) => - draft.update(agentID, (agent) => { + yield* agents.transform((editor) => + editor.update(agentID, (agent) => { agent.system = "Review the project carefully." }), ) @@ -2484,7 +2484,7 @@ describe("SessionRunnerLLM", () => { scenario("does not recover provider context overflow when automatic compaction is disabled", function* (s) { yield* setupOverflowRecovery(s) const compaction = yield* SessionCompaction.Service - yield* compaction.transform((draft) => draft.configure({ auto: false })) + yield* compaction.transform((editor) => editor.configure({ auto: false })) yield* s.llm.push( [LLMEvent.providerError({ message: "prompt too long", classification: "context-overflow" })], TestLLM.text("Must not compact", "text-unexpected-summary"), diff --git a/packages/core/test/skill.test.ts b/packages/core/test/skill.test.ts index 2a1e796baa5..d99d6b0b45f 100644 --- a/packages/core/test/skill.test.ts +++ b/packages/core/test/skill.test.ts @@ -23,11 +23,11 @@ describe("Skill", () => { it.effect("registers values with last-write-wins precedence", () => Effect.gen(function* () { const skill = yield* Skill.Service - yield* skill.transform((draft) => { - draft.add(info("review", "First")) - draft.add(info("deploy", "Deploy")) - draft.add(info("review", "Second")) - expect(draft.list().map((item) => item.id)).toEqual([Skill.ID.make("review"), Skill.ID.make("deploy")]) + yield* skill.transform((editor) => { + editor.add(info("review", "First")) + editor.add(info("deploy", "Deploy")) + editor.add(info("review", "Second")) + expect(editor.list().map((item) => item.id)).toEqual([Skill.ID.make("review"), Skill.ID.make("deploy")]) }) expect(yield* skill.list()).toEqual([info("review", "Second"), info("deploy", "Deploy")]) @@ -39,17 +39,17 @@ describe("Skill", () => { it.effect("updates and removes registered values", () => Effect.gen(function* () { const skill = yield* Skill.Service - yield* skill.transform((draft) => { - draft.add(info("review", "Initial")) - draft.update("review", (value) => { + yield* skill.transform((editor) => { + editor.add(info("review", "Initial")) + editor.update("review", (value) => { value.description = "Updated" value.id = Skill.ID.make("ignored") }) - draft.update("missing", () => { + editor.update("missing", () => { throw new Error("unreachable") }) - draft.add(info("deploy", "Deploy")) - draft.remove("deploy") + editor.add(info("deploy", "Deploy")) + editor.remove("deploy") }) expect(yield* skill.list()).toEqual([info("review", "Updated")]) @@ -60,9 +60,9 @@ describe("Skill", () => { Effect.gen(function* () { const skill = yield* Skill.Service const original = info("review", "Initial") - yield* skill.transform((draft) => draft.add(original)) - const updated = yield* skill.transform((draft) => - draft.update("review", (value) => { + yield* skill.transform((editor) => editor.add(original)) + const updated = yield* skill.transform((editor) => + editor.update("review", (value) => { value.description = "Updated" }), ) @@ -85,7 +85,7 @@ describe("Skill", () => { ) yield* Effect.yieldNow - yield* skill.transform((draft) => draft.add(info("review", "Visible"))) + yield* skill.transform((editor) => editor.add(info("review", "Visible"))) expect(yield* Deferred.await(updated).pipe(Effect.timeout("1 second"))).toEqual([info("review", "Visible")]) yield* Fiber.interrupt(fiber) }), @@ -94,8 +94,8 @@ describe("Skill", () => { it.effect("filters values by agent permissions", () => Effect.gen(function* () { const agents = yield* Agent.Service - yield* agents.transform((draft) => - draft.update(Agent.ID.make("reviewer"), (agent) => { + yield* agents.transform((editor) => + editor.update(Agent.ID.make("reviewer"), (agent) => { agent.permissions.push({ action: "skill", resource: "deploy", effect: "deny" }) }), ) diff --git a/packages/core/test/snapshot.test.ts b/packages/core/test/snapshot.test.ts index 1f1027ac9f9..9e1b03956ac 100644 --- a/packages/core/test/snapshot.test.ts +++ b/packages/core/test/snapshot.test.ts @@ -166,7 +166,7 @@ describe("Snapshot", () => { yield* Effect.gen(function* () { const snapshot = yield* Snapshot.Service - const registration = yield* snapshot.transform((draft) => draft.configure(false)) + const registration = yield* snapshot.transform((editor) => editor.configure(false)) expect(yield* snapshot.capture()).toBeUndefined() yield* registration.dispose diff --git a/packages/core/test/state-replay.test.ts b/packages/core/test/state-replay.test.ts index 51b630075cd..4c92c1a4b97 100644 --- a/packages/core/test/state-replay.test.ts +++ b/packages/core/test/state-replay.test.ts @@ -47,14 +47,14 @@ describe("State replay properties", () => { const states = sources.map((_, index) => State.create({ initial: (): Value => ({ value: sources[index], order: [] }), - draft: (draft) => draft, + editor: (editor) => editor, notify: () => Effect.sync(() => void notifications[index]++), }), ) - const callbacks = operations.map((operation, index) => (draft: Value) => { + const callbacks = operations.map((operation, index) => (editor: Value) => { calls++ - draft.value = apply(draft.value, operation) - draft.order.push(index) + editor.value = apply(editor.value, operation) + editor.order.push(index) }) const registrations: { handle: State.Registration; callback: number; active: boolean }[][] = [[], []] const expected = (index: number): Value => { @@ -138,7 +138,7 @@ describe("State replay properties", () => { let calls = 0 const state = State.create({ initial: () => ({ value: 1, order: new Array() }), - draft: (draft) => draft, + editor: (editor) => editor, }) const registrations: State.Registration[] = [] const retained: { value: Value; snapshot: Value }[] = [] @@ -155,10 +155,10 @@ describe("State replay properties", () => { Effect.gen(function* () { const before = calls const added = yield* Effect.forEach(chunk.append, (operation) => - state.transform((draft) => { + state.transform((editor) => { calls++ - draft.value = apply(draft.value, operation) - draft.order.push(draft.order.length) + editor.value = apply(editor.value, operation) + editor.order.push(editor.order.length) }), ) registrations.push(...added) diff --git a/packages/core/test/state.test.ts b/packages/core/test/state.test.ts index 1b74ee4105c..8aa05c9ebf1 100644 --- a/packages/core/test/state.test.ts +++ b/packages/core/test/state.test.ts @@ -11,7 +11,7 @@ describe("State", () => { let block = true const state = State.create({ initial: () => ({ values: [] as string[] }), - draft: (draft) => ({ add: (value: string) => draft.values.push(value) }), + editor: (editor) => ({ add: (value: string) => editor.values.push(value) }), notify: () => block ? Deferred.succeed(rebuilding, undefined).pipe(Effect.andThen(Deferred.await(release))) : Effect.void, }) @@ -38,12 +38,12 @@ describe("State", () => { const observed: string[][] = [] const state: State.Interface<{ values: string[] }, { add: (item: string) => void }> = State.create({ initial: () => ({ values: [] as string[] }), - draft: (draft) => ({ add: (item: string) => draft.values.push(item) }), + editor: (editor) => ({ add: (item: string) => editor.values.push(item) }), notify: () => Effect.sync(() => observed.push([...state.get().values])), }) - yield* state.transform((draft) => { - draft.add("value") + yield* state.transform((editor) => { + editor.add("value") }) // Update events publish from notify, so consumers reading on the event @@ -57,7 +57,7 @@ describe("State", () => { let value = "first" const state = State.create({ initial: () => ({ values: [] as string[] }), - draft: (draft) => ({ add: (item: string) => draft.values.push(item) }), + editor: (editor) => ({ add: (item: string) => editor.values.push(item) }), }) yield* state.transform((editor) => { @@ -75,7 +75,7 @@ describe("State", () => { Effect.gen(function* () { const state = State.create({ initial: () => ({ values: [] as string[] }), - draft: (draft) => ({ add: (item: string) => draft.values.push(item) }), + editor: (editor) => ({ add: (item: string) => editor.values.push(item) }), }) yield* state.transform((editor) => { editor.add("first") @@ -98,25 +98,25 @@ describe("State", () => { let finalized = 0 const first = State.create({ initial: () => ({ values: [] as string[] }), - draft: (draft) => ({ add: (item: string) => draft.values.push(item) }), + editor: (editor) => ({ add: (item: string) => editor.values.push(item) }), notify: () => Effect.sync(() => finalized++), }) const second = State.create({ initial: () => ({ values: [] as string[] }), - draft: (draft) => ({ add: (item: string) => draft.values.push(item) }), + editor: (editor) => ({ add: (item: string) => editor.values.push(item) }), notify: () => Effect.sync(() => finalized++), }) yield* State.batch( Effect.gen(function* () { - yield* first.transform((draft) => { - draft.add("first") + yield* first.transform((editor) => { + editor.add("first") }) - yield* first.transform((draft) => { - draft.add("second") + yield* first.transform((editor) => { + editor.add("second") }) - yield* second.transform((draft) => { - draft.add("third") + yield* second.transform((editor) => { + editor.add("third") }) expect(finalized).toBe(0) }), @@ -134,7 +134,7 @@ describe("State", () => { let disposed = 0 const state = State.create({ initial: () => ({ values: [] as string[] }), - draft: (draft) => ({ add: (item: string) => draft.values.push(item) }), + editor: (editor) => ({ add: (item: string) => editor.values.push(item) }), notify: () => Effect.sync(() => finalized++), }) const scope = yield* Scope.make() @@ -142,7 +142,7 @@ describe("State", () => { scope, Effect.sync(() => disposed++), ) - const registration = yield* state.transform((draft) => draft.add("value")).pipe(Scope.provide(scope)) + const registration = yield* state.transform((editor) => editor.add("value")).pipe(Scope.provide(scope)) expect(finalized).toBe(1) yield* State.shutdown(Scope.close(scope, Exit.void)) @@ -160,12 +160,12 @@ describe("State", () => { const finalized: string[] = [] const closing = State.create({ initial: () => ({}), - draft: (draft) => draft, + editor: (editor) => editor, notify: () => Effect.sync(() => finalized.push("closing")), }) const live = State.create({ initial: () => ({}), - draft: (draft) => draft, + editor: (editor) => editor, notify: () => Effect.sync(() => finalized.push("live")), }) const scope = yield* Scope.make() @@ -187,11 +187,11 @@ describe("State", () => { let finalized = 0 const state = State.create({ initial: () => ({ values: [] as string[] }), - draft: (draft) => ({ add: (item: string) => draft.values.push(item) }), + editor: (editor) => ({ add: (item: string) => editor.values.push(item) }), notify: () => Effect.sync(() => finalized++), }) - yield* state.transform((draft) => { - draft.add("value") + yield* state.transform((editor) => { + editor.add("value") }) finalized = 0 @@ -209,19 +209,19 @@ describe("State rebuild", () => { Effect.gen(function* () { const state = State.create({ initial: () => ({ values: new Array(), tags: new Map() }), - draft: (data) => data, + editor: (data) => data, }) yield* State.batch( Effect.gen(function* () { - yield* state.transform((draft) => { - draft.values.push("first") - draft.tags.set("first", 1) + yield* state.transform((editor) => { + editor.values.push("first") + editor.tags.set("first", 1) }) const retained = state.get() expect(state.get()).toBe(retained) - yield* state.transform((draft) => { - draft.values.push("second") - draft.tags.set("second", 2) + yield* state.transform((editor) => { + editor.values.push("second") + editor.tags.set("second", 2) }) const current = state.get() expect(current).not.toBe(retained) @@ -234,12 +234,12 @@ describe("State rebuild", () => { }), ) - it.effect("recreates the draft with every rebuild", () => + it.effect("recreates the editor with every rebuild", () => Effect.gen(function* () { let drafts = 0 const state = State.create({ initial: () => ({ values: new Array() }), - draft: (data) => { + editor: (data) => { drafts++ let sequence = 0 return { add: () => data.values.push(++sequence) } @@ -247,10 +247,10 @@ describe("State rebuild", () => { }) yield* State.batch( Effect.gen(function* () { - yield* state.transform((draft) => draft.add()) + yield* state.transform((editor) => editor.add()) expect(state.get().values).toEqual([1]) expect(drafts).toBe(1) - yield* state.transform((draft) => draft.add()) + yield* state.transform((editor) => editor.add()) expect(state.get().values).toEqual([1, 2]) expect(drafts).toBe(2) yield* state.reload() @@ -267,7 +267,7 @@ describe("State rebuild", () => { const notifications: string[][] = [] const state = State.create({ initial: () => ({ values: new Array() }), - draft: (data) => data, + editor: (data) => data, notify: (value) => Effect.sync(() => notifications.push([...value.values])), }) expect(state.get().values).toEqual([]) @@ -276,31 +276,31 @@ describe("State rebuild", () => { yield* State.batch( Effect.gen(function* () { - yield* state.transform((draft) => { + yield* state.transform((editor) => { calls.push("first") - draft.values.push("first") + editor.values.push("first") }) - yield* state.transform((draft) => { + yield* state.transform((editor) => { calls.push("second") - draft.values.push("second") + editor.values.push("second") }) expect(calls).toEqual([]) const view = state.get() expect(view.values).toEqual(["first", "second"]) expect(state.get()).toBe(view) expect(calls).toEqual(["first", "second"]) - yield* state.transform((draft) => { + yield* state.transform((editor) => { calls.push("third") - draft.values.push("third") + editor.values.push("third") }) expect(calls).toEqual(["first", "second"]) expect(state.get()).not.toBe(view) expect(state.get().values).toEqual(["first", "second", "third"]) expect(view.values).toEqual(["first", "second"]) expect(calls).toEqual(["first", "second", "first", "second", "third"]) - yield* state.transform((draft) => { + yield* state.transform((editor) => { calls.push("fourth") - draft.values.push("fourth") + editor.values.push("fourth") }) expect(notifications).toEqual([]) }), @@ -313,22 +313,22 @@ describe("State rebuild", () => { it.effect("replays every callback after each change outside a batch", () => Effect.gen(function* () { const calls: number[] = [] - const state = State.create({ initial: () => ({ value: 2 }), draft: (data) => data }) - yield* state.transform((draft) => { + const state = State.create({ initial: () => ({ value: 2 }), editor: (data) => data }) + yield* state.transform((editor) => { calls.push(1) - draft.value += 3 + editor.value += 3 }) - yield* state.transform((draft) => { + yield* state.transform((editor) => { calls.push(2) - draft.value *= 4 + editor.value *= 4 }) // Each registration outside a batch notifies immediately, and notification materializes. expect(calls).toEqual([1, 1, 2]) expect(state.get().value).toBe(20) expect(calls).toEqual([1, 1, 2]) - yield* state.transform((draft) => { + yield* state.transform((editor) => { calls.push(3) - draft.value -= 1 + editor.value -= 1 }) expect(state.get().value).toBe(19) expect(calls).toEqual([1, 1, 2, 1, 2, 3]) @@ -338,20 +338,20 @@ describe("State rebuild", () => { it.effect(`rebuilds noncommutative edits after removing position ${removed}`, () => Effect.gen(function* () { const calls: number[] = [] - const state = State.create({ initial: () => ({ value: 5 }), draft: (data) => data }) + const state = State.create({ initial: () => ({ value: 5 }), editor: (data) => data }) const registrations = yield* State.batch( Effect.all([ - state.transform((draft) => { + state.transform((editor) => { calls.push(0) - draft.value += 1 + editor.value += 1 }), - state.transform((draft) => { + state.transform((editor) => { calls.push(1) - draft.value *= 3 + editor.value *= 3 }), - state.transform((draft) => { + state.transform((editor) => { calls.push(2) - draft.value -= 4 + editor.value -= 4 }), ]), ) @@ -371,8 +371,8 @@ describe("State rebuild", () => { it.effect("keeps equal callback registrations independently disposable", () => Effect.gen(function* () { - const state = State.create({ initial: () => ({ value: 0 }), draft: (data) => data }) - const callback = (draft: { value: number }) => draft.value++ + const state = State.create({ initial: () => ({ value: 0 }), editor: (data) => data }) + const callback = (editor: { value: number }) => editor.value++ const first = yield* state.transform(callback) const second = yield* state.transform(callback) expect(state.get().value).toBe(2) @@ -388,7 +388,7 @@ describe("State rebuild", () => { it.effect("does not evaluate a pending callback removed before the first read", () => Effect.gen(function* () { let calls = 0 - const state = State.create({ initial: () => ({ value: 0 }), draft: (data) => data }) + const state = State.create({ initial: () => ({ value: 0 }), editor: (data) => data }) yield* State.batch( Effect.gen(function* () { const registration = yield* state.transform(() => calls++) @@ -407,12 +407,12 @@ describe("State rebuild", () => { let notifications = 0 const state = State.create({ initial: () => ({ value: 0 }), - draft: (data) => data, + editor: (data) => data, notify: () => Effect.sync(() => notifications++), }) - yield* state.transform((draft) => { + yield* state.transform((editor) => { calls++ - draft.value += source + editor.value += source }) notifications = 0 source = 2 @@ -426,7 +426,7 @@ describe("State rebuild", () => { source = 3 yield* state.reload() expect(state.get().value).toBe(3) - yield* state.transform((draft) => (draft.value *= 10)) + yield* state.transform((editor) => (editor.value *= 10)) expect(state.get().value).toBe(30) expect(calls).toBe(4) expect(notifications).toBe(1) @@ -439,7 +439,7 @@ describe("State rebuild", () => { it.effect("resamples a changing initial value even when there are no transforms", () => Effect.gen(function* () { let source = 1 - const state = State.create({ initial: () => ({ value: source }), draft: (data) => data }) + const state = State.create({ initial: () => ({ value: source }), editor: (data) => data }) expect(state.get().value).toBe(1) // A captured input changed but nothing invalidated the value, so reads stay cached. source = 2 @@ -462,15 +462,15 @@ describe("State rebuild", () => { initializations++ return { values: new Array() } }, - draft: (data) => data, + editor: (data) => data, }) - yield* state.transform((draft) => draft.values.push("first")) + yield* state.transform((editor) => editor.values.push("first")) const before = state.get() expect(initializations).toBe(2) yield* State.batch( Effect.gen(function* () { - yield* state.transform((draft) => { - draft.values.push("second") + yield* state.transform((editor) => { + editor.values.push("second") if (fail) throw new Error("failed edit") }) expect(() => state.get()).toThrow("failed edit") @@ -482,7 +482,7 @@ describe("State rebuild", () => { fail = false expect(state.get().values).toEqual(["first", "second"]) expect(initializations).toBe(5) - yield* state.transform((draft) => draft.values.push("third")) + yield* state.transform((editor) => editor.values.push("third")) expect(state.get().values).toEqual(["first", "second", "third"]) expect(initializations).toBe(6) }), @@ -492,18 +492,18 @@ describe("State rebuild", () => { it.effect("recovers by disposing a failing callback without keeping its partial edits", () => Effect.gen(function* () { - const state = State.create({ initial: () => ({ value: 2 }), draft: (data) => data }) - yield* state.transform((draft) => (draft.value *= 3)) + const state = State.create({ initial: () => ({ value: 2 }), editor: (data) => data }) + yield* state.transform((editor) => (editor.value *= 3)) yield* State.batch( Effect.gen(function* () { - const failing = yield* state.transform((draft) => { - draft.value += 100 + const failing = yield* state.transform((editor) => { + editor.value += 100 throw new Error("bad callback") }) expect(() => state.get()).toThrow("bad callback") yield* failing.dispose expect(state.get().value).toBe(6) - yield* state.transform((draft) => (draft.value += 1)) + yield* state.transform((editor) => (editor.value += 1)) expect(state.get().value).toBe(7) }), ) @@ -522,7 +522,7 @@ describe("State notification boundaries", () => { let block = true const first = State.create({ initial: () => ({ value: 0 }), - draft: (data) => data, + editor: (data) => data, notify: () => Effect.gen(function* () { observed.push("first") @@ -533,13 +533,13 @@ describe("State notification boundaries", () => { }) const second = State.create({ initial: () => ({ value: 0 }), - draft: (data) => data, + editor: (data) => data, notify: () => Effect.sync(() => observed.push("second")), }) const writer = yield* State.batch( Effect.gen(function* () { - yield* first.transform((draft) => draft.value++) - yield* second.transform((draft) => draft.value++) + yield* first.transform((editor) => editor.value++) + yield* second.transform((editor) => editor.value++) if (phase !== "body") return yield* Deferred.succeed(entered, undefined) yield* Effect.never @@ -560,20 +560,20 @@ describe("State notification boundaries", () => { let notifications = 0 const state = State.create({ initial: () => ({ value: 0 }), - draft: (data) => data, + editor: (data) => data, notify: () => Effect.sync(() => notifications++), }) const inherit = yield* State.batch( Effect.gen(function* () { - yield* state.transform((draft) => draft.value++) - yield* State.batch(state.transform((draft) => draft.value++)) + yield* state.transform((editor) => editor.value++) + yield* State.batch(state.transform((editor) => editor.value++)) expect(state.get().value).toBe(2) expect(notifications).toBe(0) return yield* State.inherit() }), ) expect(notifications).toBe(1) - yield* inherit(state.transform((draft) => draft.value++)) + yield* inherit(state.transform((editor) => editor.value++)) expect(state.get().value).toBe(3) expect(notifications).toBe(2) }), @@ -584,10 +584,10 @@ describe("State notification boundaries", () => { const scope = yield* Scope.Scope const observed: number[] = [] let added = false - const other = State.create({ initial: () => ({ value: 0 }), draft: (data) => data }) + const other = State.create({ initial: () => ({ value: 0 }), editor: (data) => data }) const state: State.Interface = State.create({ initial: () => ({}), - draft: (data) => data, + editor: (data) => data, notify: () => Effect.gen(function* () { observed.push(other.get().value) @@ -599,7 +599,7 @@ describe("State notification boundaries", () => { yield* State.batch( Effect.gen(function* () { yield* state.transform(() => {}) - yield* other.transform((draft) => (draft.value = 42)) + yield* other.transform((editor) => (editor.value = 42)) }), ) expect(observed).toEqual([42, 42]) @@ -613,7 +613,7 @@ describe("State notification boundaries", () => { const observed: number[] = [] const state: State.Interface<{ value: number }, { value: number }> = State.create({ initial: () => ({ value: 0 }), - draft: (data) => data, + editor: (data) => data, notify: () => Effect.gen(function* () { observed.push(state.get().value) @@ -623,7 +623,7 @@ describe("State notification boundaries", () => { yield* state.reload() }), }) - yield* state.transform((draft) => (draft.value = source)) + yield* state.transform((editor) => (editor.value = source)) source = 2 reloadAgain = true yield* state.reload() @@ -637,12 +637,12 @@ describe("State notification boundaries", () => { let fail = true const first = State.create({ initial: () => ({}), - draft: (data) => data, + editor: (data) => data, notify: () => (fail ? Effect.die("observer failed") : Effect.void), }) const second = State.create({ initial: () => ({}), - draft: (data) => data, + editor: (data) => data, notify: () => Effect.sync(() => observed.push("second")), }) const exit = yield* State.batch( @@ -668,7 +668,7 @@ describe("State notification boundaries", () => { let notifications = 0 const state = State.create({ initial: () => ({}), - draft: (data) => data, + editor: (data) => data, notify: () => Effect.sync(() => { notifications++ diff --git a/packages/core/test/tool-output.test.ts b/packages/core/test/tool-output.test.ts index 1c14c9acada..35e47d19c1b 100644 --- a/packages/core/test/tool-output.test.ts +++ b/packages/core/test/tool-output.test.ts @@ -23,7 +23,7 @@ const withStore = ( return Effect.gen(function* () { const output = yield* ToolOutput.Service const fs = yield* FSUtil.Service - if (limits) yield* output.transform((draft) => draft.configure(limits)) + if (limits) yield* output.transform((editor) => editor.configure(limits)) return yield* body(output, fs, tmp.path) }).pipe(Effect.provide(layer)) }, diff --git a/packages/core/test/tool-read.test.ts b/packages/core/test/tool-read.test.ts index 56fc610ca17..776235c610b 100644 --- a/packages/core/test/tool-read.test.ts +++ b/packages/core/test/tool-read.test.ts @@ -395,7 +395,7 @@ describe("ReadTool", () => { mime: "image/png", } const image = yield* Image.Service - yield* image.transform((draft) => draft.configure({ autoResize: false, maxWidth: 4 })) + yield* image.transform((editor) => editor.configure({ autoResize: false, maxWidth: 4 })) const registry = yield* Tool.Service expect( @@ -429,7 +429,7 @@ describe("ReadTool", () => { mime: "image/png", } const image = yield* Image.Service - yield* image.transform((draft) => draft.configure({ maxWidth: 4 })) + yield* image.transform((editor) => editor.configure({ maxWidth: 4 })) const registry = yield* Tool.Service const result = yield* executeTool(registry, { sessionID, @@ -471,15 +471,15 @@ describe("ReadTool", () => { [5, "="], [6, ""], ] as const) { - yield* image.transform((draft) => draft.configure({ maxWidth, maxBase64Bytes: 1_024 })) + yield* image.transform((editor) => editor.configure({ maxWidth, maxBase64Bytes: 1_024 })) const candidate = yield* image.normalize("wide.png", content) expect(candidate.mime).toBe("image/png") expect(candidate.content.match(/=*$/)?.[0]).toBe(padding) - yield* image.transform((draft) => draft.configure({ maxBase64Bytes: candidate.content.length })) + yield* image.transform((editor) => editor.configure({ maxBase64Bytes: candidate.content.length })) expect(yield* image.normalize("wide.png", content)).toEqual(candidate) - yield* image.transform((draft) => draft.configure({ maxBase64Bytes: candidate.content.length - 1 })) + yield* image.transform((editor) => editor.configure({ maxBase64Bytes: candidate.content.length - 1 })) const smaller = yield* image.normalize("wide.png", content) expect(smaller.mime).toBe("image/png") expect(smaller.content.length).toBeLessThan(candidate.content.length) @@ -499,7 +499,7 @@ describe("ReadTool", () => { mime: "image/png", } const image = yield* Image.Service - yield* image.transform((draft) => draft.configure({ maxBase64Bytes: 1 })) + yield* image.transform((editor) => editor.configure({ maxBase64Bytes: 1 })) const registry = yield* Tool.Service expect( diff --git a/packages/core/test/tool-registry.test.ts b/packages/core/test/tool-registry.test.ts index 076fd92d8ea..ef6397e72b1 100644 --- a/packages/core/test/tool-registry.test.ts +++ b/packages/core/test/tool-registry.test.ts @@ -65,20 +65,20 @@ const constant = (text: string): Info => ({ }) const transform = (service: Tool.Interface, tools: Readonly>, options?: Tool.Options) => - service.transform((draft) => - Object.entries(tools).forEach(([name, tool]) => draft.add({ ...tool, name, options: options ?? tool.options })), + service.transform((editor) => + Object.entries(tools).forEach(([name, tool]) => editor.add({ ...tool, name, options: options ?? tool.options })), ) describe("Tool", () => { - it.effect("reads the current draft tools by effective name", () => + it.effect("reads the current editor tools by effective name", () => Effect.gen(function* () { const service = yield* Tool.Service yield* transform(service, { echo: make() }, { namespace: "acme", codemode: false }) - yield* service.transform((draft) => { - expect(draft.list().map((tool) => tool.id)).toEqual(["acme_echo"]) - expect(draft.get("acme_echo")?.id).toBe("acme_echo") - expect(draft.get("acme_echo")?.name).toBe("echo") - expect(draft.get("missing")).toBeUndefined() + yield* service.transform((editor) => { + expect(editor.list().map((tool) => tool.id)).toEqual(["acme_echo"]) + expect(editor.get("acme_echo")?.id).toBe("acme_echo") + expect(editor.get("acme_echo")?.name).toBe("echo") + expect(editor.get("missing")).toBeUndefined() }) }), ) @@ -89,18 +89,18 @@ describe("Tool", () => { let text = "original" const source = yield* Scope.make() yield* service - .transform((draft) => { - draft.add({ ...constant(text), name: "echo", options: { namespace: "acme", codemode: false } }) - draft.add({ ...make(), name: "hidden" }) + .transform((editor) => { + editor.add({ ...constant(text), name: "echo", options: { namespace: "acme", codemode: false } }) + editor.add({ ...make(), name: "hidden" }) }) .pipe(Scope.provide(source)) const original = yield* service.snapshot() - const update = yield* service.transform((draft) => { - draft.update("missing", () => { + const update = yield* service.transform((editor) => { + editor.update("missing", () => { throw new Error("must not create a tool") }) - draft.remove("missing") - draft.update("acme_echo", (tool) => { + editor.remove("missing") + editor.update("acme_echo", (tool) => { const execute = tool.execute tool.description = "Updated" tool.execute = (input, context) => @@ -110,7 +110,7 @@ describe("Tool", () => { }) }) const scope = yield* Scope.make() - yield* service.transform((draft) => draft.remove("hidden")).pipe(Scope.provide(scope)) + yield* service.transform((editor) => editor.remove("hidden")).pipe(Scope.provide(scope)) expect((yield* service.snapshot()).codeModeCatalog?.tools).toEqual([]) expect((yield* executeTool(service, call("acme_echo"))).output).toEqual({ text: "original updated" }) @@ -130,8 +130,8 @@ describe("Tool", () => { "hidden", ]) - yield* service.transform((draft) => - draft.update("acme_echo", (tool) => { + yield* service.transform((editor) => + editor.update("acme_echo", (tool) => { tool.description = "Updated again" }), ) @@ -143,16 +143,16 @@ describe("Tool", () => { it.effect("updates schemas and executors without renaming tools and applies removal in order", () => Effect.gen(function* () { const service = yield* Tool.Service - yield* service.transform((draft) => { - draft.add({ ...make(), options: { namespace: "acme.tools", codemode: false } }) - draft.add({ ...make(), name: "removed", options: { codemode: false } }) - draft.remove("removed") - draft.update("removed", () => { + yield* service.transform((editor) => { + editor.add({ ...make(), options: { namespace: "acme.tools", codemode: false } }) + editor.add({ ...make(), name: "removed", options: { codemode: false } }) + editor.remove("removed") + editor.update("removed", () => { throw new Error("must not resurrect a tool") }) - draft.remove("acme_tools_echo") - draft.add({ ...make(), options: { namespace: "acme.tools", codemode: false } }) - draft.update("acme_tools_echo", (tool) => { + editor.remove("acme_tools_echo") + editor.add({ ...make(), options: { namespace: "acme.tools", codemode: false } }) + editor.update("acme_tools_echo", (tool) => { tool.name = "renamed" tool.options = { namespace: "other", codemode: false } tool.input = Schema.Struct({ value: Schema.Finite }) @@ -182,8 +182,8 @@ describe("Tool", () => { Effect.gen(function* () { const service = yield* Tool.Service yield* transform(service, { echo: make() }, { codemode: false }) - yield* service.transform((draft) => - draft.update("echo", (tool) => { + yield* service.transform((editor) => + editor.update("echo", (tool) => { Object.assign(tool, { description: undefined }) }), ) @@ -196,7 +196,7 @@ describe("Tool", () => { Effect.gen(function* () { const service = yield* Tool.Service let source: Info[] = [] - yield* service.transform((draft) => source.forEach((tool) => draft.add(tool))) + yield* service.transform((editor) => source.forEach((tool) => editor.add(tool))) expect((yield* service.snapshot()).definitions.map((tool) => tool.name)).toEqual(["execute"]) const tool = { ...constant("first"), name: "echo", options: { codemode: false } } @@ -222,15 +222,15 @@ describe("Tool", () => { Effect.gen(function* () { const service = yield* Tool.Service const runs: string[] = [] - yield* service.transform((draft) => { + yield* service.transform((editor) => { runs.push("base") - draft.add({ ...constant("base"), name: "echo", options: { codemode: false } }) + editor.add({ ...constant("base"), name: "echo", options: { codemode: false } }) }) const scope = yield* Scope.make() const overlay = yield* service - .transform((draft) => { + .transform((editor) => { runs.push("overlay") - draft.add({ ...constant("overlay"), name: "echo", options: { codemode: false } }) + editor.add({ ...constant("overlay"), name: "echo", options: { codemode: false } }) }) .pipe(Scope.provide(scope)) // Each registration outside a batch notifies immediately, and every rebuild replays all transforms. @@ -253,13 +253,13 @@ describe("Tool", () => { const scope = yield* Scope.make() yield* State.batch( Effect.gen(function* () { - yield* service.transform((draft) => { + yield* service.transform((editor) => { runs.push("base") - draft.add({ ...constant("base"), name: "echo", options: { codemode: false } }) + editor.add({ ...constant("base"), name: "echo", options: { codemode: false } }) }) - yield* service.transform((draft) => { + yield* service.transform((editor) => { runs.push("overlay") - draft.add({ ...constant("overlay"), name: "echo", options: { codemode: false } }) + editor.add({ ...constant("overlay"), name: "echo", options: { codemode: false } }) }) expect(runs).toEqual([]) expect((yield* service.snapshot()).definitions.map((tool) => tool.name)).toEqual(["echo", "execute"]) @@ -279,7 +279,7 @@ describe("Tool", () => { const service = yield* Tool.Service yield* transform(service, { echo_tool: constant("base") }, { codemode: false }) let source = [{ ...constant("overlay"), name: "echo.tool", options: { codemode: false } }] - const registration = yield* service.transform((draft) => source.forEach((tool) => draft.add(tool))) + const registration = yield* service.transform((editor) => source.forEach((tool) => editor.add(tool))) expect((yield* executeTool(service, call("echo_tool"))).output).toEqual({ text: "overlay" }) source = [...source, { ...constant("collision"), name: "echo_tool", options: { codemode: false } }] @@ -288,7 +288,7 @@ describe("Tool", () => { yield* registration.dispose expect((yield* executeTool(service, call("echo_tool"))).output).toEqual({ text: "base" }) - yield* service.transform((draft) => source.forEach((tool) => draft.add(tool))) + yield* service.transform((editor) => source.forEach((tool) => editor.add(tool))) source = [{ ...constant("invalid"), name: "", options: { codemode: false } }] yield* service.reload() @@ -406,11 +406,11 @@ describe("Tool", () => { it.effect("keeps healthy tools when another namespace is invalid", () => Effect.gen(function* () { const service = yield* Tool.Service - yield* service.transform((draft) => { - draft.namespace({ name: "invalid..namespace", description: "Invalid" }) - draft.add({ ...make(), name: "first", options: { codemode: false } }) - draft.add({ ...make(), name: "second", options: { namespace: "invalid..namespace", codemode: false } }) - draft.add({ ...make(), name: "second", options: { namespace: "invalid__namespace" } }) + yield* service.transform((editor) => { + editor.namespace({ name: "invalid..namespace", description: "Invalid" }) + editor.add({ ...make(), name: "first", options: { codemode: false } }) + editor.add({ ...make(), name: "second", options: { namespace: "invalid..namespace", codemode: false } }) + editor.add({ ...make(), name: "second", options: { namespace: "invalid__namespace" } }) }) const snapshot = yield* service.snapshot() @@ -424,13 +424,13 @@ describe("Tool", () => { it.effect("keeps namespace descriptions beside catalog tools", () => Effect.gen(function* () { const service = yield* Tool.Service - yield* service.transform((draft) => { - draft.namespace({ name: "registry", description: "Package publishing and discovery" }) - draft.namespace({ name: "registry.search", description: "Pricing operations" }) - draft.add({ ...make(), name: "plain", options: { namespace: "legacy" } }) - draft.add({ ...make(), name: "direct", options: { namespace: "registry", codemode: false } }) - draft.add({ ...make(), name: "search", description: "Search packages", options: { namespace: "registry" } }) - draft.add({ ...make(), name: "sales", description: "Read sales", options: { namespace: "registry.search" } }) + yield* service.transform((editor) => { + editor.namespace({ name: "registry", description: "Package publishing and discovery" }) + editor.namespace({ name: "registry.search", description: "Pricing operations" }) + editor.add({ ...make(), name: "plain", options: { namespace: "legacy" } }) + editor.add({ ...make(), name: "direct", options: { namespace: "registry", codemode: false } }) + editor.add({ ...make(), name: "search", description: "Search packages", options: { namespace: "registry" } }) + editor.add({ ...make(), name: "sales", description: "Read sales", options: { namespace: "registry.search" } }) }) const snapshot = yield* service.snapshot() @@ -498,14 +498,14 @@ describe("Tool", () => { it.effect("retains namespace descriptions in executable snapshots after appended transforms", () => Effect.gen(function* () { const service = yield* Tool.Service - yield* service.transform((draft) => { - draft.namespace({ name: "acme", description: "Archival operations" }) - draft.add({ ...make(), options: { namespace: "acme" } }) + yield* service.transform((editor) => { + editor.namespace({ name: "acme", description: "Archival operations" }) + editor.add({ ...make(), options: { namespace: "acme" } }) }) const advertised = yield* service.snapshot() - yield* service.transform((draft) => { - draft.namespace({ name: "acme", description: "Billing operations" }) + yield* service.transform((editor) => { + editor.namespace({ name: "acme", description: "Billing operations" }) }) const current = yield* service.snapshot() expect(advertised.codeModeCatalog?.tools).toMatchObject([{ name: "acme", description: "Archival operations" }]) @@ -533,10 +533,10 @@ describe("Tool", () => { it.effect("preserves a top-level tool that also has child tools", () => Effect.gen(function* () { const service = yield* Tool.Service - yield* service.transform((draft) => { - draft.namespace({ name: "pricing", description: "Pricing operations" }) - draft.add({ ...make(), name: "pricing" }) - draft.add({ ...make(), name: "sales", options: { namespace: "pricing" } }) + yield* service.transform((editor) => { + editor.namespace({ name: "pricing", description: "Pricing operations" }) + editor.add({ ...make(), name: "pricing" }) + editor.add({ ...make(), name: "sales", options: { namespace: "pricing" } }) }) const snapshot = yield* service.snapshot() @@ -566,15 +566,15 @@ describe("Tool", () => { }) return Effect.gen(function* () { const service = yield* Tool.Service - yield* service.transform((draft) => { - draft.add({ ...make(), name: "healthy", options: { codemode: false } }) - draft.add({ + yield* service.transform((editor) => { + editor.add({ ...make(), name: "healthy", options: { codemode: false } }) + editor.add({ name: "phone_type", input: Schema.Struct({}), execute: () => Effect.succeed({ content: "ok" }), options: { codemode: false }, } as unknown as Info) - draft.add({ ...make(), name: "codemode" }) + editor.add({ ...make(), name: "codemode" }) }) expect(output).toEqual([ @@ -600,9 +600,9 @@ describe("Tool", () => { yield* transform(service, { echo: constant("original") }, { codemode: false }) yield* Effect.scoped( Effect.gen(function* () { - yield* service.transform((draft) => { - draft.add({ ...constant("invalid"), name: "echo", description: undefined } as unknown as Info) - draft.add({ ...make(), name: "temporary", options: { codemode: false } }) + yield* service.transform((editor) => { + editor.add({ ...constant("invalid"), name: "echo", description: undefined } as unknown as Info) + editor.add({ ...make(), name: "temporary", options: { codemode: false } }) }) const snapshot = yield* service.snapshot() expect(snapshot.definitions.map((tool) => tool.name)).toEqual(["echo", "temporary", "execute"]) @@ -620,7 +620,7 @@ describe("Tool", () => { const capture = (tools: ReadonlyArray) => Effect.scoped( Effect.gen(function* () { - yield* service.transform((draft) => tools.forEach(draft.add)) + yield* service.transform((editor) => tools.forEach(editor.add)) return (yield* service.snapshot()).definitions }), ) @@ -645,8 +645,8 @@ describe("Tool", () => { it.effect("snapshots external tools with missing input schemas", () => Effect.gen(function* () { const service = yield* Tool.Service - yield* service.transform((draft) => - draft.add({ + yield* service.transform((editor) => + editor.add({ ...make(), input: undefined, } as unknown as Info), diff --git a/packages/core/test/tool-shell.test.ts b/packages/core/test/tool-shell.test.ts index 100a14a0822..02132ccfd2d 100644 --- a/packages/core/test/tool-shell.test.ts +++ b/packages/core/test/tool-shell.test.ts @@ -239,10 +239,10 @@ const withScanner = ( return yield* withSession(fixture.active, (registry) => Effect.gen(function* () { const selection = yield* ShellSelect.Service - yield* selection.transform((draft) => draft.configure(shell)) + yield* selection.transform((editor) => editor.configure(shell)) const agents = yield* Agent.Service - yield* agents.transform((draft) => - draft.update(toolIdentity.agent, (agent) => { + yield* agents.transform((editor) => + editor.update(toolIdentity.agent, (agent) => { agent.permissions = [] }), ) @@ -348,8 +348,8 @@ describe("ShellTool scanner permissions", () => { expect((yield* saved.list()).map((item) => item.resource)).toEqual(["printf *"]) const agents = yield* Agent.Service - yield* agents.transform((draft) => - draft.update(toolIdentity.agent, (agent) => { + yield* agents.transform((editor) => + editor.update(toolIdentity.agent, (agent) => { agent.permissions = [{ action: "shell", resource: "printf hello", effect: "deny" }] }), ) @@ -384,8 +384,8 @@ describe("ShellTool scanner permissions", () => { expect(yield* Effect.promise(() => Bun.file(marker).text())).toBe("hello") const agents = yield* Agent.Service - yield* agents.transform((draft) => - draft.update(toolIdentity.agent, (agent) => { + yield* agents.transform((editor) => + editor.update(toolIdentity.agent, (agent) => { agent.permissions = [{ action: "shell", resource: "cat", effect: "deny" }] }), ) @@ -404,8 +404,8 @@ describe("ShellTool scanner permissions", () => { withScanner(portable, (registry, fixture) => Effect.gen(function* () { const agents = yield* Agent.Service - yield* agents.transform((draft) => - draft.update(toolIdentity.agent, (agent) => { + yield* agents.transform((editor) => + editor.update(toolIdentity.agent, (agent) => { agent.permissions = [{ action: "shell", resource: "*", effect: "allow" }] }), ) @@ -445,8 +445,8 @@ describe("ShellTool scanner permissions", () => { Effect.gen(function* () { yield* Effect.promise(() => fs.symlink(fixture.outside, path.join(fixture.active, "123"))) const agents = yield* Agent.Service - yield* agents.transform((draft) => - draft.update(toolIdentity.agent, (agent) => { + yield* agents.transform((editor) => + editor.update(toolIdentity.agent, (agent) => { agent.permissions = [ { action: "shell", resource: "*", effect: "allow" }, { action: "external_directory", resource: "*", effect: "deny" }, @@ -477,8 +477,8 @@ describe("ShellTool scanner permissions", () => { withScanner(portable, (registry, fixture) => Effect.gen(function* () { const agents = yield* Agent.Service - yield* agents.transform((draft) => - draft.update(toolIdentity.agent, (agent) => { + yield* agents.transform((editor) => + editor.update(toolIdentity.agent, (agent) => { agent.permissions = [ { action: "shell", resource: "*", effect: "allow" }, { action: "external_directory", resource: path.join(fixture.outside, "*"), effect: "deny" }, @@ -595,8 +595,8 @@ describe("ShellTool ordinary shell syntax", () => { (registry, directory) => Effect.gen(function* () { const agents = yield* Agent.Service - yield* agents.transform((draft) => - draft.update(toolIdentity.agent, (agent) => { + yield* agents.transform((editor) => + editor.update(toolIdentity.agent, (agent) => { agent.permissions = [ { action: "shell", resource: "*", effect: "allow" }, { action: "shell", resource: "printf *", effect: "deny" }, @@ -682,8 +682,8 @@ describe("ShellTool ordinary shell syntax", () => { }) const agents = yield* Agent.Service - yield* agents.transform((draft) => - draft.update(toolIdentity.agent, (agent) => { + yield* agents.transform((editor) => + editor.update(toolIdentity.agent, (agent) => { agent.permissions = [{ action: "shell", resource: command, effect: "deny" }] }), ) @@ -706,8 +706,8 @@ describe("ShellTool", () => { reset() return withSession(tmp.path, (registry) => Effect.gen(function* () { - yield* registry.transform((draft) => - draft.update("shell", (tool) => { + yield* registry.transform((editor) => + editor.update("shell", (tool) => { tool.options = { ...tool.options, codemode: true } }), ) @@ -1068,7 +1068,7 @@ describe("ShellTool", () => { const settled = yield* withSession(tmp.path, (registry) => Effect.gen(function* () { const selection = yield* ShellSelect.Service - yield* selection.transform((draft) => draft.configure("sh")) + yield* selection.transform((editor) => editor.configure("sh")) return yield* executeTool( registry, call({ command: 'printf hello > marker\necho "' }, "call-portable-malformed"), @@ -1117,7 +1117,7 @@ describe("ShellTool", () => { yield* withSession(tmp.path, (registry) => Effect.gen(function* () { const selection = yield* ShellSelect.Service - yield* selection.transform((draft) => draft.configure(shell)) + yield* selection.transform((editor) => editor.configure(shell)) for (const [command, output] of [ ["echo $((1 + 1))", "2\n"], ["cd ~ && pwd", `${realpathSync(os.homedir())}\n`], diff --git a/packages/core/test/tool-subagent.test.ts b/packages/core/test/tool-subagent.test.ts index 6d56089123f..f767a27cc56 100644 --- a/packages/core/test/tool-subagent.test.ts +++ b/packages/core/test/tool-subagent.test.ts @@ -153,20 +153,20 @@ const withSubagent = (location: Location.Ref) => const locations = yield* LocationServiceMap.Service yield* Plugin.Service.use((plugins) => plugins.awaitActivation).pipe(Effect.provide(locations.get(location))) yield* Agent.Service.use((agents) => - agents.transform((draft) => { + agents.transform((editor) => { // The caller identity used by executeTool; subagent permission asserts against it. - draft.update(toolIdentity.agent, (agent) => { + editor.update(toolIdentity.agent, (agent) => { agent.mode = "primary" agent.permissions.push({ action: "*", resource: "*", effect: "allow" }) }) - draft.update(Agent.ID.make("reviewer"), (agent) => { + editor.update(Agent.ID.make("reviewer"), (agent) => { agent.mode = "subagent" agent.model = childModel }) - draft.update(Agent.ID.make("fallback"), (agent) => { + editor.update(Agent.ID.make("fallback"), (agent) => { agent.mode = "subagent" }) - draft.update(Agent.ID.make("primary"), (agent) => { + editor.update(Agent.ID.make("primary"), (agent) => { agent.mode = "primary" }) }), diff --git a/packages/core/test/tool-websearch.test.ts b/packages/core/test/tool-websearch.test.ts index 4259599546d..a3c1c64eb3c 100644 --- a/packages/core/test/tool-websearch.test.ts +++ b/packages/core/test/tool-websearch.test.ts @@ -55,9 +55,9 @@ const setup = Effect.gen(function* () { const fixture = new Fixture() const websearch = yield* TestWebSearch.Service const kv = yield* KV.Service - yield* websearch.transform((draft) => + yield* websearch.transform((editor) => providers.forEach((provider) => - draft.add({ + editor.add({ ...provider, execute: () => Effect.gen(function* () { diff --git a/packages/core/test/vcs.test.ts b/packages/core/test/vcs.test.ts index e296f6090a5..4d8db4ff2a8 100644 --- a/packages/core/test/vcs.test.ts +++ b/packages/core/test/vcs.test.ts @@ -121,9 +121,9 @@ describe("Vcs", () => { withTmp((directory) => Effect.gen(function* () { const vcs = yield* Vcs.Service - const registration = yield* vcs.transform((draft) => { - draft.add(provider()) - draft.default.set("custom") + const registration = yield* vcs.transform((editor) => { + editor.add(provider()) + editor.default.set("custom") }) expect(yield* vcs.info()).toEqual({ branch: { current: "feature", default: "main" } }) @@ -145,7 +145,7 @@ describe("Vcs", () => { withGit(() => Effect.gen(function* () { const vcs = yield* Vcs.Service - const registration = yield* vcs.transform((draft) => draft.add(provider({ id: "git" }))) + const registration = yield* vcs.transform((editor) => editor.add(provider({ id: "git" }))) expect(yield* vcs.info()).toEqual({ branch: { current: "feature", default: "main" } }) yield* registration.dispose @@ -160,8 +160,8 @@ describe("Vcs", () => { const reads: string[] = [] yield* State.batch( Effect.gen(function* () { - yield* vcs.transform((draft) => { - draft.add( + yield* vcs.transform((editor) => { + editor.add( provider({ info: () => Effect.sync(() => { @@ -170,12 +170,12 @@ describe("Vcs", () => { }), }), ) - draft.default.set("custom") + editor.default.set("custom") }) expect((yield* vcs.status())[0]?.file).toBe("file.txt") expect(yield* vcs.info()).toEqual({ branch: {} }) - yield* vcs.transform((draft) => - draft.add( + yield* vcs.transform((editor) => + editor.add( provider({ info: () => Effect.sync(() => { @@ -197,8 +197,8 @@ describe("Vcs", () => { Effect.gen(function* () { const observed: VcsDiffInput[] = [] const vcs = yield* Vcs.Service - yield* vcs.transform((draft) => { - draft.add( + yield* vcs.transform((editor) => { + editor.add( provider({ diff: (input) => Effect.sync(() => { @@ -207,7 +207,7 @@ describe("Vcs", () => { }), }), ) - draft.default.set("custom") + editor.default.set("custom") }) yield* vcs.diff("committed", { context: 3, base: "release" }) @@ -230,8 +230,8 @@ describe("Vcs", () => { withTmp((directory) => Effect.gen(function* () { const vcs = yield* Vcs.Service - yield* vcs.transform((draft) => { - draft.add( + yield* vcs.transform((editor) => { + editor.add( provider({ status: () => Effect.succeed([{ file: "file.txt", additions: -1, deletions: 0, status: "added" }]), diff: () => @@ -240,7 +240,7 @@ describe("Vcs", () => { ]), }), ) - draft.default.set("custom") + editor.default.set("custom") }) expect(yield* vcs.status()).toEqual([]) @@ -257,8 +257,8 @@ describe("Vcs", () => { Effect.gen(function* () { const vcs = yield* Vcs.Service let interrupt = false - yield* vcs.transform((draft) => { - draft.add( + yield* vcs.transform((editor) => { + editor.add( provider({ info: () => (interrupt ? Effect.interrupt : Effect.succeed({ branch: { current: "feature" } })), status: () => Effect.never, @@ -266,7 +266,7 @@ describe("Vcs", () => { base: () => Effect.never, }), ) - draft.default.set("custom") + editor.default.set("custom") }) const fiber = yield* Effect.forkChild(vcs.status()) @@ -317,8 +317,8 @@ describe("Vcs", () => { let branch = "initial" let block = false yield* vcs - .transform((draft) => { - draft.add( + .transform((editor) => { + editor.add( provider({ info: () => Effect.gen(function* () { @@ -333,7 +333,7 @@ describe("Vcs", () => { }), }), ) - draft.default.set("custom") + editor.default.set("custom") }) .pipe(Scope.provide(root)) observed.length = 0 @@ -375,8 +375,8 @@ describe("Vcs", () => { const faulty = yield* Scope.make() yield* Effect.addFinalizer(() => Scope.close(faulty, Exit.void)) let branch = "initial" - yield* vcs.transform((draft) => - draft.add(provider({ id: "git", info: () => Effect.sync(() => ({ branch: { current: branch } })) })), + yield* vcs.transform((editor) => + editor.add(provider({ id: "git", info: () => Effect.sync(() => ({ branch: { current: branch } })) })), ) const failure = new Error("fixture replay failed") let replays = 0 @@ -418,8 +418,8 @@ describe("Vcs", () => { const release = yield* Deferred.make() const accepted = yield* Deferred.make() const reads: string[] = [] - yield* vcs.transform((draft) => - draft.add( + yield* vcs.transform((editor) => + editor.add( provider({ id: "git", info: () => @@ -442,8 +442,8 @@ describe("Vcs", () => { yield* Deferred.await(started) const configured = yield* State.batch( Effect.gen(function* () { - yield* vcs.transform((draft) => - draft.add( + yield* vcs.transform((editor) => + editor.add( provider({ id: "git", info: () => @@ -490,15 +490,15 @@ describe("Vcs", () => { ) return Effect.void return vcs - .transform((draft) => - draft.add(provider({ info: () => Effect.succeed({ branch: { current: "listener" } }) })), + .transform((editor) => + editor.add(provider({ info: () => Effect.succeed({ branch: { current: "listener" } }) })), ) .pipe(Scope.provide(scope), Effect.asVoid) }) yield* Effect.gen(function* () { - yield* vcs.transform((draft) => { - draft.add(provider()) - draft.default.set("custom") + yield* vcs.transform((editor) => { + editor.add(provider()) + editor.default.set("custom") }) yield* bus.publish(Done, {}) const events = (yield* Fiber.join(updates)).filter((event) => event.type === VcsEvent.BranchUpdated.type) @@ -906,10 +906,10 @@ describe("Vcs", () => { withTmp((directory) => Effect.gen(function* () { const vcs = yield* Vcs.Service - yield* vcs.transform((draft) => { + yield* vcs.transform((editor) => { // @ts-expect-error Invalid third-party wire metadata must be checked at runtime. - draft.add(provider({ base: scenario.base })) - draft.default.set("custom") + editor.add(provider({ base: scenario.base })) + editor.default.set("custom") }) expect(yield* vcs.base().pipe(Effect.flip)).toMatchObject({ _tag: "Vcs.DiffError" }) }).pipe(provide(directory)), @@ -921,14 +921,14 @@ describe("Vcs", () => { withTmp((directory) => Effect.gen(function* () { const vcs = yield* Vcs.Service - yield* vcs.transform((draft) => { - draft.add( + yield* vcs.transform((editor) => { + editor.add( provider({ base: () => Effect.succeed({ name: "release", ref: "refs/heads/release", source: "reflog" }), diff: () => Effect.fail(new Error("failed")), }), ) - draft.default.set("custom") + editor.default.set("custom") }) expect((yield* vcs.base())?.source).toBe("reflog") expect(yield* vcs.diff("committed").pipe(Effect.flip)).toMatchObject({ _tag: "Vcs.DiffError" }) diff --git a/packages/core/test/websearch.test.ts b/packages/core/test/websearch.test.ts index ac06079f095..f05a5df543a 100644 --- a/packages/core/test/websearch.test.ts +++ b/packages/core/test/websearch.test.ts @@ -12,8 +12,8 @@ const register = (id: string) => const websearch = yield* WebSearch.Service const providerID = WebSearch.ID.make(id) const calls: WebSearch.ProviderInput[] = [] - yield* websearch.transform((draft) => { - draft.add({ + yield* websearch.transform((editor) => { + editor.add({ id: providerID, name: id.toUpperCase(), execute: (input) => @@ -91,7 +91,7 @@ describe("WebSearch", () => { yield* register("exa") const parallel = yield* register("parallel") const websearch = yield* WebSearch.Service - yield* websearch.transform((draft) => draft.default.set(parallel.providerID)) + yield* websearch.transform((editor) => editor.default.set(parallel.providerID)) expect((yield* websearch.query({ query: "configured" })).providerID).toBe(parallel.providerID) }), @@ -103,7 +103,7 @@ describe("WebSearch", () => { const parallel = yield* register("parallel") const websearch = yield* WebSearch.Service const source = { providerID: exa.providerID } - yield* websearch.transform((draft) => draft.default.set(source.providerID)) + yield* websearch.transform((editor) => editor.default.set(source.providerID)) expect((yield* websearch.default())?.id).toBe(exa.providerID) source.providerID = parallel.providerID @@ -131,7 +131,7 @@ describe("WebSearch", () => { const parallel = yield* register("parallel") const websearch = yield* WebSearch.Service yield* websearch.select(parallel.providerID) - yield* websearch.transform((draft) => draft.default.set(exa.providerID)) + yield* websearch.transform((editor) => editor.default.set(exa.providerID)) expect((yield* websearch.query({ query: "configured" })).providerID).toBe(exa.providerID) }), @@ -142,7 +142,7 @@ describe("WebSearch", () => { yield* register("exa") yield* register("parallel") const websearch = yield* WebSearch.Service - yield* websearch.transform((draft) => draft.default.set("random")) + yield* websearch.transform((editor) => editor.default.set("random")) expect(["exa", "parallel"]).toContain((yield* websearch.query({ query: "random" })).providerID) }), @@ -152,7 +152,7 @@ describe("WebSearch", () => { Effect.gen(function* () { yield* register("exa") const websearch = yield* WebSearch.Service - yield* websearch.transform((draft) => draft.default.set(false)) + yield* websearch.transform((editor) => editor.default.set(false)) expect((yield* websearch.query({ query: "disabled" }).pipe(Effect.flip))._tag).toBe("WebSearch.Disabled") }), @@ -162,7 +162,7 @@ describe("WebSearch", () => { Effect.gen(function* () { yield* register("exa") const websearch = yield* WebSearch.Service - yield* websearch.transform((draft) => draft.default.set(WebSearch.ID.make("missing"))) + yield* websearch.transform((editor) => editor.default.set(WebSearch.ID.make("missing"))) expect((yield* websearch.query({ query: "fallback" }).pipe(Effect.flip))._tag).toBe("WebSearch.ProviderRequired") }), diff --git a/packages/plugin/src/README.md b/packages/plugin/src/README.md index c4632788c13..df75ce7866c 100644 --- a/packages/plugin/src/README.md +++ b/packages/plugin/src/README.md @@ -7,7 +7,7 @@ The Promise plugin API at `@opencode-ai/plugin` is the async/await equivalent of The Promise API uses Promises instead of Effects for setup, runtime hook callbacks, hook registration, `reload`, and `Registration.dispose`. Transform -draft callbacks remain synchronous. +editor callbacks remain synchronous. ## Defining A Plugin @@ -48,7 +48,7 @@ await registration.dispose() ## Transform Hooks -Transform hooks contribute to stateful domains. The draft editor is synchronous, +Transform hooks contribute to stateful domains. The editor is synchronous, so load asynchronous data before registering a transform or reloading its domain: ```ts diff --git a/packages/plugin/src/effect/PLAN.md b/packages/plugin/src/effect/PLAN.md index 286ba2e9861..bd9dfc2104d 100644 --- a/packages/plugin/src/effect/PLAN.md +++ b/packages/plugin/src/effect/PLAN.md @@ -168,7 +168,7 @@ Hook context objects may contain: - Purpose-built methods for allowed mutations - Capability methods where the operation requires more than field assignment -They must not expose core drafts or unrestricted internal objects. +They must not expose core editors or unrestricted internal objects. ## Domain Transforms Versus Runtime Hooks diff --git a/packages/plugin/src/effect/README.md b/packages/plugin/src/effect/README.md index 82fca57c85c..33785038a92 100644 --- a/packages/plugin/src/effect/README.md +++ b/packages/plugin/src/effect/README.md @@ -31,7 +31,7 @@ Registrations are owned by the plugin scope. Closing the scope removes them auto ## Transform Hooks -Transform hooks contribute to stateful domains. Their draft callbacks are +Transform hooks contribute to stateful domains. Their editor callbacks are synchronous, so load effectful data before registering a transform or reloading its domain: diff --git a/packages/plugin/src/effect/agent.ts b/packages/plugin/src/effect/agent.ts index 81bd1d03141..6233434691b 100644 --- a/packages/plugin/src/effect/agent.ts +++ b/packages/plugin/src/effect/agent.ts @@ -3,7 +3,7 @@ import type { Agent } from "@opencode-ai/schema/agent" import type { Effect, Types } from "effect" import type { Transform } from "./registration.js" -export interface AgentDraft { +export interface AgentEditor { list(): readonly Types.DeepMutable[] get(id: string): Types.DeepMutable | undefined default(id: string | undefined): void @@ -12,6 +12,6 @@ export interface AgentDraft { } export interface AgentDomain extends AgentApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } diff --git a/packages/plugin/src/effect/catalog.ts b/packages/plugin/src/effect/catalog.ts index 42395e0eb6d..27e5e8163de 100644 --- a/packages/plugin/src/effect/catalog.ts +++ b/packages/plugin/src/effect/catalog.ts @@ -9,7 +9,7 @@ export interface CatalogProviderRecord { readonly models: ReadonlyMap> } -export interface CatalogDraft { +export interface CatalogEditor { readonly provider: { list(): readonly CatalogProviderRecord[] get(providerID: string): CatalogProviderRecord | undefined @@ -28,6 +28,6 @@ export interface CatalogDraft { } export interface CatalogDomain extends CatalogApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } diff --git a/packages/plugin/src/effect/command.ts b/packages/plugin/src/effect/command.ts index ebfa3df109a..22bb3a86926 100644 --- a/packages/plugin/src/effect/command.ts +++ b/packages/plugin/src/effect/command.ts @@ -17,11 +17,11 @@ export interface CommandDefinition { readonly execute: (input: CommandInvocation) => Effect.Effect } -export interface CommandDraft { +export interface CommandEditor { add(definition: CommandDefinition): void } export interface CommandDomain extends Pick, "list"> { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } diff --git a/packages/plugin/src/effect/integration.ts b/packages/plugin/src/effect/integration.ts index c56dfd8467b..5c42d4068a6 100644 --- a/packages/plugin/src/effect/integration.ts +++ b/packages/plugin/src/effect/integration.ts @@ -74,7 +74,7 @@ export type IntegrationMethodRegistration = readonly method: IntegrationEnvMethod } -export interface IntegrationDraft { +export interface IntegrationEditor { list(): readonly IntegrationRef[] get(id: string): IntegrationRef | undefined update(id: string, update: (integration: IntegrationRef) => void): void @@ -87,7 +87,7 @@ export interface IntegrationDraft { } export interface IntegrationDomain extends Omit, "wellknown"> { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect readonly connection: { readonly active: (integrationID: string) => Effect.Effect diff --git a/packages/plugin/src/effect/mcp.ts b/packages/plugin/src/effect/mcp.ts index 794f5839054..e614991139d 100644 --- a/packages/plugin/src/effect/mcp.ts +++ b/packages/plugin/src/effect/mcp.ts @@ -3,7 +3,7 @@ import type { Mcp } from "@opencode-ai/schema/mcp" import type { Effect, Types } from "effect" import type { Transform } from "./registration.js" -export interface MCPDraft { +export interface MCPEditor { list(): readonly [string, Types.DeepMutable][] get(name: string): Types.DeepMutable | undefined set(name: string, config: Mcp.ServerConfig): void @@ -12,6 +12,6 @@ export interface MCPDraft { } export interface MCPDomain extends Pick, "list"> { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } diff --git a/packages/plugin/src/effect/reference.ts b/packages/plugin/src/effect/reference.ts index f9f92a9cf96..970e55d8230 100644 --- a/packages/plugin/src/effect/reference.ts +++ b/packages/plugin/src/effect/reference.ts @@ -3,13 +3,13 @@ import type { ReferenceApi } from "@opencode-ai/client/effect/api" import type { Effect } from "effect" import type { Transform } from "./registration.js" -export interface ReferenceDraft { +export interface ReferenceEditor { add(name: string, source: ReferenceLocalSource | ReferenceGitSource): void remove(name: string): void list(): readonly (readonly [string, ReferenceLocalSource | ReferenceGitSource])[] } export interface ReferenceDomain extends ReferenceApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } diff --git a/packages/plugin/src/effect/skill.ts b/packages/plugin/src/effect/skill.ts index cc272e015c3..e4b65416ef1 100644 --- a/packages/plugin/src/effect/skill.ts +++ b/packages/plugin/src/effect/skill.ts @@ -3,7 +3,7 @@ import { Skill } from "@opencode-ai/schema/skill" import type { Effect, Types } from "effect" import type { Transform } from "./registration.js" -export interface SkillDraft { +export interface SkillEditor { list(): readonly Types.DeepMutable[] add(skill: Skill.Info): void update(id: string, update: (skill: Types.DeepMutable) => void): void @@ -11,6 +11,6 @@ export interface SkillDraft { } export interface SkillDomain extends SkillApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } diff --git a/packages/plugin/src/effect/tool.ts b/packages/plugin/src/effect/tool.ts index d448fed7389..56b8e30015a 100644 --- a/packages/plugin/src/effect/tool.ts +++ b/packages/plugin/src/effect/tool.ts @@ -5,7 +5,7 @@ import type { SessionMessage } from "@opencode-ai/schema/session-message" import type { Effect, Types } from "effect" import type { Hooks, Transform } from "./registration.js" -export interface ToolDraft { +export interface ToolEditor { list(): readonly (Tool.Info & { readonly id: string })[] get(id: string): (Tool.Info & { readonly id: string }) | undefined namespace(namespace: Tool.Namespace): void @@ -52,7 +52,7 @@ export interface ToolFailures extends Record { } export interface ToolDomain { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect readonly hook: Hooks } diff --git a/packages/plugin/src/effect/vcs.ts b/packages/plugin/src/effect/vcs.ts index a38380ce598..20386cce8f6 100644 --- a/packages/plugin/src/effect/vcs.ts +++ b/packages/plugin/src/effect/vcs.ts @@ -34,11 +34,11 @@ export interface VcsDefinition { } export interface VcsDomain extends VcsApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } -export interface VcsDraft { +export interface VcsEditor { add(definition: VcsDefinition): void readonly default: { get(): string | undefined diff --git a/packages/plugin/src/effect/websearch.ts b/packages/plugin/src/effect/websearch.ts index 0c5748d56d8..9e3acbc8778 100644 --- a/packages/plugin/src/effect/websearch.ts +++ b/packages/plugin/src/effect/websearch.ts @@ -10,11 +10,11 @@ export interface WebSearchDefinition { } export interface WebSearchDomain extends WebSearchApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } -export interface WebSearchDraft { +export interface WebSearchEditor { add(definition: WebSearchDefinition): void readonly default: { get(): string | false | undefined diff --git a/packages/plugin/src/promise/adapter.ts b/packages/plugin/src/promise/adapter.ts index 96a6016ae66..ee0f69c0144 100644 --- a/packages/plugin/src/promise/adapter.ts +++ b/packages/plugin/src/promise/adapter.ts @@ -270,13 +270,13 @@ export function fromPromise(plugin: Plugin) { } const transform = - (domain: { - transform: (callback: (draft: Draft) => void) => Effect.Effect + (domain: { + transform: (callback: (editor: Editor) => void) => Effect.Effect }) => - (callback: (draft: Draft) => void) => + (callback: (editor: Editor) => void) => register( - domain.transform((draft) => { - callback(draft) + domain.transform((editor) => { + callback(editor) }), ) @@ -312,10 +312,10 @@ export function fromPromise(plugin: Plugin) { list: adaptApiMethod(CommandEndpoints["command.list"], host.command.list), transform: (callback) => register( - host.command.transform((draft) => + host.command.transform((editor) => callback({ add: (definition) => - draft.add({ + editor.add({ ...definition, execute: (input) => Effect.tryPromise({ try: () => definition.execute(input), catch: (cause) => cause }), @@ -377,18 +377,18 @@ export function fromPromise(plugin: Plugin) { }, transform: (callback) => register( - host.integration.transform((draft) => + host.integration.transform((editor) => callback({ - list: draft.list, - get: draft.get, - update: draft.update, - remove: draft.remove, + list: editor.list, + get: editor.get, + update: editor.update, + remove: editor.remove, method: { - list: draft.method.list, + list: editor.method.list, update: (input) => { - if (!("authorize" in input)) return draft.method.update(input) + if (!("authorize" in input)) return editor.method.update(input) const refresh = input.refresh - draft.method.update({ + editor.method.update({ ...input, authorize: (answer) => Effect.promise(() => input.authorize(answer)).pipe( @@ -410,7 +410,7 @@ export function fromPromise(plugin: Plugin) { : (credential) => Effect.promise(() => refresh(credential)), }) }, - remove: draft.method.remove, + remove: editor.method.remove, }, }), ), @@ -457,21 +457,21 @@ export function fromPromise(plugin: Plugin) { reload: () => run(host.tool.reload()), transform: (callback) => register( - host.tool.transform((draft) => + host.tool.transform((editor) => callback({ - list: () => draft.list().map((tool) => ({ ...tool, execute: promiseExecutor(tool.execute) })), + list: () => editor.list().map((tool) => ({ ...tool, execute: promiseExecutor(tool.execute) })), get: (id) => { - const tool = draft.get(id) + const tool = editor.get(id) return tool ? { ...tool, execute: promiseExecutor(tool.execute) } : undefined }, - namespace: draft.namespace, + namespace: editor.namespace, add: (tool: Info) => - draft.add({ + editor.add({ ...tool, execute: (input, context) => executePromiseTool(tool, input, context), }), update: (id, update) => - draft.update(id, (tool) => { + editor.update(id, (tool) => { const value: Info = { ...tool, execute: promiseExecutor(tool.execute), @@ -484,7 +484,7 @@ export function fromPromise(plugin: Plugin) { executePromiseTool(value, input, context), }) }), - remove: draft.remove, + remove: editor.remove, }), ), ), @@ -500,11 +500,11 @@ export function fromPromise(plugin: Plugin) { reload: () => run(host.vcs.reload()), transform: (callback) => register( - host.vcs.transform((draft) => { + host.vcs.transform((editor) => { callback({ add: (definition) => { const base = definition.base?.bind(definition) - draft.add({ + editor.add({ id: definition.id, name: definition.name, info: (input) => attempt((signal) => definition.info(input, { signal })), @@ -514,7 +514,7 @@ export function fromPromise(plugin: Plugin) { diff: (input) => attempt((signal) => definition.diff(input, { signal })), }) }, - default: draft.default, + default: editor.default, }) }), ), @@ -525,15 +525,15 @@ export function fromPromise(plugin: Plugin) { reload: () => run(host.websearch.reload()), transform: (callback) => register( - host.websearch.transform((draft) => { + host.websearch.transform((editor) => { callback({ add: (definition) => - draft.add({ + editor.add({ id: definition.id, name: definition.name, execute: (input) => attempt((signal) => definition.execute(input, { signal })), }), - default: draft.default, + default: editor.default, }) }), ), diff --git a/packages/plugin/src/promise/agent.ts b/packages/plugin/src/promise/agent.ts index 178d2bac339..228a6c7aa68 100644 --- a/packages/plugin/src/promise/agent.ts +++ b/packages/plugin/src/promise/agent.ts @@ -3,7 +3,7 @@ import type { Agent } from "@opencode-ai/schema/agent" import type { Transform } from "./registration.js" import type { DeepMutable } from "./types.js" -export interface AgentDraft { +export interface AgentEditor { list(): readonly DeepMutable[] get(id: string): DeepMutable | undefined default(id: string | undefined): void @@ -12,6 +12,6 @@ export interface AgentDraft { } export interface AgentDomain extends AgentApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Promise } diff --git a/packages/plugin/src/promise/catalog.ts b/packages/plugin/src/promise/catalog.ts index 49f9b0b48d7..018ad992459 100644 --- a/packages/plugin/src/promise/catalog.ts +++ b/packages/plugin/src/promise/catalog.ts @@ -9,7 +9,7 @@ export interface CatalogProviderRecord { readonly models: ReadonlyMap> } -export interface CatalogDraft { +export interface CatalogEditor { readonly provider: { list(): readonly CatalogProviderRecord[] get(providerID: string): CatalogProviderRecord | undefined @@ -28,6 +28,6 @@ export interface CatalogDraft { } export interface CatalogDomain extends CatalogApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Promise } diff --git a/packages/plugin/src/promise/command.ts b/packages/plugin/src/promise/command.ts index bff26c1c5ff..c51b0549d81 100644 --- a/packages/plugin/src/promise/command.ts +++ b/packages/plugin/src/promise/command.ts @@ -16,11 +16,11 @@ export interface CommandDefinition { readonly execute: (input: CommandInvocation) => Promise } -export interface CommandDraft { +export interface CommandEditor { add(definition: CommandDefinition): void } export interface CommandDomain extends Pick { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Promise } diff --git a/packages/plugin/src/promise/integration.ts b/packages/plugin/src/promise/integration.ts index 733b9fe2c80..84f59501188 100644 --- a/packages/plugin/src/promise/integration.ts +++ b/packages/plugin/src/promise/integration.ts @@ -69,7 +69,7 @@ export type IntegrationMethodRegistration = } | { readonly integrationID: string; readonly method: IntegrationEnvMethod } -export interface IntegrationDraft { +export interface IntegrationEditor { list(): readonly IntegrationRef[] get(id: string): IntegrationRef | undefined update(id: string, update: (integration: IntegrationRef) => void): void @@ -82,7 +82,7 @@ export interface IntegrationDraft { } export interface IntegrationDomain extends Omit { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Promise readonly connection: { readonly active: (integrationID: string) => Promise diff --git a/packages/plugin/src/promise/mcp.ts b/packages/plugin/src/promise/mcp.ts index 0dc275bfc75..e2a0f488ed9 100644 --- a/packages/plugin/src/promise/mcp.ts +++ b/packages/plugin/src/promise/mcp.ts @@ -3,7 +3,7 @@ import type { Mcp } from "@opencode-ai/schema/mcp" import type { Transform } from "./registration.js" import type { DeepMutable } from "./types.js" -export interface MCPDraft { +export interface MCPEditor { list(): readonly [string, DeepMutable][] get(name: string): DeepMutable | undefined set(name: string, config: Mcp.ServerConfig): void @@ -12,6 +12,6 @@ export interface MCPDraft { } export interface MCPDomain extends Pick { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Promise } diff --git a/packages/plugin/src/promise/reference.ts b/packages/plugin/src/promise/reference.ts index 7f5c43bf67c..f61f2bb3219 100644 --- a/packages/plugin/src/promise/reference.ts +++ b/packages/plugin/src/promise/reference.ts @@ -2,13 +2,13 @@ import type { ReferenceApi } from "@opencode-ai/client/promise/api" import type { ReferenceGitSource, ReferenceLocalSource } from "@opencode-ai/client" import type { Transform } from "./registration.js" -export interface ReferenceDraft { +export interface ReferenceEditor { add(name: string, source: ReferenceLocalSource | ReferenceGitSource): void remove(name: string): void list(): readonly (readonly [string, ReferenceLocalSource | ReferenceGitSource])[] } export interface ReferenceDomain extends ReferenceApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Promise } diff --git a/packages/plugin/src/promise/skill.ts b/packages/plugin/src/promise/skill.ts index 4cfc8833a0c..2010e6cde55 100644 --- a/packages/plugin/src/promise/skill.ts +++ b/packages/plugin/src/promise/skill.ts @@ -3,7 +3,7 @@ import type { Skill } from "@opencode-ai/schema/skill" import type { Transform } from "./registration.js" import type { DeepMutable } from "./types.js" -export interface SkillDraft { +export interface SkillEditor { list(): readonly DeepMutable[] add(skill: Skill.Info): void update(id: string, update: (skill: DeepMutable) => void): void @@ -11,6 +11,6 @@ export interface SkillDraft { } export interface SkillDomain extends SkillApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Promise } diff --git a/packages/plugin/src/promise/tool.ts b/packages/plugin/src/promise/tool.ts index 12484a6708c..3578bf5e4bc 100644 --- a/packages/plugin/src/promise/tool.ts +++ b/packages/plugin/src/promise/tool.ts @@ -22,7 +22,7 @@ export type Info< ) => Promise> } -interface ToolDraft { +interface ToolEditor { list(): readonly (Info & { readonly id: string })[] get(id: string): (Info & { readonly id: string }) | undefined namespace(namespace: Tool.Namespace): void @@ -63,7 +63,7 @@ interface ToolHooks { } export interface ToolDomain { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Promise readonly hook: Hooks } diff --git a/packages/plugin/src/promise/vcs.ts b/packages/plugin/src/promise/vcs.ts index 42d9c943fe5..9d32a826ce4 100644 --- a/packages/plugin/src/promise/vcs.ts +++ b/packages/plugin/src/promise/vcs.ts @@ -33,11 +33,11 @@ export interface VcsDefinition { } export interface VcsDomain extends VcsApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Promise } -export interface VcsDraft { +export interface VcsEditor { add(definition: VcsDefinition): void readonly default: { get(): string | undefined diff --git a/packages/plugin/src/promise/websearch.ts b/packages/plugin/src/promise/websearch.ts index 1075249b205..7916414e8fd 100644 --- a/packages/plugin/src/promise/websearch.ts +++ b/packages/plugin/src/promise/websearch.ts @@ -12,11 +12,11 @@ export interface WebSearchDefinition { } export interface WebSearchDomain extends WebSearchApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Promise } -export interface WebSearchDraft { +export interface WebSearchEditor { add(definition: WebSearchDefinition): void readonly default: { get(): string | false | undefined diff --git a/packages/sdk/test/embedded.test.ts b/packages/sdk/test/embedded.test.ts index d55ed7dccb5..f3c0c854717 100644 --- a/packages/sdk/test/embedded.test.ts +++ b/packages/sdk/test/embedded.test.ts @@ -210,8 +210,8 @@ it.live("embedded client exposes plugin-backed web search", () => yield* opencode.plugin({ id: `embedded-websearch-${crypto.randomUUID()}`, effect: (ctx) => - ctx.websearch.transform((draft) => { - draft.add({ + ctx.websearch.transform((editor) => { + editor.add({ id: providerID, name: "Embedded web search", execute: (input) => diff --git a/packages/sdk/test/instances-effect.test.ts b/packages/sdk/test/instances-effect.test.ts index 9021d191cb5..9dc2628a048 100644 --- a/packages/sdk/test/instances-effect.test.ts +++ b/packages/sdk/test/instances-effect.test.ts @@ -62,8 +62,8 @@ it.live( effect: (ctx) => Effect.gen(function* () { expect(ctx.app).toMatchObject({ name: "instance-test", version: "1.2.3" }) - yield* ctx.agent.transform((draft) => - draft.update(Agent.ID.make("build"), (agent) => { + yield* ctx.agent.transform((editor) => + editor.update(Agent.ID.make("build"), (agent) => { agent.permissions = [{ action: "*", resource: "*", effect: "allow" }] }), ) @@ -72,8 +72,8 @@ it.live( event.generation.temperature = 0.25 }), ) - yield* ctx.tool.transform((draft) => - draft.add({ + yield* ctx.tool.transform((editor) => + editor.add({ name: "thread_echo", description: "Report the configured thread", input: Schema.Struct({}), diff --git a/packages/server/test/session-instances.test.ts b/packages/server/test/session-instances.test.ts index 9974c45a381..a3ed0d0cc72 100644 --- a/packages/server/test/session-instances.test.ts +++ b/packages/server/test/session-instances.test.ts @@ -82,8 +82,8 @@ it.live( id: "session-instance", effect: Effect.fnUntraced(function* (ctx) { boots.push(config.id) - yield* ctx.agent.transform((draft) => - draft.update("build", (agent) => { + yield* ctx.agent.transform((editor) => + editor.update("build", (agent) => { agent.permissions = [{ action: "*", resource: "*", effect: "allow" }] }), ) @@ -103,8 +103,8 @@ it.live( if (event.action === "instance-test") event.message = config.tool }), ) - yield* ctx.tool.transform((draft) => - draft.add({ + yield* ctx.tool.transform((editor) => + editor.add({ name: config.tool, description: `Tool for ${config.id}`, input: Schema.Struct({}), @@ -117,8 +117,8 @@ it.live( }), }), ) - yield* ctx.command.transform((draft) => - draft.add({ + yield* ctx.command.transform((editor) => + editor.add({ name: "instance-check", execute: (input) => ctx.session diff --git a/packages/server/test/vcs.test.ts b/packages/server/test/vcs.test.ts index 0fc8fad70d8..8a52d94bb83 100644 --- a/packages/server/test/vcs.test.ts +++ b/packages/server/test/vcs.test.ts @@ -95,8 +95,8 @@ it.live("maps a failing base provider to HTTP 503 instead of null metadata", () revision: "test", effect: (ctx) => ctx.vcs - .transform((draft) => { - draft.add({ + .transform((editor) => { + editor.add({ id: "failing", name: "Failing VCS", info: () => Effect.succeed({ branch: {} }), @@ -105,7 +105,7 @@ it.live("maps a failing base provider to HTTP 503 instead of null metadata", () diff: () => Effect.succeed([]), base: () => Effect.fail(new Error("provider failure")), }) - draft.default.set("failing") + editor.default.set("failing") }) .pipe(Effect.asVoid), }, diff --git a/packages/www/src/docs/content/build/plugins/effect.mdx b/packages/www/src/docs/content/build/plugins/effect.mdx index 1aadc93a463..538366ff624 100644 --- a/packages/www/src/docs/content/build/plugins/effect.mdx +++ b/packages/www/src/docs/content/build/plugins/effect.mdx @@ -169,7 +169,7 @@ export default Plugin.define({ ## Transforms -Transforms synchronously edit a mutable draft. OpenCode applies transforms in plugin order, so later transforms see +Transforms synchronously edit state through an editor. OpenCode applies transforms in plugin order, so later transforms see earlier changes. Yielding the registration keeps it in the plugin scope. ```ts title="plugins/models-effect/index.ts" @@ -181,8 +181,8 @@ export default Plugin.define({ effect: (ctx) => Effect.gen(function* () { const catalog = ctx.catalog - yield* catalog.transform((draft) => { - draft.model.update("acme", "reasoner", (model) => { + yield* catalog.transform((editor) => { + editor.model.update("acme", "reasoner", (model) => { model.name = "Acme Reasoner" model.cost = [{ input: 2, output: 12, cache: { read: 0.2, write: 2 } }] }) @@ -197,11 +197,11 @@ A later transform can enforce policy across the composed catalog. effect: (ctx) => Effect.gen(function* () { const catalog = ctx.catalog - yield* catalog.transform((draft) => { - for (const provider of draft.provider.list()) { + yield* catalog.transform((editor) => { + for (const provider of editor.provider.list()) { for (const model of provider.models.values()) { if (model.cost.some((tier) => tier.output > 20)) { - draft.model.remove(model.providerID, model.id) + editor.model.remove(model.providerID, model.id) } } } @@ -217,9 +217,9 @@ effect: (ctx) => const catalog = ctx.catalog const state = { models: yield* loadFromSource() } - yield* catalog.transform((draft) => { + yield* catalog.transform((editor) => { for (const item of state.models) { - draft.model.update(item.providerID, item.id, (model) => { + editor.model.update(item.providerID, item.id, (model) => { model.name = item.name }) } @@ -257,10 +257,10 @@ Transform or reload agents. effect: (ctx) => Effect.gen(function* () { const agent = ctx.agent - yield* agent.transform((draft) => { - draft.default("build") - draft.update("build", (item) => (item.description = "Builds features and fixes bugs")) - draft.remove("legacy") + yield* agent.transform((editor) => { + editor.default("build") + editor.update("build", (item) => (item.description = "Builds features and fixes bugs")) + editor.remove("legacy") }) yield* agent.reload() }), @@ -269,7 +269,7 @@ effect: (ctx) => Schema: [`Agent.Info`](/api#schema-Agent.Info). ```ts -interface AgentDraft { +interface AgentEditor { list(): readonly Types.DeepMutable[] get(id: string): Types.DeepMutable | undefined default(id: string | undefined): void @@ -278,7 +278,7 @@ interface AgentDraft { } interface AgentDomain extends AgentApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } ``` @@ -310,12 +310,12 @@ Transform providers and models, then reload after source data changes. effect: (ctx) => Effect.gen(function* () { const catalog = ctx.catalog - yield* catalog.transform((draft) => { - draft.provider.update("anthropic", (provider) => (provider.name = "Anthropic")) - draft.model.update("anthropic", "claude-sonnet-4-5", (model) => (model.name = "Claude Sonnet 4.5")) - draft.model.default.set("anthropic", "claude-sonnet-4-5") - draft.model.remove("anthropic", "legacy-model") - draft.provider.remove("legacy-provider") + yield* catalog.transform((editor) => { + editor.provider.update("anthropic", (provider) => (provider.name = "Anthropic")) + editor.model.update("anthropic", "claude-sonnet-4-5", (model) => (model.name = "Claude Sonnet 4.5")) + editor.model.default.set("anthropic", "claude-sonnet-4-5") + editor.model.remove("anthropic", "legacy-model") + editor.provider.remove("legacy-provider") }) yield* catalog.reload() }), @@ -329,7 +329,7 @@ interface CatalogProviderRecord { readonly models: ReadonlyMap> } -interface CatalogDraft { +interface CatalogEditor { readonly provider: { list(): readonly CatalogProviderRecord[] get(providerID: string): CatalogProviderRecord | undefined @@ -348,7 +348,7 @@ interface CatalogDraft { } interface CatalogDomain extends CatalogApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } ``` @@ -373,8 +373,8 @@ effect: (ctx) => Effect.gen(function* () { const command = ctx.command const session = ctx.session - yield* command.transform((draft) => { - draft.add({ + yield* command.transform((editor) => { + editor.add({ name: "security-review", description: "Review changes for security issues", execute: (input) => @@ -406,12 +406,12 @@ interface CommandDefinition { readonly execute: (input: CommandInvocation) => Effect.Effect } -interface CommandDraft { +interface CommandEditor { add(definition: CommandDefinition): void } interface CommandDomain extends Pick, "list"> { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } ``` @@ -470,9 +470,9 @@ resources. effect: (ctx) => Effect.gen(function* () { const integration = ctx.integration - yield* integration.transform((draft) => { - draft.update("acme", (item) => (item.name = "Acme")) - draft.method.update({ + yield* integration.transform((editor) => { + editor.update("acme", (item) => (item.name = "Acme")) + editor.method.update({ integrationID: "acme", method: { id: "device", type: "oauth", label: "Sign in with Acme" }, authorize: () => @@ -509,7 +509,7 @@ type IntegrationOAuthMethodRegistration = { readonly label?: (credential: Credential.OAuth) => string | undefined } -interface IntegrationDraft { +interface IntegrationEditor { list(): readonly IntegrationRef[] get(id: string): IntegrationRef | undefined update(id: string, update: (integration: IntegrationRef) => void): void @@ -522,7 +522,7 @@ interface IntegrationDraft { } interface IntegrationDomain extends Omit, "wellknown"> { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect readonly connection: { readonly active: (integrationID: string) => Effect.Effect @@ -544,19 +544,19 @@ effect: (ctx) => }), ``` -Plugins manage MCP servers only through transforms. Use `draft.set` to add or replace a server, `draft.update` to -change its configuration, and `draft.remove` to remove it. Inspect configuration with `draft.list` and `draft.get`. +Plugins manage MCP servers only through transforms. Use `editor.set` to add or replace a server, `editor.update` to +change its configuration, and `editor.remove` to remove it. Inspect configuration with `editor.list` and `editor.get`. ```ts effect: (ctx) => Effect.gen(function* () { const mcp = ctx.mcp - yield* mcp.transform((draft) => { - const servers = draft.list() - const docs = draft.get("docs") - draft.set("docs", { type: "remote", url: "https://mcp.example.com" }) - draft.update("docs", (server) => (server.disabled = false)) - draft.remove("legacy") + yield* mcp.transform((editor) => { + const servers = editor.list() + const docs = editor.get("docs") + editor.set("docs", { type: "remote", url: "https://mcp.example.com" }) + editor.update("docs", (server) => (server.disabled = false)) + editor.remove("legacy") }) }), ``` @@ -578,7 +578,7 @@ Schemas: [`Mcp.Server`](/api#schema-Mcp.Server), [`Mcp.LocalConfigEncoded`](/api [`Mcp.RemoteConfigEncoded`](/api#schema-Mcp.RemoteConfigEncoded). ```ts -interface MCPDraft { +interface MCPEditor { list(): readonly [string, Types.DeepMutable][] get(name: string): Types.DeepMutable | undefined set(name: string, config: Mcp.ServerConfig): void @@ -587,7 +587,7 @@ interface MCPDraft { } interface MCPDomain extends Pick, "list"> { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } ``` @@ -650,10 +650,10 @@ Add or remove local and Git references, then reload after external state changes effect: (ctx) => Effect.gen(function* () { const reference = ctx.reference - yield* reference.transform((draft) => { - draft.add("handbook", { type: "local", path: "/workspace/docs/handbook" }) - draft.add("standards", { type: "git", repository: "https://github.com/acme/standards", branch: "main" }) - draft.remove("legacy") + yield* reference.transform((editor) => { + editor.add("handbook", { type: "local", path: "/workspace/docs/handbook" }) + editor.add("standards", { type: "git", repository: "https://github.com/acme/standards", branch: "main" }) + editor.remove("legacy") }) yield* reference.reload() }), @@ -663,14 +663,14 @@ Schemas: [`Reference.Info`](/api#schema-Reference.Info), [`Reference.LocalSource [`Reference.GitSource`](/api#schema-Reference.GitSource). ```ts -interface ReferenceDraft { +interface ReferenceEditor { add(name: string, source: ReferenceLocalSource | ReferenceGitSource): void remove(name: string): void list(): readonly (readonly [string, ReferenceLocalSource | ReferenceGitSource])[] } interface ReferenceDomain extends ReferenceApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } ``` @@ -753,16 +753,16 @@ Transform and reload skills. Use the re-exported Effect schema constructors for effect: (ctx) => Effect.gen(function* () { const skill = ctx.skill - yield* skill.transform((draft) => { - draft.add(Skill.Info.make({ + yield* skill.transform((editor) => { + editor.add(Skill.Info.make({ id: Skill.ID.make("review"), name: Skill.Name.make("Review"), description: "Review the current changes", location: "/workspace/.opencode/skills/review.md", content: "Review the current changes for correctness and missing tests.", })) - draft.update("review", (item) => (item.autoinvoke = true)) - draft.remove("legacy") + editor.update("review", (item) => (item.autoinvoke = true)) + editor.remove("legacy") }) yield* skill.reload() }), @@ -771,7 +771,7 @@ effect: (ctx) => Schema: [`Skill.Info`](/api#schema-Skill.Info). ```ts -interface SkillDraft { +interface SkillEditor { list(): readonly Types.DeepMutable[] add(skill: Skill.Info): void update(id: string, update: (skill: Types.DeepMutable) => void): void @@ -779,7 +779,7 @@ interface SkillDraft { } interface SkillDomain extends SkillApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } ``` @@ -829,20 +829,20 @@ interface StorageDomain { Register typed tools with Effect `Schema`. The executor receives decoded input and returns an Effect containing typed output, display content, or metadata. -The draft supports `add`, `update`, and `remove`. The transform callback is synchronous: it must not return an Effect or Promise. Load +The editor supports `add`, `update`, and `remove`. The transform callback is synchronous: it must not return an Effect or Promise. Load external data before registering or reloading. OpenCode replays active transforms in registration order on a fresh -draft; for the same effective tool name, a later valid registration overrides an earlier one. +editor; for the same effective tool name, a later valid registration overrides an earlier one. ```ts effect: (ctx) => Effect.gen(function* () { const tool = ctx.tool - yield* tool.transform((draft) => { - draft.namespace({ + yield* tool.transform((editor) => { + editor.namespace({ name: "acme", description: "Customer account tools", }) - draft.add({ + editor.add({ name: "greeting", description: "Create a greeting", input: Schema.Struct({ name: Schema.String }), @@ -868,11 +868,11 @@ namespaces and unsupported characters in tool names become `_`. Missing names ar them rather than mutating nested values. Invalid updates are logged and leave the previous definition intact. ```ts -yield* ctx.tool.transform((draft) => { - draft.update("acme_greeting", (tool) => { +yield* ctx.tool.transform((editor) => { + editor.update("acme_greeting", (tool) => { tool.description = "Greet the user by name" }) - draft.remove("acme_obsolete") + editor.remove("acme_obsolete") }) ``` @@ -890,7 +890,7 @@ Schemas: [`Tool.Content`](/api#schema-Tool.Content), [`Tool.TextContent`](/api#s [`Tool.FileContent`](/api#schema-Tool.FileContent). ```ts -interface ToolDraft { +interface ToolEditor { list(): readonly (Tool.Info & { readonly id: string })[] get(id: string): (Tool.Info & { readonly id: string }) | undefined namespace(namespace: { name: string; description: string }): void @@ -902,7 +902,7 @@ interface ToolDraft { } interface ToolDomain { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } ``` @@ -924,14 +924,14 @@ effect: (ctx) => ``` Register a location-scoped provider through a scoped transform. Providers matching the detected repository type are -selected automatically; use `draft.default.set` to select a different provider. +selected automatically; use `editor.default.set` to select a different provider. ```ts effect: (ctx) => Effect.gen(function* () { const vcs = ctx.vcs - yield* vcs.transform((draft) => { - draft.add({ + yield* vcs.transform((editor) => { + editor.add({ id: "custom", name: "Custom VCS", info: () => Effect.succeed({ branch: { current: "feature", default: "main" } }), @@ -939,7 +939,7 @@ effect: (ctx) => status: (scope) => readStatus(scope.worktree), diff: (input) => readDiff(input), }) - draft.default.set("custom") + editor.default.set("custom") }) }), ``` @@ -952,7 +952,7 @@ Schemas: [`Vcs.Info`](/api#schema-Vcs.Info), [`Vcs.FileStatus`](/api#schema-Vcs. [`FileDiff.Info`](/api#schema-FileDiff.Info). ```ts -interface VcsDraft { +interface VcsEditor { add(definition: VcsDefinition): void readonly default: { get(): string | undefined @@ -961,7 +961,7 @@ interface VcsDraft { } interface VcsDomain extends VcsApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } ``` @@ -988,13 +988,13 @@ Register an Effect executor and select the default provider. Set the default to effect: (ctx) => Effect.gen(function* () { const websearch = ctx.websearch - yield* websearch.transform((draft) => { - draft.add({ + yield* websearch.transform((editor) => { + editor.add({ id: "internal", name: "Internal search", execute: ({ query }) => searchInternal(query), }) - draft.default.set("internal") + editor.default.set("internal") }) yield* websearch.reload() }), @@ -1010,7 +1010,7 @@ interface WebSearchDefinition { readonly execute: (input: WebSearch.ProviderInput) => Effect.Effect } -interface WebSearchDraft { +interface WebSearchEditor { add(definition: WebSearchDefinition): void readonly default: { get(): string | false | undefined @@ -1019,7 +1019,7 @@ interface WebSearchDraft { } interface WebSearchDomain extends WebsearchApi { - readonly transform: Transform + readonly transform: Transform readonly reload: () => Effect.Effect } ``` diff --git a/packages/www/src/docs/content/build/plugins/index.mdx b/packages/www/src/docs/content/build/plugins/index.mdx index 372bc11137f..3cfeff70cb0 100644 --- a/packages/www/src/docs/content/build/plugins/index.mdx +++ b/packages/www/src/docs/content/build/plugins/index.mdx @@ -215,15 +215,15 @@ const build = await ctx.agent.get({ agentID: "build" }) Register a transform to inspect, update, remove, or select the default agent. ```ts -await ctx.agent.transform((draft) => { - const agents = draft.list() - const build = draft.get("build") +await ctx.agent.transform((editor) => { + const agents = editor.list() + const build = editor.get("build") - draft.default("build") - draft.update("build", (agent) => { + editor.default("build") + editor.update("build", (agent) => { agent.description = "Builds features and fixes bugs" }) - draft.remove("legacy") + editor.remove("legacy") }) ``` @@ -241,11 +241,11 @@ Schema: [`Agent.Info`](/api#schema-Agent.Info) interface AgentContext { list(input?: AgentListInput, requestOptions?: RequestOptions): Promise get(input: AgentGetInput, requestOptions?: RequestOptions): Promise - transform(callback: (draft: AgentDraft) => void): Promise + transform(callback: (editor: AgentEditor) => void): Promise reload(): Promise } -interface AgentDraft { +interface AgentEditor { list(): readonly AgentInfo[] get(id: string): AgentInfo | undefined default(id: string | undefined): void @@ -309,11 +309,11 @@ interface CatalogContext { list(input?: ModelListInput, requestOptions?: RequestOptions): Promise default(input?: ModelDefaultInput, requestOptions?: RequestOptions): Promise } - transform(callback: (draft: CatalogDraft) => void): Promise + transform(callback: (editor: CatalogEditor) => void): Promise reload(): Promise } -interface CatalogDraft { +interface CatalogEditor { provider: { list(): readonly CatalogProviderRecord[] get(providerID: string): CatalogProviderRecord | undefined @@ -348,8 +348,8 @@ const commands = await ctx.command.list() Register commands with a transform. The executor receives the session, prompt attachments, and requested delivery mode. ```ts -await ctx.command.transform((draft) => { - draft.add({ +await ctx.command.transform((editor) => { + editor.add({ name: "security-review", description: "Review changes for security issues", execute: async ({ sessionID, prompt, delivery }) => { @@ -378,11 +378,11 @@ Schemas: [`Command.Info`](/api#schema-Command.Info), ```ts interface CommandContext { list(input?: CommandListInput, requestOptions?: RequestOptions): Promise - transform(callback: (draft: CommandDraft) => void): Promise + transform(callback: (editor: CommandEditor) => void): Promise reload(): Promise } -interface CommandDraft { +interface CommandEditor { add(definition: CommandDefinition): void } @@ -439,22 +439,22 @@ await ctx.integration.command.cancel({ integrationID: "acme", attemptID: attempt Register a transform to inspect integrations and manage their authentication methods. ```ts -await ctx.integration.transform((draft) => { - const integrations = draft.list() - const acme = draft.get("acme") +await ctx.integration.transform((editor) => { + const integrations = editor.list() + const acme = editor.get("acme") - draft.update("acme", (integration) => { + editor.update("acme", (integration) => { integration.name = "Acme" }) - draft.method.update({ + editor.method.update({ integrationID: "acme", method: { id: "cli", type: "command", label: "Sign in with Acme CLI", command: ["acme", "login"] }, }) - const methods = draft.method.list("acme") + const methods = editor.method.list("acme") const legacy = methods.find((method) => method.type === "command" && method.id === "legacy") - if (legacy) draft.method.remove("acme", legacy) - draft.remove("legacy") + if (legacy) editor.method.remove("acme", legacy) + editor.remove("legacy") }) ``` @@ -496,7 +496,7 @@ interface IntegrationContext { ): Promise cancel(input: IntegrationCommandCancelInput, requestOptions?: RequestOptions): Promise } - transform(callback: (draft: IntegrationDraft) => void): Promise + transform(callback: (editor: IntegrationEditor) => void): Promise reload(): Promise connection: { active(integrationID: string): Promise @@ -504,7 +504,7 @@ interface IntegrationContext { } } -interface IntegrationDraft { +interface IntegrationEditor { list(): readonly IntegrationRef[] get(id: string): IntegrationRef | undefined update(id: string, update: (integration: IntegrationRef) => void): void @@ -525,18 +525,18 @@ List MCP servers and their current connection state. const servers = await ctx.mcp.list() ``` -Plugins manage MCP servers only through transforms. Use `draft.set` to add or replace a server, `draft.update` to -change its configuration, and `draft.remove` to remove it. Inspect configuration with `draft.list` and `draft.get`. +Plugins manage MCP servers only through transforms. Use `editor.set` to add or replace a server, `editor.update` to +change its configuration, and `editor.remove` to remove it. Inspect configuration with `editor.list` and `editor.get`. ```ts -await ctx.mcp.transform((draft) => { - const servers = draft.list() - const docs = draft.get("docs") - draft.set("docs", { type: "remote", url: "https://mcp.example.com" }) - draft.update("docs", (server) => { +await ctx.mcp.transform((editor) => { + const servers = editor.list() + const docs = editor.get("docs") + editor.set("docs", { type: "remote", url: "https://mcp.example.com" }) + editor.update("docs", (server) => { server.disabled = false }) - draft.remove("legacy") + editor.remove("legacy") }) ``` @@ -557,11 +557,11 @@ Schemas: [`Mcp.Server`](/api#schema-Mcp.Server), [`Mcp.LocalConfigEncoded`](/api ```ts interface MCPContext { list(input?: McpListInput, requestOptions?: RequestOptions): Promise - transform(callback: (draft: MCPDraft) => void): Promise + transform(callback: (editor: MCPEditor) => void): Promise reload(): Promise } -interface MCPDraft { +interface MCPEditor { list(): readonly (readonly [string, McpServerConfig])[] get(name: string): McpServerConfig | undefined set(name: string, config: McpServerConfig): void @@ -599,11 +599,11 @@ const references = await ctx.reference.list() Register a transform to inspect, add, or remove local and Git references. ```ts -await ctx.reference.transform((draft) => { - const references = draft.list() - draft.add("handbook", { type: "local", path: "/workspace/docs/handbook" }) - draft.add("standards", { type: "git", repository: "https://github.com/acme/standards", branch: "main" }) - draft.remove("legacy") +await ctx.reference.transform((editor) => { + const references = editor.list() + editor.add("handbook", { type: "local", path: "/workspace/docs/handbook" }) + editor.add("standards", { type: "git", repository: "https://github.com/acme/standards", branch: "main" }) + editor.remove("legacy") }) ``` @@ -621,11 +621,11 @@ Schemas: [`Reference.Info`](/api#schema-Reference.Info), [`Reference.LocalSource ```ts interface ReferenceContext { list(input?: ReferenceListInput, requestOptions?: RequestOptions): Promise - transform(callback: (draft: ReferenceDraft) => void): Promise + transform(callback: (editor: ReferenceEditor) => void): Promise reload(): Promise } -interface ReferenceDraft { +interface ReferenceEditor { list(): readonly (readonly [string, ReferenceLocalSource | ReferenceGitSource])[] add(name: string, source: ReferenceLocalSource | ReferenceGitSource): void remove(name: string): void @@ -720,19 +720,19 @@ const skills = await ctx.skill.list() Register a transform to inspect, add, update, or remove skills. ```ts -await ctx.skill.transform((draft) => { - const skills = draft.list() - draft.add({ +await ctx.skill.transform((editor) => { + const skills = editor.list() + editor.add({ id: "review", name: "Review", description: "Review the current changes", location: "/workspace/.opencode/skills/review.md", content: "Review the current changes for correctness and missing tests.", }) - draft.update("review", (skill) => { + editor.update("review", (skill) => { skill.autoinvoke = true }) - draft.remove("legacy") + editor.remove("legacy") }) ``` @@ -749,11 +749,11 @@ Schema: [`Skill.Info`](/api#schema-Skill.Info) ```ts interface SkillContext { list(input?: SkillListInput, requestOptions?: RequestOptions): Promise - transform(callback: (draft: SkillDraft) => void): Promise + transform(callback: (editor: SkillEditor) => void): Promise reload(): Promise } -interface SkillDraft { +interface SkillEditor { list(): readonly SkillInfo[] add(skill: SkillInfo): void update(id: string, update: (skill: SkillInfo) => void): void @@ -806,12 +806,12 @@ Register, update, and remove tools with a synchronous transform, including in Pr before registering or reloading. A later valid registration overrides the same effective tool name. ```ts -const registration = await ctx.tool.transform((draft) => { - draft.namespace({ +const registration = await ctx.tool.transform((editor) => { + editor.namespace({ name: "acme", description: "Customer account tools", }) - draft.add({ + editor.add({ name: "greeting", description: "Create a greeting", input: { @@ -836,7 +836,7 @@ changing their order; it does not rerun plugin setup. await ctx.tool.reload() ``` -Use `list()` and `get()` to inspect tools currently in the draft. Tools have an `id` containing their effective name, +Use `list()` and `get()` to inspect tools currently in the editor. Tools have an `id` containing their effective name, and `get()` returns `undefined` when that ID is not present. Use `update` and `remove` with the effective tool name, including its namespace (`acme_greeting` above). Dots in namespaces and unsupported characters in tool names become `_`. Missing names are ignored; creating a tool requires @@ -844,11 +844,11 @@ namespaces and unsupported characters in tool names become `_`. Missing names ar them rather than mutating nested values. Invalid updates are logged and leave the previous definition intact. ```ts -await ctx.tool.transform((draft) => { - draft.update("acme_greeting", (tool) => { +await ctx.tool.transform((editor) => { + editor.update("acme_greeting", (tool) => { tool.description = "Greet the user by name" }) - draft.remove("acme_obsolete") + editor.remove("acme_obsolete") }) ``` @@ -874,11 +874,11 @@ Schemas: [`Tool.Content`](/api#schema-Tool.Content), [`Tool.TextContent`](/api#s ```ts interface ToolContext { - transform(callback: (draft: ToolDraft) => void): Promise + transform(callback: (editor: ToolEditor) => void): Promise reload(): Promise } -interface ToolDraft { +interface ToolEditor { list(): readonly (ToolInfo & { readonly id: string })[] get(id: string): (ToolInfo & { readonly id: string }) | undefined namespace(namespace: { name: string; description: string }): void @@ -900,11 +900,11 @@ const diff = await ctx.vcs.diff({ mode: "working", context: 3 }) ``` Register a location-scoped VCS provider with a transform. A provider matching the detected repository type is selected -automatically; use `draft.default.set` to select a different provider. +automatically; use `editor.default.set` to select a different provider. ```ts -await ctx.vcs.transform((draft) => { - draft.add({ +await ctx.vcs.transform((editor) => { + editor.add({ id: "custom", name: "Custom VCS", info: async () => ({ branch: { current: "feature", default: "main" } }), @@ -912,7 +912,7 @@ await ctx.vcs.transform((draft) => { status: async (scope, { signal }) => readStatus(scope.worktree, signal), diff: async (input, { signal }) => readDiff(input, signal), }) - draft.default.set("custom") + editor.default.set("custom") }) ``` @@ -931,11 +931,11 @@ interface VcsContext { branches(input?: VcsBranchesInput, requestOptions?: RequestOptions): Promise status(input?: VcsStatusInput, requestOptions?: RequestOptions): Promise diff(input: VcsDiffInput, requestOptions?: RequestOptions): Promise - transform(callback: (draft: VcsDraft) => void): Promise + transform(callback: (editor: VcsEditor) => void): Promise reload(): Promise } -interface VcsDraft { +interface VcsEditor { add(definition: VcsDefinition): void default: { get(): string | undefined @@ -956,8 +956,8 @@ const results = await ctx.websearch.query({ query: "OpenCode plugins", providerI Register a provider and select the default provider with a transform. ```ts -await ctx.websearch.transform((draft) => { - draft.add({ +await ctx.websearch.transform((editor) => { + editor.add({ id: "internal", name: "Internal search", execute: async ({ query }, { signal }) => { @@ -966,14 +966,14 @@ await ctx.websearch.transform((draft) => { return [{ url: result.url, title: result.title, content: result.content, time: {} }] }, }) - draft.default.set("internal") + editor.default.set("internal") }) ``` Disable websearch by selecting `false`, or reload providers after external state changes. ```ts -await ctx.websearch.transform((draft) => draft.default.set(false)) +await ctx.websearch.transform((editor) => editor.default.set(false)) await ctx.websearch.reload() ``` @@ -985,11 +985,11 @@ Schemas: [`WebSearch.Provider`](/api#schema-WebSearch.Provider), [`WebSearch.Res interface WebSearchContext { providers(input?: WebsearchProvidersInput, requestOptions?: RequestOptions): Promise query(input: WebsearchQueryInput, requestOptions?: RequestOptions): Promise - transform(callback: (draft: WebSearchDraft) => void): Promise + transform(callback: (editor: WebSearchEditor) => void): Promise reload(): Promise } -interface WebSearchDraft { +interface WebSearchEditor { add(provider: WebSearchDefinition): void default: { get(): string | false | undefined