Преглед изворни кода

chore(core): format package (#41205)

Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
opencode-agent[bot] пре 1 недеља
родитељ
комит
b274db88b0
86 измењених фајлова са 2875 додато и 3017 уклоњено
  1. 39 118
      packages/core/schema.json
  2. 1 3
      packages/core/script/benchmark-location.ts
  3. 496 485
      packages/core/src/bus.ts
  4. 2 4
      packages/core/src/catalog.ts
  5. 1 3
      packages/core/src/codemode/catalog.ts
  6. 4 9
      packages/core/src/codemode/tool.ts
  7. 110 95
      packages/core/src/command.ts
  8. 262 257
      packages/core/src/config.ts
  9. 1 3
      packages/core/src/config/plugin/agent.ts
  10. 13 9
      packages/core/src/database/v1-migration.ts
  11. 1 6
      packages/core/src/event-logger.ts
  12. 1 3
      packages/core/src/filesystem/watcher.ts
  13. 5 1
      packages/core/src/form.ts
  14. 1 3
      packages/core/src/formatter.ts
  15. 1 3
      packages/core/src/formatter/builtins.ts
  16. 65 63
      packages/core/src/instruction-discovery.ts
  17. 1 3
      packages/core/src/instructions/builtins.ts
  18. 6 9
      packages/core/src/mcp/client.ts
  19. 570 571
      packages/core/src/mcp/index.ts
  20. 11 3
      packages/core/src/mcp/oauth.ts
  21. 1 2
      packages/core/src/mime.ts
  22. 105 105
      packages/core/src/models-dev.ts
  23. 6 5
      packages/core/src/permission.ts
  24. 6 1
      packages/core/src/permission/saved.ts
  25. 9 11
      packages/core/src/plugin/host.ts
  26. 96 95
      packages/core/src/plugin/provider/github-copilot.ts
  27. 115 111
      packages/core/src/plugin/provider/openai.ts
  28. 33 32
      packages/core/src/plugin/provider/xai.ts
  29. 2 5
      packages/core/src/project.ts
  30. 213 212
      packages/core/src/pty.ts
  31. 1 3
      packages/core/src/session.ts
  32. 5 5
      packages/core/src/session/instruction-state.ts
  33. 4 3
      packages/core/src/session/model-request.ts
  34. 4 6
      packages/core/src/session/pending.ts
  35. 5 1
      packages/core/src/session/runner/llm.ts
  36. 2 7
      packages/core/src/session/runner/publish-llm-event.ts
  37. 5 7
      packages/core/src/session/transfer.ts
  38. 1 2
      packages/core/src/shell/parser-wasm.node.ts
  39. 1 1
      packages/core/src/snapshot.ts
  40. 6 4
      packages/core/src/tool-output.ts
  41. 47 47
      packages/core/src/tool/mcp.ts
  42. 53 58
      packages/core/src/tool/plugin/question.ts
  43. 42 44
      packages/core/src/tool/plugin/skill.ts
  44. 116 119
      packages/core/src/tool/plugin/subagent.ts
  45. 53 55
      packages/core/src/tool/plugin/webfetch.ts
  46. 2 1
      packages/core/src/tool/runtime.ts
  47. 1 1
      packages/core/src/vcs.ts
  48. 4 1
      packages/core/src/vcs/git.ts
  49. 14 12
      packages/core/test/agent.test.ts
  50. 1 3
      packages/core/test/app.test.ts
  51. 2 7
      packages/core/test/bus.test.ts
  52. 6 6
      packages/core/test/codemode.test.ts
  53. 1 4
      packages/core/test/codemode/catalog.test.ts
  54. 6 11
      packages/core/test/codemode/instructions.test.ts
  55. 2 6
      packages/core/test/config/config.test.ts
  56. 0 1
      packages/core/test/effect/observability.test.ts
  57. 1 4
      packages/core/test/formatter.test.ts
  58. 6 1
      packages/core/test/instruction-state.test.ts
  59. 1 5
      packages/core/test/lib/tool.ts
  60. 12 10
      packages/core/test/mcp.test.ts
  61. 48 40
      packages/core/test/patch.test.ts
  62. 1 8
      packages/core/test/permission.test.ts
  63. 27 31
      packages/core/test/plugin.test.ts
  64. 2 5
      packages/core/test/plugin/host.ts
  65. 3 12
      packages/core/test/plugin/provider-cerebras.test.ts
  66. 2 7
      packages/core/test/plugin/provider-openai.test.ts
  67. 1 2
      packages/core/test/plugin/provider-openrouter.test.ts
  68. 1 3
      packages/core/test/plugin/system-prompt.test.ts
  69. 3 9
      packages/core/test/plugin/websearch-fixture.ts
  70. 1 4
      packages/core/test/project.test.ts
  71. 1 3
      packages/core/test/pty/pty-session.test.ts
  72. 3 1
      packages/core/test/ripgrep.test.ts
  73. 15 23
      packages/core/test/session-prompt.test.ts
  74. 2 6
      packages/core/test/session-runner-tool-events.test.ts
  75. 71 79
      packages/core/test/session-runner-tool-registry.test.ts
  76. 1 4
      packages/core/test/session-runner.test.ts
  77. 5 1
      packages/core/test/shell-parse.test.ts
  78. 1 3
      packages/core/test/snapshot.test.ts
  79. 11 14
      packages/core/test/tool-execute.test.ts
  80. 1 4
      packages/core/test/tool-output.test.ts
  81. 6 6
      packages/core/test/tool-schema.test.ts
  82. 70 65
      packages/core/test/tool-shell.test.ts
  83. 5 8
      packages/core/test/tool-skill.test.ts
  84. 3 3
      packages/core/test/util/effect-flock.test.ts
  85. 7 6
      packages/core/test/vcs-hg.test.ts
  86. 3 5
      packages/core/test/vcs.test.ts

+ 39 - 118
packages/core/schema.json

@@ -2,9 +2,7 @@
   "version": "7",
   "dialect": "sqlite",
   "id": "2d214a71-3b0a-48c1-a667-741952c4e188",
-  "prevIds": [
-    "f14a9b18-8207-487e-a3d3-227e629ba9ad"
-  ],
+  "prevIds": ["f14a9b18-8207-487e-a3d3-227e629ba9ad"],
   "ddl": [
     {
       "name": "workspace",
@@ -1385,13 +1383,9 @@
       "table": "session_v2"
     },
     {
-      "columns": [
-        "project_id"
-      ],
+      "columns": ["project_id"],
       "tableTo": "project",
-      "columnsTo": [
-        "id"
-      ],
+      "columnsTo": ["id"],
       "onUpdate": "NO ACTION",
       "onDelete": "CASCADE",
       "nameExplicit": false,
@@ -1400,13 +1394,9 @@
       "table": "workspace"
     },
     {
-      "columns": [
-        "active_account_id"
-      ],
+      "columns": ["active_account_id"],
       "tableTo": "account",
-      "columnsTo": [
-        "id"
-      ],
+      "columnsTo": ["id"],
       "onUpdate": "NO ACTION",
       "onDelete": "SET NULL",
       "nameExplicit": false,
@@ -1415,13 +1405,9 @@
       "table": "account_state"
     },
     {
-      "columns": [
-        "aggregate_id"
-      ],
+      "columns": ["aggregate_id"],
       "tableTo": "event_sequence",
-      "columnsTo": [
-        "aggregate_id"
-      ],
+      "columnsTo": ["aggregate_id"],
       "onUpdate": "NO ACTION",
       "onDelete": "CASCADE",
       "nameExplicit": false,
@@ -1430,13 +1416,9 @@
       "table": "event"
     },
     {
-      "columns": [
-        "project_id"
-      ],
+      "columns": ["project_id"],
       "tableTo": "project",
-      "columnsTo": [
-        "id"
-      ],
+      "columnsTo": ["id"],
       "onUpdate": "NO ACTION",
       "onDelete": "CASCADE",
       "nameExplicit": false,
@@ -1445,13 +1427,9 @@
       "table": "permission"
     },
     {
-      "columns": [
-        "project_id"
-      ],
+      "columns": ["project_id"],
       "tableTo": "project",
-      "columnsTo": [
-        "id"
-      ],
+      "columnsTo": ["id"],
       "onUpdate": "NO ACTION",
       "onDelete": "CASCADE",
       "nameExplicit": false,
@@ -1460,13 +1438,9 @@
       "table": "project_directory"
     },
     {
-      "columns": [
-        "session_id"
-      ],
+      "columns": ["session_id"],
       "tableTo": "session_v2",
-      "columnsTo": [
-        "id"
-      ],
+      "columnsTo": ["id"],
       "onUpdate": "NO ACTION",
       "onDelete": "CASCADE",
       "nameExplicit": false,
@@ -1475,13 +1449,9 @@
       "table": "instruction_entry"
     },
     {
-      "columns": [
-        "session_id"
-      ],
+      "columns": ["session_id"],
       "tableTo": "session_v2",
-      "columnsTo": [
-        "id"
-      ],
+      "columnsTo": ["id"],
       "onUpdate": "NO ACTION",
       "onDelete": "CASCADE",
       "nameExplicit": false,
@@ -1490,13 +1460,9 @@
       "table": "instruction_state"
     },
     {
-      "columns": [
-        "session_id"
-      ],
+      "columns": ["session_id"],
       "tableTo": "session_v2",
-      "columnsTo": [
-        "id"
-      ],
+      "columnsTo": ["id"],
       "onUpdate": "NO ACTION",
       "onDelete": "CASCADE",
       "nameExplicit": false,
@@ -1505,13 +1471,9 @@
       "table": "session_message"
     },
     {
-      "columns": [
-        "session_id"
-      ],
+      "columns": ["session_id"],
       "tableTo": "session_v2",
-      "columnsTo": [
-        "id"
-      ],
+      "columnsTo": ["id"],
       "onUpdate": "NO ACTION",
       "onDelete": "CASCADE",
       "nameExplicit": false,
@@ -1520,13 +1482,9 @@
       "table": "session_pending"
     },
     {
-      "columns": [
-        "project_id"
-      ],
+      "columns": ["project_id"],
       "tableTo": "project",
-      "columnsTo": [
-        "id"
-      ],
+      "columnsTo": ["id"],
       "onUpdate": "NO ACTION",
       "onDelete": "CASCADE",
       "nameExplicit": false,
@@ -1535,156 +1493,119 @@
       "table": "session_v2"
     },
     {
-      "columns": [
-        "email",
-        "url"
-      ],
+      "columns": ["email", "url"],
       "nameExplicit": false,
       "name": "control_account_pk",
       "entityType": "pks",
       "table": "control_account"
     },
     {
-      "columns": [
-        "project_id",
-        "directory"
-      ],
+      "columns": ["project_id", "directory"],
       "nameExplicit": false,
       "name": "project_directory_pk",
       "entityType": "pks",
       "table": "project_directory"
     },
     {
-      "columns": [
-        "session_id",
-        "key"
-      ],
+      "columns": ["session_id", "key"],
       "nameExplicit": false,
       "name": "instruction_entry_pk",
       "entityType": "pks",
       "table": "instruction_entry"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "nameExplicit": false,
       "name": "workspace_pk",
       "table": "workspace",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "nameExplicit": false,
       "name": "account_state_pk",
       "table": "account_state",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "nameExplicit": false,
       "name": "account_pk",
       "table": "account",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "nameExplicit": false,
       "name": "credential_pk",
       "table": "credential",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "aggregate_id"
-      ],
+      "columns": ["aggregate_id"],
       "nameExplicit": false,
       "name": "event_sequence_pk",
       "table": "event_sequence",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "nameExplicit": false,
       "name": "event_pk",
       "table": "event",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "key"
-      ],
+      "columns": ["key"],
       "nameExplicit": false,
       "name": "kv_pk",
       "table": "kv",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "nameExplicit": false,
       "name": "permission_pk",
       "table": "permission",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "nameExplicit": false,
       "name": "project_pk",
       "table": "project",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "hash"
-      ],
+      "columns": ["hash"],
       "nameExplicit": false,
       "name": "instruction_blob_pk",
       "table": "instruction_blob",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "session_id"
-      ],
+      "columns": ["session_id"],
       "nameExplicit": false,
       "name": "instruction_state_pk",
       "table": "instruction_state",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "nameExplicit": false,
       "name": "session_message_pk",
       "table": "session_message",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "nameExplicit": false,
       "name": "session_pending_pk",
       "table": "session_pending",
       "entityType": "pks"
     },
     {
-      "columns": [
-        "id"
-      ],
+      "columns": ["id"],
       "nameExplicit": false,
       "name": "session_v2_pk",
       "table": "session_v2",
@@ -1940,4 +1861,4 @@
     }
   ],
   "renames": []
-}
+}

+ 1 - 3
packages/core/script/benchmark-location.ts

@@ -20,9 +20,7 @@ if (!Number.isInteger(iterations) || iterations < 1) {
 }
 
 const ref = Location.Ref.make({ directory: AbsolutePath.make(path.resolve(directory)) })
-const layer = AppNodeBuilder.build(
-  LayerNode.group([Database.node, Bus.node, SdkPlugins.node, LocationServiceMap.node]),
-)
+const layer = AppNodeBuilder.build(LayerNode.group([Database.node, Bus.node, SdkPlugins.node, LocationServiceMap.node]))
 
 const measure = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
   Effect.gen(function* () {

+ 496 - 485
packages/core/src/bus.ts

@@ -110,10 +110,13 @@ export interface Subscribe {
    */
   (): Stream.Stream<Event.Payload>
   <D extends Event.Definition>(definition: D): Stream.Stream<Event.Payload<D>>
-  <const D extends readonly [Event.Definition, ...Event.Definition[]]>(definitions: D): Stream.Stream<SubscribePayload<D>>
+  <const D extends readonly [Event.Definition, ...Event.Definition[]]>(
+    definitions: D,
+  ): Stream.Stream<SubscribePayload<D>>
 }
 
-const isDefinition = (input: Event.Definition | readonly Event.Definition[]): input is Event.Definition => !Array.isArray(input)
+const isDefinition = (input: Event.Definition | readonly Event.Definition[]): input is Event.Definition =>
+  !Array.isArray(input)
 
 export interface Interface {
   readonly publish: <D extends Event.Definition>(
@@ -149,7 +152,6 @@ export interface Interface {
   readonly claim: (aggregateID: string, ownerID: string) => Effect.Effect<void>
 }
 
-
 export class Service extends Context.Service<Service, Interface>()("@opencode/Bus") {}
 
 interface Options {
@@ -164,534 +166,543 @@ export function configured(options?: Options) {
   return makeGlobalNode({
     service: Service,
     deps: [Database.node],
-    layer: Layer.effect(Service, Effect.gen(function* () {
-      const pubsub = {
-        live: yield* PubSub.unbounded<Event.Payload>(),
-        durable: new Map<string, Set<PubSub.PubSub<void>>>(),
-        typed: new Map<string, PubSub.PubSub<Event.Payload>>(),
-      }
-      const projectors = new Map<string, Subscriber[]>()
-      const listeners = new Array<Subscriber>()
-      const { db } = yield* Database.Service
-      const logReadPageSize = options?.logReadPageSize ?? 512
-      const persist = options?.persist ?? false
-
-      const getOrCreate = (definition: Event.Definition) =>
-        Effect.gen(function* () {
-          const existing = pubsub.typed.get(definition.type)
-          if (existing) return existing
-          const created = yield* PubSub.unbounded<Event.Payload>()
-          pubsub.typed.set(definition.type, created)
-          return created
-        })
+    layer: Layer.effect(
+      Service,
+      Effect.gen(function* () {
+        const pubsub = {
+          live: yield* PubSub.unbounded<Event.Payload>(),
+          durable: new Map<string, Set<PubSub.PubSub<void>>>(),
+          typed: new Map<string, PubSub.PubSub<Event.Payload>>(),
+        }
+        const projectors = new Map<string, Subscriber[]>()
+        const listeners = new Array<Subscriber>()
+        const { db } = yield* Database.Service
+        const logReadPageSize = options?.logReadPageSize ?? 512
+        const persist = options?.persist ?? false
 
-      yield* Effect.addFinalizer(() =>
-        Effect.gen(function* () {
-          yield* PubSub.shutdown(pubsub.live)
-          yield* Effect.forEach(
-            pubsub.durable.values(),
-            (pubsubs) => Effect.forEach(pubsubs, PubSub.shutdown, { discard: true }),
-            { discard: true },
-          )
-          yield* Effect.forEach(pubsub.typed.values(), PubSub.shutdown, { discard: true })
-        }),
-      )
-
-      function commitDurableEvent(
-        definition: Event.Definition,
-        event: Event.Payload,
-        input?: {
-          readonly seq: number
-          readonly aggregateID: string
-          readonly ownerID?: string
-          readonly strictOwner?: boolean
-        },
-        commit?: (seq: number) => Effect.Effect<void>,
-      ) {
-        return Effect.gen(function* () {
-          const durable = definition?.durable
-          if (durable) {
-            const aggregateID = (event.data as Record<string, unknown>)[durable.aggregate]
-            if (typeof aggregateID !== "string") {
-              yield* Effect.die(
-                new InvalidDurableEventError({
-                  type: event.type,
-                  message: `Expected string aggregate field ${durable.aggregate}`,
-                }),
-              )
-            } else {
-              if (input && input.aggregateID !== aggregateID) {
+        const getOrCreate = (definition: Event.Definition) =>
+          Effect.gen(function* () {
+            const existing = pubsub.typed.get(definition.type)
+            if (existing) return existing
+            const created = yield* PubSub.unbounded<Event.Payload>()
+            pubsub.typed.set(definition.type, created)
+            return created
+          })
+
+        yield* Effect.addFinalizer(() =>
+          Effect.gen(function* () {
+            yield* PubSub.shutdown(pubsub.live)
+            yield* Effect.forEach(
+              pubsub.durable.values(),
+              (pubsubs) => Effect.forEach(pubsubs, PubSub.shutdown, { discard: true }),
+              { discard: true },
+            )
+            yield* Effect.forEach(pubsub.typed.values(), PubSub.shutdown, { discard: true })
+          }),
+        )
+
+        function commitDurableEvent(
+          definition: Event.Definition,
+          event: Event.Payload,
+          input?: {
+            readonly seq: number
+            readonly aggregateID: string
+            readonly ownerID?: string
+            readonly strictOwner?: boolean
+          },
+          commit?: (seq: number) => Effect.Effect<void>,
+        ) {
+          return Effect.gen(function* () {
+            const durable = definition?.durable
+            if (durable) {
+              const aggregateID = (event.data as Record<string, unknown>)[durable.aggregate]
+              if (typeof aggregateID !== "string") {
                 yield* Effect.die(
                   new InvalidDurableEventError({
                     type: event.type,
-                    message: `Aggregate mismatch: expected ${input.aggregateID}, got ${aggregateID}`,
+                    message: `Expected string aggregate field ${durable.aggregate}`,
                   }),
                 )
-              }
-              const list = projectors.get(versionedType(definition.type, durable.version)) ?? []
-              return yield* Effect.uninterruptible(
-                Effect.gen(function* () {
-                  const committed = yield* db
-                    .transaction(
-                      () =>
-                        Effect.gen(function* () {
-                          const row = yield* db
-                            .select({ seq: EventSequenceTable.seq, ownerID: EventSequenceTable.owner_id })
-                            .from(EventSequenceTable)
-                            .where(eq(EventSequenceTable.aggregate_id, aggregateID))
-                            .get()
-                            .pipe(Effect.orDie)
-                          const latest = row?.seq ?? -1
-                          const encoded = Schema.encodeUnknownSync(definition.data)(event.data) as Record<
-                            string,
-                            unknown
-                          >
-                          if (input?.strictOwner && row?.ownerID && row.ownerID !== input.ownerID) {
-                            yield* Effect.die(
-                              new InvalidDurableEventError({
-                                type: event.type,
-                                message: `Replay owner mismatch for aggregate ${aggregateID}: expected ${row.ownerID}, got ${input.ownerID ?? "none"}`,
-                              }),
-                            )
-                          }
-                          if (input && input.seq <= latest) {
-                            if (!persist) return
-                            const stored = yield* db
-                              .select()
-                              .from(EventTable)
-                              .where(and(eq(EventTable.aggregate_id, aggregateID), eq(EventTable.seq, input.seq)))
+              } else {
+                if (input && input.aggregateID !== aggregateID) {
+                  yield* Effect.die(
+                    new InvalidDurableEventError({
+                      type: event.type,
+                      message: `Aggregate mismatch: expected ${input.aggregateID}, got ${aggregateID}`,
+                    }),
+                  )
+                }
+                const list = projectors.get(versionedType(definition.type, durable.version)) ?? []
+                return yield* Effect.uninterruptible(
+                  Effect.gen(function* () {
+                    const committed = yield* db
+                      .transaction(
+                        () =>
+                          Effect.gen(function* () {
+                            const row = yield* db
+                              .select({ seq: EventSequenceTable.seq, ownerID: EventSequenceTable.owner_id })
+                              .from(EventSequenceTable)
+                              .where(eq(EventSequenceTable.aggregate_id, aggregateID))
                               .get()
                               .pipe(Effect.orDie)
-                            if (
-                              stored?.id === event.id &&
-                              stored.type === versionedType(definition.type, durable.version) &&
-                              stored.created === DateTime.toEpochMillis(event.created ?? DateTime.makeUnsafe(0)) &&
-                              isDeepStrictEqual(stored.data, encoded)
-                            ) {
-                              if (input.ownerID && row?.ownerID == null) {
-                                yield* db
-                                  .update(EventSequenceTable)
-                                  .set({ owner_id: input.ownerID })
-                                  .where(eq(EventSequenceTable.aggregate_id, aggregateID))
-                                  .run()
-                                  .pipe(Effect.orDie)
+                            const latest = row?.seq ?? -1
+                            const encoded = Schema.encodeUnknownSync(definition.data)(event.data) as Record<
+                              string,
+                              unknown
+                            >
+                            if (input?.strictOwner && row?.ownerID && row.ownerID !== input.ownerID) {
+                              yield* Effect.die(
+                                new InvalidDurableEventError({
+                                  type: event.type,
+                                  message: `Replay owner mismatch for aggregate ${aggregateID}: expected ${row.ownerID}, got ${input.ownerID ?? "none"}`,
+                                }),
+                              )
+                            }
+                            if (input && input.seq <= latest) {
+                              if (!persist) return
+                              const stored = yield* db
+                                .select()
+                                .from(EventTable)
+                                .where(and(eq(EventTable.aggregate_id, aggregateID), eq(EventTable.seq, input.seq)))
+                                .get()
+                                .pipe(Effect.orDie)
+                              if (
+                                stored?.id === event.id &&
+                                stored.type === versionedType(definition.type, durable.version) &&
+                                stored.created === DateTime.toEpochMillis(event.created ?? DateTime.makeUnsafe(0)) &&
+                                isDeepStrictEqual(stored.data, encoded)
+                              ) {
+                                if (input.ownerID && row?.ownerID == null) {
+                                  yield* db
+                                    .update(EventSequenceTable)
+                                    .set({ owner_id: input.ownerID })
+                                    .where(eq(EventSequenceTable.aggregate_id, aggregateID))
+                                    .run()
+                                    .pipe(Effect.orDie)
+                                }
+                                return
                               }
+                              yield* Effect.die(
+                                new InvalidDurableEventError({
+                                  type: event.type,
+                                  message: `Replay diverged at aggregate ${aggregateID} sequence ${input.seq}`,
+                                }),
+                              )
+                            }
+                            if (input && row?.ownerID && row.ownerID !== input.ownerID) {
                               return
                             }
-                            yield* Effect.die(
-                              new InvalidDurableEventError({
-                                type: event.type,
-                                message: `Replay diverged at aggregate ${aggregateID} sequence ${input.seq}`,
-                              }),
-                            )
-                          }
-                          if (input && row?.ownerID && row.ownerID !== input.ownerID) {
-                            return
-                          }
-                          const seq = input?.seq ?? latest + 1
-                          if (input && seq !== latest + 1) {
-                            yield* Effect.die(
-                              new InvalidDurableEventError({
-                                type: event.type,
-                                message: `Sequence mismatch for aggregate ${aggregateID}: expected ${latest + 1}, got ${seq}`,
-                              }),
-                            )
-                          }
-                          if (persist) {
-                            const stored = yield* db
-                              .select({ aggregateID: EventTable.aggregate_id, seq: EventTable.seq })
-                              .from(EventTable)
-                              .where(eq(EventTable.id, event.id))
-                              .get()
-                              .pipe(Effect.orDie)
-                            if (stored)
+                            const seq = input?.seq ?? latest + 1
+                            if (input && seq !== latest + 1) {
                               yield* Effect.die(
                                 new InvalidDurableEventError({
                                   type: event.type,
-                                  message: `Event ${event.id} already exists at aggregate ${stored.aggregateID} sequence ${stored.seq}`,
+                                  message: `Sequence mismatch for aggregate ${aggregateID}: expected ${latest + 1}, got ${seq}`,
                                 }),
                               )
-                          }
-                          const committed = {
-                            ...event,
-                            durable: { aggregateID, seq, version: durable.version },
-                          } as Event.Payload
-                          for (const projector of list) {
-                            yield* projector(committed)
-                          }
-                          if (commit) yield* commit(seq)
-                          yield* db
-                            .insert(EventSequenceTable)
-                            .values([{ aggregate_id: aggregateID, seq, owner_id: input?.ownerID }])
-                            .onConflictDoUpdate({
-                              target: EventSequenceTable.aggregate_id,
-                              set: {
-                                seq: sql`max(${EventSequenceTable.seq}, ${seq})`,
-                                ...(input?.ownerID && row?.ownerID == null ? { owner_id: input.ownerID } : {}),
-                              },
-                            })
-                            .run()
-                            .pipe(Effect.orDie)
-                          if (persist)
+                            }
+                            if (persist) {
+                              const stored = yield* db
+                                .select({ aggregateID: EventTable.aggregate_id, seq: EventTable.seq })
+                                .from(EventTable)
+                                .where(eq(EventTable.id, event.id))
+                                .get()
+                                .pipe(Effect.orDie)
+                              if (stored)
+                                yield* Effect.die(
+                                  new InvalidDurableEventError({
+                                    type: event.type,
+                                    message: `Event ${event.id} already exists at aggregate ${stored.aggregateID} sequence ${stored.seq}`,
+                                  }),
+                                )
+                            }
+                            const committed = {
+                              ...event,
+                              durable: { aggregateID, seq, version: durable.version },
+                            } as Event.Payload
+                            for (const projector of list) {
+                              yield* projector(committed)
+                            }
+                            if (commit) yield* commit(seq)
                             yield* db
-                              .insert(EventTable)
-                              .values([
-                                {
-                                  id: event.id,
-                                  aggregate_id: aggregateID,
-                                  seq,
-                                  created: DateTime.toEpochMillis(event.created ?? DateTime.makeUnsafe(0)),
-                                  type: versionedType(definition.type, durable.version),
-                                  data: encoded,
+                              .insert(EventSequenceTable)
+                              .values([{ aggregate_id: aggregateID, seq, owner_id: input?.ownerID }])
+                              .onConflictDoUpdate({
+                                target: EventSequenceTable.aggregate_id,
+                                set: {
+                                  seq: sql`max(${EventSequenceTable.seq}, ${seq})`,
+                                  ...(input?.ownerID && row?.ownerID == null ? { owner_id: input.ownerID } : {}),
                                 },
-                              ])
+                              })
                               .run()
                               .pipe(Effect.orDie)
-                          return { aggregateID, seq }
-                        }),
-                      { behavior: "immediate" },
-                    )
-                    .pipe(Effect.orDie)
-                  if (committed) {
-                    yield* Effect.forEach(
-                      pubsub.durable.get(committed.aggregateID) ?? [],
-                      (wake) => PubSub.publish(wake, undefined),
-                      { discard: true },
-                    )
-                  }
-                  return committed
+                            if (persist)
+                              yield* db
+                                .insert(EventTable)
+                                .values([
+                                  {
+                                    id: event.id,
+                                    aggregate_id: aggregateID,
+                                    seq,
+                                    created: DateTime.toEpochMillis(event.created ?? DateTime.makeUnsafe(0)),
+                                    type: versionedType(definition.type, durable.version),
+                                    data: encoded,
+                                  },
+                                ])
+                                .run()
+                                .pipe(Effect.orDie)
+                            return { aggregateID, seq }
+                          }),
+                        { behavior: "immediate" },
+                      )
+                      .pipe(Effect.orDie)
+                    if (committed) {
+                      yield* Effect.forEach(
+                        pubsub.durable.get(committed.aggregateID) ?? [],
+                        (wake) => PubSub.publish(wake, undefined),
+                        { discard: true },
+                      )
+                    }
+                    return committed
+                  }),
+                )
+              }
+            }
+          })
+        }
+
+        function publishEvent<D extends Event.Definition>(
+          definition: D,
+          event: Event.Payload<D>,
+          commit?: PublishOptions["commit"],
+        ) {
+          return Effect.gen(function* () {
+            if (!definition?.durable && commit)
+              return yield* Effect.die(
+                new InvalidDurableEventError({
+                  type: event.type,
+                  message: "Local commit hooks require a durable event",
                 }),
               )
-            }
-          }
-        })
-      }
-
-      function publishEvent<D extends Event.Definition>(definition: D, event: Event.Payload<D>, commit?: PublishOptions["commit"]) {
-        return Effect.gen(function* () {
-          if (!definition?.durable && commit)
-            return yield* Effect.die(
-              new InvalidDurableEventError({
-                type: event.type,
-                message: "Local commit hooks require a durable event",
-              }),
-            )
-          if (definition?.durable) {
-            const committed = yield* commitDurableEvent(definition, event as Event.Payload, undefined, commit)
-            if (committed) {
-              event = {
-                ...event,
-                durable: envelope(committed.aggregateID, committed.seq, definition.durable.version),
+            if (definition?.durable) {
+              const committed = yield* commitDurableEvent(definition, event as Event.Payload, undefined, commit)
+              if (committed) {
+                event = {
+                  ...event,
+                  durable: envelope(committed.aggregateID, committed.seq, definition.durable.version),
+                }
+                yield* notify(event as Event.Payload, true)
+                return event
               }
-              yield* notify(event as Event.Payload, true)
-              return event
             }
-          }
-          yield* notify(event as Event.Payload, false)
-          return event
-        })
-      }
-
-      const observe = (event: Event.Payload, observer: (event: Event.Payload) => Effect.Effect<void>) =>
-        Effect.suspend(() => observer(event)).pipe(
-          Effect.catchCauseIf(
-            (cause) => !Cause.hasInterrupts(cause),
-            (cause) => Effect.logError("Event listener failed", { eventID: event.id, eventType: event.type, cause }),
-          ),
-        )
+            yield* notify(event as Event.Payload, false)
+            return event
+          })
+        }
 
-      function notify(event: Event.Payload, isolateListeners: boolean) {
-        return Effect.gen(function* () {
-          yield* Effect.forEach(
-            listeners,
-            (listener) => (isolateListeners ? observe(event, listener) : listener(event)),
-            { discard: true },
-          )
-          const typed = pubsub.typed.get(event.type)
-          if (typed) yield* PubSub.publish(typed, event)
-          yield* PubSub.publish(pubsub.live, event)
-        })
-      }
-
-      function publish<D extends Event.Definition>(definition: D, data: Event.Data<D>, options?: PublishOptions) {
-        return Effect.gen(function* () {
-          const serviceLocation = Option.getOrUndefined(yield* Effect.serviceOption(Location.Service))
-          const location =
-            options?.location ??
-            (serviceLocation
-              ? { directory: serviceLocation.directory, workspaceID: serviceLocation.workspaceID }
-              : undefined)
-          return yield* publishEvent(
-            definition,
-            {
-              id: options?.id ?? Event.ID.create(),
-              created: yield* DateTime.now,
-              ...(options?.metadata ? { metadata: options.metadata } : {}),
-              type: definition.type,
-              ...(location ? { location } : {}),
-              data,
-            } as Event.Payload<D>,
-            options?.commit,
+        const observe = (event: Event.Payload, observer: (event: Event.Payload) => Effect.Effect<void>) =>
+          Effect.suspend(() => observer(event)).pipe(
+            Effect.catchCauseIf(
+              (cause) => !Cause.hasInterrupts(cause),
+              (cause) => Effect.logError("Event listener failed", { eventID: event.id, eventType: event.type, cause }),
+            ),
           )
-        })
-      }
-
-      function replay(
-        event: SerializedEvent,
-        options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
-      ) {
-        return Effect.gen(function* () {
-          const definition = Durable.get(event.type)
-          if (!definition?.durable) {
-            yield* Effect.die(
-              new InvalidDurableEventError({ type: event.type, message: `Unknown durable event type ${event.type}` }),
+
+        function notify(event: Event.Payload, isolateListeners: boolean) {
+          return Effect.gen(function* () {
+            yield* Effect.forEach(
+              listeners,
+              (listener) => (isolateListeners ? observe(event, listener) : listener(event)),
+              { discard: true },
             )
-          } else {
-            const payload = {
-              id: event.id,
-              created: event.created ?? DateTime.makeUnsafe(0),
-              type: definition.type,
-              data: Schema.decodeUnknownSync(definition.data)(event.data),
-            } as Event.Payload
-            const committed = yield* commitDurableEvent(definition, payload, {
-              seq: event.seq,
-              aggregateID: event.aggregateID,
-              ownerID: options?.ownerID,
-              strictOwner: options?.strictOwner,
-            })
-            if (committed && options?.publish) {
-              yield* notify(
-                {
-                  ...payload,
-                  durable: envelope(committed.aggregateID, committed.seq, definition.durable.version),
-                },
-                true,
+            const typed = pubsub.typed.get(event.type)
+            if (typed) yield* PubSub.publish(typed, event)
+            yield* PubSub.publish(pubsub.live, event)
+          })
+        }
+
+        function publish<D extends Event.Definition>(definition: D, data: Event.Data<D>, options?: PublishOptions) {
+          return Effect.gen(function* () {
+            const serviceLocation = Option.getOrUndefined(yield* Effect.serviceOption(Location.Service))
+            const location =
+              options?.location ??
+              (serviceLocation
+                ? { directory: serviceLocation.directory, workspaceID: serviceLocation.workspaceID }
+                : undefined)
+            return yield* publishEvent(
+              definition,
+              {
+                id: options?.id ?? Event.ID.create(),
+                created: yield* DateTime.now,
+                ...(options?.metadata ? { metadata: options.metadata } : {}),
+                type: definition.type,
+                ...(location ? { location } : {}),
+                data,
+              } as Event.Payload<D>,
+              options?.commit,
+            )
+          })
+        }
+
+        function replay(
+          event: SerializedEvent,
+          options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
+        ) {
+          return Effect.gen(function* () {
+            const definition = Durable.get(event.type)
+            if (!definition?.durable) {
+              yield* Effect.die(
+                new InvalidDurableEventError({ type: event.type, message: `Unknown durable event type ${event.type}` }),
               )
+            } else {
+              const payload = {
+                id: event.id,
+                created: event.created ?? DateTime.makeUnsafe(0),
+                type: definition.type,
+                data: Schema.decodeUnknownSync(definition.data)(event.data),
+              } as Event.Payload
+              const committed = yield* commitDurableEvent(definition, payload, {
+                seq: event.seq,
+                aggregateID: event.aggregateID,
+                ownerID: options?.ownerID,
+                strictOwner: options?.strictOwner,
+              })
+              if (committed && options?.publish) {
+                yield* notify(
+                  {
+                    ...payload,
+                    durable: envelope(committed.aggregateID, committed.seq, definition.durable.version),
+                  },
+                  true,
+                )
+              }
             }
-          }
-        })
-      }
-
-      function replayAll(
-        events: SerializedEvent[],
-        options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
-      ) {
-        return Effect.gen(function* () {
-          const source = events[0]?.aggregateID
-          if (!source) return undefined
-          if (events.some((event) => event.aggregateID !== source)) {
-            yield* Effect.die(
-              new InvalidDurableEventError({
-                type: events[0]?.type ?? "unknown",
-                message: "Replay events must belong to the same aggregate",
+          })
+        }
+
+        function replayAll(
+          events: SerializedEvent[],
+          options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
+        ) {
+          return Effect.gen(function* () {
+            const source = events[0]?.aggregateID
+            if (!source) return undefined
+            if (events.some((event) => event.aggregateID !== source)) {
+              yield* Effect.die(
+                new InvalidDurableEventError({
+                  type: events[0]?.type ?? "unknown",
+                  message: "Replay events must belong to the same aggregate",
+                }),
+              )
+            }
+            for (const event of events) {
+              yield* replay(event, options)
+            }
+            return source
+          })
+        }
+
+        function remove(aggregateID: string) {
+          return db
+            .transaction(() =>
+              Effect.gen(function* () {
+                yield* db.delete(EventSequenceTable).where(eq(EventSequenceTable.aggregate_id, aggregateID)).run()
+                yield* db.delete(EventTable).where(eq(EventTable.aggregate_id, aggregateID)).run()
               }),
             )
-          }
-          for (const event of events) {
-            yield* replay(event, options)
-          }
-          return source
-        })
-      }
+            .pipe(Effect.orDie)
+        }
 
-      function remove(aggregateID: string) {
-        return db
-          .transaction(() =>
-            Effect.gen(function* () {
-              yield* db.delete(EventSequenceTable).where(eq(EventSequenceTable.aggregate_id, aggregateID)).run()
-              yield* db.delete(EventTable).where(eq(EventTable.aggregate_id, aggregateID)).run()
-            }),
-          )
-          .pipe(Effect.orDie)
-      }
-
-      function claim(aggregateID: string, ownerID: string) {
-        return db
-          .update(EventSequenceTable)
-          .set({ owner_id: ownerID })
-          .where(eq(EventSequenceTable.aggregate_id, aggregateID))
-          .run()
-          .pipe(Effect.orDie)
-      }
-
-      const local = <A extends Event.Payload>(stream: Stream.Stream<A>) =>
-        Stream.unwrap(
-          Effect.serviceOption(Location.Service).pipe(
-            Effect.map((location) =>
-              Option.match(location, {
-                onNone: () => stream,
-                onSome: (location) =>
-                  stream.pipe(
-                    Stream.filter(
-                      (event) =>
-                        !event.location ||
-                        (event.location.directory === location.directory &&
-                          event.location.workspaceID === location.workspaceID),
+        function claim(aggregateID: string, ownerID: string) {
+          return db
+            .update(EventSequenceTable)
+            .set({ owner_id: ownerID })
+            .where(eq(EventSequenceTable.aggregate_id, aggregateID))
+            .run()
+            .pipe(Effect.orDie)
+        }
+
+        const local = <A extends Event.Payload>(stream: Stream.Stream<A>) =>
+          Stream.unwrap(
+            Effect.serviceOption(Location.Service).pipe(
+              Effect.map((location) =>
+                Option.match(location, {
+                  onNone: () => stream,
+                  onSome: (location) =>
+                    stream.pipe(
+                      Stream.filter(
+                        (event) =>
+                          !event.location ||
+                          (event.location.directory === location.directory &&
+                            event.location.workspaceID === location.workspaceID),
+                      ),
                     ),
-                  ),
-              }),
+                }),
+              ),
             ),
-          ),
-        )
+          )
 
-      function subscribe(): Stream.Stream<Event.Payload>
-      function subscribe<D extends Event.Definition>(definition: D): Stream.Stream<Event.Payload<D>>
-      function subscribe<const D extends readonly [Event.Definition, ...Event.Definition[]]>(
-        definitions: D,
-      ): Stream.Stream<SubscribePayload<D>>
-      function subscribe(input?: Event.Definition | readonly Event.Definition[]): Stream.Stream<Event.Payload> {
-        if (input === undefined) return streamLive()
-        if (isDefinition(input)) {
-          return local(Stream.unwrap(getOrCreate(input).pipe(Effect.map((pubsub) => Stream.fromPubSub(pubsub)))))
+        function subscribe(): Stream.Stream<Event.Payload>
+        function subscribe<D extends Event.Definition>(definition: D): Stream.Stream<Event.Payload<D>>
+        function subscribe<const D extends readonly [Event.Definition, ...Event.Definition[]]>(
+          definitions: D,
+        ): Stream.Stream<SubscribePayload<D>>
+        function subscribe(input?: Event.Definition | readonly Event.Definition[]): Stream.Stream<Event.Payload> {
+          if (input === undefined) return streamLive()
+          if (isDefinition(input)) {
+            return local(Stream.unwrap(getOrCreate(input).pipe(Effect.map((pubsub) => Stream.fromPubSub(pubsub)))))
+          }
+          const types = new Set(input.map((definition) => definition.type))
+          return streamLive().pipe(Stream.filter((event) => types.has(event.type)))
         }
-        const types = new Set(input.map((definition) => definition.type))
-        return streamLive().pipe(Stream.filter((event) => types.has(event.type)))
-      }
-
-      const streamLive = (): Stream.Stream<Event.Payload> => local(Stream.fromPubSub(pubsub.live))
-
-      const readAfter = (
-        aggregateID: string,
-        after: number,
-        input: { readonly through: number; readonly limit: number },
-      ) =>
-        (options?.beforeAggregateRead?.(aggregateID) ?? Effect.void).pipe(
-          Effect.andThen(
-            Effect.suspend(() => {
-              const query = db
-                .select()
-                .from(EventTable)
-                .where(
-                  and(
-                    eq(EventTable.aggregate_id, aggregateID),
-                    gt(EventTable.seq, after),
-                    lte(EventTable.seq, input.through),
-                  ),
-                )
-                .orderBy(asc(EventTable.seq))
-              return query.limit(input.limit).all()
-            }),
-          ),
-          Effect.orDie,
-          // Skip types missing from the durable manifest instead of failing the
-          // read: the aggregate may hold events this process cannot decode. The
-          // raw tail seq keeps cursors advancing across the resulting gaps.
-          Effect.map((rows) => ({
-            seq: rows.at(-1)?.seq,
-            events: rows.flatMap((event) => {
-              if (!Durable.get(event.type)?.durable) return []
-              return [
-                decodeSerializedEvent({
-                  id: event.id,
-                  created: DateTime.makeUnsafe(event.created),
-                  aggregateID: event.aggregate_id,
-                  seq: event.seq,
-                  type: event.type,
-                  data: event.data,
-                }),
-              ]
-            }),
-          })),
-        )
 
-      const subscribeDurable = (aggregateID: string) =>
-        Effect.gen(function* () {
-          const wake = yield* PubSub.sliding<void>(1)
-          const subscription = yield* PubSub.subscribe(wake)
-          yield* Effect.acquireRelease(
-            Effect.sync(() => {
-              const wakes = pubsub.durable.get(aggregateID) ?? new Set()
-              wakes.add(wake)
-              pubsub.durable.set(aggregateID, wakes)
-            }),
-            () =>
-              Effect.sync(() => {
-                const wakes = pubsub.durable.get(aggregateID)
-                wakes?.delete(wake)
-                if (wakes?.size === 0) pubsub.durable.delete(aggregateID)
-              }).pipe(Effect.andThen(PubSub.shutdown(wake))),
+        const streamLive = (): Stream.Stream<Event.Payload> => local(Stream.fromPubSub(pubsub.live))
+
+        const readAfter = (
+          aggregateID: string,
+          after: number,
+          input: { readonly through: number; readonly limit: number },
+        ) =>
+          (options?.beforeAggregateRead?.(aggregateID) ?? Effect.void).pipe(
+            Effect.andThen(
+              Effect.suspend(() => {
+                const query = db
+                  .select()
+                  .from(EventTable)
+                  .where(
+                    and(
+                      eq(EventTable.aggregate_id, aggregateID),
+                      gt(EventTable.seq, after),
+                      lte(EventTable.seq, input.through),
+                    ),
+                  )
+                  .orderBy(asc(EventTable.seq))
+                return query.limit(input.limit).all()
+              }),
+            ),
+            Effect.orDie,
+            // Skip types missing from the durable manifest instead of failing the
+            // read: the aggregate may hold events this process cannot decode. The
+            // raw tail seq keeps cursors advancing across the resulting gaps.
+            Effect.map((rows) => ({
+              seq: rows.at(-1)?.seq,
+              events: rows.flatMap((event) => {
+                if (!Durable.get(event.type)?.durable) return []
+                return [
+                  decodeSerializedEvent({
+                    id: event.id,
+                    created: DateTime.makeUnsafe(event.created),
+                    aggregateID: event.aggregate_id,
+                    seq: event.seq,
+                    type: event.type,
+                    data: event.data,
+                  }),
+                ]
+              }),
+            })),
           )
-          return subscription
-        })
 
-      const log = (input: {
-        readonly aggregateID: string
-        readonly after?: number
-        readonly follow?: boolean
-      }): Stream.Stream<LogItem> =>
-        Stream.unwrap(
+        const subscribeDurable = (aggregateID: string) =>
           Effect.gen(function* () {
-            let sequence = input.after ?? -1
-            const readThrough = (through: number): Stream.Stream<Event.Payload> =>
-              Stream.paginate(sequence, (cursor) =>
-                readAfter(input.aggregateID, cursor, { through, limit: logReadPageSize }).pipe(
-                  Effect.tap((page) =>
-                    Effect.sync(() => {
-                      sequence = page.seq ?? sequence
-                    }),
-                  ),
-                  Effect.map(
-                    (page) =>
-                      [
-                        page.events,
-                        page.seq !== undefined && page.seq < through ? Option.some(page.seq) : Option.none<number>(),
-                      ] as const,
+            const wake = yield* PubSub.sliding<void>(1)
+            const subscription = yield* PubSub.subscribe(wake)
+            yield* Effect.acquireRelease(
+              Effect.sync(() => {
+                const wakes = pubsub.durable.get(aggregateID) ?? new Set()
+                wakes.add(wake)
+                pubsub.durable.set(aggregateID, wakes)
+              }),
+              () =>
+                Effect.sync(() => {
+                  const wakes = pubsub.durable.get(aggregateID)
+                  wakes?.delete(wake)
+                  if (wakes?.size === 0) pubsub.durable.delete(aggregateID)
+                }).pipe(Effect.andThen(PubSub.shutdown(wake))),
+            )
+            return subscription
+          })
+
+        const log = (input: {
+          readonly aggregateID: string
+          readonly after?: number
+          readonly follow?: boolean
+        }): Stream.Stream<LogItem> =>
+          Stream.unwrap(
+            Effect.gen(function* () {
+              let sequence = input.after ?? -1
+              const readThrough = (through: number): Stream.Stream<Event.Payload> =>
+                Stream.paginate(sequence, (cursor) =>
+                  readAfter(input.aggregateID, cursor, { through, limit: logReadPageSize }).pipe(
+                    Effect.tap((page) =>
+                      Effect.sync(() => {
+                        sequence = page.seq ?? sequence
+                      }),
+                    ),
+                    Effect.map(
+                      (page) =>
+                        [
+                          page.events,
+                          page.seq !== undefined && page.seq < through ? Option.some(page.seq) : Option.none<number>(),
+                        ] as const,
+                    ),
                   ),
-                ),
+                )
+              // Subscribing before the historical read means events committed during
+              // replay either appear in the read or arrive through a post-marker wake.
+              const wakes = input.follow ? yield* subscribeDurable(input.aggregateID) : undefined
+              const target = yield* latestSequence(db, input.aggregateID)
+              const marker: EventLog.Synced = {
+                type: "log.synced",
+                aggregateID: input.aggregateID,
+                ...(target >= 0 ? { seq: Event.Seq.make(target) } : {}),
+              }
+              const replay: Stream.Stream<LogItem> = readThrough(target).pipe(
+                Stream.map((event): LogItem => event),
+                Stream.concat(Stream.make(marker)),
               )
-            // Subscribing before the historical read means events committed during
-            // replay either appear in the read or arrive through a post-marker wake.
-            const wakes = input.follow ? yield* subscribeDurable(input.aggregateID) : undefined
-            const target = yield* latestSequence(db, input.aggregateID)
-            const marker: EventLog.Synced = {
-              type: "log.synced",
-              aggregateID: input.aggregateID,
-              ...(target >= 0 ? { seq: Event.Seq.make(target) } : {}),
-            }
-            const replay: Stream.Stream<LogItem> = readThrough(target).pipe(
-              Stream.map((event): LogItem => event),
-              Stream.concat(Stream.make(marker)),
-            )
-            if (!wakes) return replay
-            const live: Stream.Stream<LogItem> = Stream.fromSubscription(wakes).pipe(
-              Stream.mapEffect(() => latestSequence(db, input.aggregateID)),
-              Stream.filter((target) => target > sequence),
-              Stream.flatMap((target) => readThrough(target)),
-              Stream.map((event): LogItem => event),
-            )
-            return Stream.concat(replay, live)
-          }),
-        )
+              if (!wakes) return replay
+              const live: Stream.Stream<LogItem> = Stream.fromSubscription(wakes).pipe(
+                Stream.mapEffect(() => latestSequence(db, input.aggregateID)),
+                Stream.filter((target) => target > sequence),
+                Stream.flatMap((target) => readThrough(target)),
+                Stream.map((event): LogItem => event),
+              )
+              return Stream.concat(replay, live)
+            }),
+          )
 
-      const listen = (listener: Subscriber): Effect.Effect<Unsubscribe> =>
-        Effect.sync(() => {
-          listeners.push(listener)
-          return Effect.sync(() => {
-            const index = listeners.indexOf(listener)
-            if (index >= 0) listeners.splice(index, 1)
+        const listen = (listener: Subscriber): Effect.Effect<Unsubscribe> =>
+          Effect.sync(() => {
+            listeners.push(listener)
+            return Effect.sync(() => {
+              const index = listeners.indexOf(listener)
+              if (index >= 0) listeners.splice(index, 1)
+            })
           })
-        })
 
-      const project = <D extends Event.Definition>(definition: D, projector: Subscriber<D>): Effect.Effect<void> =>
-        Effect.sync(() => {
-          const key = definition.durable ? versionedType(definition.type, definition.durable.version) : definition.type
-          const list = projectors.get(key) ?? []
-          list.push((event) => projector(event as Event.Payload<D>))
-          projectors.set(key, list)
-        })
+        const project = <D extends Event.Definition>(definition: D, projector: Subscriber<D>): Effect.Effect<void> =>
+          Effect.sync(() => {
+            const key = definition.durable
+              ? versionedType(definition.type, definition.durable.version)
+              : definition.type
+            const list = projectors.get(key) ?? []
+            list.push((event) => projector(event as Event.Payload<D>))
+            projectors.set(key, list)
+          })
 
-      return Service.of({
-        publish,
-        subscribe,
-        log,
-        listen,
-        project,
-        replay,
-        replayAll,
-        remove,
-        claim,
-      })
-    })),
+        return Service.of({
+          publish,
+          subscribe,
+          log,
+          listen,
+          project,
+          replay,
+          replayAll,
+          remove,
+          claim,
+        })
+      }),
+    ),
   })
 }
 

+ 2 - 4
packages/core/src/catalog.ts

@@ -115,8 +115,7 @@ const layer = Layer.effect(
                 }
                 draft.providers.set(providerID, record)
               }
-              const model =
-                record.models.get(modelID) ?? (Model.Info.default(providerID, modelID) as Model.MutableInfo)
+              const model = record.models.get(modelID) ?? (Model.Info.default(providerID, modelID) as Model.MutableInfo)
               if (!record.models.has(modelID)) record.models.set(modelID, model)
               fn(model)
               model.id = modelID
@@ -249,8 +248,7 @@ const layer = Layer.effect(
               items,
               Order.mapInput(
                 Order.Number,
-                (item: (typeof candidates)[number]) =>
-                  (item.cost / maxCost) * 0.8 + (item.age / maxAge) * 0.2,
+                (item: (typeof candidates)[number]) => (item.cost / maxCost) * 0.8 + (item.age / maxAge) * 0.2,
               ),
             )
             return projectModel(selected.model, provider)

+ 1 - 3
packages/core/src/codemode/catalog.ts

@@ -46,9 +46,7 @@ export function summarize(entries: ReadonlyArray<Entry>, budget = INLINE_BUDGET)
         .map((entry) => {
           const firstLine = entry.description.split("\n", 1)[0]?.trim() ?? ""
           const description =
-            firstLine.length > DESCRIPTION_LIMIT
-              ? firstLine.slice(0, DESCRIPTION_LIMIT - 3) + "..."
-              : firstLine
+            firstLine.length > DESCRIPTION_LIMIT ? firstLine.slice(0, DESCRIPTION_LIMIT - 3) + "..." : firstLine
           const suffix = description.length === 0 ? "" : ` // ${description}`
           return { path: entry.path, line: `  - ${entry.signature}${suffix}` }
         })

+ 4 - 9
packages/core/src/codemode/tool.ts

@@ -43,14 +43,9 @@ const description = [
 
 export const create = (
   registrations: ReadonlyMap<string, Info>,
-  executeTool: (
-    name: string,
-    tool: Info,
-    input: unknown,
-    context: Context,
-  ) => Effect.Effect<Result, Error>,
+  executeTool: (name: string, tool: Info, input: unknown, context: Context) => Effect.Effect<Result, Error>,
 ) => {
-  return ({
+  return {
     name: "execute",
     description,
     input: CodeMode.Input,
@@ -76,7 +71,7 @@ export const create = (
               const content =
                 typeof executed.content === "string"
                   ? [{ type: "text" as const, text: executed.content }]
-                  : executed.content ?? []
+                  : (executed.content ?? [])
               const outputFileParts = outputFiles(content)
               if (outputFileParts.length > 0)
                 yield* Ref.update(files, (items) => [...items, { index, files: outputFileParts }])
@@ -134,7 +129,7 @@ export const create = (
           metadata,
         }
       }),
-  }) satisfies Info
+  } satisfies Info
 }
 
 export const catalog = (registrations: ReadonlyMap<string, Info>) => {

+ 110 - 95
packages/core/src/command.ts

@@ -52,103 +52,108 @@ export interface Interface extends State.Transformable<Draft> {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/Command") {}
 
-export const layer = (options?: ShellSelect.Options) => Layer.effect(
-  Service,
-  Effect.gen(function* () {
-    const mcp = yield* MCP.Service
-    const bus = yield* Bus.Service
-    const processes = yield* AppProcess.Service
-    const config = yield* Config.Service
-    const location = yield* Location.Service
-    const state = State.create<Data, Draft>({
-      name: "command",
-      initial: () => ({ commands: new Map() }),
-      draft: (draft) => ({
-        list: () => Array.from(draft.commands.values()) as Info[],
-        get: (name) => draft.commands.get(name),
-        update: (name, update) => {
-          const current = draft.commands.get(name) ?? ({ name, template: "" } as Types.DeepMutable<Info>)
-          if (!draft.commands.has(name)) draft.commands.set(name, current)
-          update(current)
-          current.name = name
-        },
-        remove: (name) => {
-          draft.commands.delete(name)
-        },
-      }),
-      finalize: () => bus.publish(Command.Event.Updated, {}).pipe(Effect.asVoid),
-    })
-    const staticCommand = (name: string) => state.get().commands.get(name) as Info | undefined
-    const mcpCommands = Effect.fnUntraced(function* () {
-      return (yield* mcp.prompts()).map((prompt) =>
-        Info.make({
-          name: mcpCommandName(prompt.server, prompt.name),
-          template: "",
-          description: prompt.description,
+export const layer = (options?: ShellSelect.Options) =>
+  Layer.effect(
+    Service,
+    Effect.gen(function* () {
+      const mcp = yield* MCP.Service
+      const bus = yield* Bus.Service
+      const processes = yield* AppProcess.Service
+      const config = yield* Config.Service
+      const location = yield* Location.Service
+      const state = State.create<Data, Draft>({
+        name: "command",
+        initial: () => ({ commands: new Map() }),
+        draft: (draft) => ({
+          list: () => Array.from(draft.commands.values()) as Info[],
+          get: (name) => draft.commands.get(name),
+          update: (name, update) => {
+            const current = draft.commands.get(name) ?? ({ name, template: "" } as Types.DeepMutable<Info>)
+            if (!draft.commands.has(name)) draft.commands.set(name, current)
+            update(current)
+            current.name = name
+          },
+          remove: (name) => {
+            draft.commands.delete(name)
+          },
         }),
-      )
-    })
-
-    return Service.of({
-      reload: state.reload,
-      transform: state.transform,
-      get: Effect.fn("Command.get")(function* (name) {
-        const command = staticCommand(name)
-        if (command) return command
-        return (yield* mcpCommands()).find((command) => command.name === name)
-      }),
-      list: Effect.fn("Command.list")(function* () {
-        const commands = Array.from(state.get().commands.values()) as Info[]
-        const names = new Set(commands.map((command) => command.name))
-        return [
-          ...commands,
-          ...(yield* mcpCommands()).filter((command) => !names.has(command.name)),
-        ]
-      }),
-      evaluate: Effect.fn("Command.evaluate")(function* (input) {
-        const command = staticCommand(input.name)
-        if (command) return yield* evaluateTemplate(input.name, command.template, input.arguments ?? "", {
-          config,
-          location,
-          processes,
-          shell: options,
-        })
-
-        const prompt = (yield* mcp.prompts()).find((prompt) => mcpCommandName(prompt.server, prompt.name) === input.name)
-        if (!prompt) return yield* new NotFoundError({ command: input.name, message: `Command not found: ${input.name}` })
-        const result = yield* mcp
-          .prompt({
-            server: prompt.server,
-            name: prompt.name,
-            args: Object.fromEntries(
-              (prompt.arguments ?? []).map((argument, index) => [
-                argument.name,
-                parseArguments(input.arguments ?? "")[index] ?? "",
-              ]),
-            ),
-          })
-          .pipe(
-            Effect.catchTag(
-              "MCP.NotFoundError",
-              () =>
+        finalize: () => bus.publish(Command.Event.Updated, {}).pipe(Effect.asVoid),
+      })
+      const staticCommand = (name: string) => state.get().commands.get(name) as Info | undefined
+      const mcpCommands = Effect.fnUntraced(function* () {
+        return (yield* mcp.prompts()).map((prompt) =>
+          Info.make({
+            name: mcpCommandName(prompt.server, prompt.name),
+            template: "",
+            description: prompt.description,
+          }),
+        )
+      })
+
+      return Service.of({
+        reload: state.reload,
+        transform: state.transform,
+        get: Effect.fn("Command.get")(function* (name) {
+          const command = staticCommand(name)
+          if (command) return command
+          return (yield* mcpCommands()).find((command) => command.name === name)
+        }),
+        list: Effect.fn("Command.list")(function* () {
+          const commands = Array.from(state.get().commands.values()) as Info[]
+          const names = new Set(commands.map((command) => command.name))
+          return [...commands, ...(yield* mcpCommands()).filter((command) => !names.has(command.name))]
+        }),
+        evaluate: Effect.fn("Command.evaluate")(function* (input) {
+          const command = staticCommand(input.name)
+          if (command)
+            return yield* evaluateTemplate(input.name, command.template, input.arguments ?? "", {
+              config,
+              location,
+              processes,
+              shell: options,
+            })
+
+          const prompt = (yield* mcp.prompts()).find(
+            (prompt) => mcpCommandName(prompt.server, prompt.name) === input.name,
+          )
+          if (!prompt)
+            return yield* new NotFoundError({ command: input.name, message: `Command not found: ${input.name}` })
+          const result = yield* mcp
+            .prompt({
+              server: prompt.server,
+              name: prompt.name,
+              args: Object.fromEntries(
+                (prompt.arguments ?? []).map((argument, index) => [
+                  argument.name,
+                  parseArguments(input.arguments ?? "")[index] ?? "",
+                ]),
+              ),
+            })
+            .pipe(
+              Effect.catchTag("MCP.NotFoundError", () =>
                 Effect.fail(
                   new EvaluationError({
                     command: input.name,
                     message: `MCP server could not be found while evaluating prompt: ${prompt.server}`,
                   }),
                 ),
-            ),
-          )
-        if (!result)
-          return yield* new EvaluationError({
-            command: input.name,
-            message: `MCP prompt could not be evaluated: ${prompt.server}:${prompt.name}`,
-          })
-        return { text: result.messages.map((message) => promptMessageText(message.content)).join("\n").trim() }
-      }),
-    })
-  }),
-)
+              ),
+            )
+          if (!result)
+            return yield* new EvaluationError({
+              command: input.name,
+              message: `MCP prompt could not be evaluated: ${prompt.server}:${prompt.name}`,
+            })
+          return {
+            text: result.messages
+              .map((message) => promptMessageText(message.content))
+              .join("\n")
+              .trim(),
+          }
+        }),
+      })
+    }),
+  )
 
 function evaluateTemplate(
   command: string,
@@ -179,7 +184,8 @@ function evaluateArguments(template: string, input: string) {
     return args[argIndex]
   })
   const withArguments = expanded.replaceAll("$ARGUMENTS", input)
-  if (placeholders.length === 0 && !template.includes("$ARGUMENTS") && input.trim()) return `${withArguments}\n\n${input}`.trim()
+  if (placeholders.length === 0 && !template.includes("$ARGUMENTS") && input.trim())
+    return `${withArguments}\n\n${input}`.trim()
   return withArguments.trim()
 }
 
@@ -201,14 +207,23 @@ const evaluateShell = Effect.fnUntraced(function* (
     (match) => {
       const source = match[1] ?? ""
       return services.processes
-        .run(ChildProcess.make(shell, ShellSelect.args(shell, source), { cwd: services.location.directory, stdin: "ignore" }), {
-          combineOutput: true,
-        })
+        .run(
+          ChildProcess.make(shell, ShellSelect.args(shell, source), {
+            cwd: services.location.directory,
+            stdin: "ignore",
+          }),
+          {
+            combineOutput: true,
+          },
+        )
         .pipe(
           Effect.map((result) => (result.output ?? Buffer.concat([result.stdout, result.stderr])).toString("utf8")),
           Effect.mapError(
             (error) =>
-              new EvaluationError({ command, message: `Shell interpolation failed for ${JSON.stringify(source)}: ${error.message}` }),
+              new EvaluationError({
+                command,
+                message: `Shell interpolation failed for ${JSON.stringify(source)}: ${error.message}`,
+              }),
           ),
         )
     },

+ 262 - 257
packages/core/src/config.ts

@@ -78,287 +78,292 @@ export const testLayer = (initial: Entry[] = []) =>
     }),
   )
 
-export const layer = (options?: Options) => Layer.effect(
-  Service,
-  Effect.gen(function* () {
-    const fs = yield* FSUtil.Service
-    const global = yield* Global.Service
-    const location = yield* Location.Service
-    const watcher = yield* Watcher.Service
-    const bus = yield* Bus.Service
-    const credentials = yield* Credential.Service
-    const wellknown = yield* WellKnown.Service
-    const names = ["opencode.json", "opencode.jsonc"]
-    const reloadLock = Semaphore.makeUnsafe(1)
-    const decodeOptions = { errors: "all", onExcessProperty: "ignore", propertyOrder: "original" } as const
-    const decodeInfo = Schema.decodeUnknownOption(Info, decodeOptions)
-    const parseInfo = Effect.fn("Config.parseInfo")(function* (text: string, source: string) {
-      const errors: ParseError[] = []
-      const input: unknown = parse(text, errors, { allowTrailingComma: true })
-      if (errors.length) {
+export const layer = (options?: Options) =>
+  Layer.effect(
+    Service,
+    Effect.gen(function* () {
+      const fs = yield* FSUtil.Service
+      const global = yield* Global.Service
+      const location = yield* Location.Service
+      const watcher = yield* Watcher.Service
+      const bus = yield* Bus.Service
+      const credentials = yield* Credential.Service
+      const wellknown = yield* WellKnown.Service
+      const names = ["opencode.json", "opencode.jsonc"]
+      const reloadLock = Semaphore.makeUnsafe(1)
+      const decodeOptions = { errors: "all", onExcessProperty: "ignore", propertyOrder: "original" } as const
+      const decodeInfo = Schema.decodeUnknownOption(Info, decodeOptions)
+      const parseInfo = Effect.fn("Config.parseInfo")(function* (text: string, source: string) {
+        const errors: ParseError[] = []
+        const input: unknown = parse(text, errors, { allowTrailingComma: true })
+        if (errors.length) {
+          yield* Effect.logWarning("configuration normalization diagnostic", {
+            source,
+            path: "$",
+            kind: "invalid",
+            action: "rejected malformed JSON or JSONC document",
+          })
+          return
+        }
+        const result = ConfigNormalize.normalize(input)
+        yield* Effect.forEach(result.diagnostics, (diagnostic) =>
+          Effect.logWarning("configuration normalization diagnostic", {
+            source,
+            path: diagnostic.path[0] === "$" ? "$" : `$.${diagnostic.path.join(".")}`,
+            kind: diagnostic.kind,
+            action: diagnostic.message,
+          }),
+        )
+        if (result.type === "rejected") return
+        const info = Option.getOrUndefined(decodeInfo(result.encoded))
+        if (info) return info
         yield* Effect.logWarning("configuration normalization diagnostic", {
           source,
           path: "$",
           kind: "invalid",
-          action: "rejected malformed JSON or JSONC document",
+          action: "rejected canonical configuration after final validation",
         })
-        return
-      }
-      const result = ConfigNormalize.normalize(input)
-      yield* Effect.forEach(result.diagnostics, (diagnostic) =>
-        Effect.logWarning("configuration normalization diagnostic", {
-          source,
-          path: diagnostic.path[0] === "$" ? "$" : `$.${diagnostic.path.join(".")}`,
-          kind: diagnostic.kind,
-          action: diagnostic.message,
-        }),
-      )
-      if (result.type === "rejected") return
-      const info = Option.getOrUndefined(decodeInfo(result.encoded))
-      if (info) return info
-      yield* Effect.logWarning("configuration normalization diagnostic", {
-        source,
-        path: "$",
-        kind: "invalid",
-        action: "rejected canonical configuration after final validation",
       })
-    })
 
-    const loadFile = Effect.fnUntraced(function* (filepath: string) {
-      const text = yield* fs.readFileStringSafe(filepath)
-      if (text === undefined) return
-      const substituted = yield* ConfigVariable.substitute({ type: "path", path: filepath, text })
-      const info = yield* parseInfo(substituted, filepath)
-      if (!info) return
-      return new Document({ type: "document", path: filepath, info })
-    })
+      const loadFile = Effect.fnUntraced(function* (filepath: string) {
+        const text = yield* fs.readFileStringSafe(filepath)
+        if (text === undefined) return
+        const substituted = yield* ConfigVariable.substitute({ type: "path", path: filepath, text })
+        const info = yield* parseInfo(substituted, filepath)
+        if (!info) return
+        return new Document({ type: "document", path: filepath, info })
+      })
 
-    const loadWellknown = Effect.fn("Config.loadWellknown")(function* () {
-      const entries = yield* wellknown
-        .entries()
-        .pipe(
-          Effect.catch((error) =>
-            Effect.logWarning("failed to discover wellknown config", { error }).pipe(Effect.as([] as const)),
-          ),
-        )
-      return yield* Effect.forEach(entries, (entry) =>
-        Effect.gen(function* () {
-          const auth = entry.manifest.auth
-          if (!auth) return []
-          const credential = (yield* credentials.list(entry.integrationID)).findLast(
-            (credential) => credential.value.type === "key",
-          )
-          if (!credential || credential.value.type !== "key") return []
-          const variables = { [auth.env]: credential.value.key }
-          const configs = yield* wellknown.resolve(entry, variables).pipe(Effect.orDie)
-          return yield* Effect.forEach(configs, (config) =>
-            ConfigVariable.substitute({
-              type: "virtual",
-              source: entry.origin,
-              dir: entry.origin,
-              text: JSON.stringify(config),
-              env: variables,
-            }).pipe(
-              Effect.flatMap((text) => parseInfo(text, entry.origin)),
-              Effect.map((info) => (info ? new Document({ type: "document", info }) : undefined)),
+      const loadWellknown = Effect.fn("Config.loadWellknown")(function* () {
+        const entries = yield* wellknown
+          .entries()
+          .pipe(
+            Effect.catch((error) =>
+              Effect.logWarning("failed to discover wellknown config", { error }).pipe(Effect.as([] as const)),
             ),
-          ).pipe(Effect.map((documents) => documents.filter((document) => document !== undefined)))
-        }),
-      ).pipe(Effect.map((documents) => documents.flat()))
-    })
-
-    const loadDirectory = Effect.fnUntraced(function* (directory: AbsolutePath) {
-      return [
-        ...(yield* Effect.forEach(names, (file) => loadFile(path.join(directory, file))).pipe(
-          Effect.map((configs) => configs.filter((config): config is Document => config !== undefined)),
-        )),
-        new Directory({ type: "directory", path: directory }),
-      ]
-    })
+          )
+        return yield* Effect.forEach(entries, (entry) =>
+          Effect.gen(function* () {
+            const auth = entry.manifest.auth
+            if (!auth) return []
+            const credential = (yield* credentials.list(entry.integrationID)).findLast(
+              (credential) => credential.value.type === "key",
+            )
+            if (!credential || credential.value.type !== "key") return []
+            const variables = { [auth.env]: credential.value.key }
+            const configs = yield* wellknown.resolve(entry, variables).pipe(Effect.orDie)
+            return yield* Effect.forEach(configs, (config) =>
+              ConfigVariable.substitute({
+                type: "virtual",
+                source: entry.origin,
+                dir: entry.origin,
+                text: JSON.stringify(config),
+                env: variables,
+              }).pipe(
+                Effect.flatMap((text) => parseInfo(text, entry.origin)),
+                Effect.map((info) => (info ? new Document({ type: "document", info }) : undefined)),
+              ),
+            ).pipe(Effect.map((documents) => documents.filter((document) => document !== undefined)))
+          }),
+        ).pipe(Effect.map((documents) => documents.flat()))
+      })
 
-    const discover = Effect.fn("Config.discover")(function* () {
-      const globalDirectory = AbsolutePath.make(global.config)
-      const globalAgentsDirectory = AbsolutePath.make(path.join(global.home, ".agents"))
-      const globalClaudeDirectory = AbsolutePath.make(path.join(global.home, ".claude"))
-      const locationIsGlobal = path.resolve(location.directory) === path.resolve(global.config)
-      const discovered = locationIsGlobal || options?.project === false
-        ? []
-        : yield* fs
-            .up({
-              targets: [".opencode", ".claude", ".agents", ...names.toReversed()],
-              start: location.directory,
-            })
-            .pipe(Effect.orDie)
+      const loadDirectory = Effect.fnUntraced(function* (directory: AbsolutePath) {
+        return [
+          ...(yield* Effect.forEach(names, (file) => loadFile(path.join(directory, file))).pipe(
+            Effect.map((configs) => configs.filter((config): config is Document => config !== undefined)),
+          )),
+          new Directory({ type: "directory", path: directory }),
+        ]
+      })
 
-      // We load certain files from a few other folders in the ecosystem
-      const claude = [
-        ...new Set([
-          ...((yield* fs.isDir(globalClaudeDirectory)) ? [globalClaudeDirectory] : []),
-          ...discovered.filter((item) => path.basename(item) === ".claude"),
-        ]),
-      ].map((directory) => new ClaudeDirectory({ type: "claude", path: AbsolutePath.make(directory) }))
-      const agents = [
-        ...new Set([
-          ...((yield* fs.isDir(globalAgentsDirectory)) ? [globalAgentsDirectory] : []),
-          ...discovered.filter((item) => path.basename(item) === ".agents"),
-        ]),
-      ].map((directory) => new AgentsDirectory({ type: "agents", path: AbsolutePath.make(directory) }))
+      const discover = Effect.fn("Config.discover")(function* () {
+        const globalDirectory = AbsolutePath.make(global.config)
+        const globalAgentsDirectory = AbsolutePath.make(path.join(global.home, ".agents"))
+        const globalClaudeDirectory = AbsolutePath.make(path.join(global.home, ".claude"))
+        const locationIsGlobal = path.resolve(location.directory) === path.resolve(global.config)
+        const discovered =
+          locationIsGlobal || options?.project === false
+            ? []
+            : yield* fs
+                .up({
+                  targets: [".opencode", ".claude", ".agents", ...names.toReversed()],
+                  start: location.directory,
+                })
+                .pipe(Effect.orDie)
 
-      const directories = [
-        globalDirectory,
-        ...discovered
-          .filter((item) => path.basename(item) === ".opencode")
-          .toReversed()
-          .map((directory) => AbsolutePath.make(directory)),
-      ]
-      const directPaths = discovered
-        .filter((item) => ![".agents", ".claude", ".opencode"].includes(path.basename(item)))
-        .toReversed()
-      const direct = yield* Effect.forEach(directPaths, (filepath) =>
-        loadFile(filepath).pipe(
-          Effect.map((config) => [
-            ...(config ? [config] : []),
-            new File({ type: "file", path: AbsolutePath.make(filepath) }),
+        // We load certain files from a few other folders in the ecosystem
+        const claude = [
+          ...new Set([
+            ...((yield* fs.isDir(globalClaudeDirectory)) ? [globalClaudeDirectory] : []),
+            ...discovered.filter((item) => path.basename(item) === ".claude"),
           ]),
-        ),
-      ).pipe(
-        Effect.orDie,
-        Effect.map((entries) => entries.flat()),
-      )
+        ].map((directory) => new ClaudeDirectory({ type: "claude", path: AbsolutePath.make(directory) }))
+        const agents = [
+          ...new Set([
+            ...((yield* fs.isDir(globalAgentsDirectory)) ? [globalAgentsDirectory] : []),
+            ...discovered.filter((item) => path.basename(item) === ".agents"),
+          ]),
+        ].map((directory) => new AgentsDirectory({ type: "agents", path: AbsolutePath.make(directory) }))
 
-      const file = options?.file
-      const explicit = file
-        ? yield* loadFile(path.resolve(file)).pipe(
+        const directories = [
+          globalDirectory,
+          ...discovered
+            .filter((item) => path.basename(item) === ".opencode")
+            .toReversed()
+            .map((directory) => AbsolutePath.make(directory)),
+        ]
+        const directPaths = discovered
+          .filter((item) => ![".agents", ".claude", ".opencode"].includes(path.basename(item)))
+          .toReversed()
+        const direct = yield* Effect.forEach(directPaths, (filepath) =>
+          loadFile(filepath).pipe(
             Effect.map((config) => [
               ...(config ? [config] : []),
-              new File({ type: "file", path: AbsolutePath.make(path.resolve(file)) }),
+              new File({ type: "file", path: AbsolutePath.make(filepath) }),
             ]),
-            Effect.orDie,
-          )
-        : []
-      const content = options?.content !== undefined
-        ? yield* ConfigVariable.substitute({
-            type: "virtual",
-            source: "OPENCODE_CONFIG_CONTENT",
-            dir: location.directory,
-            text: options.content,
-          }).pipe(
-            Effect.flatMap((text) => parseInfo(text, "OPENCODE_CONFIG_CONTENT")),
-            Effect.map((info) => (info ? [new Document({ type: "document", info })] : [])),
-            Effect.orDie,
-          )
-        : []
+          ),
+        ).pipe(
+          Effect.orDie,
+          Effect.map((entries) => entries.flat()),
+        )
+
+        const file = options?.file
+        const explicit = file
+          ? yield* loadFile(path.resolve(file)).pipe(
+              Effect.map((config) => [
+                ...(config ? [config] : []),
+                new File({ type: "file", path: AbsolutePath.make(path.resolve(file)) }),
+              ]),
+              Effect.orDie,
+            )
+          : []
+        const content =
+          options?.content !== undefined
+            ? yield* ConfigVariable.substitute({
+                type: "virtual",
+                source: "OPENCODE_CONFIG_CONTENT",
+                dir: location.directory,
+                text: options.content,
+              }).pipe(
+                Effect.flatMap((text) => parseInfo(text, "OPENCODE_CONFIG_CONTENT")),
+                Effect.map((info) => (info ? [new Document({ type: "document", info })] : [])),
+                Effect.orDie,
+              )
+            : []
 
-      const supplementary = yield* Effect.forEach(directories, loadDirectory).pipe(Effect.orDie)
-      return [
-        ...(yield* loadWellknown().pipe(Effect.orDie)),
-        ...claude,
-        ...agents,
-        ...(supplementary[0] ?? []),
-        ...explicit,
-        ...direct,
-        ...supplementary.slice(1).flat(),
-        ...content,
-      ]
-    })
+        const supplementary = yield* Effect.forEach(directories, loadDirectory).pipe(Effect.orDie)
+        return [
+          ...(yield* loadWellknown().pipe(Effect.orDie)),
+          ...claude,
+          ...agents,
+          ...(supplementary[0] ?? []),
+          ...explicit,
+          ...direct,
+          ...supplementary.slice(1).flat(),
+          ...content,
+        ]
+      })
 
-    const initial = yield* discover()
-    let configs = initial
-    const updates = yield* PubSub.unbounded<Watcher.Update>()
-    // Vendored trees inside config roots (a plugin's node_modules, a nested
-    // .git) produce event blizzards that can never change discovery output.
-    const ignore = ["node_modules", ".git", "**/{node_modules,.git}/**"]
-    // Watch-once: roots leave discovery only by deletion, so a stale watch is
-    // inert, bounded, and dies with this layer — and keeping a deleted root's
-    // watch alive is exactly what makes its recreation observable.
-    const watched = new Set<string>()
-    const reconcile = Effect.fn("Config.reconcileWatches")(function* (entries: readonly Entry[]) {
-      const directories = entries.flatMap((entry) => (entry.type === "directory" ? [entry.path] : []))
-      const files = entries.flatMap((entry) => (entry.type === "file" ? [entry.path] : []))
-      const targets = [
-        ...directories.map((path) => ({ path, type: "directory" as const, ignore })),
-        ...files
-          .filter((file) => !directories.some((directory) => FSUtil.contains(directory, file)))
-          .map((path) => ({ path, type: "file" as const })),
-      ]
-      for (const target of targets) {
-        const key = JSON.stringify(target)
-        if (watched.has(key)) continue
-        watched.add(key)
-        const stream = yield* watcher.subscribe(target)
-        yield* stream.pipe(
-          Stream.runForEach((update) => PubSub.publish(updates, update)),
-          Effect.forkScoped({ startImmediately: true }),
-        )
-      }
-    })
+      const initial = yield* discover()
+      let configs = initial
+      const updates = yield* PubSub.unbounded<Watcher.Update>()
+      // Vendored trees inside config roots (a plugin's node_modules, a nested
+      // .git) produce event blizzards that can never change discovery output.
+      const ignore = ["node_modules", ".git", "**/{node_modules,.git}/**"]
+      // Watch-once: roots leave discovery only by deletion, so a stale watch is
+      // inert, bounded, and dies with this layer — and keeping a deleted root's
+      // watch alive is exactly what makes its recreation observable.
+      const watched = new Set<string>()
+      const reconcile = Effect.fn("Config.reconcileWatches")(function* (entries: readonly Entry[]) {
+        const directories = entries.flatMap((entry) => (entry.type === "directory" ? [entry.path] : []))
+        const files = entries.flatMap((entry) => (entry.type === "file" ? [entry.path] : []))
+        const targets = [
+          ...directories.map((path) => ({ path, type: "directory" as const, ignore })),
+          ...files
+            .filter((file) => !directories.some((directory) => FSUtil.contains(directory, file)))
+            .map((path) => ({ path, type: "file" as const })),
+        ]
+        for (const target of targets) {
+          const key = JSON.stringify(target)
+          if (watched.has(key)) continue
+          watched.add(key)
+          const stream = yield* watcher.subscribe(target)
+          yield* stream.pipe(
+            Stream.runForEach((update) => PubSub.publish(updates, update)),
+            Effect.forkScoped({ startImmediately: true }),
+          )
+        }
+      })
 
-    const reload = Effect.fn("Config.reload")(() =>
-      reloadLock.withPermit(
-        Effect.gen(function* () {
-          const next = yield* discover()
-          if (isDeepStrictEqual(configs, next)) return
-          configs = next
-          yield* reconcile(next)
-          yield* bus.publish(Event.Updated, {})
-        }),
-      ),
-    )
+      const reload = Effect.fn("Config.reload")(() =>
+        reloadLock.withPermit(
+          Effect.gen(function* () {
+            const next = yield* discover()
+            if (isDeepStrictEqual(configs, next)) return
+            configs = next
+            yield* reconcile(next)
+            yield* bus.publish(Event.Updated, {})
+          }),
+        ),
+      )
 
-    yield* Stream.fromPubSub(updates).pipe(
-      Stream.debounce("100 millis"),
-      Stream.runForEach((update) =>
-        reload().pipe(
-          Effect.catchCause((cause) => Effect.logError("failed to reload config", { path: update.path, cause })),
+      yield* Stream.fromPubSub(updates).pipe(
+        Stream.debounce("100 millis"),
+        Stream.runForEach((update) =>
+          reload().pipe(
+            Effect.catchCause((cause) => Effect.logError("failed to reload config", { path: update.path, cause })),
+          ),
         ),
-      ),
-      Effect.forkScoped({ startImmediately: true }),
-    )
-    yield* bus.subscribe(Integration.Event.ConnectionUpdated).pipe(
-      Stream.filterEffect((event) =>
-        wellknown.entries().pipe(
-          Effect.map((entries) => entries.some((entry) => entry.integrationID === event.data.integrationID)),
-          Effect.catch(() => Effect.succeed(false)),
+        Effect.forkScoped({ startImmediately: true }),
+      )
+      yield* bus.subscribe(Integration.Event.ConnectionUpdated).pipe(
+        Stream.filterEffect((event) =>
+          wellknown.entries().pipe(
+            Effect.map((entries) => entries.some((entry) => entry.integrationID === event.data.integrationID)),
+            Effect.catch(() => Effect.succeed(false)),
+          ),
         ),
-      ),
-      Stream.runForEach(() =>
-        reload().pipe(Effect.catchCause((cause) => Effect.logError("failed to reload wellknown config", { cause }))),
-      ),
-      Effect.forkScoped({ startImmediately: true }),
-    )
-    yield* bus.subscribe(WellKnown.Event.Updated).pipe(
-      Stream.runForEach(() =>
-        reload().pipe(Effect.catchCause((cause) => Effect.logError("failed to reload wellknown sources", { cause }))),
-      ),
-      Effect.forkScoped({ startImmediately: true }),
-    )
-    yield* Effect.sleep("10 minutes").pipe(
-      Effect.andThen(
-        Effect.suspend(() => {
-          if (!wellknown.snapshot().length) return Effect.void
-          return Effect.gen(function* () {
-            const changed = yield* wellknown.refresh().pipe(
-              Effect.catch((error) =>
-                Effect.logWarning("failed to refresh wellknown manifests", { error }).pipe(Effect.as(false)),
-              ),
-            )
-            if (!changed) yield* reload()
-          }).pipe(Effect.catchCause((cause) => Effect.logWarning("failed to refresh wellknown config", { cause })))
-        }),
-      ),
-      Effect.forever,
-      Effect.forkScoped({ startImmediately: true }),
-    )
-    yield* reconcile(initial)
+        Stream.runForEach(() =>
+          reload().pipe(Effect.catchCause((cause) => Effect.logError("failed to reload wellknown config", { cause }))),
+        ),
+        Effect.forkScoped({ startImmediately: true }),
+      )
+      yield* bus.subscribe(WellKnown.Event.Updated).pipe(
+        Stream.runForEach(() =>
+          reload().pipe(Effect.catchCause((cause) => Effect.logError("failed to reload wellknown sources", { cause }))),
+        ),
+        Effect.forkScoped({ startImmediately: true }),
+      )
+      yield* Effect.sleep("10 minutes").pipe(
+        Effect.andThen(
+          Effect.suspend(() => {
+            if (!wellknown.snapshot().length) return Effect.void
+            return Effect.gen(function* () {
+              const changed = yield* wellknown
+                .refresh()
+                .pipe(
+                  Effect.catch((error) =>
+                    Effect.logWarning("failed to refresh wellknown manifests", { error }).pipe(Effect.as(false)),
+                  ),
+                )
+              if (!changed) yield* reload()
+            }).pipe(Effect.catchCause((cause) => Effect.logWarning("failed to refresh wellknown config", { cause })))
+          }),
+        ),
+        Effect.forever,
+        Effect.forkScoped({ startImmediately: true }),
+      )
+      yield* reconcile(initial)
 
-    return Service.of({
-      entries: Effect.fn("Config.entries")(function* () {
-        return configs
-      }),
-      changes: () => Stream.fromPubSub(updates),
-    })
-  }),
-)
+      return Service.of({
+        entries: Effect.fn("Config.entries")(function* () {
+          return configs
+        }),
+        changes: () => Stream.fromPubSub(updates),
+      })
+    }),
+  )
 
 export function configured(options?: Options) {
   return makeLocationNode({

+ 1 - 3
packages/core/src/config/plugin/agent.ts

@@ -63,9 +63,7 @@ export const Plugin = define({
               Effect.catch(() => Effect.succeed(undefined)),
             ),
           ).pipe(
-            Effect.map((documents) =>
-              documents.filter((document): document is Document => document !== undefined),
-            ),
+            Effect.map((documents) => documents.filter((document): document is Document => document !== undefined)),
           )
         })
       }).pipe(Effect.map((documents) => documents.flat()))

+ 13 - 9
packages/core/src/database/v1-migration.ts

@@ -92,9 +92,7 @@ type Options = {
   readonly nextDatabasePath?: string
 }
 
-type MigrationState =
-  | { readonly phase: "sessions"; readonly cursor?: string }
-  | { readonly phase: "completed" }
+type MigrationState = { readonly phase: "sessions"; readonly cursor?: string } | { readonly phase: "completed" }
 
 type RuntimeState =
   | { readonly status: "idle" }
@@ -487,7 +485,10 @@ export function run(options: Options = {}): Effect.Effect<RunResult, never, Data
             .transaction((tx) =>
               Effect.gen(function* () {
                 yield* tx.delete(EventTable).run()
-                yield* tx.insert(KVTable).values({ key: MIGRATION_STATE_KEY, value: { phase: "sessions" } }).run()
+                yield* tx
+                  .insert(KVTable)
+                  .values({ key: MIGRATION_STATE_KEY, value: { phase: "sessions" } })
+                  .run()
               }),
             )
             .pipe(Effect.orDie)
@@ -505,7 +506,9 @@ export function run(options: Options = {}): Effect.Effect<RunResult, never, Data
           updateProgress({ label: "Migrating sessions", numerator: migrated + completed, denominator })
         })
         updateProgress({ label: "Migrating sessions", numerator: migrated + sourceTotal, denominator })
-        const projects = new Set((yield* db.all<{ id: string }>(sql`SELECT id FROM project`)).map((project) => project.id))
+        const projects = new Set(
+          (yield* db.all<{ id: string }>(sql`SELECT id FROM project`)).map((project) => project.id),
+        )
         while (true) {
           const state = yield* readState(db)
           const cursorValue = state?.phase === "sessions" ? state.cursor : undefined
@@ -594,7 +597,7 @@ export function run(options: Options = {}): Effect.Effect<RunResult, never, Data
                 numerator: (runtimeState.progress.numerator ?? 0) + 1,
                 denominator,
               },
-          }
+            }
           yield* Effect.yieldNow
         }
         yield* db
@@ -681,9 +684,10 @@ function importNextDatabase(
           })
         }
         const messages = source
-          .query<NextMessage, [string]>(
-            "SELECT id, session_id, type, seq, time_created, time_updated, data FROM session_message WHERE session_id = ? ORDER BY seq",
-          )
+          .query<
+            NextMessage,
+            [string]
+          >("SELECT id, session_id, type, seq, time_created, time_updated, data FROM session_message WHERE session_id = ? ORDER BY seq")
           .all(session.id)
         yield* db
           .transaction((tx) =>

+ 1 - 6
packages/core/src/event-logger.ts

@@ -4,12 +4,7 @@ import { Effect, Layer } from "effect"
 import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
 import { Bus } from "./bus"
 
-const Types = new Set([
-  "agent.updated",
-  "catalog.updated",
-  "command.updated",
-  "config.updated",
-])
+const Types = new Set(["agent.updated", "catalog.updated", "command.updated", "config.updated"])
 
 export const layer = Layer.effectDiscard(
   Effect.gen(function* () {

+ 1 - 3
packages/core/src/filesystem/watcher.ts

@@ -92,9 +92,7 @@ export const layer = (options?: Options) =>
       const watchers = yield* RcMap.make({
         lookup: (key: Key) =>
           Effect.gen(function* () {
-            const pubsub = yield* Effect.acquireRelease(PubSub.unbounded<Update>(), (pubsub) =>
-              PubSub.shutdown(pubsub),
-            )
+            const pubsub = yield* Effect.acquireRelease(PubSub.unbounded<Update>(), (pubsub) => PubSub.shutdown(pubsub))
             const subscription = yield* Effect.acquireRelease(
               native.subscribe({
                 type: key.type,

+ 5 - 1
packages/core/src/form.ts

@@ -183,7 +183,11 @@ export const layer = Layer.effect(
           const invalid = validateAnswer(entry.form, input.answer)
           if (invalid) return yield* new InvalidAnswerError({ id: input.id, message: invalid })
           const next: TerminalState = { status: "answered", answer: input.answer }
-          yield* bus.publish(Form.Event.Replied, { id: input.id, sessionID: entry.form.sessionID, answer: input.answer })
+          yield* bus.publish(Form.Event.Replied, {
+            id: input.id,
+            sessionID: entry.form.sessionID,
+            answer: input.answer,
+          })
           yield* Cache.set(forms, input.id, { ...entry, state: next })
           yield* Deferred.succeed(entry.deferred, next)
         }),

+ 1 - 3
packages/core/src/formatter.ts

@@ -77,9 +77,7 @@ const layer = Layer.effect(
 
     const file = Effect.fn("Formatter.file")(function* (filepath: string) {
       yield* load
-      const matching = formatters.filter((formatter) =>
-        formatter.extensions.includes(path.extname(filepath)),
-      )
+      const matching = formatters.filter((formatter) => formatter.extensions.includes(path.extname(filepath)))
 
       for (const formatter of matching) {
         const enabled = yield* command(formatter)

+ 1 - 3
packages/core/src/formatter/builtins.ts

@@ -213,9 +213,7 @@ export function make(input: {
       const bin = which("uv")
       if (!bin) return disabled
       const output = yield* commandOutput([bin, "format", "--help"])
-      return output._tag === "Some" && output.value.exitCode === 0
-        ? [bin, "format", "--", "$FILE"]
-        : disabled
+      return output._tag === "Some" && output.value.exitCode === 0 ? [bin, "format", "--", "$FILE"] : disabled
     }),
   }
 

+ 65 - 63
packages/core/src/instruction-discovery.ts

@@ -28,74 +28,76 @@ export type Options = typeof Options.Type
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/InstructionDiscovery") {}
 
-export const layer = (options?: Options) => Layer.effect(
-  Service,
-  Effect.gen(function* () {
-    const fs = yield* FSUtil.Service
-    const global = yield* Global.Service
-    const location = yield* Location.Service
+export const layer = (options?: Options) =>
+  Layer.effect(
+    Service,
+    Effect.gen(function* () {
+      const fs = yield* FSUtil.Service
+      const global = yield* Global.Service
+      const location = yield* Location.Service
 
-    const source = (value: ReadonlyArray<File> | Instructions.Unavailable | Instructions.Removed) =>
-      Instructions.make<ReadonlyArray<File>>({
-        key,
-        codec: Schema.toCodecJson(Files),
-        read: Effect.succeed(value),
-        render: {
-          initial: render,
-          changed: (_previous, current) =>
-            `These instructions replace all previously loaded ambient instructions.\n\n${render(current)}`,
-          removed: () => "Previously loaded instructions no longer apply.",
-        },
-      })
+      const source = (value: ReadonlyArray<File> | Instructions.Unavailable | Instructions.Removed) =>
+        Instructions.make<ReadonlyArray<File>>({
+          key,
+          codec: Schema.toCodecJson(Files),
+          read: Effect.succeed(value),
+          render: {
+            initial: render,
+            changed: (_previous, current) =>
+              `These instructions replace all previously loaded ambient instructions.\n\n${render(current)}`,
+            removed: () => "Previously loaded instructions no longer apply.",
+          },
+        })
 
-    const observe = Effect.fn("InstructionDiscovery.observe")(function* () {
-      const start = yield* fs.resolve(location.directory)
-      const stop = yield* fs.resolve(location.project.directory)
-      const fromProject = relative(stop, start)
-      const insideProject =
-        fromProject === "" || (fromProject !== ".." && !fromProject.startsWith(`..${sep}`) && !isAbsolute(fromProject))
-      const discovered = new Set(
-        yield* Effect.forEach(
-          options?.project === false || !insideProject
-            ? []
-            : yield* fs.up({
-                targets: ["AGENTS.md"],
-                start,
-                stop,
-              }),
-          fs.resolve,
-        ),
-      )
-      const paths = Array.dedupe([yield* fs.resolve(join(global.config, "AGENTS.md")), ...discovered])
-      const files = yield* Effect.forEach(
-        paths,
-        (path) =>
-          fs
-            .readFileStringSafe(path)
-            .pipe(
-              Effect.map((content) =>
-                content === undefined ? undefined : new File({ path: AbsolutePath.make(path), content }),
+      const observe = Effect.fn("InstructionDiscovery.observe")(function* () {
+        const start = yield* fs.resolve(location.directory)
+        const stop = yield* fs.resolve(location.project.directory)
+        const fromProject = relative(stop, start)
+        const insideProject =
+          fromProject === "" ||
+          (fromProject !== ".." && !fromProject.startsWith(`..${sep}`) && !isAbsolute(fromProject))
+        const discovered = new Set(
+          yield* Effect.forEach(
+            options?.project === false || !insideProject
+              ? []
+              : yield* fs.up({
+                  targets: ["AGENTS.md"],
+                  start,
+                  stop,
+                }),
+            fs.resolve,
+          ),
+        )
+        const paths = Array.dedupe([yield* fs.resolve(join(global.config, "AGENTS.md")), ...discovered])
+        const files = yield* Effect.forEach(
+          paths,
+          (path) =>
+            fs
+              .readFileStringSafe(path)
+              .pipe(
+                Effect.map((content) =>
+                  content === undefined ? undefined : new File({ path: AbsolutePath.make(path), content }),
+                ),
               ),
-            ),
-        { concurrency: "unbounded" },
-      )
-      if (files.some((file, index) => file === undefined && discovered.has(paths[index])))
-        return Instructions.unavailable
-      return files.filter((file): file is File => file !== undefined)
-    })
+          { concurrency: "unbounded" },
+        )
+        if (files.some((file, index) => file === undefined && discovered.has(paths[index])))
+          return Instructions.unavailable
+        return files.filter((file): file is File => file !== undefined)
+      })
 
-    return Service.of({
-      load: () =>
-        observe().pipe(
-          Effect.map((files) =>
-            Array.isArray(files) && files.length === 0 ? source(Instructions.removed) : source(files),
+      return Service.of({
+        load: () =>
+          observe().pipe(
+            Effect.map((files) =>
+              Array.isArray(files) && files.length === 0 ? source(Instructions.removed) : source(files),
+            ),
+            Effect.catch(() => Effect.succeed(source(Instructions.unavailable))),
+            Effect.catchDefect(() => Effect.succeed(source(Instructions.unavailable))),
           ),
-          Effect.catch(() => Effect.succeed(source(Instructions.unavailable))),
-          Effect.catchDefect(() => Effect.succeed(source(Instructions.unavailable))),
-        ),
-    })
-  }),
-)
+      })
+    }),
+  )
 
 export function configured(options?: Options) {
   return makeLocationNode({

+ 1 - 3
packages/core/src/instructions/builtins.ts

@@ -39,9 +39,7 @@ const layer = Layer.effect(
               ),
               render: {
                 initial: (environment) =>
-                  ["Here is some useful information about the environment you are running in:", environment].join(
-                    "\n",
-                  ),
+                  ["Here is some useful information about the environment you are running in:", environment].join("\n"),
                 changed: (_previous, environment) =>
                   ["The environment you are running in is now:", environment].join("\n"),
               },

+ 6 - 9
packages/core/src/mcp/client.ts

@@ -209,16 +209,13 @@ export const connect = Effect.fnUntraced(function* (
       authProvider,
     })
   })
-  const client = new Client(
-    clientInfo,
-    {
-      capabilities: {
-        ...(elicitation ? { elicitation: { form: { applyDefaults: true }, url: {} } } : {}),
-        // https://github.com/anomalyco/opencode/issues/2308
-        roots: {},
-      },
+  const client = new Client(clientInfo, {
+    capabilities: {
+      ...(elicitation ? { elicitation: { form: { applyDefaults: true }, url: {} } } : {}),
+      // https://github.com/anomalyco/opencode/issues/2308
+      roots: {},
     },
-  )
+  })
   client.setRequestHandler(ListRootsRequestSchema, () =>
     Promise.resolve({ roots: [{ uri: pathToFileURL(directory).href }] }),
   )

+ 570 - 571
packages/core/src/mcp/index.ts

@@ -166,616 +166,615 @@ export const Options = Schema.Struct({
 })
 export type Options = typeof Options.Type
 
-export const layer = (options?: Options) => Layer.effect(
-  Service,
-  Effect.gen(function* () {
-    const config = yield* Config.Service
-    const location = yield* Location.Service
-    const bus = yield* Bus.Service
-    const forms = yield* Form.Service
-    const integration = yield* Integration.Service
-    const credentials = yield* Credential.Service
-    const root = yield* Scope.make()
-    const fork = yield* FiberSet.makeRuntime<never, void, never>()
-    yield* Effect.addFinalizer((exit) => Scope.close(root, exit))
-
-    const documents = (yield* config.entries()).filter((entry): entry is Document => entry.type === "document")
-    // Global MCP timeout defaults, later config files overriding earlier ones.
-    const timeout = Object.assign(
-      {},
-      ...documents.flatMap((entry) => (entry.info.mcp?.timeout ? [entry.info.mcp.timeout] : [])),
-    )
-    // Later config files win for duplicate server names; per-server timeout overrides globals.
-    const runtime = new Map<ServerName, ServerEntry>()
-    // Serializes lifecycle operations per server. Anything taking this lock from a connection
-    // callback must stay forked: lifecycle operations close scopes while holding it, firing onClose.
-    const locks = KeyedMutex.makeUnsafe<ServerName>()
-    const urlElicitations = new Map<string, Form.ID>()
-    for (const entry of documents) {
-      for (const [name, server] of Object.entries(entry.info.mcp?.servers ?? {})) {
-        runtime.set(ServerName.make(name), {
-          config: { ...server, timeout: { ...timeout, ...server.timeout } },
-          status: { status: "pending" },
-          startup: Deferred.makeUnsafe<void>(),
-        })
-      }
-    }
-
-    // Register every remote server as an OAuth integration so credentials live in the global store
-    // rather than in committed config. Servers that connect anonymously simply never use the method.
-    const owned = new Set<Integration.ID>()
-    const register = Effect.fnUntraced(function* (name: ServerName, entry: ServerEntry) {
-      if (entry.config.type !== "remote" || entry.config.oauth === false) return
-      const remote = entry.config
-      // Key identity on name + url, not url alone: two configs for the same url under different names are
-      // distinct logical servers that may hold different accounts, so they must not share a credential row.
-      const suffix =
-        "mcp_" +
-        createHash("sha1")
-          .update(name + "\u0000" + remote.url)
-          .digest("hex")
-          .slice(0, 16)
-      const integrationID = Integration.ID.make(suffix)
-      entry.integrationID = integrationID
-      owned.add(integrationID)
-      const methodID = Integration.MethodID.make(suffix)
-      // Each registration gets its own child scope so disposal detaches it from the root scope
-      // entirely; registering directly on root would accumulate a dead finalizer per replaced or
-      // removed server for the lifetime of the layer.
-      const scope = yield* Scope.fork(root)
-      entry.registration = { dispose: Scope.close(scope, Exit.void) }
-      yield* integration
-        .transform((draft) => {
-          draft.update(integrationID, (ref) => {
-            ref.name = name
-          })
-          draft.method.update({
-            integrationID,
-            method: { id: methodID, type: "oauth", label: name },
-            authorize: () => MCPOAuth.authorize({ name, config: remote, methodID }),
+export const layer = (options?: Options) =>
+  Layer.effect(
+    Service,
+    Effect.gen(function* () {
+      const config = yield* Config.Service
+      const location = yield* Location.Service
+      const bus = yield* Bus.Service
+      const forms = yield* Form.Service
+      const integration = yield* Integration.Service
+      const credentials = yield* Credential.Service
+      const root = yield* Scope.make()
+      const fork = yield* FiberSet.makeRuntime<never, void, never>()
+      yield* Effect.addFinalizer((exit) => Scope.close(root, exit))
+
+      const documents = (yield* config.entries()).filter((entry): entry is Document => entry.type === "document")
+      // Global MCP timeout defaults, later config files overriding earlier ones.
+      const timeout = Object.assign(
+        {},
+        ...documents.flatMap((entry) => (entry.info.mcp?.timeout ? [entry.info.mcp.timeout] : [])),
+      )
+      // Later config files win for duplicate server names; per-server timeout overrides globals.
+      const runtime = new Map<ServerName, ServerEntry>()
+      // Serializes lifecycle operations per server. Anything taking this lock from a connection
+      // callback must stay forked: lifecycle operations close scopes while holding it, firing onClose.
+      const locks = KeyedMutex.makeUnsafe<ServerName>()
+      const urlElicitations = new Map<string, Form.ID>()
+      for (const entry of documents) {
+        for (const [name, server] of Object.entries(entry.info.mcp?.servers ?? {})) {
+          runtime.set(ServerName.make(name), {
+            config: { ...server, timeout: { ...timeout, ...server.timeout } },
+            status: { status: "pending" },
+            startup: Deferred.makeUnsafe<void>(),
           })
-        })
-        .pipe(Scope.provide(scope))
-    })
-    yield* Effect.forEach(runtime, ([name, entry]) => register(name, entry), { discard: true })
-
-    const requireServer = Effect.fnUntraced(function* (server: ServerName | string) {
-      const name = ServerName.make(server)
-      const entry = runtime.get(name)
-      if (!entry) return yield* new NotFoundError({ server: name })
-      return { name, entry }
-    })
-
-    // Builds the connect-time auth provider for a remote OAuth-integration server. The SDK presents and
-    // refreshes stored tokens, persisting refreshes back to the same credential row. The provider never
-    // opens a browser, so an auth-gated connect ends in UnauthorizedError -> needs_auth rather than a redirect.
-    const connectProvider = Effect.fnUntraced(function* (entry: ServerEntry) {
-      if (entry.config.type !== "remote" || !entry.integrationID) return undefined
-      const remote = entry.config
-      const oauth = remote.oauth || undefined
-      const base = {
-        redirectUrl: oauth?.redirect_uri ?? "http://127.0.0.1/callback",
-        scope: oauth?.scope,
-        client: oauth?.client_id ? { id: oauth.client_id, secret: oauth.client_secret } : undefined,
-        // No browser during connect: an auth-gated server surfaces needs_auth instead of opening a browser.
-        onRedirect: () => {},
+        }
       }
-      const stored = yield* credentials.list(entry.integrationID)
-      const found = stored.find((credential) => credential.value.type === "oauth")
-      if (!found || found.value.type !== "oauth")
-        // No stored credential yet: an empty in-memory store still lets the SDK run the auth handshake, which
-        // ends in UnauthorizedError -> needs_auth. Returning no provider instead would let the transport throw
-        // a raw HTTP error, hiding the auth requirement behind a generic failed status. Anonymous servers are
-        // unaffected: tokens() returns undefined, so no auth header is sent and the SDK never calls auth().
-        return MCPOAuth.provider({ ...base, store: MCPOAuth.memoryStore() })
-      const credentialID = found.id
-      const methodID = found.value.methodID
-      let current: Credential.OAuth | undefined = found.value
-      return MCPOAuth.provider({
-        ...base,
-        // Drop a credential the SDK rejected so the next connect cleanly reports needs_auth. Uses the raw
-        // credential service (no integration event) to avoid re-triggering the reconnect subscriber mid-connect.
-        invalidate: async (scope) => {
-          if (scope === "verifier" || scope === "discovery") return
-          current = undefined
-          await Effect.runPromise(credentials.remove(credentialID))
-        },
-        store: {
-          tokens: async () => (current ? MCPOAuth.toTokens(current) : undefined),
-          saveTokens: async (tokens) => {
-            current = MCPOAuth.toCredential({
-              methodID,
-              serverUrl: remote.url,
-              tokens,
-              client: current ? MCPOAuth.clientFromCredential(current) : undefined,
+
+      // Register every remote server as an OAuth integration so credentials live in the global store
+      // rather than in committed config. Servers that connect anonymously simply never use the method.
+      const owned = new Set<Integration.ID>()
+      const register = Effect.fnUntraced(function* (name: ServerName, entry: ServerEntry) {
+        if (entry.config.type !== "remote" || entry.config.oauth === false) return
+        const remote = entry.config
+        // Key identity on name + url, not url alone: two configs for the same url under different names are
+        // distinct logical servers that may hold different accounts, so they must not share a credential row.
+        const suffix =
+          "mcp_" +
+          createHash("sha1")
+            .update(name + "\u0000" + remote.url)
+            .digest("hex")
+            .slice(0, 16)
+        const integrationID = Integration.ID.make(suffix)
+        entry.integrationID = integrationID
+        owned.add(integrationID)
+        const methodID = Integration.MethodID.make(suffix)
+        // Each registration gets its own child scope so disposal detaches it from the root scope
+        // entirely; registering directly on root would accumulate a dead finalizer per replaced or
+        // removed server for the lifetime of the layer.
+        const scope = yield* Scope.fork(root)
+        entry.registration = { dispose: Scope.close(scope, Exit.void) }
+        yield* integration
+          .transform((draft) => {
+            draft.update(integrationID, (ref) => {
+              ref.name = name
+            })
+            draft.method.update({
+              integrationID,
+              method: { id: methodID, type: "oauth", label: name },
+              authorize: () => MCPOAuth.authorize({ name, config: remote, methodID }),
             })
-            await Effect.runPromise(credentials.update(credentialID, { value: current }))
+          })
+          .pipe(Scope.provide(scope))
+      })
+      yield* Effect.forEach(runtime, ([name, entry]) => register(name, entry), { discard: true })
+
+      const requireServer = Effect.fnUntraced(function* (server: ServerName | string) {
+        const name = ServerName.make(server)
+        const entry = runtime.get(name)
+        if (!entry) return yield* new NotFoundError({ server: name })
+        return { name, entry }
+      })
+
+      // Builds the connect-time auth provider for a remote OAuth-integration server. The SDK presents and
+      // refreshes stored tokens, persisting refreshes back to the same credential row. The provider never
+      // opens a browser, so an auth-gated connect ends in UnauthorizedError -> needs_auth rather than a redirect.
+      const connectProvider = Effect.fnUntraced(function* (entry: ServerEntry) {
+        if (entry.config.type !== "remote" || !entry.integrationID) return undefined
+        const remote = entry.config
+        const oauth = remote.oauth || undefined
+        const base = {
+          redirectUrl: oauth?.redirect_uri ?? "http://127.0.0.1/callback",
+          scope: oauth?.scope,
+          client: oauth?.client_id ? { id: oauth.client_id, secret: oauth.client_secret } : undefined,
+          // No browser during connect: an auth-gated server surfaces needs_auth instead of opening a browser.
+          onRedirect: () => {},
+        }
+        const stored = yield* credentials.list(entry.integrationID)
+        const found = stored.find((credential) => credential.value.type === "oauth")
+        if (!found || found.value.type !== "oauth")
+          // No stored credential yet: an empty in-memory store still lets the SDK run the auth handshake, which
+          // ends in UnauthorizedError -> needs_auth. Returning no provider instead would let the transport throw
+          // a raw HTTP error, hiding the auth requirement behind a generic failed status. Anonymous servers are
+          // unaffected: tokens() returns undefined, so no auth header is sent and the SDK never calls auth().
+          return MCPOAuth.provider({ ...base, store: MCPOAuth.memoryStore() })
+        const credentialID = found.id
+        const methodID = found.value.methodID
+        let current: Credential.OAuth | undefined = found.value
+        return MCPOAuth.provider({
+          ...base,
+          // Drop a credential the SDK rejected so the next connect cleanly reports needs_auth. Uses the raw
+          // credential service (no integration event) to avoid re-triggering the reconnect subscriber mid-connect.
+          invalidate: async (scope) => {
+            if (scope === "verifier" || scope === "discovery") return
+            current = undefined
+            await Effect.runPromise(credentials.remove(credentialID))
           },
-          clientInformation: async () => (current ? MCPOAuth.clientFromCredential(current) : undefined),
-          saveClientInformation: async () => {},
-          codeVerifier: async () => undefined,
-          saveCodeVerifier: async () => {},
-        },
+          store: {
+            tokens: async () => (current ? MCPOAuth.toTokens(current) : undefined),
+            saveTokens: async (tokens) => {
+              current = MCPOAuth.toCredential({
+                methodID,
+                serverUrl: remote.url,
+                tokens,
+                client: current ? MCPOAuth.clientFromCredential(current) : undefined,
+              })
+              await Effect.runPromise(credentials.update(credentialID, { value: current }))
+            },
+            clientInformation: async () => (current ? MCPOAuth.clientFromCredential(current) : undefined),
+            saveClientInformation: async () => {},
+            codeVerifier: async () => undefined,
+            saveCodeVerifier: async () => {},
+          },
+        })
       })
-    })
-
-    const elicitation = {
-      create: (input: {
-        readonly server: string
-        readonly params: MCPClient.ElicitationParams
-        readonly signal: AbortSignal
-      }) =>
-        Effect.gen(function* () {
-          if (input.params.mode === "url") {
-            const formID = Form.ID.create()
-            const key = input.server + "\u0000" + input.params.elicitationId
-            urlElicitations.set(key, formID)
+
+      const elicitation = {
+        create: (input: {
+          readonly server: string
+          readonly params: MCPClient.ElicitationParams
+          readonly signal: AbortSignal
+        }) =>
+          Effect.gen(function* () {
+            if (input.params.mode === "url") {
+              const formID = Form.ID.create()
+              const key = input.server + "\u0000" + input.params.elicitationId
+              urlElicitations.set(key, formID)
+              return yield* forms
+                .ask({
+                  id: formID,
+                  sessionID: GLOBAL_ELICITATION_SESSION_ID,
+                  title: `${input.server} is requesting input`,
+                  metadata: {
+                    kind: "mcp-elicitation",
+                    server: input.server,
+                    elicitationID: input.params.elicitationId,
+                    message: input.params.message,
+                  },
+                  fields: [{ key: URL_ELICITATION_FIELD_KEY, type: "external", url: input.params.url }],
+                })
+                .pipe(
+                  Effect.raceFirst(waitForAbort(input.signal)),
+                  Effect.ensuring(Effect.sync(() => urlElicitations.delete(key))),
+                  Effect.map(
+                    (state): MCPClient.ElicitationResult => ({
+                      action: state.status === "answered" ? "accept" : "cancel",
+                    }),
+                  ),
+                )
+            }
+            const params = input.params
+            const [field, ...fields] = Object.entries(params.requestedSchema.properties).map(([key, property]) =>
+              toElicitationField(key, property, params.requestedSchema.required?.includes(key) === true),
+            )
+            if (!field) return { action: "accept", content: {} }
             return yield* forms
               .ask({
-                id: formID,
                 sessionID: GLOBAL_ELICITATION_SESSION_ID,
                 title: `${input.server} is requesting input`,
-                metadata: {
-                  kind: "mcp-elicitation",
-                  server: input.server,
-                  elicitationID: input.params.elicitationId,
-                  message: input.params.message,
-                },
-                fields: [{ key: URL_ELICITATION_FIELD_KEY, type: "external", url: input.params.url }],
+                metadata: { kind: "mcp-elicitation", server: input.server, message: params.message },
+                fields: [field, ...fields],
               })
               .pipe(
                 Effect.raceFirst(waitForAbort(input.signal)),
-                Effect.ensuring(Effect.sync(() => urlElicitations.delete(key))),
-                Effect.map(
-                  (state): MCPClient.ElicitationResult => ({
-                    action: state.status === "answered" ? "accept" : "cancel",
-                  }),
-                ),
-              )
-          }
-          const params = input.params
-          const [field, ...fields] = Object.entries(params.requestedSchema.properties).map(([key, property]) =>
-            toElicitationField(key, property, params.requestedSchema.required?.includes(key) === true),
-          )
-          if (!field) return { action: "accept", content: {} }
-          return yield* forms
-            .ask({
-              sessionID: GLOBAL_ELICITATION_SESSION_ID,
-              title: `${input.server} is requesting input`,
-              metadata: { kind: "mcp-elicitation", server: input.server, message: params.message },
-              fields: [field, ...fields],
-            })
-            .pipe(
-              Effect.raceFirst(waitForAbort(input.signal)),
-              Effect.map((state): MCPClient.ElicitationResult => {
-                if (state.status !== "answered") return { action: "cancel" }
-                return {
-                  action: "accept",
-                  content: Object.fromEntries(
-                    Object.entries(state.answer).map(
-                      ([key, value]): [string, NonNullable<MCPClient.ElicitationResult["content"]>[string]] =>
-                        typeof value === "object" ? [key, Array.from(value)] : [key, value],
+                Effect.map((state): MCPClient.ElicitationResult => {
+                  if (state.status !== "answered") return { action: "cancel" }
+                  return {
+                    action: "accept",
+                    content: Object.fromEntries(
+                      Object.entries(state.answer).map(
+                        ([key, value]): [string, NonNullable<MCPClient.ElicitationResult["content"]>[string]] =>
+                          typeof value === "object" ? [key, Array.from(value)] : [key, value],
+                      ),
                     ),
-                  ),
-                }
+                  }
+                }),
+              )
+          }),
+        complete: (input: { readonly server: string; readonly elicitationID: string }) =>
+          Effect.gen(function* () {
+            const formID = urlElicitations.get(input.server + "\u0000" + input.elicitationID)
+            if (!formID) return
+            yield* forms.reply({ id: formID, answer: { [URL_ELICITATION_FIELD_KEY]: true } }).pipe(Effect.ignore)
+          }),
+      } satisfies MCPClient.ElicitationHandler
+
+      const toTool = (server: ServerName, entry: ServerEntry, def: MCPClient.ToolDefinition) =>
+        new Tool({
+          server,
+          name: def.name,
+          codemode: entry.config.codemode,
+          description: def.description,
+          inputSchema: def.inputSchema,
+          outputSchema: def.outputSchema,
+        })
+
+      const toPrompt = (server: ServerName, def: MCPClient.PromptDefinition) =>
+        new Prompt({
+          server,
+          name: def.name,
+          description: def.description,
+          arguments: def.arguments?.map(
+            (argument) =>
+              new PromptArgument({
+                name: argument.name,
+                description: argument.description,
+                required: argument.required,
               }),
-            )
-        }),
-      complete: (input: { readonly server: string; readonly elicitationID: string }) =>
-        Effect.gen(function* () {
-          const formID = urlElicitations.get(input.server + "\u0000" + input.elicitationID)
-          if (!formID) return
-          yield* forms.reply({ id: formID, answer: { [URL_ELICITATION_FIELD_KEY]: true } }).pipe(Effect.ignore)
-        }),
-    } satisfies MCPClient.ElicitationHandler
-
-    const toTool = (server: ServerName, entry: ServerEntry, def: MCPClient.ToolDefinition) =>
-      new Tool({
-        server,
-        name: def.name,
-        codemode: entry.config.codemode,
-        description: def.description,
-        inputSchema: def.inputSchema,
-        outputSchema: def.outputSchema,
-      })
+          ),
+        })
 
-    const toPrompt = (server: ServerName, def: MCPClient.PromptDefinition) =>
-      new Prompt({
-        server,
-        name: def.name,
-        description: def.description,
-        arguments: def.arguments?.map(
-          (argument) =>
-            new PromptArgument({
-              name: argument.name,
-              description: argument.description,
-              required: argument.required,
-            }),
-        ),
-      })
+      const toResource = (server: ServerName, def: MCPClient.ResourceDefinition) =>
+        Resource.make({
+          server,
+          name: def.name,
+          uri: def.uri,
+          description: def.description,
+          mimeType: def.mimeType,
+        })
 
-    const toResource = (server: ServerName, def: MCPClient.ResourceDefinition) =>
-      Resource.make({
-        server,
-        name: def.name,
-        uri: def.uri,
-        description: def.description,
-        mimeType: def.mimeType,
-      })
+      const toResourceTemplate = (server: ServerName, def: MCPClient.ResourceTemplateDefinition) =>
+        ResourceTemplate.make({
+          server,
+          name: def.name,
+          uriTemplate: def.uriTemplate,
+          description: def.description,
+          mimeType: def.mimeType,
+        })
 
-    const toResourceTemplate = (server: ServerName, def: MCPClient.ResourceTemplateDefinition) =>
-      ResourceTemplate.make({
-        server,
-        name: def.name,
-        uriTemplate: def.uriTemplate,
-        description: def.description,
-        mimeType: def.mimeType,
-      })
+      const refreshTools = (name: ServerName, entry: ServerEntry, connection: MCPClient.Connection) =>
+        connection.tools().pipe(
+          Effect.map((defs) => {
+            entry.tools = defs.map((def) => toTool(name, entry, def))
+          }),
+        )
 
-    const refreshTools = (name: ServerName, entry: ServerEntry, connection: MCPClient.Connection) =>
-      connection.tools().pipe(
-        Effect.map((defs) => {
-          entry.tools = defs.map((def) => toTool(name, entry, def))
-        }),
-      )
+      const refreshPrompts = (name: ServerName, entry: ServerEntry, connection: MCPClient.Connection) =>
+        connection.prompts().pipe(
+          Effect.map((defs) => {
+            entry.prompts = defs.map((def) => toPrompt(name, def))
+          }),
+          Effect.andThen(bus.publish(Command.Event.Updated, {})),
+          Effect.catch(() =>
+            Effect.sync(() => (entry.prompts = [])).pipe(Effect.andThen(bus.publish(Command.Event.Updated, {}))),
+          ),
+        )
 
-    const refreshPrompts = (name: ServerName, entry: ServerEntry, connection: MCPClient.Connection) =>
-      connection.prompts().pipe(
-        Effect.map((defs) => {
-          entry.prompts = defs.map((def) => toPrompt(name, def))
-        }),
-        Effect.andThen(bus.publish(Command.Event.Updated, {})),
-        Effect.catch(() =>
-          Effect.sync(() => (entry.prompts = [])).pipe(Effect.andThen(bus.publish(Command.Event.Updated, {}))),
-        ),
-      )
+      // Runs a connection callback under the server lock, dropping it if the connection is no longer
+      // the entry's live client, so late SDK callbacks cannot commit obsolete state.
+      const whenLive =
+        (name: ServerName, entry: ServerEntry, connection: MCPClient.Connection) =>
+        <E>(effect: Effect.Effect<void, E>) =>
+          fork(
+            Effect.suspend(() => (entry.client === connection ? effect : Effect.void)).pipe(
+              locks.withLock(name),
+              Effect.ignore,
+            ),
+          )
 
-    // Runs a connection callback under the server lock, dropping it if the connection is no longer
-    // the entry's live client, so late SDK callbacks cannot commit obsolete state.
-    const whenLive =
-      (name: ServerName, entry: ServerEntry, connection: MCPClient.Connection) =>
-      <E>(effect: Effect.Effect<void, E>) =>
-        fork(
-          Effect.suspend(() => (entry.client === connection ? effect : Effect.void)).pipe(
-            locks.withLock(name),
-            Effect.ignore,
+      const watch = (name: ServerName, entry: ServerEntry, connection: MCPClient.Connection) => {
+        const live = whenLive(name, entry, connection)
+        connection.onClose(() =>
+          live(
+            Effect.gen(function* () {
+              entry.client = undefined
+              entry.tools = undefined
+              entry.prompts = undefined
+              entry.status = { status: "failed", error: "Connection closed" }
+              yield* bus.publish(McpEvent.ToolsChanged, { server: name }).pipe(Effect.ignore)
+              yield* bus.publish(McpEvent.ResourcesChanged, { server: name }).pipe(Effect.ignore)
+              yield* bus.publish(Command.Event.Updated, {}).pipe(Effect.ignore)
+              yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
+            }),
+          ),
+        )
+        connection.onLog((message) => fork(serverLog(name, message).pipe(Effect.ignore)))
+        connection.onToolsChanged(() =>
+          live(
+            refreshTools(name, entry, connection).pipe(
+              Effect.andThen(bus.publish(McpEvent.ToolsChanged, { server: name })),
+            ),
           ),
         )
+        connection.onPromptsChanged(() => live(refreshPrompts(name, entry, connection)))
+        connection.onResourcesChanged(() => live(bus.publish(McpEvent.ResourcesChanged, { server: name })))
+      }
 
-    const watch = (name: ServerName, entry: ServerEntry, connection: MCPClient.Connection) => {
-      const live = whenLive(name, entry, connection)
-      connection.onClose(() =>
-        live(
-          Effect.gen(function* () {
-            entry.client = undefined
-            entry.tools = undefined
-            entry.prompts = undefined
-            entry.status = { status: "failed", error: "Connection closed" }
+      const serverLog = (server: ServerName, message: MCPClient.LogMessage) => {
+        const fields = { server, logger: message.logger, level: message.level, data: message.data }
+        switch (message.level) {
+          case "debug":
+            return Effect.logDebug("MCP server log", fields)
+          case "info":
+          case "notice":
+            return Effect.logInfo("MCP server log", fields)
+          case "warning":
+            return Effect.logWarning("MCP server log", fields)
+          case "error":
+          case "critical":
+          case "alert":
+          case "emergency":
+            return Effect.logError("MCP server log", fields)
+        }
+      }
+
+      const startServer = (name: ServerName, entry: ServerEntry) =>
+        Effect.gen(function* () {
+          // Announce the handshake so connect() and credential reconnects don't show a stale
+          // disabled/failed status for the duration of the connection attempt.
+          entry.status = { status: "pending" }
+          yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
+          const scope = yield* Scope.fork(root)
+          entry.scope = scope
+          const authProvider = yield* connectProvider(entry)
+          // List tools as part of connect so a failure here marks the server failed rather than
+          // leaving it connected with a silently empty tool list and no path to recover.
+          const result = yield* MCPClient.connect(
+            name,
+            entry.config,
+            location.directory,
+            authProvider,
+            elicitation,
+            options?.clientInfo,
+          ).pipe(
+            Effect.flatMap((connection) => connection.tools().pipe(Effect.map((tools) => ({ connection, tools })))),
+            Scope.provide(scope),
+            Effect.exit,
+          )
+          if (Exit.isSuccess(result)) {
+            entry.client = result.value.connection
+            entry.tools = result.value.tools.map((def) => toTool(name, entry, def))
+            entry.prompts = []
+            entry.status = { status: "connected" }
+            watch(name, entry, result.value.connection)
+            yield* Effect.logInfo("mcp connected", { server: name, tools: entry.tools.length })
+            // Announce the new tool set so the tool registry registers it. A server that finishes connecting
+            // after the initial registration sweep and emits no list-changed notification would otherwise
+            // stay invisible to the model.
             yield* bus.publish(McpEvent.ToolsChanged, { server: name }).pipe(Effect.ignore)
             yield* bus.publish(McpEvent.ResourcesChanged, { server: name }).pipe(Effect.ignore)
-            yield* bus.publish(Command.Event.Updated, {}).pipe(Effect.ignore)
             yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
-          }),
-        ),
-      )
-      connection.onLog((message) => fork(serverLog(name, message).pipe(Effect.ignore)))
-      connection.onToolsChanged(() =>
-        live(
-          refreshTools(name, entry, connection).pipe(
-            Effect.andThen(bus.publish(McpEvent.ToolsChanged, { server: name })),
-          ),
-        ),
-      )
-      connection.onPromptsChanged(() => live(refreshPrompts(name, entry, connection)))
-      connection.onResourcesChanged(() => live(bus.publish(McpEvent.ResourcesChanged, { server: name })))
-    }
+            whenLive(name, entry, result.value.connection)(refreshPrompts(name, entry, result.value.connection))
+            return
+          }
+          yield* Scope.close(scope, Exit.void)
+          entry.scope = undefined
+          const error = Cause.squash(result.cause)
+          entry.status =
+            error instanceof MCPClient.NeedsAuthError
+              ? { status: "needs_auth" }
+              : { status: "failed", error: error instanceof Error ? error.message : String(error) }
+          yield* Effect.logWarning("mcp connect failed", { server: name, status: entry.status })
+          yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
+        }).pipe(Effect.ensuring(Deferred.succeed(entry.startup, undefined)))
 
-    const serverLog = (server: ServerName, message: MCPClient.LogMessage) => {
-      const fields = { server, logger: message.logger, level: message.level, data: message.data }
-      switch (message.level) {
-        case "debug":
-          return Effect.logDebug("MCP server log", fields)
-        case "info":
-        case "notice":
-          return Effect.logInfo("MCP server log", fields)
-        case "warning":
-          return Effect.logWarning("MCP server log", fields)
-        case "error":
-        case "critical":
-        case "alert":
-        case "emergency":
-          return Effect.logError("MCP server log", fields)
-      }
-    }
+      const stopServer = Effect.fnUntraced(function* (name: ServerName, entry: ServerEntry) {
+        const scope = entry.scope
+        if (!scope) return
+        entry.scope = undefined
+        entry.client = undefined
+        entry.tools = undefined
+        entry.prompts = undefined
+        yield* Scope.close(scope, Exit.void)
+        yield* bus.publish(McpEvent.ToolsChanged, { server: name }).pipe(Effect.ignore)
+        yield* bus.publish(McpEvent.ResourcesChanged, { server: name }).pipe(Effect.ignore)
+        yield* bus.publish(Command.Event.Updated, {}).pipe(Effect.ignore)
+      })
 
-    const startServer = (name: ServerName, entry: ServerEntry) =>
-      Effect.gen(function* () {
-        // Announce the handshake so connect() and credential reconnects don't show a stale
-        // disabled/failed status for the duration of the connection attempt.
-        entry.status = { status: "pending" }
-        yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
-        const scope = yield* Scope.fork(root)
-        entry.scope = scope
-        const authProvider = yield* connectProvider(entry)
-        // List tools as part of connect so a failure here marks the server failed rather than
-        // leaving it connected with a silently empty tool list and no path to recover.
-        const result = yield* MCPClient.connect(
-          name,
-          entry.config,
-          location.directory,
-          authProvider,
-          elicitation,
-          options?.clientInfo,
-        ).pipe(
-          Effect.flatMap((connection) => connection.tools().pipe(Effect.map((tools) => ({ connection, tools })))),
-          Scope.provide(scope),
-          Effect.exit,
-        )
-        if (Exit.isSuccess(result)) {
-          entry.client = result.value.connection
-          entry.tools = result.value.tools.map((def) => toTool(name, entry, def))
-          entry.prompts = []
-          entry.status = { status: "connected" }
-          watch(name, entry, result.value.connection)
-          yield* Effect.logInfo("mcp connected", { server: name, tools: entry.tools.length })
-          // Announce the new tool set so the tool registry registers it. A server that finishes connecting
-          // after the initial registration sweep and emits no list-changed notification would otherwise
-          // stay invisible to the model.
-          yield* bus.publish(McpEvent.ToolsChanged, { server: name }).pipe(Effect.ignore)
-          yield* bus.publish(McpEvent.ResourcesChanged, { server: name }).pipe(Effect.ignore)
-          yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
-          whenLive(name, entry, result.value.connection)(refreshPrompts(name, entry, result.value.connection))
-          return
+      // Disabled servers settle their startup immediately so queries never block on them.
+      for (const [name, entry] of runtime) {
+        if (entry.config.disabled) {
+          entry.status = { status: "disabled" }
+          Deferred.doneUnsafe(entry.startup, Exit.void)
+          continue
         }
-        yield* Scope.close(scope, Exit.void)
-        entry.scope = undefined
-        const error = Cause.squash(result.cause)
-        entry.status =
-          error instanceof MCPClient.NeedsAuthError
-            ? { status: "needs_auth" }
-            : { status: "failed", error: error instanceof Error ? error.message : String(error) }
-        yield* Effect.logWarning("mcp connect failed", { server: name, status: entry.status })
-        yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
-      }).pipe(Effect.ensuring(Deferred.succeed(entry.startup, undefined)))
-
-    const stopServer = Effect.fnUntraced(function* (name: ServerName, entry: ServerEntry) {
-      const scope = entry.scope
-      if (!scope) return
-      entry.scope = undefined
-      entry.client = undefined
-      entry.tools = undefined
-      entry.prompts = undefined
-      yield* Scope.close(scope, Exit.void)
-      yield* bus.publish(McpEvent.ToolsChanged, { server: name }).pipe(Effect.ignore)
-      yield* bus.publish(McpEvent.ResourcesChanged, { server: name }).pipe(Effect.ignore)
-      yield* bus.publish(Command.Event.Updated, {}).pipe(Effect.ignore)
-    })
-
-    // Disabled servers settle their startup immediately so queries never block on them.
-    for (const [name, entry] of runtime) {
-      if (entry.config.disabled) {
-        entry.status = { status: "disabled" }
-        Deferred.doneUnsafe(entry.startup, Exit.void)
-        continue
+        fork(startServer(name, entry).pipe(locks.withLock(name)))
       }
-      fork(startServer(name, entry).pipe(locks.withLock(name)))
-    }
 
-    // Bring a server online (or back to needs_auth) when its integration's credential changes, so an
-    // OAuth login takes effect without a restart. Only fires for the integrations we registered.
-    const reconnect = (integrationID: Integration.ID) =>
-      Effect.gen(function* () {
-        const match = Array.from(runtime).find(([, entry]) => entry.integrationID === integrationID)
-        if (!match) return
-        const name = match[0]
-        yield* Effect.gen(function* () {
-          // add() or remove() may have replaced or deleted the entry while we waited for the lock.
-          const entry = runtime.get(name)
-          if (!entry || entry.integrationID !== integrationID) return
-          if (entry.status.status === "disabled") return
-          yield* stopServer(name, entry)
-          yield* startServer(name, entry)
-        }).pipe(locks.withLock(name))
-      })
-    fork(
-      bus.subscribe(Integration.Event.ConnectionUpdated).pipe(
-        Stream.filter((event) => owned.has(event.data.integrationID)),
-        Stream.runForEach((event) => Effect.sync(() => fork(reconnect(event.data.integrationID)))),
-        Effect.ignore,
-      ),
-    )
-
-    // Suspend so each await sees current entries; a bare Map iterator is exhausted after one run.
-    const whenAllReady = Effect.suspend(() =>
-      Effect.forEach(Array.from(runtime.values()), (entry) => Deferred.await(entry.startup), {
-        concurrency: "unbounded",
-        discard: true,
-      }),
-    )
-    return Service.of({
-      servers: Effect.fn("MCP.servers")(function* () {
-        return Array.from(runtime)
-          .toSorted(([a], [b]) => a.localeCompare(b))
-          .map(
-            ([name, entry]) =>
-              new ServerInfo({ name, status: entry.status, integrationID: entry.integrationID }),
-          )
-      }),
-      add: Effect.fn("MCP.add")(function* (server, config) {
-        const name = ServerName.make(server)
-        yield* Effect.gen(function* () {
-          const previous = runtime.get(name)
-          if (previous) {
-            yield* stopServer(name, previous)
-            if (previous.integrationID) owned.delete(previous.integrationID)
-            if (previous.registration) yield* previous.registration.dispose
-          }
-          const entry: ServerEntry = {
-            config: { ...config, timeout: { ...timeout, ...config.timeout } },
-            status: { status: "pending" },
-            startup: Deferred.makeUnsafe<void>(),
-          }
-          runtime.set(name, entry)
+      // Bring a server online (or back to needs_auth) when its integration's credential changes, so an
+      // OAuth login takes effect without a restart. Only fires for the integrations we registered.
+      const reconnect = (integrationID: Integration.ID) =>
+        Effect.gen(function* () {
+          const match = Array.from(runtime).find(([, entry]) => entry.integrationID === integrationID)
+          if (!match) return
+          const name = match[0]
           yield* Effect.gen(function* () {
-            yield* register(name, entry)
-            if (config.disabled) {
-              entry.status = { status: "disabled" }
-              yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
-              return
-            }
+            // add() or remove() may have replaced or deleted the entry while we waited for the lock.
+            const entry = runtime.get(name)
+            if (!entry || entry.integrationID !== integrationID) return
+            if (entry.status.status === "disabled") return
+            yield* stopServer(name, entry)
             yield* startServer(name, entry)
-          }).pipe(
-            // Settle startup even when register fails or add is interrupted, so an entry that made it
-            // into runtime can never hang readers awaiting its startup.
-            Effect.ensuring(Effect.sync(() => Deferred.doneUnsafe(entry.startup, Exit.void))),
-          )
-        }).pipe(locks.withLock(name))
-      }),
-      connect: Effect.fn("MCP.connect")(function* (server) {
-        const name = ServerName.make(server)
-        yield* Effect.gen(function* () {
-          const target = yield* requireServer(name)
-          yield* stopServer(name, target.entry)
-          yield* startServer(name, target.entry)
-        }).pipe(locks.withLock(name))
-      }),
-      disconnect: Effect.fn("MCP.disconnect")(function* (server) {
-        const name = ServerName.make(server)
-        yield* Effect.gen(function* () {
-          const target = yield* requireServer(name)
-          yield* stopServer(name, target.entry)
-          target.entry.status = { status: "disabled" }
-          yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
-        }).pipe(locks.withLock(name))
-      }),
-      remove: Effect.fn("MCP.remove")(function* (server) {
-        const name = ServerName.make(server)
-        yield* Effect.gen(function* () {
-          const target = yield* requireServer(name)
-          yield* stopServer(name, target.entry)
-          if (target.entry.integrationID) owned.delete(target.entry.integrationID)
-          if (target.entry.registration) yield* target.entry.registration.dispose
-          // Credentials are kept: they are keyed by name + url, so re-adding the same server
-          // reuses them without forcing re-auth, matching add()'s replacement semantics.
-          runtime.delete(name)
-          yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
-        }).pipe(locks.withLock(name))
-      }),
-      tools: Effect.fn("MCP.tools")(function* () {
-        yield* whenAllReady
-        return Array.from(runtime.values())
-          .flatMap((entry) => entry.tools ?? [])
-          .toSorted((a, b) => a.server.localeCompare(b.server) || a.name.localeCompare(b.name))
-      }),
-      callTool: Effect.fn("MCP.callTool")(function* (input) {
-        const target = yield* requireServer(input.server)
-        yield* Deferred.await(target.entry.startup)
-        if (!target.entry.client)
-          return yield* new ToolCallError({
+          }).pipe(locks.withLock(name))
+        })
+      fork(
+        bus.subscribe(Integration.Event.ConnectionUpdated).pipe(
+          Stream.filter((event) => owned.has(event.data.integrationID)),
+          Stream.runForEach((event) => Effect.sync(() => fork(reconnect(event.data.integrationID)))),
+          Effect.ignore,
+        ),
+      )
+
+      // Suspend so each await sees current entries; a bare Map iterator is exhausted after one run.
+      const whenAllReady = Effect.suspend(() =>
+        Effect.forEach(Array.from(runtime.values()), (entry) => Deferred.await(entry.startup), {
+          concurrency: "unbounded",
+          discard: true,
+        }),
+      )
+      return Service.of({
+        servers: Effect.fn("MCP.servers")(function* () {
+          return Array.from(runtime)
+            .toSorted(([a], [b]) => a.localeCompare(b))
+            .map(([name, entry]) => new ServerInfo({ name, status: entry.status, integrationID: entry.integrationID }))
+        }),
+        add: Effect.fn("MCP.add")(function* (server, config) {
+          const name = ServerName.make(server)
+          yield* Effect.gen(function* () {
+            const previous = runtime.get(name)
+            if (previous) {
+              yield* stopServer(name, previous)
+              if (previous.integrationID) owned.delete(previous.integrationID)
+              if (previous.registration) yield* previous.registration.dispose
+            }
+            const entry: ServerEntry = {
+              config: { ...config, timeout: { ...timeout, ...config.timeout } },
+              status: { status: "pending" },
+              startup: Deferred.makeUnsafe<void>(),
+            }
+            runtime.set(name, entry)
+            yield* Effect.gen(function* () {
+              yield* register(name, entry)
+              if (config.disabled) {
+                entry.status = { status: "disabled" }
+                yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
+                return
+              }
+              yield* startServer(name, entry)
+            }).pipe(
+              // Settle startup even when register fails or add is interrupted, so an entry that made it
+              // into runtime can never hang readers awaiting its startup.
+              Effect.ensuring(Effect.sync(() => Deferred.doneUnsafe(entry.startup, Exit.void))),
+            )
+          }).pipe(locks.withLock(name))
+        }),
+        connect: Effect.fn("MCP.connect")(function* (server) {
+          const name = ServerName.make(server)
+          yield* Effect.gen(function* () {
+            const target = yield* requireServer(name)
+            yield* stopServer(name, target.entry)
+            yield* startServer(name, target.entry)
+          }).pipe(locks.withLock(name))
+        }),
+        disconnect: Effect.fn("MCP.disconnect")(function* (server) {
+          const name = ServerName.make(server)
+          yield* Effect.gen(function* () {
+            const target = yield* requireServer(name)
+            yield* stopServer(name, target.entry)
+            target.entry.status = { status: "disabled" }
+            yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
+          }).pipe(locks.withLock(name))
+        }),
+        remove: Effect.fn("MCP.remove")(function* (server) {
+          const name = ServerName.make(server)
+          yield* Effect.gen(function* () {
+            const target = yield* requireServer(name)
+            yield* stopServer(name, target.entry)
+            if (target.entry.integrationID) owned.delete(target.entry.integrationID)
+            if (target.entry.registration) yield* target.entry.registration.dispose
+            // Credentials are kept: they are keyed by name + url, so re-adding the same server
+            // reuses them without forcing re-auth, matching add()'s replacement semantics.
+            runtime.delete(name)
+            yield* bus.publish(McpEvent.StatusChanged, { server: name }).pipe(Effect.ignore)
+          }).pipe(locks.withLock(name))
+        }),
+        tools: Effect.fn("MCP.tools")(function* () {
+          yield* whenAllReady
+          return Array.from(runtime.values())
+            .flatMap((entry) => entry.tools ?? [])
+            .toSorted((a, b) => a.server.localeCompare(b.server) || a.name.localeCompare(b.name))
+        }),
+        callTool: Effect.fn("MCP.callTool")(function* (input) {
+          const target = yield* requireServer(input.server)
+          yield* Deferred.await(target.entry.startup)
+          if (!target.entry.client)
+            return yield* new ToolCallError({
+              server: target.name,
+              tool: input.name,
+              message: "MCP server is not connected",
+            })
+          const result = yield* target.entry.client
+            .callTool({ name: input.name, args: input.args })
+            .pipe(
+              Effect.mapError(
+                (error) => new ToolCallError({ server: target.name, tool: input.name, message: error.message }),
+              ),
+            )
+          return new ToolResult({
             server: target.name,
             tool: input.name,
-            message: "MCP server is not connected",
+            isError: result.isError,
+            structured: result.structured,
+            content: result.content,
           })
-        const result = yield* target.entry.client
-          .callTool({ name: input.name, args: input.args })
-          .pipe(
-            Effect.mapError(
-              (error) => new ToolCallError({ server: target.name, tool: input.name, message: error.message }),
+        }),
+        instructions: Effect.fn("MCP.instructions")(function* () {
+          yield* whenAllReady
+          return Array.from(runtime)
+            .flatMap(([server, entry]) => {
+              const instructions = entry.client?.instructions
+              if (!instructions) return []
+              return [new ServerInstructions({ server, instructions })]
+            })
+            .toSorted((a, b) => a.server.localeCompare(b.server))
+        }),
+        prompts: Effect.fn("MCP.prompts")(function* () {
+          return Array.from(runtime.values())
+            .flatMap((entry) => entry.prompts ?? [])
+            .toSorted((a, b) => a.server.localeCompare(b.server) || a.name.localeCompare(b.name))
+        }),
+        prompt: Effect.fn("MCP.prompt")(function* (input) {
+          const target = yield* requireServer(input.server)
+          yield* Deferred.await(target.entry.startup)
+          if (!target.entry.client) return undefined
+          const result = yield* target.entry.client
+            .prompt({ name: input.name, args: input.args })
+            .pipe(Effect.catch(() => Effect.succeed(undefined)))
+          if (!result) return undefined
+          return new PromptResult({
+            server: target.name,
+            name: input.name,
+            messages: result.messages.map(
+              (message) => new PromptMessage({ role: message.role, content: message.content }),
             ),
+          })
+        }),
+        resourceCatalog: Effect.fn("MCP.resourceCatalog")(function* () {
+          yield* whenAllReady
+          const catalogs = yield* Effect.forEach(
+            Array.from(runtime),
+            ([name, entry]) => {
+              if (!entry.client) return Effect.succeed({ resources: [], templates: [] })
+              return Effect.all(
+                {
+                  resources: entry.client.resources().pipe(Effect.catch(() => Effect.succeed([]))),
+                  templates: entry.client.resourceTemplates().pipe(Effect.catch(() => Effect.succeed([]))),
+                },
+                { concurrency: "unbounded" },
+              ).pipe(
+                Effect.map((catalog) => ({
+                  resources: catalog.resources.map((def) => toResource(name, def)),
+                  templates: catalog.templates.map((def) => toResourceTemplate(name, def)),
+                })),
+              )
+            },
+            { concurrency: "unbounded" },
           )
-        return new ToolResult({
-          server: target.name,
-          tool: input.name,
-          isError: result.isError,
-          structured: result.structured,
-          content: result.content,
-        })
-      }),
-      instructions: Effect.fn("MCP.instructions")(function* () {
-        yield* whenAllReady
-        return Array.from(runtime)
-          .flatMap(([server, entry]) => {
-            const instructions = entry.client?.instructions
-            if (!instructions) return []
-            return [new ServerInstructions({ server, instructions })]
+          return ResourceCatalog.make({
+            resources: catalogs
+              .flatMap((catalog) => catalog.resources)
+              .toSorted(
+                (a, b) =>
+                  a.server.localeCompare(b.server) || a.name.localeCompare(b.name) || a.uri.localeCompare(b.uri),
+              ),
+            templates: catalogs
+              .flatMap((catalog) => catalog.templates)
+              .toSorted(
+                (a, b) =>
+                  a.server.localeCompare(b.server) ||
+                  a.name.localeCompare(b.name) ||
+                  a.uriTemplate.localeCompare(b.uriTemplate),
+              ),
           })
-          .toSorted((a, b) => a.server.localeCompare(b.server))
-      }),
-      prompts: Effect.fn("MCP.prompts")(function* () {
-        return Array.from(runtime.values())
-          .flatMap((entry) => entry.prompts ?? [])
-          .toSorted((a, b) => a.server.localeCompare(b.server) || a.name.localeCompare(b.name))
-      }),
-      prompt: Effect.fn("MCP.prompt")(function* (input) {
-        const target = yield* requireServer(input.server)
-        yield* Deferred.await(target.entry.startup)
-        if (!target.entry.client) return undefined
-        const result = yield* target.entry.client
-          .prompt({ name: input.name, args: input.args })
-          .pipe(Effect.catch(() => Effect.succeed(undefined)))
-        if (!result) return undefined
-        return new PromptResult({
-          server: target.name,
-          name: input.name,
-          messages: result.messages.map(
-            (message) => new PromptMessage({ role: message.role, content: message.content }),
-          ),
-        })
-      }),
-      resourceCatalog: Effect.fn("MCP.resourceCatalog")(function* () {
-        yield* whenAllReady
-        const catalogs = yield* Effect.forEach(
-          Array.from(runtime),
-          ([name, entry]) => {
-            if (!entry.client) return Effect.succeed({ resources: [], templates: [] })
-            return Effect.all(
-              {
-                resources: entry.client.resources().pipe(Effect.catch(() => Effect.succeed([]))),
-                templates: entry.client.resourceTemplates().pipe(Effect.catch(() => Effect.succeed([]))),
-              },
-              { concurrency: "unbounded" },
-            ).pipe(
-              Effect.map((catalog) => ({
-                resources: catalog.resources.map((def) => toResource(name, def)),
-                templates: catalog.templates.map((def) => toResourceTemplate(name, def)),
-              })),
-            )
-          },
-          { concurrency: "unbounded" },
-        )
-        return ResourceCatalog.make({
-          resources: catalogs
-            .flatMap((catalog) => catalog.resources)
-            .toSorted(
-              (a, b) => a.server.localeCompare(b.server) || a.name.localeCompare(b.name) || a.uri.localeCompare(b.uri),
-            ),
-          templates: catalogs
-            .flatMap((catalog) => catalog.templates)
-            .toSorted(
-              (a, b) =>
-                a.server.localeCompare(b.server) ||
-                a.name.localeCompare(b.name) ||
-                a.uriTemplate.localeCompare(b.uriTemplate),
-            ),
-        })
-      }),
-      readResource: Effect.fn("MCP.readResource")(function* (input) {
-        const target = yield* requireServer(input.server)
-        yield* Deferred.await(target.entry.startup)
-        if (!target.entry.client) return undefined
-        const result = yield* target.entry.client
-          .readResource({ uri: input.uri })
-          .pipe(Effect.catch(() => Effect.succeed(undefined)))
-        if (!result) return undefined
-        return ResourceContent.make({
-          server: target.name,
-          uri: input.uri,
-          contents: result.contents,
-        })
-      }),
-    })
-  }),
-)
+        }),
+        readResource: Effect.fn("MCP.readResource")(function* (input) {
+          const target = yield* requireServer(input.server)
+          yield* Deferred.await(target.entry.startup)
+          if (!target.entry.client) return undefined
+          const result = yield* target.entry.client
+            .readResource({ uri: input.uri })
+            .pipe(Effect.catch(() => Effect.succeed(undefined)))
+          if (!result) return undefined
+          return ResourceContent.make({
+            server: target.name,
+            uri: input.uri,
+            contents: result.contents,
+          })
+        }),
+      })
+    }),
+  )
 
 export function configured(options?: Options) {
   return makeLocationNode({

+ 11 - 3
packages/core/src/mcp/oauth.ts

@@ -160,7 +160,9 @@ export const authorize = (input: {
       }
       const fail = (reason: string) => {
         Effect.runFork(Deferred.fail(code, new Error(reason)))
-        response.writeHead(400, { "Content-Type": "text/html" }).end(OauthCallbackPage.error(reason, { provider: input.name }))
+        response
+          .writeHead(400, { "Content-Type": "text/html" })
+          .end(OauthCallbackPage.error(reason, { provider: input.name }))
       }
       const error = url.searchParams.get("error_description") ?? url.searchParams.get("error")
       if (error) return fail(error)
@@ -216,7 +218,12 @@ export const authorize = (input: {
 
     // The provider may already hold valid tokens (e.g. a re-auth), in which case there is no browser step.
     if (result === "AUTHORIZED") {
-      return { url: input.config.url, instructions: `Connected to ${input.name}.`, mode: "auto" as const, callback: finalize }
+      return {
+        url: input.config.url,
+        instructions: `Connected to ${input.name}.`,
+        mode: "auto" as const,
+        callback: finalize,
+      }
     }
     if (!authorizationUrl)
       return yield* Effect.fail(new Error(`MCP server "${input.name}" did not provide an authorization URL`))
@@ -228,7 +235,8 @@ export const authorize = (input: {
       callback: Deferred.await(code).pipe(
         Effect.flatMap((value) =>
           Effect.tryPromise({
-            try: () => auth(oauthProvider, { serverUrl: input.config.url, authorizationCode: value, scope: oauth?.scope }),
+            try: () =>
+              auth(oauthProvider, { serverUrl: input.config.url, authorizationCode: value, scope: oauth?.scope }),
             catch: (error) => (error instanceof Error ? error : new Error(String(error))),
           }),
         ),

+ 1 - 2
packages/core/src/mime.ts

@@ -10,8 +10,7 @@ export function detect(bytes: Uint8Array) {
     return "image/webp"
   if (
     startsWith(bytes.subarray(4), [0x66, 0x74, 0x79, 0x70]) &&
-    (startsWith(bytes.subarray(8), [0x61, 0x76, 0x69, 0x66]) ||
-      startsWith(bytes.subarray(8), [0x61, 0x76, 0x69, 0x73]))
+    (startsWith(bytes.subarray(8), [0x61, 0x76, 0x69, 0x66]) || startsWith(bytes.subarray(8), [0x61, 0x76, 0x69, 0x73]))
   )
     return "image/avif"
   return isText(bytes) ? "text/plain" : "application/octet-stream"

+ 105 - 105
packages/core/src/models-dev.ts

@@ -539,122 +539,122 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Mo
 
 export const layer = (options?: Options) =>
   Layer.effect(
-  Service,
-  Effect.gen(function* () {
-    const fs = yield* FSUtil.Service
-    const bus = yield* Bus.Service
+    Service,
+    Effect.gen(function* () {
+      const fs = yield* FSUtil.Service
+      const bus = yield* Bus.Service
       const app = yield* App.Metadata
-    const http = HttpClient.filterStatusOk(
-      (yield* HttpClient.HttpClient).pipe(
-        HttpClient.retryTransient({
-          retryOn: "errors-and-responses",
-          times: 2,
-          schedule: Schedule.exponential(200).pipe(Schedule.jittered),
-        }),
-      ),
-    )
+      const http = HttpClient.filterStatusOk(
+        (yield* HttpClient.HttpClient).pipe(
+          HttpClient.retryTransient({
+            retryOn: "errors-and-responses",
+            times: 2,
+            schedule: Schedule.exponential(200).pipe(Schedule.jittered),
+          }),
+        ),
+      )
 
-    const source = options?.url || "https://models.dev"
-    const fetch = options?.fetch ?? true
+      const source = options?.url || "https://models.dev"
+      const fetch = options?.fetch ?? true
       const userAgent = App.useragent(app)
-    const filepath = path.join(
-      Global.Path.cache,
-      source === "https://models.dev" ? "models.json" : `models-${Hash.fast(source)}.json`,
-    )
-    const ttl = Duration.minutes(5)
-    const lockKey = `models-dev:${filepath}`
-
-    const fresh = Effect.fnUntraced(function* () {
-      const stat = yield* fs.stat(filepath).pipe(Effect.catch(() => Effect.succeed(undefined)))
-      if (!stat) return false
-      const mtime = Option.getOrElse(stat.mtime, () => new Date(0)).getTime()
-      return Date.now() - mtime < Duration.toMillis(ttl)
-    })
-
-    const fetchApi = Effect.fn("ModelsDev.fetchApi")(function* () {
-      return yield* HttpClientRequest.get(`${source}/api.json`).pipe(
-        HttpClientRequest.setHeader("User-Agent", userAgent),
-        http.execute,
-        Effect.flatMap((res) => res.text),
-        Effect.timeout("10 seconds"),
+      const filepath = path.join(
+        Global.Path.cache,
+        source === "https://models.dev" ? "models.json" : `models-${Hash.fast(source)}.json`,
       )
-    })
+      const ttl = Duration.minutes(5)
+      const lockKey = `models-dev:${filepath}`
+
+      const fresh = Effect.fnUntraced(function* () {
+        const stat = yield* fs.stat(filepath).pipe(Effect.catch(() => Effect.succeed(undefined)))
+        if (!stat) return false
+        const mtime = Option.getOrElse(stat.mtime, () => new Date(0)).getTime()
+        return Date.now() - mtime < Duration.toMillis(ttl)
+      })
+
+      const fetchApi = Effect.fn("ModelsDev.fetchApi")(function* () {
+        return yield* HttpClientRequest.get(`${source}/api.json`).pipe(
+          HttpClientRequest.setHeader("User-Agent", userAgent),
+          http.execute,
+          Effect.flatMap((res) => res.text),
+          Effect.timeout("10 seconds"),
+        )
+      })
 
-    const loadFromDisk = fs.readJson(options?.file ?? filepath).pipe(
-      Effect.map((input) => input as Record<string, SourceProvider>),
-      Effect.catch((error) => {
+      const loadFromDisk = fs.readJson(options?.file ?? filepath).pipe(
+        Effect.map((input) => input as Record<string, SourceProvider>),
+        Effect.catch((error) => {
           if (options?.file === undefined && error._tag === "FileSystemError" && error.method === "readJson") {
-          return fs.remove(filepath, { force: true }).pipe(Effect.ignore, Effect.as(undefined))
-        }
-        return Effect.succeed(undefined)
-      }),
-    )
-
-    const loadSnapshot = Effect.sync(() =>
-      typeof OPENCODE_MODELS_DEV === "undefined" ? undefined : OPENCODE_MODELS_DEV,
-    )
-
-    const fetchAndWrite = Effect.fn("ModelsDev.fetchAndWrite")(function* () {
-      const text = yield* fetchApi()
-      const tempfile = `${filepath}.${process.pid}.${Date.now()}.tmp`
-      yield* fs.writeWithDirs(tempfile, text).pipe(
-        Effect.andThen(fs.rename(tempfile, filepath)),
-        Effect.catch((error) =>
-          Effect.gen(function* () {
-            yield* fs.remove(tempfile, { force: true }).pipe(Effect.ignore)
-            return yield* Effect.fail(error)
-          }),
-        ),
-      )
-      return text
-    })
-
-    const populate = Effect.gen(function* () {
-      const fromDisk = yield* loadFromDisk
-      if (fromDisk) return normalize(fromDisk)
-      const bundled = yield* loadSnapshot
-      if (bundled) return normalize(bundled)
-      if (!fetch) return []
-      // Flock is cross-process: concurrent opencode CLIs can race on this cache file.
-      const text = yield* Effect.scoped(
-        Effect.gen(function* () {
-          yield* Flock.effect(lockKey)
-          return yield* fetchAndWrite()
+            return fs.remove(filepath, { force: true }).pipe(Effect.ignore, Effect.as(undefined))
+          }
+          return Effect.succeed(undefined)
         }),
       )
-      return normalize(JSON.parse(text) as Record<string, SourceProvider>)
-    }).pipe(Effect.withSpan("ModelsDev.populate"), Effect.orDie)
-
-    const [cachedGet, invalidate] = yield* Effect.cachedInvalidateWithTTL(populate, Duration.infinity)
-
-    const get = (): Effect.Effect<readonly Snapshot[]> => cachedGet
-
-    const refresh = Effect.fn("ModelsDev.refresh")(function* (force = false) {
-      if (!force && (yield* fresh())) return
-      yield* Effect.scoped(
-        Effect.gen(function* () {
-          yield* Flock.effect(lockKey)
-          // Re-check under the lock: another process may have refreshed between
-          // our outer check and lock acquisition.
-          if (!force && (yield* fresh())) return
-          yield* fetchAndWrite()
-          yield* invalidate
-          yield* bus.publish(ModelsDev.Event.Refreshed, {})
-        }),
-      ).pipe(
-        Effect.tapCause((cause) => Effect.logError("Failed to fetch models.dev", { cause: cause })),
-        Effect.ignore,
+
+      const loadSnapshot = Effect.sync(() =>
+        typeof OPENCODE_MODELS_DEV === "undefined" ? undefined : OPENCODE_MODELS_DEV,
       )
-    })
 
-    if (fetch && !process.argv.includes("--get-yargs-completions")) {
-      // Schedule.spaced runs the effect once, then waits between completions.
-      yield* Effect.forkScoped(refresh().pipe(Effect.repeat(Schedule.spaced(ttl)), Effect.ignore))
-    }
+      const fetchAndWrite = Effect.fn("ModelsDev.fetchAndWrite")(function* () {
+        const text = yield* fetchApi()
+        const tempfile = `${filepath}.${process.pid}.${Date.now()}.tmp`
+        yield* fs.writeWithDirs(tempfile, text).pipe(
+          Effect.andThen(fs.rename(tempfile, filepath)),
+          Effect.catch((error) =>
+            Effect.gen(function* () {
+              yield* fs.remove(tempfile, { force: true }).pipe(Effect.ignore)
+              return yield* Effect.fail(error)
+            }),
+          ),
+        )
+        return text
+      })
+
+      const populate = Effect.gen(function* () {
+        const fromDisk = yield* loadFromDisk
+        if (fromDisk) return normalize(fromDisk)
+        const bundled = yield* loadSnapshot
+        if (bundled) return normalize(bundled)
+        if (!fetch) return []
+        // Flock is cross-process: concurrent opencode CLIs can race on this cache file.
+        const text = yield* Effect.scoped(
+          Effect.gen(function* () {
+            yield* Flock.effect(lockKey)
+            return yield* fetchAndWrite()
+          }),
+        )
+        return normalize(JSON.parse(text) as Record<string, SourceProvider>)
+      }).pipe(Effect.withSpan("ModelsDev.populate"), Effect.orDie)
+
+      const [cachedGet, invalidate] = yield* Effect.cachedInvalidateWithTTL(populate, Duration.infinity)
+
+      const get = (): Effect.Effect<readonly Snapshot[]> => cachedGet
 
-    return Service.of({ get, refresh })
-  }),
-)
+      const refresh = Effect.fn("ModelsDev.refresh")(function* (force = false) {
+        if (!force && (yield* fresh())) return
+        yield* Effect.scoped(
+          Effect.gen(function* () {
+            yield* Flock.effect(lockKey)
+            // Re-check under the lock: another process may have refreshed between
+            // our outer check and lock acquisition.
+            if (!force && (yield* fresh())) return
+            yield* fetchAndWrite()
+            yield* invalidate
+            yield* bus.publish(ModelsDev.Event.Refreshed, {})
+          }),
+        ).pipe(
+          Effect.tapCause((cause) => Effect.logError("Failed to fetch models.dev", { cause: cause })),
+          Effect.ignore,
+        )
+      })
+
+      if (fetch && !process.argv.includes("--get-yargs-completions")) {
+        // Schedule.spaced runs the effect once, then waits between completions.
+        yield* Effect.forkScoped(refresh().pipe(Effect.repeat(Schedule.spaced(ttl)), Effect.ignore))
+      }
+
+      return Service.of({ get, refresh })
+    }),
+  )
 
 export function configured(options?: Options) {
   return makeGlobalNode({

+ 6 - 5
packages/core/src/permission.ts

@@ -139,14 +139,15 @@ const layer = Layer.effect(
 
     const savedRules = Effect.fnUntraced(function* () {
       return (yield* saved.list({ projectID: location.project.id })).map(
-        (item): Permission.Rule => ({ action: item.action, resource: item.resource, effect: "allow" }),
+        (item): Permission.Rule => ({
+          action: item.action,
+          resource: item.resource,
+          effect: "allow",
+        }),
       )
     })
 
-    const configured = Effect.fn("Permission.configured")(function* (
-      sessionID: SessionSchema.ID,
-      agentID?: Agent.ID,
-    ) {
+    const configured = Effect.fn("Permission.configured")(function* (sessionID: SessionSchema.ID, agentID?: Agent.ID) {
       const session = yield* sessions.get(sessionID)
       if (!session) return yield* new SessionErrors.NotFoundError({ sessionID })
       const agent = yield* agents.resolve(agentID ?? session.agent)

+ 6 - 1
packages/core/src/permission/saved.ts

@@ -47,7 +47,12 @@ const layer = Layer.effect(
         .all()
         .pipe(Effect.orDie)
       return rows.map(
-        (row): Info => ({ id: row.id, projectID: row.project_id, action: row.action, resource: row.resource }),
+        (row): Info => ({
+          id: row.id,
+          projectID: row.project_id,
+          action: row.action,
+          resource: row.resource,
+        }),
       )
     })
 

+ 9 - 11
packages/core/src/plugin/host.ts

@@ -47,17 +47,13 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: import("../p
       workspaceID: location.workspaceID,
       project: location.project,
     })
-  const locationRef = (input?: {
-    readonly location?: { readonly directory?: string; readonly workspace?: string }
-  }) =>
+  const locationRef = (input?: { readonly location?: { readonly directory?: string; readonly workspace?: string } }) =>
     input?.location === undefined
       ? undefined
       : Location.Ref.make({
           directory: AbsolutePath.make(input.location.directory ?? location.directory),
           workspaceID:
-            input.location.workspace === undefined
-              ? location.workspaceID
-              : Workspace.ID.make(input.location.workspace),
+            input.location.workspace === undefined ? location.workspaceID : Workspace.ID.make(input.location.workspace),
         })
   const isCurrentLocation = (ref: Location.Ref) =>
     ref.directory === location.directory && ref.workspaceID === location.workspaceID
@@ -72,9 +68,12 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: import("../p
         const ref = locationRef(input)
         const output =
           ref && !isCurrentLocation(ref)
-            ? runtime.location.agent
-                .list(ref)
-                .pipe(Effect.map((result) => ({ ...result, data: result.data.find((agent) => agent.id === input.agentID) })))
+            ? runtime.location.agent.list(ref).pipe(
+                Effect.map((result) => ({
+                  ...result,
+                  data: result.data.find((agent) => agent.id === input.agentID),
+                })),
+              )
             : response(agents.get(input.agentID))
         return output.pipe(
           Effect.flatMap((result) =>
@@ -162,8 +161,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: import("../p
                 mutable(draft.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)),
+              remove: (providerID, modelID) => draft.model.remove(Provider.ID.make(providerID), Model.ID.make(modelID)),
               default: {
                 get: draft.model.default.get,
                 set: (providerID, modelID) =>

+ 96 - 95
packages/core/src/plugin/provider/github-copilot.ts

@@ -40,106 +40,107 @@ const decodeUser = Schema.decodeUnknownOption(User)
 const JsonBody = Schema.UnknownFromJsonString
 const decodeBody = Schema.decodeUnknownOption(JsonBody)
 
-const oauth = (app: App.Info) => ({
-  integrationID: Integration.ID.make("github-copilot"),
-  method: {
-    id: methodID,
-    type: "oauth",
-    label: "Login with GitHub Copilot",
-    prompts: [
-      {
-        type: "select",
-        key: "deploymentType",
-        message: "Select GitHub deployment type",
-        options: [
-          { label: "GitHub.com", value: "github.com", hint: "Public" },
-          { label: "GitHub Enterprise", value: "enterprise", hint: "Data residency or self-hosted" },
-        ],
-      },
-      {
-        type: "text",
-        key: "enterpriseUrl",
-        message: "Enter your GitHub Enterprise URL or domain",
-        placeholder: "company.ghe.com or https://company.ghe.com",
-        when: { key: "deploymentType", op: "eq", value: "enterprise" },
-      },
-    ],
-  },
-  authorize: (inputs) =>
-    Effect.gen(function* () {
-      const enterprise = inputs.deploymentType === "enterprise"
-      if (enterprise && !inputs.enterpriseUrl) return yield* Effect.fail(new Error("Enterprise URL is required"))
-      const domain = enterprise ? normalizeDomain(inputs.enterpriseUrl ?? "") : "github.com"
-      const urls = oauthURLs(domain)
-      const device = yield* request(urls.device, {
-        method: "POST",
-        headers: headers(app),
-        body: JSON.stringify({ client_id: clientID, scope: "read:user" }),
-      }).pipe(Effect.map(Schema.decodeUnknownSync(Device)))
-      const interval = Math.max(device.interval, 1) * 1000
-
-      const poll = (wait: number): Effect.Effect<Credential.OAuth, unknown> =>
-        request(urls.token, {
+const oauth = (app: App.Info) =>
+  ({
+    integrationID: Integration.ID.make("github-copilot"),
+    method: {
+      id: methodID,
+      type: "oauth",
+      label: "Login with GitHub Copilot",
+      prompts: [
+        {
+          type: "select",
+          key: "deploymentType",
+          message: "Select GitHub deployment type",
+          options: [
+            { label: "GitHub.com", value: "github.com", hint: "Public" },
+            { label: "GitHub Enterprise", value: "enterprise", hint: "Data residency or self-hosted" },
+          ],
+        },
+        {
+          type: "text",
+          key: "enterpriseUrl",
+          message: "Enter your GitHub Enterprise URL or domain",
+          placeholder: "company.ghe.com or https://company.ghe.com",
+          when: { key: "deploymentType", op: "eq", value: "enterprise" },
+        },
+      ],
+    },
+    authorize: (inputs) =>
+      Effect.gen(function* () {
+        const enterprise = inputs.deploymentType === "enterprise"
+        if (enterprise && !inputs.enterpriseUrl) return yield* Effect.fail(new Error("Enterprise URL is required"))
+        const domain = enterprise ? normalizeDomain(inputs.enterpriseUrl ?? "") : "github.com"
+        const urls = oauthURLs(domain)
+        const device = yield* request(urls.device, {
           method: "POST",
           headers: headers(app),
-          body: JSON.stringify({
-            client_id: clientID,
-            device_code: device.device_code,
-            grant_type: "urn:ietf:params:oauth:grant-type:device_code",
-          }),
-        }).pipe(
-          Effect.map(Schema.decodeUnknownSync(Token)),
-          Effect.flatMap((token) => {
-            if (token.access_token) {
-              const access = token.access_token
-              return request(
-                `${domain === "github.com" ? "https://api.github.com" : `https://api.${domain}`}/copilot_internal/user`,
-                {
-                  headers: {
-                    Accept: "application/json",
-                    Authorization: `Bearer ${access}`,
-                    "User-Agent": App.useragent(app),
-                    "X-GitHub-Api-Version": userApiVersion,
+          body: JSON.stringify({ client_id: clientID, scope: "read:user" }),
+        }).pipe(Effect.map(Schema.decodeUnknownSync(Device)))
+        const interval = Math.max(device.interval, 1) * 1000
+
+        const poll = (wait: number): Effect.Effect<Credential.OAuth, unknown> =>
+          request(urls.token, {
+            method: "POST",
+            headers: headers(app),
+            body: JSON.stringify({
+              client_id: clientID,
+              device_code: device.device_code,
+              grant_type: "urn:ietf:params:oauth:grant-type:device_code",
+            }),
+          }).pipe(
+            Effect.map(Schema.decodeUnknownSync(Token)),
+            Effect.flatMap((token) => {
+              if (token.access_token) {
+                const access = token.access_token
+                return request(
+                  `${domain === "github.com" ? "https://api.github.com" : `https://api.${domain}`}/copilot_internal/user`,
+                  {
+                    headers: {
+                      Accept: "application/json",
+                      Authorization: `Bearer ${access}`,
+                      "User-Agent": App.useragent(app),
+                      "X-GitHub-Api-Version": userApiVersion,
+                    },
                   },
-                },
-              ).pipe(
-                Effect.map((user) => Option.getOrUndefined(decodeUser(user))?.endpoints?.api?.replace(/\/+$/, "")),
-                Effect.catch(() => Effect.succeed(undefined)),
-                Effect.map((apiEndpoint) =>
-                  Credential.OAuth.make({
-                    type: "oauth",
-                    methodID,
-                    refresh: access,
-                    access,
-                    expires: 0,
-                    ...((enterprise || apiEndpoint) && {
-                      metadata: {
-                        ...(enterprise ? { enterpriseUrl: domain } : {}),
-                        ...(apiEndpoint ? { apiEndpoint } : {}),
-                      },
+                ).pipe(
+                  Effect.map((user) => Option.getOrUndefined(decodeUser(user))?.endpoints?.api?.replace(/\/+$/, "")),
+                  Effect.catch(() => Effect.succeed(undefined)),
+                  Effect.map((apiEndpoint) =>
+                    Credential.OAuth.make({
+                      type: "oauth",
+                      methodID,
+                      refresh: access,
+                      access,
+                      expires: 0,
+                      ...((enterprise || apiEndpoint) && {
+                        metadata: {
+                          ...(enterprise ? { enterpriseUrl: domain } : {}),
+                          ...(apiEndpoint ? { apiEndpoint } : {}),
+                        },
+                      }),
                     }),
-                  }),
-                ),
-              )
-            }
-            if (token.error === "authorization_pending")
-              return Effect.sleep(wait + pollingSafetyMargin).pipe(Effect.andThen(poll(wait)))
-            if (token.error === "slow_down") {
-              const next = token.interval && token.interval > 0 ? token.interval * 1000 : wait + 5000
-              return Effect.sleep(next + pollingSafetyMargin).pipe(Effect.andThen(poll(next)))
-            }
-            return Effect.fail(new Error(`Device authorization failed${token.error ? `: ${token.error}` : ""}`))
-          }),
-        )
+                  ),
+                )
+              }
+              if (token.error === "authorization_pending")
+                return Effect.sleep(wait + pollingSafetyMargin).pipe(Effect.andThen(poll(wait)))
+              if (token.error === "slow_down") {
+                const next = token.interval && token.interval > 0 ? token.interval * 1000 : wait + 5000
+                return Effect.sleep(next + pollingSafetyMargin).pipe(Effect.andThen(poll(next)))
+              }
+              return Effect.fail(new Error(`Device authorization failed${token.error ? `: ${token.error}` : ""}`))
+            }),
+          )
 
-      return {
-        mode: "auto" as const,
-        url: device.verification_uri,
-        instructions: `Enter code: ${device.user_code}`,
-        callback: poll(interval),
-      }
-    }),
-}) satisfies IntegrationOAuthMethodRegistration
+        return {
+          mode: "auto" as const,
+          url: device.verification_uri,
+          instructions: `Enter code: ${device.user_code}`,
+          callback: poll(interval),
+        }
+      }),
+  }) satisfies IntegrationOAuthMethodRegistration
 
 export const GithubCopilotPlugin = define({
   id: "opencode.provider.github-copilot",

+ 115 - 111
packages/core/src/plugin/provider/openai.ts

@@ -44,122 +44,126 @@ const Claims = Schema.fromJsonString(
 )
 const decodeClaims = Schema.decodeUnknownOption(Claims)
 
-const browser = (app: App.Info) => ({
-  integrationID: Integration.ID.make("openai"),
-  method: {
-    id: browserMethodID,
-    type: "oauth",
-    label: "ChatGPT Pro/Plus (browser)",
-  },
-  authorize: () =>
-    Effect.gen(function* () {
-      const pkce = yield* Effect.promise(generatePKCE)
-      const state = base64UrlEncode(crypto.getRandomValues(new Uint8Array(32)).buffer)
-      const code = yield* Deferred.make<string, Error>()
-      const redirect = `http://localhost:${callbackPort}/auth/callback`
-      const server = createServer((request, response) => {
-        const url = new URL(request.url ?? "/", `http://localhost:${callbackPort}`)
-        if (url.pathname !== "/auth/callback") {
-          response.writeHead(404).end("Not found")
-          return
-        }
-        const error = url.searchParams.get("error_description") ?? url.searchParams.get("error")
-        const value = url.searchParams.get("code")
-        if (error) {
-          Effect.runFork(Deferred.fail(code, new Error(error)))
-          response
-            .writeHead(400, { "Content-Type": "text/html" })
-            .end(OauthCallbackPage.error(error, { provider: "ChatGPT" }))
-          return
-        }
-        if (!value || url.searchParams.get("state") !== state) {
-          const message = value ? "Invalid OAuth state" : "Missing authorization code"
-          Effect.runFork(Deferred.fail(code, new Error(message)))
+const browser = (app: App.Info) =>
+  ({
+    integrationID: Integration.ID.make("openai"),
+    method: {
+      id: browserMethodID,
+      type: "oauth",
+      label: "ChatGPT Pro/Plus (browser)",
+    },
+    authorize: () =>
+      Effect.gen(function* () {
+        const pkce = yield* Effect.promise(generatePKCE)
+        const state = base64UrlEncode(crypto.getRandomValues(new Uint8Array(32)).buffer)
+        const code = yield* Deferred.make<string, Error>()
+        const redirect = `http://localhost:${callbackPort}/auth/callback`
+        const server = createServer((request, response) => {
+          const url = new URL(request.url ?? "/", `http://localhost:${callbackPort}`)
+          if (url.pathname !== "/auth/callback") {
+            response.writeHead(404).end("Not found")
+            return
+          }
+          const error = url.searchParams.get("error_description") ?? url.searchParams.get("error")
+          const value = url.searchParams.get("code")
+          if (error) {
+            Effect.runFork(Deferred.fail(code, new Error(error)))
+            response
+              .writeHead(400, { "Content-Type": "text/html" })
+              .end(OauthCallbackPage.error(error, { provider: "ChatGPT" }))
+            return
+          }
+          if (!value || url.searchParams.get("state") !== state) {
+            const message = value ? "Invalid OAuth state" : "Missing authorization code"
+            Effect.runFork(Deferred.fail(code, new Error(message)))
+            response
+              .writeHead(400, { "Content-Type": "text/html" })
+              .end(OauthCallbackPage.error(message, { provider: "ChatGPT" }))
+            return
+          }
+          Effect.runFork(Deferred.succeed(code, value))
           response
-            .writeHead(400, { "Content-Type": "text/html" })
-            .end(OauthCallbackPage.error(message, { provider: "ChatGPT" }))
-          return
+            .writeHead(200, { "Content-Type": "text/html" })
+            .end(OauthCallbackPage.success({ provider: "ChatGPT" }))
+        })
+        yield* Effect.callback<void, Error>((resume) => {
+          server.once("error", (error) => resume(Effect.fail(error)))
+          server.listen(callbackPort, "localhost", () => resume(Effect.void))
+        })
+        yield* Effect.addFinalizer(() => Effect.sync(() => server.close()))
+        return {
+          mode: "auto" as const,
+          url: authorizeURL(redirect, pkce, state),
+          instructions: "Complete authorization in your browser. This window will close automatically.",
+          callback: Deferred.await(code).pipe(
+            Effect.flatMap((value) => exchange(value, redirect, pkce, app)),
+            Effect.map((tokens) => credential(browserMethodID, tokens)),
+          ),
         }
-        Effect.runFork(Deferred.succeed(code, value))
-        response.writeHead(200, { "Content-Type": "text/html" }).end(OauthCallbackPage.success({ provider: "ChatGPT" }))
-      })
-      yield* Effect.callback<void, Error>((resume) => {
-        server.once("error", (error) => resume(Effect.fail(error)))
-        server.listen(callbackPort, "localhost", () => resume(Effect.void))
-      })
-      yield* Effect.addFinalizer(() => Effect.sync(() => server.close()))
-      return {
-        mode: "auto" as const,
-        url: authorizeURL(redirect, pkce, state),
-        instructions: "Complete authorization in your browser. This window will close automatically.",
-        callback: Deferred.await(code).pipe(
-          Effect.flatMap((value) => exchange(value, redirect, pkce, app)),
-          Effect.map((tokens) => credential(browserMethodID, tokens)),
-        ),
-      }
-    }),
-  refresh: (value) => refresh(browserMethodID, value, app),
-}) satisfies IntegrationOAuthMethodRegistration
+      }),
+    refresh: (value) => refresh(browserMethodID, value, app),
+  }) satisfies IntegrationOAuthMethodRegistration
 
-const headless = (app: App.Info) => ({
-  integrationID: Integration.ID.make("openai"),
-  method: {
-    id: headlessMethodID,
-    type: "oauth",
-    label: "ChatGPT Pro/Plus (headless)",
-  },
-  authorize: () =>
-    Effect.gen(function* () {
-      const device = yield* request<{ device_auth_id: string; user_code: string; interval: string }>(
-        `${issuer}/api/accounts/deviceauth/usercode`,
-        {
-          method: "POST",
-          headers: headers("application/json", app),
-          body: JSON.stringify({ client_id: clientID }),
-        },
-      )
-      const interval = Math.max(Number.parseInt(device.interval) || 5, 1) * 1000
-      return {
-        mode: "auto" as const,
-        url: `${issuer}/codex/device`,
-        instructions: `Enter code: ${device.user_code}`,
-        callback: Effect.gen(function* () {
-          while (true) {
-            const response = yield* Effect.tryPromise({
-              try: (signal) =>
-                fetch(`${issuer}/api/accounts/deviceauth/token`, {
-                  method: "POST",
-                  headers: headers("application/json", app),
-                  body: JSON.stringify({ device_auth_id: device.device_auth_id, user_code: device.user_code }),
-                  signal,
-                }),
-              catch: (cause) => cause,
-            })
-            if (response.ok) {
-              const data = (yield* Effect.promise(() => response.json())) as {
-                authorization_code: string
-                code_verifier: string
+const headless = (app: App.Info) =>
+  ({
+    integrationID: Integration.ID.make("openai"),
+    method: {
+      id: headlessMethodID,
+      type: "oauth",
+      label: "ChatGPT Pro/Plus (headless)",
+    },
+    authorize: () =>
+      Effect.gen(function* () {
+        const device = yield* request<{ device_auth_id: string; user_code: string; interval: string }>(
+          `${issuer}/api/accounts/deviceauth/usercode`,
+          {
+            method: "POST",
+            headers: headers("application/json", app),
+            body: JSON.stringify({ client_id: clientID }),
+          },
+        )
+        const interval = Math.max(Number.parseInt(device.interval) || 5, 1) * 1000
+        return {
+          mode: "auto" as const,
+          url: `${issuer}/codex/device`,
+          instructions: `Enter code: ${device.user_code}`,
+          callback: Effect.gen(function* () {
+            while (true) {
+              const response = yield* Effect.tryPromise({
+                try: (signal) =>
+                  fetch(`${issuer}/api/accounts/deviceauth/token`, {
+                    method: "POST",
+                    headers: headers("application/json", app),
+                    body: JSON.stringify({ device_auth_id: device.device_auth_id, user_code: device.user_code }),
+                    signal,
+                  }),
+                catch: (cause) => cause,
+              })
+              if (response.ok) {
+                const data = (yield* Effect.promise(() => response.json())) as {
+                  authorization_code: string
+                  code_verifier: string
+                }
+                return credential(
+                  headlessMethodID,
+                  yield* exchange(
+                    data.authorization_code,
+                    `${issuer}/deviceauth/callback`,
+                    { verifier: data.code_verifier, challenge: "" },
+                    app,
+                  ),
+                )
               }
-              return credential(
-                headlessMethodID,
-                yield* exchange(
-                  data.authorization_code,
-                  `${issuer}/deviceauth/callback`,
-                  { verifier: data.code_verifier, challenge: "" },
-                  app,
-                ),
-              )
-            }
-            if (response.status !== 403 && response.status !== 404) {
-              return yield* Effect.fail(new Error(`Device authorization failed: ${response.status}`))
+              if (response.status !== 403 && response.status !== 404) {
+                return yield* Effect.fail(new Error(`Device authorization failed: ${response.status}`))
+              }
+              yield* Effect.sleep(interval + pollingSafetyMargin)
             }
-            yield* Effect.sleep(interval + pollingSafetyMargin)
-          }
-        }),
-      }
-    }),
-  refresh: (value) => refresh(headlessMethodID, value, app),
-}) satisfies IntegrationOAuthMethodRegistration
+          }),
+        }
+      }),
+    refresh: (value) => refresh(headlessMethodID, value, app),
+  }) satisfies IntegrationOAuthMethodRegistration
 
 export const OpenAIPlugin = define({
   id: "opencode.provider.openai",

+ 33 - 32
packages/core/src/plugin/provider/xai.ts

@@ -35,40 +35,41 @@ const DeviceError = Schema.Struct({
 })
 const decodeDeviceError = Schema.decodeUnknownOption(Schema.fromJsonString(DeviceError))
 
-const device = (app: App.Info) => ({
-  integrationID: Integration.ID.make("xai"),
-  method: {
-    id: deviceMethodID,
-    type: "oauth",
-    label: "SuperGrok Subscription",
-  },
-  authorize: () =>
-    request(
-      `${issuer}/device/code`,
-      {
-        method: "POST",
-        headers: headers(app),
-        body: new URLSearchParams({ client_id: clientID, scope, referrer: "opencode" }).toString(),
-      },
-      Device,
-    ).pipe(
-      Effect.flatMap((value) =>
-        Clock.currentTimeMillis.pipe(
-          Effect.map((created) => {
-            const lifetime = positiveSeconds(value.expires_in, 0)
-            return {
-              mode: "auto" as const,
-              url: value.verification_uri_complete ?? value.verification_uri,
-              instructions: `Open ${value.verification_uri} on any device and enter code: ${value.user_code}`,
-              ...(lifetime ? { expiresAt: created + lifetime * 1000 } : {}),
-              callback: poll(value, app).pipe(Effect.flatMap((tokens) => credential(deviceMethodID, tokens))),
-            }
-          }),
+const device = (app: App.Info) =>
+  ({
+    integrationID: Integration.ID.make("xai"),
+    method: {
+      id: deviceMethodID,
+      type: "oauth",
+      label: "SuperGrok Subscription",
+    },
+    authorize: () =>
+      request(
+        `${issuer}/device/code`,
+        {
+          method: "POST",
+          headers: headers(app),
+          body: new URLSearchParams({ client_id: clientID, scope, referrer: "opencode" }).toString(),
+        },
+        Device,
+      ).pipe(
+        Effect.flatMap((value) =>
+          Clock.currentTimeMillis.pipe(
+            Effect.map((created) => {
+              const lifetime = positiveSeconds(value.expires_in, 0)
+              return {
+                mode: "auto" as const,
+                url: value.verification_uri_complete ?? value.verification_uri,
+                instructions: `Open ${value.verification_uri} on any device and enter code: ${value.user_code}`,
+                ...(lifetime ? { expiresAt: created + lifetime * 1000 } : {}),
+                callback: poll(value, app).pipe(Effect.flatMap((tokens) => credential(deviceMethodID, tokens))),
+              }
+            }),
+          ),
         ),
       ),
-    ),
-  refresh: (value) => refresh(deviceMethodID, Credential.OAuth.make({ ...value, methodID: deviceMethodID }), app),
-}) satisfies IntegrationOAuthMethodRegistration
+    refresh: (value) => refresh(deviceMethodID, Credential.OAuth.make({ ...value, methodID: deviceMethodID }), app),
+  }) satisfies IntegrationOAuthMethodRegistration
 
 export const XAIPlugin = define({
   id: "opencode.provider.xai",

+ 2 - 5
packages/core/src/project.ts

@@ -45,12 +45,9 @@ export interface Resolved {
 }
 
 // Keep this filesystem-only; permission checks use it and should not execute VCS commands.
-export const root = Effect.fn("Project.root")(function* (
-  fs: FSUtil.Interface,
-  input: AbsolutePath,
-) {
+export const root = Effect.fn("Project.root")(function* (fs: FSUtil.Interface, input: AbsolutePath) {
   return yield* fs.up({ targets: [".git", ".hg"], start: input, mode: "first" }).pipe(
-    Effect.map((matches) => matches[0] ? AbsolutePath.make(path.dirname(matches[0])) : undefined),
+    Effect.map((matches) => (matches[0] ? AbsolutePath.make(path.dirname(matches[0])) : undefined)),
     Effect.catch(() => Effect.succeed(undefined)),
   )
 })

+ 213 - 212
packages/core/src/pty.ts

@@ -89,229 +89,230 @@ export interface Interface {
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/Pty") {}
 
-export const layer = (options?: ShellSelect.Options) => Layer.effect(
-  Service,
-  Effect.gen(function* () {
-    const bus = yield* Bus.Service
-    const location = yield* Location.Service
-    const config = yield* Config.Service
-    const context = yield* Effect.context()
-    const runFork = Effect.runForkWith(context)
-    const sessions = new Map<PtyID, Active>()
-    const exitOrder: PtyID[] = []
-
-    function notifyEnd(session: Active, event: { exitCode?: number }) {
-      for (const subscriber of session.subscribers.values()) {
-        if (!subscriber.active) {
-          subscriber.end = event
-          continue
+export const layer = (options?: ShellSelect.Options) =>
+  Layer.effect(
+    Service,
+    Effect.gen(function* () {
+      const bus = yield* Bus.Service
+      const location = yield* Location.Service
+      const config = yield* Config.Service
+      const context = yield* Effect.context()
+      const runFork = Effect.runForkWith(context)
+      const sessions = new Map<PtyID, Active>()
+      const exitOrder: PtyID[] = []
+
+      function notifyEnd(session: Active, event: { exitCode?: number }) {
+        for (const subscriber of session.subscribers.values()) {
+          if (!subscriber.active) {
+            subscriber.end = event
+            continue
+          }
+          try {
+            subscriber.onEnd(event)
+          } catch {}
         }
-        try {
-          subscriber.onEnd(event)
-        } catch {}
-      }
-      session.subscribers.clear()
-    }
-
-    function teardown(session: Active) {
-      for (const listener of session.listeners) listener.dispose()
-      session.listeners.length = 0
-      if (session.info.status === "running") {
-        try {
-          session.process.kill()
-        } catch {}
-      }
-      notifyEnd(session, {})
-    }
-
-    yield* Effect.addFinalizer(() =>
-      Effect.sync(() => {
-        for (const session of sessions.values()) teardown(session)
-        sessions.clear()
-        exitOrder.length = 0
-      }),
-    )
-
-    const requireSession = Effect.fn("Pty.requireSession")(function* (id: PtyID) {
-      const session = sessions.get(id)
-      if (!session) return yield* new NotFoundError({ ptyID: id })
-      return session
-    })
-
-    const removeSession = Effect.fnUntraced(function* (id: PtyID) {
-      const session = sessions.get(id)
-      if (!session) return
-      sessions.delete(id)
-      const index = exitOrder.indexOf(id)
-      if (index !== -1) exitOrder.splice(index, 1)
-      yield* Effect.logInfo("removing session", { id })
-      teardown(session)
-      yield* bus.publish(Pty.Event.Deleted, { id: session.info.id })
-    })
-
-    const remove = Effect.fn("Pty.remove")(function* (id: PtyID) {
-      yield* requireSession(id)
-      yield* removeSession(id)
-    })
-
-    const list = Effect.fn("Pty.list")(function* () {
-      return Array.from(sessions.values()).map((session) => session.info)
-    })
-
-    const get = Effect.fn("Pty.get")(function* (id: PtyID) {
-      return (yield* requireSession(id)).info
-    })
-
-    const create = Effect.fn("Pty.create")(function* (input: CreateInput) {
-      const id = PtyID.ascending()
-      const command = input.command || ShellSelect.preferred(Config.latest(yield* config.entries(), "shell"), options)
-      const args = ShellSelect.login(command) ? [...(input.args ?? []), "-l"] : [...(input.args ?? [])]
-      const cwd = input.cwd || location.directory
-      const env = {
-        ...process.env,
-        ...input.env,
-        TERM: "xterm-256color",
-        OPENCODE_TERMINAL: "1",
-      } as Record<string, string>
-      if (process.platform === "win32") {
-        env.LC_ALL = "C.UTF-8"
-        env.LC_CTYPE = "C.UTF-8"
-        env.LANG = "C.UTF-8"
+        session.subscribers.clear()
       }
-      yield* Effect.logInfo("creating session", { id, cmd: command, args, cwd })
-      const { spawn } = yield* Effect.promise(() => pty())
-      const proc = yield* Effect.sync(() => spawn(command, args, { name: "xterm-256color", cwd, env }))
-      const info: Info = {
-        id,
-        title: input.title || `Terminal ${id.slice(-4)}`,
-        command,
-        args,
-        cwd,
-        status: "running",
-        pid: proc.pid,
-      }
-      const session: Active = {
-        info,
-        process: proc,
-        buffer: "",
-        bufferCursor: 0,
-        cursor: 0,
-        subscribers: new Map(),
-        listeners: [],
+
+      function teardown(session: Active) {
+        for (const listener of session.listeners) listener.dispose()
+        session.listeners.length = 0
+        if (session.info.status === "running") {
+          try {
+            session.process.kill()
+          } catch {}
+        }
+        notifyEnd(session, {})
       }
-      sessions.set(id, session)
-      session.listeners.push(
-        proc.onData((chunk) => {
-          session.cursor += chunk.length
-          for (const [token, subscriber] of session.subscribers.entries()) {
-            if (!subscriber.active) {
-              subscriber.pending.push(chunk)
-              continue
+
+      yield* Effect.addFinalizer(() =>
+        Effect.sync(() => {
+          for (const session of sessions.values()) teardown(session)
+          sessions.clear()
+          exitOrder.length = 0
+        }),
+      )
+
+      const requireSession = Effect.fn("Pty.requireSession")(function* (id: PtyID) {
+        const session = sessions.get(id)
+        if (!session) return yield* new NotFoundError({ ptyID: id })
+        return session
+      })
+
+      const removeSession = Effect.fnUntraced(function* (id: PtyID) {
+        const session = sessions.get(id)
+        if (!session) return
+        sessions.delete(id)
+        const index = exitOrder.indexOf(id)
+        if (index !== -1) exitOrder.splice(index, 1)
+        yield* Effect.logInfo("removing session", { id })
+        teardown(session)
+        yield* bus.publish(Pty.Event.Deleted, { id: session.info.id })
+      })
+
+      const remove = Effect.fn("Pty.remove")(function* (id: PtyID) {
+        yield* requireSession(id)
+        yield* removeSession(id)
+      })
+
+      const list = Effect.fn("Pty.list")(function* () {
+        return Array.from(sessions.values()).map((session) => session.info)
+      })
+
+      const get = Effect.fn("Pty.get")(function* (id: PtyID) {
+        return (yield* requireSession(id)).info
+      })
+
+      const create = Effect.fn("Pty.create")(function* (input: CreateInput) {
+        const id = PtyID.ascending()
+        const command = input.command || ShellSelect.preferred(Config.latest(yield* config.entries(), "shell"), options)
+        const args = ShellSelect.login(command) ? [...(input.args ?? []), "-l"] : [...(input.args ?? [])]
+        const cwd = input.cwd || location.directory
+        const env = {
+          ...process.env,
+          ...input.env,
+          TERM: "xterm-256color",
+          OPENCODE_TERMINAL: "1",
+        } as Record<string, string>
+        if (process.platform === "win32") {
+          env.LC_ALL = "C.UTF-8"
+          env.LC_CTYPE = "C.UTF-8"
+          env.LANG = "C.UTF-8"
+        }
+        yield* Effect.logInfo("creating session", { id, cmd: command, args, cwd })
+        const { spawn } = yield* Effect.promise(() => pty())
+        const proc = yield* Effect.sync(() => spawn(command, args, { name: "xterm-256color", cwd, env }))
+        const info: Info = {
+          id,
+          title: input.title || `Terminal ${id.slice(-4)}`,
+          command,
+          args,
+          cwd,
+          status: "running",
+          pid: proc.pid,
+        }
+        const session: Active = {
+          info,
+          process: proc,
+          buffer: "",
+          bufferCursor: 0,
+          cursor: 0,
+          subscribers: new Map(),
+          listeners: [],
+        }
+        sessions.set(id, session)
+        session.listeners.push(
+          proc.onData((chunk) => {
+            session.cursor += chunk.length
+            for (const [token, subscriber] of session.subscribers.entries()) {
+              if (!subscriber.active) {
+                subscriber.pending.push(chunk)
+                continue
+              }
+              try {
+                subscriber.onData(chunk)
+              } catch {
+                session.subscribers.delete(token)
+              }
             }
+            session.buffer += chunk
+            if (session.buffer.length <= BUFFER_LIMIT) return
+            const excess = session.buffer.length - BUFFER_LIMIT
+            session.buffer = session.buffer.slice(excess)
+            session.bufferCursor += excess
+          }),
+          proc.onExit(({ exitCode }) => {
+            if (session.info.status === "exited") return
+            session.info.status = "exited"
+            session.info.exitCode = exitCode
+            notifyEnd(session, { exitCode })
+            exitOrder.push(id)
+            runFork(
+              Effect.gen(function* () {
+                yield* Effect.logInfo("session exited", { id, exitCode })
+                yield* bus.publish(Pty.Event.Exited, { id, exitCode })
+                while (exitOrder.length > EXITED_LIMIT) {
+                  const oldest = exitOrder[0]
+                  if (!oldest) break
+                  yield* removeSession(oldest)
+                }
+              }),
+            )
+          }),
+        )
+        yield* bus.publish(Pty.Event.Created, { info })
+        return info
+      })
+
+      const update = Effect.fn("Pty.update")(function* (id: PtyID, input: UpdateInput) {
+        const session = yield* requireSession(id)
+        if (input.title) session.info.title = input.title
+        if (input.size && session.info.status === "running") session.process.resize(input.size.cols, input.size.rows)
+        yield* bus.publish(Pty.Event.Updated, { info: session.info })
+        return session.info
+      })
+
+      const write = Effect.fn("Pty.write")(function* (id: PtyID, data: string) {
+        const session = yield* requireSession(id)
+        if (session.info.status === "running") session.process.write(data)
+      })
+
+      const attach = Effect.fn("Pty.attach")(function* (id: PtyID, input: AttachInput) {
+        const session = yield* requireSession(id)
+        if (session.info.status !== "running") return yield* new ExitedError({ ptyID: id })
+        yield* Effect.logInfo("client attached to session", { id, directory: location.directory })
+        const token = {}
+        const subscriber: Subscriber = {
+          onData: input.onData,
+          onEnd: input.onEnd,
+          active: false,
+          detached: false,
+          pending: [],
+        }
+        session.subscribers.set(token, subscriber)
+        const start = session.bufferCursor
+        const end = session.cursor
+        const from =
+          input.cursor === -1
+            ? end
+            : typeof input.cursor === "number" && Number.isSafeInteger(input.cursor)
+              ? Math.max(0, input.cursor)
+              : 0
+        const replay = (() => {
+          if (!session.buffer || from >= end) return ""
+          const offset = Math.max(0, from - start)
+          if (offset >= session.buffer.length) return ""
+          return session.buffer.slice(offset)
+        })()
+        return {
+          replay,
+          cursor: end,
+          write: (data: string) => {
+            if (session.info.status === "running") session.process.write(data)
+          },
+          activate: () => {
+            if (subscriber.active || subscriber.detached) return
+            subscriber.active = true
             try {
-              subscriber.onData(chunk)
+              for (const chunk of subscriber.pending) subscriber.onData(chunk)
+              subscriber.pending.length = 0
+              if (subscriber.end) subscriber.onEnd(subscriber.end)
             } catch {
               session.subscribers.delete(token)
             }
-          }
-          session.buffer += chunk
-          if (session.buffer.length <= BUFFER_LIMIT) return
-          const excess = session.buffer.length - BUFFER_LIMIT
-          session.buffer = session.buffer.slice(excess)
-          session.bufferCursor += excess
-        }),
-        proc.onExit(({ exitCode }) => {
-          if (session.info.status === "exited") return
-          session.info.status = "exited"
-          session.info.exitCode = exitCode
-          notifyEnd(session, { exitCode })
-          exitOrder.push(id)
-          runFork(
-            Effect.gen(function* () {
-              yield* Effect.logInfo("session exited", { id, exitCode })
-              yield* bus.publish(Pty.Event.Exited, { id, exitCode })
-              while (exitOrder.length > EXITED_LIMIT) {
-                const oldest = exitOrder[0]
-                if (!oldest) break
-                yield* removeSession(oldest)
-              }
-            }),
-          )
-        }),
-      )
-      yield* bus.publish(Pty.Event.Created, { info })
-      return info
-    })
-
-    const update = Effect.fn("Pty.update")(function* (id: PtyID, input: UpdateInput) {
-      const session = yield* requireSession(id)
-      if (input.title) session.info.title = input.title
-      if (input.size && session.info.status === "running") session.process.resize(input.size.cols, input.size.rows)
-      yield* bus.publish(Pty.Event.Updated, { info: session.info })
-      return session.info
-    })
-
-    const write = Effect.fn("Pty.write")(function* (id: PtyID, data: string) {
-      const session = yield* requireSession(id)
-      if (session.info.status === "running") session.process.write(data)
-    })
-
-    const attach = Effect.fn("Pty.attach")(function* (id: PtyID, input: AttachInput) {
-      const session = yield* requireSession(id)
-      if (session.info.status !== "running") return yield* new ExitedError({ ptyID: id })
-      yield* Effect.logInfo("client attached to session", { id, directory: location.directory })
-      const token = {}
-      const subscriber: Subscriber = {
-        onData: input.onData,
-        onEnd: input.onEnd,
-        active: false,
-        detached: false,
-        pending: [],
-      }
-      session.subscribers.set(token, subscriber)
-      const start = session.bufferCursor
-      const end = session.cursor
-      const from =
-        input.cursor === -1
-          ? end
-          : typeof input.cursor === "number" && Number.isSafeInteger(input.cursor)
-            ? Math.max(0, input.cursor)
-            : 0
-      const replay = (() => {
-        if (!session.buffer || from >= end) return ""
-        const offset = Math.max(0, from - start)
-        if (offset >= session.buffer.length) return ""
-        return session.buffer.slice(offset)
-      })()
-      return {
-        replay,
-        cursor: end,
-        write: (data: string) => {
-          if (session.info.status === "running") session.process.write(data)
-        },
-        activate: () => {
-          if (subscriber.active || subscriber.detached) return
-          subscriber.active = true
-          try {
-            for (const chunk of subscriber.pending) subscriber.onData(chunk)
+          },
+          detach: () => {
+            subscriber.detached = true
             subscriber.pending.length = 0
-            if (subscriber.end) subscriber.onEnd(subscriber.end)
-          } catch {
+            subscriber.end = undefined
             session.subscribers.delete(token)
-          }
-        },
-        detach: () => {
-          subscriber.detached = true
-          subscriber.pending.length = 0
-          subscriber.end = undefined
-          session.subscribers.delete(token)
-        },
-      }
-    })
+          },
+        }
+      })
 
-    return Service.of({ list, get, create, update, remove, write, attach })
-  }),
-)
+      return Service.of({ list, get, create, update, remove, write, attach })
+    }),
+  )
 
 export function configured(options?: ShellSelect.Options) {
   return makeLocationNode({ service: Service, layer: layer(options), deps: [Bus.node, Location.node, Config.node] })

+ 1 - 3
packages/core/src/session.ts

@@ -345,9 +345,7 @@ const layer = Layer.effect(
         Effect.gen(function* () {
           yield* mutation(bus, { sessionID: input.sessionID, id: input.inputID }).pipe(
             Effect.catchDefect((defect) =>
-              defect instanceof SessionPending.LifecycleConflict
-                ? pendingConflict(input)
-                : Effect.die(defect),
+              defect instanceof SessionPending.LifecycleConflict ? pendingConflict(input) : Effect.die(defect),
             ),
           )
           if (wake) yield* execution.wake(input.sessionID)

+ 5 - 5
packages/core/src/session/instruction-state.ts

@@ -66,7 +66,10 @@ const renderUpdateText = Effect.fnUntraced(function* (
   observation: Observation,
 ) {
   const replaced = Object.entries(observation.previous).filter(([key]) => Object.hasOwn(observation.delta, key))
-  const blobs = yield* loadBlobs(db, replaced.map(([, hash]) => hash))
+  const blobs = yield* loadBlobs(
+    db,
+    replaced.map(([, hash]) => hash),
+  )
   const previous = Object.fromEntries(replaced.map(([key, hash]) => [key, requireBlob(blobs, hash)]))
   const admitted = new Map(
     Object.entries(observation.blobs).map(([hash, value]) => [Instructions.Hash.make(hash), value]),
@@ -193,10 +196,7 @@ export const preview = Effect.fn("InstructionState.preview")(function* (
     const values = dereference(result.current, observedBlobs)
     return { initial: Instructions.renderInitial(instructions, values), update: "" }
   }
-  const stored = yield* loadBlobs(db, [
-    ...Object.values(state.initial_values),
-    ...Object.values(state.current_values),
-  ])
+  const stored = yield* loadBlobs(db, [...Object.values(state.initial_values), ...Object.values(state.current_values)])
   return {
     initial: Instructions.renderInitial(instructions, dereference(state.initial_values, stored)),
     update: Instructions.renderUpdate(

+ 4 - 3
packages/core/src/session/model-request.ts

@@ -249,7 +249,9 @@ export const layer = Layer.effect(
               model: resolved.ref,
               request: before.request,
               response: new Response(
-                [204, 205, 304].includes(response.status) ? null : yield* Stream.toReadableStreamEffect(response.stream),
+                [204, 205, 304].includes(response.status)
+                  ? null
+                  : yield* Stream.toReadableStreamEffect(response.stream),
                 { status: response.status, headers: response.headers },
               ),
             })
@@ -274,8 +276,7 @@ export const layer = Layer.effect(
         )
       }
       const executeTool: Prepared["executeTool"] = (input) => {
-        if (stepLimitReached)
-          return new Tool.Error({ message: "Tools are disabled after the maximum agent steps" })
+        if (stepLimitReached) return new Tool.Error({ message: "Tools are disabled after the maximum agent steps" })
         const tool = hooked.get(input.call.name)
         // A registered tool absent from the hooked set was removed or renamed by a hook.
         if (!tool && registry.has(input.call.name))

+ 4 - 6
packages/core/src/session/pending.ts

@@ -349,14 +349,12 @@ const projectDelivery = Effect.fn("SessionPending.projectDelivery")(function* (
   if (!updated) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
 })
 
-export const projectSteered = Effect.fn("SessionPending.projectSteered")(
-  (db: DatabaseService, input: PendingRef) =>
-    projectDelivery(db, { ...input, from: "queue", to: "steer" }),
+export const projectSteered = Effect.fn("SessionPending.projectSteered")((db: DatabaseService, input: PendingRef) =>
+  projectDelivery(db, { ...input, from: "queue", to: "steer" }),
 )
 
-export const projectQueued = Effect.fn("SessionPending.projectQueued")(
-  (db: DatabaseService, input: PendingRef) =>
-    projectDelivery(db, { ...input, from: "steer", to: "queue" }),
+export const projectQueued = Effect.fn("SessionPending.projectQueued")((db: DatabaseService, input: PendingRef) =>
+  projectDelivery(db, { ...input, from: "steer", to: "queue" }),
 )
 
 export const settleCompaction = Effect.fn("SessionPending.settleCompaction")(function* (

+ 5 - 1
packages/core/src/session/runner/llm.ts

@@ -38,7 +38,11 @@ import { ToolOutput } from "../../tool-output"
 type CallOutcome = Data.TaggedEnum<{
   Completed: { readonly needsContinuation: boolean; readonly step: number }
   Retry: { readonly step: number }
-  Continue: { readonly cause: AIError; readonly error: SessionRunnerRetry.RetryableFailure["error"]; readonly step: number }
+  Continue: {
+    readonly cause: AIError
+    readonly error: SessionRunnerRetry.RetryableFailure["error"]
+    readonly step: number
+  }
   Restart: { readonly step: number; readonly recoveredOverflow: boolean }
 }>
 const CallOutcome = Data.taggedEnum<CallOutcome>()

+ 2 - 7
packages/core/src/session/runner/publish-llm-event.ts

@@ -510,8 +510,7 @@ export const createLLMEventPublisher = (bus: Pick<Bus.Interface, "publish">, inp
 
   const progress = Effect.fnUntraced(function* (id: string, update: Tool.Metadata) {
     const tool = tools.get(id)
-    if (!tool?.called || tool.settled)
-      return yield* Effect.die(new Error(`Tool progress outside running call: ${id}`))
+    if (!tool?.called || tool.settled) return yield* Effect.die(new Error(`Tool progress outside running call: ${id}`))
     tool.progress = update
     yield* bus.publish(SessionEvent.Tool.Progress, {
       sessionID: input.sessionID,
@@ -522,11 +521,7 @@ export const createLLMEventPublisher = (bus: Pick<Bus.Interface, "publish">, inp
   })
 
   /** Publishes one canonical terminal event for a locally executed tool call. */
-  const toolExecution = Effect.fnUntraced(function* (
-    id: string,
-    name: string,
-    result: Tool.Result,
-  ) {
+  const toolExecution = Effect.fnUntraced(function* (id: string, name: string, result: Tool.Result) {
     const tool = tools.get(id)
     if (!tool?.called) return yield* Effect.die(new Error(`Tool execution before call: ${id}`))
     if (tool.name !== name)

+ 5 - 7
packages/core/src/session/transfer.ts

@@ -33,10 +33,7 @@ export interface Interface {
     sessionID: Session.ID
     sanitize?: boolean
   }) => Effect.Effect<Data, Session.NotFoundError | Session.MessageDecodeError>
-  readonly import: (input: {
-    data: Data
-    location: Location.Ref
-  }) => Effect.Effect<Session.Info, ImportConflictError>
+  readonly import: (input: { data: Data; location: Location.Ref }) => Effect.Effect<Session.Info, ImportConflictError>
 }
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/SessionTransfer") {}
@@ -108,7 +105,9 @@ const layer = Layer.effect(
               version: app.version,
               projectID: project.id,
               location: input.location,
-              subpath: RelativePath.make(path.relative(project.directory, input.location.directory).replaceAll("\\", "/")),
+              subpath: RelativePath.make(
+                path.relative(project.directory, input.location.directory).replaceAll("\\", "/"),
+              ),
               title: input.data.info.title,
               agent: input.data.info.agent,
               model: input.data.info.model,
@@ -230,8 +229,7 @@ function sanitizeMessage(message: SessionMessage.Info): SessionMessage.Info {
           ? undefined
           : redact("synthetic-description", message.id, message.description),
     }
-  if (message.type === "system")
-    return { ...message, metadata: meta, text: redact("system", message.id, message.text) }
+  if (message.type === "system") return { ...message, metadata: meta, text: redact("system", message.id, message.text) }
   if (message.type === "skill") return { ...message, metadata: meta, text: redact("skill", message.id, message.text) }
   if (message.type === "shell")
     return {

+ 1 - 2
packages/core/src/shell/parser-wasm.node.ts

@@ -4,8 +4,7 @@ const require = createRequire(import.meta.url)
 
 export const shellParserWasm = {
   runtime: process.env.OPENCODE_TREE_SITTER_WASM_PATH ?? require.resolve("web-tree-sitter/tree-sitter.wasm"),
-  bash:
-    process.env.OPENCODE_TREE_SITTER_BASH_WASM_PATH ?? require.resolve("tree-sitter-bash/tree-sitter-bash.wasm"),
+  bash: process.env.OPENCODE_TREE_SITTER_BASH_WASM_PATH ?? require.resolve("tree-sitter-bash/tree-sitter-bash.wasm"),
   powershell:
     process.env.OPENCODE_TREE_SITTER_POWERSHELL_WASM_PATH ??
     require.resolve("tree-sitter-powershell/tree-sitter-powershell.wasm"),

+ 1 - 1
packages/core/src/snapshot.ts

@@ -59,7 +59,7 @@ export interface Interface {
   /**
    * Restore selected project-relative paths from their associated trees. A path
    * absent from its selected tree is removed; paths outside the map are untouched.
-  */
+   */
   readonly restore: (input: RestoreInput) => Effect.Effect<void, Error>
 }
 

+ 6 - 4
packages/core/src/tool-output.ts

@@ -24,9 +24,7 @@ export interface Interface {
 export class Service extends Context.Service<Service, Interface>()("@opencode/ToolOutput") {}
 
 const cleanup = Effect.fn("ToolOutput.cleanup")(function* (fs: FSUtil.Interface, directory: string) {
-  const cutoff = Identifier.timestamp(
-    Identifier.create("tool", "ascending", Date.now() - Duration.toMillis(RETENTION)),
-  )
+  const cutoff = Identifier.timestamp(Identifier.create("tool", "ascending", Date.now() - Duration.toMillis(RETENTION)))
   const entries = yield* fs.readDirectory(directory).pipe(
     Effect.map((entries) => entries.filter((entry) => /^tool_[0-9a-f]{12}/.test(entry))),
     Effect.catch(() => Effect.succeed([])),
@@ -122,7 +120,11 @@ const cleanupLayer = Layer.effectDiscard(
   }),
 )
 
-const cleanupNode = makeGlobalNode({ name: "tool-output-cleanup", layer: cleanupLayer, deps: [FSUtil.node, Global.node] })
+const cleanupNode = makeGlobalNode({
+  name: "tool-output-cleanup",
+  layer: cleanupLayer,
+  deps: [FSUtil.node, Global.node],
+})
 
 export const node = makeLocationNode({
   service: Service,

+ 47 - 47
packages/core/src/tool/mcp.ts

@@ -49,54 +49,54 @@ export const layer = Layer.effectDiscard(
                 output: (tool.outputSchema ?? {}) as JsonSchema.JsonSchema,
                 execute: (input, context) =>
                   Effect.gen(function* () {
-                yield* permission.assert({
-                  action: name(tool.server, tool.name),
-                  resources: ["*"],
-                  save: ["*"],
-                  metadata: {},
-                  sessionID: context.sessionID,
-                  agent: context.agent,
-                  source: {
-                    type: "tool",
-                    messageID: context.messageID,
-                    id: context.id,
-                  },
-                })
-                const result = yield* mcp
-                  .callTool({
-                    server: tool.server,
-                    name: tool.name,
-                    args: (input ?? {}) as Record<string, unknown>,
-                  })
-                  .pipe(
-                    Effect.catchTags({
-                      "MCP.NotFoundError": (error) =>
-                        new ToolFailure({ message: `MCP server "${error.server}" is not available` }),
-                      "MCP.ToolCallError": (error) => new ToolFailure({ message: error.message }),
-                    }),
-                  )
-                if (result.isError)
-                  return yield* new ToolFailure({
-                    message:
-                      result.content
-                        .flatMap((part) => (part.type === "text" ? [part.text] : []))
-                        .join("\n")
-                        .trim() || "MCP tool returned an error",
-                  })
-                const content = result.content.map((part) =>
-                  part.type === "text"
-                    ? { type: "text" as const, text: part.text }
-                    : {
-                        type: "file" as const,
-                        uri: `data:${part.mimeType};base64,${part.data}`,
-                        mime: part.mimeType,
+                    yield* permission.assert({
+                      action: name(tool.server, tool.name),
+                      resources: ["*"],
+                      save: ["*"],
+                      metadata: {},
+                      sessionID: context.sessionID,
+                      agent: context.agent,
+                      source: {
+                        type: "tool",
+                        messageID: context.messageID,
+                        id: context.id,
                       },
-                )
-                const text = content.flatMap((part) => (part.type === "text" ? [part.text] : [])).join("\n")
-                return {
-                  output: result.structured ?? (text === "" ? null : text),
-                  ...(content.length === 0 ? {} : { content }),
-                }
+                    })
+                    const result = yield* mcp
+                      .callTool({
+                        server: tool.server,
+                        name: tool.name,
+                        args: (input ?? {}) as Record<string, unknown>,
+                      })
+                      .pipe(
+                        Effect.catchTags({
+                          "MCP.NotFoundError": (error) =>
+                            new ToolFailure({ message: `MCP server "${error.server}" is not available` }),
+                          "MCP.ToolCallError": (error) => new ToolFailure({ message: error.message }),
+                        }),
+                      )
+                    if (result.isError)
+                      return yield* new ToolFailure({
+                        message:
+                          result.content
+                            .flatMap((part) => (part.type === "text" ? [part.text] : []))
+                            .join("\n")
+                            .trim() || "MCP tool returned an error",
+                      })
+                    const content = result.content.map((part) =>
+                      part.type === "text"
+                        ? { type: "text" as const, text: part.text }
+                        : {
+                            type: "file" as const,
+                            uri: `data:${part.mimeType};base64,${part.data}`,
+                            mime: part.mimeType,
+                          },
+                    )
+                    const text = content.flatMap((part) => (part.type === "text" ? [part.text] : [])).join("\n")
+                    return {
+                      output: result.structured ?? (text === "" ? null : text),
+                      ...(content.length === 0 ? {} : { content }),
+                    }
                   }).pipe(
                     Effect.mapError((error) =>
                       error instanceof ToolFailure

+ 53 - 58
packages/core/src/tool/plugin/question.ts

@@ -35,10 +35,7 @@ export class CancelledError extends Schema.TaggedErrorClass<CancelledError>()("Q
   }
 }
 
-export const toModelOutput = (
-  questions: ReadonlyArray<Question.Prompt>,
-  answers: ReadonlyArray<Question.Answer>,
-) => {
+export const toModelOutput = (questions: ReadonlyArray<Question.Prompt>, answers: ReadonlyArray<Question.Answer>) => {
   const formatted = questions
     .map(
       (question, index) =>
@@ -56,62 +53,60 @@ export const Plugin = {
 
     yield* ctx.tool
       .transform((draft) =>
-        draft.add(
-          ({
-            name,
-            options: { codemode: false },
-            description,
-            input: Input,
-            output: Output,
-            execute: (input, context) =>
-              permission
-                .assert({
-                  action: "question",
-                  resources: ["*"],
-                  sessionID: context.sessionID,
-                  agent: context.agent,
-                  source: { type: "tool", messageID: context.messageID, id: context.id },
-                })
-                .pipe(
-                  Effect.mapError((error) => new ToolFailure({ message: "Permission denied: question", error })),
-                  Effect.andThen(
-                    forms
-                      .ask({
-                        sessionID: context.sessionID,
-                        title: "Questions",
-                        metadata: {
-                          kind: "question",
-                          tool: { messageID: context.messageID, id: context.id },
-                        },
-                        fields: [
-                          toField(input.questions[0], 0),
-                          ...input.questions.slice(1).map((question, index) => toField(question, index + 1)),
-                        ],
-                      })
-                      .pipe(Effect.orDie),
-                  ),
-                  Effect.flatMap((state) => {
-                    // Deliberate defect tunnel (see Permission.assert): a dismissal must dodge
-                    // leaf `mapError` blankets so it never becomes model-facing tool output; it
-                    // resurfaces as a typed failure at SessionModelRequest.executeTool.
-                    if (state.status === "cancelled") return Effect.die(new CancelledError())
-                    const output = {
-                      answers: input.questions.map((_, index): Question.Answer => {
-                        const value = state.answer[`q${index}`]
-                        if (value === undefined) return []
-                        if (typeof value === "object") return Array.from(value)
-                        return [String(value)]
-                      }),
-                    }
-                    return Effect.succeed({
-                      output,
-                      content: toModelOutput(input.questions, output.answers),
-                      metadata: { answers: output.answers },
+        draft.add({
+          name,
+          options: { codemode: false },
+          description,
+          input: Input,
+          output: Output,
+          execute: (input, context) =>
+            permission
+              .assert({
+                action: "question",
+                resources: ["*"],
+                sessionID: context.sessionID,
+                agent: context.agent,
+                source: { type: "tool", messageID: context.messageID, id: context.id },
+              })
+              .pipe(
+                Effect.mapError((error) => new ToolFailure({ message: "Permission denied: question", error })),
+                Effect.andThen(
+                  forms
+                    .ask({
+                      sessionID: context.sessionID,
+                      title: "Questions",
+                      metadata: {
+                        kind: "question",
+                        tool: { messageID: context.messageID, id: context.id },
+                      },
+                      fields: [
+                        toField(input.questions[0], 0),
+                        ...input.questions.slice(1).map((question, index) => toField(question, index + 1)),
+                      ],
                     })
-                  }),
+                    .pipe(Effect.orDie),
                 ),
-          }),
-        ),
+                Effect.flatMap((state) => {
+                  // Deliberate defect tunnel (see Permission.assert): a dismissal must dodge
+                  // leaf `mapError` blankets so it never becomes model-facing tool output; it
+                  // resurfaces as a typed failure at SessionModelRequest.executeTool.
+                  if (state.status === "cancelled") return Effect.die(new CancelledError())
+                  const output = {
+                    answers: input.questions.map((_, index): Question.Answer => {
+                      const value = state.answer[`q${index}`]
+                      if (value === undefined) return []
+                      if (typeof value === "object") return Array.from(value)
+                      return [String(value)]
+                    }),
+                  }
+                  return Effect.succeed({
+                    output,
+                    content: toModelOutput(input.questions, output.answers),
+                    metadata: { answers: output.answers },
+                  })
+                }),
+              ),
+        }),
       )
       .pipe(Effect.orDie)
   }),

+ 42 - 44
packages/core/src/tool/plugin/skill.ts

@@ -56,50 +56,48 @@ export const Plugin = {
     const permission = yield* Permission.Service
     yield* ctx.tool
       .transform((draft) =>
-        draft.add(
-          ({
-            name,
-            options: { codemode: false },
-            description,
-            input: Input,
-            output: Output,
-            execute: (input, context) =>
-              Effect.gen(function* () {
-                const current = yield* skills.list()
-                const skill = current.find((skill) => skill.id === input.id)
-                if (!skill) return yield* unableToLoad(input.id)
-                return yield* Effect.gen(function* () {
-                  yield* permission.assert({
-                    action: name,
-                    resources: [skill.id],
-                    save: [skill.id],
-                    sessionID: context.sessionID,
-                    agent: context.agent,
-                    source: { type: "tool", messageID: context.messageID, id: context.id },
-                  })
-                  const directory = path.dirname(skill.location)
-                  const files =
-                    path.basename(skill.location) === "SKILL.md"
-                      ? (yield* fs.scan("**/*", { cwd: directory, absolute: true, include: "file", dot: true }))
-                          .filter((file) => path.basename(file) !== "SKILL.md")
-                          .toSorted()
-                          .slice(0, FILE_LIMIT)
-                      : []
-                  return {
-                    name: skill.name,
-                    directory,
-                    output: toModelOutput(skill, files),
-                  }
-                }).pipe(Effect.mapError((error) => unableToLoad(input.id, error)))
-              }).pipe(
-                Effect.map((output) => ({
-                  output,
-                  content: output.output,
-                  metadata: { name: output.name, directory: output.directory },
-                })),
-              ),
-          }),
-        ),
+        draft.add({
+          name,
+          options: { codemode: false },
+          description,
+          input: Input,
+          output: Output,
+          execute: (input, context) =>
+            Effect.gen(function* () {
+              const current = yield* skills.list()
+              const skill = current.find((skill) => skill.id === input.id)
+              if (!skill) return yield* unableToLoad(input.id)
+              return yield* Effect.gen(function* () {
+                yield* permission.assert({
+                  action: name,
+                  resources: [skill.id],
+                  save: [skill.id],
+                  sessionID: context.sessionID,
+                  agent: context.agent,
+                  source: { type: "tool", messageID: context.messageID, id: context.id },
+                })
+                const directory = path.dirname(skill.location)
+                const files =
+                  path.basename(skill.location) === "SKILL.md"
+                    ? (yield* fs.scan("**/*", { cwd: directory, absolute: true, include: "file", dot: true }))
+                        .filter((file) => path.basename(file) !== "SKILL.md")
+                        .toSorted()
+                        .slice(0, FILE_LIMIT)
+                    : []
+                return {
+                  name: skill.name,
+                  directory,
+                  output: toModelOutput(skill, files),
+                }
+              }).pipe(Effect.mapError((error) => unableToLoad(input.id, error)))
+            }).pipe(
+              Effect.map((output) => ({
+                output,
+                content: output.output,
+                metadata: { name: output.name, directory: output.directory },
+              })),
+            ),
+        }),
       )
       .pipe(Effect.orDie)
   }),

+ 116 - 119
packages/core/src/tool/plugin/subagent.ts

@@ -112,138 +112,135 @@ export const Plugin = {
 
     yield* ctx.tool
       .transform((draft) =>
-        draft.add(
-          ({
-            name,
-            options: { codemode: false },
-            description,
-            input: Input,
-            output: Output,
-            execute: (input, context) =>
-              Effect.gen(function* () {
-                const parent = yield* runtime.session
-                  .get(context.sessionID)
-                  .pipe(
-                    Effect.mapError(
-                      (error) => new ToolFailure({ message: `Parent session not found: ${context.sessionID}`, error }),
-                    ),
-                  )
-                let current = parent
-                let depth = 0
-                while (current.parentID) {
-                  depth++
-                  current = yield* runtime.session
-                    .get(current.parentID)
-                    .pipe(
-                      Effect.mapError(
-                        (error) => new ToolFailure({ message: `Parent session not found: ${current.parentID}`, error }),
-                      ),
-                    )
-                }
-                const limit = Config.latest(yield* config.entries(), "experimental")?.subagent_depth ?? 1
-                if (depth >= limit)
-                  return yield* new ToolFailure({
-                    message: `Subagent depth limit reached (${limit}). Increase "experimental.subagent_depth" to allow nested subagents.`,
-                  })
-                const agent = yield* agents.resolve(input.agent)
-                if (agent === undefined) return yield* new ToolFailure({ message: `Unknown agent: ${input.agent}` })
-                if (agent.mode === "primary")
-                  return yield* new ToolFailure({ message: `Agent ${input.agent} cannot run as a subagent` })
-                yield* permission
-                  .assert({
-                    action: name,
-                    resources: [agent.id],
-                    save: [agent.id],
-                    sessionID: context.sessionID,
-                    agent: context.agent,
-                    source: {
-                      type: "tool",
-                      messageID: context.messageID,
-                      id: context.id,
-                    },
-                  })
-                  .pipe(Effect.mapError((error) => new ToolFailure({ message: `Subagent denied: ${agent.id}`, error })))
-
-                // Model selection is policy/config/session state, not an LLM-facing tool argument.
-                const model = agent.model ?? parent.model
-                const child = yield* runtime.session
-                  .create({
-                    parentID: context.sessionID,
-                    title: input.description,
-                    agent: Agent.ID.make(input.agent),
-                    model,
-                    // TODO(opencode kkdvxn): derive restricted subagent permissions from the parent
-                    // session (V1 deriveSubagentSessionPermission). MVP uses the agent's own permissions.
-                  })
+        draft.add({
+          name,
+          options: { codemode: false },
+          description,
+          input: Input,
+          output: Output,
+          execute: (input, context) =>
+            Effect.gen(function* () {
+              const parent = yield* runtime.session
+                .get(context.sessionID)
+                .pipe(
+                  Effect.mapError(
+                    (error) => new ToolFailure({ message: `Parent session not found: ${context.sessionID}`, error }),
+                  ),
+                )
+              let current = parent
+              let depth = 0
+              while (current.parentID) {
+                depth++
+                current = yield* runtime.session
+                  .get(current.parentID)
                   .pipe(
                     Effect.mapError(
-                      (error) => new ToolFailure({ message: `Parent session not found: ${context.sessionID}`, error }),
+                      (error) => new ToolFailure({ message: `Parent session not found: ${current.parentID}`, error }),
                     ),
                   )
+              }
+              const limit = Config.latest(yield* config.entries(), "experimental")?.subagent_depth ?? 1
+              if (depth >= limit)
+                return yield* new ToolFailure({
+                  message: `Subagent depth limit reached (${limit}). Increase "experimental.subagent_depth" to allow nested subagents.`,
+                })
+              const agent = yield* agents.resolve(input.agent)
+              if (agent === undefined) return yield* new ToolFailure({ message: `Unknown agent: ${input.agent}` })
+              if (agent.mode === "primary")
+                return yield* new ToolFailure({ message: `Agent ${input.agent} cannot run as a subagent` })
+              yield* permission
+                .assert({
+                  action: name,
+                  resources: [agent.id],
+                  save: [agent.id],
+                  sessionID: context.sessionID,
+                  agent: context.agent,
+                  source: {
+                    type: "tool",
+                    messageID: context.messageID,
+                    id: context.id,
+                  },
+                })
+                .pipe(Effect.mapError((error) => new ToolFailure({ message: `Subagent denied: ${agent.id}`, error })))
 
-                const background = input.background === true
-                yield* context.progress({
-                  metadata: { sessionID: child.id, status: "running" },
+              // Model selection is policy/config/session state, not an LLM-facing tool argument.
+              const model = agent.model ?? parent.model
+              const child = yield* runtime.session
+                .create({
+                  parentID: context.sessionID,
+                  title: input.description,
+                  agent: Agent.ID.make(input.agent),
+                  model,
+                  // TODO(opencode kkdvxn): derive restricted subagent permissions from the parent
+                  // session (V1 deriveSubagentSessionPermission). MVP uses the agent's own permissions.
                 })
+                .pipe(
+                  Effect.mapError(
+                    (error) => new ToolFailure({ message: `Parent session not found: ${context.sessionID}`, error }),
+                  ),
+                )
 
-                const run = Effect.gen(function* () {
-                  // The child session owns its agent/model (set at create); prompt only admits input.
-                  yield* runtime.session.prompt({
-                    sessionID: child.id,
-                    text: ["You are a subagent spawned by another session.", input.prompt].join("\n"),
-                    resume: false,
-                  })
-                  yield* runtime.session.resume(child.id)
-                  return yield* latestAssistantText(child.id)
-                }).pipe(Effect.onInterrupt(() => runtime.session.interrupt(child.id)))
+              const background = input.background === true
+              yield* context.progress({
+                metadata: { sessionID: child.id, status: "running" },
+              })
 
-                const info = yield* runtime.job.start({
-                  id: child.id,
-                  type: name,
-                  title: input.description,
-                  metadata: {},
-                  run,
+              const run = Effect.gen(function* () {
+                // The child session owns its agent/model (set at create); prompt only admits input.
+                yield* runtime.session.prompt({
+                  sessionID: child.id,
+                  text: ["You are a subagent spawned by another session.", input.prompt].join("\n"),
+                  resume: false,
                 })
+                yield* runtime.session.resume(child.id)
+                return yield* latestAssistantText(child.id)
+              }).pipe(Effect.onInterrupt(() => runtime.session.interrupt(child.id)))
 
-                if (background) {
-                  yield* runtime.job.background(info.id)
-                  yield* notifyWhenDone(context.sessionID, child.id, agent.name, input.description)
-                  return {
-                    sessionID: child.id,
-                    status: "running" as const,
-                    output: backgroundStarted(child.id),
-                  }
+              const info = yield* runtime.job.start({
+                id: child.id,
+                type: name,
+                title: input.description,
+                metadata: {},
+                run,
+              })
+
+              if (background) {
+                yield* runtime.job.background(info.id)
+                yield* notifyWhenDone(context.sessionID, child.id, agent.name, input.description)
+                return {
+                  sessionID: child.id,
+                  status: "running" as const,
+                  output: backgroundStarted(child.id),
                 }
+              }
 
-                const result = yield* runtime.job.block({ id: child.id, sessionID: context.sessionID }).pipe(
-                  Effect.onInterrupt(() =>
-                    Effect.all([runtime.session.interrupt(child.id), runtime.job.cancel(child.id)], {
-                      discard: true,
-                    }),
-                  ),
-                )
-                if (result?.type === "backgrounded") {
-                  yield* notifyWhenDone(context.sessionID, child.id, agent.name, input.description)
-                  return {
-                    sessionID: child.id,
-                    status: "running" as const,
-                    output: backgroundStarted(child.id),
-                  }
+              const result = yield* runtime.job.block({ id: child.id, sessionID: context.sessionID }).pipe(
+                Effect.onInterrupt(() =>
+                  Effect.all([runtime.session.interrupt(child.id), runtime.job.cancel(child.id)], {
+                    discard: true,
+                  }),
+                ),
+              )
+              if (result?.type === "backgrounded") {
+                yield* notifyWhenDone(context.sessionID, child.id, agent.name, input.description)
+                return {
+                  sessionID: child.id,
+                  status: "running" as const,
+                  output: backgroundStarted(child.id),
                 }
-                if (result?.info.status === "error")
-                  return yield* new ToolFailure({ message: result.info.error ?? "Subagent failed" })
-                if (result?.info.status === "cancelled")
-                  return yield* new ToolFailure({ message: "Subagent cancelled" })
-                return { sessionID: child.id, status: "completed" as const, output: result?.info.output ?? NO_TEXT }
-              }).pipe(
-                Effect.map((output) => ({
-                  output,
-                  content: output.output,
-                  metadata: { sessionID: output.sessionID, status: output.status },
-                })),
-              ),
-          }),
-        ),
+              }
+              if (result?.info.status === "error")
+                return yield* new ToolFailure({ message: result.info.error ?? "Subagent failed" })
+              if (result?.info.status === "cancelled") return yield* new ToolFailure({ message: "Subagent cancelled" })
+              return { sessionID: child.id, status: "completed" as const, output: result?.info.output ?? NO_TEXT }
+            }).pipe(
+              Effect.map((output) => ({
+                output,
+                content: output.output,
+                metadata: { sessionID: output.sessionID, status: output.status },
+              })),
+            ),
+        }),
       )
       .pipe(Effect.orDie)
 

+ 53 - 55
packages/core/src/tool/plugin/webfetch.ts

@@ -118,62 +118,60 @@ export const Plugin = {
 
     yield* ctx.tool
       .transform((draft) =>
-        draft.add(
-          ({
-            name,
-            options: { codemode: false },
-            description,
-            input: Input,
-            output: Output,
-            execute: (input, context) =>
-              Effect.gen(function* () {
-                yield* Effect.try({
-                  try: () => assertHttpUrl(new URL(input.url)),
-                  catch: (error) => error,
-                })
-
-                yield* permission.assert({
-                  action: name,
-                  resources: [input.url],
-                  save: ["*"],
-                  metadata: input,
-                  sessionID: context.sessionID,
-                  agent: context.agent,
-                  source: { type: "tool", messageID: context.messageID, id: context.id },
-                })
-
-                const { body, contentType } = yield* Effect.gen(function* () {
-                  const response = yield* execute(http, input.url, input.format).pipe(
-                    Effect.catchIf(isCloudflareChallenge, () => execute(http, input.url, input.format, "opencode")),
-                  )
-                  const contentType = response.headers["content-type"] || ""
-                  const mime = mimeFrom(contentType)
-                  if (isImageAttachment(mime))
-                    return yield* Effect.fail(new Error(`Unsupported fetched image content type: ${mime}`))
-                  if (!isTextualMime(mime))
-                    return yield* Effect.fail(new Error(`Unsupported fetched file content type: ${mime}`))
-                  return { body: yield* collectBody(response), contentType }
-                }).pipe(
-                  Effect.timeoutOrElse({
-                    duration: Duration.seconds(input.timeout ?? DEFAULT_TIMEOUT_SECONDS),
-                    orElse: () => Effect.fail(new Error("Request timed out")),
-                  }),
+        draft.add({
+          name,
+          options: { codemode: false },
+          description,
+          input: Input,
+          output: Output,
+          execute: (input, context) =>
+            Effect.gen(function* () {
+              yield* Effect.try({
+                try: () => assertHttpUrl(new URL(input.url)),
+                catch: (error) => error,
+              })
+
+              yield* permission.assert({
+                action: name,
+                resources: [input.url],
+                save: ["*"],
+                metadata: input,
+                sessionID: context.sessionID,
+                agent: context.agent,
+                source: { type: "tool", messageID: context.messageID, id: context.id },
+              })
+
+              const { body, contentType } = yield* Effect.gen(function* () {
+                const response = yield* execute(http, input.url, input.format).pipe(
+                  Effect.catchIf(isCloudflareChallenge, () => execute(http, input.url, input.format, "opencode")),
                 )
-                const content = new TextDecoder().decode(body)
-                const output = yield* Effect.try({
-                  try: () => convert(content, contentType, input.format),
-                  catch: (error) => error,
-                })
-                const result = {
-                  url: input.url,
-                  contentType,
-                  format: input.format,
-                  output,
-                }
-                return { output: result, content: result.output, metadata: { contentType: result.contentType } }
-              }).pipe(Effect.mapError((error) => new ToolFailure({ message: `Unable to fetch ${input.url}`, error }))),
-          }),
-        ),
+                const contentType = response.headers["content-type"] || ""
+                const mime = mimeFrom(contentType)
+                if (isImageAttachment(mime))
+                  return yield* Effect.fail(new Error(`Unsupported fetched image content type: ${mime}`))
+                if (!isTextualMime(mime))
+                  return yield* Effect.fail(new Error(`Unsupported fetched file content type: ${mime}`))
+                return { body: yield* collectBody(response), contentType }
+              }).pipe(
+                Effect.timeoutOrElse({
+                  duration: Duration.seconds(input.timeout ?? DEFAULT_TIMEOUT_SECONDS),
+                  orElse: () => Effect.fail(new Error("Request timed out")),
+                }),
+              )
+              const content = new TextDecoder().decode(body)
+              const output = yield* Effect.try({
+                try: () => convert(content, contentType, input.format),
+                catch: (error) => error,
+              })
+              const result = {
+                url: input.url,
+                contentType,
+                format: input.format,
+                output,
+              }
+              return { output: result, content: result.output, metadata: { contentType: result.contentType } }
+            }).pipe(Effect.mapError((error) => new ToolFailure({ message: `Unable to fetch ${input.url}`, error }))),
+        }),
       )
       .pipe(Effect.orDie)
   }),

+ 2 - 1
packages/core/src/tool/runtime.ts

@@ -44,7 +44,8 @@ const encodeOutput = (schema: Tool.ValueSchema<any>, value: unknown) => {
   if (Schema.isSchema(schema))
     return Schema.encodeEffect(schema)(value).pipe(
       Effect.mapError(
-        (error) => new Tool.Error({ message: `Tool returned an invalid value for its output schema: ${error.message}` }),
+        (error) =>
+          new Tool.Error({ message: `Tool returned an invalid value for its output schema: ${error.message}` }),
       ),
     )
   if (isStandardSchema(schema))

+ 1 - 1
packages/core/src/vcs.ts

@@ -46,7 +46,7 @@ const layer = Layer.effect(
     const bus = yield* Bus.Service
     const impl = adapter(proc, fs, location)
     const vcs = location.vcs
-    const state = { info: impl ? yield* impl.info() : { branch: {} } satisfies Info }
+    const state = { info: impl ? yield* impl.info() : ({ branch: {} } satisfies Info) }
 
     if (vcs && impl) {
       const store = yield* fs.realPath(vcs.store).pipe(Effect.catch(() => Effect.succeed(vcs.store)))

+ 4 - 1
packages/core/src/vcs/git.ts

@@ -181,7 +181,10 @@ function makeGit(proc: AppProcess.Interface) {
     if (remote) {
       const head = yield* run(["symbolic-ref", `refs/remotes/${remote}/HEAD`], { cwd })
       if (head.exitCode === 0) {
-        const ref = head.text().trim().replace(/^refs\/remotes\//, "")
+        const ref = head
+          .text()
+          .trim()
+          .replace(/^refs\/remotes\//, "")
         const name = ref.startsWith(`${remote}/`) ? ref.slice(`${remote}/`.length) : ""
         if (name) return { name, ref } satisfies Base
       }

+ 14 - 12
packages/core/test/agent.test.ts

@@ -130,18 +130,20 @@ describe("Agent", () => {
       expect(info?.permissions.slice(0, Agent.Info.default(id).permissions.length)).toEqual(
         Agent.Info.default(id).permissions,
       )
-      expect(Permission.evaluate("external_directory", path.join(global.data, "shell", "*", "*"), info?.permissions ?? []).effect).toBe(
-        "allow",
-      )
-      expect(Permission.evaluate("external_directory", path.join(global.data, "tool-output", "*"), info?.permissions ?? []).effect).toBe(
-        "allow",
-      )
-      expect(Permission.evaluate("external_directory", path.join(global.config, "*"), info?.permissions ?? []).effect).toBe(
-        "allow",
-      )
-      expect(Permission.evaluate("external_directory", path.join(global.tmp, "*"), info?.permissions ?? []).effect).toBe(
-        "allow",
-      )
+      expect(
+        Permission.evaluate("external_directory", path.join(global.data, "shell", "*", "*"), info?.permissions ?? [])
+          .effect,
+      ).toBe("allow")
+      expect(
+        Permission.evaluate("external_directory", path.join(global.data, "tool-output", "*"), info?.permissions ?? [])
+          .effect,
+      ).toBe("allow")
+      expect(
+        Permission.evaluate("external_directory", path.join(global.config, "*"), info?.permissions ?? []).effect,
+      ).toBe("allow")
+      expect(
+        Permission.evaluate("external_directory", path.join(global.tmp, "*"), info?.permissions ?? []).effect,
+      ).toBe("allow")
 
       yield* agent.transform((editor) => editor.remove(id))
       expect(yield* agent.get(id)).toBeUndefined()

+ 1 - 3
packages/core/test/app.test.ts

@@ -2,7 +2,5 @@ import { expect, test } from "bun:test"
 import { App } from "@opencode-ai/core/app"
 
 test("formats app metadata as a user agent", () => {
-  expect(App.useragent(App.make({ name: "sdk", version: "1.2.3", channel: "beta" }))).toBe(
-    "opencode/beta/1.2.3/sdk",
-  )
+  expect(App.useragent(App.make({ name: "sdk", version: "1.2.3", channel: "beta" }))).toBe("opencode/beta/1.2.3/sdk")
 })

+ 2 - 7
packages/core/test/bus.test.ts

@@ -105,13 +105,9 @@ const it = testEffect(
   ]),
 )
 const itWithoutLocation = testEffect(
-  AppNodeBuilder.build(LayerNode.group([Database.node, Bus.node]), [
-    [Bus.node, Bus.configured({ persist: true })],
-  ]),
-)
-const itWithoutPersistence = testEffect(
-  AppNodeBuilder.build(LayerNode.group([Database.node, Bus.node])),
+  AppNodeBuilder.build(LayerNode.group([Database.node, Bus.node]), [[Bus.node, Bus.configured({ persist: true })]]),
 )
+const itWithoutPersistence = testEffect(AppNodeBuilder.build(LayerNode.group([Database.node, Bus.node])))
 
 describe("Bus", () => {
   it.effect("subscribes to multiple event definitions with a discriminated payload union", () =>
@@ -1341,5 +1337,4 @@ describe("Bus", () => {
       }).pipe(Effect.provide(eventLayer))
     }),
   )
-
 })

+ 6 - 6
packages/core/test/codemode.test.ts

@@ -12,12 +12,12 @@ describe("CodeMode", () => {
       const tools = yield* Tool.Service
       yield* tools.transform((draft) =>
         draft.add({
-            name: "echo",
-            description: "Echo text",
-            input: Schema.Struct({ text: Schema.String }),
-            output: Schema.String,
-            options: { pinned: true },
-            execute: ({ text }) => Effect.succeed({ output: text }),
+          name: "echo",
+          description: "Echo text",
+          input: Schema.Struct({ text: Schema.String }),
+          output: Schema.String,
+          options: { pinned: true },
+          execute: ({ text }) => Effect.succeed({ output: text }),
         }),
       )
 

+ 1 - 4
packages/core/test/codemode/catalog.test.ts

@@ -48,10 +48,7 @@ describe("CodeModeCatalog.summarize", () => {
   })
 
   test("always retains pinned tools beyond the inline budget", () => {
-    const pinned = [
-      entry("alpha.first", "First", undefined, true),
-      entry("beta.second", "Second", undefined, true),
-    ]
+    const pinned = [entry("alpha.first", "First", undefined, true), entry("beta.second", "Second", undefined, true)]
     const catalog = CodeModeCatalog.summarize([...pinned, entry("alpha.unpinned", "Unpinned")], 0)
 
     expect(catalog.shown).toBe(2)

+ 6 - 11
packages/core/test/codemode/instructions.test.ts

@@ -70,20 +70,20 @@ describe("CodeModeInstructions", () => {
   )
 
   it.effect("stores a canonical sorted snapshot so registration order does not churn history", () => {
-    const alpha = ({
+    const alpha = {
       name: "alpha",
       description: "Alpha tool",
       input: Schema.Struct({}),
       output: Schema.String,
       execute: () => Effect.succeed({ output: "alpha" }),
-    })
-    const zeta = ({
+    }
+    const zeta = {
       name: "zeta",
       description: "Zeta tool",
       input: Schema.Struct({}),
       output: Schema.String,
       execute: () => Effect.succeed({ output: "zeta" }),
-    })
+    }
     const layer = AppNodeBuilder.build(Tool.node, [
       [Location.node, Location.boundNode({ directory: AbsolutePath.make("/project") })],
     ])
@@ -96,9 +96,7 @@ describe("CodeModeInstructions", () => {
             draft.add({ ...zeta, options: { namespace: "tools" } })
             draft.add({ ...alpha, options: { namespace: "tools" } })
           })
-          return yield* readInitial(
-            CodeModeInstructions.make((yield* tools.snapshot()).codeModeCatalog),
-          )
+          return yield* readInitial(CodeModeInstructions.make((yield* tools.snapshot()).codeModeCatalog))
         }),
       )
       const reordered = yield* Effect.scoped(
@@ -107,10 +105,7 @@ describe("CodeModeInstructions", () => {
             draft.add({ ...alpha, options: { namespace: "tools" } })
             draft.add({ ...zeta, options: { namespace: "tools" } })
           })
-          return yield* readUpdate(
-            CodeModeInstructions.make((yield* tools.snapshot()).codeModeCatalog),
-            initialized,
-          )
+          return yield* readUpdate(CodeModeInstructions.make((yield* tools.snapshot()).codeModeCatalog), initialized)
         }),
       )
 

+ 2 - 6
packages/core/test/config/config.test.ts

@@ -373,9 +373,7 @@ describe("Config", () => {
             const initial = yield* config.entries()
             expect(Config.latest(initial, "shell")).toBe("project")
             expect(
-              initial.flatMap((entry) =>
-                entry.type === "document" && entry.info.shell ? [entry.info.shell] : [],
-              ),
+              initial.flatMap((entry) => (entry.type === "document" && entry.info.shell ? [entry.info.shell] : [])),
             ).toEqual(["secret", "global", "project"])
             const updated = yield* bus
               .subscribe(Event.Updated)
@@ -387,9 +385,7 @@ describe("Config", () => {
             const refreshed = yield* config.entries()
             expect(Config.latest(refreshed, "shell")).toBe("project")
             expect(
-              refreshed.flatMap((entry) =>
-                entry.type === "document" && entry.info.shell ? [entry.info.shell] : [],
-              ),
+              refreshed.flatMap((entry) => (entry.type === "document" && entry.info.shell ? [entry.info.shell] : [])),
             ).toEqual(["next", "global", "project"])
           }).pipe(
             Effect.provide(testLayer(project, global, project, undefined, undefined, credentialNode, wellknownNode)),

+ 0 - 1
packages/core/test/effect/observability.test.ts

@@ -12,7 +12,6 @@ const otelResourceAttributes = process.env.OTEL_RESOURCE_ATTRIBUTES
 afterEach(() => {
   if (otelResourceAttributes === undefined) delete process.env.OTEL_RESOURCE_ATTRIBUTES
   else process.env.OTEL_RESOURCE_ATTRIBUTES = otelResourceAttributes
-
 })
 
 describe("resource", () => {

+ 1 - 4
packages/core/test/formatter.test.ts

@@ -39,10 +39,7 @@ function formatterLayer(directory: string, configured?: ConfigInput["formatter"]
     ],
     [
       Location.node,
-      Layer.succeed(
-        Location.Service,
-        Location.Service.of(location({ directory: AbsolutePath.make(directory) })),
-      ),
+      Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make(directory) }))),
     ],
     [Npm.node, Layer.mock(Npm.Service, { which: () => Effect.succeed(undefined) })],
   ])

+ 6 - 1
packages/core/test/instruction-state.test.ts

@@ -14,7 +14,12 @@ import { AbsolutePath } from "@opencode-ai/core/schema"
 import { InstructionState } from "@opencode-ai/core/session/instruction-state"
 import { SessionProjector } from "@opencode-ai/core/session/projector"
 import { SessionSchema } from "@opencode-ai/core/session/schema"
-import { InstructionBlobTable, InstructionStateTable, SessionMessageTable, SessionTable } from "@opencode-ai/core/session/sql"
+import {
+  InstructionBlobTable,
+  InstructionStateTable,
+  SessionMessageTable,
+  SessionTable,
+} from "@opencode-ai/core/session/sql"
 import { testEffect } from "./lib/effect"
 
 const it = testEffect(

+ 1 - 5
packages/core/test/lib/tool.ts

@@ -16,11 +16,7 @@ export const toolIdentity = {
 export const toolDefinitions = (registry: Tool.Interface, permissions?: Permission.Ruleset) =>
   registry.snapshot(permissions).pipe(Effect.map((toolSet) => toolSet.definitions))
 
-export function waitForTool(
-  registry: Tool.Interface,
-  name: string,
-  remaining = 1000,
-): Effect.Effect<void, Error> {
+export function waitForTool(registry: Tool.Interface, name: string, remaining = 1000): Effect.Effect<void, Error> {
   return Effect.gen(function* () {
     if ((yield* toolDefinitions(registry)).some((tool) => tool.name === name)) return
     if (remaining === 0) {

+ 12 - 10
packages/core/test/mcp.test.ts

@@ -909,16 +909,18 @@ it.effect("waits for permission before calling an MCP tool", () =>
     const registry = yield* Tool.Service
     const toolSet = yield* waitForCodeModeTool(registry, "demo.search")
 
-    const fiber = yield* toolSet.execute({
-      sessionID: Session.ID.make("ses_mcp_permission"),
-      ...toolIdentity,
-      call: {
-        type: "tool-call",
-        id: "call_mcp_permission",
-        name: "execute",
-        input: { code: "return await tools.demo.search({})" },
-      },
-    }).pipe(Effect.forkScoped)
+    const fiber = yield* toolSet
+      .execute({
+        sessionID: Session.ID.make("ses_mcp_permission"),
+        ...toolIdentity,
+        call: {
+          type: "tool-call",
+          id: "call_mcp_permission",
+          name: "execute",
+          input: { code: "return await tools.demo.search({})" },
+        },
+      })
+      .pipe(Effect.forkScoped)
     expect(yield* Deferred.await(assertion)).toEqual({
       action: "demo_search",
       resources: ["*"],

+ 48 - 40
packages/core/test/patch.test.ts

@@ -177,11 +177,7 @@ describe("Patch", () => {
   })
 
   test("preserves the end-of-file marker", () => {
-    expect(
-      parse(
-        "*** Begin Patch\n*** Update File: file.txt\n@@\n+quux\n*** End of File\n\n*** End Patch",
-      ),
-    ).toEqual([
+    expect(parse("*** Begin Patch\n*** Update File: file.txt\n@@\n+quux\n*** End of File\n\n*** End Patch")).toEqual([
       {
         type: "update",
         path: "file.txt",
@@ -192,16 +188,16 @@ describe("Patch", () => {
   })
 
   test("allows an end-of-file marker before an explicit chunk", () => {
-    expect(
-      parse("*** Begin Patch\n*** Update File: file.txt\n*** End of File\n@@\n-old\n+new\n*** End Patch"),
-    ).toEqual([
-      {
-        type: "update",
-        path: "file.txt",
-        movePath: undefined,
-        chunks: [{ oldLines: ["old"], newLines: ["new"], changeContext: undefined }],
-      },
-    ])
+    expect(parse("*** Begin Patch\n*** Update File: file.txt\n*** End of File\n@@\n-old\n+new\n*** End Patch")).toEqual(
+      [
+        {
+          type: "update",
+          path: "file.txt",
+          movePath: undefined,
+          chunks: [{ oldLines: ["old"], newLines: ["new"], changeContext: undefined }],
+        },
+      ],
+    )
   })
 
   test("allows an end-of-file marker before an implicit chunk and move", () => {
@@ -247,9 +243,9 @@ describe("Patch", () => {
   })
 
   test("appends a pure-addition chunk to a nonempty file", () => {
-    expect(Patch.derive("update.txt", [{ oldLines: [], newLines: ["added 1", "added 2"] }], "line 1\nline 2\n").content).toBe(
-      "line 1\nline 2\nadded 1\nadded 2\n",
-    )
+    expect(
+      Patch.derive("update.txt", [{ oldLines: [], newLines: ["added 1", "added 2"] }], "line 1\nline 2\n").content,
+    ).toBe("line 1\nline 2\nadded 1\nadded 2\n")
   })
 
   test("applies a pure-addition chunk after an earlier replacement", () => {
@@ -303,27 +299,35 @@ describe("Patch", () => {
 
   test("matches Unicode minus signs and spaces", () => {
     expect(
-      Patch.derive("minus.txt", [{ oldLines: ["value - 1"], newLines: ["value - 2"] }], "value − 1\n")
-        .content,
+      Patch.derive("minus.txt", [{ oldLines: ["value - 1"], newLines: ["value - 2"] }], "value − 1\n").content,
     ).toBe("value - 2\n")
-    const spaces = ["\u00A0", "\u2002", "\u2003", "\u2004", "\u2005", "\u2006", "\u2007", "\u2008", "\u2009", "\u200A", "\u202F", "\u205F", "\u3000"]
-    spaces.forEach(
-      (space) => {
-        expect(
-          Patch.derive(
-            "spaces.txt",
-            [{ oldLines: ["hello world"], newLines: ["hello there"] }],
-            `hello${space}world\n`,
-          ).content,
-        ).toBe("hello there\n")
-      },
-    )
+    const spaces = [
+      "\u00A0",
+      "\u2002",
+      "\u2003",
+      "\u2004",
+      "\u2005",
+      "\u2006",
+      "\u2007",
+      "\u2008",
+      "\u2009",
+      "\u200A",
+      "\u202F",
+      "\u205F",
+      "\u3000",
+    ]
+    spaces.forEach((space) => {
+      expect(
+        Patch.derive("spaces.txt", [{ oldLines: ["hello world"], newLines: ["hello there"] }], `hello${space}world\n`)
+          .content,
+      ).toBe("hello there\n")
+    })
   })
 
   test("does not normalize ellipses", () => {
-    expect(() =>
-      Patch.derive("ellipsis.txt", [{ oldLines: ["wait..."], newLines: ["done"] }], "wait…\n"),
-    ).toThrow("Failed to find expected lines")
+    expect(() => Patch.derive("ellipsis.txt", [{ oldLines: ["wait..."], newLines: ["done"] }], "wait…\n")).toThrow(
+      "Failed to find expected lines",
+    )
   })
 
   test("prefers a later exact match over an earlier normalized match", () => {
@@ -357,9 +361,9 @@ describe("Patch", () => {
   })
 
   test("identifies a missing blank line", () => {
-    expect(() =>
-      Patch.derive("update.txt", [{ oldLines: [""], newLines: ["added"] }], "content\n"),
-    ).toThrow("Failed to find an expected blank line in update.txt")
+    expect(() => Patch.derive("update.txt", [{ oldLines: [""], newLines: ["added"] }], "content\n")).toThrow(
+      "Failed to find an expected blank line in update.txt",
+    )
   })
 
   test("parses an update without an explicit first chunk header", () => {
@@ -483,7 +487,9 @@ describe("Patch", () => {
       "Invalid hunk at line 4: Unexpected line found in update hunk: '@@'",
     )
     expect(() =>
-      parse("*** Begin Patch\n*** Update File: file.txt\n@@\n*** Update File: other.txt\n@@\n-old\n+new\n*** End Patch"),
+      parse(
+        "*** Begin Patch\n*** Update File: file.txt\n@@\n*** Update File: other.txt\n@@\n-old\n+new\n*** End Patch",
+      ),
     ).toThrow("Invalid hunk at line 4: Unexpected line found in update hunk: '*** Update File: other.txt'")
     expect(() => parse("*** Begin Patch\n*** Update File: file.txt\n@@\nbad\n*** End Patch")).toThrow(
       "Invalid hunk at line 4: Unexpected line found in update hunk: 'bad'",
@@ -497,7 +503,9 @@ describe("Patch", () => {
     expect(() => parse("*** Begin Patch\n*** Update File: file.txt\n@@foo\n*** End Patch")).toThrow(
       "Invalid hunk at line 3: Unexpected line found in update hunk: '@@foo'",
     )
-    expect(() => parse("*** Begin Patch\n*** Update File: file.txt\n@@\n-old\n*** Frobnicate File: foo\n*** End Patch")).toThrow(
+    expect(() =>
+      parse("*** Begin Patch\n*** Update File: file.txt\n@@\n-old\n*** Frobnicate File: foo\n*** End Patch"),
+    ).toThrow(
       "Invalid hunk at line 5: Expected update hunk to start with a @@ context marker, got: '*** Frobnicate File: foo'",
     )
   })

+ 1 - 8
packages/core/test/permission.test.ts

@@ -26,14 +26,7 @@ const current = Layer.succeed(
 )
 const it = testEffect(
   AppNodeBuilder.build(
-    LayerNode.group([
-      Database.node,
-      Bus.node,
-      SessionStore.node,
-      PermissionSaved.node,
-      Agent.node,
-      Permission.node,
-    ]),
+    LayerNode.group([Database.node, Bus.node, SessionStore.node, PermissionSaved.node, Agent.node, Permission.node]),
     [[Location.node, current]],
   ),
 )

+ 27 - 31
packages/core/test/plugin.test.ts

@@ -250,16 +250,14 @@ describe("Plugin", () => {
         effect: (ctx) =>
           ctx.tool
             .transform((draft) =>
-              draft.add(
-                ({
-                  name: "plugin_tool",
-                  options: { codemode: false },
-                  description: "Plugin tool",
-                  input: Schema.Struct({}),
-                  output: Schema.Struct({ ok: Schema.Boolean }),
-                  execute: () => Effect.succeed({ output: { ok: true } }),
-                }),
-              ),
+              draft.add({
+                name: "plugin_tool",
+                options: { codemode: false },
+                description: "Plugin tool",
+                input: Schema.Struct({}),
+                output: Schema.Struct({ ok: Schema.Boolean }),
+                execute: () => Effect.succeed({ output: { ok: true } }),
+              }),
             )
             .pipe(Effect.orDie),
       })
@@ -276,15 +274,14 @@ describe("Plugin", () => {
     Effect.gen(function* () {
       const plugins = yield* Plugin.Service
       const registry = yield* Tool.Service
-      const tool = (name: string, description: string, options?: Tool.Options) =>
-        ({
-          name,
-          options,
-          description,
-          input: Schema.Struct({}),
-          output: Schema.Struct({ ok: Schema.Boolean }),
-          execute: () => Effect.succeed({ output: { ok: true } }),
-        })
+      const tool = (name: string, description: string, options?: Tool.Options) => ({
+        name,
+        options,
+        description,
+        input: Schema.Struct({}),
+        output: Schema.Struct({ ok: Schema.Boolean }),
+        execute: () => Effect.succeed({ output: { ok: true } }),
+      })
       const plugin = EffectPlugin.define({
         id: "grouped-tools",
         effect: (ctx) =>
@@ -323,17 +320,15 @@ describe("Plugin", () => {
           Effect.gen(function* () {
             yield* ctx.tool
               .transform((draft) =>
-                draft.add(
-                  ({
-                    name: "echo",
-                    options: { codemode: false },
-                    description: "Echo",
-                    input: Schema.Struct({ text: Schema.String }),
-                    output: Schema.Struct({ text: Schema.String }),
-                    execute: ({ text }) =>
-                      Effect.sync(() => executed.push({ text })).pipe(Effect.as({ output: { text } })),
-                  }),
-                ),
+                draft.add({
+                  name: "echo",
+                  options: { codemode: false },
+                  description: "Echo",
+                  input: Schema.Struct({ text: Schema.String }),
+                  output: Schema.Struct({ text: Schema.String }),
+                  execute: ({ text }) =>
+                    Effect.sync(() => executed.push({ text })).pipe(Effect.as({ output: { text } })),
+                }),
               )
               .pipe(Effect.orDie)
 
@@ -368,7 +363,8 @@ describe("Plugin", () => {
             yield* ctx.tool
               .hook("execute.after", (event) =>
                 Effect.sync(() => {
-                  if (event.status === "completed" && Array.isArray(event.result.content)) event.result.content.splice(0)
+                  if (event.status === "completed" && Array.isArray(event.result.content))
+                    event.result.content.splice(0)
                 }),
               )
               .pipe(Effect.asVoid)

+ 2 - 5
packages/core/test/plugin/host.ts

@@ -226,8 +226,7 @@ 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) => draft.model.remove(Provider.ID.make(providerID), Model.ID.make(modelID)),
             default: {
               get: () => {
                 const value = draft.model.default.get()
@@ -417,9 +416,7 @@ function method(value: Integration.Method) {
   }
 }
 
-function internalMethod(
-  value: IntegrationMethodRegistration["method"],
-): Integration.Method {
+function internalMethod(value: IntegrationMethodRegistration["method"]): Integration.Method {
   if (value.type === "env") return value
   if (value.type === "key") return value
   if (value.type === "command") {

+ 3 - 12
packages/core/test/plugin/provider-cerebras.test.ts

@@ -65,10 +65,7 @@ describe("CerebrasPlugin", () => {
       yield* addPlugin()
       const result = yield* aisdk.runSDK({
         model: Model.Info.make({
-          ...Model.Info.default(
-            Provider.ID.make("custom-cerebras"),
-            Model.ID.make("llama-4-scout-17b-16e-instruct"),
-          ),
+          ...Model.Info.default(Provider.ID.make("custom-cerebras"), Model.ID.make("llama-4-scout-17b-16e-instruct")),
           modelID: Model.ID.make("llama-4-scout-17b-16e-instruct"),
           package: "aisdk:test-provider",
         }),
@@ -88,10 +85,7 @@ describe("CerebrasPlugin", () => {
       yield* addPlugin()
       yield* aisdk.runSDK({
         model: Model.Info.make({
-          ...Model.Info.default(
-            Provider.ID.make("custom-cerebras"),
-            Model.ID.make("llama-4-scout-17b-16e-instruct"),
-          ),
+          ...Model.Info.default(Provider.ID.make("custom-cerebras"), Model.ID.make("llama-4-scout-17b-16e-instruct")),
           modelID: Model.ID.make("llama-4-scout-17b-16e-instruct"),
           package: "aisdk:test-provider",
         }),
@@ -110,10 +104,7 @@ describe("CerebrasPlugin", () => {
       yield* addPlugin()
       const result = yield* aisdk.runSDK({
         model: Model.Info.make({
-          ...Model.Info.default(
-            Provider.ID.make("custom-cerebras"),
-            Model.ID.make("llama-4-scout-17b-16e-instruct"),
-          ),
+          ...Model.Info.default(Provider.ID.make("custom-cerebras"), Model.ID.make("llama-4-scout-17b-16e-instruct")),
           modelID: Model.ID.make("llama-4-scout-17b-16e-instruct"),
           package: "aisdk:test-provider",
         }),

+ 2 - 7
packages/core/test/plugin/provider-openai.test.ts

@@ -128,12 +128,8 @@ describe("OpenAIPlugin", () => {
       expect(eligible.cost).toEqual([])
       expect(eligible.limit).toEqual({ context: 400_000, input: 272_000, output: 128_000 })
       expect(eligible.enabled).toBe(true)
-      expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.5-pro"))).enabled).toBe(
-        false,
-      )
-      expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.4-pro"))).enabled).toBe(
-        false,
-      )
+      expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.5-pro"))).enabled).toBe(false)
+      expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.4-pro"))).enabled).toBe(false)
       expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.4"))).limit).toEqual({
         context: 400_000,
         input: 272_000,
@@ -180,5 +176,4 @@ describe("OpenAIPlugin", () => {
       expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-4.1"))).enabled).toBe(true)
     }),
   )
-
 })

+ 1 - 2
packages/core/test/plugin/provider-openrouter.test.ts

@@ -75,8 +75,7 @@ describe("OpenRouterPlugin", () => {
       })
       yield* addPlugin()
       expect(
-        (yield* catalog.model.get(Provider.ID.make("custom-openrouter"), Model.ID.make("gpt-5-chat-latest")))
-          ?.enabled,
+        (yield* catalog.model.get(Provider.ID.make("custom-openrouter"), Model.ID.make("gpt-5-chat-latest")))?.enabled,
       ).toBe(true)
     }),
   )

+ 1 - 3
packages/core/test/plugin/system-prompt.test.ts

@@ -43,9 +43,7 @@ describe("SystemPromptPlugin", () => {
     expect(PROMPT_META).toContain("`edit` for editing")
     expect(PROMPT_META).toContain("`write` for creating files")
     expect(PROMPT_META).toContain("https://opencode.ai/v2/docs/")
-    expect(PROMPT_META).not.toMatch(
-      /TodoWrite|Task tool|WebFetch|\bBash\b|https:\/\/opencode\.ai\/docs/,
-    )
+    expect(PROMPT_META).not.toMatch(/TodoWrite|Task tool|WebFetch|\bBash\b|https:\/\/opencode\.ai\/docs/)
   })
 
   test("uses granular IDs with a common prefix", () => {

+ 3 - 9
packages/core/test/plugin/websearch-fixture.ts

@@ -41,15 +41,9 @@ const http = Layer.succeed(
 
 export const webSearchIntegrationTest = testEffect(
   Layer.merge(
-    AppNodeBuilder.build(
-      LayerNode.group([Integration.node, Credential.node, Bus.node, Form.node, WebSearch.node]),
-      [
-        [
-          Config.node,
-          Config.testLayer(),
-        ],
-      ],
-    ),
+    AppNodeBuilder.build(LayerNode.group([Integration.node, Credential.node, Bus.node, Form.node, WebSearch.node]), [
+      [Config.node, Config.testLayer()],
+    ]),
     http,
   ),
 )

+ 1 - 4
packages/core/test/project.test.ts

@@ -366,10 +366,7 @@ describe("Project.resolve", () => {
       })
       expect(
         (yield* project.directories({ projectID: id })).toSorted((a, b) => a.directory.localeCompare(b.directory)),
-      ).toEqual([
-        { directory: yield* real(tmp.path) },
-        { directory: yield* real(worktree), strategy: "git_worktree" },
-      ])
+      ).toEqual([{ directory: yield* real(tmp.path) }, { directory: yield* real(worktree), strategy: "git_worktree" }])
     }),
   )
 })

+ 1 - 3
packages/core/test/pty/pty-session.test.ts

@@ -213,9 +213,7 @@ const configuredIt = testEffect(
       Layer.mock(Config.Service)({
         entries: () =>
           Effect.succeed(
-            configuredShell
-              ? [new Document({ type: "document", info: new Info({ shell: configuredShell }) })]
-              : [],
+            configuredShell ? [new Document({ type: "document", info: new Info({ shell: configuredShell }) })] : [],
           ),
       }),
     ],

+ 3 - 1
packages/core/test/ripgrep.test.ts

@@ -129,7 +129,9 @@ describe("Ripgrep", () => {
       Effect.promise(() => tmpdir()),
       (tmp) =>
         Effect.gen(function* () {
-          yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "generated.ts"), `Cloudflare${"x".repeat(70 * 1024)}\n`))
+          yield* Effect.promise(() =>
+            fs.writeFile(path.join(tmp.path, "generated.ts"), `Cloudflare${"x".repeat(70 * 1024)}\n`),
+          )
 
           const matches = yield* (yield* Ripgrep.Service).grep({
             cwd: tmp.path,

+ 15 - 23
packages/core/test/session-prompt.test.ts

@@ -563,11 +563,7 @@ describe("Session.prompt", () => {
       const input = { sessionID, id: messageID, text: "Fix the failing tests", resume: false }
       const first = yield* session.prompt(input)
       yield* SessionPending.promote(db, bus, sessionID, "steer")
-      yield* db
-        .delete(EventTable)
-        .where(eq(EventTable.aggregate_id, sessionID))
-        .run()
-        .pipe(Effect.orDie)
+      yield* db.delete(EventTable).where(eq(EventTable.aggregate_id, sessionID)).run().pipe(Effect.orDie)
 
       const retried = yield* session.prompt(input)
 
@@ -982,13 +978,9 @@ describe("Session.prompt", () => {
 
       expect(input.delivery).toBe("queue")
       expect(yield* SessionPending.has(db, sessionID, "input")).toBe(true)
-      expect(
-        yield* SessionPending.promote(db, bus, sessionID, "steer"),
-      ).toBe(0)
+      expect(yield* SessionPending.promote(db, bus, sessionID, "steer")).toBe(0)
       expect(yield* session.messages({ sessionID })).toEqual([])
-      expect(
-        yield* SessionPending.promote(db, bus, sessionID, "input"),
-      ).toBe(1)
+      expect(yield* SessionPending.promote(db, bus, sessionID, "input")).toBe(1)
       expect(yield* SessionPending.has(db, sessionID, "input")).toBe(false)
       expect(yield* session.messages({ sessionID })).toMatchObject([
         { id: input.id, type: "synthetic", text: "Queued completion" },
@@ -1058,14 +1050,10 @@ describe("Session.pending", () => {
         { id: second.id, type: "user", delivery: "steer" },
       ])
 
-      expect(
-        yield* SessionPending.promote(db, bus, sessionID, "input"),
-      ).toBe(2)
+      expect(yield* SessionPending.promote(db, bus, sessionID, "input")).toBe(2)
       expect(yield* session.pending(sessionID)).toMatchObject([{ id: queued.id, type: "synthetic" }])
 
-      expect(
-        yield* SessionPending.promote(db, bus, sessionID, "input"),
-      ).toBe(1)
+      expect(yield* SessionPending.promote(db, bus, sessionID, "input")).toBe(1)
       expect(yield* session.pending(sessionID)).toEqual([])
     }),
   )
@@ -1104,9 +1092,11 @@ describe("Session.pending", () => {
 
       expect(yield* session.pending(sessionID)).toEqual([])
       expect(yield* eventCount(Bus.versionedType(SessionEvent.InputCancelled.type, 1))).toBe(1)
-      expect(
-        yield* session.cancelPending({ sessionID, inputID }).pipe(Effect.flip),
-      ).toMatchObject({ _tag: "Session.PendingInputConflictError", sessionID, inputID })
+      expect(yield* session.cancelPending({ sessionID, inputID }).pipe(Effect.flip)).toMatchObject({
+        _tag: "Session.PendingInputConflictError",
+        sessionID,
+        inputID,
+      })
       expect(yield* eventCount(Bus.versionedType(SessionEvent.InputCancelled.type, 1))).toBe(1)
 
       const retried = yield* session.prompt({
@@ -1151,9 +1141,11 @@ describe("Session.pending", () => {
       expect(wakeCalls).toEqual([])
       expect(yield* eventCount(Bus.versionedType(SessionEvent.InputQueued.type, 1))).toBe(1)
 
-      expect(
-        yield* session.steerPending({ sessionID, inputID: alreadySteered.id }).pipe(Effect.flip),
-      ).toMatchObject({ _tag: "Session.PendingInputConflictError", sessionID, inputID: alreadySteered.id })
+      expect(yield* session.steerPending({ sessionID, inputID: alreadySteered.id }).pipe(Effect.flip)).toMatchObject({
+        _tag: "Session.PendingInputConflictError",
+        sessionID,
+        inputID: alreadySteered.id,
+      })
       yield* session.cancelPending({ sessionID, inputID: alreadySteered.id })
       expect(wakeCalls).toEqual([])
       expect(yield* eventCount(Bus.versionedType(SessionEvent.InputSteered.type, 1))).toBe(1)

+ 2 - 6
packages/core/test/session-runner-tool-events.test.ts

@@ -24,9 +24,7 @@ const capture = (providerMetadataKey = "anthropic", options?: { readonly interru
       const publish = Effect.sync(() => {
         const event = { id: Event.ID.create(), type: definition.type, data } as Event.Payload<typeof definition>
         published.push({
-          type: definition.durable
-            ? Bus.versionedType(definition.type, definition.durable.version)
-            : definition.type,
+          type: definition.durable ? Bus.versionedType(definition.type, definition.durable.version) : definition.type,
           data,
         })
         return event
@@ -226,9 +224,7 @@ test("provider-executed tool metadata is flattened using the route key", async (
 test("binary failure emits no success event", async () => {
   const { published, publisher } = capture()
   await Effect.runPromise(publisher.publish(call))
-  await Effect.runPromise(
-    publisher.failTool(call.id, { type: "tool.execution", message: "Cannot read binary file" }),
-  )
+  await Effect.runPromise(publisher.failTool(call.id, { type: "tool.execution", message: "Cannot read binary file" }))
   expect(published.some((event) => event.type === "session.tool.success.2")).toBe(false)
   expect(published.some((event) => event.type === "session.tool.failed.2")).toBe(true)
 })

+ 71 - 79
packages/core/test/session-runner-tool-registry.test.ts

@@ -48,33 +48,25 @@ const call = (name: string, id = `call-${name}`): Parameters<Tool.Snapshot["exec
   call: { type: "tool-call", id, name, input: { text: name } },
 })
 
-const make = (): Info =>
-  ({
-    name: "echo",
-    description: "Echo text",
-    input: Schema.Struct({ text: Schema.String }),
-    output: Schema.Struct({ text: Schema.String }),
-    execute: ({ text }) => Effect.succeed({ output: { text }, content: text }),
-  })
-
-const constant = (text: string): Info =>
-  ({
-    name: "constant",
-    description: "Return text",
-    input: Schema.Struct({ text: Schema.String }),
-    output: Schema.Struct({ text: Schema.String }),
-    execute: () => Effect.succeed({ output: { text }, content: text }),
-  })
-
-const transform = (
-  service: Tool.Interface,
-  tools: Readonly<Record<string, Info>>,
-  options?: Tool.Options,
-) =>
+const make = (): Info => ({
+  name: "echo",
+  description: "Echo text",
+  input: Schema.Struct({ text: Schema.String }),
+  output: Schema.Struct({ text: Schema.String }),
+  execute: ({ text }) => Effect.succeed({ output: { text }, content: text }),
+})
+
+const constant = (text: string): Info => ({
+  name: "constant",
+  description: "Return text",
+  input: Schema.Struct({ text: Schema.String }),
+  output: Schema.Struct({ text: Schema.String }),
+  execute: () => Effect.succeed({ output: { text }, content: text }),
+})
+
+const transform = (service: Tool.Interface, tools: Readonly<Record<string, Info>>, options?: Tool.Options) =>
   service.transform((draft) =>
-    Object.entries(tools).forEach(([name, tool]) =>
-      draft.add({ ...tool, name, options: options ?? tool.options }),
-    ),
+    Object.entries(tools).forEach(([name, tool]) => draft.add({ ...tool, name, options: options ?? tool.options })),
   )
 
 describe("Tool", () => {
@@ -95,8 +87,9 @@ describe("Tool", () => {
       const invalid = yield* transform(service, { "123": make() }, { codemode: false }).pipe(Effect.flip)
       expect(invalid.message).toBe("Invalid tool name: 123")
 
-      const collision = yield* transform(service, { "echo.tool": make(), echo_tool: make() }, { codemode: false })
-        .pipe(Effect.flip)
+      const collision = yield* transform(service, { "echo.tool": make(), echo_tool: make() }, { codemode: false }).pipe(
+        Effect.flip,
+      )
       expect(collision.message).toBe("Duplicate normalized tool name: echo_tool")
       expect((yield* service.snapshot()).definitions.map((tool) => tool.name)).toEqual(["execute"])
     }),
@@ -202,11 +195,7 @@ describe("Tool", () => {
           { action: "*", resource: "*", effect: "deny" },
         ]),
       ).toEqual([])
-      expect(yield* names([{ action: "edit", resource: "*", effect: "deny" }])).toEqual([
-        "bash",
-        "question",
-        "execute",
-      ])
+      expect(yield* names([{ action: "edit", resource: "*", effect: "deny" }])).toEqual(["bash", "question", "execute"])
     }),
   )
 
@@ -239,13 +228,12 @@ describe("Tool", () => {
       const service = yield* Tool.Service
       const scope = yield* Scope.make()
       const registered = yield* Deferred.make<void>()
-      const fiber = yield* transform(service, { echo: make() }, { codemode: false })
-        .pipe(
-          Effect.andThen(Deferred.succeed(registered, undefined)),
-          Effect.andThen(Effect.never),
-          Scope.provide(scope),
-          Effect.forkChild,
-        )
+      const fiber = yield* transform(service, { echo: make() }, { codemode: false }).pipe(
+        Effect.andThen(Deferred.succeed(registered, undefined)),
+        Effect.andThen(Effect.never),
+        Scope.provide(scope),
+        Effect.forkChild,
+      )
       yield* Deferred.await(registered)
       yield* Fiber.interrupt(fiber)
 
@@ -258,15 +246,16 @@ describe("Tool", () => {
   it.effect("returns model errors without swallowing interruption or defects", () =>
     Effect.gen(function* () {
       const service = yield* Tool.Service
-      yield* transform(service,
+      yield* transform(
+        service,
         {
-          failed: ({
+          failed: {
             name: "failed",
             description: "Failed",
             input: Schema.Struct({}),
             output: Schema.Struct({ ok: Schema.Boolean }),
             execute: () => Effect.fail(new Tool.Error({ message: "Denied" })),
-          }),
+          },
         },
         { codemode: false },
       )
@@ -285,15 +274,16 @@ describe("Tool", () => {
         }),
       ).toEqual({ status: "error", error: { type: "tool.execution", message: "Unknown tool: missing" } })
 
-      yield* transform(service,
+      yield* transform(
+        service,
         {
-          defect: ({
+          defect: {
             name: "defect",
             description: "Defect",
             input: Schema.Struct({}),
             output: Schema.Struct({}),
             execute: () => Effect.die("unexpected executor defect"),
-          }),
+          },
         },
         { codemode: false },
       )
@@ -326,16 +316,17 @@ describe("Tool", () => {
     Effect.gen(function* () {
       const service = yield* Tool.Service
       const contexts: Tool.Context[] = []
-      yield* transform(service,
+      yield* transform(
+        service,
         {
-          context: ({
+          context: {
             name: "context",
             description: "Context",
             input: Schema.Struct({}),
             output: Schema.Struct({ ok: Schema.Boolean }),
             execute: (_, context) =>
               Effect.sync(() => contexts.push(context)).pipe(Effect.as({ output: { ok: true } })),
-          }),
+          },
         },
         { codemode: false },
       )
@@ -353,9 +344,10 @@ describe("Tool", () => {
   it.effect("normalizes image tool output once and drops unresizable images", () =>
     Effect.gen(function* () {
       const service = yield* Tool.Service
-      yield* transform(service,
+      yield* transform(
+        service,
         {
-          snapshot: ({
+          snapshot: {
             name: "snapshot",
             description: "Return images",
             input: Schema.Struct({ text: Schema.String }),
@@ -375,7 +367,7 @@ describe("Tool", () => {
                   { type: "text", text },
                 ],
               }),
-          }),
+          },
         },
         { codemode: false },
       )
@@ -405,9 +397,7 @@ describe("Tool", () => {
           if (event.status !== "completed") return
           event.result = {
             ...event.result,
-            content: [
-              { type: "file", uri: "data:image/png;base64,aW1hZ2U=", mime: "image/png", name: "hook.png" },
-            ],
+            content: [{ type: "file", uri: "data:image/png;base64,aW1hZ2U=", mime: "image/png", name: "hook.png" }],
           }
         }),
       )
@@ -426,16 +416,17 @@ describe("Tool", () => {
   it.effect("publishes progress metadata unchanged", () =>
     Effect.gen(function* () {
       const service = yield* Tool.Service
-      yield* transform(service,
+      yield* transform(
+        service,
         {
-          progressive: ({
+          progressive: {
             name: "progressive",
             description: "Emit image progress",
             input: Schema.Struct({ text: Schema.String }),
             output: Schema.Struct({ text: Schema.String }),
             execute: ({ text }, context) =>
               context.progress({ stage: "capture" }).pipe(Effect.as({ output: { text } })),
-          }),
+          },
         },
         { codemode: false },
       )
@@ -462,16 +453,17 @@ describe("Tool", () => {
           encode: SchemaGetter.transform((value) => value === "yes"),
         }),
       )
-      yield* transform(service,
+      yield* transform(
+        service,
         {
-          transformed: ({
+          transformed: {
             name: "transformed",
             description: "Transform values",
             input: Schema.Struct({ value: Transformed }),
             output: Schema.Struct({ value: Transformed }),
             execute: ({ value }) =>
               Effect.sync(() => executed.push(value)).pipe(Effect.as({ output: { value }, content: String(value) })),
-          }),
+          },
         },
         { codemode: false },
       )
@@ -501,9 +493,10 @@ describe("Tool", () => {
       })
       expect(executed).toEqual(["yes"])
 
-      yield* transform(service,
+      yield* transform(
+        service,
         {
-          invalid_output: ({
+          invalid_output: {
             name: "invalid_output",
             description: "Return invalid output",
             input: Schema.Struct({}),
@@ -520,7 +513,7 @@ describe("Tool", () => {
               ),
             }),
             execute: () => Effect.succeed({ output: { value: "invalid" } }),
-          }),
+          },
         },
         { codemode: false },
       )
@@ -570,19 +563,18 @@ describe("Tool", () => {
       const executed: string[] = []
       const scope = yield* Scope.make()
       yield* transform(service, {
-          echo: ({
-            name: "echo",
-            description: "Echo text",
-            input: Schema.Struct({ text: Schema.String }),
-            output: Schema.Struct({ text: Schema.String }),
-            execute: ({ text }, context) =>
-              Effect.sync(() => executed.push(`old:${text}`)).pipe(
-                Effect.andThen(context.progress({ stage: "old" })),
-                Effect.as({ output: { text } }),
-              ),
-          }),
-        })
-        .pipe(Scope.provide(scope))
+        echo: {
+          name: "echo",
+          description: "Echo text",
+          input: Schema.Struct({ text: Schema.String }),
+          output: Schema.Struct({ text: Schema.String }),
+          execute: ({ text }, context) =>
+            Effect.sync(() => executed.push(`old:${text}`)).pipe(
+              Effect.andThen(context.progress({ stage: "old" })),
+              Effect.as({ output: { text } }),
+            ),
+        },
+      }).pipe(Scope.provide(scope))
       const toolSet = yield* service.snapshot()
       const execute = toolSet.definitions.find((tool) => tool.name === "execute")
       expect(toolSet.codeModeCatalog?.[0]?.signature).toContain("tools.echo")
@@ -590,13 +582,13 @@ describe("Tool", () => {
       expect(execute?.description).not.toContain("Echo text")
       yield* Scope.close(scope, Exit.void)
       yield* transform(service, {
-        echo: ({
+        echo: {
           name: "echo",
           description: "Echo text",
           input: Schema.Struct({ text: Schema.String }),
           output: Schema.Struct({ text: Schema.String }),
           execute: ({ text }) => Effect.sync(() => executed.push(`new:${text}`)).pipe(Effect.as({ output: { text } })),
-        }),
+        },
       })
 
       const progress: Tool.Metadata[] = []

+ 1 - 4
packages/core/test/session-runner.test.ts

@@ -3254,10 +3254,7 @@ describe("SessionRunnerLLM", () => {
       yield* stream.started
 
       expect(requests).toHaveLength(2)
-      expect(requests.map((request) => request.promptCacheKey)).toEqual([
-        sessionID,
-        otherSessionID,
-      ])
+      expect(requests.map((request) => request.promptCacheKey)).toEqual([sessionID, otherSessionID])
       yield* stream.release
       yield* Fiber.join(first)
       yield* Fiber.join(second)

+ 5 - 1
packages/core/test/shell-parse.test.ts

@@ -20,7 +20,11 @@ describe("ShellParse", () => {
 
   test("splits PowerShell commands case-insensitively", async () => {
     const result = await Effect.runPromise(
-      ShellParse.scan("Get-ChildItem; Write-Output 'done'", "C:\\Program Files\\PowerShell\\7\\pwsh.exe", "C:\\workspace"),
+      ShellParse.scan(
+        "Get-ChildItem; Write-Output 'done'",
+        "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
+        "C:\\workspace",
+      ),
     )
     expect(result.commands).toEqual([
       { resource: "Get-ChildItem", save: "Get-ChildItem *" },

+ 1 - 3
packages/core/test/snapshot.test.ts

@@ -199,7 +199,5 @@ async function initGit(directory: string, commit = false) {
   await $`git init`.cwd(directory).quiet()
   await $`git -c core.fsmonitor=false add .`.cwd(directory).quiet()
   if (!commit) return
-  await $`git -c user.email=test@opencode.test -c user.name=Test commit --no-gpg-sign -m initial`
-    .cwd(directory)
-    .quiet()
+  await $`git -c user.email=test@opencode.test -c user.name=Test commit --no-gpg-sign -m initial`.cwd(directory).quiet()
 }

+ 11 - 14
packages/core/test/tool-execute.test.ts

@@ -33,27 +33,27 @@ test("execute describes invariant Code Mode behavior", () => {
 })
 
 test("canonical execution distinguishes declared, model-only, and raw schema outputs", async () => {
-  const declared: Info = ({
+  const declared: Info = {
     name: "declared",
     description: "Declared",
     input: Schema.Struct({ value: Schema.String }),
     output: Schema.Struct({ value: Schema.String }),
     execute: ({ value }) => Effect.succeed({ output: { value } }),
-  })
+  }
   const modelOnlyInput = Schema.Struct({})
-  const modelOnly = ({
+  const modelOnly = {
     name: "model_only",
     description: "Model only",
     input: modelOnlyInput,
     execute: () => Effect.succeed({ content: "visible only", metadata: { kind: "model" } }),
-  }) satisfies Info<typeof modelOnlyInput, undefined>
-  const raw: Info = ({
+  } satisfies Info<typeof modelOnlyInput, undefined>
+  const raw: Info = {
     name: "raw",
     description: "Raw",
     input: {},
     output: {},
     execute: (input) => Effect.succeed({ output: input, content: "raw" }),
-  })
+  }
 
   expect(await Effect.runPromise(execute(declared, { value: "encoded" }, context))).toEqual({
     output: { value: "encoded" },
@@ -95,22 +95,22 @@ test("declared outputs cannot bypass validation and raw outputs stay JSON-compat
 })
 
 test("execute supports callable namespace tools", async () => {
-  const callable: Info = ({
+  const callable: Info = {
     name: "admin",
     description: "Administer Slack",
     input: Schema.Struct({}),
     output: Schema.String,
     options: { namespace: "slack" },
     execute: () => Effect.succeed({ output: "admin" }),
-  })
-  const child: Info = ({
+  }
+  const child: Info = {
     name: "create",
     description: "Create a Slack resource",
     input: Schema.Struct({}),
     output: Schema.String,
     options: { namespace: "slack.admin" },
     execute: () => Effect.succeed({ output: "created" }),
-  })
+  }
   const codeMode = createCodeMode(
     new Map([
       ["slack_admin", callable],
@@ -118,10 +118,7 @@ test("execute supports callable namespace tools", async () => {
     ]),
   )
   const result = await Effect.runPromise(
-    codeMode.execute(
-      { code: "return [await tools.slack.admin({}), await tools.slack.admin.create({})]" },
-      context,
-    ),
+    codeMode.execute({ code: "return [await tools.slack.admin({}), await tools.slack.admin.create({})]" }, context),
   )
 
   expect(result.metadata).toEqual({

+ 1 - 4
packages/core/test/tool-output.test.ts

@@ -147,10 +147,7 @@ describe("ToolOutput", () => {
     withStore((output, fs, root) =>
       Effect.gen(function* () {
         const directory = path.join(root, ToolOutput.DIRECTORY)
-        const old = path.join(
-          directory,
-          Identifier.create("tool", "ascending", Date.now() - 8 * 24 * 60 * 60 * 1_000),
-        )
+        const old = path.join(directory, Identifier.create("tool", "ascending", Date.now() - 8 * 24 * 60 * 60 * 1_000))
         const recent = path.join(directory, Identifier.ascending("tool"))
         yield* fs.ensureDir(directory)
         yield* fs.writeFileString(old, "old")

+ 6 - 6
packages/core/test/tool-schema.test.ts

@@ -121,13 +121,13 @@ test("portable schemas validate and describe typed tools", async () => {
       },
     },
   }
-  const tool: Info = ({
+  const tool: Info = {
     name: "portable",
     description: "Portable tool",
     input,
     output,
     execute: ({ count }) => Effect.succeed({ output: count + 1 }),
-  })
+  }
 
   expect(definition(tool)).toEqual({
     name: "portable",
@@ -170,13 +170,13 @@ test("portable schema failures become tool failures", async () => {
 test("canonical results carry metadata with typed output", async () => {
   const input = Schema.Struct({ value: Schema.String })
   const output = Schema.Struct({ value: Schema.String, internal: Schema.Boolean })
-  const tool: Info = ({
+  const tool: Info = {
     name: "annotated",
     description: "Annotated tool",
     input,
     output,
     execute: ({ value }) => Effect.succeed({ output: { value, internal: true }, metadata: { value }, content: value }),
-  })
+  }
 
   expect(await Effect.runPromise(tool.execute({ value: "out" }, {} as Tool.Context))).toEqual({
     output: { value: "out", internal: true },
@@ -187,12 +187,12 @@ test("canonical results carry metadata with typed output", async () => {
 
 test("raw JSON schemas are render-only and omitted output means model-only", async () => {
   const input = { type: "object", properties: { value: { type: "string" } } }
-  const tool: Info = ({
+  const tool: Info = {
     name: "raw",
     description: "Raw tool",
     input,
     execute: (input) => Effect.succeed({ content: JSON.stringify(input) }),
-  })
+  }
 
   expect(definition(tool)).toEqual({
     name: "raw",

+ 70 - 65
packages/core/test/tool-shell.test.ts

@@ -309,27 +309,29 @@ describe("ShellTool", () => {
     ),
   )
 
-  it.live("captures stderr-only and mixed stdout/stderr output", () =>
-    Effect.acquireUseRelease(
-      Effect.promise(() => tmpdir()),
-      (tmp) => {
-        reset()
-        return withSession(tmp.path, (registry) =>
-          Effect.gen(function* () {
-            const stderr = yield* executeTool(registry, call({ command: stderrCommand }, "call-stderr"))
-            expect(stderr.metadata).toMatchObject({ exit: 0, truncated: false })
-            expect(stderr.content?.[0]).toEqual({ type: "text", text: "stderr only" })
-
-            const mixed = yield* executeTool(registry, call({ command: mixedOutputCommand }, "call-mixed"))
-            expect(mixed.metadata).toMatchObject({ exit: 0, truncated: false })
-            const output = mixed.content?.[0]?.type === "text" ? mixed.content[0].text : ""
-            expect(output).toContain("stdout")
-            expect(output).toContain("stderr")
-          }),
-        )
-      },
-      (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
-    ),
+  it.live(
+    "captures stderr-only and mixed stdout/stderr output",
+    () =>
+      Effect.acquireUseRelease(
+        Effect.promise(() => tmpdir()),
+        (tmp) => {
+          reset()
+          return withSession(tmp.path, (registry) =>
+            Effect.gen(function* () {
+              const stderr = yield* executeTool(registry, call({ command: stderrCommand }, "call-stderr"))
+              expect(stderr.metadata).toMatchObject({ exit: 0, truncated: false })
+              expect(stderr.content?.[0]).toEqual({ type: "text", text: "stderr only" })
+
+              const mixed = yield* executeTool(registry, call({ command: mixedOutputCommand }, "call-mixed"))
+              expect(mixed.metadata).toMatchObject({ exit: 0, truncated: false })
+              const output = mixed.content?.[0]?.type === "text" ? mixed.content[0].text : ""
+              expect(output).toContain("stdout")
+              expect(output).toContain("stderr")
+            }),
+          )
+        },
+        (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
+      ),
     { timeout: 15_000 },
   )
 
@@ -390,7 +392,9 @@ describe("ShellTool", () => {
         const command = isWindows
           ? `Set-Location -LiteralPath '${outside.path}'; (Get-Location).Path`
           : `cd '${outside.path}' && pwd`
-        return withSession(active.path, (registry) => executeTool(registry, call({ command }, "call-external-cd"))).pipe(
+        return withSession(active.path, (registry) =>
+          executeTool(registry, call({ command }, "call-external-cd")),
+        ).pipe(
           Effect.andThen(
             Effect.sync(() => {
               expect(assertions.map((item) => item.action)).toEqual(["external_directory", "shell"])
@@ -476,32 +480,34 @@ describe("ShellTool", () => {
     ),
   )
 
-  it.live("truncates the model view and points at the saved output file when output overflows", () =>
-    Effect.acquireUseRelease(
-      Effect.promise(() => tmpdir()),
-      (tmp) => {
-        reset()
-        const bytes = ToolOutput.MAX_BYTES + 1024
-        return withSession(tmp.path, (registry) =>
-          executeTool(registry, call({ command: overflowCommand(bytes) }, "call-overflow")),
-        ).pipe(
-          Effect.andThen((settled) =>
-            Effect.sync(() => {
-              expect(settled.metadata).toMatchObject({ exit: 0, truncated: true })
-              const content = settled.content?.[0]
-              if (!content || content.type !== "text") throw new Error("Expected text content")
-              expect(content.text.includes("output-start")).toBe(false)
-              expect(content.text.includes("output-end")).toBe(true)
-              expect(content).toMatchObject({
-                type: "text",
-                text: expect.stringContaining("output truncated; full output saved to:"),
-              })
-            }),
-          ),
-        )
-      },
-      (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
-    ),
+  it.live(
+    "truncates the model view and points at the saved output file when output overflows",
+    () =>
+      Effect.acquireUseRelease(
+        Effect.promise(() => tmpdir()),
+        (tmp) => {
+          reset()
+          const bytes = ToolOutput.MAX_BYTES + 1024
+          return withSession(tmp.path, (registry) =>
+            executeTool(registry, call({ command: overflowCommand(bytes) }, "call-overflow")),
+          ).pipe(
+            Effect.andThen((settled) =>
+              Effect.sync(() => {
+                expect(settled.metadata).toMatchObject({ exit: 0, truncated: true })
+                const content = settled.content?.[0]
+                if (!content || content.type !== "text") throw new Error("Expected text content")
+                expect(content.text.includes("output-start")).toBe(false)
+                expect(content.text.includes("output-end")).toBe(true)
+                expect(content).toMatchObject({
+                  type: "text",
+                  text: expect.stringContaining("output truncated; full output saved to:"),
+                })
+              }),
+            ),
+          )
+        },
+        (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
+      ),
     { timeout: 15_000 },
   )
 
@@ -546,10 +552,7 @@ describe("ShellTool", () => {
             Effect.gen(function* () {
               const observed = yield* Deferred.make<string>()
               yield* executeTool(registry, {
-                ...call(
-                  { command: progressOverflowCommand(ToolOutput.MAX_BYTES + 1024, release) },
-                  "call-progress",
-                ),
+                ...call({ command: progressOverflowCommand(ToolOutput.MAX_BYTES + 1024, release) }, "call-progress"),
                 progress: (update) =>
                   Effect.gen(function* () {
                     if (typeof update.shellID !== "string") return
@@ -591,10 +594,12 @@ describe("ShellTool", () => {
     { timeout: 10_000 },
   )
 
-  it.live("returns a useful timeout outcome", () =>
-    Effect.acquireUseRelease(
-      Effect.promise(() => tmpdir()),
-      (tmp) => {
+  it.live(
+    "returns a useful timeout outcome",
+    () =>
+      Effect.acquireUseRelease(
+        Effect.promise(() => tmpdir()),
+        (tmp) => {
           reset()
           return withSession(tmp.path, (registry) =>
             executeTool(registry, call({ command: timeoutOutputCommand, timeout: isWindows ? 3_000 : 50 })),
@@ -607,15 +612,15 @@ describe("ShellTool", () => {
                   text: expect.stringContaining("before timeout"),
                 })
                 expect(settled.content?.[1]).toMatchObject({
-                type: "text",
-                text: expect.stringContaining("Command timed out"),
-              })
-            }),
-          ),
-        )
-      },
-      (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
-    ),
+                  type: "text",
+                  text: expect.stringContaining("Command timed out"),
+                })
+              }),
+            ),
+          )
+        },
+        (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
+      ),
     { timeout: 15_000 },
   )
 

+ 5 - 8
packages/core/test/tool-skill.test.ts

@@ -85,14 +85,11 @@ describe("SkillTool", () => {
               list: () => Effect.succeed(current),
             }),
           )
-          const skillToolLayer = AppNodeBuilder.build(
-            LayerNode.group([Tool.node, skillToolNode]),
-            [
-              [Permission.node, permission],
-              [Skill.node, skills],
-              [Image.node, imagePassthrough],
-            ],
-          )
+          const skillToolLayer = AppNodeBuilder.build(LayerNode.group([Tool.node, skillToolNode]), [
+            [Permission.node, permission],
+            [Skill.node, skills],
+            [Image.node, imagePassthrough],
+          ])
 
           return yield* Effect.gen(function* () {
             const registry = yield* Tool.Service

+ 3 - 3
packages/core/test/util/effect-flock.test.ts

@@ -146,9 +146,9 @@ describe("util.effect-flock", () => {
         Effect.gen(function* () {
           yield* flock.acquire(key, dir)
           const started = performance.now()
-          const error = yield* Effect.scoped(
-            flock.acquire(key, dir, { staleMs: 10_000, timeoutMs: 300 }),
-          ).pipe(Effect.flip)
+          const error = yield* Effect.scoped(flock.acquire(key, dir, { staleMs: 10_000, timeoutMs: 300 })).pipe(
+            Effect.flip,
+          )
           expect(error._tag).toBe("LockTimeoutError")
           expect(performance.now() - started).toBeLessThan(1_000)
         }),

+ 7 - 6
packages/core/test/vcs-hg.test.ts

@@ -46,7 +46,10 @@ const withHg = <A, E, R>(f: (directory: string) => Effect.Effect<A, E, R>) =>
   )
 
 async function hg(directory: string, ...args: string[]) {
-  await $`hg ${args}`.cwd(directory).env({ ...process.env, HGPLAIN: "1" }).quiet()
+  await $`hg ${args}`
+    .cwd(directory)
+    .env({ ...process.env, HGPLAIN: "1" })
+    .quiet()
 }
 
 async function commitAll(directory: string, message: string) {
@@ -117,11 +120,9 @@ describeHg("Vcs mercurial", () => {
         const bus = yield* Bus.Service
         expect(yield* vcs.info()).toEqual({ branch: { current: "default", default: "default" } })
 
-        const updated = yield* bus.subscribe(VcsEvent.BranchUpdated).pipe(
-          Stream.take(1),
-          Stream.runHead,
-          Effect.forkScoped({ startImmediately: true }),
-        )
+        const updated = yield* bus
+          .subscribe(VcsEvent.BranchUpdated)
+          .pipe(Stream.take(1), Stream.runHead, Effect.forkScoped({ startImmediately: true }))
         yield* Effect.promise(() => hg(directory, "branch", "-q", "feature"))
         expect(yield* vcs.info()).toEqual({ branch: { current: "default", default: "default" } })
 

+ 3 - 5
packages/core/test/vcs.test.ts

@@ -104,11 +104,9 @@ describe("Vcs", () => {
         const bus = yield* Bus.Service
         expect(yield* vcs.info()).toEqual({ branch: { current: "main", default: undefined } })
 
-        const updated = yield* bus.subscribe(VcsEvent.BranchUpdated).pipe(
-          Stream.take(1),
-          Stream.runHead,
-          Effect.forkScoped({ startImmediately: true }),
-        )
+        const updated = yield* bus
+          .subscribe(VcsEvent.BranchUpdated)
+          .pipe(Stream.take(1), Stream.runHead, Effect.forkScoped({ startImmediately: true }))
         yield* Effect.promise(() => $`git checkout -q -b feature`.cwd(directory).quiet())
 
         yield* bus.publish(FileSystem.Event.Changed, { file: path.join(directory, "HEAD"), event: "change" })