Dashboard › opencode › Session 1bn0rDkPuEzP
1bn0rDkPuEzPRldIXRead-only correctness review frozen v18. Base ab9408c81cc9916177641ae8e9f08624fc27b5b9. Patch /tmp/opencode/session-recovery-final-v18.patch SHA-256 63c11067ed78471a8109455f164327a1c6dc2ff547e5cb78cad185fe2d844e82. SEA /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server SHA-256 c5730f7d6017ebd8dde2a2e91365e574469ef10494ec632632b91d7e2318885c. Verify immutable inputs, clean apply, 34 paths; inspect all. No mutations/tests/builds/services/recall. Primary v17 blocker: durable event transaction may commit Step.Failed before interruptible notification returns. V18 cancellation no longer trusts in-memory assistantFailed; after tool reconciliation it reads claimed assistant row in session_message and if durable time.completed exists, settles exact cancelling->cancelled with no duplicate Step.Interrupted. Regression EventV2 listener blocks after observing Step.Failed (post durable commit/pre-return), then cancellation asserts one Failed, zero Interrupted, no claim/recovery. Publication defects retain cancelling. Reassess all. Accepted check-call/process-local/skew residuals. Evidence exact tree typecheck, recovery 229/0, Core 1186/0, migration upgrades/script pass, exact smoke health/index/recovery/idempotence. PASS/CONCERN/MUST-FIX file:line and end exactly MERGE or DO-NOT-MERGE. Empty invalid.
63c11067ed78471a8109455f164327a1c6dc2ff547e5cb78cad185fe2d844e82 /tmp/opencode/session-recovery-final-v18.patch c5730f7d6017ebd8dde2a2e91365e574469ef10494ec632632b91d7e2318885c /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server dc661f30cb2055ad6248fdc59b14998d834497be commit 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 packages/core/src/session/message-updater.ts M ...
<path>/tmp/opencode/session-recovery-final-v18.patch</path>
<type>file</type>
<content>
1: diff --git a/packages/client/src/generated/types.ts b/packages/client/src/generated/types.ts
2: index 3b3188c874..5130fa9496 100644
3: --- a/packages/client/src/generated/types.ts
4: +++ b/packages/client/src/generated/types.ts
5: @@ -775,8 +775,17 @@ export type SessionsHistoryOutput = {
6: }>
7: }
8: readonly delivery: "steer" | "queue"
9: + readonly requestExecution?: boolean
10: }
11: }
12: + | {
13: + readonly id: string
14: + readonly metadata?: { readonly [x: string]: JsonValue }
15: + readonly type: "session.next.prompt.execution_requested"
16: + readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
17: + readonly location?: { readonly directory: string; readonly workspaceID?: string }
18: + readonly data: { readonly timestamp: number; ...
ancestor_exit=0 1 reverse_check_exit=0 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 34 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 A packages/core/src/database/migration/20260914170650_session-recovery.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 A packages/core/src/session/recovery.ts M packages/core/src/sessio...
M .lore.md M packages/core/schema.json M packages/core/src/database/migration.gen.ts A packages/core/src/database/migration/20260914170650_session-recovery.ts A packages/core/src/database/migration/20260914214636_session-execution.ts A packages/core/src/database/migration/20260914222526_session-execution-phase.ts M packages/core/src/database/schema.gen.ts M packages/core/src/session/compaction.ts M packages/core/src/session/execution/local.ts M packages/core/src/session/message-updater.ts M packages/core/src/session/projector.ts A packages/core/src/session/recovery.ts M packages/core/src/session/runner/llm.ts M packages/core/src/session/runner/publish-llm-event.ts M packages/core/src/session/sql.ts A packages/core/test/session-execution-local.test.ts M packages/core/test/session-projector.test.ts M packages/core/test/session-runner-tool-events.test.ts M packages/core/test/session-runner.test.ts M packages/opencode/src/server/routes/instance/httpapi/server.ts M packages/schema/src/sessi...
db805d69bb8050542704959e96750fa19218168aa758df4b6bcb65d5edbd5aff -
db805d69bb8050542704959e96750fa19218168aa758df4b6bcb65d5edbd5aff -
10908 11038 /tmp/opencode/session-recovery-final-v18.patch
entries=34 mismatches=0
<skill_content name="effect">
# Skill: effect
# Effect
This codebase uses Effect for typed, composable TypeScript services, schemas, and workflows.
## Source Of Truth
Use the current Effect v4 / effect-smol source, not memory or older Effect v2/v3 examples.
1. If `.opencode/references/effect-smol` is missing, clone `https://github.com/Effect-TS/effect-smol` there. Do this in the project, not in the skill folder.
2. Search `.opencode/references/effect-smol` for exact APIs, examples, tests, and naming patterns before answering or implementing Effect-specific code.
3. Also inspect existing repo code for local house style before introducing new patterns.
4. Prefer answers and implementations backed by specific source files or nearby repo examples.
## Guidelines
- Prefer current Effect v4 APIs and project-local patterns over old blog posts, examples, or package-memory guesses.
- Use `Effect.gen(function* () { ... })` for multi-step workflows.
- Use `Effect.fn("Name")` or ...
<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, 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: const where = ...
<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/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/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/runner/llm.ts</path>
<type>file</type>
<content>
601: executionID: input.executionID,
602: ownerID: input.ownerID,
603: }
604: const recovering = input.recoveryInputID !== undefined
605: const hasSteer = recovering ? false : yield* SessionInput.hasPending(db, input.sessionID, "steer")
606: const hasQueue = recovering || hasSteer ? false : yield* SessionInput.hasPending(db, input.sessionID, "queue")
607: if (!recovering && !input.force && !hasSteer && !hasQueue) return
608: yield* failInterruptedTools(input.sessionID, claim)
609: let promotion: Promotion | undefined = input.recoveryInputID
610: ? { recoveryInputID: input.recoveryInputID }
611: : hasSteer
612: ? "steer"
613: : hasQueue
614: ? "queue"
615: : undefined
616: let shouldRun = recovering || input.force || hasSteer || hasQueue
617: while ...
<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/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
451: return
452: }
453: case "step-finish":
454: yield* flush()
455: assistantActive = false
456: if (stepSettlement) return yield* Effect.die("Duplicate step finish")
457: stepSettlement = { finish: event.reason, tokens: tokens(event.usage) }
458: return
459: case "finish":
460: return
461: case "provider-error":
462: yield* failAssistant(event.message)
463: providerFailed = true
464: return
465: }
466: })
467:
468: return {
469: publish,
470: flush,
471: failAssistant,
472: interruptAssistant,
473: failUnsettledTools,
474: beginCancellationSettlement: (commit: () => Effect.Effect<void>) => {
475: eventCommit = commit
476: },
477: hasActiveAssistant: () => assistantActive,
478: hasAssistantStarted: () => ...
<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/input.ts</path>
<type>file</type>
<content>
501: rows: ReadonlyArray<typeof SessionInputTable.$inferSelect>,
502: commit?: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
503: ) {
504: for (const row of rows) {
505: const id = SessionMessage.ID.make(row.id)
506: yield* events
507: .publish(
508: SessionEvent.Prompted,
509: {
510: sessionID,
511: timestamp: DateTime.makeUnsafe(row.time_created),
512: messageID: id,
513: prompt: decodePrompt(row.prompt),
514: delivery: row.delivery,
515: },
516: commit ? { commit: () => commit(row) } : undefined,
517: )
518: .pipe(
519: Effect.catchDefect((defect) =>
520: defect instanceof LifecycleConflict
521: ? find(db, id).pipe(
522: Effect.flatMap((stored) => (stored?.promotedSeq === undefined ? Effect.die(defect) : ...
<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: ...
<path>/home/byk/Code/opencode/packages/core/src/database/schema.gen.ts</path> <type>file</type> <content> 120: \`time_created\` integer NOT NULL, 121: \`time_updated\` integer NOT NULL, 122: \`time_initialized\` integer, 123: \`sandboxes\` text NOT NULL, 124: \`commands\` text 125: ); 126: `) 127: yield* tx.run(` 128: CREATE TABLE \`message\` ( 129: \`id\` text PRIMARY KEY, 130: \`session_id\` text NOT NULL, 131: \`time_created\` integer NOT NULL, 132: \`time_updated\` integer NOT NULL, 133: \`data\` text NOT NULL, 134: CONSTRAINT \`fk_message_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE 135: ); 136: `) 137: yield* tx.run(` 138: CREATE TABLE \`part\` ( 139: \`id\` text PRIMARY KEY, 140: \`message_id\` text NOT NULL, 141: ...
<path>/home/byk/Code/opencode/packages/core/src/database/schema.gen.ts</path> <type>file</type> <content> 286: yield* tx.run(`CREATE UNIQUE INDEX \`session_execution_session_idx\` ON \`session_execution\` (\`session_id\`);`) 287: yield* tx.run(`CREATE INDEX \`session_execution_owner_idx\` ON \`session_execution\` (\`owner_id\`);`) 288: yield* tx.run( 289: `CREATE INDEX \`session_execution_recovery_scan_idx\` ON \`session_execution\` (\`recovery_error\`,\`expires_at\`,\`time_created\`,\`id\`);`, 290: ) 291: yield* tx.run( 292: `CREATE INDEX \`session_input_session_pending_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`delivery\`,\`admitted_seq\`);`, 293: ) 294: yield* tx.run( 295: `CREATE UNIQUE INDEX \`session_input_session_admitted_seq_idx\` ON \`session_input\` (\`session_id\`,\`admitted_seq\`);`, 296: ) 297: yield* tx.run( 298: `CREATE UNIQUE INDEX ...
<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 ...
Found 79 matches
/home/byk/Code/opencode/packages/schema/test/event-manifest.test.ts:
Line 53: SessionEvent.PromptExecutionRequested,
/home/byk/Code/opencode/packages/schema/src/session-event.ts:
Line 94: export const PromptAdmitted = Event.define({
Line 102: export type PromptAdmitted = typeof PromptAdmitted.Type
Line 104: export const PromptExecutionRequested = Event.define({
Line 112: export type PromptExecutionRequested = typeof PromptExecutionRequested.Type
Line 489: PromptAdmitted,
Line 490: PromptExecutionRequested,
Line 498: Step.Interrupted,
Line 499: Step.Recovered,
Line 523: PromptAdmitted,
Line 524: PromptExecutionRequested,
Line 532: Step.Interrupted,
Line 533: Step.Recovered,
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
Line 600: event.type.startsWith(SessionEvent.Step.Interrupted.type),
Line 633: yield* events.publish(SessionEvent.Step.Recovered, {
Line 3580: ...
<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/projector.ts</path>
<type>file</type>
<content>
481: yield* events.project(SessionEvent.RevertEvent.Staged, (event) =>
482: db
483: .update(SessionTable)
484: .set({
485: revert: { ...event.data.revert, files: event.data.revert.files ? [...event.data.revert.files] : undefined },
486: time_updated: DateTime.toEpochMillis(event.data.timestamp),
487: })
488: .where(eq(SessionTable.id, event.data.sessionID))
489: .run()
490: .pipe(Effect.orDie, Effect.asVoid),
491: )
492: yield* events.project(SessionEvent.RevertEvent.Cleared, (event) =>
493: db
494: .update(SessionTable)
495: .set({ revert: null, time_updated: DateTime.toEpochMillis(event.data.timestamp) })
496: .where(eq(SessionTable.id, event.data.sessionID))
497: .run()
498: .pipe(Effect.orDie, Effect.asVoid),
499: )
500: yield* ...
<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/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/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/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.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/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/opencode/src/server/routes/instance/httpapi/server.ts</path>
<type>file</type>
<content>
45: import { Discovery } from "@/skill/discovery"
46: import { Snapshot } from "@/snapshot"
47: import { Storage } from "@/storage/storage"
48: import { ToolRegistry } from "@/tool/registry"
49: import { Truncate } from "@/tool/truncate"
50: import { Worktree } from "@/worktree"
51: import { RuntimeFlags } from "@/effect/runtime-flags"
52: import { MoveSession } from "@opencode-ai/core/control-plane/move-session"
53: import { Database } from "@opencode-ai/core/database/database"
54: import { AppNodeBuilderV1 } from "@/effect/app-node-builder-v1"
55: import { LayerNode } from "@opencode-ai/core/effect/layer-node"
56: import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
57: import { EventV2 } from "@opencode-ai/core/event"
58: import { ModelsDev } from "@opencode-ai/core/models-dev"
59: import { Npm } from "@opencode-ai/core/npm"
60: import { ...
Found 2 matches /home/byk/Code/opencode/packages/opencode/src/server/routes/instance/httpapi/server.ts: Line 300: AppNodeBuilderV1.build(LayerNode.group([SessionV2.node, SessionRecovery.node]), [ Line 302: [SessionExecution.node, SessionExecutionLocal.node],
<path>/home/byk/Code/opencode/packages/opencode/src/server/routes/instance/httpapi/server.ts</path> <type>file</type> <content> 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: Layer.provide(sessionLocationLayer), 297: Layer.provide(locationLayer), 298: Layer.provide(PtyEnvironment.layer), 299: Layer.provide( 300: AppNodeBuilderV1.build(LayerNode.group([SessionV2.node, SessionRecovery.node]), [ 301: [LocationServiceMap.node, locationServiceMapV2], 302: [SessionExecution.node, ...
<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/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>
Found 2 matches
/home/byk/Code/opencode/packages/schema/src/session-message.ts:
Line 12: export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe(
Line 14: statics((schema) => ({ create: () => schema.make("msg_" + ascending()) })),
Found 27 matches
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
Line 787: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "fence publication" }), resume: false })
Line 793: .set({ id: "publication-successor", owner_id: "successor", expires_at: 60_000 })
Line 2055: it.effect("keeps a running assistant recoverable when Step failure publication defects", () =>
Line 2060: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail Step publication" }), resume: false })
Line 2063: CREATE TRIGGER fail_step_failure_publication
Line 2067: SELECT RAISE(ABORT, 'crash before Step failure publication');
Line 2073: Effect.ensuring(db.run(sql`DROP TRIGGER fail_step_failure_publication`).pipe(Effect.orDie)),
Line 2091: it.effect("never records assistant start state when Step start publication defects", () =>
Line 2096: yield* session.prompt({ sessionID, prompt: Prompt.make({ ...
<path>/home/byk/Code/opencode/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
2020: .flatMap((message) => message.content)
2021: .find((part) => part.type === "tool" && part.id === "call-hosted-pending"),
2022: ).toMatchObject({ type: "tool", state: { status: "running" } })
2023: expect(
2024: yield* db
2025: .select({ type: EventTable.type })
2026: .from(EventTable)
2027: .where(eq(EventTable.type, "session.next.step.ended.2"))
2028: .all(),
2029: ).toEqual([])
2030: }),
2031: )
2032:
2033: it.effect("never commits a Step end after the provider stream fails", () =>
2034: Effect.gen(function* () {
2035: yield* setup
2036: const session = yield* SessionV2.Service
2037: const { db } = yield* Database.Service
2038: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail after finish" }), resume: false })
2039: ...
<path>/home/byk/Code/opencode/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3840: expect(result).toMatchObject({ _tag: "Failure" })
3841: }),
3842: 15_000,
3843: )
3844:
3845: it.effect("durably fails blocked local tools when a provider turn is interrupted", () =>
3846: Effect.gen(function* () {
3847: yield* setup
3848: const session = yield* SessionV2.Service
3849: yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Interrupt blocked tool" }), resume: false })
3850: executions.length = 0
3851: toolExecutionGate = yield* Deferred.make<void>()
3852: responseStream = Stream.concat(
3853: Stream.fromIterable([
3854: LLMEvent.stepStart({ index: 0 }),
3855: LLMEvent.toolCall({ id: "call-before-interrupt", name: "echo", input: { text: "blocked" } }),
3856: ]),
3857: Stream.never,
3858: )
3859:
3860: const run = yield* ...
<path>/home/byk/Code/opencode/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3200: prompt: Prompt.make({ text: "Wait in queue" }),
3201: delivery: "queue",
3202: resume: false,
3203: })
3204:
3205: requests.length = 0
3206: yield* (yield* SessionExecution.Service).wake(sessionID)
3207: yield* Effect.yieldNow
3208:
3209: expect(requests).toHaveLength(1)
3210: expect(userTexts(requests[0]!)).toEqual(["Wait in queue"])
3211: }),
3212: )
3213:
3214: it.effect("retries inbox input after prompt projection rolls back", () =>
3215: Effect.gen(function* () {
3216: yield* setup
3217: const session = yield* SessionV2.Service
3218: const events = yield* EventV2.Service
3219: const defect = new Error("fail after prompt promotion")
3220: let fail = true
3221: yield* events.project(SessionEvent.Prompted, () => (fail ? Effect.die(defect) : Effect.void))
3222: ...
Found 12 matches
/home/byk/Code/opencode/packages/core/src/event.ts:
Line 127: readonly publish: <D extends Definition>(
Line 136: readonly listen: (listener: Subscriber) => Effect.Effect<Unsubscribe>
Line 155: const unsubscribe = yield* events.listen((event) =>
Line 181: const listeners = new Array<Subscriber>()
Line 402: (cause) => Effect.logError("Event listener failed", { eventID: event.id, eventType: event.type, cause }),
Line 409: listeners,
Line 410: (listener) => (isolateListeners ? observe(event, listener) : listener(event)),
Line 606: const listen = (listener: Subscriber): Effect.Effect<Unsubscribe> =>
Line 608: listeners.push(listener)
Line 610: const index = listeners.indexOf(listener)
Line 611: if (index >= 0) listeners.splice(index, 1)
Line 627: listen,
<path>/home/byk/Code/opencode/packages/core/src/event.ts</path>
<type>file</type>
<content>
350: }),
351: { behavior: "immediate" },
352: )
353: .pipe(Effect.orDie)
354: if (committed) {
355: yield* Effect.forEach(
356: pubsub.durable.get(committed.aggregateID) ?? [],
357: (wake) => PubSub.publish(wake, undefined),
358: { discard: true },
359: )
360: }
361: return committed
362: }),
363: )
364: }
365: }
366: })
367: }
368:
369: function publishEvent<D extends Definition>(definition: D, event: Payload<D>, commit?: PublishOptions["commit"]) {
370: return Effect.gen(function* () {
371: if (!definition?.durable && commit)
372: return yield* ...
<path>/home/byk/Code/opencode/packages/core/src/event.ts</path>
<type>file</type>
<content>
250: const encoded = Schema.encodeUnknownSync(definition.data)(event.data) as Record<
251: string,
252: unknown
253: >
254: if (input?.strictOwner && row?.ownerID && row.ownerID !== input.ownerID) {
255: yield* Effect.die(
256: new InvalidDurableEventError({
257: type: event.type,
258: message: `Replay owner mismatch for aggregate ${aggregateID}: expected ${row.ownerID}, got ${input.ownerID ?? "none"}`,
259: }),
260: )
261: }
262: if (input && input.seq <= latest) {
263: const stored = yield* db
264: ...
Found 34 matches /tmp/opencode/session-recovery-final-v18.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>/tmp/opencode/session-recovery-final-v18.patch</path>
<type>file</type>
<content>
4222: diff --git a/packages/core/test/database-migration.test.ts b/packages/core/test/database-migration.test.ts
4223: index 464ce2695a..9814b75a11 100644
4224: --- a/packages/core/test/database-migration.test.ts
4225: +++ b/packages/core/test/database-migration.test.ts
4226: @@ -4,7 +4,7 @@ import { fileURLToPath } from "url"
4227: import path from "path"
4228: import { SqliteClient } from "@effect/sql-sqlite-bun"
4229: import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
4230: -import { Effect, Layer } from "effect"
4231: +import { Effect, Exit, Layer } from "effect"
4232: import { eq, inArray, sql } from "drizzle-orm"
4233: import { DatabaseMigration } from "@opencode-ai/core/database/migration"
4234: import { migrations } from "@opencode-ai/core/database/migration.gen"
4235: @@ -16,6 +16,7 @@ import eventSourcedSessionInputMigration from ...
<path>/tmp/opencode/session-recovery-final-v18.patch</path>
<type>file</type>
<content>
5528: + id: sessionID,
5529: + project_id: Project.ID.global,
5530: + slug: "test",
5531: + directory: "/project",
5532: + title: "test",
5533: + version: "test",
5534: + wake_attempts: 60_000,
5535: + })
5536: + .run()
5537: + const firstID = SessionMessage.ID.make("msg_retry_debt_first")
5538: + yield* SessionInput.admit(db, events, {
5539: + id: firstID,
5540: + sessionID,
5541: + prompt: Prompt.make({ text: "first" }),
5542: + delivery: "steer",
5543: + requestExecution: true,
5544: + })
5545: + expect(yield* SessionInput.promoteSteers(db, events, sessionID, yield* EventV2.latestSequence(db, sessionID))).toBe(1)
5546: + expect(yield* db.select({ attempts: SessionTable.wake_attempts }).from(SessionTable).get()).toEqual({ attempts: 0 })
5547: +
5548: + ...
<path>/tmp/opencode/session-recovery-final-v18.patch</path>
<type>file</type>
<content>
6828: +
6829: + it.effect("never quarantines projection failure after recovery ownership expires", () =>
6830: + Effect.gen(function* () {
6831: + const { db } = yield* Database.Service
6832: + const events = yield* EventV2.Service
6833: + yield* db
6834: + .insert(ProjectTable)
6835: + .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
6836: + .run()
6837: + yield* db
6838: + .insert(SessionTable)
6839: + .values({
6840: + id: sessionID,
6841: + project_id: Project.ID.global,
6842: + slug: "test",
6843: + directory: "/project",
6844: + title: "test",
6845: + version: "test",
6846: + })
6847: + .run()
6848: + yield* db
6849: + .insert(SessionExecutionTable)
6850: + .values({
6851: + id: ...
<path>/tmp/opencode/session-recovery-final-v18.patch</path>
<type>file</type>
<content>
7528: + aggregateID: event.aggregate_id,
7529: + seq: event.seq,
7530: + type: event.type,
7531: + data: event.data,
7532: + })),
7533: + )
7534: +})
7535: +
7536: +const recover = () =>
7537: + SessionRecovery.recover().pipe(
7538: + Effect.provideService(
7539: + SessionExecution.Service,
7540: + SessionExecution.Service.of({
7541: + active: Effect.succeed(new Set()),
7542: + resume: () => Effect.void,
7543: + interrupt: () => Effect.void,
7544: + wake: (id) => Effect.sync(() => wakeCalls.push(id)).pipe(Effect.asVoid),
7545: + }),
7546: + ),
7547: + )
7548: +
7549: describe("SessionV2.prompt", () => {
7550: it.effect("exposes the execution registry", () =>
7551: Effect.gen(function* () {
7552: @@ -289,6 +327,165 @@ describe("SessionV2.prompt", () => {
7553: }),
7554: )
7555:
7556: + it.effect("never ...
<path>/tmp/opencode/session-recovery-final-v18.patch</path>
<type>file</type>
<content>
8128: @@ -456,12 +487,24 @@ const fragmentFixture = (kind: FragmentKind, id: string, chunks: readonly string
8129: LLMEvent.toolInputStart({ id, name: "echo" }),
8130: ...chunks.map((text) => LLMEvent.toolInputDelta({ id, name: "echo", text })),
8131: ]
8132: - const expectedContent = { type: "tool", id, state: { status: "pending", input: text } }
8133: + const expectedContent = {
8134: + type: "tool",
8135: + id,
8136: + state: {
8137: + status: "error",
8138: + error: { type: "unknown", message: "Tool execution interrupted" },
8139: + },
8140: + }
8141: return {
8142: delta: SessionEvent.Tool.Input.Delta,
8143: partialEvents,
8144: completeEvents: [...partialEvents, LLMEvent.toolInputEnd({ id, name: "echo" })],
8145: - expectedAssistant: { type: "assistant", content: ...
<path>/tmp/opencode/session-recovery-final-v18.patch</path>
<type>file</type>
<content>
9372: + yield* insertExecution(sessionID)
9373: + const run = yield* runner.run({ sessionID, force: true, executionID, ownerID: "test" }).pipe(Effect.forkChild)
9374: while (executions.length === 0) yield* Effect.yieldNow
9375: yield* Fiber.interrupt(run)
9376: toolExecutionGate = undefined
9377: @@ -3044,7 +4105,10 @@ describe("SessionRunnerLLM", () => {
9378: {
9379: type: "tool",
9380: id: "call-await-interrupt",
9381: - state: { status: "error", error: { type: "unknown", message: "Tool execution interrupted" } },
9382: + state: {
9383: + status: "error",
9384: + error: { type: "unknown", message: "Tool execution outcome unknown after interruption" },
9385: + },
9386: },
9387: ],
9388: },
9389: @@ -3299,6 +4363,7 @@ ...
<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/sdk/js/src/v2/gen/types.gen.ts</path>
<type>file</type>
<content>
1: // This file is auto-generated by @hey-api/openapi-ts
2:
3: export type ClientOptions = {
4: baseUrl: `${string}://${string}` | (string & {})
5: }
6:
7: export type Event =
8: | EventModelsDevRefreshed
9: | EventIntegrationUpdated
10: | EventIntegrationConnectionUpdated
11: | EventCatalogUpdated
12: | EventSessionCreated
13: | EventSessionUpdated
14: | EventSessionDeleted
15: | EventMessageUpdated
16: | EventMessageRemoved
17: | EventMessagePartUpdated
18: | EventMessagePartRemoved
19: | EventSessionNextAgentSwitched
20: | EventSessionNextModelSwitched
21: | EventSessionNextMoved
22: | EventSessionNextPrompted
23: | EventSessionNextPromptAdmitted
24: | EventSessionNextPromptExecutionRequested
25: | EventSessionNextContextUpdated
26: | EventSessionNextSynthetic
27: | EventSessionNextShellStarted
28: | EventSessionNextShellEnded
29: ...
<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>
3585: export type SyncEventSessionNextStepInterrupted = {
3586: type: "sync"
3587: id: string
3588: syncEvent: {
3589: type: "session.next.step.interrupted.1"
3590: id: string
3591: seq: number
3592: aggregateID: string
3593: data: {
3594: timestamp: number
3595: sessionID: string
3596: assistantMessageID: string
3597: error: SessionErrorUnknown
3598: }
3599: }
3600: }
3601:
3602: export type SyncEventSessionNextStepRecovered = {
3603: type: "sync"
3604: id: string
3605: syncEvent: {
3606: type: "session.next.step.recovered.1"
3607: id: string
3608: seq: number
3609: aggregateID: string
3610: data: {
3611: timestamp: number
3612: sessionID: string
3613: executionID: string
3614: assistantMessageID?: string
3615: phase: "ready" | "unknown" | "continue"
3616: }
3617: ...
Found 2 matches /home/byk/Code/opencode/packages/core/schema.json: Line 5: "prevIds": [ /home/byk/Code/opencode/packages/core/script/migration.ts: Line 11: const snapshot = path.join(root, "packages/core/schema.json")
<path>/home/byk/Code/opencode/packages/core/script/migration.ts</path>
<type>file</type>
<content>
1: #!/usr/bin/env bun
2:
3: import { $ } from "bun"
4: import fs from "fs/promises"
5: import os from "os"
6: import path from "path"
7: import { pathToFileURL } from "url"
8: import { parseArgs } from "util"
9:
10: const root = path.resolve(import.meta.dirname, "../../..")
11: const snapshot = path.join(root, "packages/core/schema.json")
12: const tsDir = path.join(root, "packages/core/src/database/migration")
13: const registry = path.join(root, "packages/core/src/database/migration.gen.ts")
14: const schema = path.join(root, "packages/core/src/database/schema.gen.ts")
15: const args = parseArgs({
16: args: process.argv.slice(2),
17: options: {
18: check: { type: "boolean" },
19: name: { type: "string" },
20: },
21: })
22:
23: if (args.values.check) {
24: await check()
25: process.exit(0)
26: }
27:
28: await generate()
29:
30: async function generate() {
31: ...
<path>/home/byk/Code/opencode/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
470: export namespace RevertEvent {
471: export const Staged = Event.define({
472: type: "session.next.revert.staged",
473: ...options,
474: schema: { ...Base, revert: Revert.State },
475: })
476: export const Cleared = Event.define({ type: "session.next.revert.cleared", ...options, schema: Base })
477: export const Committed = Event.define({
478: type: "session.next.revert.committed",
479: ...options,
480: schema: { ...Base, messageID: SessionMessage.ID },
481: })
482: }
483:
484: export const DurableDefinitions = Event.inventory(
485: AgentSwitched,
486: ModelSwitched,
487: Moved,
488: Prompted,
489: PromptAdmitted,
490: PromptExecutionRequested,
491: ContextUpdated,
492: Synthetic,
493: Shell.Started,
494: Shell.Ended,
495: Step.Started,
496: Step.Ended,
497: Step.Failed,
498: Step.Interrupted,
499: ...
<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: ...
Found 14 matches
/home/byk/Code/opencode/packages/sdk/openapi.json:
Line 15336: "$ref": "#/components/schemas/EventSessionNextStepInterrupted"
Line 15339: "$ref": "#/components/schemas/EventSessionNextStepRecovered"
Line 20604: "$ref": "#/components/schemas/SyncEventSessionNextStepInterrupted"
Line 20607: "$ref": "#/components/schemas/SyncEventSessionNextStepRecovered"
Line 23838: "$ref": "#/components/schemas/SessionNextStepInterrupted"
Line 23841: "$ref": "#/components/schemas/SessionNextStepRecovered"
Line 24227: "$ref": "#/components/schemas/SessionNextStepInterrupted"
Line 24230: "$ref": "#/components/schemas/SessionNextStepRecovered"
Line 26226: "SyncEventSessionNextStepInterrupted": {
Line 26283: "SyncEventSessionNextStepRecovered": {
Line 29120: "SessionNextStepInterrupted": {
Line 29178: "SessionNextStepRecovered": ...
<path>/home/byk/Code/opencode/packages/sdk/openapi.json</path>
<type>file</type>
<content>
26220: "additionalProperties": false
26221: }
26222: },
26223: "required": ["type", "id", "syncEvent"],
26224: "additionalProperties": false
26225: },
26226: "SyncEventSessionNextStepInterrupted": {
26227: "type": "object",
26228: "properties": {
26229: "type": {
26230: "type": "string",
26231: "enum": ["sync"]
26232: },
26233: "id": {
26234: "type": "string",
26235: "pattern": "^evt_"
26236: },
26237: "syncEvent": {
26238: "type": "object",
26239: "properties": {
26240: "type": {
26241: "type": "string",
26242: "enum": ["session.next.step.interrupted.1"]
26243: },
26244: "id": {
26245: "type": "string",
26246: ...
Found 13 matches /home/byk/Code/opencode/packages/core/schema.json: Line 1869: "name": "fk_session_recovery_session_id_session_id_fk", Line 1884: "name": "fk_session_recovery_assistant_message_id_session_message_id_fk", Line 1899: "name": "fk_session_recovery_continuation_message_id_session_input_id_fk", Line 1982: "name": "session_recovery_pk", Line 2266: "name": "session_execution_session_idx", Line 2280: "name": "session_execution_owner_idx", Line 2306: "name": "session_execution_recovery_scan_idx", Line 2390: "name": "session_input_wake_pending_promoted_session_idx", Line 2480: "name": "session_recovery_continuation_message_idx", Line 2510: "name": "session_recovery_wake_pending_error_attempts_created_execution_idx", Line 2572: "name": "session_execution_phase_check", Line 2578: "name": "session_recovery_wake_attempts_check", Line 2584: "name": ...
/tmp/opencode/session-recovery-final-v17.patch /tmp/opencode/session-recovery-v17-processes.txt
--- /tmp/opencode/session-recovery-final-v17.patch 2026-09-16 23:58:55.914307051 +0000
+++ /tmp/opencode/session-recovery-final-v18.patch 2026-09-17 00:20:15.877211454 +0000
@@ -3138,7 +3138,7 @@
}
diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts
-index 874086a06b..3853bb7d75 100644
+index 874086a06b..7e2a273c61 100644
--- a/packages/core/src/session/runner/llm.ts
+++ b/packages/core/src/session/runner/llm.ts
@@ -8,7 +8,8 @@ import {
@@ -3159,7 +3159,7 @@
+import { SessionMessage } from "../message"
import { SessionSchema } from "../schema"
import { SessionStore } from "../store"
-+import { SessionExecutionTable, SessionRecoveryTable } from "../sql"
++import { SessionExecutionTable, SessionMessageTable, SessionRecoveryTable } from "../sql"
import { type RunError, Service } from "./index"
import { SessionRunnerModel } from "./model"
import { createLLMEventPublisher } from "./publish-llm-event"
@@ -3454,7 +3454,7 @@
...
No files found
/home/byk/Code/opencode/packages/client/src/generated-effect/index.ts /home/byk/Code/opencode/packages/client/src/generated-effect/client.ts /home/byk/Code/opencode/packages/client/src/generated-effect/client-error.ts /home/byk/Code/opencode/packages/client/src/generated-effect/.httpapi-codegen.json
<path>/home/byk/Code/opencode/packages/core/test/session-run-coordinator.test.ts</path>
<type>file</type>
<content>
250: const firstStarted = yield* Deferred.make<void>()
251: const cleanupStarted = yield* Deferred.make<void>()
252: const cleanupGate = yield* Deferred.make<void>()
253: const secondStarted = yield* Deferred.make<void>()
254: let runs = 0
255: const coordinator = yield* SessionRunCoordinator.make({
256: drain: () =>
257: Effect.sync(() => ++runs).pipe(
258: Effect.flatMap((run) =>
259: run === 1
260: ? Deferred.succeed(firstStarted, undefined).pipe(
261: Effect.andThen(Effect.never),
262: Effect.onInterrupt(() =>
263: Deferred.succeed(cleanupStarted, undefined).pipe(Effect.andThen(Deferred.await(cleanupGate))),
264: ),
265: )
266: ...
0 2b58ddf5ae8a0458e2955c33b695e8bbeb55fdf6bf3cfff0af2fd71b427b1969 - 1 df7b12f5536c14237e39ba69ffe5c8acc0c9c450a7a9337511978c18fe658486 - 2 b3058c1243d47100b3df48820931c38fbe0cb63dfaa5bab68ec467ee016c2196 - 3 db805d69bb8050542704959e96750fa19218168aa758df4b6bcb65d5edbd5aff - 4 ec6acb6079a5a7d5334577f1a6bb0d86bf1cff789c4933d9c445533d327b2287 - 5 023cf7e2cde4850cadeb86e1aafc8fc4e08300085631d4937ce5242670231c11 - 6 94fe74cf938470c8138f69ad73c59151db4473b6d4d0fd494ba98d368e4fbdac - 7 4e18c58add811bb4aa3b2e61a484dc5240b1f7636bb061f7cf3ea4e962a2505c - 8 eb8cb6fe880c868b26a83831d1e747a8d2639fb18ef844c24c620d8ff5eef6a1 - 9 06a2960450778f6170a9c7f5d1d2ce5708910b4a6036435149c3fbfe725bd163 - 10 d6b7a4b3fc8b2fa3784ee06915c0a1720b1ec16203af6760f60427b03d1cc96d -
diff --git a/packages/client/src/generated/types.ts b/packages/client/src/generated/types.ts
index 3b3188c874..5130fa9496 100644
--- a/packages/client/src/generated/types.ts
+++ b/packages/client/src/generated/types.ts
@@ -775,8 +775,17 @@ export type SessionsHistoryOutput = {
}>
}
readonly delivery: "steer" | "queue"
+ readonly requestExecution?: boolean
}
}
+ | {
+ readonly id: string
+ readonly metadata?: { readonly [x: string]: JsonValue }
+ readonly type: "session.next.prompt.execution_requested"
+ readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
+ readonly location?: { readonly directory: string; readonly workspaceID?: string }
+ readonly data: { readonly timestamp: number; readonly sessionID: string; readonly messageID: string }
+ }
| {
readonly id: string
readonly metadata?: { readonly [x: ...
base {'id': 'f14a9b18-8207-487e-a3d3-227e629ba9ad', 'prevIds': ['169a0f0f-d58f-479f-b024-fa1c7b9a09db']}
head {'id': '57591e35-6f8f-4cda-932c-6a08e85caae7', 'prevIds': ['9401e0ed-b1e3-4557-bc94-1c9b29e1e09c']}
candidate {'id': '3bc49670-06d1-4eba-9a45-8c8128bdcf6f', 'prevIds': ['d4ff43d2-c4f9-442c-bada-a7d488b42fd2']}
No files found
No files found
63c11067ed78471a8109455f164327a1c6dc2ff547e5cb78cad185fe2d844e82 /tmp/opencode/session-recovery-final-v18.patch c5730f7d6017ebd8dde2a2e91365e574469ef10494ec632632b91d7e2318885c /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server commit
63c11067ed78471a8109455f164327a1c6dc2ff547e5cb78cad185fe2d844e82.c5730f7d6017ebd8dde2a2e91365e574469ef10494ec632632b91d7e2318885c.ab9408c81cc9916177641ae8e9f08624fc27b5b9 exists as a commit.packages/core/src/session/runner/llm.ts:415-466 — V18 fixes the V17 blocker. Cancellation reconciles tools first, reads the assistant attached to the exact claimed execution, checks durable time.completed, and settles cancelling → cancelled without publishing a duplicate Step.Interrupted.packages/core/src/event.ts:320-348, packages/core/src/event.ts:378-390 — projectors and claim commit hooks complete inside the durable event transaction before listeners run. The assistant-row check therefore observes the committed Step.Failed projection even while post-commit notification remains blocked.packages/core/test/session-runner.test.ts:3951-3997 — the regression reaches the exact boundary: the listener blocks after observing committed Step.Failed; cancellation then proves one Failed event, zero Interrupted events, no execution claim, and no recovery row.packages/core/src/session/execution/claim.ts:27-33, packages/core/src/session/execution/claim.ts:84-108 — updates, cancellation settlement, and deletion require exact Session, execution, owner, and unexpired lease identity. Expired claims never renew.packages/core/src/session/execution/local.ts:97-121, packages/core/src/session/execution/local.ts:128-162 — cancellation ownership survives runner and finalizer completion; cleanup clears only an exact settled claim and preserves uncertain cancellation or ...