Dashboard › opencode › Session 1Tc0aHgT03zU
1Tc0aHgT03zUwjNb2Perform an independent, read-only adversarial correctness review of the exact frozen interrupted-session recovery candidate.
Immutable scope:
Do not edit files, run formatters, start services, or mutate VCS. Read the patch and immutable base/current blobs as needed. First verify both hashes exactly. Review every patch file, with special focus on:
Known validation evidence (do not trust ...
b4b30666f3a5e9a5bcbfe7237b16e113f684568d67b9d360e65c5655e649009f /tmp/opencode/session-recovery-final-v3.patch 2dd8f7fffa4b382799ebfa093932baa6dd7c5f3e896de5553edb14dbccd7b627 /home/byk/Code/opencode/packages/opencode/dist/server/opencode-server dc661f30cb2055ad6248fdc59b14998d834497be
<path>/tmp/opencode/session-recovery-final-v3.patch</path>
<type>file</type>
<content>
1: diff --git a/packages/core/schema.json b/packages/core/schema.json
2: index d0eeeebd5c419b02287413e8e521fefcbc3c4f27..dad07b6ce6bdb941c0bde9bc7224749dedfc6a39 100644
3: --- a/packages/core/schema.json
4: +++ b/packages/core/schema.json
5: @@ -1,8 +1,8 @@
6: {
7: "version": "7",
8: "dialect": "sqlite",
9: - "id": "f14a9b18-8207-487e-a3d3-227e629ba9ad",
10: - "prevIds": ["169a0f0f-d58f-479f-b024-fa1c7b9a09db"],
11: + "id": "797e6838-8a41-4793-932a-d200aa8d51fb",
12: + "prevIds": ["0445bfe8-162e-4719-a49b-23f1210dcacd"],
13: "ddl": [
14: {
15: "name": "workspace",
16: @@ -60,6 +60,10 @@
17: "name": "session_context_epoch",
18: "entityType": "tables"
19: },
20: + {
21: + "name": "session_execution",
22: + "entityType": "tables"
23: + },
24: {
25: "name": "session_input",
26: "entityType": "tables"
27: @@ -68,6 +72,10 ...
<path>/tmp/opencode/session-recovery-final-v3.patch</path>
<type>file</type>
<content>
1367: @@ -247,42 +409,84 @@ export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* (
1368: events: EventV2.Interface,
1369: sessionID: SessionSchema.ID,
1370: cutoff: number,
1371: + commit?: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
1372: ) {
1373: const rows = yield* db
1374: - .select()
1375: + .select({ input: SessionInputTable })
1376: .from(SessionInputTable)
1377: + .leftJoin(
1378: + SessionRecoveryTable,
1379: + eq(SessionRecoveryTable.continuation_message_id, SessionInputTable.id),
1380: + )
1381: .where(
1382: and(
1383: eq(SessionInputTable.session_id, sessionID),
1384: isNull(SessionInputTable.promoted_seq),
1385: eq(SessionInputTable.delivery, "steer"),
1386: lte(SessionInputTable.admitted_seq, cutoff),
1387: + ...
<path>/tmp/opencode/session-recovery-final-v3.patch</path>
<type>file</type>
<content>
2533: + const hasQueue = recovering || hasSteer ? false : yield* SessionInput.hasPending(db, input.sessionID, "queue")
2534: + if (!recovering && !input.force && !hasSteer && !hasQueue) return
2535: + yield* failInterruptedTools(input.sessionID, claim)
2536: + let promotion: Promotion | undefined = input.recoveryInputID
2537: + ? { recoveryInputID: input.recoveryInputID }
2538: + : hasSteer
2539: + ? "steer"
2540: + : hasQueue
2541: + ? "queue"
2542: + : undefined
2543: + let shouldRun = recovering || input.force || hasSteer || hasQueue
2544: while (shouldRun) {
2545: let needsContinuation = true
2546: let step = 1
2547: while (needsContinuation) {
2548: - const result = yield* runTurn(input.sessionID, promotion, step)
2549: + const result = yield* ...
<path>/tmp/opencode/session-recovery-final-v3.patch</path>
<type>file</type>
<content>
3782: + directory: "/project",
3783: + title: "test",
3784: + version: "test",
3785: + })),
3786: + )
3787: + .run()
3788: + yield* events.publish(SessionEvent.Step.Recovered, {
3789: + sessionID,
3790: + timestamp: created,
3791: + executionID: "cross-session-continuation",
3792: + phase: "ready",
3793: + })
3794: + yield* db.run(
3795: + sql`UPDATE session_input SET session_id = ${other} WHERE id = ${continuation("cross-session-continuation")}`,
3796: + )
3797: + const wakes: string[] = []
3798: +
3799: + yield* SessionRecovery.recover().pipe(
3800: + Effect.provideService(
3801: + SessionExecution.Service,
3802: + SessionExecution.Service.of({
3803: + ...recoveryExecution,
3804: + wake: (id) => Effect.sync(() => ...
<path>/tmp/opencode/session-recovery-final-v3.patch</path>
<type>file</type>
<content>
5187: ? { type: "tool", id: fragmentID(kind, "interrupted"), state: { status: "error" } }
5188: @@ -555,6 +595,289 @@ const verifyPartialFlushOnInterruption = (kind: FragmentKind) =>
5189: })
5190:
5191: describe("SessionRunnerLLM", () => {
5192: + it.effect("redrives only the synthetic recovery continuation", () =>
5193: + Effect.gen(function* () {
5194: + yield* setup
5195: + const session = yield* SessionV2.Service
5196: + const events = yield* EventV2.Service
5197: + const executionService = yield* SessionExecution.Service
5198: + const unrelatedID = SessionMessage.ID.make("msg_recovery_unrelated")
5199: + yield* session.prompt({
5200: + id: unrelatedID,
5201: + sessionID,
5202: + prompt: Prompt.make({ text: "unrelated admit-only" }),
5203: + delivery: "queue",
5204: + resume: false,
5205: + })
5206: + ...
<path>/tmp/opencode/session-recovery-final-v3.patch</path>
<type>file</type>
<content>
6633: },
6634: - "reasoningID": {
6635: + "textID": {
6636: "type": "string"
6637: },
6638: "text": {
6639: "type": "string"
6640: - },
6641: - "providerMetadata": {
6642: - "$ref": "#/components/schemas/LLMProviderMetadata"
6643: }
6644: },
6645: - "required": ["timestamp", "sessionID", "assistantMessageID", "reasoningID", "text"],
6646: + "required": ["timestamp", "sessionID", "assistantMessageID", "textID", "text"],
6647: "additionalProperties": false
6648: }
6649: },
6650: @@ -26258,7 +26458,7 @@
6651: "required": ["type", "id", "syncEvent"],
6652: "additionalProperties": false
6653: ...
Found 32 matches /tmp/opencode/session-recovery-final-v3.patch: Line 1: diff --git a/packages/core/schema.json b/packages/core/schema.json Line 410: diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts Line 421: diff --git a/packages/core/src/database/migration/20260914170650_session-recovery.ts b/packages/core/src/database/migration/20260914170650_session-recovery.ts Line 477: diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts Line 563: diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts Line 595: diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts Line 684: diff --git a/packages/core/src/session/context-epoch.ts b/packages/core/src/session/context-epoch.ts Line 860: diff --git a/packages/core/src/session/execution/local.ts b/packages/core/src/session/execution/local.ts Line 1029: diff --git ...
<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/recovery.ts</path>
<type>file</type>
<content>
1: export * as SessionRecovery from "./recovery"
2:
3: import { and, asc, desc, eq, gt, isNull, lte, or, sql } from "drizzle-orm"
4: import { Clock, DateTime, Effect, Layer, Option, Schedule, Schema } from "effect"
5: import { Database } from "../database/database"
6: import { makeGlobalNode } from "../effect/app-node"
7: import { EventV2 } from "../event"
8: import { SessionEvent } from "./event"
9: import { SessionExecution } from "./execution"
10: import { SessionProjector } from "./projector"
11: import { SessionExecutionClaim } from "./execution/claim"
12: import { continuation, message } from "./recovery-id"
13: import { Prompt } from "./prompt"
14: import { SessionInput } from "./input"
15: import { SessionSchema } from "./schema"
16: import {
17: SESSION_EXECUTION_LEASE_MS,
18: SessionExecutionTable,
19: SessionInputTable,
20: SessionRecoveryTable,
21: ...
<path>/home/byk/Code/opencode/packages/core/src/session/execution/local.ts</path>
<type>file</type>
<content>
1: import { and, asc, eq, isNull } from "drizzle-orm"
2: import { Cause, Clock, Duration, Effect, Exit, Layer, Schedule } from "effect"
3: import { Database } from "../../database/database"
4: import { LocationServiceMap } from "../../location-service-map"
5: import { makeGlobalNode } from "../../effect/app-node"
6: import { SessionRunCoordinator } from "../run-coordinator"
7: import { SessionRunner } from "../runner"
8: import { SessionMessage } from "../message"
9: import { SessionSchema } from "../schema"
10: import { SessionStore } from "../store"
11: import { SessionExecution } from "../execution"
12: import { SessionExecutionClaim } from "./claim"
13: import { SESSION_EXECUTION_LEASE_MS, SessionExecutionTable, SessionRecoveryTable } from "../sql"
14:
15: /** Current-process routing for implicit-local Locations. Future remote placement belongs here. ...
Your prior response was empty and therefore invalid. This is your one permitted evidence-or-blocker retry. The candidate remains unchanged and the hashes were just reverified:
Complete the original independent read-only correctness review now. Do not use Lore recall. Read the canonical patch directly and inspect base blobs or current package files only when needed. Do not edit, test, build, start services, or mutate VCS. You must return substantive evidence: either findings with exact patch/current file:line references or an exact blocker naming the failed tool/path. Classify MUST-FIX, CONCERN, and PASS; explicitly assess closure of prior v2 findings; end exactly MERGE or DO-NOT-MERGE. Empty or progress-only output disqualifies this reviewer.
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_0a7a8f22c001TF1P1I3OV2AZYU
+ { promoted_seq: null },
+ )
+ expect(yield* db.select().from(SessionInputTable).where(eq(SessionInputTable.id, ordinaryID)).get()).toMatchObject({
+ promoted_seq: expect.any(Number),
+ })
+ }),
+ )
+
+ it.effect("quarantines a malformed claim without blocking a valid claim", () =>
+ Effect.gen(function* () {
+ const { db } = yield* Database.Service
+ const validSession = SessionV2.ID.make("ses_recovery_valid")
+ yield* db
+ .insert(ProjectTable)
+ .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
+ .run()
+ yield* db
+ .insert(SessionTable)
+ .values(
+ [sessionID, validSession].map((id) => ({
+ id,
+ project_id: Project.ID.global,
+ slug: id,
+ directory: "/project",
+ ...
No files found
/home/byk/Code/opencode/packages/core/src/event.ts
Found 34 matches
/home/byk/Code/opencode/packages/core/src/event.ts:
Line 122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
Line 123: readonly commit?: (seq: number) => Effect.Effect<void>
Line 127: readonly publish: <D extends Definition>(
Line 140: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
Line 144: options?: { readonly publish?: boolean; readonly ownerID?: string; readonly strictOwner?: boolean },
Line 205: function commitDurableEvent(
Line 214: commit?: (seq: number) => Effect.Effect<void>,
Line 239: const committed = yield* db
Line 316: const committed = {
Line 321: yield* projector(committed)
Line 323: if (commit) yield* commit(seq)
Line 354: if (committed) {
Line 356: ...
<path>/home/byk/Code/opencode/packages/core/src/event.ts</path>
<type>file</type>
<content>
110: export class SubscriberOverflowError extends Schema.TaggedErrorClass<SubscriberOverflowError>()(
111: "EventV2.SubscriberOverflow",
112: { capacity: Schema.Int },
113: ) {}
114:
115: export const define = Event.define
116: export const versionedType = Event.versionedType
117:
118: export interface PublishOptions {
119: readonly id?: ID
120: readonly metadata?: Record<string, unknown>
121: readonly location?: Location.Ref
122: /** Local operational projection committed atomically with a new durable event. Not replayed or serialized. */
123: readonly commit?: (seq: number) => Effect.Effect<void>
124: }
125:
126: export interface Interface {
127: readonly publish: <D extends Definition>(
128: definition: D,
129: data: Data<D>,
130: options?: PublishOptions,
131: ) => Effect.Effect<Payload<D>>
132: readonly subscribe: <D extends Definition>(definition: D) => ...
Found 7 matches
/tmp/opencode/session-recovery-final-v3.patch:
Line 3236: import { Prompt } from "@opencode-ai/core/session/prompt"
Line 5023: import { Prompt } from "@opencode-ai/core/session/prompt"
Line 5072: import { Prompt } from "@opencode-ai/core/session/prompt"
Line 5747: import { CorsConfig, isAllowedCorsOrigin, type CorsOptions } from "@opencode-ai/server/cors"
Line 7213: diff --git a/packages/server/src/routes.ts b/packages/server/src/routes.ts
Line 7215: --- a/packages/server/src/routes.ts
Line 7216: +++ b/packages/server/src/routes.ts
<path>/home/byk/Code/opencode/packages/core/src/session/runner/llm.ts</path>
<type>file</type>
<content>
170: }
171: }
172:
173: const continueAfterCompaction = (step: number) => new TurnTransitionError({ _tag: "ContinueAfterCompaction", step })
174: const continueAfterOverflowCompaction = (step: number) =>
175: new TurnTransitionError({ _tag: "ContinueAfterOverflowCompaction", step })
176:
177: const loadSystemContext = (agent: AgentV2.Selection) =>
178: Effect.all([systemContext.load(), skillGuidance.load(agent), referenceGuidance.load()], {
179: concurrency: "unbounded",
180: }).pipe(Effect.map(SystemContext.combine))
181:
182: const runTurnAttempt = Effect.fn("SessionRunner.runTurn")(function* (
183: sessionID: SessionSchema.ID,
184: executionID: string,
185: promotion: Promotion | undefined,
186: step: number,
187: ownerID: string,
188: recoverOverflow?: typeof ...
<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/schema/src/event-manifest.ts</path>
<type>file</type>
<content>
1: export * as EventManifest from "./event-manifest"
2:
3: import { Catalog } from "./catalog"
4: import { Durable } from "./durable-event-manifest"
5: import { Event } from "./event"
6: import { FileSystem } from "./filesystem"
7: import { FileSystemWatcher } from "./filesystem-watcher"
8: import { InstallationEvent } from "./installation-event"
9: import { Integration } from "./integration"
10: import { LegacyEvent } from "./legacy-event"
11: import { LspEvent } from "./lsp-event"
12: import { McpEvent } from "./mcp-event"
13: import { ModelsDev } from "./models-dev"
14: import { Permission } from "./permission"
15: import { PermissionV1 } from "./permission-v1"
16: import { Plugin } from "./plugin"
17: import { Project } from "./project"
18: import { ProjectDirectories } from "./project-directories"
19: import { Pty } from "./pty"
20: import { Question } from "./question"
21: ...
<path>/home/byk/Code/opencode/packages/schema/src/session-event.ts</path>
<type>file</type>
<content>
450: messageID: SessionMessage.ID,
451: text: Schema.String,
452: },
453: })
454: export type Delta = typeof Delta.Type
455:
456: export const Ended = Event.define({
457: type: "session.next.compaction.ended",
458: ...options,
459: schema: {
460: ...Base,
461: messageID: SessionMessage.ID,
462: reason: Started.data.fields.reason,
463: text: Schema.String,
464: recent: Schema.String,
465: },
466: })
467: export type Ended = typeof Ended.Type
468: }
469:
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 = ...
<path>/home/byk/Code/opencode/packages/schema/src/durable-event-manifest.ts</path>
<type>file</type>
<content>
1: export * as DurableEventManifest from "./durable-event-manifest"
2:
3: import { Event } from "./event"
4: import { SessionEvent } from "./session-event"
5: import { SessionV1 } from "./session-v1"
6:
7: export const SessionDurable = {
8: definitions: Event.durable(SessionEvent.DurableDefinitions),
9: schema: SessionEvent.Durable,
10: } as const
11:
12: export const Durable = Event.durable([
13: ...SessionV1.Event.Definitions.filter((definition) => definition.durable !== undefined),
14: ...SessionEvent.DurableDefinitions,
15: ])
(End of file - total 15 lines)
</content>
Found 42 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 104: export const PromptExecutionRequested = Event.define({
Line 112: export type PromptExecutionRequested = typeof PromptExecutionRequested.Type
Line 221: type: "session.next.step.recovered",
Line 490: PromptExecutionRequested,
Line 524: PromptExecutionRequested,
/home/byk/Code/opencode/packages/sdk/openapi.json:
Line 15339: "$ref": "#/components/schemas/EventSessionNextStepRecovered"
Line 18289: "enum": ["session.next.step.recovered"]
Line 20607: "$ref": "#/components/schemas/SyncEventSessionNextStepRecovered"
Line 23841: "$ref": "#/components/schemas/SessionNextStepRecovered"
Line 24230: "$ref": "#/components/schemas/SessionNextStepRecovered"
Line 26283: ...
<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, eq, isNull, lte } from "drizzle-orm"
4: import { DateTime, Effect, 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 { SessionSchema } from "./schema"
12: import { SessionInputTable, SessionMessageTable, SessionRecoveryTable } from "./sql"
13:
14: type DatabaseService = Database.Interface["db"]
15:
16: export { Admitted, Delivery }
17:
18: const decodePrompt = Schema.decodeUnknownSync(Prompt)
19: const encodePrompt = Schema.encodeSync(Prompt)
20:
21: export const decode = (value: unknown) => Schema.decodeUnknownOption(Prompt)(value)
22:
23: ...
<path>/home/byk/Code/opencode/packages/core/src/session/input.ts</path>
<type>file</type>
<content>
470: export const promoteExact = Effect.fn("SessionInput.promoteExact")(function* (
471: db: DatabaseService,
472: events: EventV2.Interface,
473: sessionID: SessionSchema.ID,
474: messageID: SessionMessage.ID,
475: commit: (row: typeof SessionInputTable.$inferSelect) => Effect.Effect<void>,
476: ) {
477: const row = yield* db
478: .select()
479: .from(SessionInputTable)
480: .where(
481: and(
482: eq(SessionInputTable.id, messageID),
483: eq(SessionInputTable.session_id, sessionID),
484: isNull(SessionInputTable.promoted_seq),
485: eq(SessionInputTable.delivery, "queue"),
486: ),
487: )
488: .get()
489: .pipe(Effect.orDie)
490: if (!row) return yield* Effect.die(`Pending recovery input not found: ${messageID}`)
491: yield* publish(db, events, sessionID, [row], commit)
492: })
(End of file - total 492 ...
<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<Pick<typeof SessionExecutionTable.$inferInsert, "assistant_message_id" | "phase">>
18:
19: export class Lost extends Error {
20: constructor(readonly claim: Claim) {
21: super(`Session execution claim lost: ${claim.executionID}`)
22: }
23: }
24:
25: const where = (claim: Claim, now: number) =>
26: and(
27: ...
Found 38 matches
/home/byk/Code/opencode/packages/core/src/snapshot.ts:
Line 19: operation: Schema.Literals(["capture", "files", "diff", "preview", "restore"]),
Line 47: * best-effort capture fails.
Line 49: readonly capture: () => Effect.Effect<ID | undefined>
Line 52: * List project-relative paths changed between two captured trees without
Line 58: * Generate structured per-file diffs between two captured trees. `context`
Line 77: * Replace the snapshot index with a captured tree and check out all its entries.
Line 103: return yield* new Error({ operation: "capture", message: "Location is outside the project" })
Line 108: if (!source) return yield* new Error({ operation: "capture", message: "Project is not a Git repository" })
Line 121: .pipe(Effect.mapError((cause) => failure("capture", cause)))
Line 129: const capture = Effect.fn("Snapshot.capture")(function* () {
Line 134: yield* ...
<path>/home/byk/Code/opencode/packages/core/src/snapshot.ts</path>
<type>file</type>
<content>
90: const fs = yield* FSUtil.Service
91: const git = yield* Git.Service
92: const global = yield* Global.Service
93: const location = yield* Location.Service
94: const source = yield* git.repo.discover(location.project.directory)
95: const worktree = source
96: ? AbsolutePath.make(yield* fs.realPath(source.worktree).pipe(Effect.orDie))
97: : location.project.directory
98: const gitDirectory = AbsolutePath.make(path.join(global.data, "snapshot", location.project.id, Hash.fast(worktree)))
99:
100: const scope = Effect.fnUntraced(function* () {
101: const relative = path.relative(worktree, location.directory)
102: if (relative.startsWith("..") || path.isAbsolute(relative))
103: return yield* new Error({ operation: "capture", message: "Location is outside the project" })
104: return RelativePath.make(relative.replaceAll("\\", ...
<path>/home/byk/Code/opencode/packages/core/test/session-projector.test.ts</path>
<type>file</type>
<content>
760: expect(yield* db.select().from(SessionRecoveryTable).all()).toHaveLength(1)
761: expect(yield* db.select().from(SessionInputTable).all()).toHaveLength(1)
762: }),
763: )
764:
765: it.effect("never recovers a pre-promotion claim when input commits at the recovery boundary", () =>
766: Effect.gen(function* () {
767: const { db } = yield* Database.Service
768: const events = yield* EventV2.Service
769: yield* db
770: .insert(ProjectTable)
771: .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
772: .run()
773: yield* db
774: .insert(SessionTable)
775: .values({
776: id: sessionID,
777: project_id: Project.ID.global,
778: slug: "test",
779: directory: "/project",
780: title: "test",
781: ...
Found 1 matches /home/byk/Code/opencode/packages/core/src/session/projector.ts: Line 504: .delete(SessionMessageTable)
<path>/home/byk/Code/opencode/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
470: yield* events.project(SessionEvent.RevertEvent.Staged, (event) =>
471: db
472: .update(SessionTable)
473: .set({
474: revert: { ...event.data.revert, files: event.data.revert.files ? [...event.data.revert.files] : undefined },
475: time_updated: DateTime.toEpochMillis(event.data.timestamp),
476: })
477: .where(eq(SessionTable.id, event.data.sessionID))
478: .run()
479: .pipe(Effect.orDie, Effect.asVoid),
480: )
481: yield* events.project(SessionEvent.RevertEvent.Cleared, (event) =>
482: db
483: .update(SessionTable)
484: .set({ revert: null, time_updated: DateTime.toEpochMillis(event.data.timestamp) })
485: .where(eq(SessionTable.id, event.data.sessionID))
486: .run()
487: .pipe(Effect.orDie, Effect.asVoid),
488: )
489: yield* ...
Found 5 matches
/home/byk/Code/opencode/packages/core/src/session/runner/publish-llm-event.ts:
Line 84: let stepSettlement: { readonly finish: string; readonly tokens: ReturnType<typeof tokens> } | undefined
Line 444: if (stepSettlement) return yield* Effect.die("Duplicate step finish")
Line 445: stepSettlement = { finish: event.reason, tokens: tokens(event.usage) }
Line 464: hasProviderError: () => providerFailed,
Line 465: stepSettlement: () => stepSettlement,
<path>/home/byk/Code/opencode/packages/core/src/session/runner/publish-llm-event.ts</path>
<type>file</type>
<content>
360: const tool = tools.get(event.id)!
361: if (!tool.inputEnded) yield* endToolInput(event)
362: if (tool.name !== event.name)
363: return yield* Effect.die(`Tool call name changed for ${event.id}: ${tool.name} -> ${event.name}`)
364: if (tool.called) return yield* Effect.die(`Duplicate tool call: ${event.id}`)
365: tool.called = true
366: tool.providerExecuted = event.providerExecuted === true
367: tool.providerMetadata = event.providerMetadata
368: yield* publishEvent(SessionEvent.Tool.Called, {
369: sessionID: input.sessionID,
370: timestamp: yield* timestamp,
371: assistantMessageID: tool.assistantMessageID,
372: callID: event.id,
373: tool: event.name,
374: input: record(event.input),
375: provider: {
376: ...
<path>/home/byk/Code/opencode/packages/core/src/session/message-updater.ts</path>
<type>file</type>
<content>
190: const currentAssistant = yield* adapter.getCurrentAssistant()
191: if (currentAssistant) {
192: yield* adapter.updateAssistant(
193: produce(currentAssistant, (draft) => {
194: draft.time.completed = event.data.timestamp
195: }),
196: )
197: }
198: yield* adapter.appendMessage(
199: SessionMessage.Assistant.make({
200: id: event.data.assistantMessageID,
201: type: "assistant",
202: agent: event.data.agent,
203: model: event.data.model,
204: time: { created: event.data.timestamp },
205: content: [],
206: snapshot: event.data.snapshot ? { start: event.data.snapshot } : undefined,
207: }),
208: )
209: })
210: },
211: ...
Found 42 matches
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
Line 1816: LLMEvent.toolCall({ id: "call-error", name: "write", input: { path: "README.md" }, providerExecuted: true }),
Line 1823: providerExecuted: true,
Line 1836: providerExecuted: true,
Line 2131: id: "hosted-search",
Line 2134: providerExecuted: true,
Line 2135: providerMetadata: { openai: { itemId: "hosted-search" } },
Line 2138: id: "hosted-search",
Line 2141: providerExecuted: true,
Line 2158: id: "hosted-search",
Line 2161: providerExecuted: true,
Line 2162: providerMetadata: { openai: { itemId: "hosted-search" } },
Line 2166: id: "hosted-search",
Line 2169: providerExecuted: true,
Line 2811: it.effect("durably fails hosted tools left running by a prior process before continuing inline", () =>
Line 2818: prompt: ...
<path>/home/byk/Code/opencode/packages/core/test/session-runner.test.ts</path>
<type>file</type>
<content>
3760: yield* setup
3761: const session = yield* SessionV2.Service
3762: yield* session.prompt({
3763: sessionID,
3764: prompt: Prompt.make({ text: "Do not continue failed provider" }),
3765: resume: false,
3766: })
3767:
3768: requests.length = 0
3769: const executionCount = executions.length
3770: response = [
3771: LLMEvent.stepStart({ index: 0 }),
3772: LLMEvent.toolCall({ id: "call-before-provider-error", name: "echo", input: { text: "settled" } }),
3773: LLMEvent.providerError({ message: "Provider unavailable" }),
3774: ]
3775:
3776: yield* session.resume(sessionID)
3777:
3778: expect(requests).toHaveLength(1)
3779: expect(executions.slice(executionCount)).toEqual(["settled"])
3780: }),
3781: )
3782:
3783: it.effect("durably fails a hosted tool when ...
(no output)
<path>/home/byk/Code/opencode/packages/core/src/session/projector.ts</path>
<type>file</type>
<content>
370: )
371: yield* events.project(SessionEvent.PromptAdmitted, (event) =>
372: Effect.gen(function* () {
373: if (event.durable === undefined) return yield* Effect.die("Durable Session event is missing aggregate sequence")
374: yield* SessionInput.projectAdmitted(db, {
375: admittedSeq: event.durable.seq,
376: id: event.data.messageID,
377: sessionID: event.data.sessionID,
378: prompt: event.data.prompt,
379: delivery: event.data.delivery,
380: requestExecution: event.data.requestExecution ?? false,
381: timeCreated: event.data.timestamp,
382: })
383: }),
384: )
385: yield* events.project(SessionEvent.PromptExecutionRequested, (event) =>
386: SessionInput.projectExecutionRequested(db, {
387: id: event.data.messageID,
388: sessionID: ...
Found 5 matches
/home/byk/Code/opencode/packages/core/test/session-runner.test.ts:
Line 598: it.effect("redrives only the synthetic recovery continuation", () =>
Line 784: it.effect("never starts a local tool after ownership changes", () =>
Line 1474: it.effect("keeps automatic compaction unknown until Compaction.Ended commits", () =>
Line 1954: it.effect("recovers the continuation committed with a settled tool step", () =>
/home/byk/Code/opencode/packages/core/test/session-projector.test.ts:
Line 494: it.effect("quarantines a full batch of poison recovery rows before redriving valid work", () =>
<path>/home/byk/Code/opencode/packages/core/src/session.ts</path>
<type>file</type>
<content>
350: .pipe(Effect.as(events.durable({ aggregateID: input.sessionID, after: input.after }))),
351: ).pipe(Stream.filter((event): event is SessionEvent.DurableEvent => isDurableSessionEvent(event))),
352: history: Effect.fn("V2Session.history")(function* (input) {
353: yield* result.get(input.sessionID)
354: return yield* EventV2.readAggregate(db, {
355: ...input,
356: aggregateID: input.sessionID,
357: manifest: SessionDurable,
358: })
359: }),
360: prompt: Effect.fn("V2Session.prompt")((input) =>
361: Effect.uninterruptible(
362: Effect.gen(function* () {
363: yield* result.get(input.sessionID)
364: const prompt = resolvePrompt(input.prompt)
365: const messageID = input.id ?? SessionMessage.ID.create()
366: const delivery = ...
<path>/home/byk/Code/opencode/packages/core/src/session/compaction.ts</path>
<type>file</type>
<content>
180: }
181:
182: export const make = (dependencies: Dependencies) => {
183: const config = settings(dependencies.config)
184: const compactAfterOverflow = Effect.fn("SessionCompaction.compactAfterOverflow")(function* (input: CompactInput) {
185: const context = input.model.route.defaults.limits?.context
186: if (context === undefined || context <= 0) return false
187: const output = input.request.generation?.maxTokens ?? input.model.route.defaults.limits?.output ?? 0
188: const selected = select(input.entries, config.tokens)
189: const previousSummary = input.entries.find((entry) => entry.message.type === "compaction")?.message
190: if (!selected || (selected.head.length === 0 && previousSummary?.type !== "compaction")) return false
191: const summaryPrompt = buildPrompt({
192: previousSummary: previousSummary?.type === "compaction" ? ...
<path>/home/byk/Code/opencode/packages/core/src/session/sql.ts</path>
<type>file</type>
<content>
145: session_id: text()
146: .$type<SessionSchema.ID>()
147: .notNull()
148: .references(() => SessionTable.id, { onDelete: "cascade" }),
149: prompt: text({ mode: "json" }).notNull().$type<Prompt>(),
150: delivery: text().$type<SessionInput.Delivery>().notNull(),
151: admitted_seq: integer().notNull(),
152: promoted_seq: integer(),
153: wake_pending: integer({ mode: "boolean" }).notNull().default(false),
154: time_created: integer()
155: .notNull()
156: .$default(() => Date.now()),
157: },
158: (table) => [
159: index("session_input_session_pending_delivery_seq_idx").on(
160: table.session_id,
161: table.promoted_seq,
162: table.delivery,
163: table.admitted_seq,
164: ),
165: uniqueIndex("session_input_session_admitted_seq_idx").on(table.session_id, table.admitted_seq),
166: ...
b4b30666f3a5e9a5bcbfe7237b16e113f684568d67b9d360e65c5655e649009f2dd8f7fffa4b382799ebfa093932baa6dd7c5f3e896de5553edb14dbccd7b627git apply --check --reverse /tmp/opencode/session-recovery-final-v3.patch succeeds against the reviewed package files.SessionExecutionLocal selects any pending recovery row and passes its continuation directly to the runner:
packages/core/src/session/execution/local.ts:48-63That query checks only session_id, wake_pending, and recovery_error. It does not validate:
Those checks exist only in the periodic scanner:
packages/core/src/session/recovery.ts:198-310The runner then calls promoteExact:
packages/core/src/session/runner/llm.ts:218-225But promoteExact checks only ID, Session, pending status, and queue delivery:
packages/core/src/session/input.ts:470-491Concrete failure sequence:
promoteExact promotes it and clears its wake through the Prompted projector.