@ordewell/core 0.9.0 → 0.10.0

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 (63) hide show
  1. package/dist/{IFileSystem-DpaaFdit.d.ts → IFileSystem-BCJBHFwB.d.ts} +3 -1
  2. package/dist/{IFileSystem-_m7HdoxJ.d.mts → IFileSystem-Bj6fy1cG.d.mts} +3 -1
  3. package/dist/{IRunner-BiQio8gJ.d.ts → IRunner-C7_SSpzG.d.ts} +614 -77
  4. package/dist/{IRunner-CxiymkP_.d.mts → IRunner-hWAJC4hg.d.mts} +614 -77
  5. package/dist/ModeResolver-m1d8Cb-j.d.mts +35 -0
  6. package/dist/ModeResolver-u5ZhRC8x.d.ts +35 -0
  7. package/dist/{Task-1Q19y7sZ.d.mts → Task-CxwnCJLi.d.mts} +346 -127
  8. package/dist/{Task-1Q19y7sZ.d.ts → Task-CxwnCJLi.d.ts} +346 -127
  9. package/dist/{chunk-UUU46PVQ.mjs → chunk-2IIRSBEQ.mjs} +109 -142
  10. package/dist/chunk-2IIRSBEQ.mjs.map +1 -0
  11. package/dist/{chunk-QUARDKVT.mjs → chunk-CH5IGWFS.mjs} +546 -967
  12. package/dist/chunk-CH5IGWFS.mjs.map +1 -0
  13. package/dist/chunk-DJAPDO6E.mjs +1358 -0
  14. package/dist/chunk-DJAPDO6E.mjs.map +1 -0
  15. package/dist/chunk-DNFT4SQG.mjs +97 -0
  16. package/dist/chunk-DNFT4SQG.mjs.map +1 -0
  17. package/dist/index.d.mts +3204 -3926
  18. package/dist/index.d.ts +3204 -3926
  19. package/dist/index.js +22283 -16472
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +20220 -14690
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/parsing.d.mts +55 -3
  24. package/dist/parsing.d.ts +55 -3
  25. package/dist/parsing.js +311 -124
  26. package/dist/parsing.js.map +1 -1
  27. package/dist/parsing.mjs +86 -6
  28. package/dist/parsing.mjs.map +1 -1
  29. package/dist/plan-utils-B6yJdpgi.d.ts +2902 -0
  30. package/dist/plan-utils-CKXdD1AD.d.mts +2902 -0
  31. package/dist/plan-utils.d.mts +4 -5
  32. package/dist/plan-utils.d.ts +4 -5
  33. package/dist/plan-utils.js +471 -86
  34. package/dist/plan-utils.js.map +1 -1
  35. package/dist/plan-utils.mjs +68 -19
  36. package/dist/testing.d.mts +22 -4
  37. package/dist/testing.d.ts +22 -4
  38. package/dist/testing.js +46 -9
  39. package/dist/testing.js.map +1 -1
  40. package/dist/testing.mjs +47 -11
  41. package/dist/testing.mjs.map +1 -1
  42. package/package.json +2 -10
  43. package/skills/tdd/SKILL.md +0 -1
  44. package/dist/ModeResolver-DE-o2lRF.d.mts +0 -48
  45. package/dist/ModeResolver-EWlpmVAy.d.ts +0 -48
  46. package/dist/chunk-2M4X3VB7.mjs +0 -432
  47. package/dist/chunk-2M4X3VB7.mjs.map +0 -1
  48. package/dist/chunk-H5O74L77.mjs +0 -185
  49. package/dist/chunk-H5O74L77.mjs.map +0 -1
  50. package/dist/chunk-QUARDKVT.mjs.map +0 -1
  51. package/dist/chunk-UUU46PVQ.mjs.map +0 -1
  52. package/dist/chunk-V3JHS7AX.mjs +0 -27
  53. package/dist/chunk-V3JHS7AX.mjs.map +0 -1
  54. package/dist/order-labels.d.mts +0 -30
  55. package/dist/order-labels.d.ts +0 -30
  56. package/dist/order-labels.js +0 -54
  57. package/dist/order-labels.js.map +0 -1
  58. package/dist/order-labels.mjs +0 -13
  59. package/dist/order-labels.mjs.map +0 -1
  60. package/dist/parsing-CZdp2p_n.d.ts +0 -154
  61. package/dist/parsing-GgUJoYKs.d.mts +0 -154
  62. package/dist/plan-utils-6rUr7ZOx.d.ts +0 -1222
  63. package/dist/plan-utils-P3PIEwNa.d.mts +0 -1222
@@ -0,0 +1,2902 @@
1
+ import { $ as RunnerId, c as DiscoveredModel, ac as Task, a9 as SkillsService, a4 as SkillInfo, U as RepoGroupLayout, aj as TaskSkillSnapshot, ai as TaskSkillNotice, aa as SubagentOutcome, ao as UsageRecord, ak as TaskSnapshot, a as AwaitingReason, I as IWorktreeIsolation, v as IsolationRun, j as IsolationHandoff, H as PlanIsolation, w as IsolationTaskRecord, af as TaskIsolation, F as IsolationView, r as IsolationOutcome, T as RepairEvidence, am as TreeSnapshot, ad as TaskDiffRead, ae as TaskDiffResult, q as IsolationMergeResult, aq as Verdict, _ as RunSettings, L as LegacyPlanState, C as ConversationMessage, a6 as SkillLoadNotice, P as PendingPlanTurn, A as AttemptUsage, M as ModelUsage, ap as UsageTotals, N as PlanUsage, e as ExecutionRecord, X as ResearchStep, J as PlanState, ab as SupervisorLevel, Y as ResearchStepOutcome, V as ResearchLogEntry, O as PlannerUsage, ag as TaskIsolationState, an as UsageLine } from './Task-CxwnCJLi.js';
2
+ import { R as RunnerModeInfo } from './ModeResolver-u5ZhRC8x.js';
3
+ import { B as IRunnerSession, aB as TaskMessageFrom, ah as RunnerCommand, am as RunnerMcpServer, au as StructuredTurnEnd, j as ApprovalDecision, at as StructuredEvent, y as IConfig, z as IRunner, ap as RunnerRegistry, aa as QueuedTaskMessage, k as ApprovalKind, q as ApprovalSource } from './IRunner-C7_SSpzG.js';
4
+
5
+ interface LiveTailOptions {
6
+ maxLines: number;
7
+ /** A previous {@link LiveTail.nextOffset}: only output after it is rendered. */
8
+ sinceOffset?: number;
9
+ }
10
+ interface LiveTail {
11
+ /** Plain text, escapes and control characters removed. */
12
+ text: string;
13
+ /** Total output received so far; pass it back as `sinceOffset` to read only what follows. */
14
+ nextOffset: number;
15
+ running: boolean;
16
+ }
17
+ /**
18
+ * The one owner of a task attempt's output: what it printed while running
19
+ * and what it answered once it ended.
20
+ */
21
+ interface TaskOutputSource {
22
+ /** Start capturing a session; replaces any earlier capture of the task. */
23
+ attach(taskId: string, session: IRunnerSession): void;
24
+ /** Stop capturing the task's session; what was captured stays readable. */
25
+ detach(taskId: string): void;
26
+ /** Drop every capture (a new plan reuses task ids). */
27
+ reset(): void;
28
+ /** The task's final answer: the summary its runner reported through `task_complete`, else what its last turn said. */
29
+ finalText(taskId: string): string;
30
+ /** The summary the runner reported through `task_complete` in its latest turn, if it made that call. */
31
+ report(taskId: string): string | undefined;
32
+ /** Recent output of the task's latest capture, or null if it never had one. */
33
+ liveTail(taskId: string, opts: LiveTailOptions): LiveTail | null;
34
+ }
35
+
36
+ /**
37
+ * The catalog a model/task-mode edit is checked against — the same discovered
38
+ * models and manifest modes the planner was shown in the per-turn catalog
39
+ * block (`PlannerConversation.catalogBlock`), so a refusal here can never name something
40
+ * as invalid that the planner was never told about, or vice versa.
41
+ */
42
+ interface EditCatalog {
43
+ modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>;
44
+ runnerModes: Partial<Record<RunnerId, RunnerModeInfo[]>>;
45
+ /** Raw (unfiltered) allowlist, keyed by runner — the same shape `coerceAssignments` takes. */
46
+ perRunnerAllowlist?: Partial<Record<RunnerId, string[]>>;
47
+ /** Full (true) or Guarded: the mode a task the planner writes lands on unless it is read-only (ADR-0031). Full when absent. */
48
+ autonomousDefault?: boolean;
49
+ }
50
+
51
+ /**
52
+ * Targeted task edits emitted by the planner conversation (the 'task_ops'
53
+ * ConversationTurn). Task references accept a task id, a "#<order>" ref, or a
54
+ * bare order number — cheap models rarely echo UUIDs correctly.
55
+ */
56
+ /**
57
+ * A task as the planner writes it: its fields, less the mode, plus whether it
58
+ * must not change files (ADR-0031), and `thinkingEffort` as a name for its
59
+ * model's effort, so an effort can change without restating the model.
60
+ */
61
+ type PlannerTaskSpec = Partial<Task> & {
62
+ readOnly?: boolean;
63
+ thinkingEffort?: string | null;
64
+ };
65
+ type TaskOp = {
66
+ op: 'update';
67
+ taskId: string;
68
+ changes: PlannerTaskSpec;
69
+ } | {
70
+ op: 'add';
71
+ task: PlannerTaskSpec;
72
+ handle?: string;
73
+ } | {
74
+ op: 'remove';
75
+ taskId: string;
76
+ } | {
77
+ op: 'reorder';
78
+ taskIds: string[];
79
+ } | {
80
+ op: 'merge';
81
+ taskIds: string[];
82
+ merged: PlannerTaskSpec;
83
+ handle?: string;
84
+ } | {
85
+ op: 'split';
86
+ taskId: string;
87
+ parts: PlannerTaskSpec[];
88
+ handle?: string;
89
+ } | {
90
+ op: 'rearm';
91
+ taskId: string;
92
+ changes?: PlannerTaskSpec;
93
+ };
94
+ declare function textHasTaskOps(text: string): boolean;
95
+ /** Parse a `{"taskOps":[...]}` reply. Throws PlanParseError when the JSON is unusable. */
96
+ declare function parseTaskOpsJson(text: string): TaskOp[];
97
+ interface ApplyTaskOpsResult {
98
+ ok: boolean;
99
+ tasks: readonly Task[];
100
+ errors: string[];
101
+ /** Human-readable summary of what changed, for the chat transcript. */
102
+ summary: string[];
103
+ }
104
+ /**
105
+ * Pre-flight check for a merge: would collapsing the selected tasks into one
106
+ * (rewiring every dependent of any selected task to the survivor) keep the
107
+ * dependency graph valid? Rejects locked tasks and merges whose rewiring would
108
+ * introduce a cycle. Display order is NOT checked here — neither
109
+ * consecutiveness (a UI concern) nor a dependency the merge pulls out of order,
110
+ * which {@link applyTaskOps} repairs; refusing on order would make this
111
+ * pre-flight stricter than the applier it stands in for.
112
+ */
113
+ declare function canMergeTasks(tasks: readonly Task[], selectedIds: string[]): {
114
+ ok: boolean;
115
+ error?: string;
116
+ };
117
+ /**
118
+ * The shape the dependency helpers below read. Structural rather than `Task`,
119
+ * because the TUI projects tasks into its own `TaskView`: a surface's
120
+ * dependency picker and the API's validation must agree on which dependencies
121
+ * are legal, and a signature only core can satisfy would have forced the TUI
122
+ * to keep a second copy of the rule.
123
+ */
124
+ interface TaskRef {
125
+ id: string;
126
+ order: number;
127
+ title: string;
128
+ dependencies: string[];
129
+ }
130
+ /** The tasks listing `taskId` as a dependency — exactly what removing it detaches. */
131
+ declare function dependentsOf<T extends Pick<TaskRef, 'id' | 'dependencies'>>(tasks: T[], taskId: string): T[];
132
+ /**
133
+ * The tasks that may become dependencies of `taskId`: those displayed before it.
134
+ *
135
+ * Offering only earlier tasks is what keeps a hand-edited graph valid without a
136
+ * cycle check — dependencies then only ever point backwards in display order,
137
+ * the same invariant `applyTaskOps` enforces. Omit `taskId` for a task that does
138
+ * not exist yet: it lands last, so every current task is a candidate.
139
+ */
140
+ declare function dependencyCandidates<T extends Pick<TaskRef, 'id' | 'order'>>(tasks: readonly T[], taskId?: string): T[];
141
+ /** Pre-flight check for a hand-edited dependency list: every id exists and comes earlier. */
142
+ declare function canSetDependencies<T extends TaskRef>(tasks: readonly T[], taskId: string, dependencies: string[]): {
143
+ ok: boolean;
144
+ error?: string;
145
+ };
146
+ /** Pre-flight check for a split: the task exists and is not locked by execution. */
147
+ declare function canSplitTask(tasks: readonly Task[], taskId: string): {
148
+ ok: boolean;
149
+ error?: string;
150
+ };
151
+ /**
152
+ * Apply task ops to a snapshot of the plan, atomically: either every op
153
+ * applies and the result validates (deps resolve, no cycles, running and
154
+ * completed tasks untouched), or nothing is returned and `errors` explains
155
+ * why. The caller commits `tasks` on ok.
156
+ */
157
+ declare function applyTaskOps(currentTasks: readonly Task[], newOps: TaskOp[], runners: RunnerId[], catalog?: EditCatalog, held?: readonly TaskOp[]): ApplyTaskOpsResult;
158
+
159
+ /**
160
+ * Task skills end to end (ADR-0024): a skill reaches a runner only by being
161
+ * attached to its task in the plan, and Ordewell puts its body in the task's
162
+ * prompt — so it works the same on every runner, and nothing is written to a
163
+ * harness's own skills directory.
164
+ */
165
+ /** Where a task's skill names are looked up: global first, then the workspace folders it reads. */
166
+ interface SkillLookup extends Pick<SkillsService, 'findSkill' | 'searchedDirs'> {
167
+ /**
168
+ * Set when tasks get worktrees: of `skills`, the workspace ones git will not
169
+ * carry into them, each SKILL.md path mapped to the folder to commit,
170
+ * relative to the workspace root.
171
+ */
172
+ uncommitted?(skills: readonly SkillInfo[]): Promise<Map<string, string>>;
173
+ }
174
+ /**
175
+ * Where a workspace's skills are read, known without asking git: the repos
176
+ * of its group in layout order (`['.']` for one repository), and whether
177
+ * tasks will get worktrees, which only a committed skill reaches.
178
+ */
179
+ interface SkillLayout {
180
+ repos: readonly string[];
181
+ worktrees: boolean;
182
+ }
183
+
184
+ /**
185
+ * Where the run's tasks will work: `false` in the shared workspace root;
186
+ * otherwise each in its own worktree of every repo of the group (ADR-0013,
187
+ * ADR-0014), which is what decides if tasks on the same file must be ordered.
188
+ */
189
+ type IsolatedExecution = false | RepoGroupLayout;
190
+ interface PlannerModes {
191
+ autonomousDefault: boolean;
192
+ /** Not a toggle: a fact about the run. */
193
+ isolatedExecution: IsolatedExecution;
194
+ /**
195
+ * Where the run's tasks will look their skills up (ADR-0024), also a fact
196
+ * about the run. A plan attaching a planner skill is refused against it,
197
+ * and a coding agent's submit_plan call is refused in its turn rather than
198
+ * accepted and then failed. Absent: the plan is not checked.
199
+ */
200
+ taskSkills?: SkillLookup;
201
+ }
202
+
203
+ /**
204
+ * One entry of a structured task's log (ADR-0018, P1): what a runner did,
205
+ * normalized and serializable, streamed live as `task_log` and appended to
206
+ * the attempt's `.jsonl` file. The live view and the reload fold the same
207
+ * events through `reduceTaskLog`, so the two cannot disagree.
208
+ *
209
+ * Readers skip a type they do not know, which is how later events join
210
+ * without a format version.
211
+ */
212
+ type TaskLogEvent =
213
+ /**
214
+ * The task skills this attempt's prompt carried, as read at its spawn
215
+ * (ADR-0024): first in its log. The file keeps their bodies; a surface is
216
+ * sent notices ({@link taskLogForSurface}).
217
+ */
218
+ {
219
+ type: 'task_skills';
220
+ skills: TaskSkillSnapshot[] | TaskSkillNotice[];
221
+ }
222
+ /**
223
+ * A turn began by delivering `message`; `messageId` names it when it had been
224
+ * queued, `forced` when it was force sent. `from` on this and the other
225
+ * message events marks one Ordewell sent rather than the user.
226
+ */
227
+ | {
228
+ type: 'turn_start';
229
+ message: string;
230
+ messageId?: string;
231
+ forced?: boolean;
232
+ from?: TaskMessageFrom;
233
+ }
234
+ /**
235
+ * A turn began by running the runner's own command `/name args` (#66): the
236
+ * `turn_start` of a command turn. `messageId` names it when it had been queued.
237
+ */
238
+ | {
239
+ type: 'command';
240
+ name: string;
241
+ args?: string;
242
+ messageId?: string;
243
+ }
244
+ /** What the running command printed, for one that runs without the model. */
245
+ | {
246
+ type: 'command_output';
247
+ text: string;
248
+ }
249
+ /** The runner compacted its own context, asked to or on its own. */
250
+ | {
251
+ type: 'compaction';
252
+ trigger: 'manual' | 'auto';
253
+ preTokens?: number;
254
+ postTokens?: number;
255
+ }
256
+ /** What the runner loaded from the user's and the project's setup, as its process started. */
257
+ | {
258
+ type: 'runner_loaded';
259
+ commands: RunnerCommand[];
260
+ mcpServers: RunnerMcpServer[];
261
+ } | {
262
+ type: 'turn_end';
263
+ reason: StructuredTurnEnd;
264
+ } | {
265
+ type: 'text_delta';
266
+ text: string;
267
+ }
268
+ /** A complete run of the agent's reply, authoritative over the deltas streamed for it. */
269
+ | {
270
+ type: 'text';
271
+ text: string;
272
+ } | {
273
+ type: 'thinking_delta';
274
+ text: string;
275
+ subagentId?: string;
276
+ } | {
277
+ type: 'thinking';
278
+ text: string;
279
+ subagentId?: string;
280
+ }
281
+ /** `args` is the call's arguments as JSON, exactly as the runner announced them. */
282
+ | {
283
+ type: 'tool_call';
284
+ id: string;
285
+ name: string;
286
+ args: string;
287
+ subagentId?: string;
288
+ }
289
+ /** `omittedLines` is set when {@link trimToolOutput} cut the middle of `output`. */
290
+ | {
291
+ type: 'tool_result';
292
+ id: string;
293
+ output: string;
294
+ success: boolean;
295
+ omittedLines?: number;
296
+ subagentId?: string;
297
+ } | {
298
+ type: 'subagent_started';
299
+ subagentId: string;
300
+ brief: string;
301
+ model?: string;
302
+ } | {
303
+ type: 'subagent_finished';
304
+ subagentId: string;
305
+ outcome: SubagentOutcome;
306
+ digest: string;
307
+ } | {
308
+ type: 'usage';
309
+ record: UsageRecord;
310
+ }
311
+ /**
312
+ * Waiting in Ordewell's queue — again, for a message the runner was handed
313
+ * and let go of (ADR-0023, D4). `forced` moves it ahead of the rest (F2).
314
+ */
315
+ | {
316
+ type: 'message_queued';
317
+ messageId: string;
318
+ text: string;
319
+ forced?: boolean;
320
+ from?: TaskMessageFrom;
321
+ } | {
322
+ type: 'message_removed';
323
+ messageId: string;
324
+ } | {
325
+ type: 'message_undelivered';
326
+ messageId: string;
327
+ text: string;
328
+ from?: TaskMessageFrom;
329
+ }
330
+ /** The runner accepted a queued message into its running turn; it can no longer be taken back. */
331
+ | {
332
+ type: 'message_handed_over';
333
+ messageId: string;
334
+ }
335
+ /**
336
+ * The model read a message inside its running turn (ADR-0023, Q2), logged
337
+ * where it read it. A message that opened a turn is that turn's `turn_start`.
338
+ */
339
+ | {
340
+ type: 'message_delivered';
341
+ messageId: string;
342
+ text: string;
343
+ from?: TaskMessageFrom;
344
+ }
345
+ /** The agent's own words for a failed turn. */
346
+ | {
347
+ type: 'error';
348
+ message: string;
349
+ }
350
+ /**
351
+ * The runner asked to use a tool its mode does not cover (ADR-0018, A1).
352
+ * `approvalId` is what the answer is given under; `args` is the call's
353
+ * arguments as JSON; `allowForTask` says the runner offered its own
354
+ * session-scoped grant. `decided` is set when the request was never left
355
+ * open — an Ordewell tool or an auto-approving mode — so a surface does not
356
+ * offer to answer what is already answered (it is still followed by the
357
+ * matching `approval_decided`, which a reader may rely on).
358
+ */
359
+ | {
360
+ type: 'approval_requested';
361
+ approvalId: string;
362
+ tool: string;
363
+ args: string;
364
+ allowForTask: boolean;
365
+ toolCallId?: string;
366
+ decided?: ApprovalDecision['decision'];
367
+ } | {
368
+ type: 'approval_decided';
369
+ approvalId: string;
370
+ decision: ApprovalDecision['decision'];
371
+ note?: string;
372
+ }
373
+ /** The request went unanswered: the runner withdrew it, or its process ended. */
374
+ | {
375
+ type: 'approval_withdrawn';
376
+ approvalId: string;
377
+ }
378
+ /**
379
+ * The runner's process ended. A call it had not reported back on is not
380
+ * running any more: a task's runner is ended the moment its `task_complete`
381
+ * lands, often before it reports that call's result.
382
+ */
383
+ | {
384
+ type: 'runner_ended';
385
+ };
386
+ /**
387
+ * Keep a tool result's head and tail. A build log or a whole file read can
388
+ * run to megabytes, and every attempt's file keeps it for good; the ends are
389
+ * where a reader looks — the command's start, and how it finished.
390
+ */
391
+ declare function trimToolOutput(output: string): {
392
+ output: string;
393
+ omittedLines?: number;
394
+ };
395
+ /**
396
+ * The log entry for one structured event, or null for what the log does not
397
+ * keep: background-agent launches, which the subagent's own events already show.
398
+ */
399
+ declare function toTaskLogEvent(event: StructuredEvent): TaskLogEvent | null;
400
+ /** A log as a surface is sent it, live or read back: everything but the skills' bodies. */
401
+ declare function taskLogForSurface(events: readonly TaskLogEvent[]): TaskLogEvent[];
402
+ /**
403
+ * Merge runs of deltas into one event each. Deltas arrive a few characters at
404
+ * a time; merged, a batch is one message on the wire and one line on disk,
405
+ * and the view folds to the same blocks either way.
406
+ */
407
+ declare function coalesceTaskLog(events: readonly TaskLogEvent[]): TaskLogEvent[];
408
+ /**
409
+ * What an attempt did to the world, for the retry of a task whose effects
410
+ * outlive it (ADR-0020): one line per tool call and how it ended.
411
+ */
412
+ declare function digestTaskLog(events: readonly TaskLogEvent[], maxCalls: number): string;
413
+
414
+ type NotificationAction = {
415
+ label: string;
416
+ value: string;
417
+ };
418
+ interface INotification {
419
+ info(msg: string): void;
420
+ warn(msg: string): void;
421
+ error(msg: string): void;
422
+ confirm(msg: string, options: string[]): Promise<string | undefined>;
423
+ }
424
+
425
+ /**
426
+ * The deep module owning all plan-shaped state. Holds `planTasks` (the ordered
427
+ * tree the user edits), the flattened `allTasks` view and the `taskMap` index.
428
+ * The orchestrator calls `markCompleted`/`markFailed`/`markInProgress`/`retry`
429
+ * to update task status — it never mutates task state directly.
430
+ *
431
+ * Completion and failure are read from `task.status` and nothing else, so
432
+ * `isCompleted`, `completedCount`, `isAllComplete` and the scheduler's
433
+ * dependency checks cannot disagree. The store owns its task objects: `load`
434
+ * copies what it is given, the getters hand out readonly views, and
435
+ * `snapshot` is the copy a caller may keep or write to disk.
436
+ *
437
+ * `rebuild` is the internal seam that keeps the flat views in sync with the
438
+ * tree.
439
+ *
440
+ * `planRunners` lives here because it's part of the plan's identity (the runner
441
+ * set, carried on the plan). `validateAssignedRunners` is pure store logic.
442
+ */
443
+ declare class PlanStore {
444
+ private _planTasks;
445
+ private _allTasks;
446
+ private _taskMap;
447
+ /** Task id → the top-level task it hangs under, itself for a top-level task. */
448
+ private _rootMap;
449
+ private _planRunners;
450
+ private _onMutate;
451
+ private _executionLog;
452
+ /** Hook called after every structural mutation (add/remove/update/resetForRun). */
453
+ set onMutate(cb: (() => void) | null);
454
+ get planTasks(): ReadonlyArray<Readonly<Task>>;
455
+ get allTasks(): ReadonlyArray<Readonly<Task>>;
456
+ get planRunners(): RunnerId[];
457
+ get completedCount(): number;
458
+ get failedCount(): number;
459
+ isAllComplete(): boolean;
460
+ isAnyFailed(): boolean;
461
+ isCompleted(id: string): boolean;
462
+ isSkipped(id: string): boolean;
463
+ /** Nothing is left to do: every task completed or was skipped. */
464
+ isFinished(): boolean;
465
+ isFailed(id: string): boolean;
466
+ get(taskId: string): Readonly<Task> | undefined;
467
+ /**
468
+ * Whether a task runs as an ops task (ADR-0020). Only a top-level AI task
469
+ * can be one; a subtask runs with the top-level task it hangs under.
470
+ */
471
+ isOps(taskId: string): boolean;
472
+ /** A copy of the task tree, detached from the store: later status changes do not reach it. */
473
+ snapshot(): Task[];
474
+ getExecutionLog(): ReadonlyArray<TaskSnapshot>;
475
+ appendToLog(snapshot: TaskSnapshot): void;
476
+ /**
477
+ * Drop a task's archived snapshot. Un-marking a completion has to erase the
478
+ * "finished" record too — dependent tasks are prompted from the log, so a
479
+ * left-behind snapshot would keep feeding them a result that no longer exists.
480
+ */
481
+ removeFromLog(taskId: string): void;
482
+ clearLog(): void;
483
+ private notifyMutate;
484
+ private countStatus;
485
+ load(tasks: ReadonlyArray<Readonly<Task>>, runners: readonly RunnerId[]): void;
486
+ add(partial: Partial<Task>): Task;
487
+ remove(taskId: string): void;
488
+ update(taskId: string, changes: Partial<Task>): Task | undefined;
489
+ /**
490
+ * Run preparation as one named op: flip every AI task to 'approved'. By
491
+ * default completed tasks keep their status so a reloaded half-finished plan
492
+ * resumes the remainder instead of re-running work that already succeeded,
493
+ * and skipped ones stay skipped: a new run is not a retry of them.
494
+ * `preserveCompleted: false` is for a freshly committed plan, where
495
+ * everything starts over. `keepScheduledResumes` leaves a task waiting on
496
+ * its runner's usage-limit reset waiting: an edit adopted between runs is
497
+ * not the user starting over.
498
+ */
499
+ resetForRun(opts?: {
500
+ preserveCompleted?: boolean;
501
+ keepScheduledResumes?: boolean;
502
+ }): void;
503
+ markCompleted(id: string): void;
504
+ markFailed(id: string): void;
505
+ markSkipped(id: string, reason?: string): void;
506
+ markInProgress(id: string): void;
507
+ markAwaitingUser(id: string, reason?: AwaitingReason): void;
508
+ /** When a task paused on a usage limit resumes on its own; undefined leaves it to a manual retry. */
509
+ setTaskResumeAt(id: string, resumeAt: string | undefined): void;
510
+ markPending(id: string): void;
511
+ retry(id: string): void;
512
+ /** Kept on the task: it was force-started past its merge gate, before these dependencies' work was merged (ADR-0020). */
513
+ setForcedPastGate(id: string, dependencies: string[]): void;
514
+ /** A reason outlives nothing: any status but `awaiting_user` drops it, and the resume time with it; any status drops a skip's reason. */
515
+ private setStatus;
516
+ blockDependents(id: string): void;
517
+ unblockDependents(id: string): void;
518
+ setTaskVerdict(id: string, verdict: Task['verdict']): void;
519
+ setTaskOutputSummary(id: string, summary: Task['outputSummary']): void;
520
+ setTaskRunnerSessionId(id: string, runnerSessionId: string | undefined): void;
521
+ startAttemptUsage(id: string, attempt: number, runner: string, startedAt: string): void;
522
+ recordAttemptUsage(id: string, attempt: number, record: UsageRecord, now: string): void;
523
+ endAttemptUsage(id: string, attempt: number, now: string): void;
524
+ setTaskAttemptSkills(id: string, skills: Task['attemptSkills']): void;
525
+ /**
526
+ * Widen the plan's runner set. Retargeting a task onto a runner the plan has
527
+ * not used before has to land here as well as on the plan state, or
528
+ * {@link resolveTaskRunner} reads the new runner as foreign and spawns the
529
+ * plan's first one instead.
530
+ */
531
+ admitRunner(runner: RunnerId): void;
532
+ resolveTaskRunner(task: Readonly<Task>): RunnerId;
533
+ private rebuild;
534
+ private validateAssignedRunners;
535
+ }
536
+
537
+ type IsolationNoticeLevel = 'info' | 'warn' | 'error';
538
+ interface AttemptWorkspace {
539
+ cwd: string;
540
+ worktree: boolean;
541
+ continuationMessage?: string;
542
+ }
543
+ /**
544
+ * What the controller reports. It never schedules or emits on its own: the
545
+ * orchestrator turns these into observer events and decides what runs next.
546
+ */
547
+ interface IsolationRunListener {
548
+ /**
549
+ * The run record changed and should be persisted with the plan. `detached`
550
+ * is set instead when the record that changed is no longer the plan's own —
551
+ * a release that outlived the plan load that replaced its run — and belongs
552
+ * to the session that was left.
553
+ */
554
+ changed(detached?: IsolationRun): void;
555
+ /** A run did not start: `repos` of the group have tracked changes. */
556
+ blocked(repos: string[]): void;
557
+ /** An isolated run closed and handed its integration branches over. */
558
+ handoff(handoff: IsolationHandoff): void;
559
+ /** Already said through the notifications; also for a surface that shows no toasts. */
560
+ notice(level: IsolationNoticeLevel, message: string): void;
561
+ }
562
+ interface IsolationRunControllerDeps {
563
+ isolation: IWorktreeIsolation;
564
+ config: IConfig;
565
+ notifications: INotification;
566
+ workspaceRoot: () => string;
567
+ listener: IsolationRunListener;
568
+ /**
569
+ * The tasks no clean-up of the whole run may take a worktree from: an
570
+ * attempt is running them, or the plan has them in progress or waiting on
571
+ * the user mid-attempt.
572
+ */
573
+ liveTasks: () => ReadonlySet<string>;
574
+ }
575
+ /**
576
+ * The lifecycle of a plan's isolation run (ADR-0013, ADR-0014, ADR-0020),
577
+ * between the scheduler and the git layer: deciding, at a run's first change
578
+ * task, whether it executes in worktrees, a blocked run and how it goes on,
579
+ * each attempt's working directory, releasing worktrees, which landed work the
580
+ * user has merged, the tree check of an ops task, the handoff when the run
581
+ * closes, and what the user does with it afterwards. The record it keeps
582
+ * outlives one run — a resumed plan continues it.
583
+ */
584
+ declare class IsolationRunController {
585
+ private readonly isolation;
586
+ private readonly config;
587
+ private readonly notifications;
588
+ private readonly workspaceRoot;
589
+ private readonly listener;
590
+ private readonly liveTasks;
591
+ private run;
592
+ /** Copied paths already reported for the current run: every task gets the same copies. */
593
+ private reportedCopies;
594
+ private reportedSetupFailures;
595
+ /**
596
+ * How the open run executes; null while no run is open, `undecided` until
597
+ * its first change task asks. A run is one Execute-Plan or one manual task
598
+ * run, from its start until it settles or is stopped.
599
+ */
600
+ private mode;
601
+ /** Resolver task id → the conflicted task it resolves; see {@link linkResolver}. */
602
+ private resolvers;
603
+ private opening;
604
+ /** Bumped by an interrupt, so an activation in flight across it does not open the run it ended. */
605
+ private activations;
606
+ /** The start a dirty tree turned away, handed back once the user chooses how to go on. */
607
+ private blockedStart;
608
+ /** The dirty repos behind {@link blockedStart}, for the stash notice. */
609
+ private blockedRepos;
610
+ constructor(deps: IsolationRunControllerDeps);
611
+ /** The plan's run record, open or not; null when the plan has not isolated. */
612
+ get current(): IsolationRun | null;
613
+ get isOpen(): boolean;
614
+ /** The open run has decided whether it isolates. */
615
+ get decided(): boolean;
616
+ /** The open run executes in worktrees. */
617
+ get isolating(): boolean;
618
+ /** A run is waiting on the user to stash or to go on without isolation. */
619
+ get blocked(): boolean;
620
+ /** What the plan persists of isolated execution; null when no run ever isolated. */
621
+ get planIsolation(): PlanIsolation | null;
622
+ /** The one guard of every action on the run: a plan that never isolated is a request refused, not a fault. */
623
+ requireRun(): IsolationRun;
624
+ /** A task's record in the open run, only while that run isolates. */
625
+ openRecord(taskId: string): IsolationTaskRecord | undefined;
626
+ /** Where a task's isolated work stands; null when the plan has no isolation run to speak of. */
627
+ taskIsolation(taskId: string): TaskIsolation | null;
628
+ view(): IsolationView | null;
629
+ /**
630
+ * Take over a plan's persisted isolation, or none for a plan that has not
631
+ * isolated yet. Whatever a crashed process left behind for the run — a
632
+ * worktree still marked active, a directory no record owns — is pruned,
633
+ * while kept, failed and conflicted worktrees stay for the user.
634
+ *
635
+ * Deliberately not reported as a change: adopting is not a change to
636
+ * persist, and a host that adopts without persisting (VS Code's restore)
637
+ * would otherwise write a new session file on every reload.
638
+ */
639
+ adopt(state: PlanIsolation | null): Promise<void>;
640
+ /** Open a run if none is, leaving whether it isolates to its first change task. */
641
+ open(): void;
642
+ /**
643
+ * Decide once per run, as its first change task starts, whether it executes
644
+ * in worktrees (ADR-0020): an ops task needs none, so a run of only ops
645
+ * tasks never asks. `resume` is what a dirty tree parks until the user
646
+ * chooses how to go on. Resolves false while the run waits on that choice.
647
+ */
648
+ decide(resume: () => Promise<void>): Promise<boolean>;
649
+ /**
650
+ * Whether the run in force, or else the next one, gives each task its own
651
+ * worktrees, and of which repo group — what the planner is told, since it
652
+ * decides whether tasks on the same file have to be ordered and which shared
653
+ * paths two tasks must not edit at once. A tree that would block counts as
654
+ * not isolating: the user may yet run without isolation, and ordering is
655
+ * the safe rule then.
656
+ */
657
+ plannerLayout(): Promise<IsolatedExecution>;
658
+ /**
659
+ * Where the workspace's skills are read, and whether tasks get worktrees.
660
+ * Read synchronously inside a planner turn or a chip edit, so it comes from
661
+ * the files, not git or the planner's last layout: a restored session is
662
+ * right from its first edit. The run's repos while it isolates; otherwise
663
+ * the group the workspace holds, in worktrees unless isolation is off or
664
+ * this run chose to share the root.
665
+ */
666
+ skillLayout(): SkillLayout;
667
+ /**
668
+ * Go on with the start a dirty tree turned away. `stash` puts the user's
669
+ * tracked changes on the git stash first, so the run isolates; `shared` runs
670
+ * this one run in the workspace root, knowingly. Returns the parked start for
671
+ * the caller to replay; null when nothing was parked.
672
+ */
673
+ continueBlocked(how: 'stash' | 'shared'): Promise<(() => Promise<void>) | null>;
674
+ /**
675
+ * The working directory of an attempt that may run in a worktree — which
676
+ * attempts do is the attempt kind's to say (see `attemptKind.attemptCwd`):
677
+ * the worktree prepared for it in an isolated run, the kept one for a
678
+ * conflict repair, else the workspace root. `worktree` says which. Does not
679
+ * itself report a worktree it creates as a change — the caller does once
680
+ * the attempt is committed as running, so a spawn abandoned or failed after
681
+ * this settles is not misreported as a change that stuck.
682
+ */
683
+ attemptCwd(task: Task, intent: 'fresh' | 'repair' | 'continuation'): Promise<AttemptWorkspace>;
684
+ /** The workspace root, for an attempt that runs outside any worktree. */
685
+ workspaceCwd(task: Task): Promise<{
686
+ cwd: string;
687
+ worktree: boolean;
688
+ }>;
689
+ private reopenCwd;
690
+ private prepareCwd;
691
+ /**
692
+ * Land a task's work on the run's integration branches. The record is
693
+ * reported changed once the landing is set and before the first merge, so
694
+ * a crash mid-landing leaves the tips to roll back to, and again once it
695
+ * settles. A git error is `failed`, never a throw.
696
+ */
697
+ integrate(task: Task): Promise<IsolationOutcome>;
698
+ /** What a conflict repair's work shows (ADR-0015); git that cannot tell counts against it. */
699
+ verifyRepair(task: Task): Promise<RepairEvidence>;
700
+ /**
701
+ * Let go of a task's worktree. `keep` leaves it and its branch for
702
+ * inspection; otherwise both go. A landing still in flight settles first, so
703
+ * the worktree is never torn down underneath its merge. Bound to the run
704
+ * the call was made in: a plan loaded meanwhile adopts its own run, whose
705
+ * records share these task ids and are not this release's to touch.
706
+ */
707
+ release(taskId: string, opts: {
708
+ keep: boolean;
709
+ }, landing?: Promise<unknown> | null): Promise<void>;
710
+ /**
711
+ * Close the open run. An isolated one hands its integration branch over for
712
+ * review — unless everything it landed is merged already, at merge gates or
713
+ * by the run's automatic Merge all, which leaves nothing to hand over, so it
714
+ * is cleared up as a Merge all would have.
715
+ */
716
+ close(): Promise<void>;
717
+ /**
718
+ * Whether a task's landed work still waits to be merged into the user's
719
+ * branch — what holds an ops or user task at its merge gate (ADR-0020). A
720
+ * task with no record never landed anything to wait for.
721
+ */
722
+ awaitsMerge(taskId: string): boolean;
723
+ /**
724
+ * Look again for landed work the user has merged — by Merge all, or by hand
725
+ * with git. Resolves true when some was found.
726
+ */
727
+ refreshInHead(): Promise<boolean>;
728
+ /**
729
+ * The tracked state of the workspace as an attempt whose tree is checked
730
+ * starts (see `attemptKind.checksTree`), for {@link filesChangedSince}
731
+ * (ADR-0020). Null where the check does not run: a run that shares the
732
+ * workspace root, where every task's edits land in it, or a workspace git
733
+ * cannot isolate at all.
734
+ */
735
+ snapshotWorkspace(): Promise<TreeSnapshot | null>;
736
+ /** Tracked files changed since `snapshot`; none when the run has since gone on in the workspace root. */
737
+ filesChangedSince(snapshot: TreeSnapshot | null): Promise<string[]>;
738
+ /**
739
+ * A stop or a plan load: the open run ends without a handoff and a parked
740
+ * start is dropped. `keepOpen` spares a run the scheduler is still driving.
741
+ */
742
+ interrupt(opts?: {
743
+ keepOpen?: boolean;
744
+ }): void;
745
+ /**
746
+ * Mark which added task resolves which conflict. The resolver merges the
747
+ * conflicted task's branch by hand in its own worktree; once that lands, the
748
+ * conflicted task can land in turn — through the same merge, so a resolver
749
+ * that did not really bring it along conflicts again instead of being taken
750
+ * at its word.
751
+ */
752
+ linkResolver(resolverId: string, conflictedId: string): void;
753
+ /** The conflicted task a landed resolver was added for, forgotten as it is returned. */
754
+ takeResolver(resolverId: string): string | undefined;
755
+ taskDiff(taskId: string, options: TaskDiffRead): Promise<TaskDiffResult>;
756
+ reviewDiff(): Promise<string>;
757
+ /**
758
+ * "Merge all": the run's integration branches into whatever the user has
759
+ * checked out, in every repo or none. During a run it merges what has
760
+ * landed so far and the run goes on (ADR-0020): its branches stay for the
761
+ * tasks still to land. Once a settled run merged everything, it has nothing
762
+ * left to hand over, so it is cleared up and forgotten; a branch the user's
763
+ * HEAD somehow does not contain stays for the next run's sweep.
764
+ */
765
+ merge(): Promise<IsolationMergeResult>;
766
+ /**
767
+ * Worktrees and task branches go; the integration branch and the record stay
768
+ * for review and merge. Refused while a task of the run is still live.
769
+ */
770
+ cleanup(): Promise<void>;
771
+ /**
772
+ * The run and everything it made go, and the plan forgets it; the next run
773
+ * starts afresh. Refused while a task of the run is still live.
774
+ */
775
+ discard(): Promise<void>;
776
+ /** The records of tasks still live, whose worktrees no clean-up of the whole run may take. */
777
+ private liveRecords;
778
+ private refuseWhileLive;
779
+ /**
780
+ * Clear a settled run up once everything it landed is merged — and only
781
+ * when nothing else is left in it: a live task, or a worktree holding work
782
+ * that never landed, keeps the whole run, worktrees and branches, rather
783
+ * than leaving that work to a deletion.
784
+ */
785
+ private clearMerged;
786
+ /** Say what a removal kept back instead of deleting: where the work went, or which worktree stayed. */
787
+ private report;
788
+ private forget;
789
+ private assess;
790
+ private activate;
791
+ private begin;
792
+ /** What earlier runs left merged in the group goes; a failure here is worth a word, never a stopped run. */
793
+ private sweep;
794
+ /**
795
+ * The plan's run carries on while it still has any record: anything landed
796
+ * means a resumed plan's dependents must start from a tip that holds their
797
+ * predecessors' work, and anything held — a kept attempt, a conflict, a
798
+ * repair — is work the user may still want, which a fresh run's mint would
799
+ * delete. Only a run with no records at all is superseded.
800
+ */
801
+ private continuableRun;
802
+ /**
803
+ * A run with no records at all holds only superseded attempts, so it goes
804
+ * whole. One that cannot be continued for another reason — it ran from a
805
+ * different workspace path — keeps its integration branch in each repo that
806
+ * has not merged it: only the user gives landed work up.
807
+ *
808
+ * False when `interrupted` came while git was minting: a newer activation
809
+ * may be minting its own run, so this one is never installed over it, and
810
+ * is discarded rather than left as branches no record names.
811
+ */
812
+ private mint;
813
+ private reportCopies;
814
+ private reportSetupFailures;
815
+ private tell;
816
+ }
817
+
818
+ /** Which repair of a task an attempt is, of the most `conflictRepairAttempts` allows. */
819
+ interface RepairAttempt {
820
+ n: number;
821
+ limit: number;
822
+ }
823
+ /** One thing the user is told about a landing. */
824
+ interface LandingMessage {
825
+ level: 'info' | 'warn' | 'error';
826
+ text: string;
827
+ /** Also handed to a surface that shows no toasts: ADR-0015 logs every conflict repair as a notice. */
828
+ repairLog?: true;
829
+ }
830
+ /**
831
+ * How a landing settled, for the orchestrator to apply — it marks the task,
832
+ * starts the repair and says the messages. By the time one is returned the
833
+ * merge has happened or not, and the run record says so.
834
+ *
835
+ * - `landed`: the work is on the integration branch of every repo it changed.
836
+ * - `nothing-to-land`: the attempt ran in the workspace root, or the task
837
+ * holds no unlanded work; it is done as it stands.
838
+ * - `repair-needed`: the landing conflicted and the task is owed `repair`
839
+ * (ADR-0015), in the worktree kept for it.
840
+ * - `awaiting_user`: the work did not land and waits on the user, worktree
841
+ * kept — a conflict with no repair left (`conflict`), a merge git refused
842
+ * (`landing-failed`), or a repair that did not land (`repair-failed`). None
843
+ * of them fails the task, so none blocks its dependents.
844
+ */
845
+ type LandingOutcome = {
846
+ kind: 'landed';
847
+ messages: LandingMessage[];
848
+ } | {
849
+ kind: 'nothing-to-land';
850
+ messages: LandingMessage[];
851
+ } | {
852
+ kind: 'repair-needed';
853
+ repair: RepairAttempt;
854
+ messages: LandingMessage[];
855
+ } | {
856
+ kind: 'awaiting_user';
857
+ reason: 'conflict' | 'landing-failed' | 'repair-failed';
858
+ messages: LandingMessage[];
859
+ };
860
+ interface LandingDeps {
861
+ runs: IsolationRunController;
862
+ config: Pick<IConfig, 'conflictRepairAttempts'>;
863
+ /** Read-only: only the orchestrator changes a task. `planTasks` labels a subtask by its dotted reference. */
864
+ tasks: Pick<PlanStore, 'get' | 'planTasks'>;
865
+ }
866
+ /**
867
+ * Landing and Conflict repair (ADR-0013, ADR-0014, ADR-0015): a passed
868
+ * attempt's work onto the run's integration branches through the
869
+ * {@link IsolationRunController}, and what a landing that did not go through
870
+ * leads to. It answers with a {@link LandingOutcome} rather than acting on it —
871
+ * it never marks a task, starts an attempt or emits — so the scheduler stays
872
+ * the one place that decides what runs. A repair is spent when it starts: the
873
+ * controller's `reopen` counts it before the runner spawns, and nothing here
874
+ * hands one back.
875
+ */
876
+ declare class Landing {
877
+ private readonly runs;
878
+ private readonly config;
879
+ private readonly tasks;
880
+ constructor(deps: LandingDeps);
881
+ /**
882
+ * Land a passed attempt's work. One whose worktree has no unlanded record
883
+ * behind it any more — the run was discarded under it — cannot land.
884
+ */
885
+ landPassed(task: Task, attempt: {
886
+ worktree: boolean;
887
+ }): Promise<LandingOutcome>;
888
+ /** Mark complete is a passed verdict the user vouches for: what the task holds unlanded lands the same way. */
889
+ landVouched(task: Task): Promise<LandingOutcome>;
890
+ /**
891
+ * A repair's verdict decides only whether its work may try to land; the
892
+ * task keeps the verdict its own work earned. Evidence comes before the
893
+ * queue, and the landing after it is the one every task goes through, so a
894
+ * repair the tip has moved past again is a fresh conflict, not a pass.
895
+ * `current` says whether the attempt is still the task's: a stop that comes
896
+ * while the evidence is read has already cancelled the work, and the merge
897
+ * must not bring it in anyway.
898
+ */
899
+ settleRepair(task: Task, verdict: Verdict, current: () => boolean): Promise<LandingOutcome>;
900
+ /** A repair that ended without landing leaves the task as its conflict did: waiting on the user, worktree and refs kept. */
901
+ unrepaired(task: Task, why: string): Promise<Extract<LandingOutcome, {
902
+ kind: 'awaiting_user';
903
+ }>>;
904
+ /**
905
+ * Land the conflicted task a resolver was added for, now that the resolver
906
+ * has landed. Its branch is on the integration branch by then, so it merges
907
+ * clean — and one the resolver did not really bring along conflicts again
908
+ * instead of being taken at its word. Null when there is nothing to land.
909
+ */
910
+ landResolved(resolverId: string): Promise<{
911
+ task: Readonly<Task>;
912
+ outcome: LandingOutcome;
913
+ } | null>;
914
+ /** The repair a conflicted task is owed next; null when repair is off, used up, or there is no isolated run to repair it in. */
915
+ nextRepair(taskId: string): RepairAttempt | null;
916
+ /** What a repair is asked to do; read once `reopen` has recorded the tips it must bring in. */
917
+ repairPrompt(task: Task): string;
918
+ private hasUnlandedWork;
919
+ private unmerged;
920
+ private noRepairReason;
921
+ private describeEvidence;
922
+ private isGroup;
923
+ }
924
+
925
+ /**
926
+ * The variables one workspace's planner and agents run with, on top of the
927
+ * daemon's own environment (ADR-0016).
928
+ *
929
+ * The daemon and the VS Code host inherit whatever environment they were
930
+ * started from, once. Started anywhere but the project's shell — a desktop
931
+ * launcher, another directory, a daemon already up for another project — they
932
+ * miss what the project sets for itself, like a `CLAUDE_CONFIG_DIR` choosing
933
+ * the account the agents run under. So each spawn asks for it by its own cwd:
934
+ * a task worktree lives under the workspace, so walking up finds the same
935
+ * `.envrc` and `.ordewell/env` the workspace root does.
936
+ */
937
+ interface WorkspaceEnv {
938
+ env: Record<string, string>;
939
+ /** An `.envrc` direnv refuses to load until the user allows it. */
940
+ blockedEnvrc: string | null;
941
+ /** Keys dropped because they would change how processes load or where Ordewell's own settings live. */
942
+ refused: string[];
943
+ /** An `.ordewell/env` ignored because git tracks it — a cloned repository must not choose these. */
944
+ trackedEnvFile: string | null;
945
+ }
946
+
947
+ /** What ran an automatic Merge all: a task reaching its merge gate, or the run settling with landed work unmerged. */
948
+ type AutoMergeTrigger = {
949
+ kind: 'gate';
950
+ taskId: string;
951
+ } | {
952
+ kind: 'run_end';
953
+ };
954
+ /**
955
+ * Who opened a run (ADR-0029): a call the user made, or one made for them; a
956
+ * call of the supervisor's own; or a usage-limit reset resuming what it paused.
957
+ * Only the user's starts a run's supervision afresh.
958
+ */
959
+ type RunOrigin = 'user' | 'supervisor' | 'limit-reset';
960
+ /** What the scheduler reads of the open run's settings, at each decision it makes from them. */
961
+ type SchedulerRunSettings = Pick<RunSettings, 'mergeAtGatesAutomatically'>;
962
+ /**
963
+ * The one notification channel out of the orchestrator. Everything that used
964
+ * to travel over a separate callback (onRefresh) is an observer event; the
965
+ * Session subscribes once and turns these into SessionMessages.
966
+ */
967
+ interface OrchestratorObserver {
968
+ /** Any task-shaped state changed (store mutation, checkpoint, retry, …). */
969
+ onTaskChanged?(): void;
970
+ /**
971
+ * A run reached an outcome that stands until the user acts — completed,
972
+ * failed, or awaiting the user — on its own, not as the answer to a call.
973
+ * Emitted before the `onTaskChanged` that announces it, so a listener can
974
+ * save the outcome before any surface is told.
975
+ */
976
+ onTaskSettled?(data: {
977
+ taskId: string;
978
+ }): void;
979
+ onRunStarted?(data: {
980
+ origin: RunOrigin;
981
+ }): void;
982
+ /** An automatic Merge all settled, at a gate or at the run's end; failures are attention events for a supervisor. */
983
+ onAutoMerge?(data: {
984
+ trigger: AutoMergeTrigger;
985
+ result: IsolationMergeResult;
986
+ }): void;
987
+ onTick?(): void;
988
+ onUsageChanged?(): void;
989
+ onExecutionComplete?(): void;
990
+ /**
991
+ * A task paused on its runner's usage limit, or its resume changed:
992
+ * `resumeAt` is when it resumes on its own (ISO time), null when it waits
993
+ * for a manual retry — the reset could not be read, the resume was
994
+ * cancelled, or the resumed attempt hit the limit again at once.
995
+ * `cancelled` marks the resume being cancelled, which is no new pause.
996
+ */
997
+ onUsageLimitWait?(data: {
998
+ taskId: string;
999
+ runner: string;
1000
+ resumeAt: string | null;
1001
+ cancelled?: true;
1002
+ }): void;
1003
+ /** A live task's runner went silent (ADR-0018's idle timer); paused while it waits on input, a checkpoint or an approval. */
1004
+ onTaskIdle?(data: {
1005
+ taskId: string;
1006
+ idleSince: string;
1007
+ }): void;
1008
+ onReviewNeeded?(data: {
1009
+ tasks: Task[];
1010
+ planRunners: RunnerId[];
1011
+ }): void;
1012
+ onReviewApproved?(data: {
1013
+ tasks: Task[];
1014
+ }): void;
1015
+ onCheckpoint?(data: {
1016
+ taskId: string;
1017
+ taskTitle: string;
1018
+ summary: string;
1019
+ }): void;
1020
+ /**
1021
+ * The isolation run record changed and should be persisted with the plan.
1022
+ * `detached` is the record of a run another plan has replaced since — a
1023
+ * release that outlived a plan load — for the session it belongs to.
1024
+ */
1025
+ onIsolationChanged?(detached?: IsolationRun): void;
1026
+ /** A run did not start: the tree is dirty, and the user picks stash or no isolation. `repos` names the dirty repos of a group. */
1027
+ onIsolationBlocked?(data: {
1028
+ reason: 'dirty';
1029
+ repos: string[];
1030
+ }): void;
1031
+ /** An isolated run settled; emitted before `onExecutionComplete`, which surfaces treat as terminal. */
1032
+ onIsolationHandoff?(handoff: IsolationHandoff): void;
1033
+ /**
1034
+ * What a run says about how it isolates — the fallback to the workspace root,
1035
+ * shared paths, copies, a stash. Beside the notification channel, which a
1036
+ * daemon may leave unwired, so a surface without toasts can still show it.
1037
+ */
1038
+ onIsolationNotice?(data: {
1039
+ level: 'info' | 'warn' | 'error';
1040
+ message: string;
1041
+ }): void;
1042
+ }
1043
+ /**
1044
+ * A message or interrupt a task cannot take: it is not running, or it is
1045
+ * waiting on something a message does not answer.
1046
+ * The request is wrong, not the orchestrator, so a surface says why.
1047
+ */
1048
+ declare class TaskControlError extends Error {
1049
+ constructor(message: string);
1050
+ }
1051
+ type AttemptPhase = 'starting' | 'running' | 'integrating';
1052
+ /** Read-only view of a task's live attempt. */
1053
+ interface TaskAttemptSnapshot {
1054
+ taskId: string;
1055
+ attempt: number;
1056
+ phase: AttemptPhase;
1057
+ sessionId: string | null;
1058
+ runner: string;
1059
+ cwd: string | null;
1060
+ startedAt: string;
1061
+ skills: readonly TaskSkillSnapshot[];
1062
+ }
1063
+ /**
1064
+ * The wiring a {@link TaskOrchestrator} runs on, every collaborator resolved.
1065
+ * {@link TaskOrchestrator.compose} is the only production caller; tests build
1066
+ * one through it too. The constructor makes nothing itself, so there is exactly
1067
+ * one place defaults live.
1068
+ */
1069
+ interface TaskOrchestratorDeps {
1070
+ config: IConfig;
1071
+ notifications: INotification;
1072
+ runner: IRunner;
1073
+ store: PlanStore;
1074
+ output: TaskOutputSource;
1075
+ /** The plan's isolation run and the open run's lifecycle (ADR-0013). */
1076
+ runs: IsolationRunController;
1077
+ /** A passed attempt's work onto the integration branch, and any conflict repair. */
1078
+ landing: Landing;
1079
+ registry: RunnerRegistry | null;
1080
+ /** The workspace root, read at call time — VS Code can change it. */
1081
+ workspaceRoot: () => string;
1082
+ /** The workspace's own variables for a task's cwd (ADR-0016). */
1083
+ workspaceEnv: (cwd: string) => Promise<WorkspaceEnv>;
1084
+ /** The skill catalog over workspace roots, as {@link workspaceSkillRoots} orders them — read at each spawn. */
1085
+ skillsAt: (roots: readonly string[]) => SkillLookup;
1086
+ /** What the task's last saved attempt did, or null when it left no log (ADR-0020). */
1087
+ previousAttemptFromLog: (taskId: string) => string | null;
1088
+ /**
1089
+ * The open run's settings, read as each automatic merge is decided rather
1090
+ * than copied when the run opens, so a change to the run under way applies
1091
+ * at once (ADR-0030); absent before any run.
1092
+ */
1093
+ runSettings: () => SchedulerRunSettings | undefined;
1094
+ }
1095
+ /**
1096
+ * What a caller supplies to {@link TaskOrchestrator.compose}; every optional
1097
+ * one gets its default there. This is what a host or test chooses, not the
1098
+ * resolved wiring the orchestrator itself runs on.
1099
+ */
1100
+ interface TaskOrchestratorOptions {
1101
+ config: IConfig;
1102
+ notifications: INotification;
1103
+ runner: IRunner;
1104
+ store?: PlanStore;
1105
+ output?: TaskOutputSource;
1106
+ isolation?: IWorktreeIsolation;
1107
+ registry?: RunnerRegistry | null;
1108
+ workspaceRoot?: () => string;
1109
+ workspaceEnv?: (cwd: string) => Promise<WorkspaceEnv>;
1110
+ skillsAt?: (roots: readonly string[]) => SkillLookup;
1111
+ previousAttemptFromLog?: (taskId: string) => string | null;
1112
+ runSettings?: () => SchedulerRunSettings | undefined;
1113
+ }
1114
+ /**
1115
+ * The pure scheduler. Owns execution state (`running`, `planStatus`,
1116
+ * `reviewApproved`, the live task attempts, the start hold) and the verifier.
1117
+ * All task-shaped state — the plan tree, the flat index, the completed set
1118
+ * — lives in {@link PlanStore}, injected at construction. The orchestrator
1119
+ * calls `store.markCompleted(id)` / `store.markFailed(id)` instead of mutating
1120
+ * task state directly. A task completes only after the runner reports it done
1121
+ * through `task_complete` on its attempt's token; process exit without that
1122
+ * evidence is a visible failure and does not unblock dependent work.
1123
+ */
1124
+ declare class TaskOrchestrator {
1125
+ private config;
1126
+ private notifications;
1127
+ private runner;
1128
+ private store;
1129
+ private output;
1130
+ private attempts;
1131
+ private verifier;
1132
+ private running;
1133
+ private planStatus;
1134
+ /** Planner turns in flight that new starts wait for (ADR-0029); running attempts carry on. */
1135
+ private startHolds;
1136
+ private reviewApproved;
1137
+ private autoMergeBlocked;
1138
+ private mergesInFlight;
1139
+ private retryCounts;
1140
+ private spawnCounts;
1141
+ /** A full-plan run a usage-limit pause holds: no new start until the reset resumes it. */
1142
+ private heldOnLimit;
1143
+ /**
1144
+ * A full-plan run that ended with failed tasks and the tasks they block
1145
+ * (ADR-0034). A retry of a failed task continues it, so what it blocked
1146
+ * starts once it passes — without this the retried task ran alone.
1147
+ */
1148
+ private endedOnFailure;
1149
+ /** A Merge all is under way: no ops task starts until it ends (ADR-0020). */
1150
+ private merging;
1151
+ /** The last "waiting for Merge all" notice, so a run paused at a gate says it once, not on every tick. */
1152
+ private gateNotice;
1153
+ /** The workspace's own variables for a task's cwd (ADR-0016); swapped out in tests. */
1154
+ private workspaceEnv;
1155
+ /** What the workspace env has already warned about, so a run says it once, not per task. */
1156
+ private envWarnings;
1157
+ /**
1158
+ * Tasks pulled out of auto-scheduling (user-cancelled or failed to spawn).
1159
+ * They stay 'pending' — "not executed" — but the scheduler skips them until
1160
+ * the user retries or force-starts, which would otherwise loop forever on a
1161
+ * task whose spawn always throws.
1162
+ */
1163
+ private onHold;
1164
+ /**
1165
+ * Tasks Mark complete is landing after it ended their attempt: in progress
1166
+ * with no attempt for as long as that takes, and not orphans for it.
1167
+ */
1168
+ private completing;
1169
+ /** One wait per runner for its usage limit to reset; the tasks it resumes, and when, are on the plan (#84). */
1170
+ private limitWaits;
1171
+ /**
1172
+ * The plan's isolation run and the open run's lifecycle (ADR-0013); see
1173
+ * {@link IsolationRunController}. Read and acted on there directly, except
1174
+ * Merge all: that goes through {@link mergeRun}, which keeps it apart from
1175
+ * ops tasks and starts the tasks its merge lets through.
1176
+ */
1177
+ readonly runs: IsolationRunController;
1178
+ /** A passed attempt's work onto the integration branch, and the conflict repair a landing may need. */
1179
+ private landing;
1180
+ private registry;
1181
+ private workspaceRootFn;
1182
+ private observers;
1183
+ private skillsAt;
1184
+ private previousAttemptFromLog;
1185
+ private runSettings;
1186
+ constructor(deps: TaskOrchestratorDeps);
1187
+ /**
1188
+ * The composition root: resolves every optional collaborator and wires the
1189
+ * isolation listener that the constructor cannot, because it needs the
1190
+ * instance's own (private) emit seam. Nothing else in the
1191
+ * codebase constructs an orchestrator, so this is the one place defaults
1192
+ * live — a host or test supplies what it cares about and gets the rest.
1193
+ */
1194
+ static compose(options: TaskOrchestratorOptions): TaskOrchestrator;
1195
+ /** Advisory silence timestamp for a task's live runner, or null if not idle. */
1196
+ getIdleSince(taskId: string): string | null;
1197
+ /**
1198
+ * What an ops retry is told about the attempt before. The saved log is read
1199
+ * whether or not the session was reloaded, so both behave alike; the output
1200
+ * buffer is the fallback for a runner that has no log. A reload clears the
1201
+ * attempt count along with the buffer, so a retry made since is what says
1202
+ * there was an attempt to report on.
1203
+ */
1204
+ private opsPreviousAttempt;
1205
+ /** Recent clean output of a task's latest attempt, running or ended; null if it never ran. */
1206
+ getLiveOutput(taskId: string, opts: LiveTailOptions): LiveTail | null;
1207
+ get storeInstance(): PlanStore;
1208
+ /**
1209
+ * The variables a task's agent gets from its workspace. What cannot be
1210
+ * applied is said once — silently starting without them is how agents ran
1211
+ * under the wrong account when an edited `.envrc` was left unallowed.
1212
+ */
1213
+ private envForTask;
1214
+ approveCheckpoint(taskId: string): void;
1215
+ rejectCheckpoint(taskId: string, reason?: string): void;
1216
+ /** What the task's waiting checkpoint asks, whole; undefined once it settles or when none waits. */
1217
+ getCheckpointQuestion(taskId: string): string | undefined;
1218
+ awaitsCheckpoint(taskId: string): boolean;
1219
+ /**
1220
+ * Send a structured task a user message (ADR-0018, M1). Mid-turn it queues
1221
+ * behind the turn; a task waiting for input takes it at once and is back in
1222
+ * progress. Returns the message's id, for {@link removeQueuedTaskMessage}.
1223
+ */
1224
+ sendTaskMessage(taskId: string, text: string): string;
1225
+ /**
1226
+ * Force send (ADR-0023, F1–F3): interrupt the running turn and deliver the
1227
+ * message as the next one, ahead of anything queued, without the task
1228
+ * waiting for input in between. A task already waiting for input just takes it.
1229
+ */
1230
+ forceSendTaskMessage(taskId: string, text: string): string;
1231
+ /**
1232
+ * Run the task runner's own command `/name args` (#66) as a turn of its
1233
+ * own, waiting behind a running turn as a message would. Ordewell carries
1234
+ * it; what it does is the runner's business (#24).
1235
+ */
1236
+ invokeTaskCommand(taskId: string, name: string, args?: string): string;
1237
+ private messageTask;
1238
+ /** Force send a message not yet delivered, queued or handed over; false once it was delivered. */
1239
+ forceSendQueuedTaskMessage(taskId: string, id: string): boolean;
1240
+ /** Take back a message still queued behind a turn; false once it was delivered. */
1241
+ removeQueuedTaskMessage(taskId: string, id: string): boolean;
1242
+ /** Stop a structured task's running turn; it then waits for input like any turn that ends without a `task_complete` call. */
1243
+ interruptTask(taskId: string): Promise<void>;
1244
+ /** What is waiting for a structured task's turn to end; empty for any other task. */
1245
+ getQueuedTaskMessages(taskId: string): QueuedTaskMessage[];
1246
+ private runnerSession;
1247
+ /**
1248
+ * A structured turn that ended without a `task_complete` call (ADR-0018, W1). No
1249
+ * verdict is guessed: the task waits for input, unless a checkpoint in the
1250
+ * same turn already has it waiting, or a queued message went straight out —
1251
+ * the session never passes through idle then, so nothing flickers. The first
1252
+ * turn of an attempt to end that way, cleanly, gets the completion reminder
1253
+ * instead: weaker models often finish the work and report in prose.
1254
+ */
1255
+ private onTurnEnd;
1256
+ subscribe(observer: OrchestratorObserver): () => void;
1257
+ /**
1258
+ * Isolated per observer: one that throws must not take down the scheduler
1259
+ * or stop the remaining observers from hearing the event.
1260
+ */
1261
+ private emit;
1262
+ get isRunning(): boolean;
1263
+ /**
1264
+ * A runner is executing *right now*. Narrower than {@link isRunning}, which
1265
+ * also covers the armed-but-idle scheduler `tick()` deliberately leaves
1266
+ * behind when a plan is paused on a user task, a checkpoint or a hold —
1267
+ * nothing is executing then, so nothing is reading the plan mid-mutation.
1268
+ */
1269
+ get hasLiveWork(): boolean;
1270
+ get isReviewApproved(): boolean;
1271
+ /** The scheduler is driving the plan: ready tasks start on their own as slots free. Narrower than {@link isRunning}. */
1272
+ get isScheduling(): boolean;
1273
+ /**
1274
+ * A full-plan run with nothing starting that something still resumes: a
1275
+ * usage limit's reset, or a retry of a failed task its blocked tasks wait on.
1276
+ */
1277
+ get awaitsResume(): boolean;
1278
+ get status(): 'approved' | 'running' | 'completed';
1279
+ get activeTaskIds(): string[];
1280
+ /** Task id → session id of every attempt whose runner is up; a spawn in flight has no session yet. */
1281
+ get activeSessionMap(): Map<string, string>;
1282
+ getAttempt(taskId: string): TaskAttemptSnapshot | undefined;
1283
+ /** A planner turn holds new starts: what is ready starts when it releases them. */
1284
+ get startsHeld(): boolean;
1285
+ /**
1286
+ * Hold the scheduler's new starts while a planner turn decides what runs
1287
+ * next, so its edits do not race a task that would otherwise start under
1288
+ * it. Running attempts are untouched. The release is idempotent and starts
1289
+ * nothing itself: whoever releases ticks, which starts what became ready.
1290
+ */
1291
+ holdStarts(): () => void;
1292
+ /**
1293
+ * "Merge all"; a settled run that merged everything is cleared up and
1294
+ * forgotten. During a run it merges what has landed and the run goes on:
1295
+ * the tasks waiting at a merge gate for that work then start (ADR-0020).
1296
+ * Never alongside ops work, either way round ({@link mergeExcludes}).
1297
+ */
1298
+ mergeRun(): Promise<IsolationMergeResult>;
1299
+ private mergeLanded;
1300
+ /**
1301
+ * Close the settled run. With merge at gates automatically on, landed work
1302
+ * not merged yet is merged first, through the gate's path, so no surface
1303
+ * hears of a handoff for a run about to merge; one that stopped is handed
1304
+ * over as before.
1305
+ */
1306
+ private closeRun;
1307
+ /**
1308
+ * The run as a surface offers Merge all mid-run (ADR-0020): what has landed
1309
+ * and is not merged, and whether the run is paused at its gates with nothing
1310
+ * else running. Null once everything landed is merged.
1311
+ */
1312
+ runMergeView(): RunMergeView | null;
1313
+ /**
1314
+ * The dependencies an ops or user task waits on at its merge gate: those
1315
+ * whose landed work is not in the user's branch yet (ADR-0020). Empty once
1316
+ * the task has started, and for every other task.
1317
+ */
1318
+ getMergeGate(taskId: string): string[];
1319
+ /** The failed tasks a task not started yet waits on, through its dependencies (ADR-0034); empty when it is not blocked. */
1320
+ getBlockedBy(taskId: string): string[];
1321
+ loadPlan(tasks: readonly Task[], planRunners?: RunnerId[]): void;
1322
+ /**
1323
+ * Adopt an edited plan without letting go of the run in progress. Everything
1324
+ * the scheduler owns — live sessions, holds, retry counts, the verifier and
1325
+ * the review approval — survives, because `loadPlan` clears all of it and a
1326
+ * mid-run edit is not a new run. Only the tasks change.
1327
+ *
1328
+ * The ids with live sessions are defended: one dropped from the edited plan is
1329
+ * carried over (its verdict still has to land somewhere), and one whose status
1330
+ * the snapshot predates is put back to `in_progress` — adopting the snapshot's
1331
+ * "pending" would offer the scheduler work a runner is already doing.
1332
+ *
1333
+ * A failed task stays failed, unlike a plan load's fresh chance: an edit is
1334
+ * not a retry, and the tasks it blocks wait for that retry (ADR-0034).
1335
+ */
1336
+ reconcilePlan(newTasks: Task[], planRunners?: RunnerId[]): void;
1337
+ start(origin?: RunOrigin): Promise<void>;
1338
+ /** A usage-limit pause holds the run: spawning more would spend the same exhausted limit. */
1339
+ private holdOnLimit;
1340
+ stop(): void;
1341
+ /** Name the tasks a stop took down, so none is mistaken for one still at work. */
1342
+ private sayStopped;
1343
+ /**
1344
+ * A task in progress with no attempt has nothing running it: a stop, a plan
1345
+ * load or anything else that ended its attempt left its status behind. It
1346
+ * is put back to not started, and said, so it can be retried rather than
1347
+ * read as running — or, in an isolated run, count as live work forever.
1348
+ */
1349
+ private recoverOrphans;
1350
+ /**
1351
+ * Task ids still live, whose worktrees no clean-up of the whole run may
1352
+ * take: an attempt is running them, or the plan has them in progress or
1353
+ * waiting on the user mid-attempt — at a checkpoint, or for input. One
1354
+ * waiting on the user's decision instead (a conflict, a usage limit, files
1355
+ * an ops task changed) is not: no runner is at work on it, and a clean-up
1356
+ * keeps what it holds on a branch before its worktree goes.
1357
+ */
1358
+ private liveTaskIds;
1359
+ /**
1360
+ * Whether the run may close with its handoff: no task is live. A handoff
1361
+ * tells the user the run has finished, and a Merge all after it clears the
1362
+ * run up, so neither may happen under a task the plan still shows at work.
1363
+ */
1364
+ private get settled();
1365
+ /**
1366
+ * The boundary a verdict crosses: nothing awaits the verifier's listener, so
1367
+ * a throw from reading the output, landing, releasing a worktree or the tick
1368
+ * after would be an unhandled rejection — and an attempt left integrating is
1369
+ * live work forever. One that never settled fails (a repair waits on the
1370
+ * user, as its conflict did) and blocks only its dependents; either way the
1371
+ * run says why and goes on.
1372
+ */
1373
+ private deliverVerdict;
1374
+ /** Work nothing awaits: a rejection is said, not left unhandled. */
1375
+ private unawaited;
1376
+ private onVerdict;
1377
+ private afterVerdict;
1378
+ /**
1379
+ * A repair's verdict never replaces the verdict the task's own work earned,
1380
+ * and a repair that does not land leaves the task waiting on the user —
1381
+ * never a failed task, so never one that blocks its dependents.
1382
+ */
1383
+ private settleRepair;
1384
+ /**
1385
+ * Hand an attempt's verdict to Landing. The attempt stays live while that
1386
+ * settles — it may wait on the module's merge queue — so a cancel, retry or
1387
+ * stop in that window still wins, and waits for the merge before tearing the
1388
+ * worktree down; nothing counts the task as done before its work is on the
1389
+ * integration branch.
1390
+ */
1391
+ private land;
1392
+ /** Settle a task by how its landing went; `onLanded` is what the path that landed it adds to completing it. */
1393
+ private applyLanding;
1394
+ /**
1395
+ * Work that did not land waits on the user — it never fails the task or
1396
+ * blocks its dependents — unless its conflict is owed a repair, which takes
1397
+ * the slot the ending attempt freed and never one more than the run allows.
1398
+ */
1399
+ private settleUnlanded;
1400
+ private landResolved;
1401
+ getReadyTasks(): Task[];
1402
+ private readiness;
1403
+ private mergesAutomatically;
1404
+ /**
1405
+ * The user changed the run settings under an open run (ADR-0030). The
1406
+ * scheduler reads them at each decision it makes from them, so a merge gate
1407
+ * already reached is merged now rather than at the next event. Fire-and-forget
1408
+ * — no caller waits on the change — and its failure is said through
1409
+ * {@link unawaited}, never left unhandled.
1410
+ */
1411
+ runSettingsChanged(): void;
1412
+ private openRun;
1413
+ /**
1414
+ * Open the run a task starts in, and decide whether it isolates if the
1415
+ * task's next attempt needs that ({@link decidesIsolation}). False while a
1416
+ * dirty tree waits on the user; `resume` is what their choice replays.
1417
+ */
1418
+ private openFor;
1419
+ /** The kind a task's next attempt starts as, short of a repair or a continue: what is weighed before one exists. */
1420
+ private nextAttemptKind;
1421
+ /**
1422
+ * Cancel a running (or scheduled) task: kill its session and return it to
1423
+ * 'pending' — "not executed". The task is put on hold so the scheduler
1424
+ * doesn't immediately restart it; Retry / Force Start release the hold.
1425
+ *
1426
+ * The attempt's worktree is kept, as a stopped or failed one is: a runner is
1427
+ * often cancelled because it looked stuck after doing the work, and Mark
1428
+ * complete can still land that work. The next attempt replaces it.
1429
+ */
1430
+ cancelTask(taskId: string): Promise<void>;
1431
+ private cancelAttempt;
1432
+ /**
1433
+ * Let go of a task that is leaving the plan. A live runner is cancelled
1434
+ * as {@link cancelTask} does, but its worktree goes: no task is left to land
1435
+ * it into. A spawn still in flight just loses its attempt,
1436
+ * which is what makes {@link startTask} kill the session it is about to
1437
+ * receive. The id's cross-attempt bookkeeping goes too — a hold or retry
1438
+ * count kept for a task that no longer exists would be inherited by nothing.
1439
+ */
1440
+ releaseTask(taskId: string): Promise<void>;
1441
+ markTaskComplete(taskId: string): Promise<void>;
1442
+ /**
1443
+ * Undo a completion: return the task to "not executed" — pending, verdict and
1444
+ * summary dropped, archive entry removed. It is put on hold like a cancel, so
1445
+ * a running plan does not immediately re-spawn the work the user just
1446
+ * un-marked; Retry / Force Start / Run release the hold. Dependents fall back
1447
+ * to waiting on their own, because the scheduler gates on `isCompleted`.
1448
+ */
1449
+ markTaskIncomplete(taskId: string): Promise<void>;
1450
+ /**
1451
+ * Skip: end a task as `skipped`, with no verdict and nothing of its work
1452
+ * landed. Its worktree is kept, as a failed attempt's is, so the user can
1453
+ * still look at it or Mark complete it later. Its dependents go ahead
1454
+ * without it, and a run that ended on a failure continues for them, as a
1455
+ * retry would continue it. A running task is stopped before it is skipped.
1456
+ */
1457
+ skipTask(taskId: string, reason?: string, origin?: RunOrigin): Promise<void>;
1458
+ private logAndArchive;
1459
+ retryTask(taskId: string, origin?: RunOrigin): Promise<void>;
1460
+ /**
1461
+ * Continue a finished structured task in its saved runner session (ADR-0018,
1462
+ * K1): a retry whose first turn is the user's message, sent to the session
1463
+ * the task ended in rather than a fresh one given its prompt again. The
1464
+ * worktree keeps the same path, where the runner keeps its sessions, and
1465
+ * restores the previous work so that session's memory matches its files.
1466
+ */
1467
+ continueTask(taskId: string, message: string, origin?: RunOrigin): Promise<void>;
1468
+ /**
1469
+ * Keep a task paused on its runner's usage limit from resuming on its own:
1470
+ * it waits for a manual retry, as one whose reset time could not be read
1471
+ * does. What a surface's cancel calls, and what moving the task to another
1472
+ * runner has to call first. False when no resume was scheduled for it.
1473
+ */
1474
+ cancelScheduledResume(taskId: string): boolean;
1475
+ /** Run before writing assignments so cancellation sees the old runner's shared wait. */
1476
+ releaseMovedResume(taskId: string, changes: Partial<Task>): Partial<Task>;
1477
+ /**
1478
+ * A task's attempt stopped on its runner's usage limit, without a
1479
+ * completion call. Failing would paint a red X on a task that only has to
1480
+ * wait, and spawning more tasks would spend the same exhausted limit, so the
1481
+ * task pauses and the run holds. When the runner said when its limit
1482
+ * resets, the task joins its runner's wait and resumes on its own then
1483
+ * (#84); otherwise, or when it was resumed a moment ago and hit the limit
1484
+ * again, it waits for the user to retry it.
1485
+ */
1486
+ private pauseOnLimit;
1487
+ /**
1488
+ * The limit arrived as the turn's error with the runner still up: the
1489
+ * attempt ends here, as its verdict would have ended it, so a resume is a
1490
+ * continue in a fresh process (ADR-0018, L1/K1).
1491
+ */
1492
+ private stopOnLimit;
1493
+ /** The tasks waiting on `runner`'s reset to resume on their own. */
1494
+ private waitingOn;
1495
+ /**
1496
+ * Arm `runner`'s one wait for the latest resume time among its tasks, and
1497
+ * put that time on each of them, so they resume together.
1498
+ */
1499
+ private armLimitWait;
1500
+ /**
1501
+ * The waits a loaded plan still owes. A resume time that passed while
1502
+ * nothing was running — the daemon was down, say — resumes at once.
1503
+ */
1504
+ private rearmLimitWaits;
1505
+ /**
1506
+ * Resume a task paused on its runner's usage limit now, in its saved
1507
+ * session, once the user says the limit has reset early. The limit is the
1508
+ * runner's account's, so every task waiting on that runner's reset resumes
1509
+ * with it, as at the reset itself. `message` follows the reset notice in the
1510
+ * same continuation. A resumed attempt that hits the limit again at once
1511
+ * waits for the user, as an automatic resume's does: that is what keeps a
1512
+ * caller from resuming it round and round.
1513
+ */
1514
+ resumeNow(taskId: string, origin?: RunOrigin, message?: string): Promise<void>;
1515
+ private resumeAfterLimit;
1516
+ /**
1517
+ * Retry a task paused on a usage limit, continuing its saved session where
1518
+ * it left one (ADR-0018, K1), then release the hold the pause put on the
1519
+ * run once nothing else waits on a reset.
1520
+ */
1521
+ private resumeTask;
1522
+ /**
1523
+ * Manually start a single AI task right now, bypassing dependency/readiness
1524
+ * gating (the "force start" affordance on a task card). Reuses the scheduler's
1525
+ * own startTask so a force-started task gets the same augmented prompt, session
1526
+ * tracking, and exit handling — callers must not re-spawn the runner themselves.
1527
+ * No-op if the task is unknown, not an AI task, or already running.
1528
+ */
1529
+ forceStartTask(taskId: string, origin?: RunOrigin): Promise<void>;
1530
+ private refuseOpsDuringMerge;
1531
+ /**
1532
+ * A force start passes a merge gate (ADR-0020), and the surface confirmed it
1533
+ * first; the choice is kept on the task and said, because the work it acts
1534
+ * on is not in the user's branch.
1535
+ */
1536
+ private notePastGate;
1537
+ /**
1538
+ * Run exactly one task outside full-plan scheduling. The active/starting
1539
+ * session still contributes to {@link hasLiveWork} so every surface exposes
1540
+ * Stop and disables Execute Plan, but onVerdict cannot auto-schedule other
1541
+ * tasks because the plan scheduler's `running` flag remains false.
1542
+ */
1543
+ runTask(taskId: string): Promise<void>;
1544
+ getCompletedCount(): number;
1545
+ getTotalCount(): number;
1546
+ isAllComplete(): boolean;
1547
+ isAnyFailed(): boolean;
1548
+ approveReview(origin?: RunOrigin): Promise<void>;
1549
+ tick(): Promise<void>;
1550
+ /** How a run that ended on failures ends: what it finished, what failed, and what that blocks. */
1551
+ /** Skipped work is not completed work, so a run with any says so rather than that everything completed. */
1552
+ private sayFinished;
1553
+ private sayEndedOnFailure;
1554
+ /** Whether the scheduler may start `id` now: it passes every readiness gate and a slot is free. */
1555
+ private stillReady;
1556
+ /**
1557
+ * A run paused at merge gates says so, as plainly as a task that waits on
1558
+ * the user: which tasks wait, and that Merge all lets them go on (ADR-0020).
1559
+ */
1560
+ private sayGate;
1561
+ private startTask;
1562
+ /**
1563
+ * The fallible half of starting a task: resolving its cwd/worktree and
1564
+ * spawning the runner. Resolves false when the attempt never committed —
1565
+ * abandoned because it was ended from under it, or a failed spawn already
1566
+ * unwound (both leave `startTask` with nothing further to announce).
1567
+ */
1568
+ private spawnAttempt;
1569
+ /** For a skill the main checkout has, the folder a worktree only gets once it is committed. */
1570
+ private uncommittedSkillFolder;
1571
+ /**
1572
+ * The roots whose `.ordewell/skills/` an attempt at `cwd` reads: in a repo
1573
+ * group, `cwd` holds each repo — a worktree of it when isolated, the
1574
+ * workspace's own checkout when not — and no folder of its own (ADR-0014).
1575
+ */
1576
+ private skillRoots;
1577
+ /**
1578
+ * A continue whose runner never announced the session it was told to
1579
+ * resume. Nothing was continued, and nothing fresh was started in its place.
1580
+ */
1581
+ private unresumed;
1582
+ /**
1583
+ * A spawn whose attempt ended while it was in flight: kill what it produced
1584
+ * and take back the claim it made. Only the claim — whatever ended the
1585
+ * attempt may have decided the task since (mark complete, cancel, retry).
1586
+ */
1587
+ private abandonSpawn;
1588
+ private tell;
1589
+ private say;
1590
+ /**
1591
+ * Replay the start a dirty tree turned away. `stash` puts the user's tracked
1592
+ * changes on the git stash first, so the run isolates; `shared` runs this one
1593
+ * run in the workspace root, knowingly.
1594
+ */
1595
+ continueBlockedRun(how: 'stash' | 'shared'): Promise<void>;
1596
+ /**
1597
+ * The one way an attempt ends. Dropping the record is itself what
1598
+ * invalidates a spawn still in flight ({@link startTask} kills the session
1599
+ * it receives for an attempt that is no longer current).
1600
+ *
1601
+ * A user interruption also bumps the verifier's generation *before* stopping
1602
+ * the runner: a runner may fire onExit synchronously from
1603
+ * stop(), and if that exit reaches VerdictEngine under the still-valid
1604
+ * generation it delivers a verdict that marks the task 'completed' for one
1605
+ * tick — long enough for the scheduler to start a dependent task. Stop and
1606
+ * load reset the whole verifier themselves.
1607
+ */
1608
+ private endAttempt;
1609
+ /** Kept on the task once the attempt ends, so a continue can resume it after a reload (ADR-0018, K1). */
1610
+ private saveNativeSession;
1611
+ private endAllAttempts;
1612
+ }
1613
+
1614
+ type SerializedTaskStatus = {
1615
+ id: string;
1616
+ status: Task['status'];
1617
+ verdict: {
1618
+ outcome: 'pass' | 'fail';
1619
+ reason: string;
1620
+ checks: Verdict['checks'];
1621
+ } | null;
1622
+ /** Advisory silence timestamp from VerdictEngine — not part of task status semantics. */
1623
+ idleSince?: string | null;
1624
+ /** Absent unless the plan has an isolation run, so a shared-root plan's updates are unchanged. */
1625
+ isolation?: TaskIsolation;
1626
+ /** What an `awaiting_user` task waits on, when it was saved (ADR-0018, W1). */
1627
+ awaitingReason?: AwaitingReason;
1628
+ /** When a task paused on a usage limit resumes on its own (ISO time); absent when it waits for a manual retry. */
1629
+ resumeAt?: string;
1630
+ /** Why a `skipped` task was skipped; absent when no reason was given, and on any other status. */
1631
+ skipReason?: string;
1632
+ /** The whole question of the checkpoint the task waits at; absent when it waits at none. */
1633
+ checkpoint?: string;
1634
+ /** Messages waiting for a structured task's turn to end, oldest first; absent when there are none. */
1635
+ queued?: QueuedTaskMessage[];
1636
+ /** Set when the task can be continued in its saved runner session (ADR-0018, K1); the id itself stays in the daemon. */
1637
+ continuable?: true;
1638
+ /**
1639
+ * How many of a structured task's runner requests wait for an answer
1640
+ * (ADR-0018, A1) — "waiting for approval", which leaves `status` alone.
1641
+ * Absent when none do.
1642
+ */
1643
+ awaitingApproval?: number;
1644
+ /**
1645
+ * The dependencies an ops or user task waits on at its merge gate: their
1646
+ * work has landed but is not merged into the user's branch (ADR-0020).
1647
+ * Absent when it waits for no Merge all.
1648
+ */
1649
+ mergeGate?: string[];
1650
+ /** The dependencies, by title, a force start went past the merge gate of (ADR-0020). */
1651
+ forcedPastGate?: string[];
1652
+ attemptUsage?: AttemptUsage[];
1653
+ };
1654
+ /**
1655
+ * Landed work not yet in the user's branch. `paused` means the running plan
1656
+ * waits at a merge gate with no attempts active.
1657
+ */
1658
+ interface RunMergeView {
1659
+ waitingAtGate: boolean;
1660
+ paused: boolean;
1661
+ repos: IsolationHandoff['repos'];
1662
+ landed: IsolationHandoff['landed'];
1663
+ }
1664
+ type SerializedTask = {
1665
+ id: string;
1666
+ order: number;
1667
+ title: string;
1668
+ type: string;
1669
+ /** Without it a plan broadcast after a run reads as every task pending. */
1670
+ status: Task['status'];
1671
+ description: string;
1672
+ dependencies: string[];
1673
+ assignedRunner: RunnerId;
1674
+ assignedModel: Task['assignedModel'] | null;
1675
+ taskMode: string;
1676
+ prompt: string | null;
1677
+ subtasks: SerializedTask[];
1678
+ userSteps: Task['userSteps'];
1679
+ autonomy: Task['autonomy'];
1680
+ sliceType: Task['sliceType'];
1681
+ userStoriesCovered: Task['userStoriesCovered'];
1682
+ /** As on {@link SerializedTaskStatus}: only while the task is `awaiting_user`. */
1683
+ awaitingReason?: AwaitingReason;
1684
+ resumeAt?: string;
1685
+ /** As on {@link SerializedTaskStatus}: only while the task is `skipped`. */
1686
+ skipReason?: string;
1687
+ /** Set only on an ops task (ADR-0020). */
1688
+ ops?: true;
1689
+ /** Task skills attached by name; absent when none. */
1690
+ skills?: string[];
1691
+ /** Set only on a task an Act supervisor added (ADR-0029). */
1692
+ addedBy?: 'supervisor';
1693
+ attemptUsage?: AttemptUsage[];
1694
+ };
1695
+ /** Skill loads go out as notices: their bodies are for the planner, and stay in the session. */
1696
+ type SerializedConversationMessage = Omit<ConversationMessage, 'skill'> & {
1697
+ skill?: SkillLoadNotice;
1698
+ };
1699
+ type SerializedPendingTurn = Omit<PendingPlanTurn, 'skills'> & {
1700
+ skills?: SkillLoadNotice[];
1701
+ };
1702
+ /**
1703
+ * A task as a surface is sent it: its attempt's skills are notices, whole in
1704
+ * the session only, and its runner session id stays there too — no surface shows it,
1705
+ * and only a continue reads the runner's own session id.
1706
+ */
1707
+ type SurfaceTask<T extends Task = Task> = Omit<T, 'attemptSkills' | 'subtasks' | 'runnerSessionId'> & {
1708
+ attemptSkills?: TaskSkillNotice[];
1709
+ subtasks: SurfaceTask[];
1710
+ };
1711
+ /** A plan as the daemon and the VS Code webview are sent it: the session's own state, minus every skill body. */
1712
+ type SurfacePlan = Omit<LegacyPlanState, 'tasks' | 'conversationHistory' | 'pendingTurns' | 'queuedMessages'> & {
1713
+ tasks: SurfaceTask[];
1714
+ conversationHistory?: SerializedConversationMessage[];
1715
+ pendingTurns?: SerializedPendingTurn[];
1716
+ };
1717
+ /** The phase-tagged shape of {@link SurfacePlan}. */
1718
+ type SurfacePlanState = (Omit<Extract<PlanState, {
1719
+ phase: 'planning';
1720
+ }>, 'pendingTasks'> & {
1721
+ pendingTasks: SurfaceTask[];
1722
+ }) | (Omit<Extract<PlanState, {
1723
+ phase: 'executing';
1724
+ }>, 'pendingTasks' | 'executionLog'> & {
1725
+ pendingTasks: SurfaceTask[];
1726
+ executionLog: SurfaceTask<TaskSnapshot>[];
1727
+ });
1728
+ type SerializedPlan = {
1729
+ runSettings?: LegacyPlanState['runSettings'];
1730
+ tasks: SerializedTask[];
1731
+ runners: RunnerId[];
1732
+ generatedAt: string;
1733
+ conversationHistory?: SerializedConversationMessage[];
1734
+ prdMarkdown?: string;
1735
+ };
1736
+ /** How a planner turn ended: the reply kind it settled on, a user stop, or a failure. */
1737
+ type PlannerTurnOutcome = 'message' | 'plan' | 'task_ops' | 'stopped' | 'error';
1738
+ /**
1739
+ * Everything a session tells its surfaces, over one broadcast seam.
1740
+ *
1741
+ * A planner turn (#47) streams between `planner_turn_started` and
1742
+ * `planner_turn_ended` with the same `turnId`; every turn-scoped message in
1743
+ * between carries it. The stream is provisional and the settled messages are
1744
+ * authoritative:
1745
+ * - `planner_message` replaces the streamed text of the turn's final segment —
1746
+ * a surface drops what it accumulated and shows the message instead.
1747
+ * - A reply that is a JSON envelope (plan, taskOps, taskQuery) never arrives
1748
+ * as `planner_text_delta`; it streams as `plan_token`, the "building plan"
1749
+ * display.
1750
+ * - A subagent's own text never appears in the reply; its activity arrives
1751
+ * tagged with its `subagentId`.
1752
+ */
1753
+ type SessionMessage =
1754
+ /**
1755
+ * The plan, whole. `turnId` names the planner turn whose commit this
1756
+ * broadcast carries, so the turn's building plan becomes the marker; it is
1757
+ * absent on every other broadcast of the plan.
1758
+ */
1759
+ {
1760
+ type: 'plan_generated';
1761
+ plan: SerializedPlan;
1762
+ goal: string;
1763
+ runners: RunnerId[];
1764
+ turnId?: string;
1765
+ }
1766
+ /**
1767
+ * The settled reply of a planner turn, emitted by the session once the turn
1768
+ * is classified. Authoritative over any `planner_text_delta` of its turn's
1769
+ * final segment. `turnId` is absent for replies sent outside a streamed turn.
1770
+ * `supervisor`: the supervisor said it in a wake-up turn (ADR-0029).
1771
+ */
1772
+ | {
1773
+ type: 'planner_message';
1774
+ content: string;
1775
+ timestamp: string;
1776
+ turnId?: string;
1777
+ supervisor?: true;
1778
+ }
1779
+ /**
1780
+ * A planner turn began. Emitted once per turn by whoever runs the turn,
1781
+ * before any other message carrying its `turnId`. `prompt` is the user's
1782
+ * message, as typed, when the turn answers one; `skills` are what its
1783
+ * `/name` tokens loaded, absent when none did. `wakeUps` are the wake-up
1784
+ * digests the turn delivers, shown ahead of the prompt; `supervisor` marks a
1785
+ * wake-up turn, which no user message opened (ADR-0029). `forced`: the
1786
+ * prompt was force sent, and this turn replaces the one it stopped.
1787
+ */
1788
+ | {
1789
+ type: 'planner_turn_started';
1790
+ turnId: string;
1791
+ prompt?: string;
1792
+ skills?: SkillLoadNotice[];
1793
+ wakeUps?: string[];
1794
+ supervisor?: true;
1795
+ forced?: true;
1796
+ } | {
1797
+ type: 'planner_skill_loaded';
1798
+ turnId: string;
1799
+ skill: SkillLoadNotice;
1800
+ }
1801
+ /**
1802
+ * A planner turn is over; nothing more carries its `turnId`. Emitted exactly
1803
+ * once per `planner_turn_started`, stop and failure included, after the
1804
+ * turn's `planner_message` when it has one. `forceSent`: a force send
1805
+ * stopped it, and the forced turn follows — the user did not stop the
1806
+ * conversation, so a surface keeps the prompts it holds (ADR-0023, F2).
1807
+ */
1808
+ | {
1809
+ type: 'planner_turn_ended';
1810
+ turnId: string;
1811
+ outcome: PlannerTurnOutcome;
1812
+ forceSent?: true;
1813
+ }
1814
+ /**
1815
+ * Reply prose as it streams, appended in order within its segment. A segment
1816
+ * is one continuous run of model text; text before a tool call is its own
1817
+ * segment, and a later segment never rewrites an earlier one. Never carries
1818
+ * a JSON envelope or a subagent's text (see the union's invariants).
1819
+ */
1820
+ | {
1821
+ type: 'planner_text_delta';
1822
+ turnId: string;
1823
+ segmentId: string;
1824
+ text: string;
1825
+ }
1826
+ /**
1827
+ * Exposed reasoning as it streams, from the planner or — tagged with
1828
+ * `subagentId` — from one of its subagents. Never part of the reply. The one
1829
+ * thinking message for every backend: `segmentId` is set only where the
1830
+ * backend streams thinking in segments (the API loops; harness planners do
1831
+ * not), and `turnId` is absent for thinking outside a turn (one-shot plans).
1832
+ */
1833
+ | {
1834
+ type: 'planner_thinking_delta';
1835
+ turnId?: string;
1836
+ segmentId?: string;
1837
+ subagentId?: string;
1838
+ text: string;
1839
+ }
1840
+ /**
1841
+ * Text streamed for an attempt the turn discarded (a corrective retry) is
1842
+ * taken back: a surface removes it. With `segmentId`, only that segment;
1843
+ * without, all of the turn's text not yet settled by a `planner_message`.
1844
+ * A segment that streamed to the plan display (`plan_token`) takes the
1845
+ * turn's building plan with it.
1846
+ */
1847
+ | {
1848
+ type: 'planner_text_retracted';
1849
+ turnId: string;
1850
+ segmentId?: string;
1851
+ }
1852
+ /**
1853
+ * The planner's running usage for the session, subagents included, emitted
1854
+ * after a model call reports usage. `totals` already contains every
1855
+ * `bySubagent` entry. `contextFill` is the last planner prompt against the
1856
+ * model's window, omitted when the window is unknown. Cost appears only as
1857
+ * reported by a provider or runner (see `UsageRecord`).
1858
+ */
1859
+ | {
1860
+ type: 'planner_usage';
1861
+ byModel?: ModelUsage[];
1862
+ model?: string;
1863
+ turnId?: string;
1864
+ totals: UsageTotals;
1865
+ bySubagent?: Record<string, UsageTotals>;
1866
+ contextFill?: {
1867
+ usedTokens: number;
1868
+ windowTokens: number;
1869
+ };
1870
+ }
1871
+ /**
1872
+ * A subagent began work on `brief`. Emitted by the planner backend that
1873
+ * spawned it (ADR-0005 research agents, or a harness planner's own), before
1874
+ * any message tagged with its `subagentId`.
1875
+ */
1876
+ | {
1877
+ type: 'subagent_started';
1878
+ turnId?: string;
1879
+ subagentId: string;
1880
+ brief: string;
1881
+ model?: string;
1882
+ }
1883
+ /**
1884
+ * A subagent is done; nothing more is tagged with its `subagentId`. Emitted
1885
+ * once per `subagent_started`. `digest` is what it handed back to the
1886
+ * planner; `usage` is its own share, already counted in `planner_usage`.
1887
+ */
1888
+ | {
1889
+ type: 'subagent_finished';
1890
+ turnId?: string;
1891
+ subagentId: string;
1892
+ outcome: SubagentOutcome;
1893
+ digest: string;
1894
+ usage?: UsageTotals;
1895
+ } | {
1896
+ type: 'status_update';
1897
+ tasks: SerializedTaskStatus[];
1898
+ runMerge?: RunMergeView;
1899
+ usage?: PlanUsage;
1900
+ } | {
1901
+ type: 'review_needed';
1902
+ tasks: SerializedTask[];
1903
+ } | {
1904
+ type: 'review_approved';
1905
+ } | {
1906
+ type: 'checkpoint';
1907
+ taskId: string;
1908
+ taskTitle: string;
1909
+ summary: string;
1910
+ } | {
1911
+ type: 'execution_complete';
1912
+ summary: ExecutionSummary;
1913
+ } | {
1914
+ type: 'execution_stopped';
1915
+ } | {
1916
+ type: 'execution_action';
1917
+ record: ExecutionRecord;
1918
+ text: string;
1919
+ } | {
1920
+ type: 'task_updated';
1921
+ taskId: string;
1922
+ changes: Record<string, unknown>;
1923
+ } | {
1924
+ type: 'task_started';
1925
+ taskId: string;
1926
+ order: number;
1927
+ title: string;
1928
+ runner: RunnerId;
1929
+ modelId?: string;
1930
+ } | {
1931
+ type: 'task_output';
1932
+ taskId: string;
1933
+ text: string;
1934
+ }
1935
+ /**
1936
+ * A structured task's log as it happens (ADR-0018, P1): the next events of
1937
+ * the task's attempt `attempt`, in order — the same ones appended to that
1938
+ * attempt's file, so a surface folding these and one replaying the file
1939
+ * draw the same blocks.
1940
+ */
1941
+ | {
1942
+ type: 'task_log';
1943
+ taskId: string;
1944
+ attempt: number;
1945
+ events: TaskLogEvent[];
1946
+ } | {
1947
+ type: 'isolation_blocked';
1948
+ reason: 'dirty';
1949
+ repos?: string[];
1950
+ message: string;
1951
+ } | {
1952
+ type: 'isolation_handoff';
1953
+ repos: IsolationHandoff['repos'];
1954
+ landed: IsolationHandoff['landed'];
1955
+ deferredTo?: IsolationHandoff['deferredTo'];
1956
+ } | {
1957
+ type: 'isolation_merge';
1958
+ result: IsolationMergeResult;
1959
+ trigger?: AutoMergeTrigger;
1960
+ } | {
1961
+ type: 'planner_liveness';
1962
+ } | {
1963
+ type: 'research_step';
1964
+ tool: string;
1965
+ toolLabel?: string;
1966
+ args: string;
1967
+ subagentId?: string;
1968
+ toolCallId?: string;
1969
+ turnId?: string;
1970
+ } | {
1971
+ type: 'plan_token';
1972
+ token: string;
1973
+ turnId?: string;
1974
+ segmentId?: string;
1975
+ } | {
1976
+ type: 'research_step_done';
1977
+ step: ResearchStep;
1978
+ subagentId?: string;
1979
+ turnId?: string;
1980
+ } | {
1981
+ type: 'approval_request';
1982
+ id: string;
1983
+ kind: ApprovalKind;
1984
+ subject: string;
1985
+ scope: string;
1986
+ detail?: string;
1987
+ turnId?: string;
1988
+ } | {
1989
+ type: 'approval_settled';
1990
+ id: string;
1991
+ granted: boolean;
1992
+ } | {
1993
+ type: 'approval_decided';
1994
+ kind: ApprovalKind;
1995
+ subject: string;
1996
+ scope: string;
1997
+ detail?: string;
1998
+ granted: boolean;
1999
+ source: Exclude<ApprovalSource, 'asked'>;
2000
+ };
2001
+ /**
2002
+ * A plan a surface read whole rather than off the message stream: a route's
2003
+ * answer, a loaded session. Folds into a {@link RunView} as a plan refresh.
2004
+ */
2005
+ type PlanSnapshot = {
2006
+ type: 'plan_snapshot';
2007
+ plan: SurfacePlan | SurfacePlanState;
2008
+ };
2009
+ /** What only the status stream says about a task; a plan refresh keeps these as they were. */
2010
+ type RunTaskStatus = Partial<Pick<SerializedTaskStatus, 'verdict' | 'idleSince' | 'isolation' | 'checkpoint' | 'queued' | 'continuable' | 'awaitingApproval' | 'mergeGate' | 'forcedPastGate'>>;
2011
+ /** A task in the run view: the plan's fields, then the status stream's. */
2012
+ type RunTask = Omit<SerializedTask, 'subtasks'> & RunTaskStatus & {
2013
+ subtasks: RunTask[];
2014
+ };
2015
+ /**
2016
+ * A session's run as every surface draws it (the [run view](../../../../CONTEXT.md)):
2017
+ * folded from {@link SessionMessage}s and plan snapshots by `reduceRun`, or
2018
+ * taken whole from `Session.runSnapshot()` by a surface that joins late.
2019
+ */
2020
+ interface RunView {
2021
+ /** In plan order, subtasks nested under their parent. */
2022
+ tasks: RunTask[];
2023
+ runSettings?: RunSettings;
2024
+ runMerge: RunMergeView | null;
2025
+ usage?: PlanUsage;
2026
+ handoff: IsolationHandoff | null;
2027
+ mergeResult: IsolationMergeResult | null;
2028
+ }
2029
+ type RunInput = SessionMessage | PlanSnapshot;
2030
+ /**
2031
+ * A line a run wants the user to read — how it isolates, what it shares. Not a
2032
+ * {@link SessionMessage}: hosts that show notices as toasts already do, and
2033
+ * widening the union would break every exhaustive switch over it. A host with
2034
+ * no toast channel (the daemon) hands these to its clients by its own means.
2035
+ */
2036
+ type SessionNotice = {
2037
+ type: 'notice';
2038
+ level: 'info' | 'warn' | 'error';
2039
+ message: string;
2040
+ };
2041
+ type SessionBroadcaster = (msg: SessionMessage) => void;
2042
+ declare function serializeTask(t: SurfaceTask): SerializedTask;
2043
+ declare function serializeTaskStatus(t: Task, idleSince?: string | null, isolation?: TaskIsolation | null, queued?: readonly QueuedTaskMessage[], awaitingApproval?: number, mergeGate?: readonly string[], checkpoint?: string): SerializedTaskStatus;
2044
+ /** The one place a plan's skill bodies come off before it leaves the session: every wire path that carries plan state goes through here. */
2045
+ declare function surfacePlan({ queuedMessages: _carriedOverAtLoad, ...plan }: LegacyPlanState): SurfacePlan;
2046
+ declare function surfacePlanState(state: PlanState): SurfacePlanState;
2047
+ declare function serializePlan(plan: LegacyPlanState): SerializedPlan;
2048
+ /**
2049
+ * What a finished run did, task by task: `blocked` are those a failure kept
2050
+ * from starting (ADR-0034); `skipped` ended without a verdict and landed
2051
+ * nothing, so they are neither completed nor failed.
2052
+ */
2053
+ interface ExecutionSummary {
2054
+ total: number;
2055
+ completed: number;
2056
+ failed: number;
2057
+ blocked: number;
2058
+ skipped: number;
2059
+ }
2060
+ declare function executionSummary(tasks: readonly Task[]): ExecutionSummary;
2061
+ declare const CHECKPOINT_TRUNCATE_LENGTH = 120;
2062
+ /** Truncate a checkpoint summary to a single line of at most CHECKPOINT_TRUNCATE_LENGTH chars. */
2063
+ declare function truncateCheckpointSummary(summary: string): string;
2064
+
2065
+ /**
2066
+ * Task references: the one grammar for naming a task in a plan, both ways.
2067
+ * A label is rendered from a task's order under its parent ("2", "2.1"), and
2068
+ * a reference written by anyone — the planner, a tool call, a command — is
2069
+ * resolved back against the plan tree. Pure and import-free so a
2070
+ * browser-bundled surface can use it through `@ordewell/core/plan-utils`.
2071
+ */
2072
+ /** The shape a reference resolves over: the plan's task tree, or a surface's projection of it. */
2073
+ interface RefTask<T> {
2074
+ id: string;
2075
+ order: number;
2076
+ title: string;
2077
+ subtasks?: readonly T[];
2078
+ }
2079
+ /**
2080
+ * The parent a label extends. A label is the full path ("3.2.1"), so a parent
2081
+ * below the top level carries the label it was itself reached by; without one
2082
+ * the parent's order stands for it, which is right for a top-level parent.
2083
+ */
2084
+ interface RefParent {
2085
+ order: number;
2086
+ label?: string;
2087
+ }
2088
+ /** Dotted path label for a task, e.g. "2.1" for the first subtask of task 2, "2.1.3" one level down. */
2089
+ declare function taskOrderLabel(task: {
2090
+ order: number;
2091
+ }, parent?: RefParent): string;
2092
+ /** How a surface refers to a task in prose: "#2", or "#2.1" for a subtask. */
2093
+ declare function taskRef(task: {
2094
+ order: number;
2095
+ }, parent?: RefParent): string;
2096
+ /** A task named in full, as a confirmation or a picker title does: "#2 Deploy". */
2097
+ declare function titledTaskRef(task: {
2098
+ order: number;
2099
+ title: string;
2100
+ }, parent?: RefParent): string;
2101
+ /**
2102
+ * The reference {@link taskRef} renders for the task a plan tree holds, by id
2103
+ * — its full path, dotted, for a subtask. Undefined when the tree holds no such task.
2104
+ */
2105
+ declare function taskRefOf<T extends RefTask<T>>(tasks: readonly T[], id: string): string | undefined;
2106
+ /**
2107
+ * The reference and title {@link titledTaskRef} renders for the task a plan
2108
+ * tree holds, by id — its full path, dotted, for a subtask. Undefined when the
2109
+ * tree holds no such task.
2110
+ */
2111
+ declare function titledTaskRefOf<T extends RefTask<T>>(tasks: readonly T[], id: string): string | undefined;
2112
+ /**
2113
+ * Resolve a written reference to a task at any depth: an id, then an order
2114
+ * label with or without "#" ("#2", "2", "#2.1"), then an exact title.
2115
+ *
2116
+ * An order label is read against the tree, never a flattened list: subtask
2117
+ * orders restart at 1 under each parent, so "#2" always means top-level task 2
2118
+ * and a subtask is reachable only by its dotted path ("#2.1", "#2.1.3").
2119
+ */
2120
+ declare function resolveTaskRef<T extends RefTask<T>>(tasks: readonly T[], ref: string | number): T | undefined;
2121
+
2122
+ /**
2123
+ * Pure, no-imports helper so a browser surface can format a conflict's files
2124
+ * without pulling `isolationRecord` — and its `path` import — into a webview
2125
+ * bundle. Kept out of `isolationRecord.ts` for exactly that reason.
2126
+ */
2127
+ declare function capConflictFiles(files: string[], max?: number): string;
2128
+
2129
+ /** How a run is started, watched and merged: the user's, not a plan's (ADR-0030). */
2130
+ interface GlobalRunSettings {
2131
+ supervisorLevel: SupervisorLevel;
2132
+ mergeAtGatesAutomatically: boolean;
2133
+ /** Start a plan when the planner submits it, approved as if the user had pressed Execute. */
2134
+ autorun: boolean;
2135
+ /** Retries an Act supervisor may make of one task in a run (a loop guard). */
2136
+ supervisorRetryLimit: number;
2137
+ /** Reads a wake-up turn may make, in place of a user message's caps. */
2138
+ wakeUpReadBudget: number;
2139
+ }
2140
+ declare const RUN_SETTINGS_DEFAULTS: Readonly<GlobalRunSettings>;
2141
+ declare class RunSettingsError extends Error {
2142
+ constructor(message: string);
2143
+ }
2144
+ interface UserSettings {
2145
+ modelAllowlist?: Record<string, string[]>;
2146
+ /** Last model (and its thinking effort) the user chose for each planner backend, keyed by AiProvider id. */
2147
+ plannerModels?: Record<string, {
2148
+ model: string;
2149
+ effort?: string;
2150
+ }>;
2151
+ /**
2152
+ * Runners the user picked for planning. Absent — not empty — is what falls
2153
+ * back to the environment's defaults; `[]` is a deliberate "none of them".
2154
+ */
2155
+ enabledRunners?: string[];
2156
+ /** Full (true) or Guarded (false), ADR-0001; Full until the user chooses. */
2157
+ autonomousMode?: boolean;
2158
+ supervisorLevel?: SupervisorLevel;
2159
+ mergeAtGatesAutomatically?: boolean;
2160
+ autorun?: boolean;
2161
+ supervisorRetryLimit?: number;
2162
+ wakeUpReadBudget?: number;
2163
+ }
2164
+ /**
2165
+ * The model allowlist, remembered planner models and enabled runners are shared
2166
+ * across processes by default. `ORDEWELL_SETTINGS_PATH` lets independent runs
2167
+ * use separate files: otherwise a write in one run would change another's
2168
+ * settings when `getAll()` detects the new mtime.
2169
+ */
2170
+ declare function getSettingsPath(): string;
2171
+ /** The run settings `settings` holds, each one it leaves out at its default. */
2172
+ declare function runSettingsOf(settings: UserSettings): GlobalRunSettings;
2173
+ declare class SettingsService {
2174
+ private filePath;
2175
+ private cache;
2176
+ private cachedMtimeMs;
2177
+ constructor(filePath?: string);
2178
+ getAll(): UserSettings;
2179
+ private fileMtimeMs;
2180
+ getModelAllowlist(runner: string): string[] | undefined;
2181
+ setModelAllowlist(runner: string, ids: string[] | undefined): void;
2182
+ getPlannerModel(provider: string): {
2183
+ model: string;
2184
+ effort?: string;
2185
+ } | undefined;
2186
+ setPlannerModel(provider: string, entry: {
2187
+ model: string;
2188
+ effort?: string;
2189
+ } | undefined): void;
2190
+ getEnabledRunners(): string[] | undefined;
2191
+ setEnabledRunners(ids: string[]): void;
2192
+ /**
2193
+ * Full or Guarded (ADR-0001), as saved here (ADR-0030). A value set in the
2194
+ * environment wins, so a script or CI run can fix its own without touching
2195
+ * the user's file.
2196
+ */
2197
+ autonomousMode(): boolean;
2198
+ /** Whether `ORDEWELL_AUTONOMOUS_MODE` decides the level, whatever this file says. */
2199
+ autonomousModeOverridden(): boolean;
2200
+ setAutonomousMode(enabled: boolean): void;
2201
+ getRunSettings(): GlobalRunSettings;
2202
+ /** Checks every value before writing any, so a refused change saves nothing. */
2203
+ updateRunSettings(changes: Partial<GlobalRunSettings>): GlobalRunSettings;
2204
+ private load;
2205
+ private persist;
2206
+ }
2207
+
2208
+ declare const EMPTY_RUN: RunView;
2209
+ declare function reduceRun(view: RunView, input: RunInput): RunView;
2210
+
2211
+ /** What the blocked rule reads of a task; structural, so a surface's own task shape answers it too. */
2212
+ interface BlockingSubject {
2213
+ id: string;
2214
+ status: string;
2215
+ dependencies: readonly string[];
2216
+ subtasks?: readonly BlockingSubject[];
2217
+ }
2218
+ /**
2219
+ * The blocked rule (ADR-0034): a task not started yet is blocked while a
2220
+ * dependency failed or is itself blocked. One already at work — force-started,
2221
+ * or waiting on the user — is not, and does not pass a block on. Each
2222
+ * blocked task maps to the failed tasks it waits on, in dependency order and
2223
+ * named once; an unblocked task is absent. Nothing is stored, so a retry that
2224
+ * passes is all it takes to lift a block.
2225
+ *
2226
+ * Takes the plan tree or a flat list; a subtask follows the same rule.
2227
+ */
2228
+ declare function blockingFailures(tasks: readonly BlockingSubject[]): Map<string, string[]>;
2229
+
2230
+ /**
2231
+ * @param toolLabel The agent's own name for the tool, when a harness planner
2232
+ * produced the call (ADR-0009). It replaces the member name in the summary, so
2233
+ * the timeline says `Edit` rather than the catch-all `agent_tool` — and says
2234
+ * `WebFetch` rather than claiming a shell command ran.
2235
+ */
2236
+ declare function summarizeToolCall(tool: string, argsJson: string, toolLabel?: string): string;
2237
+ /**
2238
+ * Classify a tool outcome from its result text and success flag, so a surface
2239
+ * can render a refused `rm` or a denied `npm test` distinctly from a
2240
+ * successful run without re-deriving the refusal signatures per surface. The
2241
+ * refusal/denial strings come from `commandPolicy.ts`, `BaseFileSystem.ts`,
2242
+ * `webReadPolicy.ts` and the research-subagent wrapper — keeping the matching
2243
+ * here means the signatures stay in one place alongside the human summary.
2244
+ */
2245
+ declare function classifyOutcome(success: boolean, output: string): ResearchStepOutcome;
2246
+
2247
+ /**
2248
+ * What a surface draws for one planner conversation (#51): an ordered list of
2249
+ * display blocks, built once in core from the `SessionMessage` stream and drawn
2250
+ * per surface. Every block carries an `id` that stays the same for as long as
2251
+ * the block exists, so a surface can key its own UI state on it — whether a
2252
+ * block is expanded is that UI state, and deliberately not part of a block.
2253
+ */
2254
+ type DisplayBlock = MessageBlock | ThinkingDisplayBlock | ToolBlock | SubagentBlock | ApprovalBlock | PlanBlock | SkillLoadBlock | UsageBlock | CommandBlock | CompactionBlock | RunnerLoadedBlock;
2255
+ /**
2256
+ * `agent` is a task runner speaking in a structured task's log (ADR-0018); the
2257
+ * planner is `planner`. In a wake-up turn (ADR-0029) the planner speaks as
2258
+ * `supervisor`, and `wake_up` is the run update that woke it.
2259
+ */
2260
+ type MessageRole = 'user' | 'planner' | 'agent' | 'supervisor' | 'wake_up' | 'system' | 'error';
2261
+ interface MessageBlock {
2262
+ type: 'message';
2263
+ id: string;
2264
+ role: MessageRole;
2265
+ text: string;
2266
+ /** Deltas are still arriving. */
2267
+ streaming: boolean;
2268
+ turnId?: string;
2269
+ /**
2270
+ * Present while the text is the streamed segment of a reply rather than a
2271
+ * settled message: provisional, replaced by the turn's `planner_message` when
2272
+ * it is the final segment, and never saved to the transcript.
2273
+ */
2274
+ segmentId?: string;
2275
+ /** A user's message: the skills its `/name` tokens loaded, for a surface to mark those tokens (see `loadedSkillTokens`). */
2276
+ skills?: readonly string[];
2277
+ /** A user's message that was force sent ("Send now"), stopping the turn before it (ADR-0023). */
2278
+ forced?: true;
2279
+ }
2280
+ interface ThinkingDisplayBlock {
2281
+ type: 'thinking';
2282
+ id: string;
2283
+ text: string;
2284
+ streaming: boolean;
2285
+ turnId?: string;
2286
+ segmentId?: string;
2287
+ /** Set when a subagent thought it; the block then sits among that subagent's children. */
2288
+ subagentId?: string;
2289
+ }
2290
+ /** The two halves of a Claude Code-style command row: `Name(keyArg)`. */
2291
+ interface ToolHeadline {
2292
+ name: string;
2293
+ keyArg: string;
2294
+ }
2295
+ type ToolStatus = 'pending' | 'ok' | 'error' | 'denied' | 'interrupted';
2296
+ /** What a file edit changed, counted from its diff. */
2297
+ interface DiffStat {
2298
+ added: number;
2299
+ removed: number;
2300
+ }
2301
+ interface ToolBlock {
2302
+ type: 'tool';
2303
+ id: string;
2304
+ toolCallId?: string;
2305
+ tool: string;
2306
+ toolLabel?: string;
2307
+ headline: ToolHeadline;
2308
+ /** The arguments exactly as announced (JSON), for the expanded view. */
2309
+ args: string;
2310
+ status: ToolStatus;
2311
+ /** How the call ended, finer than `status`: a refused command and a denied path both read `denied`. */
2312
+ outcome?: ResearchStepOutcome;
2313
+ output: string;
2314
+ /** Every line of `output`, counted as {@link outputPreview} counts them. */
2315
+ outputLineCount: number;
2316
+ /** Present when the call edited a file and `output` is its diff: a surface draws it as one. */
2317
+ diff?: DiffStat;
2318
+ turnId?: string;
2319
+ /**
2320
+ * The subagent this call starts. The call becomes that subagent's block once
2321
+ * the subagent announces itself, so the two never show as separate rows.
2322
+ */
2323
+ spawns?: string;
2324
+ }
2325
+ type SubagentStatus = 'running' | SubagentOutcome;
2326
+ type SubagentChild = ToolBlock | ThinkingDisplayBlock | MessageBlock;
2327
+ interface SubagentBlock {
2328
+ type: 'subagent';
2329
+ id: string;
2330
+ subagentId: string;
2331
+ /** The planner's call that started it, when one was announced. */
2332
+ toolCallId?: string;
2333
+ brief: string;
2334
+ model?: string;
2335
+ status: SubagentStatus;
2336
+ children: readonly SubagentChild[];
2337
+ /** What it handed back to the planner. */
2338
+ digest: string;
2339
+ /** Its own share, already counted in the usage line. */
2340
+ usage?: UsageTotals;
2341
+ turnId?: string;
2342
+ }
2343
+ /** `withdrawn`: a runner's request that went unanswered — the runner cancelled it, or its process ended. */
2344
+ type ApprovalStatus = 'pending' | 'granted' | 'denied' | 'withdrawn';
2345
+ interface ApprovalBlock {
2346
+ type: 'approval';
2347
+ id: string;
2348
+ /** The request's id, which a surface answers through. Absent for a decision nobody was asked about. */
2349
+ approvalId?: string;
2350
+ kind: ApprovalKind;
2351
+ subject: string;
2352
+ scope: string;
2353
+ detail?: string;
2354
+ status: ApprovalStatus;
2355
+ /** Absent while pending. */
2356
+ decidedBy?: ApprovalSource;
2357
+ turnId?: string;
2358
+ /** A runner's request (`runner_tool`) that can also be allowed for the rest of its task. */
2359
+ allowForTask?: boolean;
2360
+ /** Granted for the rest of the task, not this call alone. */
2361
+ forTask?: boolean;
2362
+ /** What a denial told the agent. */
2363
+ note?: string;
2364
+ /** The runner's tool call the request is about. */
2365
+ toolCallId?: string;
2366
+ }
2367
+ type PlanMarkerStatus = 'building' | 'generated' | 'updated';
2368
+ interface PlanBlock {
2369
+ type: 'plan';
2370
+ id: string;
2371
+ status: PlanMarkerStatus;
2372
+ /** The plan envelope streamed so far — `parsePartialPlan` reads rows from it. Empty once settled. */
2373
+ text: string;
2374
+ taskCount?: number;
2375
+ turnId?: string;
2376
+ /** While building: the segment whose envelope is streaming, which a retraction of that segment takes back. */
2377
+ segmentId?: string;
2378
+ }
2379
+ /** A skill loaded into the conversation: one line naming it and the SKILL.md that won. Its body is never shown. */
2380
+ interface SkillLoadBlock extends SkillLoadNotice {
2381
+ type: 'skill_load';
2382
+ id: string;
2383
+ turnId?: string;
2384
+ }
2385
+ /** How the turn a runner command opened ended, or `running` while it runs. */
2386
+ type CommandStatus = 'running' | 'done' | 'failed' | 'stopped';
2387
+ /** A runner's own command a user ran in a task (#66): `/name args`, and what it printed. */
2388
+ interface CommandBlock {
2389
+ type: 'command';
2390
+ id: string;
2391
+ name: string;
2392
+ args?: string;
2393
+ status: CommandStatus;
2394
+ /** What a command that runs without the model printed; its other outcomes are the turn's own blocks. */
2395
+ output: string;
2396
+ }
2397
+ /** The runner compacted its own context: asked to (`/compact`) or on its own. */
2398
+ interface CompactionBlock {
2399
+ type: 'compaction';
2400
+ id: string;
2401
+ trigger: 'manual' | 'auto';
2402
+ preTokens?: number;
2403
+ postTokens?: number;
2404
+ }
2405
+ /**
2406
+ * What a task's runner loaded from the user's and the project's own setup, as
2407
+ * its process started: its commands and skills, and its MCP servers. Drawn
2408
+ * collapsed unless the surface shows detail.
2409
+ */
2410
+ interface RunnerLoadedBlock {
2411
+ type: 'runner_loaded';
2412
+ id: string;
2413
+ commands: readonly RunnerCommand[];
2414
+ mcpServers: readonly RunnerMcpServer[];
2415
+ }
2416
+ /** The token line. There is at most one, and it is always the last block. */
2417
+ interface UsageBlock {
2418
+ byModel?: ModelUsage[];
2419
+ model?: string;
2420
+ type: 'usage';
2421
+ id: string;
2422
+ totals: UsageTotals;
2423
+ contextFill?: {
2424
+ usedTokens: number;
2425
+ windowTokens: number;
2426
+ };
2427
+ bySubagent?: Record<string, UsageTotals>;
2428
+ }
2429
+
2430
+ /** What every view over display blocks holds; each view adds its own state beside it. */
2431
+ interface BlockList {
2432
+ readonly blocks: readonly DisplayBlock[];
2433
+ readonly nextId: number;
2434
+ }
2435
+
2436
+ /**
2437
+ * A line a surface adds to the conversation itself rather than receiving from
2438
+ * the session: the user's prompt as it is sent, a notice, an error.
2439
+ */
2440
+ interface LocalEntry {
2441
+ type: 'local_entry';
2442
+ role: 'user' | 'system' | 'error';
2443
+ text: string;
2444
+ }
2445
+ type ConversationInput = SessionMessage | LocalEntry;
2446
+ interface ConversationView extends BlockList {
2447
+ /**
2448
+ * The newest transcript entry the view accounts for. Plan markers and system
2449
+ * notes reach a surface only inside the transcript a `plan_generated`
2450
+ * carries, so entries after this one are what is new in it.
2451
+ */
2452
+ readonly transcriptAt?: string;
2453
+ /** Reloaded arrivals, keyed by pending turn id until the live turn or transcript accounts for them. */
2454
+ readonly pendingInputs?: readonly {
2455
+ input: SerializedPendingTurn;
2456
+ blockIds: readonly string[];
2457
+ }[];
2458
+ }
2459
+ declare const EMPTY_CONVERSATION: ConversationView;
2460
+ /**
2461
+ * Fold one input into the view. Pure and incremental: blocks the input does
2462
+ * not touch keep their identity, and an input that changes nothing returns
2463
+ * `view` itself — surfaces memoize their drawing on that, and text deltas
2464
+ * arrive quickly.
2465
+ */
2466
+ declare function reduceConversation(view: ConversationView, input: ConversationInput): ConversationView;
2467
+
2468
+ /**
2469
+ * The view a saved session reopens with: what its transcript and research log
2470
+ * kept, in the order it happened — messages, notices and the compaction
2471
+ * summary, plan markers, tool calls with each subagent's calls nested under
2472
+ * it, and the token line. Reasoning and streamed text were never saved, so a
2473
+ * reload has none.
2474
+ *
2475
+ * The research log interleaves with the transcript by time. Entries older than
2476
+ * a compaction's first kept message went with the turns it condensed.
2477
+ * Pending inputs follow the landed records in arrival order; they are not
2478
+ * transcript entries until their turn lands.
2479
+ */
2480
+ declare function fromTranscript(conversationHistory: readonly SerializedConversationMessage[] | undefined, researchLog: readonly ResearchLogEntry[] | undefined, plannerUsage?: PlannerUsage, pendingTurns?: readonly SerializedPendingTurn[]): ConversationView;
2481
+
2482
+ /**
2483
+ * What a surface draws for one attempt of a structured task (ADR-0018, O1b):
2484
+ * the same display blocks as the planner conversation, built from the task's
2485
+ * {@link TaskLogEvent}s. No planner turn ids — a task's turns are its own,
2486
+ * one at a time.
2487
+ */
2488
+ interface TaskLogView extends BlockList {
2489
+ /** Messages not yet delivered, oldest first; `handedOver` marks the ones the runner already has. */
2490
+ readonly queued: readonly QueuedTaskMessage[];
2491
+ /** A turn is running. */
2492
+ readonly working: boolean;
2493
+ /** How the last turn ended; absent before the first one does. */
2494
+ readonly lastTurnEnd?: StructuredTurnEnd;
2495
+ /** What the runner reported over the attempt, subagents included. */
2496
+ readonly usage?: PlannerUsage;
2497
+ /**
2498
+ * The block each run of deltas streams into, keyed by stream, so the run's
2499
+ * authoritative copy replaces it instead of repeating it below.
2500
+ */
2501
+ readonly streams: Readonly<Record<string, string>>;
2502
+ /** The turn's latest agent text block, which a failure that repeats it takes the place of. */
2503
+ readonly latestText?: string;
2504
+ }
2505
+ declare const EMPTY_TASK_LOG: TaskLogView;
2506
+ /** A runner's tool request, read the way its call's row reads: `Bash(npm test)`. */
2507
+ declare function runnerToolSubject(tool: string, args: string): string;
2508
+ /**
2509
+ * Fold one task-log event into the view. Pure and incremental, like
2510
+ * `reduceConversation`: an event that changes nothing returns `view` itself,
2511
+ * and an event of a type this build does not know is skipped — a log written
2512
+ * by a newer Ordewell still reads.
2513
+ */
2514
+ declare function reduceTaskLog(view: TaskLogView, event: TaskLogEvent): TaskLogView;
2515
+ /** A whole attempt's events, folded from the start — what a reload shows. */
2516
+ declare function replayTaskLog(events: readonly TaskLogEvent[], from?: TaskLogView): TaskLogView;
2517
+
2518
+ /**
2519
+ * The one-line head of a command row, shared by every surface: the tool's name
2520
+ * and the argument that says what the call is about.
2521
+ *
2522
+ * @param args The call's arguments as announced (JSON). Anything that is not a
2523
+ * JSON object is shown as it came, on one line.
2524
+ * @param toolLabel A harness planner's own name for the tool (ADR-0009).
2525
+ */
2526
+ declare function toolHeadline(tool: string, args: string, toolLabel?: string): ToolHeadline;
2527
+ interface OutputPreview {
2528
+ lines: string[];
2529
+ hiddenLineCount: number;
2530
+ }
2531
+ /** The lines of a tool's output as a reader sees them — what {@link outputPreview} shows and counts. */
2532
+ declare function outputLines(output: string): string[];
2533
+ /** The head of a tool's output for a collapsed row, and how many lines it leaves out. */
2534
+ declare function outputPreview(output: string, maxLines: number): OutputPreview;
2535
+ /**
2536
+ * The lines a diff adds and removes, or null when `output` is not one: every
2537
+ * line a hunk header, context, an addition or a removal, and at least one of
2538
+ * the last two.
2539
+ */
2540
+ declare function diffStat(output: string): DiffStat | null;
2541
+ /** `gap` stands between two hunks, for the lines a diff leaves out. */
2542
+ type DiffRowKind = 'added' | 'removed' | 'context' | 'gap';
2543
+ interface DiffRow {
2544
+ kind: DiffRowKind;
2545
+ /** The line in the file: the old file's for a removal, the new file's otherwise. */
2546
+ line?: number;
2547
+ text: string;
2548
+ }
2549
+ /** A diff as the rows a surface draws, numbered as an editor would number them. */
2550
+ declare function diffRows(output: string): DiffRow[];
2551
+ /** "Added 2 lines, removed 1 line": the summary every surface puts on an edit's row. */
2552
+ declare function diffSummary({ added, removed }: DiffStat): string;
2553
+
2554
+ /**
2555
+ * Prompts the user sent while a planner turn was in flight, waiting to be the
2556
+ * next turn's input. The hold is a plain immutable list, oldest first, so a
2557
+ * pure reducer can keep it in state and a host can keep it in a field — and
2558
+ * either can draw it as is.
2559
+ */
2560
+ type PromptHold = readonly string[];
2561
+ declare const EMPTY_HOLD: PromptHold;
2562
+ /** One prompt taken out of the hold, and what is left behind. */
2563
+ interface TakenPrompt {
2564
+ text: string;
2565
+ rest: PromptHold;
2566
+ }
2567
+ declare function holdPrompt(hold: PromptHold, text: string): PromptHold;
2568
+ /** The prompt a settled turn sends next: the oldest, so they go in the order typed. */
2569
+ declare function drainNext(hold: PromptHold): TakenPrompt | undefined;
2570
+ /**
2571
+ * Takes back the newest prompt — the one the user most likely just regretted —
2572
+ * while the older ones stay queued behind the turn.
2573
+ */
2574
+ declare function unsendLatest(hold: PromptHold): TakenPrompt | undefined;
2575
+ /**
2576
+ * Takes one prompt out by position — a "Send now" on that held row — leaving
2577
+ * every other prompt in the order it was typed.
2578
+ */
2579
+ declare function takeAt(hold: PromptHold, index: number): TakenPrompt | undefined;
2580
+ /**
2581
+ * A stopped turn takes its queue with it: the prompts were written against a
2582
+ * turn that no longer exists, so they come back as one draft to edit and
2583
+ * resend rather than firing off after the stop.
2584
+ */
2585
+ declare function unsendAll(hold: PromptHold): TakenPrompt | undefined;
2586
+ /** Where an unsent prompt lands in the drafting input: above what is being typed, never replacing it. */
2587
+ declare function aheadOfDraft(text: string, draft: string): string;
2588
+
2589
+ /**
2590
+ * The planner turn a surface has open, and the one its user stopped. Kept
2591
+ * beside the view rather than in it: the view is what a turn drew, this is
2592
+ * whether the surface still wants to hear from it.
2593
+ */
2594
+ interface TurnGate {
2595
+ readonly open: string | null;
2596
+ readonly stopped: string | null;
2597
+ }
2598
+ declare const NO_TURN: TurnGate;
2599
+ interface GatedConversation {
2600
+ view: ConversationView;
2601
+ gate: TurnGate;
2602
+ }
2603
+ /**
2604
+ * One input into the view, the stop rule applied: a turn the user stopped is
2605
+ * over on screen, and whatever it streams until the backend notices is
2606
+ * dropped. An input that changes nothing hands back the same view and gate.
2607
+ */
2608
+ declare function followTurn(view: ConversationView, gate: TurnGate, input: ConversationInput): GatedConversation;
2609
+ /**
2610
+ * The user stopped the planner. The turn ends on screen now rather than when
2611
+ * the backend notices the abort, so the surface is free at once and the
2612
+ * turn's late output never lands. Nothing changes when no turn is open.
2613
+ */
2614
+ declare function stopTurn(view: ConversationView, gate: TurnGate): GatedConversation;
2615
+
2616
+ /**
2617
+ * Whether the conversation holds anything the collapsed view hides — thinking,
2618
+ * a command row's arguments and output, a subagent's children and digest,
2619
+ * what a task's runner loaded. The
2620
+ * detail toggle is the only expansion control (ADR-0017, X1), so a surface
2621
+ * offers it only when it would do something.
2622
+ */
2623
+ declare function hasHiddenDetail(blocks: readonly DisplayBlock[]): boolean;
2624
+
2625
+ /** The conversation line for a task starting, worded the same on every surface. */
2626
+ declare function taskStartedNotice(title: string, runner?: string): string;
2627
+
2628
+ /** Where a skill name is invoked: `start` is its `/`, `end` is just past the name, before any punctuation. */
2629
+ interface SkillToken {
2630
+ name: string;
2631
+ start: number;
2632
+ end: number;
2633
+ }
2634
+ /**
2635
+ * The first `/name` token for each name in `text`, names lower-cased. Only
2636
+ * the first counts because a skill loads once per message however often it is
2637
+ * named, so a later repeat is plain text.
2638
+ */
2639
+ declare function skillTokens(text: string): SkillToken[];
2640
+ /** The tokens in a sent message that loaded one of `loaded` — what a surface highlights in the user's bubble. */
2641
+ declare function loadedSkillTokens(text: string, loaded: readonly string[]): SkillToken[];
2642
+
2643
+ /**
2644
+ * What a row's status means to a reader, before any surface picks a word,
2645
+ * icon or colour for it. `quiet` is a running task whose runner has printed
2646
+ * nothing for a while — the status is still `in_progress`, so only the row,
2647
+ * never the store, tells the two apart.
2648
+ */
2649
+ type TaskStatusKind = 'todo' | 'running' | 'quiet' | 'awaiting' | 'blocked' | 'failed' | 'skipped' | 'done';
2650
+ /** A task with a runner live behind it. `in_progress` is the store's own status. */
2651
+ declare const isTaskRunning: (task: {
2652
+ status: string;
2653
+ }) => boolean;
2654
+ /** Statuses of a task that has not started: what can still be started by hand, or flipped between change and ops (ADR-0020). */
2655
+ declare const NOT_STARTED: ReadonlySet<string>;
2656
+ declare function taskStatusKind(task: {
2657
+ status: string;
2658
+ idleSince?: string | null;
2659
+ }): TaskStatusKind;
2660
+ /** What an awaiting_user task waits on (ADR-0018, W1); null when no reason was saved. */
2661
+ declare function awaitingLabel(task: {
2662
+ status: string;
2663
+ awaitingReason?: AwaitingReason;
2664
+ }): string | null;
2665
+ /** "Blocked by #1, #2.1": the failed tasks a blocked task waits on, by reference (ADR-0034). */
2666
+ declare function blockedByLabel(refs: readonly string[]): string;
2667
+ /** "Skipped: no GPU on this host", or "Skipped" when no reason was given. */
2668
+ declare function skippedLabel(reason: string | null): string;
2669
+ /** Runner requests a task waits on (ADR-0018, A1); null when none do. */
2670
+ declare function approvalLabel(count: number | undefined): string | null;
2671
+ /**
2672
+ * When a paused task resumes, as a reader's clock shows it: "8:10pm", or
2673
+ * "Oct 14, 8:10pm" when it is not today. Read in the local time zone of
2674
+ * whoever formats it.
2675
+ */
2676
+ declare function resumeTime(at: Date, now?: Date): string;
2677
+ /** "Resumes at 8:10pm" for a task paused on a usage limit that resumes on its own; null for any other. */
2678
+ declare function resumeLabel(task: {
2679
+ status: string;
2680
+ awaitingReason?: AwaitingReason;
2681
+ resumeAt?: string | null;
2682
+ }, now?: Date): string | null;
2683
+
2684
+ /**
2685
+ * What a user can do to one task from its row. For a task paused on a usage
2686
+ * limit, `resume-now` resumes it in its saved session once the user says the
2687
+ * limit has reset, and `cancel-resume` keeps it from resuming on its own.
2688
+ */
2689
+ type TaskRowAction = 'cancel' | 'resume-now' | 'cancel-resume' | 'skip' | 'force-start' | 'complete' | 'uncomplete';
2690
+ /** A subtask runs with its parent, so its row offers a different set than a top-level task's. */
2691
+ type TaskRowPlacement = 'task' | 'subtask';
2692
+ interface ActionSubject {
2693
+ status: string;
2694
+ type: 'ai' | 'user';
2695
+ awaitingReason?: string;
2696
+ resumeAt?: string | null;
2697
+ /** Its last attempt left a runner session to continue (ADR-0018, K1). */
2698
+ continuable?: boolean;
2699
+ }
2700
+ /** The actions a task's row offers while a plan runs, in the order they are laid out. */
2701
+ declare function taskRowActions(task: ActionSubject, placement?: TaskRowPlacement): TaskRowAction[];
2702
+ /**
2703
+ * One toggle in both directions: a done task is taken back, anything else is
2704
+ * marked done. A second control for the reverse is one nobody remembers.
2705
+ */
2706
+ declare function markAction(task: {
2707
+ status: string;
2708
+ }): 'complete' | 'uncomplete';
2709
+ /**
2710
+ * The mark an action asks the session for, or null when it is not a mark.
2711
+ * Skip is not one: it ends the task without vouching for its work, which
2712
+ * only Mark complete does.
2713
+ */
2714
+ declare function markRequestFor(action: string): 'complete' | 'uncomplete' | null;
2715
+ /** Change versus ops can still be flipped (ADR-0020): an AI task that has not started. */
2716
+ declare function opsEditable(task: ActionSubject): boolean;
2717
+
2718
+ /**
2719
+ * A task as both surfaces hold it, with the live state each tracks beside it
2720
+ * (silence, approvals, isolation, the merge gate). Structural, because the TUI
2721
+ * projects tasks into its own shape.
2722
+ */
2723
+ interface TaskRowSubject {
2724
+ id: string;
2725
+ order: number;
2726
+ type: 'ai' | 'user';
2727
+ status: string;
2728
+ dependencies: readonly string[];
2729
+ taskMode?: string;
2730
+ ops?: boolean;
2731
+ idleSince?: string | null;
2732
+ awaitingReason?: AwaitingReason;
2733
+ skipReason?: string;
2734
+ /** When a task paused on a usage limit resumes on its own (ISO time). */
2735
+ resumeAt?: string | null;
2736
+ /** Its last attempt left a runner session to continue (ADR-0018, K1). */
2737
+ continuable?: boolean;
2738
+ awaitingApproval?: number;
2739
+ isolation?: TaskRowIsolation | null;
2740
+ mergeGate?: readonly string[];
2741
+ forcedPastGate?: readonly string[];
2742
+ attemptUsage?: readonly AttemptUsage[];
2743
+ }
2744
+ interface TaskRowIsolation {
2745
+ state: TaskIsolationState;
2746
+ repos?: string[];
2747
+ conflictRepo?: string;
2748
+ conflictFiles?: string[];
2749
+ repair?: {
2750
+ attempt: number;
2751
+ limit: number;
2752
+ };
2753
+ repairedFiles?: string[];
2754
+ }
2755
+ /**
2756
+ * The plan a row labels its references against: the tree, so a subtask's
2757
+ * dependency reads dotted ("#2.1") rather than by its parent-scoped order.
2758
+ * With statuses and dependencies it also tells which tasks a failure blocks.
2759
+ */
2760
+ interface PlanRefTask {
2761
+ id: string;
2762
+ order: number;
2763
+ title: string;
2764
+ status?: string;
2765
+ dependencies?: readonly string[];
2766
+ subtasks?: readonly PlanRefTask[];
2767
+ }
2768
+ interface TaskRowContext {
2769
+ /** Set for a subtask: it is labelled by its dotted order. */
2770
+ parent?: {
2771
+ order: number;
2772
+ } | null;
2773
+ /** Which action set the row offers; a row with a parent is a subtask's unless told otherwise. */
2774
+ placement?: TaskRowPlacement;
2775
+ /** The plan tree a dependency, gate or block is labelled against; one it does not hold is shown by id. */
2776
+ tasks?: readonly PlanRefTask[];
2777
+ /** The modes the task's runner declares; autonomy is a tag on one of them (ADR-0001). */
2778
+ modes?: readonly {
2779
+ id: string;
2780
+ autonomous?: boolean;
2781
+ }[];
2782
+ /** What a resume time is read against; the clock when absent. */
2783
+ now?: Date;
2784
+ }
2785
+ /** Whether the row stands for an AI task, a manual one, or an ops task (ADR-0020). */
2786
+ type TaskRowKind = 'ai' | 'user' | 'ops';
2787
+ interface TaskRowUsage extends UsageLine {
2788
+ attempt: number;
2789
+ wallTimeMs?: number;
2790
+ }
2791
+ interface TaskRowView {
2792
+ usage: TaskRowUsage | null;
2793
+ /** "2", or "2.1" for a subtask. */
2794
+ orderLabel: string;
2795
+ kind: TaskRowKind;
2796
+ status: TaskStatusKind;
2797
+ /** A runner is live behind it — `quiet` included. */
2798
+ running: boolean;
2799
+ /** What an awaiting task waits on, in sentence case; null when nothing was saved. */
2800
+ awaiting: string | null;
2801
+ /** "Resumes at 8:10pm" while a task paused on a usage limit resumes on its own; null otherwise. */
2802
+ resumes: string | null;
2803
+ /** Runner requests waiting on the user, in sentence case; null when none do. */
2804
+ approvals: string | null;
2805
+ /** The task's mode is tagged autonomous: it runs without permission prompts. */
2806
+ autonomous: boolean;
2807
+ /** The repos its isolated work changed, by path; empty for a group of one. */
2808
+ repos: string[];
2809
+ /** Integration stopped on a conflict: the one isolation state that needs the user. */
2810
+ conflict: {
2811
+ repo: string | null;
2812
+ files: string | null;
2813
+ } | null;
2814
+ /** A conflict repair in flight (ADR-0015) — progress, not a blocker. */
2815
+ repairing: {
2816
+ files: string | null;
2817
+ attempt: {
2818
+ attempt: number;
2819
+ limit: number;
2820
+ } | null;
2821
+ } | null;
2822
+ /** The files a conflict repair was started for; `landed` once the task is integrated. */
2823
+ repaired: {
2824
+ files: string;
2825
+ landed: boolean;
2826
+ } | null;
2827
+ /** The dependencies, by reference, whose work is not merged into the user's branch yet. */
2828
+ mergeGate: string[] | null;
2829
+ /** The failed tasks, by reference, a task not started waits on through its dependencies (ADR-0034); null when none. */
2830
+ blockedBy: string[] | null;
2831
+ /** Why a skipped task was skipped, as whoever skipped it put it; null for any other, or one skipped without a reason. */
2832
+ skipReason: string | null;
2833
+ /** An ops task changed tracked files in the user's checkout and nothing was committed. */
2834
+ opsChangedFiles: boolean;
2835
+ /** The dependencies, by title, a force start went past the merge gate of. */
2836
+ forcedPastGate: readonly string[] | null;
2837
+ /** The task's dependencies, by reference. */
2838
+ dependencies: string[];
2839
+ /** What the row offers while a plan runs. */
2840
+ actions: TaskRowAction[];
2841
+ /** The one mark its toggle sets. */
2842
+ mark: 'complete' | 'uncomplete';
2843
+ opsEditable: boolean;
2844
+ }
2845
+ /** The repos a task changed, for a row that names them; none for a group of one. */
2846
+ declare const namedRepos: (repos: readonly string[] | undefined) => string[];
2847
+ /**
2848
+ * Everything a plan row says about one task, decided once: each surface picks
2849
+ * its own words, glyphs and casing for these facts, and derives none of them.
2850
+ */
2851
+ declare function taskRowView(task: TaskRowSubject, context?: TaskRowContext): TaskRowView;
2852
+
2853
+ interface Named {
2854
+ id: string;
2855
+ order: number;
2856
+ title: string;
2857
+ subtasks?: readonly Named[];
2858
+ }
2859
+ /** Each id as "#N title", subtasks dotted, by the plan's tree; one it no longer holds stays a bare id. */
2860
+ declare function titledRefs(ids: readonly string[], tasks: readonly Named[]): string[];
2861
+ /**
2862
+ * What removing a task does to the tasks that depend on it, or null when none
2863
+ * do. Named rather than counted: the removal rewrites their dependency lists,
2864
+ * and a bare "Remove task?" would hide that.
2865
+ */
2866
+ declare function dependentsNotice(dependents: readonly {
2867
+ order: number;
2868
+ title: string;
2869
+ }[]): string | null;
2870
+ /**
2871
+ * What a start past a merge gate is asked with (ADR-0020): the work the task
2872
+ * would act without, named. `subject` is how the asking surface refers to the
2873
+ * task — "It" under a title that already names it.
2874
+ */
2875
+ declare function pastGateConfirmation(unmerged: readonly string[], subject?: string): string;
2876
+ interface NewTaskFields {
2877
+ title: string;
2878
+ description: string;
2879
+ prompt: string;
2880
+ type: 'ai';
2881
+ }
2882
+ /**
2883
+ * What a hand-added task starts as: an AI task whose description is its title
2884
+ * and whose prompt, unless one was typed, is too. Null for a title with nothing
2885
+ * in it, rather than a nameless task. Assignments are left for the session to
2886
+ * derive from the runner's catalog.
2887
+ */
2888
+ declare function newTaskFields(title: string, prompt?: string): NewTaskFields | null;
2889
+
2890
+ /** How every surface words the run settings (ADR-0030), so they all say the same thing. */
2891
+ declare const SUPERVISOR_LEVEL_LABEL: Record<SupervisorLevel, string>;
2892
+ declare const supervisorSummary: (run: GlobalRunSettings) => string;
2893
+ declare const automergeSummary: (run: GlobalRunSettings) => string;
2894
+ declare const autorunSummary: (run: GlobalRunSettings) => string;
2895
+ declare const ACT_RISK = "Tasks it adds run too, ops tasks included, which can act on real systems unattended.";
2896
+ declare const AUTORUN_RISK = "Plans start without review, ops tasks included, which can act on real systems unattended. In Full, the planner may also start tasks it adds later.";
2897
+ /** `on`/`off` as a switch's value; null for anything else. */
2898
+ declare function parseSwitch(arg: string): boolean | null;
2899
+ /** A whole number at least `min`, as typed; null for anything else. */
2900
+ declare function parseCount(arg: string | undefined, min: number): number | null;
2901
+
2902
+ export { type RunnerLoadedBlock as $, ACT_RISK as A, type BlockingSubject as B, CHECKPOINT_TRUNCATE_LENGTH as C, type DiffRow as D, EMPTY_CONVERSATION as E, type NotificationAction as F, type GatedConversation as G, type OutputPreview as H, type INotification as I, type PlanMarkerStatus as J, type PlanSnapshot as K, type LiveTail as L, type MessageBlock as M, NOT_STARTED as N, type OrchestratorObserver as O, type PlanBlock as P, PlanStore as Q, type PlannerModes as R, type PromptHold as S, RUN_SETTINGS_DEFAULTS as T, type RefTask as U, type RunInput as V, type RunMergeView as W, RunSettingsError as X, type RunTask as Y, type RunTaskStatus as Z, type RunView as _, AUTORUN_RISK as a, diffRows as a$, SUPERVISOR_LEVEL_LABEL as a0, type SerializedConversationMessage as a1, type SerializedPendingTurn as a2, type SerializedPlan as a3, type SerializedTask as a4, type SerializedTaskStatus as a5, type SessionBroadcaster as a6, type SessionMessage as a7, type SessionNotice as a8, SettingsService as a9, type TaskRowUsage as aA, type TaskRowView as aB, type TaskStatusKind as aC, type ThinkingDisplayBlock as aD, type ToolBlock as aE, type ToolHeadline as aF, type ToolStatus as aG, type TurnGate as aH, type UsageBlock as aI, type UserSettings as aJ, aheadOfDraft as aK, applyTaskOps as aL, approvalLabel as aM, automergeSummary as aN, autorunSummary as aO, awaitingLabel as aP, blockedByLabel as aQ, blockingFailures as aR, canMergeTasks as aS, canSetDependencies as aT, canSplitTask as aU, capConflictFiles as aV, classifyOutcome as aW, coalesceTaskLog as aX, dependencyCandidates as aY, dependentsNotice as aZ, dependentsOf as a_, type SkillLoadBlock as aa, type SkillLookup as ab, type SkillToken as ac, type SubagentBlock as ad, type SubagentChild as ae, type SubagentStatus as af, type SurfacePlan as ag, type SurfacePlanState as ah, type SurfaceTask as ai, type TakenPrompt as aj, type TaskAttemptSnapshot as ak, TaskControlError as al, type TaskLogEvent as am, type TaskLogView as an, type TaskOp as ao, TaskOrchestrator as ap, type TaskOrchestratorDeps as aq, type TaskOrchestratorOptions as ar, type TaskOutputSource as as, type TaskRef as at, type TaskRowAction as au, type TaskRowContext as av, type TaskRowIsolation as aw, type TaskRowKind as ax, type TaskRowPlacement as ay, type TaskRowSubject as az, type ApplyTaskOpsResult as b, diffStat as b0, diffSummary as b1, digestTaskLog as b2, drainNext as b3, executionSummary as b4, followTurn as b5, fromTranscript as b6, getSettingsPath as b7, hasHiddenDetail as b8, holdPrompt as b9, skippedLabel as bA, stopTurn as bB, summarizeToolCall as bC, supervisorSummary as bD, surfacePlan as bE, surfacePlanState as bF, takeAt as bG, taskLogForSurface as bH, taskOrderLabel as bI, taskRef as bJ, taskRefOf as bK, taskRowActions as bL, taskRowView as bM, taskStartedNotice as bN, taskStatusKind as bO, textHasTaskOps as bP, titledRefs as bQ, titledTaskRef as bR, titledTaskRefOf as bS, toTaskLogEvent as bT, toolHeadline as bU, trimToolOutput as bV, truncateCheckpointSummary as bW, unsendAll as bX, unsendLatest as bY, isTaskRunning as ba, loadedSkillTokens as bb, markAction as bc, markRequestFor as bd, namedRepos as be, newTaskFields as bf, opsEditable as bg, outputLines as bh, outputPreview as bi, parseCount as bj, parseSwitch as bk, parseTaskOpsJson as bl, pastGateConfirmation as bm, reduceConversation as bn, reduceRun as bo, reduceTaskLog as bp, replayTaskLog as bq, resolveTaskRef as br, resumeLabel as bs, resumeTime as bt, runSettingsOf as bu, runnerToolSubject as bv, serializePlan as bw, serializeTask as bx, serializeTaskStatus as by, skillTokens as bz, type ApprovalBlock as c, type ApprovalStatus as d, type AutoMergeTrigger as e, type CommandBlock as f, type CommandStatus as g, type CompactionBlock as h, type ConversationInput as i, type ConversationView as j, type DiffRowKind as k, type DiffStat as l, type DisplayBlock as m, EMPTY_HOLD as n, EMPTY_RUN as o, EMPTY_TASK_LOG as p, type EditCatalog as q, type ExecutionSummary as r, type GlobalRunSettings as s, type IsolatedExecution as t, IsolationRunController as u, type LiveTailOptions as v, type LocalEntry as w, type MessageRole as x, NO_TURN as y, type NewTaskFields as z };