Dashboard › opencode › Session 0KwX3B0dBb2S
0KwX3B0dBb2SzqCQgRead-only independent security review frozen v15. Base ab9408c81cc9916177641ae8e9f08624fc27b5b9. Patch /tmp/opencode/session-recovery-final-v15.patch SHA-256 509563ba0cd17246a1d223ac7a6f3fabee1a49892c61b215c3fd7ca4744b6c77. SEA /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server SHA-256 83a833aa93cc185825feffe107fba1bd8e3789d1ff5fe5f8cda7df1962aa078d. Verify exact immutable inputs/clean apply/34 paths; inspect every path. No mutation/tests/builds/services. Focus v14 closures: cancelling->cancelled settlement only after durable interrupted tool/Step publication; only explicit coordinator cleanup clears; publication defect leaves cancelling. Explicit resume durably arms oldest ordinary unpromoted input before execution claim/run, recovery-owned rows excluded, crash redrives exact input without synthetic continue. Current client generated contracts included. Scan index covers recovery_error/expires_at/time_created/id. Reassess stale owner, identity/cross-Session, retries/replay, fairness/DoS, cancellation, provider/tools, schemas/auth/dependencies. Accepted residual final check-call/process-local/skew. Evidence 247 recovery, 1184 Core all pass, six typechecks, manifest/migration/smoke pass. Return exact PASS/CONCERN/MUST-FIX file:line and end MERGE or DO-NOT-MERGE. Empty invalid; no recall.
509563ba0cd17246a1d223ac7a6f3fabee1a49892c61b215c3fd7ca4744b6c77 /tmp/opencode/session-recovery-final-v15.patch 83a833aa93cc185825feffe107fba1bd8e3789d1ff5fe5f8cda7df1962aa078d /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server ab9408c81cc9916177641ae8e9f08624fc27b5b9 dc661f30cb2055ad6248fdc59b14998d834497be M .lore.md M AGENTS.md 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/migration/20260914170650_session-recovery.ts D packages/core/src/database/migration/20260914214636_session-execution.ts D packages/core/src/database/migration/20260914222526_session-execution-phase.ts M packages/core/src/database/schema.gen.ts M packages/core/src/session.ts M packages/core/src/session/compaction.ts M packages/core/src/session/context-epoch.ts M packages/core/src/session/execution/local.ts M packages/core/src/session/input.ts M ...
34 create mode 100644 packages/core/src/database/migration/20260914170650_session-recovery.ts create mode 100644 packages/core/src/session/recovery.ts create mode 100644 packages/core/test/session-execution-local.test.ts create mode 100644 packages/core/src/session/execution/claim.ts create mode 100644 packages/core/src/session/recovery-id.ts
804a75899c328134e0b1e0fd0ffc1c57d5fcb9095e8e2ad74eb76e071f7e69bb - 6522a67bf68d5a1ce285d760752e800ac53a7f9a547fa1b9199b0afd2afb8403 -
Found 34 matches /tmp/opencode/session-recovery-final-v15.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 1025: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts Line 1036: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts Line 1104: diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts Line 1210: diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts Line 1250: diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts Line 1339: diff --git a/packages/core/src/session/context-epoch.ts b/packages/core/src/session/context-epoch.ts Line 1515: diff --git ...
<path>/home/byk/Code/opencode/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<
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: const where = (claim: Claim, ...
<path>/home/byk/Code/opencode/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>/home/byk/Code/opencode/packages/core/src/session/recovery.ts</path>
<type>file</type>
<content>
1: export * as SessionRecovery from "./recovery"
2:
3: import { and, asc, desc, eq, exists, gt, isNull, lte, or, sql } from "drizzle-orm"
4: import { Clock, DateTime, Effect, Layer, Option, Schedule, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { makeGlobalNode } from "../effect/app-node"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionExecution } from "./execution"
10: import { SessionProjector } from "./projector"
11: import { SessionExecutionClaim } from "./execution/claim"
12: import { SessionInput } from "./input"
13: import { continuation } from "./recovery-id"
14: import { SessionSchema } from "./schema"
15: import {
16: MAX_WAKE_ATTEMPTS,
17: SESSION_EXECUTION_LEASE_MS,
18: SessionExecutionTable,
19: SessionInputTable,
20: SessionRecoveryTable,
21: SessionMessageTable,
22: ...
<path>/home/byk/Code/opencode/packages/core/src/session/execution/local.ts</path>
<type>file</type>
<content>
1: import { and, asc, desc, eq, isNull } from "drizzle-orm"
2: import { Cause, Clock, Duration, Effect, Exit, Layer, Schedule } from "effect"
3: import { Database } from "../../database/database"
4: import { LocationServiceMap } from "../../location-service-map"
5: import { makeGlobalNode } from "../../effect/app-node"
6: import { SessionRunCoordinator } from "../run-coordinator"
7: import { SessionRunner } from "../runner"
8: import { SessionMessage } from "../message"
9: import { SessionSchema } from "../schema"
10: import { SessionStore } from "../store"
11: import { SessionExecution } from "../execution"
12: import { SessionExecutionClaim } from "./claim"
13: import {
14: SESSION_EXECUTION_LEASE_MS,
15: SessionExecutionTable,
16: SessionMessageTable,
17: SessionRecoveryTable,
18: } from "../sql"
19:
20: /** Current-process routing for implicit-local Locations. ...
<path>/home/byk/Code/opencode/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: events: (input) ...
<path>/home/byk/Code/opencode/packages/core/src/session.ts</path>
<type>file</type>
<content>
427: resume: Effect.fn("V2Session.resume")(function* (sessionID) {
428: yield* result.get(sessionID)
429: yield* SessionInput.requestPendingExecution(db, events, sessionID)
430: yield* execution.resume(sessionID)
431: }),
432: interrupt: Effect.fn("V2Session.interrupt")((sessionID) =>
433: Effect.uninterruptible(execution.interrupt(sessionID)),
434: ),
435: revert: {
436: stage: Effect.fn("V2Session.revert.stage")(function* (input) {
437: const session = yield* result.get(input.sessionID)
438: return yield* SessionRevert.stage({ session, messageID: input.messageID, files: input.files }).pipe(
439: Effect.provideService(Database.Service, database),
440: Effect.provideService(EventV2.Service, events),
441: Effect.provide(locations.get(session.location)),
442: ...
<path>/home/byk/Code/opencode/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 = Schema.encodeSync(Prompt)
21: const ...
<path>/home/byk/Code/opencode/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
1: export * as SessionProjector from "./projector"
2:
3: import { and, desc, eq, gt, or, sql } from "drizzle-orm"
4: import { DateTime, Effect, Layer, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { makeGlobalNode } from "../effect/app-node"
8: import { SessionEvent } from "./event"
9: import { SessionV1 } from "../v1/session"
10: import { WorkspaceTable } from "../control-plane/workspace.sql"
11: import { SessionMessage } from "./message"
12: import { SessionMessageUpdater } from "./message-updater"
13: import { SessionInput } from "./input"
14: import { Prompt } from "./prompt"
15: import { continuation } from "./recovery-id"
16: import { WorkspaceV2 } from "../workspace"
17: import {
18: MessageTable,
19: PartTable,
20: SessionInputTable,
21: SessionMessageTable,
22: SessionRecoveryTable,
23: ...
<path>/home/byk/Code/opencode/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>/home/byk/Code/opencode/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. */
21: export interface Interface {
22: /** Drains eligible durable work. ...
<path>/home/byk/Code/opencode/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 "../../system-context/registry"
24: ...
<path>/home/byk/Code/opencode/packages/core/src/session/run-coordinator.ts</path>
<type>file</type>
<content>
1: export * as SessionRunCoordinator from "./run-coordinator"
2:
3: import { Deferred, Effect, Exit, Fiber, FiberSet, Scope } from "effect"
4:
5: /** Serializes execution for each key while allowing different keys to run concurrently. */
6: export interface Coordinator<Key, E> {
7: /** Snapshots keys with an execution owned by this coordinator. */
8: readonly active: Effect.Effect<ReadonlySet<Key>>
9: /** Starts execution while idle or joins the active execution. */
10: readonly run: (key: Key) => Effect.Effect<void, E>
11: /** Registers one coalesced follow-up after newly recorded work. */
12: readonly wake: (key: Key) => Effect.Effect<void>
13: /** Stops active execution, runs cleanup, then starts work that arrived while stopping. */
14: readonly interrupt: (key: Key, cleanup?: Effect.Effect<void>) => Effect.Effect<void>
15: }
16:
17: type Entry<E> = {
18: ...
<path>/home/byk/Code/opencode/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>/home/byk/Code/opencode/packages/core/src/session/context-epoch.ts</path>
<type>file</type>
<content>
1: export * as SessionContextEpoch from "./context-epoch"
2:
3: import { eq } from "drizzle-orm"
4: import { DateTime, Effect, Schema } from "effect"
5: import type { Database } from "../database/database"
6: import { EventV2 } from "../event"
7: import { SystemContext } from "../system-context/index"
8: import { ContextSnapshotDecodeError } from "./error"
9: import { SessionEvent } from "./event"
10: import { SessionHistory } from "./history"
11: import { SessionInput } from "./input"
12: import { SessionMessage } from "./message"
13: import { SessionSchema } from "./schema"
14: import { SessionContextEpochTable } from "./sql"
15: import { SessionExecutionClaim } from "./execution/claim"
16:
17: type DatabaseService = Database.Interface["db"]
18:
19: interface Prepared {
20: readonly baseline: string
21: readonly baselineSeq: number
22: }
23:
24: export function ...
<path>/home/byk/Code/opencode/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>/home/byk/Code/opencode/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 { Revert } from ...
<path>/home/byk/Code/opencode/packages/core/src/database/migration/20260914170650_session-recovery.ts</path>
<type>file</type>
<content>
1: import { Effect } from "effect"
2: import type { DatabaseMigration } from "../migration"
3:
4: export default {
5: id: "20260914170650_session-recovery",
6: up(tx) {
7: return Effect.gen(function* () {
8: yield* tx.run(`ALTER TABLE \`session_input\` ADD \`wake_pending\` integer DEFAULT false NOT NULL;`)
9: yield* tx.run(
10: `ALTER TABLE \`session\` ADD \`wake_attempts\` integer DEFAULT 0 NOT NULL CONSTRAINT \`session_wake_attempts_check\` CHECK(\`wake_attempts\` >= 0 AND \`wake_attempts\` <= 9007199254740991);`,
11: )
12: yield* tx.run(`
13: CREATE TABLE \`session_execution\` (
14: \`id\` text NOT NULL,
15: \`session_id\` text NOT NULL,
16: \`owner_id\` text NOT NULL,
17: \`assistant_message_id\` text,
18: \`phase\` text NOT NULL,
19: ...
Found 64 matches /home/byk/Code/opencode/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 1009: "name": "expires_at", Line 1011: "table": "session_execution" Line 1019: "name": "recovery_error", 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": ...
<path>/home/byk/Code/opencode/packages/core/schema.json</path>
<type>file</type>
<content>
2250: "where": null,
2251: "origin": "manual",
2252: "name": "part_session_idx",
2253: "entityType": "indexes",
2254: "table": "part"
2255: },
2256: {
2257: "columns": [
2258: {
2259: "value": "session_id",
2260: "isExpression": false
2261: }
2262: ],
2263: "isUnique": true,
2264: "where": null,
2265: "origin": "manual",
2266: "name": "session_execution_session_idx",
2267: "entityType": "indexes",
2268: "table": "session_execution"
2269: },
2270: {
2271: "columns": [
2272: {
2273: "value": "owner_id",
2274: "isExpression": false
2275: }
2276: ],
2277: "isUnique": false,
2278: "where": null,
2279: "origin": "manual",
2280: "name": "session_execution_owner_idx",
2281: "entityType": ...
<path>/home/byk/Code/opencode/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: import("./migration/20260413175956_chief_energizer"),
17: ...
<path>/home/byk/Code/opencode/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 { Format } from ...
<path>/home/byk/Code/opencode/packages/opencode/src/server/routes/instance/httpapi/server.ts</path>
<type>file</type>
<content>
180: Layer.provide(PluginPtyEnvironment.layer),
181: Layer.provide([serverHttpApiAuthLayer, v2SchemaErrorLayer]),
182: )
183:
184: // `OpenApi.fromApi` is non-trivial; defer until /doc is actually hit so
185: // processes that never serve it (CLI, scripts) don't pay at module load.
186: // `HttpServerResponse.jsonUnsafe` runs JSON.stringify eagerly, so caching
187: // the response also caches the serialized body — every /doc request reuses
188: // the same Uint8Array instead of re-stringifying the spec.
189: const docResponse = lazy(() => HttpServerResponse.jsonUnsafe(OpenApi.fromApi(PublicApi)))
190:
191: const docRoute = HttpRouter.use((router) => router.add("GET", "/doc", () => Effect.succeed(docResponse()))).pipe(
192: Layer.provide(authOnlyRouterLayer),
193: )
194:
195: const uiRoute = HttpRouter.use((router) =>
196: Effect.gen(function* () ...
<path>/home/byk/Code/opencode/packages/server/src/routes.ts</path>
<type>file</type>
<content>
1: import { Database } from "@opencode-ai/core/database/database"
2: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
3: import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
4: import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
5: import { EventV2 } from "@opencode-ai/core/event"
6: import { Credential } from "@opencode-ai/core/credential"
7: import { PermissionSaved } from "@opencode-ai/core/permission/saved"
8: import { PtyTicket } from "@opencode-ai/core/pty/ticket"
9: import { SessionV2 } from "@opencode-ai/core/session"
10: import { SessionExecution } from "@opencode-ai/core/session/execution"
11: import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
12: import { SessionExecutionLocal } from "@opencode-ai/core/session/execution/local"
13: import { SessionRecovery } from ...
<path>/home/byk/Code/opencode/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 Source extends ...
<path>/home/byk/Code/opencode/packages/schema/src/session-event.ts</path> <type>file</type> <content> 500: Text.Started, 501: Text.Ended, 502: Tool.Input.Started, 503: Tool.Input.Ended, 504: Tool.Called, 505: Tool.Progress, 506: Tool.Success, 507: Tool.Failed, 508: Reasoning.Started, 509: Reasoning.Ended, 510: Retried, 511: Compaction.Started, 512: Compaction.Ended, 513: RevertEvent.Staged, 514: RevertEvent.Cleared, 515: RevertEvent.Committed, 516: ) 517: 518: export const Definitions = Event.inventory( 519: AgentSwitched, 520: ModelSwitched, 521: Moved, 522: Prompted, 523: PromptAdmitted, 524: PromptExecutionRequested, 525: ContextUpdated, 526: Synthetic, 527: Shell.Started, 528: Shell.Ended, 529: Step.Started, 530: Step.Ended, 531: Step.Failed, 532: Step.Interrupted, 533: Step.Recovered, 534: Text.Started, 535: Text.Delta, 536: Text.Ended, 537: Reasoning.Started, 538: Reasoning.Delta, 539: ...
Found 4 matches /home/byk/Code/opencode/packages/client/src/generated/types.ts: Line 784: readonly type: "session.next.prompt.execution_requested" Line 908: readonly type: "session.next.step.recovered" Line 1278: readonly type: "session.next.prompt.execution_requested" Line 1402: readonly type: "session.next.step.recovered"
<path>/home/byk/Code/opencode/packages/client/src/generated/types.ts</path>
<type>file</type>
<content>
750: readonly delivery: "steer" | "queue"
751: }
752: }
753: | {
754: readonly id: string
755: readonly metadata?: { readonly [x: string]: JsonValue }
756: readonly type: "session.next.prompt.admitted"
757: readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
758: readonly location?: { readonly directory: string; readonly workspaceID?: string }
759: readonly data: {
760: readonly timestamp: number
761: readonly sessionID: string
762: readonly messageID: string
763: readonly prompt: {
764: readonly text: string
765: readonly files?: ReadonlyArray<{
766: readonly uri: string
767: readonly mime: string
768: readonly name?: string
769: readonly ...
<path>/home/byk/Code/opencode/packages/client/src/generated/types.ts</path>
<type>file</type>
<content>
1250: readonly type: "session.next.prompt.admitted"
1251: readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
1252: readonly location?: { readonly directory: string; readonly workspaceID?: string }
1253: readonly data: {
1254: readonly timestamp: number
1255: readonly sessionID: string
1256: readonly messageID: string
1257: readonly prompt: {
1258: readonly text: string
1259: readonly files?: ReadonlyArray<{
1260: readonly uri: string
1261: readonly mime: string
1262: readonly name?: string
1263: readonly description?: string
1264: readonly source?: { readonly start: number; readonly end: number; readonly text: string }
1265: }>
1266: readonly agents?: ReadonlyArray<{
1267: ...
Found 8 matches /home/byk/Code/opencode/packages/sdk/js/src/v2/gen/types.gen.ts: Line 879: type: "session.next.prompt.execution_requested" Line 983: type: "session.next.step.recovered" Line 3439: type: "session.next.prompt.execution_requested.1" Line 3606: type: "session.next.step.recovered.1" Line 4371: type: "session.next.prompt.execution_requested" Line 4565: type: "session.next.step.recovered" Line 6470: type: "session.next.prompt.execution_requested" Line 6583: type: "session.next.step.recovered"
<path>/home/byk/Code/opencode/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
840: messageID: string
841: model: ModelRef
842: }
843: }
844: | {
845: id: string
846: type: "session.next.moved"
847: properties: {
848: timestamp: number
849: sessionID: string
850: location: LocationRef
851: subdirectory?: string
852: }
853: }
854: | {
855: id: string
856: type: "session.next.prompted"
857: properties: {
858: timestamp: number
859: sessionID: string
860: messageID: string
861: prompt: Prompt
862: delivery: "steer" | "queue"
863: }
864: }
865: | {
866: id: string
867: type: "session.next.prompt.admitted"
868: properties: {
869: timestamp: number
870: sessionID: string
871: messageID: string
872: ...
<path>/home/byk/Code/opencode/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
3400: id: string
3401: syncEvent: {
3402: type: "session.next.prompted.1"
3403: id: string
3404: seq: number
3405: aggregateID: string
3406: data: {
3407: timestamp: number
3408: sessionID: string
3409: messageID: string
3410: prompt: Prompt
3411: delivery: "steer" | "queue"
3412: }
3413: }
3414: }
3415:
3416: export type SyncEventSessionNextPromptAdmitted = {
3417: type: "sync"
3418: id: string
3419: syncEvent: {
3420: type: "session.next.prompt.admitted.1"
3421: id: string
3422: seq: number
3423: aggregateID: string
3424: data: {
3425: timestamp: number
3426: sessionID: string
3427: messageID: string
3428: prompt: Prompt
3429: delivery: "steer" | "queue"
3430: requestExecution?: boolean
3431: }
3432: }
3433: }
3434:
3435: export type ...
<path>/home/byk/Code/opencode/packages/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
4320: }
4321: }
4322:
4323: export type SessionNextPrompted = {
4324: id: string
4325: metadata?: {
4326: [key: string]: unknown
4327: }
4328: type: "session.next.prompted"
4329: durable?: {
4330: aggregateID: string
4331: seq: number
4332: version: number
4333: }
4334: location?: LocationRef
4335: data: {
4336: timestamp: number
4337: sessionID: string
4338: messageID: string
4339: prompt: Prompt
4340: delivery: "steer" | "queue"
4341: }
4342: }
4343:
4344: export type SessionNextPromptAdmitted = {
4345: id: string
4346: metadata?: {
4347: [key: string]: unknown
4348: }
4349: type: "session.next.prompt.admitted"
4350: durable?: {
4351: aggregateID: string
4352: seq: number
4353: version: number
4354: }
4355: location?: LocationRef
4356: data: {
4357: timestamp: number
4358: sessionID: ...
Found 14 matches
/home/byk/Code/opencode/packages/sdk/js/src/v2/gen/types.gen.ts:
Line 24: | EventSessionNextPromptExecutionRequested
Line 33: | EventSessionNextStepRecovered
Line 1650: | SyncEventSessionNextPromptExecutionRequested
Line 1659: | SyncEventSessionNextStepRecovered
Line 2785: | SessionNextPromptExecutionRequested
Line 2794: | SessionNextStepRecovered
Line 2915: | SessionNextPromptExecutionRequested
Line 2924: | SessionNextStepRecovered
Line 3435: export type SyncEventSessionNextPromptExecutionRequested = {
Line 3602: export type SyncEventSessionNextStepRecovered = {
Line 4366: export type SessionNextPromptExecutionRequested = {
Line 4560: export type SessionNextStepRecovered = {
Line 6468: export type EventSessionNextPromptExecutionRequested = {
Line 6581: export type EventSessionNextStepRecovered = {
Found 8 matches /home/byk/Code/opencode/packages/sdk/openapi.json: Line 17919: "enum": ["session.next.prompt.execution_requested"] Line 18289: "enum": ["session.next.step.recovered"] Line 25740: "enum": ["session.next.prompt.execution_requested.1"] Line 26299: "enum": ["session.next.step.recovered.1"] Line 28622: "enum": ["session.next.prompt.execution_requested"] Line 29190: "enum": ["session.next.step.recovered"] Line 34831: "enum": ["session.next.prompt.execution_requested"] Line 35201: "enum": ["session.next.step.recovered"]
Found 83 matches
/tmp/opencode/session-recovery-final-v15.patch:
Line 4196: + test("adds final recovery ownership tables to a populated predecessor", async () => {
Line 4311: + it.effect("clears the claim after a successful drain", () =>
Line 4320: + it.effect("snapshots the latest assistant sequence when acquiring a claim", () =>
Line 4352: + it.effect("preserves the claim when its scope closes", () =>
Line 4368: + it.effect("clears the claim after explicit interruption", () =>
Line 4382: + it.effect("never starts a second owner while the lease is live", () =>
Line 4405: + it.effect("never overwrites an expired lease", () =>
Line 4432: + it.effect("never re-enters its own uncertain live lease", () =>
Line 4459: + it.effect("renews heartbeat expiry from the current clock", () =>
Line 4482: + it.effect("never revives an expired claim on heartbeat", () =>
Line 4500: + it.effect("settles cancellation before starting a pending successor", () =>
...
<path>/home/byk/Code/opencode/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 { SessionSchema } from "@opencode-ai/core/session/schema"
11: import { SessionExecutionTable, SessionTable } from "@opencode-ai/core/session/sql"
12: import { testEffect } from "./lib/effect"
13:
14: const sessionID = SessionSchema.ID.make("ses_execution_local")
15: const it = testEffect(Database.layerFromPath(":memory:"))
16:
17: const ...
<path>/tmp/opencode/session-recovery-final-v15.patch</path>
<type>file</type>
<content>
1515: diff --git a/packages/core/src/session/execution/local.ts b/packages/core/src/session/execution/local.ts
1516: index d874c40832..865b5e35d2 100644
1517: --- a/packages/core/src/session/execution/local.ts
1518: +++ b/packages/core/src/session/execution/local.ts
1519: @@ -1,46 +1,192 @@
1520: -import { Cause, Effect, Layer } from "effect"
1521: +import { and, asc, desc, eq, isNull } from "drizzle-orm"
1522: +import { Cause, Clock, Duration, Effect, Exit, Layer, Schedule } from "effect"
1523: +import { Database } from "../../database/database"
1524: import { LocationServiceMap } from "../../location-service-map"
1525: import { makeGlobalNode } from "../../effect/app-node"
1526: import { SessionRunCoordinator } from "../run-coordinator"
1527: import { SessionRunner } from "../runner"
1528: +import { SessionMessage } from "../message"
1529: import { SessionSchema } from "../schema"
1530: ...
Found 5 matches
/home/byk/Code/opencode/packages/core/src/event.ts:
Line 127: readonly publish: <D extends Definition>(
Line 140: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
Line 144: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
Line 443: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
Line 482: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
<path>/home/byk/Code/opencode/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: definition: ...
<path>/home/byk/Code/opencode/packages/core/src/event.ts</path>
<type>file</type>
<content>
205: function commitDurableEvent(
206: definition: Definition,
207: event: Payload,
208: input?: {
209: readonly seq: number
210: readonly aggregateID: string
211: readonly ownerID?: string
212: readonly strictOwner?: boolean
213: },
214: commit?: (seq: number) => Effect.Effect<void>,
215: ) {
216: return Effect.gen(function* () {
217: const durable = definition?.durable
218: if (durable) {
219: const aggregateID = (event.data as Record<string, unknown>)[durable.aggregate]
220: if (typeof aggregateID !== "string") {
221: yield* Effect.die(
222: new InvalidDurableEventError({
223: type: event.type,
224: message: `Expected string aggregate field ${durable.aggregate}`,
225: ...
<path>/home/byk/Code/opencode/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
1350:
1351: it.effect("preserves the baseline while context is temporarily unavailable", () =>
1352: Effect.gen(function* () {
1353: yield* setup
1354: const session = yield* SessionV2.Service
1355: const events = yield* EventV2.Service
1356: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
1357:
1358: requests.length = 0
1359: response = []
1360: yield* session.resume(sessionID)
1361: yield* events.publish(SessionEvent.ModelSwitched, {
1362: sessionID,
1363: messageID: SessionMessage.ID.create(),
1364: timestamp: DateTime.makeUnsafe(1),
1365: model: { id: ModelV2.ID.make("replacement"), providerID: ProviderV2.ID.make("fake") },
1366: })
1367: systemUnavailable = true
1368: yield* session.prompt({ sessionID, prompt: Prompt.make({ ...
<path>/home/byk/Code/opencode/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
4180: response = [LLMEvent.stepStart({ index: 0 }), LLMEvent.providerError({ message: "Provider unavailable" })]
4181:
4182: yield* session.resume(sessionID)
4183:
4184: expect(requests).toHaveLength(1)
4185: expect(yield* session.context(sessionID)).toMatchObject([
4186: { type: "user", text: "Fail durably" },
4187: { type: "assistant", finish: "error", error: { type: "unknown", message: "Provider unavailable" } },
4188: ])
4189: }),
4190: )
4191:
4192: it.effect("projects provider errors emitted before assistant step start", () =>
4193: Effect.gen(function* () {
4194: yield* setup
4195: const session = yield* SessionV2.Service
4196: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail before step" }), resume: false })
4197:
4198: requests.length = 0
4199: response = ...
Found 4 matches
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
Line 3591: it.live("retains the claim after user decline until runner finalizers finish", () =>
Line 3788: it.live("never marks a failed Step safe while a sibling tool finalizer is active", () =>
Line 3906: it.effect("retains cancellation ownership when interrupted tool publication defects", () =>
Line 3951: it.effect("retains the claim after Step interruption until runner finalizers finish", () =>
<path>/home/byk/Code/opencode/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3500: }),
3501: })
3502: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Call blocked" }), resume: false })
3503:
3504: requests.length = 0
3505: responses = [
3506: [
3507: LLMEvent.stepStart({ index: 0 }),
3508: LLMEvent.toolCall({ id: "call-blocked", name: "blocked", input: {} }),
3509: LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
3510: LLMEvent.finish({ reason: "tool-calls" }),
3511: ],
3512: [
3513: LLMEvent.stepStart({ index: 0 }),
3514: LLMEvent.stepFinish({ index: 0, reason: "stop" }),
3515: LLMEvent.finish({ reason: "stop" }),
3516: ],
3517: ]
3518:
3519: yield* session.resume(sessionID)
3520:
3521: expect(requests).toHaveLength(2)
3522: expect(yield* ...
Found 1 matches
/home/byk/Code/opencode/packages/core/test/session-projector.test.ts:
Line 1245: it.effect("redrives an explicitly resumed admit-only input after a pre-promotion crash", () =>
<path>/home/byk/Code/opencode/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1160: version: "test",
1161: })
1162: .run()
1163: yield* SessionInput.admit(db, events, {
1164: id: SessionMessage.ID.make("msg_lost_ordinary_wake"),
1165: sessionID,
1166: prompt: Prompt.make({ text: "recover ordinary input" }),
1167: delivery: "steer",
1168: requestExecution: true,
1169: })
1170: let wakes = 0
1171: const execution = SessionExecution.Service.of({
1172: ...recoveryExecution,
1173: wake: () => Effect.sync(() => wakes++).pipe(Effect.asVoid),
1174: })
1175:
1176: yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, execution))
1177: yield* SessionRecovery.recover().pipe(Effect.provideService(SessionExecution.Service, execution))
1178:
1179: expect(wakes).toBe(2)
1180: expect(yield* ...
Found 1 matches /home/byk/Code/opencode/packages/core/test/session-projector.test.ts: Line 1272: expect(yield* SessionInput.requestPendingExecution(db, events, sessionID)).toBe(true)
Found 21 matches
/home/byk/Code/opencode/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\` FOREIGN KEY ...
<path>/home/byk/Code/opencode/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* tx.run(` 158: ...
packages/client/src/generated/types.ts | 72 + packages/core/schema.json | 615 ++++ packages/core/src/database/migration.gen.ts | 1 .../migration/20260914170650_session-recovery.ts | 62 packages/core/src/database/schema.gen.ts | 53 packages/core/src/session.ts | 10 packages/core/src/session/compaction.ts | 47 packages/core/src/session/context-epoch.ts | 88 - packages/core/src/session/execution/local.ts | 184 + packages/core/src/session/input.ts | 422 +++ packages/core/src/session/message-updater.ts | 69 packages/core/src/session/projector.ts | 89 + packages/core/src/session/recovery.ts | 504 ++++ packages/core/src/session/run-coordinator.ts | 35 packages/core/src/session/runner/index.ts | 4 packages/core/src/session/runner/llm.ts | 398 ++- ...
<path>/home/byk/Code/opencode/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
2050: Effect.provideService(
2051: SessionExecution.Service,
2052: SessionExecution.Service.of({
2053: ...recoveryExecution,
2054: wake: (id) => Effect.sync(() => wakes.push(id)).pipe(Effect.asVoid),
2055: }),
2056: ),
2057: )
2058:
2059: expect(wakes).toEqual([])
2060: expect(yield* db.select().from(SessionRecoveryTable).all()).toEqual([])
2061: expect(yield* db.select().from(SessionInputTable).all()).toEqual([])
2062: expect(yield* db.select().from(SessionExecutionTable).get()).toMatchObject({
2063: id: "expired-cancellation",
2064: recovery_error: "Cancellation outcome unknown after owner loss",
2065: })
2066: }),
2067: )
2068:
2069: it.effect("never quarantines cancellation after recovery ownership expires", () =>
2070: ...
<path>/home/byk/Code/opencode/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
1980: slug: id,
1981: directory: "/project",
1982: title: "test",
1983: version: "test",
1984: })),
1985: )
1986: .run()
1987: yield* db.run(sql`PRAGMA ignore_check_constraints = ON`)
1988: yield* db.run(
1989: sql`INSERT INTO session_execution (id, session_id, owner_id, phase, time_created, expires_at) VALUES ('malformed', ${sessionID}, 'dead', 'invalid', 0, 0)`,
1990: )
1991: yield* db
1992: .insert(SessionExecutionTable)
1993: .values({
1994: id: "valid",
1995: session_id: validSession,
1996: owner_id: "dead",
1997: phase: "ready",
1998: time_created: 1,
1999: expires_at: 0,
2000: })
2001: .run()
2002:
2003: yield* ...
<path>/home/byk/Code/opencode/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: import ...
Found 5 matches
/home/byk/Code/opencode/packages/core/test/session-prompt.test.ts:
Line 330: it.effect("never wakes execution when retrying an already promoted prompt", () =>
Line 374: it.effect("durably records execution intent before advisory wake", () =>
Line 408: it.effect("replays historical prompt admission without execution intent as admit-only", () =>
Line 440: it.effect("turns an admit-only exact retry into durable execution intent", () =>
Line 514: it.effect("never arms durable wake for a conflicting execution retry", () =>
<path>/home/byk/Code/opencode/packages/core/test/session-prompt.test.ts</path>
<type>file</type>
<content>
280:
281: const first = yield* session.prompt(input)
282: const second = yield* session.prompt(input)
283:
284: expect(second.id).not.toBe(first.id)
285: expect(yield* session.messages({ sessionID })).toEqual([])
286: expect(yield* admittedCount).toBe(2)
287: }),
288: )
289:
290: it.effect("returns the original recorded message when the ID is retried", () =>
291: Effect.gen(function* () {
292: yield* setup
293: const session = yield* SessionV2.Service
294: const input = {
295: sessionID,
296: id: messageID,
297: prompt: Prompt.make({ text: "Fix the failing tests" }),
298: resume: false,
299: }
300:
301: const first = yield* session.prompt(input)
302: const retried = yield* session.prompt(input)
303:
304: expect(retried).toEqual(first)
305: expect(yield* ...
<path>/home/byk/Code/opencode/packages/core/test/session-run-coordinator.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect } from "bun:test"
2: import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect"
3: import { SessionRunCoordinator } from "@opencode-ai/core/session/run-coordinator"
4: import { testEffect } from "./lib/effect"
5:
6: const it = testEffect(Layer.empty)
7:
8: describe("SessionRunCoordinator", () => {
9: it.effect("joins concurrent resumes for one key", () =>
10: Effect.scoped(
11: Effect.gen(function* () {
12: const gate = yield* Deferred.make<void>()
13: let runs = 0
14: const coordinator = yield* SessionRunCoordinator.make({
15: drain: () => Effect.sync(() => runs++).pipe(Effect.andThen(Deferred.await(gate))),
16: })
17:
18: const first = yield* coordinator.run("session").pipe(Effect.forkChild)
19: yield* Effect.yieldNow
20: const second = yield* ...
<path>/home/byk/Code/opencode/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 "@opencode-ai/core/config"
14: import { ...
<path>/home/byk/Code/opencode/packages/core/test/session-runner-tool-events.test.ts</path>
<type>file</type>
<content>
1: import { expect, test } from "bun:test"
2: import { Effect, Schema, Stream } from "effect"
3: import { LLMEvent } from "@opencode-ai/llm"
4: import { EventV2 } from "@opencode-ai/core/event"
5: import { SessionEvent } from "@opencode-ai/core/session/event"
6: import { SessionMessage } from "@opencode-ai/core/session/message"
7: import { SessionV2 } from "@opencode-ai/core/session"
8: import { ModelV2 } from "@opencode-ai/core/model"
9: import { ProviderV2 } from "@opencode-ai/core/provider"
10: import { createLLMEventPublisher } from "@opencode-ai/core/session/runner/publish-llm-event"
11:
12: const sessionID = SessionV2.ID.make("ses_tool_event_test")
13: const base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB"
14:
15: const capture = () => {
16: const published: Array<{ readonly type: string; readonly data: unknown }> = []
17: const events = ...
<path>/home/byk/Code/opencode/packages/schema/test/event-manifest.test.ts</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "bun:test"
2: import { FileSystem, Integration, Permission, Project, Reference, Session, Workspace } from "../src"
3: import { EventManifest } from "../src/event-manifest"
4: import { IdeEvent } from "../src/ide-event"
5: import { SessionEvent } from "../src/session-event"
6: import { SessionTodo } from "../src/session-todo"
7: import { SessionV1 } from "../src/session-v1"
8: import { WorkspaceEvent } from "../src/workspace-event"
9:
10: describe("public event manifest", () => {
11: test("owns the complete public event surface", () => {
12: expect(EventManifest.ServerDefinitions.length).toBe(61)
13: expect(EventManifest.Definitions.length).toBe(91)
14: expect(SessionV1.Event.Definitions).toEqual([
15: SessionV1.Event.Created,
16: SessionV1.Event.Updated,
17: SessionV1.Event.Deleted,
18: ...
<path>/home/byk/Code/opencode/packages/sdk/openapi.json</path>
<type>file</type>
<content>
17885: "type": "string",
17886: "pattern": "^ses"
17887: },
17888: "messageID": {
17889: "type": "string",
17890: "pattern": "^msg_"
17891: },
17892: "prompt": {
17893: "$ref": "#/components/schemas/Prompt"
17894: },
17895: "delivery": {
17896: "type": "string",
17897: "enum": ["steer", "queue"]
17898: },
17899: "requestExecution": {
17900: "type": "boolean"
17901: }
17902: },
17903: "required": ["timestamp", "sessionID", "messageID", "prompt", "delivery"],
17904: ...
/usr/bin/bash: line 1: python: command not found
verified 34 patch preimages against base and postimages against current files 1
Found 17 matches
/tmp/opencode/session-recovery-final-v15.patch:
Line 1003: + "value": "\"phase\" IN ('ready', 'safe', 'unknown', 'continue', 'cancelling', 'cancelled')",
Line 1065: + CONSTRAINT \`session_execution_phase_check\` CHECK(\`phase\` IN ('ready', 'safe', 'unknown', 'continue', 'cancelling', 'cancelled')),
Line 1125: + CONSTRAINT "session_execution_phase_check" CHECK("phase" IN ('ready', 'safe', 'unknown', 'continue', 'cancelling', 'cancelled'))
Line 1636: + if (current?.phase === "cancelling" || current?.phase === "cancelled") return
Line 1653: + yield* SessionExecutionClaim.update(db, claim, { phase: "cancelling" })
Line 1659: + current?.phase === "cancelled" ||
Line 1660: + (current?.phase === "cancelling" && current.assistantMessageID === null)
Line 2570: +const Phase = Schema.Literals(["ready", "safe", "unknown", "continue", "cancelling", "cancelled"])
Line 2665: + ...
ab9408c81cc9916177641ae8e9f08624fc27b5b9.509563ba0cd17246a1d223ac7a6f3fabee1a49892c61b215c3fd7ca4744b6c77.83a833aa93cc185825feffe107fba1bd8e3789d1ff5fe5f8cda7df1962aa078d.packages/core/src/session/execution/local.ts:133-135 first writes phase: "cancelling" and only then enters coordinator.interrupt(...). During that gap, the still-running owner can perform another claim update.
packages/core/src/session/execution/claim.ts:47-49 guards identity, ownership, and expiry, but it does not prevent a same-owner update from replacing cancelling or cancelled.
Several runner transitions can therefore erase the cancellation marker:
packages/core/src/session/runner/llm.ts:232-233 changes any non-continue phase, including cancelling, back to ready.packages/core/src/session/runner/llm.ts:490-494 can replace it with safe or continue.packages/core/src/session/runner/llm.ts:216-218 treats a phase that was overwritten after cancellation as an ordinary renewal instead of rejecting settlement.This creates two unsafe outcomes:
safe, the non-coordinator exit path at packages/core/src/session/execution/local.ts:117-119 clears the claim. This bypasses the explicit cleanup callback at ...