Dashboard › opencode › Session 17YqagsqBhVv
17YqagsqBhVvZJ7vdPerform a strict READ-ONLY correctness/adversarial review of the frozen candidate in /tmp/opencode/session-recovery-v19-review. Do not edit files, run tests/builds/services, mutate Git/index/refs, or use recall. Read applicable AGENTS.md/package guidance and inspect every changed file plus necessary immutable-base context. Immutable inputs: base commit ab9408c81cc9916177641ae8e9f08624fc27b5b9; frozen patch /tmp/opencode/session-recovery-final-v19.patch SHA-256 a2bce01664538243cb8ba16c6ca8f0364c6f79463ad6677567044a1b3e1e74fe; exact SEA /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server SHA-256 753b05afb86195246b211237ca5b11e26a7c08da863d70a711da2c93ee453c8a; exactly 34 changed paths. Verify hashes and patch applicability/read-only provenance first. Review lifecycle, transaction, replay, concurrency, recovery identity, lease/fencing, cancellation, provider/tool ordering, generated contracts, migration lineage, and test adequacy. Focus especially on: (1) Step.Started can commit before notification is interrupted; explicit interrupt must settle only after the runner/finalizers join, publish one durable Step.Interrupted against the durable assistant, reconcile unresolved tools, atomically move cancelling->cancelled, then clear before a successor starts; (2) publication defects and already-completed assistants; (3) same-owner cancellation phases cannot be overwritten; (4) resume:false followed by explicit resume durably arms the exact original input before execution acquisition and restart redrives it without synthetic continue; (5) scan index and generated current-client surfaces. Treat final synchronous check-to-external-call, process-local interruption routing, and bounded wall-clock skew as disclosed residuals, but report any missing immediate check or larger race. Findings first with MUST-FIX/CONCERN and exact file:line evidence. If no blocker, explicitly say so and residual risks. End with exactly MERGE or DO-NOT-MERGE.
a2bce01664538243cb8ba16c6ca8f0364c6f79463ad6677567044a1b3e1e74fe /tmp/opencode/session-recovery-final-v19.patch 753b05afb86195246b211237ca5b11e26a7c08da863d70a711da2c93ee453c8a /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server 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/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 packages/core/src/session/message-updater.ts M packages/core/src/session/projector.ts M packages/core/src/session/run-coordinator.ts M packages/core/src/session/runner/index.ts M packages/core/src/session/runner/llm.ts M packages/core/src/session/runner/publish-llm-event.ts M packages/core/src/session/sql.ts M packages/core/test/database-migration.test.ts M ...
/tmp/opencode/session-recovery-v19-review/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/ui/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/desktop/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/app/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/stats/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/session-ui/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/core/src/tool/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/effect-drizzle-sqlite/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/app/e2e/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/app/e2e/performance/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/llm/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/schema/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/codemode/AGENTS.md /tmp/opencode/session-recovery-v19-review/packages/opencode/AGENTS.md /tmp/opencode/session-recovery...
<path>/tmp/opencode/session-recovery-v19-review/AGENTS.md</path> <type>file</type> <content> 1: - To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`. 2: - After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly. 3: - Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server. 4: - The default branch in this repo is `dev`. 5: - Local `main` ref may not exist; use `dev` or `origin/dev` for diffs. 6: 7: ## Branch Names 8: 9: Use a short branch name of at most three words, separated by hyphens. Do not use slashes or type prefixes such as `feat/` or `fix/`. 10: 11: Examples: `session-recovery`, `fix-scroll-state`, `regenerate-sdk`. ...
<path>/tmp/opencode/session-recovery-v19-review/packages/schema/AGENTS.md</path> <type>file</type> <content> 1: # Schema Package Guide 2: 3: `@opencode-ai/schema` owns browser-safe wire and storage contracts shared by protocol, server, core, and generated SDKs. Keep runtime behavior, service layers, side effects, and host-local implementation details in the domain package that owns them. 4: 5: ## Package Boundary 6: 7: - Preserve the dependency direction: `@opencode-ai/schema <- @opencode-ai/protocol <- @opencode-ai/server`. 8: - Schema values should be serializable contract definitions, not service implementations or runtime registries. 9: - A domain may keep a minimal public wire contract here when SDK generation needs it, but do not move the broader runtime model into Schema just because an event is public. `plugin.added` is the current example: Schema may own the minimum browser-safe event payload, while plugin runtime behavior stays outside Schema. ...
<path>/tmp/opencode/session-recovery-v19-review/packages/opencode/AGENTS.md</path>
<type>file</type>
<content>
1: # opencode database guide
2:
3: ## Database
4:
5: - **Schema**: Drizzle schema lives in `packages/core/src/**/*.sql.ts`.
6: - **Migrations**: database migrations live in `packages/core` and are applied by core.
7:
8: ## Development server
9:
10: - Running `bun dev` from `packages/opencode` starts the live interactive TUI. Do not run it as a blocking foreground command when you need to inspect the result.
11: - Start it in `tmux` instead: `tmux new-session -d -s opencode-dev 'bun dev'`.
12: - Capture the current TUI output with: `tmux capture-pane -pt opencode-dev`.
13: - Stop the session explicitly when done: `tmux kill-session -t opencode-dev`.
14:
15: # Module shape
16:
17: Do not use `export namespace Foo { ... }` for module organization. It is not
18: standard ESM, it prevents tree-shaking, and it breaks Node's native TypeScript
19: runner. ...
<path>/tmp/opencode/session-recovery-v19-review/packages/opencode/src/server/routes/instance/httpapi/AGENTS.md</path>
<type>file</type>
<content>
1: # HttpApi Route Patterns
2:
3: Use `HttpApiBuilder.group(...)` for normal HTTP endpoints, including streaming HTTP responses such as server-sent events. Handlers should yield stable services once while building the handler layer, then close over those services in endpoint implementations.
4:
5: ```ts
6: export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", (handlers) =>
7: Effect.gen(function* () {
8: const session = yield* Session.Service
9:
10: return handlers.handle("list", () => session.list())
11: }),
12: )
13: ```
14:
15: For SSE endpoints, stay in `HttpApiBuilder.group(...)` and return `HttpServerResponse.stream(...)` from the handler. Annotate the endpoint success schema with `HttpApiSchema.asText({ contentType: "text/event-stream" })` so OpenAPI documents the stream content type. ...
<path>/tmp/opencode/session-recovery-final-v19.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; ...
Found 34 matches /tmp/opencode/session-recovery-final-v19.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 ...
verified 34 patch paths: every base blob and candidate postimage matches each patch index line packages/client/src/generated/types.ts packages/core/schema.json packages/core/src/database/migration.gen.ts packages/core/src/database/migration/20260914170650_session-recovery.ts packages/core/src/database/schema.gen.ts packages/core/src/session.ts packages/core/src/session/compaction.ts packages/core/src/session/context-epoch.ts packages/core/src/session/execution/local.ts packages/core/src/session/input.ts packages/core/src/session/message-updater.ts packages/core/src/session/projector.ts packages/core/src/session/recovery.ts packages/core/src/session/run-coordinator.ts packages/core/src/session/runner/index.ts packages/core/src/session/runner/llm.ts packages/core/src/session/runner/publish-llm-event.ts packages/core/src/session/sql.ts packages/core/test/database-migration.test.ts packages/core/test/session-execution-local.test.ts packages/core/test/session-projector.test.ts packages/core...
<path>/tmp/opencode/session-recovery-v19-review/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: ...
<path>/tmp/opencode/session-recovery-v19-review/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>/tmp/opencode/session-recovery-v19-review/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: ...
<path>/tmp/opencode/session-recovery-v19-review/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, DateTime, Duration, Effect, Exit, Layer, Option, 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 { EventV2 } from "../../event"
13: import { SessionEvent } from "../event"
14: import { SessionExecutionClaim } from "./claim"
15: import {
16: SESSION_EXECUTION_LEASE_MS,
17: SessionExecutionTable,
18: ...
<path>/tmp/opencode/session-recovery-v19-review/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: ...
<path>/tmp/opencode/session-recovery-v19-review/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 = ...
<path>/tmp/opencode/session-recovery-v19-review/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: ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
500: yield* events.project(SessionEvent.RevertEvent.Committed, (event) =>
501: Effect.gen(function* () {
502: const boundary = yield* db
503: .select({ seq: SessionMessageTable.seq })
504: .from(SessionMessageTable)
505: .where(
506: and(
507: eq(SessionMessageTable.session_id, event.data.sessionID),
508: eq(SessionMessageTable.id, event.data.messageID),
509: ),
510: )
511: .get()
512: .pipe(Effect.orDie)
513: if (!boundary) return yield* Effect.die(`Revert boundary message not found: ${event.data.messageID}`)
514: yield* db
515: .delete(SessionMessageTable)
516: .where(
517: and(eq(SessionMessageTable.session_id, event.data.sessionID), gt(SessionMessageTable.seq, boundary.seq)),
518: ...
No files found
/tmp/opencode/session-recovery-v19-review/packages/core/src/event.ts
Found 34 matches
/tmp/opencode/session-recovery-v19-review/packages/core/src/event.ts:
Line 122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
Line 123: readonly commit?: (seq: number) => Effect.Effect<void>
Line 127: readonly publish: <D extends Definition>(
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 205: function commitDurableEvent(
Line 214: commit?: (seq: number) => Effect.Effect<void>,
Line 239: const committed = yield* db
Line 316: const committed = {
Line 321: yield* projector(committed)
Line 323: if (commit) yield* commit(seq)
Line 354: if (committed) {
...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/src/event.ts</path>
<type>file</type>
<content>
100: },
101: data: event.data,
102: }),
103: )
104: return {
105: events,
106: hasMore: rows.length > input.limit,
107: }
108: })
109:
110: export class SubscriberOverflowError extends Schema.TaggedErrorClass<SubscriberOverflowError>()(
111: "EventV2.SubscriberOverflow",
112: { capacity: Schema.Int },
113: ) {}
114:
115: export const define = Event.define
116: export const versionedType = Event.versionedType
117:
118: export interface PublishOptions {
119: readonly id?: ID
120: readonly metadata?: Record<string, unknown>
121: readonly location?: Location.Ref
122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
123: readonly commit?: (seq: number) => Effect.Effect<void>
124: }
125:
126: export interface Interface {
127: readonly publish: <D extends Definition>(
128: ...
<path>/tmp/opencode/session-recovery-v19-review/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. ...
<path>/tmp/opencode/session-recovery-v19-review/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 ...
<path>/tmp/opencode/session-recovery-v19-review/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>/tmp/opencode/session-recovery-v19-review/packages/core/src/session.ts</path>
<type>file</type>
<content>
1: export * as SessionV2 from "./session"
2: export * from "./session/schema"
3:
4: import { DateTime, Effect, Layer, Schema, Context, Stream } from "effect"
5: import { ListAnchor } from "@opencode-ai/schema/session"
6: import { and, asc, desc, eq, gt, like, lt, or, type SQL } from "drizzle-orm"
7: import { ProjectV2 } from "./project"
8: import { WorkspaceV2 } from "./workspace"
9: import { ModelV2 } from "./model"
10: import { Location } from "./location"
11: import { SessionMessage } from "./session/message"
12: import { Prompt } from "./session/prompt"
13: import { PromptInput } from "@opencode-ai/schema/prompt-input"
14: import { EventV2 } from "./event"
15: import { Database } from "./database/database"
16: import { SessionProjector } from "./session/projector"
17: import { SessionMessageTable, SessionTable } from "./session/sql"
18: import { SessionSchema } from ...
<path>/tmp/opencode/session-recovery-v19-review/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 { ...
<path>/tmp/opencode/session-recovery-v19-review/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 ...
Found 21 matches
/tmp/opencode/session-recovery-v19-review/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\` ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/src/database/schema.gen.ts</path> <type>file</type> <content> 130: \`session_id\` text NOT NULL, 131: \`time_created\` integer NOT NULL, 132: \`time_updated\` integer NOT NULL, 133: \`data\` text NOT NULL, 134: CONSTRAINT \`fk_message_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE 135: ); 136: `) 137: yield* tx.run(` 138: CREATE TABLE \`part\` ( 139: \`id\` text PRIMARY KEY, 140: \`message_id\` text NOT NULL, 141: \`session_id\` text NOT NULL, 142: \`time_created\` integer NOT NULL, 143: \`time_updated\` integer NOT NULL, 144: \`data\` text NOT NULL, 145: CONSTRAINT \`fk_part_message_id_message_id_fk\` FOREIGN KEY (\`message_id\`) REFERENCES \`message\`(\`id\`) ON DELETE CASCADE 146: ); 147: `) 148: ...
<path>/tmp/opencode/session-recovery-v19-review/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: ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/schema.json</path>
<type>file</type>
<content>
1: {
2: "version": "7",
3: "dialect": "sqlite",
4: "id": "3bc49670-06d1-4eba-9a45-8c8128bdcf6f",
5: "prevIds": [
6: "d4ff43d2-c4f9-442c-bada-a7d488b42fd2"
7: ],
8: "ddl": [
9: {
10: "name": "workspace",
11: "entityType": "tables"
12: },
13: {
14: "name": "data_migration",
15: "entityType": "tables"
16: },
17: {
18: "name": "account_state",
19: "entityType": "tables"
20: },
21: {
22: "name": "account",
23: "entityType": "tables"
24: },
25: {
26: "name": "control_account",
27: "entityType": "tables"
28: },
29: {
30: "name": "credential",
31: "entityType": "tables"
32: },
33: {
34: "name": "event_sequence",
35: "entityType": "tables"
36: },
37: {
38: "name": "event",
39: "entityType": "tables"
40: },
41: ...
f14a9b18-8207-487e-a3d3-227e629ba9ad ['169a0f0f-d58f-479f-b024-fa1c7b9a09db']
Found 1 matches /tmp/opencode/session-recovery-v19-review/packages/core/schema.json: Line 6: "d4ff43d2-c4f9-442c-bada-a7d488b42fd2"
<path>/tmp/opencode/session-recovery-v19-review/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 ...
Found 100 matches (more matches available)
/tmp/opencode/session-recovery-v19-review/packages/core/test/session-create.test.ts:
Line 199: yield* session.prompt({ sessionID: created.id, prompt: Prompt.make({ text: "Hello" }), resume: false })
Line 220: resume: false,
/tmp/opencode/session-recovery-v19-review/packages/core/test/session-runner-tool-registry.test.ts:
Line 135: it.effect("preserves an interrupted registration until its scope closes", () =>
Line 149: yield* Fiber.interrupt(fiber)
Line 157: it.effect("returns model errors without swallowing interruption or defects", () =>
/tmp/opencode/session-recovery-v19-review/packages/core/test/session-projector.test.ts:
Line 45: interrupt: () => Effect.void,
Line 186: expect(yield* db.select().from(SessionRecoveryTable).get()).toMatchObject({ wake_pending: false })
Line 208: expect(yield* db.select().from(SessionRecoveryTable).get()).toMatchObject({ wake_pending: false })
...
packages/client/src/generated/types.ts | 72 + packages/core/schema.json | 615 ++++ packages/core/src/database/migration.gen.ts | 1 .../migration/20260914170650_session-recovery.ts | 62 packages/core/src/database/schema.gen.ts | 53 packages/core/src/session.ts | 10 packages/core/src/session/compaction.ts | 47 packages/core/src/session/context-epoch.ts | 88 - packages/core/src/session/execution/local.ts | 219 +- packages/core/src/session/input.ts | 422 +++ packages/core/src/session/message-updater.ts | 69 packages/core/src/session/projector.ts | 89 + packages/core/src/session/recovery.ts | 504 ++++ packages/core/src/session/run-coordinator.ts | 35 packages/core/src/session/runner/index.ts | 4 packages/core/src/session/runner/llm.ts | 462 +++ ...
<path>/tmp/opencode/session-recovery-v19-review/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: ...
<path>/tmp/opencode/session-recovery-v19-review/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>/tmp/opencode/session-recovery-v19-review/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>/tmp/opencode/session-recovery-v19-review/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>/tmp/opencode/session-recovery-v19-review/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 { ...
<path>/tmp/opencode/session-recovery-v19-review/packages/opencode/src/server/routes/instance/httpapi/server.ts</path> <type>file</type> <content> 295: Layer.provide(Layer.succeed(CorsConfig)(corsOptions)), 296: Layer.provide(sessionLocationLayer), 297: Layer.provide(locationLayer), 298: Layer.provide(PtyEnvironment.layer), 299: Layer.provide( 300: AppNodeBuilderV1.build(LayerNode.group([SessionV2.node, SessionRecovery.node]), [ 301: [LocationServiceMap.node, locationServiceMapV2], 302: [SessionExecution.node, SessionExecutionLocal.node], 303: ]), 304: ), 305: Layer.provide(locationServiceMapV2), 306: 307: Layer.provide(AppNodeBuilderV1.build(app)), 308: // Must stay last: layers provided later in this pipe build beneath earlier ones, 309: // so Observability must come after every service graph. Otherwise eagerly forked 310: // fibers (e.g. ...
<path>/tmp/opencode/session-recovery-v19-review/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 ...
<path>/tmp/opencode/session-recovery-v19-review/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
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: Step.Recovered,
500: Text.Started,
501: Text.Ended,
502: Tool.Input.Started,
503: Tool.Input.Ended,
504: Tool.Called,
505: Tool.Progress,
506: Tool.Success,
507: Tool.Failed,
508: Reasoning.Started,
509: Reasoning.Ended,
510: Retried,
511: Compaction.Started,
512: Compaction.Ended,
513: RevertEvent.Staged,
514: RevertEvent.Cleared,
515: RevertEvent.Committed,
516: )
517:
518: export const ...
Found 6 matches /tmp/opencode/session-recovery-v19-review/packages/client/src/generated/types.ts: 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 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 12 matches /tmp/opencode/session-recovery-v19-review/packages/sdk/js/src/v2/gen/types.gen.ts: Line 879: type: "session.next.prompt.execution_requested" Line 973: type: "session.next.step.interrupted" Line 983: type: "session.next.step.recovered" 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 4371: type: "session.next.prompt.execution_requested" Line 4545: type: "session.next.step.interrupted" Line 4565: type: "session.next.step.recovered" Line 6470: type: "session.next.prompt.execution_requested" Line 6572: type: "session.next.step.interrupted" Line 6583: type: "session.next.step.recovered"
Found 4 matches /tmp/opencode/session-recovery-v19-review/packages/sdk/js/src/v2/gen/types.gen.ts: Line 874: requestExecution?: boolean Line 3430: requestExecution?: boolean Line 4362: requestExecution?: boolean Line 6464: requestExecution?: boolean
Found 12 matches /tmp/opencode/session-recovery-v19-review/packages/sdk/openapi.json: Line 17919: "enum": ["session.next.prompt.execution_requested"] Line 18253: "enum": ["session.next.step.interrupted"] Line 18289: "enum": ["session.next.step.recovered"] Line 25740: "enum": ["session.next.prompt.execution_requested.1"] Line 26242: "enum": ["session.next.step.interrupted.1"] Line 26299: "enum": ["session.next.step.recovered.1"] Line 28622: "enum": ["session.next.prompt.execution_requested"] Line 29132: "enum": ["session.next.step.interrupted"] Line 29190: "enum": ["session.next.step.recovered"] Line 34831: "enum": ["session.next.prompt.execution_requested"] Line 35165: "enum": ["session.next.step.interrupted"] Line 35201: "enum": ["session.next.step.recovered"]
<path>/tmp/opencode/session-recovery-v19-review/packages/client/src/generated/types.ts</path>
<type>file</type>
<content>
740: readonly mime: string
741: readonly name?: string
742: readonly description?: string
743: readonly source?: { readonly start: number; readonly end: number; readonly text: string }
744: }>
745: readonly agents?: ReadonlyArray<{
746: readonly name: string
747: readonly source?: { readonly start: number; readonly end: number; readonly text: string }
748: }>
749: }
750: readonly delivery: "steer" | "queue"
751: }
752: }
753: | {
754: readonly id: string
755: readonly metadata?: { readonly [x: string]: JsonValue }
756: readonly type: "session.next.prompt.admitted"
757: readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
758: ...
<path>/tmp/opencode/session-recovery-v19-review/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 17 matches
/tmp/opencode/session-recovery-v19-review/packages/core/test/session-runner.test.ts:
Line 845: it.effect("never fails interrupted tools after ownership changes", () =>
Line 1034: it.effect("interrupts a source Location runner after a Session moves", () =>
Line 1757: it.effect("interrupts overflow recovery while the summary provider is running", () =>
Line 2055: it.effect("keeps a running assistant recoverable when Step failure publication defects", () =>
Line 2091: it.effect("never records assistant start state when Step start publication defects", () =>
Line 2128: it.effect("durably reconciles a local tool when success publication defects", () =>
Line 2738: it.effect("preserves durable queued input for a later wake after interruption", () =>
Line 2781: it.effect("preserves durable steering input for a later resume after interruption", () =>
Line 3535: it.effect("interrupts runner continuation when permission approval is ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3770: while (executions.length === 0) yield* Effect.yieldNow
3771: yield* Effect.yieldNow
3772: yield* Deferred.succeed(toolExecutionGate, undefined)
3773: expect(yield* Fiber.join(run).pipe(Effect.flip)).toBe(failure)
3774: toolExecutionGate = undefined
3775:
3776: expect(yield* session.context(sessionID)).toMatchObject([
3777: { type: "user", text: "Settle before failing" },
3778: {
3779: type: "assistant",
3780: content: [
3781: { type: "tool", id: "call-before-failure", state: { status: "completed", structured: { text: "settle" } } },
3782: ],
3783: },
3784: ])
3785: }),
3786: )
3787:
3788: it.live("never marks a failed Step safe while a sibling tool finalizer is active", () =>
3789: Effect.gen(function* () {
3790: yield* ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1980: slug: id,
1981: directory: "/project",
1982: title: "test",
1983: version: "test",
1984: })),
1985: )
1986: .run()
1987: yield* db.run(sql`PRAGMA ignore_check_constraints = ON`)
1988: yield* db.run(
1989: sql`INSERT INTO session_execution (id, session_id, owner_id, phase, time_created, expires_at) VALUES ('malformed', ${sessionID}, 'dead', 'invalid', 0, 0)`,
1990: )
1991: yield* db
1992: .insert(SessionExecutionTable)
1993: .values({
1994: id: "valid",
1995: session_id: validSession,
1996: owner_id: "dead",
1997: phase: "ready",
1998: time_created: 1,
1999: expires_at: 0,
2000: })
2001: .run()
2002:
2003: yield* ...
Found 6 matches
/tmp/opencode/session-recovery-v19-review/packages/core/src/session/execution/local.ts:
Line 147: yield* SessionExecutionClaim.settleCancellation(db, claim, { phase: "cancelled", assistant_message_id: null })
Line 161: SessionExecutionClaim.settleCancellation(db, claim, { phase: "cancelled", assistant_message_id: null }),
Line 178: settleCancellation(claim).pipe(
/tmp/opencode/session-recovery-v19-review/packages/core/src/session/runner/llm.ts:
Line 203: const renewCancellation = () => SessionExecutionClaim.settleCancellation(db, claim)
Line 205: SessionExecutionClaim.settleCancellation(db, claim, { phase: "cancelled", assistant_message_id: null })
Line 365: yield* settleCancellation()
export * as SessionInput from "./input"
import { and, asc, eq, isNull, lte } from "drizzle-orm"
import { DateTime, Effect, Schema } from "effect"
import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
import type { Database } from "../database/database"
import type { EventV2 } from "../event"
import { SessionEvent } from "./event"
import { SessionMessage } from "./message"
import { Prompt } from "./prompt"
import { SessionSchema } from "./schema"
import { SessionInputTable, SessionMessageTable } from "./sql"
type DatabaseService = Database.Interface["db"]
export { Admitted, Delivery }
const decodePrompt = Schema.decodeUnknownSync(Prompt)
const encodePrompt = Schema.encodeSync(Prompt)
const fromRow = (row: typeof SessionInputTable.$inferSelect): Admitted =>
Admitted.make({
admittedSeq: row.admitted_seq,
id: SessionMessage.ID.make(row.id),
sessionID: SessionSchema.ID.make(row.session_id),
prompt: decodePrompt(row.prompt),
delivery: ...
Found 1 matches
/tmp/opencode/session-recovery-v19-review/packages/schema/src/session-message.ts:
Line 12: export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe(
<path>/tmp/opencode/session-recovery-v19-review/packages/schema/src/session-message.ts</path>
<type>file</type>
<content>
1: export * as SessionMessage from "./session-message"
2:
3: import { Schema } from "effect"
4: import { optional } from "./schema"
5: import { ProviderMetadata, ToolContent } from "./llm"
6: import { Model } from "./model"
7: import { FileAttachment, Prompt } from "./prompt"
8: import { DateTimeUtcFromMillis, RelativePath, statics } from "./schema"
9: import { SessionID } from "./session-id"
10: import { ascending } from "./identifier"
11:
12: export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe(
13: Schema.brand("Session.Message.ID"),
14: statics((schema) => ({ create: () => schema.make("msg_" + ascending()) })),
15: )
16: export type ID = typeof ID.Type
17:
18: export interface UnknownError extends Schema.Schema.Type<typeof UnknownError> {}
19: export const UnknownError = Schema.Struct({
20: type: Schema.Literal("unknown"),
21: ...
Found 10 matches /tmp/opencode/session-recovery-v19-review/packages/server/src/routes.ts: Line 32: SessionV2.node, /tmp/opencode/session-recovery-v19-review/packages/opencode/src/server/routes/instance/httpapi/server.ts: Line 300: AppNodeBuilderV1.build(LayerNode.group([SessionV2.node, SessionRecovery.node]), [ /tmp/opencode/session-recovery-v19-review/packages/opencode/test/session/prompt.test.ts: Line 732: LayerNode.compile(SessionV2.node, [ /tmp/opencode/session-recovery-v19-review/packages/opencode/test/session/compaction.test.ts: Line 614: Effect.provide(AppNodeBuilder.build(SessionV2.node, [[SessionExecution.node, SessionExecution.noopLayer]])), /tmp/opencode/session-recovery-v19-review/packages/core/test/session-create.test.ts: Line 40: LayerNode.group([Database.node, EventV2.node, SessionProjector.node, SessionStore.node, SessionV2.node]), /tmp/opencode/session-recovery-v19-review/packages/core/test/session-runner.test.ts: ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/test/session-prompt.test.ts</path>
<type>file</type>
<content>
140: it.effect("exposes the execution registry", () =>
141: Effect.gen(function* () {
142: activeSessions.add(sessionID)
143: expect(Array.from(yield* (yield* SessionV2.Service).active)).toEqual([sessionID])
144: }).pipe(Effect.ensuring(Effect.sync(() => activeSessions.clear()))),
145: )
146:
147: it.effect("delegates execution continuation through SessionExecution", () =>
148: Effect.gen(function* () {
149: yield* setup
150: const session = yield* SessionV2.Service
151: executionCalls.length = 0
152: wakeCalls.length = 0
153: yield* session.resume(sessionID)
154: expect(executionCalls).toEqual([sessionID])
155: expect(wakeCalls).toEqual([])
156: }),
157: )
158:
159: it.effect("delegates process-local interruption through SessionExecution", () =>
160: Effect.gen(function* () ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/test/session-prompt.test.ts</path>
<type>file</type>
<content>
640: )
641:
642: it.effect("reprojects pending inbox input without scheduling execution", () =>
643: Effect.gen(function* () {
644: yield* setup
645: const { db } = yield* Database.Service
646: const session = yield* SessionV2.Service
647: wakeCalls.length = 0
648: yield* session.prompt({
649: id: messageID,
650: sessionID,
651: prompt: Prompt.make({ text: "Replay pending" }),
652: resume: false,
653: })
654: yield* replaySessionProjection()
655:
656: expect(yield* admitted(messageID)).toMatchObject({ id: messageID, prompt: { text: "Replay pending" } })
657: expect(yield* db.select().from(SessionInputTable).where(eq(SessionInputTable.id, messageID)).get()).toMatchObject(
658: {
659: wake_pending: false,
660: },
661: )
662: ...
Found 7 matches
/tmp/opencode/session-recovery-v19-review/packages/core/test/session-projector.test.ts:
Line 300: expect(messages[0]?.type).toBe("synthetic")
Line 1144: it.effect("redrives durable ordinary input execution intent after a lost advisory wake", () =>
Line 1874: executionID: "synthetic-stale",
Line 1880: assistantMessageID: SessionMessage.ID.make("msg_recovery_after_synthetic"),
Line 3052: text: "synthetic context",
Line 3119: "synthetic",
Line 3178: ).toMatchObject({ type: "synthetic" })
Found 100 matches (more matches available)
/tmp/opencode/session-recovery-v19-review/packages/core/test/session-runner.test.ts:
Line 386: resume: false,
Line 522: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: prompt }), resume: false })
Line 553: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: prompt }), resume: false })
Line 579: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: prompt }), resume: false })
Line 631: resume: false,
Line 675: resume: false,
Line 688: it.effect("clears durable execution intent when ordinary input is promoted", () =>
Line 695: resume: false,
Line 761: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "fence provider" }), resume: false })
Line 787: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "fence publication" }), resume: false })
Line 829: yield* session.prompt({ sessionID, prompt: ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
500: partialEvents,
501: completeEvents: [...partialEvents, LLMEvent.toolInputEnd({ id, name: "echo" })],
502: expectedAssistant: {
503: type: "assistant",
504: finish: "error",
505: error: { type: "unknown", message: "Provider Step ended without settlement" },
506: content: [expectedContent],
507: },
508: expectedContent,
509: }
510: }
511: }
512: }
513:
514: const verifyEphemeralDeltas = (kind: FragmentKind) =>
515: Effect.gen(function* () {
516: yield* setup
517: const session = yield* SessionV2.Service
518: const prompt = `Stream ${kind}`
519: const chunks = Array.from({ length: 32 }, (_, index) => `${index},`)
520: const fixture = fragmentFixture(kind, fragmentID(kind, "many"), chunks)
521: const expectedContext = [{ type: "user", text: ...
<path>/tmp/opencode/session-recovery-v19-review/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: ...
<path>/tmp/opencode/session-recovery-v19-review/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 ...
<path>/tmp/opencode/session-recovery-final-v19.patch</path>
<type>file</type>
<content>
109: diff --git a/packages/core/schema.json b/packages/core/schema.json
110: index d0eeeebd5c..81e5eca808 100644
111: --- a/packages/core/schema.json
112: +++ b/packages/core/schema.json
113: @@ -1,8 +1,10 @@
114: {
115: "version": "7",
116: "dialect": "sqlite",
117: - "id": "f14a9b18-8207-487e-a3d3-227e629ba9ad",
118: - "prevIds": ["169a0f0f-d58f-479f-b024-fa1c7b9a09db"],
119: + "id": "3bc49670-06d1-4eba-9a45-8c8128bdcf6f",
120: + "prevIds": [
121: + "d4ff43d2-c4f9-442c-bada-a7d488b42fd2"
122: + ],
123: "ddl": [
124: {
125: "name": "workspace",
126: @@ -60,6 +62,10 @@
127: "name": "session_context_epoch",
128: "entityType": "tables"
129: },
130: + {
131: + "name": "session_execution",
132: + "entityType": "tables"
133: + },
134: {
135: "name": "session_input",
136: "entityType": "tables"
137: @@ -68,6 +74,10 ...
Found 50 matches
/tmp/opencode/session-recovery-v19-review/packages/core/test/session-projector.test.ts:
Line 68: it.effect("atomically reconciles one abandoned execution into one continuation", () =>
Line 222: it.effect("records a recovery continuation without forging an assistant identity", () =>
Line 258: it.effect("recovers each abandoned execution once across repeated startup scans", () =>
Line 304: it.effect("never recovers a live execution lease", () =>
Line 342: it.effect("never recovers an execution superseded by a newer assistant", () =>
Line 504: it.effect("recovers assistant-less continuation at its current assistant boundary", () =>
Line 555: it.effect("redrives a durable recovery wake after admission", () =>
Line 606: it.effect("quarantines a malformed recovery row without blocking a valid wake", () =>
Line 660: it.effect("quarantines a full batch of poison recovery rows before redriving valid work", () =>
Line 725: ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1120: phase: "ready",
1121: })
1122: yield* db.run(
1123: sql`UPDATE session_input SET session_id = ${other} WHERE id = ${continuation("cross-session-continuation")}`,
1124: )
1125: const wakes: string[] = []
1126:
1127: yield* SessionRecovery.recover().pipe(
1128: Effect.provideService(
1129: SessionExecution.Service,
1130: SessionExecution.Service.of({
1131: ...recoveryExecution,
1132: wake: (id) => Effect.sync(() => wakes.push(id)).pipe(Effect.asVoid),
1133: }),
1134: ),
1135: )
1136:
1137: expect(wakes).toEqual([])
1138: expect(yield* db.select().from(SessionRecoveryTable).get()).toMatchObject({
1139: recovery_error: "Invalid recovery continuation input",
1140: })
1141: }),
1142: )
1143:
1144: ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1550: title: "test",
1551: version: "test",
1552: })
1553: .run()
1554: yield* events.publish(SessionEvent.Step.Recovered, {
1555: sessionID,
1556: timestamp: created,
1557: executionID: "conflicting-recovery-identity",
1558: phase: "continue",
1559: })
1560: yield* db
1561: .insert(SessionExecutionTable)
1562: .values({
1563: id: "conflicting-recovery-identity",
1564: session_id: sessionID,
1565: owner_id: "dead-owner",
1566: phase: "ready",
1567: time_created: 0,
1568: expires_at: 0,
1569: })
1570: .run()
1571:
1572: yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, recoveryExecution))
1573:
1574: expect(yield* ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/test/session-runner-recorded.test.ts</path>
<type>file</type>
<content>
1: import { HttpRecorder } from "@opencode-ai/http-recorder"
2: import { HttpRecorderInternal } from "@opencode-ai/http-recorder/internal"
3: import * as OpenAIChat from "@opencode-ai/llm/protocols/openai-chat"
4: import { Auth, LLMClient, RequestExecutor } from "@opencode-ai/llm/route"
5: import { Database } from "@opencode-ai/core/database/database"
6: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
7: import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
8: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
9: import { EventV2 } from "@opencode-ai/core/event"
10: import { EventTable } from "@opencode-ai/core/event/sql"
11: import { PermissionV2 } from "@opencode-ai/core/permission"
12: import { AgentV2 } from "@opencode-ai/core/agent"
13: import { Config } from ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/test/session-runner-tool-events.test.ts</path>
<type>file</type>
<content>
1: import { expect, test } from "bun:test"
2: import { Effect, Schema, Stream } from "effect"
3: import { LLMEvent } from "@opencode-ai/llm"
4: import { EventV2 } from "@opencode-ai/core/event"
5: import { SessionEvent } from "@opencode-ai/core/session/event"
6: import { SessionMessage } from "@opencode-ai/core/session/message"
7: import { SessionV2 } from "@opencode-ai/core/session"
8: import { ModelV2 } from "@opencode-ai/core/model"
9: import { ProviderV2 } from "@opencode-ai/core/provider"
10: import { createLLMEventPublisher } from "@opencode-ai/core/session/runner/publish-llm-event"
11:
12: const sessionID = SessionV2.ID.make("ses_tool_event_test")
13: const base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB"
14:
15: const capture = () => {
16: const published: Array<{ readonly type: string; readonly data: unknown }> = []
17: const events = ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/test/session-run-coordinator.test.ts</path>
<type>file</type>
<content>
180: let runs = 0
181: const coordinator = yield* SessionRunCoordinator.make({
182: drain: () =>
183: Effect.sync(() => ++runs).pipe(
184: Effect.flatMap((run) =>
185: run === 1
186: ? Deferred.await(firstGate)
187: : run === 2
188: ? Deferred.succeed(secondStarted, undefined).pipe(Effect.andThen(Deferred.await(secondGate)))
189: : Deferred.succeed(thirdStarted, undefined),
190: ),
191: ),
192: })
193:
194: const resumed = yield* coordinator.run("session").pipe(Effect.forkChild)
195: yield* Effect.yieldNow
196: yield* coordinator.wake("session")
197: yield* Deferred.succeed(firstGate, undefined)
198: yield* ...
/tmp/opencode/session-recovery-v19-review/packages/client/src/generated-effect/client-error.ts /tmp/opencode/session-recovery-v19-review/packages/client/src/generated-effect/index.ts /tmp/opencode/session-recovery-v19-review/packages/client/src/generated-effect/.httpapi-codegen.json /tmp/opencode/session-recovery-v19-review/packages/client/src/generated-effect/client.ts
No files found
<path>/tmp/opencode/session-recovery-v19-review/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
840: messageID: string
841: model: ModelRef
842: }
843: }
844: | {
845: id: string
846: type: "session.next.moved"
847: properties: {
848: timestamp: number
849: sessionID: string
850: location: LocationRef
851: subdirectory?: string
852: }
853: }
854: | {
855: id: string
856: type: "session.next.prompted"
857: properties: {
858: timestamp: number
859: sessionID: string
860: messageID: string
861: prompt: Prompt
862: delivery: "steer" | "queue"
863: }
864: }
865: | {
866: id: string
867: type: "session.next.prompt.admitted"
868: properties: {
869: timestamp: number
870: sessionID: string
871: messageID: ...
<path>/tmp/opencode/session-recovery-v19-review/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
3390: timestamp: number
3391: sessionID: string
3392: location: LocationRef
3393: subdirectory?: string
3394: }
3395: }
3396: }
3397:
3398: export type SyncEventSessionNextPrompted = {
3399: type: "sync"
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: ...
Found 6 matches
/tmp/opencode/session-recovery-v19-review/packages/sdk/js/src/v2/gen/types.gen.ts:
Line 1650: | SyncEventSessionNextPromptExecutionRequested
Line 1658: | SyncEventSessionNextStepInterrupted
Line 1659: | SyncEventSessionNextStepRecovered
Line 3435: export type SyncEventSessionNextPromptExecutionRequested = {
Line 3585: export type SyncEventSessionNextStepInterrupted = {
Line 3602: export type SyncEventSessionNextStepRecovered = {
<path>/tmp/opencode/session-recovery-v19-review/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
4320: }
4321: }
4322:
4323: export type SessionNextPrompted = {
4324: id: string
4325: metadata?: {
4326: [key: string]: unknown
4327: }
4328: type: "session.next.prompted"
4329: durable?: {
4330: aggregateID: string
4331: seq: number
4332: version: number
4333: }
4334: location?: LocationRef
4335: data: {
4336: timestamp: number
4337: sessionID: string
4338: messageID: string
4339: prompt: Prompt
4340: delivery: "steer" | "queue"
4341: }
4342: }
4343:
4344: export type SessionNextPromptAdmitted = {
4345: id: string
4346: metadata?: {
4347: [key: string]: unknown
4348: }
4349: type: "session.next.prompt.admitted"
4350: durable?: {
4351: aggregateID: string
4352: seq: number
4353: version: number
4354: }
4355: location?: LocationRef
4356: data: {
4357: timestamp: ...
Found 21 matches
/tmp/opencode/session-recovery-v19-review/packages/sdk/js/src/v2/gen/types.gen.ts:
Line 24: | EventSessionNextPromptExecutionRequested
Line 32: | EventSessionNextStepInterrupted
Line 33: | EventSessionNextStepRecovered
Line 1650: | SyncEventSessionNextPromptExecutionRequested
Line 1658: | SyncEventSessionNextStepInterrupted
Line 1659: | SyncEventSessionNextStepRecovered
Line 2785: | SessionNextPromptExecutionRequested
Line 2793: | SessionNextStepInterrupted
Line 2794: | SessionNextStepRecovered
Line 2915: | SessionNextPromptExecutionRequested
Line 2923: | SessionNextStepInterrupted
Line 2924: | SessionNextStepRecovered
Line 3435: export type SyncEventSessionNextPromptExecutionRequested = {
Line 3585: export type SyncEventSessionNextStepInterrupted = {
Line 3602: export type SyncEventSessionNextStepRecovered = {
Line 4366: export type SessionNextPromptExecutionRequested = {
Line 4540: ...
{"entityType": "tables", "name": "session_execution"}
{"entityType": "tables", "name": "session_recovery"}
{"autoincrement": false, "default": null, "entityType": "columns", "generated": null, "name": "id", "notNull": true, "table": "session_execution", "type": "text"}
{"autoincrement": false, "default": null, "entityType": "columns", "generated": null, "name": "session_id", "notNull": true, "table": "session_execution", "type": "text"}
{"autoincrement": false, "default": null, "entityType": "columns", "generated": null, "name": "owner_id", "notNull": true, "table": "session_execution", "type": "text"}
{"autoincrement": false, "default": null, "entityType": "columns", "generated": null, "name": "assistant_message_id", "notNull": false, "table": "session_execution", "type": "text"}
{"autoincrement": false, "default": null, "entityType": "columns", "generated": null, "name": "phase", "notNull": true, "table": "session_execution", "type": "text"}
{"autoincrement": false, "default": "-1", ...
components/schemas/GlobalEvent/properties/payload/anyOf/16/properties/type {"enum": ["session.next.prompt.execution_requested"], "type": "string"}
components/schemas/GlobalEvent/properties/payload/anyOf/24/properties/type {"enum": ["session.next.step.interrupted"], "type": "string"}
components/schemas/GlobalEvent/properties/payload/anyOf/25/properties/type {"enum": ["session.next.step.recovered"], "type": "string"}
components/schemas/SyncEventSessionNextPromptExecution_requested/properties/syncEvent/properties/type {"enum": ["session.next.prompt.execution_requested.1"], "type": "string"}
components/schemas/SyncEventSessionNextStepInterrupted/properties/syncEvent/properties/type {"enum": ["session.next.step.interrupted.1"], "type": "string"}
components/schemas/SyncEventSessionNextStepRecovered/properties/syncEvent/properties/type {"enum": ["session.next.step.recovered.1"], "type": "string"}
components/schemas/SessionNextPromptExecution_requested/properties/type {"enum": ...
=== SyncEventSessionNextPromptExecution_requested
{
"additionalProperties": false,
"properties": {
"id": {
"pattern": "^evt_",
"type": "string"
},
"syncEvent": {
"additionalProperties": false,
"properties": {
"aggregateID": {
"type": "string"
},
"data": {
"additionalProperties": false,
"properties": {
"messageID": {
"pattern": "^msg_",
"type": "string"
},
"sessionID": {
"pattern": "^ses",
"type": "string"
},
"timestamp": {
"type": "number"
}
},
"required": [
"timestamp",
"sessionID",
"messageID"
],
"type": "object"
},
"id": {
"pattern": "^evt_",
"type": "string"
},
"seq": {
"type": "number"
},
"type": ...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1360: )
1361:
1362: expect(wakes).toEqual([sessionID])
1363: expect(yield* db.select().from(SessionExecutionTable).all()).toEqual([])
1364: expect(yield* db.select().from(SessionRecoveryTable).all()).toHaveLength(1)
1365: expect(yield* db.select().from(SessionInputTable).all()).toHaveLength(1)
1366: }),
1367: )
1368:
1369: it.effect("never recovers a pre-promotion claim when input commits at the recovery boundary", () =>
1370: Effect.gen(function* () {
1371: const { db } = yield* Database.Service
1372: const events = yield* EventV2.Service
1373: yield* db
1374: .insert(ProjectTable)
1375: .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
1376: .run()
1377: yield* db
1378: .insert(SessionTable)
1379: ...
Found 16 matches
/tmp/opencode/session-recovery-v19-review/packages/core/test/session-projector.test.ts:
Line 57: time: { created: DateTime.Utc; completed?: DateTime.Utc } = { created },
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 }))
Line 3125: time: { completed: DateTime.makeUnsafe(1) },
Line 3192: const completed = SessionMessage.Assistant.make({
Line 3193: id: SessionMessage.ID.make("msg_assistant_completed"),
Line 3198: time: { created: DateTime.makeUnsafe(1), completed: DateTime.makeUnsafe(2) },
Line 3202: yield* SessionMessageUpdater.memory({ messages: [stale, completed] ...
Found 3 matches
/tmp/opencode/session-recovery-v19-review/packages/core/test/session-runner.test.ts:
Line 2055: it.effect("keeps a running assistant recoverable when Step failure publication defects", () =>
Line 2067: SELECT RAISE(ABORT, 'crash before Step failure publication');
Line 3952: it.effect("settles cancellation after Step failure while a local tool is active", () =>
Found 61 matches
/tmp/opencode/session-recovery-v19-review/packages/core/src/event.ts:
Line 126: export interface Interface {
/tmp/opencode/session-recovery-v19-review/packages/core/src/state.ts:
Line 53: export interface Interface<State, DraftApi> extends Transformable<DraftApi> {
/tmp/opencode/session-recovery-v19-review/packages/core/src/control-plane/move-session.ts:
Line 63: export interface Interface {
/tmp/opencode/session-recovery-v19-review/packages/core/src/global.ts:
Line 41: export interface Interface {
/tmp/opencode/session-recovery-v19-review/packages/core/src/config.ts:
Line 128: export interface Interface {
/tmp/opencode/session-recovery-v19-review/packages/core/src/agent.ts:
Line 35: export interface Interface extends State.Transformable<Draft> {
/tmp/opencode/session-recovery-v19-review/packages/core/src/pty/ticket.ts:
Line 20: export interface Interface {
/tmp/opencode/session-recovery-v19-review/packages/core/src/background-job.ts:
...
<path>/tmp/opencode/session-recovery-v19-review/packages/core/src/snapshot.ts</path>
<type>file</type>
<content>
35: /** Paths are relative to the project root. */
36: readonly files: ReadonlyMap<RelativePath, ID>
37: }
38:
39: export interface PreviewInput extends RestoreInput {
40: readonly context?: number
41: }
42:
43: export interface Interface {
44: /**
45: * Capture the current Location-scoped filesystem state as a content-addressed
46: * tree. Returns `undefined` when snapshots are disabled, unsupported, or the
47: * best-effort capture fails.
48: */
49: readonly capture: () => Effect.Effect<ID | undefined>
50:
51: /**
52: * List project-relative paths changed between two captured trees without
53: * loading file contents or generating patches.
54: */
55: readonly files: (input: CompareInput) => Effect.Effect<readonly RelativePath[], Error>
56:
57: /**
58: * Generate structured per-file diffs between two captured trees. ...
<path>/tmp/opencode/session-recovery-v19-review/packages/schema/src/durable-event-manifest.ts</path>
<type>file</type>
<content>
1: export * as DurableEventManifest from "./durable-event-manifest"
2:
3: import { Event } from "./event"
4: import { SessionEvent } from "./session-event"
5: import { SessionV1 } from "./session-v1"
6:
7: export const SessionDurable = {
8: definitions: Event.durable(SessionEvent.DurableDefinitions),
9: schema: SessionEvent.Durable,
10: } as const
11:
12: export const Durable = Event.durable([
13: ...SessionV1.Event.Definitions.filter((definition) => definition.durable !== undefined),
14: ...SessionEvent.DurableDefinitions,
15: ])
(End of file - total 15 lines)
</content>
Found 100 matches (more matches available)
/tmp/opencode/session-recovery-v19-review/packages/core/test/session-runner.test.ts:
Line 619: it.effect("redrives only the synthetic recovery continuation", () =>
Line 688: it.effect("clears durable execution intent when ordinary input is promoted", () =>
Line 715: it.effect("redrives an ordinary prompt after its advisory wake is lost", () =>
Line 752: it.effect("never dispatches a provider after ownership changes", () =>
Line 778: it.effect("never publishes streamed output after ownership changes", () =>
Line 805: it.effect("never starts a local tool after ownership changes", () =>
Line 845: it.effect("never fails interrupted tools after ownership changes", () =>
Line 902: it.effect("advertises and executes a globally attached application tool", () =>
Line 958: it.effect("starts a real runner turn after default prompt recording", () =>
Line 977: it.effect("streams one request with registry ...
Found 30 matches
/tmp/opencode/session-recovery-v19-review/packages/core/src/database/migration/20260914170650_session-recovery.ts:
Line 22: \`recovery_error\` text,
Line 37: \`recovery_error\` text,
Line 49: `CREATE INDEX \`session_execution_recovery_scan_idx\` ON \`session_execution\` (\`recovery_error\`, \`expires_at\`, \`time_created\`, \`id\`);`,
Line 58: `CREATE INDEX \`session_recovery_wake_pending_error_attempts_created_execution_idx\` ON \`session_recovery\` (\`wake_pending\`, \`recovery_error\`, \`wake_attempts\`, \`time_created\`, \`execution_id\`);`,
/tmp/opencode/session-recovery-v19-review/packages/core/src/session/input.ts:
Line 406: recovery.recovery_error ??
Line 625: .set({ recovery_error: defect.reason })
Line 630: isNull(SessionRecoveryTable.recovery_error),
/tmp/opencode/session-recovery-v19-review/packages/core/src/session/sql.ts:
Line 200: recovery_error: ...
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/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 packages/core/src/session/message-updater.ts M packages/core/src/session/projector.ts M packages/core/src/session/run-coordinator.ts M packages/core/src/session/runner/index.ts M packages/core/src/session/runner/llm.ts M packages/core/src/session/runner/publish-llm-event.ts M packages/core/src/session/sql.ts M packages/core/test/database-migration.test.ts M packages/core/test/session-projector.test.ts M packages/core/test/session-prompt.test.ts M packages/core/test/session-run-coordinator.test.ts M packages/core/test/session-runner-recorded.test.ts M packages/core/test/session-runner-tool-events.test.ts M ...
SessionRunner publishes Step.Interrupted and atomically changes cancelling -> cancelled inside the runner at packages/core/src/session/runner/llm.ts:307-374. The enclosing scope does not finish until packages/core/src/session/runner/llm.ts:569, so runner finalizers can still be active after cancellation has become durable.
The regression at packages/core/test/session-runner.test.ts:4055-4106 confirms the wrong ordering: it observes one durable Step.Interrupted at lines 4078-4084 while the injected runner finalizer remains blocked, then releases the finalizer at line 4103.
SessionRunCoordinator correctly joins the owner before cleanup at packages/core/src/session/run-coordinator.ts:107-108, but cleanup often finds cancelled and only clears the claim at packages/core/src/session/execution/local.ts:130-131. It does not defer publication and the cancelling -> cancelled transition until after that join.
This violates the required lifecycle order. The blocked-finalizer test should assert that the claim remains cancelling and no Step.Interrupted exists until the finalizer joins, followed by exactly one atomic interruption settlement and claim clear.
Both cancellation paths create a real cancelled crash boundary before clearing:
packages/core/src/session/execution/local.ts:146-148Step.Interrupted: packages/core/src/session/execution/local.ts:151-164packages/core/src/session/runner/llm.ts:359-371Recovery treats cancelled exactly like uncertain cancelling work and quarantines it at packages/core/src/session/recovery.ts:121-139.
That quarantine is permanent:
recovery_error at packages/core/src/session/recovery.ts:71-76. ...