Dashboard › opencode › Session 1C0iHSxv8ICe
1C0iHSxv8ICeCEp6kStrict READ-ONLY correctness/adversarial review of frozen v22 in /tmp/opencode/session-recovery-v22-review. Never edit, run tests/builds/services, mutate VCS/index/refs, or use recall. Verify immutable inputs first: base ab9408c81cc9916177641ae8e9f08624fc27b5b9; patch /tmp/opencode/session-recovery-final-v22.patch SHA-256 5af96209cef23e735f09dbf2f4b2f5b0f51b208ece9e5b325e4e217b46003643; SEA /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server SHA-256 10a3698a5e3b009f811a1b27eefab9e1083b560e24efee5f537f003d7edd7384; 34 paths. Inspect all changed files and relevant context. Focus on: cancellation after durable Step.Failed with unresolved local tools must preserve the provider failure, publish durable unknown tool outcomes only after tool/finalizer join, atomically set cancelled on last tool, clear exact claim, and emit no duplicate Step.Interrupted/recovery; Step.Interrupted publication failure must roll back tool settlement and cancellation transition, leave exact claim cancelling and block successor; concurrent interrupts join one cleanup and preserve wake; failed-Step restart recovery never creates continuation but reconciles unresolved tools idempotently; continue-phase still promotes; schema ancestry directly names f14a9b18-8207-487e-a3d3-227e629ba9ad. Recheck ownership, replay, corruption, fairness, snapshots, generated contracts/layering. Accepted residuals: final check-call, process-local routing, bounded skew. Findings first with exact file:line and MUST-FIX/CONCERN. End exactly MERGE or DO-NOT-MERGE.
5af96209cef23e735f09dbf2f4b2f5b0f51b208ece9e5b325e4e217b46003643 /tmp/opencode/session-recovery-final-v22.patch 10a3698a5e3b009f811a1b27eefab9e1083b560e24efee5f537f003d7edd7384 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server /tmp/opencode/session-recovery-final-v22.patch 433468 bytes 1789636732 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server 157879104 bytes 1789636682
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 ...
apply_cached_exit=0 reverse_worktree_exit=0 diff_check_exit=0 34
patch_paths 34 unique_paths 34 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/test/session-prompt.test.ts packages/core/test/session-run-coor...
/tmp/opencode/session-recovery-v22-review/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/app/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/app/e2e/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/app/e2e/performance/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/ui/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/session-ui/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/effect-drizzle-sqlite/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/llm/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/codemode/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/stats/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/desktop/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/opencode/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/opencode/src/session/llm/AGENTS.md /tmp/opencode/session-recovery-v22-review/packages/schema/AGENTS.md /tmp/opencode/sessi...
<path>/tmp/opencode/session-recovery-v22-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-v22-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-v22-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-v22-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. ...
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 | 279 +- 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/run-coordinator.ts | 40 +- packages/core/src/session/runner/index.ts | 4 + packages/core/src/session/runner/llm.ts | 393 ++- .../core/src/session/runner/publish-llm-event.ts | 156 +- packages/core/src/session/sql.ts | 89 +- packages/core/test/database-migration.test.ts | 67 +- packages/core/test/session-projector.test.ts | 2925 +++++++++++++++++++- packages/core/test/session-prompt.test.ts | 272 +- packages/core/test/session-run-coordinator.test.ts ...
<path>/tmp/opencode/session-recovery-v22-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-v22-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-v22-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 { SessionMessage } from "./message"
14: import { continuation } from "./recovery-id"
15: import { SessionSchema } from "./schema"
16: import {
17: MAX_WAKE_ATTEMPTS,
18: SESSION_EXECUTION_LEASE_MS,
19: SessionExecutionTable,
20: ...
<path>/tmp/opencode/session-recovery-v22-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, Deferred, Duration, Effect, Exit, Layer, Option, Schedule, Schema } 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-v22-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: ...
No files found
/tmp/opencode/session-recovery-v22-review/packages/core/src/event.ts /tmp/opencode/session-recovery-v22-review/packages/core/src/session/event.ts
<path>/tmp/opencode/session-recovery-v22-review/packages/core/src/event.ts</path>
<type>file</type>
<content>
1: export * as EventV2 from "./event"
2:
3: import { Cause, Context, Effect, Layer, Option, PubSub, Queue, Schema, Stream } from "effect"
4: import { Event } from "@opencode-ai/schema/event"
5: import type { Data, Definition, Payload } from "@opencode-ai/schema/event"
6: import { and, asc, eq, gt, inArray } from "drizzle-orm"
7: import { Database } from "./database/database"
8: import { EventSequenceTable, EventTable } from "./event/sql"
9: import { Location } from "./location"
10: import { makeGlobalNode } from "./effect/app-node"
11: import { isDeepStrictEqual } from "node:util"
12: import { Durable } from "@opencode-ai/schema/durable-event-manifest"
13:
14: export const ID = Event.ID
15: export type ID = import("@opencode-ai/schema/event").ID
16: export type { Data, Definition, Payload } from "@opencode-ai/schema/event"
17:
18: export type Subscriber<D extends Definition ...
<path>/tmp/opencode/session-recovery-v22-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-v22-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-v22-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-v22-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-v22-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-v22-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-v22-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-v22-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 ...
<path>/tmp/opencode/session-recovery-v22-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: ...
Found 1 matches /tmp/opencode/session-recovery-v22-review/packages/core/schema.json: Line 6: "f14a9b18-8207-487e-a3d3-227e629ba9ad"
<path>/tmp/opencode/session-recovery-v22-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: "f14a9b18-8207-487e-a3d3-227e629ba9ad"
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: ...
<path>/tmp/opencode/session-recovery-v22-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-v22-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-v22-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-v22-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-v22-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-v22-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 { ...
diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts
index 2dabfb2d6f..1fbd6285d0 100644
--- a/packages/core/src/session.ts
+++ b/packages/core/src/session.ts
@@ -348,56 +348,57 @@ const layer = Layer.effect(
result
.get(input.sessionID)
.pipe(Effect.as(events.durable({ aggregateID: input.sessionID, after: input.after }))),
).pipe(Stream.filter((event): event is SessionEvent.DurableEvent => isDurableSessionEvent(event))),
history: Effect.fn("V2Session.history")(function* (input) {
yield* result.get(input.sessionID)
return yield* EventV2.readAggregate(db, {
...input,
aggregateID: input.sessionID,
manifest: SessionDurable,
})
}),
prompt: Effect.fn("V2Session.prompt")((input) =>
Effect.uninterruptible(
Effect.gen(function* () {
yield* result.get(input.sessionID)
const prompt = ...
Found 21 matches
/tmp/opencode/session-recovery-v22-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-v22-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: ...
Found 100 matches (more matches available)
/tmp/opencode/session-recovery-v22-review/packages/core/test/event.test.ts:
Line 81: const it = testEffect(
Line 84: const itWithoutLocation = testEffect(AppNodeBuilder.build(LayerNode.group([Database.node, EventV2.node])))
Line 139: expect(Event.latest([latest, historical]).get("test.out-of-order")).toBe(latest)
Line 140: expect(Event.latest([historical, latest]).get("test.out-of-order")).toBe(latest)
Line 331: Stream.runForEach(() => Deferred.succeed(consuming, undefined).pipe(Effect.andThen(Deferred.await(release)))),
Line 337: yield* Deferred.await(consuming)
Line 343: const slowExit = yield* Fiber.await(slow)
Line 466: ? Deferred.succeed(readStarted, undefined).pipe(Effect.andThen(Deferred.await(continueRead)))
Line 474: yield* Deferred.await(readStarted)
/tmp/opencode/session-recovery-v22-review/packages/core/test/config/agent.test.ts:
Line 19: const it = ...
<path>/tmp/opencode/session-recovery-v22-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 95 matches
/tmp/opencode/session-recovery-v22-review/packages/core/test/session-run-coordinator.test.ts:
Line 31: it.effect("joins a wake-started execution without forcing a successor", () =>
Line 141: it.effect("coalesces wakes received during active execution", () =>
Line 209: it.effect("does nothing when interrupted while idle", () =>
Line 218: it.effect("interrupts active execution and clears its pending wake", () =>
Line 247: it.effect("runs a wake registered during interruption cleanup", () =>
Line 285: it.effect("finalizes interruption when cleanup dies", () =>
Line 332: it.effect("starts a resume registered during interruption cleanup", () =>
Line 368: it.effect("joins concurrent interruption behind one cleanup barrier", () =>
Line 423: it.effect("starts one follow-up when a wake races with failure", () =>
Line 453: it.effect("does not cancel execution when a joined waiter is interrupted", () ...
<path>/tmp/opencode/session-recovery-v22-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3800: input: Schema.Struct({}),
3801: output: Schema.Struct({}),
3802: execute: () =>
3803: Deferred.succeed(executionStarted, undefined).pipe(
3804: Effect.andThen(Effect.never),
3805: Effect.ensuring(
3806: Deferred.succeed(finalizerStarted, undefined).pipe(Effect.andThen(Deferred.await(releaseFinalizer))),
3807: ),
3808: ),
3809: }),
3810: sibling_defect: Tool.make({
3811: description: "Fail while another tool remains active",
3812: input: Schema.Struct({}),
3813: output: Schema.Struct({}),
3814: execute: () => Deferred.await(executionStarted).pipe(Effect.andThen(Effect.die("sibling defect"))),
3815: }),
3816: })
3817: yield* session.prompt({ sessionID, prompt: Prompt.make({ ...
<path>/tmp/opencode/session-recovery-v22-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect } from "bun:test"
2: import { DateTime, Deferred, Effect, Exit, Fiber, Schema } from "effect"
3: import { TestClock } from "effect/testing"
4: import { asc, eq, sql } from "drizzle-orm"
5: import { Database } from "@opencode-ai/core/database/database"
6: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
7: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
8: import { EventV2 } from "@opencode-ai/core/event"
9: import { EventTable } from "@opencode-ai/core/event/sql"
10: import { ModelV2 } from "@opencode-ai/core/model"
11: import { Project } from "@opencode-ai/core/project"
12: import { ProjectTable } from "@opencode-ai/core/project/sql"
13: import { ProviderV2 } from "@opencode-ai/core/provider"
14: import { AbsolutePath } from "@opencode-ai/core/schema"
15: import { SessionV2 } from ...
<path>/tmp/opencode/session-recovery-v22-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-v22-review/packages/core/test/session-run-coordinator.test.ts</path>
<type>file</type>
<content>
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* Deferred.await(secondStarted)
199: yield* coordinator.wake("session")
200: yield* Deferred.succeed(secondGate, undefined)
201: yield* Deferred.await(thirdStarted)
202: yield* Fiber.join(resumed)
203:
204: expect(runs).toBe(3)
205: }),
206: ),
207: )
208:
209: it.effect("does nothing when interrupted while idle", () =>
210: Effect.scoped(
211: Effect.gen(function* () {
212: const coordinator = yield* SessionRunCoordinator.make({ drain: () => Effect.void })
213: ...
<path>/tmp/opencode/session-recovery-v22-review/packages/core/test/session-prompt.test.ts</path>
<type>file</type>
<content>
280:
281: const first = yield* session.prompt(input)
282: const second = yield* session.prompt(input)
283:
284: expect(second.id).not.toBe(first.id)
285: expect(yield* session.messages({ sessionID })).toEqual([])
286: expect(yield* admittedCount).toBe(2)
287: }),
288: )
289:
290: it.effect("returns the original recorded message when the ID is retried", () =>
291: Effect.gen(function* () {
292: yield* setup
293: const session = yield* SessionV2.Service
294: const input = {
295: sessionID,
296: id: messageID,
297: prompt: Prompt.make({ text: "Fix the failing tests" }),
298: resume: false,
299: }
300:
301: const first = yield* session.prompt(input)
302: const retried = yield* session.prompt(input)
303:
304: expect(retried).toEqual(first)
305: ...
<path>/tmp/opencode/session-recovery-v22-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 10 matches /tmp/opencode/session-recovery-v22-review/packages/client/src/generated/types.ts: Line 778: readonly requestExecution?: boolean Line 784: readonly type: "session.next.prompt.execution_requested" Line 860: readonly type: "session.next.step.ended" Line 882: readonly type: "session.next.step.failed" Line 895: readonly type: "session.next.step.interrupted" Line 1272: readonly requestExecution?: boolean Line 1278: readonly type: "session.next.prompt.execution_requested" Line 1354: readonly type: "session.next.step.ended" Line 1376: readonly type: "session.next.step.failed" Line 1389: readonly type: "session.next.step.interrupted"
<path>/tmp/opencode/session-recovery-v22-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: ...
Found 20 matches /tmp/opencode/session-recovery-v22-review/packages/sdk/js/src/v2/gen/types.gen.ts: Line 874: requestExecution?: boolean Line 879: type: "session.next.prompt.execution_requested" Line 941: type: "session.next.step.ended" Line 963: type: "session.next.step.failed" Line 973: type: "session.next.step.interrupted" Line 3430: requestExecution?: boolean Line 3439: type: "session.next.prompt.execution_requested.1" Line 3543: type: "session.next.step.ended.2" Line 3572: type: "session.next.step.failed.2" Line 3589: type: "session.next.step.interrupted.1" Line 4362: requestExecution?: boolean Line 4371: type: "session.next.prompt.execution_requested" Line 4493: type: "session.next.step.ended" Line 4525: type: "session.next.step.failed" Line 4545: type: "session.next.step.interrupted" Line 6464: requestExecution?: boolean Line 6470: type: ...
Found 20 matches
/tmp/opencode/session-recovery-v22-review/packages/sdk/openapi.json:
Line 17899: "requestExecution": {
Line 17919: "enum": ["session.next.prompt.execution_requested"]
Line 18140: "enum": ["session.next.step.ended"]
Line 18217: "enum": ["session.next.step.failed"]
Line 18253: "enum": ["session.next.step.interrupted"]
Line 25709: "requestExecution": {
Line 25740: "enum": ["session.next.prompt.execution_requested.1"]
Line 26087: "enum": ["session.next.step.ended.2"]
Line 26185: "enum": ["session.next.step.failed.2"]
Line 26242: "enum": ["session.next.step.interrupted.1"]
Line 28599: "requestExecution": {
Line 28622: "enum": ["session.next.prompt.execution_requested"]
Line 28975: "enum": ["session.next.step.ended"]
...
Found 57 matches /tmp/opencode/session-recovery-v22-review/packages/core/schema.json: Line 66: "name": "session_execution", Line 78: "name": "session_recovery", Line 941: "table": "session_execution" Line 951: "table": "session_execution" Line 961: "table": "session_execution" Line 971: "table": "session_execution" Line 981: "table": "session_execution" Line 991: "table": "session_execution" Line 1001: "table": "session_execution" Line 1011: "table": "session_execution" Line 1021: "table": "session_execution" Line 1089: "name": "wake_pending", Line 1181: "table": "session_recovery" Line 1191: "table": "session_recovery" Line 1201: "table": "session_recovery" Line 1211: "table": "session_recovery" Line 1221: "table": "session_recovery" Line 1229: "name": "wake_pending", Line 1231: "table": "session_recovery" Line ...
<path>/tmp/opencode/session-recovery-final-v22.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 1 matches /tmp/opencode/session-recovery-final-v22.patch: Line 11387: diff --git a/packages/core/src/session/execution/claim.ts b/packages/core/src/session/execution/claim.ts
<path>/tmp/opencode/session-recovery-final-v22.patch</path>
<type>file</type>
<content>
11387: diff --git a/packages/core/src/session/execution/claim.ts b/packages/core/src/session/execution/claim.ts
11388: new file mode 100644
11389: index 0000000000..5c9cd38f23
11390: --- /dev/null
11391: +++ b/packages/core/src/session/execution/claim.ts
11392: @@ -0,0 +1,112 @@
11393: +export * as SessionExecutionClaim from "./claim"
11394: +
11395: +import { and, eq, gt, notInArray } from "drizzle-orm"
11396: +import { Clock, Effect } from "effect"
11397: +import type { Database } from "../../database/database"
11398: +import type { SessionSchema } from "../schema"
11399: +import { SESSION_EXECUTION_LEASE_MS, SessionExecutionTable } from "../sql"
11400: +
11401: +type DatabaseService = Database.Interface["db"]
11402: +
11403: +export type Claim = {
11404: + readonly sessionID: SessionSchema.ID
11405: + readonly executionID: string
11406: + readonly ownerID: string
(Showing lines 11387-11406 ...
entries 34 blob_mismatches 0
Found 8 matches /tmp/opencode/session-recovery-v22-review/packages/effect-drizzle-sqlite/src/effect-sqlite/session.ts: Line 199: return yield* transaction(tx) Line 217: ) => Effect.Effect<A, SqlError | E, R> = (tx) => this.session.transaction(tx) /tmp/opencode/session-recovery-v22-review/packages/effect-drizzle-sqlite/src/up-migrations/effect-sqlite.ts: Line 94: yield* session.transaction((tx) => /tmp/opencode/session-recovery-v22-review/packages/effect-drizzle-sqlite/src/sqlite-core/effect/db.ts: Line 239: ) => Effect.Effect<A, E | SqlError, R> = (tx, config) => this.session.transaction(tx, config) Line 270: const transaction: Q["transaction"] = (...args: [any]) => primary.transaction(...args) /tmp/opencode/session-recovery-v22-review/packages/effect-drizzle-sqlite/src/sqlite-core/effect/session.ts: Line 476: yield* session.transaction((tx) ...
<path>/tmp/opencode/session-recovery-v22-review/packages/effect-drizzle-sqlite/src/effect-sqlite/session.ts</path>
<type>file</type>
<content>
150: Context.add(services, this.client.transactionService, [connection, id]),
151: ).pipe(
152: Effect.exit,
153: Effect.flatMap((exit) => {
154: const finalize = Exit.isSuccess(exit)
155: ? id === 0
156: ? this.executeTransactionStatement(connection, "commit").pipe(
157: // SQLite keeps the transaction open after deferred constraint commit failures.
158: Effect.catch((error) =>
159: this.executeTransactionStatement(connection, "rollback").pipe(
160: Effect.catch(() => Effect.void),
161: Effect.andThen(Effect.fail(error)),
162: ...
<path>/tmp/opencode/session-recovery-v22-review/packages/effect-drizzle-sqlite/src/effect-sqlite/session.ts</path>
<type>file</type>
<content>
100: )
101: }
102:
103: private execute(query: Query, params: unknown[], method: SQLiteExecuteMethod | "values") {
104: const statement = this.client.unsafe(query.sql, params)
105: if (method === "values") return statement.values
106: if (method === "get") return statement.withoutTransform.pipe(Effect.map((rows) => rows[0]))
107: return statement.withoutTransform
108: }
109:
110: private isInTransaction() {
111: return Effect.serviceOption(this.client.transactionService).pipe(Effect.map((option) => option._tag === "Some"))
112: }
113:
114: private executeTransactionStatement(connection: Effect.Success<SqlClient["reserve"]>, query: string) {
115: return connection.executeUnprepared(query, [], undefined).pipe(Effect.asVoid)
116: }
117:
118: private withTransaction<A, E, R>(effect: Effect.Effect<A, ...
diff --git a/packages/schema/src/session-event.ts b/packages/schema/src/session-event.ts
index 3a559c3e38..2599bbb78f 100644
--- a/packages/schema/src/session-event.ts
+++ b/packages/schema/src/session-event.ts
@@ -67,64 +67,77 @@ export const ModelSwitched = Event.define({
...options,
schema: {
...Base,
messageID: SessionMessage.ID,
model: Model.Ref,
},
})
export type ModelSwitched = typeof ModelSwitched.Type
export const Moved = Event.define({
type: "session.next.moved",
...options,
schema: {
...Base,
location: Location.Ref,
subdirectory: RelativePath.pipe(optional),
},
})
export type Moved = typeof Moved.Type
export const Prompted = Event.define({
type: "session.next.prompted",
...options,
schema: PromptFields,
})
export type Prompted = typeof Prompted.Type
export const PromptAdmitted = Event.define({
type: "session.next.prompt.admitted",
...options,
- schema: PromptFields,
+ schema: {
+ ...
diff --git a/packages/core/test/database-migration.test.ts b/packages/core/test/database-migration.test.ts
index 464ce2695a..9814b75a11 100644
--- a/packages/core/test/database-migration.test.ts
+++ b/packages/core/test/database-migration.test.ts
@@ -1,30 +1,31 @@
import { describe, expect, test } from "bun:test"
import { $ } from "bun"
import { fileURLToPath } from "url"
import path from "path"
import { SqliteClient } from "@effect/sql-sqlite-bun"
import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
-import { Effect, Layer } from "effect"
+import { Effect, Exit, Layer } from "effect"
import { eq, inArray, sql } from "drizzle-orm"
import { DatabaseMigration } from "@opencode-ai/core/database/migration"
import { migrations } from "@opencode-ai/core/database/migration.gen"
import workspaceNameMigration from "@opencode-ai/core/database/migration/20260410174513_workspace-name"
import sessionUsageMigration from ...
diff --git a/packages/core/src/session/input.ts b/packages/core/src/session/input.ts
index 14b613678d..61f6a9a465 100644
--- a/packages/core/src/session/input.ts
+++ b/packages/core/src/session/input.ts
@@ -1,288 +1,648 @@
export * as SessionInput from "./input"
-import { and, asc, eq, isNull, lte } from "drizzle-orm"
-import { DateTime, Effect, Schema } from "effect"
+import { and, asc, desc, eq, isNull, lte } from "drizzle-orm"
+import { DateTime, Effect, Option, Schema } from "effect"
import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
import type { Database } from "../database/database"
-import type { EventV2 } from "../event"
+import { EventV2 } from "../event"
import { SessionEvent } from "./event"
import { SessionMessage } from "./message"
import { Prompt } from "./prompt"
+import { continuation, message } from "./recovery-id"
import { SessionSchema } from "./schema"
-import { SessionInputTable, SessionMessageTable } from "./sql"
+import { ...
5af96209cef23e735f09dbf2f4b2f5b0f51b208ece9e5b325e4e217b46003643 /tmp/opencode/session-recovery-final-v22.patch 10a3698a5e3b009f811a1b27eefab9e1083b560e24efee5f537f003d7edd7384 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server ab9408c81cc9916177641ae8e9f08624fc27b5b9 34
MUST-FIX — interruption can strand a newly committed execution claim.
packages/core/src/session/execution/local.ts:40-73 commits the claim before registering it in activeClaims, while cleanup only wraps the later race at packages/core/src/session/execution/local.ts:90-125. An interrupt during that handoff sees no claim and takes the fallback at packages/core/src/session/execution/local.ts:217-223. If interruption becomes pending while the transaction commits, the drain can exit before line 73. The durable ready claim then has no owner or heartbeat, blocks successors until lease expiry, and may trigger false recovery. Acquisition, registration, and cleanup ownership need one uninterruptible handoff plus a deterministic regression at the commit/registration boundary.
MUST-FIX — corrupt assistant projections are retried forever and can starve valid recovery.
packages/core/src/session/recovery.ts:169-177 converts message decoding errors into defects. The outer handler at packages/core/src/session/recovery.ts:316-323 only logs them, leaving recovery_error unset. Because scans always take the oldest 100 rows (packages/core/src/session/recovery.ts:72-79), use a 30-second lease, and repeat every 10 seconds (packages/core/src/session/recovery.ts:547-552, packages/core/src/session/sql.ts:253), 300 older corrupt rows can continually recycle and prevent later valid claims from entering the batch. Decode failures must quarantine the exact live claim under its execution and owner fence.
ab9408c81cc9916177641ae8e9f08624fc27b5b95af96209cef23e735f09dbf2f4b2f5b0f51b208ece9e5b325e4e217b4600364310a3698a5e3b009f811a1b27eefab9e1083b560e24efee5f537f003d7edd7384Date: Sep 17, 2026 * 🔴 (09:27) User asserted the recovery invariant: “never continues a Step that durably failed before owner loss.” * 🔴 (09:27) User asserted the execution-lease recovery invariant: “never recovers a live execution lease.” * 🔴 (09:27) User asserted the supersession invariant: “never recovers an execution superseded by a newer assistant.” * 🔴 (09:27) User asserted the phase-pa…
Date: Sep 17, 2026 * 🔴 (09:26) User asserted the `SessionExecutionLocal` lease invariant: “never starts a second owner while the lease is live”; the corresponding test expects only 1 claim after a second execution instance attempts to wake the same session. * 🔴 (09:26) User asserted the `SessionExecutionLocal` lease invariant: “never overwrites an expired lease”; the corresponding test verifies…
Date: Sep 17, 2026 * 🟡 (09:25) `packages/server/src/routes.ts` adds `SessionRecovery` to `applicationServices` alongside `SessionV2`; `AppNodeBuilder.build(applicationServices, [[SessionExecution.node, SessionExecutionLocal.node]])` substitutes local session execution. `createRoutes(password?)` uses username `"opencode"` with the supplied password or `ServerAuth.Config.layer`; `createEmbeddedRou…
Date: Sep 17, 2026 * 🟡 (09:24) `packages/core/src/session/sql.ts` defines `MAX_WAKE_ATTEMPTS = Number.MAX_SAFE_INTEGER`; `SessionTable.wake_attempts` defaults to `0` and is constrained by `session_wake_attempts_check` to `0..MAX_WAKE_ATTEMPTS`. * 🟡 (09:24) `SessionInputTable` in `packages/core/src/session/sql.ts` includes `wake_pending` as a non-null boolean defaulting to `false`; index `sessio…
Date: Sep 17, 2026 * 🔴 (09:24) User directed that local execution ownership be replaced with durable multi-node ownership when clustered. * 🟡 (09:24) The session orchestration file imports `SessionExecutionTable` and `SessionRecoveryTable` from `../sql` and documents durable continuation recovery as a separate future slice requiring an explicit retry policy. * 🟡 (09:24) The session orchestrati…
Date: Sep 17, 2026 * 🔴 (09:23) User asserted the session-recovery invariant: “A newer turn supersedes stale incomplete rows; never resume an older assistant projection.” * 🟡 (09:23) The database projection code defines `SessionAlreadyProjected extends Error`, maps session/message data into `SessionTable`, `MessageTable`, and `SessionMessageTable`, and updates token usage through `applyUsage(...…
Date: Sep 17, 2026 * 🟡 (09:23) Inspected `/tmp/opencode/session-recovery-v22-review/packages/core/src/session/run-coordinator.ts` (134 lines), which exports namespace `SessionRunCoordinator` and defines `Coordinator<Key, E>` to serialize execution per key while allowing different keys to run concurrently. * 🟡 (09:23) `Coordinator<Key, E>` exposes four operations: `active` snapshots keys owned b…
Date: Sep 17, 2026 * 🟡 (09:22) Review inspection continued into the `SessionExecution` implementation that imports `LocationServiceMap`, `SessionStore`, and `SESSION_EXECUTION_LEASE_MS`; it documents current-process routing for implicit-local Locations and reserves future remote placement for that layer. * 🟡 (09:22) The inspected implementation computes `leaseDuration` with `Duration.toMillis(S…
Date: Sep 17, 2026 * 🔴 [requested-review] (09:20) User requested a strict READ-ONLY correctness/adversarial review of frozen v22 in `/tmp/opencode/session-recovery-v22-review`; prohibited editing, running tests/builds/services, mutating VCS/index/refs, and using recall. * 🔴 (09:20) User specified immutable review inputs: base commit `ab9408c81cc9916177641ae8e9f08624fc27b5b9`; patch `/tmp/openco…