@ordewell/core 0.9.1 → 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-BjiyCQ8O.d.ts → IFileSystem-BCJBHFwB.d.ts} +1 -1
  2. package/dist/{IFileSystem-Cy-TAWvu.d.mts → IFileSystem-Bj6fy1cG.d.mts} +1 -1
  3. package/dist/{IRunner-Corods20.d.ts → IRunner-C7_SSpzG.d.ts} +228 -13
  4. package/dist/{IRunner-D6YfbmHn.d.mts → IRunner-hWAJC4hg.d.mts} +228 -13
  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-OOZWSUIE.mjs → chunk-CH5IGWFS.mjs} +545 -966
  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 +3107 -3988
  18. package/dist/index.d.ts +3107 -3988
  19. package/dist/index.js +21576 -17642
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +19222 -15561
  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 +45 -9
  39. package/dist/testing.js.map +1 -1
  40. package/dist/testing.mjs +46 -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-OOZWSUIE.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-BmUp_Lko.d.mts +0 -1222
  63. package/dist/plan-utils-tWfH48YX.d.ts +0 -1222
@@ -24,12 +24,21 @@ __export(plan_utils_exports, {
24
24
  DEFAULT_RUNNERS: () => DEFAULT_RUNNERS,
25
25
  EMPTY_CONVERSATION: () => EMPTY_CONVERSATION,
26
26
  EMPTY_HOLD: () => EMPTY_HOLD,
27
+ EMPTY_RUN: () => EMPTY_RUN,
27
28
  EMPTY_TASK_LOG: () => EMPTY_TASK_LOG,
28
29
  NOT_STARTED: () => NOT_STARTED,
29
30
  NO_TURN: () => NO_TURN,
31
+ SUPERVISOR_LEVEL_LABEL: () => SUPERVISOR_LEVEL_LABEL,
32
+ addAttemptUsage: () => addAttemptUsage,
33
+ addModelUsage: () => addModelUsage,
34
+ addPlannerUsage: () => addPlannerUsage,
35
+ addUsage: () => addUsage,
36
+ addUsageTotals: () => addUsageTotals,
30
37
  aheadOfDraft: () => aheadOfDraft,
31
38
  approvalLabel: () => approvalLabel,
32
39
  awaitingLabel: () => awaitingLabel,
40
+ blockedByLabel: () => blockedByLabel,
41
+ blockingFailures: () => blockingFailures,
33
42
  canMergeTasks: () => canMergeTasks,
34
43
  canSetDependencies: () => canSetDependencies,
35
44
  canSplitTask: () => canSplitTask,
@@ -41,10 +50,12 @@ __export(plan_utils_exports, {
41
50
  diffStat: () => diffStat,
42
51
  diffSummary: () => diffSummary,
43
52
  drainNext: () => drainNext,
53
+ endAttemptUsage: () => endAttemptUsage,
44
54
  followTurn: () => followTurn,
45
55
  fromTranscript: () => fromTranscript,
46
56
  hasHiddenDetail: () => hasHiddenDetail,
47
57
  holdPrompt: () => holdPrompt,
58
+ isMeasured: () => isMeasured,
48
59
  isTaskRunning: () => isTaskRunning,
49
60
  loadedSkillTokens: () => loadedSkillTokens,
50
61
  markAction: () => markAction,
@@ -54,32 +65,43 @@ __export(plan_utils_exports, {
54
65
  opsEditable: () => opsEditable,
55
66
  outputLines: () => outputLines,
56
67
  outputPreview: () => outputPreview,
68
+ parseCount: () => parseCount,
69
+ partedPromptUsage: () => partedPromptUsage,
57
70
  pastGateConfirmation: () => pastGateConfirmation,
71
+ pausedOnLimit: () => pausedOnLimit,
72
+ planUsage: () => planUsage,
73
+ plannerContextFill: () => plannerContextFill,
58
74
  reduceConversation: () => reduceConversation,
59
75
  reduceTaskLog: () => reduceTaskLog,
60
76
  replayTaskLog: () => replayTaskLog,
77
+ resolveTaskRef: () => resolveTaskRef,
78
+ resumeLabel: () => resumeLabel,
79
+ resumeTime: () => resumeTime,
61
80
  runnerToolSubject: () => runnerToolSubject,
62
81
  skillTokens: () => skillTokens,
82
+ skippedLabel: () => skippedLabel,
83
+ startAttemptUsage: () => startAttemptUsage,
63
84
  stopTurn: () => stopTurn,
64
85
  summarizeToolCall: () => summarizeToolCall,
86
+ takeAt: () => takeAt,
65
87
  taskOrderLabel: () => taskOrderLabel,
66
88
  taskRef: () => taskRef,
89
+ taskRefOf: () => taskRefOf,
67
90
  taskRowActions: () => taskRowActions,
68
91
  taskRowView: () => taskRowView,
69
92
  taskStartedNotice: () => taskStartedNotice,
70
93
  taskStatusKind: () => taskStatusKind,
71
94
  titledRefs: () => titledRefs,
72
95
  titledTaskRef: () => titledTaskRef,
96
+ titledTaskRefOf: () => titledTaskRefOf,
73
97
  toolHeadline: () => toolHeadline,
74
98
  truncateCheckpointSummary: () => truncateCheckpointSummary,
75
99
  unsendAll: () => unsendAll,
76
- unsendLatest: () => unsendLatest
100
+ unsendLatest: () => unsendLatest,
101
+ usageLine: () => usageLine
77
102
  });
78
103
  module.exports = __toCommonJS(plan_utils_exports);
79
104
 
80
- // src/services/TaskOps.ts
81
- var import_uuid2 = require("uuid");
82
-
83
105
  // src/models/Task.ts
84
106
  var import_uuid = require("uuid");
85
107
 
@@ -102,9 +124,9 @@ function loadedSkillTokens(text2, loaded) {
102
124
  return skillTokens(text2).filter((t) => loaded.includes(t.name));
103
125
  }
104
126
 
105
- // src/order-labels.ts
127
+ // src/services/TaskRefs.ts
106
128
  function taskOrderLabel(task, parent) {
107
- return parent ? `${parent.order}.${task.order}` : String(task.order);
129
+ return parent ? `${parent.label ?? parent.order}.${task.order}` : String(task.order);
108
130
  }
109
131
  function taskRef(task, parent) {
110
132
  return `#${taskOrderLabel(task, parent)}`;
@@ -112,10 +134,49 @@ function taskRef(task, parent) {
112
134
  function titledTaskRef(task, parent) {
113
135
  return `${taskRef(task, parent)} ${task.title}`;
114
136
  }
137
+ function* depthFirst(tasks, parent, parentLabel) {
138
+ for (const task of tasks) {
139
+ const label = taskOrderLabel(task, parent ? { order: parent.order, label: parentLabel } : void 0);
140
+ yield { task, parent, label };
141
+ yield* depthFirst(task.subtasks ?? [], task, label);
142
+ }
143
+ }
144
+ function findTaskRef(tasks, id) {
145
+ for (const match of depthFirst(tasks)) if (match.task.id === id) return match;
146
+ return void 0;
147
+ }
148
+ function taskRefOf(tasks, id) {
149
+ const found = findTaskRef(tasks, id);
150
+ return found && `#${found.label}`;
151
+ }
152
+ function titledTaskRefOf(tasks, id) {
153
+ const found = findTaskRef(tasks, id);
154
+ return found && `#${found.label} ${found.task.title}`;
155
+ }
156
+ function resolveTaskRef(tasks, ref) {
157
+ const s = String(ref).trim();
158
+ if (!s) return void 0;
159
+ for (const { task } of depthFirst(tasks)) if (task.id === s) return task;
160
+ const label = s.startsWith("#") ? s.slice(1) : s;
161
+ if (/^\d+(\.\d+)*$/.test(label)) {
162
+ let level = tasks;
163
+ let found;
164
+ for (const order of label.split(".").map(Number)) {
165
+ found = level?.find((t) => t.order === order);
166
+ level = found?.subtasks;
167
+ }
168
+ return found;
169
+ }
170
+ for (const { task } of depthFirst(tasks)) if (task.title === s) return task;
171
+ return void 0;
172
+ }
115
173
 
116
174
  // src/models/Task.ts
117
175
  var SKILL_NAME = new RegExp(`^${SKILL_NAME_PATTERN}$`);
118
176
  var DEFAULT_RUNNERS = ["claude-code"];
177
+ function pausedOnLimit(task) {
178
+ return task.status === "awaiting_user" && task.awaitingReason === "usage-limit";
179
+ }
119
180
 
120
181
  // src/taskRow/status.ts
121
182
  var isTaskRunning = (task) => task.status === "in_progress" || task.status === "running";
@@ -126,6 +187,8 @@ function taskStatusKind(task) {
126
187
  return "done";
127
188
  case "failed":
128
189
  return "failed";
190
+ case "skipped":
191
+ return "skipped";
129
192
  case "blocked":
130
193
  return "blocked";
131
194
  case "awaiting_user":
@@ -138,39 +201,63 @@ var AWAITING_LABELS = {
138
201
  input: "Waiting for your input",
139
202
  checkpoint: "Checkpoint",
140
203
  conflict: "Merge conflict",
141
- "files-changed": "Changed tracked files"
204
+ "files-changed": "Changed tracked files",
205
+ "usage-limit": "Paused on a usage limit"
142
206
  };
143
207
  function awaitingLabel(task) {
144
208
  return task.status === "awaiting_user" && task.awaitingReason ? AWAITING_LABELS[task.awaitingReason] : null;
145
209
  }
210
+ function blockedByLabel(refs) {
211
+ return `Blocked by ${refs.join(", ")}`;
212
+ }
213
+ function skippedLabel(reason) {
214
+ return reason ? `Skipped: ${reason}` : "Skipped";
215
+ }
146
216
  function approvalLabel(count) {
147
217
  if (!count) return null;
148
218
  return count > 1 ? `Waiting for approval (${count})` : "Waiting for approval";
149
219
  }
220
+ function resumeTime(at, now = /* @__PURE__ */ new Date()) {
221
+ const clock = at.toLocaleTimeString("en-US", { hour: "numeric", minute: at.getMinutes() === 0 ? void 0 : "2-digit", hour12: true }).replace(/[ \u202f]([AP]M)$/i, (_m, meridiem) => meridiem.toLowerCase());
222
+ if (at.toDateString() === now.toDateString()) return clock;
223
+ return `${at.toLocaleDateString("en-US", { month: "short", day: "numeric" })}, ${clock}`;
224
+ }
225
+ function resumeLabel(task, now = /* @__PURE__ */ new Date()) {
226
+ if (task.status !== "awaiting_user" || task.awaitingReason !== "usage-limit" || !task.resumeAt) return null;
227
+ const at = new Date(task.resumeAt);
228
+ return Number.isNaN(at.getTime()) ? null : `Resumes at ${resumeTime(at, now)}`;
229
+ }
150
230
 
151
- // src/services/TaskOps.ts
152
- function detectCycle(tasks) {
231
+ // src/services/PlannedTask.ts
232
+ var import_uuid2 = require("uuid");
233
+ function findCycle(tasks) {
153
234
  const byId = new Map(tasks.map((t) => [t.id, t]));
154
- const visiting = /* @__PURE__ */ new Set();
235
+ const path = [];
155
236
  const done = /* @__PURE__ */ new Set();
156
237
  const visit = (id) => {
157
238
  if (done.has(id)) return null;
158
- if (visiting.has(id)) return id;
159
- visiting.add(id);
239
+ const at = path.indexOf(id);
240
+ if (at >= 0) return path.slice(at);
241
+ path.push(id);
160
242
  for (const dep of byId.get(id)?.dependencies ?? []) {
161
243
  const hit = visit(dep);
162
244
  if (hit) return hit;
163
245
  }
164
- visiting.delete(id);
246
+ path.pop();
165
247
  done.add(id);
166
248
  return null;
167
249
  };
168
250
  for (const t of tasks) {
169
251
  const hit = visit(t.id);
170
- if (hit) return byId.get(hit)?.title ?? hit;
252
+ if (hit) return hit.map((id) => byId.get(id));
171
253
  }
172
254
  return null;
173
255
  }
256
+ function detectCycle(tasks) {
257
+ return findCycle(tasks)?.[0].title ?? null;
258
+ }
259
+
260
+ // src/services/TaskOps.ts
174
261
  function canMergeTasks(tasks, selectedIds) {
175
262
  if (selectedIds.length < 2) return { ok: false, error: "Select at least two tasks to merge" };
176
263
  const idSet = new Set(selectedIds);
@@ -467,9 +554,13 @@ function normalizeAgentArgs(tool, args) {
467
554
  function settledMessage(id, role, text2, turnId) {
468
555
  return { type: "message", id, role, text: text2, streaming: false, ...turnId ? { turnId } : {} };
469
556
  }
470
- function userMessage(id, text2, skills, turnId) {
557
+ function userMessage(id, text2, skills, { turnId, forced } = {}) {
471
558
  const block = settledMessage(id, "user", text2, turnId);
472
- return skills.length > 0 ? { ...block, skills: skills.map((s) => s.name) } : block;
559
+ return {
560
+ ...block,
561
+ ...skills.length > 0 ? { skills: skills.map((s) => s.name) } : {},
562
+ ...forced ? { forced: true } : {}
563
+ };
473
564
  }
474
565
  function skillLoadNotice({ invokedBy, name, source, path, attaches }) {
475
566
  return { invokedBy, name, source, path, ...attaches ? { attaches } : {} };
@@ -488,8 +579,8 @@ function subagentBlock(id, { toolCallId, model, usage, turnId, ...fields }) {
488
579
  ...turnId ? { turnId } : {}
489
580
  };
490
581
  }
491
- function usageBlock(id, { totals, bySubagent, contextFill }) {
492
- return { type: "usage", id, totals, ...bySubagent ? { bySubagent } : {}, ...contextFill ? { contextFill } : {} };
582
+ function usageBlock(id, { totals, bySubagent, byModel, model, contextFill }) {
583
+ return { type: "usage", id, totals, ...byModel ? { byModel } : {}, ...model ? { model } : {}, ...bySubagent ? { bySubagent } : {}, ...contextFill ? { contextFill } : {} };
493
584
  }
494
585
  var STATUS_OF_OUTCOME = {
495
586
  success: "ok",
@@ -528,6 +619,44 @@ function planMarker(content) {
528
619
  return { status: content.startsWith("Plan updated") ? "updated" : "generated", ...count ? { taskCount: Number(count[1]) } : {} };
529
620
  }
530
621
 
622
+ // src/services/blocking.ts
623
+ var UNSTARTED = /* @__PURE__ */ new Set(["pending", "approved", "blocked"]);
624
+ function blockingFailures(tasks) {
625
+ const byId = /* @__PURE__ */ new Map();
626
+ const index = (list) => {
627
+ for (const t of list) {
628
+ byId.set(t.id, t);
629
+ index(t.subtasks ?? []);
630
+ }
631
+ };
632
+ index(tasks);
633
+ const memo = /* @__PURE__ */ new Map();
634
+ const failuresOf = (id, visiting) => {
635
+ const known = memo.get(id);
636
+ if (known) return known;
637
+ const task = byId.get(id);
638
+ if (!task || !UNSTARTED.has(task.status) || visiting.has(id)) return [];
639
+ visiting.add(id);
640
+ const failed = /* @__PURE__ */ new Set();
641
+ for (const depId of task.dependencies) {
642
+ const dep = byId.get(depId);
643
+ if (!dep || dep.status === "completed" || dep.status === "skipped") continue;
644
+ if (dep.status === "failed") failed.add(depId);
645
+ else for (const root of failuresOf(depId, visiting)) failed.add(root);
646
+ }
647
+ visiting.delete(id);
648
+ const result = [...failed];
649
+ memo.set(id, result);
650
+ return result;
651
+ };
652
+ const blocked = /* @__PURE__ */ new Map();
653
+ for (const id of byId.keys()) {
654
+ const failures = failuresOf(id, /* @__PURE__ */ new Set());
655
+ if (failures.length > 0) blocked.set(id, failures);
656
+ }
657
+ return blocked;
658
+ }
659
+
531
660
  // src/services/SessionMessage.ts
532
661
  var CHECKPOINT_TRUNCATE_LENGTH = 120;
533
662
  function truncateCheckpointSummary(summary) {
@@ -541,7 +670,22 @@ function capConflictFiles(files, max = 5) {
541
670
  return files.length <= max ? files.join(", ") : `${files.slice(0, max).join(", ")}, +${files.length - max} more`;
542
671
  }
543
672
 
673
+ // src/services/runSettingsText.ts
674
+ var SUPERVISOR_LEVEL_LABEL = { off: "Off", watch: "Watch", act: "Act" };
675
+ function parseCount(arg, min) {
676
+ if (arg === void 0 || !/^\d+$/.test(arg)) return null;
677
+ const n = Number(arg);
678
+ return n >= min ? n : null;
679
+ }
680
+
544
681
  // src/models/Usage.ts
682
+ function partedPromptUsage(parts) {
683
+ const reported = [parts.uncached, parts.cacheRead, parts.cacheWrite].filter((n) => n !== void 0);
684
+ return {
685
+ ...reported.length > 0 ? { inputTokens: reported.reduce((a, b) => a + b, 0) } : {},
686
+ ...parts.cacheRead !== void 0 ? { cachedInputTokens: parts.cacheRead } : {}
687
+ };
688
+ }
545
689
  var TOKEN_FIELDS = ["inputTokens", "outputTokens", "cachedInputTokens"];
546
690
  function addUsage(totals, record) {
547
691
  const next = { ...totals };
@@ -556,13 +700,21 @@ function addUsage(totals, record) {
556
700
  return next;
557
701
  }
558
702
  function addPlannerUsage(usage, record) {
559
- const next = { ...usage, totals: addUsage(usage.totals, record) };
703
+ const next = { ...usage, totals: addUsage(usage.totals, record), byModel: addModelUsage(usage.byModel ?? [], record) };
560
704
  if (record.subagentId) {
561
705
  next.bySubagent = { ...usage.bySubagent, [record.subagentId]: addUsage(usage.bySubagent?.[record.subagentId] ?? {}, record) };
562
706
  return next;
563
707
  }
708
+ if (!record.aggregate) {
709
+ if (record.source !== usage.source || record.model !== usage.model) {
710
+ delete next.lastPromptTokens;
711
+ delete next.contextWindow;
712
+ }
713
+ next.source = record.source;
714
+ next.model = record.model;
715
+ }
564
716
  if (record.inputTokens !== void 0) next.lastPromptTokens = record.inputTokens;
565
- if (record.contextWindow !== void 0 && record.contextWindow > 0) next.contextWindow = record.contextWindow;
717
+ if (record.contextWindow !== void 0) next.contextWindow = record.contextWindow > 0 ? record.contextWindow : void 0;
566
718
  return next;
567
719
  }
568
720
  function isMeasured(totals) {
@@ -572,6 +724,8 @@ function usageLine(usage) {
572
724
  const contextFill = plannerContextFill(usage);
573
725
  return {
574
726
  totals: usage.totals,
727
+ ...usage.byModel ? { byModel: usage.byModel } : {},
728
+ ...usage.model ? { model: usage.model } : {},
575
729
  ...usage.bySubagent ? { bySubagent: usage.bySubagent } : {},
576
730
  ...contextFill ? { contextFill } : {}
577
731
  };
@@ -580,6 +734,48 @@ function plannerContextFill(usage) {
580
734
  if (usage.lastPromptTokens === void 0 || usage.contextWindow === void 0 || usage.contextWindow <= 0) return void 0;
581
735
  return { usedTokens: usage.lastPromptTokens, windowTokens: usage.contextWindow };
582
736
  }
737
+ function addModelUsage(models, record) {
738
+ const index = models.findIndex((m) => m.source === record.source && m.model === record.model);
739
+ if (index < 0) return [...models, { source: record.source, ...record.model ? { model: record.model } : {}, totals: addUsage({}, record) }];
740
+ return models.map((m, i) => i === index ? { ...m, totals: addUsage(m.totals, record) } : m);
741
+ }
742
+ function startAttemptUsage(attempt, runner, startedAt) {
743
+ return { attempt, runner, startedAt, wallTimeMs: 0, totals: {}, byModel: [] };
744
+ }
745
+ function addAttemptUsage(usage, record, now) {
746
+ const folded = addPlannerUsage(usage, record);
747
+ return { ...usage, ...folded, lastPromptTokens: folded.lastPromptTokens, contextWindow: folded.contextWindow, wallTimeMs: Math.max(0, Date.parse(now) - Date.parse(usage.startedAt)) };
748
+ }
749
+ function endAttemptUsage(usage, now) {
750
+ return { ...usage, endedAt: now, wallTimeMs: Math.max(0, Date.parse(now) - Date.parse(usage.startedAt)) };
751
+ }
752
+ function addUsageTotals(totals, addition) {
753
+ let next = addUsage(totals, { source: "", inputTokens: addition.inputTokens, outputTokens: addition.outputTokens, cachedInputTokens: addition.cachedInputTokens });
754
+ for (const [currency, amount] of Object.entries(addition.reportedCost ?? {})) next = addUsage(next, { source: "", reportedCost: { currency, amount } });
755
+ return next;
756
+ }
757
+ function planUsage(tasks) {
758
+ const result = { totals: {}, byModel: [], wallTimeMs: 0, attempts: 0 };
759
+ for (const task of tasks) {
760
+ const child = planUsage(task.subtasks ?? []);
761
+ result.totals = addUsageTotals(result.totals, child.totals);
762
+ result.wallTimeMs += child.wallTimeMs;
763
+ result.attempts += child.attempts;
764
+ const models = [...child.byModel];
765
+ for (const usage of task.attemptUsage ?? []) {
766
+ result.totals = addUsageTotals(result.totals, usage.totals);
767
+ result.wallTimeMs += usage.wallTimeMs;
768
+ result.attempts++;
769
+ models.push(...usage.byModel ?? []);
770
+ }
771
+ for (const model of models) {
772
+ const existing = result.byModel.find((m) => m.source === model.source && m.model === model.model);
773
+ if (existing) existing.totals = addUsageTotals(existing.totals, model.totals);
774
+ else result.byModel.push({ ...model, totals: addUsageTotals({}, model.totals) });
775
+ }
776
+ }
777
+ return result;
778
+ }
583
779
 
584
780
  // src/conversation/lanes.ts
585
781
  function findLastIndex(items, match) {
@@ -710,16 +906,55 @@ function finalSegmentIndex(blocks, turnId, pastPlan = false) {
710
906
  for (let i = blocks.length - 1; i >= 0; i--) {
711
907
  const block = blocks[i];
712
908
  if (block.type === "usage" || block.type === "thinking" || block.type === "approval" || pastPlan && block.type === "plan") continue;
909
+ if (block.type === "command" || block.type === "compaction" || block.type === "runner_loaded") continue;
713
910
  if (block.turnId !== turnId) continue;
714
911
  return isUnsettledSegment(block, turnId) ? i : -1;
715
912
  }
716
913
  return -1;
717
914
  }
718
- function openTurn(view, { turnId, prompt = "", skills = [] }) {
915
+ function openTurn(view, { turnId, prompt = "", skills = [], wakeUps = [], forced }) {
916
+ const adopted = takePendingInputs(view, prompt, wakeUps, turnId);
917
+ view = adopted.view;
918
+ wakeUps = wakeUps.slice(adopted.wakeUps);
919
+ if (wakeUps.length === 0) return adopted.prompt || prompt === "" && adopted.wakeUps > 0 ? view : promptLine(view, turnId, prompt, skills, forced);
920
+ const said = prompt === "" || adopted.prompt ? view : promptLine(view, turnId, prompt, skills, forced);
921
+ const at = prompt === "" ? said.blocks.length : findLastIndex(said.blocks, (b) => b.type === "message" && b.role === "user" && b.turnId === turnId);
922
+ const updates = wakeUps.map((text2, n) => settledMessage(`b${said.nextId + n}`, "wake_up", text2, turnId));
923
+ return { ...said, blocks: [...said.blocks.slice(0, at), ...updates, ...said.blocks.slice(at)], nextId: said.nextId + updates.length };
924
+ }
925
+ function takePendingInputs(view, prompt, wakeUps, turnId) {
926
+ const pending = view.pendingInputs ?? [];
927
+ const users = pending.filter(({ input }) => input.source === "user");
928
+ const updates = pending.filter(({ input }) => input.source === "supervisor");
929
+ const taken = /* @__PURE__ */ new Set();
930
+ let matchedPrompt = false;
931
+ for (let n = 1; prompt !== void 0 && n <= users.length; n++) {
932
+ if (users.slice(0, n).map(({ input }) => input.text).join("\n\n") !== prompt) continue;
933
+ for (const { input } of users.slice(0, n)) taken.add(input.id);
934
+ matchedPrompt = true;
935
+ break;
936
+ }
937
+ let matchedUpdates = 0;
938
+ while (matchedUpdates < wakeUps.length && updates[matchedUpdates]?.input.text === wakeUps[matchedUpdates]) {
939
+ taken.add(updates[matchedUpdates++].input.id);
940
+ }
941
+ if (taken.size === 0) return { view, prompt: false, wakeUps: 0 };
942
+ const blockIds = new Set(pending.filter(({ input }) => taken.has(input.id)).flatMap(({ blockIds: blockIds2 }) => blockIds2));
943
+ return {
944
+ view: {
945
+ ...view,
946
+ blocks: turnId === void 0 ? view.blocks : view.blocks.map((block) => blockIds.has(block.id) ? { ...block, turnId } : block),
947
+ pendingInputs: pending.filter(({ input }) => !taken.has(input.id))
948
+ },
949
+ prompt: matchedPrompt,
950
+ wakeUps: matchedUpdates
951
+ };
952
+ }
953
+ function promptLine(view, turnId, prompt, skills, forced) {
719
954
  const i = findLastIndex(view.blocks, (b) => b.type === "message" && b.role === "user");
720
955
  const sent = view.blocks[i];
721
956
  const adopted = sent?.type === "message" && sent.turnId === void 0 && sent.text === prompt;
722
- const said = adopted ? { ...view, blocks: replaceAt(view.blocks, i, userMessage(sent.id, prompt, skills, turnId)) } : append(view, (id) => userMessage(id, prompt, skills, turnId));
957
+ const said = adopted ? { ...view, blocks: replaceAt(view.blocks, i, userMessage(sent.id, prompt, skills, { turnId, forced })) } : append(view, (id) => userMessage(id, prompt, skills, { turnId, forced }));
723
958
  if (skills.length === 0) return said;
724
959
  const at = adopted ? i + 1 : findLastIndex(said.blocks, (b) => b.type === "message" && b.role === "user") + 1;
725
960
  const loads = skills.map((skill, n) => skillLoadBlock(`b${said.nextId + n}`, skill, turnId));
@@ -737,14 +972,15 @@ function dropBuildingPlan(view, turnId, segmentId) {
737
972
  const blocks = view.blocks.filter((b) => !(isBuildingPlan(b, turnId) && (segmentId === void 0 || b.segmentId === segmentId)));
738
973
  return blocks.length === view.blocks.length ? view : { ...view, blocks };
739
974
  }
740
- function settleReply(view, { content, turnId }) {
975
+ function settleReply(view, { content, turnId, supervisor }) {
976
+ const role = supervisor ? "supervisor" : "planner";
741
977
  if (turnId !== void 0) {
742
978
  const i = finalSegmentIndex(view.blocks, turnId);
743
979
  if (i >= 0) {
744
- return dropBuildingPlan({ ...view, blocks: replaceAt(view.blocks, i, settledMessage(view.blocks[i].id, "planner", content, turnId)) }, turnId);
980
+ return dropBuildingPlan({ ...view, blocks: replaceAt(view.blocks, i, settledMessage(view.blocks[i].id, role, content, turnId)) }, turnId);
745
981
  }
746
982
  }
747
- return append(dropBuildingPlan(view, turnId), (id) => settledMessage(id, "planner", content, turnId));
983
+ return append(dropBuildingPlan(view, turnId), (id) => settledMessage(id, role, content, turnId));
748
984
  }
749
985
  function retractText(view, { turnId, segmentId }) {
750
986
  const blocks = view.blocks.filter((b) => !(isUnsettledSegment(b, turnId) && (segmentId === void 0 || b.segmentId === segmentId)));
@@ -858,6 +1094,8 @@ function syncTranscript(view, { plan, turnId }) {
858
1094
  let latest = view.transcriptAt;
859
1095
  for (const [i, entry] of history.entries()) {
860
1096
  const isNew = view.transcriptAt === void 0 || entry.timestamp > view.transcriptAt;
1097
+ if (isNew && entry.role === "user" && !entry.kind) next = takePendingInputs(next, entry.content, []).view;
1098
+ if (isNew && entry.kind === "wake_up") next = takePendingInputs(next, void 0, [entry.content]).view;
861
1099
  if (isNew && entry.kind === "plan_generated") next = markPlan(next, entry.content, i === committed ? turnId : void 0);
862
1100
  if (isNew && entry.kind === "system") next = append(next, (id) => settledMessage(id, "system", entry.content));
863
1101
  if (isNew && entry.kind === "compaction") next = markCompaction(next, entry.content);
@@ -901,6 +1139,8 @@ function reduceConversation(view, input) {
901
1139
  return settleApproval(view, input);
902
1140
  case "approval_decided":
903
1141
  return decideApproval(view, input);
1142
+ case "execution_action":
1143
+ return append(view, (id) => settledMessage(id, "system", input.text));
904
1144
  case "planner_usage":
905
1145
  return reportUsage(view, input);
906
1146
  default:
@@ -918,9 +1158,22 @@ function fromEntry(entry, loaded = []) {
918
1158
  const { skill } = entry;
919
1159
  return (id) => skillLoadBlock(id(), skillLoadNotice(skill));
920
1160
  }
921
- if (entry.role === "user" && !entry.kind) return (id) => userMessage(id(), entry.content, loaded.map(skillLoadNotice));
922
- const role = entry.kind === "system" || entry.kind === "compaction" ? "system" : entry.role === "user" ? "user" : "planner";
923
- return (id) => settledMessage(id(), role, entry.content);
1161
+ if (entry.role === "user" && !entry.kind) return (id) => userMessage(id(), entry.content, loaded.map(skillLoadNotice), { forced: entry.forced });
1162
+ return (id) => settledMessage(id(), roleOf(entry), entry.content);
1163
+ }
1164
+ function roleOf(entry) {
1165
+ switch (entry.kind) {
1166
+ case "system":
1167
+ case "compaction":
1168
+ case "execution":
1169
+ return "system";
1170
+ case "wake_up":
1171
+ return "wake_up";
1172
+ case "supervisor":
1173
+ return "supervisor";
1174
+ default:
1175
+ return entry.role === "user" ? "user" : "planner";
1176
+ }
924
1177
  }
925
1178
  function loadsAfter(entries, index) {
926
1179
  const loads = [];
@@ -931,7 +1184,7 @@ function loadsAfter(entries, index) {
931
1184
  return loads;
932
1185
  }
933
1186
  function rankOf(entry) {
934
- return entry.role === "user" && (!entry.kind || entry.kind === "skill_load") ? USER : SETTLED;
1187
+ return entry.role === "user" && (!entry.kind || entry.kind === "skill_load" || entry.kind === "wake_up") ? USER : SETTLED;
935
1188
  }
936
1189
  function spawnBrief(step) {
937
1190
  try {
@@ -989,7 +1242,7 @@ function research(log) {
989
1242
  }
990
1243
  return placed;
991
1244
  }
992
- function fromTranscript(conversationHistory, researchLog, plannerUsage) {
1245
+ function fromTranscript(conversationHistory, researchLog, plannerUsage, pendingTurns = []) {
993
1246
  const history = conversationHistory ?? [];
994
1247
  const compacted = history[0]?.kind === "compaction" ? history[0] : void 0;
995
1248
  const kept = compacted ? history.slice(1) : history;
@@ -1003,9 +1256,27 @@ function fromTranscript(conversationHistory, researchLog, plannerUsage) {
1003
1256
  let nextId = 1;
1004
1257
  const id = () => `b${nextId++}`;
1005
1258
  const blocks = placed.map((item) => item.build(id));
1259
+ const pendingInputs = [];
1260
+ for (const input of pendingTurns) {
1261
+ const start = blocks.length;
1262
+ if (input.source === "supervisor") {
1263
+ blocks.push(settledMessage(id(), "wake_up", input.text));
1264
+ } else {
1265
+ const skills = (input.skills ?? []).map(skillLoadNotice);
1266
+ blocks.push(userMessage(id(), input.text, skills, { forced: input.forced }));
1267
+ blocks.push(...skills.map((skill) => skillLoadBlock(id(), skill)));
1268
+ }
1269
+ pendingInputs.push({ input: { ...input, skills: input.skills?.map(skillLoadNotice) }, blockIds: blocks.slice(start).map((block) => block.id) });
1270
+ }
1006
1271
  if (plannerUsage && isMeasured(plannerUsage.totals)) blocks.push(usageBlock(id(), usageLine(plannerUsage)));
1007
1272
  const transcriptAt = history.reduce((latest, e) => latest === void 0 || e.timestamp > latest ? e.timestamp : latest, void 0);
1008
- return { blocks, nextId, ...transcriptAt ? { transcriptAt } : {} };
1273
+ return { blocks, nextId, ...transcriptAt ? { transcriptAt } : {}, ...pendingInputs.length > 0 ? { pendingInputs } : {} };
1274
+ }
1275
+
1276
+ // src/utils/sameMessage.ts
1277
+ function sameMessage(a, b) {
1278
+ const bare = (value) => value.trim().replace(/\.+$/, "").trimEnd();
1279
+ return bare(a) !== "" && bare(a) === bare(b);
1009
1280
  }
1010
1281
 
1011
1282
  // src/conversation/taskLog.ts
@@ -1046,10 +1317,17 @@ function streamWhole(view, lane, key, text2, make) {
1046
1317
  return appendOutput(next, lane, make);
1047
1318
  }
1048
1319
  function agentText(view, text2) {
1049
- return streamWhole(view, null, TEXT_STREAM, text2, (id) => settledMessage(id, "agent", blockText(text2)));
1320
+ const next = streamWhole(view, null, TEXT_STREAM, text2, (id) => settledMessage(id, "agent", blockText(text2)));
1321
+ return { ...next, latestText: next.nextId > view.nextId ? `b${view.nextId}` : view.streams[TEXT_STREAM] };
1050
1322
  }
1051
1323
  function agentTextDelta(view, text2) {
1052
- return streamDelta(view, null, TEXT_STREAM, text2, (id, t) => ({ type: "message", id, role: "agent", text: t, streaming: true }));
1324
+ const next = streamDelta(view, null, TEXT_STREAM, text2, (id, t) => ({ type: "message", id, role: "agent", text: t, streaming: true }));
1325
+ return { ...next, latestText: next.streams[TEXT_STREAM] };
1326
+ }
1327
+ function failTurn(view, message) {
1328
+ const repeated = view.blocks.find((b) => b.id === view.latestText);
1329
+ const settled = repeated?.type === "message" && repeated.role === "agent" && sameMessage(repeated.text, message) ? withoutStream({ ...view, blocks: view.blocks.filter((b) => b !== repeated) }, TEXT_STREAM) : view;
1330
+ return appendOutput({ ...settled, latestText: void 0 }, null, (id) => settledMessage(id, "error", message));
1053
1331
  }
1054
1332
  function thinkingBlock(id, text2, streaming, subagentId) {
1055
1333
  return { type: "thinking", id, text: text2, streaming, ...subagentId ? { subagentId } : {} };
@@ -1114,19 +1392,22 @@ function runnerToolSubject(tool, args) {
1114
1392
  const headline = toolHeadline(mapped, parsed ? JSON.stringify(normalizeAgentArgs(mapped, parsed)) : args, toolLabel);
1115
1393
  return headline.keyArg ? `${headline.name}(${headline.keyArg})` : headline.name;
1116
1394
  }
1117
- function requestApproval2(view, { approvalId, tool, args, allowForTask, toolCallId }) {
1395
+ function requestApproval2(view, { approvalId, tool, args, allowForTask, toolCallId, decided }) {
1118
1396
  if (view.blocks.some((b) => b.type === "approval" && b.approvalId === approvalId)) return view;
1119
- return append(view, (id) => ({
1120
- type: "approval",
1121
- id,
1122
- approvalId,
1123
- kind: "runner_tool",
1124
- subject: runnerToolSubject(tool, args),
1125
- scope: tool,
1126
- status: "pending",
1127
- ...allowForTask ? { allowForTask } : {},
1128
- ...toolCallId ? { toolCallId } : {}
1129
- }));
1397
+ return append(view, (id) => {
1398
+ const block = {
1399
+ type: "approval",
1400
+ id,
1401
+ approvalId,
1402
+ kind: "runner_tool",
1403
+ subject: runnerToolSubject(tool, args),
1404
+ scope: tool,
1405
+ status: "pending",
1406
+ ...allowForTask ? { allowForTask } : {},
1407
+ ...toolCallId ? { toolCallId } : {}
1408
+ };
1409
+ return decided ? answered(block, decided) : block;
1410
+ });
1130
1411
  }
1131
1412
  function settleApproval2(view, approvalId, settle) {
1132
1413
  const i = findLastIndex(view.blocks, (b) => b.type === "approval" && b.approvalId === approvalId);
@@ -1134,36 +1415,73 @@ function settleApproval2(view, approvalId, settle) {
1134
1415
  if (block?.type !== "approval" || block.status !== "pending") return view;
1135
1416
  return { ...view, blocks: replaceAt(view.blocks, i, settle(block)) };
1136
1417
  }
1137
- function decideApproval2(view, { approvalId, decision, note }) {
1138
- return settleApproval2(view, approvalId, (block) => ({
1418
+ function answered(block, decision, note) {
1419
+ return {
1139
1420
  ...block,
1140
1421
  status: decision === "deny" ? "denied" : "granted",
1141
1422
  decidedBy: "asked",
1142
1423
  ...decision === "allowForTask" ? { forTask: true } : {},
1143
1424
  ...note ? { note } : {}
1144
- }));
1425
+ };
1426
+ }
1427
+ function decideApproval2(view, { approvalId, decision, note }) {
1428
+ return settleApproval2(view, approvalId, (block) => answered(block, decision, note));
1429
+ }
1430
+ function startTurn(view, { message, messageId, from, forced }) {
1431
+ const queued = messageId ? view.queued.filter((m) => m.id !== messageId) : view.queued;
1432
+ const working = { ...view, queued, working: true, latestText: void 0 };
1433
+ return message ? append(working, (id) => ({ ...sentMessage(id, message, from), ...forced ? { forced: true } : {} })) : working;
1434
+ }
1435
+ function sentMessage(id, text2, from) {
1436
+ return from === "ordewell" ? settledMessage(id, "system", `Ordewell: ${text2}`) : settledMessage(id, "user", text2);
1145
1437
  }
1146
- function startTurn(view, { message, messageId }) {
1438
+ function startCommand(view, { name, args, messageId }) {
1147
1439
  const queued = messageId ? view.queued.filter((m) => m.id !== messageId) : view.queued;
1148
- const working = { ...view, queued, working: true };
1149
- return message ? append(working, (id) => settledMessage(id, "user", message)) : working;
1440
+ return append({ ...view, queued, working: true, latestText: void 0 }, (id) => ({
1441
+ type: "command",
1442
+ id,
1443
+ name,
1444
+ ...args ? { args } : {},
1445
+ status: "running",
1446
+ output: ""
1447
+ }));
1448
+ }
1449
+ function runningCommand(view) {
1450
+ return findLastIndex(view.blocks, (b) => b.type === "command" && b.status === "running");
1451
+ }
1452
+ function commandOutput(view, { text: text2 }) {
1453
+ const i = runningCommand(view);
1454
+ const block = view.blocks[i];
1455
+ if (block?.type !== "command") return append(view, (id) => settledMessage(id, "system", text2));
1456
+ return { ...view, blocks: replaceAt(view.blocks, i, { ...block, output: block.output ? `${block.output}
1457
+ ${text2}` : text2 }) };
1458
+ }
1459
+ function appendStep(view, make) {
1460
+ const next = append(view, make);
1461
+ return sealLane(next, null, findLastIndex(next.blocks, (b) => b.type !== "usage"));
1150
1462
  }
1151
1463
  var CUT = { completed: null, interrupted: "stopped", failed: "failed" };
1464
+ var COMMAND_END = { completed: "done", interrupted: "stopped", failed: "failed" };
1152
1465
  function endTurn2(view, { reason }) {
1153
- const closed = closeOpenBlocks({ ...view, working: false, lastTurnEnd: reason, streams: {} }, CUT[reason]);
1466
+ const i = runningCommand(view);
1467
+ const command = view.blocks[i];
1468
+ const settled = command?.type === "command" ? { ...view, blocks: replaceAt(view.blocks, i, { ...command, status: COMMAND_END[reason] }) } : view;
1469
+ const closed = closeOpenBlocks({ ...settled, working: false, lastTurnEnd: reason, streams: {}, latestText: void 0 }, CUT[reason]);
1154
1470
  return reason === "interrupted" ? append(closed, (id) => settledMessage(id, "system", "Interrupted.")) : closed;
1155
1471
  }
1156
- function queueMessage(view, { messageId, text: text2, forced }) {
1472
+ function queueMessage(view, { messageId, text: text2, forced, from }) {
1157
1473
  const listed = view.queued.find((m) => m.id === messageId);
1474
+ const sender = from ? { from } : {};
1158
1475
  if (forced) {
1159
1476
  const rest = view.queued.filter((m) => m !== listed);
1160
1477
  const at = rest.findIndex((m) => !m.forced);
1161
1478
  const placed = at < 0 ? rest.length : at;
1162
- return { ...view, queued: [...rest.slice(0, placed), { id: messageId, text: text2, forced: true }, ...rest.slice(placed)] };
1479
+ const handedOver = listed?.handedOver ? { handedOver: true } : {};
1480
+ return { ...view, queued: [...rest.slice(0, placed), { id: messageId, text: text2, forced: true, ...handedOver, ...sender }, ...rest.slice(placed)] };
1163
1481
  }
1164
- if (!listed) return { ...view, queued: [...view.queued, { id: messageId, text: text2 }] };
1482
+ if (!listed) return { ...view, queued: [...view.queued, { id: messageId, text: text2, ...sender }] };
1165
1483
  if (!listed.handedOver) return view;
1166
- return { ...view, queued: view.queued.map((m) => m === listed ? { id: m.id, text: m.text } : m) };
1484
+ return { ...view, queued: view.queued.map((m) => m === listed ? { id: m.id, text: m.text, ...m.forced ? { forced: true } : {}, ...sender } : m) };
1167
1485
  }
1168
1486
  function handOver(view, { messageId }) {
1169
1487
  const listed = view.queued.find((m) => m.id === messageId);
@@ -1171,18 +1489,44 @@ function handOver(view, { messageId }) {
1171
1489
  return { ...view, queued: view.queued.map((m) => m === listed ? { ...m, handedOver: true } : m) };
1172
1490
  }
1173
1491
  function deliverMidTurn(view, event) {
1174
- return appendOutput(unqueueMessage(view, event), null, (id) => settledMessage(id, "user", event.text));
1492
+ return appendOutput(unqueueMessage(view, event), null, (id) => sentMessage(id, event.text, event.from));
1175
1493
  }
1176
1494
  function unqueueMessage(view, { messageId }) {
1177
1495
  const queued = view.queued.filter((m) => m.id !== messageId);
1178
1496
  return queued.length === view.queued.length ? view : { ...view, queued };
1179
1497
  }
1498
+ function endCall(block) {
1499
+ return block.type === "tool" && block.status === "pending" ? finishedTool(block, "", "not_executed") : block;
1500
+ }
1501
+ function endOpenCalls(view) {
1502
+ let changed = false;
1503
+ const blocks = view.blocks.map((block) => {
1504
+ const ended = block.type === "subagent" ? block.children.some((c) => c.type === "tool" && c.status === "pending") ? { ...block, children: block.children.map(endCall) } : block : block.type === "tool" ? endCall(block) : block;
1505
+ if (ended !== block) changed = true;
1506
+ return ended;
1507
+ });
1508
+ return changed ? { ...view, blocks } : view;
1509
+ }
1180
1510
  function reduceTaskLog(view, event) {
1181
1511
  switch (event.type) {
1182
1512
  case "task_skills":
1183
1513
  return append(view, (id) => settledMessage(id, "system", `Skills: ${event.skills.map((s) => `${s.name} (${s.path})`).join(", ")}`));
1184
1514
  case "turn_start":
1185
1515
  return startTurn(view, event);
1516
+ case "command":
1517
+ return startCommand(view, event);
1518
+ case "command_output":
1519
+ return commandOutput(view, event);
1520
+ case "compaction":
1521
+ return appendStep(view, (id) => ({
1522
+ type: "compaction",
1523
+ id,
1524
+ trigger: event.trigger,
1525
+ ...event.preTokens !== void 0 ? { preTokens: event.preTokens } : {},
1526
+ ...event.postTokens !== void 0 ? { postTokens: event.postTokens } : {}
1527
+ }));
1528
+ case "runner_loaded":
1529
+ return append(view, (id) => ({ type: "runner_loaded", id, commands: event.commands, mcpServers: event.mcpServers }));
1186
1530
  case "turn_end":
1187
1531
  return endTurn2(view, event);
1188
1532
  case "text_delta":
@@ -1214,13 +1558,15 @@ function reduceTaskLog(view, event) {
1214
1558
  case "message_undelivered":
1215
1559
  return append(unqueueMessage(view, event), (id) => settledMessage(id, "system", `${event.text} \xB7 not delivered`));
1216
1560
  case "error":
1217
- return appendOutput(view, null, (id) => settledMessage(id, "error", event.message));
1561
+ return failTurn(view, event.message);
1218
1562
  case "approval_requested":
1219
1563
  return requestApproval2(view, event);
1220
1564
  case "approval_decided":
1221
1565
  return decideApproval2(view, event);
1222
1566
  case "approval_withdrawn":
1223
1567
  return settleApproval2(view, event.approvalId, (block) => ({ ...block, status: "withdrawn" }));
1568
+ case "runner_ended":
1569
+ return endOpenCalls(view);
1224
1570
  default:
1225
1571
  return view;
1226
1572
  }
@@ -1242,6 +1588,10 @@ function unsendLatest(hold) {
1242
1588
  if (hold.length === 0) return void 0;
1243
1589
  return { text: hold[hold.length - 1], rest: hold.slice(0, -1) };
1244
1590
  }
1591
+ function takeAt(hold, index) {
1592
+ if (index < 0 || index >= hold.length) return void 0;
1593
+ return { text: hold[index], rest: [...hold.slice(0, index), ...hold.slice(index + 1)] };
1594
+ }
1245
1595
  function unsendAll(hold) {
1246
1596
  if (hold.length === 0) return void 0;
1247
1597
  return { text: hold.join("\n"), rest: EMPTY_HOLD };
@@ -1295,7 +1645,7 @@ function stopTurn(view, gate) {
1295
1645
 
1296
1646
  // src/conversation/detail.ts
1297
1647
  function hasHiddenDetail(blocks) {
1298
- return blocks.some((b) => b.type === "thinking" || b.type === "tool" || b.type === "subagent");
1648
+ return blocks.some((b) => b.type === "thinking" || b.type === "tool" || b.type === "subagent" || b.type === "runner_loaded");
1299
1649
  }
1300
1650
 
1301
1651
  // src/conversation/notices.ts
@@ -1306,20 +1656,29 @@ function taskStartedNotice(title, runner) {
1306
1656
  // src/taskRow/actions.ts
1307
1657
  var UNSTARTED_AI = (t) => t.type === "ai" && NOT_STARTED.has(t.status);
1308
1658
  var QUEUED = (t) => t.status === "pending" || t.status === "approved";
1659
+ var PAUSED = (t) => t.status === "awaiting_user" && t.awaitingReason === "usage-limit";
1660
+ var RESUMES = (t) => PAUSED(t) && !!t.resumeAt;
1661
+ var RESUMABLE = (t) => PAUSED(t) && t.continuable === true;
1662
+ var HOLDS_UP = (t) => t.status === "failed" || t.status === "blocked" || t.status === "awaiting_user";
1309
1663
  var TASK_RULES = [
1310
1664
  ["cancel", (t) => t.status === "in_progress"],
1311
- ["skip", (t) => t.status === "blocked"],
1665
+ ["resume-now", RESUMABLE],
1666
+ ["cancel-resume", RESUMES],
1667
+ ["skip", HOLDS_UP],
1312
1668
  ["force-start", UNSTARTED_AI],
1313
- // A manual task has no runner to report it done, so it can always be marked.
1314
- ["complete", (t) => t.status === "awaiting_user" || t.type === "user"],
1669
+ // A manual task has no runner to report it done, so it can always be marked;
1670
+ // a skipped one's kept work can still be vouched for.
1671
+ ["complete", (t) => t.status === "awaiting_user" || t.status === "skipped" || t.type === "user"],
1315
1672
  ["uncomplete", (t) => t.status === "completed"]
1316
1673
  ];
1317
1674
  var SUBTASK_RULES = [
1318
1675
  ["cancel", (t) => t.status === "in_progress"],
1676
+ ["resume-now", RESUMABLE],
1677
+ ["cancel-resume", RESUMES],
1319
1678
  ["complete", (t) => t.status === "in_progress" && t.type === "ai"],
1320
1679
  ["force-start", (t) => t.type === "ai" && QUEUED(t)],
1321
- ["skip", QUEUED],
1322
- ["complete", (t) => t.status === "awaiting_user"],
1680
+ ["skip", (t) => QUEUED(t) || t.status === "failed"],
1681
+ ["complete", (t) => t.status === "awaiting_user" || t.status === "skipped"],
1323
1682
  ["uncomplete", (t) => t.status === "completed"]
1324
1683
  ];
1325
1684
  function taskRowActions(task, placement = "task") {
@@ -1330,7 +1689,7 @@ function markAction(task) {
1330
1689
  return task.status === "completed" ? "uncomplete" : "complete";
1331
1690
  }
1332
1691
  function markRequestFor(action) {
1333
- if (action === "complete" || action === "skip") return "complete";
1692
+ if (action === "complete") return "complete";
1334
1693
  if (action === "uncomplete") return "uncomplete";
1335
1694
  return null;
1336
1695
  }
@@ -1339,52 +1698,50 @@ function opsEditable(task) {
1339
1698
  }
1340
1699
 
1341
1700
  // src/taskRow/view.ts
1342
- var refsOf = (ids, orderOf) => ids.map((id) => {
1343
- const order = orderOf?.get(id);
1344
- return order === void 0 ? id : taskRef({ order });
1345
- });
1701
+ var refsOf = (ids, tasks) => ids.map((id) => tasks?.length ? taskRefOf(tasks, id) ?? id : id);
1346
1702
  var namedRepo = (repo) => repo && repo !== "." ? repo : null;
1347
1703
  var namedRepos = (repos) => (repos ?? []).filter((r) => r !== ".");
1348
1704
  var filesOf = (files) => files?.length ? capConflictFiles(files) : null;
1705
+ var asBlocking = (tasks) => tasks.map((t) => ({ id: t.id, status: t.status ?? "", dependencies: t.dependencies ?? [], subtasks: asBlocking(t.subtasks ?? []) }));
1706
+ function blockedBy(task, tasks) {
1707
+ if (!tasks?.length || !NOT_STARTED.has(task.status)) return [];
1708
+ return blockingFailures(asBlocking(tasks)).get(task.id) ?? [];
1709
+ }
1349
1710
  function taskRowView(task, context = {}) {
1350
1711
  const parent = context.parent ?? void 0;
1351
1712
  const isolation = task.isolation && task.isolation.state !== "none" ? task.isolation : null;
1713
+ const latestUsage = task.attemptUsage?.at(-1);
1714
+ const blocked = blockedBy(task, context.tasks);
1352
1715
  return {
1716
+ usage: latestUsage ? { ...usageLine(latestUsage), attempt: latestUsage.attempt, wallTimeMs: latestUsage.wallTimeMs, byModel: latestUsage.byModel } : task.type === "ai" && ["completed", "failed", "in_progress", "awaiting_user"].includes(task.status) ? { totals: {}, attempt: 0, byModel: [] } : null,
1353
1717
  orderLabel: taskOrderLabel(task, parent),
1354
1718
  kind: task.type === "user" ? "user" : task.ops ? "ops" : "ai",
1355
- status: taskStatusKind(task),
1719
+ status: blocked.length > 0 ? "blocked" : taskStatusKind(task),
1356
1720
  running: isTaskRunning(task),
1357
1721
  awaiting: awaitingLabel(task),
1722
+ resumes: resumeLabel(task, context.now),
1358
1723
  approvals: approvalLabel(task.awaitingApproval),
1359
1724
  autonomous: task.type === "ai" && context.modes?.find((m) => m.id === task.taskMode)?.autonomous === true,
1360
1725
  repos: namedRepos(isolation?.repos),
1361
1726
  conflict: isolation?.state === "conflict" ? { repo: namedRepo(isolation.conflictRepo), files: filesOf(isolation.conflictFiles) } : null,
1362
1727
  repairing: isolation?.state === "repairing" ? { files: filesOf(isolation.conflictFiles), attempt: isolation.repair ?? null } : null,
1363
1728
  repaired: isolation?.repairedFiles?.length ? { files: capConflictFiles(isolation.repairedFiles), landed: isolation.state === "integrated" } : null,
1364
- mergeGate: task.mergeGate?.length ? refsOf(task.mergeGate, context.orderOf) : null,
1729
+ mergeGate: task.mergeGate?.length ? refsOf(task.mergeGate, context.tasks) : null,
1730
+ blockedBy: blocked.length > 0 ? refsOf(blocked, context.tasks) : null,
1731
+ skipReason: task.status === "skipped" ? task.skipReason ?? null : null,
1365
1732
  opsChangedFiles: task.status === "awaiting_user" && task.awaitingReason === "files-changed",
1366
1733
  forcedPastGate: task.forcedPastGate?.length ? task.forcedPastGate : null,
1367
- dependencies: refsOf(task.dependencies, context.orderOf),
1368
- actions: taskRowActions(task, context.placement ?? (parent ? "subtask" : "task")),
1734
+ dependencies: refsOf(task.dependencies, context.tasks),
1735
+ // A block read off the plan offers what a stored one does: Skip among them.
1736
+ actions: taskRowActions(blocked.length > 0 ? { ...task, status: "blocked" } : task, context.placement ?? (parent ? "subtask" : "task")),
1369
1737
  mark: markAction(task),
1370
1738
  opsEditable: opsEditable(task)
1371
1739
  };
1372
1740
  }
1373
1741
 
1374
1742
  // src/taskRow/texts.ts
1375
- function findNamed(tasks, id) {
1376
- for (const task of tasks) {
1377
- if (task.id === id) return task;
1378
- const nested = findNamed(task.subtasks ?? [], id);
1379
- if (nested) return nested;
1380
- }
1381
- return void 0;
1382
- }
1383
1743
  function titledRefs(ids, tasks) {
1384
- return ids.map((id) => {
1385
- const task = findNamed(tasks, id);
1386
- return task ? titledTaskRef(task) : id;
1387
- });
1744
+ return ids.map((id) => titledTaskRefOf(tasks, id) ?? id);
1388
1745
  }
1389
1746
  function dependentsNotice(dependents) {
1390
1747
  if (dependents.length === 0) return null;
@@ -1399,18 +1756,30 @@ function newTaskFields(title, prompt) {
1399
1756
  if (!name) return null;
1400
1757
  return { title: name, description: name, prompt: prompt?.trim() ? prompt : name, type: "ai" };
1401
1758
  }
1759
+
1760
+ // src/services/runView.ts
1761
+ var EMPTY_RUN = { tasks: [], runMerge: null, handoff: null, mergeResult: null };
1402
1762
  // Annotate the CommonJS export names for ESM import in node:
1403
1763
  0 && (module.exports = {
1404
1764
  CHECKPOINT_TRUNCATE_LENGTH,
1405
1765
  DEFAULT_RUNNERS,
1406
1766
  EMPTY_CONVERSATION,
1407
1767
  EMPTY_HOLD,
1768
+ EMPTY_RUN,
1408
1769
  EMPTY_TASK_LOG,
1409
1770
  NOT_STARTED,
1410
1771
  NO_TURN,
1772
+ SUPERVISOR_LEVEL_LABEL,
1773
+ addAttemptUsage,
1774
+ addModelUsage,
1775
+ addPlannerUsage,
1776
+ addUsage,
1777
+ addUsageTotals,
1411
1778
  aheadOfDraft,
1412
1779
  approvalLabel,
1413
1780
  awaitingLabel,
1781
+ blockedByLabel,
1782
+ blockingFailures,
1414
1783
  canMergeTasks,
1415
1784
  canSetDependencies,
1416
1785
  canSplitTask,
@@ -1422,10 +1791,12 @@ function newTaskFields(title, prompt) {
1422
1791
  diffStat,
1423
1792
  diffSummary,
1424
1793
  drainNext,
1794
+ endAttemptUsage,
1425
1795
  followTurn,
1426
1796
  fromTranscript,
1427
1797
  hasHiddenDetail,
1428
1798
  holdPrompt,
1799
+ isMeasured,
1429
1800
  isTaskRunning,
1430
1801
  loadedSkillTokens,
1431
1802
  markAction,
@@ -1435,25 +1806,39 @@ function newTaskFields(title, prompt) {
1435
1806
  opsEditable,
1436
1807
  outputLines,
1437
1808
  outputPreview,
1809
+ parseCount,
1810
+ partedPromptUsage,
1438
1811
  pastGateConfirmation,
1812
+ pausedOnLimit,
1813
+ planUsage,
1814
+ plannerContextFill,
1439
1815
  reduceConversation,
1440
1816
  reduceTaskLog,
1441
1817
  replayTaskLog,
1818
+ resolveTaskRef,
1819
+ resumeLabel,
1820
+ resumeTime,
1442
1821
  runnerToolSubject,
1443
1822
  skillTokens,
1823
+ skippedLabel,
1824
+ startAttemptUsage,
1444
1825
  stopTurn,
1445
1826
  summarizeToolCall,
1827
+ takeAt,
1446
1828
  taskOrderLabel,
1447
1829
  taskRef,
1830
+ taskRefOf,
1448
1831
  taskRowActions,
1449
1832
  taskRowView,
1450
1833
  taskStartedNotice,
1451
1834
  taskStatusKind,
1452
1835
  titledRefs,
1453
1836
  titledTaskRef,
1837
+ titledTaskRefOf,
1454
1838
  toolHeadline,
1455
1839
  truncateCheckpointSummary,
1456
1840
  unsendAll,
1457
- unsendLatest
1841
+ unsendLatest,
1842
+ usageLine
1458
1843
  });
1459
1844
  //# sourceMappingURL=plan-utils.js.map