@ordewell/core 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ModeResolver-CHsaoV7M.d.ts → ModeResolver-B64aUZXJ.d.ts} +1 -1
- package/dist/{ModeResolver-b_7bPlBS.d.mts → ModeResolver-CA2fUU9h.d.mts} +1 -1
- package/dist/{Task-BTEH-ztX.d.mts → Task-CxGgN6I0.d.mts} +503 -16
- package/dist/{Task-BTEH-ztX.d.ts → Task-CxGgN6I0.d.ts} +503 -16
- package/dist/{chunk-BKTDYGIP.mjs → chunk-2IFKPYPO.mjs} +441 -17
- package/dist/chunk-2IFKPYPO.mjs.map +1 -0
- package/dist/{chunk-BFLVOYNR.mjs → chunk-FX33UKAD.mjs} +8 -6
- package/dist/chunk-FX33UKAD.mjs.map +1 -0
- package/dist/{chunk-F6WS3JLQ.mjs → chunk-PKJTN2PD.mjs} +20 -11
- package/dist/chunk-PKJTN2PD.mjs.map +1 -0
- package/dist/{chunk-HP2LNKST.mjs → chunk-RZ23BMVR.mjs} +345 -134
- package/dist/chunk-RZ23BMVR.mjs.map +1 -0
- package/dist/chunk-UDUATSCJ.mjs +48 -0
- package/dist/chunk-UDUATSCJ.mjs.map +1 -0
- package/dist/{IRunner-BwzyUVIL.d.mts → eventStore-CTCSKS9E.d.ts} +916 -129
- package/dist/{IRunner-DjJkYwko.d.ts → eventStore-CcB8yELZ.d.mts} +916 -129
- package/dist/index.d.mts +677 -97
- package/dist/index.d.ts +677 -97
- package/dist/index.js +8233 -2450
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6951 -1772
- package/dist/index.mjs.map +1 -1
- package/dist/parsing.d.mts +2 -2
- package/dist/parsing.d.ts +2 -2
- package/dist/parsing.js +277 -21
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -3
- package/dist/plan-utils.d.mts +8 -5
- package/dist/plan-utils.d.ts +8 -5
- package/dist/plan-utils.js +129 -16
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +17 -3
- package/dist/{runSettingsText-x-YML2ay.d.mts → runSettingsText-DFVpe6aU.d.mts} +1142 -934
- package/dist/{runSettingsText-CvZ40IPv.d.ts → runSettingsText-lKLDsyli.d.ts} +1142 -934
- package/dist/testing.d.mts +83 -4
- package/dist/testing.d.ts +83 -4
- package/dist/testing.js +142 -1
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +126 -2
- package/dist/testing.mjs.map +1 -1
- package/docker/task.Dockerfile +22 -0
- package/package.json +2 -1
- package/skills/diagnose/SKILL.md +2 -2
- package/skills/explore/SKILL.md +1 -1
- package/dist/chunk-BFLVOYNR.mjs.map +0 -1
- package/dist/chunk-BKTDYGIP.mjs.map +0 -1
- package/dist/chunk-F6WS3JLQ.mjs.map +0 -1
- package/dist/chunk-HP2LNKST.mjs.map +0 -1
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing.ts","../src/models/Task.ts","../src/conversation/skillTokens.ts","../src/services/repoPath.ts","../src/services/isolationRecord.ts"],"sourcesContent":["import type { IConfig } from './interfaces/IConfig';\nimport type { IFileSystem, ToolOutcome } from './interfaces/IFileSystem';\nimport type { InvokedCommand, IRunnerSession, QueuedTaskMessage, ScriptExit, StructuredEvent, StructuredTurnEnd, TaskMessageFrom } from './interfaces/IRunner';\nimport type { RunnerCommand } from './models/RunnerCommand';\nimport type { RunnerSessionCost } from './models/Usage';\nimport type { HandoffCommit } from './models/Handoff';\nimport type { ApprovalDecision } from './interfaces/IApproval';\nimport type { CheckpointAnswer, TaskCompleteArgs } from './services/mcp/tools';\nimport type {\n IsolationAvailability,\n IsolationHandoff,\n IsolationMergeResult,\n IsolationOutcome,\n IsolationPruneResult,\n IsolationRemoval,\n IsolationRevertResult,\n IsolationRun,\n IntegrationDisposal,\n LandedWork,\n PreparedTask,\n PrepareIntent,\n IWorktreeIsolation,\n RepairEvidence,\n RevertCommand,\n RevertWatch,\n SetupFailure,\n TreeSnapshot,\n} from './interfaces/IWorktreeIsolation';\nimport { DEFAULT_RUNNERS, type Task } from './models/Task';\nimport { handoffOf, integrationBranchFor, noRemoval, SELF_REPO } from './services/isolationRecord';\n\n/**\n * Let an already-queued promise chain run to its next await before asserting,\n * without a wall-clock sleep. For a chain whose length is not known, drain the\n * microtask queue repeatedly. Only helps a chain that does not wait on a timer.\n */\nexport async function flushMicrotasks(rounds = 20): Promise<void> {\n for (let i = 0; i < rounds; i += 1) await Promise.resolve();\n}\n\nexport function fakeConfig(overrides: Partial<IConfig> = {}): IConfig {\n return {\n aiProvider: 'openrouter',\n apiKey: 'sk-test',\n planningModel: 'test-model',\n enabledRunners: [...DEFAULT_RUNNERS],\n maxParallelSessions: 3,\n researchEnabled: false,\n researchMaxSteps: 10,\n researchMaxFileSize: 100_000,\n openAiBaseUrl: 'https://api.openai.com/v1',\n openAiApiKey: '',\n openrouterKey: '',\n geminiKey: '',\n openaiCompatibleBaseUrl: '',\n openaiCompatibleApiKey: '',\n orchestratorModel: '',\n researchSubagentModel: '',\n geminiModel: '',\n planMapEnabled: true,\n autonomousMode: true,\n // Off so an orchestrator built without an injected isolation never runs\n // real git against whatever repository the tests happen to run in.\n worktreeIsolation: false,\n workspaceRepos: [],\n worktreeLinks: [],\n // Off so a scripted conflict stays a conflict unless a test opts into repairing it.\n conflictRepairAttempts: 0,\n approvalMode: 'ask',\n approvalPreApproved: [],\n // Off so a test that is not about the allowlist sees every prompt it raises.\n approvalDefaults: false,\n effectAllowlist: [],\n notifyCommand: [],\n webReadBudget: 40,\n plannerNativeShell: false,\n setProviderModelLists: () => {},\n getProviderBaseUrl: () => '',\n // Every vendor holds a key, so no planner turn is refused for want of one\n // unless a test takes it away.\n getProviderApiKey: () => 'sk-test',\n ...overrides,\n };\n}\n\nconst EMPTY_OUTCOME: ToolOutcome = { success: false, output: '', truncated: false };\n\n/**\n * A complete {@link IFileSystem} stub. Lives here rather than in each package's\n * test folder so adding a tool to the interface is one edit, not one per suite.\n */\nexport function fakeFileSystem(overrides: Partial<IFileSystem> = {}): IFileSystem {\n return {\n readFile: async () => EMPTY_OUTCOME,\n readFiles: async () => EMPTY_OUTCOME,\n glob: async () => EMPTY_OUTCOME,\n grep: async () => EMPTY_OUTCOME,\n findSymbol: async () => EMPTY_OUTCOME,\n listDir: async () => EMPTY_OUTCOME,\n bash: async () => EMPTY_OUTCOME,\n getWorkspaceRoot: () => '/workspace',\n ...overrides,\n };\n}\n\nexport class FakeRunnerSession implements IRunnerSession {\n private outputCbs: Array<(text: string) => void> = [];\n private exitCbs: Array<(code: number) => void> = [];\n output = '';\n written: string[] = [];\n killed = false;\n\n state: 'working' | 'idle' = 'working';\n messages: QueuedTaskMessage[] = [];\n /** Every message a turn was started with, in order. */\n delivered: string[] = [];\n /** The ones of {@link delivered} Ordewell sent: the completion reminder (ADR-0018, W1). */\n fromOrdewell: string[] = [];\n interrupts = 0;\n private messageCount = 0;\n private turnEndCbs: Array<(reason: StructuredTurnEnd) => void> = [];\n private eventCbs: Array<(event: StructuredEvent) => void> = [];\n private completeCbs: Array<(report: TaskCompleteArgs) => void> = [];\n private checkpointHandler: ((question: string, signal: AbortSignal) => Promise<CheckpointAnswer>) | null = null;\n\n constructor(public id = 's1', public taskId = 't1', public sessionId: string | null = 'native-1') {}\n\n /** `stop()` was asked for: the runner winds its turn down, still reporting, until {@link emitExit}. */\n stopped = false;\n private markGone!: () => void;\n private readonly gone = new Promise<void>((resolve) => { this.markGone = resolve; });\n\n onOutput(cb: (text: string) => void): void { this.outputCbs.push(cb); }\n onExit(cb: (code: number) => void): void { this.exitCbs.push(cb); }\n kill(): void { this.killed = true; }\n stop(): Promise<void> {\n this.stopped = true;\n return this.gone;\n }\n getOutput(): string { return this.output; }\n\n emitOutput(text: string): void {\n this.output += text;\n for (const cb of this.outputCbs) cb(text);\n }\n emitExit(code: number): void {\n for (const cb of this.exitCbs) cb(code);\n this.markGone();\n }\n write(text: string): void {\n this.written.push(text);\n const message = text.trim();\n if (message) this.sendMessage(message);\n }\n\n turnState(): 'working' | 'idle' { return this.state; }\n onTurnEnd(cb: (reason: StructuredTurnEnd) => void): void { this.turnEndCbs.push(cb); }\n onEvent(cb: (event: StructuredEvent) => void): void { this.eventCbs.push(cb); }\n onTaskComplete(cb: (report: TaskCompleteArgs) => void): void { this.completeCbs.push(cb); }\n /**\n * The runner calls `task_complete`, as `StructuredSession` relays it. Here it\n * is published at once; a test that needs the real session's turn-end\n * ordering drives `StructuredSession` itself.\n */\n reportComplete(report: TaskCompleteArgs): void {\n for (const cb of this.completeCbs) cb(report);\n }\n onToolCheckpoint(handler: (question: string, signal: AbortSignal) => Promise<CheckpointAnswer>): void { this.checkpointHandler = handler; }\n private scriptExitCbs: Array<(exit: ScriptExit) => void> = [];\n onScriptExit(cb: (exit: ScriptExit) => void): void { this.scriptExitCbs.push(cb); }\n /** A script task's body ended, as `ScriptSession` hands it on (ADR-0035). */\n reportScriptExit(exit: ScriptExit): void {\n for (const cb of this.scriptExitCbs) cb(exit);\n }\n /** The runner calls `checkpoint`, as `StructuredSession` relays it; settles with the answer. */\n callCheckpoint(question: string, signal: AbortSignal = new AbortController().signal): Promise<CheckpointAnswer> {\n if (!this.checkpointHandler) throw new Error('no checkpoint handler attached');\n return this.checkpointHandler(question, signal);\n }\n sendMessage(text: string, from?: TaskMessageFrom): string {\n this.messageCount += 1;\n const id = `msg-${this.messageCount}`;\n if (this.state === 'idle') this.deliver(text, from);\n else this.messages.push({ id, text, ...(from ? { from } : {}) });\n return id;\n }\n /** Interrupts, and the turn that replaces the interrupted one opens with the forced message. */\n forceSend(text: string): string {\n if (this.state === 'idle') return this.sendMessage(text);\n this.messageCount += 1;\n const id = `msg-${this.messageCount}`;\n this.putForced({ id, text, forced: true });\n void this.interrupt();\n return id;\n }\n forceSendQueued(id: string): boolean {\n const message = this.messages.find((m) => m.id === id);\n if (!message) return false;\n if (message.forced) return true;\n this.messages = this.messages.filter((m) => m !== message);\n this.putForced({ ...message, forced: true });\n if (this.state === 'working') void this.interrupt();\n return true;\n }\n private putForced(message: QueuedTaskMessage): void {\n const at = this.messages.findIndex((m) => !m.forced);\n this.messages.splice(at < 0 ? this.messages.length : at, 0, message);\n }\n removeQueued(id: string): boolean {\n const before = this.messages.length;\n this.messages = this.messages.filter((m) => m.id !== id);\n return this.messages.length < before;\n }\n queued(): QueuedTaskMessage[] { return this.messages.map((m) => ({ ...m })); }\n async interrupt(): Promise<void> {\n this.interrupts += 1;\n if (this.state === 'working') this.emitTurnEnd('interrupted');\n }\n nativeSessionId(): string | null { return this.sessionId; }\n /** What `sessionCost()` reports: the runner's running cost as counted, null while unknown. */\n cost: RunnerSessionCost | null = null;\n sessionCost(): RunnerSessionCost | null { return this.cost; }\n\n /** What `commands()` reports: the runner's own commands (#66). */\n runnerCommands: RunnerCommand[] = [];\n /** Every runner command invoked, in order. */\n invoked: InvokedCommand[] = [];\n commands(): RunnerCommand[] { return [...this.runnerCommands]; }\n /** Runs it as a message that starts with it, which is what a runner reads. */\n invokeCommand(name: string, args = ''): string {\n this.invoked.push({ name, ...(args.trim() ? { args: args.trim() } : {}) });\n return this.sendMessage(args.trim() ? `/${name} ${args.trim()}` : `/${name}`);\n }\n\n /** Every answer this session was given, in order. */\n answers: Array<{ id: string; decision: ApprovalDecision }> = [];\n private readonly openPermissions = new Set<string>();\n\n /** The runner asks to use a tool, as `StructuredSession` announces it. */\n requestPermission(id: string, name: string, input: Record<string, unknown>, suggestions: unknown[] = []): void {\n this.openPermissions.add(id);\n this.emitEvent({ type: 'permission_request', id, name, detail: JSON.stringify(input), input, suggestions });\n }\n /** The runner gives up on a request, as an interrupt makes it. */\n withdrawPermission(id: string): void {\n if (this.openPermissions.delete(id)) this.emitEvent({ type: 'permission_withdrawn', id });\n }\n answerPermission(id: string, decision: ApprovalDecision): boolean {\n if (!this.openPermissions.delete(id)) return false;\n this.answers.push({ id, decision });\n this.emitEvent({ type: 'permission_decided', id, decision });\n return true;\n }\n\n emitEvent(event: StructuredEvent): void {\n for (const cb of this.eventCbs) cb(event);\n }\n /** The runner reads a queued message inside the running turn, as `StructuredSession` reports it (ADR-0023). */\n deliverMidTurn(id: string): void {\n const message = this.messages.find((m) => m.id === id);\n if (!message) return;\n this.messages = this.messages.filter((m) => m !== message);\n this.emitEvent({ type: 'message_delivered', messageId: id, text: message.text, ...(message.from ? { from: message.from } : {}) });\n }\n /**\n * The turn ends without a `task_complete` call, and so does the turn the\n * completion reminder opens if one was sent (ADR-0018, W1): the task waits for input.\n */\n waitForInput(): void {\n const reminders = this.fromOrdewell.length;\n this.emitTurnEnd('completed');\n if (this.fromOrdewell.length > reminders) this.emitTurnEnd('completed');\n }\n emitTurnEnd(reason: StructuredTurnEnd): void {\n const next = this.messages.shift();\n if (!next) this.state = 'idle';\n for (const cb of this.turnEndCbs) cb(reason);\n if (next) this.deliver(next.text, next.from);\n }\n\n private deliver(text: string, from?: TaskMessageFrom): void {\n this.state = 'working';\n this.delivered.push(text);\n if (from === 'ordewell') this.fromOrdewell.push(text);\n this.emitEvent({ type: 'turn_start', text, ...(from ? { from } : {}) });\n }\n}\n\nexport type FakeIsolationCall =\n | { op: 'isActive'; workspaceRoot: string }\n | { op: 'stash'; workspaceRoot: string }\n | { op: 'startRun'; workspaceRoot: string }\n | { op: 'prepare' | 'reopen' | 'verifyRepair'; taskId: string }\n | { op: 'integrate'; taskId: string }\n | { op: 'release'; taskId: string; keep: boolean }\n | { op: 'handoff' | 'pruneOrphans' | 'reviewDiff' | 'mergeIntoCheckedOut' | 'sweep' | 'findInHead' | 'snapshotTree' | 'changedSince' }\n | { op: 'discard'; integration: IntegrationDisposal }\n | { op: 'readLanded' | 'revert'; taskIds: readonly string[] };\n\n/**\n * An in-memory {@link IWorktreeIsolation} for scheduling tests: no git, no\n * filesystem. Every call is logged in `calls`; `prepare` hands back a\n * deterministic fake cwd. Set `availability` to exercise the fallbacks, `outcomes`\n * to script a conflict, and `holdIntegration` to keep a task un-integrated so a\n * test can observe that its dependents wait. `repos` makes the run a group of\n * several; `changes` and `stopsIn` say which of them a task changes and where\n * its landing stops. `repairEvidence` scripts what a conflict repair's\n * evidence check finds, and `removal` what a removal kept back.\n */\nexport class FakeWorktreeIsolation implements IWorktreeIsolation {\n availability: IsolationAvailability = { active: true };\n /** The repo paths `startRun` groups: a group of one at `.` unless set. */\n repos: string[] = [SELF_REPO];\n /** Per task id, the repos it changes; every repo of the group when not listed. */\n changes = new Map<string, string[]>();\n /** Per task id, the repo its landing stops in when its outcome is not `merged`; its first changed repo when not listed. */\n stopsIn = new Map<string, string>();\n /** Per task id, the files a `conflict` outcome names; empty when not listed. */\n conflictFiles = new Map<string, string[]>();\n /** What `mergeIntoCheckedOut` answers; a `merged` one also puts every landed task in HEAD, as git would. */\n mergeResult: IsolationMergeResult = { outcome: 'merged' };\n /** Task ids whose landed work `findInHead` finds in HEAD besides those a merge put there, as a merge by hand would. */\n mergedByHand = new Set<string>();\n /** What `snapshotTree` answers; null as for a workspace with no repo. */\n treeSnapshot: TreeSnapshot | null = {};\n /** What `changedSince` answers: the tracked files an ops task changed. */\n changedFiles: string[] = [];\n /** What `startRun` shares and `prepare` copies, to exercise their notices. */\n shared: string[] = [];\n sharedRepos: string[] = [];\n copied: string[] = [];\n setupFailed: SetupFailure[] = [];\n /** Set to make `startRun` throw, as git does when no repo of the group can be isolated. */\n startRunError: Error | null = null;\n /** Set to make `discard` / `sweep` throw after logging the call. */\n discardError: Error | null = null;\n sweepError: Error | null = null;\n /** What `pruneOrphans` reports as kept back from the sweep. */\n keptOnPrune: IsolationPruneResult['kept'] = [];\n /**\n * What every removal — a `release` that removes, `discard`, `pruneOrphans` —\n * reports it kept back. A refused task keeps its record, as git's does.\n */\n removal: IsolationRemoval = noRemoval();\n /** Per task id; a task not listed integrates as `merged`. */\n outcomes = new Map<string, IsolationOutcome>();\n /** What `revert` stops with; a revert that goes through when null. */\n revertStop: Exclude<IsolationRevertResult, { outcome: 'reverted' }> | null = null;\n /** What `readLanded` finds of a task's work in the user's branch, by task id. */\n inBranch = new Map<string, RevertCommand[]>();\n /** Per task id, what `verifyRepair` finds; a task not listed passes. */\n repairEvidence = new Map<string, RepairEvidence>();\n calls: FakeIsolationCall[] = [];\n private holds = new Map<string, Promise<void>>();\n private verifyHolds = new Map<string, Promise<void>>();\n private revertHold: Promise<void> | null = null;\n private runCount = 0;\n\n private log(call: FakeIsolationCall): void { this.calls.push(call); }\n\n /** Task ids in the order `op` was called for them. */\n taskIdsFor(op: 'prepare' | 'reopen' | 'verifyRepair' | 'integrate' | 'release'): string[] {\n return this.calls.flatMap((c) => (c.op === op && 'taskId' in c ? [c.taskId] : []));\n }\n\n /** Make `integrate` for this task wait until the returned function is called. */\n holdIntegration(taskId: string): () => void {\n let open!: () => void;\n this.holds.set(taskId, new Promise<void>((resolve) => { open = resolve; }));\n return open;\n }\n\n /** Make the next `revert` wait, past its `refusal`, until the returned function is called. */\n holdRevert(): () => void {\n let open!: () => void;\n this.revertHold = new Promise<void>((resolve) => { open = resolve; });\n return open;\n }\n\n /** Make `verifyRepair` for this task wait until the returned function is called. */\n holdVerification(taskId: string): () => void {\n let open!: () => void;\n this.verifyHolds.set(taskId, new Promise<void>((resolve) => { open = resolve; }));\n return open;\n }\n\n async isActive(workspaceRoot: string): Promise<IsolationAvailability> {\n this.log({ op: 'isActive', workspaceRoot });\n return this.availability;\n }\n\n /** Like git: once the tracked changes are stashed, the tree is no longer dirty. */\n async stash(workspaceRoot: string): Promise<void> {\n this.log({ op: 'stash', workspaceRoot });\n if (!this.availability.active && this.availability.reason === 'dirty') this.availability = { active: true };\n }\n\n async startRun(workspaceRoot: string): Promise<IsolationRun> {\n this.log({ op: 'startRun', workspaceRoot });\n if (this.startRunError) throw this.startRunError;\n const id = `run${++this.runCount}`;\n return {\n id,\n workspaceRoot,\n repos: this.repos.map((repo) => ({\n path: repo, root: `${workspaceRoot}/${repo}`.replace(/\\/\\.$/, ''), baseRef: 'base0000', baseBranch: 'main', integrationBranch: integrationBranchFor(id),\n })),\n shared: [...this.shared],\n sharedRepos: [...this.sharedRepos],\n tasks: {},\n keepsEveryLanding: true,\n carried: {},\n };\n }\n\n async prepare(task: Task, run: IsolationRun, _intent?: PrepareIntent, opts: { readOnly?: boolean } = {}): Promise<PreparedTask> {\n this.log({ op: 'prepare', taskId: task.id });\n const name = `${task.order}-${task.id}`;\n const cwd = `/fake-worktrees/${run.id}/${name}`;\n const branch = `ordewell/${run.id}/${name}`;\n run.tasks[task.id] = {\n taskId: task.id, order: task.order, title: task.title, branch, workspace: cwd, status: 'active',\n repos: Object.fromEntries(run.repos.map((r) => [r.path, { worktree: r.path === SELF_REPO ? cwd : `${cwd}/${r.path}`, linked: [] }])),\n ...(opts.readOnly ? { readOnly: true } : {}),\n };\n return { cwd, branch, copied: [...this.copied], ...(this.setupFailed.length > 0 ? { setupFailed: [...this.setupFailed] } : {}) };\n }\n\n /** Like git: the same cwd, each changed repo's tip recorded, the repair counted. */\n async reopen(task: Task, run: IsolationRun): Promise<PreparedTask> {\n this.log({ op: 'reopen', taskId: task.id });\n const record = run.tasks[task.id];\n if (record?.status !== 'conflict') throw new Error(`Task ${task.order} has no conflict to repair`);\n const changed = Object.entries(record.repos).filter(([repo, entry]) => entry.changed || repo === record.conflictRepo).map(([repo]) => repo);\n const files = (record.conflictFiles ?? []).map((file) => (record.conflictRepo && record.conflictRepo !== SELF_REPO ? `${record.conflictRepo}/${file}` : file));\n record.repairs = (record.repairs ?? 0) + 1;\n record.repairBase = Object.fromEntries(changed.map((repo) => [repo, `tip-${repo}`]));\n record.repairedFiles = [...new Set([...(record.repairedFiles ?? []), ...files])];\n record.status = 'repairing';\n return { cwd: record.workspace, branch: record.branch, copied: [] };\n }\n\n async verifyRepair(task: Task, _run: IsolationRun): Promise<RepairEvidence> {\n this.log({ op: 'verifyRepair', taskId: task.id });\n await this.verifyHolds.get(task.id);\n return this.repairEvidence.get(task.id) ?? { ok: true };\n }\n\n /** Like git: the landing is recorded and persisted before the (held) merge, and cleared once it settles. */\n async integrate(task: Task, run: IsolationRun, persist: () => void = () => undefined, commit?: HandoffCommit): Promise<IsolationOutcome> {\n this.log({ op: 'integrate', taskId: task.id });\n const record = run.tasks[task.id];\n if (!record) return 'failed';\n if (commit) record.commit = commit;\n if (record.readOnly) {\n record.status = 'failed';\n record.landingError = 'a read-only task lands nothing';\n return 'failed';\n }\n const changed = this.changes.get(task.id) ?? run.repos.map((r) => r.path);\n for (const [repo, entry] of Object.entries(record.repos)) entry.changed = changed.includes(repo) || (entry.changed ?? false);\n if (changed.length > 0) {\n run.landing = { taskId: task.id, tips: Object.fromEntries(changed.map((repo) => [repo, `tip-${repo}`])) };\n persist();\n }\n await this.holds.get(task.id);\n delete run.landing;\n const outcome = this.outcomes.get(task.id) ?? 'merged';\n record.status = outcome;\n delete record.repairBase;\n if (outcome === 'merged') {\n delete record.conflictRepo;\n delete record.conflictFiles;\n for (const repo of changed) {\n ((run.landings ??= {})[task.id] ??= {})[repo] = [...run.landings?.[task.id]?.[repo] ?? [], `tip-${repo}`];\n (run.landedTasks ??= {})[task.id] = { taskId: task.id, order: task.order, title: task.title };\n }\n } else {\n record.conflictRepo = this.stopsIn.get(task.id) ?? changed[0] ?? SELF_REPO;\n const files = this.conflictFiles.get(task.id);\n if (files) record.conflictFiles = files;\n else delete record.conflictFiles;\n }\n return outcome;\n }\n\n /** Newest landing first, read as the plan's highest order: scheduling tests land in plan order. */\n async readLanded(run: IsolationRun, taskIds: readonly string[]): Promise<LandedWork> {\n this.log({ op: 'readLanded', taskIds });\n const records = taskIds.flatMap((id) => run.tasks[id] ?? run.landedTasks?.[id] ?? []).sort((a, b) => b.order - a.order);\n const tasks = records.map((r) => ({\n taskId: r.taskId, order: r.order, title: r.title,\n repos: Object.keys(run.landings?.[r.taskId] ?? {}),\n }));\n return { ok: true, tasks, inBranch: records.flatMap((r) => this.inBranch.get(r.taskId) ?? []) };\n }\n\n /** Like git: the turn, then the judgement, then the records go in one step with the tasks back. */\n async revert(run: IsolationRun, taskIds: readonly string[], watch: RevertWatch): Promise<IsolationRevertResult> {\n this.log({ op: 'revert', taskIds });\n const hold = this.revertHold;\n this.revertHold = null;\n await hold;\n const reason = watch.refusal();\n if (reason) return { outcome: 'refused', reason };\n if (this.revertStop) return this.revertStop;\n const landed = await this.readLanded(run, taskIds);\n if (!landed.ok) return { outcome: 'failed', error: landed.error };\n if (landed.inBranch.length > 0) return { outcome: 'in-branch', commands: landed.inBranch };\n run.landing = { taskId: taskIds[0], tips: {}, reverts: {} };\n watch.persist();\n delete run.landing;\n for (const id of taskIds) {\n // Like git: a later attempt's record that did not land stays for its worktree, its conflict over.\n if (run.tasks[id]?.status === 'merged') delete run.tasks[id];\n else if (run.tasks[id]?.status === 'conflict') run.tasks[id].status = 'kept';\n delete run.landings?.[id];\n delete run.landedTasks?.[id];\n delete run.landedOn?.[id];\n }\n run.reverted = true;\n watch.settled(taskIds);\n return { outcome: 'reverted', tasks: landed.tasks };\n }\n\n async release(run: IsolationRun, taskId: string, opts: { keep: boolean }): Promise<IsolationRemoval> {\n this.log({ op: 'release', taskId, keep: opts.keep });\n const record = run.tasks[taskId];\n // Like git: a read-only task's record goes whole, with nothing kept (ADR-0046).\n if (record?.readOnly) {\n delete run.tasks[taskId];\n return noRemoval();\n }\n const removal = opts.keep || !record ? noRemoval() : this.removed();\n const refused = removal.refused.some((r) => r.task?.taskId === taskId);\n if (!opts.keep && !refused) delete run.tasks[taskId];\n else if (record?.status === 'active') record.status = 'kept';\n else if (record?.status === 'repairing') {\n record.status = 'conflict';\n delete record.repairBase;\n }\n return removal;\n }\n\n private removed(): IsolationRemoval {\n return { preserved: [...this.removal.preserved], refused: [...this.removal.refused] };\n }\n\n async handoff(run: IsolationRun): Promise<IsolationHandoff> {\n this.log({ op: 'handoff' });\n return handoffOf(run);\n }\n\n async pruneOrphans(): Promise<IsolationPruneResult> {\n this.log({ op: 'pruneOrphans' });\n return { kept: [...this.keptOnPrune], ...this.removed() };\n }\n async reviewDiff(): Promise<string> { this.log({ op: 'reviewDiff' }); return ''; }\n async mergeIntoCheckedOut(run: IsolationRun): Promise<IsolationMergeResult> {\n this.log({ op: 'mergeIntoCheckedOut' });\n if (this.mergeResult.outcome === 'merged') {\n for (const record of Object.values(run.tasks)) if (record.status === 'merged') this.mergedByHand.add(record.taskId);\n }\n return this.mergeResult;\n }\n async findInHead(run: IsolationRun): Promise<string[]> {\n this.log({ op: 'findInHead' });\n const changed: string[] = [];\n for (const record of Object.values(run.tasks)) {\n if (record.status !== 'merged') continue;\n const merged = this.mergedByHand.has(record.taskId);\n if (merged === !!record.inHead) continue;\n if (merged) record.inHead = true;\n else delete record.inHead;\n changed.push(record.taskId);\n }\n return changed;\n }\n async snapshotTree(): Promise<TreeSnapshot | null> { this.log({ op: 'snapshotTree' }); return this.treeSnapshot; }\n async changedSince(): Promise<string[]> { this.log({ op: 'changedSince' }); return [...this.changedFiles]; }\n async discard(_run: IsolationRun, opts: { integration: IntegrationDisposal }): Promise<IsolationRemoval> {\n this.log({ op: 'discard', integration: opts.integration });\n if (this.discardError) throw this.discardError;\n return this.removed();\n }\n async sweep(): Promise<void> {\n this.log({ op: 'sweep' });\n if (this.sweepError) throw this.sweepError;\n }\n}\n","import { v4 as uuidv4 } from 'uuid';\nimport type { PlanIsolation } from '../interfaces/IWorktreeIsolation';\nimport type { AttemptUsage, PlannerUsage, ReportedWindow, UsageRecord, UsageTotals } from './Usage';\nimport type { ExecutionRecord, ScriptConsent, SupervisorLevel } from './Execution';\nimport type { SkillSource } from '../services/SkillsService';\nimport type { SupervisionMemorySnapshot } from '../services/LoopGuards';\nimport type { SupervisorSnapshot } from '../services/Supervisor';\nimport type { TaskLoop } from './Loop';\nimport type { HandoffDigest } from './Handoff';\nimport { SKILL_NAME_PATTERN } from '../conversation/skillTokens';\nimport { subtreeIds, walkPlan } from '../services/PlanTree';\nimport { migratePlanStateIsolation } from '../services/isolationRecord';\nimport { interruptedVerdict } from '../services/VerdictEngine';\n\nexport interface UserStep {\n order: number;\n instruction: string;\n completed: boolean;\n}\n\n/**\n * One deterministic signal gathered while verifying a completed task. `completion_marker` is only in verdicts saved before completion was reported through `task_complete` alone.\n * `script_exit` is a script task's exit status, its one evidence (ADR-0035).\n */\nexport interface VerificationCheck {\n name: 'exit_code' | 'completion_marker' | 'task_complete' | 'manual' | 'script_exit';\n passed: boolean;\n /** A check that did not apply. Skipped checks don't affect the verdict. */\n skipped: boolean;\n detail: string;\n}\n\n/** Evidence-based verdict for a completed task. Single end-to-end outcome produced by verification. */\nexport interface Verdict {\n outcome: 'pass' | 'fail';\n reason: string;\n checks: VerificationCheck[];\n decidedAt: string;\n /**\n * What the attempt's evidence said of the work: the status the runner's\n * `task_complete` call reported, or a script's exit read the same way —\n * `done` on exit 0, `failed` on a non-zero exit, a signal or its time limit,\n * `blocked` when it could not start. A loop starts a round only on `failed`\n * (ADR-0045 L2). Absent when nothing reported: a runner that exited without\n * the call, Mark complete, a stopped script, an attempt the host died in, a\n * verdict saved before it was kept.\n */\n reported?: 'done' | 'failed' | 'blocked';\n}\n\nexport interface TaskOutputSummary {\n reviewReason: string;\n /** The `task_complete` summary, the task's report; absent when the attempt ended without one, and from sessions saved before it was kept. */\n report?: string;\n /** Empty when there is a report: the turn's final text is the report itself. */\n logTail: string;\n capturedAt: string;\n}\n\n/** `script` is an ops task whose body Ordewell runs with no model (ADR-0035). */\nexport type TaskType = 'ai' | 'user' | 'script';\n/**\n * `skipped` is terminal and verdict-less: the task will not be waited for, and\n * nothing it did lands. Its dependents go ahead without it once the work it\n * waited on is done. There is no stored `blocked`: a task is blocked when a\n * failed dependency says so (ADR-0034), and a file that kept the old status\n * loads as pending.\n */\nexport type TaskStatus = 'pending' | 'approved' | 'in_progress' | 'completed' | 'failed' | 'awaiting_user' | 'skipped';\nexport type TaskMode = string;\n\n/**\n * Why an `awaiting_user` task waits (ADR-0018, W1): a structured turn that\n * ended without a `task_complete` call, a checkpoint question, work that did not\n * land, an ops task that changed tracked files (ADR-0020), or its runner's\n * usage limit. Saved, so no surface has to guess it from whether an attempt\n * is live.\n */\nexport type AwaitingReason = 'input' | 'checkpoint' | 'conflict' | 'files-changed' | 'usage-limit';\n\nexport function isAwaitingReason(value: unknown): value is AwaitingReason {\n return value === 'input' || value === 'checkpoint' || value === 'conflict' || value === 'files-changed' || value === 'usage-limit';\n}\n\nexport interface TaskModelAssignment {\n modelId: string;\n modelLabel: string;\n thinkingEffort?: string;\n /**\n * All variant ids the model offered when this assignment was made. Carried\n * on the assignment because runners need it at spawn time (opencode's TUI\n * only honors an assigned variant when the others are config-disabled) and\n * the discovery catalog isn't available there.\n */\n availableVariants?: string[];\n}\n\nexport type RunnerId = string;\n\n/**\n * Bring loaded tasks to the structured-only shape, in place. A task saved\n * while the terminal transport existed may say it ran there, and why; neither\n * means anything now, and such a task left no session to continue. Its\n * completion marker id went with the text markers: completion is reported\n * only through `task_complete`.\n */\nexport function migrateLoadedTasks(tasks: readonly Task[]): void {\n for (const task of tasks) {\n if (typeof task.runnerSessionId !== 'string') delete task.runnerSessionId;\n if (typeof task.carriedFrom !== 'string') delete task.carriedFrom;\n if (typeof task.runnerSessionOwner !== 'string') delete task.runnerSessionOwner;\n const legacy = task as Task & { transport?: { kind?: unknown; nativeSessionId?: unknown } };\n if (!task.runnerSessionId && legacy.transport?.kind === 'structured' && typeof legacy.transport.nativeSessionId === 'string') {\n task.runnerSessionId = legacy.transport.nativeSessionId;\n }\n delete legacy.transport;\n delete (task as { completionMarker?: unknown }).completionMarker;\n // The effort once had a second home on the task; the spawn reads only the model's.\n const effort = (task as { thinkingEffort?: unknown }).thinkingEffort;\n if (typeof effort === 'string' && task.assignedModel && !task.assignedModel.thinkingEffort) task.assignedModel.thinkingEffort = effort;\n delete (task as { thinkingEffort?: unknown }).thinkingEffort;\n migrateLoadedTasks(task.subtasks ?? []);\n }\n}\n\n/**\n * Nothing is running when a plan comes off disk, so a task saved in progress\n * lost its attempt with the host that ran it. It is failed, in place, so the\n * inbox lists it and what depends on it waits (ADR-0053). Its attempts and\n * logs are left as they are.\n */\nexport function failInterruptedTasks(tasks: readonly Task[], now: string): void {\n for (const task of flattenTasks(tasks)) {\n if (task.status !== 'in_progress') continue;\n task.status = 'failed';\n task.verdict = interruptedVerdict(now);\n }\n}\n\n/**\n * A task an edit made manual has nothing to retry, and is the user's to do: one\n * failed, or waiting on the user with no runner to hear an answer, waits as a\n * manual task does (ADR-0053). A runner still at a question keeps it, and the\n * answer is still its to read, until it ends or the plan comes off disk.\n */\nexport function releaseMadeManual(tasks: readonly Task[], live: (taskId: string) => boolean = () => false): void {\n for (const task of flattenTasks(tasks)) {\n if (task.type !== 'user' || live(task.id) || (task.status !== 'failed' && task.status !== 'awaiting_user')) continue;\n task.status = 'pending';\n delete task.awaitingReason;\n delete task.resumeAt;\n }\n}\n\n/**\n * Nothing claims a repair when a plan comes off disk: what owed it went with\n * the host that saved it. So a task saved not started whose landing conflicted\n * is a conflict waiting on the user, in place, and the inbox lists it; the\n * next Execute still repairs it (ADR-0015, ADR-0053).\n */\nexport function settleOwedRepairs(plan: LegacyPlanState): void {\n const records = plan.isolation?.run.tasks;\n if (!records) return;\n for (const task of flattenTasks(plan.tasks)) {\n if ((task.status !== 'pending' && task.status !== 'approved') || records[task.id]?.status !== 'conflict') continue;\n task.status = 'awaiting_user';\n task.awaitingReason = 'conflict';\n }\n}\n\n/**\n * Drop what a build before the per-message research budget was removed wrote\n * for it (ADR-0037): the budget, its two run settings, its per-turn flag, and\n * the report wait a task carried. The persisted types no longer name these,\n * so a reopened session would otherwise spread them onto the wire and write\n * them back; they are deleted by key, never widened into the types.\n */\nexport function dropRetiredPlanFields(plan: LegacyPlanState): void {\n const raw = plan as unknown as Record<string, unknown>;\n delete raw.researchBudget;\n const settings = plan.runSettings as unknown as Record<string, unknown> | undefined;\n if (settings) {\n delete settings.researchMaxParallel;\n delete settings.researchMaxPerMessage;\n }\n for (const turn of plan.pendingTurns ?? []) delete (turn as unknown as Record<string, unknown>).budgetStarted;\n for (const task of flattenTasks(plan.tasks)) delete (task as unknown as Record<string, unknown>).researchWait;\n}\n\n/**\n * Move a mid-run edit an older build queued for its next batch boundary into\n * the pending turns: it was never answered, so it waits like any other.\n */\nfunction carryOverQueuedEdits(plan: LegacyPlanState): void {\n const queued = plan.queuedMessages ?? [];\n delete plan.queuedMessages;\n if (queued.length === 0) return;\n const carried = queued.map(({ id, text, timestamp, skills }): PendingPlanTurn => ({ id, source: 'user', text, timestamp, ...(skills ? { skills } : {}) }));\n plan.pendingTurns = [...(plan.pendingTurns ?? []), ...carried];\n}\n\n/**\n * Bring a loaded plan to the shape this build reads, in place: every plan-shape\n * migration lives here, so each can be tested from an old shape without a whole\n * Session, as {@link migrateLoadedTasks} does for tasks.\n *\n * `adopting` is false when a Session reloads the plan it already holds: the\n * live run's task pauses are fresher than the save, so they stay.\n */\nexport function migrateLoadedPlan(plan: LegacyPlanState, { adopting = true }: { adopting?: boolean } = {}): void {\n // Saved before approval had its own record: the status said it, and\n // executing a plan approved every task it held. Read once, here, and never\n // again — a surface rewrites the status for display.\n plan.approved ??= plan.status !== 'draft' && plan.status !== 'rejected';\n if (!plan.approvedTasks && plan.approved) {\n plan.approvedTasks = flattenTasks(plan.tasks).map((t) => t.id);\n }\n // A saved reset time belonged to a run that is gone; loading is not consent\n // to spend tokens (ADR-0044).\n if (adopting) {\n for (const task of flattenTasks(plan.tasks)) delete task.resumeAt;\n }\n migratePlanStateIsolation(plan);\n // Older builds copied the since-removed transport setting onto the plan.\n // Every plan now runs structured (ADR-0018), so the copy is not carried on.\n delete (plan as { runnerTransport?: unknown }).runnerTransport;\n // Run settings are the user's now (ADR-0030); an older plan's own copies\n // are not read, and go with this save.\n for (const key of ['supervisorLevel', 'mergeAtGatesAutomatically', 'supervisorRetryLimit', 'wakeUpReadBudget']) {\n delete (plan as unknown as Record<string, unknown>)[key];\n }\n // The per-message research budget is gone (ADR-0037); a plan saved before\n // its removal is read once and written back without its keys.\n dropRetiredPlanFields(plan);\n carryOverQueuedEdits(plan);\n}\n\n/**\n * A task skill as one attempt received it: read from the task's own worktree\n * (or global) at spawn, kept so the user can see what the runner was told\n * even after the skill's file changes.\n */\nexport interface TaskSkillSnapshot {\n name: string;\n source: SkillSource;\n path: string;\n content: string;\n}\n\n/** A task skill as a surface is told of it: the body stays in the task's log file and the session. */\nexport type TaskSkillNotice = Omit<TaskSkillSnapshot, 'content'>;\n\nexport interface Task {\n id: string;\n order: number;\n title: string;\n description: string;\n type: TaskType;\n status: TaskStatus;\n dependencies: string[];\n prompt?: string;\n userSteps?: UserStep[];\n subtasks: Task[];\n verdict?: Verdict;\n outputSummary?: TaskOutputSummary;\n /**\n * The latest attempt's handoff, as the plan keeps it (ADR-0047): its\n * summary, first findings and artifacts. The whole handoff, report and all,\n * is in that attempt's log. Absent when the attempt handed none over, and\n * on a plan saved before handoffs, which reads `outputSummary` instead.\n */\n handoff?: HandoffDigest;\n assignedModel?: TaskModelAssignment;\n assignedRunner: RunnerId;\n taskMode?: TaskMode;\n autonomy?: 'AFK' | 'HITL';\n sliceType?: 'HITL' | 'AFK';\n userStoriesCovered?: string[];\n /** The runner's saved session for a later continue; cleared when a fresh attempt starts. */\n runnerSessionId?: string;\n /**\n * The runner that session belongs to: a session cannot cross runners, so\n * Continue is offered only while the task is still assigned to it. Absent\n * on a plan saved before it was recorded; the last attempt's runner stands\n * in (see `sessionOwner`).\n */\n runnerSessionOwner?: RunnerId;\n /** Set only while `status` is `awaiting_user`, and not always then — a pause saved by an older build has none. */\n awaitingReason?: AwaitingReason;\n /**\n * How many waits have ended since its latest attempt started, or since the\n * task was written if none has: an attempt can wait many times, and this\n * tells one wait from the next in an escalation's id (ADR-0053). Saved with\n * the plan, so the id holds across a reload. Absent counts as none.\n */\n waitsEnded?: number;\n /**\n * When a task paused on its runner's usage limit is resumed on its own (ISO\n * time): the reset the runner announced, plus a short margin. Absent when no\n * reset time could be read or the user cancelled the resume — the task then\n * waits for a manual retry. Set only with `awaitingReason` `usage-limit`.\n */\n resumeAt?: string;\n /**\n * The runner a task paused on its usage limit was moved off (ADR-0018, U1):\n * its next attempt is a carry-over, a fresh session that starts from that\n * attempt's files. Cleared once that attempt runs, and by a retry.\n */\n carriedFrom?: RunnerId;\n /** Why the task was skipped, as whoever skipped it put it; set only while `status` is `skipped`, and not always then. */\n skipReason?: string;\n /**\n * The user reverted its landed work (ADR-0054): saved with the plan, so the\n * hold that keeps it from running again outlives this process. Set only\n * while it waits as the revert left it: its next start clears it.\n */\n reverted?: true;\n /**\n * An ops task (ADR-0020): it changes no repository files, runs at the\n * workspace root rather than in a worktree, and waits for the change tasks\n * it depends on to be merged into the user's branch. Top-level AI tasks\n * only; absent means a change task. A script task is one by nature and\n * stores none (see {@link runsAsOps}).\n */\n ops?: boolean;\n /** A top-level read-only AI task whose report returns to the planner (ADR-0037 R1). */\n research?: boolean;\n /** A retry still owes research's report under its original authority (ADR-0037 R3). */\n researchOrigin?: TaskReportTo;\n /**\n * The dependencies, by title, whose work was not merged into the user's\n * branch when the user force-started this task past its merge gate\n * (ADR-0020). Cleared when the task is retried.\n */\n forcedPastGate?: string[];\n /**\n * Task skills (`applies-to: task`) attached by name. Their bodies are put in\n * the runner's prompt at spawn, resolved where the task runs — a subtask's\n * are its own, never its parent's. Absent means none.\n */\n skills?: string[];\n /** The skills the latest attempt was given, as read when it spawned. */\n attemptSkills?: TaskSkillSnapshot[];\n /**\n * Who put the task in the plan, when it was not the user's own planning:\n * `supervisor` for a task an Act supervisor added during a run, which\n * counts as approved to run (ADR-0029). Set by the session, never by an op.\n */\n addedBy?: 'supervisor';\n /**\n * An attempt of it has started, so its chat exists from then on: kept after\n * a failure, a cancel, a skip or a completion, and restored with the plan.\n * Set by the attempt's own start — never by a status or a manual mark.\n */\n attempted?: true;\n attemptUsage?: AttemptUsage[];\n /**\n * A script task's body (ADR-0035), exactly one of these two: argument\n * vectors run in order with no shell, stopping at the first that fails, or\n * one script run by the research shell's interpreter.\n */\n commands?: string[][];\n script?: string;\n /** A script task's time limit; absent means ten minutes (`DEFAULT_SCRIPT_TIMEOUT_MS`). */\n timeoutMs?: number;\n /** Set on a loop's check (ADR-0045): the tasks that fix what it finds, and how many times it may run. */\n loop?: TaskLoop;\n /**\n * The round of a loop's check's latest run, or the one it is running;\n * absent is round 1. Execution state, saved so a reload resumes mid-round,\n * and started over by a retry of the check (ADR-0045 L5, L7).\n */\n loopRound?: number;\n /**\n * A planner turn waits for this task's result (ADR-0037, R2): set when the\n * tool that started it answered before it ended, or when research lands.\n * Cleared once its end is reported, in a turn acting for whoever started it (R3).\n */\n reportTo?: TaskReportTo;\n}\n\n/**\n * Who started a task whose result is owed to the planner (ADR-0037, R3): a\n * planner turn in the user's conversation, or the supervisor, at its level\n * then, with script consent (ADR-0035) or the research profile (ADR-0037 R4).\n */\nexport type TaskReportTo = ({ consent: ScriptConsent; research?: never } | { research: true; consent?: never }) & (\n | { actor: 'planner-turn' }\n | { actor: 'supervisor'; level: SupervisorLevel }\n | { actor: 'subplanner'; depth: number }\n);\n\n/** Whose authority a turn opened without the user acts with (ADR-0037, R3): the user's turn, or the supervisor's. */\nexport type TurnAuthority = 'user' | 'supervisor';\n\nexport interface DiscoveredMode {\n id: string;\n label: string;\n description: string;\n}\n\nexport type ResearchToolType =\n | 'read_file' | 'read_files' | 'glob' | 'grep' | 'find_symbol' | 'list_dir'\n | 'bash' | 'fetch' | 'web_search' | 'spawn_research_agent'\n | 'task_query' | 'task_output' | 'task_diff'\n /**\n * A tool belonging to a harness planner's own toolbox (ADR-0009) that has no\n * Ordewell equivalent — Edit, WebFetch, TodoWrite, whatever a coding agent\n * ships next. The real name travels in `toolLabel` rather than being\n * relabelled as a tool it is not; the union stays closed so the\n * exhaustiveness checks in every surface's icon/label switch survive.\n */\n | 'agent_tool';\n\n/**\n * What happened when a research tool call ran, for honest per-surface\n * rendering. The broadcast seam carries this on every `research_step_done` so\n * surfaces do not have to pattern-match refusal text to tell a refused `rm`\n * from a successful `rm` — the old render path flipped a `✓` for both.\n */\nexport type ResearchStepOutcome = 'success' | 'failure' | 'refused' | 'denied' | 'not_executed';\n\nexport interface ResearchStep {\n id: string;\n tool: ResearchToolType;\n /** The tool's own name when it came from a harness planner — always set for `agent_tool`. */\n toolLabel?: string;\n args: string;\n result: string;\n success: boolean;\n outcome: ResearchStepOutcome;\n /** The model's tool_call id, so a surface can match `tool_result` to the\n * pending `tool_call` it announced — robust under parallel same-tool rounds. */\n toolCallId?: string;\n /** The research subagent that ran the call, so a reload regroups it under that subagent. */\n subagentId?: string;\n timestamp: string;\n thinkingText?: string;\n}\n\nexport interface UserPromptEntry {\n id: string;\n type: 'user_prompt' | 'system';\n content: string;\n timestamp: string;\n}\n\nexport type SubagentOutcome = 'done' | 'failed' | 'stopped';\n\n/**\n * One subagent's whole run, logged when it finishes: what it was asked, how it\n * ended and what it reported. Its steps stay separate entries carrying the same\n * `subagentId`, so an older reader that knows only steps still shows them.\n */\nexport interface SubagentLogEntry {\n id: string;\n type: 'subagent';\n subagentId: string;\n brief: string;\n model?: string;\n outcome: SubagentOutcome;\n digest: string;\n usage?: UsageTotals;\n timestamp: string;\n}\n\nexport type ResearchLogEntry = ResearchStep | UserPromptEntry | SubagentLogEntry;\n\nexport interface ResearchProgress {\n // 'liveness' carries no content: a raw-line signal from the harness process\n // (ADR-0009) that reaches a surface even when a turn is producing nothing\n // visible — a subagent's filtered chatter, most often — so an idle watchdog\n // downstream doesn't mistake \"nothing to show\" for \"nothing happening\".\n //\n // The turn-scoped kinds below carry one planner turn's stream (#47); see the\n // matching SessionMessage variants for the invariants each one keeps.\n // 'text_delta' is reply text as the model streams it (`text`, `segmentId`),\n // an envelope included: the turn's owner routes each segment to prose or to\n // the plan display, so no backend has to tell them apart. 'text_retracted'\n // takes back an attempt's text: `segmentId`'s, or when absent everything the\n // backend call that sends it has streamed.\n // 'usage' reports one model call (`record`). 'subagent_started' carries\n // `brief` and `model`; 'subagent_finished' carries `outcome`, `digest` and\n // `usage`. 'compaction' is one phase of the planner's own compaction\n // (`compaction`); a subagent's never is one.\n type:\n | 'thinking' | 'tool_call' | 'tool_result' | 'plan_token' | 'interrupted' | 'liveness'\n | 'text_delta' | 'text_retracted' | 'usage' | 'subagent_started' | 'subagent_finished' | 'compaction';\n /** Minted by whoever runs the turn and passed through untouched; absent outside a turn. */\n turnId?: string;\n /** One continuous run of model text — text before a tool call is its own segment. */\n segmentId?: string;\n text?: string;\n tool?: string;\n /** Harness planners (ADR-0009): the agent's own name for a tool Ordewell has no member for. */\n toolLabel?: string;\n toolArgs?: string;\n toolResult?: string;\n planToken?: string;\n step?: ResearchStep;\n /** The model's tool_call id, threaded on tool_call and tool_result so a\n * surface can match the result to its pending call — robust under parallel\n * same-tool rounds where LIFO-by-name matching mislabels summaries. */\n toolCallId?: string;\n /** Present when this event originates from (or reports on) one spawned research subagent (issue #34). */\n subagentId?: string;\n record?: UsageRecord;\n brief?: string;\n model?: string;\n outcome?: SubagentOutcome;\n digest?: string;\n usage?: UsageTotals;\n compaction?: PlannerCompaction;\n}\n\nexport interface ThinkingBlock {\n id: string;\n text: string;\n}\n\nexport interface StreamThinkingEvent {\n type: 'thinking';\n block: ThinkingBlock;\n}\n\nexport interface StreamStepEvent {\n type: 'step';\n step: ResearchStep;\n}\n\nexport type StreamEvent = StreamThinkingEvent | StreamStepEvent;\n\nexport interface DiscoveredModel {\n modelId: string;\n modelLabel: string;\n runnerProvider?: string;\n /**\n * Human-facing provider name as the runner itself reports it (e.g.\n * \"OpenCode Zen\" for `runnerProvider: 'opencode'`). Populated from the\n * runner's own provider catalog when available; when absent the UI derives a\n * label from `runnerProvider` by title-casing.\n */\n runnerProviderLabel?: string;\n /**\n * The runner whose catalog listed this model. Stamped once, at the single\n * `ModelDiscovery.discover` choke point, so a flat cross-runner list can\n * still say where each entry came from — `runnerProvider` alone cannot:\n * OpenCode reports most of its catalog as `openrouter`, which names the\n * serving backend, not the agent Ordewell would spawn.\n */\n runnerId?: string;\n /** The runner's display name (`OpenCode`), from its manifest. */\n runnerLabel?: string;\n variants: { id: string; label: string }[];\n /**\n * The model's context window when the runner or catalog reports it (#49).\n * Read by the planner-model lookup so context fill can be shown; absent when\n * unknown rather than defaulted to zero. Gemini's is its `inputTokenLimit`:\n * the prompt alone, the reply limited by `maxOutputTokens` apart from it.\n */\n contextWindow?: number;\n /** The longest reply the catalog says the model is served with, when it says. */\n maxOutputTokens?: number;\n}\n\nexport type PlanStatus = 'draft' | 'approved' | 'rejected' | 'running' | 'completed';\n\n/**\n * One entry of the planner's persisted dialogue (ADR-0002). The single source\n * of truth for both UI redisplay and conversational context. Tool-call results\n * are NOT stored here — they live in the AI service's tool-use history;\n * `researchLog` remains the persisted tool trace for the UI.\n */\nexport interface ConversationMessage {\n role: 'user' | 'assistant';\n content: string;\n timestamp: string;\n /**\n * Timeline marker: 'plan_generated' records the point in the dialogue where\n * the plan was committed (the UI anchors the plan card there on restore);\n * 'system' is a host-injected notice; 'compaction' is the summary a\n * compaction appended, which stands in for the earlier messages in the\n * planner's context while the transcript keeps them (see `checkpoint`). 'skill_load' is a skill loaded into the\n * conversation, its snapshot in `skill`; one the user invoked follows the\n * message that named it. 'execution' is a call at the command seam\n * (ADR-0029), its record in `execution`. 'wake_up' is the digest a wake-up\n * gave the supervisor (role 'user': the model was told it), and\n * 'supervisor' what the supervisor said in a wake-up turn (ADR-0029).\n * 'error' is a planner turn that ended on the planner's usage limit, drawn\n * as a failure and never replayed to the planner: it is not the planner's\n * answer, and its inputs are sent again (see {@link PlannerResendState}).\n * Absent for ordinary chat turns, so sessions saved before markers existed\n * degrade gracefully.\n */\n kind?: 'plan_generated' | 'system' | 'compaction' | 'skill_load' | 'execution' | 'wake_up' | 'supervisor' | 'error';\n actor?: 'Ordewell setting';\n /** Present exactly when `kind` is 'skill_load'. */\n skill?: SkillLoad;\n /** Present exactly when `kind` is 'execution'. */\n execution?: ExecutionRecord;\n /** A user's message that was force sent, stopping the turn before it (ADR-0023). */\n forced?: true;\n /**\n * Set on a 'compaction' entry that is a checkpoint of the planner's context\n * (ADR-0002). A 'compaction' entry without one is a legacy summary when it\n * stands first — it replaced what it condensed — and a marker drawn but\n * never replayed anywhere else.\n */\n checkpoint?: ContextCheckpoint;\n /**\n * What a reply showed, run by run, where each began (ADR-0017 A1). Beside\n * `content`, which stays the reply as the planner is told it. Absent on\n * entries saved before runs were kept, which reload as one message.\n */\n runs?: ReplyRun[];\n}\n\n/**\n * Where a compaction cut the planner's context (ADR-0002): its summary stands\n * for every transcript entry before `keptFrom`, and the entries from there on\n * replay verbatim. Entries are only appended, or inserted at a held turn's\n * position, which no checkpoint's cut passes, so the index never moves.\n */\nexport interface ContextCheckpoint {\n /** Stable across saves, forks and the wire, so a surface can tell this checkpoint from another with the same text. */\n id: string;\n /** An index into the whole transcript, at or before the checkpoint entry itself. */\n keptFrom: number;\n trigger: 'manual' | 'auto';\n}\n\n/**\n * How a planner compaction ended. `condensed`: a summary stands in for the\n * older context (a checkpoint was written, or a harness planner reported its\n * own compaction done). `pruned`: older recoverable tool output was dropped\n * from the live context alone. `unchanged`: nothing needed doing. `paused`:\n * the context could not be brought to fit, so the planner waits.\n */\nexport type PlannerCompactionOutcome = 'condensed' | 'pruned' | 'unchanged' | 'stopped' | 'failed' | 'paused';\n\n/**\n * One planner compaction's lifecycle as it was observed (ADR-0017): both\n * phases share `id`, which is the checkpoint's id when one is written. Only\n * Ordewell's own compaction announces a start; a harness planner's native one\n * is reported when it is over, and nothing invents the start it never sent.\n * Sizes are only ever what a backend reported.\n */\nexport type PlannerCompaction =\n | { phase: 'started'; id: string; origin: 'ordewell'; trigger: ContextCheckpoint['trigger']; at: string }\n | {\n phase: 'ended';\n id: string;\n origin: 'ordewell' | 'native';\n trigger: ContextCheckpoint['trigger'];\n outcome: PlannerCompactionOutcome;\n at: string;\n preTokens?: number;\n postTokens?: number;\n };\n\n/**\n * One run of a planner reply's text: the segment it streamed under, its\n * settled text, and when it began. A run that never streamed (a message the\n * conversation wrote in the planner's place) has a segment no stream used.\n */\nexport interface ReplyRun {\n segmentId: string;\n text: string;\n /** ISO timestamp. */\n startedAt: string;\n}\n\n/**\n * A skill's SKILL.md as it was when loaded into a planner conversation. A\n * snapshot rather than a reference: a resumed, forked or rewound conversation\n * replays the body the planner originally saw, even after the file changed or\n * was deleted.\n */\nexport interface SkillLoad {\n invokedBy: 'user' | 'planner';\n name: string;\n source: SkillSource;\n /** The SKILL.md that won, home-abbreviated (`~/...`): which copy loaded is what a reader needs from it. */\n path: string;\n content: string;\n /**\n * Set when the user named a task skill: it is not loaded into the planner's\n * context, which is only told to attach it to the tasks it fits. `content`\n * is empty; the description is what the planner's directive quotes.\n */\n attaches?: { description: string };\n}\n\n/** A skill load as a surface announces it — the body stays with the transcript. */\nexport type SkillLoadNotice = Omit<SkillLoad, 'content'>;\n\n/** A message the user sent, as opposed to a skill load their message caused or a wake-up digest. */\nexport function isUserMessage(entry: Pick<ConversationMessage, 'role' | 'kind'>): boolean {\n return entry.role === 'user' && entry.kind !== 'skill_load' && entry.kind !== 'wake_up';\n}\n\n/** A follow-up an older build queued behind a run's batch boundary; read only to carry it over as a {@link PendingPlanTurn}. */\nexport interface QueuedMessage {\n id: string;\n text: string;\n timestamp: string;\n skills?: SkillLoad[];\n}\n\n/** Who a planner turn answers (ADR-0029): the user's message, or a wake-up batch the supervisor is given. */\nexport type PlanTurnSource = 'user' | 'supervisor';\n\n/**\n * A planner turn's input not yet answered by a turn that ended. It leaves the\n * plan with the change its turn made, in the same save, so a reload replays\n * exactly the inputs no turn answered — once. A supervisor's input is a\n * wake-up batch not yet delivered (ADR-0029): it stays until a turn lands\n * with it, whatever turns fail meanwhile.\n */\nexport interface PendingPlanTurn {\n id: string;\n source: PlanTurnSource;\n /** The user's message, or a wake-up batch's digest. */\n text: string;\n timestamp: string;\n /** What its `/name` tokens loaded when it was sent, so a replay sends the same skills. */\n skills?: SkillLoad[];\n /** Force sent: it waits ahead of every input not forced, and its turn is its own (ADR-0023, F2). */\n forced?: true;\n /**\n * A supervisor input carrying a report the planner asked for (ADR-0037, R2),\n * which opens a turn at every supervisor level: `user` for a task started in\n * the user's turn, which the turn then acts as (R3).\n */\n report?: TurnAuthority;\n /**\n * The tasks a supervisor input is wholly about, absent when any of it is about\n * the run. While the user has stopped all of them it opens no turn (ADR-0029).\n */\n tasks?: string[];\n}\n\n/**\n * The run's own copy of the user's run settings (ADR-0030): taken when the\n * user starts it, changed at once by a change made in this session, and\n * lowered by a tripped guard or a failed supervisor.\n */\nexport interface RunSettings {\n mergeAtGatesAutomatically: boolean;\n /** Absent on a run started before supervisor levels existed: Off. */\n supervisorLevel?: SupervisorLevel;\n /** Absent on a run started before ADR-0030: the user's setting. */\n supervisorRetryLimit?: number;\n /** Absent on a run started before ADR-0030: the user's setting. */\n wakeUpReadBudget?: number;\n /** Why the supervisor stopped for the rest of this run, which carries on as Off; absent while it watches. */\n unsupervised?: string;\n}\n\n/**\n * What supervision remembers across a daemon restart (ADR-0029): the loop\n * guards' memory and the wake-up state not yet delivered with a turn.\n */\nexport interface SupervisionSnapshot {\n /** Retries, failed-attempt shapes, the supervisor's edits and what the user was told. */\n guards?: SupervisionMemorySnapshot;\n /** The events inside their debounce window and the wake-up rate's timestamps. */\n wake?: SupervisorSnapshot;\n}\n\n/**\n * What waits on the planner's usage limit. With `at`, the inputs no turn\n * answered are sent again then, on their own; without it — the reset could\n * not be read, the resend hit the limit again, or it was cancelled — they go\n * with the next input: the user's message, or a wake-up.\n */\nexport interface PlannerResendState {\n /** The runner whose account hit the limit; a planner on another one is not held by it. */\n runner?: RunnerId;\n at?: string;\n}\n\n/**\n * The planner paused on its context (ADR-0002): an API planner's call could\n * not be brought inside its model's window, so the turn ended with nothing\n * landed and the planner waits for the user's Retry compaction. What it\n * keeps is bounded: the inputs stay in `pendingTurns`, and only the calls\n * that changed something are listed, never the live context itself.\n */\nexport interface PlannerContextPause {\n /** The model the call did not fit. */\n model: string;\n at: string;\n /** What the user is told: why it paused, and what Retry compaction does. */\n reason: string;\n /** The pending inputs the paused turn took: a retry answers these, and what arrived since waits for the turn after it. */\n inputs: string[];\n /**\n * The calls the paused turn made that changed something or reached out,\n * each with the head of what it returned (`effectLedger`). It is never\n * replayed to the planner as a note: it is what lets a retry see that a\n * turn which ran effects cannot start again from the transcript, and what\n * the refusal lists for the user.\n */\n done?: string[];\n /** More such calls ran than `done` keeps: the refusal could not list all of them, and a retry without the live context stays refused. */\n doneTruncated?: true;\n}\n\nexport interface LegacyPlanState {\n /** Research profiles the planner approved as they landed: lifting one withdraws its approval (ADR-0037 R4). */\n researchApprovals?: string[];\n runSettings?: RunSettings;\n /** What supervision remembers across a daemon restart (ADR-0029); absent on plans saved before it existed. */\n supervision?: SupervisionSnapshot;\n tasks: Task[];\n generatedAt: string;\n status: PlanStatus;\n runners: RunnerId[];\n lastUpdated: string;\n researchLog?: ResearchLogEntry[];\n /** The planner dialogue — user messages and assistant messages, in order (ADR-0002). */\n conversationHistory?: ConversationMessage[];\n /** Full markdown PRD once written by the planner (PRD mode), also saved to .scratch/<slug>/PRD.md. */\n prdMarkdown?: string;\n /** Planner turn inputs no turn has answered yet (ADR-0029); a reload replays them. */\n pendingTurns?: PendingPlanTurn[];\n /** Written by builds that queued mid-run edits; a load moves it into {@link pendingTurns}. */\n queuedMessages?: QueuedMessage[];\n /**\n * The plan's isolation run (ADR-0013), written from the orchestrator at\n * persist time and read back only when a saved plan is adopted. It names\n * branches and worktrees that belong to this plan alone: a fork of the plan\n * must leave it behind rather than share it.\n */\n isolation?: PlanIsolation;\n /**\n * The id of the plan's last isolated run, which closed with everything it\n * landed merged into the user's branch; absent once a later run decides how\n * it executes. With no run record left, it is what tells a Merge all asked\n * for after that run from one in a plan that never isolated (ADR-0029).\n */\n mergedRun?: string;\n /** Kept so a reopened session shows the same token line (#49). */\n plannerUsage?: PlannerUsage;\n /** The windows reported over the session, so a reopened one knows each model's from its first call. */\n contextWindows?: ReportedWindow[];\n /** The planner hit its usage limit and the inputs its turn took wait to be sent again; absent otherwise. */\n plannerResend?: PlannerResendState;\n /** The planner's context could not fit its model's window: its turn's inputs wait for Retry compaction; absent otherwise. */\n plannerContextPause?: PlannerContextPause;\n /**\n * The tasks the user approved to run: every task when they executed the\n * plan, and each one they started or retried themselves since. A command\n * from anyone else starts only these (ADR-0029); a task the planner added\n * after the approval waits for the user's. Only tasks still in the plan: a\n * task that leaves takes its approval with it.\n */\n approvedTasks?: string[];\n /**\n * The user approved the plan to run: Execute plan, signing off a review, or\n * autorun's start (ADR-0029, ADR-0030). Kept apart from {@link status},\n * which surfaces rewrite for display; a user's start of one task approves\n * that task, not the plan.\n */\n approved?: boolean;\n}\n\nexport interface Message {\n id: string;\n role: 'user' | 'planner' | 'system';\n content: string;\n timestamp: number;\n}\n\nexport interface TaskSnapshot extends Task {\n completedAt: number;\n verdict?: Verdict;\n retryCount: number;\n finalized: boolean;\n}\n\n/** The plan a surface reads whole carries the approval record, so it can tell which tasks wait for the user (ADR-0035). */\nexport type PlanState = (\n | {\n phase: 'planning';\n history: Message[];\n message: string;\n pendingTasks: Task[];\n }\n | {\n phase: 'executing';\n history: Message[];\n message: string;\n executionLog: TaskSnapshot[];\n pendingTasks: Task[];\n goal: string;\n runners: string[];\n status: PlanStatus;\n }\n) & ApprovalRecord;\n\nfunction hasPhase(raw: unknown): raw is PlanState {\n return typeof raw === 'object' && raw !== null && 'phase' in raw;\n}\n\nfunction hasTasks(raw: unknown): raw is LegacyPlanState {\n return typeof raw === 'object' && raw !== null && 'tasks' in raw && Array.isArray((raw as Record<string, unknown>).tasks);\n}\n\nfunction logEntryToMessage(entry: ResearchLogEntry): Message {\n const timestamp = new Date(entry.timestamp).getTime();\n if (!('type' in entry)) {\n return { id: entry.id, role: 'system', content: JSON.stringify({ tool: entry.tool, args: entry.args, result: entry.result }), timestamp };\n }\n if (entry.type === 'subagent') {\n const { subagentId, brief, outcome, digest } = entry;\n return { id: entry.id, role: 'system', content: JSON.stringify({ subagentId, brief, outcome, digest }), timestamp };\n }\n return { id: entry.id, role: entry.type === 'user_prompt' ? 'user' : 'system', content: entry.content, timestamp };\n}\n\nfunction migrateHistory(raw: LegacyPlanState): Message[] {\n const messages: Message[] = [];\n\n if (raw.researchLog) {\n messages.push(...raw.researchLog.map(logEntryToMessage));\n }\n\n if (raw.queuedMessages) {\n for (const qm of raw.queuedMessages) {\n messages.push({\n id: qm.id,\n role: 'system',\n content: qm.text,\n timestamp: new Date(qm.timestamp).getTime(),\n });\n }\n }\n\n return messages;\n}\n\nexport function migratePlanState(raw: unknown): PlanState {\n if (hasPhase(raw)) return raw;\n\n if (!hasTasks(raw)) {\n return {\n phase: 'planning',\n history: [],\n message: '',\n pendingTasks: [],\n };\n }\n\n const hasExecution = raw.tasks.some(\n (t) => t.status === 'completed' || t.status === 'failed',\n );\n\n const messages = migrateHistory(raw);\n const record = approvalRecordOf(raw);\n\n if (hasExecution) {\n const executionLog: TaskSnapshot[] = raw.tasks\n .filter((t) => t.status === 'completed' || t.status === 'failed')\n .map((t) => ({\n ...t,\n completedAt: new Date(raw.lastUpdated).getTime(),\n verdict: t.verdict,\n retryCount: 0,\n finalized: true,\n }));\n\n const pendingTasks = raw.tasks.filter(\n (t) => t.status !== 'completed' && t.status !== 'failed',\n );\n\n return {\n phase: 'executing',\n history: messages,\n message: '',\n executionLog,\n pendingTasks,\n goal: '',\n runners: raw.runners,\n status: raw.status,\n ...record,\n };\n }\n\n return {\n phase: 'planning',\n history: messages,\n message: '',\n pendingTasks: raw.tasks,\n ...record,\n };\n}\n\nexport { taskOrderLabel, taskRef, taskRefOf, titledTaskRef, titledTaskRefOf, resolveTaskRef, resolveUserTaskRef } from '../services/TaskRefs';\n\nexport function createTask(overrides: Partial<Task> = {}): Task {\n const skills = skillNames(overrides.skills);\n const type = overrides.type ?? 'ai';\n return {\n id: overrides.id ?? uuidv4(),\n order: overrides.order ?? 0,\n title: overrides.title ?? '',\n description: overrides.description ?? '',\n type,\n status: overrides.status ?? 'pending',\n dependencies: overrides.dependencies ?? [],\n prompt: overrides.prompt,\n userSteps: overrides.userSteps,\n subtasks: overrides.subtasks ?? [],\n verdict: overrides.verdict,\n outputSummary: overrides.outputSummary,\n ...(overrides.handoff ? { handoff: structuredClone(overrides.handoff) } : {}),\n ...(overrides.attempted ? { attempted: true as const } : {}),\n ...(overrides.attemptUsage ? { attemptUsage: structuredClone(overrides.attemptUsage) } : {}),\n assignedModel: overrides.assignedModel,\n assignedRunner: overrides.assignedRunner ?? DEFAULT_RUNNERS[0],\n // A script task runs in no mode (ADR-0035): one given it would read as a mode it runs in.\n taskMode: overrides.taskMode ?? (type === 'script' ? undefined : 'build'),\n autonomy: overrides.autonomy,\n sliceType: overrides.sliceType,\n userStoriesCovered: overrides.userStoriesCovered,\n ...(opsFlag(overrides.ops, overrides.type) ? { ops: true } : {}),\n ...(skills ? { skills } : {}),\n ...(overrides.addedBy === 'supervisor' ? { addedBy: 'supervisor' as const } : {}),\n ...(overrides.commands !== undefined ? { commands: overrides.commands.map((argv) => [...argv]) } : {}),\n ...(overrides.script !== undefined ? { script: overrides.script } : {}),\n ...(overrides.timeoutMs !== undefined ? { timeoutMs: overrides.timeoutMs } : {}),\n ...(overrides.loop ? { loop: { body: [...overrides.loop.body], maxRounds: overrides.loop.maxRounds } } : {}),\n ...(overrides.loopRound !== undefined ? { loopRound: overrides.loopRound } : {}),\n ...(overrides.research !== undefined ? { research: overrides.research } : {}),\n };\n}\n\n/**\n * A name a skill can have, lower-cased as `/name` is. Anything else — a path\n * separator, `..` — is joined into a path and could only reach outside the\n * skill dirs.\n */\nconst SKILL_NAME = new RegExp(`^${SKILL_NAME_PATTERN}$`);\n\nexport function isSkillName(name: string): boolean {\n return SKILL_NAME.test(name);\n}\n\n/**\n * The `skills` field as a task stores it, however it arrived: the planner's\n * JSON is untyped. Names are trimmed, lower-cased and deduplicated, and one\n * no skill could have is dropped; none at all is stored absent, so a plan\n * without skills reads exactly as it did before them.\n */\nexport function skillNames(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const names = [...new Set(value.filter((v): v is string => typeof v === 'string').map((v) => v.trim().toLowerCase()).filter(isSkillName))];\n return names.length > 0 ? names : undefined;\n}\n\n/**\n * The skills of a task a merge or a split makes from others: what the edit\n * chose when it named any, none when it chose an explicit `[]`, else what the\n * tasks it came from had — a malformed value is not a request to drop them.\n */\nexport function inheritedSkills(chosen: unknown, inherited: readonly string[] | undefined): string[] | undefined {\n if (Array.isArray(chosen) && chosen.length === 0) return undefined;\n return skillNames(chosen) ?? skillNames(inherited);\n}\n\n/**\n * The `ops` flag as a task stores it (ADR-0020), however it arrived. Only a\n * literal `true` on an AI task marks an ops task: the planner's JSON is\n * untyped, and a quoted \"true\" read as ops would move a task out of its\n * worktree. Anything else is a change task, stored absent so saved plans need\n * no migration. Whether the task may be ops at all — a subtask may not — is\n * for whoever knows where it sits: the planner intake refuses it (`checkPlanInvariants`).\n */\nexport function opsFlag(value: unknown, type: Task['type'] = 'ai'): true | undefined {\n return value === true && type === 'ai' ? true : undefined;\n}\n\n/**\n * Whether a task runs as an ops task (ADR-0020): an AI task marked so, or a\n * script task, which is one by nature and never marked (ADR-0035).\n */\nexport function runsAsOps(task: Pick<Task, 'ops' | 'type'>): boolean {\n return task.type === 'script' || opsFlag(task.ops, task.type) === true;\n}\n\n/**\n * The flag of a task a merge or a split makes from others: what the edit\n * chose, else ops only when every task it came from was. A merge that takes\n * in a change edits files, so it is a change; a split part runs where its\n * task did.\n */\nexport function inheritedOps(from: readonly Pick<Task, 'ops'>[], chosen?: boolean): boolean {\n return chosen ?? from.every((t) => opsFlag(t.ops) === true);\n}\n\n/**\n * The runner set a plan has until one is chosen. The one runner named outside\n * its declaration on purpose: a default is a product choice that no manifest\n * or connector implies.\n */\nexport const DEFAULT_RUNNERS: readonly RunnerId[] = ['claude-code'];\n\nexport function createEmptyPlan(): LegacyPlanState {\n return {\n tasks: [],\n generatedAt: new Date().toISOString(),\n status: 'draft',\n runners: [...DEFAULT_RUNNERS],\n lastUpdated: new Date().toISOString(),\n };\n}\n\nexport function flattenTasks(tasks: readonly Task[]): Task[] {\n return [...walkPlan(tasks)].map(({ task }) => task);\n}\n\n/**\n * A phase-tagged plan rejoined into its tree. The execution log is flat — a\n * finished subtask is logged on its own as well as inside its parent — and in\n * the order tasks finished, so read as a list it puts subtasks at the top level\n * under orders that clash with real top-level tasks. A task's own entry is the\n * latest word on it: its parent's copy was taken when the parent last changed.\n */\nexport function rejoinPlanTree<T extends { id: string; order: number; subtasks?: T[] }>(pending: readonly T[], log: readonly T[]): T[] {\n const latest = new Map<string, T>();\n for (const task of [...pending, ...log]) latest.set(task.id, task);\n const nested = new Set<string>();\n const markNested = (task: T): void => {\n for (const sub of task.subtasks ?? []) {\n nested.add(sub.id);\n markNested(sub);\n }\n };\n for (const task of latest.values()) markNested(task);\n const rebuild = (task: T): T => {\n const own = latest.get(task.id) ?? task;\n return own.subtasks?.length ? { ...own, subtasks: own.subtasks.map(rebuild) } : own;\n };\n return [...latest.values()].filter((task) => !nested.has(task.id)).map(rebuild).sort((a, b) => a.order - b.order);\n}\n\n/**\n * A flattened task with the parent it hangs under, null for a top-level task,\n * and the path label and reference (`#2.1.3`) `TaskRefs` renders for it.\n */\nexport interface TaskWithParent {\n task: Task;\n parent: Task | null;\n label: string;\n ref: string;\n}\n\nexport function flattenTasksWithParents(tasks: readonly Task[]): TaskWithParent[] {\n return [...walkPlan(tasks)].map(({ task, parent, label }) => ({ task, parent: parent ?? null, label, ref: `#${label}` }));\n}\n\nexport function addTaskToPlan(tasks: readonly Task[], partial: Partial<Task>): Task[] {\n const maxOrder = tasks.reduce((max, t) => Math.max(max, t.order), 0);\n const newTask = createTask({\n ...partial,\n order: partial.order ?? maxOrder + 1,\n status: 'pending',\n });\n return renumberTasks([...tasks, newTask]);\n}\n\n/**\n * A removed task leaves every dependency on it, and every loop's body it was\n * in (ADR-0045 L8): a loop it leaves with no body is no loop. Whether its\n * removal may go ahead at all — a loop's check may not be removed — is the\n * caller's to say.\n */\nexport function removeTaskFromPlan(tasks: readonly Task[], taskId: string): Task[] {\n const removed = subtreeIds(tasks, taskId);\n const prune = (level: readonly Task[]): Task[] => level\n .filter((t) => !removed.has(t.id))\n .map((t) => {\n const pruned: Task = {\n ...t,\n dependencies: t.dependencies.filter((depId) => !removed.has(depId)),\n subtasks: prune(t.subtasks),\n };\n if (!t.loop?.body.some((id) => removed.has(id))) return pruned;\n const body = t.loop.body.filter((id) => !removed.has(id));\n if (body.length > 0) return { ...pruned, loop: { ...t.loop, body } };\n delete pruned.loop;\n delete pruned.loopRound;\n return pruned;\n });\n return renumberTasks(prune(tasks));\n}\n\nexport function updateTaskInPlan(tasks: readonly Task[], taskId: string, changes: Partial<Task>): Task[] {\n return renumberTasks(\n tasks.map((t) => {\n if (t.id === taskId) return { ...t, ...changes, id: t.id };\n return { ...t, subtasks: updateTaskInPlan(t.subtasks, taskId, changes) };\n })\n );\n}\n\nexport function renumberTasks(tasks: readonly Task[]): Task[] {\n return tasks.map((t, i) => ({\n ...t,\n order: i + 1,\n subtasks: renumberTasks(t.subtasks),\n }));\n}\n\n/** A task waiting on the user with its attempt still live: the runner sits at a checkpoint or for input. */\nexport function waitsMidAttempt(task: Pick<Task, 'status' | 'awaitingReason'>): boolean {\n return task.status === 'awaiting_user' && (task.awaitingReason === 'checkpoint' || task.awaitingReason === 'input');\n}\n\n/** A task paused on its runner's usage limit, whether it resumes on its own or waits for the user (#84). */\nexport function pausedOnLimit(task: Readonly<Task>): boolean {\n return task.status === 'awaiting_user' && task.awaitingReason === 'usage-limit';\n}\n\n/** A task paused on a usage limit that resumes on its own when the limit resets (#84). */\nexport function waitsForReset(task: Readonly<Task>): boolean {\n return pausedOnLimit(task) && !!task.resumeAt;\n}\n\n/** The user's approval record, as a plan carries it (ADR-0029). */\nexport type ApprovalRecord = Pick<LegacyPlanState, 'approved' | 'approvedTasks'>;\n\n/** A copy of the plan's approval record, for a plan read whole; a field the plan lacks stays absent. */\nexport function approvalRecordOf(plan: ApprovalRecord): ApprovalRecord {\n return {\n ...(plan.approved !== undefined ? { approved: plan.approved } : {}),\n ...(plan.approvedTasks ? { approvedTasks: [...plan.approvedTasks] } : {}),\n };\n}\n\n/**\n * A task the scheduler leaves for the user (ADR-0035): an AI or script task\n * not started yet, in a plan they approved, outside their approval record —\n * the planner added it since, or changed a script's body. It starts once they\n * start it or execute the plan. In a plan they never approved nothing is\n * theirs yet, so nothing is singled out.\n */\nexport function waitsForApproval(task: Pick<Task, 'id' | 'type' | 'status'>, record: ApprovalRecord): boolean {\n return record.approved === true\n && task.type !== 'user'\n && (task.status === 'pending' || task.status === 'approved')\n && !(record.approvedTasks ?? []).includes(task.id);\n}\n\n/**\n * Who put each task of an adopted list in the plan. A task the plan already\n * had keeps its origin, whatever the planner wrote; a new one is the\n * supervisor's when `bySupervisor`, and nobody's in particular otherwise, so\n * no planner can mark its own task as the supervisor's. `added` lists the new\n * tasks, subtasks included.\n */\nexport function stampOrigin(before: readonly Task[], after: readonly Task[], bySupervisor: boolean): { tasks: Task[]; added: string[] } {\n const prior = new Map(flattenTasks([...before]).map((t) => [t.id, t]));\n const added: string[] = [];\n const stamp = (tasks: readonly Task[]): Task[] => tasks.map((t) => {\n const was = prior.get(t.id);\n const origin = was ? was.addedBy : bySupervisor ? 'supervisor' : undefined;\n if (!was) added.push(t.id);\n const stamped: Task = { ...t, subtasks: stamp(t.subtasks ?? []) };\n if (origin) stamped.addedBy = origin;\n else delete stamped.addedBy;\n return stamped;\n });\n return { tasks: stamp(after), added };\n}\n\n/** Done, running, or held for the user: execution state only a runner or the user can change. */\nconst SETTLED_STATUSES: ReadonlySet<TaskStatus> = new Set<TaskStatus>(['completed', 'in_progress', 'awaiting_user']);\n\nfunction holdsSettledWork(task: Task): boolean {\n return SETTLED_STATUSES.has(task.status) || (task.subtasks ?? []).some(holdsSettledWork);\n}\n\n/**\n * Lay a planner-written task list over the plan it rewrites without letting it\n * change execution state. A planner restates tasks; it never witnessed one run,\n * so the status it writes is not evidence. A settled task is kept exactly as\n * it stands wherever the rewrite names it, and put back beside its old\n * neighbour where the rewrite leaves it out. Every other task keeps the status\n * it had; a task the rewrite adds starts pending. A failed task the rewrite\n * turns into a script task or out of one is kept as it was: what ran, and\n * where, is fixed once it has run (ADR-0020, ADR-0035).\n */\nexport function keepExecutionState(current: readonly Task[], rewrite: Task[]): Task[] {\n const existing = new Map(flattenTasks(current).map((t) => [t.id, t]));\n const named = new Set(flattenTasks(rewrite).map((t) => t.id));\n\n const overlay = (tasks: Task[], was: readonly Task[]): Task[] => {\n const result = tasks.map((t): Task => {\n const prior = existing.get(t.id);\n if (prior && SETTLED_STATUSES.has(prior.status)) return { ...prior };\n if (prior?.status === 'failed' && (prior.type === 'script') !== (t.type === 'script')) return { ...prior };\n return {\n ...t,\n status: prior?.status ?? 'pending',\n skipReason: prior?.skipReason,\n reverted: prior?.reverted,\n verdict: prior?.verdict,\n outputSummary: prior?.outputSummary,\n handoff: prior?.handoff,\n runnerSessionId: prior?.runnerSessionId,\n runnerSessionOwner: prior?.runnerSessionOwner,\n carriedFrom: prior?.carriedFrom,\n attemptSkills: prior?.attemptSkills,\n attempted: prior?.attempted,\n attemptUsage: prior?.attemptUsage,\n loopRound: prior?.loopRound,\n waitsEnded: prior?.waitsEnded,\n // Where a task that has run ran is fixed, like its runner session (ADR-0020).\n ...(prior?.status === 'failed' ? { ops: prior.ops } : {}),\n forcedPastGate: prior?.forcedPastGate,\n reportTo: prior?.reportTo,\n researchOrigin: prior?.researchOrigin,\n subtasks: overlay(t.subtasks ?? [], prior?.subtasks ?? []),\n };\n });\n was.forEach((left, i) => {\n if (named.has(left.id) || !holdsSettledWork(left)) return;\n const neighbour = was.slice(0, i).reverse().find((t) => result.some((r) => r.id === t.id));\n const at = neighbour ? result.findIndex((r) => r.id === neighbour.id) + 1 : 0;\n result.splice(at, 0, { ...left });\n });\n return result;\n };\n\n return renumberTasks(overlay(rewrite, current));\n}\n","/** A name a skill can have, lower-cased: the one pattern for a `/name` token and for a skill folder. */\nexport const SKILL_NAME_PATTERN = '[a-z0-9][a-z0-9_-]*';\n\n/**\n * A `/skill-name` token anywhere in a message: whitespace (or string start)\n * before it, a skill name in any case, optional trailing punctuation that\n * isn't part of the name, then whitespace (or string end). The punctuation\n * group is what lets \"/grilling,\" name \"grilling\".\n */\nconst SKILL_TOKEN = new RegExp(`(^|\\\\s)\\\\/(${SKILL_NAME_PATTERN})([,.!?;:]*)(?=\\\\s|$)`, 'gi');\n\n/** Where a skill name is invoked: `start` is its `/`, `end` is just past the name, before any punctuation. */\nexport interface SkillToken {\n name: string;\n start: number;\n end: number;\n}\n\n/**\n * The first `/name` token for each name in `text`, names lower-cased. Only\n * the first counts because a skill loads once per message however often it is\n * named, so a later repeat is plain text.\n */\nexport function skillTokens(text: string): SkillToken[] {\n const tokens: SkillToken[] = [];\n const seen = new Set<string>();\n for (const match of text.matchAll(SKILL_TOKEN)) {\n const name = match[2].toLowerCase();\n if (seen.has(name)) continue;\n seen.add(name);\n const start = match.index + match[1].length;\n tokens.push({ name, start, end: start + 1 + match[2].length });\n }\n return tokens;\n}\n\n/** The tokens in a sent message that loaded one of `loaded` — what a surface highlights in the user's bubble. */\nexport function loadedSkillTokens(text: string, loaded: readonly string[]): SkillToken[] {\n return skillTokens(text).filter((t) => loaded.includes(t.name));\n}\n","/**\n * The path of the repo in a workspace that is itself the repository. Kept in\n * its own module, with no imports, so a browser surface can word a merge\n * result without pulling in the isolation record's `path` dependency.\n */\nexport const SELF_REPO = '.';\n","import * as path from 'path';\nimport type {\n IsolationHandoff,\n IsolationLandedTask,\n IsolationRemoval,\n IsolationRepo,\n IsolationRun,\n IsolationTaskRecord,\n IsolationTaskStatus,\n IsolationView,\n PlanIsolation,\n PreservedWork,\n RepoGroupLayout,\n TaskIsolation,\n} from '../interfaces/IWorktreeIsolation';\nimport { SELF_REPO } from './repoPath';\n\nexport { SELF_REPO };\n\nexport function integrationBranchFor(runId: string): string {\n return `ordewell/${runId}/integration`;\n}\n\nexport function repoRootOf(workspaceRoot: string, repoPath: string): string {\n return path.join(workspaceRoot, repoPath);\n}\n\n/** A removal that kept nothing back. */\nexport function noRemoval(): IsolationRemoval {\n return { preserved: [], refused: [] };\n}\n\n/**\n * A `prepare` that failed after it had already replaced the last attempt of\n * the task: that attempt's unlanded work was kept on `preserved` first, and no\n * record names it any more, so whoever tells the user about the failure has to\n * tell them where it went. Reads as the failure it wraps.\n */\nexport class PrepareFailedError extends Error {\n constructor(readonly failure: unknown, readonly preserved: PreservedWork[]) {\n super(failure instanceof Error ? failure.message : String(failure));\n this.name = failure instanceof Error ? failure.name : 'Error';\n this.cause = failure;\n }\n}\n\n/** Add what `from` kept back to `into`. */\nexport function absorbRemoval(into: IsolationRemoval, from: IsolationRemoval): IsolationRemoval {\n into.preserved.push(...from.preserved);\n into.refused.push(...from.refused);\n return into;\n}\n\n/** The group a run isolates, as the planner is told it. */\nexport function layoutOf(run: IsolationRun): RepoGroupLayout {\n return { repos: run.repos.map((r) => r.path), shared: [...run.shared] };\n}\n\nconst ISOLATION_STATE: Record<IsolationTaskStatus, Exclude<TaskIsolation['state'], 'none'>> = {\n active: 'active',\n merged: 'integrated',\n conflict: 'conflict',\n repairing: 'repairing',\n kept: 'kept',\n failed: 'kept',\n};\n\n/** Whether a saved record's status is one this build knows; a newer daemon may have written one it does not. */\nconst isKnownStatus = (status: string): status is IsolationTaskStatus =>\n Object.prototype.hasOwnProperty.call(ISOLATION_STATE, status);\n\n/** Paths of the repos a task brought commits to. */\nexport function changedReposOf(record: IsolationTaskRecord): string[] {\n return Object.entries(record.repos).filter(([, r]) => r.changed).map(([repoPath]) => repoPath);\n}\n\n/** Whether a landed task has work in a repo: a later attempt's record says nothing of the merges before it. */\nexport function landedInRepo(run: IsolationRun, record: IsolationTaskRecord, repoPath: string): boolean {\n return !!record.repos[repoPath]?.changed || run.landings?.[record.taskId]?.[repoPath] !== undefined;\n}\n\n/**\n * Landed work a revert can undo (ADR-0054): a merge of its own in every repo it\n * changed, none of it in the user's branch yet, and nothing of it carried in by\n * a resolver. `landings` are the task's merges in {@link IsolationRun.landings},\n * which a later attempt's record says nothing of: a task at work again keeps its\n * mark off, and a revert would refuse it. A run with no `carried` record, or\n * none given, offers nothing: every revert of such a run is refused.\n */\nfunction revertible(record: IsolationTaskRecord, run?: Pick<IsolationRun, 'landings' | 'carried'>): boolean {\n if (record.inHead || record.status === 'active' || record.status === 'repairing' || !run?.carried || run.carried[record.taskId]) return false;\n const landings = run?.landings?.[record.taskId];\n const changed = record.status === 'merged' ? changedReposOf(record) : [];\n return (changed.length > 0 || landings !== undefined) && changed.every((repo) => record.repos[repo].landedTip || landings?.[repo]);\n}\n\n/**\n * `repairLimit` is `conflictRepairAttempts` as it stands now, which is what the\n * next repair will be held to. Without one — a saved plan never carries server\n * config — the repair attempt is left out and a live status update fills it in.\n */\nexport function taskIsolationOf(record: IsolationTaskRecord, repairLimit?: number, run?: Pick<IsolationRun, 'landings' | 'carried'>): TaskIsolation {\n return {\n state: ISOLATION_STATE[record.status],\n branch: record.branch,\n worktree: record.workspace,\n repos: changedReposOf(record),\n ...(record.conflictRepo ? { conflictRepo: record.conflictRepo } : {}),\n ...(record.conflictFiles?.length ? { conflictFiles: record.conflictFiles } : {}),\n ...(record.repairs && repairLimit !== undefined ? { repair: { attempt: record.repairs, limit: repairLimit } } : {}),\n ...(record.repairedFiles?.length ? { repairedFiles: record.repairedFiles } : {}),\n ...(revertible(record, run) ? { revertible: true as const } : {}),\n };\n}\n\n/**\n * Every task's mark in a run. A task with merges on the branch and no record\n * left, a retry having dropped it, has no state to show and can still be\n * reverted. A status this build does not know is its own mark-less task, not a\n * crash: a newer daemon may have written one.\n */\nexport function taskIsolationsOf(run: IsolationRun, repairLimit?: number): Record<string, TaskIsolation> {\n const tasks: Record<string, TaskIsolation> = {};\n for (const taskId of Object.keys(run.landings ?? {})) {\n if (run.carried && !run.carried[taskId]) tasks[taskId] = { state: 'none', revertible: true };\n }\n for (const [taskId, record] of Object.entries(run.tasks)) {\n if (isKnownStatus(record.status)) tasks[taskId] = taskIsolationOf(record, repairLimit, run);\n }\n return tasks;\n}\n\n/**\n * What a run hands over: each repo's integration branch and base, and what\n * landed, in plan order. Work the user has merged already, at a merge gate\n * (ADR-0020), is not handed over again.\n */\nexport function handoffOf(run: IsolationRun): IsolationHandoff {\n const merged = Object.values(run.tasks)\n .filter((r) => r.status === 'merged' && !r.inHead)\n .sort((a, b) => a.order - b.order);\n const entry = (r: IsolationTaskRecord): IsolationLandedTask => ({\n taskId: r.taskId, order: r.order, title: r.title,\n ...(r.repairedFiles?.length ? { repairedFiles: r.repairedFiles } : {}),\n });\n return {\n repos: run.repos.map((repo) => ({\n path: repo.path,\n integrationBranch: repo.integrationBranch,\n baseRef: repo.baseRef,\n ...(repo.baseBranch ? { baseBranch: repo.baseBranch } : {}),\n ...(repo.startedDetached ? { startedDetached: true as const } : {}),\n landed: merged.filter((r) => landedInRepo(run, r, repo.path)).map(entry),\n })),\n landed: merged.map(entry),\n };\n}\n\n/** A task record as ADR-0013 persisted it, for one repository. */\nexport interface Adr0013TaskRecord {\n taskId: string;\n order: number;\n title: string;\n branch: string;\n worktree: string;\n status: IsolationTaskStatus;\n linked: string[];\n}\n\n/** A run as ADR-0013 persisted it (0.4.23): one repository, its refs on the run itself. */\nexport interface Adr0013IsolationRun {\n id: string;\n workspaceRoot: string;\n baseRef: string;\n baseBranch?: string;\n integrationBranch: string;\n tasks: Record<string, Adr0013TaskRecord>;\n}\n\nexport interface Adr0013PlanIsolation {\n run: Adr0013IsolationRun;\n resolvers: Record<string, string>;\n}\n\nfunction isAdr0013(run: IsolationRun | Adr0013IsolationRun): run is Adr0013IsolationRun {\n return 'integrationBranch' in run && typeof run.integrationBranch === 'string';\n}\n\nfunction fromAdr0013Task(r: Adr0013TaskRecord): IsolationTaskRecord {\n return {\n taskId: r.taskId,\n order: r.order,\n title: r.title,\n branch: r.branch,\n workspace: r.worktree,\n status: r.status,\n // A merged task's branch was merged into the one repo there was; what it\n // changed was never recorded, and landing is what `changed` stands for.\n repos: { [SELF_REPO]: { worktree: r.worktree, linked: r.linked ?? [], ...(r.status === 'merged' ? { changed: true } : {}) } },\n ...(r.status === 'conflict' ? { conflictRepo: SELF_REPO } : {}),\n };\n}\n\n/**\n * A persisted plan isolation in today's shape. A run saved in the ADR-0013\n * format — recognised by its own `integrationBranch` — becomes a group of one\n * at `.`, keeping its branches, so a session saved by 0.4.23 resumes and hands\n * off exactly as it would have.\n */\nexport function migratePlanIsolation(state: PlanIsolation | Adr0013PlanIsolation): PlanIsolation {\n const { run, resolvers } = state;\n if (!isAdr0013(run)) return { run, resolvers: resolvers ?? {} };\n const repo: IsolationRepo = {\n path: SELF_REPO,\n root: repoRootOf(run.workspaceRoot, SELF_REPO),\n baseRef: run.baseRef,\n ...(run.baseBranch ? { baseBranch: run.baseBranch } : {}),\n integrationBranch: run.integrationBranch,\n };\n const tasks = Object.fromEntries(Object.entries(run.tasks ?? {}).map(([id, r]) => [id, fromAdr0013Task(r)]));\n return { run: { id: run.id, workspaceRoot: run.workspaceRoot, repos: [repo], shared: [], sharedRepos: [], tasks }, resolvers: resolvers ?? {} };\n}\n\n/** Bring a loaded plan's isolation record, if any, to today's shape in place. */\nexport function migratePlanStateIsolation(plan: { isolation?: PlanIsolation | Adr0013PlanIsolation }): void {\n if (plan.isolation?.run) plan.isolation = migratePlanIsolation(plan.isolation);\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isArray = (value: unknown): value is readonly unknown[] => Array.isArray(value);\n\n/**\n * Whether a payload carries a run this module can read. The check tells today's\n * record from an ADR-0013 one and stops at the fields both shapes need; the\n * record's own fields are trusted from there, as `stateStore` trusts the JSON it\n * loaded. An older daemon still sends the ADR-0013 shape, so the migration runs\n * on the way in rather than being assumed.\n */\nfunction carriesIsolation(plan: unknown): plan is { isolation: PlanIsolation | Adr0013PlanIsolation } {\n if (!isRecord(plan) || !isRecord(plan.isolation)) return false;\n const { run } = plan.isolation;\n if (!isRecord(run) || !isRecord(run.tasks)) return false;\n if (isArray(run.repos)) {\n return run.repos.some((repo) => isRecord(repo)\n && typeof repo.path === 'string'\n && typeof repo.integrationBranch === 'string'\n && typeof repo.baseRef === 'string');\n }\n return typeof run.integrationBranch === 'string' && typeof run.baseRef === 'string';\n}\n\n/**\n * What a persisted plan says about its isolated run, as a surface shows it: the\n * handoff and each task's mark. `null` when the payload carries no record — a\n * planner reply, or a plan that never isolated — which says nothing either way.\n *\n * This is the one reading of a saved record outside the daemon: the migration\n * from the ADR-0013 shape happens here, so no surface grows its own parser.\n * `repairLimit` is server config a saved plan never carries; without it the\n * repair attempt is left for a live status update to fill in.\n */\nexport function isolationViewOfPlan(plan: unknown, repairLimit?: number): IsolationView | null {\n if (!carriesIsolation(plan)) return null;\n const { run } = migratePlanIsolation(plan.isolation);\n return { tasks: taskIsolationsOf(run, repairLimit), handoff: handoffOf(run) };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts","../src/models/Task.ts","../src/conversation/skillTokens.ts","../src/services/repoPath.ts","../src/services/isolationRecord.ts","../src/services/executionLease.ts","../src/models/TreeEvent.ts"],"sourcesContent":["import type { IConfig } from './interfaces/IConfig';\nimport type { IFileSystem, ToolOutcome } from './interfaces/IFileSystem';\nimport type { InvokedCommand, IRunnerSession, QueuedTaskMessage, ScriptExit, StructuredEvent, StructuredTurnEnd, TaskMessageFrom } from './interfaces/IRunner';\nimport type { RunnerCommand } from './models/RunnerCommand';\nimport type { RunnerSessionCost } from './models/Usage';\nimport type { HandoffCommit } from './models/Handoff';\nimport type { ApprovalDecision } from './interfaces/IApproval';\nimport type { CheckpointAnswer, TaskCompleteArgs } from './services/mcp/tools';\nimport type {\n IsolationAvailability,\n IsolationHandoff,\n IsolationMergeResult,\n IsolationOutcome,\n IsolationPruneResult,\n IsolationRemoval,\n IsolationRevertResult,\n IsolationRun,\n IntegrationDisposal,\n LandedWork,\n PreparedTask,\n PrepareIntent,\n IWorktreeIsolation,\n RepairEvidence,\n RevertCommand,\n RevertWatch,\n SetupFailure,\n TreeSnapshot,\n} from './interfaces/IWorktreeIsolation';\nimport { DEFAULT_RUNNERS, type Task } from './models/Task';\nimport { GUARANTEES, refuseUnproven, type ExecutionBackend, type Guarantee, type GuaranteeReport, type LeaseRecord, type LeaseRepo, type LeaseRequest, type LeaseSpec, type OpenLease, type ReservedLease, type RunnerPlacement } from './services/executionLease';\nimport type { EventStore } from './services/eventStore';\nimport { isWithin, type NodePath, type TreeEvent, type TreeFact } from './models/TreeEvent';\nimport { handoffOf, integrationBranchFor, noRemoval, SELF_REPO } from './services/isolationRecord';\n\n/**\n * Let an already-queued promise chain run to its next await before asserting,\n * without a wall-clock sleep. For a chain whose length is not known, drain the\n * microtask queue repeatedly. Only helps a chain that does not wait on a timer.\n */\nexport async function flushMicrotasks(rounds = 20): Promise<void> {\n for (let i = 0; i < rounds; i += 1) await Promise.resolve();\n}\n\nexport function fakeConfig(overrides: Partial<IConfig> = {}): IConfig {\n return {\n aiProvider: 'openrouter',\n apiKey: 'sk-test',\n planningModel: 'test-model',\n enabledRunners: [...DEFAULT_RUNNERS],\n maxParallelSessions: 3,\n researchEnabled: false,\n researchMaxSteps: 10,\n researchMaxFileSize: 100_000,\n openAiBaseUrl: 'https://api.openai.com/v1',\n openAiApiKey: '',\n openrouterKey: '',\n geminiKey: '',\n openaiCompatibleBaseUrl: '',\n openaiCompatibleApiKey: '',\n orchestratorModel: '',\n researchSubagentModel: '',\n geminiModel: '',\n planMapEnabled: true,\n autonomousMode: true,\n // Off so an orchestrator built without an injected isolation never runs\n // real git against whatever repository the tests happen to run in.\n worktreeIsolation: false,\n workspaceRepos: [],\n worktreeLinks: [],\n // Off so a scripted conflict stays a conflict unless a test opts into repairing it.\n conflictRepairAttempts: 0,\n approvalMode: 'ask',\n approvalPreApproved: [],\n // Off so a test that is not about the allowlist sees every prompt it raises.\n approvalDefaults: false,\n effectAllowlist: [],\n notifyCommand: [],\n webReadBudget: 40,\n plannerNativeShell: false,\n setProviderModelLists: () => {},\n getProviderBaseUrl: () => '',\n // Every vendor holds a key, so no planner turn is refused for want of one\n // unless a test takes it away.\n getProviderApiKey: () => 'sk-test',\n ...overrides,\n };\n}\n\nconst EMPTY_OUTCOME: ToolOutcome = { success: false, output: '', truncated: false };\n\n/**\n * A complete {@link IFileSystem} stub. Lives here rather than in each package's\n * test folder so adding a tool to the interface is one edit, not one per suite.\n */\nexport function fakeFileSystem(overrides: Partial<IFileSystem> = {}): IFileSystem {\n return {\n readFile: async () => EMPTY_OUTCOME,\n readFiles: async () => EMPTY_OUTCOME,\n glob: async () => EMPTY_OUTCOME,\n grep: async () => EMPTY_OUTCOME,\n findSymbol: async () => EMPTY_OUTCOME,\n listDir: async () => EMPTY_OUTCOME,\n bash: async () => EMPTY_OUTCOME,\n getWorkspaceRoot: () => '/workspace',\n ...overrides,\n };\n}\n\nexport class FakeRunnerSession implements IRunnerSession {\n private outputCbs: Array<(text: string) => void> = [];\n private exitCbs: Array<(code: number) => void> = [];\n output = '';\n written: string[] = [];\n killed = false;\n\n state: 'working' | 'idle' = 'working';\n messages: QueuedTaskMessage[] = [];\n /** Every message a turn was started with, in order. */\n delivered: string[] = [];\n /** The ones of {@link delivered} Ordewell sent: the completion reminder (ADR-0018, W1). */\n fromOrdewell: string[] = [];\n interrupts = 0;\n private messageCount = 0;\n private turnEndCbs: Array<(reason: StructuredTurnEnd) => void> = [];\n private eventCbs: Array<(event: StructuredEvent) => void> = [];\n private completeCbs: Array<(report: TaskCompleteArgs) => void> = [];\n private checkpointHandler: ((question: string, signal: AbortSignal) => Promise<CheckpointAnswer>) | null = null;\n\n constructor(public id = 's1', public taskId = 't1', public sessionId: string | null = 'native-1') {}\n\n /** `stop()` was asked for: the runner winds its turn down, still reporting, until {@link emitExit}. */\n stopped = false;\n private markGone!: () => void;\n private readonly gone = new Promise<void>((resolve) => { this.markGone = resolve; });\n\n onOutput(cb: (text: string) => void): void { this.outputCbs.push(cb); }\n onExit(cb: (code: number) => void): void { this.exitCbs.push(cb); }\n kill(): void { this.killed = true; }\n stop(): Promise<void> {\n this.stopped = true;\n return this.gone;\n }\n getOutput(): string { return this.output; }\n\n emitOutput(text: string): void {\n this.output += text;\n for (const cb of this.outputCbs) cb(text);\n }\n emitExit(code: number): void {\n for (const cb of this.exitCbs) cb(code);\n this.markGone();\n }\n write(text: string): void {\n this.written.push(text);\n const message = text.trim();\n if (message) this.sendMessage(message);\n }\n\n turnState(): 'working' | 'idle' { return this.state; }\n onTurnEnd(cb: (reason: StructuredTurnEnd) => void): void { this.turnEndCbs.push(cb); }\n onEvent(cb: (event: StructuredEvent) => void): void { this.eventCbs.push(cb); }\n onTaskComplete(cb: (report: TaskCompleteArgs) => void): void { this.completeCbs.push(cb); }\n /**\n * The runner calls `task_complete`, as `StructuredSession` relays it. Here it\n * is published at once; a test that needs the real session's turn-end\n * ordering drives `StructuredSession` itself.\n */\n reportComplete(report: TaskCompleteArgs): void {\n for (const cb of this.completeCbs) cb(report);\n }\n onToolCheckpoint(handler: (question: string, signal: AbortSignal) => Promise<CheckpointAnswer>): void { this.checkpointHandler = handler; }\n private scriptExitCbs: Array<(exit: ScriptExit) => void> = [];\n onScriptExit(cb: (exit: ScriptExit) => void): void { this.scriptExitCbs.push(cb); }\n /** A script task's body ended, as `ScriptSession` hands it on (ADR-0035). */\n reportScriptExit(exit: ScriptExit): void {\n for (const cb of this.scriptExitCbs) cb(exit);\n }\n /** The runner calls `checkpoint`, as `StructuredSession` relays it; settles with the answer. */\n callCheckpoint(question: string, signal: AbortSignal = new AbortController().signal): Promise<CheckpointAnswer> {\n if (!this.checkpointHandler) throw new Error('no checkpoint handler attached');\n return this.checkpointHandler(question, signal);\n }\n sendMessage(text: string, from?: TaskMessageFrom): string {\n this.messageCount += 1;\n const id = `msg-${this.messageCount}`;\n if (this.state === 'idle') this.deliver(text, from);\n else this.messages.push({ id, text, ...(from ? { from } : {}) });\n return id;\n }\n /** Interrupts, and the turn that replaces the interrupted one opens with the forced message. */\n forceSend(text: string): string {\n if (this.state === 'idle') return this.sendMessage(text);\n this.messageCount += 1;\n const id = `msg-${this.messageCount}`;\n this.putForced({ id, text, forced: true });\n void this.interrupt();\n return id;\n }\n forceSendQueued(id: string): boolean {\n const message = this.messages.find((m) => m.id === id);\n if (!message) return false;\n if (message.forced) return true;\n this.messages = this.messages.filter((m) => m !== message);\n this.putForced({ ...message, forced: true });\n if (this.state === 'working') void this.interrupt();\n return true;\n }\n private putForced(message: QueuedTaskMessage): void {\n const at = this.messages.findIndex((m) => !m.forced);\n this.messages.splice(at < 0 ? this.messages.length : at, 0, message);\n }\n removeQueued(id: string): boolean {\n const before = this.messages.length;\n this.messages = this.messages.filter((m) => m.id !== id);\n return this.messages.length < before;\n }\n queued(): QueuedTaskMessage[] { return this.messages.map((m) => ({ ...m })); }\n async interrupt(): Promise<void> {\n this.interrupts += 1;\n if (this.state === 'working') this.emitTurnEnd('interrupted');\n }\n nativeSessionId(): string | null { return this.sessionId; }\n /** What `sessionCost()` reports: the runner's running cost as counted, null while unknown. */\n cost: RunnerSessionCost | null = null;\n sessionCost(): RunnerSessionCost | null { return this.cost; }\n\n /** What `commands()` reports: the runner's own commands (#66). */\n runnerCommands: RunnerCommand[] = [];\n /** Every runner command invoked, in order. */\n invoked: InvokedCommand[] = [];\n commands(): RunnerCommand[] { return [...this.runnerCommands]; }\n /** Runs it as a message that starts with it, which is what a runner reads. */\n invokeCommand(name: string, args = ''): string {\n this.invoked.push({ name, ...(args.trim() ? { args: args.trim() } : {}) });\n return this.sendMessage(args.trim() ? `/${name} ${args.trim()}` : `/${name}`);\n }\n\n /** Every answer this session was given, in order. */\n answers: Array<{ id: string; decision: ApprovalDecision }> = [];\n private readonly openPermissions = new Set<string>();\n\n /** The runner asks to use a tool, as `StructuredSession` announces it. */\n requestPermission(id: string, name: string, input: Record<string, unknown>, suggestions: unknown[] = []): void {\n this.openPermissions.add(id);\n this.emitEvent({ type: 'permission_request', id, name, detail: JSON.stringify(input), input, suggestions });\n }\n /** The runner gives up on a request, as an interrupt makes it. */\n withdrawPermission(id: string): void {\n if (this.openPermissions.delete(id)) this.emitEvent({ type: 'permission_withdrawn', id });\n }\n answerPermission(id: string, decision: ApprovalDecision): boolean {\n if (!this.openPermissions.delete(id)) return false;\n this.answers.push({ id, decision });\n this.emitEvent({ type: 'permission_decided', id, decision });\n return true;\n }\n\n emitEvent(event: StructuredEvent): void {\n for (const cb of this.eventCbs) cb(event);\n }\n /** The runner reads a queued message inside the running turn, as `StructuredSession` reports it (ADR-0023). */\n deliverMidTurn(id: string): void {\n const message = this.messages.find((m) => m.id === id);\n if (!message) return;\n this.messages = this.messages.filter((m) => m !== message);\n this.emitEvent({ type: 'message_delivered', messageId: id, text: message.text, ...(message.from ? { from: message.from } : {}) });\n }\n /**\n * The turn ends without a `task_complete` call, and so does the turn the\n * completion reminder opens if one was sent (ADR-0018, W1): the task waits for input.\n */\n waitForInput(): void {\n const reminders = this.fromOrdewell.length;\n this.emitTurnEnd('completed');\n if (this.fromOrdewell.length > reminders) this.emitTurnEnd('completed');\n }\n emitTurnEnd(reason: StructuredTurnEnd): void {\n const next = this.messages.shift();\n if (!next) this.state = 'idle';\n for (const cb of this.turnEndCbs) cb(reason);\n if (next) this.deliver(next.text, next.from);\n }\n\n private deliver(text: string, from?: TaskMessageFrom): void {\n this.state = 'working';\n this.delivered.push(text);\n if (from === 'ordewell') this.fromOrdewell.push(text);\n this.emitEvent({ type: 'turn_start', text, ...(from ? { from } : {}) });\n }\n}\n\n/** An in-memory {@link EventStore}. `failing` makes the next appends reject, as a full disk would. */\nexport class FakeEventStore implements EventStore {\n readonly events: TreeEvent[] = [];\n failing: Error | null = null;\n private closed = false;\n\n async append(events: readonly TreeFact[]): Promise<void> {\n if (this.closed) throw new Error('This event log is closed.');\n if (this.failing) throw this.failing;\n for (const event of events) this.events.push({ ...event, seq: this.events.length + 1 });\n }\n\n async *read(after: number, path?: NodePath): AsyncIterable<TreeEvent> {\n for (const event of [...this.events]) {\n if (event.seq > after && (path === undefined || isWithin(event.node, path))) yield event;\n }\n }\n\n async close(): Promise<void> {\n this.closed = true;\n }\n}\n\n/**\n * An in-memory {@link ExecutionBackend}, as {@link FakeWorktreeIsolation} fakes\n * git: a count of slots and nothing else. `requests`, `opened` and `released`\n * log what the seam was asked; `holdOpen` keeps an open waiting, `openError`\n * fails one, `refuse` turns a role down, `provenGuarantees` is what its probe\n * proves, and `freed` is what a release does, a pool's tick.\n */\nexport class FakeExecutionBackend implements ExecutionBackend {\n /** Slots; a planner, a merge and a delegated task take none, as a flat run's do not. */\n limit = Infinity;\n held = 0;\n requests: LeaseRequest[] = [];\n opened: { request: LeaseRequest; spec: LeaseSpec }[] = [];\n released: { id: string; reason: string }[] = [];\n letGoCalls: { taskId: string; keep: boolean; reason: string }[] = [];\n openError: Error | null = null;\n refuse: { role: LeaseRequest['role']; reason: string } | null = null;\n provenGuarantees: readonly Guarantee[] = [];\n /** What exportResult reports of a session, in place of what it is told. */\n sessionState: { nativeSessionId: string } | null = null;\n /** The repositories an open says its worktree holds. */\n repos: readonly LeaseRepo[] = [];\n /** Where an open puts its runner, for a lease that does not run it on this host. */\n placement: RunnerPlacement | null = null;\n /** Brings a task's work back; absent, it is where the attempt ran. */\n collect?: (taskId: string) => Promise<void>;\n /** Waits on what it has running, as a closing Session does; absent, nothing outlives a runner. */\n settle?: () => Promise<void>;\n /** The workspace root's repositories; absent, none is known. */\n checkoutRepos?: () => readonly LeaseRepo[];\n records: LeaseRecord[] = [];\n freed: () => void = () => undefined;\n private openHold: Promise<void> | null = null;\n private nextId = 0;\n\n /** Make the next open wait until the returned function is called. */\n holdOpen(): () => void {\n let open!: () => void;\n this.openHold = new Promise<void>((resolve) => { open = resolve; });\n return open;\n }\n\n async probe(): Promise<GuaranteeReport> {\n const at = new Date(0).toISOString();\n return Object.fromEntries(GUARANTEES.map((g) => [g, { proven: this.provenGuarantees.includes(g), by: 'fake probe', at }])) as GuaranteeReport;\n }\n\n private takesSlot(request: LeaseRequest): boolean {\n return request.role === 'task' || request.role === 'script';\n }\n\n refusal(request: LeaseRequest): string | null {\n if (this.refuse?.role === request.role) return this.refuse.reason;\n return this.takesSlot(request) && this.held >= this.limit ? `no slot for ${request.node.taskId ?? request.role}` : null;\n }\n\n reserve(request: LeaseRequest): { lease: ReservedLease } | { refused: string } {\n this.requests.push(request);\n const refused = this.refusal(request);\n if (refused) return { refused };\n const holdsSlot = this.takesSlot(request);\n if (holdsSlot) this.held += 1;\n const id = `lease-${++this.nextId}`;\n let live = true;\n return {\n lease: {\n id,\n holdsSlot,\n open: (spec) => this.open(request, spec),\n release: (reason) => {\n if (!live) return;\n live = false;\n this.released.push({ id, reason });\n if (holdsSlot) this.held -= 1;\n this.freed();\n },\n },\n };\n }\n\n private async open(request: LeaseRequest, spec: LeaseSpec): Promise<OpenLease> {\n this.opened.push({ request, spec });\n const guarantees = await this.probe();\n refuseUnproven(spec.required, guarantees);\n const hold = this.openHold;\n this.openHold = null;\n await hold;\n if (this.openError) throw this.openError;\n const worktree = spec.workspace.kind === 'worktree';\n return {\n workspace: { cwd: worktree ? `/fake-lease/${spec.task?.id}` : '/repo', worktree, ...(this.repos.length ? { repos: this.repos } : {}) },\n guarantees,\n exportResult: (session) => ({ refs: {}, sessionState: { ...session, ...this.sessionState } }),\n ...(this.placement ? { placement: this.placement } : {}),\n };\n }\n\n async letGo(taskId: string, { keep, landing, reason }: { keep: boolean; landing?: Promise<unknown> | null; reason: string }): Promise<void> {\n await landing;\n this.letGoCalls.push({ taskId, keep, reason });\n }\n\n async list(): Promise<readonly LeaseRecord[]> {\n return this.records;\n }\n}\n\nexport type FakeIsolationCall =\n | { op: 'isActive'; workspaceRoot: string }\n | { op: 'stash'; workspaceRoot: string }\n | { op: 'startRun'; workspaceRoot: string }\n | { op: 'prepare' | 'reopen' | 'verifyRepair'; taskId: string }\n | { op: 'integrate'; taskId: string }\n | { op: 'release'; taskId: string; keep: boolean }\n | { op: 'handoff' | 'pruneOrphans' | 'reviewDiff' | 'mergeIntoCheckedOut' | 'sweep' | 'findInHead' | 'snapshotTree' | 'changedSince' }\n | { op: 'discard'; integration: IntegrationDisposal }\n | { op: 'readLanded' | 'revert'; taskIds: readonly string[] };\n\n/**\n * An in-memory {@link IWorktreeIsolation} for scheduling tests: no git, no\n * filesystem. Every call is logged in `calls`; `prepare` hands back a\n * deterministic fake cwd. Set `availability` to exercise the fallbacks, `outcomes`\n * to script a conflict, and `holdIntegration` to keep a task un-integrated so a\n * test can observe that its dependents wait. `repos` makes the run a group of\n * several; `changes` and `stopsIn` say which of them a task changes and where\n * its landing stops. `repairEvidence` scripts what a conflict repair's\n * evidence check finds, and `removal` what a removal kept back.\n */\nexport class FakeWorktreeIsolation implements IWorktreeIsolation {\n availability: IsolationAvailability = { active: true };\n /** What `gitShortfall` answers. */\n gitShortfallText: string | null = null;\n /** The repo paths `startRun` groups: a group of one at `.` unless set. */\n repos: string[] = [SELF_REPO];\n /** Per task id, the repos it changes; every repo of the group when not listed. */\n changes = new Map<string, string[]>();\n /** Per task id, the repo its landing stops in when its outcome is not `merged`; its first changed repo when not listed. */\n stopsIn = new Map<string, string>();\n /** Per task id, the files a `conflict` outcome names; empty when not listed. */\n conflictFiles = new Map<string, string[]>();\n /** What `mergeIntoCheckedOut` answers; a `merged` one also puts every landed task in HEAD, as git would. */\n mergeResult: IsolationMergeResult = { outcome: 'merged' };\n /** Task ids whose landed work `findInHead` finds in HEAD besides those a merge put there, as a merge by hand would. */\n mergedByHand = new Set<string>();\n /** What `snapshotTree` answers; null as for a workspace with no repo. */\n treeSnapshot: TreeSnapshot | null = {};\n /** What `changedSince` answers: the tracked files an ops task changed. */\n changedFiles: string[] = [];\n /** What `startRun` shares and `prepare` copies, to exercise their notices. */\n shared: string[] = [];\n sharedRepos: string[] = [];\n copied: string[] = [];\n setupFailed: SetupFailure[] = [];\n /** Set to make `startRun` throw, as git does when no repo of the group can be isolated. */\n startRunError: Error | null = null;\n /** Set to make `discard` / `sweep` throw after logging the call. */\n discardError: Error | null = null;\n sweepError: Error | null = null;\n /** What `pruneOrphans` reports as kept back from the sweep. */\n keptOnPrune: IsolationPruneResult['kept'] = [];\n /**\n * What every removal — a `release` that removes, `discard`, `pruneOrphans` —\n * reports it kept back. A refused task keeps its record, as git's does.\n */\n removal: IsolationRemoval = noRemoval();\n /** Per task id; a task not listed integrates as `merged`. */\n outcomes = new Map<string, IsolationOutcome>();\n /** What `revert` stops with; a revert that goes through when null. */\n revertStop: Exclude<IsolationRevertResult, { outcome: 'reverted' }> | null = null;\n /** What `readLanded` finds of a task's work in the user's branch, by task id. */\n inBranch = new Map<string, RevertCommand[]>();\n /** Per task id, what `verifyRepair` finds; a task not listed passes. */\n repairEvidence = new Map<string, RepairEvidence>();\n calls: FakeIsolationCall[] = [];\n private holds = new Map<string, Promise<void>>();\n private verifyHolds = new Map<string, Promise<void>>();\n private revertHold: Promise<void> | null = null;\n private runCount = 0;\n\n private log(call: FakeIsolationCall): void { this.calls.push(call); }\n\n /** Task ids in the order `op` was called for them. */\n taskIdsFor(op: 'prepare' | 'reopen' | 'verifyRepair' | 'integrate' | 'release'): string[] {\n return this.calls.flatMap((c) => (c.op === op && 'taskId' in c ? [c.taskId] : []));\n }\n\n /** Make `integrate` for this task wait until the returned function is called. */\n holdIntegration(taskId: string): () => void {\n let open!: () => void;\n this.holds.set(taskId, new Promise<void>((resolve) => { open = resolve; }));\n return open;\n }\n\n /** Make the next `revert` wait, past its `refusal`, until the returned function is called. */\n holdRevert(): () => void {\n let open!: () => void;\n this.revertHold = new Promise<void>((resolve) => { open = resolve; });\n return open;\n }\n\n /** Make `verifyRepair` for this task wait until the returned function is called. */\n holdVerification(taskId: string): () => void {\n let open!: () => void;\n this.verifyHolds.set(taskId, new Promise<void>((resolve) => { open = resolve; }));\n return open;\n }\n\n async gitShortfall(): Promise<string | null> {\n return this.gitShortfallText;\n }\n\n async isActive(workspaceRoot: string): Promise<IsolationAvailability> {\n this.log({ op: 'isActive', workspaceRoot });\n return this.availability;\n }\n\n /** Like git: once the tracked changes are stashed, the tree is no longer dirty. */\n async stash(workspaceRoot: string): Promise<void> {\n this.log({ op: 'stash', workspaceRoot });\n if (!this.availability.active && this.availability.reason === 'dirty') this.availability = { active: true };\n }\n\n async startRun(workspaceRoot: string, stateRoot?: string): Promise<IsolationRun> {\n this.log({ op: 'startRun', workspaceRoot });\n if (this.startRunError) throw this.startRunError;\n const id = `run${++this.runCount}`;\n return {\n id,\n workspaceRoot,\n ...(stateRoot !== undefined ? { stateRoot } : {}),\n repos: this.repos.map((repo) => ({\n path: repo, root: `${workspaceRoot}/${repo}`.replace(/\\/\\.$/, ''), baseRef: 'base0000', baseBranch: 'main', integrationBranch: integrationBranchFor(id),\n })),\n shared: [...this.shared],\n sharedRepos: [...this.sharedRepos],\n tasks: {},\n keepsEveryLanding: true,\n carried: {},\n };\n }\n\n async prepare(task: Task, run: IsolationRun, _intent?: PrepareIntent, opts: { readOnly?: boolean } = {}): Promise<PreparedTask> {\n this.log({ op: 'prepare', taskId: task.id });\n const name = `${task.order}-${task.id}`;\n const cwd = `/fake-worktrees/${run.id}/${name}`;\n const branch = `ordewell/${run.id}/${name}`;\n run.tasks[task.id] = {\n taskId: task.id, order: task.order, title: task.title, branch, workspace: cwd, status: 'active',\n repos: Object.fromEntries(run.repos.map((r) => [r.path, { worktree: r.path === SELF_REPO ? cwd : `${cwd}/${r.path}`, linked: [] }])),\n ...(opts.readOnly ? { readOnly: true } : {}),\n };\n return { cwd, branch, copied: [...this.copied], ...(this.setupFailed.length > 0 ? { setupFailed: [...this.setupFailed] } : {}) };\n }\n\n /** Like git: the same cwd, each changed repo's tip recorded, the repair counted. */\n async reopen(task: Task, run: IsolationRun): Promise<PreparedTask> {\n this.log({ op: 'reopen', taskId: task.id });\n const record = run.tasks[task.id];\n if (record?.status !== 'conflict') throw new Error(`Task ${task.order} has no conflict to repair`);\n const changed = Object.entries(record.repos).filter(([repo, entry]) => entry.changed || repo === record.conflictRepo).map(([repo]) => repo);\n const files = (record.conflictFiles ?? []).map((file) => (record.conflictRepo && record.conflictRepo !== SELF_REPO ? `${record.conflictRepo}/${file}` : file));\n record.repairs = (record.repairs ?? 0) + 1;\n record.repairBase = Object.fromEntries(changed.map((repo) => [repo, `tip-${repo}`]));\n record.repairedFiles = [...new Set([...(record.repairedFiles ?? []), ...files])];\n record.status = 'repairing';\n return { cwd: record.workspace, branch: record.branch, copied: [] };\n }\n\n async verifyRepair(task: Task, _run: IsolationRun): Promise<RepairEvidence> {\n this.log({ op: 'verifyRepair', taskId: task.id });\n await this.verifyHolds.get(task.id);\n return this.repairEvidence.get(task.id) ?? { ok: true };\n }\n\n /** Like git: the landing is recorded and persisted before the (held) merge, and cleared once it settles. */\n async integrate(task: Task, run: IsolationRun, persist: () => void = () => undefined, commit?: HandoffCommit): Promise<IsolationOutcome> {\n this.log({ op: 'integrate', taskId: task.id });\n const record = run.tasks[task.id];\n if (!record) return 'failed';\n if (commit) record.commit = commit;\n if (record.readOnly) {\n record.status = 'failed';\n record.landingError = 'a read-only task lands nothing';\n return 'failed';\n }\n const changed = this.changes.get(task.id) ?? run.repos.map((r) => r.path);\n for (const [repo, entry] of Object.entries(record.repos)) entry.changed = changed.includes(repo) || (entry.changed ?? false);\n if (changed.length > 0) {\n run.landing = { taskId: task.id, tips: Object.fromEntries(changed.map((repo) => [repo, `tip-${repo}`])) };\n persist();\n }\n await this.holds.get(task.id);\n delete run.landing;\n const outcome = this.outcomes.get(task.id) ?? 'merged';\n record.status = outcome;\n delete record.repairBase;\n if (outcome === 'merged') {\n delete record.conflictRepo;\n delete record.conflictFiles;\n for (const repo of changed) {\n ((run.landings ??= {})[task.id] ??= {})[repo] = [...run.landings?.[task.id]?.[repo] ?? [], `tip-${repo}`];\n (run.landedTasks ??= {})[task.id] = { taskId: task.id, order: task.order, title: task.title };\n }\n } else {\n record.conflictRepo = this.stopsIn.get(task.id) ?? changed[0] ?? SELF_REPO;\n const files = this.conflictFiles.get(task.id);\n if (files) record.conflictFiles = files;\n else delete record.conflictFiles;\n }\n return outcome;\n }\n\n /** Newest landing first, read as the plan's highest order: scheduling tests land in plan order. */\n async readLanded(run: IsolationRun, taskIds: readonly string[]): Promise<LandedWork> {\n this.log({ op: 'readLanded', taskIds });\n const records = taskIds.flatMap((id) => run.tasks[id] ?? run.landedTasks?.[id] ?? []).sort((a, b) => b.order - a.order);\n const tasks = records.map((r) => ({\n taskId: r.taskId, order: r.order, title: r.title,\n repos: Object.keys(run.landings?.[r.taskId] ?? {}),\n }));\n return { ok: true, tasks, inBranch: records.flatMap((r) => this.inBranch.get(r.taskId) ?? []) };\n }\n\n /** Like git: the turn, then the judgement, then the records go in one step with the tasks back. */\n async revert(run: IsolationRun, taskIds: readonly string[], watch: RevertWatch): Promise<IsolationRevertResult> {\n this.log({ op: 'revert', taskIds });\n const hold = this.revertHold;\n this.revertHold = null;\n await hold;\n const reason = watch.refusal();\n if (reason) return { outcome: 'refused', reason };\n if (this.revertStop) return this.revertStop;\n const landed = await this.readLanded(run, taskIds);\n if (!landed.ok) return { outcome: 'failed', error: landed.error };\n if (landed.inBranch.length > 0) return { outcome: 'in-branch', commands: landed.inBranch };\n run.landing = { taskId: taskIds[0], tips: {}, reverts: {} };\n watch.persist();\n delete run.landing;\n for (const id of taskIds) {\n // Like git: a later attempt's record that did not land stays for its worktree, its conflict over.\n if (run.tasks[id]?.status === 'merged') delete run.tasks[id];\n else if (run.tasks[id]?.status === 'conflict') run.tasks[id].status = 'kept';\n delete run.landings?.[id];\n delete run.landedTasks?.[id];\n delete run.landedOn?.[id];\n }\n run.reverted = true;\n watch.settled(taskIds);\n return { outcome: 'reverted', tasks: landed.tasks };\n }\n\n async release(run: IsolationRun, taskId: string, opts: { keep: boolean }): Promise<IsolationRemoval> {\n this.log({ op: 'release', taskId, keep: opts.keep });\n const record = run.tasks[taskId];\n // Like git: a read-only task's record goes whole, with nothing kept (ADR-0046).\n if (record?.readOnly) {\n delete run.tasks[taskId];\n return noRemoval();\n }\n const removal = opts.keep || !record ? noRemoval() : this.removed();\n const refused = removal.refused.some((r) => r.task?.taskId === taskId);\n if (!opts.keep && !refused) delete run.tasks[taskId];\n else if (record?.status === 'active') record.status = 'kept';\n else if (record?.status === 'repairing') {\n record.status = 'conflict';\n delete record.repairBase;\n }\n return removal;\n }\n\n private removed(): IsolationRemoval {\n return { preserved: [...this.removal.preserved], refused: [...this.removal.refused] };\n }\n\n async handoff(run: IsolationRun): Promise<IsolationHandoff> {\n this.log({ op: 'handoff' });\n return handoffOf(run);\n }\n\n async pruneOrphans(): Promise<IsolationPruneResult> {\n this.log({ op: 'pruneOrphans' });\n return { kept: [...this.keptOnPrune], ...this.removed() };\n }\n async reviewDiff(): Promise<string> { this.log({ op: 'reviewDiff' }); return ''; }\n async mergeIntoCheckedOut(run: IsolationRun): Promise<IsolationMergeResult> {\n this.log({ op: 'mergeIntoCheckedOut' });\n if (this.mergeResult.outcome === 'merged') {\n for (const record of Object.values(run.tasks)) if (record.status === 'merged') this.mergedByHand.add(record.taskId);\n }\n return this.mergeResult;\n }\n async findInHead(run: IsolationRun): Promise<string[]> {\n this.log({ op: 'findInHead' });\n const changed: string[] = [];\n for (const record of Object.values(run.tasks)) {\n if (record.status !== 'merged') continue;\n const merged = this.mergedByHand.has(record.taskId);\n if (merged === !!record.inHead) continue;\n if (merged) record.inHead = true;\n else delete record.inHead;\n changed.push(record.taskId);\n }\n return changed;\n }\n async snapshotTree(): Promise<TreeSnapshot | null> { this.log({ op: 'snapshotTree' }); return this.treeSnapshot; }\n async changedSince(): Promise<string[]> { this.log({ op: 'changedSince' }); return [...this.changedFiles]; }\n async discard(_run: IsolationRun, opts: { integration: IntegrationDisposal }): Promise<IsolationRemoval> {\n this.log({ op: 'discard', integration: opts.integration });\n if (this.discardError) throw this.discardError;\n return this.removed();\n }\n async sweep(): Promise<void> {\n this.log({ op: 'sweep' });\n if (this.sweepError) throw this.sweepError;\n }\n}\n","import { v4 as uuidv4 } from 'uuid';\nimport type { PlanIsolation } from '../interfaces/IWorktreeIsolation';\nimport type { AttemptUsage, PlannerUsage, ReportedWindow, UsageRecord, UsageTotals } from './Usage';\nimport type { ExecutionRecord, ScriptConsent, SupervisorLevel } from './Execution';\nimport type { SkillSource } from '../services/SkillsService';\nimport type { SupervisionMemorySnapshot } from '../services/LoopGuards';\nimport type { SupervisorSnapshot } from '../services/Supervisor';\nimport type { TaskLoop } from './Loop';\nimport type { HandoffDigest } from './Handoff';\nimport type { DelegatedBudget, TaskScope } from './Delegation';\nimport type { Grant } from '../services/Mandate';\nimport { SKILL_NAME_PATTERN } from '../conversation/skillTokens';\nimport { subtreeIds, walkPlan } from '../services/PlanTree';\nimport { migratePlanStateIsolation } from '../services/isolationRecord';\nimport { interruptedVerdict } from '../services/VerdictEngine';\n\nexport interface UserStep {\n order: number;\n instruction: string;\n completed: boolean;\n}\n\n/**\n * One deterministic signal gathered while verifying a completed task. `completion_marker` is only in verdicts saved before completion was reported through `task_complete` alone.\n * `script_exit` is a script task's exit status, its one evidence (ADR-0035).\n */\nexport interface VerificationCheck {\n name: 'exit_code' | 'completion_marker' | 'task_complete' | 'manual' | 'script_exit' | 'protected_paths';\n passed: boolean;\n /** A check that did not apply. Skipped checks don't affect the verdict. */\n skipped: boolean;\n detail: string;\n}\n\n/** Evidence-based verdict for a completed task. Single end-to-end outcome produced by verification. */\nexport interface Verdict {\n outcome: 'pass' | 'fail';\n reason: string;\n checks: VerificationCheck[];\n decidedAt: string;\n /**\n * What the attempt's evidence said of the work: the status the runner's\n * `task_complete` call reported, or a script's exit read the same way —\n * `done` on exit 0, `failed` on a non-zero exit, a signal or its time limit,\n * `blocked` when it could not start. A loop starts a round only on `failed`\n * (ADR-0045 L2). Absent when nothing reported: a runner that exited without\n * the call, Mark complete, a stopped script, an attempt the host died in, a\n * verdict saved before it was kept.\n */\n reported?: 'done' | 'failed' | 'blocked';\n}\n\nexport interface TaskOutputSummary {\n reviewReason: string;\n /** The `task_complete` summary, the task's report; absent when the attempt ended without one, and from sessions saved before it was kept. */\n report?: string;\n /** Empty when there is a report: the turn's final text is the report itself. */\n logTail: string;\n capturedAt: string;\n}\n\n/**\n * `script` is an ops task whose body Ordewell runs with no model (ADR-0035);\n * `delegated` holds a goal, and its attempt is a child plan (ADR-0055).\n */\nexport type TaskType = 'ai' | 'user' | 'script' | 'delegated';\n/**\n * `skipped` is terminal and verdict-less: the task will not be waited for, and\n * nothing it did lands. Its dependents go ahead without it once the work it\n * waited on is done. There is no stored `blocked`: a task is blocked when a\n * failed dependency says so (ADR-0034), and a file that kept the old status\n * loads as pending.\n */\nexport type TaskStatus = 'pending' | 'approved' | 'in_progress' | 'completed' | 'failed' | 'awaiting_user' | 'skipped';\nexport type TaskMode = string;\n\n/**\n * Why an `awaiting_user` task waits (ADR-0018, W1): a structured turn that\n * ended without a `task_complete` call, a checkpoint question, work that did not\n * land, an ops task that changed tracked files (ADR-0020), or its runner's\n * usage limit. Saved, so no surface has to guess it from whether an attempt\n * is live.\n */\nexport type AwaitingReason = 'input' | 'checkpoint' | 'question-lost' | 'conflict' | 'files-changed' | 'usage-limit';\n\nexport function isAwaitingReason(value: unknown): value is AwaitingReason {\n return value === 'input' || value === 'checkpoint' || value === 'question-lost' || value === 'conflict' || value === 'files-changed' || value === 'usage-limit';\n}\n\nexport interface TaskModelAssignment {\n modelId: string;\n modelLabel: string;\n thinkingEffort?: string;\n /**\n * All variant ids the model offered when this assignment was made. Carried\n * on the assignment because runners need it at spawn time (opencode's TUI\n * only honors an assigned variant when the others are config-disabled) and\n * the discovery catalog isn't available there.\n */\n availableVariants?: string[];\n}\n\nexport type RunnerId = string;\n\n/**\n * Bring loaded tasks to the structured-only shape, in place. A task saved\n * while the terminal transport existed may say it ran there, and why; neither\n * means anything now, and such a task left no session to continue. Its\n * completion marker id went with the text markers: completion is reported\n * only through `task_complete`.\n */\nexport function migrateLoadedTasks(tasks: readonly Task[]): void {\n for (const task of tasks) {\n if (typeof task.runnerSessionId !== 'string') delete task.runnerSessionId;\n if (typeof task.carriedFrom !== 'string') delete task.carriedFrom;\n if (typeof task.runnerSessionOwner !== 'string') delete task.runnerSessionOwner;\n const legacy = task as Task & { transport?: { kind?: unknown; nativeSessionId?: unknown } };\n if (!task.runnerSessionId && legacy.transport?.kind === 'structured' && typeof legacy.transport.nativeSessionId === 'string') {\n task.runnerSessionId = legacy.transport.nativeSessionId;\n }\n delete legacy.transport;\n delete (task as { completionMarker?: unknown }).completionMarker;\n // The effort once had a second home on the task; the spawn reads only the model's.\n const effort = (task as { thinkingEffort?: unknown }).thinkingEffort;\n if (typeof effort === 'string' && task.assignedModel && !task.assignedModel.thinkingEffort) task.assignedModel.thinkingEffort = effort;\n delete (task as { thinkingEffort?: unknown }).thinkingEffort;\n migrateLoadedTasks(task.subtasks ?? []);\n }\n}\n\n/**\n * Nothing is running when a plan comes off disk, so a task saved in progress\n * lost its attempt with the host that ran it. It is failed, in place, so the\n * inbox lists it and what depends on it waits (ADR-0053). Its attempts and\n * logs are left as they are. A delegated task parked at its subplanner's\n * question is no failure: it says the question is lost, and its child waits\n * to be continued (ADR-0055 D17).\n */\nexport function failInterruptedTasks(tasks: readonly Task[], now: string): void {\n for (const task of flattenTasks(tasks)) {\n if (task.type === 'delegated' && task.awaitingReason === 'checkpoint') task.awaitingReason = 'question-lost';\n if (task.status !== 'in_progress') continue;\n task.status = 'failed';\n task.verdict = interruptedVerdict(now);\n }\n}\n\n/**\n * A task an edit made manual has nothing to retry, and is the user's to do: one\n * failed, or waiting on the user with no runner to hear an answer, waits as a\n * manual task does (ADR-0053). A runner still at a question keeps it, and the\n * answer is still its to read, until it ends or the plan comes off disk.\n */\nexport function releaseMadeManual(tasks: readonly Task[], live: (taskId: string) => boolean = () => false): void {\n for (const task of flattenTasks(tasks)) {\n if (task.type !== 'user' || live(task.id) || (task.status !== 'failed' && task.status !== 'awaiting_user')) continue;\n task.status = 'pending';\n delete task.awaitingReason;\n delete task.resumeAt;\n }\n}\n\n/**\n * Nothing claims a repair when a plan comes off disk: what owed it went with\n * the host that saved it. So a task saved not started whose landing conflicted\n * is a conflict waiting on the user, in place, and the inbox lists it; the\n * next Execute still repairs it (ADR-0015, ADR-0053).\n */\nexport function settleOwedRepairs(plan: LegacyPlanState): void {\n const records = plan.isolation?.run.tasks;\n if (!records) return;\n for (const task of flattenTasks(plan.tasks)) {\n if ((task.status !== 'pending' && task.status !== 'approved') || records[task.id]?.status !== 'conflict') continue;\n task.status = 'awaiting_user';\n task.awaitingReason = 'conflict';\n }\n}\n\n/**\n * Drop what a build before the per-message research budget was removed wrote\n * for it (ADR-0037): the budget, its two run settings, its per-turn flag, and\n * the report wait a task carried. The persisted types no longer name these,\n * so a reopened session would otherwise spread them onto the wire and write\n * them back; they are deleted by key, never widened into the types.\n */\nexport function dropRetiredPlanFields(plan: LegacyPlanState): void {\n const raw = plan as unknown as Record<string, unknown>;\n delete raw.researchBudget;\n const settings = plan.runSettings as unknown as Record<string, unknown> | undefined;\n if (settings) {\n delete settings.researchMaxParallel;\n delete settings.researchMaxPerMessage;\n }\n for (const turn of plan.pendingTurns ?? []) delete (turn as unknown as Record<string, unknown>).budgetStarted;\n for (const task of flattenTasks(plan.tasks)) delete (task as unknown as Record<string, unknown>).researchWait;\n}\n\n/**\n * Move a mid-run edit an older build queued for its next batch boundary into\n * the pending turns: it was never answered, so it waits like any other.\n */\nfunction carryOverQueuedEdits(plan: LegacyPlanState): void {\n const queued = plan.queuedMessages ?? [];\n delete plan.queuedMessages;\n if (queued.length === 0) return;\n const carried = queued.map(({ id, text, timestamp, skills }): PendingPlanTurn => ({ id, source: 'user', text, timestamp, ...(skills ? { skills } : {}) }));\n plan.pendingTurns = [...(plan.pendingTurns ?? []), ...carried];\n}\n\n/**\n * Bring a loaded plan to the shape this build reads, in place: every plan-shape\n * migration lives here, so each can be tested from an old shape without a whole\n * Session, as {@link migrateLoadedTasks} does for tasks.\n *\n * `adopting` is false when a Session reloads the plan it already holds: the\n * live run's task pauses are fresher than the save, so they stay.\n */\nexport function migrateLoadedPlan(plan: LegacyPlanState, { adopting = true }: { adopting?: boolean } = {}): void {\n // Saved before approval had its own record: the status said it, and\n // executing a plan approved every task it held. Read once, here, and never\n // again — a surface rewrites the status for display.\n plan.approved ??= plan.status !== 'draft' && plan.status !== 'rejected';\n if (!plan.approvedTasks && plan.approved) {\n plan.approvedTasks = flattenTasks(plan.tasks).map((t) => t.id);\n }\n // A saved reset time belonged to a run that is gone; loading is not consent\n // to spend tokens (ADR-0044).\n if (adopting) {\n for (const task of flattenTasks(plan.tasks)) delete task.resumeAt;\n }\n migratePlanStateIsolation(plan);\n // Older builds copied the since-removed transport setting onto the plan.\n // Every plan now runs structured (ADR-0018), so the copy is not carried on.\n delete (plan as { runnerTransport?: unknown }).runnerTransport;\n // Run settings are the user's now (ADR-0030); an older plan's own copies\n // are not read, and go with this save.\n for (const key of ['supervisorLevel', 'mergeAtGatesAutomatically', 'supervisorRetryLimit', 'wakeUpReadBudget']) {\n delete (plan as unknown as Record<string, unknown>)[key];\n }\n // The per-message research budget is gone (ADR-0037); a plan saved before\n // its removal is read once and written back without its keys.\n dropRetiredPlanFields(plan);\n carryOverQueuedEdits(plan);\n}\n\n/**\n * A task skill as one attempt received it: read from the task's own worktree\n * (or global) at spawn, kept so the user can see what the runner was told\n * even after the skill's file changes.\n */\nexport interface TaskSkillSnapshot {\n name: string;\n source: SkillSource;\n path: string;\n content: string;\n}\n\n/** A task skill as a surface is told of it: the body stays in the task's log file and the session. */\nexport type TaskSkillNotice = Omit<TaskSkillSnapshot, 'content'>;\n\nexport interface Task {\n id: string;\n order: number;\n title: string;\n description: string;\n type: TaskType;\n status: TaskStatus;\n dependencies: string[];\n prompt?: string;\n userSteps?: UserStep[];\n subtasks: Task[];\n verdict?: Verdict;\n outputSummary?: TaskOutputSummary;\n /**\n * The latest attempt's handoff, as the plan keeps it (ADR-0047): its\n * summary, first findings and artifacts. The whole handoff, report and all,\n * is in that attempt's log. Absent when the attempt handed none over, and\n * on a plan saved before handoffs, which reads `outputSummary` instead.\n */\n handoff?: HandoffDigest;\n assignedModel?: TaskModelAssignment;\n assignedRunner: RunnerId;\n taskMode?: TaskMode;\n autonomy?: 'AFK' | 'HITL';\n sliceType?: 'HITL' | 'AFK';\n userStoriesCovered?: string[];\n /** The runner's saved session for a later continue; cleared when a fresh attempt starts. */\n runnerSessionId?: string;\n /**\n * The runner that session belongs to: a session cannot cross runners, so\n * Continue is offered only while the task is still assigned to it. Absent\n * on a plan saved before it was recorded; the last attempt's runner stands\n * in (see `sessionOwner`).\n */\n runnerSessionOwner?: RunnerId;\n /** Set only while `status` is `awaiting_user`, and not always then — a pause saved by an older build has none. */\n awaitingReason?: AwaitingReason;\n /**\n * The question a delegated task's subplanner is parked on (ADR-0056 M8). The\n * live one is held in memory by what answers it, so this copy is only what a\n * restart can still say was lost (ADR-0055 D17). Goes with the wait.\n */\n parkedQuestion?: string;\n /**\n * How many waits have ended since its latest attempt started, or since the\n * task was written if none has: an attempt can wait many times, and this\n * tells one wait from the next in an escalation's id (ADR-0053). Saved with\n * the plan, so the id holds across a reload. Absent counts as none.\n */\n waitsEnded?: number;\n /**\n * When a task paused on its runner's usage limit is resumed on its own (ISO\n * time): the reset the runner announced, plus a short margin. Absent when no\n * reset time could be read or the user cancelled the resume — the task then\n * waits for a manual retry. Set only with `awaitingReason` `usage-limit`.\n */\n resumeAt?: string;\n /**\n * The runner a task paused on its usage limit was moved off (ADR-0018, U1):\n * its next attempt is a carry-over, a fresh session that starts from that\n * attempt's files. Cleared once that attempt runs, and by a retry.\n */\n carriedFrom?: RunnerId;\n /** Why the task was skipped, as whoever skipped it put it; set only while `status` is `skipped`, and not always then. */\n skipReason?: string;\n /**\n * The user reverted its landed work (ADR-0054): saved with the plan, so the\n * hold that keeps it from running again outlives this process. Set only\n * while it waits as the revert left it: its next start clears it.\n */\n reverted?: true;\n /**\n * An ops task (ADR-0020): it changes no repository files, runs at the\n * workspace root rather than in a worktree, and waits for the change tasks\n * it depends on to be merged into the user's branch. Top-level AI tasks\n * only; absent means a change task. A script task is one by nature and\n * stores none (see {@link runsAsOps}).\n */\n ops?: boolean;\n /** A top-level read-only AI task whose report returns to the planner (ADR-0037 R1). */\n research?: boolean;\n /** A retry still owes research's report under its original authority (ADR-0037 R3). */\n researchOrigin?: TaskReportTo;\n /**\n * The dependencies, by title, whose work was not merged into the user's\n * branch when the user force-started this task past its merge gate\n * (ADR-0020). Cleared when the task is retried.\n */\n forcedPastGate?: string[];\n /**\n * Task skills (`applies-to: task`) attached by name. Their bodies are put in\n * the runner's prompt at spawn, resolved where the task runs — a subtask's\n * are its own, never its parent's. Absent means none.\n */\n skills?: string[];\n /** The skills the latest attempt was given, as read when it spawned. */\n attemptSkills?: TaskSkillSnapshot[];\n /**\n * Who put the task in the plan, when it was not the user's own planning:\n * `supervisor` for a task an Act supervisor added during a run, which\n * counts as approved to run (ADR-0029). Set by the session, never by an op.\n */\n addedBy?: 'supervisor';\n /**\n * An attempt of it has started, so its chat exists from then on: kept after\n * a failure, a cancel, a skip or a completion, and restored with the plan.\n * Set by the attempt's own start — never by a status or a manual mark.\n */\n attempted?: true;\n attemptUsage?: AttemptUsage[];\n /**\n * A script task's body (ADR-0035), exactly one of these two: argument\n * vectors run in order with no shell, stopping at the first that fails, or\n * one script run by the research shell's interpreter.\n */\n commands?: string[][];\n script?: string;\n /** A script task's time limit; absent means ten minutes (`DEFAULT_SCRIPT_TIMEOUT_MS`). */\n timeoutMs?: number;\n /** What a delegated task's child plan must achieve, in place of a prompt (ADR-0055 D1). */\n goal?: string;\n /** A delegated task's budget (ADR-0056 M5). */\n budget?: DelegatedBudget;\n /** What a delegated task asks for past the floor; absent asks for nothing (ADR-0056 M2). */\n grants?: Grant[];\n /** The paths a delegated task's child may not change, what its check runs among them (ADR-0055 D7). */\n protectedPaths?: string[];\n /** What a delegated task declares it works on (ADR-0057 R6). */\n scope?: TaskScope;\n /**\n * On a delegated task, it answers a question and lands nothing (ADR-0055 D1).\n * On a script task, it runs sealed in a throwaway worktree, past no gate (D8).\n * A read-only AI task says so by its mode (ADR-0031) and stores none.\n */\n readOnly?: boolean;\n /** Set on a loop's check (ADR-0045): the tasks that fix what it finds, and how many times it may run. */\n loop?: TaskLoop;\n /**\n * The round of a loop's check's latest run, or the one it is running;\n * absent is round 1. Execution state, saved so a reload resumes mid-round,\n * and started over by a retry of the check (ADR-0045 L5, L7).\n */\n loopRound?: number;\n /**\n * A planner turn waits for this task's result (ADR-0037, R2): set when the\n * tool that started it answered before it ended, or when research lands.\n * Cleared once its end is reported, in a turn acting for whoever started it (R3).\n */\n reportTo?: TaskReportTo;\n}\n\n/**\n * Who started a task whose result is owed to the planner (ADR-0037, R3): a\n * planner turn in the user's conversation, or the supervisor, at its level\n * then, with script consent (ADR-0035) or the research profile (ADR-0037 R4).\n */\nexport type TaskReportTo = ({ consent: ScriptConsent; research?: never } | { research: true; consent?: never }) & (\n | { actor: 'planner-turn' }\n | { actor: 'supervisor'; level: SupervisorLevel }\n | { actor: 'subplanner'; depth: number }\n);\n\n/** Whose authority a turn opened without the user acts with (ADR-0037, R3): the user's turn, or the supervisor's. */\nexport type TurnAuthority = 'user' | 'supervisor';\n\nexport interface DiscoveredMode {\n id: string;\n label: string;\n description: string;\n}\n\nexport type ResearchToolType =\n | 'read_file' | 'read_files' | 'glob' | 'grep' | 'find_symbol' | 'list_dir'\n | 'bash' | 'fetch' | 'web_search' | 'spawn_research_agent'\n | 'task_query' | 'task_output' | 'task_diff'\n /**\n * A tool belonging to a harness planner's own toolbox (ADR-0009) that has no\n * Ordewell equivalent — Edit, WebFetch, TodoWrite, whatever a coding agent\n * ships next. The real name travels in `toolLabel` rather than being\n * relabelled as a tool it is not; the union stays closed so the\n * exhaustiveness checks in every surface's icon/label switch survive.\n */\n | 'agent_tool';\n\n/**\n * What happened when a research tool call ran, for honest per-surface\n * rendering. The broadcast seam carries this on every `research_step_done` so\n * surfaces do not have to pattern-match refusal text to tell a refused `rm`\n * from a successful `rm` — the old render path flipped a `✓` for both.\n */\nexport type ResearchStepOutcome = 'success' | 'failure' | 'refused' | 'denied' | 'not_executed';\n\nexport interface ResearchStep {\n id: string;\n tool: ResearchToolType;\n /** The tool's own name when it came from a harness planner — always set for `agent_tool`. */\n toolLabel?: string;\n args: string;\n result: string;\n success: boolean;\n outcome: ResearchStepOutcome;\n /** The model's tool_call id, so a surface can match `tool_result` to the\n * pending `tool_call` it announced — robust under parallel same-tool rounds. */\n toolCallId?: string;\n /** The research subagent that ran the call, so a reload regroups it under that subagent. */\n subagentId?: string;\n timestamp: string;\n thinkingText?: string;\n}\n\nexport interface UserPromptEntry {\n id: string;\n type: 'user_prompt' | 'system';\n content: string;\n timestamp: string;\n}\n\nexport type SubagentOutcome = 'done' | 'failed' | 'stopped';\n\n/**\n * One subagent's whole run, logged when it finishes: what it was asked, how it\n * ended and what it reported. Its steps stay separate entries carrying the same\n * `subagentId`, so an older reader that knows only steps still shows them.\n */\nexport interface SubagentLogEntry {\n id: string;\n type: 'subagent';\n subagentId: string;\n brief: string;\n model?: string;\n outcome: SubagentOutcome;\n digest: string;\n usage?: UsageTotals;\n timestamp: string;\n}\n\nexport type ResearchLogEntry = ResearchStep | UserPromptEntry | SubagentLogEntry;\n\nexport interface ResearchProgress {\n // 'liveness' carries no content: a raw-line signal from the harness process\n // (ADR-0009) that reaches a surface even when a turn is producing nothing\n // visible — a subagent's filtered chatter, most often — so an idle watchdog\n // downstream doesn't mistake \"nothing to show\" for \"nothing happening\".\n //\n // The turn-scoped kinds below carry one planner turn's stream (#47); see the\n // matching SessionMessage variants for the invariants each one keeps.\n // 'text_delta' is reply text as the model streams it (`text`, `segmentId`),\n // an envelope included: the turn's owner routes each segment to prose or to\n // the plan display, so no backend has to tell them apart. 'text_retracted'\n // takes back an attempt's text: `segmentId`'s, or when absent everything the\n // backend call that sends it has streamed.\n // 'usage' reports one model call (`record`). 'subagent_started' carries\n // `brief` and `model`; 'subagent_finished' carries `outcome`, `digest` and\n // `usage`. 'compaction' is one phase of the planner's own compaction\n // (`compaction`); a subagent's never is one.\n type:\n | 'thinking' | 'tool_call' | 'tool_result' | 'plan_token' | 'interrupted' | 'liveness'\n | 'text_delta' | 'text_retracted' | 'usage' | 'subagent_started' | 'subagent_finished' | 'compaction';\n /** Minted by whoever runs the turn and passed through untouched; absent outside a turn. */\n turnId?: string;\n /** One continuous run of model text — text before a tool call is its own segment. */\n segmentId?: string;\n text?: string;\n tool?: string;\n /** Harness planners (ADR-0009): the agent's own name for a tool Ordewell has no member for. */\n toolLabel?: string;\n toolArgs?: string;\n toolResult?: string;\n planToken?: string;\n step?: ResearchStep;\n /** The model's tool_call id, threaded on tool_call and tool_result so a\n * surface can match the result to its pending call — robust under parallel\n * same-tool rounds where LIFO-by-name matching mislabels summaries. */\n toolCallId?: string;\n /** Present when this event originates from (or reports on) one spawned research subagent (issue #34). */\n subagentId?: string;\n record?: UsageRecord;\n brief?: string;\n model?: string;\n outcome?: SubagentOutcome;\n digest?: string;\n usage?: UsageTotals;\n compaction?: PlannerCompaction;\n}\n\nexport interface ThinkingBlock {\n id: string;\n text: string;\n}\n\nexport interface StreamThinkingEvent {\n type: 'thinking';\n block: ThinkingBlock;\n}\n\nexport interface StreamStepEvent {\n type: 'step';\n step: ResearchStep;\n}\n\nexport type StreamEvent = StreamThinkingEvent | StreamStepEvent;\n\nexport interface DiscoveredModel {\n modelId: string;\n modelLabel: string;\n runnerProvider?: string;\n /**\n * Human-facing provider name as the runner itself reports it (e.g.\n * \"OpenCode Zen\" for `runnerProvider: 'opencode'`). Populated from the\n * runner's own provider catalog when available; when absent the UI derives a\n * label from `runnerProvider` by title-casing.\n */\n runnerProviderLabel?: string;\n /**\n * The runner whose catalog listed this model. Stamped once, at the single\n * `ModelDiscovery.discover` choke point, so a flat cross-runner list can\n * still say where each entry came from — `runnerProvider` alone cannot:\n * OpenCode reports most of its catalog as `openrouter`, which names the\n * serving backend, not the agent Ordewell would spawn.\n */\n runnerId?: string;\n /** The runner's display name (`OpenCode`), from its manifest. */\n runnerLabel?: string;\n variants: { id: string; label: string }[];\n /**\n * The model's context window when the runner or catalog reports it (#49).\n * Read by the planner-model lookup so context fill can be shown; absent when\n * unknown rather than defaulted to zero. Gemini's is its `inputTokenLimit`:\n * the prompt alone, the reply limited by `maxOutputTokens` apart from it.\n */\n contextWindow?: number;\n /** The longest reply the catalog says the model is served with, when it says. */\n maxOutputTokens?: number;\n}\n\nexport type PlanStatus = 'draft' | 'approved' | 'rejected' | 'running' | 'completed';\n\n/**\n * One entry of the planner's persisted dialogue (ADR-0002). The single source\n * of truth for both UI redisplay and conversational context. Tool-call results\n * are NOT stored here — they live in the AI service's tool-use history;\n * `researchLog` remains the persisted tool trace for the UI.\n */\nexport interface ConversationMessage {\n role: 'user' | 'assistant';\n content: string;\n timestamp: string;\n /**\n * Timeline marker: 'plan_generated' records the point in the dialogue where\n * the plan was committed (the UI anchors the plan card there on restore);\n * 'system' is a host-injected notice; 'compaction' is the summary a\n * compaction appended, which stands in for the earlier messages in the\n * planner's context while the transcript keeps them (see `checkpoint`). 'skill_load' is a skill loaded into the\n * conversation, its snapshot in `skill`; one the user invoked follows the\n * message that named it. 'execution' is a call at the command seam\n * (ADR-0029), its record in `execution`. 'wake_up' is the digest a wake-up\n * gave the supervisor (role 'user': the model was told it), and\n * 'supervisor' what the supervisor said in a wake-up turn (ADR-0029).\n * 'error' is a planner turn that ended on the planner's usage limit, drawn\n * as a failure and never replayed to the planner: it is not the planner's\n * answer, and its inputs are sent again (see {@link PlannerResendState}).\n * Absent for ordinary chat turns, so sessions saved before markers existed\n * degrade gracefully.\n */\n kind?: 'plan_generated' | 'system' | 'compaction' | 'skill_load' | 'execution' | 'wake_up' | 'supervisor' | 'error';\n actor?: 'Ordewell setting';\n /** Present exactly when `kind` is 'skill_load'. */\n skill?: SkillLoad;\n /** Present exactly when `kind` is 'execution'. */\n execution?: ExecutionRecord;\n /** A user's message that was force sent, stopping the turn before it (ADR-0023). */\n forced?: true;\n /**\n * Set on a 'compaction' entry that is a checkpoint of the planner's context\n * (ADR-0002). A 'compaction' entry without one is a legacy summary when it\n * stands first — it replaced what it condensed — and a marker drawn but\n * never replayed anywhere else.\n */\n checkpoint?: ContextCheckpoint;\n /**\n * What a reply showed, run by run, where each began (ADR-0017 A1). Beside\n * `content`, which stays the reply as the planner is told it. Absent on\n * entries saved before runs were kept, which reload as one message.\n */\n runs?: ReplyRun[];\n}\n\n/**\n * Where a compaction cut the planner's context (ADR-0002): its summary stands\n * for every transcript entry before `keptFrom`, and the entries from there on\n * replay verbatim. Entries are only appended, or inserted at a held turn's\n * position, which no checkpoint's cut passes, so the index never moves.\n */\nexport interface ContextCheckpoint {\n /** Stable across saves, forks and the wire, so a surface can tell this checkpoint from another with the same text. */\n id: string;\n /** An index into the whole transcript, at or before the checkpoint entry itself. */\n keptFrom: number;\n trigger: 'manual' | 'auto';\n}\n\n/**\n * How a planner compaction ended. `condensed`: a summary stands in for the\n * older context (a checkpoint was written, or a harness planner reported its\n * own compaction done). `pruned`: older recoverable tool output was dropped\n * from the live context alone. `unchanged`: nothing needed doing. `paused`:\n * the context could not be brought to fit, so the planner waits.\n */\nexport type PlannerCompactionOutcome = 'condensed' | 'pruned' | 'unchanged' | 'stopped' | 'failed' | 'paused';\n\n/**\n * One planner compaction's lifecycle as it was observed (ADR-0017): both\n * phases share `id`, which is the checkpoint's id when one is written. Only\n * Ordewell's own compaction announces a start; a harness planner's native one\n * is reported when it is over, and nothing invents the start it never sent.\n * Sizes are only ever what a backend reported.\n */\nexport type PlannerCompaction =\n | { phase: 'started'; id: string; origin: 'ordewell'; trigger: ContextCheckpoint['trigger']; at: string }\n | {\n phase: 'ended';\n id: string;\n origin: 'ordewell' | 'native';\n trigger: ContextCheckpoint['trigger'];\n outcome: PlannerCompactionOutcome;\n at: string;\n preTokens?: number;\n postTokens?: number;\n };\n\n/**\n * One run of a planner reply's text: the segment it streamed under, its\n * settled text, and when it began. A run that never streamed (a message the\n * conversation wrote in the planner's place) has a segment no stream used.\n */\nexport interface ReplyRun {\n segmentId: string;\n text: string;\n /** ISO timestamp. */\n startedAt: string;\n}\n\n/**\n * A skill's SKILL.md as it was when loaded into a planner conversation. A\n * snapshot rather than a reference: a resumed, forked or rewound conversation\n * replays the body the planner originally saw, even after the file changed or\n * was deleted.\n */\nexport interface SkillLoad {\n invokedBy: 'user' | 'planner';\n name: string;\n source: SkillSource;\n /** The SKILL.md that won, home-abbreviated (`~/...`): which copy loaded is what a reader needs from it. */\n path: string;\n content: string;\n /**\n * Set when the user named a task skill: it is not loaded into the planner's\n * context, which is only told to attach it to the tasks it fits. `content`\n * is empty; the description is what the planner's directive quotes.\n */\n attaches?: { description: string };\n}\n\n/** A skill load as a surface announces it — the body stays with the transcript. */\nexport type SkillLoadNotice = Omit<SkillLoad, 'content'>;\n\n/** A message the user sent, as opposed to a skill load their message caused or a wake-up digest. */\nexport function isUserMessage(entry: Pick<ConversationMessage, 'role' | 'kind'>): boolean {\n return entry.role === 'user' && entry.kind !== 'skill_load' && entry.kind !== 'wake_up';\n}\n\n/** A follow-up an older build queued behind a run's batch boundary; read only to carry it over as a {@link PendingPlanTurn}. */\nexport interface QueuedMessage {\n id: string;\n text: string;\n timestamp: string;\n skills?: SkillLoad[];\n}\n\n/** Who a planner turn answers (ADR-0029): the user's message, or a wake-up batch the supervisor is given. */\nexport type PlanTurnSource = 'user' | 'supervisor';\n\n/**\n * A planner turn's input not yet answered by a turn that ended. It leaves the\n * plan with the change its turn made, in the same save, so a reload replays\n * exactly the inputs no turn answered — once. A supervisor's input is a\n * wake-up batch not yet delivered (ADR-0029): it stays until a turn lands\n * with it, whatever turns fail meanwhile.\n */\nexport interface PendingPlanTurn {\n id: string;\n source: PlanTurnSource;\n /** The user's message, or a wake-up batch's digest. */\n text: string;\n timestamp: string;\n /** What its `/name` tokens loaded when it was sent, so a replay sends the same skills. */\n skills?: SkillLoad[];\n /** Force sent: it waits ahead of every input not forced, and its turn is its own (ADR-0023, F2). */\n forced?: true;\n /**\n * A supervisor input carrying a report the planner asked for (ADR-0037, R2),\n * which opens a turn at every supervisor level: `user` for a task started in\n * the user's turn, which the turn then acts as (R3).\n */\n report?: TurnAuthority;\n /**\n * The tasks a supervisor input is wholly about, absent when any of it is about\n * the run. While the user has stopped all of them it opens no turn (ADR-0029).\n */\n tasks?: string[];\n /** It goes with the next turn and opens none (ADR-0057 R6). */\n rides?: true;\n}\n\n/**\n * The run's own copy of the user's run settings (ADR-0030): taken when the\n * user starts it, changed at once by a change made in this session, and\n * lowered by a tripped guard or a failed supervisor.\n */\nexport interface RunSettings {\n mergeAtGatesAutomatically: boolean;\n /** Absent on a run started before supervisor levels existed: Off. */\n supervisorLevel?: SupervisorLevel;\n /** Absent on a run started before ADR-0030: the user's setting. */\n supervisorRetryLimit?: number;\n /** Absent on a run started before ADR-0030: the user's setting. */\n wakeUpReadBudget?: number;\n /** Why the supervisor stopped for the rest of this run, which carries on as Off; absent while it watches. */\n unsupervised?: string;\n}\n\n/**\n * What supervision remembers across a daemon restart (ADR-0029): the loop\n * guards' memory and the wake-up state not yet delivered with a turn.\n */\nexport interface SupervisionSnapshot {\n /** Retries, failed-attempt shapes, the supervisor's edits and what the user was told. */\n guards?: SupervisionMemorySnapshot;\n /** The events inside their debounce window and the wake-up rate's timestamps. */\n wake?: SupervisorSnapshot;\n}\n\n/**\n * What waits on the planner's usage limit. With `at`, the inputs no turn\n * answered are sent again then, on their own; without it — the reset could\n * not be read, the resend hit the limit again, or it was cancelled — they go\n * with the next input: the user's message, or a wake-up.\n */\nexport interface PlannerResendState {\n /** The runner whose account hit the limit; a planner on another one is not held by it. */\n runner?: RunnerId;\n at?: string;\n}\n\n/**\n * The planner paused on its context (ADR-0002): an API planner's call could\n * not be brought inside its model's window, so the turn ended with nothing\n * landed and the planner waits for the user's Retry compaction. What it\n * keeps is bounded: the inputs stay in `pendingTurns`, and only the calls\n * that changed something are listed, never the live context itself.\n */\nexport interface PlannerContextPause {\n /** The model the call did not fit. */\n model: string;\n at: string;\n /** What the user is told: why it paused, and what Retry compaction does. */\n reason: string;\n /** The pending inputs the paused turn took: a retry answers these, and what arrived since waits for the turn after it. */\n inputs: string[];\n /**\n * The calls the paused turn made that changed something or reached out,\n * each with the head of what it returned (`effectLedger`). It is never\n * replayed to the planner as a note: it is what lets a retry see that a\n * turn which ran effects cannot start again from the transcript, and what\n * the refusal lists for the user.\n */\n done?: string[];\n /** More such calls ran than `done` keeps: the refusal could not list all of them, and a retry without the live context stays refused. */\n doneTruncated?: true;\n}\n\nexport interface LegacyPlanState {\n /** Research profiles the planner approved as they landed: lifting one withdraws its approval (ADR-0037 R4). */\n researchApprovals?: string[];\n runSettings?: RunSettings;\n /** What supervision remembers across a daemon restart (ADR-0029); absent on plans saved before it existed. */\n supervision?: SupervisionSnapshot;\n tasks: Task[];\n generatedAt: string;\n status: PlanStatus;\n runners: RunnerId[];\n lastUpdated: string;\n researchLog?: ResearchLogEntry[];\n /** The planner dialogue — user messages and assistant messages, in order (ADR-0002). */\n conversationHistory?: ConversationMessage[];\n /** Full markdown PRD once written by the planner (PRD mode), also saved to .scratch/<slug>/PRD.md. */\n prdMarkdown?: string;\n /** Planner turn inputs no turn has answered yet (ADR-0029); a reload replays them. */\n pendingTurns?: PendingPlanTurn[];\n /** Written by builds that queued mid-run edits; a load moves it into {@link pendingTurns}. */\n queuedMessages?: QueuedMessage[];\n /**\n * The plan's isolation run (ADR-0013), written from the orchestrator at\n * persist time and read back only when a saved plan is adopted. It names\n * branches and worktrees that belong to this plan alone: a fork of the plan\n * must leave it behind rather than share it.\n */\n isolation?: PlanIsolation;\n /**\n * The id of the plan's last isolated run, which closed with everything it\n * landed merged into the user's branch; absent once a later run decides how\n * it executes. With no run record left, it is what tells a Merge all asked\n * for after that run from one in a plan that never isolated (ADR-0029).\n */\n mergedRun?: string;\n /** Kept so a reopened session shows the same token line (#49). */\n plannerUsage?: PlannerUsage;\n /** The windows reported over the session, so a reopened one knows each model's from its first call. */\n contextWindows?: ReportedWindow[];\n /** The planner hit its usage limit and the inputs its turn took wait to be sent again; absent otherwise. */\n plannerResend?: PlannerResendState;\n /** The planner's context could not fit its model's window: its turn's inputs wait for Retry compaction; absent otherwise. */\n plannerContextPause?: PlannerContextPause;\n /**\n * The tasks the user approved to run: every task when they executed the\n * plan, and each one they started or retried themselves since. A command\n * from anyone else starts only these (ADR-0029); a task the planner added\n * after the approval waits for the user's. Only tasks still in the plan: a\n * task that leaves takes its approval with it.\n */\n approvedTasks?: string[];\n /**\n * The user approved the plan to run: Execute plan, signing off a review, or\n * autorun's start (ADR-0029, ADR-0030). Kept apart from {@link status},\n * which surfaces rewrite for display; a user's start of one task approves\n * that task, not the plan.\n */\n approved?: boolean;\n}\n\nexport interface Message {\n id: string;\n role: 'user' | 'planner' | 'system';\n content: string;\n timestamp: number;\n}\n\nexport interface TaskSnapshot extends Task {\n completedAt: number;\n verdict?: Verdict;\n retryCount: number;\n finalized: boolean;\n}\n\n/** The plan a surface reads whole carries the approval record, so it can tell which tasks wait for the user (ADR-0035). */\nexport type PlanState = (\n | {\n phase: 'planning';\n history: Message[];\n message: string;\n pendingTasks: Task[];\n }\n | {\n phase: 'executing';\n history: Message[];\n message: string;\n executionLog: TaskSnapshot[];\n pendingTasks: Task[];\n goal: string;\n runners: string[];\n status: PlanStatus;\n }\n) & ApprovalRecord;\n\nfunction hasPhase(raw: unknown): raw is PlanState {\n return typeof raw === 'object' && raw !== null && 'phase' in raw;\n}\n\nfunction hasTasks(raw: unknown): raw is LegacyPlanState {\n return typeof raw === 'object' && raw !== null && 'tasks' in raw && Array.isArray((raw as Record<string, unknown>).tasks);\n}\n\nfunction logEntryToMessage(entry: ResearchLogEntry): Message {\n const timestamp = new Date(entry.timestamp).getTime();\n if (!('type' in entry)) {\n return { id: entry.id, role: 'system', content: JSON.stringify({ tool: entry.tool, args: entry.args, result: entry.result }), timestamp };\n }\n if (entry.type === 'subagent') {\n const { subagentId, brief, outcome, digest } = entry;\n return { id: entry.id, role: 'system', content: JSON.stringify({ subagentId, brief, outcome, digest }), timestamp };\n }\n return { id: entry.id, role: entry.type === 'user_prompt' ? 'user' : 'system', content: entry.content, timestamp };\n}\n\nfunction migrateHistory(raw: LegacyPlanState): Message[] {\n const messages: Message[] = [];\n\n if (raw.researchLog) {\n messages.push(...raw.researchLog.map(logEntryToMessage));\n }\n\n if (raw.queuedMessages) {\n for (const qm of raw.queuedMessages) {\n messages.push({\n id: qm.id,\n role: 'system',\n content: qm.text,\n timestamp: new Date(qm.timestamp).getTime(),\n });\n }\n }\n\n return messages;\n}\n\nexport function migratePlanState(raw: unknown): PlanState {\n if (hasPhase(raw)) return raw;\n\n if (!hasTasks(raw)) {\n return {\n phase: 'planning',\n history: [],\n message: '',\n pendingTasks: [],\n };\n }\n\n const hasExecution = raw.tasks.some(\n (t) => t.status === 'completed' || t.status === 'failed',\n );\n\n const messages = migrateHistory(raw);\n const record = approvalRecordOf(raw);\n\n if (hasExecution) {\n const executionLog: TaskSnapshot[] = raw.tasks\n .filter((t) => t.status === 'completed' || t.status === 'failed')\n .map((t) => ({\n ...t,\n completedAt: new Date(raw.lastUpdated).getTime(),\n verdict: t.verdict,\n retryCount: 0,\n finalized: true,\n }));\n\n const pendingTasks = raw.tasks.filter(\n (t) => t.status !== 'completed' && t.status !== 'failed',\n );\n\n return {\n phase: 'executing',\n history: messages,\n message: '',\n executionLog,\n pendingTasks,\n goal: '',\n runners: raw.runners,\n status: raw.status,\n ...record,\n };\n }\n\n return {\n phase: 'planning',\n history: messages,\n message: '',\n pendingTasks: raw.tasks,\n ...record,\n };\n}\n\nexport { taskOrderLabel, taskRef, taskRefOf, titledTaskRef, titledTaskRefOf, resolveTaskRef, resolveUserTaskRef } from '../services/TaskRefs';\n\nexport function createTask(overrides: Partial<Task> = {}): Task {\n const skills = skillNames(overrides.skills);\n const type = overrides.type ?? 'ai';\n return {\n id: overrides.id ?? uuidv4(),\n order: overrides.order ?? 0,\n title: overrides.title ?? '',\n description: overrides.description ?? '',\n type,\n status: overrides.status ?? 'pending',\n dependencies: overrides.dependencies ?? [],\n prompt: overrides.prompt,\n userSteps: overrides.userSteps,\n subtasks: overrides.subtasks ?? [],\n verdict: overrides.verdict,\n outputSummary: overrides.outputSummary,\n ...(overrides.handoff ? { handoff: structuredClone(overrides.handoff) } : {}),\n ...(overrides.attempted ? { attempted: true as const } : {}),\n ...(overrides.attemptUsage ? { attemptUsage: structuredClone(overrides.attemptUsage) } : {}),\n assignedModel: overrides.assignedModel,\n assignedRunner: overrides.assignedRunner ?? DEFAULT_RUNNERS[0],\n // A script task runs in no mode (ADR-0035): one given it would read as a mode it runs in.\n taskMode: overrides.taskMode ?? (type === 'script' || type === 'delegated' ? undefined : 'build'),\n autonomy: overrides.autonomy,\n sliceType: overrides.sliceType,\n userStoriesCovered: overrides.userStoriesCovered,\n ...(opsFlag(overrides.ops, overrides.type) ? { ops: true } : {}),\n ...(skills ? { skills } : {}),\n ...(overrides.addedBy === 'supervisor' ? { addedBy: 'supervisor' as const } : {}),\n ...(overrides.commands !== undefined ? { commands: overrides.commands.map((argv) => [...argv]) } : {}),\n ...(overrides.script !== undefined ? { script: overrides.script } : {}),\n ...(overrides.timeoutMs !== undefined ? { timeoutMs: overrides.timeoutMs } : {}),\n ...(overrides.goal !== undefined ? { goal: overrides.goal } : {}),\n ...(overrides.budget !== undefined ? { budget: { ...overrides.budget } } : {}),\n ...(overrides.grants !== undefined ? { grants: [...overrides.grants] } : {}),\n ...(overrides.protectedPaths !== undefined ? { protectedPaths: [...overrides.protectedPaths] } : {}),\n ...(overrides.scope !== undefined ? { scope: structuredClone(overrides.scope) } : {}),\n ...(overrides.readOnly ? { readOnly: true as const } : {}),\n ...(overrides.loop ? { loop: { body: [...overrides.loop.body], maxRounds: overrides.loop.maxRounds } } : {}),\n ...(overrides.loopRound !== undefined ? { loopRound: overrides.loopRound } : {}),\n ...(overrides.research !== undefined ? { research: overrides.research } : {}),\n };\n}\n\n/**\n * A name a skill can have, lower-cased as `/name` is. Anything else — a path\n * separator, `..` — is joined into a path and could only reach outside the\n * skill dirs.\n */\nconst SKILL_NAME = new RegExp(`^${SKILL_NAME_PATTERN}$`);\n\nexport function isSkillName(name: string): boolean {\n return SKILL_NAME.test(name);\n}\n\n/**\n * The `skills` field as a task stores it, however it arrived: the planner's\n * JSON is untyped. Names are trimmed, lower-cased and deduplicated, and one\n * no skill could have is dropped; none at all is stored absent, so a plan\n * without skills reads exactly as it did before them.\n */\nexport function skillNames(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const names = [...new Set(value.filter((v): v is string => typeof v === 'string').map((v) => v.trim().toLowerCase()).filter(isSkillName))];\n return names.length > 0 ? names : undefined;\n}\n\n/**\n * The skills of a task a merge or a split makes from others: what the edit\n * chose when it named any, none when it chose an explicit `[]`, else what the\n * tasks it came from had — a malformed value is not a request to drop them.\n */\nexport function inheritedSkills(chosen: unknown, inherited: readonly string[] | undefined): string[] | undefined {\n if (Array.isArray(chosen) && chosen.length === 0) return undefined;\n return skillNames(chosen) ?? skillNames(inherited);\n}\n\n/**\n * The `ops` flag as a task stores it (ADR-0020), however it arrived. Only a\n * literal `true` on an AI task marks an ops task: the planner's JSON is\n * untyped, and a quoted \"true\" read as ops would move a task out of its\n * worktree. Anything else is a change task, stored absent so saved plans need\n * no migration. Whether the task may be ops at all — a subtask may not — is\n * for whoever knows where it sits: the planner intake refuses it (`checkPlanInvariants`).\n */\nexport function opsFlag(value: unknown, type: Task['type'] = 'ai'): true | undefined {\n return value === true && type === 'ai' ? true : undefined;\n}\n\n/**\n * Whether a task runs as an ops task (ADR-0020): an AI task marked so, or a\n * script task, which is one by nature and never marked (ADR-0035), unless it\n * is read-only: that one runs sealed in a throwaway worktree (ADR-0055 D8).\n */\nexport function runsAsOps(task: Pick<Task, 'ops' | 'type' | 'readOnly'>): boolean {\n return (task.type === 'script' && !task.readOnly) || opsFlag(task.ops, task.type) === true;\n}\n\n/**\n * The flag of a task a merge or a split makes from others: what the edit\n * chose, else ops only when every task it came from was. A merge that takes\n * in a change edits files, so it is a change; a split part runs where its\n * task did.\n */\nexport function inheritedOps(from: readonly Pick<Task, 'ops'>[], chosen?: boolean): boolean {\n return chosen ?? from.every((t) => opsFlag(t.ops) === true);\n}\n\n/**\n * The runner set a plan has until one is chosen. The one runner named outside\n * its declaration on purpose: a default is a product choice that no manifest\n * or connector implies.\n */\nexport const DEFAULT_RUNNERS: readonly RunnerId[] = ['claude-code'];\n\nexport function createEmptyPlan(): LegacyPlanState {\n return {\n tasks: [],\n generatedAt: new Date().toISOString(),\n status: 'draft',\n runners: [...DEFAULT_RUNNERS],\n lastUpdated: new Date().toISOString(),\n };\n}\n\nexport function flattenTasks(tasks: readonly Task[]): Task[] {\n return [...walkPlan(tasks)].map(({ task }) => task);\n}\n\n/**\n * A phase-tagged plan rejoined into its tree. The execution log is flat — a\n * finished subtask is logged on its own as well as inside its parent — and in\n * the order tasks finished, so read as a list it puts subtasks at the top level\n * under orders that clash with real top-level tasks. A task's own entry is the\n * latest word on it: its parent's copy was taken when the parent last changed.\n */\nexport function rejoinPlanTree<T extends { id: string; order: number; subtasks?: T[] }>(pending: readonly T[], log: readonly T[]): T[] {\n const latest = new Map<string, T>();\n for (const task of [...pending, ...log]) latest.set(task.id, task);\n const nested = new Set<string>();\n const markNested = (task: T): void => {\n for (const sub of task.subtasks ?? []) {\n nested.add(sub.id);\n markNested(sub);\n }\n };\n for (const task of latest.values()) markNested(task);\n const rebuild = (task: T): T => {\n const own = latest.get(task.id) ?? task;\n return own.subtasks?.length ? { ...own, subtasks: own.subtasks.map(rebuild) } : own;\n };\n return [...latest.values()].filter((task) => !nested.has(task.id)).map(rebuild).sort((a, b) => a.order - b.order);\n}\n\n/**\n * A flattened task with the parent it hangs under, null for a top-level task,\n * and the path label and reference (`#2.1.3`) `TaskRefs` renders for it.\n */\nexport interface TaskWithParent {\n task: Task;\n parent: Task | null;\n label: string;\n ref: string;\n}\n\nexport function flattenTasksWithParents(tasks: readonly Task[]): TaskWithParent[] {\n return [...walkPlan(tasks)].map(({ task, parent, label }) => ({ task, parent: parent ?? null, label, ref: `#${label}` }));\n}\n\nexport function addTaskToPlan(tasks: readonly Task[], partial: Partial<Task>): Task[] {\n const maxOrder = tasks.reduce((max, t) => Math.max(max, t.order), 0);\n const newTask = createTask({\n ...partial,\n order: partial.order ?? maxOrder + 1,\n status: 'pending',\n });\n return renumberTasks([...tasks, newTask]);\n}\n\n/**\n * A removed task leaves every dependency on it, and every loop's body it was\n * in (ADR-0045 L8): a loop it leaves with no body is no loop. Whether its\n * removal may go ahead at all — a loop's check may not be removed — is the\n * caller's to say.\n */\nexport function removeTaskFromPlan(tasks: readonly Task[], taskId: string): Task[] {\n const removed = subtreeIds(tasks, taskId);\n const prune = (level: readonly Task[]): Task[] => level\n .filter((t) => !removed.has(t.id))\n .map((t) => {\n const pruned: Task = {\n ...t,\n dependencies: t.dependencies.filter((depId) => !removed.has(depId)),\n subtasks: prune(t.subtasks),\n };\n if (!t.loop?.body.some((id) => removed.has(id))) return pruned;\n const body = t.loop.body.filter((id) => !removed.has(id));\n if (body.length > 0) return { ...pruned, loop: { ...t.loop, body } };\n delete pruned.loop;\n delete pruned.loopRound;\n return pruned;\n });\n return renumberTasks(prune(tasks));\n}\n\nexport function updateTaskInPlan(tasks: readonly Task[], taskId: string, changes: Partial<Task>): Task[] {\n return renumberTasks(\n tasks.map((t) => {\n if (t.id === taskId) return { ...t, ...changes, id: t.id };\n return { ...t, subtasks: updateTaskInPlan(t.subtasks, taskId, changes) };\n })\n );\n}\n\nexport function renumberTasks(tasks: readonly Task[]): Task[] {\n return tasks.map((t, i) => ({\n ...t,\n order: i + 1,\n subtasks: renumberTasks(t.subtasks),\n }));\n}\n\n/** A task waiting on the user with its attempt still live: the runner sits at a checkpoint or for input. */\nexport function waitsMidAttempt(task: Pick<Task, 'status' | 'awaitingReason'>): boolean {\n return task.status === 'awaiting_user' && (task.awaitingReason === 'checkpoint' || task.awaitingReason === 'input');\n}\n\n/** A task paused on its runner's usage limit, whether it resumes on its own or waits for the user (#84). */\nexport function pausedOnLimit(task: Readonly<Task>): boolean {\n return task.status === 'awaiting_user' && task.awaitingReason === 'usage-limit';\n}\n\n/** A task paused on a usage limit that resumes on its own when the limit resets (#84). */\nexport function waitsForReset(task: Readonly<Task>): boolean {\n return pausedOnLimit(task) && !!task.resumeAt;\n}\n\n/** The user's approval record, as a plan carries it (ADR-0029). */\nexport type ApprovalRecord = Pick<LegacyPlanState, 'approved' | 'approvedTasks'>;\n\n/** A copy of the plan's approval record, for a plan read whole; a field the plan lacks stays absent. */\nexport function approvalRecordOf(plan: ApprovalRecord): ApprovalRecord {\n return {\n ...(plan.approved !== undefined ? { approved: plan.approved } : {}),\n ...(plan.approvedTasks ? { approvedTasks: [...plan.approvedTasks] } : {}),\n };\n}\n\n/**\n * A task the scheduler leaves for the user (ADR-0035): an AI or script task\n * not started yet, in a plan they approved, outside their approval record —\n * the planner added it since, or changed a script's body. It starts once they\n * start it or execute the plan. In a plan they never approved nothing is\n * theirs yet, so nothing is singled out.\n */\nexport function waitsForApproval(task: Pick<Task, 'id' | 'type' | 'status'>, record: ApprovalRecord): boolean {\n return record.approved === true\n && task.type !== 'user'\n && (task.status === 'pending' || task.status === 'approved')\n && !(record.approvedTasks ?? []).includes(task.id);\n}\n\n/**\n * Who put each task of an adopted list in the plan. A task the plan already\n * had keeps its origin, whatever the planner wrote; a new one is the\n * supervisor's when `bySupervisor`, and nobody's in particular otherwise, so\n * no planner can mark its own task as the supervisor's. `added` lists the new\n * tasks, subtasks included.\n */\nexport function stampOrigin(before: readonly Task[], after: readonly Task[], bySupervisor: boolean): { tasks: Task[]; added: string[] } {\n const prior = new Map(flattenTasks([...before]).map((t) => [t.id, t]));\n const added: string[] = [];\n const stamp = (tasks: readonly Task[]): Task[] => tasks.map((t) => {\n const was = prior.get(t.id);\n const origin = was ? was.addedBy : bySupervisor ? 'supervisor' : undefined;\n if (!was) added.push(t.id);\n const stamped: Task = { ...t, subtasks: stamp(t.subtasks ?? []) };\n if (origin) stamped.addedBy = origin;\n else delete stamped.addedBy;\n return stamped;\n });\n return { tasks: stamp(after), added };\n}\n\n/** Done, running, or held for the user: execution state only a runner or the user can change. */\nconst SETTLED_STATUSES: ReadonlySet<TaskStatus> = new Set<TaskStatus>(['completed', 'in_progress', 'awaiting_user']);\n\nfunction holdsSettledWork(task: Task): boolean {\n return SETTLED_STATUSES.has(task.status) || (task.subtasks ?? []).some(holdsSettledWork);\n}\n\n/**\n * Lay a planner-written task list over the plan it rewrites without letting it\n * change execution state. A planner restates tasks; it never witnessed one run,\n * so the status it writes is not evidence. A settled task is kept exactly as\n * it stands wherever the rewrite names it, and put back beside its old\n * neighbour where the rewrite leaves it out. Every other task keeps the status\n * it had; a task the rewrite adds starts pending. A failed task the rewrite\n * turns into a script task or out of one is kept as it was: what ran, and\n * where, is fixed once it has run (ADR-0020, ADR-0035).\n */\nexport function keepExecutionState(current: readonly Task[], rewrite: Task[]): Task[] {\n const existing = new Map(flattenTasks(current).map((t) => [t.id, t]));\n const named = new Set(flattenTasks(rewrite).map((t) => t.id));\n\n const overlay = (tasks: Task[], was: readonly Task[]): Task[] => {\n const result = tasks.map((t): Task => {\n const prior = existing.get(t.id);\n if (prior && SETTLED_STATUSES.has(prior.status)) return { ...prior };\n if (prior?.status === 'failed' && (prior.type === 'script') !== (t.type === 'script')) return { ...prior };\n return {\n ...t,\n status: prior?.status ?? 'pending',\n skipReason: prior?.skipReason,\n reverted: prior?.reverted,\n verdict: prior?.verdict,\n outputSummary: prior?.outputSummary,\n handoff: prior?.handoff,\n runnerSessionId: prior?.runnerSessionId,\n runnerSessionOwner: prior?.runnerSessionOwner,\n carriedFrom: prior?.carriedFrom,\n attemptSkills: prior?.attemptSkills,\n attempted: prior?.attempted,\n attemptUsage: prior?.attemptUsage,\n loopRound: prior?.loopRound,\n waitsEnded: prior?.waitsEnded,\n // Where a task that has run ran is fixed, like its runner session (ADR-0020).\n ...(prior?.status === 'failed' ? { ops: prior.ops } : {}),\n forcedPastGate: prior?.forcedPastGate,\n reportTo: prior?.reportTo,\n researchOrigin: prior?.researchOrigin,\n subtasks: overlay(t.subtasks ?? [], prior?.subtasks ?? []),\n };\n });\n was.forEach((left, i) => {\n if (named.has(left.id) || !holdsSettledWork(left)) return;\n const neighbour = was.slice(0, i).reverse().find((t) => result.some((r) => r.id === t.id));\n const at = neighbour ? result.findIndex((r) => r.id === neighbour.id) + 1 : 0;\n result.splice(at, 0, { ...left });\n });\n return result;\n };\n\n return renumberTasks(overlay(rewrite, current));\n}\n","/** A name a skill can have, lower-cased: the one pattern for a `/name` token and for a skill folder. */\nexport const SKILL_NAME_PATTERN = '[a-z0-9][a-z0-9_-]*';\n\n/**\n * A `/skill-name` token anywhere in a message: whitespace (or string start)\n * before it, a skill name in any case, optional trailing punctuation that\n * isn't part of the name, then whitespace (or string end). The punctuation\n * group is what lets \"/grilling,\" name \"grilling\".\n */\nconst SKILL_TOKEN = new RegExp(`(^|\\\\s)\\\\/(${SKILL_NAME_PATTERN})([,.!?;:]*)(?=\\\\s|$)`, 'gi');\n\n/** Where a skill name is invoked: `start` is its `/`, `end` is just past the name, before any punctuation. */\nexport interface SkillToken {\n name: string;\n start: number;\n end: number;\n}\n\n/**\n * The first `/name` token for each name in `text`, names lower-cased. Only\n * the first counts because a skill loads once per message however often it is\n * named, so a later repeat is plain text.\n */\nexport function skillTokens(text: string): SkillToken[] {\n const tokens: SkillToken[] = [];\n const seen = new Set<string>();\n for (const match of text.matchAll(SKILL_TOKEN)) {\n const name = match[2].toLowerCase();\n if (seen.has(name)) continue;\n seen.add(name);\n const start = match.index + match[1].length;\n tokens.push({ name, start, end: start + 1 + match[2].length });\n }\n return tokens;\n}\n\n/** The tokens in a sent message that loaded one of `loaded` — what a surface highlights in the user's bubble. */\nexport function loadedSkillTokens(text: string, loaded: readonly string[]): SkillToken[] {\n return skillTokens(text).filter((t) => loaded.includes(t.name));\n}\n","/**\n * The path of the repo in a workspace that is itself the repository. Kept in\n * its own module, with no imports, so a browser surface can word a merge\n * result without pulling in the isolation record's `path` dependency.\n */\nexport const SELF_REPO = '.';\n","import * as path from 'path';\nimport type {\n IsolationHandoff,\n IsolationLandedTask,\n IsolationRemoval,\n IsolationRepo,\n IsolationRun,\n IsolationTaskRecord,\n IsolationTaskStatus,\n IsolationView,\n PlanIsolation,\n PreservedWork,\n RepoGroupLayout,\n TaskIsolation,\n} from '../interfaces/IWorktreeIsolation';\nimport { SELF_REPO } from './repoPath';\n\nexport { SELF_REPO };\n\nexport function integrationBranchFor(runId: string): string {\n return `ordewell/${runId}/integration`;\n}\n\nexport function repoRootOf(workspaceRoot: string, repoPath: string): string {\n return path.join(workspaceRoot, repoPath);\n}\n\n/** A removal that kept nothing back. */\nexport function noRemoval(): IsolationRemoval {\n return { preserved: [], refused: [] };\n}\n\n/**\n * A `prepare` that failed after it had already replaced the last attempt of\n * the task: that attempt's unlanded work was kept on `preserved` first, and no\n * record names it any more, so whoever tells the user about the failure has to\n * tell them where it went. Reads as the failure it wraps.\n */\nexport class PrepareFailedError extends Error {\n constructor(readonly failure: unknown, readonly preserved: PreservedWork[]) {\n super(failure instanceof Error ? failure.message : String(failure));\n this.name = failure instanceof Error ? failure.name : 'Error';\n this.cause = failure;\n }\n}\n\n/** Add what `from` kept back to `into`. */\nexport function absorbRemoval(into: IsolationRemoval, from: IsolationRemoval): IsolationRemoval {\n into.preserved.push(...from.preserved);\n into.refused.push(...from.refused);\n return into;\n}\n\n/** The group a run isolates, as the planner is told it. */\nexport function layoutOf(run: IsolationRun): RepoGroupLayout {\n return { repos: run.repos.map((r) => r.path), shared: [...run.shared] };\n}\n\nconst ISOLATION_STATE: Record<IsolationTaskStatus, Exclude<TaskIsolation['state'], 'none'>> = {\n active: 'active',\n merged: 'integrated',\n conflict: 'conflict',\n repairing: 'repairing',\n kept: 'kept',\n failed: 'kept',\n};\n\n/** Whether a saved record's status is one this build knows; a newer daemon may have written one it does not. */\nconst isKnownStatus = (status: string): status is IsolationTaskStatus =>\n Object.prototype.hasOwnProperty.call(ISOLATION_STATE, status);\n\n/** Paths of the repos a task brought commits to. */\nexport function changedReposOf(record: IsolationTaskRecord): string[] {\n return Object.entries(record.repos).filter(([, r]) => r.changed).map(([repoPath]) => repoPath);\n}\n\n/** Whether a landed task has work in a repo: a later attempt's record says nothing of the merges before it. */\nexport function landedInRepo(run: IsolationRun, record: IsolationTaskRecord, repoPath: string): boolean {\n return !!record.repos[repoPath]?.changed || run.landings?.[record.taskId]?.[repoPath] !== undefined;\n}\n\n/**\n * Landed work a revert can undo (ADR-0054): a merge of its own in every repo it\n * changed, none of it in the user's branch yet, and nothing of it carried in by\n * a resolver. `landings` are the task's merges in {@link IsolationRun.landings},\n * which a later attempt's record says nothing of: a task at work again keeps its\n * mark off, and a revert would refuse it. A run with no `carried` record, or\n * none given, offers nothing: every revert of such a run is refused.\n */\nfunction revertible(record: IsolationTaskRecord, run?: Pick<IsolationRun, 'landings' | 'carried'>): boolean {\n if (record.inHead || record.status === 'active' || record.status === 'repairing' || !run?.carried || run.carried[record.taskId]) return false;\n const landings = run?.landings?.[record.taskId];\n const changed = record.status === 'merged' ? changedReposOf(record) : [];\n return (changed.length > 0 || landings !== undefined) && changed.every((repo) => record.repos[repo].landedTip || landings?.[repo]);\n}\n\n/**\n * `repairLimit` is `conflictRepairAttempts` as it stands now, which is what the\n * next repair will be held to. Without one — a saved plan never carries server\n * config — the repair attempt is left out and a live status update fills it in.\n */\nexport function taskIsolationOf(record: IsolationTaskRecord, repairLimit?: number, run?: Pick<IsolationRun, 'landings' | 'carried'>): TaskIsolation {\n return {\n state: ISOLATION_STATE[record.status],\n branch: record.branch,\n worktree: record.workspace,\n repos: changedReposOf(record),\n ...(record.conflictRepo ? { conflictRepo: record.conflictRepo } : {}),\n ...(record.conflictFiles?.length ? { conflictFiles: record.conflictFiles } : {}),\n ...(record.repairs && repairLimit !== undefined ? { repair: { attempt: record.repairs, limit: repairLimit } } : {}),\n ...(record.repairedFiles?.length ? { repairedFiles: record.repairedFiles } : {}),\n ...(revertible(record, run) ? { revertible: true as const } : {}),\n };\n}\n\n/**\n * Every task's mark in a run. A task with merges on the branch and no record\n * left, a retry having dropped it, has no state to show and can still be\n * reverted. A status this build does not know is its own mark-less task, not a\n * crash: a newer daemon may have written one.\n */\nexport function taskIsolationsOf(run: IsolationRun, repairLimit?: number): Record<string, TaskIsolation> {\n const tasks: Record<string, TaskIsolation> = {};\n for (const taskId of Object.keys(run.landings ?? {})) {\n if (run.carried && !run.carried[taskId]) tasks[taskId] = { state: 'none', revertible: true };\n }\n for (const [taskId, record] of Object.entries(run.tasks)) {\n if (isKnownStatus(record.status)) tasks[taskId] = taskIsolationOf(record, repairLimit, run);\n }\n return tasks;\n}\n\n/**\n * What a run hands over: each repo's integration branch and base, and what\n * landed, in plan order. Work the user has merged already, at a merge gate\n * (ADR-0020), is not handed over again.\n */\nexport function handoffOf(run: IsolationRun): IsolationHandoff {\n const merged = Object.values(run.tasks)\n .filter((r) => r.status === 'merged' && !r.inHead)\n .sort((a, b) => a.order - b.order);\n const entry = (r: IsolationTaskRecord): IsolationLandedTask => ({\n taskId: r.taskId, order: r.order, title: r.title,\n ...(r.repairedFiles?.length ? { repairedFiles: r.repairedFiles } : {}),\n });\n return {\n repos: run.repos.map((repo) => ({\n path: repo.path,\n integrationBranch: repo.integrationBranch,\n baseRef: repo.baseRef,\n ...(repo.baseBranch ? { baseBranch: repo.baseBranch } : {}),\n ...(repo.startedDetached ? { startedDetached: true as const } : {}),\n landed: merged.filter((r) => landedInRepo(run, r, repo.path)).map(entry),\n })),\n landed: merged.map(entry),\n };\n}\n\n/** A task record as ADR-0013 persisted it, for one repository. */\nexport interface Adr0013TaskRecord {\n taskId: string;\n order: number;\n title: string;\n branch: string;\n worktree: string;\n status: IsolationTaskStatus;\n linked: string[];\n}\n\n/** A run as ADR-0013 persisted it (0.4.23): one repository, its refs on the run itself. */\nexport interface Adr0013IsolationRun {\n id: string;\n workspaceRoot: string;\n baseRef: string;\n baseBranch?: string;\n integrationBranch: string;\n tasks: Record<string, Adr0013TaskRecord>;\n}\n\nexport interface Adr0013PlanIsolation {\n run: Adr0013IsolationRun;\n resolvers: Record<string, string>;\n}\n\nfunction isAdr0013(run: IsolationRun | Adr0013IsolationRun): run is Adr0013IsolationRun {\n return 'integrationBranch' in run && typeof run.integrationBranch === 'string';\n}\n\nfunction fromAdr0013Task(r: Adr0013TaskRecord): IsolationTaskRecord {\n return {\n taskId: r.taskId,\n order: r.order,\n title: r.title,\n branch: r.branch,\n workspace: r.worktree,\n status: r.status,\n // A merged task's branch was merged into the one repo there was; what it\n // changed was never recorded, and landing is what `changed` stands for.\n repos: { [SELF_REPO]: { worktree: r.worktree, linked: r.linked ?? [], ...(r.status === 'merged' ? { changed: true } : {}) } },\n ...(r.status === 'conflict' ? { conflictRepo: SELF_REPO } : {}),\n };\n}\n\n/**\n * A persisted plan isolation in today's shape. A run saved in the ADR-0013\n * format — recognised by its own `integrationBranch` — becomes a group of one\n * at `.`, keeping its branches, so a session saved by 0.4.23 resumes and hands\n * off exactly as it would have.\n */\nexport function migratePlanIsolation(state: PlanIsolation | Adr0013PlanIsolation): PlanIsolation {\n const { run, resolvers } = state;\n if (!isAdr0013(run)) return { run, resolvers: resolvers ?? {} };\n const repo: IsolationRepo = {\n path: SELF_REPO,\n root: repoRootOf(run.workspaceRoot, SELF_REPO),\n baseRef: run.baseRef,\n ...(run.baseBranch ? { baseBranch: run.baseBranch } : {}),\n integrationBranch: run.integrationBranch,\n };\n const tasks = Object.fromEntries(Object.entries(run.tasks ?? {}).map(([id, r]) => [id, fromAdr0013Task(r)]));\n return { run: { id: run.id, workspaceRoot: run.workspaceRoot, repos: [repo], shared: [], sharedRepos: [], tasks }, resolvers: resolvers ?? {} };\n}\n\n/** Bring a loaded plan's isolation record, if any, to today's shape in place. */\nexport function migratePlanStateIsolation(plan: { isolation?: PlanIsolation | Adr0013PlanIsolation }): void {\n if (plan.isolation?.run) plan.isolation = migratePlanIsolation(plan.isolation);\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isArray = (value: unknown): value is readonly unknown[] => Array.isArray(value);\n\n/**\n * Whether a payload carries a run this module can read. The check tells today's\n * record from an ADR-0013 one and stops at the fields both shapes need; the\n * record's own fields are trusted from there, as `stateStore` trusts the JSON it\n * loaded. An older daemon still sends the ADR-0013 shape, so the migration runs\n * on the way in rather than being assumed.\n */\nfunction carriesIsolation(plan: unknown): plan is { isolation: PlanIsolation | Adr0013PlanIsolation } {\n if (!isRecord(plan) || !isRecord(plan.isolation)) return false;\n const { run } = plan.isolation;\n if (!isRecord(run) || !isRecord(run.tasks)) return false;\n if (isArray(run.repos)) {\n return run.repos.some((repo) => isRecord(repo)\n && typeof repo.path === 'string'\n && typeof repo.integrationBranch === 'string'\n && typeof repo.baseRef === 'string');\n }\n return typeof run.integrationBranch === 'string' && typeof run.baseRef === 'string';\n}\n\n/**\n * What a persisted plan says about its isolated run, as a surface shows it: the\n * handoff and each task's mark. `null` when the payload carries no record — a\n * planner reply, or a plan that never isolated — which says nothing either way.\n *\n * This is the one reading of a saved record outside the daemon: the migration\n * from the ADR-0013 shape happens here, so no surface grows its own parser.\n * `repairLimit` is server config a saved plan never carries; without it the\n * repair attempt is left for a live status update to fill in.\n */\nexport function isolationViewOfPlan(plan: unknown, repairLimit?: number): IsolationView | null {\n if (!carriesIsolation(plan)) return null;\n const { run } = migratePlanIsolation(plan.isolation);\n return { tasks: taskIsolationsOf(run, repairLimit), handoff: handoffOf(run) };\n}\n","import type { PreviousWork } from '../interfaces/IWorktreeIsolation';\nimport type { RunnerId, Task } from '../models/Task';\nimport type { AttemptIntent } from './IsolationRunController';\n\n/*\n * The execution lease (ADR-0059): where a session runs, behind one seam, as\n * `LocalWorktreeLease` and the Docker backend need it. What only the egress\n * sidecar and the model gateway need comes with them (ADR-0059 L7).\n */\n\n/** What a backend can be asked to prove (ADR-0059 L3). */\nexport type Guarantee = 'fs.hostHidden' | 'fs.writeScope' | 'net.allowlist' | 'net.noMetadata' | 'cred.unreadable' | 'kernel.separate' | 'limits.memory';\n\nexport const GUARANTEES: readonly Guarantee[] = ['fs.hostHidden', 'fs.writeScope', 'net.allowlist', 'net.noMetadata', 'cred.unreadable', 'kernel.separate', 'limits.memory'];\n\n/** What a probe found: how, and when. Never declared, so an unproven entry relaxes nothing (L3). */\nexport interface GuaranteeProof {\n proven: boolean;\n by: string;\n at: string;\n}\n\nexport type GuaranteeReport = Record<Guarantee, GuaranteeProof>;\n\n/** An open that required a guarantee its backend's probe could not prove. */\nexport class LeaseGuaranteeError extends Error {\n constructor(readonly unproven: readonly Guarantee[]) {\n super(`This session requires ${unproven.join(', ')}, which its execution backend could not prove.`);\n this.name = 'LeaseGuaranteeError';\n }\n}\n\n/**\n * Work its backend would not bring back, which stays where it is and waits on\n * the user (L2): an attempt that meets it fails, and lets go of nothing.\n */\nexport class KeptWorkError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'KeptWorkError';\n }\n}\n\n/** L3's one rule: an open fails on a required entry its probe did not prove, whatever the backend. */\nexport function refuseUnproven(required: readonly Guarantee[], report: GuaranteeReport): void {\n const unproven = required.filter((guarantee) => !report[guarantee].proven);\n if (unproven.length > 0) throw new LeaseGuaranteeError(unproven);\n}\n\n/**\n * Who asks, and in what role, which is policy input: a role is resolved to a\n * {@link LeaseSpec} before a backend sees one. `child-plan` reserves a node\n * and no slot (ADR-0055 D18). So does `merge`: it starts no session, and it\n * reaches a checkout, so a backend hears of it.\n */\nexport interface LeaseRequest {\n node: { sessionId: string; taskId?: string };\n role: 'planner' | 'task' | 'script' | 'child-plan' | 'merge';\n runner?: RunnerId;\n}\n\n/** Where a lease puts its session. */\nexport type LeaseWorkspace = { kind: 'checkout' } | { kind: 'worktree'; intent: AttemptIntent };\n\nexport interface LeaseSpec {\n /** The attempt's task, as the claim read it; a planner's lease has none. */\n task?: Task;\n workspace: LeaseWorkspace;\n /** Open fails if one of these cannot be proven. */\n required: readonly Guarantee[];\n}\n\n/** A runner's session, which is what a Continue resumes: the id on the host today, and more behind a container (L2). */\nexport interface LeaseSessionState {\n runner: string;\n nativeSessionId: string | null;\n}\n\n/**\n * What outlives a lease (L2): a branch is not the whole result. A synchronous\n * read: the attempt that ends saves the session before anything can read it.\n */\nexport interface LeaseResult {\n /** Each repo's branch, by repo path; empty for work with no worktree. */\n refs: Record<string, string>;\n sessionState: LeaseSessionState;\n}\n\n/** How a lease's session ended where the host could not watch it (L3); `expired` comes with the first backend that has a time limit. */\nexport type LeaseExit = 'oom' | 'killed';\n\n/** A runner's launch, as its adapter wrote it for this host. */\nexport interface RunnerLaunch {\n command: string;\n args: readonly string[];\n env: Readonly<Record<string, string>>;\n /** Paths on this host, named in `args`, that the process reads, the one holding its token among them. */\n files: readonly string[];\n}\n\n/** What this host launches instead. */\nexport interface PlacedLaunch {\n command: string;\n args: string[];\n env: Record<string, string>;\n /** End the process where it runs: killing what this host launched does not reach it. */\n stop(): void;\n /** The process has ended. */\n ended(): void;\n}\n\n/**\n * Where a lease that does not run its session on this host puts its runner. A\n * runner is still handed a cwd and nothing more (ADR-0013): only {@link RunnerProcess}\n * reads this, and an adapter writes the launch it always wrote.\n */\nexport interface RunnerPlacement {\n place(launch: RunnerLaunch): Promise<PlacedLaunch>;\n /** Make a listener on this host's loopback reachable from there, and answer the URL to reach it by (`endpoints.mcp`). */\n reach(url: string): Promise<string>;\n /** The blob a file holds there, as git would name it, read where the attempt's work is; undefined for what cannot be read. */\n blob(file: string): Promise<string | undefined>;\n}\n\n/** What a copy of a checkout is on where a backend keeps it: no ref of the user's is at stake, and it stands on HEAD as it is. */\nexport const CHECKOUT_BRANCH = 'ordewell/checkout';\n\n/** One repository of an attempt's worktree, for a backend that keeps a copy of it elsewhere. */\nexport interface LeaseRepo {\n /** Relative to the workspace; `.` for a lone repository. */\n path: string;\n /** The repository on this host. */\n root: string;\n /** Its worktree for this task. */\n worktree: string;\n branch: string;\n integrationBranch: string;\n /** What Ordewell linked into the worktree, which is no attempt's work. */\n linked: readonly string[];\n}\n\nexport interface OpenLease {\n workspace: {\n cwd: string;\n /** The task workspace holding its worktrees, when it has any. */\n writable?: string;\n /** Whether it runs in a worktree prepared for it rather than at the root. */\n worktree: boolean;\n /** What became of the previous attempt's work, where a restore was asked for. */\n previousWork?: PreviousWork;\n /** Each repository of its worktree, plural as a repo group's are (ADR-0014). */\n repos?: readonly LeaseRepo[];\n };\n guarantees: GuaranteeReport;\n exportResult(session: LeaseSessionState): LeaseResult;\n /** Set where the runner does not run on this host. */\n placement?: RunnerPlacement;\n /** How its session ended, once it has, where the backend can tell (L3). */\n ended?(): Promise<LeaseExit | null>;\n}\n\n/**\n * Taken in the claim's synchronous step (L1). What it reserves is given back at\n * {@link release}; the workspace it opened need not go with it: a worktree\n * outlives the lease that made it, for a repair or a continue, so what becomes\n * of it is {@link ExecutionBackend.letGo}.\n */\nexport interface ReservedLease {\n readonly id: string;\n /** Whether this lease holds a slot: a delegated task's and a merge's do not. */\n readonly holdsSlot: boolean;\n /** Runs after the claim, where the workspace is made. Rejects without making one once `signal` has aborted. */\n open(spec: LeaseSpec, signal: AbortSignal): Promise<OpenLease>;\n /** Idempotent and synchronous: an attempt's end frees its slot before anything awaits (ADR-0029). */\n release(reason: string): void;\n}\n\n/** One task's workspace, as a restart finds it. */\nexport interface LeaseRecord {\n taskId: string;\n status: string;\n worktrees: readonly string[];\n}\n\nexport interface ExecutionBackend {\n /** What it can prove now. */\n probe(): Promise<GuaranteeReport>;\n /** Why this may not be reserved now, or null. Synchronous, and changes nothing: it is also asked with no claim to follow. */\n refusal(request: LeaseRequest): string | null;\n /** Synchronous, inside the claim. A refusal keeps the Session that asked, for what frees next. */\n reserve(request: LeaseRequest): { lease: ReservedLease } | { refused: string };\n /** What becomes of a task's workspace, with no lease needed: a kept worktree outlives the one that opened it. */\n letGo(taskId: string, how: { keep: boolean; landing?: Promise<unknown> | null; reason: string }): Promise<void>;\n /** What a restart finds. Starts nothing (ADR-0044). */\n list(): Promise<readonly LeaseRecord[]>;\n /**\n * Bring a task's work to its worktree, for a backend that keeps it elsewhere:\n * the half of a result that takes time and can be refused (L2). By task, since\n * a landing can come after its attempt, as a Mark complete does. Absent, the\n * work is there already. Rejects with why it must not land.\n */\n collect?(taskId: string): Promise<void>;\n /**\n * Everything it has running has gone and what was on its way has arrived, as\n * the Session around it closes. Absent, nothing outlives an attempt's runner.\n * What a next attempt comes back to stays, and it never rejects.\n */\n settle?(): Promise<void>;\n /** The workspace root's repositories, where a run knows them: a repo group's root is no repository to ask. */\n checkoutRepos?(): readonly LeaseRepo[];\n /**\n * Let go of what its tree left where only it can reach, as a Clean up gives the\n * run up. Absent, there is nothing but the worktrees. Reports what it could not\n * clear, and never rejects: it is no reason for a clean-up to fail.\n */\n reclaim?(): Promise<void>;\n}\n","import type { BoardEntry } from './Board';\n\n/**\n * A live session's allowance for what it has in flight (ADR-0056 M5), which is\n * one model call: a long context's prompt, its cache reads included, and a reply.\n * An estimate: a runner's own subagents add requests nobody sees until their\n * usage arrives.\n */\nexport const SESSION_RESERVE_TOKENS = 200_000;\n\n/*\n * What a tree's log holds (ADR-0058 S3). State is a fold of these, so an event\n * says what was decided, amounts included, and a replay decides nothing. A new\n * kind is a new member of the union: a fold passes over what it does not read.\n */\n\n/** A node's place among its parent's children, counted from 1 and never reused or moved. */\nexport type NodeId = string;\n\n/** `root/3/2`: the ids from the root down. A node's parent never changes, so neither does this. */\nexport type NodePath = string;\n\nexport const ROOT_NODE: NodePath = 'root';\n\nexport function childPath(parent: NodePath, id: NodeId): NodePath {\n return `${parent}/${id}`;\n}\n\n/** The parent of `node`, or null for the root. */\nexport function parentPath(node: NodePath): NodePath | null {\n const at = node.lastIndexOf('/');\n return at === -1 ? null : node.slice(0, at);\n}\n\n/** Whether `node` is `ancestor` or under it. */\nexport function isWithin(node: NodePath, ancestor: NodePath): boolean {\n return node === ancestor || node.startsWith(`${ancestor}/`);\n}\n\ninterface OfNode {\n /** When it was written: for a reader, and never read by a fold. */\n at: string;\n node: NodePath;\n}\n\nexport type TreeFact = OfNode & (\n /** A node joined the tree. Below the root its `limit` is carved from its parent, and `kept` of it stays for the subplanner's next turn (ADR-0056 M5). */\n | { kind: 'node_opened'; sessionId: string; taskId?: string; limit: number; kept: number }\n /** `node` confirmed closed: its parent holds its `limit` no longer, and is charged what it spent. */\n | { kind: 'node_closed' }\n /** Tokens as a runner or planner reported them. A turn spends what its node kept first. */\n | { kind: 'tokens_spent'; tokens: number; turn?: true; taskId?: string; attempt?: number }\n /** A raise: `tokens` more on `node`'s limit, from its parent's room. */\n | { kind: 'budget_raised'; tokens: number }\n /** Its parent's own spending took `tokens` of `node`'s room: `node`'s limit and its parent's `committed` fall together. */\n | { kind: 'budget_taken'; tokens: number }\n /** A subplanner parked, keeping `tokens` more for its next turn. */\n | { kind: 'reserve_kept'; tokens: number }\n /** The user set the root's limit, the tree's budget, to `limit`. */\n | { kind: 'limit_set'; limit: number }\n /** An entry on `node`'s board, whole: a board is a fold of these (ADR-0057 R8). */\n | { kind: 'board_posted'; entry: BoardEntry }\n);\n\n/** A fact as the log holds it: `seq` is its place in the tree's one order, from 1. */\nexport type TreeEvent = TreeFact & { seq: number };\n"],"mappings":"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","names":[]}
|