Dashboard › opencode › Session 080DwbWHhAy7
080DwbWHhAy7uuoG2Read-only independent security review frozen v18. Base ab9408c81cc9916177641ae8e9f08624fc27b5b9. Patch /tmp/opencode/session-recovery-final-v18.patch SHA-256 63c11067ed78471a8109455f164327a1c6dc2ff547e5cb78cad185fe2d844e82. SEA /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server SHA-256 c5730f7d6017ebd8dde2a2e91365e574469ef10494ec632632b91d7e2318885c. Verify immutable inputs, clean apply, 34 paths; inspect all. No mutation/tests/builds/services/recall. Focus v17 blocker: post-commit/pre-return notification interruption after Step.Failed. V18 reads durable assistant projection completion after cancellation reconciliation and settles ownership directly, no duplicate Interrupted. Deterministic listener blocks exact boundary. Reassess cancellation monotonicity, stale owners, provider/tools, recovery identity/replay/supersession, explicit resume, scheduling DoS, schema/client/auth/dependencies. Accepted check-call/process-local/skew. Evidence 229 recovery, Core 1186/0, typecheck/migration/smoke. PASS/CONCERN/MUST-FIX file:line; end MERGE or DO-NOT-MERGE. Empty invalid.
63c11067ed78471a8109455f164327a1c6dc2ff547e5cb78cad185fe2d844e82 /tmp/opencode/session-recovery-final-v18.patch c5730f7d6017ebd8dde2a2e91365e574469ef10494ec632632b91d7e2318885c /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server dc661f30cb2055ad6248fdc59b14998d834497be ab9408c81cc9916177641ae8e9f08624fc27b5b9 M .lore.md M AGENTS.md M packages/client/src/generated/types.ts M packages/core/schema.json M packages/core/src/database/migration.gen.ts M packages/core/src/database/migration/20260914170650_session-recovery.ts D packages/core/src/database/migration/20260914214636_session-execution.ts D packages/core/src/database/migration/20260914222526_session-execution-phase.ts M packages/core/src/database/schema.gen.ts M packages/core/src/session.ts M packages/core/src/session/compaction.ts M packages/core/src/session/context-epoch.ts M packages/core/src/session/execution/local.ts M packages/core/src/session/input.ts M ...
<path>/tmp/opencode/session-recovery-final-v18.patch</path>
<type>file</type>
<content>
1: diff --git a/packages/client/src/generated/types.ts b/packages/client/src/generated/types.ts
2: index 3b3188c874..5130fa9496 100644
3: --- a/packages/client/src/generated/types.ts
4: +++ b/packages/client/src/generated/types.ts
5: @@ -775,8 +775,17 @@ export type SessionsHistoryOutput = {
6: }>
7: }
8: readonly delivery: "steer" | "queue"
9: + readonly requestExecution?: boolean
10: }
11: }
12: + | {
13: + readonly id: string
14: + readonly metadata?: { readonly [x: string]: JsonValue }
15: + readonly type: "session.next.prompt.execution_requested"
16: + readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
17: + readonly location?: { readonly directory: string; readonly workspaceID?: string }
18: + readonly data: { readonly timestamp: number; ...
ancestor_exit=0 reverse_apply_exit=0 72 0 packages/client/src/generated/types.ts 567 48 packages/core/schema.json 1 0 packages/core/src/database/migration.gen.ts 62 0 packages/core/src/database/migration/20260914170650_session-recovery.ts 52 1 packages/core/src/database/schema.gen.ts 6 4 packages/core/src/session.ts 31 16 packages/core/src/session/compaction.ts 54 34 packages/core/src/session/context-epoch.ts 165 19 packages/core/src/session/execution/local.ts 391 31 packages/core/src/session/input.ts 69 0 packages/core/src/session/message-updater.ts 88 1 packages/core/src/session/projector.ts 504 0 packages/core/src/session/recovery.ts 30 5 packages/core/src/session/run-coordinator.ts 4 0 packages/core/src/session/runner/index.ts 322 104 packages/core/src/session/runner/llm.ts 107 46 packages/core/src/session/runner/publish-llm-event.ts 88 1 packages/core/src/session/sql.ts 66 1 packages/core/test/database-migration.test.ts 352 0 packages/core/test/session-execution-local.test.ts 2771...
OK packages/client/src/generated/types.ts 3b3188c874 3b3188c874 OK packages/core/schema.json d0eeeebd5c d0eeeebd5c OK packages/core/src/database/migration.gen.ts e6ea4eaa14 e6ea4eaa14 OK packages/core/src/database/migration/20260914170650_session-recovery.ts 0000000000 0000000000 OK packages/core/src/database/schema.gen.ts ed60fde6c5 ed60fde6c5 OK packages/core/src/session.ts 2dabfb2d6f 2dabfb2d6f OK packages/core/src/session/compaction.ts f714633295 f714633295 OK packages/core/src/session/context-epoch.ts 65b17a86a4 65b17a86a4 OK packages/core/src/session/execution/local.ts d874c40832 d874c40832 OK packages/core/src/session/input.ts 14b613678d 14b613678d OK packages/core/src/session/message-updater.ts 46118a89fe 46118a89fe OK packages/core/src/session/projector.ts 792067017d 792067017d OK packages/core/src/session/recovery.ts 0000000000 0000000000 OK packages/core/src/session/run-coordinator.ts 2f89aff9e3 2f89aff9e3 OK packages/core/src/session/runner/index.ts 634075dd91 ...
OK packages/client/src/generated/types.ts 5130fa9496 5130fa9496 OK packages/core/schema.json 81e5eca808 81e5eca808 OK packages/core/src/database/migration.gen.ts 77948f1529 77948f1529 OK packages/core/src/database/migration/20260914170650_session-recovery.ts 20eb1a4373 20eb1a4373 OK packages/core/src/database/schema.gen.ts 94881e7038 94881e7038 OK packages/core/src/session.ts 1fbd6285d0 1fbd6285d0 OK packages/core/src/session/compaction.ts 92a934c44f 92a934c44f OK packages/core/src/session/context-epoch.ts 880d42bcf9 880d42bcf9 OK packages/core/src/session/execution/local.ts 0bb4f02b2c 0bb4f02b2c OK packages/core/src/session/input.ts 61f6a9a465 61f6a9a465 OK packages/core/src/session/message-updater.ts e2d7e6b38e e2d7e6b38e OK packages/core/src/session/projector.ts 2742cabc24 2742cabc24 OK packages/core/src/session/recovery.ts e289b821c7 e289b821c7 OK packages/core/src/session/run-coordinator.ts ab4b833374 ab4b833374 OK packages/core/src/session/runner/index.ts 7b9ac3ae83 ...
<path>/home/byk/Code/opencode/packages/core/src/session/execution/claim.ts</path>
<type>file</type>
<content>
1: export * as SessionExecutionClaim from "./claim"
2:
3: import { and, eq, gt, notInArray } from "drizzle-orm"
4: import { Clock, Effect } from "effect"
5: import type { Database } from "../../database/database"
6: import type { SessionSchema } from "../schema"
7: import { SESSION_EXECUTION_LEASE_MS, SessionExecutionTable } from "../sql"
8:
9: type DatabaseService = Database.Interface["db"]
10:
11: export type Claim = {
12: readonly sessionID: SessionSchema.ID
13: readonly executionID: string
14: readonly ownerID: string
15: }
16:
17: export type Values = Partial<
18: Pick<typeof SessionExecutionTable.$inferInsert, "assistant_message_id" | "assistant_seq" | "phase">
19: >
20:
21: export class Lost extends Error {
22: constructor(readonly claim: Claim) {
23: super(`Session execution claim lost: ${claim.executionID}`)
24: }
25: }
26:
27: const where = ...
<path>/home/byk/Code/opencode/packages/core/src/session/execution/local.ts</path>
<type>file</type>
<content>
1: import { and, asc, desc, eq, isNull } from "drizzle-orm"
2: import { Cause, Clock, Duration, Effect, Exit, Layer, Schedule } from "effect"
3: import { Database } from "../../database/database"
4: import { LocationServiceMap } from "../../location-service-map"
5: import { makeGlobalNode } from "../../effect/app-node"
6: import { SessionRunCoordinator } from "../run-coordinator"
7: import { SessionRunner } from "../runner"
8: import { SessionMessage } from "../message"
9: import { SessionSchema } from "../schema"
10: import { SessionStore } from "../store"
11: import { SessionExecution } from "../execution"
12: import { SessionExecutionClaim } from "./claim"
13: import {
14: SESSION_EXECUTION_LEASE_MS,
15: SessionExecutionTable,
16: SessionMessageTable,
17: SessionRecoveryTable,
18: } from "../sql"
19:
20: /** Current-process routing for implicit-local Locations. ...
<path>/home/byk/Code/opencode/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
1: import {
2: LLM,
3: LLMClient,
4: LLMError,
5: LLMEvent,
6: Message,
7: SystemPart,
8: isContextOverflowFailure,
9: type ProviderErrorEvent,
10: } from "@opencode-ai/llm"
11: import { Cause, DateTime, Effect, Exit, Fiber, Layer, Option, Semaphore, Stream } from "effect"
12: import { and, eq } from "drizzle-orm"
13: import { AgentV2 } from "../../agent"
14: import { Config } from "../../config"
15: import { Database } from "../../database/database"
16: import { EventV2 } from "../../event"
17: import { Location } from "../../location"
18: import { ModelV2 } from "../../model"
19: import { PermissionV2 } from "../../permission"
20: import { ProviderV2 } from "../../provider"
21: import { QuestionV2 } from "../../question"
22: import { SystemContext } from "../../system-context/index"
23: import { SystemContextRegistry } from "../../system-context/registry"
24: ...
<path>/home/byk/Code/opencode/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
501: .pipe(Effect.catch(() => Effect.succeed(undefined)))
502: : undefined
503: const assistantMessageID = yield* publisher.startAssistant()
504: yield* withPublication(
505: events.publish(
506: SessionEvent.Step.Ended,
507: {
508: sessionID: session.id,
509: timestamp: yield* DateTime.now,
510: assistantMessageID,
511: finish: stepSettlement.finish,
512: cost: 0,
513: tokens: stepSettlement.tokens,
514: snapshot: endSnapshot,
515: files,
516: },
517: {
518: commit: () =>
519: updateExecution({
520: phase: needsContinuation ? ...
<path>/home/byk/Code/opencode/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
1: import { ToolOutput, type LLMEvent, type ProviderMetadata, type ToolResultValue, type Usage } from "@opencode-ai/llm"
2: import { DateTime, Effect } from "effect"
3: import { EventV2 } from "../../event"
4: import { ModelV2 } from "../../model"
5: import { SessionEvent } from "../event"
6: import { SessionMessage } from "../message"
7: import { SessionSchema } from "../schema"
8:
9: type Input = {
10: readonly sessionID: SessionSchema.ID
11: readonly agent: string
12: readonly model: ModelV2.Ref
13: readonly snapshot?: string
14: readonly startCommit: (assistantMessageID: SessionMessage.ID, seq: number) => Effect.Effect<void>
15: readonly eventCommit: () => Effect.Effect<void>
16: }
17:
18: const safe = (value: number | undefined) => Math.max(0, Number.isFinite(value) ? (value ?? ...
<path>/home/byk/Code/opencode/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
401: }
402: const result = settledOutput(event.output, event.result)
403: const provider = {
404: executed: event.providerExecuted === true || tool.providerExecuted,
405: ...(event.providerMetadata === undefined ? {} : { metadata: event.providerMetadata }),
406: }
407: if ("error" in result) {
408: yield* publishEvent(SessionEvent.Tool.Failed, {
409: sessionID: input.sessionID,
410: timestamp: yield* timestamp,
411: assistantMessageID: tool.assistantMessageID,
412: callID: event.id,
413: error: result.error,
414: result: event.result,
415: provider,
416: })
417: tool.settled = true
418: return
419: }
420: yield* publishEvent(SessionEvent.Tool.Success, {
421: ...
<path>/home/byk/Code/opencode/packages/core/src/session/run-coordinator.ts</path>
<type>file</type>
<content>
1: export * as SessionRunCoordinator from "./run-coordinator"
2:
3: import { Deferred, Effect, Exit, Fiber, FiberSet, Scope } from "effect"
4:
5: /** Serializes execution for each key while allowing different keys to run concurrently. */
6: export interface Coordinator<Key, E> {
7: /** Snapshots keys with an execution owned by this coordinator. */
8: readonly active: Effect.Effect<ReadonlySet<Key>>
9: /** Starts execution while idle or joins the active execution. */
10: readonly run: (key: Key) => Effect.Effect<void, E>
11: /** Registers one coalesced follow-up after newly recorded work. */
12: readonly wake: (key: Key) => Effect.Effect<void>
13: /** Stops active execution, runs cleanup, then starts work that arrived while stopping. */
14: readonly interrupt: (key: Key, cleanup?: Effect.Effect<void>) => Effect.Effect<void>
15: }
16:
17: type Entry<E> = {
18: ...
<path>/home/byk/Code/opencode/packages/core/src/session/recovery.ts</path>
<type>file</type>
<content>
1: export * as SessionRecovery from "./recovery"
2:
3: import { and, asc, desc, eq, exists, gt, isNull, lte, or, sql } from "drizzle-orm"
4: import { Clock, DateTime, Effect, Layer, Option, Schedule, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { makeGlobalNode } from "../effect/app-node"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionExecution } from "./execution"
10: import { SessionProjector } from "./projector"
11: import { SessionExecutionClaim } from "./execution/claim"
12: import { SessionInput } from "./input"
13: import { continuation } from "./recovery-id"
14: import { SessionSchema } from "./schema"
15: import {
16: MAX_WAKE_ATTEMPTS,
17: SESSION_EXECUTION_LEASE_MS,
18: SessionExecutionTable,
19: SessionInputTable,
20: SessionRecoveryTable,
21: SessionMessageTable,
22: ...
<path>/home/byk/Code/opencode/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
1: export * as SessionProjector from "./projector"
2:
3: import { and, desc, eq, gt, or, sql } from "drizzle-orm"
4: import { DateTime, Effect, Layer, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { makeGlobalNode } from "../effect/app-node"
8: import { SessionEvent } from "./event"
9: import { SessionV1 } from "../v1/session"
10: import { WorkspaceTable } from "../control-plane/workspace.sql"
11: import { SessionMessage } from "./message"
12: import { SessionMessageUpdater } from "./message-updater"
13: import { SessionInput } from "./input"
14: import { Prompt } from "./prompt"
15: import { continuation } from "./recovery-id"
16: import { WorkspaceV2 } from "../workspace"
17: import {
18: MessageTable,
19: PartTable,
20: SessionInputTable,
21: SessionMessageTable,
22: SessionRecoveryTable,
23: ...
<path>/home/byk/Code/opencode/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
451: const inserted = yield* db
452: .insert(SessionRecoveryTable)
453: .values({
454: session_id: event.data.sessionID,
455: execution_id: event.data.executionID,
456: assistant_message_id: event.data.assistantMessageID ?? null,
457: continuation_message_id: continuationMessageID,
458: phase: event.data.phase,
459: wake_pending: true,
460: time_created: DateTime.toEpochMillis(event.data.timestamp),
461: })
462: .onConflictDoNothing()
463: .returning({ executionID: SessionRecoveryTable.execution_id })
464: .get()
465: .pipe(Effect.orDie)
466: if (!inserted) return yield* Effect.die(`Recovery execution identity conflict: ${event.data.executionID}`)
467: }),
468: )
469: yield* ...
<path>/home/byk/Code/opencode/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
1: import { castDraft, produce, type WritableDraft } from "immer"
2: import { Effect } from "effect"
3: import { SessionEvent } from "./event"
4: import { SessionMessage } from "./message"
5: import { message } from "./recovery-id"
6:
7: export type MemoryState = {
8: messages: SessionMessage.Message[]
9: }
10:
11: export interface Adapter {
12: readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined>
13: readonly getAssistant: (messageID: SessionMessage.ID) => Effect.Effect<SessionMessage.Assistant | undefined>
14: readonly getCurrentShell: (callID: string) => Effect.Effect<SessionMessage.Shell | undefined>
15: readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect<void>
16: readonly updateShell: (shell: SessionMessage.Shell) => Effect.Effect<void>
17: readonly appendMessage: (message: ...
<path>/home/byk/Code/opencode/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
1: export * as SessionInput from "./input"
2:
3: import { and, asc, desc, eq, isNull, lte } from "drizzle-orm"
4: import { DateTime, Effect, Option, Schema } from "effect"
5: import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
6: import type { Database } from "../database/database"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionMessage } from "./message"
10: import { Prompt } from "./prompt"
11: import { continuation, message } from "./recovery-id"
12: import { SessionSchema } from "./schema"
13: import { SessionInputTable, SessionMessageTable, SessionRecoveryTable, SessionTable } from "./sql"
14:
15: type DatabaseService = Database.Interface["db"]
16:
17: export { Admitted, Delivery }
18:
19: const decodePrompt = Schema.decodeUnknownSync(Prompt)
20: const encodePrompt = Schema.encodeSync(Prompt)
21: const ...
<path>/home/byk/Code/opencode/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
531: export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* (
532: db: DatabaseService,
533: events: EventV2.Interface,
534: sessionID: SessionSchema.ID,
535: cutoff: number,
536: commit?: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
537: ) {
538: const rows = yield* db
539: .select({ input: SessionInputTable })
540: .from(SessionInputTable)
541: .leftJoin(
542: SessionRecoveryTable,
543: eq(SessionRecoveryTable.continuation_message_id, SessionInputTable.id),
544: )
545: .where(
546: and(
547: eq(SessionInputTable.session_id, sessionID),
548: isNull(SessionInputTable.promoted_seq),
549: eq(SessionInputTable.delivery, "steer"),
550: lte(SessionInputTable.admitted_seq, cutoff),
551: isNull(SessionRecoveryTable.execution_id),
552: ),
553: ...
<path>/home/byk/Code/opencode/packages/core/src/session.ts</path>
<type>file</type>
<content>
300: Effect.orDie,
301: )
302: return (direction === "previous" ? rows.toReversed() : rows).map((row) => fromRow(row))
303: }),
304: messages: Effect.fn("V2Session.messages")(function* (input) {
305: yield* result.get(input.sessionID)
306: const direction = input.cursor?.direction ?? "next"
307: const requestedOrder = input.order ?? "desc"
308: const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
309: const anchor = input.cursor
310: ? yield* db
311: .select({ seq: SessionMessageTable.seq })
312: .from(SessionMessageTable)
313: .where(
314: and(eq(SessionMessageTable.session_id, input.sessionID), eq(SessionMessageTable.id, input.cursor.id)),
315: )
316: .get()
317: ...
<path>/home/byk/Code/opencode/packages/opencode/src/server/routes/instance/httpapi/server.ts</path>
<type>file</type>
<content>
1: import { Config as EffectConfig, Context, Effect, Layer } from "effect"
2: import { HttpApiBuilder, OpenApi } from "effect/unstable/httpapi"
3: import { HttpClient, HttpMiddleware, HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http"
4: import * as Socket from "effect/unstable/socket/Socket"
5: import { FSUtil } from "@opencode-ai/core/fs-util"
6: import * as Observability from "@opencode-ai/core/observability"
7: import { Account } from "@/account/account"
8: import { Agent } from "@/agent/agent"
9: import { Auth } from "@/auth"
10: import { BackgroundJob } from "@/background/job"
11: import { Command } from "@/command"
12: import { Config } from "@/config/config"
13: import { Workspace } from "@/control-plane/workspace"
14: import { Env } from "@/env"
15: import { EventV2Bridge } from "@/event-v2-bridge"
16: import { Format } from ...
<path>/home/byk/Code/opencode/packages/opencode/src/server/routes/instance/httpapi/server.ts</path> <type>file</type> <content> 131: // Route tree: 132: // - rootApiRoutes: typed /global/* and control routes; auth is declared by RootHttpApi. 133: // - eventApiRoutes: typed SSE route with instance routing context and its existing API contract. 134: // - ptyConnectApiRoutes: typed WebSocket upgrade route with ticket-aware auth. 135: // - instanceApiRoutes: remaining typed instance routes. 136: // - uiRoute: raw catch-all fallback; auth is router middleware so public static assets can bypass it. 137: const authOnlyRouterLayer = authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.layer)) 138: const httpApiAuthLayer = authorizationLayer.pipe(Layer.provide(ServerAuth.Config.layer)) 139: const ptyConnectHttpApiAuthLayer = ptyConnectAuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.layer)) 140: const serverHttpApiAuthLayer = ...
<path>/home/byk/Code/opencode/packages/server/src/routes.ts</path>
<type>file</type>
<content>
1: import { Database } from "@opencode-ai/core/database/database"
2: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
3: import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
4: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
5: import { EventV2 } from "@opencode-ai/core/event"
6: import { Credential } from "@opencode-ai/core/credential"
7: import { PermissionSaved } from "@opencode-ai/core/permission/saved"
8: import { PtyTicket } from "@opencode-ai/core/pty/ticket"
9: import { SessionV2 } from "@opencode-ai/core/session"
10: import { SessionExecution } from "@opencode-ai/core/session/execution"
11: import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
12: import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
13: import { SessionRecovery } from ...
<path>/home/byk/Code/opencode/packages/core/src/session/sql.ts</path>
<type>file</type>
<content>
1: import { sql } from "drizzle-orm"
2: import { check, sqliteTable, text, integer, index, primaryKey, real, uniqueIndex } from "drizzle-orm/sqlite-core"
3: import * as DatabasePath from "../database/path"
4: import { ProjectTable } from "../project/sql"
5: import type { SessionMessage } from "./message"
6: import type { Prompt } from "./prompt"
7: import type { SessionInput } from "./input"
8: import type { Snapshot } from "../snapshot"
9: import { PermissionV1 } from "../v1/permission"
10: import { ProjectV2 } from "../project"
11: import type { SessionSchema } from "./schema"
12: import type { MessageID, PartID, SessionV1 } from "../v1/session"
13: import { WorkspaceV2 } from "../workspace"
14: import { Timestamps } from "../database/schema.sql"
15: import type { SystemContext } from "../system-context/index"
16: import { AgentV2 } from "../agent"
17: import type { Revert } from ...
<path>/home/byk/Code/opencode/packages/core/src/database/migration/20260914170650_session-recovery.ts</path>
<type>file</type>
<content>
1: import { Effect } from "effect"
2: import type { DatabaseMigration } from "../migration"
3:
4: export default {
5: id: "20260914170650_session-recovery",
6: up(tx) {
7: return Effect.gen(function* () {
8: yield* tx.run(`ALTER TABLE \`session_input\` ADD \`wake_pending\` integer DEFAULT false NOT NULL;`)
9: yield* tx.run(
10: `ALTER TABLE \`session\` ADD \`wake_attempts\` integer DEFAULT 0 NOT NULL CONSTRAINT \`session_wake_attempts_check\` CHECK(\`wake_attempts\` >= 0 AND \`wake_attempts\` <= 9007199254740991);`,
11: )
12: yield* tx.run(`
13: CREATE TABLE \`session_execution\` (
14: \`id\` text NOT NULL,
15: \`session_id\` text NOT NULL,
16: \`owner_id\` text NOT NULL,
17: \`assistant_message_id\` text,
18: \`phase\` text NOT NULL,
19: ...
<path>/home/byk/Code/opencode/packages/core/src/database/migration.gen.ts</path>
<type>file</type>
<content>
1: import type { DatabaseMigration } from "./migration"
2:
3: export const migrations = (
4: await Promise.all([
5: import("./migration/20260127222353_familiar_lady_ursula"),
6: import("./migration/20260211171708_add_project_commands"),
7: import("./migration/20260213144116_wakeful_the_professor"),
8: import("./migration/20260225215848_workspace"),
9: import("./migration/20260227213759_add_session_workspace_id"),
10: import("./migration/20260228203230_blue_harpoon"),
11: import("./migration/20260303231226_add_workspace_fields"),
12: import("./migration/20260309230000_move_org_to_state"),
13: import("./migration/20260312043431_session_message_cursor"),
14: import("./migration/20260323234822_events"),
15: import("./migration/20260410174513_workspace-name"),
16: import("./migration/20260413175956_chief_energizer"),
17: ...
<path>/home/byk/Code/opencode/packages/core/src/session/context-epoch.ts</path>
<type>file</type>
<content>
1: export * as SessionContextEpoch from "./context-epoch"
2:
3: import { eq } from "drizzle-orm"
4: import { DateTime, Effect, Schema } from "effect"
5: import type { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { SystemContext } from "../system-context/index"
8: import { ContextSnapshotDecodeError } from "./error"
9: import { SessionEvent } from "./event"
10: import { SessionHistory } from "./history"
11: import { SessionInput } from "./input"
12: import { SessionMessage } from "./message"
13: import { SessionSchema } from "./schema"
14: import { SessionContextEpochTable } from "./sql"
15: import { SessionExecutionClaim } from "./execution/claim"
16:
17: type DatabaseService = Database.Interface["db"]
18:
19: interface Prepared {
20: readonly baseline: string
21: readonly baselineSeq: number
22: }
23:
24: export function ...
<path>/home/byk/Code/opencode/packages/core/src/session/compaction.ts</path>
<type>file</type>
<content>
1: export * as SessionCompaction from "./compaction"
2:
3: import { LLM, LLMError, LLMEvent, Message, type LLMRequest, type Model } from "@opencode-ai/llm"
4: import { DateTime, Effect, Stream } from "effect"
5: import type { Config } from "../config"
6: import type { EventV2 } from "../event"
7: import { SessionEvent } from "./event"
8: import { SessionMessage } from "./message"
9: import { SessionSchema } from "./schema"
10: import { Token } from "../util/token"
11:
12: const DEFAULT_BUFFER = 20_000
13: const DEFAULT_KEEP_TOKENS = 8_000
14: const TOOL_OUTPUT_MAX_CHARS = 2_000
15: const SUMMARY_OUTPUT_TOKENS = 4_096
16: const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response. ...
<path>/home/byk/Code/opencode/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
1: export * as SessionEvent from "./session-event"
2:
3: import { Schema } from "effect"
4: import { optional } from "./schema"
5: import { Event } from "./event"
6: import { ProviderMetadata, ToolContent } from "./llm"
7: import { Delivery } from "./session-delivery"
8: import { Model } from "./model"
9: import { DateTimeUtcFromMillis, NonNegativeInt, RelativePath } from "./schema"
10: import { FileAttachment, Prompt } from "./prompt"
11: import { SessionID } from "./session-id"
12: import { Location } from "./location"
13: import { SessionMessage } from "./session-message"
14: import { Revert } from "./revert"
15:
16: export { FileAttachment }
17:
18: export const Source = Schema.Struct({
19: start: NonNegativeInt,
20: end: NonNegativeInt,
21: text: Schema.String,
22: }).annotate({
23: identifier: "session.next.event.source",
24: })
25: export interface Source extends ...
Found 11 matches
/home/byk/Code/opencode/packages/schema/src/session-event.ts:
Line 104: export const PromptExecutionRequested = Event.define({
Line 112: export type PromptExecutionRequested = typeof PromptExecutionRequested.Type
Line 484: export const DurableDefinitions = Event.inventory(
Line 490: PromptExecutionRequested,
Line 498: Step.Interrupted,
Line 499: Step.Recovered,
Line 524: PromptExecutionRequested,
Line 532: Step.Interrupted,
Line 533: Step.Recovered,
Line 556: export const Durable = Schema.Union(DurableDefinitions, { mode: "oneOf" })
Line 561: export const All = Schema.Union(Definitions, { mode: "oneOf" }).pipe(Schema.toTaggedUnion("type"))
<path>/home/byk/Code/opencode/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
470: export namespace RevertEvent {
471: export const Staged = Event.define({
472: type: "session.next.revert.staged",
473: ...options,
474: schema: { ...Base, revert: Revert.State },
475: })
476: export const Cleared = Event.define({ type: "session.next.revert.cleared", ...options, schema: Base })
477: export const Committed = Event.define({
478: type: "session.next.revert.committed",
479: ...options,
480: schema: { ...Base, messageID: SessionMessage.ID },
481: })
482: }
483:
484: export const DurableDefinitions = Event.inventory(
485: AgentSwitched,
486: ModelSwitched,
487: Moved,
488: Prompted,
489: PromptAdmitted,
490: PromptExecutionRequested,
491: ContextUpdated,
492: Synthetic,
493: Shell.Started,
494: Shell.Ended,
495: Step.Started,
496: Step.Ended,
497: Step.Failed,
498: Step.Interrupted,
499: ...
Found 8 matches /home/byk/Code/opencode/packages/client/src/generated/types.ts: Line 778: readonly requestExecution?: boolean Line 784: readonly type: "session.next.prompt.execution_requested" Line 895: readonly type: "session.next.step.interrupted" Line 908: readonly type: "session.next.step.recovered" Line 1272: readonly requestExecution?: boolean Line 1278: readonly type: "session.next.prompt.execution_requested" Line 1389: readonly type: "session.next.step.interrupted" Line 1402: readonly type: "session.next.step.recovered"
Found 16 matches /home/byk/Code/opencode/packages/sdk/js/src/v2/gen/types.gen.ts: Line 874: requestExecution?: boolean Line 879: type: "session.next.prompt.execution_requested" Line 973: type: "session.next.step.interrupted" Line 983: type: "session.next.step.recovered" Line 3430: requestExecution?: boolean Line 3439: type: "session.next.prompt.execution_requested.1" Line 3589: type: "session.next.step.interrupted.1" Line 3606: type: "session.next.step.recovered.1" Line 4362: requestExecution?: boolean Line 4371: type: "session.next.prompt.execution_requested" Line 4545: type: "session.next.step.interrupted" Line 4565: type: "session.next.step.recovered" Line 6464: requestExecution?: boolean Line 6470: type: "session.next.prompt.execution_requested" Line 6572: type: "session.next.step.interrupted" Line 6583: type: "session.next.step.recovered"
<path>/home/byk/Code/opencode/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
3400: id: string
3401: syncEvent: {
3402: type: "session.next.prompted.1"
3403: id: string
3404: seq: number
3405: aggregateID: string
3406: data: {
3407: timestamp: number
3408: sessionID: string
3409: messageID: string
3410: prompt: Prompt
3411: delivery: "steer" | "queue"
3412: }
3413: }
3414: }
3415:
3416: export type SyncEventSessionNextPromptAdmitted = {
3417: type: "sync"
3418: id: string
3419: syncEvent: {
3420: type: "session.next.prompt.admitted.1"
3421: id: string
3422: seq: number
3423: aggregateID: string
3424: data: {
3425: timestamp: number
3426: sessionID: string
3427: messageID: string
3428: prompt: Prompt
3429: delivery: "steer" | "queue"
3430: requestExecution?: boolean
3431: }
3432: }
3433: }
3434:
3435: export type ...
Found 21 matches
/home/byk/Code/opencode/packages/core/src/database/schema.gen.ts:
Line 158: CREATE TABLE \`session_execution\` (
Line 168: CONSTRAINT \`session_execution_pk\` PRIMARY KEY(\`id\`),
Line 169: CONSTRAINT \`fk_session_execution_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE,
Line 170: CONSTRAINT "session_execution_phase_check" CHECK("phase" IN ('ready', 'safe', 'unknown', 'continue', 'cancelling', 'cancelled'))
Line 181: \`wake_pending\` integer DEFAULT false NOT NULL,
Line 199: CREATE TABLE \`session_recovery\` (
Line 205: \`wake_pending\` integer DEFAULT true NOT NULL,
Line 206: \`wake_attempts\` integer DEFAULT 0 NOT NULL,
Line 209: CONSTRAINT \`session_recovery_pk\` PRIMARY KEY(\`execution_id\`),
Line 210: CONSTRAINT \`fk_session_recovery_session_id_session_id_fk\` FOREIGN KEY ...
<path>/home/byk/Code/opencode/packages/core/src/database/schema.gen.ts</path> <type>file</type> <content> 145: CONSTRAINT \`fk_part_message_id_message_id_fk\` FOREIGN KEY (\`message_id\`) REFERENCES \`message\`(\`id\`) ON DELETE CASCADE 146: ); 147: `) 148: yield* tx.run(` 149: CREATE TABLE \`session_context_epoch\` ( 150: \`session_id\` text PRIMARY KEY, 151: \`baseline\` text NOT NULL, 152: \`snapshot\` text NOT NULL, 153: \`baseline_seq\` integer NOT NULL, 154: CONSTRAINT \`fk_session_context_epoch_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE 155: ); 156: `) 157: yield* tx.run(` 158: CREATE TABLE \`session_execution\` ( 159: \`id\` text NOT NULL, 160: \`session_id\` text NOT NULL, 161: \`owner_id\` text NOT NULL, 162: \`assistant_message_id\` text, 163: ...
Found 5 matches
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
Line 3240: it.effect("does not strand a committed promotion when a post-commit listener defects", () =>
Line 3906: it.effect("retains cancellation ownership when interrupted tool publication defects", () =>
Line 3940: expect(yield* db.select().from(SessionExecutionTable).get()).toMatchObject({ phase: "cancelling" })
Line 3951: it.effect("settles cancellation after Step failure while a local tool is active", () =>
Line 3959: event.type !== SessionEvent.Step.Failed.type
<path>/home/byk/Code/opencode/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3820: Stream.fromIterable([
3821: LLMEvent.stepStart({ index: 0 }),
3822: LLMEvent.toolCall({ id: "call-blocked-finalizer", name: "blocked_with_finalizer", input: {} }),
3823: LLMEvent.toolCall({ id: "call-sibling-defect", name: "sibling_defect", input: {} }),
3824: ]),
3825: Stream.fail(failure),
3826: )
3827:
3828: const run = yield* session.resume(sessionID).pipe(Effect.exit, Effect.forkChild)
3829: const started = yield* Deferred.await(finalizerStarted).pipe(Effect.timeout("1 second"), Effect.exit)
3830: if (Exit.isFailure(started)) {
3831: yield* Deferred.succeed(releaseFinalizer, undefined)
3832: return yield* started
3833: }
3834:
3835: const retained = yield* db.select().from(SessionExecutionTable).get()
3836: yield* Deferred.succeed(releaseFinalizer, ...
No files found
/home/byk/Code/opencode/packages/core/src/event.ts
Found 24 matches
/home/byk/Code/opencode/packages/core/src/event.ts:
Line 127: readonly publish: <D extends Definition>(
Line 136: readonly listen: (listener: Subscriber) => Effect.Effect<Unsubscribe>
Line 140: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
Line 144: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
Line 155: const unsubscribe = yield* events.listen((event) =>
Line 181: const listeners = new Array<Subscriber>()
Line 357: (wake) => PubSub.publish(wake, undefined),
Line 369: function publishEvent<D extends Definition>(definition: D, event: Payload<D>, commit?: PublishOptions["commit"]) {
Line 402: (cause) => Effect.logError("Event listener failed", { eventID: event.id, eventType: event.type, cause }),
Line 409: listeners,
Line 410: (listener) => ...
<path>/home/byk/Code/opencode/packages/core/src/event.ts</path>
<type>file</type>
<content>
340: id: event.id,
341: aggregate_id: aggregateID,
342: seq,
343: type: versionedType(definition.type, durable.version),
344: data: encoded,
345: },
346: ])
347: .run()
348: .pipe(Effect.orDie)
349: return { aggregateID, seq }
350: }),
351: { behavior: "immediate" },
352: )
353: .pipe(Effect.orDie)
354: if (committed) {
355: yield* Effect.forEach(
356: pubsub.durable.get(committed.aggregateID) ?? [],
357: (wake) => PubSub.publish(wake, ...
Found 7 matches
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
Line 2027: .where(eq(EventTable.type, "session.next.step.ended.2"))
Line 2049: .where(eq(EventTable.type, "session.next.step.ended.2"))
Line 2184: .where(eq(EventTable.type, "session.next.step.ended.2"))
Line 3240: it.effect("does not strand a committed promotion when a post-commit listener defects", () =>
Line 3957: const failureCommitted = yield* Deferred.make<void>()
Line 3961: : Deferred.succeed(failureCommitted, undefined).pipe(Effect.andThen(Effect.never)),
Line 3975: yield* Deferred.await(failureCommitted)
Found 3 matches
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
Line 2091: it.effect("never records assistant start state when Step start publication defects", () =>
Line 2096: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail Step start publication" }), resume: false })
Line 2103: SELECT RAISE(ABORT, 'crash before Step start publication');
Found 18 matches
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
Line 3240: it.effect("does not strand a committed promotion when a post-commit listener defects", () =>
Line 3804: Effect.andThen(Effect.never),
Line 3961: : Deferred.succeed(failureCommitted, undefined).pipe(Effect.andThen(Effect.never)),
/home/byk/Code/opencode/packages/core/test/session-execution-local.test.ts:
Line 92: run: () => Deferred.succeed(started, undefined).pipe(Effect.andThen(Effect.never)),
Line 107: run: () => Deferred.succeed(started, undefined).pipe(Effect.andThen(Effect.never)),
Line 122: run: () => Deferred.succeed(firstStarted, undefined).pipe(Effect.andThen(Effect.never)),
Line 146: run: () => Deferred.succeed(firstStarted, undefined).pipe(Effect.andThen(Effect.never)),
Line 198: run: () => Deferred.succeed(started, undefined).pipe(Effect.andThen(Effect.never)),
Line 222: run: () ...
<path>/home/byk/Code/opencode/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3740: {
3741: type: "assistant",
3742: content: [
3743: {
3744: type: "tool",
3745: id: "call-question",
3746: state: { status: "error", error: { type: "unknown", message: "Tool execution interrupted" } },
3747: },
3748: ],
3749: },
3750: ])
3751: }),
3752: )
3753:
3754: it.effect("awaits started local tools before surfacing provider stream failure", () =>
3755: Effect.gen(function* () {
3756: yield* setup
3757: const session = yield* SessionV2.Service
3758: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Settle before failing" }), resume: false })
3759: const failure = providerUnavailable()
3760: toolExecutionGate = yield* Deferred.make<void>()
3761: responseStream = Stream.concat(
3762: ...
Found 3 matches
/home/byk/Code/opencode/packages/core/test/session-projector.test.ts:
Line 365: assistantRow(SessionMessage.ID.make("msg_recovery_newer"), 1, { created, completed: created }),
Line 410: .values(assistantRow(SessionMessage.ID.make(`msg_recovery_newer_${phase}`), 0, { created, completed: created }))
Line 524: .values(assistantRow(SessionMessage.ID.make("msg_recovery_current_boundary"), 0, { created, completed: created }))
Found 37 matches
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
Line 35: import { SessionRecovery } from "@opencode-ai/core/session/recovery"
Line 111: const recoveryModel = Model.make({
Line 112: id: "recovery",
Line 249: run: (claim, force, recoveryInputID) =>
Line 250: sessionRunner.run({ ...claim, force, recoveryInputID }).pipe(
Line 389: currentModel = recoveryModel
Line 619: it.effect("redrives only the synthetic recovery continuation", () =>
Line 625: const unrelatedID = SessionMessage.ID.make("msg_recovery_unrelated")
Line 636: executionID: "lost-recovery-wake",
Line 646: yield* SessionRecovery.recover()
Line 652: expect(userTexts(request)).toEqual(["Session execution interrupted by server restart", "continue"])
Line 674: prompt: Prompt.make({ text: "ordinary work after replayed recovery" }),
Line 683: expect(userTexts(requests[1]!)).toContain("ordinary work ...
<path>/home/byk/Code/opencode/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1960: name: "echo",
1961: state: {
1962: status: "completed",
1963: input: { text: "hello" },
1964: structured: { text: "hello" },
1965: content: [{ type: "text", text: "hello" }],
1966: },
1967: },
1968: ],
1969: },
1970: { type: "assistant", finish: "stop", content: [{ type: "text", id: "text-final", text: "Done" }] },
1971: ])
1972: }),
1973: )
1974:
1975: it.effect("never commits continuation before unresolved hosted tools are reconciled", () =>
1976: Effect.gen(function* () {
1977: yield* setup
1978: const session = yield* SessionV2.Service
1979: const { db } = yield* Database.Service
1980: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Mixed tools" }), resume: false ...
Found 85 matches
/tmp/opencode/session-recovery-final-v18.patch:
Line 4247: + test("adds final recovery ownership tables to a populated predecessor", async () => {
Line 4364: + it.effect("clears the claim after a successful drain", () =>
Line 4373: + it.effect("snapshots the latest assistant sequence when acquiring a claim", () =>
Line 4405: + it.effect("preserves the claim when its scope closes", () =>
Line 4421: + it.effect("clears the claim after explicit interruption", () =>
Line 4435: + it.effect("never starts a second owner while the lease is live", () =>
Line 4458: + it.effect("never overwrites an expired lease", () =>
Line 4485: + it.effect("never re-enters its own uncertain live lease", () =>
Line 4512: + it.effect("renews heartbeat expiry from the current clock", () =>
Line 4535: + it.effect("never revives an expired claim on heartbeat", () =>
Line 4553: + it.effect("settles cancellation before starting a pending successor", () =>
...
<path>/home/byk/Code/opencode/packages/core/test/session-execution-local.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect } from "bun:test"
2: import { Deferred, Effect, Exit, Fiber, Scope } from "effect"
3: import { TestClock } from "effect/testing"
4: import { eq, sql } from "drizzle-orm"
5: import { Database } from "@opencode-ai/core/database/database"
6: import { Project } from "@opencode-ai/core/project"
7: import { ProjectTable } from "@opencode-ai/core/project/sql"
8: import { AbsolutePath } from "@opencode-ai/core/schema"
9: import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
10: import { SessionExecutionClaim } from "@opencode-ai/core/session/execution/claim"
11: import { SessionMessage } from "@opencode-ai/core/session/message"
12: import { SessionSchema } from "@opencode-ai/core/session/schema"
13: import { SessionExecutionTable, SessionTable } from "@opencode-ai/core/session/sql"
14: import { testEffect } from ...
<path>/home/byk/Code/opencode/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
2140: .insert(SessionExecutionTable)
2141: .values({
2142: id: "transferred-cancellation-owner",
2143: session_id: sessionID,
2144: owner_id: "dead",
2145: phase: "cancelling",
2146: time_created: 0,
2147: expires_at: 0,
2148: })
2149: .run()
2150: yield* db.run(sql`
2151: CREATE TRIGGER transfer_cancellation_recovery_owner
2152: AFTER UPDATE OF owner_id ON session_execution
2153: WHEN NEW.id = 'transferred-cancellation-owner'
2154: BEGIN
2155: UPDATE session_execution
2156: SET owner_id = 'successor-owner', expires_at = 9007199254740991
2157: WHERE id = NEW.id;
2158: END
2159: `)
2160:
2161: yield* SessionRecovery.recover().pipe(
2162: Effect.provideService(SessionExecution.Service, ...
Found 1 matches
/home/byk/Code/opencode/packages/core/test/session-projector.test.ts:
Line 1910: it.effect("never promotes a quarantined recovery continuation as an ordinary steer", () =>
<path>/home/byk/Code/opencode/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1860: yield* db
1861: .insert(SessionTable)
1862: .values({
1863: id: sessionID,
1864: project_id: Project.ID.global,
1865: slug: "test",
1866: directory: "/project",
1867: title: "test",
1868: version: "test",
1869: })
1870: .run()
1871: yield* events.publish(SessionEvent.Step.Recovered, {
1872: sessionID,
1873: timestamp: created,
1874: executionID: "synthetic-stale",
1875: phase: "ready",
1876: })
1877: yield* events.publish(SessionEvent.Step.Started, {
1878: sessionID,
1879: timestamp: DateTime.makeUnsafe(1),
1880: assistantMessageID: SessionMessage.ID.make("msg_recovery_after_synthetic"),
1881: agent: "build",
1882: model,
1883: })
1884: const wakes: string[] = []
1885: ...
<path>/home/byk/Code/opencode/packages/core/src/session/recovery-id.ts</path>
<type>file</type>
<content>
1: import { SessionMessage } from "./message"
2:
3: export const message = (executionID: string, assistantMessageID?: SessionMessage.ID) =>
4: assistantMessageID ?? SessionMessage.ID.make(`msg_recovery_${executionID}`)
5:
6: export const continuation = (executionID: string) => SessionMessage.ID.make(`msg_recovery_continue_${executionID}`)
(End of file - total 6 lines)
</content>
<path>/home/byk/Code/opencode/packages/core/src/session/runner/index.ts</path>
<type>file</type>
<content>
1: export * as SessionRunner from "./index"
2:
3: import type { LLMError } from "@opencode-ai/llm"
4: import { Context, Effect } from "effect"
5: import { SessionSchema } from "../schema"
6: import type { SessionMessage } from "../message"
7: import type { ContextSnapshotDecodeError, MessageDecodeError } from "../error"
8: import { SessionRunnerModel } from "./model"
9: import type { SystemContext } from "../../system-context/index"
10: import type { ToolOutputStore } from "../../tool-output-store"
11:
12: export type RunError =
13: | LLMError
14: | SessionRunnerModel.Error
15: | MessageDecodeError
16: | ContextSnapshotDecodeError
17: | SystemContext.InitializationBlocked
18: | ToolOutputStore.Error
19:
20: /** Runs one local continuation from already-recorded Session history. */
21: export interface Interface {
22: /** Drains eligible durable work. ...
Found 34 matches /tmp/opencode/session-recovery-final-v18.patch: Line 1: diff --git a/packages/client/src/generated/types.ts b/packages/client/src/generated/types.ts Line 109: diff --git a/packages/core/schema.json b/packages/core/schema.json Line 1025: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts Line 1036: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts Line 1104: diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts Line 1210: diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts Line 1250: diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts Line 1339: diff --git a/packages/core/src/session/context-epoch.ts b/packages/core/src/session/context-epoch.ts Line 1515: diff --git ...
<path>/home/byk/Code/opencode/packages/core/test/database-migration.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import { $ } from "bun"
3: import { fileURLToPath } from "url"
4: import path from "path"
5: import { SqliteClient } from "@effect/sql-sqlite-bun"
6: import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
7: import { Effect, Exit, Layer } from "effect"
8: import { eq, inArray, sql } from "drizzle-orm"
9: import { DatabaseMigration } from "@opencode-ai/core/database/migration"
10: import { migrations } from "@opencode-ai/core/database/migration.gen"
11: import workspaceNameMigration from "@opencode-ai/core/database/migration/20260410174513_workspace-name"
12: import sessionUsageMigration from "@opencode-ai/core/database/migration/20260510033149_session_usage"
13: import normalizeStoragePathsMigration from "@opencode-ai/core/database/migration/20260601010001_normalize_storage_paths"
14: import ...
Found 6 matches /home/byk/Code/opencode/packages/core/schema.json: Line 2266: "name": "session_execution_session_idx", Line 2390: "name": "session_input_wake_pending_promoted_session_idx", Line 2510: "name": "session_recovery_wake_pending_error_attempts_created_execution_idx", Line 2572: "name": "session_execution_phase_check", Line 2578: "name": "session_recovery_wake_attempts_check", Line 2584: "name": "session_wake_attempts_check",
<path>/tmp/opencode/session-recovery-final-v18.patch</path>
<type>file</type>
<content>
7488: diff --git a/packages/core/test/session-prompt.test.ts b/packages/core/test/session-prompt.test.ts
7489: index c6bc9430b3..a0d6f26467 100644
7490: --- a/packages/core/test/session-prompt.test.ts
7491: +++ b/packages/core/test/session-prompt.test.ts
7492: @@ -1,6 +1,6 @@
7493: import { describe, expect } from "bun:test"
7494: import { DateTime, Effect, Fiber, Layer, Stream } from "effect"
7495: -import { eq } from "drizzle-orm"
7496: +import { asc, eq } from "drizzle-orm"
7497: import { Database } from "@opencode-ai/core/database/database"
7498: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
7499: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
7500: @@ -16,6 +16,7 @@ import { SessionMessage } from "@opencode-ai/core/session/message"
7501: import { SessionProjector } from "@opencode-ai/core/session/projector"
7502: import { SessionExecution } ...
<path>/tmp/opencode/session-recovery-final-v18.patch</path>
<type>file</type>
<content>
7815: diff --git a/packages/core/test/session-run-coordinator.test.ts b/packages/core/test/session-run-coordinator.test.ts
7816: index dfbeda664c..63dfad7a71 100644
7817: --- a/packages/core/test/session-run-coordinator.test.ts
7818: +++ b/packages/core/test/session-run-coordinator.test.ts
7819: @@ -282,6 +282,51 @@ describe("SessionRunCoordinator", () => {
7820: ),
7821: )
7822:
7823: + it.effect("finalizes interruption when cleanup dies", () =>
7824: + Effect.scoped(
7825: + Effect.gen(function* () {
7826: + const firstStarted = yield* Deferred.make<void>()
7827: + const cleanupStarted = yield* Deferred.make<void>()
7828: + const cleanupGate = yield* Deferred.make<void>()
7829: + const secondStarted = yield* Deferred.make<void>()
7830: + const defect = new Error("cleanup defect")
7831: + let runs = 0
7832: + const coordinator = ...
<path>/home/byk/Code/opencode/packages/schema/test/event-manifest.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import { FileSystem, Integration, Permission, Project, Reference, Session, Workspace } from "../src"
3: import { EventManifest } from "../src/event-manifest"
4: import { IdeEvent } from "../src/ide-event"
5: import { SessionEvent } from "../src/session-event"
6: import { SessionTodo } from "../src/session-todo"
7: import { SessionV1 } from "../src/session-v1"
8: import { WorkspaceEvent } from "../src/workspace-event"
9:
10: describe("public event manifest", () => {
11: test("owns the complete public event surface", () => {
12: expect(EventManifest.ServerDefinitions.length).toBe(61)
13: expect(EventManifest.Definitions.length).toBe(91)
14: expect(SessionV1.Event.Definitions).toEqual([
15: SessionV1.Event.Created,
16: SessionV1.Event.Updated,
17: SessionV1.Event.Deleted,
18: ...
Found 26 matches /home/byk/Code/opencode/packages/sdk/openapi.json: Line 15336: "$ref": "#/components/schemas/EventSessionNextStepInterrupted" Line 15339: "$ref": "#/components/schemas/EventSessionNextStepRecovered" Line 17919: "enum": ["session.next.prompt.execution_requested"] Line 18253: "enum": ["session.next.step.interrupted"] Line 18289: "enum": ["session.next.step.recovered"] Line 20604: "$ref": "#/components/schemas/SyncEventSessionNextStepInterrupted" Line 20607: "$ref": "#/components/schemas/SyncEventSessionNextStepRecovered" Line 23838: "$ref": "#/components/schemas/SessionNextStepInterrupted" Line 23841: "$ref": "#/components/schemas/SessionNextStepRecovered" Line 24227: "$ref": "#/components/schemas/SessionNextStepInterrupted" Line 24230: "$ref": ...
<path>/home/byk/Code/opencode/packages/sdk/openapi.json</path>
<type>file</type>
<content>
26220: "additionalProperties": false
26221: }
26222: },
26223: "required": ["type", "id", "syncEvent"],
26224: "additionalProperties": false
26225: },
26226: "SyncEventSessionNextStepInterrupted": {
26227: "type": "object",
26228: "properties": {
26229: "type": {
26230: "type": "string",
26231: "enum": ["sync"]
26232: },
26233: "id": {
26234: "type": "string",
26235: "pattern": "^evt_"
26236: },
26237: "syncEvent": {
26238: "type": "object",
26239: "properties": {
26240: "type": {
26241: "type": "string",
26242: "enum": ["session.next.step.interrupted.1"]
26243: },
26244: "id": {
26245: "type": "string",
26246: ...
<path>/home/byk/Code/opencode/packages/sdk/openapi.json</path>
<type>file</type>
<content>
25700: "pattern": "^msg_"
25701: },
25702: "prompt": {
25703: "$ref": "#/components/schemas/Prompt"
25704: },
25705: "delivery": {
25706: "type": "string",
25707: "enum": ["steer", "queue"]
25708: },
25709: "requestExecution": {
25710: "type": "boolean"
25711: }
25712: },
25713: "required": ["timestamp", "sessionID", "messageID", "prompt", "delivery"],
25714: "additionalProperties": false
25715: }
25716: },
25717: "required": ["type", "id", "seq", "aggregateID", "data"],
25718: "additionalProperties": false
25719: }
25720: },
25721: "required": ...
<path>/home/byk/Code/opencode/packages/core/script/migration.ts</path>
<type>file</type>
<content>
1: #!/usr/bin/env bun
2:
3: import { $ } from "bun"
4: import fs from "fs/promises"
5: import os from "os"
6: import path from "path"
7: import { pathToFileURL } from "url"
8: import { parseArgs } from "util"
9:
10: const root = path.resolve(import.meta.dirname, "../../..")
11: const snapshot = path.join(root, "packages/core/schema.json")
12: const tsDir = path.join(root, "packages/core/src/database/migration")
13: const registry = path.join(root, "packages/core/src/database/migration.gen.ts")
14: const schema = path.join(root, "packages/core/src/database/schema.gen.ts")
15: const args = parseArgs({
16: args: process.argv.slice(2),
17: options: {
18: check: { type: "boolean" },
19: name: { type: "string" },
20: },
21: })
22:
23: if (args.values.check) {
24: await check()
25: process.exit(0)
26: }
27:
28: await generate()
29:
30: async function generate() {
31: ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_0acc6bbe3001oNS6IH7j2Xet6C
- yield* db
- .update(SessionExecutionTable)
- .set({ phase: "unknown", assistant_message_id: null })
- .where(eq(SessionExecutionTable.session_id, session.id))
- .run()
- .pipe(Effect.orDie)
+ yield* updateExecution({
+ phase: "unknown",
+ assistant_message_id: null,
+ })
const stream = yield* restore(providerStream).pipe(Effect.exit)
const failure =
stream._tag === "Failure" ? Option.getOrUndefined(Cause.findErrorOption(stream.cause)) : undefined
@@ -322,15 +380,17 @@ const layer = Layer.effect(
entries,
model,
request,
+ beforeStream: () =>
+ updateExecution({
+ phase: "unknown",
+ assistant_message_id: ...
<path>/home/byk/Code/opencode/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
330: )
331: })
332: },
333: "session.next.tool.input.delta": () => Effect.void,
334: "session.next.tool.input.ended": (event) => {
335: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
336: const match = latestTool(draft, event.data.callID)
337: if (match && match.state.status === "pending") match.state.input = event.data.text
338: })
339: },
340: "session.next.tool.called": (event) => {
341: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
342: const match = latestTool(draft, event.data.callID)
343: if (match) {
344: match.provider = event.data.provider
345: match.time.ran = event.data.timestamp
346: match.state = castDraft(
347: ...
ab9408c81cc9916177641ae8e9f08624fc27b5b9.63c11067ed78471a8109455f164327a1c6dc2ff547e5cb78cad185fe2d844e82.c5730f7d6017ebd8dde2a2e91365e574469ef10494ec632632b91d7e2318885c.Step.Started boundarystartAssistant() commits Step.Started before storing the assistant ID in publisher memory:
packages/core/src/session/runner/publish-llm-event.ts:88-107If cancellation interrupts notification after that commit but before line 105:
publisher.interruptAssistant(...):
packages/core/src/session/runner/llm.ts:417-465interruptAssistant() calls startAssistant() again:
packages/core/src/session/runner/publish-llm-event.ts:242-260cancelling phase:
packages/core/src/session/execution/claim.ts:46-55packages/core/src/session/runner/llm.ts:291-303flush() can fail even earlier because the publisher has no current assistant ID:
packages/core/src/session/runner/publish-llm-event.ts:109-112,182-185cancelling; local finalization deliberately preserves it:
packages/core/src/session/execution/local.ts:104-119