Dashboard › opencode › Session 05DyJBAjbM2N
05DyJBAjbM2NW2LznPerform a read-only independent security/pentest review of frozen v10. Base commit ab9408c81cc9916177641ae8e9f08624fc27b5b9. Patch /tmp/opencode/session-recovery-final-v10.patch expected SHA-256 07b95915924ccf5dcfdf7ea5eb45d6cf972a6058664518935154dff9934222b2. SEA /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server expected SHA-256 1117176c4cfbf77527216b4161b54f8d3918ca7f7415747117c24f94d8e0b090. Do not mutate source, refs, index, services, or run builds/tests. Verify hashes, clean apply, inspect all 33 changed files. Threat-model corrupt durable rows/events, same- and cross-Session identity collisions, stale owners, replay, cancellation, provider/tool side effects, queue/steer leakage, wake amplification/starvation, and fail-open paths. Primary closure target from v9: recovery projection error handling now compares complete stored identity (Session, execution, assistant, deterministic continuation, phase); mismatches quarantine exact execution+owner, and zero-row quarantine proves deletion/ownership transfer. Verify new regression quarantines an execution claim blocked by a conflicting recovery identity reaches this path. Reassess cancellation cancelling fail-closed behavior, live/expired lease invariants, startAssistant ordering, provider failure and hosted-tool reconciliation, pre-promotion admission race, continuation reservation/public retry rejection, bounded fairness, schema/generated contracts, dependency direction, and authorization/isolation. The final synchronous claim-check-to-physical-external-call race is accepted residual; multi-node interrupt routing remains process-local by documented contract. Exact-source evidence: 231 recovery tests, full Core 1168 pass after isolated timeout and 10 repeated stress passes, five typechecks, manifest/migration/diff gates, exact SEA health/schema/recovery/idempotence smoke. ...
I’ll first verify the immutable inputs and application scope. I’ll stop if any hash, base, clean-apply, or 33-file claim fails. The required repo-setup skill is not available in this session, so I’ll follow the repository instructions directly and keep the review strictly read-only.
No files found
<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>
/home/byk/Code/opencode/AGENTS.md /home/byk/Code/opencode/packages/codemode/AGENTS.md /home/byk/Code/opencode/packages/session-ui/AGENTS.md /home/byk/Code/opencode/packages/schema/AGENTS.md /home/byk/Code/opencode/packages/desktop/AGENTS.md /home/byk/Code/opencode/packages/opencode/AGENTS.md /home/byk/Code/opencode/packages/opencode/test/AGENTS.md /home/byk/Code/opencode/packages/opencode/test/server/AGENTS.md /home/byk/Code/opencode/packages/ui/AGENTS.md /home/byk/Code/opencode/packages/stats/AGENTS.md /home/byk/Code/opencode/packages/llm/AGENTS.md /home/byk/Code/opencode/packages/core/src/tool/AGENTS.md /home/byk/Code/opencode/packages/effect-drizzle-sqlite/AGENTS.md /home/byk/Code/opencode/packages/opencode/src/session/llm/AGENTS.md /home/byk/Code/opencode/packages/app/AGENTS.md /home/byk/Code/opencode/packages/opencode/src/server/routes/instance/httpapi/AGENTS.md /home/byk/Code/opencode/packages/app/e2e/AGENTS.md /home/byk/Code/opencode/packages/app/e2e/performance/AGENTS.md
07b95915924ccf5dcfdf7ea5eb45d6cf972a6058664518935154dff9934222b2 /tmp/opencode/session-recovery-final-v10.patch 1117176c4cfbf77527216b4161b54f8d3918ca7f7415747117c24f94d8e0b090 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server commit 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 packages/core/src/session/recovery.ts M ...
NotFound: FileSystem.access (/tmp/opencode/session-recovery-v10-review)
33 create mode 100644 packages/core/src/database/migration/20260914170650_session-recovery.ts create mode 100644 packages/core/src/session/recovery.ts create mode 100644 packages/core/test/session-execution-local.test.ts create mode 100644 packages/core/src/session/execution/claim.ts create mode 100644 packages/core/src/session/recovery-id.ts drwxr-xr-x 18 byk byk 4096 Sep 16 18:10 /tmp/opencode
(no output)
<path>/tmp/opencode/session-recovery-v10-review/AGENTS.md</path> <type>file</type> <content> 1: - To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`. 2: - After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly. 3: - Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server. 4: - The default branch in this repo is `dev`. 5: - Local `main` ref may not exist; use `dev` or `origin/dev` for diffs. 6: 7: ## Branch Names 8: 9: Use a short branch name of at most three words, separated by hyphens. Do not use slashes or type prefixes such as `feat/` or `fix/`. 10: 11: Examples: `session-recovery`, `fix-scroll-state`, `regenerate-sdk`. ...
<path>/tmp/opencode/session-recovery-v10-review/packages/schema/AGENTS.md</path> <type>file</type> <content> 1: # Schema Package Guide 2: 3: `@opencode-ai/schema` owns browser-safe wire and storage contracts shared by protocol, server, core, and generated SDKs. Keep runtime behavior, service layers, side effects, and host-local implementation details in the domain package that owns them. 4: 5: ## Package Boundary 6: 7: - Preserve the dependency direction: `@opencode-ai/schema <- @opencode-ai/protocol <- @opencode-ai/server`. 8: - Schema values should be serializable contract definitions, not service implementations or runtime registries. 9: - A domain may keep a minimal public wire contract here when SDK generation needs it, but do not move the broader runtime model into Schema just because an event is public. `plugin.added` is the current example: Schema may own the minimum browser-safe event payload, while plugin runtime behavior stays outside Schema. ...
<path>/tmp/opencode/session-recovery-v10-review/packages/opencode/AGENTS.md</path>
<type>file</type>
<content>
1: # opencode database guide
2:
3: ## Database
4:
5: - **Schema**: Drizzle schema lives in `packages/core/src/**/*.sql.ts`.
6: - **Migrations**: database migrations live in `packages/core` and are applied by core.
7:
8: ## Development server
9:
10: - Running `bun dev` from `packages/opencode` starts the live interactive TUI. Do not run it as a blocking foreground command when you need to inspect the result.
11: - Start it in `tmux` instead: `tmux new-session -d -s opencode-dev 'bun dev'`.
12: - Capture the current TUI output with: `tmux capture-pane -pt opencode-dev`.
13: - Stop the session explicitly when done: `tmux kill-session -t opencode-dev`.
14:
15: # Module shape
16:
17: Do not use `export namespace Foo { ... }` for module organization. It is not
18: standard ESM, it prevents tree-shaking, and it breaks Node's native TypeScript
19: runner. ...
<path>/tmp/opencode/session-recovery-v10-review/packages/opencode/src/server/routes/instance/httpapi/AGENTS.md</path>
<type>file</type>
<content>
1: # HttpApi Route Patterns
2:
3: Use `HttpApiBuilder.group(...)` for normal HTTP endpoints, including streaming HTTP responses such as server-sent events. Handlers should yield stable services once while building the handler layer, then close over those services in endpoint implementations.
4:
5: ```ts
6: export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", (handlers) =>
7: Effect.gen(function* () {
8: const session = yield* Session.Service
9:
10: return handlers.handle("list", () => session.list())
11: }),
12: )
13: ```
14:
15: For SSE endpoints, stay in `HttpApiBuilder.group(...)` and return `HttpServerResponse.stream(...)` from the handler. Annotate the endpoint success schema with `HttpApiSchema.asText({ contentType: "text/event-stream" })` so OpenAPI documents the stream content type. ...
<path>/tmp/opencode/session-recovery-v10-review/CONTRIBUTING.md</path> <type>file</type> <content> 1: # Contributing to OpenCode 2: 3: We want to make it easy for you to contribute to OpenCode. Here are the most common type of changes that get merged: 4: 5: - Bug fixes 6: - Additional LSPs / Formatters 7: - Improvements to LLM performance 8: - Support for new providers 9: - Fixes for environment-specific quirks 10: - Missing standard behavior 11: - Documentation improvements 12: 13: However, any UI or core product feature must go through a design review with the core team before implementation. 14: 15: If you are unsure if a PR would be accepted, feel free to ask a maintainer or look for issues with any of the following labels: 16: 17: - [`help wanted`](https://github.com/anomalyco/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Ahelp-wanted) 18: - [`good first ...
<path>/tmp/opencode/session-recovery-v10-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": ...
<path>/tmp/opencode/session-recovery-v10-review/packages/schema/package.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://json.schemastore.org/package.json",
3: "name": "@opencode-ai/schema",
4: "private": true,
5: "type": "module",
6: "license": "MIT",
7: "exports": {
8: ".": "./src/index.ts",
9: "./*": "./src/*.ts"
10: },
11: "scripts": {
12: "typecheck": "tsgo --noEmit"
13: },
14: "dependencies": {
15: "effect": "catalog:"
16: },
17: "devDependencies": {
18: "@tsconfig/bun": "catalog:",
19: "@types/bun": "catalog:",
20: "@typescript/native-preview": "catalog:"
21: }
22: }
(End of file - total 22 lines)
</content>
<path>/tmp/opencode/session-recovery-v10-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/session-recovery-v10-review/packages/opencode/package.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://json.schemastore.org/package.json",
3: "version": "1.18.30",
4: "name": "opencode",
5: "type": "module",
6: "license": "MIT",
7: "private": true,
8: "scripts": {
9: "typecheck": "tsgo --noEmit",
10: "test": "bun test --timeout 30000 --only-failures",
11: "test:httpapi": "bun run script/httpapi-exercise.ts --mode coverage --fail-on-missing --fail-on-skip && bun run script/httpapi-exercise.ts --mode auth --fail-on-missing --fail-on-skip && bun run script/httpapi-exercise.ts --mode effect --fail-on-missing --fail-on-skip",
12: "bench:test": "bun run script/bench-test-suite.ts",
13: "profile:test": "bun run script/profile-test-files.ts",
14: "build": "bun run script/build.ts",
15: "build:server": "node script/build-server.mjs",
16: "dev": "bun run ./src/index.ts",
17: "dev:temporary": "bun run ...
9802 /tmp/opencode/session-recovery-final-v10.patch packages/core/schema.json | 579 +++++ packages/core/src/database/migration.gen.ts | 1 .../migration/20260914170650_session-recovery.ts | 58 + packages/core/src/database/schema.gen.ts | 49 packages/core/src/session.ts | 9 packages/core/src/session/compaction.ts | 47 packages/core/src/session/context-epoch.ts | 88 - packages/core/src/session/execution/local.ts | 155 + packages/core/src/session/input.ts | 373 +++ packages/core/src/session/message-updater.ts | 69 + packages/core/src/session/projector.ts | 89 + packages/core/src/session/recovery.ts | 496 +++++ packages/core/src/session/run-coordinator.ts | 35 packages/core/src/session/runner/index.ts | 4 packages/core/src/session/runner/llm.ts | 376 ++- ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/execution/claim.ts</path>
<type>file</type>
<content>
1: export * as SessionExecutionClaim from "./claim"
2:
3: import { and, eq, gt } 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-v10-review/packages/core/src/session/recovery-id.ts</path>
<type>file</type>
<content>
1: import { SessionMessage } from "./message"
2:
3: export const message = (executionID: string, assistantMessageID?: SessionMessage.ID) =>
4: assistantMessageID ?? SessionMessage.ID.make(`msg_recovery_${executionID}`)
5:
6: export const continuation = (executionID: string) => SessionMessage.ID.make(`msg_recovery_continue_${executionID}`)
(End of file - total 6 lines)
</content>
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/sql.ts</path>
<type>file</type>
<content>
1: import { sql } from "drizzle-orm"
2: import { check, sqliteTable, text, integer, index, primaryKey, real, uniqueIndex } from "drizzle-orm/sqlite-core"
3: import * as DatabasePath from "../database/path"
4: import { ProjectTable } from "../project/sql"
5: import type { SessionMessage } from "./message"
6: import type { Prompt } from "./prompt"
7: import type { SessionInput } from "./input"
8: import type { Snapshot } from "../snapshot"
9: import { PermissionV1 } from "../v1/permission"
10: import { ProjectV2 } from "../project"
11: import type { SessionSchema } from "./schema"
12: import type { MessageID, PartID, SessionV1 } from "../v1/session"
13: import { WorkspaceV2 } from "../workspace"
14: import { Timestamps } from "../database/schema.sql"
15: import type { SystemContext } from "../system-context/index"
16: import { AgentV2 } from "../agent"
17: import type { ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/database/migration/20260914170650_session-recovery.ts</path>
<type>file</type>
<content>
1: import { Effect } from "effect"
2: import type { DatabaseMigration } from "../migration"
3:
4: export default {
5: id: "20260914170650_session-recovery",
6: up(tx) {
7: return Effect.gen(function* () {
8: yield* tx.run(`ALTER TABLE \`session_input\` ADD \`wake_pending\` integer DEFAULT false NOT NULL;`)
9: yield* tx.run(
10: `ALTER TABLE \`session\` ADD \`wake_attempts\` integer DEFAULT 0 NOT NULL CONSTRAINT \`session_wake_attempts_check\` CHECK(\`wake_attempts\` >= 0 AND \`wake_attempts\` <= 9007199254740991);`,
11: )
12: yield* tx.run(`
13: CREATE TABLE \`session_execution\` (
14: \`id\` text NOT NULL,
15: \`session_id\` text NOT NULL,
16: \`owner_id\` text NOT NULL,
17: \`assistant_message_id\` text,
18: \`phase\` text NOT ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/recovery.ts</path>
<type>file</type>
<content>
1: export * as SessionRecovery from "./recovery"
2:
3: import { and, asc, desc, eq, exists, gt, isNull, lte, or, sql } from "drizzle-orm"
4: import { Clock, DateTime, Effect, Layer, Option, Schedule, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { makeGlobalNode } from "../effect/app-node"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionExecution } from "./execution"
10: import { SessionProjector } from "./projector"
11: import { SessionExecutionClaim } from "./execution/claim"
12: import { SessionInput } from "./input"
13: import { continuation } from "./recovery-id"
14: import { SessionSchema } from "./schema"
15: import {
16: MAX_WAKE_ATTEMPTS,
17: SESSION_EXECUTION_LEASE_MS,
18: SessionExecutionTable,
19: SessionInputTable,
20: SessionRecoveryTable,
21: ...
Found 1 matches
/tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts:
Line 1251: it.effect("quarantines an execution claim blocked by a conflicting recovery identity", () =>
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1180: model,
1181: })
1182:
1183: const recovered = yield* events
1184: .publish(SessionEvent.Step.Recovered, {
1185: sessionID,
1186: timestamp: DateTime.makeUnsafe(1),
1187: executionID: "cross-session",
1188: assistantMessageID: assistantID,
1189: phase: "unknown",
1190: })
1191: .pipe(Effect.exit)
1192:
1193: expect(Exit.isFailure(recovered)).toBe(true)
1194: expect(yield* db.select().from(SessionRecoveryTable).all()).toEqual([])
1195: expect(yield* db.select().from(SessionInputTable).all()).toEqual([])
1196: }),
1197: )
1198:
1199: it.effect("rejects a recovery execution identity owned by another Session", () =>
1200: Effect.gen(function* () {
1201: const { db } = yield* Database.Service
1202: const events = yield* ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
1: export * as SessionProjector from "./projector"
2:
3: import { and, desc, eq, gt, or, sql } from "drizzle-orm"
4: import { DateTime, Effect, Layer, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { makeGlobalNode } from "../effect/app-node"
8: import { SessionEvent } from "./event"
9: import { SessionV1 } from "../v1/session"
10: import { WorkspaceTable } from "../control-plane/workspace.sql"
11: import { SessionMessage } from "./message"
12: import { SessionMessageUpdater } from "./message-updater"
13: import { SessionInput } from "./input"
14: import { Prompt } from "./prompt"
15: import { continuation } from "./recovery-id"
16: import { WorkspaceV2 } from "../workspace"
17: import {
18: MessageTable,
19: PartTable,
20: SessionInputTable,
21: SessionMessageTable,
22: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
1: export * as SessionInput from "./input"
2:
3: import { and, asc, desc, eq, isNull, lte } from "drizzle-orm"
4: import { DateTime, Effect, Option, Schema } from "effect"
5: import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
6: import type { Database } from "../database/database"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionMessage } from "./message"
10: import { Prompt } from "./prompt"
11: import { continuation, message } from "./recovery-id"
12: import { SessionSchema } from "./schema"
13: import { SessionInputTable, SessionMessageTable, SessionRecoveryTable } 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-v10-review/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-v10-review/packages/core/src/session/run-coordinator.ts</path>
<type>file</type>
<content>
1: export * as SessionRunCoordinator from "./run-coordinator"
2:
3: import { Deferred, Effect, Exit, Fiber, FiberSet, Scope } from "effect"
4:
5: /** Serializes execution for each key while allowing different keys to run concurrently. */
6: export interface Coordinator<Key, E> {
7: /** Snapshots keys with an execution owned by this coordinator. */
8: readonly active: Effect.Effect<ReadonlySet<Key>>
9: /** Starts execution while idle or joins the active execution. */
10: readonly run: (key: Key) => Effect.Effect<void, E>
11: /** Registers one coalesced follow-up after newly recorded work. */
12: readonly wake: (key: Key) => Effect.Effect<void>
13: /** Stops active execution, runs cleanup, then starts work that arrived while stopping. */
14: readonly interrupt: (key: Key, cleanup?: Effect.Effect<void>) => Effect.Effect<void>
15: }
16:
17: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/runner/index.ts</path>
<type>file</type>
<content>
1: export * as SessionRunner from "./index"
2:
3: import type { LLMError } from "@opencode-ai/llm"
4: import { Context, Effect } from "effect"
5: import { SessionSchema } from "../schema"
6: import type { SessionMessage } from "../message"
7: import type { ContextSnapshotDecodeError, MessageDecodeError } from "../error"
8: import { SessionRunnerModel } from "./model"
9: import type { SystemContext } from "../../system-context/index"
10: import type { ToolOutputStore } from "../../tool-output-store"
11:
12: export type RunError =
13: | LLMError
14: | SessionRunnerModel.Error
15: | MessageDecodeError
16: | ContextSnapshotDecodeError
17: | SystemContext.InitializationBlocked
18: | ToolOutputStore.Error
19:
20: /** Runs one local continuation from already-recorded Session history. ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
1: import {
2: LLM,
3: LLMClient,
4: LLMError,
5: LLMEvent,
6: Message,
7: SystemPart,
8: isContextOverflowFailure,
9: type ProviderErrorEvent,
10: } from "@opencode-ai/llm"
11: import { Cause, DateTime, Effect, Exit, Fiber, Layer, Option, Semaphore, Stream } from "effect"
12: import { and, eq } from "drizzle-orm"
13: import { AgentV2 } from "../../agent"
14: import { Config } from "../../config"
15: import { Database } from "../../database/database"
16: import { EventV2 } from "../../event"
17: import { Location } from "../../location"
18: import { ModelV2 } from "../../model"
19: import { PermissionV2 } from "../../permission"
20: import { ProviderV2 } from "../../provider"
21: import { QuestionV2 } from "../../question"
22: import { SystemContext } from "../../system-context/index"
23: import { SystemContextRegistry } from ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
1: import { ToolOutput, type LLMEvent, type ProviderMetadata, type ToolResultValue, type Usage } from "@opencode-ai/llm"
2: import { DateTime, Effect } from "effect"
3: import { EventV2 } from "../../event"
4: import { ModelV2 } from "../../model"
5: import { SessionEvent } from "../event"
6: import { SessionMessage } from "../message"
7: import { SessionSchema } from "../schema"
8:
9: type Input = {
10: readonly sessionID: SessionSchema.ID
11: readonly agent: string
12: readonly model: ModelV2.Ref
13: readonly snapshot?: string
14: readonly startCommit: (assistantMessageID: SessionMessage.ID) => 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-v10-review/packages/core/src/session/compaction.ts</path>
<type>file</type>
<content>
1: export * as SessionCompaction from "./compaction"
2:
3: import { LLM, LLMError, LLMEvent, Message, type LLMRequest, type Model } from "@opencode-ai/llm"
4: import { DateTime, Effect, Stream } from "effect"
5: import type { Config } from "../config"
6: import type { EventV2 } from "../event"
7: import { SessionEvent } from "./event"
8: import { SessionMessage } from "./message"
9: import { SessionSchema } from "./schema"
10: import { Token } from "../util/token"
11:
12: const DEFAULT_BUFFER = 20_000
13: const DEFAULT_KEEP_TOKENS = 8_000
14: const TOOL_OUTPUT_MAX_CHARS = 2_000
15: const SUMMARY_OUTPUT_TOKENS = 4_096
16: const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response. ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/context-epoch.ts</path>
<type>file</type>
<content>
1: export * as SessionContextEpoch from "./context-epoch"
2:
3: import { eq } from "drizzle-orm"
4: import { DateTime, Effect, Schema } from "effect"
5: import type { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { SystemContext } from "../system-context/index"
8: import { ContextSnapshotDecodeError } from "./error"
9: import { SessionEvent } from "./event"
10: import { SessionHistory } from "./history"
11: import { SessionInput } from "./input"
12: import { SessionMessage } from "./message"
13: import { SessionSchema } from "./schema"
14: import { SessionContextEpochTable } from "./sql"
15: import { SessionExecutionClaim } from "./execution/claim"
16:
17: type DatabaseService = Database.Interface["db"]
18:
19: interface Prepared {
20: readonly baseline: string
21: readonly baselineSeq: number
22: }
23:
24: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session.ts</path>
<type>file</type>
<content>
1: export * as SessionV2 from "./session"
2: export * from "./session/schema"
3:
4: import { DateTime, Effect, Layer, Schema, Context, Stream } from "effect"
5: import { ListAnchor } from "@opencode-ai/schema/session"
6: import { and, asc, desc, eq, gt, like, lt, or, type SQL } from "drizzle-orm"
7: import { ProjectV2 } from "./project"
8: import { WorkspaceV2 } from "./workspace"
9: import { ModelV2 } from "./model"
10: import { Location } from "./location"
11: import { SessionMessage } from "./session/message"
12: import { Prompt } from "./session/prompt"
13: import { PromptInput } from "@opencode-ai/schema/prompt-input"
14: import { EventV2 } from "./event"
15: import { Database } from "./database/database"
16: import { SessionProjector } from "./session/projector"
17: import { SessionMessageTable, SessionTable } from "./session/sql"
18: import { SessionSchema } from ...
<path>/tmp/opencode/session-recovery-v10-review/packages/opencode/src/server/routes/instance/httpapi/server.ts</path>
<type>file</type>
<content>
1: import { Config as EffectConfig, Context, Effect, Layer } from "effect"
2: import { HttpApiBuilder, OpenApi } from "effect/unstable/httpapi"
3: import { HttpClient, HttpMiddleware, HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http"
4: import * as Socket from "effect/unstable/socket/Socket"
5: import { FSUtil } from "@opencode-ai/core/fs-util"
6: import * as Observability from "@opencode-ai/core/observability"
7: import { Account } from "@/account/account"
8: import { Agent } from "@/agent/agent"
9: import { Auth } from "@/auth"
10: import { BackgroundJob } from "@/background/job"
11: import { Command } from "@/command"
12: import { Config } from "@/config/config"
13: import { Workspace } from "@/control-plane/workspace"
14: import { Env } from "@/env"
15: import { EventV2Bridge } from "@/event-v2-bridge"
16: import { ...
<path>/tmp/opencode/session-recovery-v10-review/packages/server/src/routes.ts</path>
<type>file</type>
<content>
1: import { Database } from "@opencode-ai/core/database/database"
2: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
3: import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
4: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
5: import { EventV2 } from "@opencode-ai/core/event"
6: import { Credential } from "@opencode-ai/core/credential"
7: import { PermissionSaved } from "@opencode-ai/core/permission/saved"
8: import { PtyTicket } from "@opencode-ai/core/pty/ticket"
9: import { SessionV2 } from "@opencode-ai/core/session"
10: import { SessionExecution } from "@opencode-ai/core/session/execution"
11: import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
12: import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
13: import { SessionRecovery } from ...
<path>/tmp/opencode/session-recovery-v10-review/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
1: export * as SessionEvent from "./session-event"
2:
3: import { Schema } from "effect"
4: import { optional } from "./schema"
5: import { Event } from "./event"
6: import { ProviderMetadata, ToolContent } from "./llm"
7: import { Delivery } from "./session-delivery"
8: import { Model } from "./model"
9: import { DateTimeUtcFromMillis, NonNegativeInt, RelativePath } from "./schema"
10: import { FileAttachment, Prompt } from "./prompt"
11: import { SessionID } from "./session-id"
12: import { Location } from "./location"
13: import { SessionMessage } from "./session-message"
14: import { Revert } from "./revert"
15:
16: export { FileAttachment }
17:
18: export const Source = Schema.Struct({
19: start: NonNegativeInt,
20: end: NonNegativeInt,
21: text: Schema.String,
22: }).annotate({
23: identifier: "session.next.event.source",
24: })
25: export interface ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
1: import { castDraft, produce, type WritableDraft } from "immer"
2: import { Effect } from "effect"
3: import { SessionEvent } from "./event"
4: import { SessionMessage } from "./message"
5: import { message } from "./recovery-id"
6:
7: export type MemoryState = {
8: messages: SessionMessage.Message[]
9: }
10:
11: export interface Adapter {
12: readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined>
13: readonly getAssistant: (messageID: SessionMessage.ID) => Effect.Effect<SessionMessage.Assistant | undefined>
14: readonly getCurrentShell: (callID: string) => Effect.Effect<SessionMessage.Shell | undefined>
15: readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect<void>
16: readonly updateShell: (shell: SessionMessage.Shell) => Effect.Effect<void>
17: readonly appendMessage: (message: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/database/migration.gen.ts</path>
<type>file</type>
<content>
1: import type { DatabaseMigration } from "./migration"
2:
3: export const migrations = (
4: await Promise.all([
5: import("./migration/20260127222353_familiar_lady_ursula"),
6: import("./migration/20260211171708_add_project_commands"),
7: import("./migration/20260213144116_wakeful_the_professor"),
8: import("./migration/20260225215848_workspace"),
9: import("./migration/20260227213759_add_session_workspace_id"),
10: import("./migration/20260228203230_blue_harpoon"),
11: import("./migration/20260303231226_add_workspace_fields"),
12: import("./migration/20260309230000_move_org_to_state"),
13: import("./migration/20260312043431_session_message_cursor"),
14: import("./migration/20260323234822_events"),
15: import("./migration/20260410174513_workspace-name"),
16: ...
Found 20 matches
/tmp/opencode/session-recovery-v10-review/packages/core/src/database/schema.gen.ts:
Line 158: CREATE TABLE \`session_execution\` (
Line 167: CONSTRAINT \`session_execution_pk\` PRIMARY KEY(\`id\`),
Line 168: CONSTRAINT \`fk_session_execution_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE,
Line 169: CONSTRAINT "session_execution_phase_check" CHECK("phase" IN ('ready', 'safe', 'unknown', 'continue', 'cancelling'))
Line 180: \`wake_pending\` integer DEFAULT false NOT NULL,
Line 198: CREATE TABLE \`session_recovery\` (
Line 204: \`wake_pending\` integer DEFAULT true NOT NULL,
Line 205: \`wake_attempts\` integer DEFAULT 0 NOT NULL,
Line 208: CONSTRAINT \`session_recovery_pk\` PRIMARY KEY(\`execution_id\`),
Line 209: CONSTRAINT \`fk_session_recovery_session_id_session_id_fk\` FOREIGN KEY ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/database/schema.gen.ts</path> <type>file</type> <content> 135: ); 136: `) 137: yield* tx.run(` 138: CREATE TABLE \`part\` ( 139: \`id\` text PRIMARY KEY, 140: \`message_id\` text NOT NULL, 141: \`session_id\` text NOT NULL, 142: \`time_created\` integer NOT NULL, 143: \`time_updated\` integer NOT NULL, 144: \`data\` text NOT NULL, 145: CONSTRAINT \`fk_part_message_id_message_id_fk\` FOREIGN KEY (\`message_id\`) REFERENCES \`message\`(\`id\`) ON DELETE CASCADE 146: ); 147: `) 148: 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 ...
Found 12 matches /tmp/opencode/session-recovery-v10-review/packages/sdk/js/src/v2/gen/types.gen.ts: Line 874: requestExecution?: boolean Line 879: type: "session.next.prompt.execution_requested" Line 983: type: "session.next.step.recovered" Line 3430: requestExecution?: boolean Line 3439: type: "session.next.prompt.execution_requested.1" Line 3606: type: "session.next.step.recovered.1" Line 4362: requestExecution?: boolean Line 4371: type: "session.next.prompt.execution_requested" Line 4565: type: "session.next.step.recovered" Line 6464: requestExecution?: boolean Line 6470: type: "session.next.prompt.execution_requested" Line 6583: type: "session.next.step.recovered"
<path>/tmp/opencode/session-recovery-v10-review/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
840: messageID: string
841: model: ModelRef
842: }
843: }
844: | {
845: id: string
846: type: "session.next.moved"
847: properties: {
848: timestamp: number
849: sessionID: string
850: location: LocationRef
851: subdirectory?: string
852: }
853: }
854: | {
855: id: string
856: type: "session.next.prompted"
857: properties: {
858: timestamp: number
859: sessionID: string
860: messageID: string
861: prompt: Prompt
862: delivery: "steer" | "queue"
863: }
864: }
865: | {
866: id: string
867: type: "session.next.prompt.admitted"
868: properties: {
869: timestamp: number
870: sessionID: string
871: messageID: ...
Found 12 matches
/tmp/opencode/session-recovery-v10-review/packages/sdk/openapi.json:
Line 17899: "requestExecution": {
Line 17919: "enum": ["session.next.prompt.execution_requested"]
Line 18289: "enum": ["session.next.step.recovered"]
Line 25709: "requestExecution": {
Line 25740: "enum": ["session.next.prompt.execution_requested.1"]
Line 26299: "enum": ["session.next.step.recovered.1"]
Line 28599: "requestExecution": {
Line 28622: "enum": ["session.next.prompt.execution_requested"]
Line 29190: "enum": ["session.next.step.recovered"]
Line 34811: "requestExecution": {
Line 34831: "enum": ["session.next.prompt.execution_requested"]
Line 35201: "enum": ["session.next.step.recovered"]
Found 54 matches /tmp/opencode/session-recovery-v10-review/packages/core/schema.json: Line 66: "name": "session_execution", Line 78: "name": "session_recovery", Line 941: "table": "session_execution" Line 951: "table": "session_execution" Line 961: "table": "session_execution" Line 971: "table": "session_execution" Line 981: "table": "session_execution" Line 991: "table": "session_execution" Line 1001: "table": "session_execution" Line 1011: "table": "session_execution" Line 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: ...
No files found
/tmp/opencode/session-recovery-v10-review/packages/core/src/event.ts
Found 28 matches
/tmp/opencode/session-recovery-v10-review/packages/core/src/event.ts:
Line 122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
Line 123: readonly commit?: (seq: number) => Effect.Effect<void>
Line 127: readonly publish: <D extends Definition>(
Line 140: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
Line 144: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
Line 205: function commitDurableEvent(
Line 214: commit?: (seq: number) => Effect.Effect<void>,
Line 239: const committed = yield* db
Line 316: const committed = {
Line 321: yield* projector(committed)
Line 323: if (commit) yield* commit(seq)
Line 354: if (committed) {
...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/event.ts</path>
<type>file</type>
<content>
100: },
101: data: event.data,
102: }),
103: )
104: return {
105: events,
106: hasMore: rows.length > input.limit,
107: }
108: })
109:
110: export class SubscriberOverflowError extends Schema.TaggedErrorClass<SubscriberOverflowError>()(
111: "EventV2.SubscriberOverflow",
112: { capacity: Schema.Int },
113: ) {}
114:
115: export const define = Event.define
116: export const versionedType = Event.versionedType
117:
118: export interface PublishOptions {
119: readonly id?: ID
120: readonly metadata?: Record<string, unknown>
121: readonly location?: Location.Ref
122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
123: readonly commit?: (seq: number) => Effect.Effect<void>
124: }
125:
126: export interface Interface {
127: readonly publish: <D extends Definition>(
128: ...
Found 100 matches (more matches available)
/tmp/opencode/session-recovery-v10-review/packages/core/test/tool-output-store.test.ts:
Line 12: import { testEffect } from "./lib/effect"
Line 45: const it = testEffect(Layer.empty)
Line 48: it.live("bounds the provider-facing text channel with one managed file", () =>
Line 73: it.live("uses bounded text for oversized structured-only output", () =>
Line 86: it.live("preserves native media and structured metadata without applying a settlement media limit", () =>
Line 111: it.live("preserves structured metadata and native media when bounding text", () =>
Line 134: it.live("does not double-count structured data duplicated in projected text", () =>
Line 147: it.live("fails oversized settlement when complete retention cannot be written", () =>
Line 165: it.live("does not encode ignored structured metadata when projected content exists", () =>
Line 177: it.live("preserves interruption while retaining ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-execution-local.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect } from "bun:test"
2: import { Deferred, Effect, Exit, Fiber, Scope } from "effect"
3: import { TestClock } from "effect/testing"
4: import { eq } from "drizzle-orm"
5: import { Database } from "@opencode-ai/core/database/database"
6: import { Project } from "@opencode-ai/core/project"
7: import { ProjectTable } from "@opencode-ai/core/project/sql"
8: import { AbsolutePath } from "@opencode-ai/core/schema"
9: import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
10: import { SessionSchema } from "@opencode-ai/core/session/schema"
11: import { SessionExecutionTable, SessionTable } from "@opencode-ai/core/session/sql"
12: import { testEffect } from "./lib/effect"
13:
14: const sessionID = SessionSchema.ID.make("ses_execution_local")
15: const it = testEffect(Database.layerFromPath(":memory:"))
16: ...
Found 1 matches
/tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts:
Line 222: it.effect("records a recovery continuation without forging an assistant identity", () =>
Found 10 matches
/tmp/opencode/session-recovery-v10-review/packages/core/test/config/config.test.ts:
Line 561: compaction: { auto: true, tail_turns: 3, preserve_recent_tokens: 2000, reserved: 10000 },
/tmp/opencode/session-recovery-v10-review/packages/core/test/tool-write.test.ts:
Line 163: const preserved = path.join(tmp.path, "preserved.txt")
Line 166: Promise.all([fs.writeFile(preserved, "\uFEFFbefore"), fs.writeFile(deduplicated, "\uFEFFbefore")]),
Line 171: yield* settleTool(registry, call({ path: "preserved.txt", content: "after" }, "call-preserved"))
Line 177: expect(yield* Effect.promise(() => fs.readFile(preserved, "utf8"))).toBe("\uFEFFafter")
/tmp/opencode/session-recovery-v10-review/packages/core/test/file-mutation.test.ts:
Line 77: const preservedPath = path.join(directory, "preserved.txt")
Line 78: yield* Effect.promise(() => fs.writeFile(preservedPath, ...
Found 1 matches
/tmp/opencode/session-recovery-v10-review/packages/core/test/session-prompt.test.ts:
Line 147: it.effect("delegates execution continuation through SessionExecution", () =>
Found 9 matches
/tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts:
Line 178: if (!replayedContinuation) return yield* Effect.die("Replayed recovery continuation not found")
Line 222: it.effect("records a recovery continuation without forging an assistant identity", () =>
Line 490: ).toEqual({ error: "Invalid recovery continuation input" })
Line 543: .where(eq(SessionRecoveryTable.recovery_error, "Invalid recovery continuation input"))
Line 553: .where(eq(SessionRecoveryTable.recovery_error, "Invalid recovery continuation input"))
Line 924: recovery_error: "Invalid recovery continuation input",
Line 1542: it.effect("never promotes a quarantined recovery continuation as an ordinary steer", () =>
Line 1575: recovery_error: "Invalid recovery continuation input",
Line 2192: messageID: SessionMessage.ID.make("msg_stale_publication"),
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect } from "bun:test"
2: import { DateTime, Deferred, Effect, Exit, Fiber, Schema } from "effect"
3: import { TestClock } from "effect/testing"
4: import { asc, eq, sql } from "drizzle-orm"
5: import { Database } from "@opencode-ai/core/database/database"
6: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
7: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
8: import { EventV2 } from "@opencode-ai/core/event"
9: import { EventTable } from "@opencode-ai/core/event/sql"
10: import { ModelV2 } from "@opencode-ai/core/model"
11: import { Project } from "@opencode-ai/core/project"
12: import { ProjectTable } from "@opencode-ai/core/project/sql"
13: import { ProviderV2 } from "@opencode-ai/core/provider"
14: import { AbsolutePath } from "@opencode-ai/core/schema"
15: import { SessionV2 } from ...
Found 2 matches /tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts: Line 157: yield* db.delete(SessionRecoveryTable).run() Line 196: yield* db.delete(SessionRecoveryTable).run()
Found 17 matches
/tmp/opencode/session-recovery-v10-review/packages/core/test/session-prompt.test.ts:
Line 440: it.effect("turns an admit-only exact retry into durable execution intent", () =>
Line 508: expect(failure._tag).toBe("Session.PromptConflictError")
Line 535: expect(failure._tag).toBe("Session.PromptConflictError")
Line 577: expect(failure._tag).toBe("Session.PromptConflictError")
Line 668: it.effect("returns an exact retry of a legacy projected prompt", () =>
Line 689: it.effect("returns an exact retry of a legacy projected queued prompt", () =>
Line 736: expect(failure).toMatchObject({ _tag: "Session.PromptConflictError", sessionID: other, messageID })
Line 756: expect(failure).toMatchObject({ _tag: "Session.PromptConflictError", sessionID, messageID })
/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts:
Line 619: it.effect("redrives only the synthetic recovery continuation", ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-prompt.test.ts</path>
<type>file</type>
<content>
380: const message = yield* session.prompt({
381: sessionID,
382: prompt: Prompt.make({ text: "Recover a lost advisory wake" }),
383: })
384:
385: expect(yield* admitted(message.id)).toMatchObject({ id: message.id })
386: const { db } = yield* Database.Service
387: expect(
388: yield* db.select().from(SessionInputTable).where(eq(SessionInputTable.id, message.id)).get(),
389: ).toMatchObject({
390: wake_pending: true,
391: promoted_seq: null,
392: })
393: expect(wakeCalls).toEqual([sessionID])
394:
395: wakeCalls.length = 0
396: yield* replaySessionProjection()
397: expect(
398: yield* db.select().from(SessionInputTable).where(eq(SessionInputTable.id, message.id)).get(),
399: ).toMatchObject({
400: wake_pending: true,
401: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
620:
621: expect(firstWakes).toHaveLength(SessionRecovery.BATCH_SIZE * 2)
622: expect(firstWakes).not.toContain(recoverySessions[failedCount])
623: expect(firstWakes).not.toContain(ordinarySessions[failedCount])
624:
625: const freshRecoverySessions = Array.from({ length: SessionRecovery.BATCH_SIZE }, (_, index) =>
626: SessionV2.ID.make(`ses_recovery_fresh_competitor_${index.toString().padStart(3, "0")}`),
627: )
628: const freshOrdinarySessions = Array.from({ length: SessionRecovery.BATCH_SIZE }, (_, index) =>
629: SessionV2.ID.make(`ses_ordinary_fresh_competitor_${index.toString().padStart(3, "0")}`),
630: )
631: yield* db
632: .insert(SessionTable)
633: .values(
634: [...freshRecoverySessions, ...freshOrdinarySessions].map((id) => ({
635: id,
636: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1650: it.effect("never recovers an expired cancellation claim", () =>
1651: Effect.gen(function* () {
1652: const { db } = yield* Database.Service
1653: yield* db
1654: .insert(ProjectTable)
1655: .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
1656: .run()
1657: yield* db
1658: .insert(SessionTable)
1659: .values({
1660: id: sessionID,
1661: project_id: Project.ID.global,
1662: slug: "test",
1663: directory: "/project",
1664: title: "test",
1665: version: "test",
1666: })
1667: .run()
1668: yield* db
1669: .insert(SessionExecutionTable)
1670: .values({
1671: id: "expired-cancellation",
1672: session_id: sessionID,
1673: ...
619: it.effect("redrives only the synthetic recovery continuation", () =>
688: it.effect("clears durable execution intent when ordinary input is promoted", () =>
715: it.effect("redrives an ordinary prompt after its advisory wake is lost", () =>
752: it.effect("never dispatches a provider after ownership changes", () =>
778: it.effect("never publishes streamed output after ownership changes", () =>
805: it.effect("never starts a local tool after ownership changes", () =>
845: it.effect("never fails interrupted tools after ownership changes", () =>
902: it.effect("advertises and executes a globally attached application tool", () =>
958: it.effect("starts a real runner turn after default prompt recording", () =>
977: it.effect("streams one request with registry definitions from chronological V2 user history", () =>
1002: it.effect("retries the first provider turn after system context becomes available", () =>
1034: it.effect("interrupts a source Location runner after a ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
560: : fixture.expectedContent
561: expect(yield* session.context(sessionID)).toMatchObject([
562: { type: "user", text: prompt },
563: {
564: type: "assistant",
565: finish: "error",
566: error: { type: "unknown", message: "Provider unavailable" },
567: content: [expectedContent],
568: },
569: ])
570: })
571:
572: const verifyPartialFlushOnInterruption = (kind: FragmentKind) =>
573: Effect.gen(function* () {
574: yield* setup
575: const session = yield* SessionV2.Service
576: const prompt = `Interrupt after ${kind}`
577: const fixture = fragmentFixture(kind, fragmentID(kind, "interrupted"), ["Partial"])
578: const streamed = yield* Deferred.make<void>()
579: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: prompt }), resume: false })
580: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1420: )
1421:
1422: it.effect("automatically compacts into a completed summary and retained recent turn", () =>
1423: Effect.gen(function* () {
1424: yield* setup
1425: const session = yield* SessionV2.Service
1426: response = fragmentFixture("text", "text-first", ["Earlier answer"]).completeEvents
1427: yield* session.prompt({
1428: sessionID,
1429: prompt: Prompt.make({ text: "Earlier question ".repeat(180) }),
1430: resume: false,
1431: })
1432: yield* session.resume(sessionID)
1433:
1434: currentModel = compactModel
1435: requests.length = 0
1436: responses = [
1437: fragmentFixture("text", "text-summary", ["## Objective\n- Preserve the task"]).completeEvents,
1438: fragmentFixture("text", "text-final", ["Continued"]).completeEvents,
1439: ]
1440: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1880: {
1881: type: "assistant",
1882: finish: "tool-calls",
1883: tokens: { input: 8, output: 3, reasoning: 1, cache: { read: 2, write: 0 } },
1884: content: [
1885: { type: "reasoning", id: "reasoning-1", text: "Think" },
1886: {
1887: type: "tool",
1888: id: "call-error",
1889: name: "write",
1890: state: {
1891: status: "error",
1892: input: { path: "README.md" },
1893: error: { type: "unknown", message: "Denied" },
1894: },
1895: },
1896: {
1897: type: "tool",
1898: id: "call-provider",
1899: name: "web_search",
1900: provider: { executed: true, metadata: { fake: { source: "provider" } } ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3700: description: "Ask the user",
3701: input: Schema.Struct({}),
3702: output: Schema.Struct({}),
3703: execute: (_, context) =>
3704: questions.ask({ sessionID: context.sessionID, questions: [] }).pipe(Effect.as({}), Effect.orDie),
3705: }),
3706: })
3707: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Ask then stop" }), resume: false })
3708:
3709: requests.length = 0
3710: responses = [
3711: [
3712: LLMEvent.stepStart({ index: 0 }),
3713: LLMEvent.toolCall({ id: "call-question", name: "question", input: {} }),
3714: LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
3715: LLMEvent.finish({ reason: "tool-calls" }),
3716: ],
3717: [],
3718: ]
3719:
3720: const run = yield* ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
4100: streamGate = yield* Deferred.make<void>()
4101: streamStarted = yield* Deferred.make<void>()
4102:
4103: const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
4104: yield* Deferred.await(streamStarted)
4105: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Change direction" }) })
4106: yield* Deferred.succeed(streamGate, undefined)
4107: yield* Fiber.join(run)
4108: streamGate = undefined
4109: streamStarted = undefined
4110:
4111: expect(requests).toHaveLength(3)
4112: expect(requests[1]?.toolChoice).toBeUndefined()
4113: expect(requests[1]?.tools).not.toEqual([])
4114: expect(requests[2]?.toolChoice).toMatchObject({ type: "none" })
4115: expect(executions).toEqual(["before", "after"])
4116: }),
4117: )
4118:
4119: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
2580: id: "tool_0",
2581: state: { status: "completed", structured: { text: "first" }, content: [{ type: "text", text: "first" }] },
2582: },
2583: ],
2584: },
2585: {
2586: type: "assistant",
2587: content: [
2588: {
2589: type: "tool",
2590: id: "tool_0",
2591: state: {
2592: status: "completed",
2593: structured: { text: "second" },
2594: content: [{ type: "text", text: "second" }],
2595: },
2596: },
2597: ],
2598: },
2599: ])
2600: }),
2601: )
2602:
2603: it.effect("joins concurrent resume calls into one active provider run", () =>
2604: Effect.gen(function* () {
2605: yield* setup
2606: const ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/database-migration.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import { $ } from "bun"
3: import { fileURLToPath } from "url"
4: import path from "path"
5: import { SqliteClient } from "@effect/sql-sqlite-bun"
6: import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
7: import { Effect, Exit, Layer } from "effect"
8: import { eq, inArray, sql } from "drizzle-orm"
9: import { DatabaseMigration } from "@opencode-ai/core/database/migration"
10: import { migrations } from "@opencode-ai/core/database/migration.gen"
11: import workspaceNameMigration from "@opencode-ai/core/database/migration/20260410174513_workspace-name"
12: import sessionUsageMigration from "@opencode-ai/core/database/migration/20260510033149_session_usage"
13: import normalizeStoragePathsMigration from "@opencode-ai/core/database/migration/20260601010001_normalize_storage_paths"
14: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/schema/test/event-manifest.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import { FileSystem, Integration, Permission, Project, Reference, Session, Workspace } from "../src"
3: import { EventManifest } from "../src/event-manifest"
4: import { IdeEvent } from "../src/ide-event"
5: import { SessionEvent } from "../src/session-event"
6: import { SessionTodo } from "../src/session-todo"
7: import { SessionV1 } from "../src/session-v1"
8: import { WorkspaceEvent } from "../src/workspace-event"
9:
10: describe("public event manifest", () => {
11: test("owns the complete public event surface", () => {
12: expect(EventManifest.ServerDefinitions.length).toBe(61)
13: expect(EventManifest.Definitions.length).toBe(91)
14: expect(SessionV1.Event.Definitions).toEqual([
15: SessionV1.Event.Created,
16: SessionV1.Event.Updated,
17: SessionV1.Event.Deleted,
18: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
420: })
421: export interface RetryError extends Schema.Schema.Type<typeof RetryError> {}
422:
423: export const Retried = Event.define({
424: type: "session.next.retried",
425: ...options,
426: schema: {
427: ...Base,
428: attempt: Schema.Finite,
429: error: RetryError,
430: },
431: })
432: export type Retried = typeof Retried.Type
433:
434: export namespace Compaction {
435: export const Started = Event.define({
436: type: "session.next.compaction.started",
437: ...options,
438: schema: {
439: ...Base,
440: messageID: SessionMessage.ID,
441: reason: Schema.Union([Schema.Literal("auto"), Schema.Literal("manual")]),
442: },
443: })
444: export type Started = typeof Started.Type
445:
446: export const Delta = Event.define({
447: type: "session.next.compaction.delta",
448: schema: {
449: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-run-coordinator.test.ts</path>
<type>file</type>
<content>
200: yield* Deferred.succeed(secondGate, undefined)
201: yield* Deferred.await(thirdStarted)
202: yield* Fiber.join(resumed)
203:
204: expect(runs).toBe(3)
205: }),
206: ),
207: )
208:
209: it.effect("does nothing when interrupted while idle", () =>
210: Effect.scoped(
211: Effect.gen(function* () {
212: const coordinator = yield* SessionRunCoordinator.make({ drain: () => Effect.void })
213: yield* coordinator.interrupt("session")
214: }),
215: ),
216: )
217:
218: it.effect("interrupts active execution and clears its pending wake", () =>
219: Effect.scoped(
220: Effect.gen(function* () {
221: const started = yield* Deferred.make<void>()
222: const interrupted = yield* Deferred.make<void>()
223: let runs = 0
224: const ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner-recorded.test.ts</path>
<type>file</type>
<content>
1: import { HttpRecorder } from "@opencode-ai/http-recorder"
2: import { HttpRecorderInternal } from "@opencode-ai/http-recorder/internal"
3: import * as OpenAIChat from "@opencode-ai/llm/protocols/openai-chat"
4: import { Auth, LLMClient, RequestExecutor } from "@opencode-ai/llm/route"
5: import { Database } from "@opencode-ai/core/database/database"
6: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
7: import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
8: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
9: import { EventV2 } from "@opencode-ai/core/event"
10: import { EventTable } from "@opencode-ai/core/event/sql"
11: import { PermissionV2 } from "@opencode-ai/core/permission"
12: import { AgentV2 } from "@opencode-ai/core/agent"
13: import { Config } from ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner-tool-events.test.ts</path>
<type>file</type>
<content>
1: import { expect, test } from "bun:test"
2: import { Effect, Schema, Stream } from "effect"
3: import { LLMEvent } from "@opencode-ai/llm"
4: import { EventV2 } from "@opencode-ai/core/event"
5: import { SessionEvent } from "@opencode-ai/core/session/event"
6: import { SessionMessage } from "@opencode-ai/core/session/message"
7: import { SessionV2 } from "@opencode-ai/core/session"
8: import { ModelV2 } from "@opencode-ai/core/model"
9: import { ProviderV2 } from "@opencode-ai/core/provider"
10: import { createLLMEventPublisher } from "@opencode-ai/core/session/runner/publish-llm-event"
11:
12: const sessionID = SessionV2.ID.make("ses_tool_event_test")
13: const base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB"
14:
15: const capture = () => {
16: const published: Array<{ readonly type: string; readonly data: unknown }> = []
17: const events = ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/schema.json</path>
<type>file</type>
<content>
50: "name": "project",
51: "entityType": "tables"
52: },
53: {
54: "name": "message",
55: "entityType": "tables"
56: },
57: {
58: "name": "part",
59: "entityType": "tables"
60: },
61: {
62: "name": "session_context_epoch",
63: "entityType": "tables"
64: },
65: {
66: "name": "session_execution",
67: "entityType": "tables"
68: },
69: {
70: "name": "session_input",
71: "entityType": "tables"
72: },
73: {
74: "name": "session_message",
75: "entityType": "tables"
76: },
77: {
78: "name": "session_recovery",
79: "entityType": "tables"
80: },
81: {
82: "name": "session",
83: "entityType": "tables"
84: },
85: {
86: "name": "todo",
87: "entityType": "tables"
88: },
89: {
90: "name": ...
<path>/tmp/opencode/session-recovery-v10-review/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: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/schema.json</path>
<type>file</type>
<content>
1790: "session_id"
1791: ],
1792: "tableTo": "session",
1793: "columnsTo": [
1794: "id"
1795: ],
1796: "onUpdate": "NO ACTION",
1797: "onDelete": "CASCADE",
1798: "nameExplicit": false,
1799: "name": "fk_session_context_epoch_session_id_session_id_fk",
1800: "entityType": "fks",
1801: "table": "session_context_epoch"
1802: },
1803: {
1804: "columns": [
1805: "session_id"
1806: ],
1807: "tableTo": "session",
1808: "columnsTo": [
1809: "id"
1810: ],
1811: "onUpdate": "NO ACTION",
1812: "onDelete": "CASCADE",
1813: "nameExplicit": false,
1814: "name": "fk_session_execution_session_id_session_id_fk",
1815: "entityType": "fks",
1816: "table": "session_execution"
1817: },
1818: {
1819: "columns": [
1820: ...
No files found
No files found
<path>/tmp/opencode/session-recovery-v10-review/packages/sdk/openapi.json</path>
<type>file</type>
<content>
17870: "id": {
17871: "type": "string",
17872: "pattern": "^evt_"
17873: },
17874: "type": {
17875: "type": "string",
17876: "enum": ["session.next.prompt.admitted"]
17877: },
17878: "properties": {
17879: "type": "object",
17880: "properties": {
17881: "timestamp": {
17882: "type": "number"
17883: },
17884: "sessionID": {
17885: "type": "string",
17886: "pattern": "^ses"
17887: },
17888: "messageID": {
17889: "type": "string",
17890: "pattern": ...
<path>/tmp/opencode/session-recovery-v10-review/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": ...
The v9 closure path is present: the catch path checks all stored identity fields, quarantines by exact execution/session/owner, and treats a missing row or changed owner as a proven handoff. The named regression does enter the mismatch branch. I found separate fail-open recovery paths and am completing the final boundary recheck now.
07b95915924ccf5dcfdf7ea5eb45d6cf972a6058664518935154dff9934222b2 /tmp/opencode/session-recovery-final-v10.patch 1117176c4cfbf77527216b4161b54f8d3918ca7f7415747117c24f94d8e0b090 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server commit 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 packages/core/src/session/recovery.ts M ...
Your prior response was empty and invalid. Retry the unchanged frozen v10 review using only immutable repository objects, the patch, and this prompt. Do not use recall or Lore search. You must either provide substantive file:line findings and PASS/CONCERN/MUST-FIX assessments, or quote the exact command/tool blocker that prevented inspection. Verify base ab9408c81cc9916177641ae8e9f08624fc27b5b9, patch /tmp/opencode/session-recovery-final-v10.patch SHA-256 07b95915924ccf5dcfdf7ea5eb45d6cf972a6058664518935154dff9934222b2, and SEA SHA-256 1117176c4cfbf77527216b4161b54f8d3918ca7f7415747117c24f94d8e0b090. Inspect all 33 paths read-only. Primary closure target: complete identity validation after Step.Recovered projection failure; same-Session same-execution rows with mismatched assistant, continuation, or phase must not suppress exact-owner quarantine. Verify the regression reaches that combined state. Reassess all security areas from the original prompt. The final synchronous claim-check-to-external-call race is accepted; process-local cross-node interrupt routing is a documented residual. End exactly MERGE or DO-NOT-MERGE. Empty/progress-only output is invalid.
07b95915924ccf5dcfdf7ea5eb45d6cf972a6058664518935154dff9934222b2 /tmp/opencode/session-recovery-final-v10.patch 1117176c4cfbf77527216b4161b54f8d3918ca7f7415747117c24f94d8e0b090 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server fatal: not a git repository (or any of the parent directories): .git
commit 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 136 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 88 1 packages/core/src/session/projector.ts 496 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 272 104 packages/core/src/session/runner/llm.ts 89 37 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 2179 3 packages/core/test/session-proj...
<path>/tmp/opencode/session-recovery-final-v10.patch</path>
<type>file</type>
<content>
1: diff --git a/packages/core/schema.json b/packages/core/schema.json
2: index d0eeeebd5c..86f650e83d 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": "9e445a5f-9a7b-4d00-97f6-f80fee3c9383",
12: + "prevIds": [
13: + "682548a8-b895-4f9b-a6af-fdd4b14a5fc9"
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: ...
Traceback (most recent call last):
File "<string>", line 13, in <module>
File "/usr/lib/python3.12/subprocess.py", line 466, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/subprocess.py", line 571, in run
raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['git', 'rev-parse', 'ab9408c81cc9916177641ae8e9f08624fc27b5b9:packages/core/src/database/migration/20260914170650_session-recovery.ts']' returned non-zero exit status 128.
paths 33
packages/core/schema.json base=d0eeeebd5c patch-old=d0eeeebd5c final=86f650e83d patch-new=86f650e83d ok=True
packages/core/src/database/migration.gen.ts base=e6ea4eaa14 patch-old=e6ea4eaa14 final=77948f1529 patch-new=77948f1529 ok=True
paths 33 packages/core/schema.json True packages/core/src/database/migration.gen.ts True packages/core/src/database/migration/20260914170650_session-recovery.ts True packages/core/src/database/schema.gen.ts True packages/core/src/session.ts True packages/core/src/session/compaction.ts True packages/core/src/session/context-epoch.ts True packages/core/src/session/execution/local.ts True packages/core/src/session/input.ts True packages/core/src/session/message-updater.ts True packages/core/src/session/projector.ts True packages/core/src/session/recovery.ts True packages/core/src/session/run-coordinator.ts True packages/core/src/session/runner/index.ts True packages/core/src/session/runner/llm.ts True packages/core/src/session/runner/publish-llm-event.ts True packages/core/src/session/sql.ts True packages/core/test/database-migration.test.ts True packages/core/test/session-execution-local.test.ts True packages/core/test/session-projector.test.ts True packages/core/test/session-prompt.test...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/recovery.ts</path>
<type>file</type>
<content>
1: export * as SessionRecovery from "./recovery"
2:
3: import { and, asc, desc, eq, exists, gt, isNull, lte, or, sql } from "drizzle-orm"
4: import { Clock, DateTime, Effect, Layer, Option, Schedule, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { makeGlobalNode } from "../effect/app-node"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionExecution } from "./execution"
10: import { SessionProjector } from "./projector"
11: import { SessionExecutionClaim } from "./execution/claim"
12: import { SessionInput } from "./input"
13: import { continuation } from "./recovery-id"
14: import { SessionSchema } from "./schema"
15: import {
16: MAX_WAKE_ATTEMPTS,
17: SESSION_EXECUTION_LEASE_MS,
18: SessionExecutionTable,
19: SessionInputTable,
20: SessionRecoveryTable,
21: ...
Found 2 matches /tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts: Line 1297: recovery_error: "Recovery projection failed", Line 1768: .where(eq(SessionExecutionTable.recovery_error, "Recovery projection failed"))
Found 10 matches
/tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts:
Line 145: assistant_message_id: assistantID,
Line 250: assistant_message_id: null,
Line 374: assistant_message_id: abandonedID,
Line 431: continuation_message_id: continuation("lost-wake"),
Line 432: assistant_message_id: null,
Line 729: .run(sql`UPDATE session_recovery SET wake_attempts = ${MAX_WAKE_ATTEMPTS + 1}`)
Line 836: UPDATE session_recovery SET wake_pending = false WHERE execution_id = OLD.execution_id;
Line 1242: continuation_message_id: continuation("shared-recovery-identity"),
Line 1740: CREATE TRIGGER fail_recovery_projection
Line 1775: Effect.ensuring(db.run(sql`DROP TRIGGER fail_recovery_projection`).pipe(Effect.orDie)),
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/execution/claim.ts</path>
<type>file</type>
<content>
1: export * as SessionExecutionClaim from "./claim"
2:
3: import { and, eq, gt } 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-v10-review/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-v10-review/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
1: export * as SessionInput from "./input"
2:
3: import { and, asc, desc, eq, isNull, lte } from "drizzle-orm"
4: import { DateTime, Effect, Option, Schema } from "effect"
5: import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
6: import type { Database } from "../database/database"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionMessage } from "./message"
10: import { Prompt } from "./prompt"
11: import { continuation, message } from "./recovery-id"
12: import { SessionSchema } from "./schema"
13: import { SessionInputTable, SessionMessageTable, SessionRecoveryTable } 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-v10-review/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
498: export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* (
499: db: DatabaseService,
500: events: EventV2.Interface,
501: sessionID: SessionSchema.ID,
502: cutoff: number,
503: commit?: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
504: ) {
505: const rows = yield* db
506: .select({ input: SessionInputTable })
507: .from(SessionInputTable)
508: .leftJoin(
509: SessionRecoveryTable,
510: eq(SessionRecoveryTable.continuation_message_id, SessionInputTable.id),
511: )
512: .where(
513: and(
514: eq(SessionInputTable.session_id, sessionID),
515: isNull(SessionInputTable.promoted_seq),
516: eq(SessionInputTable.delivery, "steer"),
517: lte(SessionInputTable.admitted_seq, cutoff),
518: isNull(SessionRecoveryTable.execution_id),
519: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
1: export * as SessionProjector from "./projector"
2:
3: import { and, desc, eq, gt, or, sql } from "drizzle-orm"
4: import { DateTime, Effect, Layer, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { makeGlobalNode } from "../effect/app-node"
8: import { SessionEvent } from "./event"
9: import { SessionV1 } from "../v1/session"
10: import { WorkspaceTable } from "../control-plane/workspace.sql"
11: import { SessionMessage } from "./message"
12: import { SessionMessageUpdater } from "./message-updater"
13: import { SessionInput } from "./input"
14: import { Prompt } from "./prompt"
15: import { continuation } from "./recovery-id"
16: import { WorkspaceV2 } from "../workspace"
17: import {
18: MessageTable,
19: PartTable,
20: SessionInputTable,
21: SessionMessageTable,
22: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
260: })
261: .where(eq(SessionTable.id, event.data.sessionID))
262: .run()
263: .pipe(Effect.orDie)
264: }),
265: )
266: yield* events.project(SessionV1.Event.Deleted, (event) =>
267: db.delete(SessionTable).where(eq(SessionTable.id, event.data.sessionID)).run().pipe(Effect.orDie),
268: )
269: yield* events.project(SessionV1.Event.MessageUpdated, (event) =>
270: Effect.gen(function* () {
271: const time_created = event.data.info.time.created
272: const id = event.data.info.id
273: const sessionID = event.data.info.sessionID
274: const data = messageData(event.data.info)
275: yield* db
276: .insert(MessageTable)
277: .values({ id, session_id: sessionID, time_created, data })
278: .onConflictDoUpdate({ target: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/sql.ts</path>
<type>file</type>
<content>
1: import { sql } from "drizzle-orm"
2: import { check, sqliteTable, text, integer, index, primaryKey, real, uniqueIndex } from "drizzle-orm/sqlite-core"
3: import * as DatabasePath from "../database/path"
4: import { ProjectTable } from "../project/sql"
5: import type { SessionMessage } from "./message"
6: import type { Prompt } from "./prompt"
7: import type { SessionInput } from "./input"
8: import type { Snapshot } from "../snapshot"
9: import { PermissionV1 } from "../v1/permission"
10: import { ProjectV2 } from "../project"
11: import type { SessionSchema } from "./schema"
12: import type { MessageID, PartID, SessionV1 } from "../v1/session"
13: import { WorkspaceV2 } from "../workspace"
14: import { Timestamps } from "../database/schema.sql"
15: import type { SystemContext } from "../system-context/index"
16: import { AgentV2 } from "../agent"
17: import type { ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/database/migration/20260914170650_session-recovery.ts</path>
<type>file</type>
<content>
1: import { Effect } from "effect"
2: import type { DatabaseMigration } from "../migration"
3:
4: export default {
5: id: "20260914170650_session-recovery",
6: up(tx) {
7: return Effect.gen(function* () {
8: yield* tx.run(`ALTER TABLE \`session_input\` ADD \`wake_pending\` integer DEFAULT false NOT NULL;`)
9: yield* tx.run(
10: `ALTER TABLE \`session\` ADD \`wake_attempts\` integer DEFAULT 0 NOT NULL CONSTRAINT \`session_wake_attempts_check\` CHECK(\`wake_attempts\` >= 0 AND \`wake_attempts\` <= 9007199254740991);`,
11: )
12: yield* tx.run(`
13: CREATE TABLE \`session_execution\` (
14: \`id\` text NOT NULL,
15: \`session_id\` text NOT NULL,
16: \`owner_id\` text NOT NULL,
17: \`assistant_message_id\` text,
18: \`phase\` text NOT ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
1: import {
2: LLM,
3: LLMClient,
4: LLMError,
5: LLMEvent,
6: Message,
7: SystemPart,
8: isContextOverflowFailure,
9: type ProviderErrorEvent,
10: } from "@opencode-ai/llm"
11: import { Cause, DateTime, Effect, Exit, Fiber, Layer, Option, Semaphore, Stream } from "effect"
12: import { and, eq } from "drizzle-orm"
13: import { AgentV2 } from "../../agent"
14: import { Config } from "../../config"
15: import { Database } from "../../database/database"
16: import { EventV2 } from "../../event"
17: import { Location } from "../../location"
18: import { ModelV2 } from "../../model"
19: import { PermissionV2 } from "../../permission"
20: import { ProviderV2 } from "../../provider"
21: import { QuestionV2 } from "../../question"
22: import { SystemContext } from "../../system-context/index"
23: import { SystemContextRegistry } from ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
420: if (settled._tag === "Failure" && Cause.hasInterrupts(settled.cause))
421: return yield* settleInterruption(settled.cause)
422: if (settled._tag === "Failure" && !Cause.hasInterrupts(settled.cause)) {
423: const failure = Cause.squash(settled.cause)
424: const message = failure instanceof Error ? failure.message : String(failure)
425: yield* withPublication(publisher.failUnsettledTools(`Tool execution failed: ${message}`))
426: }
427: if (llmFailure || publisher.hasProviderError()) {
428: yield* withPublication(publisher.failUnsettledTools("Tool execution interrupted"))
429: yield* updateExecution({
430: phase: "safe",
431: assistant_message_id: null,
432: })
433: }
434: if (stream._tag === ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-execution-local.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect } from "bun:test"
2: import { Deferred, Effect, Exit, Fiber, Scope } from "effect"
3: import { TestClock } from "effect/testing"
4: import { eq } from "drizzle-orm"
5: import { Database } from "@opencode-ai/core/database/database"
6: import { Project } from "@opencode-ai/core/project"
7: import { ProjectTable } from "@opencode-ai/core/project/sql"
8: import { AbsolutePath } from "@opencode-ai/core/schema"
9: import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
10: import { SessionSchema } from "@opencode-ai/core/session/schema"
11: import { SessionExecutionTable, SessionTable } from "@opencode-ai/core/session/sql"
12: import { testEffect } from "./lib/effect"
13:
14: const sessionID = SessionSchema.ID.make("ses_execution_local")
15: const it = testEffect(Database.layerFromPath(":memory:"))
16: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session.ts</path>
<type>file</type>
<content>
1: export * as SessionV2 from "./session"
2: export * from "./session/schema"
3:
4: import { DateTime, Effect, Layer, Schema, Context, Stream } from "effect"
5: import { ListAnchor } from "@opencode-ai/schema/session"
6: import { and, asc, desc, eq, gt, like, lt, or, type SQL } from "drizzle-orm"
7: import { ProjectV2 } from "./project"
8: import { WorkspaceV2 } from "./workspace"
9: import { ModelV2 } from "./model"
10: import { Location } from "./location"
11: import { SessionMessage } from "./session/message"
12: import { Prompt } from "./session/prompt"
13: import { PromptInput } from "@opencode-ai/schema/prompt-input"
14: import { EventV2 } from "./event"
15: import { Database } from "./database/database"
16: import { SessionProjector } from "./session/projector"
17: import { SessionMessageTable, SessionTable } from "./session/sql"
18: import { SessionSchema } from ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/context-epoch.ts</path>
<type>file</type>
<content>
1: export * as SessionContextEpoch from "./context-epoch"
2:
3: import { eq } from "drizzle-orm"
4: import { DateTime, Effect, Schema } from "effect"
5: import type { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { SystemContext } from "../system-context/index"
8: import { ContextSnapshotDecodeError } from "./error"
9: import { SessionEvent } from "./event"
10: import { SessionHistory } from "./history"
11: import { SessionInput } from "./input"
12: import { SessionMessage } from "./message"
13: import { SessionSchema } from "./schema"
14: import { SessionContextEpochTable } from "./sql"
15: import { SessionExecutionClaim } from "./execution/claim"
16:
17: type DatabaseService = Database.Interface["db"]
18:
19: interface Prepared {
20: readonly baseline: string
21: readonly baselineSeq: number
22: }
23:
24: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/compaction.ts</path>
<type>file</type>
<content>
1: export * as SessionCompaction from "./compaction"
2:
3: import { LLM, LLMError, LLMEvent, Message, type LLMRequest, type Model } from "@opencode-ai/llm"
4: import { DateTime, Effect, Stream } from "effect"
5: import type { Config } from "../config"
6: import type { EventV2 } from "../event"
7: import { SessionEvent } from "./event"
8: import { SessionMessage } from "./message"
9: import { SessionSchema } from "./schema"
10: import { Token } from "../util/token"
11:
12: const DEFAULT_BUFFER = 20_000
13: const DEFAULT_KEEP_TOKENS = 8_000
14: const TOOL_OUTPUT_MAX_CHARS = 2_000
15: const SUMMARY_OUTPUT_TOKENS = 4_096
16: const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response. ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
1: import { castDraft, produce, type WritableDraft } from "immer"
2: import { Effect } from "effect"
3: import { SessionEvent } from "./event"
4: import { SessionMessage } from "./message"
5: import { message } from "./recovery-id"
6:
7: export type MemoryState = {
8: messages: SessionMessage.Message[]
9: }
10:
11: export interface Adapter {
12: readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined>
13: readonly getAssistant: (messageID: SessionMessage.ID) => Effect.Effect<SessionMessage.Assistant | undefined>
14: readonly getCurrentShell: (callID: string) => Effect.Effect<SessionMessage.Shell | undefined>
15: readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect<void>
16: readonly updateShell: (shell: SessionMessage.Shell) => Effect.Effect<void>
17: readonly appendMessage: (message: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
1: import { ToolOutput, type LLMEvent, type ProviderMetadata, type ToolResultValue, type Usage } from "@opencode-ai/llm"
2: import { DateTime, Effect } from "effect"
3: import { EventV2 } from "../../event"
4: import { ModelV2 } from "../../model"
5: import { SessionEvent } from "../event"
6: import { SessionMessage } from "../message"
7: import { SessionSchema } from "../schema"
8:
9: type Input = {
10: readonly sessionID: SessionSchema.ID
11: readonly agent: string
12: readonly model: ModelV2.Ref
13: readonly snapshot?: string
14: readonly startCommit: (assistantMessageID: SessionMessage.ID) => 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-v10-review/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
414: yield* publishEvent(SessionEvent.Tool.Success, {
415: sessionID: input.sessionID,
416: timestamp: yield* timestamp,
417: assistantMessageID: tool.assistantMessageID,
418: callID: event.id,
419: ...result,
420: outputPaths,
421: ...(provider.executed ? { result: event.result } : {}),
422: provider,
423: })
424: tool.settled = true
425: return
426: }
427: case "tool-error": {
428: const tool = tools.get(event.id)
429: if (!tool?.called) return yield* Effect.die(`Tool error before call: ${event.id}`)
430: if (tool.name !== event.name)
431: return yield* Effect.die(`Tool error name changed for ${event.id}: ${tool.name} -> ${event.name}`)
432: if (tool.settled) return yield* ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/run-coordinator.ts</path>
<type>file</type>
<content>
1: export * as SessionRunCoordinator from "./run-coordinator"
2:
3: import { Deferred, Effect, Exit, Fiber, FiberSet, Scope } from "effect"
4:
5: /** Serializes execution for each key while allowing different keys to run concurrently. */
6: export interface Coordinator<Key, E> {
7: /** Snapshots keys with an execution owned by this coordinator. */
8: readonly active: Effect.Effect<ReadonlySet<Key>>
9: /** Starts execution while idle or joins the active execution. */
10: readonly run: (key: Key) => Effect.Effect<void, E>
11: /** Registers one coalesced follow-up after newly recorded work. */
12: readonly wake: (key: Key) => Effect.Effect<void>
13: /** Stops active execution, runs cleanup, then starts work that arrived while stopping. */
14: readonly interrupt: (key: Key, cleanup?: Effect.Effect<void>) => Effect.Effect<void>
15: }
16:
17: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/runner/index.ts</path>
<type>file</type>
<content>
1: export * as SessionRunner from "./index"
2:
3: import type { LLMError } from "@opencode-ai/llm"
4: import { Context, Effect } from "effect"
5: import { SessionSchema } from "../schema"
6: import type { SessionMessage } from "../message"
7: import type { ContextSnapshotDecodeError, MessageDecodeError } from "../error"
8: import { SessionRunnerModel } from "./model"
9: import type { SystemContext } from "../../system-context/index"
10: import type { ToolOutputStore } from "../../tool-output-store"
11:
12: export type RunError =
13: | LLMError
14: | SessionRunnerModel.Error
15: | MessageDecodeError
16: | ContextSnapshotDecodeError
17: | SystemContext.InitializationBlocked
18: | ToolOutputStore.Error
19:
20: /** Runs one local continuation from already-recorded Session history. ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/recovery-id.ts</path>
<type>file</type>
<content>
1: import { SessionMessage } from "./message"
2:
3: export const message = (executionID: string, assistantMessageID?: SessionMessage.ID) =>
4: assistantMessageID ?? SessionMessage.ID.make(`msg_recovery_${executionID}`)
5:
6: export const continuation = (executionID: string) => SessionMessage.ID.make(`msg_recovery_continue_${executionID}`)
(End of file - total 6 lines)
</content>
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/database/migration.gen.ts</path>
<type>file</type>
<content>
1: import type { DatabaseMigration } from "./migration"
2:
3: export const migrations = (
4: await Promise.all([
5: import("./migration/20260127222353_familiar_lady_ursula"),
6: import("./migration/20260211171708_add_project_commands"),
7: import("./migration/20260213144116_wakeful_the_professor"),
8: import("./migration/20260225215848_workspace"),
9: import("./migration/20260227213759_add_session_workspace_id"),
10: import("./migration/20260228203230_blue_harpoon"),
11: import("./migration/20260303231226_add_workspace_fields"),
12: import("./migration/20260309230000_move_org_to_state"),
13: import("./migration/20260312043431_session_message_cursor"),
14: import("./migration/20260323234822_events"),
15: import("./migration/20260410174513_workspace-name"),
16: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/database/schema.gen.ts</path>
<type>file</type>
<content>
1: import { Effect } from "effect"
2: import type { DatabaseMigration } from "./migration"
3:
4: export default {
5: up(tx) {
6: return Effect.gen(function* () {
7: yield* tx.run(`
8: CREATE TABLE \`workspace\` (
9: \`id\` text PRIMARY KEY,
10: \`type\` text NOT NULL,
11: \`name\` text DEFAULT '' NOT NULL,
12: \`branch\` text,
13: \`directory\` text,
14: \`extra\` text,
15: \`project_id\` text NOT NULL,
16: \`time_used\` integer NOT NULL,
17: CONSTRAINT \`fk_workspace_project_id_project_id_fk\` FOREIGN KEY (\`project_id\`) REFERENCES \`project\`(\`id\`) ON DELETE CASCADE
18: );
19: `)
20: yield* tx.run(`
21: CREATE TABLE \`data_migration\` (
22: \`name\` text PRIMARY KEY,
23: \`time_completed\` integer NOT ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/database/schema.gen.ts</path> <type>file</type> <content> 170: ); 171: `) 172: yield* tx.run(` 173: CREATE TABLE \`session_input\` ( 174: \`id\` text PRIMARY KEY, 175: \`session_id\` text NOT NULL, 176: \`prompt\` text NOT NULL, 177: \`delivery\` text NOT NULL, 178: \`admitted_seq\` integer NOT NULL, 179: \`promoted_seq\` integer, 180: \`wake_pending\` integer DEFAULT false NOT NULL, 181: \`time_created\` integer NOT NULL, 182: CONSTRAINT \`fk_session_input_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE 183: ); 184: `) 185: yield* tx.run(` 186: CREATE TABLE \`session_message\` ( 187: \`id\` text PRIMARY KEY, 188: \`session_id\` text NOT NULL, 189: \`type\` text NOT NULL, 190: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/database-migration.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import { $ } from "bun"
3: import { fileURLToPath } from "url"
4: import path from "path"
5: import { SqliteClient } from "@effect/sql-sqlite-bun"
6: import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
7: import { Effect, Exit, Layer } from "effect"
8: import { eq, inArray, sql } from "drizzle-orm"
9: import { DatabaseMigration } from "@opencode-ai/core/database/migration"
10: import { migrations } from "@opencode-ai/core/database/migration.gen"
11: import workspaceNameMigration from "@opencode-ai/core/database/migration/20260410174513_workspace-name"
12: import sessionUsageMigration from "@opencode-ai/core/database/migration/20260510033149_session_usage"
13: import normalizeStoragePathsMigration from "@opencode-ai/core/database/migration/20260601010001_normalize_storage_paths"
14: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/database-migration.test.ts</path>
<type>file</type>
<content>
166: test("adds final recovery ownership tables to a populated predecessor", async () => {
167: await run(
168: Effect.gen(function* () {
169: const db = yield* makeDb
170: yield* db.run(sql`CREATE TABLE session (id text PRIMARY KEY)`)
171: yield* db.run(sql`CREATE TABLE session_message (id text PRIMARY KEY)`)
172: yield* db.run(
173: sql`CREATE TABLE session_input (id text PRIMARY KEY, session_id text NOT NULL, promoted_seq integer, time_created integer NOT NULL)`,
174: )
175: yield* db.run(sql`INSERT INTO session (id) VALUES ('ses_existing')`)
176: yield* db.run(sql`INSERT INTO session_message (id) VALUES ('msg_existing')`)
177: yield* db.run(sql`INSERT INTO session_input (id, session_id, time_created) VALUES ('msg_input', 'ses_existing', 0)`)
178:
179: yield* ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect } from "bun:test"
2: import { DateTime, Deferred, Effect, Exit, Fiber, Schema } from "effect"
3: import { TestClock } from "effect/testing"
4: import { asc, eq, sql } from "drizzle-orm"
5: import { Database } from "@opencode-ai/core/database/database"
6: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
7: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
8: import { EventV2 } from "@opencode-ai/core/event"
9: import { EventTable } from "@opencode-ai/core/event/sql"
10: import { ModelV2 } from "@opencode-ai/core/model"
11: import { Project } from "@opencode-ai/core/project"
12: import { ProjectTable } from "@opencode-ai/core/project/sql"
13: import { ProviderV2 } from "@opencode-ai/core/provider"
14: import { AbsolutePath } from "@opencode-ai/core/schema"
15: import { SessionV2 } from ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
879: it.effect("quarantines a continuation that belongs to another Session", () =>
880: Effect.gen(function* () {
881: const { db } = yield* Database.Service
882: const events = yield* EventV2.Service
883: const other = SessionV2.ID.make("ses_recovery_continuation_other")
884: yield* db
885: .insert(ProjectTable)
886: .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
887: .run()
888: yield* db
889: .insert(SessionTable)
890: .values(
891: [sessionID, other].map((id) => ({
892: id,
893: project_id: Project.ID.global,
894: slug: id,
895: directory: "/project",
896: title: "test",
897: version: "test",
898: })),
899: )
900: .run()
901: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/opencode/src/server/routes/instance/httpapi/server.ts</path>
<type>file</type>
<content>
1: import { Config as EffectConfig, Context, Effect, Layer } from "effect"
2: import { HttpApiBuilder, OpenApi } from "effect/unstable/httpapi"
3: import { HttpClient, HttpMiddleware, HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http"
4: import * as Socket from "effect/unstable/socket/Socket"
5: import { FSUtil } from "@opencode-ai/core/fs-util"
6: import * as Observability from "@opencode-ai/core/observability"
7: import { Account } from "@/account/account"
8: import { Agent } from "@/agent/agent"
9: import { Auth } from "@/auth"
10: import { BackgroundJob } from "@/background/job"
11: import { Command } from "@/command"
12: import { Config } from "@/config/config"
13: import { Workspace } from "@/control-plane/workspace"
14: import { Env } from "@/env"
15: import { EventV2Bridge } from "@/event-v2-bridge"
16: import { ...
<path>/tmp/opencode/session-recovery-v10-review/packages/opencode/src/server/routes/instance/httpapi/server.ts</path>
<type>file</type>
<content>
180: Layer.provide(PluginPtyEnvironment.layer),
181: Layer.provide([serverHttpApiAuthLayer, v2SchemaErrorLayer]),
182: )
183:
184: // `OpenApi.fromApi` is non-trivial; defer until /doc is actually hit so
185: // processes that never serve it (CLI, scripts) don't pay at module load.
186: // `HttpServerResponse.jsonUnsafe` runs JSON.stringify eagerly, so caching
187: // the response also caches the serialized body — every /doc request reuses
188: // the same Uint8Array instead of re-stringifying the spec.
189: const docResponse = lazy(() => HttpServerResponse.jsonUnsafe(OpenApi.fromApi(PublicApi)))
190:
191: const docRoute = HttpRouter.use((router) => router.add("GET", "/doc", () => Effect.succeed(docResponse()))).pipe(
192: Layer.provide(authOnlyRouterLayer),
193: )
194:
195: const uiRoute = HttpRouter.use((router) =>
196: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/server/src/routes.ts</path>
<type>file</type>
<content>
1: import { Database } from "@opencode-ai/core/database/database"
2: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
3: import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
4: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
5: import { EventV2 } from "@opencode-ai/core/event"
6: import { Credential } from "@opencode-ai/core/credential"
7: import { PermissionSaved } from "@opencode-ai/core/permission/saved"
8: import { PtyTicket } from "@opencode-ai/core/pty/ticket"
9: import { SessionV2 } from "@opencode-ai/core/session"
10: import { SessionExecution } from "@opencode-ai/core/session/execution"
11: import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
12: import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
13: import { SessionRecovery } from ...
Found 2 matches
/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts:
Line 249: run: (claim, force, recoveryInputID) =>
Line 250: sessionRunner.run({ ...claim, force, recoveryInputID }).pipe(
Found 100 matches (more matches available)
/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts:
Line 35: import { SessionRecovery } from "@opencode-ai/core/session/recovery"
Line 100: }) as unknown as LLMClientShape["stream"],
Line 111: const recoveryModel = Model.make({
Line 112: id: "recovery",
Line 249: run: (claim, force, recoveryInputID) =>
Line 250: sessionRunner.run({ ...claim, force, recoveryInputID }).pipe(
Line 322: owner_id: "test",
Line 331: owner_id: "test",
Line 389: currentModel = recoveryModel
Line 433: readonly expectedAssistant: unknown
Line 434: readonly expectedContent: unknown
Line 495: error: { type: "unknown", message: "Tool execution interrupted" },
Line 505: error: { type: "unknown", message: "Provider Step ended without settlement" },
Line 566: error: { type: "unknown", message: "Provider unavailable" },
Line 577: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
300: .insert(SessionTable)
301: .values({
302: id,
303: project_id: Project.ID.global,
304: slug: id,
305: directory: "/project",
306: title: "test",
307: version: "test",
308: })
309: .onConflictDoNothing()
310: .run()
311: .pipe(Effect.orDie)
312: })
313:
314: const insertExecution = (id: SessionV2.ID) =>
315: Effect.gen(function* () {
316: const { db } = yield* Database.Service
317: yield* db
318: .insert(SessionExecutionTable)
319: .values({
320: id: executionID,
321: session_id: id,
322: owner_id: "test",
323: phase: "ready",
324: time_created: Date.now(),
325: expires_at: Date.now() + 60_000,
326: })
327: .onConflictDoUpdate({
328: target: SessionExecutionTable.session_id,
329: ...
Found 5 matches
/tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts:
Line 2256: it.effect("projects staged, cleared, and committed reverts", () =>
Line 2283: revert: { messageID: boundary, snapshot: Snapshot.ID.make("tree"), diff: "patch", files: [] },
Line 2285: expect((yield* db.select({ revert: SessionTable.revert }).from(SessionTable).get())?.revert).toMatchObject({
Line 2291: expect((yield* db.select({ revert: SessionTable.revert }).from(SessionTable).get())?.revert).toBeNull()
Line 2295: revert: { messageID: boundary, files: [] },
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1850: .select({ attempts: SessionTable.wake_attempts })
1851: .from(SessionTable)
1852: .where(sql`${SessionTable.id} IN (${sql.join(ordinarySessions.map((id) => sql`${id}`), sql`, `)})`)
1853: .all(),
1854: ).toEqual([{ attempts: 2 }, { attempts: 2 }])
1855: }),
1856: )
1857:
1858: it.effect("bounds each deterministic recovery scan", () =>
1859: Effect.gen(function* () {
1860: const { db } = yield* Database.Service
1861: const ids = Array.from({ length: SessionRecovery.BATCH_SIZE + 1 }, (_, index) =>
1862: SessionV2.ID.make(`ses_recovery_batch_${index.toString().padStart(3, "0")}`),
1863: )
1864: yield* db
1865: .insert(ProjectTable)
1866: .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
1867: ...
Found 33 matches /tmp/opencode/session-recovery-final-v10.patch: Line 1: diff --git a/packages/core/schema.json b/packages/core/schema.json Line 881: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts Line 892: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts Line 956: diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts Line 1058: diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts Line 1090: diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts Line 1179: diff --git a/packages/core/src/session/context-epoch.ts b/packages/core/src/session/context-epoch.ts Line 1355: diff --git a/packages/core/src/session/execution/local.ts b/packages/core/src/session/execution/local.ts Line 1542: diff --git ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-prompt.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect } from "bun:test"
2: import { DateTime, Effect, Fiber, Layer, Stream } from "effect"
3: import { asc, eq } from "drizzle-orm"
4: import { Database } from "@opencode-ai/core/database/database"
5: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
6: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
7: import { EventV2 } from "@opencode-ai/core/event"
8: import { EventTable } from "@opencode-ai/core/event/sql"
9: import { SessionEvent } from "@opencode-ai/core/session/event"
10: import { Project } from "@opencode-ai/core/project"
11: import { ProjectTable } from "@opencode-ai/core/project/sql"
12: import { AbsolutePath } from "@opencode-ai/core/schema"
13: import { SessionV2 } from "@opencode-ai/core/session"
14: import { Prompt } from "@opencode-ai/core/session/prompt"
15: import { SessionMessage ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-prompt.test.ts</path>
<type>file</type>
<content>
330: it.effect("never wakes execution when retrying an already promoted prompt", () =>
331: Effect.gen(function* () {
332: yield* setup
333: const { db } = yield* Database.Service
334: const session = yield* SessionV2.Service
335: const events = yield* EventV2.Service
336: const pendingID = SessionMessage.ID.create()
337: yield* session.prompt({
338: sessionID,
339: id: messageID,
340: prompt: Prompt.make({ text: "Promote this prompt" }),
341: })
342: yield* SessionInput.promoteSteers(db, events, sessionID, Number.MAX_SAFE_INTEGER)
343: yield* session.prompt({
344: sessionID,
345: id: pendingID,
346: prompt: Prompt.make({ text: "Keep this prompt pending" }),
347: resume: false,
348: })
349: wakeCalls.length = 0
350:
351: yield* ...
No files found
/tmp/opencode/session-recovery-v10-review/packages/core/src/event.ts
Found 13 matches
/tmp/opencode/session-recovery-v10-review/packages/core/src/event.ts:
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 357: (wake) => PubSub.publish(wake, undefined),
Line 369: function publishEvent<D extends Definition>(definition: D, event: Payload<D>, commit?: PublishOptions["commit"]) {
Line 414: if (typed) yield* PubSub.publish(typed, event)
Line 415: yield* PubSub.publish(pubsub.all, event)
Line 419: function publish<D extends Definition>(definition: D, data: Data<D>, options?: PublishOptions) {
Line 427: return yield* publishEvent(
Line 443: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/event.ts</path>
<type>file</type>
<content>
110: export class SubscriberOverflowError extends Schema.TaggedErrorClass<SubscriberOverflowError>()(
111: "EventV2.SubscriberOverflow",
112: { capacity: Schema.Int },
113: ) {}
114:
115: export const define = Event.define
116: export const versionedType = Event.versionedType
117:
118: export interface PublishOptions {
119: readonly id?: ID
120: readonly metadata?: Record<string, unknown>
121: readonly location?: Location.Ref
122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
123: readonly commit?: (seq: number) => Effect.Effect<void>
124: }
125:
126: export interface Interface {
127: readonly publish: <D extends Definition>(
128: definition: D,
129: data: Data<D>,
130: options?: PublishOptions,
131: ) => Effect.Effect<Payload<D>>
132: readonly subscribe: <D extends ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/src/session/event.ts</path> <type>file</type> <content> 1: export * from "@opencode-ai/schema/session-event" 2: export * as SessionEvent from "@opencode-ai/schema/session-event" (End of file - total 2 lines) </content>
<path>/tmp/opencode/session-recovery-v10-review/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
1: export * as SessionEvent from "./session-event"
2:
3: import { Schema } from "effect"
4: import { optional } from "./schema"
5: import { Event } from "./event"
6: import { ProviderMetadata, ToolContent } from "./llm"
7: import { Delivery } from "./session-delivery"
8: import { Model } from "./model"
9: import { DateTimeUtcFromMillis, NonNegativeInt, RelativePath } from "./schema"
10: import { FileAttachment, Prompt } from "./prompt"
11: import { SessionID } from "./session-id"
12: import { Location } from "./location"
13: import { SessionMessage } from "./session-message"
14: import { Revert } from "./revert"
15:
16: export { FileAttachment }
17:
18: export const Source = Schema.Struct({
19: start: NonNegativeInt,
20: end: NonNegativeInt,
21: text: Schema.String,
22: }).annotate({
23: identifier: "session.next.event.source",
24: })
25: export interface ...
Found 10 matches
/tmp/opencode/session-recovery-v10-review/packages/schema/src/session-event.ts:
Line 94: export const PromptAdmitted = Event.define({
Line 102: export type PromptAdmitted = typeof PromptAdmitted.Type
Line 104: export const PromptExecutionRequested = Event.define({
Line 112: export type PromptExecutionRequested = typeof PromptExecutionRequested.Type
Line 489: PromptAdmitted,
Line 490: PromptExecutionRequested,
Line 499: Step.Recovered,
Line 523: PromptAdmitted,
Line 524: PromptExecutionRequested,
Line 533: Step.Recovered,
<path>/tmp/opencode/session-recovery-v10-review/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
470: export namespace RevertEvent {
471: export const Staged = Event.define({
472: type: "session.next.revert.staged",
473: ...options,
474: schema: { ...Base, revert: Revert.State },
475: })
476: export const Cleared = Event.define({ type: "session.next.revert.cleared", ...options, schema: Base })
477: export const Committed = Event.define({
478: type: "session.next.revert.committed",
479: ...options,
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: ...
Found 8 matches
/tmp/opencode/session-recovery-v10-review/packages/sdk/openapi.json:
Line 17899: "requestExecution": {
Line 18289: "enum": ["session.next.step.recovered"]
Line 25709: "requestExecution": {
Line 26299: "enum": ["session.next.step.recovered.1"]
Line 28599: "requestExecution": {
Line 29190: "enum": ["session.next.step.recovered"]
Line 34811: "requestExecution": {
Line 35201: "enum": ["session.next.step.recovered"]
<path>/tmp/opencode/session-recovery-v10-review/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
3080: export type MoveSessionDestination = {
3081: directory: string
3082: }
3083:
3084: export type ModelRef = {
3085: id: string
3086: providerID: string
3087: variant?: string
3088: }
3089:
3090: export type LocationRef = {
3091: directory: string
3092: workspaceID?: string
3093: }
3094:
3095: export type PromptSource = {
3096: start: number
3097: end: number
3098: text: string
3099: }
3100:
3101: export type PromptFileAttachment = {
3102: uri: string
3103: mime: string
3104: name?: string
3105: description?: string
3106: source?: PromptSource
3107: }
3108:
3109: export type PromptAgentAttachment = {
3110: name: string
3111: source?: PromptSource
3112: }
3113:
3114: export type SessionErrorUnknown = {
3115: type: "unknown"
3116: message: string
3117: }
3118:
3119: export type LlmProviderMetadata = {
3120: [key: ...
Found 11 matches
/tmp/opencode/session-recovery-v10-review/packages/sdk/js/src/v2/gen/types.gen.ts:
Line 33: | EventSessionNextStepRecovered
Line 874: requestExecution?: boolean
Line 1659: | SyncEventSessionNextStepRecovered
Line 2794: | SessionNextStepRecovered
Line 2924: | SessionNextStepRecovered
Line 3430: requestExecution?: boolean
Line 3602: export type SyncEventSessionNextStepRecovered = {
Line 4362: requestExecution?: boolean
Line 4560: export type SessionNextStepRecovered = {
Line 6464: requestExecution?: boolean
Line 6581: export type EventSessionNextStepRecovered = {
<path>/tmp/opencode/session-recovery-v10-review/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
3570: id: string
3571: syncEvent: {
3572: type: "session.next.step.failed.2"
3573: id: string
3574: seq: number
3575: aggregateID: string
3576: data: {
3577: timestamp: number
3578: sessionID: string
3579: assistantMessageID: string
3580: error: SessionErrorUnknown
3581: }
3582: }
3583: }
3584:
3585: export type SyncEventSessionNextStepInterrupted = {
3586: type: "sync"
3587: id: string
3588: syncEvent: {
3589: type: "session.next.step.interrupted.1"
3590: id: string
3591: seq: number
3592: aggregateID: string
3593: data: {
3594: timestamp: number
3595: sessionID: string
3596: assistantMessageID: string
3597: error: SessionErrorUnknown
3598: }
3599: }
3600: }
3601:
3602: export type SyncEventSessionNextStepRecovered = {
3603: type: "sync"
3604: id: ...
<path>/tmp/opencode/session-recovery-v10-review/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 ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-run-coordinator.test.ts</path>
<type>file</type>
<content>
218: it.effect("interrupts active execution and clears its pending wake", () =>
219: Effect.scoped(
220: Effect.gen(function* () {
221: const started = yield* Deferred.make<void>()
222: const interrupted = yield* Deferred.make<void>()
223: let runs = 0
224: const coordinator = yield* SessionRunCoordinator.make({
225: drain: () =>
226: Effect.sync(() => ++runs).pipe(
227: Effect.andThen(Deferred.succeed(started, undefined)),
228: Effect.andThen(Effect.never),
229: Effect.onInterrupt(() => Deferred.succeed(interrupted, undefined)),
230: ),
231: })
232:
233: const resumed = yield* coordinator.run("session").pipe(Effect.forkChild)
234: yield* Deferred.await(started)
235: yield* ...
Found 17 matches
/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts:
Line 75: let runnerFinalizer = Effect.void
Line 251: Effect.ensuring(Effect.suspend(() => runnerFinalizer)),
Line 333: assistant_message_id: null,
Line 359: runnerFinalizer = Effect.void
Line 646: yield* SessionRecovery.recover()
Line 735: yield* SessionRecovery.recover()
Line 1571: if (execution?.phase === "continue") return yield* Effect.die("crash after Compaction.Ended")
Line 1576: expect(yield* db.select().from(SessionExecutionTable).get()).toMatchObject({ phase: "continue" })
Line 1586: yield* SessionRecovery.recover()
Line 2109: assistant_message_id: null,
Line 2144: if (execution?.phase === "continue") return yield* Effect.die("crash after reconciled tool Step")
Line 2257: if (execution?.phase === "continue") return yield* Effect.die("crash after settled ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1480: })
1481: yield* session.resume(sessionID)
1482:
1483: expect(requests).toHaveLength(2)
1484: expect(userTexts(requests[0])[0]).toContain(
1485: "<prior-summary>\n## Objective\n- Preserve the task\n</prior-summary>",
1486: )
1487: expect(userTexts(requests[0])[0]).toContain("Recent exact request")
1488: expect((yield* (yield* SessionStore.Service).context(sessionID))[0]).toMatchObject({
1489: type: "compaction",
1490: summary: "## Objective\n- Preserve the updated task",
1491: })
1492: }),
1493: )
1494:
1495: it.effect("keeps automatic compaction unknown until Compaction.Ended commits", () =>
1496: Effect.gen(function* () {
1497: yield* setup
1498: const session = yield* SessionV2.Service
1499: const { db } = yield* Database.Service
1500: response = ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1970: { type: "assistant", finish: "stop", content: [{ type: "text", id: "text-final", text: "Done" }] },
1971: ])
1972: }),
1973: )
1974:
1975: it.effect("never commits continuation before unresolved hosted tools are reconciled", () =>
1976: Effect.gen(function* () {
1977: yield* setup
1978: const session = yield* SessionV2.Service
1979: const { db } = yield* Database.Service
1980: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Mixed tools" }), resume: false })
1981: responses = [
1982: [
1983: LLMEvent.stepStart({ index: 0 }),
1984: LLMEvent.toolCall({ id: "call-local", name: "echo", input: { text: "mixed-local" } }),
1985: LLMEvent.toolCall({
1986: id: "call-hosted-pending",
1987: name: "web_search",
1988: input: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3500: [
3501: LLMEvent.stepStart({ index: 0 }),
3502: LLMEvent.toolCall({ id: "call-blocked", name: "blocked", input: {} }),
3503: LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
3504: LLMEvent.finish({ reason: "tool-calls" }),
3505: ],
3506: [
3507: LLMEvent.stepStart({ index: 0 }),
3508: LLMEvent.stepFinish({ index: 0, reason: "stop" }),
3509: LLMEvent.finish({ reason: "stop" }),
3510: ],
3511: ]
3512:
3513: yield* session.resume(sessionID)
3514:
3515: expect(requests).toHaveLength(2)
3516: expect(yield* session.context(sessionID)).toMatchObject([
3517: { type: "user", text: "Call blocked" },
3518: {
3519: type: "assistant",
3520: content: [
3521: { type: "tool", id: ...
<path>/tmp/opencode/session-recovery-final-v10.patch</path>
<type>file</type>
<content>
5480: + sessionID: other,
5481: + timestamp: created,
5482: + executionID: "shared-recovery-identity",
5483: + phase: "ready",
5484: + })
5485: +
5486: + const recovered = yield* events
5487: + .publish(SessionEvent.Step.Recovered, {
5488: + sessionID,
5489: + timestamp: DateTime.makeUnsafe(1),
5490: + executionID: "shared-recovery-identity",
5491: + phase: "ready",
5492: + })
5493: + .pipe(Effect.exit)
5494: +
5495: + expect(Exit.isFailure(recovered)).toBe(true)
5496: + expect(yield* db.select().from(SessionRecoveryTable).all()).toEqual([
5497: + expect.objectContaining({
5498: + session_id: other,
5499: + execution_id: "shared-recovery-identity",
5500: + continuation_message_id: continuation("shared-recovery-identity"),
5501: + }),
5502: + ])
5503: ...
<path>/tmp/opencode/session-recovery-v10-review/packages/schema/test/event-manifest.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import { FileSystem, Integration, Permission, Project, Reference, Session, Workspace } from "../src"
3: import { EventManifest } from "../src/event-manifest"
4: import { IdeEvent } from "../src/ide-event"
5: import { SessionEvent } from "../src/session-event"
6: import { SessionTodo } from "../src/session-todo"
7: import { SessionV1 } from "../src/session-v1"
8: import { WorkspaceEvent } from "../src/workspace-event"
9:
10: describe("public event manifest", () => {
11: test("owns the complete public event surface", () => {
12: expect(EventManifest.ServerDefinitions.length).toBe(61)
13: expect(EventManifest.Definitions.length).toBe(91)
14: expect(SessionV1.Event.Definitions).toEqual([
15: SessionV1.Event.Created,
16: SessionV1.Event.Updated,
17: SessionV1.Event.Deleted,
18: ...
warning: Not a git repository. Use --no-index to compare two paths outside a working tree
usage: git diff --no-index [<options>] <path> <path>
Diff output format options
-p, --patch generate patch
-s, --no-patch suppress diff output
-u generate patch
-U, --unified[=<n>] generate diffs with <n> lines context
-W, --[no-]function-context
generate diffs with <n> lines context
--raw generate the diff in raw format
--patch-with-raw synonym for '-p --raw'
--patch-with-stat synonym for '-p --stat'
--numstat machine friendly --stat
--shortstat output only the last line of --stat
-X, --dirstat[=<param1,param2>...]
output the distribution of relative amount of changes for each sub-directory
--cumulative synonym for --dirstat=cumulative
--dirstat-by-file[=<param1,param2>...]
...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner-recorded.test.ts</path>
<type>file</type>
<content>
1: import { HttpRecorder } from "@opencode-ai/http-recorder"
2: import { HttpRecorderInternal } from "@opencode-ai/http-recorder/internal"
3: import * as OpenAIChat from "@opencode-ai/llm/protocols/openai-chat"
4: import { Auth, LLMClient, RequestExecutor } from "@opencode-ai/llm/route"
5: import { Database } from "@opencode-ai/core/database/database"
6: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
7: import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
8: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
9: import { EventV2 } from "@opencode-ai/core/event"
10: import { EventTable } from "@opencode-ai/core/event/sql"
11: import { PermissionV2 } from "@opencode-ai/core/permission"
12: import { AgentV2 } from "@opencode-ai/core/agent"
13: import { Config } from ...
No files found
<path>/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner-tool-events.test.ts</path>
<type>file</type>
<content>
1: import { expect, test } from "bun:test"
2: import { Effect, Schema, Stream } from "effect"
3: import { LLMEvent } from "@opencode-ai/llm"
4: import { EventV2 } from "@opencode-ai/core/event"
5: import { SessionEvent } from "@opencode-ai/core/session/event"
6: import { SessionMessage } from "@opencode-ai/core/session/message"
7: import { SessionV2 } from "@opencode-ai/core/session"
8: import { ModelV2 } from "@opencode-ai/core/model"
9: import { ProviderV2 } from "@opencode-ai/core/provider"
10: import { createLLMEventPublisher } from "@opencode-ai/core/session/runner/publish-llm-event"
11:
12: const sessionID = SessionV2.ID.make("ses_tool_event_test")
13: const base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB"
14:
15: const capture = () => {
16: const published: Array<{ readonly type: string; readonly data: unknown }> = []
17: const events = ...
Found 42 matches
/tmp/opencode/session-recovery-v10-review/packages/core/src/session/recovery.ts:
Line 267: wakeAttempts: SessionRecoveryTable.wake_attempts,
Line 274: gt(SessionRecoveryTable.wake_attempts, 0),
Line 275: lte(SessionRecoveryTable.wake_attempts, MAX_WAKE_ATTEMPTS),
Line 279: desc(sql`${SessionRecoveryTable.wake_attempts} = ${MAX_WAKE_ATTEMPTS}`),
Line 280: asc(SessionRecoveryTable.wake_attempts),
Line 292: wakeAttempts: SessionRecoveryTable.wake_attempts,
Line 299: eq(SessionRecoveryTable.wake_attempts, 0),
Line 315: wakeAttempts: SessionRecoveryTable.wake_attempts,
Line 322: gt(SessionRecoveryTable.wake_attempts, 0),
Line 323: lte(SessionRecoveryTable.wake_attempts, MAX_WAKE_ATTEMPTS),
Line 327: desc(sql`${SessionRecoveryTable.wake_attempts} = ${MAX_WAKE_ATTEMPTS}`),
Line 328: asc(SessionRecoveryTable.wake_attempts),
...
No files found
Found 49 matches /tmp/opencode/session-recovery-v10-review/packages/core/schema.json: Line 66: "name": "session_execution", Line 78: "name": "session_recovery", Line 941: "table": "session_execution" Line 951: "table": "session_execution" Line 961: "table": "session_execution" Line 971: "table": "session_execution" Line 979: "name": "phase", Line 981: "table": "session_execution" Line 991: "table": "session_execution" Line 1001: "table": "session_execution" Line 1009: "name": "recovery_error", Line 1011: "table": "session_execution" Line 1171: "table": "session_recovery" Line 1181: "table": "session_recovery" Line 1191: "table": "session_recovery" Line 1201: "table": "session_recovery" Line 1209: "name": "phase", Line 1211: "table": "session_recovery" Line 1221: "table": "session_recovery" Line 1231: "table": ...
<path>/tmp/opencode/session-recovery-v10-review/packages/core/schema.json</path>
<type>file</type>
<content>
2520: {
2521: "columns": [
2522: {
2523: "value": "session_id",
2524: "isExpression": false
2525: }
2526: ],
2527: "isUnique": false,
2528: "where": null,
2529: "origin": "manual",
2530: "name": "todo_session_idx",
2531: "entityType": "indexes",
2532: "table": "todo"
2533: },
2534: {
2535: "value": "\"phase\" IN ('ready', 'safe', 'unknown', 'continue', 'cancelling')",
2536: "name": "session_execution_phase_check",
2537: "entityType": "checks",
2538: "table": "session_execution"
2539: },
2540: {
2541: "value": "\"wake_attempts\" >= 0 AND \"wake_attempts\" <= 9007199254740991",
2542: "name": "session_recovery_wake_attempts_check",
2543: "entityType": "checks",
2544: "table": "session_recovery"
2545: },
2546: {
2547: ...
error: patch failed: packages/core/test/session-projector.test.ts:65 error: packages/core/test/session-projector.test.ts: patch does not apply error: patch failed: packages/core/src/session/recovery.ts:1 error: packages/core/src/session/recovery.ts: patch does not apply
<path>/tmp/opencode/session-recovery-final-v10.patch</path>
<type>file</type>
<content>
8200: Effect.gen(function* () {
8201: yield* setup
8202: const session = yield* SessionV2.Service
8203: + const { db } = yield* Database.Service
8204: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail hosted tool at EOF" }), resume: false })
8205: response = [
8206: LLMEvent.stepStart({ index: 0 }),
8207: @@ -3315,8 +4280,14 @@ describe("SessionRunnerLLM", () => {
8208:
8209: expect(yield* session.context(sessionID)).toMatchObject([
8210: { type: "user", text: "Fail hosted tool at EOF" },
8211: - { type: "assistant", content: [{ type: "tool", id: "call-hosted-eof", state: { status: "error" } }] },
8212: + {
8213: + type: "assistant",
8214: + finish: "error",
8215: + error: { type: "unknown", message: "Provider Step ended without settlement" },
8216: + content: [{ ...
Found 4 matches /tmp/opencode/session-recovery-final-v10.patch: Line 3778: import * as DatabasePath from "../database/path" Line 6892: import * as SessionRunnerLLM from "@opencode-ai/core/session/runner/llm" Line 7029: import * as SessionRunnerLLM from "@opencode-ai/core/session/runner/llm" Line 8230: import * as SessionExecutionLocal from "@opencode-ai/core/session/execution/local"
ab9408c81cc9916177641ae8e9f08624fc27b5b9.07b95915924ccf5dcfdf7ea5eb45d6cf972a6058664518935154dff9934222b21117176c4cfbf77527216b4161b54f8d3918ca7f7415747117c24f94d8e0b090None.
packages/core/src/session/projector.ts:399-466 treats an existing recovery row as idempotent only when all identity fields match:
Any mismatch defects at packages/core/src/session/projector.ts:422.
After projection failure, packages/core/src/session/recovery.ts:187-210 independently reloads the stored row by Session and execution and suppresses quarantine only if the complete identity matches:
Otherwise, packages/core/src/session/recovery.ts:211-247 quarantines only the exact Session, execution, and recovery owner. If the update affects zero rows, lines 228-245 prove that the row was deleted or ownership transferred; retaining the same owner without quarantine defects instead of failing open.
The regression at packages/core/test/session-projector.test.ts:1251-1303 reaches the required combined state:
continue at lines 1270-1279. ...Date: Sep 16, 2026 * 🟡 (18:25) `/tmp/opencode/session-recovery-v10-review/packages/schema/test/event-manifest.test.ts` test `"owns the complete public event surface"` asserts `EventManifest.ServerDefinitions.length === 61`, `EventManifest.Definitions.length === 91`, `EventManifest.Latest.size === 91`, and `EventManifest.Durable.size === 38`. * 🟡 (18:25) The same event-manifest test asserts the …
Date: Sep 16, 2026 * 🔴 (18:24) User stated session execution must never mark a failed `Step` safe while a sibling tool finalizer is active. * 🟡 (18:24) A continuation-direction test in `/tmp/opencode/session-recovery-v10-review/packages/core/test/session-runner.test.ts` starts a resumed stream, prompts `"Change direction"` while it is active, then verifies 3 provider requests: request 2 retains…
Date: Sep 16, 2026 * 🔴 (18:23) User stated session execution must never fail interrupted tools after ownership changes. * 🟡 (18:23) Test `"automatically compacts into a completed summary and retained recent turn"` verifies 2 model requests, each with headers `"x-session-affinity": sessionID` and `"X-Session-Id": sessionID`; the first generates summary `"## Objective\n- Preserve the task"`, and …
Date: Sep 16, 2026 * 🔴 (18:23) User reiterated that session recovery must never recover an expired cancellation claim. * 🔴 (18:23) User stated session execution must never advance the Context Epoch after ownership changes. * 🔴 (18:23) User stated session execution must never dispatch a provider after ownership changes. * 🔴 (18:23) User stated session execution must never publish streamed outp…
Date: Sep 16, 2026 * 🔴 (18:23) User stated session recovery must never wake work settled between selection and redrive CAS. * 🔴 (18:23) User stated session recovery must never recover a pre-promotion claim when input commits at the recovery boundary. * 🔴 (18:23) User stated session recovery must never promote a recovery superseded before scanner validation. * 🔴 (18:23) User reiterated that se…
Date: Sep 16, 2026 * 🔴 (18:22) User stated session recovery must never recover a live execution lease. * 🔴 (18:22) User stated session recovery must never recover an execution superseded by a newer assistant. * 🔴 (18:23) User stated a quarantined recovery continuation must never be promoted as an ordinary steer. * 🔴 (18:23) User stated a conflicting execution retry must never arm durable wake…
Date: Sep 16, 2026 * 🔴 (18:21) User stated ripgrep catch-all find results must never include git metadata. * 🔴 (18:21) User stated session execution must never start a second owner while the existing lease is live. * 🔴 (18:21) User stated session execution must never overwrite an expired lease. * 🔴 (18:21) User stated session execution must never re-enter its own uncertain live lease. * 🔴 (1…
Date: Sep 16, 2026 * 🔴 (18:19) User stated server-only dependencies should remain lazily loaded so “processes that never serve it (CLI, scripts) don't pay at module load.” * 🟡 (18:19) `/tmp/opencode/session-recovery-v10-review/packages/server/src/routes.ts` defines `applicationServices` with `Database.node`, `EventV2.node`, `httpClient`, `ToolOutputStore.cleanupNode`, `SessionV2.node`, `Session…
Date: Sep 16, 2026 * 🟡 (18:19) `packages/core/src/session/compaction.ts` configures automatic compaction defaults: `DEFAULT_BUFFER = 20_000`, `DEFAULT_KEEP_TOKENS = 8_000`, `TOOL_OUTPUT_MAX_CHARS = 2_000`, and `SUMMARY_OUTPUT_TOKENS = 4_096`; config documents can override `compaction.auto`, `compaction.buffer`, and `compaction.keep.tokens`. * 🟡 (18:19) `packages/core/src/session/compaction.ts` …
Date: Sep 16, 2026 * 🔴 (18:18) User directed that local ownership be replaced with durable multi-node ownership when clustered; this remains an unchecked TODO in `packages/core/src/session/runner/index.ts`. * 🟡 (18:18) `packages/core/src/session/runner/index.ts` states that V1 runtime-context parity must be tracked canonically in `specs/v2/session.md`. * 🟡 (18:18) `packages/core/src/session/ru…
Date: Sep 16, 2026 * 🔴 (18:17) User stated the recovery invariant: “A newer turn supersedes stale incomplete rows; never resume an older assistant projection.” * 🟡 (18:18) `packages/core/src/session/execution/local.ts:16-25` defines `make()` for current-process routing of implicit-local Locations; it generates a per-process `ownerID` with `crypto.randomUUID()` and converts `SESSION_EXECUTION_LE…
Date: Sep 16, 2026 * 🟡 (18:16) Schema inspection found `packages/core/src/session/sql.ts:23` defines `MAX_WAKE_ATTEMPTS = Number.MAX_SAFE_INTEGER`; checks on `SessionTable.wake_attempts` and `SessionRecoveryTable.wake_attempts` enforce the range `0..MAX_WAKE_ATTEMPTS`. * 🟡 (18:16) `packages/core/src/session/sql.ts:148-179` defines `SessionInputTable` with boolean `wake_pending` defaulting to `f…
Date: Sep 16, 2026 * 🔴 [requested-review] (18:12) User requested a read-only independent security/pentest review of frozen v10 at base commit `ab9408c81cc9916177641ae8e9f08624fc27b5b9`. * 🔴 (18:12) User specified patch `/tmp/opencode/session-recovery-final-v10.patch` with expected SHA-256 `07b95915924ccf5dcfdf7ea5eb45d6cf972a6058664518935154dff9934222b2`. * 🔴 (18:12) User specified SEA `/home/…