@ordewell/core 0.10.8 → 0.10.10

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 (40) hide show
  1. package/dist/{IFileSystem-C4_mpfze.d.ts → IFileSystem-1PqRCjhu.d.mts} +23 -3
  2. package/dist/{IFileSystem-DUgeWWQN.d.mts → IFileSystem-lTRL_G9Z.d.ts} +23 -3
  3. package/dist/{ModeResolver-4qNjxcDs.d.mts → ModeResolver-6TQXVsde.d.mts} +1 -1
  4. package/dist/{ModeResolver-DGK2rWHE.d.ts → ModeResolver-CoJAJD-f.d.ts} +1 -1
  5. package/dist/{Task-C0o4i3Nn.d.mts → Task-CfBM0wkV.d.mts} +100 -24
  6. package/dist/{Task-C0o4i3Nn.d.ts → Task-CfBM0wkV.d.ts} +100 -24
  7. package/dist/{chunk-H7QX32BH.mjs → chunk-24EL4Z2W.mjs} +2 -2
  8. package/dist/{chunk-DMG4ISUG.mjs → chunk-ESKK5I4F.mjs} +145 -21
  9. package/dist/chunk-ESKK5I4F.mjs.map +1 -0
  10. package/dist/{chunk-LTHKFBKM.mjs → chunk-KWS3VEER.mjs} +3 -3
  11. package/dist/{chunk-EEE2GBLG.mjs → chunk-PFH7WFIB.mjs} +1 -1
  12. package/dist/chunk-PFH7WFIB.mjs.map +1 -0
  13. package/dist/index.d.mts +145 -20
  14. package/dist/index.d.ts +145 -20
  15. package/dist/index.js +668 -112
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +537 -96
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/parsing.d.mts +2 -2
  20. package/dist/parsing.d.ts +2 -2
  21. package/dist/parsing.js.map +1 -1
  22. package/dist/parsing.mjs +3 -3
  23. package/dist/plan-utils.d.mts +4 -4
  24. package/dist/plan-utils.d.ts +4 -4
  25. package/dist/plan-utils.js +144 -18
  26. package/dist/plan-utils.js.map +1 -1
  27. package/dist/plan-utils.mjs +22 -4
  28. package/dist/{runSettingsText-BgOmLxdw.d.mts → runSettingsText-C1i9wBG1.d.mts} +61 -21
  29. package/dist/{runSettingsText-BgXvajj2.d.ts → runSettingsText-CWNrmF5U.d.ts} +61 -21
  30. package/dist/testing.d.mts +7 -2
  31. package/dist/testing.d.ts +7 -2
  32. package/dist/testing.js +11 -0
  33. package/dist/testing.js.map +1 -1
  34. package/dist/testing.mjs +12 -1
  35. package/dist/testing.mjs.map +1 -1
  36. package/package.json +1 -1
  37. package/dist/chunk-DMG4ISUG.mjs.map +0 -1
  38. package/dist/chunk-EEE2GBLG.mjs.map +0 -1
  39. /package/dist/{chunk-H7QX32BH.mjs.map → chunk-24EL4Z2W.mjs.map} +0 -0
  40. /package/dist/{chunk-LTHKFBKM.mjs.map → chunk-KWS3VEER.mjs.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/parsing.ts","../src/models/Task.ts","../src/conversation/skillTokens.ts","../src/services/PlanTree.ts","../src/services/JsonExtractor.ts","../src/services/PlannedTask.ts","../src/services/ModeResolver.ts","../src/services/ModelAllowlistResolver.ts","../src/services/TaskAssignment.ts","../src/services/PlanValidator.ts","../src/services/PartialPlanParser.ts"],"sourcesContent":["/**\n * Browser-safe entry point for the pure plan-parsing helpers.\n *\n * The main barrel (`@ordewell/core`) pulls in the AI services, which depend on\n * Node-only SDKs (`fs`, the Google/OpenAI clients) and therefore cannot be bundled\n * into a browser webview. Front-ends that only need the pure parsing/streaming\n * helpers import them from `@ordewell/core/parsing` instead.\n */\nexport { parsePlanJson } from './services/PlanValidator';\nexport { extractJsonObject, PlanParseError } from './services/JsonExtractor';\nexport { parsePartialPlan, type PartialPlanTask } from './services/PartialPlanParser';\n","import { v4 as uuidv4 } from 'uuid';\nimport type { PlanIsolation } from '../interfaces/IWorktreeIsolation';\nimport type { AttemptUsage, PlannerUsage, UsageRecord, UsageTotals } from './Usage';\nimport type { ExecutionRecord, SupervisorLevel } from './Execution';\nimport type { SkillSource } from '../services/SkillsService';\nimport type { SupervisionMemorySnapshot } from '../services/LoopGuards';\nimport type { SupervisorSnapshot } from '../services/Supervisor';\nimport { SKILL_NAME_PATTERN } from '../conversation/skillTokens';\nimport { subtreeIds, walkPlan } from '../services/PlanTree';\n\nexport interface UserStep {\n order: number;\n instruction: string;\n completed: boolean;\n}\n\n/** One deterministic signal gathered while verifying a completed task. `completion_marker` is only in verdicts saved before completion was reported through `task_complete` alone. */\nexport interface VerificationCheck {\n name: 'exit_code' | 'completion_marker' | 'task_complete' | 'manual';\n passed: boolean;\n /** A check that did not apply. Skipped checks don't affect the verdict. */\n skipped: boolean;\n detail: string;\n}\n\n/** Evidence-based verdict for a completed task. Single end-to-end outcome produced by verification. */\nexport interface Verdict {\n outcome: 'pass' | 'fail';\n reason: string;\n checks: VerificationCheck[];\n decidedAt: string;\n}\n\nexport interface TaskOutputSummary {\n reviewReason: string;\n /** The `task_complete` summary, the task's report; absent when the attempt ended without one, and from sessions saved before it was kept. */\n report?: string;\n /** Empty when there is a report: the turn's final text is the report itself. */\n logTail: string;\n capturedAt: string;\n}\n\nexport type TaskType = 'ai' | 'user';\n/**\n * `skipped` is terminal and verdict-less: the task will not be waited for, and\n * nothing it did lands. Its dependents go ahead without it once the work it\n * waited on is done. There is no stored `blocked`: a task is blocked when a\n * failed dependency says so (ADR-0034), and a file that kept the old status\n * loads as pending.\n */\nexport type TaskStatus = 'pending' | 'approved' | 'in_progress' | 'completed' | 'failed' | 'awaiting_user' | 'skipped';\nexport type TaskMode = string;\n\n/**\n * Why an `awaiting_user` task waits (ADR-0018, W1): a structured turn that\n * ended without a `task_complete` call, a checkpoint question, work that did not\n * land, an ops task that changed tracked files (ADR-0020), or its runner's\n * usage limit. Saved, so no surface has to guess it from whether an attempt\n * is live.\n */\nexport type AwaitingReason = 'input' | 'checkpoint' | 'conflict' | 'files-changed' | 'usage-limit';\n\nexport function isAwaitingReason(value: unknown): value is AwaitingReason {\n return value === 'input' || value === 'checkpoint' || value === 'conflict' || value === 'files-changed' || value === 'usage-limit';\n}\n\nexport interface TaskModelAssignment {\n modelId: string;\n modelLabel: string;\n thinkingEffort?: string;\n /**\n * All variant ids the model offered when this assignment was made. Carried\n * on the assignment because runners need it at spawn time (opencode's TUI\n * only honors an assigned variant when the others are config-disabled) and\n * the discovery catalog isn't available there.\n */\n availableVariants?: string[];\n}\n\nexport type RunnerId = string;\n\n/**\n * Bring loaded tasks to the structured-only shape, in place. A task saved\n * while the terminal transport existed may say it ran there, and why; neither\n * means anything now, and such a task left no session to continue. Its\n * completion marker id went with the text markers: completion is reported\n * only through `task_complete`.\n */\nexport function migrateLoadedTasks(tasks: readonly Task[]): void {\n for (const task of tasks) {\n if (typeof task.runnerSessionId !== 'string') delete task.runnerSessionId;\n if (typeof task.carriedFrom !== 'string') delete task.carriedFrom;\n if (typeof task.runnerSessionOwner !== 'string') delete task.runnerSessionOwner;\n const legacy = task as Task & { transport?: { kind?: unknown; nativeSessionId?: unknown } };\n if (!task.runnerSessionId && legacy.transport?.kind === 'structured' && typeof legacy.transport.nativeSessionId === 'string') {\n task.runnerSessionId = legacy.transport.nativeSessionId;\n }\n delete legacy.transport;\n delete (task as { completionMarker?: unknown }).completionMarker;\n // The effort once had a second home on the task; the spawn reads only the model's.\n const effort = (task as { thinkingEffort?: unknown }).thinkingEffort;\n if (typeof effort === 'string' && task.assignedModel && !task.assignedModel.thinkingEffort) task.assignedModel.thinkingEffort = effort;\n delete (task as { thinkingEffort?: unknown }).thinkingEffort;\n migrateLoadedTasks(task.subtasks ?? []);\n }\n}\n\n/**\n * A task skill as one attempt received it: read from the task's own worktree\n * (or global) at spawn, kept so the user can see what the runner was told\n * even after the skill's file changes.\n */\nexport interface TaskSkillSnapshot {\n name: string;\n source: SkillSource;\n path: string;\n content: string;\n}\n\n/** A task skill as a surface is told of it: the body stays in the task's log file and the session. */\nexport type TaskSkillNotice = Omit<TaskSkillSnapshot, 'content'>;\n\nexport interface Task {\n id: string;\n order: number;\n title: string;\n description: string;\n type: TaskType;\n status: TaskStatus;\n dependencies: string[];\n prompt?: string;\n userSteps?: UserStep[];\n subtasks: Task[];\n verdict?: Verdict;\n outputSummary?: TaskOutputSummary;\n assignedModel?: TaskModelAssignment;\n assignedRunner: RunnerId;\n taskMode?: TaskMode;\n autonomy?: 'AFK' | 'HITL';\n sliceType?: 'HITL' | 'AFK';\n userStoriesCovered?: string[];\n /** The runner's saved session for a later continue; cleared when a fresh attempt starts. */\n runnerSessionId?: string;\n /**\n * The runner that session belongs to: a session cannot cross runners, so\n * Continue is offered only while the task is still assigned to it. Absent\n * on a plan saved before it was recorded; the last attempt's runner stands\n * in (see `sessionOwner`).\n */\n runnerSessionOwner?: RunnerId;\n /** Set only while `status` is `awaiting_user`, and not always then — a pause saved by an older build has none. */\n awaitingReason?: AwaitingReason;\n /**\n * When a task paused on its runner's usage limit is resumed on its own (ISO\n * time): the reset the runner announced, plus a short margin. Absent when no\n * reset time could be read or the user cancelled the resume — the task then\n * waits for a manual retry. Set only with `awaitingReason` `usage-limit`.\n */\n resumeAt?: string;\n /**\n * The runner a task paused on its usage limit was moved off (ADR-0018, U1):\n * its next attempt is a carry-over, a fresh session that starts from that\n * attempt's files. Cleared once that attempt runs, and by a retry.\n */\n carriedFrom?: RunnerId;\n /** Why the task was skipped, as whoever skipped it put it; set only while `status` is `skipped`, and not always then. */\n skipReason?: string;\n /**\n * An ops task (ADR-0020): it changes no repository files, runs at the\n * workspace root rather than in a worktree, and waits for the change tasks\n * it depends on to be merged into the user's branch. Top-level AI tasks\n * only; absent means a change task.\n */\n ops?: boolean;\n /**\n * The dependencies, by title, whose work was not merged into the user's\n * branch when the user force-started this task past its merge gate\n * (ADR-0020). Cleared when the task is retried.\n */\n forcedPastGate?: string[];\n /**\n * Task skills (`applies-to: task`) attached by name. Their bodies are put in\n * the runner's prompt at spawn, resolved where the task runs — a subtask's\n * are its own, never its parent's. Absent means none.\n */\n skills?: string[];\n /** The skills the latest attempt was given, as read when it spawned. */\n attemptSkills?: TaskSkillSnapshot[];\n /**\n * Who put the task in the plan, when it was not the user's own planning:\n * `supervisor` for a task an Act supervisor added during a run, which\n * counts as approved to run (ADR-0029). Set by the session, never by an op.\n */\n addedBy?: 'supervisor';\n attemptUsage?: AttemptUsage[];\n}\n\nexport interface DiscoveredMode {\n id: string;\n label: string;\n description: string;\n}\n\nexport type ResearchToolType =\n | 'read_file' | 'read_files' | 'glob' | 'grep' | 'find_symbol' | 'list_dir'\n | 'bash' | 'fetch' | 'web_search' | 'spawn_research_agent'\n | 'task_query' | 'task_output' | 'task_diff'\n /**\n * A tool belonging to a harness planner's own toolbox (ADR-0009) that has no\n * Ordewell equivalent — Edit, WebFetch, TodoWrite, whatever a coding agent\n * ships next. The real name travels in `toolLabel` rather than being\n * relabelled as a tool it is not; the union stays closed so the\n * exhaustiveness checks in every surface's icon/label switch survive.\n */\n | 'agent_tool';\n\n/**\n * What happened when a research tool call ran, for honest per-surface\n * rendering. The broadcast seam carries this on every `research_step_done` so\n * surfaces do not have to pattern-match refusal text to tell a refused `rm`\n * from a successful `rm` — the old render path flipped a `✓` for both.\n */\nexport type ResearchStepOutcome = 'success' | 'failure' | 'refused' | 'denied' | 'not_executed';\n\nexport interface ResearchStep {\n id: string;\n tool: ResearchToolType;\n /** The tool's own name when it came from a harness planner — always set for `agent_tool`. */\n toolLabel?: string;\n args: string;\n result: string;\n success: boolean;\n outcome: ResearchStepOutcome;\n /** The model's tool_call id, so a surface can match `tool_result` to the\n * pending `tool_call` it announced — robust under parallel same-tool rounds. */\n toolCallId?: string;\n /** The research subagent that ran the call, so a reload regroups it under that subagent. */\n subagentId?: string;\n timestamp: string;\n thinkingText?: string;\n}\n\nexport interface UserPromptEntry {\n id: string;\n type: 'user_prompt' | 'system';\n content: string;\n timestamp: string;\n}\n\nexport type SubagentOutcome = 'done' | 'failed' | 'stopped';\n\n/**\n * One subagent's whole run, logged when it finishes: what it was asked, how it\n * ended and what it reported. Its steps stay separate entries carrying the same\n * `subagentId`, so an older reader that knows only steps still shows them.\n */\nexport interface SubagentLogEntry {\n id: string;\n type: 'subagent';\n subagentId: string;\n brief: string;\n model?: string;\n outcome: SubagentOutcome;\n digest: string;\n usage?: UsageTotals;\n timestamp: string;\n}\n\nexport type ResearchLogEntry = ResearchStep | UserPromptEntry | SubagentLogEntry;\n\nexport interface ResearchProgress {\n // 'liveness' carries no content: a raw-line signal from the harness process\n // (ADR-0009) that reaches a surface even when a turn is producing nothing\n // visible — a subagent's filtered chatter, most often — so an idle watchdog\n // downstream doesn't mistake \"nothing to show\" for \"nothing happening\".\n //\n // The turn-scoped kinds below carry one planner turn's stream (#47); see the\n // matching SessionMessage variants for the invariants each one keeps.\n // 'text_delta' is reply text as the model streams it (`text`, `segmentId`),\n // an envelope included: the turn's owner routes each segment to prose or to\n // the plan display, so no backend has to tell them apart. 'text_retracted'\n // takes back an attempt's text: `segmentId`'s, or when absent everything the\n // backend call that sends it has streamed.\n // 'usage' reports one model call (`record`). 'subagent_started' carries\n // `brief` and `model`; 'subagent_finished' carries `outcome`, `digest` and\n // `usage`.\n type:\n | 'thinking' | 'tool_call' | 'tool_result' | 'plan_token' | 'interrupted' | 'liveness'\n | 'text_delta' | 'text_retracted' | 'usage' | 'subagent_started' | 'subagent_finished';\n /** Minted by whoever runs the turn and passed through untouched; absent outside a turn. */\n turnId?: string;\n /** One continuous run of model text — text before a tool call is its own segment. */\n segmentId?: string;\n text?: string;\n tool?: string;\n /** Harness planners (ADR-0009): the agent's own name for a tool Ordewell has no member for. */\n toolLabel?: string;\n toolArgs?: string;\n toolResult?: string;\n planToken?: string;\n step?: ResearchStep;\n /** The model's tool_call id, threaded on tool_call and tool_result so a\n * surface can match the result to its pending call — robust under parallel\n * same-tool rounds where LIFO-by-name matching mislabels summaries. */\n toolCallId?: string;\n /** Present when this event originates from (or reports on) one spawned research subagent (issue #34). */\n subagentId?: string;\n record?: UsageRecord;\n brief?: string;\n model?: string;\n outcome?: SubagentOutcome;\n digest?: string;\n usage?: UsageTotals;\n}\n\nexport interface ThinkingBlock {\n id: string;\n text: string;\n}\n\nexport interface StreamThinkingEvent {\n type: 'thinking';\n block: ThinkingBlock;\n}\n\nexport interface StreamStepEvent {\n type: 'step';\n step: ResearchStep;\n}\n\nexport type StreamEvent = StreamThinkingEvent | StreamStepEvent;\n\nexport interface DiscoveredModel {\n modelId: string;\n modelLabel: string;\n runnerProvider?: string;\n /**\n * Human-facing provider name as the runner itself reports it (e.g.\n * \"OpenCode Zen\" for `runnerProvider: 'opencode'`). Populated from the\n * runner's own provider catalog when available; when absent the UI derives a\n * label from `runnerProvider` by title-casing.\n */\n runnerProviderLabel?: string;\n /**\n * The runner whose catalog listed this model. Stamped once, at the single\n * `ModelDiscovery.discover` choke point, so a flat cross-runner list can\n * still say where each entry came from — `runnerProvider` alone cannot:\n * OpenCode reports most of its catalog as `openrouter`, which names the\n * serving backend, not the agent Ordewell would spawn.\n */\n runnerId?: string;\n /** The runner's display name (`OpenCode`), from its manifest. */\n runnerLabel?: string;\n variants: { id: string; label: string }[];\n /**\n * The model's context window when the runner or catalog reports it (#49).\n * Read by the planner-model lookup so context fill can be shown; absent when\n * unknown rather than defaulted to zero.\n */\n contextWindow?: number;\n}\n\nexport type PlanStatus = 'draft' | 'approved' | 'rejected' | 'running' | 'completed';\n\n/**\n * One entry of the planner's persisted dialogue (ADR-0002). The single source\n * of truth for both UI redisplay and conversational context. Tool-call results\n * are NOT stored here — they live in the AI service's tool-use history;\n * `researchLog` remains the persisted tool trace for the UI.\n */\nexport interface ConversationMessage {\n role: 'user' | 'assistant';\n content: string;\n timestamp: string;\n /**\n * Timeline marker: 'plan_generated' records the point in the dialogue where\n * the plan was committed (the UI anchors the plan card there on restore);\n * 'system' is a host-injected notice; 'compaction' is the summary a\n * user-triggered compaction left in place of the earlier messages — always\n * the transcript's first entry. 'skill_load' is a skill loaded into the\n * conversation, its snapshot in `skill`; one the user invoked follows the\n * message that named it. 'execution' is a call at the command seam\n * (ADR-0029), its record in `execution`. 'wake_up' is the digest a wake-up\n * gave the supervisor (role 'user': the model was told it), and\n * 'supervisor' what the supervisor said in a wake-up turn (ADR-0029).\n * 'error' is a planner turn that ended on the planner's usage limit, drawn\n * as a failure and never replayed to the planner: it is not the planner's\n * answer, and its inputs are sent again (see {@link PlannerResendState}).\n * Absent for ordinary chat turns, so sessions saved before markers existed\n * degrade gracefully.\n */\n kind?: 'plan_generated' | 'system' | 'compaction' | 'skill_load' | 'execution' | 'wake_up' | 'supervisor' | 'error';\n actor?: 'Ordewell setting';\n /** Present exactly when `kind` is 'skill_load'. */\n skill?: SkillLoad;\n /** Present exactly when `kind` is 'execution'. */\n execution?: ExecutionRecord;\n /** A user's message that was force sent, stopping the turn before it (ADR-0023). */\n forced?: true;\n /**\n * What a reply showed, run by run, where each began (ADR-0017 A1). Beside\n * `content`, which stays the reply as the planner is told it. Absent on\n * entries saved before runs were kept, which reload as one message.\n */\n runs?: ReplyRun[];\n}\n\n/**\n * One run of a planner reply's text: the segment it streamed under, its\n * settled text, and when it began. A run that never streamed (a message the\n * conversation wrote in the planner's place) has a segment no stream used.\n */\nexport interface ReplyRun {\n segmentId: string;\n text: string;\n /** ISO timestamp. */\n startedAt: string;\n}\n\n/**\n * A skill's SKILL.md as it was when loaded into a planner conversation. A\n * snapshot rather than a reference: a resumed, forked or rewound conversation\n * replays the body the planner originally saw, even after the file changed or\n * was deleted.\n */\nexport interface SkillLoad {\n invokedBy: 'user' | 'planner';\n name: string;\n source: SkillSource;\n /** The SKILL.md that won, home-abbreviated (`~/...`): which copy loaded is what a reader needs from it. */\n path: string;\n content: string;\n /**\n * Set when the user named a task skill: it is not loaded into the planner's\n * context, which is only told to attach it to the tasks it fits. `content`\n * is empty; the description is what the planner's directive quotes.\n */\n attaches?: { description: string };\n}\n\n/** A skill load as a surface announces it — the body stays with the transcript. */\nexport type SkillLoadNotice = Omit<SkillLoad, 'content'>;\n\n/** A message the user sent, as opposed to a skill load their message caused or a wake-up digest. */\nexport function isUserMessage(entry: Pick<ConversationMessage, 'role' | 'kind'>): boolean {\n return entry.role === 'user' && entry.kind !== 'skill_load' && entry.kind !== 'wake_up';\n}\n\n/** A follow-up an older build queued behind a run's batch boundary; read only to carry it over as a {@link PendingPlanTurn}. */\nexport interface QueuedMessage {\n id: string;\n text: string;\n timestamp: string;\n skills?: SkillLoad[];\n}\n\n/** Who a planner turn answers (ADR-0029): the user's message, or a wake-up batch the supervisor is given. */\nexport type PlanTurnSource = 'user' | 'supervisor';\n\n/**\n * A planner turn's input not yet answered by a turn that ended. It leaves the\n * plan with the change its turn made, in the same save, so a reload replays\n * exactly the inputs no turn answered — once. A supervisor's input is a\n * wake-up batch not yet delivered (ADR-0029): it stays until a turn lands\n * with it, whatever turns fail meanwhile.\n */\nexport interface PendingPlanTurn {\n id: string;\n source: PlanTurnSource;\n /** The user's message, or a wake-up batch's digest. */\n text: string;\n timestamp: string;\n /** What its `/name` tokens loaded when it was sent, so a replay sends the same skills. */\n skills?: SkillLoad[];\n /** Force sent: it waits ahead of every input not forced, and its turn is its own (ADR-0023, F2). */\n forced?: true;\n}\n\n/**\n * The run's own copy of the user's run settings (ADR-0030): taken when the\n * user starts it, changed at once by a change made in this session, and\n * lowered by a tripped guard or a failed supervisor.\n */\nexport interface RunSettings {\n mergeAtGatesAutomatically: boolean;\n /** Absent on a run started before supervisor levels existed: Off. */\n supervisorLevel?: SupervisorLevel;\n /** Absent on a run started before ADR-0030: the user's setting. */\n supervisorRetryLimit?: number;\n /** Absent on a run started before ADR-0030: the user's setting. */\n wakeUpReadBudget?: number;\n /** Why the supervisor stopped for the rest of this run, which carries on as Off; absent while it watches. */\n unsupervised?: string;\n}\n\n/**\n * What supervision remembers across a daemon restart (ADR-0029): the loop\n * guards' memory and the wake-up state not yet delivered with a turn.\n */\nexport interface SupervisionSnapshot {\n /** Retries, failed-attempt shapes, the supervisor's edits and what the user was told. */\n guards?: SupervisionMemorySnapshot;\n /** The events inside their debounce window and the wake-up rate's timestamps. */\n wake?: SupervisorSnapshot;\n}\n\n/**\n * What waits on the planner's usage limit. With `at`, the inputs no turn\n * answered are sent again then, on their own; without it — the reset could\n * not be read, the resend hit the limit again, or it was cancelled — they go\n * with the next input: the user's message, or a wake-up.\n */\nexport interface PlannerResendState {\n /** The runner whose account hit the limit; a planner on another one is not held by it. */\n runner?: RunnerId;\n at?: string;\n}\n\nexport interface LegacyPlanState {\n runSettings?: RunSettings;\n /** What supervision remembers across a daemon restart (ADR-0029); absent on plans saved before it existed. */\n supervision?: SupervisionSnapshot;\n tasks: Task[];\n generatedAt: string;\n status: PlanStatus;\n runners: RunnerId[];\n lastUpdated: string;\n researchLog?: ResearchLogEntry[];\n /** The planner dialogue — user messages and assistant messages, in order (ADR-0002). */\n conversationHistory?: ConversationMessage[];\n /** Full markdown PRD once written by the planner (PRD mode), also saved to .scratch/<slug>/PRD.md. */\n prdMarkdown?: string;\n /** Planner turn inputs no turn has answered yet (ADR-0029); a reload replays them. */\n pendingTurns?: PendingPlanTurn[];\n /** Written by builds that queued mid-run edits; a load moves it into {@link pendingTurns}. */\n queuedMessages?: QueuedMessage[];\n /**\n * The plan's isolation run (ADR-0013), written from the orchestrator at\n * persist time and read back only when a saved plan is adopted. It names\n * branches and worktrees that belong to this plan alone: a fork of the plan\n * must leave it behind rather than share it.\n */\n isolation?: PlanIsolation;\n /** Kept so a reopened session shows the same token line (#49). */\n plannerUsage?: PlannerUsage;\n /** The planner hit its usage limit and the inputs its turn took wait to be sent again; absent otherwise. */\n plannerResend?: PlannerResendState;\n /**\n * The tasks the user approved to run: every task when they executed the\n * plan, and each one they started or retried themselves since. A command\n * from anyone else starts only these (ADR-0029); a task the planner added\n * after the approval waits for the user's. Only tasks still in the plan: a\n * task that leaves takes its approval with it.\n */\n approvedTasks?: string[];\n /**\n * The user approved the plan to run: Execute plan, signing off a review, or\n * autorun's start (ADR-0029, ADR-0030). Kept apart from {@link status},\n * which surfaces rewrite for display; a user's start of one task approves\n * that task, not the plan.\n */\n approved?: boolean;\n}\n\nexport interface Message {\n id: string;\n role: 'user' | 'planner' | 'system';\n content: string;\n timestamp: number;\n}\n\nexport interface TaskSnapshot extends Task {\n completedAt: number;\n verdict?: Verdict;\n retryCount: number;\n finalized: boolean;\n}\n\n/** The plan a surface reads whole carries the approval record, so it can tell which tasks wait for the user (ADR-0035). */\nexport type PlanState = (\n | {\n phase: 'planning';\n history: Message[];\n message: string;\n pendingTasks: Task[];\n }\n | {\n phase: 'executing';\n history: Message[];\n message: string;\n executionLog: TaskSnapshot[];\n pendingTasks: Task[];\n goal: string;\n runners: string[];\n status: PlanStatus;\n }\n) & ApprovalRecord;\n\nfunction hasPhase(raw: unknown): raw is PlanState {\n return typeof raw === 'object' && raw !== null && 'phase' in raw;\n}\n\nfunction hasTasks(raw: unknown): raw is LegacyPlanState {\n return typeof raw === 'object' && raw !== null && 'tasks' in raw && Array.isArray((raw as Record<string, unknown>).tasks);\n}\n\nfunction logEntryToMessage(entry: ResearchLogEntry): Message {\n const timestamp = new Date(entry.timestamp).getTime();\n if (!('type' in entry)) {\n return { id: entry.id, role: 'system', content: JSON.stringify({ tool: entry.tool, args: entry.args, result: entry.result }), timestamp };\n }\n if (entry.type === 'subagent') {\n const { subagentId, brief, outcome, digest } = entry;\n return { id: entry.id, role: 'system', content: JSON.stringify({ subagentId, brief, outcome, digest }), timestamp };\n }\n return { id: entry.id, role: entry.type === 'user_prompt' ? 'user' : 'system', content: entry.content, timestamp };\n}\n\nfunction migrateHistory(raw: LegacyPlanState): Message[] {\n const messages: Message[] = [];\n\n if (raw.researchLog) {\n messages.push(...raw.researchLog.map(logEntryToMessage));\n }\n\n if (raw.queuedMessages) {\n for (const qm of raw.queuedMessages) {\n messages.push({\n id: qm.id,\n role: 'system',\n content: qm.text,\n timestamp: new Date(qm.timestamp).getTime(),\n });\n }\n }\n\n return messages;\n}\n\nexport function migratePlanState(raw: unknown): PlanState {\n if (hasPhase(raw)) return raw;\n\n if (!hasTasks(raw)) {\n return {\n phase: 'planning',\n history: [],\n message: '',\n pendingTasks: [],\n };\n }\n\n const hasExecution = raw.tasks.some(\n (t) => t.status === 'completed' || t.status === 'failed',\n );\n\n const messages = migrateHistory(raw);\n const record = approvalRecordOf(raw);\n\n if (hasExecution) {\n const executionLog: TaskSnapshot[] = raw.tasks\n .filter((t) => t.status === 'completed' || t.status === 'failed')\n .map((t) => ({\n ...t,\n completedAt: new Date(raw.lastUpdated).getTime(),\n verdict: t.verdict,\n retryCount: 0,\n finalized: true,\n }));\n\n const pendingTasks = raw.tasks.filter(\n (t) => t.status !== 'completed' && t.status !== 'failed',\n );\n\n return {\n phase: 'executing',\n history: messages,\n message: '',\n executionLog,\n pendingTasks,\n goal: '',\n runners: raw.runners,\n status: raw.status,\n ...record,\n };\n }\n\n return {\n phase: 'planning',\n history: messages,\n message: '',\n pendingTasks: raw.tasks,\n ...record,\n };\n}\n\nexport { taskOrderLabel, taskRef, taskRefOf, titledTaskRef, titledTaskRefOf, resolveTaskRef, resolveUserTaskRef } from '../services/TaskRefs';\n\nexport function createTask(overrides: Partial<Task> = {}): Task {\n const skills = skillNames(overrides.skills);\n return {\n id: overrides.id ?? uuidv4(),\n order: overrides.order ?? 0,\n title: overrides.title ?? '',\n description: overrides.description ?? '',\n type: overrides.type ?? 'ai',\n status: overrides.status ?? 'pending',\n dependencies: overrides.dependencies ?? [],\n prompt: overrides.prompt,\n userSteps: overrides.userSteps,\n subtasks: overrides.subtasks ?? [],\n verdict: overrides.verdict,\n outputSummary: overrides.outputSummary,\n ...(overrides.attemptUsage ? { attemptUsage: structuredClone(overrides.attemptUsage) } : {}),\n assignedModel: overrides.assignedModel,\n assignedRunner: overrides.assignedRunner ?? DEFAULT_RUNNERS[0],\n taskMode: overrides.taskMode ?? 'build',\n autonomy: overrides.autonomy,\n sliceType: overrides.sliceType,\n userStoriesCovered: overrides.userStoriesCovered,\n ...(opsFlag(overrides.ops, overrides.type) ? { ops: true } : {}),\n ...(skills ? { skills } : {}),\n ...(overrides.addedBy === 'supervisor' ? { addedBy: 'supervisor' as const } : {}),\n };\n}\n\n/**\n * A name a skill can have, lower-cased as `/name` is. Anything else — a path\n * separator, `..` — is joined into a path and could only reach outside the\n * skill dirs.\n */\nconst SKILL_NAME = new RegExp(`^${SKILL_NAME_PATTERN}$`);\n\nexport function isSkillName(name: string): boolean {\n return SKILL_NAME.test(name);\n}\n\n/**\n * The `skills` field as a task stores it, however it arrived: the planner's\n * JSON is untyped. Names are trimmed, lower-cased and deduplicated, and one\n * no skill could have is dropped; none at all is stored absent, so a plan\n * without skills reads exactly as it did before them.\n */\nexport function skillNames(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const names = [...new Set(value.filter((v): v is string => typeof v === 'string').map((v) => v.trim().toLowerCase()).filter(isSkillName))];\n return names.length > 0 ? names : undefined;\n}\n\n/**\n * The skills of a task a merge or a split makes from others: what the edit\n * chose when it named any, none when it chose an explicit `[]`, else what the\n * tasks it came from had — a malformed value is not a request to drop them.\n */\nexport function inheritedSkills(chosen: unknown, inherited: readonly string[] | undefined): string[] | undefined {\n if (Array.isArray(chosen) && chosen.length === 0) return undefined;\n return skillNames(chosen) ?? skillNames(inherited);\n}\n\n/**\n * The `ops` flag as a task stores it (ADR-0020), however it arrived. Only a\n * literal `true` on an AI task marks an ops task: the planner's JSON is\n * untyped, and a quoted \"true\" read as ops would move a task out of its\n * worktree. Anything else is a change task, stored absent so saved plans need\n * no migration. Whether the task may be ops at all — a subtask may not — is\n * for whoever knows where it sits: the planner intake refuses it (`checkPlanInvariants`).\n */\nexport function opsFlag(value: unknown, type: Task['type'] = 'ai'): true | undefined {\n return value === true && type === 'ai' ? true : undefined;\n}\n\n/**\n * The flag of a task a merge or a split makes from others: what the edit\n * chose, else ops only when every task it came from was. A merge that takes\n * in a change edits files, so it is a change; a split part runs where its\n * task did.\n */\nexport function inheritedOps(from: readonly Pick<Task, 'ops'>[], chosen?: boolean): boolean {\n return chosen ?? from.every((t) => opsFlag(t.ops) === true);\n}\n\n/**\n * The runner set a plan has until one is chosen. The one runner named outside\n * its declaration on purpose: a default is a product choice that no manifest\n * or connector implies.\n */\nexport const DEFAULT_RUNNERS: readonly RunnerId[] = ['claude-code'];\n\nexport function createEmptyPlan(): LegacyPlanState {\n return {\n tasks: [],\n generatedAt: new Date().toISOString(),\n status: 'draft',\n runners: [...DEFAULT_RUNNERS],\n lastUpdated: new Date().toISOString(),\n };\n}\n\nexport function flattenTasks(tasks: readonly Task[]): Task[] {\n return [...walkPlan(tasks)].map(({ task }) => task);\n}\n\n/**\n * A phase-tagged plan rejoined into its tree. The execution log is flat — a\n * finished subtask is logged on its own as well as inside its parent — and in\n * the order tasks finished, so read as a list it puts subtasks at the top level\n * under orders that clash with real top-level tasks. A task's own entry is the\n * latest word on it: its parent's copy was taken when the parent last changed.\n */\nexport function rejoinPlanTree<T extends { id: string; order: number; subtasks?: T[] }>(pending: readonly T[], log: readonly T[]): T[] {\n const latest = new Map<string, T>();\n for (const task of [...pending, ...log]) latest.set(task.id, task);\n const nested = new Set<string>();\n const markNested = (task: T): void => {\n for (const sub of task.subtasks ?? []) {\n nested.add(sub.id);\n markNested(sub);\n }\n };\n for (const task of latest.values()) markNested(task);\n const rebuild = (task: T): T => {\n const own = latest.get(task.id) ?? task;\n return own.subtasks?.length ? { ...own, subtasks: own.subtasks.map(rebuild) } : own;\n };\n return [...latest.values()].filter((task) => !nested.has(task.id)).map(rebuild).sort((a, b) => a.order - b.order);\n}\n\n/**\n * A flattened task with the parent it hangs under, null for a top-level task,\n * and the path label and reference (`#2.1.3`) `TaskRefs` renders for it.\n */\nexport interface TaskWithParent {\n task: Task;\n parent: Task | null;\n label: string;\n ref: string;\n}\n\nexport function flattenTasksWithParents(tasks: readonly Task[]): TaskWithParent[] {\n return [...walkPlan(tasks)].map(({ task, parent, label }) => ({ task, parent: parent ?? null, label, ref: `#${label}` }));\n}\n\nexport function addTaskToPlan(tasks: readonly Task[], partial: Partial<Task>): Task[] {\n const maxOrder = tasks.reduce((max, t) => Math.max(max, t.order), 0);\n const newTask = createTask({\n ...partial,\n order: partial.order ?? maxOrder + 1,\n status: 'pending',\n });\n return renumberTasks([...tasks, newTask]);\n}\n\nexport function removeTaskFromPlan(tasks: readonly Task[], taskId: string): Task[] {\n const removed = subtreeIds(tasks, taskId);\n const prune = (level: readonly Task[]): Task[] => level\n .filter((t) => !removed.has(t.id))\n .map((t) => ({\n ...t,\n dependencies: t.dependencies.filter((depId) => !removed.has(depId)),\n subtasks: prune(t.subtasks),\n }));\n return renumberTasks(prune(tasks));\n}\n\nexport function updateTaskInPlan(tasks: readonly Task[], taskId: string, changes: Partial<Task>): Task[] {\n return renumberTasks(\n tasks.map((t) => {\n if (t.id === taskId) return { ...t, ...changes, id: t.id };\n return { ...t, subtasks: updateTaskInPlan(t.subtasks, taskId, changes) };\n })\n );\n}\n\nexport function renumberTasks(tasks: readonly Task[]): Task[] {\n return tasks.map((t, i) => ({\n ...t,\n order: i + 1,\n subtasks: renumberTasks(t.subtasks),\n }));\n}\n\n/** A task paused on its runner's usage limit, whether it resumes on its own or waits for the user (#84). */\nexport function pausedOnLimit(task: Readonly<Task>): boolean {\n return task.status === 'awaiting_user' && task.awaitingReason === 'usage-limit';\n}\n\n/** A task paused on a usage limit that resumes on its own when the limit resets (#84). */\nexport function waitsForReset(task: Readonly<Task>): boolean {\n return pausedOnLimit(task) && !!task.resumeAt;\n}\n\n/** The user's approval record, as a plan carries it (ADR-0029). */\nexport type ApprovalRecord = Pick<LegacyPlanState, 'approved' | 'approvedTasks'>;\n\n/** A copy of the plan's approval record, for a plan read whole; a field the plan lacks stays absent. */\nexport function approvalRecordOf(plan: ApprovalRecord): ApprovalRecord {\n return {\n ...(plan.approved !== undefined ? { approved: plan.approved } : {}),\n ...(plan.approvedTasks ? { approvedTasks: [...plan.approvedTasks] } : {}),\n };\n}\n\n/**\n * A task the scheduler leaves for the user (ADR-0035): an AI task not started\n * yet, in a plan they approved, outside their approval record — the planner\n * added it since. It starts once they start it or execute the plan. In a plan\n * they never approved nothing is theirs yet, so nothing is singled out.\n */\nexport function waitsForApproval(task: Pick<Task, 'id' | 'type' | 'status'>, record: ApprovalRecord): boolean {\n return record.approved === true\n && task.type === 'ai'\n && (task.status === 'pending' || task.status === 'approved')\n && !(record.approvedTasks ?? []).includes(task.id);\n}\n\n/**\n * Who put each task of an adopted list in the plan. A task the plan already\n * had keeps its origin, whatever the planner wrote; a new one is the\n * supervisor's when `bySupervisor`, and nobody's in particular otherwise, so\n * no planner can mark its own task as the supervisor's. `added` lists the new\n * tasks, subtasks included.\n */\nexport function stampOrigin(before: readonly Task[], after: readonly Task[], bySupervisor: boolean): { tasks: Task[]; added: string[] } {\n const prior = new Map(flattenTasks([...before]).map((t) => [t.id, t]));\n const added: string[] = [];\n const stamp = (tasks: readonly Task[]): Task[] => tasks.map((t) => {\n const was = prior.get(t.id);\n const origin = was ? was.addedBy : bySupervisor ? 'supervisor' : undefined;\n if (!was) added.push(t.id);\n const stamped: Task = { ...t, subtasks: stamp(t.subtasks ?? []) };\n if (origin) stamped.addedBy = origin;\n else delete stamped.addedBy;\n return stamped;\n });\n return { tasks: stamp(after), added };\n}\n\n/** Done, running, or held for the user: execution state only a runner or the user can change. */\nconst SETTLED_STATUSES: ReadonlySet<TaskStatus> = new Set<TaskStatus>(['completed', 'in_progress', 'awaiting_user']);\n\nfunction holdsSettledWork(task: Task): boolean {\n return SETTLED_STATUSES.has(task.status) || (task.subtasks ?? []).some(holdsSettledWork);\n}\n\n/**\n * Lay a planner-written task list over the plan it rewrites without letting it\n * change execution state. A planner restates tasks; it never witnessed one run,\n * so the status it writes is not evidence. A settled task is kept exactly as\n * it stands wherever the rewrite names it, and put back beside its old\n * neighbour where the rewrite leaves it out. Every other task keeps the status\n * it had; a task the rewrite adds starts pending.\n */\nexport function keepExecutionState(current: readonly Task[], rewrite: Task[]): Task[] {\n const existing = new Map(flattenTasks(current).map((t) => [t.id, t]));\n const named = new Set(flattenTasks(rewrite).map((t) => t.id));\n\n const overlay = (tasks: Task[], was: readonly Task[]): Task[] => {\n const result = tasks.map((t): Task => {\n const prior = existing.get(t.id);\n if (prior && SETTLED_STATUSES.has(prior.status)) return { ...prior };\n return {\n ...t,\n status: prior?.status ?? 'pending',\n skipReason: prior?.skipReason,\n verdict: prior?.verdict,\n outputSummary: prior?.outputSummary,\n runnerSessionId: prior?.runnerSessionId,\n runnerSessionOwner: prior?.runnerSessionOwner,\n carriedFrom: prior?.carriedFrom,\n attemptSkills: prior?.attemptSkills,\n attemptUsage: prior?.attemptUsage,\n // Where a task that has run ran is fixed, like its runner session (ADR-0020).\n ...(prior?.status === 'failed' ? { ops: prior.ops } : {}),\n forcedPastGate: prior?.forcedPastGate,\n subtasks: overlay(t.subtasks ?? [], prior?.subtasks ?? []),\n };\n });\n was.forEach((left, i) => {\n if (named.has(left.id) || !holdsSettledWork(left)) return;\n const neighbour = was.slice(0, i).reverse().find((t) => result.some((r) => r.id === t.id));\n const at = neighbour ? result.findIndex((r) => r.id === neighbour.id) + 1 : 0;\n result.splice(at, 0, { ...left });\n });\n return result;\n };\n\n return renumberTasks(overlay(rewrite, current));\n}\n","/** A name a skill can have, lower-cased: the one pattern for a `/name` token and for a skill folder. */\nexport const SKILL_NAME_PATTERN = '[a-z0-9][a-z0-9_-]*';\n\n/**\n * A `/skill-name` token anywhere in a message: whitespace (or string start)\n * before it, a skill name in any case, optional trailing punctuation that\n * isn't part of the name, then whitespace (or string end). The punctuation\n * group is what lets \"/grilling,\" name \"grilling\".\n */\nconst SKILL_TOKEN = new RegExp(`(^|\\\\s)\\\\/(${SKILL_NAME_PATTERN})([,.!?;:]*)(?=\\\\s|$)`, 'gi');\n\n/** Where a skill name is invoked: `start` is its `/`, `end` is just past the name, before any punctuation. */\nexport interface SkillToken {\n name: string;\n start: number;\n end: number;\n}\n\n/**\n * The first `/name` token for each name in `text`, names lower-cased. Only\n * the first counts because a skill loads once per message however often it is\n * named, so a later repeat is plain text.\n */\nexport function skillTokens(text: string): SkillToken[] {\n const tokens: SkillToken[] = [];\n const seen = new Set<string>();\n for (const match of text.matchAll(SKILL_TOKEN)) {\n const name = match[2].toLowerCase();\n if (seen.has(name)) continue;\n seen.add(name);\n const start = match.index + match[1].length;\n tokens.push({ name, start, end: start + 1 + match[2].length });\n }\n return tokens;\n}\n\n/** The tokens in a sent message that loaded one of `loaded` — what a surface highlights in the user's bubble. */\nexport function loadedSkillTokens(text: string, loaded: readonly string[]): SkillToken[] {\n return skillTokens(text).filter((t) => loaded.includes(t.name));\n}\n","/** Plan placement and dependency rules, shared with browser-bundled surfaces. */\nexport interface TreeTask<T> {\n id: string;\n order: number;\n title: string;\n subtasks?: readonly T[];\n}\n\nexport interface DependencyTask<T> extends TreeTask<T> {\n dependencies: readonly string[];\n}\n\nexport interface TaskPlacement<T> {\n task: T;\n parent?: T;\n ancestors: readonly T[];\n siblings: readonly T[];\n label: string;\n}\n\nexport function* walkPlan<T extends TreeTask<T>>(tasks: readonly T[]): Generator<TaskPlacement<T>> {\n function* walk(level: readonly T[], ancestors: readonly T[], parentLabel?: string): Generator<TaskPlacement<T>> {\n for (const task of level) {\n const label = parentLabel ? `${parentLabel}.${task.order}` : String(task.order);\n yield { task, parent: ancestors.at(-1), ancestors, siblings: level, label };\n yield* walk(task.subtasks ?? [], [...ancestors, task], label);\n }\n }\n yield* walk(tasks, []);\n}\n\nexport function taskPlacement<T extends TreeTask<T>>(tasks: readonly T[], id: string): TaskPlacement<T> | undefined {\n for (const row of walkPlan(tasks)) if (row.task.id === id) return row;\n return undefined;\n}\n\n/** Includes the root: deletion has to detach every edge into the removed tree. */\nexport function subtreeIds<T extends TreeTask<T>>(tasks: readonly T[], id: string): Set<string> {\n const found = taskPlacement(tasks, id);\n return new Set(found ? [...walkPlan([found.task])].map(({ task }) => task.id) : []);\n}\n\n/** Pickers offer earlier siblings; API validity is independent of display order. */\nexport function dependencyCandidates<T extends TreeTask<T>>(tasks: readonly T[], taskId?: string): T[] {\n if (!taskId) return [...tasks];\n const found = taskPlacement(tasks, taskId);\n return found ? found.siblings.filter((t) => t.order < found.task.order) : [];\n}\n\n/** The surviving tasks whose dependency lists a subtree removal will change. */\nexport function dependentsOf<T extends DependencyTask<T>>(tasks: readonly T[], taskId: string): T[] {\n const removed = subtreeIds(tasks, taskId);\n return [...walkPlan(tasks)].map(({ task }) => task)\n .filter((t) => !removed.has(t.id) && t.dependencies.some((id) => removed.has(id)));\n}\n\nexport interface DependencyCheck { ok: boolean; error?: string }\n\n/** Target restrictions, separate from cycles so a planner batch can check its final graph. */\nexport function dependencyTargetErrors<T extends TreeTask<T>>(\n tasks: readonly T[], taskId: string, dependencies: readonly string[],\n): string[] {\n const found = taskPlacement(tasks, taskId);\n if (!found) return ['Task not found'];\n const descendants = subtreeIds(tasks, taskId);\n const errors: string[] = [];\n for (const dep of dependencies) {\n if (dep === taskId) { errors.push(`\"${found.task.title}\" cannot depend on itself`); continue; }\n const target = taskPlacement(tasks, dep)?.task;\n if (!target) { errors.push(`Unknown dependency \"${dep}\"`); continue; }\n if (found.ancestors.some((t) => t.id === dep) || descendants.has(dep)) {\n errors.push(`\"${found.task.title}\" cannot depend on \"${target.title}\", its ancestor or descendant`);\n }\n }\n return errors;\n}\n\n/** The tasks on one dependency cycle, in dependency order, or null. */\nexport function findDependencyCycle<T extends DependencyTask<T>>(tasks: readonly T[]): T[] | null {\n const byId = new Map([...walkPlan(tasks)].map(({ task }) => [task.id, task]));\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 id of byId.keys()) {\n const hit = visit(id);\n if (hit) return hit.map((key) => byId.get(key)!);\n }\n return null;\n}\n\nexport function canSetDependencies<T extends DependencyTask<T>>(\n tasks: readonly T[], taskId: string, dependencies: readonly string[],\n): DependencyCheck {\n const [error] = dependencyTargetErrors(tasks, taskId, dependencies);\n if (error) return { ok: false, error };\n const replace = (level: readonly T[]): T[] => level.map((t) => ({\n ...t,\n dependencies: t.id === taskId ? dependencies : t.dependencies,\n subtasks: replace(t.subtasks ?? []),\n }));\n const cycle = findDependencyCycle(replace(tasks));\n return cycle\n ? { ok: false, error: `Dependency cycle detected: ${[...cycle, cycle[0]].map((t) => `\"${t.title}\"`).join(' → ')}` }\n : { ok: true };\n}\n","/** 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 { dependencyTargetErrors, findDependencyCycle } from './PlanTree';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Task, RunnerId, TaskModelAssignment, DEFAULT_RUNNERS, createTask, flattenTasksWithParents, inheritedSkills, skillNames } from '../models/Task';\nimport { taggedReadOnlyMode, type RunnerModeInfo } from './ModeResolver';\nimport { resolveAssignment, type AssignmentAdjustment, type AssignmentCatalog } from './TaskAssignment';\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 assignments are resolved against the catalog as it is planned,\n * and a model it names that the runner does not offer is refused; a\n * submitted plan's models are resolved as it lands instead (ADR-0003).\n */\n models?: AssignmentCatalog;\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 /** Where a made-up id comes from: a new one each time, unless the caller keeps them. */\n newId?: () => string;\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 /** What the tree's assignments were adjusted to beyond what the planner wrote, for the planner to be told. */\n adjustments: AssignmentAdjustment[];\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 assignment — model, effort, and the mode decided from read-only or\n * not, never named (ADR-0031) — is the resolver's (`resolveAssignment`),\n * as it is for every writer: a model a merge or split carries is read as\n * what the task was made from, so a part moved to another runner gets that\n * runner's;\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, newId = uuidv4 } = at;\n const base = from?.fields ?? {};\n const errors: PlanTaskError[] = [];\n const ignored: PlanTaskError[] = [];\n const renamed = new Map<string, string>();\n const adjustments: AssignmentAdjustment[] = [];\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 ? newId() : 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 const assignedModel = ai ? modelOf(raw, base.assignedModel) : undefined;\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, newId });\n subtasks.push(child.task);\n errors.push(...child.errors);\n ignored.push(...child.ignored);\n adjustments.push(...child.adjustments);\n for (const [was, now] of child.renamed) renamed.set(was, now);\n }\n }\n\n const fields = {\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 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 let taskMode = base.taskMode;\n let landedModel = assignedModel;\n if (ai && catalog.runners.includes(assignedRunner)) {\n const resolved = resolveAssignment({\n writer: catalog.models ? 'edit' : 'plan',\n task: { ...createTask(fields), taskMode },\n from: from ? { assignedRunner: base.assignedRunner ?? assignedRunner, assignedModel: base.assignedModel, taskMode: base.taskMode } : undefined,\n readOnly,\n catalog: { modelsByRunner: {}, ...catalog.models, runnerModes: catalog.runnerModes, autonomousDefault: catalog.autonomousDefault },\n });\n if (!resolved.ok) {\n // The planner names no mode: one it cannot have is about the read-only answer it gave (ADR-0031).\n errors.push({ taskId: id, field: resolved.field === 'taskMode' ? 'readOnly' : resolved.field, message: `${where}: ${resolved.error}` });\n } else {\n taskMode = resolved.task.taskMode;\n landedModel = resolved.task.assignedModel;\n adjustments.push(...resolved.adjustments);\n }\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 task = createTask({ ...fields, assignedModel: landedModel, taskMode });\n if (Array.isArray(raw.skills)) skillsAsked.set(task, raw.skills);\n return { task, errors, ignored, renamed, adjustments };\n}\n\n/**\n * The rules a plan's tasks are held to together, subtasks included, whichever\n * route they arrived by and whoever wrote them, the planner or the user.\n * `runners` are the ones the writer may put a task on (`assignableRunners`),\n * any when undefined; with `runnerModes`, an ops task is held off its\n * runner's read-only mode.\n */\nexport function checkPlanInvariants(\n tasks: readonly Task[],\n runners: readonly RunnerId[] | undefined,\n runnerModes?: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>,\n): 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.ops && t.taskMode !== undefined && t.taskMode === taggedReadOnlyMode(runnerModes?.[t.assignedRunner])) {\n fail('taskMode', `${where} is an ops task, which runs commands, so it cannot run in its runner's read-only mode`);\n }\n if (runners && t.type !== 'user' && !runners.includes(t.assignedRunner)) {\n fail('assignedRunner', `${where} has invalid assignedRunner \"${t.assignedRunner}\". Expected one of: ${runners.join(', ')}`);\n }\n }\n for (const { task: t, parent } of rows) {\n // Self-dependencies are reported with the cycle below, including its full path.\n for (const error of dependencyTargetErrors(tasks, t.id, t.dependencies.filter((id) => id !== t.id))) {\n errors.push({ taskId: t.id, field: 'dependencies', message: `${taskWhere(t.title, parent?.title)}: ${error}` });\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 return findDependencyCycle(tasks);\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 { 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 { DiscoveredModel, RunnerId } 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 `resolveAssignment` makes);\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 * The one rule for an effort and a model: the effort a task keeps on a model,\n * whether it was written with it or carried from the model before. Kept where\n * the model offers it; otherwise mapped to the nearest rung the model\n * supports (so a Claude-style \"xhigh\" on a low/medium/high model becomes\n * \"high\"); undefined — the runner's own default — when no mapping exists or\n * the model offers no levels, which is how a model discovery does not list\n * reads (`variants` empty). Core's resolver and every surface's model picker\n * call it, so a model change lands on the same effort wherever it is made.\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","import type { DiscoveredModel, RunnerId, Task, TaskModelAssignment } from '../models/Task';\nimport { clampThinkingEffort, effectiveAllowlist } from './ModelAllowlistResolver';\nimport { plannedMode, readOnlyMode, readOnlyRunners, taggedReadOnlyMode, unavailableModeReason, type RunnerModeInfo } from './ModeResolver';\n\n/**\n * Who writes a task's assignment. It decides what a model the writer names\n * meets where the task's runner does not offer it or the allowlist excludes it\n * (ADR-0003), which runners it may put a task on, and whether a live runner's\n * hold applies (ADR-0041):\n *\n * - `plan`: a whole plan the planner submitted, and every planner task as it\n * lands. A stray model is replaced, and the planner is told what moved.\n * - `edit`: the planner's edit. A stray model is refused, which the planner\n * answers in the same reply.\n * - `user`: a user's direct edit. A stray model is refused, with the reason.\n * - `userAdd`: a task the user adds. A stray model is replaced, and the user\n * is told what moved.\n * - `runner`: a switch made with the runner's own `/model`, written as the\n * user's edit. The runner already runs it, so neither the allowlist nor the\n * live runner's hold applies.\n */\nexport type AssignmentWriter = 'plan' | 'edit' | 'user' | 'userAdd' | 'runner';\n\n/**\n * What an assignment is resolved against: the discovered models, the manifest\n * modes and the allowlist, as the session holds them when the write lands —\n * the same catalog the planner's per-turn block shows, so nothing is refused\n * that the planner was never told about.\n */\nexport interface AssignmentCatalog {\n /** A runner absent, or listed with nothing, has models discovery could not find, not none. */\n modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>;\n /** Absent when no runner's modes are known: no mode is then derived. */\n runnerModes?: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>;\n /** Raw (unfiltered) allowlist, keyed by runner; read through {@link effectiveAllowlist}. */\n perRunnerAllowlist?: Partial<Record<RunnerId, string[]>>;\n /** Full (true) or Guarded: the mode a derived task lands on unless it is read-only (ADR-0001). Full when absent. */\n autonomousDefault?: boolean;\n /**\n * Whether a task's runner is live: an attempt of it is claimed, running,\n * quiet or waiting on someone. Its model, effort and runner are then that\n * runner's (ADR-0041): the process was started on them, and only the\n * runner's own command moves them. Absent means none is.\n */\n liveRunner?: (taskId: string) => boolean;\n}\n\n/** What a runner offers a task, as discovery and its manifest have it. */\nexport interface RunnerCatalog {\n models: DiscoveredModel[];\n modes: RunnerModeInfo[];\n}\n\n/** A change the resolver made to what was written, named so it is never silent (ADR-0001). */\nexport interface AssignmentAdjustment {\n taskId: string;\n title: string;\n field: 'assignedModel' | 'thinkingEffort';\n from: string | null;\n to: string | null;\n}\n\nexport interface AssignmentWrite {\n writer: AssignmentWriter;\n /** The task as written: the plan's task with the edit laid over it, or a new task. Its subtasks are not read. */\n task: Task;\n /** The task as the plan holds it; absent for a new task. */\n before?: Task;\n /**\n * What a new task is made from — the task a split part or merge starts\n * as. Its runner and model are read as `before`'s are: a part moved to\n * another runner carries a model chosen for the old one.\n */\n from?: Pick<Task, 'assignedRunner' | 'assignedModel' | 'taskMode'>;\n /** The planner's answer to read-only or not (ADR-0001). Absent, the task stays as it was. */\n readOnly?: boolean;\n catalog: AssignmentCatalog;\n}\n\nexport type AssignmentOutcome =\n | { ok: true; task: Task; adjustments: AssignmentAdjustment[] }\n | { ok: false; field: 'assignedRunner' | 'assignedModel' | 'taskMode'; error: string };\n\n/**\n * Why a task's model or effort cannot change while its runner is live, for\n * every writer but the runner itself. The plan follows a switch made with the\n * runner's own command instead (ADR-0041), so that is offered beside stopping it.\n */\nexport function liveModelRefusal(task: Pick<Task, 'title'>): string {\n return `\"${task.title}\" has a live runner, so its model and effort cannot change now: stop the task first, or use the runner's own /model`;\n}\n\n/**\n * Why a task cannot move to another runner while its runner is live: the move\n * re-derives the model from the new runner, under the same hold as\n * {@link liveModelRefusal}, and no runner's own command can make it.\n */\nexport function liveRunnerRefusal(task: Pick<Task, 'title'>): string {\n return `\"${task.title}\" has a live runner, so its runner cannot change now: stop the task first`;\n}\n\n/**\n * The runners a writer may put a task on (ADR-0003, V1): a submitted plan the\n * runners enabled now; a planner edit the plan's own as well, so an edit is\n * checked as it is made and as it lands against one set; a user any runner.\n * Undefined means any.\n */\nexport function assignableRunners(writer: AssignmentWriter, enabled: readonly RunnerId[], planRunners: readonly RunnerId[]): RunnerId[] | undefined {\n if (writer === 'plan') return [...enabled];\n if (writer === 'edit') return [...new Set([...planRunners, ...enabled])];\n return undefined;\n}\n\nfunction sameAssignment(a: TaskModelAssignment | undefined, b: TaskModelAssignment | undefined): boolean {\n return a?.modelId === b?.modelId && a?.thinkingEffort === b?.thinkingEffort;\n}\n\n/** Whether `mode` is the mode `runner`'s manifest tags read-only. */\nfunction readOnlyOn(mode: string | undefined, runner: RunnerId, catalog: AssignmentCatalog): boolean {\n return mode !== undefined && mode === taggedReadOnlyMode(catalog.runnerModes?.[runner]);\n}\n\n/**\n * The task's mode as written. The writer's own pick stands where the runner\n * offers it; otherwise a task the writer creates, moves, turns into an AI task\n * or says read-only or not of gets the mode the planner path gives it\n * (ADR-0001's fill-in table): its read-only mode, else the mode it has when\n * the runner offers it, else the runner's mode for the level. Silence keeps\n * what is, so a mode the user picked by hand survives every other edit.\n */\nfunction modeFor(write: AssignmentWrite, task: Task, moved: boolean): { ok: true; mode: string | undefined } | { ok: false; error: string } {\n const { writer, before, catalog } = write;\n const prior = before ?? write.from;\n const runner = task.assignedRunner;\n const modes = catalog.runnerModes?.[runner];\n const picked = (writer === 'user' || writer === 'userAdd') && task.taskMode !== undefined && task.taskMode !== prior?.taskMode;\n if (picked) {\n if (modes && modes.length > 0 && !modes.some((m) => m.id === task.taskMode)) {\n return { ok: false, error: `Runner \"${runner}\" has no task mode \"${task.taskMode}\": valid modes: ${modes.map((m) => m.id).join(', ')}` };\n }\n const unavailable = unavailableModeReason(modes, task.taskMode);\n if (unavailable) return { ok: false, error: `Mode \"${task.taskMode}\" of runner \"${runner}\" is unavailable on this host: ${unavailable}` };\n return { ok: true, mode: task.taskMode };\n }\n const becameAi = before?.type === 'user';\n if (writer === 'runner' || !catalog.runnerModes || (before && !moved && !becameAi && write.readOnly === undefined)) return { ok: true, mode: task.taskMode };\n // A move carries the old runner's mode id, which says read-only only on that runner.\n const carried = prior !== undefined && task.taskMode === prior.taskMode && readOnlyOn(prior.taskMode, prior.assignedRunner, catalog);\n const readOnly = write.readOnly ?? (carried || readOnlyOn(task.taskMode, runner, catalog));\n if (readOnly && !readOnlyMode(modes) && writer !== 'plan' && writer !== 'edit') {\n const reason = unavailableModeReason(modes, taggedReadOnlyMode(modes));\n const able = readOnlyRunners(catalog.runnerModes).filter((r) => r !== runner);\n return {\n ok: false,\n error: `\"${task.title}\" is read-only, and runner \"${runner}\" has no read-only mode${reason ? ' available on this host' : ''}: give it another mode first${able.length > 0 ? `, or move it to one of: ${able.join(', ')}` : ''}${reason ? `. ${reason}` : ''}`,\n };\n }\n const mode = plannedMode(runner, catalog.runnerModes, { readOnly, current: task.taskMode }, catalog.autonomousDefault ?? true);\n return mode.ok ? { ok: true, mode: mode.mode ?? task.taskMode } : mode;\n}\n\n/**\n * Resolve what one task runs on — its runner, model, thinking effort and mode\n * — for whichever writer wrote it. Every writer calls this, so they cannot\n * disagree. Returns the task with what it resolved, and each change made to\n * what was written, named; or a refusal.\n *\n * - **The live runner's hold.** While its runner is live a task's runner,\n * model and effort are that runner's (ADR-0041): any other writer is\n * refused a change, a model cleared as much as one replaced.\n * - **A model is its runner's.** A model the writer left alone stays, even off\n * the allowlist: the allowlist shapes what is written, not a model the user\n * switched to (ADR-0043). A model the task carries from another runner — a\n * move, or a part made from a task on another one — stays only where its new\n * runner lists it, or nothing is known against it, and the allowlist admits\n * it. A model the writer names is held to the runner and the allowlist as\n * its {@link AssignmentWriter} row says: the runner already runs it\n * (`runner`), or it is refused (`edit`, `user`), or replaced (`plan`,\n * `userAdd`) when only another runner lists it (it provably cannot run here)\n * or the allowlist excludes it. A model listed by no runner is not one any\n * runner disowns, so it stands, and the runner validates last.\n * - **Filling a model.** A missing, carried-off or replaced model is refilled\n * from the runner: the allowlist's first model the runner lists, else the\n * allowlist's first, else the runner's own first, else none, which the\n * runner fills with its own default.\n * - **Effort follows its model.** Whenever the model is listed, its effort is\n * held to what it offers ({@link clampThinkingEffort}, the one mapping rule:\n * the nearest level the model has, none when it has none); a model put in\n * here that discovery does not list gets none. A model discovery does not\n * list keeps the effort written with it, which the runner validates.\n * - **The mode** is the writer's pick where it has one, else derived as\n * {@link modeFor} says.\n *\n * Which runners a writer may use is the plan's rule ({@link assignableRunners},\n * `checkPlanInvariants`); this never moves a task to another runner.\n */\nexport function resolveAssignment(write: AssignmentWrite): AssignmentOutcome {\n const { writer, before, catalog } = write;\n const written = write.task;\n const prior = before ?? write.from;\n const moved = prior !== undefined && prior.assignedRunner !== written.assignedRunner;\n const title = (before ?? written).title;\n\n const live = writer !== 'runner' && !!before && !!catalog.liveRunner?.(before.id);\n if (live && moved) return { ok: false, field: 'assignedRunner', error: liveRunnerRefusal({ title }) };\n if (live && !sameAssignment(before?.assignedModel, written.assignedModel)) return { ok: false, field: 'assignedModel', error: liveModelRefusal({ title }) };\n if (written.type === 'user') return { ok: true, task: written, adjustments: [] };\n\n const mode = modeFor(write, written, moved);\n if (!mode.ok) return { ok: false, field: 'taskMode', error: mode.error };\n\n // A live runner holds its model and effort as they are, even where its model's levels have changed since.\n const model: ModelResult = live ? { ok: true, assignment: written.assignedModel } : modelFor(write, moved);\n if (!model.ok) return { ok: false, field: 'assignedModel', error: model.error };\n\n const task = model.assignment === written.assignedModel && mode.mode === written.taskMode\n ? written\n : { ...written, assignedModel: model.assignment, taskMode: mode.mode };\n const adjustments: AssignmentAdjustment[] = [];\n const note = (field: AssignmentAdjustment['field'], from: string | undefined, to: string | undefined) => {\n if ((from ?? null) !== (to ?? null)) adjustments.push({ taskId: written.id, title: written.title, field, from: from ?? null, to: to ?? null });\n };\n note('assignedModel', written.assignedModel?.modelId, task.assignedModel?.modelId);\n note('thinkingEffort', written.assignedModel?.thinkingEffort, task.assignedModel?.thinkingEffort);\n return { ok: true, task, adjustments };\n}\n\ntype ModelResult = { ok: true; assignment: TaskModelAssignment | undefined } | { ok: false; error: string };\n\n/** The model half of {@link resolveAssignment}. */\nfunction modelFor(write: AssignmentWrite, moved: boolean): ModelResult {\n const { writer, before, catalog } = write;\n const prior = before ?? write.from;\n const written = write.task.assignedModel;\n const runner = write.task.assignedRunner;\n const models = catalog.modelsByRunner;\n const own = models[runner] ?? [];\n const listing = (id: string) => own.find((m) => m.modelId === id);\n // A model the task ran with on another runner is evidence that runner lists it, which the allowlist is read with (R1).\n const carriedFrom = moved && written && prior?.assignedModel?.modelId === written.modelId ? prior.assignedRunner : undefined;\n const seen = carriedFrom && written && !models[carriedFrom]?.some((m) => m.modelId === written.modelId)\n ? { ...models, [carriedFrom]: [...(models[carriedFrom] ?? []), { modelId: written.modelId, modelLabel: written.modelLabel, variants: [] }] }\n : models;\n const allowed = effectiveAllowlist(catalog.perRunnerAllowlist?.[runner], runner, seen);\n const allows = (id: string) => !allowed || allowed.includes(id);\n\n // Held to what its model offers, unless that model is one discovery does not list.\n const settled = (assignment: TaskModelAssignment): TaskModelAssignment => {\n const known = listing(assignment.modelId);\n if (!known) return assignment;\n const thinkingEffort = clampThinkingEffort(assignment.thinkingEffort, known.variants);\n const availableVariants = known.variants.map((v) => v.id);\n if (thinkingEffort === assignment.thinkingEffort && assignment.availableVariants?.join() === availableVariants.join()) return assignment;\n const next: TaskModelAssignment = { ...assignment, availableVariants };\n if (thinkingEffort === undefined) delete next.thinkingEffort;\n else next.thinkingEffort = thinkingEffort;\n return next;\n };\n // The allowlist's order is its priority, among the models the runner lists\n // first; an allowlisted id discovery does not list still beats one the user excluded (R1).\n const refill = (effort: string | undefined): TaskModelAssignment | undefined => {\n const id = allowed?.find((a) => listing(a)) ?? allowed?.[0] ?? own[0]?.modelId;\n if (id === undefined) return undefined;\n const known = listing(id);\n const thinkingEffort = clampThinkingEffort(effort, known?.variants ?? []);\n return {\n modelId: id,\n modelLabel: known?.modelLabel ?? id,\n ...(thinkingEffort !== undefined ? { thinkingEffort } : {}),\n ...(known ? { availableVariants: known.variants.map((v) => v.id) } : {}),\n };\n };\n\n if (!written) return { ok: true, assignment: writer === 'runner' ? undefined : refill(undefined) };\n if (writer === 'runner') return { ok: true, assignment: settled(written) };\n\n const id = written.modelId;\n const same = prior?.assignedModel?.modelId === id;\n if (before && same && !moved) return { ok: true, assignment: settled(written) };\n\n const listedElsewhere = Object.entries(models).some(([other, list]) => other !== runner && list?.some((m) => m.modelId === id));\n if (same) {\n // Carried from a task on another runner, it was chosen for that one: it\n // needs this one's listing, or nothing known against it.\n const fits = !moved || !!listing(id) || (own.length === 0 && !listedElsewhere);\n return { ok: true, assignment: fits && allows(id) ? settled(written) : refill(written.thinkingEffort) };\n }\n\n if (writer === 'edit' || writer === 'user') {\n // Allowlisted ids are kept even unlisted (ADR-0003, R1); an empty list is discovery that found nothing.\n const offered = own.length === 0 || !!listing(id) || !!allowed?.includes(id);\n if (!offered) return { ok: false, error: `Runner \"${runner}\" does not offer model \"${id}\"` };\n if (!allows(id)) return { ok: false, error: `Model \"${id}\" is excluded by the allowlist for runner \"${runner}\"` };\n return { ok: true, assignment: settled(written) };\n }\n // Only another runner lists it: it provably cannot run here.\n const disowned = !listing(id) && listedElsewhere;\n return { ok: true, assignment: disowned || !allows(id) ? refill(written.thinkingEffort) : settled(written) };\n}\n\n/**\n * The planner's tasks as they land, subtasks included: each resolved as the\n * planner's ({@link resolveAssignment}, `plan`) against the catalog read now\n * and against the plan's task of the same id, so a model or allowlist that\n * changed since the write was checked is applied and named. A task the\n * planner left alone keeps its model; one whose runner is live keeps all of\n * its assignment. Modes were decided when the task was written, and are not\n * revisited. Nothing is refused here: a write the catalog no longer allows is\n * kept as it was written, and the runner validates last.\n */\nexport function resolveAssignments(\n tasks: readonly Task[],\n planned: readonly Task[],\n catalog: AssignmentCatalog,\n): { tasks: Task[]; adjustments: AssignmentAdjustment[] } {\n const prior = new Map<string, Task>();\n const index = (list: readonly Task[]) => list.forEach((t) => { prior.set(t.id, t); index(t.subtasks ?? []); });\n index(planned);\n const models = { ...catalog, runnerModes: undefined };\n const adjustments: AssignmentAdjustment[] = [];\n const walk = (list: readonly Task[]): Task[] => list.map((task) => {\n const outcome = resolveAssignment({ writer: 'plan', task, before: prior.get(task.id), catalog: models });\n const resolved = outcome.ok ? outcome.task : task;\n if (outcome.ok) adjustments.push(...outcome.adjustments);\n return { ...resolved, subtasks: walk(task.subtasks ?? []) };\n });\n return { tasks: walk(tasks), adjustments };\n}\n","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, runnerModes),\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","import { stripModelNoise } from './JsonExtractor';\n\n/** A task as seen in a still-streaming plan: its title (possibly partial) and whether its object has closed. */\nexport interface PartialPlanTask {\n title: string;\n status: 'streaming' | 'complete';\n /** Assigned model label, once it has streamed in — for the live row's chip. */\n model?: string;\n /** Set once `\"readOnly\": true` has streamed in — for the live row's chip. */\n readOnly?: true;\n}\n\n/**\n * Parse a partial (still-streaming) plan-JSON string into an ordered list of task\n * rows for the live progress view. Pure and lenient: it walks the characters of the\n * `tasks` array, emitting one row per task object once it has opened, with the title\n * read incrementally and the status flipping to `complete` when the object's brace\n * closes. The currently-streaming object stays `streaming` even with a partial title.\n * The React view is a thin renderer over this — it never parses JSON itself.\n */\nexport function parsePartialPlan(partial: string): PartialPlanTask[] {\n const text = stripModelNoise(partial);\n const arrayStart = findTasksArrayStart(text);\n if (arrayStart === -1) return [];\n\n const tasks: PartialPlanTask[] = [];\n let depth = 0; // brace depth relative to a task object (0 = between objects)\n let objText = ''; // accumulated text of the in-progress task object\n let inString = false;\n let escaped = false;\n\n for (let i = arrayStart; i < text.length; i++) {\n const ch = text[i];\n if (depth > 0) objText += ch;\n\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 === ']' && depth === 0) break; // end of the tasks array\n if (ch === '{') {\n if (depth === 0) objText = '{';\n depth++;\n } else if (ch === '}') {\n depth--;\n if (depth === 0) {\n tasks.push(rowFromObject(objText, 'complete'));\n objText = '';\n }\n }\n }\n\n // A task object that opened but never closed is the row currently streaming in.\n if (depth > 0) tasks.push(rowFromObject(objText, 'streaming'));\n return tasks;\n}\n\n/** Index just after the `[` that opens the top-level `tasks` array, or -1 if not present yet. */\nfunction findTasksArrayStart(text: string): number {\n const key = /\"tasks\"\\s*:\\s*\\[/.exec(text);\n return key ? key.index + key[0].length : -1;\n}\n\n/** Build a live row from a (possibly unterminated) task-object fragment. */\nfunction rowFromObject(fullText: string, status: PartialPlanTask['status']): PartialPlanTask {\n const objText = withoutSubtasks(fullText);\n const model = stringField(objText, 'modelLabel');\n const readOnly = /\"readOnly\"\\s*:\\s*true/.test(objText);\n return {\n title: titleFromObject(objText),\n status,\n ...(model ? { model } : {}),\n ...(readOnly ? { readOnly: true as const } : {}),\n };\n}\n\n/**\n * The fragment with its `subtasks` array cut out, so a row's title and chips\n * are the task's own and never a subtask's. An unterminated array is cut to\n * the end.\n */\nfunction withoutSubtasks(objText: string): string {\n const key = /\"subtasks\"\\s*:\\s*\\[/.exec(objText);\n if (!key) return objText;\n let depth = 0;\n let inString = false;\n let escaped = false;\n for (let i = key.index + key[0].length - 1; i < objText.length; i++) {\n const ch = objText[i];\n if (inString) {\n if (escaped) escaped = false;\n else if (ch === '\\\\') escaped = true;\n else if (ch === '\"') inString = false;\n } else if (ch === '\"') inString = true;\n else if (ch === '[') depth++;\n else if (ch === ']' && --depth === 0) return objText.slice(0, key.index) + withoutSubtasks(objText.slice(i + 1));\n }\n return objText.slice(0, key.index);\n}\n\n/** Read the `title` value out of a (possibly unterminated) task-object fragment. */\nfunction titleFromObject(objText: string): string {\n const m = /\"title\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)/.exec(objText);\n if (!m) return '';\n // The captured group may include a trailing closing quote's content only up to an\n // unescaped quote; for a completed value that's the whole title, for a streaming\n // one it's whatever has arrived so far.\n return m[1].replace(/\\\\\"/g, '\"');\n}\n\n/** Read a fully-streamed (closing-quote present) string field's value, or undefined. */\nfunction stringField(objText: string, key: string): string | undefined {\n const m = new RegExp(`\"${key}\"\\\\s*:\\\\s*\"((?:[^\"\\\\\\\\]|\\\\\\\\.)*)\"`).exec(objText);\n return m ? m[1].replace(/\\\\\"/g, '\"') : undefined;\n}\n"],"mappings":"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","names":["uuidv4","text","import_uuid","reason","id","uuidv4","isRecord","text"]}
1
+ {"version":3,"sources":["../src/parsing.ts","../src/models/Task.ts","../src/conversation/skillTokens.ts","../src/services/PlanTree.ts","../src/services/JsonExtractor.ts","../src/services/PlannedTask.ts","../src/services/ModeResolver.ts","../src/services/ModelAllowlistResolver.ts","../src/services/TaskAssignment.ts","../src/services/PlanValidator.ts","../src/services/PartialPlanParser.ts"],"sourcesContent":["/**\n * Browser-safe entry point for the pure plan-parsing helpers.\n *\n * The main barrel (`@ordewell/core`) pulls in the AI services, which depend on\n * Node-only SDKs (`fs`, the Google/OpenAI clients) and therefore cannot be bundled\n * into a browser webview. Front-ends that only need the pure parsing/streaming\n * helpers import them from `@ordewell/core/parsing` instead.\n */\nexport { parsePlanJson } from './services/PlanValidator';\nexport { extractJsonObject, PlanParseError } from './services/JsonExtractor';\nexport { parsePartialPlan, type PartialPlanTask } from './services/PartialPlanParser';\n","import { v4 as uuidv4 } from 'uuid';\nimport type { PlanIsolation } from '../interfaces/IWorktreeIsolation';\nimport type { AttemptUsage, PlannerUsage, ReportedWindow, UsageRecord, UsageTotals } from './Usage';\nimport type { ExecutionRecord, SupervisorLevel } from './Execution';\nimport type { SkillSource } from '../services/SkillsService';\nimport type { SupervisionMemorySnapshot } from '../services/LoopGuards';\nimport type { SupervisorSnapshot } from '../services/Supervisor';\nimport { SKILL_NAME_PATTERN } from '../conversation/skillTokens';\nimport { subtreeIds, walkPlan } from '../services/PlanTree';\n\nexport interface UserStep {\n order: number;\n instruction: string;\n completed: boolean;\n}\n\n/** One deterministic signal gathered while verifying a completed task. `completion_marker` is only in verdicts saved before completion was reported through `task_complete` alone. */\nexport interface VerificationCheck {\n name: 'exit_code' | 'completion_marker' | 'task_complete' | 'manual';\n passed: boolean;\n /** A check that did not apply. Skipped checks don't affect the verdict. */\n skipped: boolean;\n detail: string;\n}\n\n/** Evidence-based verdict for a completed task. Single end-to-end outcome produced by verification. */\nexport interface Verdict {\n outcome: 'pass' | 'fail';\n reason: string;\n checks: VerificationCheck[];\n decidedAt: string;\n}\n\nexport interface TaskOutputSummary {\n reviewReason: string;\n /** The `task_complete` summary, the task's report; absent when the attempt ended without one, and from sessions saved before it was kept. */\n report?: string;\n /** Empty when there is a report: the turn's final text is the report itself. */\n logTail: string;\n capturedAt: string;\n}\n\nexport type TaskType = 'ai' | 'user';\n/**\n * `skipped` is terminal and verdict-less: the task will not be waited for, and\n * nothing it did lands. Its dependents go ahead without it once the work it\n * waited on is done. There is no stored `blocked`: a task is blocked when a\n * failed dependency says so (ADR-0034), and a file that kept the old status\n * loads as pending.\n */\nexport type TaskStatus = 'pending' | 'approved' | 'in_progress' | 'completed' | 'failed' | 'awaiting_user' | 'skipped';\nexport type TaskMode = string;\n\n/**\n * Why an `awaiting_user` task waits (ADR-0018, W1): a structured turn that\n * ended without a `task_complete` call, a checkpoint question, work that did not\n * land, an ops task that changed tracked files (ADR-0020), or its runner's\n * usage limit. Saved, so no surface has to guess it from whether an attempt\n * is live.\n */\nexport type AwaitingReason = 'input' | 'checkpoint' | 'conflict' | 'files-changed' | 'usage-limit';\n\nexport function isAwaitingReason(value: unknown): value is AwaitingReason {\n return value === 'input' || value === 'checkpoint' || value === 'conflict' || value === 'files-changed' || value === 'usage-limit';\n}\n\nexport interface TaskModelAssignment {\n modelId: string;\n modelLabel: string;\n thinkingEffort?: string;\n /**\n * All variant ids the model offered when this assignment was made. Carried\n * on the assignment because runners need it at spawn time (opencode's TUI\n * only honors an assigned variant when the others are config-disabled) and\n * the discovery catalog isn't available there.\n */\n availableVariants?: string[];\n}\n\nexport type RunnerId = string;\n\n/**\n * Bring loaded tasks to the structured-only shape, in place. A task saved\n * while the terminal transport existed may say it ran there, and why; neither\n * means anything now, and such a task left no session to continue. Its\n * completion marker id went with the text markers: completion is reported\n * only through `task_complete`.\n */\nexport function migrateLoadedTasks(tasks: readonly Task[]): void {\n for (const task of tasks) {\n if (typeof task.runnerSessionId !== 'string') delete task.runnerSessionId;\n if (typeof task.carriedFrom !== 'string') delete task.carriedFrom;\n if (typeof task.runnerSessionOwner !== 'string') delete task.runnerSessionOwner;\n const legacy = task as Task & { transport?: { kind?: unknown; nativeSessionId?: unknown } };\n if (!task.runnerSessionId && legacy.transport?.kind === 'structured' && typeof legacy.transport.nativeSessionId === 'string') {\n task.runnerSessionId = legacy.transport.nativeSessionId;\n }\n delete legacy.transport;\n delete (task as { completionMarker?: unknown }).completionMarker;\n // The effort once had a second home on the task; the spawn reads only the model's.\n const effort = (task as { thinkingEffort?: unknown }).thinkingEffort;\n if (typeof effort === 'string' && task.assignedModel && !task.assignedModel.thinkingEffort) task.assignedModel.thinkingEffort = effort;\n delete (task as { thinkingEffort?: unknown }).thinkingEffort;\n migrateLoadedTasks(task.subtasks ?? []);\n }\n}\n\n/**\n * A task skill as one attempt received it: read from the task's own worktree\n * (or global) at spawn, kept so the user can see what the runner was told\n * even after the skill's file changes.\n */\nexport interface TaskSkillSnapshot {\n name: string;\n source: SkillSource;\n path: string;\n content: string;\n}\n\n/** A task skill as a surface is told of it: the body stays in the task's log file and the session. */\nexport type TaskSkillNotice = Omit<TaskSkillSnapshot, 'content'>;\n\nexport interface Task {\n id: string;\n order: number;\n title: string;\n description: string;\n type: TaskType;\n status: TaskStatus;\n dependencies: string[];\n prompt?: string;\n userSteps?: UserStep[];\n subtasks: Task[];\n verdict?: Verdict;\n outputSummary?: TaskOutputSummary;\n assignedModel?: TaskModelAssignment;\n assignedRunner: RunnerId;\n taskMode?: TaskMode;\n autonomy?: 'AFK' | 'HITL';\n sliceType?: 'HITL' | 'AFK';\n userStoriesCovered?: string[];\n /** The runner's saved session for a later continue; cleared when a fresh attempt starts. */\n runnerSessionId?: string;\n /**\n * The runner that session belongs to: a session cannot cross runners, so\n * Continue is offered only while the task is still assigned to it. Absent\n * on a plan saved before it was recorded; the last attempt's runner stands\n * in (see `sessionOwner`).\n */\n runnerSessionOwner?: RunnerId;\n /** Set only while `status` is `awaiting_user`, and not always then — a pause saved by an older build has none. */\n awaitingReason?: AwaitingReason;\n /**\n * When a task paused on its runner's usage limit is resumed on its own (ISO\n * time): the reset the runner announced, plus a short margin. Absent when no\n * reset time could be read or the user cancelled the resume — the task then\n * waits for a manual retry. Set only with `awaitingReason` `usage-limit`.\n */\n resumeAt?: string;\n /**\n * The runner a task paused on its usage limit was moved off (ADR-0018, U1):\n * its next attempt is a carry-over, a fresh session that starts from that\n * attempt's files. Cleared once that attempt runs, and by a retry.\n */\n carriedFrom?: RunnerId;\n /** Why the task was skipped, as whoever skipped it put it; set only while `status` is `skipped`, and not always then. */\n skipReason?: string;\n /**\n * An ops task (ADR-0020): it changes no repository files, runs at the\n * workspace root rather than in a worktree, and waits for the change tasks\n * it depends on to be merged into the user's branch. Top-level AI tasks\n * only; absent means a change task.\n */\n ops?: boolean;\n /**\n * The dependencies, by title, whose work was not merged into the user's\n * branch when the user force-started this task past its merge gate\n * (ADR-0020). Cleared when the task is retried.\n */\n forcedPastGate?: string[];\n /**\n * Task skills (`applies-to: task`) attached by name. Their bodies are put in\n * the runner's prompt at spawn, resolved where the task runs — a subtask's\n * are its own, never its parent's. Absent means none.\n */\n skills?: string[];\n /** The skills the latest attempt was given, as read when it spawned. */\n attemptSkills?: TaskSkillSnapshot[];\n /**\n * Who put the task in the plan, when it was not the user's own planning:\n * `supervisor` for a task an Act supervisor added during a run, which\n * counts as approved to run (ADR-0029). Set by the session, never by an op.\n */\n addedBy?: 'supervisor';\n attemptUsage?: AttemptUsage[];\n}\n\nexport interface DiscoveredMode {\n id: string;\n label: string;\n description: string;\n}\n\nexport type ResearchToolType =\n | 'read_file' | 'read_files' | 'glob' | 'grep' | 'find_symbol' | 'list_dir'\n | 'bash' | 'fetch' | 'web_search' | 'spawn_research_agent'\n | 'task_query' | 'task_output' | 'task_diff'\n /**\n * A tool belonging to a harness planner's own toolbox (ADR-0009) that has no\n * Ordewell equivalent — Edit, WebFetch, TodoWrite, whatever a coding agent\n * ships next. The real name travels in `toolLabel` rather than being\n * relabelled as a tool it is not; the union stays closed so the\n * exhaustiveness checks in every surface's icon/label switch survive.\n */\n | 'agent_tool';\n\n/**\n * What happened when a research tool call ran, for honest per-surface\n * rendering. The broadcast seam carries this on every `research_step_done` so\n * surfaces do not have to pattern-match refusal text to tell a refused `rm`\n * from a successful `rm` — the old render path flipped a `✓` for both.\n */\nexport type ResearchStepOutcome = 'success' | 'failure' | 'refused' | 'denied' | 'not_executed';\n\nexport interface ResearchStep {\n id: string;\n tool: ResearchToolType;\n /** The tool's own name when it came from a harness planner — always set for `agent_tool`. */\n toolLabel?: string;\n args: string;\n result: string;\n success: boolean;\n outcome: ResearchStepOutcome;\n /** The model's tool_call id, so a surface can match `tool_result` to the\n * pending `tool_call` it announced — robust under parallel same-tool rounds. */\n toolCallId?: string;\n /** The research subagent that ran the call, so a reload regroups it under that subagent. */\n subagentId?: string;\n timestamp: string;\n thinkingText?: string;\n}\n\nexport interface UserPromptEntry {\n id: string;\n type: 'user_prompt' | 'system';\n content: string;\n timestamp: string;\n}\n\nexport type SubagentOutcome = 'done' | 'failed' | 'stopped';\n\n/**\n * One subagent's whole run, logged when it finishes: what it was asked, how it\n * ended and what it reported. Its steps stay separate entries carrying the same\n * `subagentId`, so an older reader that knows only steps still shows them.\n */\nexport interface SubagentLogEntry {\n id: string;\n type: 'subagent';\n subagentId: string;\n brief: string;\n model?: string;\n outcome: SubagentOutcome;\n digest: string;\n usage?: UsageTotals;\n timestamp: string;\n}\n\nexport type ResearchLogEntry = ResearchStep | UserPromptEntry | SubagentLogEntry;\n\nexport interface ResearchProgress {\n // 'liveness' carries no content: a raw-line signal from the harness process\n // (ADR-0009) that reaches a surface even when a turn is producing nothing\n // visible — a subagent's filtered chatter, most often — so an idle watchdog\n // downstream doesn't mistake \"nothing to show\" for \"nothing happening\".\n //\n // The turn-scoped kinds below carry one planner turn's stream (#47); see the\n // matching SessionMessage variants for the invariants each one keeps.\n // 'text_delta' is reply text as the model streams it (`text`, `segmentId`),\n // an envelope included: the turn's owner routes each segment to prose or to\n // the plan display, so no backend has to tell them apart. 'text_retracted'\n // takes back an attempt's text: `segmentId`'s, or when absent everything the\n // backend call that sends it has streamed.\n // 'usage' reports one model call (`record`). 'subagent_started' carries\n // `brief` and `model`; 'subagent_finished' carries `outcome`, `digest` and\n // `usage`.\n type:\n | 'thinking' | 'tool_call' | 'tool_result' | 'plan_token' | 'interrupted' | 'liveness'\n | 'text_delta' | 'text_retracted' | 'usage' | 'subagent_started' | 'subagent_finished';\n /** Minted by whoever runs the turn and passed through untouched; absent outside a turn. */\n turnId?: string;\n /** One continuous run of model text — text before a tool call is its own segment. */\n segmentId?: string;\n text?: string;\n tool?: string;\n /** Harness planners (ADR-0009): the agent's own name for a tool Ordewell has no member for. */\n toolLabel?: string;\n toolArgs?: string;\n toolResult?: string;\n planToken?: string;\n step?: ResearchStep;\n /** The model's tool_call id, threaded on tool_call and tool_result so a\n * surface can match the result to its pending call — robust under parallel\n * same-tool rounds where LIFO-by-name matching mislabels summaries. */\n toolCallId?: string;\n /** Present when this event originates from (or reports on) one spawned research subagent (issue #34). */\n subagentId?: string;\n record?: UsageRecord;\n brief?: string;\n model?: string;\n outcome?: SubagentOutcome;\n digest?: string;\n usage?: UsageTotals;\n}\n\nexport interface ThinkingBlock {\n id: string;\n text: string;\n}\n\nexport interface StreamThinkingEvent {\n type: 'thinking';\n block: ThinkingBlock;\n}\n\nexport interface StreamStepEvent {\n type: 'step';\n step: ResearchStep;\n}\n\nexport type StreamEvent = StreamThinkingEvent | StreamStepEvent;\n\nexport interface DiscoveredModel {\n modelId: string;\n modelLabel: string;\n runnerProvider?: string;\n /**\n * Human-facing provider name as the runner itself reports it (e.g.\n * \"OpenCode Zen\" for `runnerProvider: 'opencode'`). Populated from the\n * runner's own provider catalog when available; when absent the UI derives a\n * label from `runnerProvider` by title-casing.\n */\n runnerProviderLabel?: string;\n /**\n * The runner whose catalog listed this model. Stamped once, at the single\n * `ModelDiscovery.discover` choke point, so a flat cross-runner list can\n * still say where each entry came from — `runnerProvider` alone cannot:\n * OpenCode reports most of its catalog as `openrouter`, which names the\n * serving backend, not the agent Ordewell would spawn.\n */\n runnerId?: string;\n /** The runner's display name (`OpenCode`), from its manifest. */\n runnerLabel?: string;\n variants: { id: string; label: string }[];\n /**\n * The model's context window when the runner or catalog reports it (#49).\n * Read by the planner-model lookup so context fill can be shown; absent when\n * unknown rather than defaulted to zero.\n */\n contextWindow?: number;\n}\n\nexport type PlanStatus = 'draft' | 'approved' | 'rejected' | 'running' | 'completed';\n\n/**\n * One entry of the planner's persisted dialogue (ADR-0002). The single source\n * of truth for both UI redisplay and conversational context. Tool-call results\n * are NOT stored here — they live in the AI service's tool-use history;\n * `researchLog` remains the persisted tool trace for the UI.\n */\nexport interface ConversationMessage {\n role: 'user' | 'assistant';\n content: string;\n timestamp: string;\n /**\n * Timeline marker: 'plan_generated' records the point in the dialogue where\n * the plan was committed (the UI anchors the plan card there on restore);\n * 'system' is a host-injected notice; 'compaction' is the summary a\n * user-triggered compaction left in place of the earlier messages — always\n * the transcript's first entry. 'skill_load' is a skill loaded into the\n * conversation, its snapshot in `skill`; one the user invoked follows the\n * message that named it. 'execution' is a call at the command seam\n * (ADR-0029), its record in `execution`. 'wake_up' is the digest a wake-up\n * gave the supervisor (role 'user': the model was told it), and\n * 'supervisor' what the supervisor said in a wake-up turn (ADR-0029).\n * 'error' is a planner turn that ended on the planner's usage limit, drawn\n * as a failure and never replayed to the planner: it is not the planner's\n * answer, and its inputs are sent again (see {@link PlannerResendState}).\n * Absent for ordinary chat turns, so sessions saved before markers existed\n * degrade gracefully.\n */\n kind?: 'plan_generated' | 'system' | 'compaction' | 'skill_load' | 'execution' | 'wake_up' | 'supervisor' | 'error';\n actor?: 'Ordewell setting';\n /** Present exactly when `kind` is 'skill_load'. */\n skill?: SkillLoad;\n /** Present exactly when `kind` is 'execution'. */\n execution?: ExecutionRecord;\n /** A user's message that was force sent, stopping the turn before it (ADR-0023). */\n forced?: true;\n /**\n * What a reply showed, run by run, where each began (ADR-0017 A1). Beside\n * `content`, which stays the reply as the planner is told it. Absent on\n * entries saved before runs were kept, which reload as one message.\n */\n runs?: ReplyRun[];\n}\n\n/**\n * One run of a planner reply's text: the segment it streamed under, its\n * settled text, and when it began. A run that never streamed (a message the\n * conversation wrote in the planner's place) has a segment no stream used.\n */\nexport interface ReplyRun {\n segmentId: string;\n text: string;\n /** ISO timestamp. */\n startedAt: string;\n}\n\n/**\n * A skill's SKILL.md as it was when loaded into a planner conversation. A\n * snapshot rather than a reference: a resumed, forked or rewound conversation\n * replays the body the planner originally saw, even after the file changed or\n * was deleted.\n */\nexport interface SkillLoad {\n invokedBy: 'user' | 'planner';\n name: string;\n source: SkillSource;\n /** The SKILL.md that won, home-abbreviated (`~/...`): which copy loaded is what a reader needs from it. */\n path: string;\n content: string;\n /**\n * Set when the user named a task skill: it is not loaded into the planner's\n * context, which is only told to attach it to the tasks it fits. `content`\n * is empty; the description is what the planner's directive quotes.\n */\n attaches?: { description: string };\n}\n\n/** A skill load as a surface announces it — the body stays with the transcript. */\nexport type SkillLoadNotice = Omit<SkillLoad, 'content'>;\n\n/** A message the user sent, as opposed to a skill load their message caused or a wake-up digest. */\nexport function isUserMessage(entry: Pick<ConversationMessage, 'role' | 'kind'>): boolean {\n return entry.role === 'user' && entry.kind !== 'skill_load' && entry.kind !== 'wake_up';\n}\n\n/** A follow-up an older build queued behind a run's batch boundary; read only to carry it over as a {@link PendingPlanTurn}. */\nexport interface QueuedMessage {\n id: string;\n text: string;\n timestamp: string;\n skills?: SkillLoad[];\n}\n\n/** Who a planner turn answers (ADR-0029): the user's message, or a wake-up batch the supervisor is given. */\nexport type PlanTurnSource = 'user' | 'supervisor';\n\n/**\n * A planner turn's input not yet answered by a turn that ended. It leaves the\n * plan with the change its turn made, in the same save, so a reload replays\n * exactly the inputs no turn answered — once. A supervisor's input is a\n * wake-up batch not yet delivered (ADR-0029): it stays until a turn lands\n * with it, whatever turns fail meanwhile.\n */\nexport interface PendingPlanTurn {\n id: string;\n source: PlanTurnSource;\n /** The user's message, or a wake-up batch's digest. */\n text: string;\n timestamp: string;\n /** What its `/name` tokens loaded when it was sent, so a replay sends the same skills. */\n skills?: SkillLoad[];\n /** Force sent: it waits ahead of every input not forced, and its turn is its own (ADR-0023, F2). */\n forced?: true;\n}\n\n/**\n * The run's own copy of the user's run settings (ADR-0030): taken when the\n * user starts it, changed at once by a change made in this session, and\n * lowered by a tripped guard or a failed supervisor.\n */\nexport interface RunSettings {\n mergeAtGatesAutomatically: boolean;\n /** Absent on a run started before supervisor levels existed: Off. */\n supervisorLevel?: SupervisorLevel;\n /** Absent on a run started before ADR-0030: the user's setting. */\n supervisorRetryLimit?: number;\n /** Absent on a run started before ADR-0030: the user's setting. */\n wakeUpReadBudget?: number;\n /** Why the supervisor stopped for the rest of this run, which carries on as Off; absent while it watches. */\n unsupervised?: string;\n}\n\n/**\n * What supervision remembers across a daemon restart (ADR-0029): the loop\n * guards' memory and the wake-up state not yet delivered with a turn.\n */\nexport interface SupervisionSnapshot {\n /** Retries, failed-attempt shapes, the supervisor's edits and what the user was told. */\n guards?: SupervisionMemorySnapshot;\n /** The events inside their debounce window and the wake-up rate's timestamps. */\n wake?: SupervisorSnapshot;\n}\n\n/**\n * What waits on the planner's usage limit. With `at`, the inputs no turn\n * answered are sent again then, on their own; without it — the reset could\n * not be read, the resend hit the limit again, or it was cancelled — they go\n * with the next input: the user's message, or a wake-up.\n */\nexport interface PlannerResendState {\n /** The runner whose account hit the limit; a planner on another one is not held by it. */\n runner?: RunnerId;\n at?: string;\n}\n\nexport interface LegacyPlanState {\n runSettings?: RunSettings;\n /** What supervision remembers across a daemon restart (ADR-0029); absent on plans saved before it existed. */\n supervision?: SupervisionSnapshot;\n tasks: Task[];\n generatedAt: string;\n status: PlanStatus;\n runners: RunnerId[];\n lastUpdated: string;\n researchLog?: ResearchLogEntry[];\n /** The planner dialogue — user messages and assistant messages, in order (ADR-0002). */\n conversationHistory?: ConversationMessage[];\n /** Full markdown PRD once written by the planner (PRD mode), also saved to .scratch/<slug>/PRD.md. */\n prdMarkdown?: string;\n /** Planner turn inputs no turn has answered yet (ADR-0029); a reload replays them. */\n pendingTurns?: PendingPlanTurn[];\n /** Written by builds that queued mid-run edits; a load moves it into {@link pendingTurns}. */\n queuedMessages?: QueuedMessage[];\n /**\n * The plan's isolation run (ADR-0013), written from the orchestrator at\n * persist time and read back only when a saved plan is adopted. It names\n * branches and worktrees that belong to this plan alone: a fork of the plan\n * must leave it behind rather than share it.\n */\n isolation?: PlanIsolation;\n /** Kept so a reopened session shows the same token line (#49). */\n plannerUsage?: PlannerUsage;\n /** The windows reported over the session, so a reopened one knows each model's from its first call. */\n contextWindows?: ReportedWindow[];\n /** The planner hit its usage limit and the inputs its turn took wait to be sent again; absent otherwise. */\n plannerResend?: PlannerResendState;\n /**\n * The tasks the user approved to run: every task when they executed the\n * plan, and each one they started or retried themselves since. A command\n * from anyone else starts only these (ADR-0029); a task the planner added\n * after the approval waits for the user's. Only tasks still in the plan: a\n * task that leaves takes its approval with it.\n */\n approvedTasks?: string[];\n /**\n * The user approved the plan to run: Execute plan, signing off a review, or\n * autorun's start (ADR-0029, ADR-0030). Kept apart from {@link status},\n * which surfaces rewrite for display; a user's start of one task approves\n * that task, not the plan.\n */\n approved?: boolean;\n}\n\nexport interface Message {\n id: string;\n role: 'user' | 'planner' | 'system';\n content: string;\n timestamp: number;\n}\n\nexport interface TaskSnapshot extends Task {\n completedAt: number;\n verdict?: Verdict;\n retryCount: number;\n finalized: boolean;\n}\n\n/** The plan a surface reads whole carries the approval record, so it can tell which tasks wait for the user (ADR-0035). */\nexport type PlanState = (\n | {\n phase: 'planning';\n history: Message[];\n message: string;\n pendingTasks: Task[];\n }\n | {\n phase: 'executing';\n history: Message[];\n message: string;\n executionLog: TaskSnapshot[];\n pendingTasks: Task[];\n goal: string;\n runners: string[];\n status: PlanStatus;\n }\n) & ApprovalRecord;\n\nfunction hasPhase(raw: unknown): raw is PlanState {\n return typeof raw === 'object' && raw !== null && 'phase' in raw;\n}\n\nfunction hasTasks(raw: unknown): raw is LegacyPlanState {\n return typeof raw === 'object' && raw !== null && 'tasks' in raw && Array.isArray((raw as Record<string, unknown>).tasks);\n}\n\nfunction logEntryToMessage(entry: ResearchLogEntry): Message {\n const timestamp = new Date(entry.timestamp).getTime();\n if (!('type' in entry)) {\n return { id: entry.id, role: 'system', content: JSON.stringify({ tool: entry.tool, args: entry.args, result: entry.result }), timestamp };\n }\n if (entry.type === 'subagent') {\n const { subagentId, brief, outcome, digest } = entry;\n return { id: entry.id, role: 'system', content: JSON.stringify({ subagentId, brief, outcome, digest }), timestamp };\n }\n return { id: entry.id, role: entry.type === 'user_prompt' ? 'user' : 'system', content: entry.content, timestamp };\n}\n\nfunction migrateHistory(raw: LegacyPlanState): Message[] {\n const messages: Message[] = [];\n\n if (raw.researchLog) {\n messages.push(...raw.researchLog.map(logEntryToMessage));\n }\n\n if (raw.queuedMessages) {\n for (const qm of raw.queuedMessages) {\n messages.push({\n id: qm.id,\n role: 'system',\n content: qm.text,\n timestamp: new Date(qm.timestamp).getTime(),\n });\n }\n }\n\n return messages;\n}\n\nexport function migratePlanState(raw: unknown): PlanState {\n if (hasPhase(raw)) return raw;\n\n if (!hasTasks(raw)) {\n return {\n phase: 'planning',\n history: [],\n message: '',\n pendingTasks: [],\n };\n }\n\n const hasExecution = raw.tasks.some(\n (t) => t.status === 'completed' || t.status === 'failed',\n );\n\n const messages = migrateHistory(raw);\n const record = approvalRecordOf(raw);\n\n if (hasExecution) {\n const executionLog: TaskSnapshot[] = raw.tasks\n .filter((t) => t.status === 'completed' || t.status === 'failed')\n .map((t) => ({\n ...t,\n completedAt: new Date(raw.lastUpdated).getTime(),\n verdict: t.verdict,\n retryCount: 0,\n finalized: true,\n }));\n\n const pendingTasks = raw.tasks.filter(\n (t) => t.status !== 'completed' && t.status !== 'failed',\n );\n\n return {\n phase: 'executing',\n history: messages,\n message: '',\n executionLog,\n pendingTasks,\n goal: '',\n runners: raw.runners,\n status: raw.status,\n ...record,\n };\n }\n\n return {\n phase: 'planning',\n history: messages,\n message: '',\n pendingTasks: raw.tasks,\n ...record,\n };\n}\n\nexport { taskOrderLabel, taskRef, taskRefOf, titledTaskRef, titledTaskRefOf, resolveTaskRef, resolveUserTaskRef } from '../services/TaskRefs';\n\nexport function createTask(overrides: Partial<Task> = {}): Task {\n const skills = skillNames(overrides.skills);\n return {\n id: overrides.id ?? uuidv4(),\n order: overrides.order ?? 0,\n title: overrides.title ?? '',\n description: overrides.description ?? '',\n type: overrides.type ?? 'ai',\n status: overrides.status ?? 'pending',\n dependencies: overrides.dependencies ?? [],\n prompt: overrides.prompt,\n userSteps: overrides.userSteps,\n subtasks: overrides.subtasks ?? [],\n verdict: overrides.verdict,\n outputSummary: overrides.outputSummary,\n ...(overrides.attemptUsage ? { attemptUsage: structuredClone(overrides.attemptUsage) } : {}),\n assignedModel: overrides.assignedModel,\n assignedRunner: overrides.assignedRunner ?? DEFAULT_RUNNERS[0],\n taskMode: overrides.taskMode ?? 'build',\n autonomy: overrides.autonomy,\n sliceType: overrides.sliceType,\n userStoriesCovered: overrides.userStoriesCovered,\n ...(opsFlag(overrides.ops, overrides.type) ? { ops: true } : {}),\n ...(skills ? { skills } : {}),\n ...(overrides.addedBy === 'supervisor' ? { addedBy: 'supervisor' as const } : {}),\n };\n}\n\n/**\n * A name a skill can have, lower-cased as `/name` is. Anything else — a path\n * separator, `..` — is joined into a path and could only reach outside the\n * skill dirs.\n */\nconst SKILL_NAME = new RegExp(`^${SKILL_NAME_PATTERN}$`);\n\nexport function isSkillName(name: string): boolean {\n return SKILL_NAME.test(name);\n}\n\n/**\n * The `skills` field as a task stores it, however it arrived: the planner's\n * JSON is untyped. Names are trimmed, lower-cased and deduplicated, and one\n * no skill could have is dropped; none at all is stored absent, so a plan\n * without skills reads exactly as it did before them.\n */\nexport function skillNames(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const names = [...new Set(value.filter((v): v is string => typeof v === 'string').map((v) => v.trim().toLowerCase()).filter(isSkillName))];\n return names.length > 0 ? names : undefined;\n}\n\n/**\n * The skills of a task a merge or a split makes from others: what the edit\n * chose when it named any, none when it chose an explicit `[]`, else what the\n * tasks it came from had — a malformed value is not a request to drop them.\n */\nexport function inheritedSkills(chosen: unknown, inherited: readonly string[] | undefined): string[] | undefined {\n if (Array.isArray(chosen) && chosen.length === 0) return undefined;\n return skillNames(chosen) ?? skillNames(inherited);\n}\n\n/**\n * The `ops` flag as a task stores it (ADR-0020), however it arrived. Only a\n * literal `true` on an AI task marks an ops task: the planner's JSON is\n * untyped, and a quoted \"true\" read as ops would move a task out of its\n * worktree. Anything else is a change task, stored absent so saved plans need\n * no migration. Whether the task may be ops at all — a subtask may not — is\n * for whoever knows where it sits: the planner intake refuses it (`checkPlanInvariants`).\n */\nexport function opsFlag(value: unknown, type: Task['type'] = 'ai'): true | undefined {\n return value === true && type === 'ai' ? true : undefined;\n}\n\n/**\n * The flag of a task a merge or a split makes from others: what the edit\n * chose, else ops only when every task it came from was. A merge that takes\n * in a change edits files, so it is a change; a split part runs where its\n * task did.\n */\nexport function inheritedOps(from: readonly Pick<Task, 'ops'>[], chosen?: boolean): boolean {\n return chosen ?? from.every((t) => opsFlag(t.ops) === true);\n}\n\n/**\n * The runner set a plan has until one is chosen. The one runner named outside\n * its declaration on purpose: a default is a product choice that no manifest\n * or connector implies.\n */\nexport const DEFAULT_RUNNERS: readonly RunnerId[] = ['claude-code'];\n\nexport function createEmptyPlan(): LegacyPlanState {\n return {\n tasks: [],\n generatedAt: new Date().toISOString(),\n status: 'draft',\n runners: [...DEFAULT_RUNNERS],\n lastUpdated: new Date().toISOString(),\n };\n}\n\nexport function flattenTasks(tasks: readonly Task[]): Task[] {\n return [...walkPlan(tasks)].map(({ task }) => task);\n}\n\n/**\n * A phase-tagged plan rejoined into its tree. The execution log is flat — a\n * finished subtask is logged on its own as well as inside its parent — and in\n * the order tasks finished, so read as a list it puts subtasks at the top level\n * under orders that clash with real top-level tasks. A task's own entry is the\n * latest word on it: its parent's copy was taken when the parent last changed.\n */\nexport function rejoinPlanTree<T extends { id: string; order: number; subtasks?: T[] }>(pending: readonly T[], log: readonly T[]): T[] {\n const latest = new Map<string, T>();\n for (const task of [...pending, ...log]) latest.set(task.id, task);\n const nested = new Set<string>();\n const markNested = (task: T): void => {\n for (const sub of task.subtasks ?? []) {\n nested.add(sub.id);\n markNested(sub);\n }\n };\n for (const task of latest.values()) markNested(task);\n const rebuild = (task: T): T => {\n const own = latest.get(task.id) ?? task;\n return own.subtasks?.length ? { ...own, subtasks: own.subtasks.map(rebuild) } : own;\n };\n return [...latest.values()].filter((task) => !nested.has(task.id)).map(rebuild).sort((a, b) => a.order - b.order);\n}\n\n/**\n * A flattened task with the parent it hangs under, null for a top-level task,\n * and the path label and reference (`#2.1.3`) `TaskRefs` renders for it.\n */\nexport interface TaskWithParent {\n task: Task;\n parent: Task | null;\n label: string;\n ref: string;\n}\n\nexport function flattenTasksWithParents(tasks: readonly Task[]): TaskWithParent[] {\n return [...walkPlan(tasks)].map(({ task, parent, label }) => ({ task, parent: parent ?? null, label, ref: `#${label}` }));\n}\n\nexport function addTaskToPlan(tasks: readonly Task[], partial: Partial<Task>): Task[] {\n const maxOrder = tasks.reduce((max, t) => Math.max(max, t.order), 0);\n const newTask = createTask({\n ...partial,\n order: partial.order ?? maxOrder + 1,\n status: 'pending',\n });\n return renumberTasks([...tasks, newTask]);\n}\n\nexport function removeTaskFromPlan(tasks: readonly Task[], taskId: string): Task[] {\n const removed = subtreeIds(tasks, taskId);\n const prune = (level: readonly Task[]): Task[] => level\n .filter((t) => !removed.has(t.id))\n .map((t) => ({\n ...t,\n dependencies: t.dependencies.filter((depId) => !removed.has(depId)),\n subtasks: prune(t.subtasks),\n }));\n return renumberTasks(prune(tasks));\n}\n\nexport function updateTaskInPlan(tasks: readonly Task[], taskId: string, changes: Partial<Task>): Task[] {\n return renumberTasks(\n tasks.map((t) => {\n if (t.id === taskId) return { ...t, ...changes, id: t.id };\n return { ...t, subtasks: updateTaskInPlan(t.subtasks, taskId, changes) };\n })\n );\n}\n\nexport function renumberTasks(tasks: readonly Task[]): Task[] {\n return tasks.map((t, i) => ({\n ...t,\n order: i + 1,\n subtasks: renumberTasks(t.subtasks),\n }));\n}\n\n/** A task paused on its runner's usage limit, whether it resumes on its own or waits for the user (#84). */\nexport function pausedOnLimit(task: Readonly<Task>): boolean {\n return task.status === 'awaiting_user' && task.awaitingReason === 'usage-limit';\n}\n\n/** A task paused on a usage limit that resumes on its own when the limit resets (#84). */\nexport function waitsForReset(task: Readonly<Task>): boolean {\n return pausedOnLimit(task) && !!task.resumeAt;\n}\n\n/** The user's approval record, as a plan carries it (ADR-0029). */\nexport type ApprovalRecord = Pick<LegacyPlanState, 'approved' | 'approvedTasks'>;\n\n/** A copy of the plan's approval record, for a plan read whole; a field the plan lacks stays absent. */\nexport function approvalRecordOf(plan: ApprovalRecord): ApprovalRecord {\n return {\n ...(plan.approved !== undefined ? { approved: plan.approved } : {}),\n ...(plan.approvedTasks ? { approvedTasks: [...plan.approvedTasks] } : {}),\n };\n}\n\n/**\n * A task the scheduler leaves for the user (ADR-0035): an AI task not started\n * yet, in a plan they approved, outside their approval record — the planner\n * added it since. It starts once they start it or execute the plan. In a plan\n * they never approved nothing is theirs yet, so nothing is singled out.\n */\nexport function waitsForApproval(task: Pick<Task, 'id' | 'type' | 'status'>, record: ApprovalRecord): boolean {\n return record.approved === true\n && task.type === 'ai'\n && (task.status === 'pending' || task.status === 'approved')\n && !(record.approvedTasks ?? []).includes(task.id);\n}\n\n/**\n * Who put each task of an adopted list in the plan. A task the plan already\n * had keeps its origin, whatever the planner wrote; a new one is the\n * supervisor's when `bySupervisor`, and nobody's in particular otherwise, so\n * no planner can mark its own task as the supervisor's. `added` lists the new\n * tasks, subtasks included.\n */\nexport function stampOrigin(before: readonly Task[], after: readonly Task[], bySupervisor: boolean): { tasks: Task[]; added: string[] } {\n const prior = new Map(flattenTasks([...before]).map((t) => [t.id, t]));\n const added: string[] = [];\n const stamp = (tasks: readonly Task[]): Task[] => tasks.map((t) => {\n const was = prior.get(t.id);\n const origin = was ? was.addedBy : bySupervisor ? 'supervisor' : undefined;\n if (!was) added.push(t.id);\n const stamped: Task = { ...t, subtasks: stamp(t.subtasks ?? []) };\n if (origin) stamped.addedBy = origin;\n else delete stamped.addedBy;\n return stamped;\n });\n return { tasks: stamp(after), added };\n}\n\n/** Done, running, or held for the user: execution state only a runner or the user can change. */\nconst SETTLED_STATUSES: ReadonlySet<TaskStatus> = new Set<TaskStatus>(['completed', 'in_progress', 'awaiting_user']);\n\nfunction holdsSettledWork(task: Task): boolean {\n return SETTLED_STATUSES.has(task.status) || (task.subtasks ?? []).some(holdsSettledWork);\n}\n\n/**\n * Lay a planner-written task list over the plan it rewrites without letting it\n * change execution state. A planner restates tasks; it never witnessed one run,\n * so the status it writes is not evidence. A settled task is kept exactly as\n * it stands wherever the rewrite names it, and put back beside its old\n * neighbour where the rewrite leaves it out. Every other task keeps the status\n * it had; a task the rewrite adds starts pending.\n */\nexport function keepExecutionState(current: readonly Task[], rewrite: Task[]): Task[] {\n const existing = new Map(flattenTasks(current).map((t) => [t.id, t]));\n const named = new Set(flattenTasks(rewrite).map((t) => t.id));\n\n const overlay = (tasks: Task[], was: readonly Task[]): Task[] => {\n const result = tasks.map((t): Task => {\n const prior = existing.get(t.id);\n if (prior && SETTLED_STATUSES.has(prior.status)) return { ...prior };\n return {\n ...t,\n status: prior?.status ?? 'pending',\n skipReason: prior?.skipReason,\n verdict: prior?.verdict,\n outputSummary: prior?.outputSummary,\n runnerSessionId: prior?.runnerSessionId,\n runnerSessionOwner: prior?.runnerSessionOwner,\n carriedFrom: prior?.carriedFrom,\n attemptSkills: prior?.attemptSkills,\n attemptUsage: prior?.attemptUsage,\n // Where a task that has run ran is fixed, like its runner session (ADR-0020).\n ...(prior?.status === 'failed' ? { ops: prior.ops } : {}),\n forcedPastGate: prior?.forcedPastGate,\n subtasks: overlay(t.subtasks ?? [], prior?.subtasks ?? []),\n };\n });\n was.forEach((left, i) => {\n if (named.has(left.id) || !holdsSettledWork(left)) return;\n const neighbour = was.slice(0, i).reverse().find((t) => result.some((r) => r.id === t.id));\n const at = neighbour ? result.findIndex((r) => r.id === neighbour.id) + 1 : 0;\n result.splice(at, 0, { ...left });\n });\n return result;\n };\n\n return renumberTasks(overlay(rewrite, current));\n}\n","/** A name a skill can have, lower-cased: the one pattern for a `/name` token and for a skill folder. */\nexport const SKILL_NAME_PATTERN = '[a-z0-9][a-z0-9_-]*';\n\n/**\n * A `/skill-name` token anywhere in a message: whitespace (or string start)\n * before it, a skill name in any case, optional trailing punctuation that\n * isn't part of the name, then whitespace (or string end). The punctuation\n * group is what lets \"/grilling,\" name \"grilling\".\n */\nconst SKILL_TOKEN = new RegExp(`(^|\\\\s)\\\\/(${SKILL_NAME_PATTERN})([,.!?;:]*)(?=\\\\s|$)`, 'gi');\n\n/** Where a skill name is invoked: `start` is its `/`, `end` is just past the name, before any punctuation. */\nexport interface SkillToken {\n name: string;\n start: number;\n end: number;\n}\n\n/**\n * The first `/name` token for each name in `text`, names lower-cased. Only\n * the first counts because a skill loads once per message however often it is\n * named, so a later repeat is plain text.\n */\nexport function skillTokens(text: string): SkillToken[] {\n const tokens: SkillToken[] = [];\n const seen = new Set<string>();\n for (const match of text.matchAll(SKILL_TOKEN)) {\n const name = match[2].toLowerCase();\n if (seen.has(name)) continue;\n seen.add(name);\n const start = match.index + match[1].length;\n tokens.push({ name, start, end: start + 1 + match[2].length });\n }\n return tokens;\n}\n\n/** The tokens in a sent message that loaded one of `loaded` — what a surface highlights in the user's bubble. */\nexport function loadedSkillTokens(text: string, loaded: readonly string[]): SkillToken[] {\n return skillTokens(text).filter((t) => loaded.includes(t.name));\n}\n","/** Plan placement and dependency rules, shared with browser-bundled surfaces. */\nexport interface TreeTask<T> {\n id: string;\n order: number;\n title: string;\n subtasks?: readonly T[];\n}\n\nexport interface DependencyTask<T> extends TreeTask<T> {\n dependencies: readonly string[];\n}\n\nexport interface TaskPlacement<T> {\n task: T;\n parent?: T;\n ancestors: readonly T[];\n siblings: readonly T[];\n label: string;\n}\n\nexport function* walkPlan<T extends TreeTask<T>>(tasks: readonly T[]): Generator<TaskPlacement<T>> {\n function* walk(level: readonly T[], ancestors: readonly T[], parentLabel?: string): Generator<TaskPlacement<T>> {\n for (const task of level) {\n const label = parentLabel ? `${parentLabel}.${task.order}` : String(task.order);\n yield { task, parent: ancestors.at(-1), ancestors, siblings: level, label };\n yield* walk(task.subtasks ?? [], [...ancestors, task], label);\n }\n }\n yield* walk(tasks, []);\n}\n\nexport function taskPlacement<T extends TreeTask<T>>(tasks: readonly T[], id: string): TaskPlacement<T> | undefined {\n for (const row of walkPlan(tasks)) if (row.task.id === id) return row;\n return undefined;\n}\n\n/** Includes the root: deletion has to detach every edge into the removed tree. */\nexport function subtreeIds<T extends TreeTask<T>>(tasks: readonly T[], id: string): Set<string> {\n const found = taskPlacement(tasks, id);\n return new Set(found ? [...walkPlan([found.task])].map(({ task }) => task.id) : []);\n}\n\n/** Pickers offer earlier siblings; API validity is independent of display order. */\nexport function dependencyCandidates<T extends TreeTask<T>>(tasks: readonly T[], taskId?: string): T[] {\n if (!taskId) return [...tasks];\n const found = taskPlacement(tasks, taskId);\n return found ? found.siblings.filter((t) => t.order < found.task.order) : [];\n}\n\n/** The surviving tasks whose dependency lists a subtree removal will change. */\nexport function dependentsOf<T extends DependencyTask<T>>(tasks: readonly T[], taskId: string): T[] {\n const removed = subtreeIds(tasks, taskId);\n return [...walkPlan(tasks)].map(({ task }) => task)\n .filter((t) => !removed.has(t.id) && t.dependencies.some((id) => removed.has(id)));\n}\n\nexport interface DependencyCheck { ok: boolean; error?: string }\n\n/** Target restrictions, separate from cycles so a planner batch can check its final graph. */\nexport function dependencyTargetErrors<T extends TreeTask<T>>(\n tasks: readonly T[], taskId: string, dependencies: readonly string[],\n): string[] {\n const found = taskPlacement(tasks, taskId);\n if (!found) return ['Task not found'];\n const descendants = subtreeIds(tasks, taskId);\n const errors: string[] = [];\n for (const dep of dependencies) {\n if (dep === taskId) { errors.push(`\"${found.task.title}\" cannot depend on itself`); continue; }\n const target = taskPlacement(tasks, dep)?.task;\n if (!target) { errors.push(`Unknown dependency \"${dep}\"`); continue; }\n if (found.ancestors.some((t) => t.id === dep) || descendants.has(dep)) {\n errors.push(`\"${found.task.title}\" cannot depend on \"${target.title}\", its ancestor or descendant`);\n }\n }\n return errors;\n}\n\n/** The tasks on one dependency cycle, in dependency order, or null. */\nexport function findDependencyCycle<T extends DependencyTask<T>>(tasks: readonly T[]): T[] | null {\n const byId = new Map([...walkPlan(tasks)].map(({ task }) => [task.id, task]));\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 id of byId.keys()) {\n const hit = visit(id);\n if (hit) return hit.map((key) => byId.get(key)!);\n }\n return null;\n}\n\nexport function canSetDependencies<T extends DependencyTask<T>>(\n tasks: readonly T[], taskId: string, dependencies: readonly string[],\n): DependencyCheck {\n const [error] = dependencyTargetErrors(tasks, taskId, dependencies);\n if (error) return { ok: false, error };\n const replace = (level: readonly T[]): T[] => level.map((t) => ({\n ...t,\n dependencies: t.id === taskId ? dependencies : t.dependencies,\n subtasks: replace(t.subtasks ?? []),\n }));\n const cycle = findDependencyCycle(replace(tasks));\n return cycle\n ? { ok: false, error: `Dependency cycle detected: ${[...cycle, cycle[0]].map((t) => `\"${t.title}\"`).join(' → ')}` }\n : { ok: true };\n}\n","/** 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 { dependencyTargetErrors, findDependencyCycle } from './PlanTree';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Task, RunnerId, TaskModelAssignment, DEFAULT_RUNNERS, createTask, flattenTasksWithParents, inheritedSkills, skillNames } from '../models/Task';\nimport { taggedReadOnlyMode, type RunnerModeInfo } from './ModeResolver';\nimport { resolveAssignment, type AssignmentAdjustment, type AssignmentCatalog } from './TaskAssignment';\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 assignments are resolved against the catalog as it is planned,\n * and a model it names that the runner does not offer is refused; a\n * submitted plan's models are resolved as it lands instead (ADR-0003).\n */\n models?: AssignmentCatalog;\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 /** Where a made-up id comes from: a new one each time, unless the caller keeps them. */\n newId?: () => string;\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 /** What the tree's assignments were adjusted to beyond what the planner wrote, for the planner to be told. */\n adjustments: AssignmentAdjustment[];\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 assignment — model, effort, and the mode decided from read-only or\n * not, never named (ADR-0031) — is the resolver's (`resolveAssignment`),\n * as it is for every writer: a model a merge or split carries is read as\n * what the task was made from, so a part moved to another runner gets that\n * runner's;\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, newId = uuidv4 } = at;\n const base = from?.fields ?? {};\n const errors: PlanTaskError[] = [];\n const ignored: PlanTaskError[] = [];\n const renamed = new Map<string, string>();\n const adjustments: AssignmentAdjustment[] = [];\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 ? newId() : 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 const assignedModel = ai ? modelOf(raw, base.assignedModel) : undefined;\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, newId });\n subtasks.push(child.task);\n errors.push(...child.errors);\n ignored.push(...child.ignored);\n adjustments.push(...child.adjustments);\n for (const [was, now] of child.renamed) renamed.set(was, now);\n }\n }\n\n const fields = {\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 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 let taskMode = base.taskMode;\n let landedModel = assignedModel;\n if (ai && catalog.runners.includes(assignedRunner)) {\n const resolved = resolveAssignment({\n writer: catalog.models ? 'edit' : 'plan',\n task: { ...createTask(fields), taskMode },\n from: from ? { assignedRunner: base.assignedRunner ?? assignedRunner, assignedModel: base.assignedModel, taskMode: base.taskMode } : undefined,\n readOnly,\n catalog: { modelsByRunner: {}, ...catalog.models, runnerModes: catalog.runnerModes, autonomousDefault: catalog.autonomousDefault },\n });\n if (!resolved.ok) {\n // The planner names no mode: one it cannot have is about the read-only answer it gave (ADR-0031).\n errors.push({ taskId: id, field: resolved.field === 'taskMode' ? 'readOnly' : resolved.field, message: `${where}: ${resolved.error}` });\n } else {\n taskMode = resolved.task.taskMode;\n landedModel = resolved.task.assignedModel;\n adjustments.push(...resolved.adjustments);\n }\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 task = createTask({ ...fields, assignedModel: landedModel, taskMode });\n if (Array.isArray(raw.skills)) skillsAsked.set(task, raw.skills);\n return { task, errors, ignored, renamed, adjustments };\n}\n\n/**\n * The rules a plan's tasks are held to together, subtasks included, whichever\n * route they arrived by and whoever wrote them, the planner or the user.\n * `runners` are the ones the writer may put a task on (`assignableRunners`),\n * any when undefined; with `runnerModes`, an ops task is held off its\n * runner's read-only mode.\n */\nexport function checkPlanInvariants(\n tasks: readonly Task[],\n runners: readonly RunnerId[] | undefined,\n runnerModes?: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>,\n): 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.ops && t.taskMode !== undefined && t.taskMode === taggedReadOnlyMode(runnerModes?.[t.assignedRunner])) {\n fail('taskMode', `${where} is an ops task, which runs commands, so it cannot run in its runner's read-only mode`);\n }\n if (runners && t.type !== 'user' && !runners.includes(t.assignedRunner)) {\n fail('assignedRunner', `${where} has invalid assignedRunner \"${t.assignedRunner}\". Expected one of: ${runners.join(', ')}`);\n }\n }\n for (const { task: t, parent } of rows) {\n // Self-dependencies are reported with the cycle below, including its full path.\n for (const error of dependencyTargetErrors(tasks, t.id, t.dependencies.filter((id) => id !== t.id))) {\n errors.push({ taskId: t.id, field: 'dependencies', message: `${taskWhere(t.title, parent?.title)}: ${error}` });\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 return findDependencyCycle(tasks);\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 { 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 { DiscoveredModel, RunnerId } 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 `resolveAssignment` makes);\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 * The one rule for an effort and a model: the effort a task keeps on a model,\n * whether it was written with it or carried from the model before. Kept where\n * the model offers it; otherwise mapped to the nearest rung the model\n * supports (so a Claude-style \"xhigh\" on a low/medium/high model becomes\n * \"high\"); undefined — the runner's own default — when no mapping exists or\n * the model offers no levels, which is how a model discovery does not list\n * reads (`variants` empty). Core's resolver and every surface's model picker\n * call it, so a model change lands on the same effort wherever it is made.\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","import type { DiscoveredModel, RunnerId, Task, TaskModelAssignment } from '../models/Task';\nimport { clampThinkingEffort, effectiveAllowlist } from './ModelAllowlistResolver';\nimport { plannedMode, readOnlyMode, readOnlyRunners, taggedReadOnlyMode, unavailableModeReason, type RunnerModeInfo } from './ModeResolver';\n\n/**\n * Who writes a task's assignment. It decides what a model the writer names\n * meets where the task's runner does not offer it or the allowlist excludes it\n * (ADR-0003), which runners it may put a task on, and whether a live runner's\n * hold applies (ADR-0041):\n *\n * - `plan`: a whole plan the planner submitted, and every planner task as it\n * lands. A stray model is replaced, and the planner is told what moved.\n * - `edit`: the planner's edit. A stray model is refused, which the planner\n * answers in the same reply.\n * - `user`: a user's direct edit. A stray model is refused, with the reason.\n * - `userAdd`: a task the user adds. A stray model is replaced, and the user\n * is told what moved.\n * - `runner`: a switch made with the runner's own `/model`, written as the\n * user's edit. The runner already runs it, so neither the allowlist nor the\n * live runner's hold applies.\n */\nexport type AssignmentWriter = 'plan' | 'edit' | 'user' | 'userAdd' | 'runner';\n\n/**\n * What an assignment is resolved against: the discovered models, the manifest\n * modes and the allowlist, as the session holds them when the write lands —\n * the same catalog the planner's per-turn block shows, so nothing is refused\n * that the planner was never told about.\n */\nexport interface AssignmentCatalog {\n /** A runner absent, or listed with nothing, has models discovery could not find, not none. */\n modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>;\n /** Absent when no runner's modes are known: no mode is then derived. */\n runnerModes?: Partial<Record<RunnerId, readonly RunnerModeInfo[]>>;\n /** Raw (unfiltered) allowlist, keyed by runner; read through {@link effectiveAllowlist}. */\n perRunnerAllowlist?: Partial<Record<RunnerId, string[]>>;\n /** Full (true) or Guarded: the mode a derived task lands on unless it is read-only (ADR-0001). Full when absent. */\n autonomousDefault?: boolean;\n /**\n * Whether a task's runner is live: an attempt of it is claimed, running,\n * quiet or waiting on someone. Its model, effort and runner are then that\n * runner's (ADR-0041): the process was started on them, and only the\n * runner's own command moves them. Absent means none is.\n */\n liveRunner?: (taskId: string) => boolean;\n}\n\n/** What a runner offers a task, as discovery and its manifest have it. */\nexport interface RunnerCatalog {\n models: DiscoveredModel[];\n modes: RunnerModeInfo[];\n}\n\n/** A change the resolver made to what was written, named so it is never silent (ADR-0001). */\nexport interface AssignmentAdjustment {\n taskId: string;\n title: string;\n field: 'assignedModel' | 'thinkingEffort';\n from: string | null;\n to: string | null;\n}\n\nexport interface AssignmentWrite {\n writer: AssignmentWriter;\n /** The task as written: the plan's task with the edit laid over it, or a new task. Its subtasks are not read. */\n task: Task;\n /** The task as the plan holds it; absent for a new task. */\n before?: Task;\n /**\n * What a new task is made from — the task a split part or merge starts\n * as. Its runner and model are read as `before`'s are: a part moved to\n * another runner carries a model chosen for the old one.\n */\n from?: Pick<Task, 'assignedRunner' | 'assignedModel' | 'taskMode'>;\n /** The planner's answer to read-only or not (ADR-0001). Absent, the task stays as it was. */\n readOnly?: boolean;\n catalog: AssignmentCatalog;\n}\n\nexport type AssignmentOutcome =\n | { ok: true; task: Task; adjustments: AssignmentAdjustment[] }\n | { ok: false; field: 'assignedRunner' | 'assignedModel' | 'taskMode'; error: string };\n\n/**\n * Why a task's model or effort cannot change while its runner is live, for\n * every writer but the runner itself. The plan follows a switch made with the\n * runner's own command instead (ADR-0041), so that is offered beside stopping it.\n */\nexport function liveModelRefusal(task: Pick<Task, 'title'>): string {\n return `\"${task.title}\" has a live runner, so its model and effort cannot change now: stop the task first, or use the runner's own /model`;\n}\n\n/**\n * Why a task cannot move to another runner while its runner is live: the move\n * re-derives the model from the new runner, under the same hold as\n * {@link liveModelRefusal}, and no runner's own command can make it.\n */\nexport function liveRunnerRefusal(task: Pick<Task, 'title'>): string {\n return `\"${task.title}\" has a live runner, so its runner cannot change now: stop the task first`;\n}\n\n/**\n * The runners a writer may put a task on (ADR-0003, V1): a submitted plan the\n * runners enabled now; a planner edit the plan's own as well, so an edit is\n * checked as it is made and as it lands against one set; a user any runner.\n * Undefined means any.\n */\nexport function assignableRunners(writer: AssignmentWriter, enabled: readonly RunnerId[], planRunners: readonly RunnerId[]): RunnerId[] | undefined {\n if (writer === 'plan') return [...enabled];\n if (writer === 'edit') return [...new Set([...planRunners, ...enabled])];\n return undefined;\n}\n\nfunction sameAssignment(a: TaskModelAssignment | undefined, b: TaskModelAssignment | undefined): boolean {\n return a?.modelId === b?.modelId && a?.thinkingEffort === b?.thinkingEffort;\n}\n\n/** Whether `mode` is the mode `runner`'s manifest tags read-only. */\nfunction readOnlyOn(mode: string | undefined, runner: RunnerId, catalog: AssignmentCatalog): boolean {\n return mode !== undefined && mode === taggedReadOnlyMode(catalog.runnerModes?.[runner]);\n}\n\n/**\n * The task's mode as written. The writer's own pick stands where the runner\n * offers it; otherwise a task the writer creates, moves, turns into an AI task\n * or says read-only or not of gets the mode the planner path gives it\n * (ADR-0001's fill-in table): its read-only mode, else the mode it has when\n * the runner offers it, else the runner's mode for the level. Silence keeps\n * what is, so a mode the user picked by hand survives every other edit.\n */\nfunction modeFor(write: AssignmentWrite, task: Task, moved: boolean): { ok: true; mode: string | undefined } | { ok: false; error: string } {\n const { writer, before, catalog } = write;\n const prior = before ?? write.from;\n const runner = task.assignedRunner;\n const modes = catalog.runnerModes?.[runner];\n const picked = (writer === 'user' || writer === 'userAdd') && task.taskMode !== undefined && task.taskMode !== prior?.taskMode;\n if (picked) {\n if (modes && modes.length > 0 && !modes.some((m) => m.id === task.taskMode)) {\n return { ok: false, error: `Runner \"${runner}\" has no task mode \"${task.taskMode}\": valid modes: ${modes.map((m) => m.id).join(', ')}` };\n }\n const unavailable = unavailableModeReason(modes, task.taskMode);\n if (unavailable) return { ok: false, error: `Mode \"${task.taskMode}\" of runner \"${runner}\" is unavailable on this host: ${unavailable}` };\n return { ok: true, mode: task.taskMode };\n }\n const becameAi = before?.type === 'user';\n if (writer === 'runner' || !catalog.runnerModes || (before && !moved && !becameAi && write.readOnly === undefined)) return { ok: true, mode: task.taskMode };\n // A move carries the old runner's mode id, which says read-only only on that runner.\n const carried = prior !== undefined && task.taskMode === prior.taskMode && readOnlyOn(prior.taskMode, prior.assignedRunner, catalog);\n const readOnly = write.readOnly ?? (carried || readOnlyOn(task.taskMode, runner, catalog));\n if (readOnly && !readOnlyMode(modes) && writer !== 'plan' && writer !== 'edit') {\n const reason = unavailableModeReason(modes, taggedReadOnlyMode(modes));\n const able = readOnlyRunners(catalog.runnerModes).filter((r) => r !== runner);\n return {\n ok: false,\n error: `\"${task.title}\" is read-only, and runner \"${runner}\" has no read-only mode${reason ? ' available on this host' : ''}: give it another mode first${able.length > 0 ? `, or move it to one of: ${able.join(', ')}` : ''}${reason ? `. ${reason}` : ''}`,\n };\n }\n const mode = plannedMode(runner, catalog.runnerModes, { readOnly, current: task.taskMode }, catalog.autonomousDefault ?? true);\n return mode.ok ? { ok: true, mode: mode.mode ?? task.taskMode } : mode;\n}\n\n/**\n * Resolve what one task runs on — its runner, model, thinking effort and mode\n * — for whichever writer wrote it. Every writer calls this, so they cannot\n * disagree. Returns the task with what it resolved, and each change made to\n * what was written, named; or a refusal.\n *\n * - **The live runner's hold.** While its runner is live a task's runner,\n * model and effort are that runner's (ADR-0041): any other writer is\n * refused a change, a model cleared as much as one replaced.\n * - **A model is its runner's.** A model the writer left alone stays, even off\n * the allowlist: the allowlist shapes what is written, not a model the user\n * switched to (ADR-0043). A model the task carries from another runner — a\n * move, or a part made from a task on another one — stays only where its new\n * runner lists it, or nothing is known against it, and the allowlist admits\n * it. A model the writer names is held to the runner and the allowlist as\n * its {@link AssignmentWriter} row says: the runner already runs it\n * (`runner`), or it is refused (`edit`, `user`), or replaced (`plan`,\n * `userAdd`) when only another runner lists it (it provably cannot run here)\n * or the allowlist excludes it. A model listed by no runner is not one any\n * runner disowns, so it stands, and the runner validates last.\n * - **Filling a model.** A missing, carried-off or replaced model is refilled\n * from the runner: the allowlist's first model the runner lists, else the\n * allowlist's first, else the runner's own first, else none, which the\n * runner fills with its own default.\n * - **Effort follows its model.** Whenever the model is listed, its effort is\n * held to what it offers ({@link clampThinkingEffort}, the one mapping rule:\n * the nearest level the model has, none when it has none); a model put in\n * here that discovery does not list gets none. A model discovery does not\n * list keeps the effort written with it, which the runner validates.\n * - **The mode** is the writer's pick where it has one, else derived as\n * {@link modeFor} says.\n *\n * Which runners a writer may use is the plan's rule ({@link assignableRunners},\n * `checkPlanInvariants`); this never moves a task to another runner.\n */\nexport function resolveAssignment(write: AssignmentWrite): AssignmentOutcome {\n const { writer, before, catalog } = write;\n const written = write.task;\n const prior = before ?? write.from;\n const moved = prior !== undefined && prior.assignedRunner !== written.assignedRunner;\n const title = (before ?? written).title;\n\n const live = writer !== 'runner' && !!before && !!catalog.liveRunner?.(before.id);\n if (live && moved) return { ok: false, field: 'assignedRunner', error: liveRunnerRefusal({ title }) };\n if (live && !sameAssignment(before?.assignedModel, written.assignedModel)) return { ok: false, field: 'assignedModel', error: liveModelRefusal({ title }) };\n if (written.type === 'user') return { ok: true, task: written, adjustments: [] };\n\n const mode = modeFor(write, written, moved);\n if (!mode.ok) return { ok: false, field: 'taskMode', error: mode.error };\n\n // A live runner holds its model and effort as they are, even where its model's levels have changed since.\n const model: ModelResult = live ? { ok: true, assignment: written.assignedModel } : modelFor(write, moved);\n if (!model.ok) return { ok: false, field: 'assignedModel', error: model.error };\n\n const task = model.assignment === written.assignedModel && mode.mode === written.taskMode\n ? written\n : { ...written, assignedModel: model.assignment, taskMode: mode.mode };\n const adjustments: AssignmentAdjustment[] = [];\n const note = (field: AssignmentAdjustment['field'], from: string | undefined, to: string | undefined) => {\n if ((from ?? null) !== (to ?? null)) adjustments.push({ taskId: written.id, title: written.title, field, from: from ?? null, to: to ?? null });\n };\n note('assignedModel', written.assignedModel?.modelId, task.assignedModel?.modelId);\n note('thinkingEffort', written.assignedModel?.thinkingEffort, task.assignedModel?.thinkingEffort);\n return { ok: true, task, adjustments };\n}\n\ntype ModelResult = { ok: true; assignment: TaskModelAssignment | undefined } | { ok: false; error: string };\n\n/** The model half of {@link resolveAssignment}. */\nfunction modelFor(write: AssignmentWrite, moved: boolean): ModelResult {\n const { writer, before, catalog } = write;\n const prior = before ?? write.from;\n const written = write.task.assignedModel;\n const runner = write.task.assignedRunner;\n const models = catalog.modelsByRunner;\n const own = models[runner] ?? [];\n const listing = (id: string) => own.find((m) => m.modelId === id);\n // A model the task ran with on another runner is evidence that runner lists it, which the allowlist is read with (R1).\n const carriedFrom = moved && written && prior?.assignedModel?.modelId === written.modelId ? prior.assignedRunner : undefined;\n const seen = carriedFrom && written && !models[carriedFrom]?.some((m) => m.modelId === written.modelId)\n ? { ...models, [carriedFrom]: [...(models[carriedFrom] ?? []), { modelId: written.modelId, modelLabel: written.modelLabel, variants: [] }] }\n : models;\n const allowed = effectiveAllowlist(catalog.perRunnerAllowlist?.[runner], runner, seen);\n const allows = (id: string) => !allowed || allowed.includes(id);\n\n // Held to what its model offers, unless that model is one discovery does not list.\n const settled = (assignment: TaskModelAssignment): TaskModelAssignment => {\n const known = listing(assignment.modelId);\n if (!known) return assignment;\n const thinkingEffort = clampThinkingEffort(assignment.thinkingEffort, known.variants);\n const availableVariants = known.variants.map((v) => v.id);\n if (thinkingEffort === assignment.thinkingEffort && assignment.availableVariants?.join() === availableVariants.join()) return assignment;\n const next: TaskModelAssignment = { ...assignment, availableVariants };\n if (thinkingEffort === undefined) delete next.thinkingEffort;\n else next.thinkingEffort = thinkingEffort;\n return next;\n };\n // The allowlist's order is its priority, among the models the runner lists\n // first; an allowlisted id discovery does not list still beats one the user excluded (R1).\n const refill = (effort: string | undefined): TaskModelAssignment | undefined => {\n const id = allowed?.find((a) => listing(a)) ?? allowed?.[0] ?? own[0]?.modelId;\n if (id === undefined) return undefined;\n const known = listing(id);\n const thinkingEffort = clampThinkingEffort(effort, known?.variants ?? []);\n return {\n modelId: id,\n modelLabel: known?.modelLabel ?? id,\n ...(thinkingEffort !== undefined ? { thinkingEffort } : {}),\n ...(known ? { availableVariants: known.variants.map((v) => v.id) } : {}),\n };\n };\n\n if (!written) return { ok: true, assignment: writer === 'runner' ? undefined : refill(undefined) };\n if (writer === 'runner') return { ok: true, assignment: settled(written) };\n\n const id = written.modelId;\n const same = prior?.assignedModel?.modelId === id;\n if (before && same && !moved) return { ok: true, assignment: settled(written) };\n\n const listedElsewhere = Object.entries(models).some(([other, list]) => other !== runner && list?.some((m) => m.modelId === id));\n if (same) {\n // Carried from a task on another runner, it was chosen for that one: it\n // needs this one's listing, or nothing known against it.\n const fits = !moved || !!listing(id) || (own.length === 0 && !listedElsewhere);\n return { ok: true, assignment: fits && allows(id) ? settled(written) : refill(written.thinkingEffort) };\n }\n\n if (writer === 'edit' || writer === 'user') {\n // Allowlisted ids are kept even unlisted (ADR-0003, R1); an empty list is discovery that found nothing.\n const offered = own.length === 0 || !!listing(id) || !!allowed?.includes(id);\n if (!offered) return { ok: false, error: `Runner \"${runner}\" does not offer model \"${id}\"` };\n if (!allows(id)) return { ok: false, error: `Model \"${id}\" is excluded by the allowlist for runner \"${runner}\"` };\n return { ok: true, assignment: settled(written) };\n }\n // Only another runner lists it: it provably cannot run here.\n const disowned = !listing(id) && listedElsewhere;\n return { ok: true, assignment: disowned || !allows(id) ? refill(written.thinkingEffort) : settled(written) };\n}\n\n/**\n * The planner's tasks as they land, subtasks included: each resolved as the\n * planner's ({@link resolveAssignment}, `plan`) against the catalog read now\n * and against the plan's task of the same id, so a model or allowlist that\n * changed since the write was checked is applied and named. A task the\n * planner left alone keeps its model; one whose runner is live keeps all of\n * its assignment. Modes were decided when the task was written, and are not\n * revisited. Nothing is refused here: a write the catalog no longer allows is\n * kept as it was written, and the runner validates last.\n */\nexport function resolveAssignments(\n tasks: readonly Task[],\n planned: readonly Task[],\n catalog: AssignmentCatalog,\n): { tasks: Task[]; adjustments: AssignmentAdjustment[] } {\n const prior = new Map<string, Task>();\n const index = (list: readonly Task[]) => list.forEach((t) => { prior.set(t.id, t); index(t.subtasks ?? []); });\n index(planned);\n const models = { ...catalog, runnerModes: undefined };\n const adjustments: AssignmentAdjustment[] = [];\n const walk = (list: readonly Task[]): Task[] => list.map((task) => {\n const outcome = resolveAssignment({ writer: 'plan', task, before: prior.get(task.id), catalog: models });\n const resolved = outcome.ok ? outcome.task : task;\n if (outcome.ok) adjustments.push(...outcome.adjustments);\n return { ...resolved, subtasks: walk(task.subtasks ?? []) };\n });\n return { tasks: walk(tasks), adjustments };\n}\n","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, runnerModes),\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","import { stripModelNoise } from './JsonExtractor';\n\n/** A task as seen in a still-streaming plan: its title (possibly partial) and whether its object has closed. */\nexport interface PartialPlanTask {\n title: string;\n status: 'streaming' | 'complete';\n /** Assigned model label, once it has streamed in — for the live row's chip. */\n model?: string;\n /** Set once `\"readOnly\": true` has streamed in — for the live row's chip. */\n readOnly?: true;\n}\n\n/**\n * Parse a partial (still-streaming) plan-JSON string into an ordered list of task\n * rows for the live progress view. Pure and lenient: it walks the characters of the\n * `tasks` array, emitting one row per task object once it has opened, with the title\n * read incrementally and the status flipping to `complete` when the object's brace\n * closes. The currently-streaming object stays `streaming` even with a partial title.\n * The React view is a thin renderer over this — it never parses JSON itself.\n */\nexport function parsePartialPlan(partial: string): PartialPlanTask[] {\n const text = stripModelNoise(partial);\n const arrayStart = findTasksArrayStart(text);\n if (arrayStart === -1) return [];\n\n const tasks: PartialPlanTask[] = [];\n let depth = 0; // brace depth relative to a task object (0 = between objects)\n let objText = ''; // accumulated text of the in-progress task object\n let inString = false;\n let escaped = false;\n\n for (let i = arrayStart; i < text.length; i++) {\n const ch = text[i];\n if (depth > 0) objText += ch;\n\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 === ']' && depth === 0) break; // end of the tasks array\n if (ch === '{') {\n if (depth === 0) objText = '{';\n depth++;\n } else if (ch === '}') {\n depth--;\n if (depth === 0) {\n tasks.push(rowFromObject(objText, 'complete'));\n objText = '';\n }\n }\n }\n\n // A task object that opened but never closed is the row currently streaming in.\n if (depth > 0) tasks.push(rowFromObject(objText, 'streaming'));\n return tasks;\n}\n\n/** Index just after the `[` that opens the top-level `tasks` array, or -1 if not present yet. */\nfunction findTasksArrayStart(text: string): number {\n const key = /\"tasks\"\\s*:\\s*\\[/.exec(text);\n return key ? key.index + key[0].length : -1;\n}\n\n/** Build a live row from a (possibly unterminated) task-object fragment. */\nfunction rowFromObject(fullText: string, status: PartialPlanTask['status']): PartialPlanTask {\n const objText = withoutSubtasks(fullText);\n const model = stringField(objText, 'modelLabel');\n const readOnly = /\"readOnly\"\\s*:\\s*true/.test(objText);\n return {\n title: titleFromObject(objText),\n status,\n ...(model ? { model } : {}),\n ...(readOnly ? { readOnly: true as const } : {}),\n };\n}\n\n/**\n * The fragment with its `subtasks` array cut out, so a row's title and chips\n * are the task's own and never a subtask's. An unterminated array is cut to\n * the end.\n */\nfunction withoutSubtasks(objText: string): string {\n const key = /\"subtasks\"\\s*:\\s*\\[/.exec(objText);\n if (!key) return objText;\n let depth = 0;\n let inString = false;\n let escaped = false;\n for (let i = key.index + key[0].length - 1; i < objText.length; i++) {\n const ch = objText[i];\n if (inString) {\n if (escaped) escaped = false;\n else if (ch === '\\\\') escaped = true;\n else if (ch === '\"') inString = false;\n } else if (ch === '\"') inString = true;\n else if (ch === '[') depth++;\n else if (ch === ']' && --depth === 0) return objText.slice(0, key.index) + withoutSubtasks(objText.slice(i + 1));\n }\n return objText.slice(0, key.index);\n}\n\n/** Read the `title` value out of a (possibly unterminated) task-object fragment. */\nfunction titleFromObject(objText: string): string {\n const m = /\"title\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)/.exec(objText);\n if (!m) return '';\n // The captured group may include a trailing closing quote's content only up to an\n // unescaped quote; for a completed value that's the whole title, for a streaming\n // one it's whatever has arrived so far.\n return m[1].replace(/\\\\\"/g, '\"');\n}\n\n/** Read a fully-streamed (closing-quote present) string field's value, or undefined. */\nfunction stringField(objText: string, key: string): string | undefined {\n const m = new RegExp(`\"${key}\"\\\\s*:\\\\s*\"((?:[^\"\\\\\\\\]|\\\\\\\\.)*)\"`).exec(objText);\n return m ? m[1].replace(/\\\\\"/g, '\"') : undefined;\n}\n"],"mappings":"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","names":["uuidv4","text","import_uuid","reason","id","uuidv4","isRecord","text"]}