@ordewell/core 0.10.2 → 0.10.4

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.
Files changed (45) hide show
  1. package/dist/{IFileSystem-CyTIoiSn.d.ts → IFileSystem-_effm8-b.d.ts} +1 -1
  2. package/dist/{IFileSystem-j0naIoAQ.d.mts → IFileSystem-vyqMdL-x.d.mts} +1 -1
  3. package/dist/{IRunner---PfQVdc.d.mts → IRunner-D_UkkMbJ.d.ts} +202 -196
  4. package/dist/{IRunner-WwL-Bjqq.d.ts → IRunner-NtTHQgLR.d.mts} +202 -196
  5. package/dist/{ModeResolver-DrMU-5lw.d.ts → ModeResolver-Dy3Ef6p-.d.ts} +1 -1
  6. package/dist/{ModeResolver-CjQ7zqTN.d.mts → ModeResolver-wnTFsDnF.d.mts} +1 -1
  7. package/dist/{Task-AfA1m4qW.d.mts → Task-DZs9eKgg.d.mts} +25 -6
  8. package/dist/{Task-AfA1m4qW.d.ts → Task-DZs9eKgg.d.ts} +25 -6
  9. package/dist/{chunk-4FEARWTS.mjs → chunk-645FALZX.mjs} +5 -5
  10. package/dist/chunk-645FALZX.mjs.map +1 -0
  11. package/dist/{chunk-3LNZDBCX.mjs → chunk-A57ITQE7.mjs} +35 -16
  12. package/dist/chunk-A57ITQE7.mjs.map +1 -0
  13. package/dist/{chunk-UQGR3UCA.mjs → chunk-FH6M5QZG.mjs} +136 -122
  14. package/dist/chunk-FH6M5QZG.mjs.map +1 -0
  15. package/dist/{chunk-CIL3BQIF.mjs → chunk-SM6BV6XS.mjs} +17 -3
  16. package/dist/chunk-SM6BV6XS.mjs.map +1 -0
  17. package/dist/index.d.mts +427 -40
  18. package/dist/index.d.ts +427 -40
  19. package/dist/index.js +2589 -1498
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +10799 -9764
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/parsing.d.mts +2 -2
  24. package/dist/parsing.d.ts +2 -2
  25. package/dist/parsing.js +12 -12
  26. package/dist/parsing.js.map +1 -1
  27. package/dist/parsing.mjs +3 -3
  28. package/dist/{plan-utils-DL7yOuJs.d.mts → plan-utils-CtKceSsQ.d.mts} +1292 -1147
  29. package/dist/{plan-utils-gpeqV-Lp.d.ts → plan-utils-POIz5d8z.d.ts} +1292 -1147
  30. package/dist/plan-utils.d.mts +4 -4
  31. package/dist/plan-utils.d.ts +4 -4
  32. package/dist/plan-utils.js +16 -0
  33. package/dist/plan-utils.js.map +1 -1
  34. package/dist/plan-utils.mjs +7 -3
  35. package/dist/testing.d.mts +3 -3
  36. package/dist/testing.d.ts +3 -3
  37. package/dist/testing.js +3 -1
  38. package/dist/testing.js.map +1 -1
  39. package/dist/testing.mjs +4 -2
  40. package/dist/testing.mjs.map +1 -1
  41. package/package.json +1 -1
  42. package/dist/chunk-3LNZDBCX.mjs.map +0 -1
  43. package/dist/chunk-4FEARWTS.mjs.map +0 -1
  44. package/dist/chunk-CIL3BQIF.mjs.map +0 -1
  45. package/dist/chunk-UQGR3UCA.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/testing.ts"],"sourcesContent":["import type { IConfig } from './interfaces/IConfig';\nimport type { IFileSystem, ToolOutcome } from './interfaces/IFileSystem';\nimport type { InvokedCommand, IRunnerSession, QueuedTaskMessage, StructuredEvent, StructuredTurnEnd, TaskMessageFrom } from './interfaces/IRunner';\nimport type { RunnerCommand } from './models/RunnerCommand';\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 IsolationRun,\n IntegrationDisposal,\n PreparedTask,\n IWorktreeIsolation,\n RepairEvidence,\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 webReadBudget: 40,\n setProviderModelLists: () => {},\n getProviderBaseUrl: () => '',\n getProviderApiKey: () => '',\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 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 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 }\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 /** The runner calls `task_complete`, as `StructuredSession` relays it. */\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 /** 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\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\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 /** 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 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 `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 };\n }\n\n async prepare(task: Task, run: IsolationRun): 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 };\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): Promise<IsolationOutcome> {\n this.log({ op: 'integrate', taskId: task.id });\n const record = run.tasks[task.id];\n if (!record) return 'failed';\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 } 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 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 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 found: string[] = [];\n for (const record of Object.values(run.tasks)) {\n if (record.status !== 'merged' || record.inHead || !this.mergedByHand.has(record.taskId)) continue;\n record.inHead = true;\n found.push(record.taskId);\n }\n return found;\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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/testing.ts"],"sourcesContent":["import type { IConfig } from './interfaces/IConfig';\nimport type { IFileSystem, ToolOutcome } from './interfaces/IFileSystem';\nimport type { InvokedCommand, IRunnerSession, QueuedTaskMessage, StructuredEvent, StructuredTurnEnd, TaskMessageFrom } from './interfaces/IRunner';\nimport type { RunnerCommand } from './models/RunnerCommand';\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 IsolationRun,\n IntegrationDisposal,\n PreparedTask,\n IWorktreeIsolation,\n RepairEvidence,\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 webReadBudget: 40,\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 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 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 }\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 /** The runner calls `task_complete`, as `StructuredSession` relays it. */\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 /** 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\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\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 /** 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 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 `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 };\n }\n\n async prepare(task: Task, run: IsolationRun): 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 };\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): Promise<IsolationOutcome> {\n this.log({ op: 'integrate', taskId: task.id });\n const record = run.tasks[task.id];\n if (!record) return 'failed';\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 } 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 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 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 found: string[] = [];\n for (const record of Object.values(run.tasks)) {\n if (record.status !== 'merged' || record.inHead || !this.mergedByHand.has(record.taskId)) continue;\n record.inHead = true;\n found.push(record.taskId);\n }\n return found;\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"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ordewell/core",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "description": "Ordewell core — planner, orchestrator, runners and verification engine.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Alessandro Costanzo Ciano",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/services/JsonExtractor.ts","../src/services/ModeResolver.ts","../src/taskRow/status.ts","../src/services/ModelAllowlistResolver.ts","../src/services/PlannedTask.ts","../src/services/TaskEditValidator.ts","../src/services/TaskOps.ts"],"sourcesContent":["/** The envelope key a full plan reply is keyed by: `{\"tasks\":[...]}`. */\nexport const PLAN_ENVELOPE_KEY = 'tasks';\n/** The envelope key a targeted-edits reply is keyed by: `{\"taskOps\":[...]}`. */\nexport const TASK_OPS_ENVELOPE_KEY = 'taskOps';\n/** The envelope key a read-only detail request is keyed by: `{\"taskQuery\":{...}}`. */\nexport const TASK_QUERY_ENVELOPE_KEY = 'taskQuery';\n\n/** Thrown when an LLM plan response cannot be parsed into a valid plan. Carries the raw text so callers can log it or retry. */\nexport class PlanParseError extends Error {\n readonly raw: string;\n /**\n * True when the reply was cut off mid-object (output-token limit) rather\n * than malformed — the two need different repairs: a truncated emission will\n * be cut at the same point again unless the retry frees context or asks for\n * terser output.\n */\n readonly truncated: boolean;\n /**\n * True when the JSON parsed fine and a *shape* rule rejected it. The generic\n * \"could not be parsed as JSON\" corrective is a lie for these, so the model\n * re-sends the same object; a semantic rejection must be repaired by naming\n * the rule it broke.\n */\n readonly semantic: boolean;\n constructor(message: string, raw: string, opts?: { truncated?: boolean; semantic?: boolean }) {\n super(message);\n this.name = 'PlanParseError';\n this.raw = raw;\n this.truncated = opts?.truncated ?? false;\n this.semantic = opts?.semantic ?? false;\n }\n}\n/**\n * Strip the noise reasoning models wrap around their answer: markdown code\n * fences and `<think>…</think>` chain-of-thought blocks (which routinely contain\n * stray braces that would otherwise derail brace-balancing). Reasoning captured\n * out-of-band by the service never reaches here, but a model that inlines its\n * thinking in the content channel still must not corrupt parsing.\n */\nexport function stripModelNoise(raw: string): string {\n return raw\n .replace(/<think>[\\s\\S]*?<\\/think>/gi, '')\n .replace(/```json\\s*/gi, '')\n .replace(/```\\s*/g, '')\n .trim();\n}\n\nconst THINK_OPEN = '<think>';\nconst THINK_CLOSE = '</think>';\nconst FENCE = '```';\nconst FENCE_JSON_TAG = 'json';\n\n/**\n * Whether a reply that begins with `prefix` opens with a JSON object once the\n * noise {@link stripModelNoise} removes is skipped — the streaming counterpart\n * of it, answering from the start of a reply that has not finished arriving.\n * Undefined while the prefix cannot tell yet: blank, inside an unclosed think\n * block, or partway through a fence or a tag.\n */\nexport function opensWithJsonObject(prefix: string): boolean | undefined {\n let rest = prefix;\n for (;;) {\n rest = rest.trimStart();\n if (!rest) return undefined;\n const head = rest.slice(0, THINK_OPEN.length).toLowerCase();\n if (head === THINK_OPEN) {\n const close = rest.toLowerCase().indexOf(THINK_CLOSE);\n if (close < 0) return undefined;\n rest = rest.slice(close + THINK_CLOSE.length);\n continue;\n }\n if (rest.startsWith(FENCE)) {\n rest = rest.slice(FENCE.length);\n const tag = rest.slice(0, FENCE_JSON_TAG.length).toLowerCase();\n if (tag === FENCE_JSON_TAG) rest = rest.slice(FENCE_JSON_TAG.length);\n else if (tag === rest.toLowerCase() && FENCE_JSON_TAG.startsWith(tag)) return undefined;\n continue;\n }\n if (THINK_OPEN.startsWith(head) || FENCE.startsWith(rest)) return undefined;\n return rest.startsWith('{');\n }\n}\n\n/**\n * Scan from `start` (which must point at a `{`) for the matching balanced close,\n * ignoring braces inside strings. Returns the object substring and whether it was\n * balanced; an unbalanced result means the input was truncated mid-object.\n */\nfunction balancedObjectAt(text: string, start: number): { json: string; balanced: boolean } {\n let depth = 0;\n let inString = false;\n let escaped = false;\n for (let i = start; i < text.length; i++) {\n const ch = text[i];\n if (inString) {\n if (escaped) escaped = false;\n else if (ch === '\\\\') escaped = true;\n else if (ch === '\"') inString = false;\n continue;\n }\n if (ch === '\"') inString = true;\n else if (ch === '{') depth++;\n else if (ch === '}') {\n depth--;\n if (depth === 0) return { json: text.slice(start, i + 1), balanced: true };\n }\n }\n return { json: text.slice(start), balanced: false };\n}\n\n/**\n * Scan an LLM response for every balanced `{…}` object carrying a `\"<key>\":`\n * entry. Strips fences and `<think>` blocks first. A matching object is\n * skipped over wholesale, so its nested braces don't produce fragment\n * candidates; non-matching objects are descended into, so a match wrapped in\n * an unrelated outer object is still found. `fallback` preserves the legacy\n * behavior for callers with no keyed match: the first balanced object, else\n * the stripped text so a JSON.parse error still surfaces.\n */\nexport function extractObjectsWithKey(raw: string, key: string): {\n matches: string[];\n fallback: { json: string; balanced: boolean };\n sawBrace: boolean;\n /** An object ran to EOF unclosed — the classic signature of truncated output. */\n sawUnbalanced: boolean;\n} {\n const stripped = stripModelNoise(raw);\n const keyRe = new RegExp(`\"${key}\"\\\\s*:`);\n\n const matches: string[] = [];\n let firstObject: string | null = null;\n let sawBrace = false;\n let sawUnbalanced = false;\n let inString = false;\n let escaped = false;\n for (let i = 0; i < stripped.length; i++) {\n const ch = stripped[i];\n if (inString) {\n if (escaped) escaped = false;\n else if (ch === '\\\\') escaped = true;\n else if (ch === '\"') inString = false;\n continue;\n }\n if (ch === '\"') { inString = true; continue; }\n if (ch !== '{') continue;\n\n sawBrace = true;\n const obj = balancedObjectAt(stripped, i);\n // A stray brace in a reasoning preamble produces an unbalanced candidate,\n // which we skip so it can't swallow the real object that follows.\n if (obj.balanced && keyRe.test(obj.json)) {\n matches.push(obj.json);\n i += obj.json.length - 1;\n continue;\n }\n if (!obj.balanced) sawUnbalanced = true;\n if (!firstObject && obj.balanced) firstObject = obj.json;\n }\n\n return {\n matches,\n fallback: firstObject !== null ? { json: firstObject, balanced: true } : { json: stripped, balanced: false },\n sawBrace,\n sawUnbalanced,\n };\n}\n\n/**\n * Pull the JSON object out of an LLM response and report whether it was balanced.\n * Strips fences and `<think>` blocks, then walks every top-level `{…}` and\n * returns the FIRST one that contains a `\"tasks\"` key — so a brace appearing\n * earlier in reasoning (or an unrelated preamble object) can't make us parse the\n * wrong thing. Falls back to the first balanced object and, failing that, the\n * first `{` onwards so a genuine `JSON.parse` error still surfaces.\n */\nexport function extractObjectWithBalance(raw: string): { json: string; balanced: boolean; sawBrace: boolean } {\n const { matches, fallback, sawBrace } = extractObjectsWithKey(raw, PLAN_ENVELOPE_KEY);\n if (matches.length > 0) return { json: matches[0], balanced: true, sawBrace };\n return { ...fallback, sawBrace };\n}\n\n/**\n * Pull the JSON object out of an LLM response. Strips markdown code fences and\n * `<think>` blocks, prefers the object containing a `tasks` key, and tolerates a\n * prose preamble and trailing prose. Returns the fence-stripped text unchanged\n * if no `{` is present so the caller's JSON.parse produces a clear failure.\n */\nexport function extractJsonObject(raw: string): string {\n return extractObjectWithBalance(raw).json;\n}\n\n/** Drop trailing commas before a closing `}` or `]` — a common model quirk. */\nexport function stripTrailingCommas(json: string): string {\n return json.replace(/,(\\s*[}\\]])/g, '$1');\n}\n\n/**\n * Escape raw control characters that appear INSIDE string literals — budget\n * models routinely emit multi-line `prompt` strings with literal newlines,\n * which strict JSON.parse rejects (\"bad control character\"). Characters\n * outside strings (formatting whitespace) are left untouched.\n */\nexport function escapeControlCharsInStrings(json: string): string {\n let out = '';\n let inString = false;\n let escaped = false;\n for (const ch of json) {\n if (inString) {\n if (escaped) { out += ch; escaped = false; continue; }\n if (ch === '\\\\') { out += ch; escaped = true; continue; }\n if (ch === '\"') { out += ch; inString = false; continue; }\n if (ch === '\\n') { out += '\\\\n'; continue; }\n if (ch === '\\r') { out += '\\\\r'; continue; }\n if (ch === '\\t') { out += '\\\\t'; continue; }\n out += ch;\n continue;\n }\n if (ch === '\"') inString = true;\n out += ch;\n }\n return out;\n}\n","import { flattenTasks, type RunnerId, type Task } from '../models/Task';\n\nexport interface RunnerModeInfo {\n id: string;\n label: string;\n description: string;\n autonomous?: boolean;\n safe?: boolean;\n readOnly?: boolean;\n sandboxed?: boolean;\n /** Why this mode cannot be used on this host (a failed probe); such a mode is shown, never assigned. */\n unavailable?: string;\n}\n\n/** The slice of RunnerRegistry this module needs — structural, so nothing here depends on the manifest layer. */\nexport interface ManifestLookup {\n getManifest(runner: RunnerId): { modes?: RunnerModeInfo[] } | undefined;\n}\n\n/**\n * A runner's modes are declared only in its manifest, never through a service,\n * so every caller that needs them fuses registry and runner list by hand. This\n * is that fusion, in one place: the planner prompt, the mode pickers on each\n * surface, and the runner-change retarget all read modes the same way.\n *\n * A runner with no modes maps to `[]` rather than being dropped — callers\n * distinguish \"declares no modes\" from \"not asked about\".\n */\nexport function runnerModesFrom(registry: ManifestLookup, runners: RunnerId[]): Record<RunnerId, RunnerModeInfo[]> {\n return Object.fromEntries(runners.map((r) => [r, registry.getManifest(r)?.modes ?? []] as const));\n}\n\n/**\n * Find the mode ID a runner resolves to under the given toggle state.\n * Tag-based first (autonomous/safe); falls back to positional last non-plan\n * if no tag is present on any mode (degrades gracefully on untagged manifests).\n * Returns undefined if `modes` is empty or undefined.\n */\nexport function resolveDefaultMode(\n modes: readonly RunnerModeInfo[] | undefined,\n autonomousDefault = true,\n): string | undefined {\n if (!modes || modes.length === 0) return undefined;\n const tag: 'autonomous' | 'safe' = autonomousDefault ? 'autonomous' : 'safe';\n const tagged = modes.find((m) => m[tag]);\n if (tagged) return tagged.id;\n for (let i = modes.length - 1; i >= 0; i--) {\n if (!modes[i].readOnly && modes[i].id !== 'plan') return modes[i].id;\n }\n return undefined;\n}\n\n/** The user-facing name of the `autonomousMode` boolean's two states. */\nexport function autonomyLevelLabel(autonomous: boolean): 'Full' | 'Guarded' {\n return autonomous ? 'Full' : 'Guarded';\n}\n\n/** `full`/`guarded` name the levels; `on`/`off` and `auto` are older words for them. Null for anything else. */\nexport function parseAutonomyLevel(arg: string | undefined): boolean | null {\n switch (arg?.trim().toLowerCase()) {\n case 'full': case 'on': return true;\n case 'guarded': case 'auto': case 'off': return false;\n default: return null;\n }\n}\n\n/**\n * The mode a manifest tags `readOnly` — never found by id, so a runner with no\n * tag has none. A mode this host cannot run is still the runner's read-only\n * mode: it is how a task already on it is recognized, and why a move of that\n * task is refused rather than made writable.\n */\nexport function taggedReadOnlyMode(modes: readonly RunnerModeInfo[] | undefined): string | undefined {\n return modes?.find((m) => m.readOnly)?.id;\n}\n\n/** The read-only mode a task can be given on this host: the tagged one, unless it is unavailable here. */\nexport function readOnlyMode(modes: readonly RunnerModeInfo[] | undefined): string | undefined {\n const mode = modes?.find((m) => m.readOnly);\n return mode && !mode.unavailable ? mode.id : undefined;\n}\n\n/** Why a mode cannot be used on this host, when it cannot. */\nexport function unavailableModeReason(modes: readonly RunnerModeInfo[] | undefined, id: string | undefined): string | undefined {\n return id === undefined ? undefined : modes?.find((m) => m.id === id)?.unavailable;\n}\n\nfunction unavailableModeError(runner: RunnerId, id: string, reason: string): string {\n return `Mode \"${id}\" of runner \"${runner}\" is unavailable on this host: ${reason}`;\n}\n\n/** What a task fails with at spawn when its planned mode is one this host cannot run; undefined when it can. */\nexport function unavailableTaskModeMessage(\n manifest: { displayName: string; modes?: readonly RunnerModeInfo[] },\n mode: string | undefined,\n): string | undefined {\n const reason = unavailableModeReason(manifest.modes, mode);\n return reason ? `Mode \"${mode}\" of ${manifest.displayName} is unavailable on this host: ${reason}` : undefined;\n}\n\n/** What the planner says about a task's mode (ADR-0031): read-only or not, and the mode the task has now, if any. */\nexport interface ModeRequest {\n readOnly?: boolean;\n current?: string;\n}\n\nexport type PlannedMode = { ok: true; mode: string | undefined } | { ok: false; error: string };\n\n/**\n * The mode a task the planner writes lands on (ADR-0031). The planner names no\n * mode: a read-only task gets its runner's read-only mode; any other keeps the\n * mode it has when the runner offers it — a mode the user picked survives an\n * edit that does not ask for a change — and otherwise gets the runner's mode\n * for the level. `readOnly: false` is the one request that moves a task off\n * its read-only mode. Undefined mode when the runner's modes are unknown.\n */\nexport function plannedMode(\n runner: RunnerId,\n runnerModes: Partial<Record<RunnerId, readonly RunnerModeInfo[]>> | undefined,\n { readOnly, current }: ModeRequest,\n autonomousDefault = true,\n): PlannedMode {\n if (!runnerModes) return { ok: true, mode: undefined };\n const modes = runnerModes[runner] ?? [];\n const readOnlyId = readOnlyMode(modes);\n if (readOnly === true) {\n if (readOnlyId) return { ok: true, mode: readOnlyId };\n const able = readOnlyRunners(runnerModes);\n const reason = unavailableModeReason(modes, taggedReadOnlyMode(modes));\n return {\n ok: false,\n error: `Runner \"${runner}\" has no read-only mode${reason ? ' available on this host' : ''} — assign a read-only task to ${able.length > 0 ? `one of: ${able.join(', ')}` : 'no runner: none of the enabled runners has one'}, or drop \"readOnly\"${reason ? `. ${reason}` : ''}`,\n };\n }\n const keep = current !== undefined && modes.some((m) => m.id === current) && !(readOnly === false && current === taggedReadOnlyMode(modes));\n const mode = keep ? current : resolveDefaultMode(modes, autonomousDefault);\n // Refused, never swapped for another mode: that would hand a task the\n // planner or user meant to be sandboxed a level of access nobody chose (ADR-0001).\n const reason = unavailableModeReason(modes, mode);\n if (mode !== undefined && reason) return { ok: false, error: unavailableModeError(runner, mode, reason) };\n return { ok: true, mode };\n}\n\n/**\n * A plan the planner restates over an existing one comes back with every mode\n * decided afresh. A restatement says read-only or not for each task, so that\n * answer stands; what it cannot say is a mode the user picked. So where a\n * task it names was planned before and is not read-only now, the earlier mode\n * is kept when its runner offers it — the rule an edit follows.\n */\nexport function carryPlannedModes(\n before: readonly Task[],\n rewrite: Task[],\n catalog: { runnerModes: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>; autonomousDefault?: boolean },\n): Task[] {\n const prior = new Map(flattenTasks(before).map((t) => [t.id, t]));\n const carry = (t: Task): Task => {\n const subtasks = (t.subtasks ?? []).map(carry);\n const was = prior.get(t.id);\n if (t.type === 'user' || !was || was.type === 'user' || t.taskMode === taggedReadOnlyMode(catalog.runnerModes[t.assignedRunner])) return { ...t, subtasks };\n const mode = plannedMode(t.assignedRunner, catalog.runnerModes, { readOnly: false, current: was.taskMode }, catalog.autonomousDefault ?? true);\n return { ...t, subtasks, ...(mode.ok && mode.mode !== undefined ? { taskMode: mode.mode } : {}) };\n };\n return rewrite.map(carry);\n}\n\n/** The runners a read-only task may go to, as the planner's prompt names them. */\nexport function readOnlyRunners(runnerModes: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>): RunnerId[] {\n return Object.entries(runnerModes).filter(([, ms]) => readOnlyMode(ms)).map(([id]) => id);\n}\n","import type { AwaitingReason } from '../models/Task';\n\n/**\n * What a row's status means to a reader, before any surface picks a word,\n * icon or colour for it. `quiet` is a running task whose runner has printed\n * nothing for a while — the status is still `in_progress`, so only the row,\n * never the store, tells the two apart.\n */\nexport type TaskStatusKind = 'todo' | 'running' | 'quiet' | 'awaiting' | 'blocked' | 'failed' | 'skipped' | 'done';\n\n/** A task with a runner live behind it. `in_progress` is the store's own status. */\nexport const isTaskRunning = (task: { status: string }): boolean =>\n task.status === 'in_progress' || task.status === 'running';\n\n/** Statuses of a task that has not started: what can still be started by hand, or flipped between change and ops (ADR-0020). */\nexport const NOT_STARTED: ReadonlySet<string> = new Set(['pending', 'approved', 'blocked']);\n\nexport function taskStatusKind(task: { status: string; idleSince?: string | null }): TaskStatusKind {\n switch (task.status) {\n case 'completed': return 'done';\n case 'failed': return 'failed';\n case 'skipped': return 'skipped';\n case 'blocked': return 'blocked';\n case 'awaiting_user': return 'awaiting';\n }\n if (!isTaskRunning(task)) return 'todo';\n return task.status === 'in_progress' && task.idleSince ? 'quiet' : 'running';\n}\n\nconst AWAITING_LABELS: Record<AwaitingReason, string> = {\n input: 'Waiting for your input',\n checkpoint: 'Checkpoint',\n conflict: 'Merge conflict',\n 'files-changed': 'Changed tracked files',\n 'usage-limit': 'Paused on a usage limit',\n};\n\n/** What an awaiting_user task waits on (ADR-0018, W1); null when no reason was saved. */\nexport function awaitingLabel(task: { status: string; awaitingReason?: AwaitingReason }): string | null {\n return task.status === 'awaiting_user' && task.awaitingReason ? AWAITING_LABELS[task.awaitingReason] : null;\n}\n\n/** \"Blocked by #1, #2.1\": the failed tasks a blocked task waits on, by reference (ADR-0034). */\nexport function blockedByLabel(refs: readonly string[]): string {\n return `Blocked by ${refs.join(', ')}`;\n}\n\n/** \"Skipped: no GPU on this host\", or \"Skipped\" when no reason was given. */\nexport function skippedLabel(reason: string | null): string {\n return reason ? `Skipped: ${reason}` : 'Skipped';\n}\n\n/** Runner requests a task waits on (ADR-0018, A1); null when none do. */\nexport function approvalLabel(count: number | undefined): string | null {\n if (!count) return null;\n return count > 1 ? `Waiting for approval (${count})` : 'Waiting for approval';\n}\n\n/**\n * When a paused task resumes, as a reader's clock shows it: \"8:10pm\", or\n * \"Oct 14, 8:10pm\" when it is not today. Read in the local time zone of\n * whoever formats it.\n */\nexport function resumeTime(at: Date, now: Date = new Date()): string {\n const clock = at.toLocaleTimeString('en-US', { hour: 'numeric', minute: at.getMinutes() === 0 ? undefined : '2-digit', hour12: true })\n .replace(/[ \\u202f]([AP]M)$/i, (_m, meridiem: string) => meridiem.toLowerCase());\n if (at.toDateString() === now.toDateString()) return clock;\n return `${at.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}, ${clock}`;\n}\n\n/** \"Resumes at 8:10pm\" for a task paused on a usage limit that resumes on its own; null for any other. */\nexport function resumeLabel(task: { status: string; awaitingReason?: AwaitingReason; resumeAt?: string | null }, now: Date = new Date()): string | null {\n if (task.status !== 'awaiting_user' || task.awaitingReason !== 'usage-limit' || !task.resumeAt) return null;\n const at = new Date(task.resumeAt);\n return Number.isNaN(at.getTime()) ? null : `Resumes at ${resumeTime(at, now)}`;\n}\n","import type { DiscoveredModel, RunnerId, Task } from '../models/Task';\n\n/**\n * The ids a runner may actually run: the user's allowlist with the ids that\n * provably belong to a *different* runner dropped. `undefined` means no\n * restriction.\n *\n * Model ids are scoped to the agent that lists them, so an OpenRouter slug\n * allowlisted for Claude Code does not limit it — it points it at something it\n * cannot spawn, and the plan only dies once a task is already running. But\n * \"this runner didn't list it\" is not enough to call an id wrong: discovery can\n * be stale or return no list at all. What settles it is\n * whether *another* runner listed the id. So:\n *\n * - listed for this runner → keep;\n * - listed for no runner → unknown, and the user said it explicitly, so keep it\n * and let the runner validate last (the same call `coerceAssignments` and\n * `TaskRetarget` make);\n * - listed only for other runners → provably not this runner's, so drop it.\n *\n * When that leaves nothing, the whole allowlist was about some other runner\n * (a settings file written before a surface scoped its picker to one). No\n * restriction is the only safe reading — the alternative is handing the planner\n * a list of models that cannot run.\n */\nexport function effectiveAllowlist(\n allowlist: string[] | undefined,\n runner: string,\n modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>> | undefined,\n): string[] | undefined {\n if (!allowlist || allowlist.length === 0) return undefined;\n if (!modelsByRunner) return allowlist;\n\n const own = new Set((modelsByRunner[runner] ?? []).map((m) => m.modelId));\n const elsewhere = new Set<string>();\n for (const [other, models] of Object.entries(modelsByRunner)) {\n if (other === runner) continue;\n for (const m of models ?? []) elsewhere.add(m.modelId);\n }\n\n const kept = allowlist.filter((id) => own.has(id) || !elsewhere.has(id));\n return kept.length > 0 ? kept : undefined;\n}\n\nexport function filterModelsForPrompt(\n modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>,\n perRunnerAllowlist: Partial<Record<RunnerId, string[]>>,\n): Partial<Record<RunnerId, DiscoveredModel[]>> {\n const result: Partial<Record<RunnerId, DiscoveredModel[]>> = {};\n for (const [runner, models] of Object.entries(modelsByRunner)) {\n if (!models) continue;\n const allowed = effectiveAllowlist(perRunnerAllowlist[runner], runner, modelsByRunner);\n if (!allowed) {\n result[runner] = models;\n continue;\n }\n const allowedSet = new Set(allowed);\n const intersection = models.filter((m) => allowedSet.has(m.modelId));\n // A set allowlist is a hard constraint: the planner must never see models\n // outside it. An allowed id no runner has listed is one discovery doesn't\n // cover, so synthesize an entry rather than leaking the full list.\n const covered = new Set(intersection.map((m) => m.modelId));\n const missing = allowed\n .filter((id) => !covered.has(id))\n .map((id) => ({ modelId: id, modelLabel: id.split('/').pop() || id, variants: [] }));\n result[runner] = [...intersection, ...missing];\n }\n return result;\n}\n\n/** Canonical low→high ordering of the well-known effort ladder. */\nconst EFFORT_LADDER = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'];\n/** Runner-specific spellings mapped onto the ladder. */\nconst EFFORT_ALIASES: Record<string, string> = { disabled: 'none', adaptive: 'medium' };\n\n/**\n * Clamp a planner/user-supplied thinking effort to what the model actually\n * offers. Invalid values map to the nearest rung the model supports (so a\n * Claude-style \"xhigh\" on a low/medium/high model becomes \"high\") and fall\n * back to undefined — the runner's own default — when no mapping exists.\n */\nexport function clampThinkingEffort(\n effort: string | undefined,\n variants: { id: string }[],\n): string | undefined {\n if (!effort) return undefined;\n if (variants.length === 0) return undefined;\n const ids = new Set(variants.map((v) => v.id));\n if (ids.has(effort)) return effort;\n const canon = EFFORT_ALIASES[effort] ?? effort;\n if (ids.has(canon)) return canon;\n const idx = EFFORT_LADDER.indexOf(canon);\n if (idx < 0) return undefined;\n for (let d = 1; d < EFFORT_LADDER.length; d++) {\n const lower = EFFORT_LADDER[idx - d];\n if (lower && ids.has(lower)) return lower;\n const higher = EFFORT_LADDER[idx + d];\n if (higher && ids.has(higher)) return higher;\n }\n return undefined;\n}\n\n/** A change coercion made to a planned task, named so it is never silent (ADR-0001). */\nexport interface Coercion {\n taskId: string;\n title: string;\n field: 'assignedModel' | 'thinkingEffort';\n from: string | null;\n to: string | null;\n}\n\nexport interface CoercedTasks {\n tasks: Task[];\n /** What changed, subtasks included, in plan order. */\n coerced: Coercion[];\n}\n\n/**\n * Snap each AI task's assignment, subtasks included, to what its runner may\n * run: a model outside the allowlist moves to the allowlist's first, a missing\n * one is filled in, and an effort is clamped to the model's variants. The\n * runner is never changed — one the plan may not use is refused before a task\n * gets here (`checkPlanInvariants`).\n */\nexport function coerceAssignments(\n tasks: readonly Task[],\n perRunnerAllowlist: Partial<Record<RunnerId, string[]>>,\n modelsByRunner?: Partial<Record<RunnerId, DiscoveredModel[]>>,\n): CoercedTasks {\n // When the discovered catalog is available, snap each assignment's\n // thinkingEffort to a variant the model really has and stamp the model's\n // variant ids on the assignment (runners need them at spawn time); a model\n // we can't find in the catalog keeps its effort untouched (the runner\n // validates last).\n const clampVariant = (task: Task): Task => {\n if (!task.assignedModel || !modelsByRunner) return task;\n const model = modelsByRunner[task.assignedRunner]?.find(\n (m) => m.modelId === task.assignedModel!.modelId,\n );\n if (!model) return task;\n const clamped = clampThinkingEffort(task.assignedModel.thinkingEffort, model.variants);\n return {\n ...task,\n assignedModel: {\n ...task.assignedModel,\n thinkingEffort: clamped,\n availableVariants: model.variants.map((v) => v.id),\n },\n };\n };\n\n // One reading of the allowlist for the whole pass, so the models a task is\n // snapped to are the same ones the planner was shown (`filterModelsForPrompt`).\n const allowedFor = (runner: RunnerId): string[] | undefined =>\n effectiveAllowlist(perRunnerAllowlist[runner], runner, modelsByRunner);\n\n // A snapped-to model keeps the catalog's label and variant list where we have\n // one — an id alone reads as the model's name in every surface, and leaves the\n // effort picker with nothing to offer.\n const assignmentFor = (runner: RunnerId, modelId: string): NonNullable<Task['assignedModel']> => {\n const known = modelsByRunner?.[runner]?.find((m) => m.modelId === modelId);\n return {\n modelId,\n modelLabel: known?.modelLabel ?? modelId,\n thinkingEffort: undefined,\n ...(known ? { availableVariants: known.variants.map((v) => v.id) } : {}),\n };\n };\n\n const coerceOne = (task: Task): Task => {\n if (task.type === 'user') return task;\n const allowlist = allowedFor(task.assignedRunner);\n // No model assigned: fill in from the allowlist or discovered catalog so\n // merge/split/add outputs (which may omit assignedModel) always land with a\n // valid model for the task's runner.\n if (!task.assignedModel) {\n if (allowlist && allowlist.length > 0) {\n return clampVariant({ ...task, assignedModel: assignmentFor(task.assignedRunner, allowlist[0]) });\n }\n const discovered = modelsByRunner?.[task.assignedRunner]?.[0];\n if (discovered) {\n return clampVariant({ ...task, assignedModel: assignmentFor(task.assignedRunner, discovered.modelId) });\n }\n return task;\n }\n if (!allowlist || allowlist.length === 0) return clampVariant(task);\n if (allowlist.includes(task.assignedModel.modelId)) return clampVariant(task);\n return { ...task, assignedModel: assignmentFor(task.assignedRunner, allowlist[0]) };\n };\n\n const coerced: Coercion[] = [];\n const walk = (list: readonly Task[]): Task[] => list.map((task) => {\n const after = coerceOne(task);\n const [fromModel, toModel] = [task.assignedModel?.modelId ?? null, after.assignedModel?.modelId ?? null];\n if (fromModel !== toModel) coerced.push({ taskId: task.id, title: task.title, field: 'assignedModel', from: fromModel, to: toModel });\n const [fromEffort, toEffort] = [task.assignedModel?.thinkingEffort ?? null, after.assignedModel?.thinkingEffort ?? null];\n if (fromEffort !== toEffort) coerced.push({ taskId: task.id, title: task.title, field: 'thinkingEffort', from: fromEffort, to: toEffort });\n return { ...after, subtasks: walk(task.subtasks ?? []) };\n });\n return { tasks: walk(tasks), coerced };\n}\n","import { v4 as uuidv4 } from 'uuid';\nimport { Task, RunnerId, TaskModelAssignment, DEFAULT_RUNNERS, createTask, flattenTasksWithParents, inheritedSkills, skillNames } from '../models/Task';\nimport { plannedMode, type RunnerModeInfo } from './ModeResolver';\nimport { checkModelAndModeValidity, type EditCatalog } from './TaskEditValidator';\n\n/** One reason a planned task was refused, pinned to the task and field it is about. */\nexport interface PlanTaskError {\n /** Absent when the envelope itself is wrong rather than one of its tasks. */\n taskId?: string;\n field: string;\n message: string;\n}\n\n/** What a planned task may be given: the runners it may run on and their modes. */\nexport interface IntakeCatalog {\n runners: readonly RunnerId[];\n /** Absent when no runner's modes are known: the mode is then guessed (ADR-0031). */\n runnerModes?: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>;\n autonomousDefault?: boolean;\n /**\n * An edit's assigned models are checked against the catalog and refused; a\n * submitted plan's are coerced after it lands instead (ADR-0003).\n */\n models?: EditCatalog;\n}\n\n/** Where a planned task lands, which decides what it inherits. */\nexport interface Placement {\n /** The task it is a subtask of. */\n parent?: Pick<Task, 'title' | 'sliceType' | 'autonomy'> & { readOnly: boolean };\n /**\n * A task made from others — a merge, or a split part: what the planner\n * leaves out comes from these fields, and it stays read-only when they were.\n */\n from?: { fields: Partial<Task>; readOnly: boolean };\n /**\n * An edit's tasks get ids of their own, so a planner cannot name one after\n * a task already in the plan. A submitted plan keeps the planner's, which\n * its dependencies are written against; one it left out is made up.\n */\n freshIds?: boolean;\n}\n\nexport interface PlannedTask {\n task: Task;\n /** Rules the task, or one of its subtasks, breaks on its own. */\n errors: PlanTaskError[];\n /**\n * What the planner asked for that is not its to set — a mode, or read-only\n * on a manual task (ADR-0031). A whole plan lands without it; an edit is\n * refused.\n */\n ignored: PlanTaskError[];\n /** The planner's id for each task of the tree that was given a fresh one. */\n renamed: Map<string, string>;\n}\n\n/** The planner names no mode (ADR-0031): it says read-only, and the mode follows. */\nexport const MODE_NOT_THE_PLANNERS = '\"taskMode\" is the user\\'s to choose — set \"readOnly\": true on a task that must not change files, and leave it out otherwise';\n\n/** A manual task runs in no mode, so it cannot be read-only. */\nexport const MANUAL_NOT_READ_ONLY = '\"readOnly\" is for AI tasks — a manual task runs in no mode';\n\n/** An ops task acts — runs commands — in the session's mode (ADR-0020); a read-only mode refuses that. */\nexport const OPS_NOT_READ_ONLY = '\"readOnly\" is not for an ops task — it runs commands, in the session\\'s mode, and a read-only mode refuses them';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction text(value: unknown): string | undefined {\n return typeof value === 'string' || typeof value === 'number' ? String(value) : undefined;\n}\n\n/**\n * Why a task may not be an ops task (ADR-0020), or null when it may: the flag\n * decides where the task runs, and a manual task runs nowhere, a subtask\n * where its parent does.\n */\nexport function opsPlacementRefusal(type: Task['type'], isSubtask: boolean): string | null {\n if (type !== 'ai') return 'is a manual task — only an AI task can be an ops task';\n if (isSubtask) return 'is a subtask, which runs with its parent — only a top-level task can be an ops task';\n return null;\n}\n\n/** How an error names a task, so the planner can find the object it wrote. */\nexport function taskWhere(title: string, parentTitle?: string): string {\n return parentTitle !== undefined ? `subtask \"${title}\" of \"${parentTitle}\"` : `Task \"${title}\"`;\n}\n\n/**\n * What each planned task asked to attach, as the planner wrote it. Kept beside\n * the task rather than on it: {@link skillNames} drops a name no skill could\n * have, and the plan's skill check still owes a warning for it. A copy of the\n * task (assignment coercion) does not carry it, so check what was planned.\n */\nconst skillsAsked = new WeakMap<Task, unknown[]>();\n\n/** The `skills` a planned task was submitted with, before normalization; undefined for a task not planned here. */\nexport function requestedSkills(task: Task): readonly unknown[] | undefined {\n return skillsAsked.get(task);\n}\n\n/** The model as a task stores it; a top-level `thinkingEffort` is the model's, as the effort has one home. */\nfunction modelOf(raw: Record<string, unknown>, from: TaskModelAssignment | undefined): TaskModelAssignment | undefined {\n const effort = text(raw.thinkingEffort);\n if (isRecord(raw.assignedModel)) {\n const m = raw.assignedModel;\n return {\n modelId: text(m.modelId) ?? '',\n modelLabel: text(m.modelLabel) ?? '',\n thinkingEffort: text(m.thinkingEffort) ?? effort,\n };\n }\n return from && effort !== undefined ? { ...from, thinkingEffort: effort } : from;\n}\n\n/**\n * Build a task from what a planner wrote, by whichever route it arrived: a\n * submitted plan, or an edit that adds, merges or splits. Every fallback a\n * planner's omission meets is here, so the routes cannot drift apart:\n *\n * - the runner is the one named, else the task's it is made from, else the\n * first one allowed — never a substitute for one named but unknown, which\n * {@link checkPlanInvariants} refuses;\n * - an AI task's prompt is its own, else its description, else its title, so\n * it is never unschedulable;\n * - the mode is decided here from read-only or not, never named (ADR-0031);\n * - an AI subtask with no slice classification takes its parent's: an `ai`\n * sub-step of an AFK slice is AFK. Not a blanket default, which would strip\n * a human gate the planner intended; a manual subtask must still say HITL.\n */\nexport function plannedTask(spec: unknown, catalog: IntakeCatalog, at: Placement = {}): PlannedTask {\n const raw = isRecord(spec) ? spec : {};\n const { parent, from, freshIds } = at;\n const base = from?.fields ?? {};\n const errors: PlanTaskError[] = [];\n const ignored: PlanTaskError[] = [];\n const renamed = new Map<string, string>();\n\n const title = text(raw.title) ?? '';\n const where = taskWhere(title, parent?.title);\n const plannerId = text(raw.id);\n const id = freshIds || !plannerId ? uuidv4() : plannerId;\n if (freshIds && plannerId) renamed.set(plannerId, id);\n\n const type: Task['type'] = raw.type === 'user' || raw.type === 'ai' ? raw.type : base.type ?? 'ai';\n const ai = type === 'ai';\n const assignedRunner = text(raw.assignedRunner) ?? base.assignedRunner ?? catalog.runners[0] ?? DEFAULT_RUNNERS[0];\n const description = text(raw.description) ?? base.description ?? title;\n\n // A manual task stores no ops flag, so this is the one place it can still be seen.\n if (raw.ops === true && !ai) errors.push({ taskId: id, field: 'ops', message: `${where} ${opsPlacementRefusal(type, !!parent)}` });\n if (raw.taskMode !== undefined) ignored.push({ taskId: id, field: 'taskMode', message: `${where}: ${MODE_NOT_THE_PLANNERS}` });\n if (!ai && raw.readOnly === true) ignored.push({ taskId: id, field: 'readOnly', message: `${where}: ${MANUAL_NOT_READ_ONLY}` });\n const ops = raw.ops !== undefined ? raw.ops === true : base.ops;\n if (ai && ops && raw.readOnly === true) ignored.push({ taskId: id, field: 'readOnly', message: `${where}: ${OPS_NOT_READ_ONLY}` });\n // A read-only task's subtasks run as tasks of their own, so they would\n // otherwise get write access the planner said the work must not have.\n const asked = typeof raw.readOnly === 'boolean' ? raw.readOnly : undefined;\n const readOnly = ai && !ops && (asked ?? from?.readOnly ?? parent?.readOnly ?? false);\n\n let taskMode = base.taskMode;\n if (ai && catalog.runners.includes(assignedRunner)) {\n const mode = plannedMode(assignedRunner, catalog.runnerModes, { readOnly, current: base.taskMode }, catalog.autonomousDefault ?? true);\n if (!mode.ok) errors.push({ taskId: id, field: 'readOnly', message: `${where}: ${mode.error}` });\n else if (mode.mode !== undefined) taskMode = mode.mode;\n }\n // With no runner modes to read, `plan` is the guess that keeps a read-only task from writing.\n if (taskMode === undefined && readOnly && !catalog.runnerModes) taskMode = 'plan';\n\n const assignedModel = ai ? modelOf(raw, base.assignedModel) : undefined;\n // Only a model the planner named: one carried over from the tasks a merge or split is made of was never its pick.\n if (assignedModel && isRecord(raw.assignedModel) && catalog.models && catalog.runners.includes(assignedRunner)) {\n const validity = checkModelAndModeValidity(assignedRunner, assignedModel, undefined, catalog.models);\n if (!validity.ok) errors.push({ taskId: id, field: 'assignedModel', message: `${where}: ${validity.error}` });\n }\n\n const slice = (value: unknown): 'AFK' | 'HITL' | undefined => (value === 'AFK' || value === 'HITL' ? value : undefined);\n // Explicit child values always win; inheritance only fills a gap, and only for `ai` tasks.\n const autonomy = ai ? slice(raw.autonomy) ?? base.autonomy ?? parent?.autonomy : undefined;\n const sliceType = slice(raw.sliceType) ?? base.sliceType ?? (ai ? parent?.sliceType : undefined);\n\n const subtasks: Task[] = [];\n if (Array.isArray(raw.subtasks)) {\n for (const s of raw.subtasks) {\n const child = plannedTask(s, catalog, { parent: { title, sliceType, autonomy, readOnly }, freshIds });\n subtasks.push(child.task);\n errors.push(...child.errors);\n ignored.push(...child.ignored);\n for (const [was, now] of child.renamed) renamed.set(was, now);\n }\n }\n\n const task = createTask({\n id,\n order: Number(raw.order ?? 0),\n title,\n description,\n type,\n dependencies: Array.isArray(raw.dependencies) ? raw.dependencies.map(String) : base.dependencies ?? [],\n prompt: text(raw.prompt) || base.prompt || (ai ? description || title || undefined : undefined),\n userSteps: Array.isArray(raw.userSteps)\n ? raw.userSteps.map((s) => (isRecord(s) ? s : {})).map((s) => ({ order: Number(s.order ?? 0), instruction: String(s.instruction ?? ''), completed: false }))\n : base.userSteps,\n subtasks,\n assignedModel,\n assignedRunner,\n taskMode,\n autonomy,\n sliceType,\n userStoriesCovered: Array.isArray(raw.userStoriesCovered) ? raw.userStoriesCovered.map(String) : base.userStoriesCovered,\n ops,\n skills: from ? inheritedSkills(raw.skills, base.skills) : skillNames(raw.skills),\n });\n if (Array.isArray(raw.skills)) skillsAsked.set(task, raw.skills);\n return { task, errors, ignored, renamed };\n}\n\n/**\n * The rules a plan's tasks are held to together, subtasks included, whichever\n * route they arrived by.\n */\nexport function checkPlanInvariants(tasks: readonly Task[], runners: readonly RunnerId[]): PlanTaskError[] {\n const rows = flattenTasksWithParents(tasks);\n const errors: PlanTaskError[] = [];\n const seen = new Set<string>();\n for (const { task: t, parent } of rows) {\n const where = taskWhere(t.title, parent?.title);\n const fail = (field: string, message: string) => errors.push({ taskId: t.id, field, message });\n if (seen.has(t.id)) fail('id', `${where} has id \"${t.id}\", which another task in the plan has too: every task, subtasks included, needs an id of its own`);\n seen.add(t.id);\n if (t.autonomy === 'AFK' && t.userSteps && t.userSteps.length > 0) {\n fail('userSteps', `${where} has autonomy \"AFK\" but contains userSteps. AFK tasks must not have user touchpoints.`);\n }\n if (t.type === 'user' && t.sliceType && t.sliceType !== 'HITL') {\n fail('sliceType', `User ${where} has sliceType \"${t.sliceType}\". User tasks must have sliceType \"HITL\".`);\n }\n const opsRefused = t.ops ? opsPlacementRefusal(t.type, parent !== null) : null;\n if (opsRefused) fail('ops', `${where} ${opsRefused}`);\n if (t.type !== 'user' && !runners.includes(t.assignedRunner)) {\n fail('assignedRunner', `${where} has invalid assignedRunner \"${t.assignedRunner}\". Expected one of: ${runners.join(', ')}`);\n }\n }\n const ids = new Set(rows.map((r) => r.task.id));\n for (const { task: t, parent } of rows) {\n for (const dep of t.dependencies) {\n if (!ids.has(dep)) errors.push({ taskId: t.id, field: 'dependencies', message: `${taskWhere(t.title, parent?.title)} depends on \"${dep}\", which is not a task in the plan` });\n }\n }\n const cycle = findCycle(rows.map((r) => r.task));\n // Pinned to a task on the cycle: a plan whose only fault is a rule a task\n // breaks is worth naming back to the planner, not re-asking for as JSON.\n if (cycle) errors.push({ taskId: cycle[0].id, field: 'dependencies', message: `Dependency cycle detected: ${[...cycle, cycle[0]].map((t) => `\"${t.title}\"`).join(' → ')}` });\n return errors;\n}\n\n/** The tasks on one dependency cycle, in dependency order, or null when there is none. A self-dependency is a cycle of one. */\nexport function findCycle(tasks: readonly Task[]): Task[] | null {\n const byId = new Map(tasks.map((t) => [t.id, t]));\n const path: string[] = [];\n const done = new Set<string>();\n const visit = (id: string): string[] | null => {\n if (done.has(id)) return null;\n const at = path.indexOf(id);\n if (at >= 0) return path.slice(at);\n path.push(id);\n for (const dep of byId.get(id)?.dependencies ?? []) {\n const hit = visit(dep);\n if (hit) return hit;\n }\n path.pop();\n done.add(id);\n return null;\n };\n for (const t of tasks) {\n const hit = visit(t.id);\n if (hit) return hit.map((id) => byId.get(id)!);\n }\n return null;\n}\n\n/** The title of a task on a dependency cycle, or null when there is none. */\nexport function detectCycle(tasks: readonly Task[]): string | null {\n return findCycle(tasks)?.[0].title ?? null;\n}\n","import { Task, flattenTasksWithParents, RunnerId, DiscoveredModel, TaskModelAssignment, TaskMode } from '../models/Task';\nimport { canSetDependencies } from './TaskOps';\nimport { effectiveAllowlist } from './ModelAllowlistResolver';\nimport { unavailableModeReason, type RunnerModeInfo } from './ModeResolver';\nimport { NOT_STARTED } from '../taskRow/status';\nimport { opsPlacementRefusal } from './PlannedTask';\n\n/**\n * Who is making the edit. The planner (`applyTaskOps`) and the direct-edit\n * path (`Session.updateTask`/`setTaskDependencies`, reached by the TUI, the\n * webview task card, and `PUT /tasks/:taskId`) are deliberately asymmetric —\n * see CONTEXT.md, \"Direct edit vs planner edit\". The planner may not reach\n * into a task that is running or already finished; the direct path may,\n * because it is the user editing their own plan, and pays what that costs at\n * the call site (cancelling a live runner, releasing blocked dependents).\n */\nexport type TaskEditActor = 'planner' | 'direct';\n\nexport interface TaskEditCheck {\n ok: boolean;\n error?: string;\n /**\n * Fields a type flip strips of meaning, to be force-cleared by the caller\n * (set to `undefined` in the applied changes) and named in whatever summary\n * reaches the transcript. Only lists fields the target actually had a value\n * for — nothing to clear reads as nothing lost.\n */\n clear?: (keyof Task)[];\n /** Fields a flip forces to a value rather than strips — a user task is always HITL — applied with {@link clear}. */\n set?: Partial<Task>;\n}\n\n/**\n * Why the planner may not touch a task execution has reached, worded with what\n * would be accepted instead: the note goes back to the model, mid-run\n * included, so it can act on it rather than retry the same edit (ADR-0029).\n * Null for a task the planner may edit.\n */\nexport function lockedTaskRefusal(task: Pick<Task, 'title' | 'status'>, action = 'changed'): string | null {\n if (task.status === 'in_progress') return `\"${task.title}\" is running and cannot be ${action} now: the user can stop it first, or change it after it finishes`;\n if (task.status === 'completed') return `\"${task.title}\" is completed and cannot be ${action}: rearm it to run it again, or add a new task for further work`;\n return null;\n}\n\nconst AI_ONLY_FIELDS = ['assignedModel', 'taskMode', 'autonomy', 'ops'] as const satisfies readonly (keyof Task)[];\n\n/**\n * The catalog a model/task-mode edit is checked against — the same discovered\n * models and manifest modes the planner was shown in the per-turn catalog\n * block (`PlannerConversation.catalogBlock`), so a refusal here can never name something\n * as invalid that the planner was never told about, or vice versa.\n */\nexport interface EditCatalog {\n modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>;\n runnerModes: Partial<Record<RunnerId, RunnerModeInfo[]>>;\n /** Raw (unfiltered) allowlist, keyed by runner — the same shape `coerceAssignments` takes. */\n perRunnerAllowlist?: Partial<Record<RunnerId, string[]>>;\n /** Full (true) or Guarded: the mode a task the planner writes lands on unless it is read-only (ADR-0031). Full when absent. */\n autonomousDefault?: boolean;\n}\n\n/**\n * Whether an assigned model and task mode are things the target runner\n * actually offers. Invented ids are refused rather than silently substituted\n * downstream (`coerceAssignments` remains the safety net for paths that never\n * go through this validator) — the planner gets the refusal, with the runner\n * named, through the same repair loop as every other edit rejection.\n *\n * `catalog` is optional: callers with no discovered catalog on hand (most unit\n * tests, a runner with no manifest modes) skip these checks rather than\n * refusing everything sight-unseen.\n */\nexport function checkModelAndModeValidity(\n runner: RunnerId,\n assignedModel: TaskModelAssignment | undefined,\n taskMode: TaskMode | undefined,\n catalog: EditCatalog | undefined,\n): TaskEditCheck {\n if (!catalog) return { ok: true };\n\n if (assignedModel) {\n const models = catalog.modelsByRunner[runner];\n if (models) {\n const modelId = assignedModel.modelId;\n const allowed = effectiveAllowlist(catalog.perRunnerAllowlist?.[runner], runner, catalog.modelsByRunner);\n // An allowlisted id discovery doesn't list is one `filterModelsForPrompt`\n // shows the planner anyway — refusing it here would contradict the\n // catalog block; the runner validates it last.\n if (!models.some((m) => m.modelId === modelId) && !allowed?.includes(modelId)) {\n return { ok: false, error: `Runner \"${runner}\" does not offer model \"${modelId}\"` };\n }\n if (allowed && !allowed.includes(modelId)) {\n return { ok: false, error: `Model \"${modelId}\" is excluded by the allowlist for runner \"${runner}\"` };\n }\n }\n }\n\n if (taskMode !== undefined) {\n const modes = catalog.runnerModes[runner];\n if (modes && modes.length > 0 && !modes.some((m) => m.id === taskMode)) {\n return { ok: false, error: `Runner \"${runner}\" has no task mode \"${taskMode}\" — valid modes: ${modes.map((m) => m.id).join(', ')}` };\n }\n const unavailable = unavailableModeReason(modes, taskMode);\n if (unavailable) return { ok: false, error: `Mode \"${taskMode}\" of runner \"${runner}\" is unavailable on this host: ${unavailable}` };\n }\n\n return { ok: true };\n}\n\n/**\n * Coherence between `type` and the fields that only make sense for one side\n * of it. Deriving user steps from an AI prompt (or vice versa) would fabricate\n * instructions nobody wrote (ADR-0001: the plan is the source of truth), so a\n * flip that lacks the content the new type needs is refused rather than\n * silently patched — the planner gets that refusal back through the repair\n * loop; a direct edit gets it as a thrown reason. A flip that IS well-formed\n * clears the fields that stopped meaning anything, named for the caller.\n */\nfunction typeCoherenceCheck(target: Task, changes: Partial<Task>): TaskEditCheck | null {\n if (!changes.type || changes.type === target.type) return null;\n\n if (changes.type === 'user') {\n if (!Array.isArray(changes.userSteps) || changes.userSteps.length === 0) {\n return { ok: false, error: `\"${target.title}\" cannot become a manual task without user steps — include \"userSteps\" in this edit` };\n }\n return {\n ok: true,\n clear: AI_ONLY_FIELDS.filter((f) => target[f] !== undefined),\n // User tasks are held to HITL, so a flip that kept the AI task's slice would be refused by the plan's own rules.\n ...(target.sliceType !== 'HITL' ? { set: { sliceType: 'HITL' as const } } : {}),\n };\n }\n\n // changes.type === 'ai'\n if (!changes.prompt || !changes.prompt.trim()) {\n return { ok: false, error: `\"${target.title}\" cannot become an AI task without a prompt — include \"prompt\" in this edit` };\n }\n return { ok: true, clear: target.userSteps && target.userSteps.length > 0 ? ['userSteps'] : [] };\n}\n\n/**\n * Whether a task may become an ops task, or stop being one (ADR-0020). Only a\n * top-level AI task can be ops, and the flag decides where the task runs, so\n * once it has started it is fixed, like its runner session.\n */\nfunction opsCheck(target: Task, isSubtask: boolean, changes: Partial<Task>): TaskEditCheck | null {\n if (!('ops' in changes) || !!changes.ops === !!target.ops) return null;\n if (!NOT_STARTED.has(target.status)) {\n return { ok: false, error: `\"${target.title}\" has started, so whether it is an ops task is fixed` };\n }\n if (!changes.ops) return null;\n const refused = opsPlacementRefusal(changes.type ?? target.type, isSubtask);\n return refused ? { ok: false, error: `\"${target.title}\" ${refused}` } : null;\n}\n\n/**\n * The rules governing one task edit, shared by both actors. Lock rules\n * (running/completed) apply to the planner only. Well-formedness rules — like\n * a hand-set dependency list, the one edit that can leave the plan\n * unschedulable, and type/content coherence on an AI↔MAN flip — describe the\n * task rather than who asked, so they run for both, through the same\n * {@link canSetDependencies} guard the pickers and the API read.\n *\n * `tasks` is the plan tree: the target is found at any depth, and its\n * dependencies are checked among its siblings, the list its order is in.\n */\nexport function validateTaskEdit(\n actor: TaskEditActor,\n tasks: readonly Task[],\n taskId: string,\n changes: Partial<Task>,\n catalog?: EditCatalog,\n): TaskEditCheck {\n const found = flattenTasksWithParents(tasks).find(({ task }) => task.id === taskId);\n if (!found) return { ok: false, error: 'Task not found' };\n const { task: target, parent } = found;\n\n const locked = actor === 'planner' ? lockedTaskRefusal(target, 'modified') : null;\n if (locked) return { ok: false, error: locked };\n\n if (changes.dependencies) {\n const depCheck = canSetDependencies(parent?.subtasks ?? tasks, taskId, changes.dependencies);\n if (!depCheck.ok) return depCheck;\n }\n\n const coherence = typeCoherenceCheck(target, changes);\n if (coherence && !coherence.ok) return coherence;\n\n const ops = opsCheck(target, parent !== null, changes);\n if (ops) return ops;\n\n const validity = checkModelAndModeValidity(changes.assignedRunner ?? target.assignedRunner, changes.assignedModel, changes.taskMode, catalog);\n if (!validity.ok) return validity;\n\n return coherence ?? { ok: true };\n}\n","import {\n Task, RunnerId, flattenTasks,\n addTaskToPlan, removeTaskFromPlan, updateTaskInPlan, renumberTasks,\n opsFlag, inheritedOps, skillNames,\n} from '../models/Task';\nimport { resolveTaskRef } from './TaskRefs';\nimport { extractObjectsWithKey, stripTrailingCommas, escapeControlCharsInStrings, PlanParseError, TASK_OPS_ENVELOPE_KEY } from './JsonExtractor';\nimport { validateTaskEdit, lockedTaskRefusal, type EditCatalog, type TaskEditActor, type TaskEditCheck } from './TaskEditValidator';\nimport { plannedTask, checkPlanInvariants, detectCycle, taskWhere, MODE_NOT_THE_PLANNERS, MANUAL_NOT_READ_ONLY, OPS_NOT_READ_ONLY, type IntakeCatalog, type PlannedTask, type PlanTaskError } from './PlannedTask';\nimport { plannedMode, taggedReadOnlyMode, type PlannedMode } from './ModeResolver';\n\n/**\n * Targeted task edits emitted by the planner conversation (the 'task_ops'\n * ConversationTurn). Task references accept a task id, a \"#<order>\" ref, or a\n * bare order number — cheap models rarely echo UUIDs correctly.\n */\n/**\n * A task as the planner writes it: its fields, less the mode, plus whether it\n * must not change files (ADR-0031), and `thinkingEffort` as a name for its\n * model's effort, so an effort can change without restating the model.\n */\nexport type PlannerTaskSpec = Partial<Task> & { readOnly?: boolean; thinkingEffort?: string | null };\n\nexport type TaskOp =\n | { op: 'update'; taskId: string; changes: PlannerTaskSpec }\n | { op: 'add'; task: PlannerTaskSpec; handle?: string }\n | { op: 'remove'; taskId: string }\n | { op: 'reorder'; taskIds: string[] }\n | { op: 'merge'; taskIds: string[]; merged: PlannerTaskSpec; handle?: string }\n | { op: 'split'; taskId: string; parts: PlannerTaskSpec[]; handle?: string }\n | { op: 'rearm'; taskId: string; changes?: PlannerTaskSpec };\n\n/** Fields the planner may change on an existing task. Everything else (id, status, verdict…) is system-owned. */\nexport const UPDATABLE_FIELDS: (keyof Task)[] = [\n 'title', 'description', 'prompt', 'dependencies', 'assignedRunner', 'assignedModel', 'taskMode', 'type', 'userSteps', 'autonomy', 'sliceType', 'ops', 'skills',\n];\n\n/**\n * The edit protocol as the planner reads it, kept beside the applier so the\n * prose can never undersell (or oversell) what {@link applyTaskOps} accepts.\n * `executionNote` rides on the ordering rule because that is the rule a\n * running execution changes.\n */\nexport function taskOpsProtocol(executionNote = '', tools = false): string[] {\n const updateChanges = [...UPDATABLE_FIELDS.filter((f) => f !== 'taskMode'), 'thinkingEffort', 'readOnly']\n .map((f) => (f === 'dependencies' ? '\"dependencies\"?:[\"<id or #order>\"]' : `\"${f}\"?`))\n .join(',');\n return [\n tools ? '- To modify specific tasks, call edit_plan, never a JSON reply. Its argument is:' : '- To modify specific tasks, reply with ONLY this JSON object:',\n tools ? ' {\"ops\": [' : ' {\"taskOps\": [',\n ` {\"op\":\"update\",\"taskId\":\"<id or #order>\",\"changes\":{${updateChanges}}},`,\n ' {\"op\":\"add\",\"task\":{\"title\",\"description\",\"prompt\",\"dependencies\":[\"<id or #order>\"],\"assignedRunner\"?,\"assignedModel\"?,\"readOnly\"?,\"ops\"?},\"handle\"?:\"<name>\"},',\n ' {\"op\":\"remove\",\"taskId\":\"<id or #order>\"},',\n ' {\"op\":\"reorder\",\"taskIds\":[\"<id or #order>\", \"... every task exactly once\"]}, // only to re-prioritise INDEPENDENT tasks',\n ' {\"op\":\"merge\",\"taskIds\":[\"<id or #order>\", \"...\"],\"merged\":{\"title\",\"description\",\"prompt\"?,\"assignedRunner\"?,\"assignedModel\"?,...},\"handle\"?:\"<name>\"},',\n ' {\"op\":\"split\",\"taskId\":\"<id or #order>\",\"parts\":[{\"title\",\"description\",\"prompt\"?,\"assignedRunner\"?,\"assignedModel\"?,...}, ...],\"handle\"?:\"<name>\"},',\n ` {\"op\":\"rearm\",\"taskId\":\"<id or #order>\",\"changes\"?:{${updateChanges}}}`,\n ' ]}',\n tools ? '- For sweeping changes, you may instead call submit_plan with the full plan.' : '- For sweeping changes, you may instead emit a full {\"tasks\":[...]} plan JSON.',\n 'Every \"<id or #order>\" ref in a batch resolves against the plan shown above, before any op in the batch runs — an earlier remove/merge/split never shifts what a later \"#N\" means. \"#N\" is top-level task N; a subtask is named by its dotted label (\"#2.1\"), as the plan lists it.',\n 'Give \"add\", \"merge\", or \"split\" a \"handle\" (any name you choose, unused elsewhere in this batch) to let a LATER op in the same batch reference the task it produces — for \"split\", the handle names its last part. A handle used before its defining op is rejected.',\n `When creating or changing tasks, set \"assignedRunner\" and \"assignedModel\" ({\"modelId\",\"modelLabel\",\"thinkingEffort\"?}) using only the runners and models ${tools ? 'list_runners and list_models return' : 'listed in <available_models>'}.`,\n 'Set \"readOnly\": true on a task that must not change any file, never on one that must run commands (tests, scripts, builds) or an ops task, and \"readOnly\": false to lift it; you never set \"taskMode\". A task you move, split or edit keeps being read-only or not unless you say otherwise, and so do the subtasks of a read-only task you add.',\n 'Keep dependencies consistent: no cycles, no references to removed tasks, and never touch running or completed tasks — \"rearm\" is the one exception, below.',\n ...(tools ? ['edit_plan checks the batch when you call it and answers with what it will change, or with the error to fix — call it again in the same reply. The edit is applied when your reply ends; edits you make in one reply join one batch.'] : []),\n 'Just declare the dependencies you want — display order is repaired for you afterwards, so a rewire or a newly added prerequisite never needs a \"reorder\" op. Only a task that is running or completed cannot be shifted, so an edit that would need one to move is rejected.' + executionNote,\n 'Flipping \"type\" between \"ai\" and \"user\" is a content change, not just a label: an update to \"user\" needs \"userSteps\" in the SAME op, and an update to \"ai\" needs \"prompt\" in the SAME op — the model/mode/effort/autonomy fields (flipping to \"user\", which also makes the task HITL) or the userSteps (flipping to \"ai\") are cleared automatically.',\n '\"skills\" in \"changes\" REPLACES the task\\'s whole skill list. To add one, send the names the plan block shows in its skills:[...] plus the new one; to drop one, send the rest; [] removes them all.',\n '\"rearm\" puts a failed OR completed task back to pending — verdict and output summary are cleared, any dependents it had blocked are released, and it may carry field changes (e.g. a corrected \"prompt\") applied in the same op. A running task cannot be re-armed. Never rearm a task just to relabel it — only when it should actually run again.',\n ];\n}\n\nexport function textHasTaskOps(text: string): boolean {\n return text.includes(`\"${TASK_OPS_ENVELOPE_KEY}\"`);\n}\n\n/** Parse a `{\"taskOps\":[...]}` reply. Throws PlanParseError when the JSON is unusable. */\nexport function parseTaskOpsJson(text: string): TaskOp[] {\n // Extract by the taskOps key specifically — a reply may also carry a\n // tasks-keyed object (schema echo, example), which must not shadow the ops.\n // Candidates are tried back to front: the last emission is the final answer.\n const { matches, fallback } = extractObjectsWithKey(text, TASK_OPS_ENVELOPE_KEY);\n const candidates = matches.length > 0 ? [...matches].reverse() : [fallback.json];\n\n let lastError: PlanParseError | null = null;\n for (const json of candidates) {\n try {\n return parseTaskOpsObject(json, text);\n } catch (err) {\n if (!(err instanceof PlanParseError)) throw err;\n if (!lastError) lastError = err;\n }\n }\n throw lastError ?? new PlanParseError('No JSON object found in task-ops reply', text);\n}\n\nfunction parseTaskOpsObject(json: string, text: string): TaskOp[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(escapeControlCharsInStrings(stripTrailingCommas(json)));\n } catch (err) {\n throw new PlanParseError(`Task-ops JSON is invalid: ${err instanceof Error ? err.message : String(err)}`, text);\n }\n const ops = (parsed as { taskOps?: unknown }).taskOps;\n if (!Array.isArray(ops) || ops.length === 0) {\n throw new PlanParseError('\"taskOps\" must be a non-empty array', text);\n }\n for (const op of ops) {\n if (typeof op !== 'object' || op === null || typeof (op as { op?: unknown }).op !== 'string') {\n throw new PlanParseError('Every task op needs an \"op\" field (update | add | remove | reorder | merge | split)', text);\n }\n }\n return ops as TaskOp[];\n}\n\ntype RefResolution = { id: string } | { error: string };\n\n/**\n * Resolve an op's task reference to a task id: a batch handle, or anything\n * {@link resolveTaskRef} reads.\n *\n * Order labels and title matches are pinned to `originalTree` — the plan as it\n * stood before the batch began — so an earlier op's renumbering can never\n * make a later op's \"#4\" land on a different task than the planner saw. A\n * literal id has no such drift (ids are stable across mutations), so id\n * matches check `liveTree` — the batch's current, mutated tasks — which is\n * also how a later op reaches a task created earlier in the same batch\n * without a handle (e.g. a split's own chained parts).\n */\nfunction resolveRef(\n ref: unknown,\n originalTree: readonly Task[],\n liveTree: Task[],\n handleOwner: Map<string, number>,\n handleId: Map<string, string>,\n opIndex: number,\n): RefResolution {\n if (typeof ref !== 'string' && typeof ref !== 'number') return { error: `invalid reference \"${String(ref)}\"` };\n const s = String(ref).trim();\n\n const ownerIdx = handleOwner.get(s);\n if (ownerIdx !== undefined) {\n if (ownerIdx >= opIndex) {\n return { error: `handle \"${s}\" is defined by a later op in this batch and cannot be referenced yet` };\n }\n const id = handleId.get(s);\n if (!id) return { error: `handle \"${s}\" was never resolved` };\n return { id };\n }\n\n if (findInTasks(liveTree, s)) return { id: s };\n const task = resolveTaskRef(originalTree, s);\n return task ? { id: task.id } : { error: `task \"${s}\" not found` };\n}\n\n/** The fields of a planner's `changes` it may set, with the ops flag read as every entry point reads it. */\nfunction updatableChanges(raw: Partial<Task> | undefined): Partial<Task> {\n const changes: Partial<Task> = {};\n for (const key of UPDATABLE_FIELDS) {\n if (raw && key in raw) (changes as Record<string, unknown>)[key] = (raw as Record<string, unknown>)[key];\n }\n if ('ops' in changes) changes.ops = opsFlag(changes.ops);\n if ('skills' in changes) changes.skills = skillNames(changes.skills);\n return changes;\n}\n\nfunction findInTasks(tasks: readonly Task[], id: string): Task | undefined {\n return flattenTasks(tasks).find((t) => t.id === id);\n}\n\nfunction findTopLevel(tasks: Task[], id: string): Task | undefined {\n return tasks.find((t) => t.id === id);\n}\n\n/** A task the repair may not reposition: execution has already reached it. */\nfunction isPinned(t: Task): boolean {\n return t.status === 'in_progress' || t.status === 'completed';\n}\n\n/**\n * Reorder top-level tasks so every dependency comes before its dependents,\n * moving as little as possible: pinned tasks keep the exact slot they are in,\n * and each free slot goes to the earliest-listed movable task whose\n * dependencies are already placed — so tasks the graph does not constrain keep\n * their relative order. Assumes an acyclic graph (the caller checks first).\n */\nfunction repairOrder(tasks: Task[]): { tasks: Task[] } | { error: string } {\n const byId = new Map(tasks.map((t) => [t.id, t]));\n const placed = new Set<string>();\n const movable = tasks.filter((t) => !isPinned(t));\n const result: Task[] = [];\n // Dependencies outside this list (a subtask id) constrain nothing here.\n const blockerOf = (t: Task): Task | undefined => {\n const id = t.dependencies.find((d) => byId.has(d) && !placed.has(d));\n return id ? byId.get(id) : undefined;\n };\n const place = (t: Task) => { result.push(t); placed.add(t.id); };\n\n for (const occupant of tasks) {\n if (isPinned(occupant)) {\n const blocker = blockerOf(occupant);\n if (blocker) return { error: pinnedRefusal(blocker, occupant, 'before') };\n place(occupant);\n continue;\n }\n const next = movable.findIndex((t) => !blockerOf(t));\n if (next < 0) {\n // Nothing is schedulable and the graph is acyclic, so every movable task\n // left waits — directly or through others — on a pinned task further down\n // that would have to come up to free this slot.\n const [stuck, pin] = trailToPin(movable[0], blockerOf);\n return { error: pinnedRefusal(stuck, pin, 'after') };\n }\n place(movable.splice(next, 1)[0]);\n }\n return { tasks: result };\n}\n\n/** Walk the waiting-on chain from `task` down to the pinned task holding it up. */\nfunction trailToPin(task: Task, blockerOf: (t: Task) => Task | undefined): [Task, Task] {\n let at = task;\n for (;;) {\n const blocker = blockerOf(at);\n if (!blocker || isPinned(blocker)) return [at, blocker ?? at];\n at = blocker;\n }\n}\n\n/** `\"B\" #2→#1, \"A\" #1→#3` for every task the repair actually moved, or null. */\nfunction describeMoves(before: Task[], after: Task[]): string | null {\n const wasAt = new Map(before.map((t, i) => [t.id, i + 1]));\n const moved = after\n .map((t, i) => ({ t, from: wasAt.get(t.id)!, to: i + 1 }))\n .filter((m) => m.from !== m.to);\n return moved.length ? moved.map((m) => `\"${m.t.title}\" #${m.from}→#${m.to}`).join(', ') : null;\n}\n\nfunction pinnedRefusal(other: Task, pin: Task, side: 'before' | 'after'): string {\n const word = pin.status === 'in_progress' ? 'running' : 'completed';\n return `\"${other.title}\" would have to run ${side} \"${pin.title}\", which is ${word} and cannot be moved`;\n}\n\nexport interface ApplyTaskOpsResult {\n ok: boolean;\n tasks: readonly Task[];\n errors: string[];\n /** Human-readable summary of what changed, for the chat transcript. */\n summary: string[];\n}\n\n/**\n * Pre-flight check for a merge: would collapsing the selected tasks into one\n * (rewiring every dependent of any selected task to the survivor) keep the\n * dependency graph valid? Rejects locked tasks and merges whose rewiring would\n * introduce a cycle. Display order is NOT checked here — neither\n * consecutiveness (a UI concern) nor a dependency the merge pulls out of order,\n * which {@link applyTaskOps} repairs; refusing on order would make this\n * pre-flight stricter than the applier it stands in for.\n */\nexport function canMergeTasks(tasks: readonly Task[], selectedIds: string[]): { ok: boolean; error?: string } {\n if (selectedIds.length < 2) return { ok: false, error: 'Select at least two tasks to merge' };\n const idSet = new Set(selectedIds);\n const toMerge = tasks.filter((t) => idSet.has(t.id));\n if (toMerge.length < 2) return { ok: false, error: 'At least two valid tasks are required to merge' };\n for (const t of toMerge) {\n if (t.status === 'in_progress') return { ok: false, error: `\"${t.title}\" is running and cannot be merged` };\n if (t.status === 'completed') return { ok: false, error: `\"${t.title}\" is completed and cannot be merged` };\n }\n const ordered = [...toMerge].sort((a, b) => a.order - b.order);\n const survivorId = '__merge_sim__';\n const sim: Task[] = tasks\n .filter((t) => !idSet.has(t.id))\n .map((t) => ({ ...t, dependencies: t.dependencies.map((d) => (idSet.has(d) ? survivorId : d)).filter((d) => d !== t.id) }));\n const unionDeps = [...new Set(toMerge.flatMap((t) => t.dependencies))].filter((d) => !idSet.has(d));\n sim.push({ ...ordered[0], id: survivorId, title: '__merge_sim__', order: ordered[0].order, dependencies: unionDeps });\n const cycle = detectCycle(sim);\n if (cycle) return { ok: false, error: `Merging would create a dependency cycle involving \"${cycle}\"` };\n return { ok: true };\n}\n\n/**\n * The shape the dependency helpers below read. Structural rather than `Task`,\n * because the TUI projects tasks into its own `TaskView`: a surface's\n * dependency picker and the API's validation must agree on which dependencies\n * are legal, and a signature only core can satisfy would have forced the TUI\n * to keep a second copy of the rule.\n */\nexport interface TaskRef {\n id: string;\n order: number;\n title: string;\n dependencies: string[];\n}\n\n/** The tasks listing `taskId` as a dependency — exactly what removing it detaches. */\nexport function dependentsOf<T extends Pick<TaskRef, 'id' | 'dependencies'>>(tasks: T[], taskId: string): T[] {\n return tasks.filter((t) => t.id !== taskId && t.dependencies.includes(taskId));\n}\n\n/**\n * The tasks that may become dependencies of `taskId`: those displayed before it.\n *\n * Offering only earlier tasks is what keeps a hand-edited graph valid without a\n * cycle check — dependencies then only ever point backwards in display order,\n * the same invariant `applyTaskOps` enforces. Omit `taskId` for a task that does\n * not exist yet: it lands last, so every current task is a candidate.\n */\nexport function dependencyCandidates<T extends Pick<TaskRef, 'id' | 'order'>>(tasks: readonly T[], taskId?: string): T[] {\n if (!taskId) return [...tasks];\n const target = tasks.find((t) => t.id === taskId);\n return target ? tasks.filter((t) => t.order < target.order) : [];\n}\n\n/** Pre-flight check for a hand-edited dependency list: every id exists and comes earlier. */\nexport function canSetDependencies<T extends TaskRef>(\n tasks: readonly T[],\n taskId: string,\n dependencies: string[],\n): { ok: boolean; error?: string } {\n const target = tasks.find((t) => t.id === taskId);\n if (!target) return { ok: false, error: 'Task not found' };\n const allowed = new Set(dependencyCandidates(tasks, taskId).map((t) => t.id));\n for (const dep of dependencies) {\n if (dep === taskId) return { ok: false, error: `\"${target.title}\" cannot depend on itself` };\n const found = tasks.find((t) => t.id === dep);\n if (!found) return { ok: false, error: `Unknown dependency \"${dep}\"` };\n if (!allowed.has(dep)) {\n return { ok: false, error: `\"${target.title}\" cannot depend on \"${found.title}\", which comes after it in the plan` };\n }\n }\n return { ok: true };\n}\n\n/** Pre-flight check for a split: the task exists and is not locked by execution. */\nexport function canSplitTask(tasks: readonly Task[], taskId: string): { ok: boolean; error?: string } {\n const target = tasks.find((t) => t.id === taskId);\n if (!target) return { ok: false, error: 'Task not found' };\n if (target.status === 'in_progress') return { ok: false, error: `\"${target.title}\" is running and cannot be split` };\n if (target.status === 'completed') return { ok: false, error: `\"${target.title}\" is completed and cannot be split` };\n return { ok: true };\n}\n\n\nfunction askedReadOnly(spec: PlannerTaskSpec | undefined): boolean | undefined {\n return typeof spec?.readOnly === 'boolean' ? spec.readOnly : undefined;\n}\n\nfunction isReadOnly(task: Task, catalog: EditCatalog | undefined): boolean {\n return task.taskMode !== undefined && task.taskMode === taggedReadOnlyMode(catalog?.runnerModes[task.assignedRunner]);\n}\n\n/**\n * The mode a planner edit lands a task on (ADR-0031). A task stays read-only\n * or not unless the planner says otherwise, so a move or split never quietly\n * gives a read-only task write access. Undefined mode with no catalog to read\n * the runner's modes from.\n */\nfunction landingMode(runner: RunnerId, readOnly: boolean | undefined, current: string | undefined, catalog: EditCatalog | undefined): PlannedMode {\n return plannedMode(runner, catalog?.runnerModes, { readOnly, current }, catalog?.autonomousDefault ?? true);\n}\n\n/** The fields an edit changed, as the planner names them: `readOnly` when that flipped, never the mode it landed on (ADR-0031). */\nfunction changedFields(target: Task, changes: Partial<Task>, catalog: EditCatalog | undefined): string[] {\n const fields = Object.keys(changes).filter((f) => f !== 'taskMode');\n if ('taskMode' in changes && isReadOnly({ ...target, ...changes }, catalog) !== isReadOnly(target, catalog)) fields.push('readOnly');\n return fields;\n}\n\n/**\n * Apply task ops to a snapshot of the plan, atomically: either every op\n * applies and the result validates (deps resolve, no cycles, running and\n * completed tasks untouched), or nothing is returned and `errors` explains\n * why. The caller commits `tasks` on ok.\n */\nexport function applyTaskOps(\n currentTasks: readonly Task[],\n newOps: TaskOp[],\n runners: RunnerId[],\n catalog?: EditCatalog,\n held: readonly TaskOp[] = [],\n): ApplyTaskOpsResult {\n // Ops already accepted earlier in the batch apply first; errors number only\n // the new ones, as the planner wrote them in the call it is answered for.\n const ops = [...held, ...newOps];\n const opLabel = (i: number) => (i < held.length ? `earlier op ${i + 1} (${ops[i].op})` : `op ${i - held.length + 1} (${ops[i].op})`);\n let tasks: Task[] = JSON.parse(JSON.stringify(currentTasks));\n const errors: string[] = [];\n const summary: string[] = [];\n\n // Batch handles: planner-chosen names for a task an add/merge/split op is\n // about to create, so a later op in the same batch can reference it before\n // it has a real id. Pre-scanned up front — a bad handle refuses the whole\n // batch before anything mutates.\n const handleOwner = new Map<string, number>();\n const handleId = new Map<string, string>();\n for (const [i, op] of ops.entries()) {\n const handle = (op as { handle?: unknown }).handle;\n if (handle === undefined) continue;\n if (typeof handle !== 'string' || !handle.trim()) {\n errors.push(`${opLabel(i)}: handle must be a non-empty string`);\n continue;\n }\n if (handleOwner.has(handle)) {\n errors.push(`${opLabel(i)}: handle \"${handle}\" is already used earlier in this batch`);\n continue;\n }\n if (resolveTaskRef(currentTasks, handle)) {\n errors.push(`${opLabel(i)}: handle \"${handle}\" collides with an existing task reference`);\n continue;\n }\n handleOwner.set(handle, i);\n }\n if (errors.length) return { ok: false, tasks: currentTasks, errors, summary: [] };\n\n const resolveDeps = (deps: unknown, opIndex: number): { ids: string[]; bad: string[] } => {\n const ids: string[] = [];\n const bad: string[] = [];\n const liveFlat = flattenTasks(tasks);\n for (const d of Array.isArray(deps) ? deps : []) {\n const r = resolveRef(d, currentTasks, tasks, handleOwner, handleId, opIndex);\n if ('error' in r) { bad.push(`${String(d)} (${r.error})`); continue; }\n if (!liveFlat.some((t) => t.id === r.id)) { bad.push(`${String(d)} (no longer exists in this batch)`); continue; }\n ids.push(r.id);\n }\n return { ids, bad };\n };\n\n const intake: IntakeCatalog = { runners, runnerModes: catalog?.runnerModes, autonomousDefault: catalog?.autonomousDefault, models: catalog };\n\n /**\n * A planned task's dependency refs, at every depth, as ids: a subtask may\n * name a task of its own tree by the id the planner gave it, or any task in\n * the plan the way every op does.\n */\n const resolveTree = (planned: PlannedTask, opIndex: number): { task: Task } | { error: string } => {\n const walk = (t: Task, parentTitle?: string): Task | string => {\n const ids: string[] = [];\n const bad: string[] = [];\n for (const d of t.dependencies) {\n const own = planned.renamed.get(d);\n if (own) { ids.push(own); continue; }\n const r = resolveDeps([d], opIndex);\n ids.push(...r.ids);\n bad.push(...r.bad);\n }\n if (bad.length) return `unknown dependencies${parentTitle !== undefined ? ` of ${taskWhere(t.title, parentTitle)}` : ''}: ${bad.join(', ')}`;\n const subtasks: Task[] = [];\n for (const sub of t.subtasks) {\n const done = walk(sub, t.title);\n if (typeof done === 'string') return done;\n subtasks.push(done);\n }\n return { ...t, dependencies: ids.filter((id) => id !== t.id), subtasks };\n };\n const done = walk(planned.task);\n return typeof done === 'string' ? { error: done } : { task: done };\n };\n\n /** Why the edit cannot have this planned task: a rule it breaks, or something it asked for that is not the planner's. */\n const refusalOf = (planned: PlannedTask): string | undefined => [...planned.ignored, ...planned.errors][0]?.message;\n\n /**\n * The resolve-and-validate half shared by \"update\" and \"rearm\": resolve the\n * target, apply the field allowlist and runner check, run the edit validator\n * under the op's audience, then resolve any dependency refs. `refuseRunning`\n * is rearm's extra lock check, kept before the field work so its message wins.\n */\n const prepareEdit = (\n op: { taskId: string; changes?: PlannerTaskSpec },\n opIndex: number,\n label: string,\n audience: TaskEditActor,\n refuseRunning = false,\n ): { target: Task; changes: Partial<Task>; check: TaskEditCheck } | { error: string } => {\n const ref = resolveRef(op.taskId, currentTasks, tasks, handleOwner, handleId, opIndex);\n if ('error' in ref) return { error: `${label}: ${ref.error}` };\n const target = findInTasks(tasks, ref.id);\n if (!target) return { error: `${label}: task \"${op.taskId}\" no longer exists in this batch (removed, merged, or split by an earlier op)` };\n if (refuseRunning && target.status === 'in_progress') return { error: `${label}: ${lockedTaskRefusal(target, 're-armed')}` };\n if (op.changes && 'taskMode' in op.changes) return { error: `${label}: ${MODE_NOT_THE_PLANNERS}` };\n const changes = updatableChanges(op.changes);\n if (op.changes && 'thinkingEffort' in op.changes) {\n const model = changes.assignedModel ?? target.assignedModel;\n if (!model) return { error: `${label}: \"thinkingEffort\" is the assigned model's — set \"assignedModel\" with it` };\n changes.assignedModel = { ...model, thinkingEffort: op.changes.thinkingEffort ?? undefined };\n }\n // Dependency refs are still unresolved (e.g. \"#3\") at this point, so the\n // check runs on everything but them; resolveDeps below handles them once\n // they're real ids.\n const changesForCheck: Partial<Task> = { ...changes };\n delete changesForCheck.dependencies;\n const check = validateTaskEdit(audience, tasks, target.id, changesForCheck, catalog);\n if (!check.ok) return { error: `${label}: ${check.error}` };\n for (const field of check.clear ?? []) (changes as Record<string, unknown>)[field] = undefined;\n Object.assign(changes, check.set);\n const readOnly = askedReadOnly(op.changes);\n if (readOnly === true && (changes.type ?? target.type) === 'user') return { error: `${label}: ${MANUAL_NOT_READ_ONLY}` };\n if ((changes.ops ?? target.ops) && (readOnly ?? isReadOnly(target, catalog))) return { error: `${label}: ${OPS_NOT_READ_ONLY}` };\n const moved = changes.assignedRunner !== undefined && changes.assignedRunner !== target.assignedRunner;\n const becomesAi = changes.type === 'ai' && target.type === 'user';\n // A runner not allowed has no mode to land on; the plan's rules refuse it once the op is applied.\n const known = runners.includes(changes.assignedRunner ?? target.assignedRunner);\n if (known && (readOnly !== undefined || moved || becomesAi) && (changes.type ?? target.type) !== 'user') {\n const mode = landingMode(changes.assignedRunner ?? target.assignedRunner, readOnly ?? isReadOnly(target, catalog), target.taskMode, catalog);\n if (!mode.ok) return { error: `${label}: ${mode.error}` };\n if (mode.mode !== undefined && mode.mode !== target.taskMode) changes.taskMode = mode.mode;\n }\n if ('dependencies' in changes) {\n const { ids, bad } = resolveDeps(changes.dependencies, opIndex);\n if (bad.length) return { error: `${label}: unknown dependencies: ${bad.join(', ')}` };\n changes.dependencies = ids.filter((id) => id !== target.id);\n }\n return { target, changes, check };\n };\n\n // A rule the batch ends in breach of is the op's that first broke it. What\n // the plan already broke before the batch — a task on a runner disabled\n // since, say — is not, so it never blocks an unrelated edit. A breach is\n // told by its task and rule, not its wording: the wording carries the title,\n // which an edit may change.\n const breachKey = (e: PlanTaskError) => `${e.taskId ?? ''}\\u0000${e.field}`;\n const standing = new Set(checkPlanInvariants(currentTasks, runners).map(breachKey));\n const brokenBy = new Map<string, string>();\n let lastLabel = opLabel(0);\n\n for (const [i, op] of ops.entries()) {\n const label = opLabel(i);\n switch (op.op) {\n case 'update': {\n const prep = prepareEdit(op, i, label, 'planner');\n if ('error' in prep) { errors.push(prep.error); break; }\n const { target, changes, check } = prep;\n if (Object.keys(changes).length === 0) {\n const readOnly = askedReadOnly(op.changes);\n if (readOnly !== undefined && isReadOnly(target, catalog) === readOnly) { summary.push(`\"${target.title}\" is already ${readOnly ? '' : 'not '}read-only`); break; }\n errors.push(`${label}: no valid changes provided`);\n break;\n }\n tasks = updateTaskInPlan(tasks, target.id, changes);\n const clearedNote = check.clear?.length ? `; cleared ${check.clear.join(', ')}` : '';\n const fields = changedFields(target, changes, catalog);\n summary.push(`Updated \"${target.title}\"${fields.length ? ` (${fields.join(', ')})` : ''}${clearedNote}`);\n break;\n }\n case 'add': {\n const spec = op.task ?? {};\n if (!spec.title) { errors.push(`${label}: new task needs a title`); break; }\n const planned = plannedTask(spec, intake, { freshIds: true });\n const refused = refusalOf(planned);\n if (refused) { errors.push(`${label}: ${refused}`); break; }\n const resolved = resolveTree(planned, i);\n if ('error' in resolved) { errors.push(`${label}: ${resolved.error}`); break; }\n tasks = addTaskToPlan(tasks, resolved.task);\n if (op.handle) handleId.set(op.handle, resolved.task.id);\n summary.push(`Added \"${spec.title}\"`);\n break;\n }\n case 'remove': {\n const ref = resolveRef(op.taskId, currentTasks, tasks, handleOwner, handleId, i);\n if ('error' in ref) { errors.push(`${label}: ${ref.error}`); break; }\n const target = findInTasks(tasks, ref.id);\n if (!target) { errors.push(`${label}: task \"${op.taskId}\" no longer exists in this batch (removed, merged, or split by an earlier op)`); break; }\n const locked = lockedTaskRefusal(target, 'removed');\n if (locked) { errors.push(`${label}: ${locked}`); break; }\n tasks = removeTaskFromPlan(tasks, target.id);\n summary.push(`Removed \"${target.title}\"`);\n break;\n }\n case 'reorder': {\n const refs = (op.taskIds ?? []).map((r) => resolveRef(r, currentTasks, tasks, handleOwner, handleId, i));\n const badRef = refs.find((r): r is { error: string } => 'error' in r);\n if (badRef) { errors.push(`${label}: ${badRef.error}`); break; }\n const resolved = (refs as { id: string }[]).map((r) => findTopLevel(tasks, r.id));\n if (resolved.some((t) => !t)) { errors.push(`${label}: unknown task in reorder list`); break; }\n const orderIds = resolved.map((t) => t!.id);\n if (new Set(orderIds).size !== tasks.length) {\n errors.push(`${label}: reorder must list every top-level task exactly once (${tasks.length} tasks)`);\n break;\n }\n const byId = new Map(tasks.map((t) => [t.id, t]));\n tasks = renumberTasks(orderIds.map((id) => byId.get(id)!));\n summary.push('Reordered tasks');\n break;\n }\n case 'merge': {\n const refs = (op.taskIds ?? []).map((r) => resolveRef(r, currentTasks, tasks, handleOwner, handleId, i));\n const badRef = refs.find((r): r is { error: string } => 'error' in r);\n if (badRef) { errors.push(`${label}: ${badRef.error}`); break; }\n const resolved = (refs as { id: string }[]).map((r) => findTopLevel(tasks, r.id));\n if (resolved.some((t) => !t)) { errors.push(`${label}: unknown task in merge list (already removed, merged, or split by an earlier op)`); break; }\n const toMerge = [...new Map(resolved.map((t) => [t!.id, t!])).values()].sort((a, b) => a.order - b.order);\n if (toMerge.length < 2) { errors.push(`${label}: merge needs at least two tasks`); break; }\n const locked = toMerge.map((t) => lockedTaskRefusal(t, 'merged')).find((refusal) => refusal !== null);\n if (locked) { errors.push(`${label}: ${locked}`); break; }\n const idSet = new Set(toMerge.map((t) => t.id));\n const unionDepsRaw = [...new Set(toMerge.flatMap((t) => t.dependencies))].filter((d) => !idSet.has(d));\n const { ids, bad } = resolveDeps(unionDepsRaw, i);\n if (bad.length) { errors.push(`${label}: unknown dependencies: ${bad.join(', ')}`); break; }\n const spec = op.merged ?? {};\n if (!spec.title) { errors.push(`${label}: merged task needs a title`); break; }\n // Read-only only when every merged task was: one that writes makes the merged task write.\n const writes = toMerge.find((t) => !isReadOnly(t, catalog));\n const stories = [...new Set(toMerge.flatMap((t) => t.userStoriesCovered ?? []))];\n const planned = plannedTask(spec, intake, {\n freshIds: true,\n from: {\n readOnly: !writes,\n fields: {\n description: toMerge.map((t) => t.description).filter(Boolean).join('\\n\\n'),\n prompt: toMerge.map((t) => t.prompt).filter(Boolean).join('\\n\\n---\\n\\n') || undefined,\n type: toMerge.some((t) => t.type === 'user') ? 'user' : 'ai',\n assignedRunner: toMerge[0].assignedRunner,\n assignedModel: toMerge.find((t) => t.assignedModel)?.assignedModel,\n taskMode: (writes ?? toMerge[0]).taskMode,\n autonomy: toMerge.find((t) => t.autonomy)?.autonomy,\n sliceType: toMerge.find((t) => t.sliceType)?.sliceType,\n ops: inheritedOps(toMerge),\n skills: toMerge.flatMap((t) => t.skills ?? []),\n userStoriesCovered: stories.length ? stories : undefined,\n },\n },\n });\n const refused = refusalOf(planned);\n if (refused) { errors.push(`${label}: ${refused}`); break; }\n const built = resolveTree({ ...planned, task: { ...planned.task, dependencies: [] } }, i);\n if ('error' in built) { errors.push(`${label}: ${built.error}`); break; }\n const mergedTask: Task = { ...built.task, dependencies: ids };\n const mergedId = mergedTask.id;\n const firstId = toMerge[0].id;\n const result: Task[] = [];\n let inserted = false;\n for (const t of tasks) {\n if (idSet.has(t.id)) {\n if (!inserted && t.id === firstId) { result.push(mergedTask); inserted = true; }\n continue;\n }\n result.push({ ...t, dependencies: t.dependencies.map((d) => (idSet.has(d) ? mergedId : d)) });\n }\n if (!inserted) result.push(mergedTask);\n tasks = renumberTasks(result);\n if (op.handle) handleId.set(op.handle, mergedId);\n summary.push(`Merged ${toMerge.length} tasks into \"${spec.title}\"`);\n break;\n }\n case 'split': {\n const ref = resolveRef(op.taskId, currentTasks, tasks, handleOwner, handleId, i);\n if ('error' in ref) { errors.push(`${label}: ${ref.error}`); break; }\n const target = findTopLevel(tasks, ref.id);\n if (!target) { errors.push(`${label}: task \"${op.taskId}\" not found`); break; }\n const locked = lockedTaskRefusal(target, 'split');\n if (locked) { errors.push(`${label}: ${locked}`); break; }\n const parts = op.parts ?? [];\n if (parts.length < 2) { errors.push(`${label}: split needs at least two parts`); break; }\n const newTasks: Task[] = [];\n let prevId: string | undefined;\n let buildError = false;\n for (let p = 0; p < parts.length; p++) {\n const spec = parts[p];\n if (!spec || !spec.title) { errors.push(`${label}: split part ${p + 1} needs a title`); buildError = true; break; }\n // Part 0 inherits the original's (external) dependencies, resolved\n // against the current plan. Later parts chain onto the previous part\n // — a sibling id created in this same op, so it needs no resolution.\n let deps: string[];\n if (p === 0) {\n const { ids, bad } = resolveDeps(target.dependencies, i);\n if (bad.length) { errors.push(`${label}: split part 1 unknown dependencies: ${bad.join(', ')}`); buildError = true; break; }\n deps = ids;\n } else {\n deps = prevId ? [prevId] : [];\n }\n const planned = plannedTask(spec, intake, {\n freshIds: true,\n from: {\n readOnly: isReadOnly(target, catalog),\n fields: {\n type: target.type,\n assignedRunner: target.assignedRunner,\n assignedModel: target.assignedModel,\n taskMode: target.taskMode,\n autonomy: target.autonomy,\n sliceType: target.sliceType,\n ops: inheritedOps([target]),\n skills: target.skills,\n userStoriesCovered: target.userStoriesCovered,\n },\n },\n });\n const refused = refusalOf(planned);\n if (refused) { errors.push(`${label}: split part ${p + 1}: ${refused}`); buildError = true; break; }\n const resolved = resolveTree({ ...planned, task: { ...planned.task, dependencies: [] } }, i);\n if ('error' in resolved) { errors.push(`${label}: split part ${p + 1}: ${resolved.error}`); buildError = true; break; }\n const nt: Task = { ...resolved.task, dependencies: deps };\n newTasks.push(nt);\n prevId = nt.id;\n }\n if (buildError) break;\n const tailId = newTasks[newTasks.length - 1].id;\n const result: Task[] = [];\n for (const t of tasks) {\n if (t.id === target.id) { result.push(...newTasks); continue; }\n result.push({ ...t, dependencies: t.dependencies.map((d) => (d === target.id ? tailId : d)) });\n }\n tasks = renumberTasks(result);\n if (op.handle) handleId.set(op.handle, tailId);\n summary.push(`Split \"${target.title}\" into ${newTasks.length} tasks`);\n break;\n }\n case 'rearm': {\n // Re-arming is the sanctioned exception to the planner lock — its whole\n // point is to touch a task the lock would otherwise refuse — so it\n // validates as a direct edit; well-formedness still applies.\n const prep = prepareEdit(op, i, label, 'direct', true);\n if ('error' in prep) { errors.push(prep.error); break; }\n const { target, changes, check } = prep;\n tasks = updateTaskInPlan(tasks, target.id, { ...changes, status: 'pending', verdict: undefined, outputSummary: undefined, awaitingReason: undefined, resumeAt: undefined, skipReason: undefined });\n // Dependents parked at 'blocked' by this task's earlier failure have\n // nothing else to release them — the scheduler reads `status` directly,\n // not a live recomputation of the dependency graph.\n for (const dep of flattenTasks(tasks)) {\n if (dep.status === 'blocked' && dep.dependencies.includes(target.id)) {\n tasks = updateTaskInPlan(tasks, dep.id, { status: 'pending' });\n }\n }\n const clearedNote = check.clear?.length ? `; cleared ${check.clear.join(', ')}` : '';\n const fields = changedFields(target, changes, catalog);\n const changedNote = fields.length ? ` (${fields.join(', ')})` : '';\n summary.push(`Re-armed \"${target.title}\"${changedNote}${clearedNote}`);\n break;\n }\n default:\n errors.push(`${label}: unknown op \"${(op as { op: string }).op}\"`);\n }\n if (errors.length) break;\n lastLabel = label;\n // Invariants are judged on the final state, so a later op may complete an\n // earlier one; here they only record which op a breach belongs to.\n const now = new Set(checkPlanInvariants(tasks, runners).map(breachKey));\n for (const key of brokenBy.keys()) if (!now.has(key)) brokenBy.delete(key);\n for (const key of now) if (!standing.has(key) && !brokenBy.has(key)) brokenBy.set(key, label);\n }\n\n if (!errors.length) {\n for (const e of checkPlanInvariants(tasks, runners)) {\n const key = breachKey(e);\n if (!standing.has(key)) errors.push(`${brokenBy.get(key) ?? lastLabel}: ${e.message}`);\n }\n }\n\n // Display order is maintained, not demanded: whatever the batch produced,\n // the plan comes back with dependencies ahead of their dependents. The\n // planner never has to re-declare the whole order to move one task.\n if (!errors.length) {\n const repaired = repairOrder(tasks);\n if ('error' in repaired) {\n errors.push(repaired.error);\n } else {\n const moves = describeMoves(tasks, repaired.tasks);\n if (moves) summary.push(`Reordered to keep dependencies first: ${moves}`);\n tasks = renumberTasks(repaired.tasks);\n }\n }\n\n return errors.length\n ? { ok: false, tasks: currentTasks, errors, summary: [] }\n : { ok: true, tasks, errors: [], summary };\n}\n"],"mappings":"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","names":["text","reason","text","done"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/services/PlanValidator.ts"],"sourcesContent":["import { Task, RunnerId, flattenTasksWithParents } from '../models/Task';\nimport { extractObjectsWithKey, stripTrailingCommas, escapeControlCharsInStrings, PlanParseError, PLAN_ENVELOPE_KEY } from './JsonExtractor';\nimport type { RunnerModeInfo } from './ModeResolver';\nimport { plannedTask, checkPlanInvariants, taskWhere, type PlanTaskError } from './PlannedTask';\n\nexport function parsePlanJson(\n raw: string,\n runners: RunnerId[],\n runnerModes?: Record<RunnerId, RunnerModeInfo[]>,\n autonomousDefault = true,\n): Task[] {\n const { matches, fallback, sawUnbalanced } = extractObjectsWithKey(raw, PLAN_ENVELOPE_KEY);\n\n // A reply can carry several tasks-keyed objects — a schema echo from the\n // prompt, an inline draft, then the real plan. The model's LAST emission is\n // its final answer, so candidates are tried back to front and the first one\n // that fully parses and validates wins.\n const candidates = matches.length > 0 ? [...matches].reverse() : [fallback.json];\n\n let lastError: PlanParseError | null = null;\n for (const json of candidates) {\n try {\n return parsePlanObject(json, raw, runners, runnerModes, autonomousDefault);\n } catch (err) {\n if (!(err instanceof PlanParseError)) throw err;\n // Prefer surfacing the error from the most plan-like candidate: the\n // last one carrying a tasks key (tried first).\n if (!lastError) lastError = err;\n }\n }\n\n // No usable tasks object anywhere, but an object ran off the end of the\n // text: the plan was almost certainly cut off mid-stream.\n if (matches.length === 0 && sawUnbalanced) {\n throw new PlanParseError(\n 'Plan response appears truncated — the model likely hit its output length limit before finishing the JSON.',\n raw,\n { truncated: true },\n );\n }\n throw lastError ?? new PlanParseError('Invalid plan: missing tasks array', raw);\n}\n\nfunction parsePlanObject(\n json: string,\n raw: string,\n runners: RunnerId[],\n runnerModes?: Record<RunnerId, RunnerModeInfo[]>,\n autonomousDefault = true,\n): Task[] {\n const cleaned = stripTrailingCommas(escapeControlCharsInStrings(json));\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(cleaned);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n throw new PlanParseError(`Plan response was not valid JSON: ${detail}`, raw);\n }\n\n const result = validatePlanTasks(parsed, runners, runnerModes, autonomousDefault);\n if (result.ok) return result.tasks;\n const [first] = result.errors;\n // An envelope problem (no tasks array, an empty one) keeps the generic\n // re-emit; only a rule broken by a task is worth naming in the corrective.\n throw new PlanParseError(first.message, raw, { semantic: first.taskId !== undefined });\n}\n\nexport type { PlanTaskError } from './PlannedTask';\nexport { requestedSkills } from './PlannedTask';\n\n/** `warnings` name what was accepted but not used, for a channel that can tell the planner. */\nexport type PlanTasksResult = { ok: true; tasks: Task[]; warnings?: string[] } | { ok: false; errors: PlanTaskError[] };\n\n/**\n * Every check a plan's tasks must pass, on an already-parsed `{ tasks }`\n * object — what a plan is held to whichever channel it arrived on. All\n * problems are reported, the slice classification first, so the first is the\n * one the text path's corrective names.\n */\nexport function validatePlanTasks(\n obj: unknown,\n runners: RunnerId[],\n runnerModes?: Record<RunnerId, RunnerModeInfo[]>,\n autonomousDefault = true,\n): PlanTasksResult {\n const rawTasks = isRecord(obj) ? obj.tasks : undefined;\n if (!Array.isArray(rawTasks)) {\n return { ok: false, errors: [{ field: 'tasks', message: 'Invalid plan: missing tasks array' }] };\n }\n if (rawTasks.length === 0) {\n return { ok: false, errors: [{ field: 'tasks', message: 'Plan contained no tasks' }] };\n }\n\n const planned = rawTasks.map((t) => plannedTask(t, { runners, runnerModes, autonomousDefault }));\n const tasks = planned.map((p) => p.task);\n const errors = [\n ...sliceRequired(tasks),\n ...planned.flatMap((p) => p.errors),\n ...checkPlanInvariants(tasks, runners),\n ];\n\n if (errors.length > 0) return { ok: false, errors };\n const ignored = planned.flatMap((p) => p.ignored);\n const warnings = [\n ...(ignored.some((i) => i.field === 'taskMode')\n ? ['\"taskMode\" is not yours to set and was ignored: each task runs in its runner\\'s mode for the user\\'s autonomy level, or its read-only mode when you set \"readOnly\": true.']\n : []),\n ...ignored.filter((i) => i.field === 'readOnly').map((i) => `${i.message} — it was ignored.`),\n ];\n return warnings.length > 0 ? { ok: true, tasks, warnings } : { ok: true, tasks };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Whether a reply that failed {@link parsePlanJson} actually LOOKED like a\n * plan attempt — a balanced tasks-keyed object, or JSON that got cut off.\n * Distinguishes \"the model tried to emit a plan and botched it\" (worth a\n * corrective retry) from prose that merely mentions `\"tasks\"` (leave alone).\n */\nexport function looksLikePlanAttempt(raw: string): boolean {\n const { matches, sawUnbalanced } = extractObjectsWithKey(raw, PLAN_ENVELOPE_KEY);\n if (matches.length > 0) return true;\n return sawUnbalanced && raw.includes(`\"${PLAN_ENVELOPE_KEY}\"`);\n}\n\n/**\n * A whole plan says how each task is sliced: an edit may leave it to the\n * task's parent or the tasks it was made from, a submitted plan may not.\n */\nfunction sliceRequired(tasks: readonly Task[]): PlanTaskError[] {\n return flattenTasksWithParents(tasks).flatMap(({ task: t, parent }) => {\n const where = taskWhere(t.title, parent?.title);\n const errors: PlanTaskError[] = [];\n if (!t.sliceType) errors.push({ taskId: t.id, field: 'sliceType', message: `${where} is missing sliceType. Every task must specify sliceType (\"AFK\" or \"HITL\").` });\n if (t.type === 'ai' && !t.autonomy) errors.push({ taskId: t.id, field: 'autonomy', message: `AI ${where} is missing autonomy. Every AI task must specify autonomy (\"AFK\" or \"HITL\").` });\n return errors;\n });\n}\n"],"mappings":"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","names":[]}