diff --git a/packages/core/src/database/migration.ts b/packages/core/src/database/migration.ts
index 3d0df873510..a6600f80c0a 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 8acb0ea127a..b41e1f56d26 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 a823c72e5a7..1fc9b545dee 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 3ddca3573d8..4c2c4853fd6 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 2e65b1af472..1d91c8e58b0 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 4d4c89de866..aa620856094 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 9f04d216c8e..7d692a09e95 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 14e7ef68cc5..3489f1e9fdd 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) =>