Dashboard › opencode › Session 1De9XSCy1L7v
1De9XSCy1L7v9ORb4Perform the final independent SECURITY/pentest review of the immutable OpenCode V2 recovery candidate. Read-only only: do not modify files, refs, index, services, artifacts, or databases; do not build or run tests. Immutable base is ab9408c81cc9916177641ae8e9f08624fc27b5b9. Frozen patch is /tmp/opencode/session-recovery-final-v7.patch with expected SHA-256 9fdee0490a8756207a93adc96c708c0327e4a9b39c58301ce55aed70fe6fa00e. Exact built SEA is /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server with expected SHA-256 f02bc81487350640f989f37bb813043c88f80a8fb59a138bf08f4ff718b1b2ba. A clean applied inspection tree exists at /tmp/opencode/session-recovery-v7-check; note newly added files are untracked there because the patch is not committed, so inspect them too. First verify the exact hashes and that the patch applies to the exact base; if either differs, report the exact blocker and stop. Inspect all 33 changed files. Threat-model malicious/corrupt durable rows, forged cross-Session IDs, stale owners, concurrent nodes, local/provider tool side effects, interrupted streams/fibers, public retry/admission of internal recovery prompts, replay/projector rebuild, starvation/DoS, counter overflow/poisoning, malformed phases, migration constraints, and unbounded work. ...
<skill_content name="jj-guide"> # Skill: jj-guide # jj Guide for AI Agents Jujutsu (jj) is a Git-compatible VCS with mutable commits, automatic snapshotting, no staging area, and first-class conflicts. This skill teaches you how to use it safely from a non-interactive agent environment. If `.jj/` exists in the repo root, this is a jj repo. **Use `jj` commands, not `git`.** In a colocated repo (`.jj/` *and* `.git/`), git tools can read the state, but mutations should go through `jj` so the operation log stays consistent. ## Critical Rules - **NEVER** use interactive flags (`-i`, `--interactive`). TUI prompts hang in agent environments. This applies to `jj split -i`, `jj squash -i`, `jj commit -i`, `jj resolve`, `jj diffedit`, etc. - **ALWAYS** pass `-m "msg"` when describing/committing. Without `-m`, an editor opens and hangs. - **VERIFY** mutations with `jj st` and `jj log` after `squash`, `abandon`, `rebase`, `restore`, `commit`. ...
<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>
9fdee0490a8756207a93adc96c708c0327e4a9b39c58301ce55aed70fe6fa00e /tmp/opencode/session-recovery-final-v7.patch f02bc81487350640f989f37bb813043c88f80a8fb59a138bf08f4ff718b1b2ba /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server
ab9408c81cc9916177641ae8e9f08624fc27b5b9 531 48 packages/core/schema.json 1 0 packages/core/src/database/migration.gen.ts 58 0 packages/core/src/database/migration/20260914170650_session-recovery.ts 48 1 packages/core/src/database/schema.gen.ts 5 4 packages/core/src/session.ts 31 16 packages/core/src/session/compaction.ts 54 34 packages/core/src/session/context-epoch.ts 135 19 packages/core/src/session/execution/local.ts 342 31 packages/core/src/session/input.ts 69 0 packages/core/src/session/message-updater.ts 77 1 packages/core/src/session/projector.ts 413 0 packages/core/src/session/recovery.ts 30 5 packages/core/src/session/run-coordinator.ts 4 0 packages/core/src/session/runner/index.ts 265 104 packages/core/src/session/runner/llm.ts 79 29 packages/core/src/session/runner/publish-llm-event.ts 78 1 packages/core/src/session/sql.ts 66 1 packages/core/test/database-migration.test.ts 283 0 packages/core/test/session-execution-local.test.ts 1866 3 packages/core/test/session-projector.t...
/tmp/opencode/session-recovery-v7-check/packages/stats/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/app/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/app/e2e/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/core/src/tool/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/app/e2e/performance/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/ui/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/effect-drizzle-sqlite/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/llm/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/desktop/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/session-ui/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/schema/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/codemode/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/opencode/AGENTS.md /tmp/opencode/session-recovery-v7-check/packages/opencode/src/server/routes/instance/httpapi/AGENTS.md /tmp/...
<path>/tmp/opencode/session-recovery-v7-check/AGENTS.md</path> <type>file</type> <content> 1: - To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`. 2: - After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly. 3: - Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server. 4: - The default branch in this repo is `dev`. 5: - Local `main` ref may not exist; use `dev` or `origin/dev` for diffs. 6: 7: ## Branch Names 8: 9: Use a short branch name of at most three words, separated by hyphens. Do not use slashes or type prefixes such as `feat/` or `fix/`. 10: 11: Examples: `session-recovery`, `fix-scroll-state`, `regenerate-sdk`. ...
<path>/tmp/opencode/session-recovery-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/AGENTS.md</path>
<type>file</type>
<content>
1: # opencode database guide
2:
3: ## Database
4:
5: - **Schema**: Drizzle schema lives in `packages/core/src/**/*.sql.ts`.
6: - **Migrations**: database migrations live in `packages/core` and are applied by core.
7:
8: ## Development server
9:
10: - Running `bun dev` from `packages/opencode` starts the live interactive TUI. Do not run it as a blocking foreground command when you need to inspect the result.
11: - Start it in `tmux` instead: `tmux new-session -d -s opencode-dev 'bun dev'`.
12: - Capture the current TUI output with: `tmux capture-pane -pt opencode-dev`.
13: - Stop the session explicitly when done: `tmux kill-session -t opencode-dev`.
14:
15: # Module shape
16:
17: Do not use `export namespace Foo { ... }` for module organization. It is not
18: standard ESM, it prevents tree-shaking, and it breaks Node's native TypeScript
19: runner. ...
<path>/tmp/opencode/session-recovery-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/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/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/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/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 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: \`time_created\` integer NOT ...
<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/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/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: ...
Found 35 matches
/tmp/opencode/session-recovery-v7-check/packages/core/test/session-prompt.test.ts:
Line 514: it.effect("never arms durable wake for a conflicting execution retry", () =>
/tmp/opencode/session-recovery-v7-check/packages/core/test/session-projector.test.ts:
Line 441: it.effect("quarantines a malformed recovery row without blocking a valid wake", () =>
Line 466: executionID: "malformed-recovery-row",
Line 475: yield* db.run(sql`UPDATE session_input SET prompt = '{}' WHERE id = ${continuation("malformed-recovery-row")}`)
Line 489: .where(eq(SessionRecoveryTable.execution_id, "malformed-recovery-row"))
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: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
430: expect.objectContaining({
431: execution_id: "lost-wake",
432: continuation_message_id: continuation("lost-wake"),
433: assistant_message_id: null,
434: phase: "unknown",
435: wake_pending: true,
436: }),
437: ])
438: }),
439: )
440:
441: it.effect("quarantines a malformed recovery row without blocking a valid wake", () =>
442: Effect.gen(function* () {
443: const { db } = yield* Database.Service
444: const events = yield* EventV2.Service
445: const valid = SessionV2.ID.make("ses_recovery_row_valid")
446: yield* db
447: .insert(ProjectTable)
448: .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
449: .run()
450: yield* db
451: .insert(SessionTable)
452: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1460: phase: "ready",
1461: })
1462: const continuationID = continuation("quarantined-steer")
1463: yield* db
1464: .update(SessionInputTable)
1465: .set({ delivery: "steer" })
1466: .where(eq(SessionInputTable.id, continuationID))
1467: .run()
1468: yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, recoveryExecution))
1469: expect(yield* db.select().from(SessionRecoveryTable).get()).toMatchObject({
1470: recovery_error: "Invalid recovery continuation input",
1471: })
1472: expect(yield* SessionInput.hasPending(db, sessionID, "steer")).toBe(false)
1473: expect(yield* SessionInput.promoteSteers(db, events, sessionID, Number.MAX_SAFE_INTEGER)).toBe(0)
1474:
1475: const ordinaryID = ...
<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/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 ?? ...
<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/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/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/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 ...
No files found
/tmp/opencode/session-recovery-v7-check/packages/core/src/event.ts
Found 23 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 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: pubsub.durable.get(committed.aggregateID) ?? [],
Line 361: return committed
Line 369: function publishEvent<D extends Definition>(definition: D, event: Payload<D>, commit?: PublishOptions["commit"]) {
Line 371: if ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/src/event.ts</path>
<type>file</type>
<content>
190: return created
191: })
192:
193: yield* Effect.addFinalizer(() =>
194: Effect.gen(function* () {
195: yield* PubSub.shutdown(pubsub.all)
196: yield* Effect.forEach(
197: pubsub.durable.values(),
198: (pubsubs) => Effect.forEach(pubsubs, PubSub.shutdown, { discard: true }),
199: { discard: true },
200: )
201: yield* Effect.forEach(pubsub.typed.values(), PubSub.shutdown, { discard: true })
202: }),
203: )
204:
205: function commitDurableEvent(
206: definition: Definition,
207: event: Payload,
208: input?: {
209: readonly seq: number
210: readonly aggregateID: string
211: readonly ownerID?: string
212: readonly strictOwner?: boolean
213: },
214: commit?: (seq: number) => ...
<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/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/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>
241: EventV2Bridge.node,
242: SessionRunState.node,
243: SessionProcessor.node,
244: SessionCompaction.node,
245: SessionRevert.node,
246: SessionSummary.node,
247: SessionPrompt.node,
248: Instruction.node,
249: LLM.node,
250: LSP.node,
251: MCP.node,
252: McpAuth.node,
253: Command.node,
254: Truncate.node,
255: ToolRegistry.node,
256: Format.node,
257: Project.node,
258: Vcs.node,
259: Workspace.node,
260: Worktree.node,
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 ...
<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 ...
Found 100 matches (more matches available) /tmp/opencode/session-recovery-final-v7.patch: Line 1: diff --git a/packages/core/schema.json b/packages/core/schema.json Line 5: @@ -1,8 +1,10 @@ Line 18: @@ -60,6 +62,10 @@ Line 29: @@ -68,6 +74,10 @@ Line 40: @@ -920,6 +930,86 @@ Line 127: @@ -980,6 +1070,16 @@ Line 144: @@ -1060,6 +1160,96 @@ Line 241: @@ -1350,6 +1540,16 @@ Line 258: @@ -1481,9 +1681,13 @@ Line 274: @@ -1492,9 +1696,13 @@ Line 290: @@ -1503,9 +1711,13 @@ Line 306: @@ -1514,9 +1726,13 @@ Line 322: @@ -1525,9 +1741,13 @@ Line 338: @@ -1536,9 +1756,13 @@ Line 354: @@ -1547,9 +1771,13 @@ Line 370: @@ -1558,9 +1786,13 @@ Line 386: @@ -1569,9 +1801,28 @@ Line 417: @@ -1580,9 +1831,13 @@ Line 433: @@ -1591,9 +1846,58 @@ Line 494: @@ -1602,9 +1906,13 @@ Line 510: @@ -1613,9 +1921,13 @@ Line 526: @@ -1624,133 +1936,192 @@ Line 738: @@ -1872,6 +2243,34 @@ Line 773: @@ -1934,6 +2333,28 @@ Line 802: @@ -2010,6 ...
<path>/tmp/opencode/session-recovery-final-v7.patch</path>
<type>file</type>
<content>
2790: readonly run: (input: {
2791: readonly sessionID: SessionSchema.ID
2792: readonly force: boolean
2793: + readonly executionID: string
2794: + readonly ownerID: string
2795: + readonly recoveryInputID?: SessionMessage.ID
2796: }) => Effect.Effect<void, RunError>
2797: }
2798:
2799: diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts
2800: index 874086a06b..6bdf8af3a3 100644
2801: --- a/packages/core/src/session/runner/llm.ts
2802: +++ b/packages/core/src/session/runner/llm.ts
2803: @@ -8,7 +8,8 @@ import {
2804: isContextOverflowFailure,
2805: type ProviderErrorEvent,
2806: } from "@opencode-ai/llm"
2807: -import { Cause, DateTime, Effect, FiberSet, Layer, Option, Semaphore, Stream } from "effect"
2808: +import { Cause, DateTime, Effect, Exit, Fiber, Layer, Option, Semaphore, Stream } from "effect"
2809: +import { ...
<path>/tmp/opencode/session-recovery-final-v7.patch</path>
<type>file</type>
<content>
3902: + it.effect("never starts a second owner while the lease is live", () =>
3903: + Effect.gen(function* () {
3904: + yield* insertSession
3905: + const firstStarted = Deferred.makeUnsafe<void>()
3906: + const firstScope = yield* Scope.make()
3907: + const first = yield* SessionExecutionLocal.make({
3908: + run: () => Deferred.succeed(firstStarted, undefined).pipe(Effect.andThen(Effect.never)),
3909: + }).pipe(Effect.provideService(Scope.Scope, firstScope))
3910: + let secondRuns = 0
3911: + const second = yield* SessionExecutionLocal.make({
3912: + run: () => Effect.sync(() => secondRuns++).pipe(Effect.asVoid),
3913: + })
3914: +
3915: + yield* first.wake(sessionID)
3916: + yield* Deferred.await(firstStarted)
3917: + yield* second.resume(sessionID)
3918: +
3919: + expect(secondRuns).toBe(0)
3920: + expect(yield* ...
Found 100 matches (more matches available)
/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts:
Line 606: describe("SessionRunnerLLM", () => {
Line 607: it.effect("redrives only the synthetic recovery continuation", () =>
Line 676: it.effect("clears durable execution intent when ordinary input is promoted", () =>
Line 703: it.effect("redrives an ordinary prompt after its advisory wake is lost", () =>
Line 740: it.effect("never dispatches a provider after ownership changes", () =>
Line 766: it.effect("never publishes streamed output after ownership changes", () =>
Line 793: it.effect("never starts a local tool after ownership changes", () =>
Line 833: it.effect("never fails interrupted tools after ownership changes", () =>
Line 890: it.effect("advertises and executes a globally attached application tool", () =>
Line 946: it.effect("starts a real runner turn after default prompt recording", () =>
Line 965: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
600: : fixture.expectedContent,
601: ],
602: },
603: ])
604: })
605:
606: describe("SessionRunnerLLM", () => {
607: it.effect("redrives only the synthetic recovery continuation", () =>
608: Effect.gen(function* () {
609: yield* setup
610: const session = yield* SessionV2.Service
611: const events = yield* EventV2.Service
612: const executionService = yield* SessionExecution.Service
613: const unrelatedID = SessionMessage.ID.make("msg_recovery_unrelated")
614: yield* session.prompt({
615: id: unrelatedID,
616: sessionID,
617: prompt: Prompt.make({ text: "unrelated admit-only" }),
618: delivery: "queue",
619: resume: false,
620: })
621: yield* events.publish(SessionEvent.Step.Recovered, {
622: sessionID,
623: timestamp: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1460: responses = [
1461: fragmentFixture("text", "text-summary-2", ["## Objective\n- Preserve the updated task"]).completeEvents,
1462: fragmentFixture("text", "text-final-2", ["Continued again"]).completeEvents,
1463: ]
1464: yield* session.prompt({
1465: sessionID,
1466: prompt: Prompt.make({ text: "Newest exact request ".repeat(180) }),
1467: resume: false,
1468: })
1469: yield* session.resume(sessionID)
1470:
1471: expect(requests).toHaveLength(2)
1472: expect(userTexts(requests[0])[0]).toContain(
1473: "<prior-summary>\n## Objective\n- Preserve the task\n</prior-summary>",
1474: )
1475: expect(userTexts(requests[0])[0]).toContain("Recent exact request")
1476: expect((yield* (yield* SessionStore.Service).context(sessionID))[0]).toMatchObject({
1477: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1940: { type: "user", text: "Echo this" },
1941: {
1942: type: "assistant",
1943: finish: "tool-calls",
1944: content: [
1945: {
1946: type: "tool",
1947: id: "call-echo",
1948: name: "echo",
1949: state: {
1950: status: "completed",
1951: input: { text: "hello" },
1952: structured: { text: "hello" },
1953: content: [{ type: "text", text: "hello" }],
1954: },
1955: },
1956: ],
1957: },
1958: { type: "assistant", finish: "stop", content: [{ type: "text", id: "text-final", text: "Done" }] },
1959: ])
1960: }),
1961: )
1962:
1963: it.effect("never commits continuation before unresolved hosted tools are reconciled", () =>
1964: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3680: sibling_defect: Tool.make({
3681: description: "Fail while another tool remains active",
3682: input: Schema.Struct({}),
3683: output: Schema.Struct({}),
3684: execute: () => Deferred.await(executionStarted).pipe(Effect.andThen(Effect.die("sibling defect"))),
3685: }),
3686: })
3687: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Run sibling tools" }), resume: false })
3688: const failure = providerUnavailable()
3689: responseStream = Stream.concat(
3690: Stream.fromIterable([
3691: LLMEvent.stepStart({ index: 0 }),
3692: LLMEvent.toolCall({ id: "call-blocked-finalizer", name: "blocked_with_finalizer", input: {} }),
3693: LLMEvent.toolCall({ id: "call-sibling-defect", name: "sibling_defect", input: {} }),
3694: ...
Found 54 matches
/tmp/opencode/session-recovery-v7-check/packages/core/test/session-prompt.test.ts:
Line 19: import { SessionRecovery } from "@opencode-ai/core/session/recovery"
Line 26: const wakeCalls: SessionV2.ID[] = []
Line 40: wake: (sessionID) =>
Line 42: wakeCalls.push(sessionID)
Line 134: wake: (id) => Effect.sync(() => wakeCalls.push(id)).pipe(Effect.asVoid),
Line 152: wakeCalls.length = 0
Line 155: expect(wakeCalls).toEqual([])
Line 265: wakeCalls.length = 0
Line 271: expect(wakeCalls).toEqual([])
Line 310: it.effect("wakes execution when an exact prompt retry recovers a committed message", () =>
Line 321: wakeCalls.length = 0
Line 326: expect(wakeCalls).toEqual([sessionID])
Line 330: it.effect("never wakes execution when retrying an already promoted prompt", () =>
Line 349: wakeCalls.length = 0
Line 358: expect(wakeCalls).toEqual([])
Line 362: ...
No files found
Found 37 matches
/tmp/opencode/session-recovery-v7-check/packages/core/test/session-projector.test.ts:
Line 68: it.effect("atomically reconciles one abandoned execution into one continuation", () =>
Line 222: it.effect("records a recovery continuation without forging an assistant identity", () =>
Line 259: it.effect("recovers each abandoned execution once across repeated startup scans", () =>
Line 305: it.effect("never recovers a live execution lease", () =>
Line 343: it.effect("never recovers an execution superseded by a newer assistant", () =>
Line 390: it.effect("redrives a durable recovery wake after admission", () =>
Line 441: it.effect("quarantines a malformed recovery row without blocking a valid wake", () =>
Line 495: it.effect("quarantines a full batch of poison recovery rows before redriving valid work", () =>
Line 560: it.effect("fairly redrives recovery and ordinary work beyond a full bounded batch", () =>
Line 696: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1140: ),
1141: )
1142:
1143: expect(wakes).toEqual([sessionID])
1144: expect(yield* db.select().from(SessionExecutionTable).all()).toEqual([])
1145: expect(yield* db.select().from(SessionRecoveryTable).all()).toEqual([])
1146: expect(yield* db.select().from(SessionInputTable).where(eq(SessionInputTable.id, inputID)).get()).toMatchObject({
1147: promoted_seq: null,
1148: wake_pending: true,
1149: })
1150: }),
1151: )
1152:
1153: it.effect("rejects a recovered assistant from another Session", () =>
1154: Effect.gen(function* () {
1155: const { db } = yield* Database.Service
1156: const events = yield* EventV2.Service
1157: const other = SessionV2.ID.make("ses_recovery_other")
1158: const assistantID = SessionMessage.ID.make("msg_recovery_other")
1159: yield* ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1810: detachedEvents,
1811: Effect.succeed(context("after")),
1812: sessionID,
1813: claim,
1814: ).pipe(Effect.exit)
1815:
1816: expect(Exit.isFailure(advanced)).toBe(true)
1817: expect(
1818: yield* db
1819: .select({ snapshot: SessionContextEpochTable.snapshot })
1820: .from(SessionContextEpochTable)
1821: .where(eq(SessionContextEpochTable.session_id, sessionID))
1822: .get(),
1823: ).toEqual(before)
1824: expect(
1825: yield* db
1826: .select({ expiresAt: SessionExecutionTable.expires_at })
1827: .from(SessionExecutionTable)
1828: .where(eq(SessionExecutionTable.session_id, sessionID))
1829: .get(),
1830: ).toEqual(leaseBefore)
1831: }),
1832: )
1833:
1834: it.effect("fences a paused ...
Found 3 matches /tmp/opencode/session-recovery-v7-check/packages/server/src/handlers/pty.ts: Line 176: // Outbound frames flow through one queue drained by a single writer so replay, live Line 196: for (const chunk of PtyProtocol.chunks(attachment.replay)) Queue.offerUnsafe(outbox, chunk) Line 216: Effect.ensuring(Effect.sync(() => attachment.detach())),
Found 9 matches
/tmp/opencode/session-recovery-v7-check/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts:
Line 5: import { EventV2 } from "@opencode-ai/core/event"
Line 6: import { EventV2Bridge } from "@/event-v2-bridge"
Line 24: const events = yield* EventV2Bridge.Service
Line 34: const replay = Effect.fn("SyncHttpApi.replay")(function* (ctx: { payload: typeof ReplayPayload.Type }) {
Line 35: const payload: EventV2.SerializedEvent[] = ctx.payload.events.map((event) => ({
Line 43: yield* Effect.logInfo("sync replay requested", {
Line 51: yield* events.replayAll(payload, { ownerID, strictOwner: true })
Line 52: yield* Effect.logInfo("sync replay complete", {
Line 87: return handlers.handle("start", start).handle("replay", replay).handle("steal", steal).handle("history", history)
<path>/tmp/opencode/session-recovery-v7-check/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts</path>
<type>file</type>
<content>
1: import { Workspace } from "@/control-plane/workspace"
2: import * as InstanceState from "@/effect/instance-state"
3: import { Session } from "@/session/session"
4: import { Database } from "@opencode-ai/core/database/database"
5: import { EventV2 } from "@opencode-ai/core/event"
6: import { EventV2Bridge } from "@/event-v2-bridge"
7: import { EventTable } from "@opencode-ai/core/event/sql"
8: import { asc } from "drizzle-orm"
9: import { and } from "drizzle-orm"
10: import { eq } from "drizzle-orm"
11: import { lte } from "drizzle-orm"
12: import { not } from "drizzle-orm"
13: import { or } from "drizzle-orm"
14: import { Effect, Scope } from "effect"
15: import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi"
16: import { InstanceHttpApi } from "../api"
17: import { HistoryPayload, ReplayPayload, SessionPayload } from ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/src/database/schema.gen.ts</path> <type>file</type> <content> 145: CONSTRAINT \`fk_part_message_id_message_id_fk\` FOREIGN KEY (\`message_id\`) REFERENCES \`message\`(\`id\`) ON DELETE CASCADE 146: ); 147: `) 148: yield* tx.run(` 149: CREATE TABLE \`session_context_epoch\` ( 150: \`session_id\` text PRIMARY KEY, 151: \`baseline\` text NOT NULL, 152: \`snapshot\` text NOT NULL, 153: \`baseline_seq\` integer NOT NULL, 154: CONSTRAINT \`fk_session_context_epoch_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE 155: ); 156: `) 157: yield* tx.run(` 158: CREATE TABLE \`session_execution\` ( 159: \`id\` text NOT NULL, 160: \`session_id\` text NOT NULL, 161: \`owner_id\` text NOT NULL, 162: \`assistant_message_id\` text, 163: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/src/database/migration.gen.ts</path>
<type>file</type>
<content>
25: import("./migration/20260511173437_session-metadata"),
26: import("./migration/20260601010001_normalize_storage_paths"),
27: import("./migration/20260601202201_amazing_prowler"),
28: import("./migration/20260602002951_lowly_union_jack"),
29: import("./migration/20260602182828_add_project_directories"),
30: import("./migration/20260603001617_session_message_projection_indexes"),
31: import("./migration/20260603040000_session_message_projection_order"),
32: import("./migration/20260603141458_session_input_inbox"),
33: import("./migration/20260603160727_jittery_ezekiel_stane"),
34: import("./migration/20260604172448_event_sourced_session_input"),
35: import("./migration/20260605003541_add_session_context_snapshot"),
36: import("./migration/20260605042240_add_context_epoch_agent"),
37: ...
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: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/core/schema.json</path>
<type>file</type>
<content>
920: "entityType": "columns",
921: "table": "session_context_epoch"
922: },
923: {
924: "type": "integer",
925: "notNull": true,
926: "autoincrement": false,
927: "default": null,
928: "generated": null,
929: "name": "baseline_seq",
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: ...
Found 15 matches
/tmp/opencode/session-recovery-v7-check/packages/sdk/js/src/v2/gen/types.gen.ts:
Line 24: | EventSessionNextPromptExecutionRequested
Line 874: requestExecution?: boolean
Line 983: type: "session.next.step.recovered"
Line 1650: | SyncEventSessionNextPromptExecutionRequested
Line 2785: | SessionNextPromptExecutionRequested
Line 2915: | SessionNextPromptExecutionRequested
Line 3430: requestExecution?: boolean
Line 3435: export type SyncEventSessionNextPromptExecutionRequested = {
Line 3606: type: "session.next.step.recovered.1"
Line 4362: requestExecution?: boolean
Line 4366: export type SessionNextPromptExecutionRequested = {
Line 4565: type: "session.next.step.recovered"
Line 6464: requestExecution?: boolean
Line 6468: export type EventSessionNextPromptExecutionRequested = {
Line 6583: type: "session.next.step.recovered"
<path>/tmp/opencode/session-recovery-v7-check/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
850: location: LocationRef
851: subdirectory?: string
852: }
853: }
854: | {
855: id: string
856: type: "session.next.prompted"
857: properties: {
858: timestamp: number
859: sessionID: string
860: messageID: string
861: prompt: Prompt
862: delivery: "steer" | "queue"
863: }
864: }
865: | {
866: id: string
867: type: "session.next.prompt.admitted"
868: properties: {
869: timestamp: number
870: sessionID: string
871: messageID: string
872: prompt: Prompt
873: delivery: "steer" | "queue"
874: requestExecution?: boolean
875: }
876: }
877: | {
878: id: string
879: type: "session.next.prompt.execution_requested"
880: ...
Found 16 matches
/tmp/opencode/session-recovery-v7-check/packages/sdk/openapi.json:
Line 17899: "requestExecution": {
Line 18289: "enum": ["session.next.step.recovered"]
Line 18301: "executionID": {
Line 18313: "required": ["timestamp", "sessionID", "executionID", "phase"],
Line 25709: "requestExecution": {
Line 26299: "enum": ["session.next.step.recovered.1"]
Line 26321: "executionID": {
Line 26333: "required": ["timestamp", "sessionID", "executionID", "phase"],
Line 28599: "requestExecution": {
Line 29190: "enum": ["session.next.step.recovered"]
Line 29221: "executionID": {
Line 29233: "required": ["timestamp", "sessionID", "executionID", "phase"],
Line 34811: "requestExecution": {
Line 35201: "enum": ...
<path>/tmp/opencode/session-recovery-v7-check/packages/sdk/openapi.json</path>
<type>file</type>
<content>
18270: "$ref": "#/components/schemas/SessionErrorUnknown"
18271: }
18272: },
18273: "required": ["timestamp", "sessionID", "assistantMessageID", "error"],
18274: "additionalProperties": false
18275: }
18276: },
18277: "required": ["id", "type", "properties"],
18278: "additionalProperties": false
18279: },
18280: {
18281: "type": "object",
18282: "properties": {
18283: "id": {
18284: "type": "string",
18285: "pattern": "^evt_"
18286: },
18287: "type": {
18288: "type": "string",
18289: "enum": ...
<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: ...
<path>/tmp/opencode/session-recovery-v7-check/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
480: schema: { ...Base, messageID: SessionMessage.ID },
481: })
482: }
483:
484: export const DurableDefinitions = Event.inventory(
485: AgentSwitched,
486: ModelSwitched,
487: Moved,
488: Prompted,
489: PromptAdmitted,
490: PromptExecutionRequested,
491: ContextUpdated,
492: Synthetic,
493: Shell.Started,
494: Shell.Ended,
495: Step.Started,
496: Step.Ended,
497: Step.Failed,
498: Step.Interrupted,
499: Step.Recovered,
500: Text.Started,
501: Text.Ended,
502: Tool.Input.Started,
503: Tool.Input.Ended,
504: Tool.Called,
505: Tool.Progress,
506: Tool.Success,
507: Tool.Failed,
508: Reasoning.Started,
509: Reasoning.Ended,
510: Retried,
511: Compaction.Started,
512: Compaction.Ended,
513: RevertEvent.Staged,
514: RevertEvent.Cleared,
515: RevertEvent.Committed,
516: )
517:
518: export const Definitions ...
<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 ...
<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-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-prompt.test.ts</path>
<type>file</type>
<content>
280:
281: const first = yield* session.prompt(input)
282: const second = yield* session.prompt(input)
283:
284: expect(second.id).not.toBe(first.id)
285: expect(yield* session.messages({ sessionID })).toEqual([])
286: expect(yield* admittedCount).toBe(2)
287: }),
288: )
289:
290: it.effect("returns the original recorded message when the ID is retried", () =>
291: Effect.gen(function* () {
292: yield* setup
293: const session = yield* SessionV2.Service
294: const input = {
295: sessionID,
296: id: messageID,
297: prompt: Prompt.make({ text: "Fix the failing tests" }),
298: resume: false,
299: }
300:
301: const first = yield* session.prompt(input)
302: const retried = yield* session.prompt(input)
303:
304: expect(retried).toEqual(first)
305: ...
packages/core/schema.json packages/core/src/database/migration.gen.ts packages/core/src/database/schema.gen.ts packages/core/src/session.ts packages/core/src/session/compaction.ts packages/core/src/session/context-epoch.ts packages/core/src/session/execution/local.ts packages/core/src/session/input.ts packages/core/src/session/message-updater.ts packages/core/src/session/projector.ts packages/core/src/session/run-coordinator.ts packages/core/src/session/runner/index.ts packages/core/src/session/runner/llm.ts packages/core/src/session/runner/publish-llm-event.ts packages/core/src/session/sql.ts packages/core/test/database-migration.test.ts packages/core/test/session-projector.test.ts packages/core/test/session-prompt.test.ts packages/core/test/session-run-coordinator.test.ts packages/core/test/session-runner-recorded.test.ts packages/core/test/session-runner-tool-events.test.ts packages/core/test/session-runner.test.ts packages/opencode/src/server/routes/instance/httpapi/server.ts packa...
The code checks ownership immediately before each external side effect, but the authorization can expire before the side effect starts:
packages/core/src/session/runner/llm.ts:304 renews the claim before creating/pulling the provider stream.packages/core/src/session/runner/llm.ts:326-335 renews the claim, then starts the local tool.packages/core/src/session/recovery.ts:81-97 lets another node take the claim once its lease expires.Concrete race:
toolMaterialization.settle(...).The same race exists between line 304’s renewal and provider dispatch. Later event publication remains fenced, but the external side effect has already happened. The regression at packages/core/test/session-runner.test.ts:793-830 changes ownership before the final renewal; it does not test a pause after a successful renewal.
This violates the required invariant that stale owners never dispatch providers or start local tools.
The claim scanner always selects the oldest 100 rows:
packages/core/src/session/recovery.ts:69-75Only malformed phases receive recovery_error:
packages/core/src/session/recovery.ts:99-117Other deterministic reconciliation defects are only logged:
packages/core/src/session/recovery.ts:143-190For example, a malformed latest assistant projection fails at packages/core/src/session/projector.ts:416-430, while a forged continuation-ID collision fails through SessionInput.projectAdmitted at packages/core/src/session/projector.ts:432-455 and packages/core/src/session/input.ts:195-217. ...