Dashboard › opencode › Session 07wUO3jQ0vdB
07wUO3jQ0vdBIkrVfRead-only adversarial correctness review of frozen v12. Base ab9408c81cc9916177641ae8e9f08624fc27b5b9. Patch /tmp/opencode/session-recovery-final-v12.patch SHA-256 must be a954d8f5b6923b0317200ee52048f0c649e8a8f6b166da03509473ca4b21ab11. SEA /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server SHA-256 must be 1f3de4dd2e48626680e3d52f48ab351ae7dca6d3bb4f996b6b3f1b7ec713daf6. Do not mutate or run tests/builds/services. Verify hashes, clean apply, inspect all 33 paths. Primary closure: assistant-less execution claims now persist assistant_seq. Ordinary acquisition must atomically snapshot the latest assistant projection sequence with claim insertion. Step.Started must atomically update assistant_message_id and assistant_seq using the event commit sequence. Recovery must clear assistant-less ready/unknown/continue claims when latest assistant seq is newer than the boundary, while valid same-execution continue at the current boundary still recovers. Inspect all four new regressions. Verify no gap lets tool/text events mask a newer foreign assistant, no off-by-one/replay issue, and no migration/schema mismatch. Also reassess complete recovery identity quarantine for pending and settled wake, ownership/cancellation, provider/tool uncertainty, replay/reservation/fairness, dependency direction. Accepted residuals: final synchronous ownership check to external call, process-local interrupt routing, bounded clock skew. Evidence: recovery 235/0/695; Core 1172/0/3284; five package typechecks; Schema manifest 2/0; migration/diff checks; exact v12 SEA smoke. Return severity-ordered file:line findings, PASS/CONCERN/MUST-FIX, residuals, end exactly MERGE or DO-NOT-MERGE. Empty invalid; no recall.
<path>/home/byk/Code/opencode</path> <type>directory</type> <entries> .dockerignore .editorconfig .git/ .gitattributes .github/ .gitignore .gitleaksignore .husky/ .lore.md .node-cache/ .opencode/ .oxlintrc.json .prettierignore .turbo/ .vscode/ .zed/ AGENTS.md artifacts/ bun.lock bunfig.toml CONTEXT.md CONTRIBUTING.md flake.lock flake.nix github/ infra/ install LICENSE nix/ node_modules/ package.json packages/ patches/ perf.data perf.data.old 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 tmp/ tsconfig.json turbo.json (71 entries) </entries>
a954d8f5b6923b0317200ee52048f0c649e8a8f6b166da03509473ca4b21ab11 /tmp/opencode/session-recovery-final-v12.patch 1f3de4dd2e48626680e3d52f48ab351ae7dca6d3bb4f996b6b3f1b7ec713daf6 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server ab9408c81cc9916177641ae8e9f08624fc27b5b9 M .lore.md M AGENTS.md M packages/core/schema.json M packages/core/src/database/migration.gen.ts M packages/core/src/database/migration/20260914170650_session-recovery.ts D packages/core/src/database/migration/20260914214636_session-execution.ts D packages/core/src/database/migration/20260914222526_session-execution-phase.ts M packages/core/src/database/schema.gen.ts M packages/core/src/session.ts M packages/core/src/session/compaction.ts M packages/core/src/session/context-epoch.ts M packages/core/src/session/execution/local.ts M packages/core/src/session/input.ts M packages/core/src/session/message-updater.ts M packages/core/src/session/projector.ts M ...
<path>/home/byk/Code/opencode/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 issue`](https://github.com/anomalyco/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) 19: - ...
<path>/home/byk/Code/opencode/package.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://json.schemastore.org/package.json",
3: "name": "opencode",
4: "description": "AI-powered development tool",
5: "private": true,
6: "type": "module",
7: "packageManager": "bun@1.3.14",
8: "scripts": {
9: "dev": "bun run --cwd packages/opencode src/index.ts",
10: "dev:desktop": "bun --cwd packages/desktop dev",
11: "dev:web": "bun --cwd packages/app dev",
12: "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
13: "dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
14: "dev:storybook": "bun --cwd packages/storybook storybook",
15: "lint": "oxlint",
16: "typecheck": "bun turbo typecheck",
17: "upgrade-opentui": "bun run script/upgrade-opentui.ts",
18: "postinstall": "bun run --cwd packages/core fix-node-pty",
19: "prepare": "husky",
20: "random": "echo ...
Found 33 matches /tmp/opencode/session-recovery-final-v12.patch: Line 1: diff --git a/packages/core/schema.json b/packages/core/schema.json Line 888: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts Line 899: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts Line 964: diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts Line 1067: diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts Line 1099: diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts Line 1188: diff --git a/packages/core/src/session/context-epoch.ts b/packages/core/src/session/context-epoch.ts Line 1364: diff --git a/packages/core/src/session/execution/local.ts b/packages/core/src/session/execution/local.ts Line 1573: diff --git ...
drwxr-xr-x 23 byk byk 4096 Sep 16 19:24 /tmp/opencode
(no output)
No files found
(no output)
<path>/tmp/opencode/session-recovery-final-v12.patch</path>
<type>file</type>
<content>
1: diff --git a/packages/core/schema.json b/packages/core/schema.json
2: index d0eeeebd5c..dedf0cc600 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": "3a5ec6d4-a60d-47da-a347-3c72396a13c1",
12: + "prevIds": [
13: + "f14a9b18-8207-487e-a3d3-227e629ba9ad"
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: ...
<path>/tmp/opencode/session-recovery-final-v12.patch</path> <type>file</type> <content> 1001: ); 1002: `) 1003: + yield* tx.run(` 1004: + CREATE TABLE \`session_recovery\` ( 1005: + \`session_id\` text NOT NULL, 1006: + \`execution_id\` text NOT NULL, 1007: + \`assistant_message_id\` text, 1008: + \`continuation_message_id\` text NOT NULL, 1009: + \`phase\` text NOT NULL, 1010: + \`wake_pending\` integer DEFAULT true NOT NULL, 1011: + \`wake_attempts\` integer DEFAULT 0 NOT NULL, 1012: + \`recovery_error\` text, 1013: + \`time_created\` integer NOT NULL, 1014: + CONSTRAINT \`session_recovery_pk\` PRIMARY KEY(\`execution_id\`), 1015: + CONSTRAINT \`fk_session_recovery_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE, 1016: + CONSTRAINT ...
<path>/tmp/opencode/session-recovery-final-v12.patch</path>
<type>file</type>
<content>
2001: + SessionEvent.Prompted,
2002: + {
2003: + sessionID,
2004: + timestamp: DateTime.makeUnsafe(row.time_created),
2005: + messageID: id,
2006: + prompt: decodePrompt(row.prompt),
2007: + delivery: row.delivery,
2008: + },
2009: + commit ? { commit: () => commit(row) } : undefined,
2010: + )
2011: .pipe(
2012: Effect.catchDefect((defect) =>
2013: defect instanceof LifecycleConflict
2014: @@ -247,42 +500,100 @@ export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* (
2015: events: EventV2.Interface,
2016: sessionID: SessionSchema.ID,
2017: cutoff: number,
2018: + commit?: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
2019: ) {
2020: const rows = yield* db
2021: - .select()
2022: + .select({ input: SessionInputTable })
2023: ...
<path>/tmp/opencode/session-recovery-final-v12.patch</path>
<type>file</type>
<content>
3001: }
3002: }
3003: })
3004:
3005: - const awaitToolFibers = (fibers: FiberSet.FiberSet<void, ToolOutputStore.Error>) =>
3006: - Effect.raceFirst(FiberSet.join(fibers), FiberSet.awaitEmpty(fibers))
3007: -
3008: // Match V1: declining a user prompt halts the loop instead of becoming model-facing tool output.
3009: const isUserDeclined = (cause: Cause.Cause<unknown>) =>
3010: cause.reasons.some(
3011: @@ -153,7 +163,10 @@ const layer = Layer.effect(
3012: // Automatic compaction completed; rebuild the request from compacted history.
3013: | { readonly _tag: "ContinueAfterCompaction"; readonly step: number }
3014: // Overflow compaction completed; rebuild once through the path without overflow recovery. ...
<path>/tmp/opencode/session-recovery-final-v12.patch</path>
<type>file</type>
<content>
4001: + sql`SELECT "notnull" AS required FROM pragma_table_info('session_recovery') WHERE name = 'recovery_error'`,
4002: + ),
4003: + ).toEqual({ required: 0 })
4004: + expect(
4005: + Exit.isFailure(
4006: + yield* db
4007: + .run(
4008: + sql`INSERT INTO session_execution (id, session_id, owner_id, phase, time_created, expires_at) VALUES ('bad', 'ses_existing', 'owner', 'invalid', 0, 1)`,
4009: + )
4010: + .pipe(Effect.exit),
4011: + ),
4012: + ).toBe(true)
4013: + }),
4014: + )
4015: + })
4016: +
4017: test("rejects a non-empty database without a session table", async () => {
4018: await expect(
4019: run(
4020: diff --git a/packages/core/test/session-execution-local.test.ts b/packages/core/test/session-execution-local.test.ts
4021: new file mode ...
<path>/tmp/opencode/session-recovery-final-v12.patch</path>
<type>file</type>
<content>
5001: + ...recoveryExecution,
5002: + wake: (id) =>
5003: + Effect.sync(() => firstWakes.push(id)).pipe(
5004: + Effect.andThen(Effect.die("simulated wake failure")),
5005: + ),
5006: + })
5007: +
5008: + yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, firstExecution))
5009: +
5010: + expect(firstWakes).toHaveLength(SessionRecovery.BATCH_SIZE * 2)
5011: + expect(firstWakes).not.toContain(recoverySessions[failedCount])
5012: + expect(firstWakes).not.toContain(ordinarySessions[failedCount])
5013: +
5014: + const freshRecoverySessions = Array.from({ length: SessionRecovery.BATCH_SIZE }, (_, index) =>
5015: + SessionV2.ID.make(`ses_recovery_fresh_competitor_${index.toString().padStart(3, "0")}`),
5016: + )
5017: + const freshOrdinarySessions = Array.from({ length: ...
<path>/tmp/opencode/session-recovery-final-v12.patch</path>
<type>file</type>
<content>
6001: + )
6002: + .run()
6003: + yield* db.run(sql`PRAGMA ignore_check_constraints = ON`)
6004: + yield* db.run(
6005: + sql`INSERT INTO session_execution (id, session_id, owner_id, phase, time_created, expires_at) VALUES ('malformed', ${sessionID}, 'dead', 'invalid', 0, 0)`,
6006: + )
6007: + yield* db
6008: + .insert(SessionExecutionTable)
6009: + .values({
6010: + id: "valid",
6011: + session_id: validSession,
6012: + owner_id: "dead",
6013: + phase: "ready",
6014: + time_created: 1,
6015: + expires_at: 0,
6016: + })
6017: + .run()
6018: +
6019: + yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, recoveryExecution))
6020: +
6021: + expect(
6022: + yield* db
6023: + .select({ error: ...
<path>/tmp/opencode/session-recovery-final-v12.patch</path>
<type>file</type>
<content>
7001: + ),
7002: + )
7003: +
7004: it.effect("starts a resume registered during interruption cleanup", () =>
7005: Effect.scoped(
7006: Effect.gen(function* () {
7007: diff --git a/packages/core/test/session-runner-recorded.test.ts b/packages/core/test/session-runner-recorded.test.ts
7008: index d45cc8c734..6f1b69f9a5 100644
7009: --- a/packages/core/test/session-runner-recorded.test.ts
7010: +++ b/packages/core/test/session-runner-recorded.test.ts
7011: @@ -19,7 +19,7 @@ import { Snapshot } from "@opencode-ai/core/snapshot"
7012: import { Prompt } from "@opencode-ai/core/session/prompt"
7013: import { SessionProjector } from "@opencode-ai/core/session/projector"
7014: import { SessionExecution } from "@opencode-ai/core/session/execution"
7015: -import { SessionRunCoordinator } from "@opencode-ai/core/session/run-coordinator"
7016: +import { SessionExecutionLocal } from ...
<path>/tmp/opencode/session-recovery-final-v12.patch</path>
<type>file</type>
<content>
8001: + resume: false,
8002: + })
8003: + responses = [
8004: + [
8005: + LLMEvent.stepStart({ index: 0 }),
8006: + LLMEvent.toolCall({ id: "call-recovery", name: "echo", input: { text: "settled" } }),
8007: + LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
8008: + LLMEvent.finish({ reason: "tool-calls" }),
8009: + ],
8010: + ]
8011: + modelResolveHook = Effect.gen(function* () {
8012: + const execution = yield* db
8013: + .select({ phase: SessionExecutionTable.phase })
8014: + .from(SessionExecutionTable)
8015: + .where(eq(SessionExecutionTable.session_id, sessionID))
8016: + .get()
8017: + .pipe(Effect.orDie)
8018: + if (execution?.phase === "continue") return yield* Effect.die("crash after settled Step.Ended")
8019: + })
8020: +
8021: + yield* ...
<path>/tmp/opencode/session-recovery-final-v12.patch</path>
<type>file</type>
<content>
9001: + },
9002: {
9003: "$ref": "#/components/schemas/SessionNextContextUpdated"
9004: },
9005: @@ -23701,6 +23834,12 @@
9006: {
9007: "$ref": "#/components/schemas/SessionNextStepFailed"
9008: },
9009: + {
9010: + "$ref": "#/components/schemas/SessionNextStepInterrupted"
9011: + },
9012: + {
9013: + "$ref": "#/components/schemas/SessionNextStepRecovered"
9014: + },
9015: {
9016: "$ref": "#/components/schemas/SessionNextTextStarted"
9017: },
9018: @@ -24060,6 +24199,9 @@
9019: {
9020: "$ref": "#/components/schemas/SessionNextPromptAdmitted"
9021: },
9022: + {
9023: + "$ref": "#/components/schemas/SessionNextPromptExecution_requested"
9024: + },
9025: ...
<path>/tmp/opencode/session-recovery-final-v12.patch</path>
<type>file</type>
<content>
9924: @@ -0,0 +1,6 @@
9925: +import { SessionMessage } from "./message"
9926: +
9927: +export const message = (executionID: string, assistantMessageID?: SessionMessage.ID) =>
9928: + assistantMessageID ?? SessionMessage.ID.make(`msg_recovery_${executionID}`)
9929: +
9930: +export const continuation = (executionID: string) => SessionMessage.ID.make(`msg_recovery_continue_${executionID}`)
(End of file - total 9930 lines)
</content>
Found 18 matches
/tmp/opencode/v12-review/packages/core/test/effect/layer-node/node-build.test.ts:
Line 74: let acquisitions = 0
Line 78: acquisitions++
Line 98: expect(acquisitions).toBe(1)
/tmp/opencode/v12-review/packages/core/test/effect/layer-node/layer-node.test.ts:
Line 104: let acquisitions = 0
Line 109: acquisitions++
Line 118: expect(acquisitions).toBe(0)
Line 122: let acquisitions = 0
Line 126: acquisitions++
Line 143: expect(acquisitions).toBe(0)
/tmp/opencode/v12-review/packages/core/test/session-projector.test.ts:
Line 390: it.effect(`never recovers an assistant-less ${phase} execution superseded by a newer assistant`, () =>
Line 415: id: `assistant-less-${phase}`,
Line 419: assistant_seq: -1,
Line 434: it.effect("recovers assistant-less continuation at its current assistant boundary", () =>
Line 459: id: "assistant-less-current",
Line ...
Found 19 matches
/tmp/opencode/v12-review/packages/core/test/session-projector.test.ts:
Line 100: yield* events.publish(SessionEvent.Step.Started, {
Line 419: assistant_seq: -1,
Line 463: assistant_seq: 0,
Line 1271: yield* events.publish(SessionEvent.Step.Started, {
Line 1416: yield* events.publish(SessionEvent.Step.Started, {
Line 1430: yield* events.publish(SessionEvent.Step.Started, {
Line 1531: yield* events.publish(SessionEvent.Step.Started, {
Line 1545: yield* events.publish(SessionEvent.Step.Started, {
Line 1600: yield* events.publish(SessionEvent.Step.Started, {
Line 2665: .publish(SessionEvent.Step.Started, {
/tmp/opencode/v12-review/packages/core/test/session-tool-progress.test.ts:
Line 51: yield* service.publish(SessionEvent.Step.Started, {
/tmp/opencode/v12-review/packages/core/test/session-runner.test.ts:
Line 852: yield* ...
Found 3 matches
/tmp/opencode/v12-review/packages/core/test/session-runner.test.ts:
Line 26: import { Snapshot } from "@opencode-ai/core/snapshot"
Line 1060: it.effect("fails gracefully when a stored context snapshot cannot be decoded", () =>
Line 1070: .set({ snapshot: { invalid: { value: "bad" } } })
<path>/tmp/opencode/v12-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: SessionRecoveryTable,
23: ...
No files found
/tmp/opencode/v12-review/packages/core/src/event.ts
Found 34 matches
/tmp/opencode/v12-review/packages/core/src/event.ts:
Line 122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
Line 123: readonly commit?: (seq: number) => Effect.Effect<void>
Line 127: readonly publish: <D extends Definition>(
Line 140: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
Line 144: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
Line 205: function commitDurableEvent(
Line 214: commit?: (seq: number) => Effect.Effect<void>,
Line 239: const committed = yield* db
Line 316: const committed = {
Line 321: yield* projector(committed)
Line 323: if (commit) yield* commit(seq)
Line 354: if (committed) {
Line 356: ...
<path>/tmp/opencode/v12-review/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 aggregateID: ...
<path>/tmp/opencode/v12-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, Duration, Effect, Exit, Layer, Schedule } from "effect"
3: import { Database } from "../../database/database"
4: import { LocationServiceMap } from "../../location-service-map"
5: import { makeGlobalNode } from "../../effect/app-node"
6: import { SessionRunCoordinator } from "../run-coordinator"
7: import { SessionRunner } from "../runner"
8: import { SessionMessage } from "../message"
9: import { SessionSchema } from "../schema"
10: import { SessionStore } from "../store"
11: import { SessionExecution } from "../execution"
12: import { SessionExecutionClaim } from "./claim"
13: import {
14: SESSION_EXECUTION_LEASE_MS,
15: SessionExecutionTable,
16: SessionMessageTable,
17: SessionRecoveryTable,
18: } from "../sql"
19:
20: /** Current-process routing for implicit-local Locations. ...
Found 3 matches
/tmp/opencode/v12-review/packages/core/src/database/database.ts:
Line 29: yield* db.run("PRAGMA busy_timeout = 5000")
/tmp/opencode/v12-review/packages/core/src/database/sqlite.node.ts:
Line 36: readonly timeout?: number
Line 153: timeout: config.timeout,
<path>/tmp/opencode/v12-review/packages/core/src/database/database.ts</path>
<type>file</type>
<content>
1: export * as Database from "./database"
2:
3: import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
4: import { layer as sqliteLayer } from "#sqlite"
5: import { Context, Effect, Layer } from "effect"
6: import { Global } from "../global"
7: import { Flag } from "../flag/flag"
8: import { isAbsolute, join } from "path"
9: import { DatabaseMigration } from "./migration"
10: import { InstallationChannel } from "../installation/version"
11: import { makeGlobalNode } from "../effect/app-node"
12:
13: const makeDatabase = EffectDrizzleSqlite.makeWithDefaults()
14: type DatabaseShape = Effect.Success<typeof makeDatabase>
15:
16: export interface Interface {
17: db: DatabaseShape
18: }
19:
20: export class Service extends Context.Service<Service, Interface>()("@opencode/v2/storage/Database") {}
21:
22: const layer = Layer.effect(
23: Service,
24: ...
<path>/tmp/opencode/v12-review/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": "./src/database/sqlite.bun.ts",
28: ...
<path>/tmp/opencode/v12-review/packages/server/package.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://json.schemastore.org/package.json",
3: "name": "@opencode-ai/server",
4: "version": "1.18.30",
5: "private": true,
6: "type": "module",
7: "license": "MIT",
8: "exports": {
9: "./*": "./src/*.ts"
10: },
11: "scripts": {
12: "typecheck": "tsgo --noEmit"
13: },
14: "dependencies": {
15: "@opencode-ai/core": "workspace:*",
16: "@opencode-ai/protocol": "workspace:*",
17: "drizzle-orm": "catalog:",
18: "effect": "catalog:"
19: },
20: "devDependencies": {
21: "@tsconfig/bun": "catalog:",
22: "@types/bun": "catalog:",
23: "@typescript/native-preview": "catalog:"
24: }
25: }
(End of file - total 25 lines)
</content>
<path>/tmp/opencode/v12-review/packages/core/src/session/recovery.ts</path>
<type>file</type>
<content>
80: (claim) =>
81: Effect.gen(function* () {
82: const now = yield* Clock.currentTimeMillis
83: const owned = yield* db
84: .update(SessionExecutionTable)
85: .set({ owner_id: ownerID, expires_at: now + SESSION_EXECUTION_LEASE_MS })
86: .where(
87: and(
88: eq(SessionExecutionTable.id, claim.id),
89: eq(SessionExecutionTable.session_id, claim.session_id),
90: eq(SessionExecutionTable.owner_id, claim.owner_id),
91: lte(SessionExecutionTable.expires_at, now),
92: isNull(SessionExecutionTable.recovery_error),
93: ),
94: )
95: .returning({ id: SessionExecutionTable.id })
96: .get()
97: .pipe(Effect.orDie)
98: if (!owned) return
99: const recoveryClaim = { sessionID: claim.session_id, ...
<path>/tmp/opencode/v12-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 NULL,
19: ...
<path>/tmp/opencode/v12-review/packages/core/src/session/sql.ts</path>
<type>file</type>
<content>
145: ],
146: )
147:
148: export const SessionInputTable = sqliteTable(
149: "session_input",
150: {
151: id: text().$type<SessionMessage.ID>().primaryKey(),
152: session_id: text()
153: .$type<SessionSchema.ID>()
154: .notNull()
155: .references(() => SessionTable.id, { onDelete: "cascade" }),
156: prompt: text({ mode: "json" }).notNull().$type<Prompt>(),
157: delivery: text().$type<SessionInput.Delivery>().notNull(),
158: admitted_seq: integer().notNull(),
159: promoted_seq: integer(),
160: wake_pending: integer({ mode: "boolean" }).notNull().default(false),
161: time_created: integer()
162: .notNull()
163: .$default(() => Date.now()),
164: },
165: (table) => [
166: index("session_input_session_pending_delivery_seq_idx").on(
167: table.session_id,
168: table.promoted_seq,
169: ...
<path>/tmp/opencode/v12-review/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
180: _tag: "ContinueAfterOverflowCompaction",
181: step,
182: })
183:
184: const loadSystemContext = (agent: AgentV2.Selection) =>
185: Effect.all([systemContext.load(), skillGuidance.load(agent), referenceGuidance.load()], {
186: concurrency: "unbounded",
187: }).pipe(Effect.map(SystemContext.combine))
188:
189: const runTurnAttempt = Effect.fn("SessionRunner.runTurn")(function* (
190: sessionID: SessionSchema.ID,
191: executionID: string,
192: promotion: Promotion | undefined,
193: step: number,
194: ownerID: string,
195: recoverOverflow?: typeof compaction.compactAfterOverflow,
196: ) {
197: const session = yield* getSession(sessionID)
198: if (session.location.directory !== location.directory || session.location.workspaceID !== location.workspaceID)
199: return ...
Found 17 matches
/tmp/opencode/v12-review/packages/core/src/session/message-updater.ts:
Line 96: const updateOwnedAssistant = (messageID: SessionMessage.ID, recipe: (draft: DraftAssistant) => void) =>
Line 212: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
Line 226: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
Line 233: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
Line 271: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
Line 300: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
Line 307: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
Line 313: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
Line 319: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
Line 335: return ...
<path>/tmp/opencode/v12-review/packages/core/src/session/message-updater.ts</path> <type>file</type> <content> 80: type DraftAssistant = WritableDraft<SessionMessage.Assistant> 81: type DraftTool = WritableDraft<SessionMessage.AssistantTool> 82: type DraftText = WritableDraft<SessionMessage.AssistantText> 83: type DraftReasoning = WritableDraft<SessionMessage.AssistantReasoning> 84: 85: const latestTool = (assistant: DraftAssistant | undefined, callID?: string) => 86: assistant?.content.findLast( 87: (item): item is DraftTool => item.type === "tool" && (callID === undefined || item.id === callID), 88: ) 89: 90: const latestText = (assistant: DraftAssistant | undefined, textID: string) => 91: assistant?.content.findLast((item): item is DraftText => item.type === "text" && item.id === textID) 92: 93: const latestReasoning = (assistant: DraftAssistant | undefined, reasoningID: string) => 94: assistant?.content.findLast((item): item is DraftReasoning ...
<path>/tmp/opencode/v12-review/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
50:
51: const settledOutput = (value: ToolOutput | undefined, result: ToolResultValue): SettledOutput => {
52: if (result.type === "error") return { error: { type: "unknown", message: message(result.value) } }
53: const settled = value ?? ToolOutput.fromResultValue(result)
54: if (!settled) throw new Error(`Unsupported tool result: ${message(result)}`)
55: return { structured: record(settled.structured), content: settled.content }
56: }
57:
58: /** Persist one provider turn without executing tools or starting a continuation turn. */
59: export const createLLMEventPublisher = (events: EventV2.Interface, input: Input) => {
60: const publishEvent = <D extends EventV2.Definition>(
61: definition: D,
62: data: EventV2.Data<D>,
63: commit: (seq: number) => Effect.Effect<void> = input.eventCommit,
64: ) =>
65: definition.durable !== ...
<path>/tmp/opencode/v12-review/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
280: const stored = yield* find(db, input.id)
281: if (stored) {
282: if (!matchesProjection(stored, input) || stored.promotedSeq !== input.promotedSeq)
283: return yield* Effect.die(new LifecycleConflict({ id: input.id }))
284: yield* settleRecoveryWake(db, input)
285: return
286: }
287:
288: yield* db
289: .insert(SessionInputTable)
290: .values({
291: id: input.id,
292: session_id: input.sessionID,
293: prompt: encodePrompt(input.prompt),
294: delivery: input.delivery,
295: admitted_seq: input.promotedSeq,
296: promoted_seq: input.promotedSeq,
297: time_created: DateTime.toEpochMillis(input.timeCreated),
298: })
299: .run()
300: .pipe(Effect.orDie)
301: yield* settleRecoveryWake(db, input)
302: })
303:
304: const settleRecoveryWake = (
305: db: DatabaseService,
306: input: { readonly id: ...
<path>/tmp/opencode/v12-review/packages/core/src/session/execution/claim.ts</path>
<type>file</type>
<content>
24: }
25: }
26:
27: const where = (claim: Claim, now: number) =>
28: and(
29: eq(SessionExecutionTable.session_id, claim.sessionID),
30: eq(SessionExecutionTable.id, claim.executionID),
31: eq(SessionExecutionTable.owner_id, claim.ownerID),
32: gt(SessionExecutionTable.expires_at, now),
33: )
34:
35: /**
36: * Lease timestamps use node wall clocks. The lease interval must remain larger than the deployment's
37: * maximum clock skew plus one renewal interval. Execution IDs fence a node immediately after takeover;
38: * an expired claim never renews itself.
39: */
40: export const update = Effect.fn("SessionExecutionClaim.update")(function* (
41: db: DatabaseService,
42: claim: Claim,
43: values: Values = {},
44: ) {
45: const now = yield* Clock.currentTimeMillis
46: const updated = yield* db
47: .update(SessionExecutionTable)
48: ...
SessionExecutionClaim.update and clear require an unexpired lease (packages/core/src/session/execution/claim.ts:27-33,40-64). Recovery follows that rule for malformed phases (packages/core/src/session/recovery.ts:102-117) but bypasses it in two branches:
expires_at > now guard or affected-row check (packages/core/src/session/recovery.ts:120-134).packages/core/src/session/recovery.ts:213-249).A recovery owner can pause past its lease, resume before another scanner completes takeover, and set recovery_error. The next scanner’s takeover then fails because takeover requires recovery_error IS NULL (packages/core/src/session/recovery.ts:83-97). The expired owner has therefore mutated durable state and blocked reevaluation after losing authority.
Guard both updates with the current time and require one affected row. Treat zero rows as ownership loss. Add deterministic cancellation and projection-failure regressions that expire the first recovery owner before its quarantine write and allow a successor to take over.
The three parameterized supersession cases seed assistant_seq: -1 directly (packages/core/test/session-projector.test.ts:390-433), while the current-boundary continuation case seeds assistant_seq: 0 directly (packages/core/test/session-projector.test.ts:434-483).
They verify recovery’s comparison but remain green if either production producer breaks:
Step.Started no longer writes its commit sequence into the claim.Add focused tests for both producers, preferably with mutations that remove each assignment. ...