Dashboard › opencode › Session 0QQVjkTzALxT
0QQVjkTzALxTkvWJHPerform a complete independent read-only security/pentest review of the frozen interrupted-session recovery candidate. Do not edit files, run builds/tests, start services, or mutate VCS. The required repo-setup skill is unavailable; explicitly note that and read applicable AGENTS.md/package instructions directly. Review ONLY immutable base ab9408c81cc9916177641ae8e9f08624fc27b5b9 plus patch /tmp/opencode/session-recovery-final-v4.patch. First verify patch SHA-256 c2c8afb07a7b6a49c1d40c5515d718ad5a934c9291e80a068693259c26c566a0, SEA artifact /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server SHA-256 56c1bc48b6e21397db05eb0d41f73d243a4b64da093fcfbe209baed65457f6e8, and that the patch applies cleanly to the exact base. Inspect all 33 changed files from immutable Git/base+patch, not the mutable worktree. Threat-model duplicate provider/tool side effects, stale owners, live lease takeover, cancellation races, recovery identity forgery, cross-Session references, malformed/poison rows, public retry/replay abuse of recovery-owned IDs, admit-only leakage, queue/steer escape, wake starvation/DoS, cleanup defects, migration integrity, API contract drift, and startup multi-node behavior. Specifically verify: hosted provider-complete tools are terminal before continuation commit; latest-assistant revalidation happens immediately before recovery wake; bounded wake scans use durable fair scheduling so unchanged/failed first-page rows cannot starve later Sessions across restarts without making one pass unbounded; coordinator interruption cleanup failure cannot wedge active state or suppress a successor wake. Verify wake-attempt compare-and-set cannot wake settled rows, counters cannot be reset by public events/replay, and indexes support bounded scans. Confirm no SessionPrompt use and no Core-to-Server dependency. ...
The required repo-setup skill is unavailable in this session, so I will follow the fallback explicitly: read the applicable AGENTS.md, contributing guidance, and package scripts directly. I will verify the frozen hashes and exact-base applicability first, then inspect only the synthetic ab9408c… + patch tree and immutable Git objects; I will not read candidate code from the mutable worktree or run tests/builds/services.
[
{
"content": "Verify frozen base, patch hash, SEA hash, 33-file scope, and clean applicability to exact base",
"status": "in_progress",
"priority": "high"
},
{
"content": "Read applicable repository and package instructions without using mutable candidate source",
"status": "pending",
"priority": "high"
},
{
"content": "Construct and fingerprint a disposable exact base+patch inspection tree outside VCS",
"status": "pending",
"priority": "high"
},
{
"content": "Audit all 33 changed files and trace recovery security invariants end to end",
"status": "pending",
"priority": "high"
},
{
"content": "Report severity-ordered findings with frozen file:line evidence and exact verdict",
"status": "pending",
"priority": "high"
}
]
c2c8afb07a7b6a49c1d40c5515d718ad5a934c9291e80a068693259c26c566a0 /tmp/opencode/session-recovery-final-v4.patch 56c1bc48b6e21397db05eb0d41f73d243a4b64da093fcfbe209baed65457f6e8 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server
commit ab9408c81cc9916177641ae8e9f08624fc27b5b9 d3323b963b9ef2de41a7ff9884299c74b93886ba 2e9407768d1401a25b69febb36657638a779053a 1789377773 feat(app): adapt vertical tab density
527 48 packages/core/schema.json 1 0 packages/core/src/database/migration.gen.ts 55 0 packages/core/src/database/migration/20260914170650_session-recovery.ts 45 0 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 66 0 packages/core/src/session/execution/claim.ts 118 19 packages/core/src/session/execution/local.ts 342 31 packages/core/src/session/input.ts 69 0 packages/core/src/session/message-updater.ts 77 1 packages/core/src/session/projector.ts 6 0 packages/core/src/session/recovery-id.ts 315 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 207 90 packages/core/src/session/runner/llm.ts 74 28 packages/core/src/session/runner/publish-llm-event.ts 70 1 packages/core/src/session/sql.ts 63 1 packages/core/test/database-migration.test.ts 239 0 packages/core/test/session-execution-local....
/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/ui/AGENTS.md /home/byk/Code/opencode/packages/desktop/AGENTS.md /home/byk/Code/opencode/packages/llm/AGENTS.md /home/byk/Code/opencode/packages/stats/AGENTS.md /home/byk/Code/opencode/packages/core/src/tool/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/effect-drizzle-sqlite/AGENTS.md /home/byk/Code/opencode/packages/app/AGENTS.md /home/byk/Code/opencode/packages/app/e2e/AGENTS.md /home/byk/Code/opencode/packages/app/e2e/performance/AGENTS.md /home/byk/Code/opencode/packages/opencode/src/server/routes/instance/httpapi/AGENTS.md /home/byk/Code/opencode/packages/opencode/src/session/llm/AGENTS.md
/home/byk/Code/opencode/package.json /home/byk/Code/opencode/CONTRIBUTING.md /home/byk/Code/opencode/artifacts/glm52-rise-video/package.json /home/byk/Code/opencode/sdks/vscode/package.json /home/byk/Code/opencode/github/package.json /home/byk/Code/opencode/packages/codemode/package.json /home/byk/Code/opencode/packages/storybook/package.json /home/byk/Code/opencode/packages/slack/package.json /home/byk/Code/opencode/packages/cli/package.json /home/byk/Code/opencode/packages/session-ui/package.json /home/byk/Code/opencode/packages/sdk/js/package.json /home/byk/Code/opencode/packages/llm/package.json /home/byk/Code/opencode/packages/web/package.json /home/byk/Code/opencode/packages/http-recorder/package.json /home/byk/Code/opencode/packages/core/package.json /home/byk/Code/opencode/packages/ui/package.json /home/byk/Code/opencode/packages/sdk-next/package.json /home/byk/Code/opencode/packages/schema/package.json /home/byk/Code/opencode/packages/protocol/package.json /home/byk/Code/openc...
- To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`. - 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. - 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. - The default branch in this repo is `dev`. - Local `main` ref may not exist; use `dev` or `origin/dev` for diffs. ## Branch Names 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/`. Examples: `session-recovery`, `fix-scroll-state`, `regenerate-sdk`. ## Commits and PR Titles Use conventional commit-style messages and PR titles: `type(scope): summary`. ...
# Schema Package Guide `@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. ## Package Boundary - Preserve the dependency direction: `@opencode-ai/schema <- @opencode-ai/protocol <- @opencode-ai/server`. - Schema values should be serializable contract definitions, not service implementations or runtime registries. - 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. - The root barrel exports canonical current domain contracts. ...
{
"$schema": "https://json.schemastore.org/package.json",
"name": "opencode",
"description": "AI-powered development tool",
"private": true,
"type": "module",
"packageManager": "bun@1.3.14",
"scripts": {
"dev": "bun run --cwd packages/opencode src/index.ts",
"dev:desktop": "bun --cwd packages/desktop dev",
"dev:web": "bun --cwd packages/app dev",
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
"dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
"dev:storybook": "bun --cwd packages/storybook storybook",
"lint": "oxlint",
"typecheck": "bun turbo typecheck",
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
"postinstall": "bun run --cwd packages/core fix-node-pty",
"prepare": "husky",
"random": "echo 'Random script'",
"sso": "aws sso login --sso-session=opencode --no-browser",
"translate:app": "bun run script/translate-app.ts",
"test": ...
Both supplied hashes match exactly, and the patch declares exactly 33 changed files. The base resolves to the requested immutable commit; I have also read the base revision’s root, Schema, OpenCode, and HttpApi instructions plus affected package scripts, without consulting mutable candidate source.
drwxr-xr-x 4 byk byk 4096 Sep 16 10:08 /tmp/opencode
ls: cannot access '/tmp/opencode/session-recovery-review-c2c8afb0': No such file or directory
(no output)
(no output)
(no output)
[
{
"content": "Verify frozen base, patch hash, SEA hash, 33-file scope, and clean applicability to exact base",
"status": "completed",
"priority": "high"
},
{
"content": "Read applicable repository and package instructions without using mutable candidate source",
"status": "completed",
"priority": "high"
},
{
"content": "Construct and fingerprint a disposable exact base+patch inspection tree outside VCS",
"status": "completed",
"priority": "high"
},
{
"content": "Audit all 33 changed files and trace recovery security invariants end to end",
"status": "in_progress",
"priority": "high"
},
{
"content": "Report severity-ordered findings with frozen file:line evidence and exact verdict",
"status": "pending",
"priority": "high"
}
]
The patch applies cleanly to an archive of exactly ab9408c…; I applied it only inside /tmp/opencode/session-recovery-review-c2c8afb0, which has no VCS metadata. I am now tracing the production state machine first, then I will cross-check every generated artifact and test file for the claimed security invariants and missing adversarial coverage.
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/session/recovery.ts</path>
<type>file</type>
<content>
1: export * as SessionRecovery from "./recovery"
2:
3: import { and, asc, desc, eq, gt, isNull, lte, or, sql } from "drizzle-orm"
4: import { Clock, DateTime, Effect, Layer, Option, Schedule, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { makeGlobalNode } from "../effect/app-node"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionExecution } from "./execution"
10: import { SessionProjector } from "./projector"
11: import { SessionExecutionClaim } from "./execution/claim"
12: import { SessionInput } from "./input"
13: import { SessionSchema } from "./schema"
14: import {
15: SESSION_EXECUTION_LEASE_MS,
16: SessionExecutionTable,
17: SessionInputTable,
18: SessionRecoveryTable,
19: SessionMessageTable,
20: } from "./sql"
21:
22: export const BATCH_SIZE = 100
23: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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 ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
481: delivery: row.delivery,
482: },
483: commit ? { commit: () => commit(row) } : undefined,
484: )
485: .pipe(
486: Effect.catchDefect((defect) =>
487: defect instanceof LifecycleConflict
488: ? find(db, id).pipe(
489: Effect.flatMap((stored) => (stored?.promotedSeq === undefined ? Effect.die(defect) : Effect.void)),
490: )
491: : Effect.die(defect),
492: ),
493: )
494: }
495: return rows.length
496: })
497:
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* ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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-review-c2c8afb0/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
331: .pipe(Effect.orDie)
332: const previous = row && usage(row.data)
333: const next = usage(event.data.part)
334: if (previous) yield* applyUsage(db, row.session_id, previous, -1)
335: if (next) yield* applyUsage(db, sessionID, next)
336: }),
337: )
338: yield* events.project(SessionEvent.AgentSwitched, (event) =>
339: db
340: .update(SessionTable)
341: .set({ agent: event.data.agent, time_updated: DateTime.toEpochMillis(event.data.timestamp) })
342: .where(eq(SessionTable.id, event.data.sessionID))
343: .run()
344: .pipe(Effect.orDie, Effect.andThen(run(db, event))),
345: )
346: yield* events.project(SessionEvent.ModelSwitched, (event) =>
347: Effect.gen(function* () {
348: yield* db
349: .update(SessionTable)
350: ...
Found 100 matches (more matches available)
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/event.test.ts:
Line 422: it.effect("replays durable aggregate events after a sequence and tails new events", () =>
Line 442: it.effect("catches durable aggregate events published during replay handoff", () =>
Line 458: it.effect("retains a durable wake committed while historical replay is paused", () =>
Line 540: it.effect("replays durable events through projectors", () =>
Line 551: yield* events.replay({
Line 564: it.effect("replay inserts external event rows", () =>
Line 570: yield* events.replay({
Line 575: data: durableData(aggregateID, "replayed"),
Line 590: "replay rejects an envelope aggregate that differs from its payload without mutating the payload aggregate",
Line 606: .replay({
Line 611: data: durableData(payloadAggregateID, "replayed"),
Line 634: it.effect("replay defects ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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-review-c2c8afb0/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(`ALTER TABLE \`session_input\` ADD \`wake_attempts\` integer DEFAULT 0 NOT NULL;`)
10: yield* tx.run(`
11: CREATE TABLE \`session_execution\` (
12: \`id\` text NOT NULL,
13: \`session_id\` text NOT NULL,
14: \`owner_id\` text NOT NULL,
15: \`assistant_message_id\` text,
16: \`phase\` text NOT NULL,
17: \`time_created\` integer NOT NULL,
18: \`expires_at\` integer NOT NULL,
19: \`recovery_error\` ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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, FiberSet, 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-review-c2c8afb0/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
495: const run = Effect.fn("SessionRunner.run")(function* (input: {
496: readonly sessionID: SessionSchema.ID
497: readonly force: boolean
498: readonly executionID: string
499: readonly ownerID: string
500: readonly recoveryInputID?: SessionMessage.ID
501: }) {
502: const claim = { sessionID: input.sessionID, executionID: input.executionID, ownerID: input.ownerID }
503: const recovering = input.recoveryInputID !== undefined
504: const hasSteer = recovering ? false : yield* SessionInput.hasPending(db, input.sessionID, "steer")
505: const hasQueue = recovering || hasSteer ? false : yield* SessionInput.hasPending(db, input.sessionID, "queue")
506: if (!recovering && !input.force && !hasSteer && !hasQueue) return
507: yield* failInterruptedTools(input.sessionID, claim)
508: let ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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 terminalCommit: (assistantMessageID: SessionMessage.ID) => Effect.Effect<void>
16: readonly interruptedCommit: (assistantMessageID: SessionMessage.ID) => Effect.Effect<void>
17: readonly eventCommit: () ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
421: case "tool-error": {
422: const tool = tools.get(event.id)
423: if (!tool?.called) return yield* Effect.die(`Tool error before call: ${event.id}`)
424: if (tool.name !== event.name)
425: return yield* Effect.die(`Tool error name changed for ${event.id}: ${tool.name} -> ${event.name}`)
426: if (tool.settled) return yield* Effect.die(`Duplicate tool error: ${event.id}`)
427: tool.settled = true
428: yield* publishEvent(SessionEvent.Tool.Failed, {
429: sessionID: input.sessionID,
430: timestamp: yield* timestamp,
431: assistantMessageID: tool.assistantMessageID,
432: callID: event.id,
433: error: { type: "unknown", message: event.message },
434: provider: {
435: executed: tool.providerExecuted,
436: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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-review-c2c8afb0/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-review-c2c8afb0/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: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
321: castDraft(
322: SessionMessage.AssistantTool.make({
323: type: "tool",
324: id: event.data.callID,
325: name: event.data.name,
326: time: { created: event.data.timestamp },
327: state: SessionMessage.ToolStatePending.make({ status: "pending", input: "" }),
328: }),
329: ),
330: )
331: })
332: },
333: "session.next.tool.input.delta": () => Effect.void,
334: "session.next.tool.input.ended": (event) => {
335: return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
336: const match = latestTool(draft, event.data.callID)
337: if (match && match.state.status === "pending") match.state.input = event.data.text
338: })
339: },
340: ...
No files found
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/event.ts
Found 25 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/event.ts:
Line 122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
Line 123: readonly commit?: (seq: number) => Effect.Effect<void>
Line 205: function commitDurableEvent(
Line 214: commit?: (seq: number) => Effect.Effect<void>,
Line 239: const committed = yield* db
Line 240: .transaction(
Line 316: const committed = {
Line 321: yield* projector(committed)
Line 323: if (commit) yield* commit(seq)
Line 354: if (committed) {
Line 356: pubsub.durable.get(committed.aggregateID) ?? [],
Line 361: return committed
Line 369: function publishEvent<D extends Definition>(definition: D, event: Payload<D>, commit?: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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 ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/opencode/src/server/routes/instance/httpapi/server.ts</path> <type>file</type> <content> 235: Todo.node, 236: Session.node, 237: SessionProjector.node, 238: SessionStatus.node, 239: BackgroundJob.node, 240: RuntimeFlags.node, 241: EventV2Bridge.node, 242: SessionRunState.node, 243: SessionProcessor.node, 244: SessionCompaction.node, 245: SessionRevert.node, 246: SessionSummary.node, 247: SessionPrompt.node, 248: Instruction.node, 249: LLM.node, 250: LSP.node, 251: MCP.node, 252: McpAuth.node, 253: Command.node, 254: Truncate.node, 255: ToolRegistry.node, 256: Format.node, 257: Project.node, 258: Vcs.node, 259: Workspace.node, 260: Worktree.node, 261: Installation.node, 262: ShareNext.node, 263: SessionShare.node, 264: InstanceStore.node, 265: httpClient, 266: EventV2.node, 267: ProjectV2.node, 268: ProjectCopy.node, 269: PtyTicket.node, 270: ]) 271: 272: ...
Found 1 matches /tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/session/runner/llm.ts: Line 51: * `SessionPrompt` monolith. Implement the unchecked items in small reviewed slices:
No files found
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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 40 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/event.ts:
Line 480: function replayAll(
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/event.test.ts:
Line 551: yield* events.replay({
Line 570: yield* events.replay({
Line 606: .replay({
Line 639: yield* events.replay({
Line 647: .replay({
Line 671: yield* events.replay({
Line 687: .replay({
Line 704: const source = yield* events.replayAll([
Line 731: const one = yield* events.replayAll([
Line 747: const two = yield* events.replayAll([
Line 789: yield* events.replay(
Line 816: yield* events.replay(replayed, { ownerID: "owner-a" })
Line 818: const exit = yield* events.replay(replayed, { ownerID: "owner-b", strictOwner: true }).pipe(Effect.exit)
Line 838: yield* events.replay(replayed, { ownerID: "owner-a", strictOwner: true })
Line 848: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts</path>
<type>file</type>
<content>
1: import { Workspace } from "@/control-plane/workspace"
2: import * as InstanceState from "@/effect/instance-state"
3: import { Session } from "@/session/session"
4: import { Database } from "@opencode-ai/core/database/database"
5: import { EventV2 } from "@opencode-ai/core/event"
6: import { EventV2Bridge } from "@/event-v2-bridge"
7: import { EventTable } from "@opencode-ai/core/event/sql"
8: import { asc } from "drizzle-orm"
9: import { and } from "drizzle-orm"
10: import { eq } from "drizzle-orm"
11: import { lte } from "drizzle-orm"
12: import { not } from "drizzle-orm"
13: import { or } from "drizzle-orm"
14: import { Effect, Scope } from "effect"
15: import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi"
16: import { InstanceHttpApi } from "../api"
17: import { HistoryPayload, ReplayPayload, SessionPayload ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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 ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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 } ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/session.ts</path>
<type>file</type>
<content>
330: .from(SessionMessageTable)
331: .where(where)
332: .orderBy(order === "asc" ? asc(SessionMessageTable.seq) : desc(SessionMessageTable.seq))
333: const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe(
334: Effect.orDie,
335: )
336: return yield* Effect.forEach(direction === "previous" ? rows.toReversed() : rows, decode)
337: }),
338: message: Effect.fn("V2Session.message")(function* (input) {
339: const stored = yield* store.message(input.messageID)
340: return stored?.sessionID === input.sessionID ? stored.message : undefined
341: }),
342: context: Effect.fn("V2Session.context")(function* (sessionID) {
343: yield* result.get(sessionID)
344: return yield* store.context(sessionID)
345: }),
346: ...
Found 100 matches (more matches available)
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/config/provider-options.test.ts:
Line 29: timeout: 1000,
Line 40: settings: { timeout: 1000 },
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/config/plugin.test.ts:
Line 242: for (let attempt = 0; attempt < 100; attempt++) {
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/config/provider.test.ts:
Line 179: limit: { context: 100, output: 50 },
Line 256: expect(model.limit).toEqual({ context: 100, output: 75 })
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/config/config.test.ts:
Line 85: { numRuns: 100 },
Line 317: tool_output: { max_lines: 1000, max_bytes: 32768 },
Line 326: timeout: { request: 10000 },
Line 342: buffer: 10000,
Line 403: ...
Found 29 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-projector.test.ts:
Line 177: if (!replayedContinuation) return yield* Effect.die("Replayed recovery continuation not found")
Line 342: it.effect("never recovers an execution superseded by a newer assistant", () =>
Line 389: it.effect("redrives a durable recovery wake after admission", () =>
Line 440: it.effect("quarantines a malformed recovery row without blocking a valid wake", () =>
Line 465: executionID: "malformed-recovery-row",
Line 474: yield* db.run(sql`UPDATE session_input SET prompt = '{}' WHERE id = ${continuation("malformed-recovery-row")}`)
Line 488: .where(eq(SessionRecoveryTable.execution_id, "malformed-recovery-row"))
Line 494: it.effect("quarantines a full batch of poison recovery rows before redriving valid work", () =>
Line 498: const poisonCount = SessionRecovery.BATCH_SIZE + 1
Line 499: const ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
100: sessionID,
101: timestamp: created,
102: assistantMessageID: assistantID,
103: agent: "build",
104: model,
105: })
106:
107: yield* events.publish(
108: SessionEvent.Step.Recovered,
109: {
110: sessionID,
111: timestamp: DateTime.makeUnsafe(1),
112: executionID: "execution",
113: assistantMessageID: assistantID,
114: phase: "unknown",
115: },
116: {
117: commit: () =>
118: db.delete(SessionExecutionTable).where(eq(SessionExecutionTable.id, "execution")).run().pipe(Effect.orDie),
119: },
120: )
121:
122: const message = yield* db.select().from(SessionMessageTable).where(eq(SessionMessageTable.id, assistantID)).get()
123: if (!message) return yield* Effect.die("Recovered ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
835: it.effect("never recovers a pre-promotion claim when input commits at the recovery boundary", () =>
836: Effect.gen(function* () {
837: const { db } = yield* Database.Service
838: const events = yield* EventV2.Service
839: yield* db
840: .insert(ProjectTable)
841: .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
842: .run()
843: yield* db
844: .insert(SessionTable)
845: .values({
846: id: sessionID,
847: project_id: Project.ID.global,
848: slug: "test",
849: directory: "/project",
850: title: "test",
851: version: "test",
852: })
853: .run()
854: yield* db
855: .insert(SessionExecutionTable)
856: .values({
857: id: ...
Found 14 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-projector.test.ts:
Line 177: if (!replayedContinuation) return yield* Effect.die("Replayed recovery continuation not found")
Line 221: it.effect("records a recovery continuation without forging an assistant identity", () =>
Line 342: it.effect("never recovers an execution superseded by a newer assistant", () =>
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 654: executionID: "cross-session-continuation",
Line 658: sql`UPDATE session_input SET session_id = ${other} WHERE id = ${continuation("cross-session-continuation")}`,
Line 674: recovery_error: "Invalid recovery continuation input",
Line ...
No files found
Found 66 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-runner.test.ts:
Line 35: import { SessionRecovery } from "@opencode-ai/core/session/recovery"
Line 110: const recoveryModel = Model.make({
Line 111: id: "recovery",
Line 248: run: (claim, force, recoveryInputID) => sessionRunner.run({ ...claim, force, recoveryInputID }),
Line 384: currentModel = recoveryModel
Line 598: it.effect("redrives only the synthetic recovery continuation", () =>
Line 604: const unrelatedID = SessionMessage.ID.make("msg_recovery_unrelated")
Line 615: executionID: "lost-recovery-wake",
Line 653: prompt: Prompt.make({ text: "ordinary work after replayed recovery" }),
Line 662: expect(userTexts(requests[1]!)).toContain("ordinary work after replayed recovery")
Line 1379: yield* events.publish(SessionEvent.Compaction.Ended, {
Line 1474: it.effect("keeps automatic compaction unknown until ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
580: ),
581: ).toBe(true)
582: expect(yield* session.context(sessionID)).toMatchObject([
583: { type: "user", text: prompt },
584: {
585: type: "assistant",
586: finish: "error",
587: error: { type: "unknown", message: "Provider Step outcome unknown after interruption" },
588: content: [
589: kind === "tool input"
590: ? { type: "tool", id: fragmentID(kind, "interrupted"), state: { status: "error" } }
591: : fixture.expectedContent,
592: ],
593: },
594: ])
595: })
596:
597: describe("SessionRunnerLLM", () => {
598: it.effect("redrives only the synthetic recovery continuation", () =>
599: Effect.gen(function* () {
600: yield* setup
601: const session = yield* SessionV2.Service
602: const events = yield* EventV2.Service
603: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1930: expect(yield* session.context(sessionID)).toMatchObject([
1931: { type: "user", text: "Echo this" },
1932: {
1933: type: "assistant",
1934: finish: "tool-calls",
1935: content: [
1936: {
1937: type: "tool",
1938: id: "call-echo",
1939: name: "echo",
1940: state: {
1941: status: "completed",
1942: input: { text: "hello" },
1943: structured: { text: "hello" },
1944: content: [{ type: "text", text: "hello" }],
1945: },
1946: },
1947: ],
1948: },
1949: { type: "assistant", finish: "stop", content: [{ type: "text", id: "text-final", text: "Done" }] },
1950: ])
1951: }),
1952: )
1953:
1954: it.effect("never ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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 = ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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 18 matches
/tmp/opencode/session-recovery-review-c2c8afb0/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'))
Line 180: \`wake_pending\` integer DEFAULT false NOT NULL,
Line 181: \`wake_attempts\` integer DEFAULT 0 NOT NULL,
Line 199: CREATE TABLE \`session_recovery\` (
Line 205: \`wake_pending\` integer DEFAULT true NOT NULL,
Line 206: \`wake_attempts\` integer DEFAULT 0 NOT NULL,
Line 209: CONSTRAINT \`session_recovery_pk\` PRIMARY KEY(\`execution_id\`),
Line 210: CONSTRAINT ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/database/schema.gen.ts</path> <type>file</type> <content> 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 \`fk_session_context_epoch_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE 155: ); 156: `) 157: yield* ...
Found 8 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/database-migration.test.ts:
Line 19: import sessionRecoveryMigration from "@opencode-ai/core/database/migration/20260914170650_session-recovery"
Line 186: sql`SELECT "notnull" AS required FROM pragma_table_info('session_execution') WHERE name = 'id'`,
Line 191: sql`SELECT dflt_value AS value FROM pragma_table_info('session_input') WHERE name = 'wake_pending'`,
Line 196: sql`SELECT pk, "notnull" AS required FROM pragma_table_info('session_recovery') WHERE name = 'execution_id'`,
Line 201: sql`SELECT "notnull" AS required FROM pragma_table_info('session_recovery') WHERE name = 'assistant_message_id'`,
Line 206: sql`SELECT "notnull" AS required FROM pragma_table_info('session_recovery') WHERE name = 'phase'`,
Line 211: sql`SELECT "notnull" AS required FROM pragma_table_info('session_recovery') WHERE name = ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/database-migration.test.ts</path>
<type>file</type>
<content>
110: await Effect.runPromise(
111: Effect.all(
112: layers.map((layer) => Effect.scoped(Layer.build(layer))),
113: { concurrency: "unbounded" },
114: ),
115: )
116: })
117: if (process.platform === "linux") {
118: test("declared schema has no ungenerated migrations", async () => {
119: const result = await $`bun ${fileURLToPath(new URL("../script/migration.ts", import.meta.url))} --check`
120: .quiet()
121: .nothrow()
122: expect(result.exitCode, result.stderr.toString()).toBe(0)
123: expect(result.stdout.toString()).toContain("No schema changes, nothing to migrate")
124: }, 30_000)
125: }
126:
127: test("applies tracked migrations to an empty database", async () => {
128: await run(
129: Effect.gen(function* () {
130: const db = yield* makeDb
131: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/opencode/src/server/routes/instance/httpapi/groups/sync.ts</path>
<type>file</type>
<content>
1: import { NonNegativeInt } from "@opencode-ai/core/schema"
2: import { EventV2 } from "@opencode-ai/core/event"
3: import { SessionID } from "@/session/schema"
4: import { Schema } from "effect"
5: import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
6: import { Authorization } from "../middleware/authorization"
7: import { InstanceContextMiddleware } from "../middleware/instance-context"
8: import { WorkspaceRoutingMiddleware, WorkspaceRoutingQuery } from "../middleware/workspace-routing"
9: import { described } from "./metadata"
10:
11: const root = "/sync"
12: export const ReplayEvent = Schema.Struct({
13: id: EventV2.ID,
14: aggregateID: Schema.String,
15: seq: NonNegativeInt,
16: type: Schema.String,
17: data: Schema.Record(Schema.String, Schema.Unknown),
18: ...
No files found
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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>
No files found
/tmp/opencode/session-recovery-review-c2c8afb0/packages/schema/src/event-manifest.ts /tmp/opencode/session-recovery-review-c2c8afb0/packages/schema/src/durable-event-manifest.ts
Found 3 matches /tmp/opencode/session-recovery-review-c2c8afb0/packages/schema/src/session-event.ts: Line 221: type: "session.next.step.recovered", Line 499: Step.Recovered, Line 533: Step.Recovered,
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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: ...
Found 15 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/sdk/js/src/v2/gen/types.gen.ts:
Line 33: | EventSessionNextStepRecovered
Line 874: requestExecution?: boolean
Line 983: type: "session.next.step.recovered"
Line 1659: | SyncEventSessionNextStepRecovered
Line 2794: | SessionNextStepRecovered
Line 2924: | SessionNextStepRecovered
Line 3430: requestExecution?: boolean
Line 3602: export type SyncEventSessionNextStepRecovered = {
Line 3606: type: "session.next.step.recovered.1"
Line 4362: requestExecution?: boolean
Line 4560: export type SessionNextStepRecovered = {
Line 4565: type: "session.next.step.recovered"
Line 6464: requestExecution?: boolean
Line 6581: export type EventSessionNextStepRecovered = {
Line 6583: type: "session.next.step.recovered"
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
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: string
3605: syncEvent: {
3606: type: "session.next.step.recovered.1"
3607: id: string
3608: seq: number
3609: aggregateID: string
3610: data: {
3611: timestamp: number
3612: sessionID: string
3613: executionID: string
3614: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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-review-c2c8afb0/packages/core/src/location-service-map.ts</path>
<type>file</type>
<content>
1: import { Context, Effect, Layer, LayerMap } from "effect"
2: import { LayerNode } from "./effect/layer-node"
3: import { Node } from "./effect/app-node"
4: import { Location } from "./location"
5: import type { LocationError, LocationServices } from "./location-services"
6:
7: export class Service extends Context.Service<
8: Service,
9: LayerMap.LayerMap<Location.Ref, LocationServices, LocationError>
10: >()("@opencode/example/LocationServiceMap") {
11: static get(ref: Location.Ref) {
12: return Layer.unwrap(Effect.map(Service, (locations) => locations.get(ref)))
13: }
14: }
15:
16: export const node = LayerNode.unbound(Service, Node.tags.values.global)
17:
18: export * as LocationServiceMap from "./location-service-map"
(End of file - total 18 lines)
</content>
Found 4 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/location-services.ts:
Line 84: export function buildLocationServiceMap(
Line 115: export const locationServiceMapLayer = buildLocationServiceMap()
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/effect/app-node-builder.ts:
Line 1: import { buildLocationServiceMap } from "../location-services"
Line 11: const locationMap = buildLocationServiceMap(replacements)
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/location-services.ts</path>
<type>file</type>
<content>
60: SkillV2.node,
61: SystemContextRegistry.node,
62: SystemContextBuiltIns.node,
63: LocationMutation.node,
64: FileMutation.node,
65: PermissionV2.node,
66: ToolOutputStore.node,
67: ToolRegistry.node,
68: ToolRegistry.toolsNode,
69: Image.node,
70: SkillGuidance.node,
71: ReferenceGuidance.node,
72: SessionTodo.node,
73: QuestionV2.node,
74: ReadToolFileSystem.node,
75: BuiltInTools.node,
76: SessionRunnerModel.node,
77: Snapshot.node,
78: SessionRunnerLLM.node,
79: ])
80:
81: export type LocationServices = LayerNode.Output<typeof locationServices>
82: export type LocationError = LayerNode.Error<typeof locationServices>
83:
84: export function buildLocationServiceMap(
85: replacements: LayerNode.Replacements = [],
86: ): Layer.Layer<LocationServiceMap.Service> {
87: return Layer.effect(
88: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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. ...
Found 56 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-prompt.test.ts:
Line 19: import { SessionRecovery } from "@opencode-ai/core/session/recovery"
Line 26: const wakeCalls: SessionV2.ID[] = []
Line 40: wake: (sessionID) =>
Line 42: wakeCalls.push(sessionID)
Line 134: wake: (id) => Effect.sync(() => wakeCalls.push(id)).pipe(Effect.asVoid),
Line 152: wakeCalls.length = 0
Line 155: expect(wakeCalls).toEqual([])
Line 265: wakeCalls.length = 0
Line 271: expect(wakeCalls).toEqual([])
Line 310: it.effect("wakes execution when an exact prompt retry recovers a committed message", () =>
Line 321: wakeCalls.length = 0
Line 326: expect(wakeCalls).toEqual([sessionID])
Line 330: it.effect("never wakes execution when retrying an already promoted prompt", () =>
Line 349: wakeCalls.length = 0
Line 358: expect(wakeCalls).toEqual([])
Line 362: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-prompt.test.ts</path>
<type>file</type>
<content>
300:
301: const first = yield* session.prompt(input)
302: const retried = yield* session.prompt(input)
303:
304: expect(retried).toEqual(first)
305: expect(yield* session.messages({ sessionID })).toEqual([])
306: expect(yield* admittedCount).toBe(1)
307: }),
308: )
309:
310: it.effect("wakes execution when an exact prompt retry recovers a committed message", () =>
311: Effect.gen(function* () {
312: yield* setup
313: const session = yield* SessionV2.Service
314: const input = {
315: sessionID,
316: id: messageID,
317: prompt: Prompt.make({ text: "Recover committed prompt" }),
318: resume: false,
319: }
320: const first = yield* session.prompt(input)
321: wakeCalls.length = 0
322:
323: const retried = yield* session.prompt({ ...input, ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-prompt.test.ts</path>
<type>file</type>
<content>
620: { id: messageID, type: "user", text: "Promote once" },
621: ])
622: }),
623: )
624:
625: it.effect("promotes steers only through the captured inbox cutoff", () =>
626: Effect.gen(function* () {
627: yield* setup
628: const { db } = yield* Database.Service
629: const session = yield* SessionV2.Service
630: const events = yield* EventV2.Service
631: const first = yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Before cutoff" }), resume: false })
632: const cutoff = first.admittedSeq
633: const second = yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "After cutoff" }), resume: false })
634:
635: yield* SessionInput.promoteSteers(db, events, sessionID, cutoff)
636:
637: expect(yield* admitted(first.id)).toHaveProperty("promotedSeq")
638: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-run-coordinator.test.ts</path>
<type>file</type>
<content>
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* coordinator.wake("session")
236: yield* coordinator.interrupt("session")
237: yield* ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts</path>
<type>file</type>
<content>
1: import { WorkspaceV2 } from "@opencode-ai/core/workspace"
2: import type { Target } from "@/control-plane/types"
3: import { Workspace } from "@/control-plane/workspace"
4: import { WorkspaceAdapterRuntime } from "@/control-plane/workspace-adapter-runtime"
5: import { Session } from "@/session/session"
6: import { HttpApiProxy } from "./proxy"
7: import * as Fence from "@/server/shared/fence"
8: import { getWorkspaceRouteSessionID, isLocalWorkspaceRoute, workspaceProxyURL } from "@/server/shared/workspace-routing"
9: import { NotFoundError } from "@/storage/storage"
10: import { Flag } from "@opencode-ai/core/flag/flag"
11: import { Context, Data, Effect, Layer, Option, Schema } from "effect"
12: import { HttpClient, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
13: import { ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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: ...
Found 50 matches /tmp/opencode/session-recovery-review-c2c8afb0/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 1089: "name": "wake_attempts", Line 1181: "table": "session_recovery" Line 1191: "table": "session_recovery" Line 1201: "table": "session_recovery" Line 1211: "table": "session_recovery" Line 1221: "table": "session_recovery" Line 1229: "name": "wake_pending", Line 1231: "table": "session_recovery" Line ...
Found 16 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/sdk/openapi.json:
Line 17899: "requestExecution": {
Line 18289: "enum": ["session.next.step.recovered"]
Line 18301: "executionID": {
Line 18313: "required": ["timestamp", "sessionID", "executionID", "phase"],
Line 25709: "requestExecution": {
Line 26299: "enum": ["session.next.step.recovered.1"]
Line 26321: "executionID": {
Line 26333: "required": ["timestamp", "sessionID", "executionID", "phase"],
Line 28599: "requestExecution": {
Line 29190: "enum": ["session.next.step.recovered"]
Line 29221: "executionID": {
Line 29233: "required": ["timestamp", "sessionID", "executionID", "phase"],
Line 34811: "requestExecution": {
Line 35201: "enum": ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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-review-c2c8afb0/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 33 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-projector.test.ts:
Line 67: it.effect("atomically reconciles one abandoned execution into one continuation", () =>
Line 221: it.effect("records a recovery continuation without forging an assistant identity", () =>
Line 258: it.effect("recovers each abandoned execution once across repeated startup scans", () =>
Line 304: it.effect("never recovers a live execution lease", () =>
Line 342: it.effect("never recovers an execution superseded by a newer assistant", () =>
Line 389: it.effect("redrives a durable recovery wake after admission", () =>
Line 440: it.effect("quarantines a malformed recovery row without blocking a valid wake", () =>
Line 494: it.effect("quarantines a full batch of poison recovery rows before redriving valid work", () =>
Line 559: it.effect("fairly redrives recovery and ordinary work beyond a full bounded batch", () =>
Line 629: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1280:
1281: it.effect("rolls back promotion and Context Epoch mutation after ownership loss", () =>
1282: Effect.gen(function* () {
1283: const { db } = yield* Database.Service
1284: const events = yield* EventV2.Service
1285: const inputID = SessionMessage.ID.make("msg_fenced_input")
1286: yield* db
1287: .insert(ProjectTable)
1288: .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
1289: .run()
1290: yield* db
1291: .insert(SessionTable)
1292: .values({
1293: id: sessionID,
1294: project_id: Project.ID.global,
1295: slug: "test",
1296: directory: "/project",
1297: title: "test",
1298: version: "test",
1299: })
1300: .run()
1301: yield* ...
Found 11 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-runner.test.ts:
Line 731: it.effect("never dispatches a provider after ownership changes", () =>
Line 757: it.effect("never publishes streamed output after ownership changes", () =>
Line 764: response = fragmentFixture("text", "text-fenced", ["stale output"]).completeEvents
Line 784: it.effect("never starts a local tool after ownership changes", () =>
Line 824: it.effect("never fails interrupted tools after ownership changes", () =>
Line 869: const stale = yield* runner.run({ sessionID, force: true, executionID, ownerID: "test" }).pipe(Effect.exit)
Line 871: expect(Exit.isFailure(stale)).toBe(true)
Line 2830: text: '{"text":"stale"}',
Line 2838: input: { text: "stale" },
Line 2894: text: '{"query":"stale"}',
Line 2902: input: { query: "stale" },
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
700: const { admitted: message } = yield* SessionInput.admit(db, events, {
701: id: SessionMessage.ID.make("msg_lost_prompt_wake"),
702: sessionID,
703: prompt: Prompt.make({ text: "run after restart" }),
704: delivery: "steer",
705: requestExecution: true,
706: })
707: response = [
708: LLMEvent.stepStart({ index: 0 }),
709: LLMEvent.stepFinish({ index: 0, reason: "stop" }),
710: LLMEvent.finish({ reason: "stop" }),
711: ]
712: requests.length = 0
713:
714: yield* SessionRecovery.recover()
715: while (requests.length === 0) yield* Effect.yieldNow
716: const executionService = yield* SessionExecution.Service
717: while ((yield* executionService.active).size > 0) yield* Effect.yieldNow
718:
719: const request = requests[0]
720: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/schema.json</path>
<type>file</type>
<content>
920: "entityType": "columns",
921: "table": "session_context_epoch"
922: },
923: {
924: "type": "integer",
925: "notNull": true,
926: "autoincrement": false,
927: "default": null,
928: "generated": null,
929: "name": "baseline_seq",
930: "entityType": "columns",
931: "table": "session_context_epoch"
932: },
933: {
934: "type": "text",
935: "notNull": true,
936: "autoincrement": false,
937: "default": null,
938: "generated": null,
939: "name": "id",
940: "entityType": "columns",
941: "table": "session_execution"
942: },
943: {
944: "type": "text",
945: "notNull": true,
946: "autoincrement": false,
947: "default": null,
948: "generated": null,
949: "name": "session_id",
950: "entityType": "columns",
951: ...
Found 1 matches /tmp/opencode/session-recovery-final-v4.patch: Line 5897: diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts
Found 1 matches /tmp/opencode/session-recovery-final-v4.patch: Line 6624: diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts
<path>/tmp/opencode/session-recovery-final-v4.patch</path>
<type>file</type>
<content>
5897: diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts
5898: index cc58b43b29..d55033dadb 100644
5899: --- a/packages/core/test/session-runner.test.ts
5900: +++ b/packages/core/test/session-runner.test.ts
5901: @@ -31,7 +31,8 @@ import { SessionMessage } from "@opencode-ai/core/session/message"
5902: import { Prompt } from "@opencode-ai/core/session/prompt"
5903: import { SessionProjector } from "@opencode-ai/core/session/projector"
5904: import { SessionExecution } from "@opencode-ai/core/session/execution"
5905: -import { SessionRunCoordinator } from "@opencode-ai/core/session/run-coordinator"
5906: +import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
5907: +import { SessionRecovery } from "@opencode-ai/core/session/recovery"
5908: import { SessionRunner } from "@opencode-ai/core/session/runner"
5909: import * as ...
Found 1 matches /tmp/opencode/session-recovery-review-c2c8afb0/packages/core/test/session-runner.test.ts: Line 2000: .where(eq(EventTable.type, "session.next.step.ended.1"))
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/event.ts</path>
<type>file</type>
<content>
480: function replayAll(
481: events: SerializedEvent[],
482: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
483: ) {
484: return Effect.gen(function* () {
485: const source = events[0]?.aggregateID
486: if (!source) return undefined
487: if (events.some((event) => event.aggregateID !== source)) {
488: yield* Effect.die(
489: new InvalidDurableEventError({
490: type: events[0]?.type ?? "unknown",
491: message: "Replay events must belong to the same aggregate",
492: }),
493: )
494: }
495: const start = events[0]?.seq ?? 0
496: for (const [index, event] of events.entries()) {
497: const seq = start + index
498: if ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/sdk/openapi.json</path>
<type>file</type>
<content>
26275: },
26276: "required": ["type", "id", "seq", "aggregateID", "data"],
26277: "additionalProperties": false
26278: }
26279: },
26280: "required": ["type", "id", "syncEvent"],
26281: "additionalProperties": false
26282: },
26283: "SyncEventSessionNextStepRecovered": {
26284: "type": "object",
26285: "properties": {
26286: "type": {
26287: "type": "string",
26288: "enum": ["sync"]
26289: },
26290: "id": {
26291: "type": "string",
26292: "pattern": "^evt_"
26293: },
26294: "syncEvent": {
26295: "type": "object",
26296: "properties": {
26297: "type": {
26298: "type": "string",
26299: "enum": ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/database/sqlite.node.ts</path>
<type>file</type>
<content>
1: import { DatabaseSync, type SQLInputValue } from "node:sqlite"
2: import { drizzle } from "drizzle-orm/node-sqlite"
3: import * as Context from "effect/Context"
4: import * as Effect from "effect/Effect"
5: import * as Fiber from "effect/Fiber"
6: import { identity } from "effect/Function"
7: import * as Layer from "effect/Layer"
8: import * as Scope from "effect/Scope"
9: import * as Semaphore from "effect/Semaphore"
10: import * as Stream from "effect/Stream"
11: import * as Reactivity from "effect/unstable/reactivity/Reactivity"
12: import * as Client from "effect/unstable/sql/SqlClient"
13: import type { Connection } from "effect/unstable/sql/SqlConnection"
14: import { classifySqliteError, SqlError } from "effect/unstable/sql/SqlError"
15: import * as Statement from "effect/unstable/sql/Statement"
16: import { Sqlite } from "./sqlite"
17:
18: const ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/session/execution.ts</path>
<type>file</type>
<content>
1: export * as SessionExecution from "./execution"
2:
3: import { Context, Effect, Layer } from "effect"
4: import { LayerNode } from "../effect/layer-node"
5: import { Node } from "../effect/app-node"
6: import { SessionRunner } from "./runner/index"
7: import { SessionSchema } from "./schema"
8:
9: export interface Interface {
10: /** Snapshots active execution owned by this process. */
11: readonly active: Effect.Effect<ReadonlySet<SessionSchema.ID>>
12: /** Starts execution while idle or joins the active execution. */
13: readonly resume: (sessionID: SessionSchema.ID) => Effect.Effect<void, SessionRunner.RunError>
14: /** Registers newly recorded work. Repeated wakeups may coalesce. */
15: readonly wake: (sessionID: SessionSchema.ID) => Effect.Effect<void>
16: /** Interrupt active work owned by this process. Idle interruption is a no-op. ...
The recovery and lease paths are mostly well fenced, but two security boundaries need closer attention: recovery events are now accepted through the public sync replay surface, and projection failures can leave valid-looking expired claims permanently retryable. I am tracing both against the frozen API manifest and bounded startup scanner before issuing the verdict.
<path>/tmp/opencode/session-recovery-final-v4.patch</path>
<type>file</type>
<content>
6820: error: SessionErrorUnknown
6821: }
6822: }
6823: + | {
6824: + id: string
6825: + type: "session.next.step.interrupted"
6826: + properties: {
6827: + timestamp: number
6828: + sessionID: string
6829: + assistantMessageID: string
6830: + error: SessionErrorUnknown
6831: + }
6832: + }
6833: + | {
6834: + id: string
6835: + type: "session.next.step.recovered"
6836: + properties: {
6837: + timestamp: number
6838: + sessionID: string
6839: + executionID: string
6840: + assistantMessageID?: string
6841: + phase: "ready" | "unknown" | "continue"
6842: + }
6843: + }
6844: | {
6845: id: string
6846: type: "session.next.text.started"
6847: @@ -1613,6 +1647,7 @@ export type GlobalEvent = {
6848: | ...
No files found
<path>/tmp/opencode/session-recovery-review-c2c8afb0/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 ...
No files found
Found 9 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/server/src/routes.ts:
Line 5: import { EventV2 } from "@opencode-ai/core/event"
Line 29: EventV2.node,
/tmp/opencode/session-recovery-review-c2c8afb0/packages/server/src/handlers/event.ts:
Line 1: import { EventV2 } from "@opencode-ai/core/event"
Line 22: const events = yield* EventV2.Service
Line 26: id: EventV2.ID.create(),
Line 33: const live = yield* EventV2.allBounded(events, subscriberCapacity)
/tmp/opencode/session-recovery-review-c2c8afb0/packages/server/src/handlers/pty.ts:
Line 176: // Outbound frames flow through one queue drained by a single writer so replay, live
Line 196: for (const chunk of PtyProtocol.chunks(attachment.replay)) Queue.offerUnsafe(outbox, chunk)
Line 216: Effect.ensuring(Effect.sync(() => attachment.detach())),
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/server/src/handlers/event.ts</path>
<type>file</type>
<content>
1: import { EventV2 } from "@opencode-ai/core/event"
2: import { OpenCodeEvent } from "@opencode-ai/protocol/groups/event"
3: import { Effect, Schema, Stream } from "effect"
4: import { HttpServerResponse } from "effect/unstable/http"
5: import { HttpApiBuilder } from "effect/unstable/httpapi"
6: import * as Sse from "effect/unstable/encoding/Sse"
7: import { Api } from "../api"
8:
9: const subscriberCapacity = 256
10:
11: function eventData(data: unknown): Sse.Event {
12: return {
13: _tag: "Event",
14: event: "message",
15: id: undefined,
16: data: JSON.stringify(Schema.encodeUnknownSync(OpenCodeEvent)(data)),
17: }
18: }
19:
20: export const EventHandler = HttpApiBuilder.group(Api, "server.event", (handlers) =>
21: Effect.gen(function* () {
22: const events = yield* EventV2.Service
23: return ...
Found 100 matches (more matches available) /tmp/opencode/session-recovery-review-c2c8afb0/packages/sdk/js/src/v2/gen/types.gen.ts: Line 1638: | SyncEventSessionCreated Line 1639: | SyncEventSessionUpdated Line 1640: | SyncEventSessionDeleted Line 1641: | SyncEventMessageUpdated Line 1642: | SyncEventMessageRemoved Line 1643: | SyncEventMessagePartUpdated Line 1644: | SyncEventMessagePartRemoved Line 1645: | SyncEventSessionNextAgentSwitched Line 1646: | SyncEventSessionNextModelSwitched Line 1647: | SyncEventSessionNextMoved Line 1648: | SyncEventSessionNextPrompted Line 1649: | SyncEventSessionNextPromptAdmitted Line 1650: | SyncEventSessionNextPromptExecutionRequested Line 1651: | SyncEventSessionNextContextUpdated Line 1652: | SyncEventSessionNextSynthetic Line 1653: | SyncEventSessionNextShellStarted Line 1654: | SyncEventSessionNextShellEnded Line 1655: | ...
Found 3 matches /tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/event.ts: Line 142: readonly replayAll: ( Line 480: function replayAll( Line 630: replayAll,
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/session/recovery.ts</path>
<type>file</type>
<content>
1: export * as SessionRecovery from "./recovery"
2:
3: import { and, asc, desc, eq, gt, isNull, lte, or, sql } from "drizzle-orm"
4: import { Clock, DateTime, Effect, Layer, Option, Schedule, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { makeGlobalNode } from "../effect/app-node"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionExecution } from "./execution"
10: import { SessionProjector } from "./projector"
11: import { SessionExecutionClaim } from "./execution/claim"
12: import { SessionInput } from "./input"
13: import { SessionSchema } from "./schema"
14: import {
15: SESSION_EXECUTION_LEASE_MS,
16: SessionExecutionTable,
17: SessionInputTable,
18: SessionRecoveryTable,
19: SessionMessageTable,
20: } from "./sql"
21:
22: export const BATCH_SIZE = 100
23: ...
Found 6 matches
/tmp/opencode/session-recovery-review-c2c8afb0/packages/opencode/src/control-plane/workspace.ts:
Line 398: const payload = evt.payload as { type?: string; syncEvent?: EventV2.SerializedEvent }
Line 401: if (payload.type === "sync" && payload.syncEvent) {
Line 402: const failed = yield* events.replay(payload.syncEvent, { publish: true, ownerID: space.id }).pipe(
/tmp/opencode/session-recovery-review-c2c8afb0/packages/opencode/src/event-v2-bridge.ts:
Line 52: syncEvent: {
/tmp/opencode/session-recovery-review-c2c8afb0/packages/opencode/src/bus/global.ts:
Line 16: event.payload.id = event.payload.syncEvent?.id ?? Identifier.create("evt", "ascending")
/tmp/opencode/session-recovery-review-c2c8afb0/packages/opencode/src/server/routes/instance/httpapi/groups/global.ts:
Line 24: syncEvent: Schema.Struct({
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/opencode/src/control-plane/workspace.ts</path>
<type>file</type>
<content>
360: )
361: .pipe(Effect.provideService(WorkspaceRef, space.id)),
362: { discard: true },
363: )
364: })
365:
366: const syncWorkspaceLoop = Effect.fn("Workspace.syncWorkspaceLoop")(function* (space: Info) {
367: const target = yield* WorkspaceAdapterRuntime.target(space)
368:
369: if (target.type === "local") return
370:
371: let attempt = 0
372:
373: while (true) {
374: setStatus(space.id, "connecting")
375:
376: const stream = yield* connectSSE(target.url, target.headers).pipe(
377: Effect.tap(() => syncHistory(space, target.url, target.headers)),
378: Effect.catch((err) =>
379: Effect.gen(function* () {
380: setStatus(space.id, "error")
381: yield* Effect.logWarning("failed to connect to global ...
Found 2 matches /tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/session/projector.ts: Line 385: yield* events.project(SessionEvent.PromptExecutionRequested, (event) => Line 399: yield* events.project(SessionEvent.Step.Recovered, (event) =>
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
350: .set({ model: event.data.model, time_updated: DateTime.toEpochMillis(event.data.timestamp) })
351: .where(eq(SessionTable.id, event.data.sessionID))
352: .run()
353: .pipe(Effect.orDie)
354: yield* run(db, event)
355: }),
356: )
357: yield* events.project(SessionEvent.Prompted, (event) =>
358: Effect.gen(function* () {
359: if (event.durable === undefined) return yield* Effect.die("Durable Session event is missing aggregate sequence")
360: yield* SessionInput.projectPrompted(db, {
361: id: event.data.messageID,
362: sessionID: event.data.sessionID,
363: prompt: event.data.prompt,
364: delivery: event.data.delivery,
365: timeCreated: event.data.timestamp,
366: promotedSeq: event.durable.seq,
367: })
368: ...
<path>/tmp/opencode/session-recovery-review-c2c8afb0/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
442: yield* db
443: .insert(SessionRecoveryTable)
444: .values({
445: session_id: event.data.sessionID,
446: execution_id: event.data.executionID,
447: assistant_message_id: event.data.assistantMessageID ?? null,
448: continuation_message_id: continuationMessageID,
449: phase: event.data.phase,
450: wake_pending: true,
451: time_created: DateTime.toEpochMillis(event.data.timestamp),
452: })
453: .onConflictDoNothing()
454: .run()
455: .pipe(Effect.orDie)
456: }),
457: )
458: yield* events.project(SessionEvent.Text.Started, (event) => run(db, event))
459: yield* events.project(SessionEvent.Text.Ended, (event) => run(db, event))
460: yield* events.project(SessionEvent.Tool.Input.Started, ...
Date: Sep 16, 2026 * 🟡 (10:49) `packages/core/src/session/execution.ts:9-18` defines `SessionExecution.Interface`: `active` snapshots `ReadonlySet<SessionSchema.ID>` executions owned by the process; `resume(sessionID)` starts idle execution or joins active execution and can fail with `SessionRunner.RunError`; `wake(sessionID)` registers newly recorded work with coalescible repeated wakeups; `int…
Date: Sep 16, 2026 * 🟡 (10:44) `packages/sdk/openapi.json:26283-26343` defines `SyncEventSessionNextStepRecovered` as a `"sync"` event whose nested `syncEvent.type` is `"session.next.step.recovered.1"`; outer required fields are `type`, `id`, and `syncEvent`, with outer `id` matching `^evt_`; nested required fields are `type`, `id`, `seq`, `aggregateID`, and `data`, with nested `id` matching `^e…
Date: Sep 16, 2026 * 🔴 (10:41) User asserted the required ownership-fencing behavior: `"never dispatches a provider after ownership changes"`. * 🔴 (10:41) User asserted the required ownership-fencing behavior: `"never publishes streamed output after ownership changes"`. * 🔴 (10:41) User asserted the required ownership-fencing behavior: `"never starts a local tool after ownership changes"`. * �…
Date: Sep 16, 2026 * 🟡 (10:38) `packages/core/test/session-runner-recorded.test.ts` configures the recorded OpenAI transport with cassette `"session-runner/openai-chat-streams-text"` under `fixtures/recordings`; `RECORD === "true"` selects `HttpRecorderInternal.cassetteLayer(..., { mode: "record" })`, otherwise `HttpRecorder.http(...)`. The model is OpenAI Chat `gpt-4o-mini` at `https://api.open…
Date: Sep 16, 2026 * 🟡 (10:27) Event-manifest implementation files are `packages/schema/src/event-manifest.ts` and `packages/schema/src/durable-event-manifest.ts`. * 🟡 (10:28) `packages/schema/src/session-event.ts` defines `"session.next.step.recovered"` and includes `Step.Recovered` in both `DurableDefinitions` (line 499) and `Definitions` (line 533). * 🟡 (10:29) `packages/schema/src/session-…
Date: Sep 16, 2026 * 🟡 (10:19) `packages/core/src/database/migration.gen.ts` registers `./migration/20260914170650_session-recovery` as the final entry in the generated `migrations` array (line 43); the file contains 39 tracked migration imports spanning `20260127222353_familiar_lady_ursula` through `20260914170650_session-recovery`. * 🟡 (10:19) Generated schema in `packages/core/src/database/s…
Date: Sep 16, 2026 * 🔴 (10:17) User asserted the session recovery invariant: “never promotes a recovery superseded before scanner validation”. * 🔴 (10:17) User asserted the session recovery invariant: “never promotes a quarantined recovery continuation as an ordinary steer”. * 🟡 (10:17) Search of `packages/core/test/session-projector.test.ts` found 14 recovery-continuation references, includin…
Date: Sep 16, 2026 * 🟡 (10:16) Inspected `packages/core/src/session.ts:338-487`. `V2Session.message` returns a stored message only when `stored?.sessionID === input.sessionID`; `V2Session.context`, `V2Session.events`, and `V2Session.history` first validate the session with `result.get(sessionID)`. * 🟡 (10:16) `packages/core/src/session.ts:346-358` exposes durable session events through `events.…
Date: Sep 16, 2026 * 🟡 (10:15) Inspected `packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts` (89 lines). `SyncHttpApi.replay` converts `ctx.payload.events` into `EventV2.SerializedEvent[]`, preserving `id`, `aggregateID`, `seq`, `type`, and a shallow-copied `data: { ...event.data }`. * 🟡 (10:15) In `packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts:42`, `…
Date: Sep 16, 2026 * 🟡 (10:14) Search found exactly 40 references to `replay`/`replayAll` across 8 files: `packages/core/src/event.ts:480`; `packages/core/test/event.test.ts:551,570,606,639,647,671,687,704,731,747,789,816,818,838,848,863,891,901,933,945,975,976,995,998,1011,1020,1041,1051,1113`; `packages/core/test/session-create.test.ts:258,268`; `packages/core/test/session-runner.test.ts:411`;…
Date: Sep 16, 2026 * 🔴 (10:13) User requires recovery to treat a newer turn as superseding stale incomplete rows and to “never resume an older assistant projection.” * 🔴 (10:13) User requires `OpenApi.fromApi(PublicApi)` to be deferred for processes that never serve `/doc`, specifically CLI processes and scripts, so they do not pay the cost at module load. * 🟡 (10:12) `packages/core/src/sessio…
Date: Sep 16, 2026 * 🔴 (10:11) User requires local execution ownership to be replaced with durable multi-node ownership when clustered. * 🟡 (10:12) `packages/core/src/session/runner/llm.ts` defines `SessionRunner.run` with required `sessionID`, `force`, `executionID`, and `ownerID`, plus optional `recoveryInputID`; the execution claim is `{ sessionID, executionID, ownerID }`, and recovery mode …
Date: Sep 16, 2026 * 🔴 (10:11) User requires recovery to never resume an older assistant projection; a newer turn supersedes stale incomplete rows. * 🟡 (10:11) Audit of `packages/core/src/session/projector.ts` found recovery validates an optional `assistantMessageID` against the latest assistant row ordered by descending `SessionMessageTable.seq`; it defects with `Recovery target is not the lat…
Date: Sep 16, 2026 * 🟡 (10:10) Audit task status changed: 1. Frozen base/patch hash/SEA hash/33-file scope/applicability verification completed; 2. Repository/package instruction review completed; 3. Disposable exact base+patch inspection tree construction and fingerprinting completed; 4. Audit of all 33 files and end-to-end recovery security invariants remained in progress; 5. Severity-ordered …
Date: Sep 16, 2026 * 🟡 (10:07) Audit plan comprised 5 ordered tasks: 1. Verify frozen base, patch hash, SEA hash, 33-file scope, and clean applicability to exact base (`in_progress`, high priority); 2. Read applicable repository and package instructions without using mutable candidate source (`pending`, high); 3. Construct and fingerprint a disposable exact base+patch inspection tree outside VCS…