Dashboard › opencode › Session 04LKl5Ca2esR
04LKl5Ca2esRMboX4Strict READ-ONLY security/pentest review of frozen v23 in /tmp/opencode/session-recovery-v23-review. Never edit, run tests/builds/services, mutate VCS/index/refs, or use recall. Verify immutable inputs first: base ab9408c81cc9916177641ae8e9f08624fc27b5b9; patch /tmp/opencode/session-recovery-final-v23.patch SHA-256 14035d7631ba25b820fdc553087469935f447af034d549e2f14f8a141eda59dc; SEA /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server SHA-256 f875987afa4336956b9f33e14eee6de0a4af90e2bc6de899cccb7cbe5a5df998; 34 paths. Inspect all changed files and relevant context. Adversarially inspect acquisition transaction/registration interruption, prospective claim visibility, acquired latch deadlock/false result, wake/resume loss around cancellation setup, cleanup defects, concurrent interrupt, exact fencing; malformed assistant projections and 100-row bounded scan starvation; final event commit atomic claim deletion and failure rollback; terminal failed-Step/tool reconciliation; stale-owner/cross-Session/corrupt identity, replay, fairness, snapshots, migration/generated clients/auth/layering. Verify schema ancestry and explicit resume. Treat final check-call, process-local routing, bounded skew as disclosed residuals. Findings first with exact file:line and MUST-FIX/CONCERN. End exactly MERGE or DO-NOT-MERGE.
<path>/tmp/opencode/session-recovery-v23-review</path> <type>directory</type> <entries> .dockerignore .editorconfig .git .gitattributes .github/ .gitignore .gitleaksignore .husky/ .lore.md .opencode/ .oxlintrc.json .prettierignore .vscode/ .zed/ AGENTS.md artifacts/ bun.lock bunfig.toml CONTEXT.md CONTRIBUTING.md flake.lock flake.nix github/ infra/ install LICENSE nix/ package.json packages/ patches/ perf/ pnpm-lock.yaml pnpm-workspace.yaml README.ar.md README.bn.md README.br.md README.bs.md README.da.md README.de.md README.es.md README.fr.md README.gr.md README.it.md README.ja.md README.ko.md README.md README.no.md README.pl.md README.ru.md README.th.md README.tr.md README.uk.md README.vi.md README.zh.md README.zht.md screenshot-uk.png script/ sdks/ SECURITY.md specs/ sst-env.d.ts sst.config.ts STATS.md tsconfig.json turbo.json (65 entries) </entries>
14035d7631ba25b820fdc553087469935f447af034d549e2f14f8a141eda59dc /tmp/opencode/session-recovery-final-v23.patch f875987afa4336956b9f33e14eee6de0a4af90e2bc6de899cccb7cbe5a5df998 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server ab9408c81cc9916177641ae8e9f08624fc27b5b9 ab9408c81cc9916177641ae8e9f08624fc27b5b9 M packages/client/src/generated/types.ts M packages/core/schema.json M packages/core/src/database/migration.gen.ts M packages/core/src/database/schema.gen.ts M packages/core/src/session.ts M packages/core/src/session/compaction.ts M packages/core/src/session/context-epoch.ts M packages/core/src/session/execution/local.ts M packages/core/src/session/input.ts M packages/core/src/session/message-updater.ts M packages/core/src/session/projector.ts M packages/core/src/session/run-coordinator.ts M packages/core/src/session/runner/index.ts M packages/core/src/session/runner/llm.ts M packages/core/src/session/runner/publish-llm-event.ts M ...
/tmp/opencode/session-recovery-v23-review/AGENTS.md /tmp/opencode/session-recovery-v23-review/packages/app/AGENTS.md /tmp/opencode/session-recovery-v23-review/packages/llm/AGENTS.md /tmp/opencode/session-recovery-v23-review/packages/opencode/AGENTS.md /tmp/opencode/session-recovery-v23-review/packages/app/e2e/AGENTS.md /tmp/opencode/session-recovery-v23-review/packages/app/e2e/performance/AGENTS.md /tmp/opencode/session-recovery-v23-review/packages/opencode/src/session/llm/AGENTS.md /tmp/opencode/session-recovery-v23-review/packages/opencode/src/server/routes/instance/httpapi/AGENTS.md /tmp/opencode/session-recovery-v23-review/packages/opencode/test/AGENTS.md /tmp/opencode/session-recovery-v23-review/packages/desktop/AGENTS.md /tmp/opencode/session-recovery-v23-review/packages/ui/AGENTS.md /tmp/opencode/session-recovery-v23-review/packages/session-ui/AGENTS.md /tmp/opencode/session-recovery-v23-review/packages/effect-drizzle-sqlite/AGENTS.md /tmp/opencode/session-recovery-v23-review/pa...
<path>/tmp/opencode/session-recovery-v23-review/AGENTS.md</path> <type>file</type> <content> 1: - To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`. 2: - After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly. 3: - Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server. 4: - The default branch in this repo is `dev`. 5: - Local `main` ref may not exist; use `dev` or `origin/dev` for diffs. 6: 7: ## Branch Names 8: 9: Use a short branch name of at most three words, separated by hyphens. Do not use slashes or type prefixes such as `feat/` or `fix/`. 10: 11: Examples: `session-recovery`, `fix-scroll-state`, `regenerate-sdk`. ...
<path>/tmp/opencode/session-recovery-v23-review/packages/opencode/src/server/routes/instance/httpapi/AGENTS.md</path>
<type>file</type>
<content>
1: # HttpApi Route Patterns
2:
3: Use `HttpApiBuilder.group(...)` for normal HTTP endpoints, including streaming HTTP responses such as server-sent events. Handlers should yield stable services once while building the handler layer, then close over those services in endpoint implementations.
4:
5: ```ts
6: export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session", (handlers) =>
7: Effect.gen(function* () {
8: const session = yield* Session.Service
9:
10: return handlers.handle("list", () => session.list())
11: }),
12: )
13: ```
14:
15: For SSE endpoints, stay in `HttpApiBuilder.group(...)` and return `HttpServerResponse.stream(...)` from the handler. Annotate the endpoint success schema with `HttpApiSchema.asText({ contentType: "text/event-stream" })` so OpenAPI documents the stream content type. ...
<path>/tmp/opencode/session-recovery-v23-review/packages/schema/AGENTS.md</path> <type>file</type> <content> 1: # Schema Package Guide 2: 3: `@opencode-ai/schema` owns browser-safe wire and storage contracts shared by protocol, server, core, and generated SDKs. Keep runtime behavior, service layers, side effects, and host-local implementation details in the domain package that owns them. 4: 5: ## Package Boundary 6: 7: - Preserve the dependency direction: `@opencode-ai/schema <- @opencode-ai/protocol <- @opencode-ai/server`. 8: - Schema values should be serializable contract definitions, not service implementations or runtime registries. 9: - A domain may keep a minimal public wire contract here when SDK generation needs it, but do not move the broader runtime model into Schema just because an event is public. `plugin.added` is the current example: Schema may own the minimum browser-safe event payload, while plugin runtime behavior stays outside Schema. ...
72 0 packages/client/src/generated/types.ts 566 47 packages/core/schema.json 1 0 packages/core/src/database/migration.gen.ts 62 0 packages/core/src/database/migration/20260914170650_session-recovery.ts 52 1 packages/core/src/database/schema.gen.ts 6 4 packages/core/src/session.ts 31 16 packages/core/src/session/compaction.ts 54 34 packages/core/src/session/context-epoch.ts 291 19 packages/core/src/session/execution/local.ts 391 31 packages/core/src/session/input.ts 69 0 packages/core/src/session/message-updater.ts 88 1 packages/core/src/session/projector.ts 579 0 packages/core/src/session/recovery.ts 40 6 packages/core/src/session/run-coordinator.ts 4 0 packages/core/src/session/runner/index.ts 289 104 packages/core/src/session/runner/llm.ts 108 48 packages/core/src/session/runner/publish-llm-event.ts 88 1 packages/core/src/session/sql.ts 66 1 packages/core/test/database-migration.test.ts 415 0 packages/core/test/session-execution-local.test.ts 2994 3 packages/core/test/session-project...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/execution/claim.ts</path>
<type>file</type>
<content>
1: export * as SessionExecutionClaim from "./claim"
2:
3: import { and, eq, gt, notInArray } 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<
18: Pick<typeof SessionExecutionTable.$inferInsert, "assistant_message_id" | "assistant_seq" | "phase">
19: >
20:
21: export class Lost extends Error {
22: constructor(readonly claim: Claim) {
23: super(`Session execution claim lost: ${claim.executionID}`)
24: }
25: }
26:
27: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/recovery-id.ts</path>
<type>file</type>
<content>
1: import { SessionMessage } from "./message"
2:
3: export const message = (executionID: string, assistantMessageID?: SessionMessage.ID) =>
4: assistantMessageID ?? SessionMessage.ID.make(`msg_recovery_${executionID}`)
5:
6: export const continuation = (executionID: string) => SessionMessage.ID.make(`msg_recovery_continue_${executionID}`)
(End of file - total 6 lines)
</content>
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/recovery.ts</path>
<type>file</type>
<content>
1: export * as SessionRecovery from "./recovery"
2:
3: import { and, asc, desc, eq, exists, gt, isNull, lte, or, sql } from "drizzle-orm"
4: import { Clock, DateTime, Effect, Layer, Option, Schedule, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { makeGlobalNode } from "../effect/app-node"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionExecution } from "./execution"
10: import { SessionProjector } from "./projector"
11: import { SessionExecutionClaim } from "./execution/claim"
12: import { SessionInput } from "./input"
13: import { SessionMessage } from "./message"
14: import { continuation } from "./recovery-id"
15: import { SessionSchema } from "./schema"
16: import {
17: MAX_WAKE_ATTEMPTS,
18: SESSION_EXECUTION_LEASE_MS,
19: SessionExecutionTable,
20: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/execution/local.ts</path>
<type>file</type>
<content>
1: import { and, asc, desc, eq, isNull } from "drizzle-orm"
2: import { Cause, Clock, DateTime, Deferred, Duration, Effect, Exit, Layer, Option, Schedule, Schema } 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 { EventV2 } from "../../event"
13: import { SessionEvent } from "../event"
14: import { SessionExecutionClaim } from "./claim"
15: import {
16: SESSION_EXECUTION_LEASE_MS,
17: SessionExecutionTable,
18: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/run-coordinator.ts</path>
<type>file</type>
<content>
1: export * as SessionRunCoordinator from "./run-coordinator"
2:
3: import { Deferred, Effect, Exit, Fiber, FiberSet, Scope } from "effect"
4:
5: /** Serializes execution for each key while allowing different keys to run concurrently. */
6: export interface Coordinator<Key, E> {
7: /** Snapshots keys with an execution owned by this coordinator. */
8: readonly active: Effect.Effect<ReadonlySet<Key>>
9: /** Starts execution while idle or joins the active execution. */
10: readonly run: (key: Key) => Effect.Effect<void, E>
11: /** Registers one coalesced follow-up after newly recorded work. */
12: readonly wake: (key: Key) => Effect.Effect<void>
13: /** Stops active execution, runs cleanup, then starts work that arrived while stopping. */
14: readonly interrupt: (
15: key: Key,
16: cleanup?: Effect.Effect<void>,
17: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
1: export * as SessionInput from "./input"
2:
3: import { and, asc, desc, eq, isNull, lte } from "drizzle-orm"
4: import { DateTime, Effect, Option, Schema } from "effect"
5: import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
6: import type { Database } from "../database/database"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionMessage } from "./message"
10: import { Prompt } from "./prompt"
11: import { continuation, message } from "./recovery-id"
12: import { SessionSchema } from "./schema"
13: import { SessionInputTable, SessionMessageTable, SessionRecoveryTable, SessionTable } from "./sql"
14:
15: type DatabaseService = Database.Interface["db"]
16:
17: export { Admitted, Delivery }
18:
19: const decodePrompt = Schema.decodeUnknownSync(Prompt)
20: const encodePrompt = ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
521: ? find(db, id).pipe(
522: Effect.flatMap((stored) => (stored?.promotedSeq === undefined ? Effect.die(defect) : Effect.void)),
523: )
524: : Effect.die(defect),
525: ),
526: )
527: }
528: return rows.length
529: })
530:
531: export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* (
532: db: DatabaseService,
533: events: EventV2.Interface,
534: sessionID: SessionSchema.ID,
535: cutoff: number,
536: commit?: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
537: ) {
538: const rows = yield* db
539: .select({ input: SessionInputTable })
540: .from(SessionInputTable)
541: .leftJoin(
542: SessionRecoveryTable,
543: eq(SessionRecoveryTable.continuation_message_id, SessionInputTable.id),
544: )
545: .where(
546: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/runner/index.ts</path>
<type>file</type>
<content>
1: export * as SessionRunner from "./index"
2:
3: import type { LLMError } from "@opencode-ai/llm"
4: import { Context, Effect } from "effect"
5: import { SessionSchema } from "../schema"
6: import type { SessionMessage } from "../message"
7: import type { ContextSnapshotDecodeError, MessageDecodeError } from "../error"
8: import { SessionRunnerModel } from "./model"
9: import type { SystemContext } from "../../system-context/index"
10: import type { ToolOutputStore } from "../../tool-output-store"
11:
12: export type RunError =
13: | LLMError
14: | SessionRunnerModel.Error
15: | MessageDecodeError
16: | ContextSnapshotDecodeError
17: | SystemContext.InitializationBlocked
18: | ToolOutputStore.Error
19:
20: /** Runs one local continuation from already-recorded Session history. ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
1: import {
2: LLM,
3: LLMClient,
4: LLMError,
5: LLMEvent,
6: Message,
7: SystemPart,
8: isContextOverflowFailure,
9: type ProviderErrorEvent,
10: } from "@opencode-ai/llm"
11: import { Cause, DateTime, Effect, Exit, Fiber, Layer, Option, Semaphore, Stream } from "effect"
12: import { and, eq } from "drizzle-orm"
13: import { AgentV2 } from "../../agent"
14: import { Config } from "../../config"
15: import { Database } from "../../database/database"
16: import { EventV2 } from "../../event"
17: import { Location } from "../../location"
18: import { ModelV2 } from "../../model"
19: import { PermissionV2 } from "../../permission"
20: import { ProviderV2 } from "../../provider"
21: import { QuestionV2 } from "../../question"
22: import { SystemContext } from "../../system-context/index"
23: import { SystemContextRegistry } from ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
461: if (stepSettlement && !publisher.hasProviderError()) {
462: yield* updateExecution()
463: const endSnapshot = yield* snapshots.capture()
464: const files =
465: startSnapshot && endSnapshot
466: ? yield* snapshots
467: .files({ from: startSnapshot, to: endSnapshot })
468: .pipe(Effect.catch(() => Effect.succeed(undefined)))
469: : undefined
470: const assistantMessageID = yield* publisher.startAssistant()
471: yield* withPublication(
472: events.publish(
473: SessionEvent.Step.Ended,
474: {
475: sessionID: session.id,
476: timestamp: yield* DateTime.now,
477: assistantMessageID,
478: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
1: import { ToolOutput, type LLMEvent, type ProviderMetadata, type ToolResultValue, type Usage } from "@opencode-ai/llm"
2: import { DateTime, Effect } from "effect"
3: import { EventV2 } from "../../event"
4: import { ModelV2 } from "../../model"
5: import { SessionEvent } from "../event"
6: import { SessionMessage } from "../message"
7: import { SessionSchema } from "../schema"
8:
9: type Input = {
10: readonly sessionID: SessionSchema.ID
11: readonly agent: string
12: readonly model: ModelV2.Ref
13: readonly snapshot?: string
14: readonly startCommit: (assistantMessageID: SessionMessage.ID, seq: number) => Effect.Effect<void>
15: readonly eventCommit: () => Effect.Effect<void>
16: }
17:
18: const safe = (value: number | undefined) => Math.max(0, Number.isFinite(value) ? (value ?? ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
421: }
422: yield* publishEvent(SessionEvent.Tool.Success, {
423: sessionID: input.sessionID,
424: timestamp: yield* timestamp,
425: assistantMessageID: tool.assistantMessageID,
426: callID: event.id,
427: ...result,
428: outputPaths,
429: ...(provider.executed ? { result: event.result } : {}),
430: provider,
431: })
432: tool.settled = true
433: return
434: }
435: case "tool-error": {
436: const tool = tools.get(event.id)
437: if (!tool?.called) return yield* Effect.die(`Tool error before call: ${event.id}`)
438: if (tool.name !== event.name)
439: return yield* Effect.die(`Tool error name changed for ${event.id}: ${tool.name} -> ${event.name}`)
440: if (tool.settled) return ...
No files found
/tmp/opencode/session-recovery-v23-review/packages/core/src/event.ts /tmp/opencode/session-recovery-v23-review/packages/core/src/session/event.ts
Found 23 matches
/tmp/opencode/session-recovery-v23-review/packages/core/src/event.ts:
Line 122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
Line 123: readonly commit?: (seq: number) => Effect.Effect<void>
Line 205: function commitDurableEvent(
Line 214: commit?: (seq: number) => Effect.Effect<void>,
Line 239: const committed = yield* db
Line 316: const committed = {
Line 321: yield* projector(committed)
Line 323: if (commit) yield* commit(seq)
Line 354: if (committed) {
Line 356: pubsub.durable.get(committed.aggregateID) ?? [],
Line 361: return committed
Line 369: function publishEvent<D extends Definition>(definition: D, event: Payload<D>, commit?: PublishOptions["commit"]) {
Line 371: if ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/event.ts</path>
<type>file</type>
<content>
100: },
101: data: event.data,
102: }),
103: )
104: return {
105: events,
106: hasMore: rows.length > input.limit,
107: }
108: })
109:
110: export class SubscriberOverflowError extends Schema.TaggedErrorClass<SubscriberOverflowError>()(
111: "EventV2.SubscriberOverflow",
112: { capacity: Schema.Int },
113: ) {}
114:
115: export const define = Event.define
116: export const versionedType = Event.versionedType
117:
118: export interface PublishOptions {
119: readonly id?: ID
120: readonly metadata?: Record<string, unknown>
121: readonly location?: Location.Ref
122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
123: readonly commit?: (seq: number) => Effect.Effect<void>
124: }
125:
126: export interface Interface {
127: readonly publish: <D extends Definition>(
128: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
1: export * as SessionProjector from "./projector"
2:
3: import { and, desc, eq, gt, or, sql } from "drizzle-orm"
4: import { DateTime, Effect, Layer, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { makeGlobalNode } from "../effect/app-node"
8: import { SessionEvent } from "./event"
9: import { SessionV1 } from "../v1/session"
10: import { WorkspaceTable } from "../control-plane/workspace.sql"
11: import { SessionMessage } from "./message"
12: import { SessionMessageUpdater } from "./message-updater"
13: import { SessionInput } from "./input"
14: import { Prompt } from "./prompt"
15: import { continuation } from "./recovery-id"
16: import { WorkspaceV2 } from "../workspace"
17: import {
18: MessageTable,
19: PartTable,
20: SessionInputTable,
21: SessionMessageTable,
22: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
399: yield* events.project(SessionEvent.Step.Recovered, (event) =>
400: Effect.gen(function* () {
401: if (event.durable === undefined) return yield* Effect.die("Durable Session event is missing aggregate sequence")
402: const continuationMessageID = continuation(event.data.executionID)
403: const existing = yield* db
404: .select({
405: sessionID: SessionRecoveryTable.session_id,
406: assistantMessageID: SessionRecoveryTable.assistant_message_id,
407: continuationMessageID: SessionRecoveryTable.continuation_message_id,
408: phase: SessionRecoveryTable.phase,
409: })
410: .from(SessionRecoveryTable)
411: .where(eq(SessionRecoveryTable.execution_id, event.data.executionID))
412: .get()
413: .pipe(Effect.orDie)
414: if ...
No files found
Found 5 matches
/tmp/opencode/session-recovery-v23-review/packages/schema/src/session-event.ts:
Line 220: export const Recovered = Event.define({
Line 221: type: "session.next.step.recovered",
Line 230: export type Recovered = typeof Recovered.Type
Line 499: Step.Recovered,
Line 533: Step.Recovered,
<path>/tmp/opencode/session-recovery-v23-review/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
180: assistantMessageID: SessionMessage.ID,
181: finish: Schema.String,
182: cost: Schema.Finite,
183: tokens: Schema.Struct({
184: input: Schema.Finite,
185: output: Schema.Finite,
186: reasoning: Schema.Finite,
187: cache: Schema.Struct({
188: read: Schema.Finite,
189: write: Schema.Finite,
190: }),
191: }),
192: snapshot: Schema.String.pipe(optional),
193: files: Schema.Array(RelativePath).pipe(optional),
194: },
195: })
196: export type Ended = typeof Ended.Type
197:
198: export const Failed = Event.define({
199: type: "session.next.step.failed",
200: ...stepSettlementOptions,
201: schema: {
202: ...Base,
203: assistantMessageID: SessionMessage.ID,
204: error: UnknownError,
205: },
206: })
207: export type Failed = ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/compaction.ts</path>
<type>file</type>
<content>
1: export * as SessionCompaction from "./compaction"
2:
3: import { LLM, LLMError, LLMEvent, Message, type LLMRequest, type Model } from "@opencode-ai/llm"
4: import { DateTime, Effect, Stream } from "effect"
5: import type { Config } from "../config"
6: import type { EventV2 } from "../event"
7: import { SessionEvent } from "./event"
8: import { SessionMessage } from "./message"
9: import { SessionSchema } from "./schema"
10: import { Token } from "../util/token"
11:
12: const DEFAULT_BUFFER = 20_000
13: const DEFAULT_KEEP_TOKENS = 8_000
14: const TOOL_OUTPUT_MAX_CHARS = 2_000
15: const SUMMARY_OUTPUT_TOKENS = 4_096
16: const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response. ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/context-epoch.ts</path>
<type>file</type>
<content>
1: export * as SessionContextEpoch from "./context-epoch"
2:
3: import { eq } from "drizzle-orm"
4: import { DateTime, Effect, Schema } from "effect"
5: import type { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { SystemContext } from "../system-context/index"
8: import { ContextSnapshotDecodeError } from "./error"
9: import { SessionEvent } from "./event"
10: import { SessionHistory } from "./history"
11: import { SessionInput } from "./input"
12: import { SessionMessage } from "./message"
13: import { SessionSchema } from "./schema"
14: import { SessionContextEpochTable } from "./sql"
15: import { SessionExecutionClaim } from "./execution/claim"
16:
17: type DatabaseService = Database.Interface["db"]
18:
19: interface Prepared {
20: readonly baseline: string
21: readonly baselineSeq: number
22: }
23:
24: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
1: import { castDraft, produce, type WritableDraft } from "immer"
2: import { Effect } from "effect"
3: import { SessionEvent } from "./event"
4: import { SessionMessage } from "./message"
5: import { message } from "./recovery-id"
6:
7: export type MemoryState = {
8: messages: SessionMessage.Message[]
9: }
10:
11: export interface Adapter {
12: readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined>
13: readonly getAssistant: (messageID: SessionMessage.ID) => Effect.Effect<SessionMessage.Assistant | undefined>
14: readonly getCurrentShell: (callID: string) => Effect.Effect<SessionMessage.Shell | undefined>
15: readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect<void>
16: readonly updateShell: (shell: SessionMessage.Shell) => Effect.Effect<void>
17: readonly appendMessage: (message: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session.ts</path>
<type>file</type>
<content>
1: export * as SessionV2 from "./session"
2: export * from "./session/schema"
3:
4: import { DateTime, Effect, Layer, Schema, Context, Stream } from "effect"
5: import { ListAnchor } from "@opencode-ai/schema/session"
6: import { and, asc, desc, eq, gt, like, lt, or, type SQL } from "drizzle-orm"
7: import { ProjectV2 } from "./project"
8: import { WorkspaceV2 } from "./workspace"
9: import { ModelV2 } from "./model"
10: import { Location } from "./location"
11: import { SessionMessage } from "./session/message"
12: import { Prompt } from "./session/prompt"
13: import { PromptInput } from "@opencode-ai/schema/prompt-input"
14: import { EventV2 } from "./event"
15: import { Database } from "./database/database"
16: import { SessionProjector } from "./session/projector"
17: import { SessionMessageTable, SessionTable } from "./session/sql"
18: import { SessionSchema } from ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session.ts</path>
<type>file</type>
<content>
360: prompt: Effect.fn("V2Session.prompt")((input) =>
361: Effect.uninterruptible(
362: Effect.gen(function* () {
363: yield* result.get(input.sessionID)
364: const prompt = resolvePrompt(input.prompt)
365: const messageID = input.id ?? SessionMessage.ID.create()
366: const delivery = input.delivery ?? "steer"
367: const expected = { sessionID: input.sessionID, messageID, prompt, delivery }
368: const admission = yield* SessionInput.admit(db, events, {
369: id: messageID,
370: sessionID: input.sessionID,
371: prompt,
372: delivery,
373: requestExecution: input.resume !== false,
374: }).pipe(
375: Effect.catchDefect((defect) =>
376: defect instanceof ...
Found 41 matches
/tmp/opencode/session-recovery-v23-review/packages/core/test/session-run-coordinator.test.ts:
Line 31: it.effect("joins a wake-started execution without forcing a successor", () =>
Line 218: it.effect("interrupts active execution and preserves its pending wake", () =>
Line 223: const successorStarted = yield* Deferred.make<void>()
Line 234: : Deferred.succeed(successorStarted, undefined),
Line 244: yield* Deferred.await(successorStarted)
Line 255: it.effect("runs a wake registered during interruption cleanup", () =>
Line 293: it.effect("preserves a wake registered during interruption setup", () =>
Line 415: it.effect("joins concurrent interruption behind one cleanup barrier", () =>
/tmp/opencode/session-recovery-v23-review/packages/core/test/session-execution-local.test.ts:
Line 122: afterClaimed: Deferred.succeed(claimed, undefined).pipe(Effect.andThen(Deferred.await(releaseClaim))),
...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/test/session-execution-local.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect } from "bun:test"
2: import { Deferred, Effect, Exit, Fiber, Scope } from "effect"
3: import { TestClock } from "effect/testing"
4: import { eq, sql } 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 { SessionExecutionClaim } from "@opencode-ai/core/session/execution/claim"
11: import { SessionMessage } from "@opencode-ai/core/session/message"
12: import { SessionSchema } from "@opencode-ai/core/session/schema"
13: import { SessionExecutionTable, SessionTable } from "@opencode-ai/core/session/sql"
14: import { testEffect ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/session/sql.ts</path>
<type>file</type>
<content>
1: import { sql } from "drizzle-orm"
2: import { check, sqliteTable, text, integer, index, primaryKey, real, uniqueIndex } from "drizzle-orm/sqlite-core"
3: import * as DatabasePath from "../database/path"
4: import { ProjectTable } from "../project/sql"
5: import type { SessionMessage } from "./message"
6: import type { Prompt } from "./prompt"
7: import type { SessionInput } from "./input"
8: import type { Snapshot } from "../snapshot"
9: import { PermissionV1 } from "../v1/permission"
10: import { ProjectV2 } from "../project"
11: import type { SessionSchema } from "./schema"
12: import type { MessageID, PartID, SessionV1 } from "../v1/session"
13: import { WorkspaceV2 } from "../workspace"
14: import { Timestamps } from "../database/schema.sql"
15: import type { SystemContext } from "../system-context/index"
16: import { AgentV2 } from "../agent"
17: import type { ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/database/migration/20260914170650_session-recovery.ts</path>
<type>file</type>
<content>
1: import { Effect } from "effect"
2: import type { DatabaseMigration } from "../migration"
3:
4: export default {
5: id: "20260914170650_session-recovery",
6: up(tx) {
7: return Effect.gen(function* () {
8: yield* tx.run(`ALTER TABLE \`session_input\` ADD \`wake_pending\` integer DEFAULT false NOT NULL;`)
9: yield* tx.run(
10: `ALTER TABLE \`session\` ADD \`wake_attempts\` integer DEFAULT 0 NOT NULL CONSTRAINT \`session_wake_attempts_check\` CHECK(\`wake_attempts\` >= 0 AND \`wake_attempts\` <= 9007199254740991);`,
11: )
12: yield* tx.run(`
13: CREATE TABLE \`session_execution\` (
14: \`id\` text NOT NULL,
15: \`session_id\` text NOT NULL,
16: \`owner_id\` text NOT NULL,
17: \`assistant_message_id\` text,
18: \`phase\` text NOT ...
Found 5 matches /tmp/opencode/session-recovery-v23-review/packages/core/src/session/projector.ts: Line 515: .delete(SessionMessageTable) /tmp/opencode/session-recovery-v23-review/packages/core/test/session-runner.test.ts: Line 415: yield* db.delete(SessionMessageTable).where(eq(SessionMessageTable.session_id, id)).run().pipe(Effect.orDie) /tmp/opencode/session-recovery-v23-review/packages/core/test/session-projector.test.ts: Line 159: yield* db.delete(SessionMessageTable).where(eq(SessionMessageTable.session_id, sessionID)).run() Line 198: yield* db.delete(SessionMessageTable).where(eq(SessionMessageTable.session_id, sessionID)).run() /tmp/opencode/session-recovery-v23-review/packages/core/test/session-prompt.test.ts: Line 114: yield* db.delete(SessionMessageTable).where(eq(SessionMessageTable.session_id, sessionID)).run().pipe(Effect.orDie)
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/database/migration.gen.ts</path>
<type>file</type>
<content>
1: import type { DatabaseMigration } from "./migration"
2:
3: export const migrations = (
4: await Promise.all([
5: import("./migration/20260127222353_familiar_lady_ursula"),
6: import("./migration/20260211171708_add_project_commands"),
7: import("./migration/20260213144116_wakeful_the_professor"),
8: import("./migration/20260225215848_workspace"),
9: import("./migration/20260227213759_add_session_workspace_id"),
10: import("./migration/20260228203230_blue_harpoon"),
11: import("./migration/20260303231226_add_workspace_fields"),
12: import("./migration/20260309230000_move_org_to_state"),
13: import("./migration/20260312043431_session_message_cursor"),
14: import("./migration/20260323234822_events"),
15: import("./migration/20260410174513_workspace-name"),
16: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/opencode/src/server/routes/instance/httpapi/server.ts</path>
<type>file</type>
<content>
1: import { Config as EffectConfig, Context, Effect, Layer } from "effect"
2: import { HttpApiBuilder, OpenApi } from "effect/unstable/httpapi"
3: import { HttpClient, HttpMiddleware, HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http"
4: import * as Socket from "effect/unstable/socket/Socket"
5: import { FSUtil } from "@opencode-ai/core/fs-util"
6: import * as Observability from "@opencode-ai/core/observability"
7: import { Account } from "@/account/account"
8: import { Agent } from "@/agent/agent"
9: import { Auth } from "@/auth"
10: import { BackgroundJob } from "@/background/job"
11: import { Command } from "@/command"
12: import { Config } from "@/config/config"
13: import { Workspace } from "@/control-plane/workspace"
14: import { Env } from "@/env"
15: import { EventV2Bridge } from "@/event-v2-bridge"
16: import { ...
<path>/tmp/opencode/session-recovery-v23-review/packages/opencode/src/server/routes/instance/httpapi/server.ts</path>
<type>file</type>
<content>
270: ])
271:
272: export function createRoutes(
273: corsOptions?: CorsOptions,
274: ): Layer.Layer<never, EffectConfig.ConfigError, RouteRequirements> {
275: const locationServiceMapV2 = buildLocationServiceMap()
276:
277: return Layer.mergeAll(
278: rootApiRoutes,
279: eventApiRoutes,
280: ptyConnectApiRoutes,
281: instanceRoutes,
282: serverRoutes,
283: docRoute,
284: uiRoute,
285: ).pipe(
286: Layer.provide([
287: errorLayer,
288: compressionLayer,
289: corsVaryFix,
290: fenceLayer,
291: cors(corsOptions),
292: AppNodeBuilderV1.build(MoveSession.node, [[LocationServiceMap.node, locationServiceMapV2]]),
293: HttpServer.layerServices,
294: ]),
295: Layer.provide(Layer.succeed(CorsConfig)(corsOptions)),
296: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/server/src/routes.ts</path>
<type>file</type>
<content>
1: import { Database } from "@opencode-ai/core/database/database"
2: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
3: import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
4: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
5: import { EventV2 } from "@opencode-ai/core/event"
6: import { Credential } from "@opencode-ai/core/credential"
7: import { PermissionSaved } from "@opencode-ai/core/permission/saved"
8: import { PtyTicket } from "@opencode-ai/core/pty/ticket"
9: import { SessionV2 } from "@opencode-ai/core/session"
10: import { SessionExecution } from "@opencode-ai/core/session/execution"
11: import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
12: import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
13: import { SessionRecovery } from ...
Found 55 matches
/tmp/opencode/session-recovery-v23-review/packages/core/test/session-projector.test.ts:
Line 68: it.effect("atomically reconciles one abandoned execution into one continuation", () =>
Line 222: it.effect("never continues a Step that durably failed before owner loss", () =>
Line 325: it.effect("records a recovery continuation without forging an assistant identity", () =>
Line 361: it.effect("recovers each abandoned execution once across repeated startup scans", () =>
Line 407: it.effect("never recovers a live execution lease", () =>
Line 445: it.effect("never recovers an execution superseded by a newer assistant", () =>
Line 493: it.effect(`never recovers an assistant-less ${phase} execution superseded by a newer assistant`, () =>
Line 538: it.effect(`never commits assistant-less ${phase} recovery after concurrent assistant supersession`, () =>
Line 607: it.effect("recovers assistant-less continuation at its current ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect } from "bun:test"
2: import { DateTime, Deferred, Effect, Exit, Fiber, Schema } from "effect"
3: import { TestClock } from "effect/testing"
4: import { asc, eq, sql } from "drizzle-orm"
5: import { Database } from "@opencode-ai/core/database/database"
6: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
7: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
8: import { EventV2 } from "@opencode-ai/core/event"
9: import { EventTable } from "@opencode-ai/core/event/sql"
10: import { ModelV2 } from "@opencode-ai/core/model"
11: import { Project } from "@opencode-ai/core/project"
12: import { ProjectTable } from "@opencode-ai/core/project/sql"
13: import { ProviderV2 } from "@opencode-ai/core/provider"
14: import { AbsolutePath } from "@opencode-ai/core/schema"
15: import { SessionV2 } from ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/test/database-migration.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import { $ } from "bun"
3: import { fileURLToPath } from "url"
4: import path from "path"
5: import { SqliteClient } from "@effect/sql-sqlite-bun"
6: import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
7: import { Effect, Exit, Layer } from "effect"
8: import { eq, inArray, sql } from "drizzle-orm"
9: import { DatabaseMigration } from "@opencode-ai/core/database/migration"
10: import { migrations } from "@opencode-ai/core/database/migration.gen"
11: import workspaceNameMigration from "@opencode-ai/core/database/migration/20260410174513_workspace-name"
12: import sessionUsageMigration from "@opencode-ai/core/database/migration/20260510033149_session_usage"
13: import normalizeStoragePathsMigration from "@opencode-ai/core/database/migration/20260601010001_normalize_storage_paths"
14: ...
Found 21 matches
/tmp/opencode/session-recovery-v23-review/packages/core/src/database/schema.gen.ts:
Line 158: CREATE TABLE \`session_execution\` (
Line 168: CONSTRAINT \`session_execution_pk\` PRIMARY KEY(\`id\`),
Line 169: CONSTRAINT \`fk_session_execution_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE,
Line 170: CONSTRAINT "session_execution_phase_check" CHECK("phase" IN ('ready', 'safe', 'unknown', 'continue', 'cancelling', 'cancelled'))
Line 181: \`wake_pending\` integer DEFAULT false 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 \`fk_session_recovery_session_id_session_id_fk\` ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/src/database/schema.gen.ts</path> <type>file</type> <content> 145: CONSTRAINT \`fk_part_message_id_message_id_fk\` FOREIGN KEY (\`message_id\`) REFERENCES \`message\`(\`id\`) ON DELETE CASCADE 146: ); 147: `) 148: yield* tx.run(` 149: CREATE TABLE \`session_context_epoch\` ( 150: \`session_id\` text PRIMARY KEY, 151: \`baseline\` text NOT NULL, 152: \`snapshot\` text NOT NULL, 153: \`baseline_seq\` integer NOT NULL, 154: CONSTRAINT \`fk_session_context_epoch_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE 155: ); 156: `) 157: yield* tx.run(` 158: CREATE TABLE \`session_execution\` ( 159: \`id\` text NOT NULL, 160: \`session_id\` text NOT NULL, 161: \`owner_id\` text NOT NULL, 162: \`assistant_message_id\` ...
Found 11 matches
/tmp/opencode/session-recovery-v23-review/packages/client/src/generated/types.ts:
Line 402: readonly resume?: boolean | null
Line 420: readonly resume?: boolean | null
Line 438: readonly resume?: boolean | null
Line 440: readonly resume?: {
Line 456: readonly resume?: boolean | null
Line 778: readonly requestExecution?: boolean
Line 784: readonly type: "session.next.prompt.execution_requested"
Line 908: readonly type: "session.next.step.recovered"
Line 1272: readonly requestExecution?: boolean
Line 1278: readonly type: "session.next.prompt.execution_requested"
Line 1402: readonly type: "session.next.step.recovered"
<path>/tmp/opencode/session-recovery-v23-review/packages/client/src/generated/types.ts</path>
<type>file</type>
<content>
380: }
381:
382: export type SessionsSwitchModelOutput = void
383:
384: export type SessionsPromptInput = {
385: readonly sessionID: { readonly sessionID: string }["sessionID"]
386: readonly id?: {
387: readonly id?: string | null
388: readonly prompt: {
389: readonly text: string
390: readonly files?: ReadonlyArray<{
391: readonly uri: string
392: readonly name?: string
393: readonly description?: string
394: readonly source?: { readonly start: number; readonly end: number; readonly text: string }
395: }>
396: readonly agents?: ReadonlyArray<{
397: readonly name: string
398: readonly source?: { readonly start: number; readonly end: number; readonly text: string }
399: }>
400: }
401: readonly delivery?: "steer" | "queue" | null
402: readonly resume?: boolean | ...
No files found
No files found
Found 2 matches
/tmp/opencode/session-recovery-v23-review/packages/server/src/handlers/session.ts:
Line 140: "session.prompt",
Line 144: .prompt({
<path>/tmp/opencode/session-recovery-v23-review/packages/server/src/handlers/session.ts</path>
<type>file</type>
<content>
110: yield* session.switchAgent({ sessionID: ctx.params.sessionID, agent: ctx.payload.agent }).pipe(
111: Effect.catchTag("Session.NotFoundError", (error) =>
112: Effect.fail(
113: new SessionNotFoundError({
114: sessionID: error.sessionID,
115: message: `Session not found: ${error.sessionID}`,
116: }),
117: ),
118: ),
119: )
120: return HttpApiSchema.NoContent.make()
121: }),
122: )
123: .handle(
124: "session.switchModel",
125: Effect.fn(function* (ctx) {
126: yield* session.switchModel({ sessionID: ctx.params.sessionID, model: ctx.payload.model }).pipe(
127: Effect.catchTag("Session.NotFoundError", (error) =>
128: Effect.fail(
129: ...
Found 34 matches /tmp/opencode/session-recovery-final-v23.patch: Line 1: diff --git a/packages/client/src/generated/types.ts b/packages/client/src/generated/types.ts Line 109: diff --git a/packages/core/schema.json b/packages/core/schema.json Line 1022: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts Line 1033: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts Line 1101: diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts Line 1207: diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts Line 1247: diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts Line 1336: diff --git a/packages/core/src/session/context-epoch.ts b/packages/core/src/session/context-epoch.ts Line 1512: diff --git ...
/usr/bin/bash: line 1: python: command not found
paths=34 generated_sha256=14035d7631ba25b820fdc553087469935f447af034d549e2f14f8a141eda59dc patch_sha256=14035d7631ba25b820fdc553087469935f447af034d549e2f14f8a141eda59dc exact=True errors=[]
Found 2 matches /tmp/opencode/session-recovery-v23-review/packages/core/src/session/execution/local.ts: Line 208: : SessionExecutionClaim.settleCancellation(db, claim), Line 249: Effect.flatMap((acquired) => (acquired ? settleCancellation(claim) : Effect.void)),
Found 29 matches
/tmp/opencode/session-recovery-v23-review/packages/core/test/session-prompt.test.ts:
Line 140: it.effect("exposes the execution registry", () =>
Line 147: it.effect("delegates execution continuation through SessionExecution", () =>
Line 159: it.effect("delegates process-local interruption through SessionExecution", () =>
Line 171: it.effect("delegates interruption without requiring a recorded Session", () =>
Line 181: it.effect("durably admits one user message before transcript promotion", () =>
Line 203: it.effect("resolves attachment MIME before admission", () =>
Line 224: it.effect("streams durable Session events after an aggregate sequence", () =>
Line 254: it.effect("resumes through a recorded message without appending another prompt", () =>
Line 275: it.effect("records distinct messages when the ID is omitted", () =>
Line 290: it.effect("returns the original recorded message when the ID is retried", () =>
Line ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/test/session-prompt.test.ts</path>
<type>file</type>
<content>
130: SessionExecution.Service.of({
131: active: Effect.succeed(new Set()),
132: resume: () => Effect.void,
133: interrupt: () => Effect.void,
134: wake: (id) => Effect.sync(() => wakeCalls.push(id)).pipe(Effect.asVoid),
135: }),
136: ),
137: )
138:
139: describe("SessionV2.prompt", () => {
140: it.effect("exposes the execution registry", () =>
141: Effect.gen(function* () {
142: activeSessions.add(sessionID)
143: expect(Array.from(yield* (yield* SessionV2.Service).active)).toEqual([sessionID])
144: }).pipe(Effect.ensuring(Effect.sync(() => activeSessions.clear()))),
145: )
146:
147: it.effect("delegates execution continuation through SessionExecution", () =>
148: Effect.gen(function* () {
149: yield* setup
150: const session = yield* SessionV2.Service
151: ...
Found 100 matches (more matches available)
/tmp/opencode/session-recovery-v23-review/packages/core/test/session-runner.test.ts:
Line 619: it.effect("redrives only the synthetic recovery continuation", () =>
Line 688: it.effect("clears durable execution intent when ordinary input is promoted", () =>
Line 715: it.effect("redrives an ordinary prompt after its advisory wake is lost", () =>
Line 752: it.effect("never dispatches a provider after ownership changes", () =>
Line 778: it.effect("never publishes streamed output after ownership changes", () =>
Line 805: it.effect("never starts a local tool after ownership changes", () =>
Line 845: it.effect("never fails interrupted tools after ownership changes", () =>
Line 902: it.effect("advertises and executes a globally attached application tool", () =>
Line 958: it.effect("starts a real runner turn after default prompt recording", () =>
Line 977: it.effect("streams one request with registry ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
580: responseStream = Stream.concat(
581: Stream.fromIterable(fixture.partialEvents),
582: Stream.fromEffect(Deferred.succeed(streamed, undefined)).pipe(Stream.flatMap(() => Stream.never)),
583: )
584:
585: const runner = yield* SessionRunner.Service
586: yield* insertExecution(sessionID)
587: const fiber = yield* runner.run({ sessionID, force: true, executionID, ownerID: "test" }).pipe(Effect.forkChild)
588: yield* Deferred.await(streamed)
589: yield* Fiber.interrupt(fiber)
590: const { db } = yield* Database.Service
591: expect(
592: yield* db
593: .select({ id: SessionExecutionTable.id, phase: SessionExecutionTable.phase })
594: .from(SessionExecutionTable)
595: .where(eq(SessionExecutionTable.session_id, sessionID))
596: .get(),
597: ).toEqual({ id: executionID, phase: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1010: requests.length = 0
1011:
1012: const exit = yield* session.resume(sessionID).pipe(Effect.exit)
1013:
1014: expect(Exit.isFailure(exit)).toBe(true)
1015: if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(SystemContext.InitializationBlocked)
1016: expect(requests).toHaveLength(0)
1017: expect(yield* SessionInput.hasPending(db, sessionID, "steer")).toBe(true)
1018: expect(
1019: yield* db
1020: .select()
1021: .from(SessionContextEpochTable)
1022: .where(eq(SessionContextEpochTable.session_id, sessionID))
1023: .get(),
1024: ).toBeUndefined()
1025:
1026: systemUnavailable = false
1027: yield* session.prompt({ id: messageID, sessionID, prompt: Prompt.make({ text: "First" }) })
1028:
1029: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
1: export * as SessionEvent from "./session-event"
2:
3: import { Schema } from "effect"
4: import { optional } from "./schema"
5: import { Event } from "./event"
6: import { ProviderMetadata, ToolContent } from "./llm"
7: import { Delivery } from "./session-delivery"
8: import { Model } from "./model"
9: import { DateTimeUtcFromMillis, NonNegativeInt, RelativePath } from "./schema"
10: import { FileAttachment, Prompt } from "./prompt"
11: import { SessionID } from "./session-id"
12: import { Location } from "./location"
13: import { SessionMessage } from "./session-message"
14: import { Revert } from "./revert"
15:
16: export { FileAttachment }
17:
18: export const Source = Schema.Struct({
19: start: NonNegativeInt,
20: end: NonNegativeInt,
21: text: Schema.String,
22: }).annotate({
23: identifier: "session.next.event.source",
24: })
25: export interface ...
diff --git a/packages/core/src/session/message-updater.ts b/packages/core/src/session/message-updater.ts
index 46118a89fe..e2d7e6b38e 100644
--- a/packages/core/src/session/message-updater.ts
+++ b/packages/core/src/session/message-updater.ts
@@ -1,44 +1,45 @@
import { castDraft, produce, type WritableDraft } from "immer"
import { Effect } from "effect"
import { SessionEvent } from "./event"
import { SessionMessage } from "./message"
+import { message } from "./recovery-id"
export type MemoryState = {
messages: SessionMessage.Message[]
}
export interface Adapter {
readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined>
readonly getAssistant: (messageID: SessionMessage.ID) => Effect.Effect<SessionMessage.Assistant | undefined>
readonly getCurrentShell: (callID: string) => Effect.Effect<SessionMessage.Shell | undefined>
readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect<void>
readonly updateShell: ...
Found 13 matches /tmp/opencode/session-recovery-v23-review/packages/sdk/js/src/v2/gen/types.gen.ts: Line 874: requestExecution?: boolean Line 879: type: "session.next.prompt.execution_requested" Line 983: type: "session.next.step.recovered" Line 3430: requestExecution?: boolean Line 3439: type: "session.next.prompt.execution_requested.1" Line 3606: type: "session.next.step.recovered.1" Line 4362: requestExecution?: boolean Line 4371: type: "session.next.prompt.execution_requested" Line 4565: type: "session.next.step.recovered" Line 6464: requestExecution?: boolean Line 6470: type: "session.next.prompt.execution_requested" Line 6583: type: "session.next.step.recovered" Line 11751: resume?: boolean
<path>/tmp/opencode/session-recovery-v23-review/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
850: location: LocationRef
851: subdirectory?: string
852: }
853: }
854: | {
855: id: string
856: type: "session.next.prompted"
857: properties: {
858: timestamp: number
859: sessionID: string
860: messageID: string
861: prompt: Prompt
862: delivery: "steer" | "queue"
863: }
864: }
865: | {
866: id: string
867: type: "session.next.prompt.admitted"
868: properties: {
869: timestamp: number
870: sessionID: string
871: messageID: string
872: prompt: Prompt
873: delivery: "steer" | "queue"
874: requestExecution?: boolean
875: }
876: }
877: | {
878: id: string
879: type: "session.next.prompt.execution_requested"
880: ...
Found 13 matches
/tmp/opencode/session-recovery-v23-review/packages/sdk/openapi.json:
Line 10604: "resume": {
Line 17899: "requestExecution": {
Line 17919: "enum": ["session.next.prompt.execution_requested"]
Line 18289: "enum": ["session.next.step.recovered"]
Line 25709: "requestExecution": {
Line 25740: "enum": ["session.next.prompt.execution_requested.1"]
Line 26299: "enum": ["session.next.step.recovered.1"]
Line 28599: "requestExecution": {
Line 28622: "enum": ["session.next.prompt.execution_requested"]
Line 29190: "enum": ["session.next.step.recovered"]
Line 34811: "requestExecution": {
Line 34831: "enum": ["session.next.prompt.execution_requested"]
Line 35201: "enum": ["session.next.step.recovered"]
<path>/tmp/opencode/session-recovery-v23-review/packages/sdk/openapi.json</path>
<type>file</type>
<content>
10570: }
10571: }
10572: }
10573: },
10574: "409": {
10575: "description": "ConflictError",
10576: "content": {
10577: "application/json": {
10578: "schema": {
10579: "$ref": "#/components/schemas/ConflictError"
10580: }
10581: }
10582: }
10583: }
10584: },
10585: "description": "Durably admit one session input and schedule agent-loop execution unless resume is false.",
10586: "summary": "Send message",
10587: "requestBody": {
10588: "content": {
10589: "application/json": {
10590: "schema": {
10591: "type": "object",
10592: "properties": {
10593: "id": {
10594: ...
Found 2 matches
/tmp/opencode/session-recovery-v23-review/packages/server/src/api.ts:
Line 3: import { SessionLocationMiddleware } from "./middleware/session-location"
Line 7: sessionLocationMiddleware: SessionLocationMiddleware,
<path>/tmp/opencode/session-recovery-v23-review/packages/server/src/api.ts</path>
<type>file</type>
<content>
1: import { makeDefaultApi } from "@opencode-ai/protocol/api"
2: import { LocationMiddleware } from "./location"
3: import { SessionLocationMiddleware } from "./middleware/session-location"
4:
5: export const Api = makeDefaultApi({
6: locationMiddleware: LocationMiddleware,
7: sessionLocationMiddleware: SessionLocationMiddleware,
8: })
(End of file - total 8 lines)
</content>
diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts
index 2dabfb2d6f..1fbd6285d0 100644
--- a/packages/core/src/session.ts
+++ b/packages/core/src/session.ts
@@ -288,199 +288,201 @@ const layer = Layer.effect(
)!,
)
}
const query = db
.select()
.from(SessionTable)
.where(conditions.length > 0 ? and(...conditions) : undefined)
.orderBy(
order === "asc" ? asc(sortColumn) : desc(sortColumn),
order === "asc" ? asc(SessionTable.id) : desc(SessionTable.id),
)
const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe(
Effect.orDie,
)
return (direction === "previous" ? rows.toReversed() : rows).map((row) => fromRow(row))
}),
messages: Effect.fn("V2Session.messages")(function* (input) {
yield* result.get(input.sessionID)
const direction = ...
<path>/tmp/opencode/session-recovery-v23-review/packages/schema/test/event-manifest.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import { FileSystem, Integration, Permission, Project, Reference, Session, Workspace } from "../src"
3: import { EventManifest } from "../src/event-manifest"
4: import { IdeEvent } from "../src/ide-event"
5: import { SessionEvent } from "../src/session-event"
6: import { SessionTodo } from "../src/session-todo"
7: import { SessionV1 } from "../src/session-v1"
8: import { WorkspaceEvent } from "../src/workspace-event"
9:
10: describe("public event manifest", () => {
11: test("owns the complete public event surface", () => {
12: expect(EventManifest.ServerDefinitions.length).toBe(61)
13: expect(EventManifest.Definitions.length).toBe(91)
14: expect(SessionV1.Event.Definitions).toEqual([
15: SessionV1.Event.Created,
16: SessionV1.Event.Updated,
17: SessionV1.Event.Deleted,
18: ...
diff --git a/packages/core/test/session-runner-recorded.test.ts b/packages/core/test/session-runner-recorded.test.ts
index d45cc8c734..ec91d1b4a4 100644
--- a/packages/core/test/session-runner-recorded.test.ts
+++ b/packages/core/test/session-runner-recorded.test.ts
@@ -1,52 +1,52 @@
import { HttpRecorder } from "@opencode-ai/http-recorder"
import { HttpRecorderInternal } from "@opencode-ai/http-recorder/internal"
import * as OpenAIChat from "@opencode-ai/llm/protocols/openai-chat"
import { Auth, LLMClient, RequestExecutor } from "@opencode-ai/llm/route"
import { Database } from "@opencode-ai/core/database/database"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { EventV2 } from "@opencode-ai/core/event"
import { EventTable } from "@opencode-ai/core/event/sql"
import { PermissionV2 } from ...
Found 19 matches
/tmp/opencode/session-recovery-v23-review/packages/core/test/session-run-coordinator.test.ts:
Line 9: it.effect("joins concurrent resumes for one key", () =>
Line 31: it.effect("joins a wake-started execution without forcing a successor", () =>
Line 57: it.effect("starts execution when woken while idle", () =>
Line 69: it.effect("snapshots only active executions", () =>
Line 102: it.effect("cleans active executions after failure and defect", () =>
Line 122: it.effect("cleans active executions when its scope closes", () =>
Line 141: it.effect("coalesces wakes received during active execution", () =>
Line 173: it.effect("runs again when woken during the follow-up", () =>
Line 209: it.effect("does nothing when interrupted while idle", () =>
Line 218: it.effect("interrupts active execution and preserves its pending wake", () =>
Line 255: it.effect("runs a wake registered during interruption cleanup", () =>
Line 293: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/test/session-run-coordinator.test.ts</path>
<type>file</type>
<content>
360: Effect.andThen(Deferred.await(cleanupGate)),
361: Effect.andThen(Effect.die(defect)),
362: ),
363: )
364: .pipe(Effect.exit, Effect.forkChild)
365: yield* Deferred.await(cleanupStarted)
366: yield* coordinator.wake("session")
367: yield* Deferred.succeed(cleanupGate, undefined)
368:
369: const exit = yield* Fiber.join(interrupted)
370: expect(Exit.isFailure(exit) && Cause.hasDies(exit.cause)).toBeTrue()
371: yield* Deferred.await(secondStarted)
372: yield* coordinator.run("session")
373: expect(Array.from(yield* coordinator.active)).toEqual([])
374: expect(runs).toBe(2)
375: }),
376: ),
377: )
378:
379: it.effect("starts a resume registered during interruption cleanup", () =>
380: ...
Found 1 matches
/home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/Effect.ts:
Line 2066: export const andThen: {
<path>/home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/Effect.ts</path> <type>file</type> <content> 2040: * const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100)) 2041: * 2042: * // Using Effect.map and Effect.flatMap 2043: * const result1 = pipe( 2044: * fetchTransactionAmount, 2045: * Effect.map((amount) => amount * 2), 2046: * Effect.flatMap((amount) => applyDiscount(amount, 5)) 2047: * ) 2048: * 2049: * Effect.runPromise(result1).then(console.log) 2050: * // Output: 190 2051: * 2052: * // Using Effect.andThen 2053: * const result2 = pipe( 2054: * fetchTransactionAmount, 2055: * Effect.andThen((amount) => Effect.succeed(amount * 2)), 2056: * Effect.andThen((amount) => applyDiscount(amount, 5)) 2057: * ) 2058: * 2059: * Effect.runPromise(result2).then(console.log) 2060: * // Output: 190 2061: * ``` 2062: * 2063: * @category sequencing 2064: * @since 2.0.0 2065: */ 2066: export const ...
Found 3 matches
/home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/Deferred.ts:
Line 321: internalEffect.sync(() => doneUnsafe(self, effect))
Line 839: * const success = Deferred.doneUnsafe(deferred, Effect.succeed(42))
Line 846: export const doneUnsafe = <A, E>(self: Deferred<A, E>, effect: Effect<A, E>): boolean => {
<path>/home/byk/.local/share/opencode/repos/github.com/Effect-TS/effect-smol/packages/effect/src/Deferred.ts</path>
<type>file</type>
<content>
820: * completion effect.
821: *
822: * **When to use**
823: *
824: * Use to complete a `Deferred` synchronously in low-level code that already has
825: * the completion effect.
826: *
827: * **Details**
828: *
829: * This mutates the `Deferred` directly and should be reserved for low-level
830: * code; prefer the effectful completion APIs when possible. Returns `true` if
831: * this call completed the `Deferred`, or `false` if it was already completed.
832: *
833: * **Example** (Completing a Deferred unsafely)
834: *
835: * ```ts
836: * import { Deferred, Effect } from "effect"
837: *
838: * const deferred = Deferred.makeUnsafe<number>()
839: * const success = Deferred.doneUnsafe(deferred, Effect.succeed(42))
840: * console.log(success) // true
841: * ```
842: *
843: * @category unsafe
844: * @since 4.0.0
845: ...
Found 1 matches /tmp/opencode/session-recovery-v23-review/packages/server/src/handlers/session.ts: Line 368: yield* session.interrupt(ctx.params.sessionID)
<path>/tmp/opencode/session-recovery-v23-review/packages/server/src/handlers/session.ts</path>
<type>file</type>
<content>
340: })
341: .pipe(
342: Effect.map((page) => ({
343: data: page.events,
344: hasMore: page.hasMore,
345: })),
346: Effect.catchTag(
347: "Session.NotFoundError",
348: (error) =>
349: new SessionNotFoundError({
350: sessionID: error.sessionID,
351: message: `Session not found: ${error.sessionID}`,
352: }),
353: ),
354: )
355: }),
356: )
357: .handle(
358: "session.events",
359: Effect.fn((ctx) =>
360: Effect.succeed(
361: session.events({ sessionID: ctx.params.sessionID, after: ctx.query.after }).pipe(Stream.orDie),
362: ),
363: ),
364: )
365: ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3820: Stream.fromIterable([
3821: LLMEvent.stepStart({ index: 0 }),
3822: LLMEvent.toolCall({ id: "call-blocked-finalizer", name: "blocked_with_finalizer", input: {} }),
3823: LLMEvent.toolCall({ id: "call-sibling-defect", name: "sibling_defect", input: {} }),
3824: ]),
3825: Stream.fail(failure),
3826: )
3827:
3828: const run = yield* session.resume(sessionID).pipe(Effect.exit, Effect.forkChild)
3829: const started = yield* Deferred.await(finalizerStarted).pipe(Effect.timeout("1 second"), Effect.exit)
3830: if (Exit.isFailure(started)) {
3831: yield* Deferred.succeed(releaseFinalizer, undefined)
3832: return yield* started
3833: }
3834:
3835: const retained = yield* db.select().from(SessionExecutionTable).get()
3836: yield* ...
Found 57 matches /tmp/opencode/session-recovery-v23-review/packages/core/schema.json: Line 66: "name": "session_execution", Line 78: "name": "session_recovery", Line 941: "table": "session_execution" Line 951: "table": "session_execution" Line 961: "table": "session_execution" Line 971: "table": "session_execution" Line 981: "table": "session_execution" Line 991: "table": "session_execution" Line 1001: "table": "session_execution" Line 1011: "table": "session_execution" Line 1021: "table": "session_execution" Line 1089: "name": "wake_pending", 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 ...
<path>/tmp/opencode/session-recovery-v23-review/packages/core/schema.json</path>
<type>file</type>
<content>
900: "entityType": "columns",
901: "table": "session_context_epoch"
902: },
903: {
904: "type": "text",
905: "notNull": true,
906: "autoincrement": false,
907: "default": null,
908: "generated": null,
909: "name": "baseline",
910: "entityType": "columns",
911: "table": "session_context_epoch"
912: },
913: {
914: "type": "text",
915: "notNull": true,
916: "autoincrement": false,
917: "default": null,
918: "generated": null,
919: "name": "snapshot",
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": ...
Found 30 matches
/tmp/opencode/session-recovery-v23-review/packages/core/src/session/context-epoch.ts:
Line 134: yield* SessionExecutionClaim.transaction(
Line 158: const updated = yield* SessionExecutionClaim.transaction(
Line 182: const updated = yield* SessionExecutionClaim.transaction(
/tmp/opencode/session-recovery-v23-review/packages/core/src/session/execution/claim.ts:
Line 40: export const update = Effect.fn("SessionExecutionClaim.update")(function* (
Line 67: export const renew = Effect.fn("SessionExecutionClaim.renew")(function* (db: DatabaseService, claim: Claim) {
Line 84: export const settleCancellation = Effect.fn("SessionExecutionClaim.settleCancellation")(function* (
Line 100: export const clear = Effect.fn("SessionExecutionClaim.clear")(function* (db: DatabaseService, claim: Claim) {
/tmp/opencode/session-recovery-v23-review/packages/core/src/session/execution/local.ts:
Line 104: SessionExecutionClaim.renew(db, ...
No files found
14035d7631ba25b820fdc553087469935f447af034d549e2f14f8a141eda59dc /tmp/opencode/session-recovery-final-v23.patch f875987afa4336956b9f33e14eee6de0a4af90e2bc6de899cccb7cbe5a5df998 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server ab9408c81cc9916177641ae8e9f08624fc27b5b9 34
MUST-FIX — explicit resume is lost while interruption is stopping an execution.
packages/core/src/session/run-coordinator.ts:81 waits for entry.done and invokes run(key) with Effect.andThen, which only runs after success. Interruption stores the interrupted owner’s failed exit in entry.done at packages/core/src/session/run-coordinator.ts:115-128. A resume registered during cleanup therefore inherits the interruption and never starts its promised forced successor. No pendingWake is set by run. This contradicts the coordinator contract at line 13 and the regression expectation at packages/core/test/session-run-coordinator.test.ts:379-410. With no pending input, the resume disappears permanently; with durable input, recovery only retries it later.
CONCERN — an interrupt racing successful claim cleanup can defect after settlement.
Successful drain deletes the durable claim at packages/core/src/session/execution/local.ts:114-118, but the prospective claim remains visible until activeClaims cleanup at lines 141-147. An interrupt in that window observes the stale registration at lines 239-243 and runs the cancellation transition at lines 263-268. That transition dies with SessionExecutionClaim.Lost. The Lost reconciliation at lines 250-260 wraps only post-interruption cleanup, not this pre-interruption transition, so an otherwise successful interrupt request can surface an internal defect/HTTP 500.
ab9408c81cc9916177641ae8e9f08624fc27b5b914035d7631ba25b820fdc553087469935f447af034d549e2f14f8a141eda59dcf875987afa4336956b9f33e14eee6de0a4af90e2bc6de899cccb7cbe5a5df998Date: Sep 17, 2026 * 🟡 (10:02) User supplied a partial code excerpt from the Session recovery implementation; visible imports include `makeGlobalNode` from `../../effect/app-node` at line 5, `SessionExecution` from `../execution` at line 11, and `SessionExecutionTable` at line 17. * 🟡 (10:02) The recovery acquisition flow constructs `const claim = { sessionID, executionID, ownerID }` at line 43…
Date: Sep 17, 2026 * 🔴 [requested-security-review] (10:00) User requested a strict READ-ONLY security/pentest review of frozen v23 at `/tmp/opencode/session-recovery-v23-review`; prohibited editing, running tests/builds/services, mutating VCS/index/refs, and using recall. * 🔴 [enforced-workflow] (10:00) User required immutable-input verification before review: base commit `ab9408c81cc9916177641…