Dashboard › opencode › Session 03DW7yo7N4xN
03DW7yo7N4xNtmGFjFinal independent CORRECTNESS/adversarial review, read-only only. Immutable base: ab9408c81cc9916177641ae8e9f08624fc27b5b9. Frozen patch: /tmp/opencode/session-recovery-final-v8.patch, expected SHA-256 446412b56ac31b2a60f98765b5f14955a771f6074ebeb9309f603a0ceefad1bc. Exact SEA: /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server, expected SHA-256 f4f0bd585d33bd47114b2d8d54082c2b061380771c808e909223350e62c8c72f. Clean applied inspection tree: /tmp/opencode/session-recovery-v7-check; newly added files may be untracked because patch is not committed. Do not modify files, refs, index, services, artifacts, or databases; do not build or run tests. First verify hashes/base/application; stop on mismatch. Inspect all 33 changed files across Schema/Core/Server/SDK. Explicitly verify closure of v7 blockers: (1) clean EOF after starting an assistant but without step-finish durably interrupts and cannot clear a running assistant without recovery; empty stream remains a no-op; (2) assistant/tool/provider in-memory settlement flags change only after durable publication, including failed Step.Failed and local Tool.Success insertion; (3) execution recovery conversion defects quarantine the exact acquired claim so 100 poison claims cannot starve claim 101; (4) MAX_WAKE_ATTEMPTS reservations change the persisted value, preserve CAS, and rotate fairness rather than same-value duplicate wakes; (5) incremental and full schemas use the same named wake-attempt constraints. Recheck exact lease fencing, cancellation renewal/finalizer cleanup, stream failure after step-finish, hosted/local tool reconciliation, fresh synthetic continue only, reservation/exact promotion/replay wake intent, bounded 100-row scans, malformed-row progress, migration lineage, and deterministic regressions. ...
The required repo-setup skill is unavailable in this session, so I will follow the repository’s checked-in guidance directly. I’ll first verify the immutable inputs and stop immediately if any identity, hash, or application check differs.
No files found
<path>/tmp/opencode/session-recovery-v7-check</path> <type>directory</type> <entries> .dockerignore .editorconfig .git .gitattributes .github/ .gitignore .gitleaksignore .husky/ .lore.md .opencode/ .oxlintrc.json .prettierignore .vscode/ .zed/ AGENTS.md artifacts/ bun.lock bunfig.toml CONTEXT.md CONTRIBUTING.md flake.lock flake.nix github/ infra/ install LICENSE nix/ package.json packages/ patches/ perf/ pnpm-lock.yaml pnpm-workspace.yaml README.ar.md README.bn.md README.br.md README.bs.md README.da.md README.de.md README.es.md README.fr.md README.gr.md README.it.md README.ja.md README.ko.md README.md README.no.md README.pl.md README.ru.md README.th.md README.tr.md README.uk.md README.vi.md README.zh.md README.zht.md screenshot-uk.png script/ sdks/ SECURITY.md specs/ sst-env.d.ts sst.config.ts STATS.md tsconfig.json turbo.json (65 entries) </entries>
446412b56ac31b2a60f98765b5f14955a771f6074ebeb9309f603a0ceefad1bc /tmp/opencode/session-recovery-final-v8.patch f4f0bd585d33bd47114b2d8d54082c2b061380771c808e909223350e62c8c72f /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server ab9408c81cc9916177641ae8e9f08624fc27b5b9 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 ...
<path>/tmp/opencode/session-recovery-final-v8.patch</path>
<type>file</type>
<content>
1: diff --git a/packages/core/schema.json b/packages/core/schema.json
2: index d0eeeebd5c..7e176bd78b 100644
3: --- a/packages/core/schema.json
4: +++ b/packages/core/schema.json
5: @@ -1,8 +1,10 @@
6: {
7: "version": "7",
8: "dialect": "sqlite",
9: - "id": "f14a9b18-8207-487e-a3d3-227e629ba9ad",
10: - "prevIds": ["169a0f0f-d58f-479f-b024-fa1c7b9a09db"],
11: + "id": "682548a8-b895-4f9b-a6af-fdd4b14a5fc9",
12: + "prevIds": [
13: + "d6936168-4f91-42dc-ad64-130da940b0f8"
14: + ],
15: "ddl": [
16: {
17: "name": "workspace",
18: @@ -60,6 +62,10 @@
19: "name": "session_context_epoch",
20: "entityType": "tables"
21: },
22: + {
23: + "name": "session_execution",
24: + "entityType": "tables"
25: + },
26: {
27: "name": "session_input",
28: "entityType": "tables"
29: @@ -68,6 +74,10 @@
30: "name": "session_message",
31: ...
verified 28 patch entries, 33 applied paths, old/new blob identities patch entries=28 path mismatch extra=['packages/core/src/database/migration/20260914170650_session-recovery.ts', 'packages/core/src/session/execution/claim.ts', 'packages/core/src/session/recovery-id.ts', 'packages/core/src/session/recovery.ts', 'packages/core/test/session-execution-local.test.ts'] missing=[]
Found 38 matches /tmp/opencode/session-recovery-final-v8.patch: Line 2: index d0eeeebd5c..7e176bd78b 100644 Line 882: index e6ea4eaa14..77948f1529 100644 Line 892: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts Line 894: index 0000000000..31fd351cb1 Line 957: index ed60fde6c5..a4b613c119 100644 Line 1059: index 2dabfb2d6f..fc5868837a 100644 Line 1091: index f714633295..92a934c44f 100644 Line 1180: index 65b17a86a4..880d42bcf9 100644 Line 1356: index d874c40832..37cfe48115 100644 Line 1542: index 14b613678d..0b38eb2519 100644 Line 2088: index 46118a89fe..e2d7e6b38e 100644 Line 2182: index 792067017d..43a9d26b12 100644 Line 2289: diff --git a/packages/core/src/session/recovery.ts b/packages/core/src/session/recovery.ts Line 2291: index 0000000000..93e5ad4953 Line 2745: index 2f89aff9e3..ab4b833374 100644 Line 2814: index ...
verified 33 patch entries, 33 applied paths, old/new blob identities
/tmp/opencode/session-recovery-v7-check/packages/schema/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/opencode/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/opencode/src/session/llm/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/opencode/src/server/routes/instance/httpapi/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/opencode/test/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/core/src/tool/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/opencode/test/server/AGENTS.md
<path>/tmp/opencode/session-recovery-v7-check/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-v7-check/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-v7-check/CONTRIBUTING.md</path> <type>file</type> <content> 1: # Contributing to OpenCode 2: 3: We want to make it easy for you to contribute to OpenCode. Here are the most common type of changes that get merged: 4: 5: - Bug fixes 6: - Additional LSPs / Formatters 7: - Improvements to LLM performance 8: - Support for new providers 9: - Fixes for environment-specific quirks 10: - Missing standard behavior 11: - Documentation improvements 12: 13: However, any UI or core product feature must go through a design review with the core team before implementation. 14: 15: If you are unsure if a PR would be accepted, feel free to ask a maintainer or look for issues with any of the following labels: 16: 17: - [`help wanted`](https://github.com/anomalyco/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Ahelp-wanted) 18: - [`good first ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/package.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://json.schemastore.org/package.json",
3: "version": "1.18.30",
4: "name": "@opencode-ai/core",
5: "type": "module",
6: "license": "MIT",
7: "private": true,
8: "scripts": {
9: "db": "bun drizzle-kit",
10: "migration": "bun run script/migration.ts",
11: "fix-node-pty": "bun run script/fix-node-pty.ts",
12: "test": "bun test --only-failures",
13: "typecheck": "tsgo --noEmit"
14: },
15: "bin": {
16: "opencode": "./bin/opencode"
17: },
18: "exports": {
19: "./effect/layer-node": "./src/effect/layer-node.ts",
20: "./effect/app-node": "./src/effect/app-node.ts",
21: "./session/runner": "./src/session/runner/index.ts",
22: "./system-context": "./src/system-context/index.ts",
23: "./*": "./src/*.ts"
24: },
25: "imports": {
26: "#sqlite": {
27: "bun": ...
<path>/tmp/opencode/session-recovery-v7-check/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-v7-check/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-v7-check/packages/core/src/database/schema.gen.ts</path>
<type>file</type>
<content>
1: import { Effect } from "effect"
2: import type { DatabaseMigration } from "./migration"
3:
4: export default {
5: up(tx) {
6: return Effect.gen(function* () {
7: yield* tx.run(`
8: CREATE TABLE \`workspace\` (
9: \`id\` text PRIMARY KEY,
10: \`type\` text NOT NULL,
11: \`name\` text DEFAULT '' NOT NULL,
12: \`branch\` text,
13: \`directory\` text,
14: \`extra\` text,
15: \`project_id\` text NOT NULL,
16: \`time_used\` integer NOT NULL,
17: CONSTRAINT \`fk_workspace_project_id_project_id_fk\` FOREIGN KEY (\`project_id\`) REFERENCES \`project\`(\`id\`) ON DELETE CASCADE
18: );
19: `)
20: yield* tx.run(`
21: CREATE TABLE \`data_migration\` (
22: \`name\` text PRIMARY KEY,
23: \`time_completed\` integer NOT ...
<path>/tmp/opencode/session-recovery-v7-check/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-v7-check/packages/core/src/session/execution/claim.ts</path>
<type>file</type>
<content>
1: export * as SessionExecutionClaim from "./claim"
2:
3: import { and, eq, gt } 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<Pick<typeof SessionExecutionTable.$inferInsert, "assistant_message_id" | "phase">>
18:
19: export class Lost extends Error {
20: constructor(readonly claim: Claim) {
21: super(`Session execution claim lost: ${claim.executionID}`)
22: }
23: }
24:
25: const where = (claim: Claim, now: number) ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/src/session/execution/local.ts</path>
<type>file</type>
<content>
1: import { and, asc, eq, isNull } from "drizzle-orm"
2: import { Cause, Clock, Duration, Effect, Exit, Layer, Schedule } from "effect"
3: import { Database } from "../../database/database"
4: import { LocationServiceMap } from "../../location-service-map"
5: import { makeGlobalNode } from "../../effect/app-node"
6: import { SessionRunCoordinator } from "../run-coordinator"
7: import { SessionRunner } from "../runner"
8: import { SessionMessage } from "../message"
9: import { SessionSchema } from "../schema"
10: import { SessionStore } from "../store"
11: import { SessionExecution } from "../execution"
12: import { SessionExecutionClaim } from "./claim"
13: import { SESSION_EXECUTION_LEASE_MS, SessionExecutionTable, SessionRecoveryTable } from "../sql"
14:
15: /** Current-process routing for implicit-local Locations. Future remote placement belongs here. ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/src/session/run-coordinator.ts</path>
<type>file</type>
<content>
1: export * as SessionRunCoordinator from "./run-coordinator"
2:
3: import { Deferred, Effect, Exit, Fiber, FiberSet, Scope } from "effect"
4:
5: /** Serializes execution for each key while allowing different keys to run concurrently. */
6: export interface Coordinator<Key, E> {
7: /** Snapshots keys with an execution owned by this coordinator. */
8: readonly active: Effect.Effect<ReadonlySet<Key>>
9: /** Starts execution while idle or joins the active execution. */
10: readonly run: (key: Key) => Effect.Effect<void, E>
11: /** Registers one coalesced follow-up after newly recorded work. */
12: readonly wake: (key: Key) => Effect.Effect<void>
13: /** Stops active execution, runs cleanup, then starts work that arrived while stopping. */
14: readonly interrupt: (key: Key, cleanup?: Effect.Effect<void>) => Effect.Effect<void>
15: }
16:
17: type ...
<path>/tmp/opencode/session-recovery-v7-check/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 { SessionSchema } from "./schema"
14: import {
15: MAX_WAKE_ATTEMPTS,
16: SESSION_EXECUTION_LEASE_MS,
17: SessionExecutionTable,
18: SessionInputTable,
19: SessionRecoveryTable,
20: SessionMessageTable,
21: SessionTable,
22: } from ...
<path>/tmp/opencode/session-recovery-v7-check/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-v7-check/packages/core/src/session/projector.ts</path> <type>file</type> <content> 461: yield* events.project(SessionEvent.Tool.Input.Ended, (event) => run(db, event)) 462: yield* events.project(SessionEvent.Tool.Called, (event) => run(db, event)) 463: yield* events.project(SessionEvent.Tool.Progress, (event) => run(db, event)) 464: yield* events.project(SessionEvent.Tool.Success, (event) => run(db, event)) 465: yield* events.project(SessionEvent.Tool.Failed, (event) => run(db, event)) 466: yield* events.project(SessionEvent.Reasoning.Started, (event) => run(db, event)) 467: yield* events.project(SessionEvent.Reasoning.Ended, (event) => run(db, event)) 468: // yield* events.project(SessionEvent.Retried, (event) => run(db, event)) 469: yield* events.project(SessionEvent.Compaction.Ended, (event) => run(db, event)) 470: yield* events.project(SessionEvent.RevertEvent.Staged, (event) => 471: db 472: ...
No files found
/tmp/opencode/session-recovery-v7-check/packages/core/src/event.ts
Found 28 matches
/tmp/opencode/session-recovery-v7-check/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-v7-check/packages/core/src/event.ts</path>
<type>file</type>
<content>
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: definition: D,
129: data: Data<D>,
130: options?: PublishOptions,
131: ) => Effect.Effect<Payload<D>>
132: readonly subscribe: <D extends Definition>(definition: D) => Stream.Stream<Payload<D>>
133: readonly all: () => Stream.Stream<Payload>
134: readonly durable: (input: { readonly ...
<path>/tmp/opencode/session-recovery-v7-check/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-v7-check/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 } from "./sql"
14:
15: type DatabaseService = Database.Interface["db"]
16:
17: export { Admitted, Delivery }
18:
19: const decodePrompt = Schema.decodeUnknownSync(Prompt)
20: const encodePrompt = Schema.encodeSync(Prompt)
21: const ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
521: .orderBy(asc(SessionInputTable.admitted_seq))
522: .all()
523: .pipe(Effect.orDie)
524: return yield* publish(
525: db,
526: events,
527: sessionID,
528: rows.map((row) => row.input),
529: commit,
530: )
531: })
532:
533: export const promoteNextQueued = Effect.fn("SessionInput.promoteNextQueued")(function* (
534: db: DatabaseService,
535: events: EventV2.Interface,
536: sessionID: SessionSchema.ID,
537: commit?: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
538: ) {
539: const row = yield* db
540: .select({ input: SessionInputTable })
541: .from(SessionInputTable)
542: .leftJoin(
543: SessionRecoveryTable,
544: eq(SessionRecoveryTable.continuation_message_id, SessionInputTable.id),
545: )
546: .where(
547: and(
548: eq(SessionInputTable.session_id, ...
<path>/tmp/opencode/session-recovery-v7-check/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-v7-check/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-v7-check/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) => Effect.Effect<void>
15: readonly eventCommit: () => Effect.Effect<void>
16: }
17:
18: const safe = (value: number | undefined) => Math.max(0, Number.isFinite(value) ? (value ?? ...
Found 38 matches
/tmp/opencode/session-recovery-v7-check/packages/core/test/session-projector.test.ts:
Line 28: MAX_WAKE_ATTEMPTS,
Line 188: const settled = yield* db
Line 200: settled.map((event) => ({
Line 495: it.effect("quarantines a full batch of poison recovery rows before redriving valid work", () =>
Line 499: const poisonCount = SessionRecovery.BATCH_SIZE + 1
Line 500: const sessions = Array.from({ length: poisonCount + 1 }, (_, index) =>
Line 501: SessionV2.ID.make(`ses_recovery_poison_${index.toString().padStart(3, "0")}`),
Line 526: executionID: `poison-${index.toString().padStart(3, "0")}`,
Line 531: yield* db.run(sql`UPDATE session_input SET prompt = '{}' WHERE time_created < ${poisonCount}`)
Line 549: expect(wakes).toEqual([sessions[poisonCount]])
Line 556: ).toHaveLength(poisonCount)
Line 730: .run(sql`UPDATE session_recovery SET wake_attempts = ...
Found 11 matches
/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts:
Line 688: it.effect("clears durable execution intent when ordinary input is promoted", () =>
Line 694: prompt: Prompt.make({ text: "clear durable wake" }),
Line 1085: it.effect("reuses one durable baseline after the context producer changes", () =>
Line 1119: it.effect("includes the effective default agent system before durable context", () =>
Line 2147: it.effect("keeps provider failure unknown until unresolved tools are durable", () =>
Line 2305: it.effect("restores durable reasoning provider metadata in a second-turn request", () =>
Line 2362: it.effect("replays durable provider-executed tool results inline in a second-turn request", () =>
Line 2695: it.effect("preserves durable queued input for a later wake after interruption", () =>
Line 2738: it.effect("preserves durable steering input for a later resume after interruption", () ...
Found 11 matches
/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts:
Line 212: : SystemContext.empty,
Line 215: const referenceGuidance = Layer.mock(ReferenceGuidance.Service, { load: () => Effect.succeed(SystemContext.empty) })
Line 505: error: { type: "unknown", message: "Provider Step ended without settlement" },
Line 2417: it.effect("starts recorded local tools eagerly and awaits settlement before continuing", () =>
Line 3941: it.effect("durably fails blocked local tools when interrupted while awaiting settlement", () =>
Line 3945: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Interrupt tool settlement" }), resume: false })
Line 3964: { type: "user", text: "Interrupt tool settlement" },
Line 4225: it.effect("durably fails a hosted tool left unresolved at normal provider EOF", () =>
Line 4230: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail ...
<path>/tmp/opencode/session-recovery-v7-check/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-v7-check/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3650: { type: "assistant", finish: "stop" },
3651: ])
3652: }),
3653: )
3654:
3655: it.effect("interrupts runner continuation when a question is dismissed", () =>
3656: Effect.gen(function* () {
3657: yield* setup
3658: const session = yield* SessionV2.Service
3659: const registry = yield* ToolRegistry.Service
3660: const questions = yield* QuestionV2.Service
3661: yield* registry.register({
3662: question: Tool.make({
3663: description: "Ask the user",
3664: input: Schema.Struct({}),
3665: output: Schema.Struct({}),
3666: execute: (_, context) =>
3667: questions.ask({ sessionID: context.sessionID, questions: [] }).pipe(Effect.as({}), Effect.orDie),
3668: }),
3669: })
3670: yield* session.prompt({ sessionID, prompt: Prompt.make({ ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
420: seq: event.seq,
421: type: event.type,
422: data: event.data,
423: })),
424: )
425: })
426:
427: type FragmentKind = "text" | "reasoning" | "tool input"
428:
429: type FragmentFixture = {
430: readonly delta: EventV2.Definition
431: readonly completeEvents: LLMEvent[]
432: readonly partialEvents: LLMEvent[]
433: readonly expectedAssistant: unknown
434: readonly expectedContent: unknown
435: }
436:
437: const fragmentKinds: readonly FragmentKind[] = ["text", "reasoning", "tool input"]
438:
439: const fragmentID = (kind: FragmentKind, suffix: string) => `${kind === "tool input" ? "call" : kind}-${suffix}`
440:
441: const fragmentFixture = (kind: FragmentKind, id: string, chunks: readonly string[]): FragmentFixture => {
442: const text = chunks.join("")
443: switch (kind) {
444: case "text": {
445: ...
Found 2 matches
/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts:
Line 505: error: { type: "unknown", message: "Provider Step ended without settlement" },
Line 4249: error: { type: "unknown", message: "Provider Step ended without settlement" },
Found 32 matches
/tmp/opencode/session-recovery-final-v8.patch:
Line 2309: + MAX_WAKE_ATTEMPTS,
Line 2522: + lte(SessionRecoveryTable.wake_attempts, MAX_WAKE_ATTEMPTS),
Line 2526: + desc(sql`${SessionRecoveryTable.wake_attempts} = ${MAX_WAKE_ATTEMPTS}`),
Line 2570: + lte(SessionRecoveryTable.wake_attempts, MAX_WAKE_ATTEMPTS),
Line 2574: + desc(sql`${SessionRecoveryTable.wake_attempts} = ${MAX_WAKE_ATTEMPTS}`),
Line 2607: + .set({ wake_attempts: row.wakeAttempts === MAX_WAKE_ATTEMPTS ? 1 : row.wakeAttempts + 1 })
Line 2639: + .where(and(ordinaryWhere, gt(SessionTable.wake_attempts, 0), lte(SessionTable.wake_attempts, MAX_WAKE_ATTEMPTS)))
Line 2642: + desc(sql`${SessionTable.wake_attempts} = ${MAX_WAKE_ATTEMPTS}`),
Line 2671: + and(ordinaryWhere, gt(SessionTable.wake_attempts, 0), lte(SessionTable.wake_attempts, MAX_WAKE_ATTEMPTS)),
Line 2675: + desc(sql`${SessionTable.wake_attempts} ...
Found 3 matches
/tmp/opencode/session-recovery-final-v8.patch:
Line 3923: test("rejects a non-empty database without a session table", async () => {
Line 5975: + Effect.succeed(SystemContext.empty),
Line 6890: response = []
<path>/tmp/opencode/session-recovery-final-v8.patch</path>
<type>file</type>
<content>
6840: - active: coordinator.active,
6841: - resume: coordinator.run,
6842: - wake: coordinator.wake,
6843: - interrupt: coordinator.interrupt,
6844: + return yield* SessionExecutionLocal.make({
6845: + run: (claim, force, recoveryInputID) =>
6846: + sessionRunner.run({ ...claim, force, recoveryInputID }).pipe(
6847: + Effect.ensuring(Effect.suspend(() => runnerFinalizer)),
6848: + ),
6849: })
6850: }),
6851: -).pipe(Layer.provide(runnerLayer))
6852: +).pipe(Layer.provide([runnerLayer, LayerNode.compile(Database.node)]))
6853: const it = testEffect(
6854: AppNodeBuilder.build(
6855: LayerNode.group([
6856: @@ -309,6 +311,34 @@ const insertSession = (id: SessionV2.ID) =>
6857: .pipe(Effect.orDie)
6858: })
6859:
6860: +const insertExecution = (id: SessionV2.ID) =>
6861: + Effect.gen(function* () {
6862: + const { db } = ...
<path>/tmp/opencode/session-recovery-v7-check/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-v7-check/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
381: content: [...event.data.content],
382: outputPaths: event.data.outputPaths ? [...event.data.outputPaths] : [],
383: result: event.data.result,
384: }),
385: )
386: }
387: })
388: },
389: "session.next.tool.failed": (event) => {
390: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
391: const match = latestTool(draft, event.data.callID)
392: if (match && (match.state.status === "pending" || match.state.status === "running")) {
393: match.provider = {
394: executed: event.data.provider.executed || match.provider?.executed === true,
395: metadata: match.provider?.metadata,
396: resultMetadata: event.data.provider.metadata,
397: }
398: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/src/session/context-epoch.ts</path>
<type>file</type>
<content>
1: export * as SessionContextEpoch from "./context-epoch"
2:
3: import { eq } from "drizzle-orm"
4: import { DateTime, Effect, Schema } from "effect"
5: import type { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { SystemContext } from "../system-context/index"
8: import { ContextSnapshotDecodeError } from "./error"
9: import { SessionEvent } from "./event"
10: import { SessionHistory } from "./history"
11: import { SessionInput } from "./input"
12: import { SessionMessage } from "./message"
13: import { SessionSchema } from "./schema"
14: import { SessionContextEpochTable } from "./sql"
15: import { SessionExecutionClaim } from "./execution/claim"
16:
17: type DatabaseService = Database.Interface["db"]
18:
19: interface Prepared {
20: readonly baseline: string
21: readonly baselineSeq: number
22: }
23:
24: export ...
<path>/tmp/opencode/session-recovery-v7-check/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-v7-check/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-v7-check/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-v7-check/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-v7-check/packages/opencode/src/server/routes/instance/httpapi/server.ts</path>
<type>file</type>
<content>
261: Installation.node,
262: ShareNext.node,
263: SessionShare.node,
264: InstanceStore.node,
265: httpClient,
266: EventV2.node,
267: ProjectV2.node,
268: ProjectCopy.node,
269: PtyTicket.node,
270: ])
271:
272: export function createRoutes(
273: corsOptions?: CorsOptions,
274: ): Layer.Layer<never, EffectConfig.ConfigError, RouteRequirements> {
275: const locationServiceMapV2 = buildLocationServiceMap()
276:
277: return Layer.mergeAll(
278: rootApiRoutes,
279: eventApiRoutes,
280: ptyConnectApiRoutes,
281: instanceRoutes,
282: serverRoutes,
283: docRoute,
284: uiRoute,
285: ).pipe(
286: Layer.provide([
287: errorLayer,
288: compressionLayer,
289: corsVaryFix,
290: fenceLayer,
291: cors(corsOptions),
292: AppNodeBuilderV1.build(MoveSession.node, ...
<path>/tmp/opencode/session-recovery-v7-check/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 50 matches
/tmp/opencode/session-recovery-v7-check/packages/schema/src/event-manifest.ts:
Line 4: import { Durable } from "./durable-event-manifest"
Line 34: const sessionV1DurableDefinitions = SessionV1.Event.Definitions.filter((definition) => definition.durable !== undefined)
Line 35: const sessionV1LiveDefinitions = SessionV1.Event.Definitions.filter((definition) => definition.durable === undefined)
Line 37: const coreDefinitions = Event.inventory(...sessionV1DurableDefinitions, ...SessionEvent.Definitions)
Line 39: const foundationDefinitions = Event.inventory(
Line 40: ...ModelsDev.Event.Definitions,
Line 41: ...Integration.Event.Definitions,
Line 42: ...Catalog.Event.Definitions,
Line 43: ...coreDefinitions,
Line 46: const featureDefinitions = Event.inventory(
Line 47: ...FileSystem.Event.Definitions,
Line 48: ...Reference.Event.Definitions,
Line 49: ...Permission.Event.Definitions,
Line 50: ...
<path>/tmp/opencode/session-recovery-v7-check/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>
<path>/tmp/opencode/session-recovery-final-v8.patch</path>
<type>file</type>
<content>
8070: index 5694afdd30..0861792411 100644
8071: --- a/packages/schema/test/event-manifest.test.ts
8072: +++ b/packages/schema/test/event-manifest.test.ts
8073: @@ -9,8 +9,8 @@ import { WorkspaceEvent } from "../src/workspace-event"
8074:
8075: describe("public event manifest", () => {
8076: test("owns the complete public event surface", () => {
8077: - expect(EventManifest.ServerDefinitions.length).toBe(55)
8078: - expect(EventManifest.Definitions.length).toBe(85)
8079: + expect(EventManifest.ServerDefinitions.length).toBe(61)
8080: + expect(EventManifest.Definitions.length).toBe(91)
8081: expect(SessionV1.Event.Definitions).toEqual([
8082: SessionV1.Event.Created,
8083: SessionV1.Event.Updated,
8084: @@ -23,8 +23,8 @@ describe("public event manifest", () => {
8085: SessionV1.Event.Diff,
8086: SessionV1.Event.Error,
8087: ])
8088: - ...
<path>/tmp/opencode/session-recovery-final-v8.patch</path>
<type>file</type>
<content>
7950: ])
7951: + expect(yield* db.select().from(SessionExecutionTable).get()).toBeUndefined()
7952: }),
7953: )
7954:
7955: diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts
7956: index fb9d2db656..748e8aa43b 100644
7957: --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts
7958: +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts
7959: @@ -66,6 +66,7 @@ import { SessionProjector } from "@opencode-ai/core/session/projector"
7960: import { SessionV2 } from "@opencode-ai/core/session"
7961: import { SessionExecution } from "@opencode-ai/core/session/execution"
7962: import * as SessionExecutionLocal from "@opencode-ai/core/session/execution/local"
7963: +import { SessionRecovery } from "@opencode-ai/core/session/recovery"
7964: import { lazy } from ...
<path>/tmp/opencode/session-recovery-v7-check/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-v7-check/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: ...
Found 33 matches /tmp/opencode/session-recovery-final-v8.patch: Line 1: diff --git a/packages/core/schema.json b/packages/core/schema.json Line 881: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts Line 892: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts Line 956: diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts Line 1058: diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts Line 1090: diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts Line 1179: diff --git a/packages/core/src/session/context-epoch.ts b/packages/core/src/session/context-epoch.ts Line 1355: diff --git a/packages/core/src/session/execution/local.ts b/packages/core/src/session/execution/local.ts Line 1541: diff --git ...
Found 54 matches /tmp/opencode/session-recovery-v7-check/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 1079: "name": "wake_pending", Line 1171: "table": "session_recovery" Line 1181: "table": "session_recovery" Line 1191: "table": "session_recovery" Line 1201: "table": "session_recovery" Line 1211: "table": "session_recovery" Line 1219: "name": "wake_pending", Line 1221: "table": "session_recovery" Line 1229: "name": "wake_attempts", Line 1231: ...
Found 29 matches
/tmp/opencode/session-recovery-v7-check/packages/sdk/js/src/v2/gen/types.gen.ts:
Line 24: | EventSessionNextPromptExecutionRequested
Line 32: | EventSessionNextStepInterrupted
Line 33: | EventSessionNextStepRecovered
Line 874: requestExecution?: boolean
Line 989: phase: "ready" | "unknown" | "continue"
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 3430: requestExecution?: boolean
Line 3435: export type SyncEventSessionNextPromptExecutionRequested = {
Line 3585: export type ...
Found 16 matches
/tmp/opencode/session-recovery-v7-check/packages/sdk/openapi.json:
Line 17899: "requestExecution": {
Line 17919: "enum": ["session.next.prompt.execution_requested"]
Line 18253: "enum": ["session.next.step.interrupted"]
Line 18289: "enum": ["session.next.step.recovered"]
Line 25709: "requestExecution": {
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 28599: "requestExecution": {
Line 28622: "enum": ["session.next.prompt.execution_requested"]
Line 29132: "enum": ["session.next.step.interrupted"]
Line 29190: "enum": ["session.next.step.recovered"]
Line 34811: "requestExecution": {
Line ...
<path>/tmp/opencode/session-recovery-v7-check/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-v7-check/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
901: .run()
902: yield* events.publish(SessionEvent.Step.Recovered, {
903: sessionID,
904: timestamp: created,
905: executionID: "cross-session-continuation",
906: phase: "ready",
907: })
908: yield* db.run(
909: sql`UPDATE session_input SET session_id = ${other} WHERE id = ${continuation("cross-session-continuation")}`,
910: )
911: const wakes: string[] = []
912:
913: yield* SessionRecovery.recover().pipe(
914: Effect.provideService(
915: SessionExecution.Service,
916: SessionExecution.Service.of({
917: ...recoveryExecution,
918: wake: (id) => Effect.sync(() => wakes.push(id)).pipe(Effect.asVoid),
919: }),
920: ),
921: )
922:
923: expect(wakes).toEqual([])
924: expect(yield* ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1801: baseline: String,
1802: update: (_previous, current) => current,
1803: })
1804: yield* db
1805: .insert(ProjectTable)
1806: .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
1807: .run()
1808: yield* db
1809: .insert(SessionTable)
1810: .values({
1811: id: sessionID,
1812: project_id: Project.ID.global,
1813: slug: "test",
1814: directory: "/project",
1815: title: "test",
1816: version: "test",
1817: })
1818: .run()
1819: yield* db
1820: .insert(SessionExecutionTable)
1821: .values({
1822: id: "stale-advance",
1823: session_id: sessionID,
1824: owner_id: "stale-owner",
1825: phase: "ready",
1826: ...
<path>/tmp/opencode/session-recovery-v7-check/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 } 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 { SessionSchema } from "@opencode-ai/core/session/schema"
11: import { SessionExecutionTable, SessionTable } from "@opencode-ai/core/session/sql"
12: import { testEffect } from "./lib/effect"
13:
14: const sessionID = SessionSchema.ID.make("ses_execution_local")
15: const it = testEffect(Database.layerFromPath(":memory:"))
16: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-run-coordinator.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect } from "bun:test"
2: import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect"
3: import { SessionRunCoordinator } from "@opencode-ai/core/session/run-coordinator"
4: import { testEffect } from "./lib/effect"
5:
6: const it = testEffect(Layer.empty)
7:
8: describe("SessionRunCoordinator", () => {
9: it.effect("joins concurrent resumes for one key", () =>
10: Effect.scoped(
11: Effect.gen(function* () {
12: const gate = yield* Deferred.make<void>()
13: let runs = 0
14: const coordinator = yield* SessionRunCoordinator.make({
15: drain: () => Effect.sync(() => runs++).pipe(Effect.andThen(Deferred.await(gate))),
16: })
17:
18: const first = yield* coordinator.run("session").pipe(Effect.forkChild)
19: yield* Effect.yieldNow
20: const second ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-run-coordinator.test.ts</path>
<type>file</type>
<content>
351:
352: yield* coordinator.wake("session")
353: yield* Deferred.await(firstStarted)
354: const interrupt = yield* coordinator.interrupt("session").pipe(Effect.forkChild)
355: yield* Deferred.await(cleanupStarted)
356: const resumed = yield* coordinator.run("session").pipe(Effect.forkChild)
357: yield* Deferred.succeed(cleanupGate, undefined)
358: yield* Effect.all([Fiber.join(interrupt), Fiber.join(resumed)])
359: yield* Deferred.await(secondStarted)
360:
361: expect(forces).toEqual([false, true])
362: }),
363: ),
364: )
365:
366: it.effect("starts one follow-up when a wake races with failure", () =>
367: Effect.scoped(
368: Effect.gen(function* () {
369: const gate = yield* Deferred.make<void>()
370: const secondStarted = yield* ...
<path>/tmp/opencode/session-recovery-final-v8.patch</path>
<type>file</type>
<content>
2744: diff --git a/packages/core/src/session/run-coordinator.ts b/packages/core/src/session/run-coordinator.ts
2745: index 2f89aff9e3..ab4b833374 100644
2746: --- a/packages/core/src/session/run-coordinator.ts
2747: +++ b/packages/core/src/session/run-coordinator.ts
2748: @@ -10,13 +10,14 @@ export interface Coordinator<Key, E> {
2749: readonly run: (key: Key) => Effect.Effect<void, E>
2750: /** Registers one coalesced follow-up after newly recorded work. */
2751: readonly wake: (key: Key) => Effect.Effect<void>
2752: - /** Stops active execution and waits for its cleanup. */
2753: - readonly interrupt: (key: Key) => Effect.Effect<void>
2754: + /** Stops active execution, runs cleanup, then starts work that arrived while stopping. */
2755: + readonly interrupt: (key: Key, cleanup?: Effect.Effect<void>) => Effect.Effect<void>
2756: }
2757:
2758: type Entry<E> = {
2759: readonly ...
Found 29 matches
/tmp/opencode/session-recovery-v7-check/packages/core/test/session-prompt.test.ts:
Line 140: it.effect("exposes the execution registry", () =>
Line 147: it.effect("delegates execution continuation through SessionExecution", () =>
Line 159: it.effect("delegates process-local interruption through SessionExecution", () =>
Line 171: it.effect("delegates interruption without requiring a recorded Session", () =>
Line 181: it.effect("durably admits one user message before transcript promotion", () =>
Line 203: it.effect("resolves attachment MIME before admission", () =>
Line 224: it.effect("streams durable Session events after an aggregate sequence", () =>
Line 254: it.effect("resumes through a recorded message without appending another prompt", () =>
Line 275: it.effect("records distinct messages when the ID is omitted", () =>
Line 290: it.effect("returns the original recorded message when the ID is retried", () =>
Line ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-prompt.test.ts</path>
<type>file</type>
<content>
250: ).toEqual([[1, "session.next.prompt.admitted"]])
251: }),
252: )
253:
254: it.effect("resumes through a recorded message without appending another prompt", () =>
255: Effect.gen(function* () {
256: yield* setup
257: const session = yield* SessionV2.Service
258: const message = yield* session.prompt({
259: sessionID,
260: prompt: Prompt.make({ text: "Fix the failing tests" }),
261: resume: false,
262: })
263:
264: executionCalls.length = 0
265: wakeCalls.length = 0
266: yield* session.resume(sessionID)
267:
268: expect(yield* session.messages({ sessionID })).toEqual([])
269: expect(yield* admitted(message.id)).not.toHaveProperty("promotedSeq")
270: expect(executionCalls).toEqual([sessionID])
271: expect(wakeCalls).toEqual([])
272: }),
273: ...
<path>/tmp/opencode/session-recovery-v7-check/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 ...
Found 97 matches
/tmp/opencode/session-recovery-v7-check/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 definitions from chronological ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1950: expect(executions).toEqual(["hello"])
1951: expect(yield* session.context(sessionID)).toMatchObject([
1952: { type: "user", text: "Echo this" },
1953: {
1954: type: "assistant",
1955: finish: "tool-calls",
1956: content: [
1957: {
1958: type: "tool",
1959: id: "call-echo",
1960: name: "echo",
1961: state: {
1962: status: "completed",
1963: input: { text: "hello" },
1964: structured: { text: "hello" },
1965: content: [{ type: "text", text: "hello" }],
1966: },
1967: },
1968: ],
1969: },
1970: { type: "assistant", finish: "stop", content: [{ type: "text", id: "text-final", text: "Done" }] },
1971: ])
1972: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
740: const request = requests[0]
741: if (!request) return yield* Effect.die("Redriven provider request not found")
742: expect(userTexts(request)).toEqual(["run after restart"])
743: expect(
744: yield* db.select().from(SessionInputTable).where(eq(SessionInputTable.id, message.id)).get(),
745: ).toMatchObject({
746: wake_pending: false,
747: promoted_seq: expect.any(Number),
748: })
749: }),
750: )
751:
752: it.effect("never dispatches a provider after ownership changes", () =>
753: Effect.gen(function* () {
754: yield* setup
755: const session = yield* SessionV2.Service
756: const { db } = yield* Database.Service
757: const resolving = Deferred.makeUnsafe<void>()
758: const resumeResolution = Deferred.makeUnsafe<void>()
759: modelResolveHook = ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1470: requests.length = 0
1471: executions.length = 0
1472: responses = [
1473: fragmentFixture("text", "text-summary-2", ["## Objective\n- Preserve the updated task"]).completeEvents,
1474: fragmentFixture("text", "text-final-2", ["Continued again"]).completeEvents,
1475: ]
1476: yield* session.prompt({
1477: sessionID,
1478: prompt: Prompt.make({ text: "Newest exact request ".repeat(180) }),
1479: resume: false,
1480: })
1481: yield* session.resume(sessionID)
1482:
1483: expect(requests).toHaveLength(2)
1484: expect(userTexts(requests[0])[0]).toContain(
1485: "<prior-summary>\n## Objective\n- Preserve the task\n</prior-summary>",
1486: )
1487: expect(userTexts(requests[0])[0]).toContain("Recent exact request")
1488: expect((yield* (yield* ...
/tmp/opencode/session-recovery-v7-check/packages/core/schema.json
<path>/tmp/opencode/session-recovery-v7-check/packages/core/schema.json</path>
<type>file</type>
<content>
2520: {
2521: "columns": [
2522: {
2523: "value": "session_id",
2524: "isExpression": false
2525: }
2526: ],
2527: "isUnique": false,
2528: "where": null,
2529: "origin": "manual",
2530: "name": "todo_session_idx",
2531: "entityType": "indexes",
2532: "table": "todo"
2533: },
2534: {
2535: "value": "\"phase\" IN ('ready', 'safe', 'unknown', 'continue')",
2536: "name": "session_execution_phase_check",
2537: "entityType": "checks",
2538: "table": "session_execution"
2539: },
2540: {
2541: "value": "\"wake_attempts\" >= 0 AND \"wake_attempts\" <= 9007199254740991",
2542: "name": "session_recovery_wake_attempts_check",
2543: "entityType": "checks",
2544: "table": "session_recovery"
2545: },
2546: {
2547: "value": ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/src/event.ts</path>
<type>file</type>
<content>
369: function publishEvent<D extends Definition>(definition: D, event: Payload<D>, commit?: PublishOptions["commit"]) {
370: return Effect.gen(function* () {
371: if (!definition?.durable && commit)
372: return yield* Effect.die(
373: new InvalidDurableEventError({
374: type: event.type,
375: message: "Local commit hooks require a durable event",
376: }),
377: )
378: if (definition?.durable) {
379: const committed = yield* commitDurableEvent(definition, event as Payload, undefined, commit)
380: if (committed) {
381: event = {
382: ...event,
383: durable: {
384: aggregateID: committed.aggregateID,
385: seq: committed.seq,
386: version: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/src/session/execution.ts</path>
<type>file</type>
<content>
1: export * as SessionExecution from "./execution"
2:
3: import { Context, Effect, Layer } from "effect"
4: import { LayerNode } from "../effect/layer-node"
5: import { Node } from "../effect/app-node"
6: import { SessionRunner } from "./runner/index"
7: import { SessionSchema } from "./schema"
8:
9: export interface Interface {
10: /** Snapshots active execution owned by this process. */
11: readonly active: Effect.Effect<ReadonlySet<SessionSchema.ID>>
12: /** Starts execution while idle or joins the active execution. */
13: readonly resume: (sessionID: SessionSchema.ID) => Effect.Effect<void, SessionRunner.RunError>
14: /** Registers newly recorded work. Repeated wakeups may coalesce. */
15: readonly wake: (sessionID: SessionSchema.ID) => Effect.Effect<void>
16: /** Interrupt active work owned by this process. Idle interruption is a no-op. ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/schema.json</path>
<type>file</type>
<content>
930: "entityType": "columns",
931: "table": "session_context_epoch"
932: },
933: {
934: "type": "text",
935: "notNull": true,
936: "autoincrement": false,
937: "default": null,
938: "generated": null,
939: "name": "id",
940: "entityType": "columns",
941: "table": "session_execution"
942: },
943: {
944: "type": "text",
945: "notNull": true,
946: "autoincrement": false,
947: "default": null,
948: "generated": null,
949: "name": "session_id",
950: "entityType": "columns",
951: "table": "session_execution"
952: },
953: {
954: "type": "text",
955: "notNull": true,
956: "autoincrement": false,
957: "default": null,
958: "generated": null,
959: "name": "owner_id",
960: "entityType": "columns",
961: "table": ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
940: },
941: ])
942: expect(yield* session.context(sessionID)).toMatchObject([
943: { type: "user", text: "Use application context" },
944: {
945: type: "assistant",
946: content: [
947: {
948: type: "tool",
949: id: "call-application",
950: state: { status: "completed", structured: { answer: "HELLO" } },
951: },
952: ],
953: },
954: ])
955: }),
956: )
957:
958: it.effect("starts a real runner turn after default prompt recording", () =>
959: Effect.gen(function* () {
960: yield* setup
961: const session = yield* SessionV2.Service
962: requests.length = 0
963: responses = undefined
964: streamGate = undefined
965: streamStarted = undefined
966: response = []
967: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/sdk/openapi.json</path>
<type>file</type>
<content>
25680: "pattern": "^evt_"
25681: },
25682: "seq": {
25683: "type": "number"
25684: },
25685: "aggregateID": {
25686: "type": "string"
25687: },
25688: "data": {
25689: "type": "object",
25690: "properties": {
25691: "timestamp": {
25692: "type": "number"
25693: },
25694: "sessionID": {
25695: "type": "string",
25696: "pattern": "^ses"
25697: },
25698: "messageID": {
25699: "type": "string",
25700: "pattern": "^msg_"
25701: },
25702: "prompt": {
25703: "$ref": ...
Found 3 matches
/tmp/opencode/session-recovery-v7-check/packages/sdk/openapi.json:
Line 28545: "SessionNextPromptAdmitted": {
Line 29120: "SessionNextStepInterrupted": {
Line 29178: "SessionNextStepRecovered": {
<path>/tmp/opencode/session-recovery-v7-check/packages/sdk/openapi.json</path>
<type>file</type>
<content>
29115: }
29116: },
29117: "required": ["id", "type", "data"],
29118: "additionalProperties": false
29119: },
29120: "SessionNextStepInterrupted": {
29121: "type": "object",
29122: "properties": {
29123: "id": {
29124: "type": "string",
29125: "pattern": "^evt_"
29126: },
29127: "metadata": {
29128: "type": "object"
29129: },
29130: "type": {
29131: "type": "string",
29132: "enum": ["session.next.step.interrupted"]
29133: },
29134: "durable": {
29135: "type": "object",
29136: "properties": {
29137: "aggregateID": {
29138: "type": "string"
29139: },
29140: "seq": {
29141: "type": "integer"
29142: ...
The frozen inputs and all 33 applied blobs match. I have traced the five v7 blockers through persistence, projection, runner state, ownership fencing, startup wiring, generated contracts, migrations, and their focused regressions. I am doing the final unchanged-state verification now; I will not execute the SEA or any tests.
446412b56ac31b2a60f98765b5f14955a771f6074ebeb9309f603a0ceefad1bc /tmp/opencode/session-recovery-final-v8.patch f4f0bd585d33bd47114b2d8d54082c2b061380771c808e909223350e62c8c72f /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server ab9408c81cc9916177641ae8e9f08624fc27b5b9 final verification: 33 patch entries, 33 applied paths, clean index, old/new blobs exact
<path>/tmp/opencode/session-recovery-final-v8.patch</path>
<type>file</type>
<content>
3500: @@ -200,14 +222,41 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
3501: if (assistantFailed) return
3502: yield* flush()
3503: const assistantMessageID = yield* startAssistant()
3504: + yield* publishEvent(
3505: + SessionEvent.Step.Failed,
3506: + {
3507: + sessionID: input.sessionID,
3508: + timestamp: yield* timestamp,
3509: + assistantMessageID,
3510: + error: { type: "unknown", message },
3511: + },
3512: + {
3513: + commit: input.eventCommit,
3514: + }.commit,
3515: + )
3516: + assistantActive = false
3517: + assistantFailed = true
3518: + })
3519: +
3520: + const interruptAssistant = Effect.fnUntraced(function* (
3521: + message: string,
3522: + commit: () => Effect.Effect<void> = input.eventCommit,
3523: + ) {
3524: + if (assistantFailed) return
3525: ...
None.
None.
The acknowledged instruction interval before external provider/tool dispatch remains irreducible. The implementation does not claim exactly-once dispatch and never replays uncertain work.
ab9408c81cc9916177641ae8e9f08624fc27b5b9; patch SHA-256 446412...ad1bc; SEA SHA-256 f4f0bd...c72f. All 33 paths and old/new blob hashes match; reverse application succeeds; index remains clean.Step.Interrupted; failed publication leaves the claim unknown for recovery. Empty streams create no assistant (runner/llm.ts:437-444, publish-llm-event.ts:241-260, tests session-runner.test.ts:958-999).publish-llm-event.ts:221-239,262-280,362-457). Publication-defect regressions cover Step.Failed and local Tool.Success (session-runner.test.ts:2049-2121).recovery.ts:69-117,143-212). The 100-poison/claim-101 regression is explicit (session-projector.test.ts:1545-1608).MAX_WAKE_ATTEMPTS to 1, retain attempts/wake/error CAS predicates, guarantee retry capacity, and rotate work (recovery.ts:215-333,335-432; regression session-projector.test.ts:1611-1683).session_wake_attempts_check and session_recovery_wake_attempts_check with identical bounds (20260914170650_session-recovery.ts:9-11,39; schema.gen.ts:212,248; schema.json:2541-2549).Date: Sep 16, 2026 * 🟡 (15:47) Search of `/tmp/opencode/session-recovery-v7-check/packages/sdk/openapi.json` found 3 schemas: `SessionNextPromptAdmitted` at line 28545, `SessionNextStepInterrupted` at line 29120, and `SessionNextStepRecovered` at line 29178. * 🟡 (15:49) `/tmp/opencode/session-recovery-v7-check/packages/sdk/openapi.json` lines 29120-29176 define `SessionNextStepInterrupted`: eve…
Date: Sep 16, 2026 * 🟡 (15:47) `/tmp/opencode/session-recovery-v7-check/packages/sdk/openapi.json` lines 25680-25721 show a sync-event schema whose `data` properties are `timestamp`, `sessionID` matching `^ses`, `messageID` matching `^msg_`, `prompt` referencing `#/components/schemas/Prompt`, `delivery` enum `["steer", "queue"]`, and optional boolean `requestExecution`; required data fields are …
Date: Sep 16, 2026 * 🟡 (15:45) `/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts` lines 958-975 define test `"starts a real runner turn after default prompt recording"`: after `setup`, runner state is reset, `session.prompt({ sessionID, prompt: Prompt.make({ text: "Run automatically" }) })` is called with default resume behavior, exactly 1 provider request is exp…
Date: Sep 16, 2026 * 🟡 (15:44) `/tmp/opencode/session-recovery-v7-check/packages/core/schema.json` lines 934-1011 define `session_execution` columns: `id` text NOT NULL; `session_id` text NOT NULL; `owner_id` text NOT NULL; nullable `assistant_message_id` text; `phase` text NOT NULL; `time_created` integer NOT NULL; `expires_at` integer NOT NULL; and nullable `recovery_error` text. None are auto…
Date: Sep 16, 2026 * 🟡 (15:42) `/tmp/opencode/session-recovery-v7-check/packages/core/schema.json` lines 2534-2539 define check `session_execution_phase_check` on table `session_execution` with expression `"phase" IN ('ready', 'safe', 'unknown', 'continue')`. * 🟡 (15:42) `/tmp/opencode/session-recovery-v7-check/packages/core/schema.json` lines 2540-2545 define check `session_recovery_wake_attem…
Date: Sep 16, 2026 * 🔴 (15:40) User specified the transactional invariant `"never commits continuation before unresolved hosted tools are reconciled"` in `packages/core/test/session-runner.test.ts` at line 1975. * 🔴 (15:40) User specified the provider-failure invariant `"never commits a Step end after the provider stream fails"` in `packages/core/test/session-runner.test.ts` at line 2027. * 🔴 …
Date: Sep 16, 2026 * 🟡 (15:39) Patch `/tmp/opencode/session-recovery-final-v8.patch` changes `packages/core/src/session/run-coordinator.ts`: `Coordinator.interrupt` now accepts optional `cleanup?: Effect.Effect<void>` and is documented to stop active execution, run cleanup, then start work that arrived while stopping. * 🟡 (15:39) `packages/core/src/session/run-coordinator.ts` adds `exit?: Exit.…
Date: Sep 16, 2026 * 🔴 (15:38) User specified the lease invariant `"never starts a second owner while the lease is live"`, represented by an `it.effect(...)` test that expects exactly 1 persisted claim. * 🔴 (15:38) User specified the expired-lease invariant `"never overwrites an expired lease"`, represented by an `it.effect(...)` test that expects `secondRuns` to remain `0`. * 🔴 (15:38) User s…
Date: Sep 16, 2026 * 🔴 (15:38) User specified the recovery-boundary invariant `"never recovers a pre-promotion claim when input commits at the recovery boundary"`, represented by an `it.effect(...)` test. * 🔴 (15:38) User specified the scanner-validation invariant `"never promotes a recovery superseded before scanner validation"`, represented by an `it.effect(...)` test. * 🔴 (15:38) User speci…
Date: Sep 16, 2026 * 🔴 (15:38) User specified the recovery invariant `"never recovers a live execution lease"`, represented by an `it.effect(...)` test. * 🔴 (15:38) User specified the recovery invariant `"never recovers an execution superseded by a newer assistant"`, represented by an `it.effect(...)` test. * 🔴 (15:38) User specified the redrive CAS invariant `"never wakes work settled between…
Date: Sep 16, 2026 * 🟡 (15:36) `packages/core/src/session/recovery-id.ts` defines `message(executionID: string, assistantMessageID?: SessionMessage.ID)`, which returns the supplied `assistantMessageID` or deterministically creates `SessionMessage.ID.make(\`msg_recovery_${executionID}\`)`. * 🟡 (15:36) `packages/core/src/session/recovery-id.ts` defines `continuation(executionID: string)`, which c…
Date: Sep 16, 2026 * 🔴 (15:35) User requires server-only documentation work to remain lazy so processes that never serve it, including CLI and scripts, do not pay the cost at module load; the OpenAPI document is cached so requests reuse the same `Uint8Array` rather than re-stringifying the specification. * 🟡 (15:35) `packages/opencode/src/server/routes/instance/httpapi/server.ts` `createRoutes(…
Date: Sep 16, 2026 * 🔴 (15:34) User requires session recovery to “never resume an older assistant projection”; a newer turn supersedes stale incomplete rows. * 🟡 (15:34) `packages/core/src/session/message-updater.ts` uses assistant-message ownership checks via `updateOwnedAssistant(event.data.assistantMessageID, ...)`, preventing events from mutating a superseded assistant projection. * 🟡 (15:…
Date: Sep 16, 2026 * 🔴 (15:33) User requires the runner to “never dispatches a provider after ownership changes.” The regression test is in `packages/core/test/session-runner.test.ts:752`. * 🟡 (15:33) Search of `packages/core/test/session-runner.test.ts` found `"Provider Step ended without settlement"` at lines 505 and 4249, represented as `{ type: "unknown", message: "Provider Step ended witho…
Date: Sep 16, 2026 * 🔴 (15:32) User specified a session-runner requirement/TODO: “Replace local ownership with durable multi-node ownership when clustered,” superseding reliance on local ownership for clustered operation. Located in `packages/core/src/session/runner/index.ts:55`. * 🔴 (15:32) User requires recovery redrive to “never wakes work settled between selection and redrive CAS.” The regr…
Date: Sep 16, 2026 * 🟡 (15:32) `SessionInput.promoteNextQueued` in `packages/core/src/session/input.ts` selects at most one unpromoted `"queue"` input for a session, excluding inputs already referenced by `SessionRecoveryTable.continuation_message_id`; it chooses the earliest by ascending `SessionInputTable.admitted_seq`, returns `false` when none exists, and otherwise publishes the selected inp…
Date: Sep 16, 2026 * 🟡 (15:29) `packages/core/src/session/execution/local.ts` implements current-process routing for implicit-local Locations through `SessionExecutionLocal.make`; it generates one process-level `ownerID` with `crypto.randomUUID()`, tracks claims in `activeClaims: Map<SessionSchema.ID, SessionExecutionClaim.Claim>`, and derives renewal timing from `SESSION_EXECUTION_LEASE_MS`. * …
Date: Sep 16, 2026 * 🟡 (15:28) Updated patch verification reported exactly `verified 33 patch entries, 33 applied paths, old/new blob identities`. * 🟡 (15:28) Repository-specific instruction files found at 7 paths: `/tmp/opencode/session-recovery-v7-check/packages/schema/AGENTS.md`, `/tmp/opencode/session-recovery-v7-check/packages/opencode/AGENTS.md`, `/tmp/opencode/session-recovery-v7-check/p…
Date: Sep 16, 2026 * 🟡 (15:26) Repository inspected at `/tmp/opencode/session-recovery-v7-check`; directory contained exactly 65 entries: `.dockerignore`, `.editorconfig`, `.git`, `.gitattributes`, `.github/`, `.gitignore`, `.gitleaksignore`, `.husky/`, `.lore.md`, `.opencode/`, `.oxlintrc.json`, `.prettierignore`, `.vscode/`, `.zed/`, `AGENTS.md`, `artifacts/`, `bun.lock`, `bunfig.toml`, `CONTE…