@ordewell/core 0.5.4 → 0.5.5

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 (44) hide show
  1. package/dist/{ITerminalRunner-Bd-vAJnw.d.ts → ITerminalRunner-BV9Rd2o9.d.ts} +3 -1
  2. package/dist/{ITerminalRunner-ByeoLF57.d.mts → ITerminalRunner-C77ZNZS9.d.mts} +3 -1
  3. package/dist/{ModeResolver-DVJ7HV3k.d.mts → ModeResolver-D-SUFRNF.d.mts} +1 -1
  4. package/dist/{ModeResolver-Dkig8ghQ.d.ts → ModeResolver-D3XO0fT9.d.ts} +1 -1
  5. package/dist/{Task-BxQkPlXO.d.mts → Task-Vl5Zq_D-.d.mts} +168 -7
  6. package/dist/{Task-BxQkPlXO.d.ts → Task-Vl5Zq_D-.d.ts} +168 -7
  7. package/dist/{chunk-JVMDEHRQ.mjs → chunk-HD2FWPRV.mjs} +72 -37
  8. package/dist/chunk-HD2FWPRV.mjs.map +1 -0
  9. package/dist/{chunk-GWPIYDQW.mjs → chunk-KLN7ELXO.mjs} +897 -18
  10. package/dist/chunk-KLN7ELXO.mjs.map +1 -0
  11. package/dist/{chunk-XWOUIA6A.mjs → chunk-UUBGVCGJ.mjs} +2 -2
  12. package/dist/index.d.mts +320 -60
  13. package/dist/index.d.ts +320 -60
  14. package/dist/index.js +2497 -672
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +1605 -659
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/order-labels.d.mts +1 -1
  19. package/dist/order-labels.d.ts +1 -1
  20. package/dist/{parsing-CF_grC29.d.ts → parsing-BTP4bwkk.d.ts} +11 -3
  21. package/dist/{parsing-DRp4dPC0.d.mts → parsing-CDtRSxBY.d.mts} +11 -3
  22. package/dist/parsing.d.mts +3 -3
  23. package/dist/parsing.d.ts +3 -3
  24. package/dist/parsing.js.map +1 -1
  25. package/dist/parsing.mjs +2 -2
  26. package/dist/plan-utils-BFaPo-IT.d.ts +708 -0
  27. package/dist/plan-utils-pE4TBwxl.d.mts +708 -0
  28. package/dist/plan-utils.d.mts +3 -3
  29. package/dist/plan-utils.d.ts +3 -3
  30. package/dist/plan-utils.js +668 -7
  31. package/dist/plan-utils.js.map +1 -1
  32. package/dist/plan-utils.mjs +38 -4
  33. package/dist/testing.d.mts +5 -3
  34. package/dist/testing.d.ts +5 -3
  35. package/dist/testing.js +3 -0
  36. package/dist/testing.js.map +1 -1
  37. package/dist/testing.mjs +3 -0
  38. package/dist/testing.mjs.map +1 -1
  39. package/package.json +1 -1
  40. package/dist/chunk-GWPIYDQW.mjs.map +0 -1
  41. package/dist/chunk-JVMDEHRQ.mjs.map +0 -1
  42. package/dist/plan-utils-CkNbqAmS.d.ts +0 -329
  43. package/dist/plan-utils-CtB3_Ovf.d.mts +0 -329
  44. /package/dist/{chunk-XWOUIA6A.mjs.map → chunk-UUBGVCGJ.mjs.map} +0 -0
@@ -0,0 +1,708 @@
1
+ import { r as IsolationTaskStatus, P as PlanIsolation, H as RunnerId, a as DiscoveredModel, T as Task, L as LegacyPlanState, Q as QueuedMessage, a4 as UsageTotals, O as SubagentOutcome, aa as Verdict, U as TaskIsolation, d as IsolationHandoff, k as IsolationMergeResult, E as ResearchStep, F as ResearchStepOutcome, C as ConversationMessage, z as ResearchLogEntry, w as PlannerUsage } from './Task-Vl5Zq_D-.js';
2
+ import { R as RunnerModeInfo } from './ModeResolver-D3XO0fT9.js';
3
+ import { A as ApprovalKind, e as ApprovalSource } from './ApprovalPolicy-BVhGdECT.js';
4
+
5
+ /** A conflict's files as one surface shows them: every one, up to `max`, then how many more. */
6
+ declare function capConflictFiles(files: string[], max?: number): string;
7
+ /** A task record as ADR-0013 persisted it, for one repository. */
8
+ interface Adr0013TaskRecord {
9
+ taskId: string;
10
+ order: number;
11
+ title: string;
12
+ branch: string;
13
+ worktree: string;
14
+ status: IsolationTaskStatus;
15
+ linked: string[];
16
+ }
17
+ /** A run as ADR-0013 persisted it (0.4.23): one repository, its refs on the run itself. */
18
+ interface Adr0013IsolationRun {
19
+ id: string;
20
+ workspaceRoot: string;
21
+ baseRef: string;
22
+ baseBranch?: string;
23
+ integrationBranch: string;
24
+ tasks: Record<string, Adr0013TaskRecord>;
25
+ }
26
+ interface Adr0013PlanIsolation {
27
+ run: Adr0013IsolationRun;
28
+ resolvers: Record<string, string>;
29
+ }
30
+ /**
31
+ * A persisted plan isolation in today's shape. A run saved in the ADR-0013
32
+ * format — recognised by its own `integrationBranch` — becomes a group of one
33
+ * at `.`, keeping its branches, so a session saved by 0.4.23 resumes and hands
34
+ * off exactly as it would have.
35
+ */
36
+ declare function migratePlanIsolation(state: PlanIsolation | Adr0013PlanIsolation): PlanIsolation;
37
+
38
+ /**
39
+ * The catalog a model/task-mode edit is checked against — the same discovered
40
+ * models and manifest modes the planner was shown in the per-turn catalog
41
+ * block (`PlannerConversation.catalogBlock`), so a refusal here can never name something
42
+ * as invalid that the planner was never told about, or vice versa.
43
+ */
44
+ interface EditCatalog {
45
+ modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>;
46
+ runnerModes: Partial<Record<RunnerId, RunnerModeInfo[]>>;
47
+ /** Raw (unfiltered) allowlist, keyed by runner — the same shape `coerceAssignments` takes. */
48
+ perRunnerAllowlist?: Partial<Record<RunnerId, string[]>>;
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
+ type TaskOp = {
57
+ op: 'update';
58
+ taskId: string;
59
+ changes: Partial<Task>;
60
+ } | {
61
+ op: 'add';
62
+ task: Partial<Task>;
63
+ handle?: string;
64
+ } | {
65
+ op: 'remove';
66
+ taskId: string;
67
+ } | {
68
+ op: 'reorder';
69
+ taskIds: string[];
70
+ } | {
71
+ op: 'merge';
72
+ taskIds: string[];
73
+ merged: Partial<Task>;
74
+ handle?: string;
75
+ } | {
76
+ op: 'split';
77
+ taskId: string;
78
+ parts: Partial<Task>[];
79
+ handle?: string;
80
+ } | {
81
+ op: 'rearm';
82
+ taskId: string;
83
+ changes?: Partial<Task>;
84
+ };
85
+ declare function textHasTaskOps(text: string): boolean;
86
+ /** Parse a `{"taskOps":[...]}` reply. Throws PlanParseError when the JSON is unusable. */
87
+ declare function parseTaskOpsJson(text: string): TaskOp[];
88
+ interface ApplyTaskOpsResult {
89
+ ok: boolean;
90
+ tasks: Task[];
91
+ errors: string[];
92
+ /** Human-readable summary of what changed, for the chat transcript. */
93
+ summary: string[];
94
+ }
95
+ /**
96
+ * Pre-flight check for a merge: would collapsing the selected tasks into one
97
+ * (rewiring every dependent of any selected task to the survivor) keep the
98
+ * dependency graph valid? Rejects locked tasks and merges whose rewiring would
99
+ * introduce a cycle. Display order is NOT checked here — neither
100
+ * consecutiveness (a UI concern) nor a dependency the merge pulls out of order,
101
+ * which {@link applyTaskOps} repairs; refusing on order would make this
102
+ * pre-flight stricter than the applier it stands in for.
103
+ */
104
+ declare function canMergeTasks(tasks: Task[], selectedIds: string[]): {
105
+ ok: boolean;
106
+ error?: string;
107
+ };
108
+ /**
109
+ * The shape the dependency helpers below read. Structural rather than `Task`,
110
+ * because the TUI projects tasks into its own `TaskView`: a surface's
111
+ * dependency picker and the API's validation must agree on which dependencies
112
+ * are legal, and a signature only core can satisfy would have forced the TUI
113
+ * to keep a second copy of the rule.
114
+ */
115
+ interface TaskRef {
116
+ id: string;
117
+ order: number;
118
+ title: string;
119
+ dependencies: string[];
120
+ }
121
+ /** The tasks listing `taskId` as a dependency — exactly what removing it detaches. */
122
+ declare function dependentsOf<T extends Pick<TaskRef, 'id' | 'dependencies'>>(tasks: T[], taskId: string): T[];
123
+ /**
124
+ * The tasks that may become dependencies of `taskId`: those displayed before it.
125
+ *
126
+ * Offering only earlier tasks is what keeps a hand-edited graph valid without a
127
+ * cycle check — dependencies then only ever point backwards in display order,
128
+ * the same invariant `applyTaskOps` enforces. Omit `taskId` for a task that does
129
+ * not exist yet: it lands last, so every current task is a candidate.
130
+ */
131
+ declare function dependencyCandidates<T extends Pick<TaskRef, 'id' | 'order'>>(tasks: T[], taskId?: string): T[];
132
+ /** Pre-flight check for a hand-edited dependency list: every id exists and comes earlier. */
133
+ declare function canSetDependencies<T extends TaskRef>(tasks: T[], taskId: string, dependencies: string[]): {
134
+ ok: boolean;
135
+ error?: string;
136
+ };
137
+ /** Pre-flight check for a split: the task exists and is not locked by execution. */
138
+ declare function canSplitTask(tasks: Task[], taskId: string): {
139
+ ok: boolean;
140
+ error?: string;
141
+ };
142
+ /**
143
+ * Apply task ops to a snapshot of the plan, atomically: either every op
144
+ * applies and the result validates (deps resolve, no cycles, running and
145
+ * completed tasks untouched), or nothing is returned and `errors` explains
146
+ * why. The caller commits `tasks` on ok.
147
+ */
148
+ declare function applyTaskOps(currentTasks: Task[], ops: TaskOp[], runners: RunnerId[], catalog?: EditCatalog): ApplyTaskOpsResult;
149
+
150
+ type SerializedTaskStatus = {
151
+ id: string;
152
+ status: string;
153
+ verdict: {
154
+ outcome: 'pass' | 'fail';
155
+ reason: string;
156
+ checks: Verdict['checks'];
157
+ } | null;
158
+ /** Advisory silence timestamp from VerdictEngine — not part of task status semantics. */
159
+ idleSince?: string | null;
160
+ /** Absent unless the plan has an isolation run, so a shared-root plan's updates are unchanged. */
161
+ isolation?: TaskIsolation;
162
+ };
163
+ type SerializedTask = {
164
+ id: string;
165
+ order: number;
166
+ title: string;
167
+ type: string;
168
+ description: string;
169
+ dependencies: string[];
170
+ assignedRunner: RunnerId;
171
+ assignedModel: Task['assignedModel'] | null;
172
+ taskMode: string;
173
+ prompt: string | null;
174
+ subtasks: SerializedTask[];
175
+ userSteps: Task['userSteps'];
176
+ thinkingEffort: Task['thinkingEffort'];
177
+ autonomy: Task['autonomy'];
178
+ sliceType: Task['sliceType'];
179
+ userStoriesCovered: Task['userStoriesCovered'];
180
+ };
181
+ type SerializedPlan = {
182
+ tasks: SerializedTask[];
183
+ runners: RunnerId[];
184
+ generatedAt: string;
185
+ conversationHistory?: LegacyPlanState['conversationHistory'];
186
+ prdMarkdown?: string;
187
+ queuedMessages?: QueuedMessage[];
188
+ };
189
+ /** How a planner turn ended: the reply kind it settled on, a user stop, or a failure. */
190
+ type PlannerTurnOutcome = 'message' | 'plan' | 'task_ops' | 'stopped' | 'error';
191
+ /**
192
+ * Everything a session tells its surfaces, over one broadcast seam.
193
+ *
194
+ * A planner turn (#47) streams between `planner_turn_started` and
195
+ * `planner_turn_ended` with the same `turnId`; every turn-scoped message in
196
+ * between carries it. The stream is provisional and the settled messages are
197
+ * authoritative:
198
+ * - `planner_message` replaces the streamed text of the turn's final segment —
199
+ * a surface drops what it accumulated and shows the message instead.
200
+ * - A reply that is a JSON envelope (plan, taskOps, taskQuery) never arrives
201
+ * as `planner_text_delta`; it streams as `plan_token`, the "building plan"
202
+ * display.
203
+ * - A subagent's own text never appears in the reply; its activity arrives
204
+ * tagged with its `subagentId`.
205
+ */
206
+ type SessionMessage =
207
+ /**
208
+ * The plan, whole. `turnId` names the planner turn whose commit this
209
+ * broadcast carries, so the turn's building plan becomes the marker; it is
210
+ * absent on every other broadcast of the plan.
211
+ */
212
+ {
213
+ type: 'plan_generated';
214
+ plan: SerializedPlan;
215
+ goal: string;
216
+ runners: RunnerId[];
217
+ turnId?: string;
218
+ }
219
+ /**
220
+ * The settled reply of a planner turn, emitted by the session once the turn
221
+ * is classified. Authoritative over any `planner_text_delta` of its turn's
222
+ * final segment. `turnId` is absent for replies sent outside a streamed turn.
223
+ */
224
+ | {
225
+ type: 'planner_message';
226
+ content: string;
227
+ timestamp: string;
228
+ turnId?: string;
229
+ }
230
+ /**
231
+ * A planner turn began. Emitted once per turn by whoever runs the turn,
232
+ * before any other message carrying its `turnId`. `prompt` is the user's
233
+ * message when the turn answers one.
234
+ */
235
+ | {
236
+ type: 'planner_turn_started';
237
+ turnId: string;
238
+ prompt?: string;
239
+ }
240
+ /**
241
+ * A planner turn is over; nothing more carries its `turnId`. Emitted exactly
242
+ * once per `planner_turn_started`, stop and failure included, after the
243
+ * turn's `planner_message` when it has one.
244
+ */
245
+ | {
246
+ type: 'planner_turn_ended';
247
+ turnId: string;
248
+ outcome: PlannerTurnOutcome;
249
+ }
250
+ /**
251
+ * Reply prose as it streams, appended in order within its segment. A segment
252
+ * is one continuous run of model text; text before a tool call is its own
253
+ * segment, and a later segment never rewrites an earlier one. Never carries
254
+ * a JSON envelope or a subagent's text (see the union's invariants).
255
+ */
256
+ | {
257
+ type: 'planner_text_delta';
258
+ turnId: string;
259
+ segmentId: string;
260
+ text: string;
261
+ }
262
+ /**
263
+ * Exposed reasoning as it streams, from the planner or — tagged with
264
+ * `subagentId` — from one of its subagents. Never part of the reply. The one
265
+ * thinking message for every backend: `segmentId` is set only where the
266
+ * backend streams thinking in segments (the API loops; harness planners do
267
+ * not), and `turnId` is absent for thinking outside a turn (one-shot plans).
268
+ */
269
+ | {
270
+ type: 'planner_thinking_delta';
271
+ turnId?: string;
272
+ segmentId?: string;
273
+ subagentId?: string;
274
+ text: string;
275
+ }
276
+ /**
277
+ * Text streamed for an attempt the turn discarded (a corrective retry) is
278
+ * taken back: a surface removes it. With `segmentId`, only that segment;
279
+ * without, all of the turn's text not yet settled by a `planner_message`.
280
+ * A segment that streamed to the plan display (`plan_token`) takes the
281
+ * turn's building plan with it.
282
+ */
283
+ | {
284
+ type: 'planner_text_retracted';
285
+ turnId: string;
286
+ segmentId?: string;
287
+ }
288
+ /**
289
+ * The planner's running usage for the session, subagents included, emitted
290
+ * after a model call reports usage. `totals` already contains every
291
+ * `bySubagent` entry. `contextFill` is the last planner prompt against the
292
+ * model's window, omitted when the window is unknown. Cost appears only as
293
+ * reported by a provider or runner (see `UsageRecord`).
294
+ */
295
+ | {
296
+ type: 'planner_usage';
297
+ turnId?: string;
298
+ totals: UsageTotals;
299
+ bySubagent?: Record<string, UsageTotals>;
300
+ contextFill?: {
301
+ usedTokens: number;
302
+ windowTokens: number;
303
+ };
304
+ }
305
+ /**
306
+ * A subagent began work on `brief`. Emitted by the planner backend that
307
+ * spawned it (ADR-0005 research agents, or a harness planner's own), before
308
+ * any message tagged with its `subagentId`.
309
+ */
310
+ | {
311
+ type: 'subagent_started';
312
+ turnId?: string;
313
+ subagentId: string;
314
+ brief: string;
315
+ model?: string;
316
+ }
317
+ /**
318
+ * A subagent is done; nothing more is tagged with its `subagentId`. Emitted
319
+ * once per `subagent_started`. `digest` is what it handed back to the
320
+ * planner; `usage` is its own share, already counted in `planner_usage`.
321
+ */
322
+ | {
323
+ type: 'subagent_finished';
324
+ turnId?: string;
325
+ subagentId: string;
326
+ outcome: SubagentOutcome;
327
+ digest: string;
328
+ usage?: UsageTotals;
329
+ } | {
330
+ type: 'status_update';
331
+ tasks: SerializedTaskStatus[];
332
+ } | {
333
+ type: 'review_needed';
334
+ tasks: SerializedTask[];
335
+ } | {
336
+ type: 'review_approved';
337
+ } | {
338
+ type: 'checkpoint';
339
+ taskId: string;
340
+ taskTitle: string;
341
+ summary: string;
342
+ } | {
343
+ type: 'execution_complete';
344
+ summary: {
345
+ total: number;
346
+ completed: number;
347
+ failed: number;
348
+ };
349
+ } | {
350
+ type: 'execution_stopped';
351
+ } | {
352
+ type: 'queue_ready';
353
+ } | {
354
+ type: 'task_updated';
355
+ taskId: string;
356
+ changes: Record<string, unknown>;
357
+ } | {
358
+ type: 'task_started';
359
+ taskId: string;
360
+ order: number;
361
+ title: string;
362
+ runner: RunnerId;
363
+ modelId?: string;
364
+ } | {
365
+ type: 'task_output';
366
+ taskId: string;
367
+ text: string;
368
+ } | {
369
+ type: 'isolation_blocked';
370
+ reason: 'dirty';
371
+ repos?: string[];
372
+ message: string;
373
+ } | {
374
+ type: 'isolation_handoff';
375
+ repos: IsolationHandoff['repos'];
376
+ landed: IsolationHandoff['landed'];
377
+ } | {
378
+ type: 'isolation_merge';
379
+ result: IsolationMergeResult;
380
+ } | {
381
+ type: 'planner_liveness';
382
+ } | {
383
+ type: 'research_step';
384
+ tool: string;
385
+ toolLabel?: string;
386
+ args: string;
387
+ subagentId?: string;
388
+ toolCallId?: string;
389
+ turnId?: string;
390
+ } | {
391
+ type: 'plan_token';
392
+ token: string;
393
+ turnId?: string;
394
+ segmentId?: string;
395
+ } | {
396
+ type: 'research_step_done';
397
+ step: ResearchStep;
398
+ subagentId?: string;
399
+ turnId?: string;
400
+ } | {
401
+ type: 'approval_request';
402
+ id: string;
403
+ kind: ApprovalKind;
404
+ subject: string;
405
+ scope: string;
406
+ detail?: string;
407
+ turnId?: string;
408
+ } | {
409
+ type: 'approval_settled';
410
+ id: string;
411
+ granted: boolean;
412
+ } | {
413
+ type: 'approval_decided';
414
+ kind: ApprovalKind;
415
+ subject: string;
416
+ scope: string;
417
+ detail?: string;
418
+ granted: boolean;
419
+ source: Exclude<ApprovalSource, 'asked'>;
420
+ };
421
+ /**
422
+ * A line a run wants the user to read — how it isolates, what it shares. Not a
423
+ * {@link SessionMessage}: hosts that show notices as toasts already do, and
424
+ * widening the union would break every exhaustive switch over it. A host with
425
+ * no toast channel (the daemon) hands these to its clients by its own means.
426
+ */
427
+ type SessionNotice = {
428
+ type: 'notice';
429
+ level: 'info' | 'warn' | 'error';
430
+ message: string;
431
+ };
432
+ type SessionBroadcaster = (msg: SessionMessage) => void;
433
+ declare function serializeTask(t: Task): SerializedTask;
434
+ declare function serializeTaskStatus(t: Task, idleSince?: string | null, isolation?: TaskIsolation | null): SerializedTaskStatus;
435
+ declare function serializePlan(plan: LegacyPlanState): SerializedPlan;
436
+ declare function executionSummary(tasks: Task[]): {
437
+ total: number;
438
+ completed: number;
439
+ failed: number;
440
+ };
441
+ declare const CHECKPOINT_TRUNCATE_LENGTH = 120;
442
+ /** Truncate a checkpoint summary to a single line of at most CHECKPOINT_TRUNCATE_LENGTH chars. */
443
+ declare function truncateCheckpointSummary(summary: string): string;
444
+
445
+ /**
446
+ * @param toolLabel The agent's own name for the tool, when a harness planner
447
+ * produced the call (ADR-0009). It replaces the member name in the summary, so
448
+ * the timeline says `Edit` rather than the catch-all `agent_tool` — and says
449
+ * `WebFetch` rather than claiming a shell command ran.
450
+ */
451
+ declare function summarizeToolCall(tool: string, argsJson: string, toolLabel?: string): string;
452
+ /**
453
+ * Classify a tool outcome from its result text and success flag, so a surface
454
+ * can render a refused `rm` or a denied `npm test` distinctly from a
455
+ * successful run without re-deriving the refusal signatures per surface. The
456
+ * refusal/denial strings come from `commandPolicy.ts`, `BaseFileSystem.ts`,
457
+ * and the research-subagent wrapper — keeping the matching here means the
458
+ * signatures stay in one place alongside the human summary.
459
+ */
460
+ declare function classifyOutcome(success: boolean, output: string): ResearchStepOutcome;
461
+
462
+ /**
463
+ * What a surface draws for one planner conversation (#51): an ordered list of
464
+ * display blocks, built once in core from the `SessionMessage` stream and drawn
465
+ * per surface. Every block carries an `id` that stays the same for as long as
466
+ * the block exists, so a surface can key its own UI state on it — whether a
467
+ * block is expanded is that UI state, and deliberately not part of a block.
468
+ */
469
+ type DisplayBlock = MessageBlock | ThinkingDisplayBlock | ToolBlock | SubagentBlock | ApprovalBlock | PlanBlock | UsageBlock;
470
+ type MessageRole = 'user' | 'planner' | 'system' | 'error';
471
+ interface MessageBlock {
472
+ type: 'message';
473
+ id: string;
474
+ role: MessageRole;
475
+ text: string;
476
+ /** Deltas are still arriving. */
477
+ streaming: boolean;
478
+ turnId?: string;
479
+ /**
480
+ * Present while the text is the streamed segment of a reply rather than a
481
+ * settled message: provisional, replaced by the turn's `planner_message` when
482
+ * it is the final segment, and never saved to the transcript.
483
+ */
484
+ segmentId?: string;
485
+ }
486
+ interface ThinkingDisplayBlock {
487
+ type: 'thinking';
488
+ id: string;
489
+ text: string;
490
+ streaming: boolean;
491
+ turnId?: string;
492
+ segmentId?: string;
493
+ /** Set when a subagent thought it; the block then sits among that subagent's children. */
494
+ subagentId?: string;
495
+ }
496
+ /** The two halves of a Claude Code-style command row: `Name(keyArg)`. */
497
+ interface ToolHeadline {
498
+ name: string;
499
+ keyArg: string;
500
+ }
501
+ type ToolStatus = 'pending' | 'ok' | 'error' | 'denied' | 'interrupted';
502
+ interface ToolBlock {
503
+ type: 'tool';
504
+ id: string;
505
+ toolCallId?: string;
506
+ tool: string;
507
+ toolLabel?: string;
508
+ headline: ToolHeadline;
509
+ /** The arguments exactly as announced (JSON), for the expanded view. */
510
+ args: string;
511
+ status: ToolStatus;
512
+ /** How the call ended, finer than `status`: a refused command and a denied path both read `denied`. */
513
+ outcome?: ResearchStepOutcome;
514
+ output: string;
515
+ /** Every line of `output`, counted as {@link outputPreview} counts them. */
516
+ outputLineCount: number;
517
+ turnId?: string;
518
+ /**
519
+ * The subagent this call starts. The call becomes that subagent's block once
520
+ * the subagent announces itself, so the two never show as separate rows.
521
+ */
522
+ spawns?: string;
523
+ }
524
+ type SubagentStatus = 'running' | SubagentOutcome;
525
+ type SubagentChild = ToolBlock | ThinkingDisplayBlock | MessageBlock;
526
+ interface SubagentBlock {
527
+ type: 'subagent';
528
+ id: string;
529
+ subagentId: string;
530
+ /** The planner's call that started it, when one was announced. */
531
+ toolCallId?: string;
532
+ brief: string;
533
+ model?: string;
534
+ status: SubagentStatus;
535
+ children: readonly SubagentChild[];
536
+ /** What it handed back to the planner. */
537
+ digest: string;
538
+ /** Its own share, already counted in the usage line. */
539
+ usage?: UsageTotals;
540
+ turnId?: string;
541
+ }
542
+ type ApprovalStatus = 'pending' | 'granted' | 'denied';
543
+ interface ApprovalBlock {
544
+ type: 'approval';
545
+ id: string;
546
+ /** The request's id, which a surface answers through. Absent for a decision nobody was asked about. */
547
+ approvalId?: string;
548
+ kind: ApprovalKind;
549
+ subject: string;
550
+ scope: string;
551
+ detail?: string;
552
+ status: ApprovalStatus;
553
+ /** Absent while pending. */
554
+ decidedBy?: ApprovalSource;
555
+ turnId?: string;
556
+ }
557
+ type PlanMarkerStatus = 'building' | 'generated' | 'updated';
558
+ interface PlanBlock {
559
+ type: 'plan';
560
+ id: string;
561
+ status: PlanMarkerStatus;
562
+ /** The plan envelope streamed so far — `parsePartialPlan` reads rows from it. Empty once settled. */
563
+ text: string;
564
+ taskCount?: number;
565
+ turnId?: string;
566
+ /** While building: the segment whose envelope is streaming, which a retraction of that segment takes back. */
567
+ segmentId?: string;
568
+ }
569
+ /** The token line. There is at most one, and it is always the last block. */
570
+ interface UsageBlock {
571
+ type: 'usage';
572
+ id: string;
573
+ totals: UsageTotals;
574
+ contextFill?: {
575
+ usedTokens: number;
576
+ windowTokens: number;
577
+ };
578
+ bySubagent?: Record<string, UsageTotals>;
579
+ }
580
+
581
+ /**
582
+ * A line a surface adds to the conversation itself rather than receiving from
583
+ * the session: the user's prompt as it is sent, a notice, an error.
584
+ */
585
+ interface LocalEntry {
586
+ type: 'local_entry';
587
+ role: 'user' | 'system' | 'error';
588
+ text: string;
589
+ }
590
+ type ConversationInput = SessionMessage | LocalEntry;
591
+ interface ConversationView {
592
+ readonly blocks: readonly DisplayBlock[];
593
+ readonly nextId: number;
594
+ /**
595
+ * The newest transcript entry the view accounts for. Plan markers and system
596
+ * notes reach a surface only inside the transcript a `plan_generated`
597
+ * carries, so entries after this one are what is new in it.
598
+ */
599
+ readonly transcriptAt?: string;
600
+ }
601
+ declare const EMPTY_CONVERSATION: ConversationView;
602
+ /**
603
+ * Fold one input into the view. Pure and incremental: blocks the input does
604
+ * not touch keep their identity, and an input that changes nothing returns
605
+ * `view` itself — surfaces memoize their drawing on that, and text deltas
606
+ * arrive quickly.
607
+ */
608
+ declare function reduceConversation(view: ConversationView, input: ConversationInput): ConversationView;
609
+
610
+ /**
611
+ * The view a saved session reopens with: what its transcript and research log
612
+ * kept, in the order it happened — messages, notices and the compaction
613
+ * summary, plan markers, tool calls with each subagent's calls nested under
614
+ * it, and the token line. Reasoning and streamed text were never saved, so a
615
+ * reload has none.
616
+ *
617
+ * The research log interleaves with the transcript by time. Entries older than
618
+ * a compaction's first kept message went with the turns it condensed.
619
+ */
620
+ declare function fromTranscript(conversationHistory: readonly ConversationMessage[] | undefined, researchLog: readonly ResearchLogEntry[] | undefined, plannerUsage?: PlannerUsage): ConversationView;
621
+
622
+ /**
623
+ * The one-line head of a command row, shared by every surface: the tool's name
624
+ * and the argument that says what the call is about.
625
+ *
626
+ * @param args The call's arguments as announced (JSON). Anything that is not a
627
+ * JSON object is shown as it came, on one line.
628
+ * @param toolLabel A harness planner's own name for the tool (ADR-0009).
629
+ */
630
+ declare function toolHeadline(tool: string, args: string, toolLabel?: string): ToolHeadline;
631
+ interface OutputPreview {
632
+ lines: string[];
633
+ hiddenLineCount: number;
634
+ }
635
+ /** The lines of a tool's output as a reader sees them — what {@link outputPreview} shows and counts. */
636
+ declare function outputLines(output: string): string[];
637
+ /** The head of a tool's output for a collapsed row, and how many lines it leaves out. */
638
+ declare function outputPreview(output: string, maxLines: number): OutputPreview;
639
+
640
+ /**
641
+ * Prompts the user sent while a planner turn was in flight, waiting to be the
642
+ * next turn's input. The hold is a plain immutable list, oldest first, so a
643
+ * pure reducer can keep it in state and a host can keep it in a field — and
644
+ * either can draw it as is.
645
+ */
646
+ type PromptHold = readonly string[];
647
+ declare const EMPTY_HOLD: PromptHold;
648
+ /** One prompt taken out of the hold, and what is left behind. */
649
+ interface TakenPrompt {
650
+ text: string;
651
+ rest: PromptHold;
652
+ }
653
+ declare function holdPrompt(hold: PromptHold, text: string): PromptHold;
654
+ /** The prompt a settled turn sends next: the oldest, so they go in the order typed. */
655
+ declare function drainNext(hold: PromptHold): TakenPrompt | undefined;
656
+ /**
657
+ * Takes back the newest prompt — the one the user most likely just regretted —
658
+ * while the older ones stay queued behind the turn.
659
+ */
660
+ declare function unsendLatest(hold: PromptHold): TakenPrompt | undefined;
661
+ /**
662
+ * A stopped turn takes its queue with it: the prompts were written against a
663
+ * turn that no longer exists, so they come back as one draft to edit and
664
+ * resend rather than firing off after the stop.
665
+ */
666
+ declare function unsendAll(hold: PromptHold): TakenPrompt | undefined;
667
+ /** Where an unsent prompt lands in the drafting input: above what is being typed, never replacing it. */
668
+ declare function aheadOfDraft(text: string, draft: string): string;
669
+
670
+ /**
671
+ * The planner turn a surface has open, and the one its user stopped. Kept
672
+ * beside the view rather than in it: the view is what a turn drew, this is
673
+ * whether the surface still wants to hear from it.
674
+ */
675
+ interface TurnGate {
676
+ readonly open: string | null;
677
+ readonly stopped: string | null;
678
+ }
679
+ declare const NO_TURN: TurnGate;
680
+ interface GatedConversation {
681
+ view: ConversationView;
682
+ gate: TurnGate;
683
+ }
684
+ /**
685
+ * One input into the view, the stop rule applied: a turn the user stopped is
686
+ * over on screen, and whatever it streams until the backend notices is
687
+ * dropped. An input that changes nothing hands back the same view and gate.
688
+ */
689
+ declare function followTurn(view: ConversationView, gate: TurnGate, input: ConversationInput): GatedConversation;
690
+ /**
691
+ * The user stopped the planner. The turn ends on screen now rather than when
692
+ * the backend notices the abort, so the surface is free at once and the
693
+ * turn's late output never lands. Nothing changes when no turn is open.
694
+ */
695
+ declare function stopTurn(view: ConversationView, gate: TurnGate): GatedConversation;
696
+
697
+ /**
698
+ * Whether the conversation holds anything the collapsed view hides — thinking,
699
+ * a command row's arguments and output, a subagent's children and digest. The
700
+ * detail toggle is the only expansion control (ADR-0017, X1), so a surface
701
+ * offers it only when it would do something.
702
+ */
703
+ declare function hasHiddenDetail(blocks: readonly DisplayBlock[]): boolean;
704
+
705
+ /** The conversation line for a task starting, worded the same on every surface. */
706
+ declare function taskStartedNotice(title: string, runner?: string): string;
707
+
708
+ export { holdPrompt as $, type Adr0013IsolationRun as A, aheadOfDraft as B, CHECKPOINT_TRUNCATE_LENGTH as C, type DisplayBlock as D, EMPTY_CONVERSATION as E, applyTaskOps as F, type GatedConversation as G, canMergeTasks as H, canSetDependencies as I, canSplitTask as J, capConflictFiles as K, type LocalEntry as L, type MessageBlock as M, NO_TURN as N, type OutputPreview as O, type PlanBlock as P, classifyOutcome as Q, dependencyCandidates as R, type SerializedPlan as S, type TakenPrompt as T, type UsageBlock as U, dependentsOf as V, drainNext as W, executionSummary as X, followTurn as Y, fromTranscript as Z, hasHiddenDetail as _, type Adr0013PlanIsolation as a, migratePlanIsolation as a0, outputLines as a1, outputPreview as a2, parseTaskOpsJson as a3, reduceConversation as a4, serializePlan as a5, serializeTask as a6, serializeTaskStatus as a7, stopTurn as a8, summarizeToolCall as a9, taskStartedNotice as aa, textHasTaskOps as ab, toolHeadline as ac, truncateCheckpointSummary as ad, unsendAll as ae, unsendLatest as af, type Adr0013TaskRecord as b, type ApplyTaskOpsResult as c, type ApprovalBlock as d, type ApprovalStatus as e, type ConversationInput as f, type ConversationView as g, EMPTY_HOLD as h, type MessageRole as i, type PlanMarkerStatus as j, type PromptHold as k, type SerializedTask as l, type SerializedTaskStatus as m, type SessionBroadcaster as n, type SessionMessage as o, type SessionNotice as p, type SubagentBlock as q, type SubagentChild as r, type SubagentStatus as s, type TaskOp as t, type TaskRef as u, type ThinkingDisplayBlock as v, type ToolBlock as w, type ToolHeadline as x, type ToolStatus as y, type TurnGate as z };