diff --git a/packages/core/src/database/migration.ts b/packages/core/src/database/migration.ts index 3d0df87351..a6600f80c0 100644 --- a/packages/core/src/database/migration.ts +++ b/packages/core/src/database/migration.ts @@ -90,31 +90,20 @@ export function applyOnly(db: Database, input: Migration[]) { ) }), ) - if (migration.foreignKeys !== false) { - yield* apply.pipe( - Effect.tapError((error) => - Effect.logError("database migration failed", { - migration: migration.id, - durationMs: Date.now() - started, - error, - }), - ), - ) - yield* Effect.logInfo("database migration completed", { - migration: migration.id, - durationMs: Date.now() - started, - }) - continue - } - // Durable Object SQLite rejects the foreign_keys toggle; the closest - // allowlisted relaxation is deferring enforcement to transaction commit. - const relaxForeignKeys = supportsForeignKeyToggle - ? db.run(sql`PRAGMA foreign_keys = OFF`) - : db.run(sql`PRAGMA defer_foreign_keys = ON`) - const restoreForeignKeys = supportsForeignKeyToggle ? db.run(sql`PRAGMA foreign_keys = ON`) : Effect.void - yield* relaxForeignKeys - yield* apply.pipe( - Effect.ensuring(restoreForeignKeys.pipe(Effect.orDie)), + const run = + migration.foreignKeys !== false + ? apply + : Effect.gen(function* () { + // Durable Object SQLite rejects the foreign_keys toggle; the closest + // allowlisted relaxation is deferring enforcement to transaction commit. + const relaxForeignKeys = supportsForeignKeyToggle + ? db.run(sql`PRAGMA foreign_keys = OFF`) + : db.run(sql`PRAGMA defer_foreign_keys = ON`) + const restoreForeignKeys = supportsForeignKeyToggle ? db.run(sql`PRAGMA foreign_keys = ON`) : Effect.void + yield* relaxForeignKeys + yield* apply.pipe(Effect.ensuring(restoreForeignKeys.pipe(Effect.orDie))) + }) + yield* run.pipe( Effect.tapError((error) => Effect.logError("database migration failed", { migration: migration.id, diff --git a/packages/core/src/effect/app-node-builder.ts b/packages/core/src/effect/app-node-builder.ts index 8acb0ea127..b41e1f56d2 100644 --- a/packages/core/src/effect/app-node-builder.ts +++ b/packages/core/src/effect/app-node-builder.ts @@ -4,16 +4,13 @@ import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { makeGlobalNode } from "@opencode-ai/util/effect/app-node" export function build(root: LayerNode.Node, replacements: LayerNode.Replacements = []) { - let allReplacements = replacements - // Only build the location service map if it's actually needed - if (LayerNode.hasUnbound(root, LocationServiceMap.node) && !hasReplacement(replacements, LocationServiceMap.node)) { - const locationMap = buildLocationServiceMap(replacements) - const locationMapNode = makeGlobalNode({ service: LocationServiceMap.Service, layer: locationMap, deps: [] }) - allReplacements = replacements.concat([[LocationServiceMap.node, locationMapNode]]) - } + if (!LayerNode.hasUnbound(root, LocationServiceMap.node) || hasReplacement(replacements, LocationServiceMap.node)) + return LayerNode.compile(root, replacements) - return LayerNode.compile(root, allReplacements) + const locationMap = buildLocationServiceMap(replacements) + const locationMapNode = makeGlobalNode({ service: LocationServiceMap.Service, layer: locationMap, deps: [] }) + return LayerNode.compile(root, replacements.concat([[LocationServiceMap.node, locationMapNode]])) } function hasReplacement(replacements: LayerNode.Replacements, node: LayerNode.Node) { diff --git a/packages/core/src/filesystem.ts b/packages/core/src/filesystem.ts index a823c72e5a..1fc9b545de 100644 --- a/packages/core/src/filesystem.ts +++ b/packages/core/src/filesystem.ts @@ -70,7 +70,7 @@ const baseLayer = Layer.effect( return yield* Effect.die(new Error("Path escapes the location")) const real = yield* fs.realPath(absolute).pipe(Effect.orDie) if (!FSUtil.contains(root, real)) return yield* Effect.die(new Error("Path escapes the location")) - return { absolute, real, directory: location.directory, root } + return { absolute, real, directory: location.directory } }) return Service.of({ find: search.find, diff --git a/packages/core/src/tool/plugin/patch.ts b/packages/core/src/tool/plugin/patch.ts index 3ddca3573d..4c2c4853fd 100644 --- a/packages/core/src/tool/plugin/patch.ts +++ b/packages/core/src/tool/plugin/patch.ts @@ -286,10 +286,10 @@ export const Plugin = { }), { discard: true }, ) - const files = yield* Effect.forEach(prepared, (change) => { - if (change.type === "delete") return Effect.succeed(patchFile(change)) + const files = prepared.map((change) => { + if (change.type === "delete") return patchFile(change) const target = change.type === "update" && change.moveTarget ? change.moveTarget : change.target - return Effect.succeed(patchFile(change, formatted.get(target.absolute))) + return patchFile(change, formatted.get(target.absolute)) }) return { applied, files } }).pipe( diff --git a/packages/core/src/tool/plugin/shell.ts b/packages/core/src/tool/plugin/shell.ts index 2e65b1af47..1d91c8e58b 100644 --- a/packages/core/src/tool/plugin/shell.ts +++ b/packages/core/src/tool/plugin/shell.ts @@ -94,6 +94,13 @@ const toolResult = (output: Output) => { } } +const backgroundResult = (shellID: string) => ({ + output: BACKGROUND_STARTED, + shellID, + truncated: false, + status: "running" as const, +}) + export const Plugin = { id: "opencode.tool.shell", effect: Effect.fn("ShellTool.Plugin")(function* (ctx: PluginContext) { @@ -303,12 +310,7 @@ export const Plugin = { if (input.background === true) { yield* runtime.job.background(job.id) yield* notifyWhenDone(context.sessionID, context.id, info.id, info.command, settled) - return { - output: BACKGROUND_STARTED, - shellID: info.id, - truncated: false, - status: "running" as const, - } + return backgroundResult(info.id) } const result = yield* runtime.job @@ -317,12 +319,7 @@ export const Plugin = { if (result?.type === "backgrounded") { yield* shell.timeout(info.id, 0) yield* notifyWhenDone(context.sessionID, context.id, info.id, info.command, settled) - return { - output: BACKGROUND_STARTED, - shellID: info.id, - truncated: false, - status: "running" as const, - } + return backgroundResult(info.id) } if (result?.info.status === "error") return yield* Effect.fail(new Error(result.info.error ?? "Command failed")) diff --git a/packages/core/src/tool/plugin/subagent.ts b/packages/core/src/tool/plugin/subagent.ts index 4d4c89de86..aa62085609 100644 --- a/packages/core/src/tool/plugin/subagent.ts +++ b/packages/core/src/tool/plugin/subagent.ts @@ -12,12 +12,15 @@ import { SessionSchema } from "../../session/schema.js" export const name = "subagent" const NO_TEXT = "Subagent completed without a text response." -const backgroundStarted = (sessionID: SessionSchema.ID) => - [ +const backgroundResult = (sessionID: SessionSchema.ID) => ({ + sessionID, + status: "running" as const, + output: [ `The subagent is working in the background (sessionID: ${sessionID}). You will be notified automatically when it finishes.`, "DO NOT sleep, poll for progress, ask the subagent for status, or duplicate this subagent's work; avoid working with the same files or topics it is using.", "Work on non-overlapping tasks, or briefly tell the user what you launched and end your response.", - ].join("\n") + ].join("\n"), +}) export const Input = Schema.Struct({ agent: Schema.String.annotate({ description: "The type of specialized agent to use for this task" }), @@ -259,11 +262,7 @@ export const Plugin = { if (background) { yield* runtime.job.background(info.id) yield* notifyWhenDone(context.sessionID, child.id, agent.name, input.description, info.started_at) - return { - sessionID: child.id, - status: "running" as const, - output: backgroundStarted(child.id), - } + return backgroundResult(child.id) } const result = yield* runtime.job.block({ id: child.id, sessionID: context.sessionID }).pipe( @@ -281,11 +280,7 @@ export const Plugin = { input.description, result.info.started_at, ) - return { - sessionID: child.id, - status: "running" as const, - output: backgroundStarted(child.id), - } + return backgroundResult(child.id) } // Failure surfaces keep the sessionID visible so the model can continue the child. if (result?.info.status === "error") diff --git a/packages/core/src/tool/plugin/webfetch.ts b/packages/core/src/tool/plugin/webfetch.ts index 9f04d216c8..7d692a09e9 100644 --- a/packages/core/src/tool/plugin/webfetch.ts +++ b/packages/core/src/tool/plugin/webfetch.ts @@ -47,7 +47,6 @@ const acceptHeader = (format: Format) => { case "html": return "text/html;q=1.0, application/xhtml+xml;q=0.9, text/plain;q=0.8, text/markdown;q=0.7, */*;q=0.1" } - return "*/*" } const headers = (format: Format, userAgent: string) => ({ diff --git a/packages/core/src/worktree.ts b/packages/core/src/worktree.ts index 14e7ef68cc..3489f1e9fd 100644 --- a/packages/core/src/worktree.ts +++ b/packages/core/src/worktree.ts @@ -220,8 +220,6 @@ const layer = Layer.effect( const gitStrategy = yield* WorktreeGit.make yield* register(gitStrategy).pipe(Effect.orDie) - const strategies = () => Array.from(registry.values()) - const source = Effect.fnUntraced(function* (input: AbsolutePath | undefined, projectID: ProjectSchema.ID) { const sourceDirectory = input ?? (yield* ops.primary(projectID))?.directory if (!sourceDirectory) return yield* new SourceDirectoryNotFoundError({ projectID }) @@ -290,7 +288,7 @@ const layer = Layer.effect( const discovered = yield* Effect.forEach( sourceDirectories, (sourceDirectory) => - Effect.forEach(strategies(), (strategy) => + Effect.forEach(Array.from(registry.values()), (strategy) => strategy.list(sourceDirectory).pipe( Effect.catchTag("Worktree.DirectoryUnavailableError", () => Effect.succeed([])), Effect.map((items) =>