@ordewell/core 0.10.12 → 0.11.1

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 (50) hide show
  1. package/AUTHORS.md +37 -0
  2. package/NOTICE +71 -0
  3. package/README.md +1 -1
  4. package/dist/{IFileSystem-1PqRCjhu.d.mts → IRunner-B5l9PCTv.d.ts} +926 -102
  5. package/dist/{IFileSystem-lTRL_G9Z.d.ts → IRunner-DBgDx_Mr.d.mts} +926 -102
  6. package/dist/{ModeResolver-CoJAJD-f.d.ts → ModeResolver-BDAgouuE.d.mts} +9 -2
  7. package/dist/{ModeResolver-6TQXVsde.d.mts → ModeResolver-DyXeZ9ib.d.ts} +9 -2
  8. package/dist/{Task-CfBM0wkV.d.mts → Task-BL4Idm9r.d.mts} +656 -26
  9. package/dist/{Task-CfBM0wkV.d.ts → Task-BL4Idm9r.d.ts} +656 -26
  10. package/dist/{chunk-ESKK5I4F.mjs → chunk-4QXOWEGC.mjs} +1888 -334
  11. package/dist/chunk-4QXOWEGC.mjs.map +1 -0
  12. package/dist/{chunk-KWS3VEER.mjs → chunk-MYXMIVWA.mjs} +6 -6
  13. package/dist/chunk-MYXMIVWA.mjs.map +1 -0
  14. package/dist/{chunk-PFH7WFIB.mjs → chunk-QTZ5XTBY.mjs} +228 -15
  15. package/dist/chunk-QTZ5XTBY.mjs.map +1 -0
  16. package/dist/{chunk-24EL4Z2W.mjs → chunk-YAFNYNFX.mjs} +342 -36
  17. package/dist/chunk-YAFNYNFX.mjs.map +1 -0
  18. package/dist/index.d.mts +1488 -769
  19. package/dist/index.d.ts +1488 -769
  20. package/dist/index.js +32003 -20350
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +29380 -19553
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/parsing.d.mts +2 -2
  25. package/dist/parsing.d.ts +2 -2
  26. package/dist/parsing.js +232 -25
  27. package/dist/parsing.js.map +1 -1
  28. package/dist/parsing.mjs +3 -3
  29. package/dist/plan-utils.d.mts +4 -4
  30. package/dist/plan-utils.d.ts +4 -4
  31. package/dist/plan-utils.js +1156 -96
  32. package/dist/plan-utils.js.map +1 -1
  33. package/dist/plan-utils.mjs +144 -8
  34. package/dist/{runSettingsText-Jb_efnI4.d.mts → runSettingsText-BKwWs052.d.mts} +2882 -621
  35. package/dist/{runSettingsText-C5uv2axI.d.ts → runSettingsText-CAXiTpsj.d.ts} +2882 -621
  36. package/dist/testing.d.mts +10 -4
  37. package/dist/testing.d.ts +10 -4
  38. package/dist/testing.js +30 -7
  39. package/dist/testing.js.map +1 -1
  40. package/dist/testing.mjs +26 -7
  41. package/dist/testing.mjs.map +1 -1
  42. package/package.json +7 -3
  43. package/skills/diagnose/SKILL.md +52 -0
  44. package/skills/explore/SKILL.md +65 -0
  45. package/dist/chunk-24EL4Z2W.mjs.map +0 -1
  46. package/dist/chunk-ESKK5I4F.mjs.map +0 -1
  47. package/dist/chunk-KWS3VEER.mjs.map +0 -1
  48. package/dist/chunk-NJGID6BG.mjs +0 -115
  49. package/dist/chunk-NJGID6BG.mjs.map +0 -1
  50. package/dist/chunk-PFH7WFIB.mjs.map +0 -1
@@ -37,27 +37,47 @@ __export(plan_utils_exports, {
37
37
  EMPTY_RUN: () => EMPTY_RUN,
38
38
  EMPTY_TASK_LOG: () => EMPTY_TASK_LOG,
39
39
  EMPTY_TASK_LOG_FOLLOW: () => EMPTY_TASK_LOG_FOLLOW,
40
+ IDLE_ACTIVITY: () => IDLE_ACTIVITY,
40
41
  NOT_STARTED: () => NOT_STARTED,
41
42
  NO_TURN: () => NO_TURN,
43
+ PLANNED_WITH_LABEL: () => PLANNED_WITH_LABEL,
44
+ REPORT_PREVIEW_LINES: () => REPORT_PREVIEW_LINES,
45
+ SEVERITY_LABEL: () => SEVERITY_LABEL,
42
46
  SUPERVISOR_LEVEL_LABEL: () => SUPERVISOR_LEVEL_LABEL,
43
47
  WAITS_FOR_APPROVAL_LABEL: () => WAITS_FOR_APPROVAL_LABEL,
48
+ accountLimitPercentText: () => accountLimitPercentText,
49
+ accountLimitWindowLabel: () => accountLimitWindowLabel,
50
+ accountLimitsOf: () => accountLimitsOf,
51
+ accountLimitsText: () => accountLimitsText,
52
+ accountLimitsTitle: () => accountLimitsTitle,
44
53
  addAttemptUsage: () => addAttemptUsage,
45
54
  addModelUsage: () => addModelUsage,
46
55
  addPlannerUsage: () => addPlannerUsage,
47
56
  addUsage: () => addUsage,
48
57
  addUsageTotals: () => addUsageTotals,
49
58
  aheadOfDraft: () => aheadOfDraft,
59
+ approvalKindText: () => approvalKindText,
50
60
  approvalLabel: () => approvalLabel,
61
+ approvalStatusText: () => approvalStatusText,
62
+ assignedRunnerOf: () => assignedRunnerOf,
51
63
  assignmentIssue: () => assignmentIssue,
52
64
  assignmentIssueText: () => assignmentIssueText,
53
65
  awaitingLabel: () => awaitingLabel,
66
+ betweenRounds: () => betweenRounds,
67
+ blockNotice: () => blockNotice,
68
+ blockNotices: () => blockNotices,
54
69
  blockedByLabel: () => blockedByLabel,
55
70
  blockingFailures: () => blockingFailures,
56
71
  canMergeTasks: () => canMergeTasks,
57
72
  canSetDependencies: () => canSetDependencies,
58
73
  canSplitTask: () => canSplitTask,
59
74
  capConflictFiles: () => capConflictFiles,
75
+ cardEditNeedsUnstarted: () => cardEditNeedsUnstarted,
76
+ cardEditable: () => cardEditable,
77
+ checkOf: () => checkOf,
60
78
  clampThinkingEffort: () => clampThinkingEffort,
79
+ compactionText: () => compactionText,
80
+ confirmedOverride: () => confirmedOverride,
61
81
  contextMeterText: () => contextMeterText,
62
82
  contextMeterTitle: () => contextMeterTitle,
63
83
  costText: () => costText,
@@ -65,47 +85,89 @@ __export(plan_utils_exports, {
65
85
  dependencyCandidates: () => dependencyCandidates,
66
86
  dependentsNotice: () => dependentsNotice,
67
87
  dependentsOf: () => dependentsOf,
88
+ describeIsolationPreview: () => describeIsolationPreview,
89
+ describeMergeResult: () => describeMergeResult,
68
90
  diffRows: () => diffRows,
69
91
  diffStat: () => diffStat,
70
92
  diffSummary: () => diffSummary,
71
93
  drainNext: () => drainNext,
94
+ editableWhileRunning: () => editableWhileRunning,
72
95
  endAttemptUsage: () => endAttemptUsage,
73
96
  followTaskLog: () => followTaskLog,
74
97
  followTurn: () => followTurn,
75
98
  formatCost: () => formatCost,
76
99
  formatTokenCount: () => formatTokenCount,
77
100
  fromTranscript: () => fromTranscript,
101
+ handoffBaseLine: () => handoffBaseLine,
102
+ handoffBranchName: () => handoffBranchName,
103
+ handoffFindingsText: () => handoffFindingsText,
104
+ handoffPeekParts: () => handoffPeekParts,
105
+ handoffReferenceText: () => handoffReferenceText,
106
+ hasAccountLimits: () => hasAccountLimits,
78
107
  hasHiddenDetail: () => hasHiddenDetail,
108
+ hasLongReport: () => hasLongReport,
109
+ holdLabel: () => holdLabel,
79
110
  holdPrompt: () => holdPrompt,
111
+ isFileEditTool: () => isFileEditTool,
80
112
  isMeasured: () => isMeasured,
113
+ isRepoGroup: () => isRepoGroup,
81
114
  isTaskRunning: () => isTaskRunning,
82
115
  loadedSkillTokens: () => loadedSkillTokens,
116
+ logContinues: () => logContinues,
117
+ logResumes: () => logResumes,
118
+ loopBodyLabel: () => loopBodyLabel,
119
+ loopBodyRound: () => loopBodyRound,
120
+ loopCheckLabel: () => loopCheckLabel,
121
+ loopCheckState: () => loopCheckState,
122
+ loopLeaveNotice: () => loopLeaveNotice,
123
+ loopRemovalRefusal: () => loopRemovalRefusal,
124
+ loopRoundOf: () => loopRoundOf,
125
+ loopRowParts: () => loopRowParts,
126
+ manualOverrideConfirmation: () => manualOverrideConfirmation,
127
+ manualOverrideStopFirst: () => manualOverrideStopFirst,
83
128
  markAction: () => markAction,
84
- markRequestFor: () => markRequestFor,
85
129
  markdownHtml: () => markdownHtml,
86
130
  markdownTokens: () => markdownTokens,
131
+ mergeOutcome: () => mergeOutcome,
87
132
  modelCostTitle: () => modelCostTitle,
88
133
  namedRepos: () => namedRepos,
89
134
  newTaskFields: () => newTaskFields,
135
+ noExecutorRefusal: () => noExecutorRefusal,
90
136
  opsEditable: () => opsEditable,
137
+ outOfRounds: () => outOfRounds,
91
138
  outputLines: () => outputLines,
92
139
  outputPreview: () => outputPreview,
140
+ overrideNeedsConfirmation: () => overrideNeedsConfirmation,
93
141
  parseCount: () => parseCount,
94
142
  partedPromptUsage: () => partedPromptUsage,
95
143
  pastGateConfirmation: () => pastGateConfirmation,
144
+ patchView: () => patchView,
96
145
  pausedOnLimit: () => pausedOnLimit,
146
+ planBlocks: () => planBlocks,
147
+ planMarkerParts: () => planMarkerParts,
97
148
  planUsage: () => planUsage,
98
149
  plannerContextFill: () => plannerContextFill,
150
+ receiptLabel: () => receiptLabel,
99
151
  reconcileApprovals: () => reconcileApprovals,
100
152
  reduceConversation: () => reduceConversation,
101
153
  reduceTaskLog: () => reduceTaskLog,
154
+ repairedLanded: () => repairedLanded,
155
+ repairedNotice: () => repairedNotice,
156
+ repeatingChecks: () => repeatingChecks,
102
157
  replayTaskLog: () => replayTaskLog,
158
+ repoBaseLine: () => repoBaseLine,
159
+ repoResultLines: () => repoResultLines,
103
160
  resolveTaskRef: () => resolveTaskRef,
104
161
  resolveUserTaskRef: () => resolveUserTaskRef,
105
162
  resumeLabel: () => resumeLabel,
106
163
  resumeTime: () => resumeTime,
107
164
  resumedSessionWindow: () => resumedSessionWindow,
165
+ runHoldsEditors: () => runHoldsEditors,
108
166
  runnerToolSubject: () => runnerToolSubject,
167
+ savedPatchView: () => savedPatchView,
168
+ savedPatchViews: () => savedPatchViews,
169
+ scriptTaskText: () => scriptTaskText,
170
+ severityLabel: () => severityLabel,
109
171
  skillTokens: () => skillTokens,
110
172
  skippedLabel: () => skippedLabel,
111
173
  startAttemptUsage: () => startAttemptUsage,
@@ -114,6 +176,10 @@ __export(plan_utils_exports, {
114
176
  subtreeIds: () => subtreeIds,
115
177
  summarizeToolCall: () => summarizeToolCall,
116
178
  takeAt: () => takeAt,
179
+ taskActionCommand: () => taskActionCommand,
180
+ taskChatAvailable: () => taskChatAvailable,
181
+ taskLogCommands: () => taskLogCommands,
182
+ taskLogHeaderView: () => taskLogHeaderView,
117
183
  taskOrderLabel: () => taskOrderLabel,
118
184
  taskPlacement: () => taskPlacement,
119
185
  taskRef: () => taskRef,
@@ -133,7 +199,9 @@ __export(plan_utils_exports, {
133
199
  unsendLatest: () => unsendLatest,
134
200
  usageLine: () => usageLine,
135
201
  usageText: () => usageText,
136
- walkPlan: () => walkPlan
202
+ wakeUpItems: () => wakeUpItems,
203
+ walkPlan: () => walkPlan,
204
+ worstSeverity: () => worstSeverity
137
205
  });
138
206
  module.exports = __toCommonJS(plan_utils_exports);
139
207
 
@@ -243,6 +311,9 @@ function canSetDependencies(tasks, taskId, dependencies) {
243
311
  return cycle ? { ok: false, error: `Dependency cycle detected: ${[...cycle, cycle[0]].map((t) => `"${t.title}"`).join(" \u2192 ")}` } : { ok: true };
244
312
  }
245
313
 
314
+ // src/services/repoPath.ts
315
+ var SELF_REPO = ".";
316
+
246
317
  // src/services/TaskRefs.ts
247
318
  function taskOrderLabel(task, parent) {
248
319
  return parent ? `${parent.label ?? parent.order}.${task.order}` : String(task.order);
@@ -331,10 +402,21 @@ function awaitingLabel(task) {
331
402
  function blockedByLabel(refs) {
332
403
  return `Blocked by ${refs.join(", ")}`;
333
404
  }
405
+ function holdLabel(reason) {
406
+ return `On hold: ${reason}`;
407
+ }
334
408
  function skippedLabel(reason) {
335
409
  return reason ? `Skipped: ${reason}` : "Skipped";
336
410
  }
337
411
  var WAITS_FOR_APPROVAL_LABEL = "Waits for your approval";
412
+ var RECEIPT_LABELS = {
413
+ done: "Completion received",
414
+ failed: "Failure report received",
415
+ blocked: "Blocked report received"
416
+ };
417
+ function receiptLabel(task) {
418
+ return isTaskRunning(task) && task.completionReceived ? RECEIPT_LABELS[task.completionReceived] : null;
419
+ }
338
420
  function approvalLabel(count) {
339
421
  if (!count) return null;
340
422
  return count > 1 ? `Waiting for approval (${count})` : "Waiting for approval";
@@ -386,6 +468,106 @@ function clampThinkingEffort(effort, variants) {
386
468
  return void 0;
387
469
  }
388
470
 
471
+ // src/models/ScriptTask.ts
472
+ var DEFAULT_SCRIPT_TIMEOUT_MS = 10 * 6e4;
473
+ var MAX_SCRIPT_TIMEOUT_MS = 24 * 60 * 6e4;
474
+ var SCRIPT_BODY_FIELDS = ["commands", "script", "timeoutMs"];
475
+ function scriptBodyOf(task) {
476
+ if (task.type !== "script" || scriptBodyErrors(task).length > 0) return null;
477
+ const timeoutMs = task.timeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS;
478
+ return task.commands ? { commands: task.commands.map((argv) => [...argv]), timeoutMs } : { script: task.script ?? "", timeoutMs };
479
+ }
480
+ function scriptBodyErrors(task) {
481
+ if (task.type !== "script") {
482
+ return SCRIPT_BODY_FIELDS.filter((f) => task[f] !== void 0).map((field) => ({ field, message: `has "${field}", which only a script task has: set "type": "script", or leave it out` }));
483
+ }
484
+ const errors = [];
485
+ const { commands, script, timeoutMs } = task;
486
+ if (commands !== void 0 && script !== void 0) {
487
+ errors.push({ field: "commands", message: 'has both "commands" and "script": a script task runs exactly one of them' });
488
+ } else if (commands === void 0 && script === void 0) {
489
+ errors.push({ field: "commands", message: 'is a script task with neither "commands" nor "script": give it the argument vectors to run, or the script' });
490
+ }
491
+ if (commands !== void 0) errors.push(...commandsErrors(commands));
492
+ if (script !== void 0 && (typeof script !== "string" || !script.trim())) {
493
+ errors.push({ field: "script", message: 'has an empty "script": write the script it runs' });
494
+ }
495
+ if (timeoutMs !== void 0 && !(typeof timeoutMs === "number" && Number.isInteger(timeoutMs) && timeoutMs > 0 && timeoutMs <= MAX_SCRIPT_TIMEOUT_MS)) {
496
+ errors.push({ field: "timeoutMs", message: `has "timeoutMs" ${JSON.stringify(timeoutMs)}: the time limit is a whole number of milliseconds from 1 to ${MAX_SCRIPT_TIMEOUT_MS}, or left out for ten minutes` });
497
+ }
498
+ return errors;
499
+ }
500
+ function commandsErrors(commands) {
501
+ const shape = 'a list of argument vectors, each a list of strings, like [["git", "push", "origin", "main"]]';
502
+ if (!Array.isArray(commands) || commands.length === 0) return [{ field: "commands", message: `has "commands" that is not ${shape}` }];
503
+ for (const [i, argv] of commands.entries()) {
504
+ const n = i + 1;
505
+ if (!Array.isArray(argv) || !argv.every((word) => typeof word === "string")) {
506
+ return [{ field: "commands", message: `has command ${n} that is not an argument vector: "commands" is ${shape}` }];
507
+ }
508
+ if (argv.length === 0) return [{ field: "commands", message: `has command ${n} as an empty argument vector: name the program it runs` }];
509
+ if (!argv[0].trim()) return [{ field: "commands", message: `has command ${n} naming no program: its first word is the program it runs` }];
510
+ }
511
+ return [];
512
+ }
513
+ var PLAIN_WORD = /^[A-Za-z0-9_@%+=:,./-]+$/;
514
+ var UNSEEN = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
515
+ function commandLine(argv) {
516
+ return argv.map(quotedWord).join(" ");
517
+ }
518
+ function quotedWord(word) {
519
+ if (PLAIN_WORD.test(word)) return word;
520
+ if (!UNSEEN.test(word)) return `'${word.replace(/'/g, `'\\''`)}'`;
521
+ return `$'${[...word].map(escaped).join("")}'`;
522
+ }
523
+ var ESCAPES = { "\\": "\\\\", "'": "\\'", "\n": "\\n", "\r": "\\r", " ": "\\t" };
524
+ function escaped(char) {
525
+ return ESCAPES[char] ?? (UNSEEN.test(char) ? codeOf(char) : char);
526
+ }
527
+ function codeOf(char) {
528
+ const code = char.codePointAt(0) ?? 0;
529
+ const hex = code.toString(16).toUpperCase();
530
+ return code <= 255 ? `\\x${hex.padStart(2, "0")}` : code <= 65535 ? `\\u${hex.padStart(4, "0")}` : `\\U${hex.padStart(8, "0")}`;
531
+ }
532
+ function scriptBodyText(body) {
533
+ const shown = "commands" in body ? body.commands.map(commandLine) : [body.script.replace(/\r\n|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, (char) => char === "\r\n" || char === "\n" || char === " " ? char : `<U+${(char.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, "0")}>`)];
534
+ return [...shown, "", `Time limit: ${timeLimitText(body.timeoutMs)}.`].join("\n");
535
+ }
536
+ function scriptTaskText(task) {
537
+ const body = scriptBodyOf(task);
538
+ return body ? scriptBodyText(body) : "This script task has no valid body, so it cannot run.";
539
+ }
540
+ function timeLimitText(ms) {
541
+ const units = [[36e5, "hour"], [6e4, "minute"], [1e3, "second"], [1, "millisecond"]];
542
+ const [size, name] = units.find(([unit]) => ms % unit === 0) ?? units[units.length - 1];
543
+ const n = ms / size;
544
+ return `${n} ${name}${n === 1 ? "" : "s"}`;
545
+ }
546
+
547
+ // src/models/Loop.ts
548
+ function loopRoundOf(check) {
549
+ return check.loopRound ?? 1;
550
+ }
551
+ function foundWanting(check) {
552
+ return check.verdict?.outcome === "fail" && check.verdict.reported === "failed";
553
+ }
554
+ function betweenRounds(check) {
555
+ return !!check.loop && check.status === "failed" && foundWanting(check) && loopRoundOf(check) < check.loop.maxRounds;
556
+ }
557
+ function outOfRounds(check) {
558
+ return !!check.loop && check.status === "failed" && foundWanting(check) && loopRoundOf(check) >= check.loop.maxRounds;
559
+ }
560
+ function checkOf(tasks, taskId) {
561
+ return tasks.find((t) => t.loop?.body.includes(taskId));
562
+ }
563
+ function loopRemovalRefusal(task) {
564
+ return task.loop ? `"${task.title}" is a loop's check: remove its body tasks first, or drop its loop ("loop": null), so they do not run on their own` : null;
565
+ }
566
+ function loopReshapeRefusal(tasks, target, action) {
567
+ if (target.loop) return `"${target.title}" is a loop's check and cannot be ${action}: drop its loop first ("loop": null)`;
568
+ return checkOf(tasks, target.id) ? `"${target.title}" is in a loop's body and cannot be ${action}: take it out of the loop first` : null;
569
+ }
570
+
389
571
  // src/services/PlannedTask.ts
390
572
  function findCycle(tasks) {
391
573
  return findDependencyCycle(tasks);
@@ -403,6 +585,8 @@ function canMergeTasks(tasks, selectedIds) {
403
585
  for (const t of toMerge) {
404
586
  if (t.status === "in_progress") return { ok: false, error: `"${t.title}" is running and cannot be merged` };
405
587
  if (t.status === "completed") return { ok: false, error: `"${t.title}" is completed and cannot be merged` };
588
+ const looped = loopReshapeRefusal(tasks, t, "merged");
589
+ if (looped) return { ok: false, error: looped };
406
590
  }
407
591
  const ordered = [...toMerge].sort((a, b) => a.order - b.order);
408
592
  const survivorId = "__merge_sim__";
@@ -418,7 +602,8 @@ function canSplitTask(tasks, taskId) {
418
602
  if (!target) return { ok: false, error: "Task not found" };
419
603
  if (target.status === "in_progress") return { ok: false, error: `"${target.title}" is running and cannot be split` };
420
604
  if (target.status === "completed") return { ok: false, error: `"${target.title}" is completed and cannot be split` };
421
- return { ok: true };
605
+ const looped = loopReshapeRefusal(tasks, target, "split");
606
+ return looped ? { ok: false, error: looped } : { ok: true };
422
607
  }
423
608
 
424
609
  // src/services/researchStepSummary.ts
@@ -481,6 +666,72 @@ function summarizeToolCall(tool, argsJson, toolLabel) {
481
666
  }
482
667
  return display;
483
668
  }
669
+ var SEALED_RUN_MARK = "[Ran sealed";
670
+ function ranSealed(output) {
671
+ return output.startsWith(SEALED_RUN_MARK);
672
+ }
673
+ function withoutSealedMark(output) {
674
+ if (!ranSealed(output)) return output;
675
+ const end = output.indexOf("\n");
676
+ return end < 0 ? "" : output.slice(end + 1);
677
+ }
678
+
679
+ // src/services/harness/agentTools.ts
680
+ var KNOWN_TOOLS = {
681
+ // Claude Code
682
+ read: "read_file",
683
+ grep: "grep",
684
+ glob: "glob",
685
+ bash: "bash",
686
+ // Its second shell tool, under the same opt-in and launcher (ADR-0036 N6).
687
+ monitor: "bash",
688
+ ls: "list_dir",
689
+ webfetch: "fetch",
690
+ websearch: "web_search",
691
+ // Codex — its sandboxed command tool is a shell by another name
692
+ shell: "bash",
693
+ exec_command: "bash",
694
+ local_shell: "bash",
695
+ // OpenCode
696
+ list: "list_dir",
697
+ fetch: "fetch",
698
+ // Ordewell's own research shell (ADR-0026), under every runner's name for it
699
+ runcommand: "bash"
700
+ };
701
+ var ORDEWELL_TOOL_PREFIX = /^(mcp__ordewell__|ordewell_)/i;
702
+ var FILE_EDIT_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "multiedit", "notebookedit", "applypatch", "patch", "add", "update", "delete", "filechange"]);
703
+ function isFileEditTool(name) {
704
+ return FILE_EDIT_TOOLS.has(name.trim().toLowerCase().replace(/[_-]/g, ""));
705
+ }
706
+ function mapAgentTool(name) {
707
+ const normalized = name.trim().replace(ORDEWELL_TOOL_PREFIX, "").toLowerCase().replace(/[_-]/g, "");
708
+ const direct = KNOWN_TOOLS[normalized] ?? KNOWN_TOOLS[name.trim().toLowerCase()];
709
+ if (direct) {
710
+ return { tool: direct, toolLabel: name };
711
+ }
712
+ return { tool: "agent_tool", toolLabel: name };
713
+ }
714
+ function normalizeAgentArgs(tool, args) {
715
+ const out = { ...args };
716
+ const alias = (from, to) => {
717
+ if (out[to] === void 0 && out[from] !== void 0) out[to] = out[from];
718
+ };
719
+ if (tool === "read_file" || tool === "list_dir") {
720
+ alias("filePath", "path");
721
+ alias("file_path", "path");
722
+ alias("target", "path");
723
+ }
724
+ if (tool === "bash") {
725
+ alias("cmd", "command");
726
+ alias("script", "command");
727
+ if (typeof out.command !== "string" && Array.isArray(out.command)) {
728
+ out.command = out.command.join(" ");
729
+ }
730
+ }
731
+ if (tool === "fetch") alias("uri", "url");
732
+ if (tool === "web_search") alias("q", "query");
733
+ return out;
734
+ }
484
735
 
485
736
  // src/conversation/format.ts
486
737
  var ANSI_OR_CTRL_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?|\x1b[()][AB012]|\x1b[=>]|[\x00-\x08\x0b-\x1f\x7f]/g;
@@ -610,61 +861,6 @@ function diffSummary({ added, removed }) {
610
861
  return `Added ${lines(added)}, removed ${lines(removed)}`;
611
862
  }
612
863
 
613
- // src/services/harness/agentTools.ts
614
- var KNOWN_TOOLS = {
615
- // Claude Code
616
- read: "read_file",
617
- grep: "grep",
618
- glob: "glob",
619
- bash: "bash",
620
- ls: "list_dir",
621
- webfetch: "fetch",
622
- websearch: "web_search",
623
- // Codex — its sandboxed command tool is a shell by another name
624
- shell: "bash",
625
- exec_command: "bash",
626
- local_shell: "bash",
627
- // OpenCode
628
- list: "list_dir",
629
- fetch: "fetch",
630
- // Ordewell's own research shell (ADR-0026), under every runner's name for it
631
- runcommand: "bash"
632
- };
633
- var ORDEWELL_TOOL_PREFIX = /^(mcp__ordewell__|ordewell_)/i;
634
- var FILE_EDIT_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "multiedit", "notebookedit", "applypatch", "patch", "add", "update", "delete", "filechange"]);
635
- function isFileEditTool(name) {
636
- return FILE_EDIT_TOOLS.has(name.trim().toLowerCase().replace(/[_-]/g, ""));
637
- }
638
- function mapAgentTool(name) {
639
- const normalized = name.trim().replace(ORDEWELL_TOOL_PREFIX, "").toLowerCase().replace(/[_-]/g, "");
640
- const direct = KNOWN_TOOLS[normalized] ?? KNOWN_TOOLS[name.trim().toLowerCase()];
641
- if (direct) {
642
- return { tool: direct, toolLabel: name };
643
- }
644
- return { tool: "agent_tool", toolLabel: name };
645
- }
646
- function normalizeAgentArgs(tool, args) {
647
- const out = { ...args };
648
- const alias = (from, to) => {
649
- if (out[to] === void 0 && out[from] !== void 0) out[to] = out[from];
650
- };
651
- if (tool === "read_file" || tool === "list_dir") {
652
- alias("filePath", "path");
653
- alias("file_path", "path");
654
- alias("target", "path");
655
- }
656
- if (tool === "bash") {
657
- alias("cmd", "command");
658
- alias("script", "command");
659
- if (typeof out.command !== "string" && Array.isArray(out.command)) {
660
- out.command = out.command.join(" ");
661
- }
662
- }
663
- if (tool === "fetch") alias("uri", "url");
664
- if (tool === "web_search") alias("q", "query");
665
- return out;
666
- }
667
-
668
864
  // src/conversation/records.ts
669
865
  function settledMessage(id, role, text2, turnId) {
670
866
  return { type: "message", id, role, text: text2, streaming: false, ...turnId ? { turnId } : {} };
@@ -694,6 +890,20 @@ function subagentBlock(id, { toolCallId, model, usage, turnId, ...fields }) {
694
890
  ...turnId ? { turnId } : {}
695
891
  };
696
892
  }
893
+ function compactionBlock(id, { trigger, compactionId, origin, status, summary, preTokens, postTokens, turnId }) {
894
+ return {
895
+ type: "compaction",
896
+ id,
897
+ trigger,
898
+ ...compactionId ? { compactionId } : {},
899
+ ...origin ? { origin } : {},
900
+ ...status ? { status } : {},
901
+ ...summary ? { summary } : {},
902
+ ...preTokens !== void 0 ? { preTokens } : {},
903
+ ...postTokens !== void 0 ? { postTokens } : {},
904
+ ...turnId ? { turnId } : {}
905
+ };
906
+ }
697
907
  function usageBlock(id, { totals, bySubagent, byModel, model, contextFill }) {
698
908
  return { type: "usage", id, totals, ...byModel ? { byModel } : {}, ...model ? { model } : {}, ...bySubagent ? { bySubagent } : {}, ...contextFill ? { contextFill } : {} };
699
909
  }
@@ -706,7 +916,9 @@ var STATUS_OF_OUTCOME = {
706
916
  };
707
917
  function finishedTool(call, output, outcome) {
708
918
  const diff = outcome === "success" && isFileEditTool(call.toolLabel ?? call.tool) ? diffStat(output) : null;
709
- return { ...call, status: STATUS_OF_OUTCOME[outcome], outcome, output, outputLineCount: outputLines(output).length, ...diff ? { diff } : {} };
919
+ const sealed2 = call.tool === "bash" && ranSealed(output);
920
+ const shown = sealed2 ? withoutSealedMark(output) : output;
921
+ return { ...call, status: STATUS_OF_OUTCOME[outcome], outcome, output: shown, outputLineCount: outputLines(shown).length, ...diff ? { diff } : {}, ...sealed2 ? { sealed: sealed2 } : {} };
710
922
  }
711
923
  function settledTool(call, step) {
712
924
  return finishedTool(call, step.result, step.outcome);
@@ -737,6 +949,12 @@ function planMarker(content) {
737
949
  // src/services/blocking.ts
738
950
  var UNSTARTED = /* @__PURE__ */ new Set(["pending", "approved"]);
739
951
  function blockingFailures(tasks) {
952
+ return planBlocks(tasks).blocked;
953
+ }
954
+ function repeatingChecks(tasks) {
955
+ return planBlocks(tasks).repeating;
956
+ }
957
+ function planBlocks(tasks) {
740
958
  const byId = /* @__PURE__ */ new Map();
741
959
  const index = (list) => {
742
960
  for (const t of list) {
@@ -746,6 +964,19 @@ function blockingFailures(tasks) {
746
964
  };
747
965
  index(tasks);
748
966
  const memo = /* @__PURE__ */ new Map();
967
+ const stopped = /* @__PURE__ */ new Map();
968
+ const repeats = (check) => {
969
+ if (!betweenRounds(check)) return false;
970
+ const known = stopped.get(check.id);
971
+ if (known !== void 0) return !known;
972
+ stopped.set(check.id, false);
973
+ const halted = (check.loop?.body ?? []).some((id) => {
974
+ const body = byId.get(id);
975
+ return !!body && (body.status === "failed" || body.status === "skipped" || failuresOf(id, /* @__PURE__ */ new Set()).length > 0);
976
+ });
977
+ stopped.set(check.id, halted);
978
+ return !halted;
979
+ };
749
980
  const failuresOf = (id, visiting) => {
750
981
  const known = memo.get(id);
751
982
  if (known) return known;
@@ -756,8 +987,10 @@ function blockingFailures(tasks) {
756
987
  for (const depId of task.dependencies) {
757
988
  const dep = byId.get(depId);
758
989
  if (!dep || dep.status === "completed" || dep.status === "skipped") continue;
759
- if (dep.status === "failed") failed.add(depId);
760
- else for (const root of failuresOf(depId, visiting)) failed.add(root);
990
+ if (dep.status === "failed") {
991
+ if (betweenRounds(dep) && (dep.loop?.body.includes(id) || repeats(dep))) continue;
992
+ failed.add(depId);
993
+ } else for (const root of failuresOf(depId, visiting)) failed.add(root);
761
994
  }
762
995
  visiting.delete(id);
763
996
  const result = [...failed];
@@ -765,14 +998,17 @@ function blockingFailures(tasks) {
765
998
  return result;
766
999
  };
767
1000
  const blocked = /* @__PURE__ */ new Map();
768
- for (const id of byId.keys()) {
1001
+ const repeating = /* @__PURE__ */ new Set();
1002
+ for (const [id, task] of byId) {
769
1003
  const failures = failuresOf(id, /* @__PURE__ */ new Set());
770
1004
  if (failures.length > 0) blocked.set(id, failures);
1005
+ if (repeats(task)) repeating.add(id);
771
1006
  }
772
- return blocked;
1007
+ return { blocked, repeating };
773
1008
  }
774
1009
 
775
1010
  // src/services/SessionMessage.ts
1011
+ var IDLE_ACTIVITY = { starting: false, running: false, scheduled: false, resumable: false, advancesOnItsOwn: false };
776
1012
  var CHECKPOINT_TRUNCATE_LENGTH = 120;
777
1013
  function truncateCheckpointSummary(summary) {
778
1014
  const firstLine = summary.split("\n")[0].trim();
@@ -785,8 +1021,155 @@ function capConflictFiles(files, max = 5) {
785
1021
  return files.length <= max ? files.join(", ") : `${files.slice(0, max).join(", ")}, +${files.length - max} more`;
786
1022
  }
787
1023
 
1024
+ // src/services/mergeResultNotice.ts
1025
+ function blockNotice({ repo, reason, files, expected, actual, rebasing }) {
1026
+ switch (reason) {
1027
+ case "merge-in-progress":
1028
+ return `${repo} has a merge in progress`;
1029
+ case "conflict":
1030
+ return `${repo} would conflict in ${files.join(", ")}`;
1031
+ case "uncommitted-changes":
1032
+ return `${repo} has uncommitted changes to ${files.join(", ")}`;
1033
+ case "partial-landing":
1034
+ return `${repo} holds part of a task whose landing could not be rolled back`;
1035
+ case "wrong-branch":
1036
+ return `${repo === SELF_REPO ? "your checkout" : repo} is ${actual ? `on ${actual}` : rebasing ? "in the middle of a rebase" : "on a detached HEAD"}; this run started on ${expected}`;
1037
+ case "git-error":
1038
+ return `git could not check ${repo}`;
1039
+ }
1040
+ }
1041
+ function blockNotices(blocked) {
1042
+ return blocked.map(blockNotice);
1043
+ }
1044
+ function branchFix(blocked) {
1045
+ if (blocked.length === 0 || blocked.some((b) => b.reason !== "wrong-branch")) return "";
1046
+ const rebasing = blocked.filter((b) => b.rebasing);
1047
+ if (rebasing.length > 0) {
1048
+ const where = rebasing.map((b) => b.repo === SELF_REPO ? "your checkout" : b.repo).join(", ");
1049
+ return ` Finish or abort the rebase in ${where}${rebasing.length < blocked.length ? " and check out the branch each other repository started on" : ""}, then press Merge all again.`;
1050
+ }
1051
+ const started = [...new Set(blocked.map((b) => b.expected))];
1052
+ return started.length === 1 && blocked.length === 1 ? ` Check out ${started[0]} and press Merge all again.` : " Check out the branch each repository started on and press Merge all again.";
1053
+ }
1054
+ function landedNotice(landed) {
1055
+ if (landed.length === 0) return "Nothing was merged.";
1056
+ return landed.length === 1 ? `${landed[0]} was merged already and stays merged.` : `${landed.join(", ")} were merged already and stay merged.`;
1057
+ }
1058
+ function repairedLanded(handoff) {
1059
+ return handoff.landed.filter((t) => t.repairedFiles?.length);
1060
+ }
1061
+ function repairedNotice(handoff) {
1062
+ const repaired = repairedLanded(handoff);
1063
+ if (repaired.length === 0) return "";
1064
+ const named = repaired.map((t) => `${t.title}${t.repairedFiles?.length ? ` (${capConflictFiles(t.repairedFiles)})` : ""}`);
1065
+ const list = named.length === 1 ? named[0] : `${named.slice(0, -1).join(", ")} and ${named[named.length - 1]}`;
1066
+ return ` ${list} landed through ${repaired.length === 1 ? "a conflict repair" : "conflict repairs"}.`;
1067
+ }
1068
+ function describeMergeResult(result, branch, group, repaired = []) {
1069
+ switch (result.outcome) {
1070
+ case "merged": {
1071
+ const base = group ? `Merged ${branch} into the checked-out branch of every repository.` : `Merged ${branch} into your checked-out branch.`;
1072
+ const repairedNoticeText = repairedNotice({ landed: repaired });
1073
+ return { level: "info", message: `${base}${repairedNoticeText}` };
1074
+ }
1075
+ case "blocked":
1076
+ return { level: "warn", message: `Merged nothing, so every tree is as it was: ${blockNotices(result.blocked).join("; ")}.${branchFix(result.blocked)}` };
1077
+ case "conflict":
1078
+ return {
1079
+ level: "warn",
1080
+ message: result.repo === SELF_REPO ? `Merging ${branch} conflicted, so it was aborted: your tree is as it was.` : `Merging ${branch} conflicted in ${result.repo}${result.files?.length ? ` (${result.files.join(", ")})` : ""}, so it was aborted there. ${landedNotice(result.landed ?? [])}`
1081
+ };
1082
+ case "failed": {
1083
+ const lone = result.repo === SELF_REPO;
1084
+ const where = lone ? "" : ` in ${result.repo}`;
1085
+ if (result.error) {
1086
+ return { level: "error", message: `Could not merge ${branch}${where}: git said "${result.error}". ${lone ? "Your tree is as it was." : landedNotice(result.landed ?? [])}` };
1087
+ }
1088
+ return {
1089
+ level: "error",
1090
+ message: lone ? `Could not merge ${branch}: finish or abort the merge already in progress, then try again.` : `Could not merge ${branch}${where}: finish or abort any merge in progress there, then try again. ${landedNotice(result.landed ?? [])}`
1091
+ };
1092
+ }
1093
+ }
1094
+ }
1095
+ function mergeOutcome(result, branch, group, repaired = []) {
1096
+ const ok = result.outcome === "merged";
1097
+ if (!group && result.outcome === "conflict") {
1098
+ return { ok, message: `Merging ${branch} conflicted, so it was aborted: your tree is as it was. Merge it with git and resolve the conflict there.` };
1099
+ }
1100
+ const { message } = describeMergeResult(result, branch, group, repaired);
1101
+ return { ok, message: ok || !group ? message : `${message} Each repository's ${branch} is a plain branch you can merge by hand.` };
1102
+ }
1103
+
1104
+ // src/services/isolationPreviewText.ts
1105
+ var SHARED_NAMED = 5;
1106
+ var OPS_TASKS = "Ops and script tasks use your checkout.";
1107
+ function repoName(repo) {
1108
+ if (repo.path !== ".") return repo.path;
1109
+ return repo.top ? `Repository above this workspace (${repo.top})` : "This workspace";
1110
+ }
1111
+ function repoDetail(repo, status) {
1112
+ const head = repo.head;
1113
+ const base = head.kind === "branch" ? `on ${head.branch} (${head.commit})` : head.kind === "detached" ? `detached HEAD at ${head.commit}` : head.kind === "unknown" ? "branch not read" : head.branch ? `no commits yet on ${head.branch}` : "no commits yet";
1114
+ const shared = head.kind === "unborn" && (status === "ready" || status === "dirty") ? "; tasks share it instead of isolating it" : "";
1115
+ return `${base}${shared}${repo.dirty ? " \xB7 uncommitted changes to tracked files" : ""}`;
1116
+ }
1117
+ function sharedLine(shared) {
1118
+ if (shared.length === 0) return void 0;
1119
+ const more = shared.length - SHARED_NAMED;
1120
+ const named = shared.slice(0, SHARED_NAMED).join(", ");
1121
+ return `Shared live between tasks: ${more > 0 ? `${named} and ${more} more` : named}`;
1122
+ }
1123
+ function describeIsolationPreview(preview) {
1124
+ const repos = preview.repos.map((repo) => ({ name: repoName(repo), detail: repoDetail(repo, preview.status) }));
1125
+ const shared = sharedLine(preview.shared);
1126
+ const notice = (summary, note) => ({ summary, repos, ...shared ? { shared } : {}, ...note ? { note } : {} });
1127
+ switch (preview.status) {
1128
+ case "ready":
1129
+ return notice("Code tasks run in isolated git worktrees based on the repositories and branches below.", `${OPS_TASKS} The base branch is read again when a run starts.`);
1130
+ case "dirty":
1131
+ return notice(
1132
+ "Code tasks get isolated worktrees from the repositories and branches below once you decide what to do with uncommitted changes.",
1133
+ `Starting a run asks whether to stash them or run without isolation. ${OPS_TASKS}`
1134
+ );
1135
+ case "disabled":
1136
+ return notice("Worktree isolation is turned off, so tasks run in the workspace without isolated worktrees.");
1137
+ case "git-missing":
1138
+ return notice("Git was not found, so tasks run in the workspace without isolated worktrees.");
1139
+ case "no-commits":
1140
+ return notice("No repository has a commit yet, so tasks run in the workspace without isolated worktrees.");
1141
+ case "no-repo":
1142
+ return notice("No Git repository detected. Tasks run in the workspace without isolated worktrees.");
1143
+ case "failed":
1144
+ return notice(
1145
+ "Could not read the Git repositories just now, so Ordewell can't say yet whether code tasks get isolated worktrees.",
1146
+ `${preview.error ?? "Git did not answer"}. It is checked again when a run starts.`
1147
+ );
1148
+ }
1149
+ }
1150
+
1151
+ // src/services/mergeWording.ts
1152
+ function isRepoGroup(handoff) {
1153
+ return handoff.repos.some((r) => r.path !== ".");
1154
+ }
1155
+ function handoffBranchName(handoff) {
1156
+ return [...new Set(handoff.repos.map((r) => r.integrationBranch))].join(", ");
1157
+ }
1158
+ function repoBaseLine(repo, length) {
1159
+ const commit = repo.baseRef.slice(0, length);
1160
+ return repo.baseBranch ? `${repo.baseBranch} @ ${commit}` : `${repo.startedDetached ? "detached at" : "base"} ${commit}`;
1161
+ }
1162
+ function handoffBaseLine(handoff, length) {
1163
+ const group = isRepoGroup(handoff);
1164
+ return handoff.repos.map((repo) => `${group ? `${repo.path}: ` : ""}${repoBaseLine(repo, length)}`).join(", ");
1165
+ }
1166
+ function repoResultLines(handoff) {
1167
+ return handoff.repos.map(({ path, landed }) => `${path}: ${landed.length === 0 ? "nothing to merge" : `${landed.length} task${landed.length === 1 ? "" : "s"} landed`}`);
1168
+ }
1169
+
788
1170
  // src/services/runSettingsText.ts
789
1171
  var SUPERVISOR_LEVEL_LABEL = { off: "Off", watch: "Watch", act: "Act" };
1172
+ var PLANNED_WITH_LABEL = "Planned-with line in commits Ordewell writes";
790
1173
  function parseCount(arg, min) {
791
1174
  if (arg === void 0 || !/^\d+$/.test(arg)) return null;
792
1175
  const n = Number(arg);
@@ -970,13 +1353,17 @@ function formatCost(currency, amount) {
970
1353
  function contextMeterText(fill) {
971
1354
  if (!fill) return null;
972
1355
  const used = `ctx ${formatTokenCount(fill.usedTokens)}`;
973
- if (!fill.windowTokens) return used;
974
- const share = fill.usedTokens / fill.windowTokens * 100;
975
- return `${used} (${share < 1 ? "<1" : Math.round(share)}%)`;
1356
+ const share = fill.windowTokens ? fill.usedTokens / fill.windowTokens * 100 : void 0;
1357
+ const meter = share === void 0 ? used : `${used} (${share < 1 ? "<1" : Math.round(share)}%)`;
1358
+ return fill.autoCompaction ? `${meter} \xB7 no auto-compact` : meter;
976
1359
  }
977
1360
  function contextMeterTitle(fill) {
978
1361
  const used = fill.usedTokens.toLocaleString("en-US");
979
- return fill.windowTokens ? `${used} of ${fill.windowTokens.toLocaleString("en-US")} tokens` : `${used} tokens`;
1362
+ const counts = fill.windowTokens ? `${used} of ${fill.windowTokens.toLocaleString("en-US")} tokens` : `${used} tokens`;
1363
+ const off = fill.autoCompaction;
1364
+ if (!off) return counts;
1365
+ const why = off.off === "settings" ? "Automatic compaction is off in the settings file" : off.model ? `Automatic compaction is off: no catalog Ordewell holds states ${off.model}'s window` : "Automatic compaction is off: no planner model is selected";
1366
+ return `${counts}. ${why}; /compact still condenses the conversation.`;
980
1367
  }
981
1368
  function costParts(totals) {
982
1369
  const rank = (currency) => currency.toLowerCase() === "usd" ? "" : currency.toUpperCase();
@@ -1023,6 +1410,13 @@ function tasksTotalText(tasks) {
1023
1410
  return parts.length > 0 ? `Tasks total: ${parts.join(" \xB7 ")}` : null;
1024
1411
  }
1025
1412
 
1413
+ // src/services/conversationSummary.ts
1414
+ function isCheckpoint(value) {
1415
+ if (!value || typeof value !== "object") return false;
1416
+ const { id, keptFrom, trigger } = value;
1417
+ return typeof id === "string" && id !== "" && Number.isInteger(keptFrom) && (trigger === "manual" || trigger === "auto");
1418
+ }
1419
+
1026
1420
  // src/conversation/lanes.ts
1027
1421
  function findLastIndex(items, match) {
1028
1422
  for (let i = items.length - 1; i >= 0; i--) if (match(items[i])) return i;
@@ -1136,7 +1530,11 @@ function closeSubagent(block, cut) {
1136
1530
  return status === block.status && children.every((c, i) => c === block.children[i]) ? block : { ...block, status, children };
1137
1531
  }
1138
1532
  function closeOpenBlocks(view, cut) {
1139
- const blocks = view.blocks.map((b) => b.type === "subagent" ? closeSubagent(b, cut) : closeBlock(b, cut !== null));
1533
+ const blocks = view.blocks.map((b) => {
1534
+ if (b.type === "subagent") return closeSubagent(b, cut);
1535
+ if (b.type === "compaction") return b.status === "running" && (cut === "stopped" || cut === "failed") ? { ...b, status: cut } : b;
1536
+ return closeBlock(b, cut !== null);
1537
+ });
1140
1538
  return blocks.every((b, i) => b === view.blocks[i]) ? view : { ...view, blocks };
1141
1539
  }
1142
1540
 
@@ -1152,7 +1550,7 @@ function finalSegmentIndex(blocks, turnId, pastPlan = false) {
1152
1550
  for (let i = blocks.length - 1; i >= 0; i--) {
1153
1551
  const block = blocks[i];
1154
1552
  if (block.type === "usage" || block.type === "thinking" || block.type === "approval" || pastPlan && block.type === "plan") continue;
1155
- if (block.type === "command" || block.type === "compaction" || block.type === "runner_loaded") continue;
1553
+ if (block.type === "command" || block.type === "compaction" || block.type === "runner_loaded" || block.type === "handoff") continue;
1156
1554
  if (block.turnId !== turnId) continue;
1157
1555
  return isUnsettledSegment(block, turnId) ? i : -1;
1158
1556
  }
@@ -1168,10 +1566,11 @@ function openTurn(view, { turnId, prompt = "", skills = [], wakeUps = [], forced
1168
1566
  const updates = wakeUps.map((text2, n) => settledMessage(`b${said.nextId + n}`, "wake_up", text2, turnId));
1169
1567
  return { ...said, blocks: [...said.blocks.slice(0, at), ...updates, ...said.blocks.slice(at)], nextId: said.nextId + updates.length };
1170
1568
  }
1171
- function takePendingInputs(view, prompt, wakeUps, turnId) {
1569
+ function takePendingInputs(view, prompt, wakeUps, turnId, heldAt) {
1172
1570
  const pending = view.pendingInputs ?? [];
1173
- const users = pending.filter(({ input }) => input.source === "user");
1174
- const updates = pending.filter(({ input }) => input.source === "supervisor");
1571
+ const arrived = ({ input }) => heldAt === void 0 || input.timestamp <= heldAt;
1572
+ const users = pending.filter((p) => p.input.source === "user" && arrived(p));
1573
+ const updates = pending.filter((p) => p.input.source === "supervisor" && arrived(p));
1175
1574
  const taken = /* @__PURE__ */ new Set();
1176
1575
  let matchedPrompt = false;
1177
1576
  for (let n = 1; prompt !== void 0 && n <= users.length; n++) {
@@ -1307,7 +1706,7 @@ function markPlan(view, content, turnId) {
1307
1706
  }
1308
1707
  return append(view, (id) => ({ type: "plan", id, text: "", ...marker }));
1309
1708
  }
1310
- function requestApproval(view, { id: approvalId, kind, subject, scope, detail, turnId }) {
1709
+ function requestApproval(view, { id: approvalId, kind, subject, scope, detail, body, turnId }) {
1311
1710
  return append(view, (id) => ({
1312
1711
  type: "approval",
1313
1712
  id,
@@ -1317,6 +1716,7 @@ function requestApproval(view, { id: approvalId, kind, subject, scope, detail, t
1317
1716
  scope,
1318
1717
  status: "pending",
1319
1718
  ...detail ? { detail } : {},
1719
+ ...body ? { body } : {},
1320
1720
  ...turnId ? { turnId } : {}
1321
1721
  }));
1322
1722
  }
@@ -1347,11 +1747,48 @@ function endTurn(view, { turnId, outcome }) {
1347
1747
  if (planText >= 0) next = { ...next, blocks: next.blocks.filter((_, i) => i !== planText) };
1348
1748
  return closeOpenBlocks(next, outcome === "stopped" ? "stopped" : outcome === "error" ? "failed" : null);
1349
1749
  }
1350
- function markCompaction(view, summary) {
1750
+ function compactionAt(view, compactionId) {
1751
+ return findLastIndex(view.blocks, (b) => b.type === "compaction" && b.compactionId === compactionId);
1752
+ }
1753
+ function followCompaction(view, { compaction, turnId }) {
1754
+ const i = compactionAt(view, compaction.id);
1755
+ const at = view.blocks[i];
1756
+ const known = at?.type === "compaction" ? at : void 0;
1757
+ if (compaction.phase === "started") {
1758
+ if (known) return view;
1759
+ return append(view, (id) => compactionBlock(id, { compactionId: compaction.id, origin: compaction.origin, trigger: compaction.trigger, status: "running", turnId }));
1760
+ }
1761
+ const { outcome, preTokens, postTokens } = compaction;
1762
+ if (known && known.status !== "running") return view;
1763
+ if (outcome === "unchanged") return known ? { ...view, blocks: view.blocks.filter((_, n) => n !== i) } : view;
1764
+ if (!known && (outcome === "stopped" || outcome === "failed")) return view;
1765
+ const ended = (id) => compactionBlock(id, {
1766
+ compactionId: compaction.id,
1767
+ origin: compaction.origin,
1768
+ trigger: compaction.trigger,
1769
+ status: outcome,
1770
+ preTokens,
1771
+ postTokens,
1772
+ turnId: known?.turnId ?? turnId
1773
+ });
1774
+ return known ? { ...view, blocks: replaceAt(view.blocks, i, ended(known.id)) } : append(view, ended);
1775
+ }
1776
+ function markCompaction(view, entry) {
1777
+ const { content: summary } = entry;
1778
+ const checkpoint = isCheckpoint(entry.checkpoint) ? entry.checkpoint : void 0;
1779
+ if (checkpoint) {
1780
+ const i2 = compactionAt(view, checkpoint.id);
1781
+ const known = view.blocks[i2];
1782
+ if (known?.type !== "compaction") {
1783
+ return append(view, (id) => compactionBlock(id, { compactionId: checkpoint.id, origin: "ordewell", trigger: checkpoint.trigger, status: "condensed", summary }));
1784
+ }
1785
+ if (known.status === "condensed" && known.summary === summary) return view;
1786
+ return { ...view, blocks: replaceAt(view.blocks, i2, { ...known, status: "condensed", summary }) };
1787
+ }
1788
+ const settled = (id) => compactionBlock(id, { origin: "ordewell", trigger: "manual", status: "condensed", summary });
1351
1789
  const i = findLastIndex(view.blocks, (b) => b.type === "message" && b.role === "planner" && b.turnId === void 0 && b.text === summary);
1352
1790
  const announced = view.blocks[i];
1353
- if (announced?.type === "message") return { ...view, blocks: replaceAt(view.blocks, i, { ...announced, role: "system" }) };
1354
- return append(view, (id) => settledMessage(id, "system", summary));
1791
+ return announced ? { ...view, blocks: replaceAt(view.blocks, i, settled(announced.id)) } : append(view, settled);
1355
1792
  }
1356
1793
  function syncTranscript(view, { plan, turnId }) {
1357
1794
  const history = plan.conversationHistory ?? [];
@@ -1360,12 +1797,13 @@ function syncTranscript(view, { plan, turnId }) {
1360
1797
  let latest = view.transcriptAt;
1361
1798
  for (const [i, entry] of history.entries()) {
1362
1799
  const isNew = view.transcriptAt === void 0 || entry.timestamp > view.transcriptAt;
1363
- if (isNew && entry.role === "user" && !entry.kind) next = takePendingInputs(next, entry.content, []).view;
1364
- if (isNew && entry.kind === "wake_up") next = takePendingInputs(next, void 0, [entry.content]).view;
1800
+ const heldAt = isNew ? void 0 : entry.timestamp;
1801
+ if (entry.role === "user" && !entry.kind) next = takePendingInputs(next, entry.content, [], void 0, heldAt).view;
1802
+ if (entry.kind === "wake_up") next = takePendingInputs(next, void 0, [entry.content], void 0, heldAt).view;
1365
1803
  if (isNew && entry.kind === "plan_generated") next = markPlan(next, entry.content, i === committed ? turnId : void 0);
1366
1804
  if (isNew && entry.kind === "system") next = append(next, (id) => settledMessage(id, "system", entry.content));
1367
1805
  if (isNew && entry.kind === "error") next = append(next, (id) => settledMessage(id, "error", entry.content));
1368
- if (isNew && entry.kind === "compaction") next = markCompaction(next, entry.content);
1806
+ if (isNew && entry.kind === "compaction") next = markCompaction(next, entry);
1369
1807
  if (latest === void 0 || entry.timestamp > latest) latest = entry.timestamp;
1370
1808
  }
1371
1809
  return next === view && latest === view.transcriptAt ? view : { ...next, transcriptAt: latest };
@@ -1417,6 +1855,8 @@ function reduceConversation(view, input) {
1417
1855
  return append(view, (id) => settledMessage(id, "system", input.text));
1418
1856
  case "planner_usage":
1419
1857
  return reportUsage(view, input);
1858
+ case "planner_compaction":
1859
+ return followCompaction(view, input);
1420
1860
  default:
1421
1861
  return view;
1422
1862
  }
@@ -1432,13 +1872,22 @@ function fromEntry(entry, loaded = []) {
1432
1872
  const { skill } = entry;
1433
1873
  return (id) => skillLoadBlock(id(), skillLoadNotice(skill));
1434
1874
  }
1875
+ if (entry.kind === "compaction") {
1876
+ const checkpoint = isCheckpoint(entry.checkpoint) ? entry.checkpoint : void 0;
1877
+ return (id) => compactionBlock(id(), {
1878
+ compactionId: checkpoint?.id,
1879
+ origin: "ordewell",
1880
+ trigger: checkpoint?.trigger ?? "manual",
1881
+ status: "condensed",
1882
+ summary: entry.content
1883
+ });
1884
+ }
1435
1885
  if (entry.role === "user" && !entry.kind) return (id) => userMessage(id(), entry.content, loaded.map(skillLoadNotice), { forced: entry.forced });
1436
1886
  return (id) => settledMessage(id(), roleOf(entry), entry.content);
1437
1887
  }
1438
1888
  function roleOf(entry) {
1439
1889
  switch (entry.kind) {
1440
1890
  case "system":
1441
- case "compaction":
1442
1891
  case "execution":
1443
1892
  return "system";
1444
1893
  case "wake_up":
@@ -1549,6 +1998,17 @@ function fromTranscript(conversationHistory, researchLog, plannerUsage, pendingT
1549
1998
  return { blocks, nextId, ...transcriptAt ? { transcriptAt } : {}, ...pendingInputs.length > 0 ? { pendingInputs } : {} };
1550
1999
  }
1551
2000
 
2001
+ // src/models/TaskLog.ts
2002
+ function secondsText(ms) {
2003
+ return `${Math.round(ms / 100) / 10} s`;
2004
+ }
2005
+ function scriptExitText(exit) {
2006
+ const seconds = secondsText(exit.durationMs);
2007
+ if (exit.error !== void 0) return `could not start: ${exit.error}`;
2008
+ const how = exit.signal ? `ended by ${exit.signal}` : `exited ${exit.code ?? "unknown"}`;
2009
+ return `${how} after ${seconds}${exit.timedOut ? ": the time limit ran out" : ""}`;
2010
+ }
2011
+
1552
2012
  // src/utils/sameMessage.ts
1553
2013
  function sameMessage(a, b) {
1554
2014
  const bare = (value) => value.trim().replace(/\.+$/, "").trimEnd();
@@ -1785,6 +2245,43 @@ function unqueueMessage(view, { messageId }) {
1785
2245
  const queued = view.queued.filter((m) => m.id !== messageId);
1786
2246
  return queued.length === view.queued.length ? view : { ...view, queued };
1787
2247
  }
2248
+ function runScriptStep(view, event) {
2249
+ const [command, toolLabel] = "argv" in event ? [commandLine(event.argv), "Run"] : [event.script, "Script"];
2250
+ return appendOutput({ ...view, working: true }, null, (id) => pendingTool(id, { tool: "bash", toolLabel, args: JSON.stringify({ command }) }));
2251
+ }
2252
+ function runningScriptStep(view) {
2253
+ return findLastIndex(view.blocks, (b) => b.type === "tool" && b.status === "pending" && b.tool === "bash");
2254
+ }
2255
+ function scriptStepOutput(view, { text: text2, omittedChars }) {
2256
+ const said = omittedChars ? `\u2026 ${omittedChars} characters omitted \u2026
2257
+ ${text2}` : text2;
2258
+ const i = runningScriptStep(view);
2259
+ const block = view.blocks[i];
2260
+ if (block?.type !== "tool") return append(view, (id) => settledMessage(id, "system", said));
2261
+ const output = block.output + said;
2262
+ return { ...view, blocks: replaceAt(view.blocks, i, { ...block, output, outputLineCount: outputLines(output).length }) };
2263
+ }
2264
+ function endScriptStep(view, event) {
2265
+ const status = scriptExitText(event);
2266
+ const passed = event.code === 0 && !event.signal && event.error === void 0;
2267
+ const i = runningScriptStep(view);
2268
+ const block = view.blocks[i];
2269
+ const ended = { ...view, working: false };
2270
+ if (block?.type !== "tool") return append(ended, (id) => settledMessage(id, "system", status));
2271
+ const output = block.output.trim() ? `${block.output.replace(/\n+$/, "")}
2272
+ ${status}` : status;
2273
+ return { ...ended, blocks: replaceAt(view.blocks, i, finishedTool(block, output, passed ? "success" : "failure")) };
2274
+ }
2275
+ function sameHandoff(a, b) {
2276
+ return JSON.stringify(a) === JSON.stringify(b);
2277
+ }
2278
+ function showHandoff(view, { handoff }) {
2279
+ const i = findLastIndex(view.blocks, (b) => b.type === "handoff");
2280
+ const block = view.blocks[i];
2281
+ if (block?.type !== "handoff") return appendStep(view, (id) => ({ type: "handoff", id, handoff }));
2282
+ if (sameHandoff(block.handoff, handoff)) return view;
2283
+ return { ...view, blocks: replaceAt(view.blocks, i, { ...block, handoff }) };
2284
+ }
1788
2285
  function endCall(block) {
1789
2286
  return block.type === "tool" && block.status === "pending" ? finishedTool(block, "", "not_executed") : block;
1790
2287
  }
@@ -1857,6 +2354,14 @@ function reduceTaskLog(view, event) {
1857
2354
  return decideApproval2(view, event);
1858
2355
  case "approval_withdrawn":
1859
2356
  return settleApproval2(view, event.approvalId, (block) => ({ ...block, status: "withdrawn" }));
2357
+ case "script_run":
2358
+ return runScriptStep(view, event);
2359
+ case "script_output":
2360
+ return scriptStepOutput(view, event);
2361
+ case "script_exit":
2362
+ return endScriptStep(view, event);
2363
+ case "handoff":
2364
+ return showHandoff(view, event);
1860
2365
  case "runner_ended":
1861
2366
  return closeOpenBlocks({ ...endOpenCalls(view), working: false, streams: {}, latestText: void 0 }, "stopped");
1862
2367
  default:
@@ -1867,6 +2372,161 @@ function replayTaskLog(events, from = EMPTY_TASK_LOG) {
1867
2372
  return events.reduce(reduceTaskLog, from);
1868
2373
  }
1869
2374
 
2375
+ // src/models/Handoff.ts
2376
+ var SEVERITY_LABEL = {
2377
+ blocker: "Blocker",
2378
+ major: "Major",
2379
+ minor: "Minor",
2380
+ info: "Info"
2381
+ };
2382
+ function severityLabel(severity) {
2383
+ return SEVERITY_LABEL[severity];
2384
+ }
2385
+ var SEVERITY_RANK = { blocker: 0, major: 1, minor: 2, info: 3 };
2386
+ function worstSeverity(findings) {
2387
+ let worst = null;
2388
+ for (const { severity } of findings) {
2389
+ if (worst === null || SEVERITY_RANK[severity] < SEVERITY_RANK[worst]) worst = severity;
2390
+ }
2391
+ return worst;
2392
+ }
2393
+ function handoffReferenceText(ref, taskName = () => void 0) {
2394
+ switch (ref.kind) {
2395
+ case "file":
2396
+ return `${ref.line !== void 0 ? `${ref.path}:${ref.line}` : ref.path}${ref.resolved === true ? "" : " (unresolved)"}`;
2397
+ case "commit":
2398
+ return `commit ${ref.sha}${ref.repo ? ` in ${ref.repo}` : ""}${ref.resolved === true ? "" : " (unresolved)"}`;
2399
+ case "task":
2400
+ return `task ${taskName(ref.task) ?? ref.task}${ref.finding ? ` ${ref.finding}` : ""}`;
2401
+ case "url":
2402
+ return ref.url;
2403
+ case "patch":
2404
+ return patchText(ref);
2405
+ }
2406
+ }
2407
+ var FLAG_WORDS = {
2408
+ excluded: "not carried (links and submodules)",
2409
+ dropped: "dropped over the size cap",
2410
+ normalized: "normalized by git attributes"
2411
+ };
2412
+ function counted(count, noun) {
2413
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
2414
+ }
2415
+ function byteSize(bytes) {
2416
+ if (bytes < 1024) return `${bytes} B`;
2417
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2418
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2419
+ }
2420
+ function patchFlagText(flag) {
2421
+ const more = flag.count - flag.paths.length;
2422
+ return `${FLAG_WORDS[flag.kind]}: ${counted(flag.count, "file")}${flag.paths.length ? ` (${flag.paths.join(", ")}${more > 0 ? `, +${more} more` : ""})` : ""}`;
2423
+ }
2424
+ function patchChangesText(patch) {
2425
+ const binary = patch.binary > 0 ? `, ${patch.binary} binary` : "";
2426
+ return `${counted(patch.files, "file")}${binary}, +${patch.added} -${patch.removed}, ${byteSize(patch.bytes)}`;
2427
+ }
2428
+ function patchLinesText(patch) {
2429
+ const binary = patch.binary > 0 ? `, ${patch.binary} binary` : "";
2430
+ return `${counted(patch.files, "file")}${binary}, +${patch.added} -${patch.removed}`;
2431
+ }
2432
+ function patchText(patch) {
2433
+ const flags = patch.flags?.length ? `; ${patch.flags.map(patchFlagText).join("; ")}` : "";
2434
+ return `patch ${patch.path} (attempt ${patch.attempt}): ${patchChangesText(patch)}, from commit ${patch.base.slice(0, 12)}${flags}`;
2435
+ }
2436
+ function patchView(patch) {
2437
+ return {
2438
+ path: patch.path,
2439
+ attempt: `attempt ${patch.attempt}`,
2440
+ changes: patchChangesText(patch),
2441
+ base: `from commit ${patch.base.slice(0, 12)}`,
2442
+ saved: `saved as ${patch.path} (attempt ${patch.attempt})`,
2443
+ flags: (patch.flags ?? []).map(patchFlagText),
2444
+ peek: `patch: ${patchLinesText(patch)}`
2445
+ };
2446
+ }
2447
+ function savedPatchViews(handoff) {
2448
+ return (handoff?.artifacts ?? []).filter((a) => a.kind === "patch").map(patchView);
2449
+ }
2450
+ function savedPatchView(handoff) {
2451
+ return savedPatchViews(handoff)[0] ?? null;
2452
+ }
2453
+
2454
+ // src/conversation/labels.ts
2455
+ var APPROVAL_KIND_TEXT = {
2456
+ shell_command: "Run a command",
2457
+ url_fetch: "Fetch a URL",
2458
+ web_search: "Search the web",
2459
+ external_path: "Read outside the workspace",
2460
+ mcp_tool: "Use an MCP tool",
2461
+ mcp_server: "Start a project MCP server",
2462
+ runner_tool: "Use a tool",
2463
+ task_start: "Start work"
2464
+ };
2465
+ function approvalKindText(kind) {
2466
+ return APPROVAL_KIND_TEXT[kind];
2467
+ }
2468
+ var APPROVAL_SOURCE_TEXT = {
2469
+ "pre-approved": "pre-approved",
2470
+ remembered: "remembered",
2471
+ mode: "policy",
2472
+ "no-channel": "no approval channel",
2473
+ excluded: "excluded",
2474
+ guard: "blocked address",
2475
+ secret: "secret check",
2476
+ budget: "web-read budget",
2477
+ unsupported: "not usable on this runner version"
2478
+ };
2479
+ function approvalStatusText(block) {
2480
+ const policy = block.decidedBy && block.decidedBy !== "asked" ? APPROVAL_SOURCE_TEXT[block.decidedBy] : void 0;
2481
+ switch (block.status) {
2482
+ case "pending":
2483
+ return "Waiting for you";
2484
+ case "granted":
2485
+ return policy ? `Auto-approved (${policy})` : block.forTask ? "Approved for this task" : "Approved";
2486
+ case "denied":
2487
+ return policy ? `Auto-denied (${policy})` : "Denied";
2488
+ case "withdrawn":
2489
+ return "Withdrawn";
2490
+ }
2491
+ }
2492
+ function planMarkerParts(block) {
2493
+ if (block.status === "building") return ["Building plan\u2026"];
2494
+ const count = block.taskCount === void 0 ? [] : [`${block.taskCount} task${block.taskCount === 1 ? "" : "s"}`];
2495
+ return [`Plan ${block.status}`, ...count];
2496
+ }
2497
+ function handoffFindingsText(count) {
2498
+ return count === 0 ? "no findings" : `${count} finding${count === 1 ? "" : "s"}`;
2499
+ }
2500
+ function firstNonBlankLine(text2) {
2501
+ return text2.split(/\r?\n/).map((line) => line.trim()).find((line) => line !== "") ?? "";
2502
+ }
2503
+ function handoffPeekParts(handoff) {
2504
+ const severity = handoff.severity ? [severityLabel(handoff.severity)] : [];
2505
+ const commit = handoff.commit ? [`Commit: ${handoff.commit.subject.trim()}`] : [];
2506
+ const summary = firstNonBlankLine(handoff.summary);
2507
+ return [handoffFindingsText(handoff.findingCount), ...severity, ...handoff.patch ? [handoff.patch.peek] : [], ...commit, ...summary ? [summary] : []];
2508
+ }
2509
+ var COMPACTION_STATUS_TEXT = {
2510
+ running: "Compacting context\u2026",
2511
+ stopped: "Compaction stopped \xB7 nothing changed",
2512
+ failed: "Compaction failed \xB7 nothing changed",
2513
+ paused: "Context does not fit \xB7 the planner is paused",
2514
+ pruned: "Older tool output trimmed from context",
2515
+ unchanged: "Context unchanged"
2516
+ };
2517
+ function compactedBy({ origin, trigger }) {
2518
+ if (origin === "native") return trigger === "auto" ? "The planner compacted its context on its own" : "The planner compacted its context";
2519
+ if (origin === "ordewell") return trigger === "auto" ? "Context compacted automatically" : "Context compacted";
2520
+ return trigger === "auto" ? "Context compacted by the runner on its own" : "Context compacted";
2521
+ }
2522
+ function compactionText(block) {
2523
+ const status = block.status ? COMPACTION_STATUS_TEXT[block.status] : void 0;
2524
+ if (status) return status;
2525
+ const { preTokens, postTokens } = block;
2526
+ const sizes = preTokens !== void 0 && postTokens !== void 0 ? ` \xB7 ${formatTokenCount(preTokens)} \u2192 ${formatTokenCount(postTokens)} tokens` : "";
2527
+ return `${compactedBy(block)}${sizes}`;
2528
+ }
2529
+
1870
2530
  // src/conversation/promptHold.ts
1871
2531
  var EMPTY_HOLD = [];
1872
2532
  function holdPrompt(hold, text2) {
@@ -1935,9 +2595,45 @@ function stopTurn(view, gate) {
1935
2595
  };
1936
2596
  }
1937
2597
 
2598
+ // src/services/WakePolicy.ts
2599
+ var REPORT_LEAD = ", whose result you asked for, has ended.";
2600
+
2601
+ // src/conversation/wakeUp.ts
2602
+ var DIGEST_HEAD = "Run update:";
2603
+ var REPORT_PREVIEW_LINES = 8;
2604
+ function hasLongReport(text2) {
2605
+ return wakeUpItems(text2).some((item) => item.kind === "report" && item.lines.length > REPORT_PREVIEW_LINES);
2606
+ }
2607
+ var REPORTED = /^task (#[\d.]+ ".*")$/;
2608
+ function wakeUpItems(text2) {
2609
+ const items = [];
2610
+ let report = null;
2611
+ for (const line of text2.replace(/\r\n?/g, "\n").split("\n")) {
2612
+ if (report && (line.startsWith(" ") || line === "")) {
2613
+ report.lines.push(line.slice(2));
2614
+ continue;
2615
+ }
2616
+ report = null;
2617
+ if (line === DIGEST_HEAD && items.length === 0) continue;
2618
+ const bullet = line.startsWith("- ") ? line.slice(2) : null;
2619
+ const at = bullet?.indexOf(REPORT_LEAD) ?? -1;
2620
+ const task = bullet === null || at < 0 ? void 0 : REPORTED.exec(bullet.slice(0, at))?.[1];
2621
+ if (bullet !== null && task !== void 0) {
2622
+ report = { kind: "report", task, lines: [bullet.slice(at + REPORT_LEAD.length).trimStart()] };
2623
+ items.push(report);
2624
+ } else items.push({ kind: "line", text: line });
2625
+ }
2626
+ return items.map((item) => item.kind === "report" ? { ...item, lines: trimmed(item.lines) } : item);
2627
+ }
2628
+ function trimmed(lines2) {
2629
+ const kept = [...lines2];
2630
+ while (kept.length > 1 && kept[kept.length - 1].trim() === "") kept.pop();
2631
+ return kept;
2632
+ }
2633
+
1938
2634
  // src/conversation/detail.ts
1939
2635
  function hasHiddenDetail(blocks) {
1940
- return blocks.some((b) => b.type === "thinking" || b.type === "tool" || b.type === "subagent" || b.type === "runner_loaded");
2636
+ return blocks.some((b) => b.type === "thinking" || b.type === "tool" || b.type === "subagent" || b.type === "runner_loaded" || b.type === "message" && b.role === "wake_up" && hasLongReport(b.text) || b.type === "handoff" && (b.handoff.report?.replace(/\n+$/, "").split("\n").length ?? 0) > REPORT_PREVIEW_LINES);
1941
2637
  }
1942
2638
 
1943
2639
  // src/conversation/notices.ts
@@ -2029,8 +2725,26 @@ function followTaskLog(state, input) {
2029
2725
  }
2030
2726
  }
2031
2727
 
2728
+ // src/taskRow/editability.ts
2729
+ function editableWhileRunning(task, run) {
2730
+ if (!NOT_STARTED.has(task.status)) return false;
2731
+ if (!run) return true;
2732
+ if ((run.awaitingApproval ?? 0) > 0) return false;
2733
+ return run.isolation?.state !== "active" && run.isolation?.state !== "repairing";
2734
+ }
2735
+ function runHoldsEditors(isExecuting, activity) {
2736
+ return isExecuting || activity.starting || activity.advancesOnItsOwn;
2737
+ }
2738
+ function cardEditable(runHolds, task, run) {
2739
+ return !runHolds || editableWhileRunning(task, run);
2740
+ }
2741
+ function cardEditNeedsUnstarted(runHolds, task) {
2742
+ return runHolds || NOT_STARTED.has(task.status);
2743
+ }
2744
+
2032
2745
  // src/taskRow/actions.ts
2033
2746
  var UNSTARTED_AI = (t) => t.type === "ai" && NOT_STARTED.has(t.status);
2747
+ var UNSTARTED_RUN = (t) => t.type !== "user" && NOT_STARTED.has(t.status);
2034
2748
  var QUEUED = (t) => t.status === "pending" || t.status === "approved";
2035
2749
  var PAUSED = (t) => t.status === "awaiting_user" && t.awaitingReason === "usage-limit";
2036
2750
  var RESUMES = (t) => PAUSED(t) && !!t.resumeAt;
@@ -2041,7 +2755,7 @@ var TASK_RULES = [
2041
2755
  ["resume-now", RESUMABLE],
2042
2756
  ["cancel-resume", RESUMES],
2043
2757
  ["skip", HOLDS_UP],
2044
- ["force-start", UNSTARTED_AI],
2758
+ ["force-start", UNSTARTED_RUN],
2045
2759
  // A manual task has no runner to report it done, so it can always be marked;
2046
2760
  // a skipped one's kept work can still be vouched for.
2047
2761
  ["complete", (t) => t.status === "awaiting_user" || t.status === "skipped" || t.type === "user"],
@@ -2064,16 +2778,43 @@ function taskRowActions(task, placement = "task") {
2064
2778
  function markAction(task) {
2065
2779
  return task.status === "completed" ? "uncomplete" : "complete";
2066
2780
  }
2067
- function markRequestFor(action) {
2068
- if (action === "complete") return "complete";
2069
- if (action === "uncomplete") return "uncomplete";
2070
- return null;
2781
+ function taskActionCommand(action, task, reason) {
2782
+ switch (action) {
2783
+ case "run":
2784
+ return { command: "run_task", task };
2785
+ case "retry":
2786
+ return { command: "retry_task", task, from: "fresh" };
2787
+ case "cancel":
2788
+ return { command: "stop_task", task };
2789
+ // The user says the limit has reset early: the task resumes in its saved session now.
2790
+ case "resume-now":
2791
+ return { command: "retry_task", task, from: "session" };
2792
+ case "cancel-resume":
2793
+ return { command: "cancel_resume", task };
2794
+ case "skip":
2795
+ return { command: "skip_task", task, ...reason ? { reason } : {} };
2796
+ case "force-start":
2797
+ return { command: "force_start", task };
2798
+ case "complete":
2799
+ return { command: "mark_complete", task };
2800
+ case "uncomplete":
2801
+ return { command: "mark_not_done", task };
2802
+ }
2803
+ }
2804
+ function overrideNeedsConfirmation(task) {
2805
+ return task.type !== "user";
2806
+ }
2807
+ function confirmedOverride(command, seen) {
2808
+ return command.command === "mark_complete" || command.command === "mark_not_done" ? { ...command, confirmed: true, ...seen ? { seen } : {} } : command;
2071
2809
  }
2072
2810
  function opsEditable(task) {
2073
2811
  return UNSTARTED_AI(task);
2074
2812
  }
2075
2813
 
2076
2814
  // src/services/AssignmentScope.ts
2815
+ function assignedRunnerOf(task) {
2816
+ return task.type === "ai" ? task.assignedRunner || void 0 : void 0;
2817
+ }
2077
2818
  var OVER = /* @__PURE__ */ new Set(["completed", "skipped"]);
2078
2819
  function assignmentIssue(task, scope) {
2079
2820
  if (task.type !== "ai" || OVER.has(task.status)) return null;
@@ -2098,15 +2839,105 @@ function assignmentConsequence({ status, awaitingReason, resumeAt }) {
2098
2839
  return "runs as assigned unless moved";
2099
2840
  }
2100
2841
 
2842
+ // src/taskRow/loop.ts
2843
+ function loopCheckState(check, goesOn) {
2844
+ switch (check.status) {
2845
+ case "completed":
2846
+ return "passed";
2847
+ case "in_progress":
2848
+ case "running":
2849
+ case "awaiting_user":
2850
+ return "running";
2851
+ case "failed":
2852
+ if (betweenRounds(check)) return goesOn ? "repeating" : "stopped";
2853
+ return outOfRounds(check) ? "out-of-rounds" : "failed";
2854
+ case "pending":
2855
+ case "approved":
2856
+ case "blocked":
2857
+ return loopRoundOf(check) > 1 ? "running" : "waiting";
2858
+ default:
2859
+ return "ended";
2860
+ }
2861
+ }
2862
+ var roundsOf = (count) => `${count} round${count === 1 ? "" : "s"}`;
2863
+ function loopCheckLabel(check, state) {
2864
+ const round = loopRoundOf(check);
2865
+ const of = `round ${round} of ${check.loop.maxRounds}`;
2866
+ switch (state) {
2867
+ case "waiting":
2868
+ case "ended":
2869
+ return `Loop of up to ${roundsOf(check.loop.maxRounds)}`;
2870
+ case "running":
2871
+ return `Round ${round} of ${check.loop.maxRounds}`;
2872
+ case "repeating":
2873
+ return `Round ${round} of ${check.loop.maxRounds}: fixing`;
2874
+ case "stopped":
2875
+ return `Stopped in ${of}`;
2876
+ case "failed":
2877
+ return `Failed in ${of}`;
2878
+ case "passed":
2879
+ return `${check.verdict?.outcome === "pass" ? "Passed" : "Completed"} in ${of}`;
2880
+ case "out-of-rounds":
2881
+ return `Failed after ${roundsOf(round)}`;
2882
+ }
2883
+ }
2884
+ function loopBodyRound(check, bodyStatus) {
2885
+ if (!["in_progress", "running", "awaiting_user", "completed", "failed"].includes(bodyStatus)) return null;
2886
+ return check.status === "failed" ? loopRoundOf(check) : Math.max(1, loopRoundOf(check) - 1);
2887
+ }
2888
+ function loopBodyLabel(checkRef, round, maxRounds) {
2889
+ return `Runs if ${checkRef} fails${round === null ? "" : ` (round ${round} of ${maxRounds})`}`;
2890
+ }
2891
+
2101
2892
  // src/taskRow/view.ts
2893
+ function taskChatAvailable(task) {
2894
+ if (task.type === "user") return false;
2895
+ return task.attempted === true || (task.attemptUsage?.length ?? 0) > 0;
2896
+ }
2102
2897
  var refsOf = (ids, tasks) => ids.map((id) => tasks?.length ? taskRefOf(tasks, id) ?? id : id);
2103
2898
  var namedRepo = (repo) => repo && repo !== "." ? repo : null;
2104
2899
  var namedRepos = (repos) => (repos ?? []).filter((r) => r !== ".");
2105
2900
  var filesOf = (files) => files?.length ? capConflictFiles(files) : null;
2106
- var asBlocking = (tasks) => tasks.map((t) => ({ id: t.id, status: t.status ?? "", dependencies: t.dependencies ?? [], subtasks: asBlocking(t.subtasks ?? []) }));
2107
- function blockedBy(task, tasks) {
2108
- if (!tasks?.length || !NOT_STARTED.has(task.status)) return [];
2109
- return blockingFailures(asBlocking(tasks)).get(task.id) ?? [];
2901
+ var asBlocking = (tasks) => tasks.map((t) => ({
2902
+ id: t.id,
2903
+ status: t.status ?? "",
2904
+ dependencies: t.dependencies ?? [],
2905
+ subtasks: asBlocking(t.subtasks ?? []),
2906
+ ...t.loop ? { loop: t.loop, loopRound: t.loopRound, ...t.verdict ? { verdict: t.verdict } : {} } : {}
2907
+ }));
2908
+ function blockedBy(task, reading) {
2909
+ if (!reading || !NOT_STARTED.has(task.status)) return [];
2910
+ return reading.blocks.blocked.get(task.id) ?? [];
2911
+ }
2912
+ function loopOf(task, reading) {
2913
+ const tasks = reading?.tasks;
2914
+ if (task.loop) {
2915
+ const check2 = { id: task.id, status: task.status, loop: task.loop, loopRound: task.loopRound, ...task.verdict ? { verdict: task.verdict } : {} };
2916
+ const state = loopCheckState(check2, reading ? reading.blocks.repeating.has(task.id) : true);
2917
+ const body = refsOf(task.loop.body, tasks);
2918
+ const halted = reading ? task.loop.body.filter((id) => {
2919
+ const status = reading.tasks.find((t) => t.id === id)?.status;
2920
+ return status === "failed" || status === "skipped" || reading.blocks.blocked.has(id);
2921
+ }) : [];
2922
+ const detail = state === "repeating" ? `Found problems: runs again once ${body.join(", ")} ${body.length === 1 ? "has" : "have"} landed` : state === "stopped" ? `${refsOf(halted, tasks).join(", ")} did not pass: retry ${halted.length === 1 ? "it" : "them"} and the loop goes on` : null;
2923
+ return { role: "check", state, label: loopCheckLabel(check2, state), detail, body };
2924
+ }
2925
+ const owner = reading && checkOf(reading.tasks, task.id);
2926
+ if (!owner?.loop) return null;
2927
+ const [check] = refsOf([owner.id], tasks);
2928
+ const round = loopBodyRound({ status: owner.status ?? "", loopRound: owner.loopRound }, task.status);
2929
+ return { role: "body", label: loopBodyLabel(check, round, owner.loop.maxRounds), check, round };
2930
+ }
2931
+ function handoffOf(task) {
2932
+ if (!task.handoff) return null;
2933
+ const patch = savedPatchView(task.handoff);
2934
+ return {
2935
+ ...task.handoff.commit ? { commit: task.handoff.commit } : {},
2936
+ summary: task.handoff.summary,
2937
+ findingCount: task.handoff.findingCount,
2938
+ severity: worstSeverity(task.handoff.findings),
2939
+ ...patch ? { patch } : {}
2940
+ };
2110
2941
  }
2111
2942
  function flagOf(task, scope) {
2112
2943
  const issue = scope && task.assignedRunner !== void 0 ? assignmentIssue({ ...task, assignedRunner: task.assignedRunner }, scope) : null;
@@ -2117,17 +2948,26 @@ function taskRowView(task, context = {}) {
2117
2948
  const isolation = task.isolation && task.isolation.state !== "none" ? task.isolation : null;
2118
2949
  const spend = taskUsageSummary(task);
2119
2950
  const latestAttempt = task.attemptUsage?.at(-1);
2120
- const blocked = blockedBy(task, context.tasks);
2951
+ const tasks = task.loop ? context.tasks?.map((t) => t.id === task.id ? { ...t, status: task.status, loopRound: task.loopRound, verdict: task.verdict } : t) : context.tasks;
2952
+ const reading = tasks?.length ? { tasks, blocks: planBlocks(asBlocking(tasks)) } : null;
2953
+ const blocked = blockedBy(task, reading);
2954
+ const loop = loopOf(task, reading);
2121
2955
  return {
2122
2956
  usage: latestAttempt ? { totals: spend.totals, ...spend.contextFill ? { contextFill: spend.contextFill } : {}, attempt: latestAttempt.attempt } : task.type === "ai" && ["completed", "failed", "in_progress", "awaiting_user"].includes(task.status) ? { totals: {}, attempt: 0 } : null,
2123
2957
  orderLabel: taskOrderLabel(task, parent),
2124
- kind: task.type === "user" ? "user" : task.ops ? "ops" : "ai",
2125
- status: blocked.length > 0 ? "blocked" : taskStatusKind(task),
2958
+ kind: task.type === "user" ? "user" : task.type === "script" ? "script" : task.ops ? "ops" : "ai",
2959
+ // Between rounds a check waits for its fix as a task waits for its dependencies, and is no failure (ADR-0045 L3).
2960
+ status: blocked.length > 0 ? "blocked" : loop?.role === "check" && loop.state === "repeating" ? "todo" : taskStatusKind(task),
2126
2961
  running: isTaskRunning(task),
2962
+ chatAvailable: taskChatAvailable(task),
2127
2963
  awaiting: awaitingLabel(task),
2128
2964
  resumes: resumeLabel(task, context.now),
2129
2965
  approvals: approvalLabel(task.awaitingApproval),
2966
+ receipt: receiptLabel(task),
2130
2967
  waitsForApproval: task.waitsForApproval && NOT_STARTED.has(task.status) ? WAITS_FOR_APPROVAL_LABEL : null,
2968
+ holdReason: task.holdReason?.trim() || null,
2969
+ research: task.type === "ai" && task.research === true,
2970
+ runner: assignedRunnerOf(task) ?? null,
2131
2971
  ...flagOf(task, context.runnerScope),
2132
2972
  autonomous: task.type === "ai" && context.modes?.find((m) => m.id === task.taskMode)?.autonomous === true,
2133
2973
  repos: namedRepos(isolation?.repos),
@@ -2139,6 +2979,8 @@ function taskRowView(task, context = {}) {
2139
2979
  skipReason: task.status === "skipped" ? task.skipReason ?? null : null,
2140
2980
  opsChangedFiles: task.status === "awaiting_user" && task.awaitingReason === "files-changed",
2141
2981
  forcedPastGate: task.forcedPastGate?.length ? task.forcedPastGate : null,
2982
+ loop,
2983
+ handoff: handoffOf(task),
2142
2984
  dependencies: refsOf(task.dependencies, context.tasks),
2143
2985
  // A block read off the plan offers what a stored one does: Skip among them.
2144
2986
  actions: taskRowActions(blocked.length > 0 ? { ...task, status: "blocked" } : task, context.placement ?? (parent ? "subtask" : "task")),
@@ -2146,6 +2988,91 @@ function taskRowView(task, context = {}) {
2146
2988
  opsEditable: opsEditable(task)
2147
2989
  };
2148
2990
  }
2991
+ function loopRowParts(loop) {
2992
+ if (loop.role === "body") return [loop.label];
2993
+ const runs = loop.state === "waiting" || loop.state === "running" ? `Runs ${loop.body.join(", ")} if it fails` : null;
2994
+ return [loop.label, loop.detail ?? runs].filter((part) => part !== null);
2995
+ }
2996
+
2997
+ // src/taskRow/logHeader.ts
2998
+ var STATE_LABEL = {
2999
+ done: "Done",
3000
+ failed: "Failed",
3001
+ running: "Running",
3002
+ quiet: "Stalled",
3003
+ awaiting: "Waiting for your input",
3004
+ blocked: "Blocked",
3005
+ skipped: "Skipped",
3006
+ todo: "To do"
3007
+ };
3008
+ function stateOf(task, follow, approvals) {
3009
+ if (task.status === "completed") return { kind: "done", label: STATE_LABEL.done };
3010
+ if (task.status === "failed") return { kind: "failed", label: STATE_LABEL.failed };
3011
+ if (task.status === "skipped") return { kind: "skipped", label: skippedLabel(task.skipReason ?? null) };
3012
+ const approval = approvalLabel(approvals);
3013
+ if (approval) return { kind: "approval", label: approval };
3014
+ if (follow.view.working) return { kind: "running", label: "Working" };
3015
+ if (task.status === "awaiting_user") {
3016
+ return { kind: task.awaitingReason === "checkpoint" ? "checkpoint" : "awaiting", label: awaitingLabel(task) ?? STATE_LABEL.awaiting };
3017
+ }
3018
+ if (task.status === "blocked") return { kind: "blocked", label: STATE_LABEL.blocked };
3019
+ if (follow.view.lastTurnEnd === "failed") return { kind: "failed", label: "Turn failed" };
3020
+ if (follow.view.lastTurnEnd === "interrupted") return { kind: "todo", label: "Interrupted" };
3021
+ const kind = taskStatusKind(task);
3022
+ return { kind, label: STATE_LABEL[kind] };
3023
+ }
3024
+ function loggedUsage(view) {
3025
+ const last = view.blocks.at(-1);
3026
+ if (last?.type !== "usage") return null;
3027
+ const text2 = usageText(last);
3028
+ return text2 ? { text: text2, ...last.contextFill ? { contextFill: last.contextFill } : {} } : null;
3029
+ }
3030
+ function usageRow(text2, contextFill) {
3031
+ return text2 ? { text: text2, ...contextFill ? { contextFill } : {} } : null;
3032
+ }
3033
+ function usageOf(task, follow, attempt) {
3034
+ const attempts = task.attemptUsage ?? [];
3035
+ const latest = attempts.at(-1);
3036
+ const past = attempt > 0 && attempt !== latest?.attempt;
3037
+ const total = taskUsageSummary(task);
3038
+ const totalText = usageText(total);
3039
+ if (!past) {
3040
+ if (attempts.length > 0) return totalText ? [{ text: totalText, ...total.contextFill ? { contextFill: total.contextFill } : {} }] : [];
3041
+ const logged = loggedUsage(follow.view);
3042
+ return logged ? [logged] : [];
3043
+ }
3044
+ const onScreen = attempts.find((a) => a.attempt === attempt);
3045
+ const own = onScreen ? usageRow(usageText({ totals: onScreen.totals, contextFill: plannerContextFill(onScreen) }), plannerContextFill(onScreen)) : loggedUsage(follow.view);
3046
+ const rows = [];
3047
+ if (own) rows.push(onScreen ? { ...own, text: `attempt ${onScreen.attempt}: ${own.text}` } : own);
3048
+ if (totalText) rows.push({ text: `task total: ${totalText}`, ...total.contextFill ? { contextFill: total.contextFill } : {} });
3049
+ return rows;
3050
+ }
3051
+ function taskLogCommands(view) {
3052
+ for (let i = view.blocks.length - 1; i >= 0; i--) {
3053
+ const block = view.blocks[i];
3054
+ if (block.type === "runner_loaded") return block.commands;
3055
+ }
3056
+ return [];
3057
+ }
3058
+ function logContinues(task) {
3059
+ const finished = task.status === "completed" || task.status === "failed" || task.status === "skipped";
3060
+ return task.continuable === true && (finished || task.status === "awaiting_user" && task.awaitingReason === "usage-limit");
3061
+ }
3062
+ function logResumes(task) {
3063
+ return task.continuable === true && task.status === "awaiting_user" && task.awaitingReason === "usage-limit";
3064
+ }
3065
+ function taskLogHeaderView(task, follow, approvals, attempt) {
3066
+ return {
3067
+ state: stateOf(task, follow, approvals),
3068
+ resumes: resumeLabel(task),
3069
+ continuable: logContinues(task),
3070
+ resumable: logResumes(task),
3071
+ working: follow.view.working,
3072
+ usage: usageOf(task, follow, attempt),
3073
+ commands: taskLogCommands(follow.view)
3074
+ };
3075
+ }
2149
3076
 
2150
3077
  // src/taskRow/texts.ts
2151
3078
  function titledRefs(ids, tasks) {
@@ -2156,17 +3083,82 @@ function dependentsNotice(dependents, tasks = []) {
2156
3083
  const subject = dependents.length === 1 ? "1 task depends" : `${dependents.length} tasks depend`;
2157
3084
  return `${subject} on it and will lose that dependency: ${dependents.map((t) => (t.id ? titledTaskRefOf(tasks, t.id) : void 0) ?? titledTaskRef(t)).join(", ")}.`;
2158
3085
  }
3086
+ function loopLeaveNotice(tasks, taskId) {
3087
+ const check = checkOf(tasks, taskId);
3088
+ if (!check?.loop) return null;
3089
+ const [named] = titledRefs([check.id], tasks);
3090
+ return check.loop.body.length > 1 ? `It is in the body of ${named}'s loop, and leaves it.` : `It is the whole body of ${named}'s loop, so the loop is dropped with it.`;
3091
+ }
2159
3092
  function pastGateConfirmation(unmerged, subject = "This task") {
2160
3093
  return `${subject} waits for Merge all: the work of ${unmerged.join(", ")} is not merged into your branch yet, so it would act without it. Starting it now is kept on the task.`;
2161
3094
  }
3095
+ function manualOverrideConfirmation(direction, label) {
3096
+ return direction === "complete" ? `Mark ${label} complete? You vouch for its work although no passing verdict said it was done. Any changes it kept may land in your run, and the tasks that depend on it are released to start on a future run.` : `Mark ${label} not done? This clears its completion and holds it until you retry or start it. The work it did stays where it is, and tasks that already ran on its result are not undone.`;
3097
+ }
3098
+ function manualOverrideStopFirst(direction, label) {
3099
+ return `${label.charAt(0).toUpperCase()}${label.slice(1)} cannot be marked ${direction === "complete" ? "complete" : "not done"} while the plan is starting, running or waiting to resume, because the run could still move it on. Stop the run first, and mark it once everything has stopped.`;
3100
+ }
2162
3101
  function newTaskFields(title, prompt) {
2163
3102
  const name = title.trim();
2164
3103
  if (!name) return null;
2165
3104
  return { title: name, description: name, prompt: prompt?.trim() ? prompt : name, type: "ai" };
2166
3105
  }
3106
+ function noExecutorRefusal(type, manual, script) {
3107
+ return type === "script" ? `A script task runs no model, so ${script}` : `Manual tasks do not run an executor, so ${manual}`;
3108
+ }
2167
3109
 
2168
3110
  // src/services/runView.ts
2169
- var EMPTY_RUN = { tasks: [], runMerge: null, handoff: null, mergeResult: null };
3111
+ var EMPTY_RUN = { tasks: [], runMerge: null, handoff: null, mergeResult: null, activity: IDLE_ACTIVITY };
3112
+
3113
+ // src/models/AccountLimits.ts
3114
+ function hasAccountLimits(snapshot) {
3115
+ return (snapshot.status === "fresh" || snapshot.status === "stale") && snapshot.buckets.some((bucket) => bucket.windows.some((window) => window.usedPercent !== null));
3116
+ }
3117
+ function accountLimitsOf(snapshots, runner, model) {
3118
+ if (!runner) return [];
3119
+ return snapshots.filter((snapshot) => snapshot.harness === runner && (snapshot.provider === void 0 || (model?.startsWith(`${snapshot.provider}/`) ?? false)));
3120
+ }
3121
+ var WEEK = 7 * 24 * 60;
3122
+ var DAY = 24 * 60;
3123
+ var SLOT_LABELS = { primary: "Primary", secondary: "Secondary", tertiary: "Tertiary" };
3124
+ function accountLimitWindowLabel(window) {
3125
+ if (window.period === "month") return "Monthly";
3126
+ const minutes = window.durationMinutes;
3127
+ if (minutes === null) return SLOT_LABELS[window.slot];
3128
+ if (minutes === WEEK) return "Weekly";
3129
+ if (minutes === DAY) return "Daily";
3130
+ if (minutes % WEEK === 0) return `${minutes / WEEK}w`;
3131
+ if (minutes % DAY === 0) return `${minutes / DAY}d`;
3132
+ if (minutes % 60 === 0) return `${minutes / 60}h`;
3133
+ return `${minutes}m`;
3134
+ }
3135
+ function accountLimitPercentText(percent) {
3136
+ const shown = percent > 0 && percent < 1 ? "<1" : String(Math.round(percent));
3137
+ return `${shown}% used`;
3138
+ }
3139
+ function accountLimitsText(snapshot) {
3140
+ if (!hasAccountLimits(snapshot)) return null;
3141
+ const parts = snapshot.buckets.flatMap((bucket, index) => {
3142
+ const windows = bucket.windows.flatMap((window) => window.usedPercent === null ? [] : [`${accountLimitWindowLabel(window)} ${accountLimitPercentText(window.usedPercent)}`]);
3143
+ if (windows.length === 0) return [];
3144
+ return index === 0 ? windows : [`${bucket.name ?? bucket.id} ${windows.join(" \xB7 ")}`];
3145
+ });
3146
+ return `${snapshot.label} ${parts.join(" \xB7 ")}${snapshot.status === "stale" ? " (stale)" : ""}`;
3147
+ }
3148
+ function accountLimitsTitle(snapshot, time) {
3149
+ const lines2 = snapshot.buckets.flatMap((bucket, index) => bucket.windows.map((window) => {
3150
+ const owner = index === 0 ? snapshot.label : `${snapshot.label} ${bucket.name ?? bucket.id}`;
3151
+ const share = window.usedPercent === null ? "not reported" : `${window.usedPercent}% used`;
3152
+ const reset = window.resetsAt ? `, resets ${time(new Date(window.resetsAt))}` : "";
3153
+ return `${owner} ${accountLimitWindowLabel(window)}: ${share}${reset}`;
3154
+ }));
3155
+ if (snapshot.plan) lines2.push(`Plan: ${snapshot.plan}`);
3156
+ if (snapshot.readAt) {
3157
+ const read = time(new Date(snapshot.readAt));
3158
+ lines2.push(snapshot.status === "stale" ? `Last read ${read}; not current, a newer read is pending or failed` : `Read ${read}`);
3159
+ }
3160
+ return lines2.join("\n");
3161
+ }
2170
3162
  // Annotate the CommonJS export names for ESM import in node:
2171
3163
  0 && (module.exports = {
2172
3164
  CHECKPOINT_TRUNCATE_LENGTH,
@@ -2176,27 +3168,47 @@ var EMPTY_RUN = { tasks: [], runMerge: null, handoff: null, mergeResult: null };
2176
3168
  EMPTY_RUN,
2177
3169
  EMPTY_TASK_LOG,
2178
3170
  EMPTY_TASK_LOG_FOLLOW,
3171
+ IDLE_ACTIVITY,
2179
3172
  NOT_STARTED,
2180
3173
  NO_TURN,
3174
+ PLANNED_WITH_LABEL,
3175
+ REPORT_PREVIEW_LINES,
3176
+ SEVERITY_LABEL,
2181
3177
  SUPERVISOR_LEVEL_LABEL,
2182
3178
  WAITS_FOR_APPROVAL_LABEL,
3179
+ accountLimitPercentText,
3180
+ accountLimitWindowLabel,
3181
+ accountLimitsOf,
3182
+ accountLimitsText,
3183
+ accountLimitsTitle,
2183
3184
  addAttemptUsage,
2184
3185
  addModelUsage,
2185
3186
  addPlannerUsage,
2186
3187
  addUsage,
2187
3188
  addUsageTotals,
2188
3189
  aheadOfDraft,
3190
+ approvalKindText,
2189
3191
  approvalLabel,
3192
+ approvalStatusText,
3193
+ assignedRunnerOf,
2190
3194
  assignmentIssue,
2191
3195
  assignmentIssueText,
2192
3196
  awaitingLabel,
3197
+ betweenRounds,
3198
+ blockNotice,
3199
+ blockNotices,
2193
3200
  blockedByLabel,
2194
3201
  blockingFailures,
2195
3202
  canMergeTasks,
2196
3203
  canSetDependencies,
2197
3204
  canSplitTask,
2198
3205
  capConflictFiles,
3206
+ cardEditNeedsUnstarted,
3207
+ cardEditable,
3208
+ checkOf,
2199
3209
  clampThinkingEffort,
3210
+ compactionText,
3211
+ confirmedOverride,
2200
3212
  contextMeterText,
2201
3213
  contextMeterTitle,
2202
3214
  costText,
@@ -2204,47 +3216,89 @@ var EMPTY_RUN = { tasks: [], runMerge: null, handoff: null, mergeResult: null };
2204
3216
  dependencyCandidates,
2205
3217
  dependentsNotice,
2206
3218
  dependentsOf,
3219
+ describeIsolationPreview,
3220
+ describeMergeResult,
2207
3221
  diffRows,
2208
3222
  diffStat,
2209
3223
  diffSummary,
2210
3224
  drainNext,
3225
+ editableWhileRunning,
2211
3226
  endAttemptUsage,
2212
3227
  followTaskLog,
2213
3228
  followTurn,
2214
3229
  formatCost,
2215
3230
  formatTokenCount,
2216
3231
  fromTranscript,
3232
+ handoffBaseLine,
3233
+ handoffBranchName,
3234
+ handoffFindingsText,
3235
+ handoffPeekParts,
3236
+ handoffReferenceText,
3237
+ hasAccountLimits,
2217
3238
  hasHiddenDetail,
3239
+ hasLongReport,
3240
+ holdLabel,
2218
3241
  holdPrompt,
3242
+ isFileEditTool,
2219
3243
  isMeasured,
3244
+ isRepoGroup,
2220
3245
  isTaskRunning,
2221
3246
  loadedSkillTokens,
3247
+ logContinues,
3248
+ logResumes,
3249
+ loopBodyLabel,
3250
+ loopBodyRound,
3251
+ loopCheckLabel,
3252
+ loopCheckState,
3253
+ loopLeaveNotice,
3254
+ loopRemovalRefusal,
3255
+ loopRoundOf,
3256
+ loopRowParts,
3257
+ manualOverrideConfirmation,
3258
+ manualOverrideStopFirst,
2222
3259
  markAction,
2223
- markRequestFor,
2224
3260
  markdownHtml,
2225
3261
  markdownTokens,
3262
+ mergeOutcome,
2226
3263
  modelCostTitle,
2227
3264
  namedRepos,
2228
3265
  newTaskFields,
3266
+ noExecutorRefusal,
2229
3267
  opsEditable,
3268
+ outOfRounds,
2230
3269
  outputLines,
2231
3270
  outputPreview,
3271
+ overrideNeedsConfirmation,
2232
3272
  parseCount,
2233
3273
  partedPromptUsage,
2234
3274
  pastGateConfirmation,
3275
+ patchView,
2235
3276
  pausedOnLimit,
3277
+ planBlocks,
3278
+ planMarkerParts,
2236
3279
  planUsage,
2237
3280
  plannerContextFill,
3281
+ receiptLabel,
2238
3282
  reconcileApprovals,
2239
3283
  reduceConversation,
2240
3284
  reduceTaskLog,
3285
+ repairedLanded,
3286
+ repairedNotice,
3287
+ repeatingChecks,
2241
3288
  replayTaskLog,
3289
+ repoBaseLine,
3290
+ repoResultLines,
2242
3291
  resolveTaskRef,
2243
3292
  resolveUserTaskRef,
2244
3293
  resumeLabel,
2245
3294
  resumeTime,
2246
3295
  resumedSessionWindow,
3296
+ runHoldsEditors,
2247
3297
  runnerToolSubject,
3298
+ savedPatchView,
3299
+ savedPatchViews,
3300
+ scriptTaskText,
3301
+ severityLabel,
2248
3302
  skillTokens,
2249
3303
  skippedLabel,
2250
3304
  startAttemptUsage,
@@ -2253,6 +3307,10 @@ var EMPTY_RUN = { tasks: [], runMerge: null, handoff: null, mergeResult: null };
2253
3307
  subtreeIds,
2254
3308
  summarizeToolCall,
2255
3309
  takeAt,
3310
+ taskActionCommand,
3311
+ taskChatAvailable,
3312
+ taskLogCommands,
3313
+ taskLogHeaderView,
2256
3314
  taskOrderLabel,
2257
3315
  taskPlacement,
2258
3316
  taskRef,
@@ -2272,6 +3330,8 @@ var EMPTY_RUN = { tasks: [], runMerge: null, handoff: null, mergeResult: null };
2272
3330
  unsendLatest,
2273
3331
  usageLine,
2274
3332
  usageText,
2275
- walkPlan
3333
+ wakeUpItems,
3334
+ walkPlan,
3335
+ worstSeverity
2276
3336
  });
2277
3337
  //# sourceMappingURL=plan-utils.js.map