@ordewell/cli 0.11.2 → 0.12.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.
package/dist/main.js CHANGED
@@ -25,7 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
 
26
26
  // src/main.ts
27
27
  var import_path8 = require("path");
28
- var import_core56 = require("@ordewell/core");
28
+ var import_core60 = require("@ordewell/core");
29
29
 
30
30
  // src/utils/env.ts
31
31
  var import_fs = require("fs");
@@ -108,6 +108,9 @@ function moveAutonomyToSettings() {
108
108
  }
109
109
  }
110
110
 
111
+ // src/help.ts
112
+ var import_core3 = require("@ordewell/core");
113
+
111
114
  // src/runnerNames.ts
112
115
  var import_core2 = require("@ordewell/core");
113
116
  function manifests() {
@@ -156,6 +159,7 @@ Tasks (<id> is an order number or a task ID):
156
159
  ordewell remove-task <id> Remove a task from the current plan
157
160
  ordewell complete <id> Mark a task complete (alias: mark-complete; asks first, --yes skips the question)
158
161
  ordewell uncomplete <id> Mark a completed task not done (asks first, --yes skips the question)
162
+ ordewell revert <id> Take a landed task's work back off the integration branch (lists what goes with it, asks first, --yes skips the question)
159
163
  ordewell skip <id> Skip a task: nothing it did lands, dependents go ahead
160
164
  ordewell force-start <id> Approve a task and start it now, or once its dependencies finish (asks past a merge gate)
161
165
  ordewell run-task <id> Run only one task
@@ -187,7 +191,7 @@ Planner, models and runners:
187
191
  ordewell automerge [on|off] (headless) Merge at merge gates and at the end of a run; /settings covers it
188
192
  ordewell trailer [on|off] (headless) End Ordewell's commits with "Planned-with: Ordewell"; set only here or in settings.json
189
193
  ordewell autorun [on|off] (headless) Start each plan the planner submits, without review; /settings covers it
190
- ordewell parallel [<n>] (headless) How many AI tasks run at once, a live run included; /settings covers it
194
+ ${import_core3.DELEGATION_OFFERED ? " ordewell delegation [off|<tokens>] (headless) Whether a plan may delegate, and a tree's token budget; /settings covers it\n" : ""} ordewell parallel [<n>] (headless) How many AI tasks run at once, a live run included; /settings covers it
191
195
  ordewell refresh Re-check runners; reread settings and cached models
192
196
  ordewell models List every provider's catalog (works without a server)
193
197
 
@@ -296,6 +300,9 @@ Environment:
296
300
  ORDEWELL_EFFECT_ALLOW Script tasks the planner may run unasked, comma-separated rules matched
297
301
  against each whole command, flags included: "git push origin feature/*".
298
302
  Every command must be covered; none is by default
303
+ ORDEWELL_NOTIFY_COMMAND A command to run when something new waits on you, as a JSON array:
304
+ '["program", "arg"]'. No shell; the escalation is in
305
+ ORDEWELL_ESCALATION_ID, _KIND, _TASK and _REASON. A workspace cannot set it
299
306
  ORDEWELL_PORT Daemon port CLI commands target (default: 3742)
300
307
  ORDEWELL_DIRENV false: agents skip the project's direnv .envrc
301
308
  ORDEWELL_RESEARCH_ENABLED true (default) or false
@@ -310,7 +317,7 @@ var import_os = require("os");
310
317
  var import_path2 = require("path");
311
318
  var import_http = __toESM(require("http"));
312
319
  var import_net = require("net");
313
- var import_core3 = require("@ordewell/core");
320
+ var import_core4 = require("@ordewell/core");
314
321
 
315
322
  // src/utils/args.ts
316
323
  function inlineValue(arg, name) {
@@ -374,7 +381,7 @@ function positionals(args) {
374
381
  // src/daemon.ts
375
382
  var DEFAULT_PORT = 3742;
376
383
  function configDir() {
377
- return (0, import_core3.globalDataDir)();
384
+ return (0, import_core4.globalDataDir)();
378
385
  }
379
386
  function oldConfigDir() {
380
387
  return (0, import_path2.join)((0, import_os.homedir)(), ".config", "ordewell");
@@ -396,12 +403,12 @@ function readPidFile(file) {
396
403
  }
397
404
  }
398
405
  async function daemonInfo(port) {
399
- const token = (0, import_core3.readDaemonToken)(port);
406
+ const token = (0, import_core4.readDaemonToken)(port);
400
407
  try {
401
408
  const res = await httpGet(
402
409
  `http://127.0.0.1:${port}/api/server`,
403
410
  2e3,
404
- token ? { Authorization: (0, import_core3.bearerHeaderValue)(token) } : {}
411
+ token ? { Authorization: (0, import_core4.bearerHeaderValue)(token) } : {}
405
412
  );
406
413
  if (res.status !== 200) return { kind: "unverifiable" };
407
414
  const parsed = JSON.parse(res.data);
@@ -443,12 +450,12 @@ function getWebServerPath() {
443
450
  );
444
451
  }
445
452
  async function isDaemonRunning(port = DEFAULT_PORT) {
446
- const token = (0, import_core3.readDaemonToken)(port);
453
+ const token = (0, import_core4.readDaemonToken)(port);
447
454
  try {
448
455
  const res = await httpGet(
449
456
  `http://127.0.0.1:${port}/api/workspaces`,
450
457
  2e3,
451
- token ? { Authorization: (0, import_core3.bearerHeaderValue)(token) } : {}
458
+ token ? { Authorization: (0, import_core4.bearerHeaderValue)(token) } : {}
452
459
  );
453
460
  return res.status === 200 || res.status === 401;
454
461
  } catch {
@@ -514,7 +521,7 @@ async function stopDaemon(port = DEFAULT_PORT) {
514
521
  (0, import_fs2.unlinkSync)(pidFile);
515
522
  } catch {
516
523
  }
517
- (0, import_core3.clearDaemonToken)(port);
524
+ (0, import_core4.clearDaemonToken)(port);
518
525
  console.error(`Removed an unreadable PID file for port ${port}. Is the server running?`);
519
526
  return false;
520
527
  }
@@ -530,7 +537,7 @@ async function stopDaemon(port = DEFAULT_PORT) {
530
537
  (0, import_fs2.unlinkSync)(pidFile);
531
538
  } catch {
532
539
  }
533
- (0, import_core3.clearDaemonToken)(port);
540
+ (0, import_core4.clearDaemonToken)(port);
534
541
  console.log("Server was not running (stale PID removed).");
535
542
  return true;
536
543
  }
@@ -597,7 +604,7 @@ function resolvePort(args = []) {
597
604
  // src/apiClient.ts
598
605
  var import_http2 = __toESM(require("http"));
599
606
  var import_ws = __toESM(require("ws"));
600
- var import_core4 = require("@ordewell/core");
607
+ var import_core5 = require("@ordewell/core");
601
608
  var DEFAULT_HTTP_TIMEOUT_MS = 15 * 60 * 1e3;
602
609
  var daemonAgent = new import_http2.default.Agent({ keepAlive: true, timeout: 0 });
603
610
  var DaemonError = class extends Error {
@@ -675,7 +682,7 @@ var ApiClient = class _ApiClient {
675
682
  * A missing file is not an error here — the daemon's own 401 names the path.
676
683
  */
677
684
  token() {
678
- return (0, import_core4.readDaemonToken)(this.port);
685
+ return (0, import_core5.readDaemonToken)(this.port);
679
686
  }
680
687
  /**
681
688
  * A daemon holds only the sessions adopted since it started, so after a
@@ -727,7 +734,7 @@ var ApiClient = class _ApiClient {
727
734
  agent: daemonAgent,
728
735
  headers: {
729
736
  ...body ? { "Content-Type": "application/json" } : {},
730
- ...token ? { Authorization: (0, import_core4.bearerHeaderValue)(token) } : {}
737
+ ...token ? { Authorization: (0, import_core5.bearerHeaderValue)(token) } : {}
731
738
  },
732
739
  ...idleBoundMs !== void 0 ? { timeout: idleBoundMs } : {}
733
740
  };
@@ -754,7 +761,7 @@ var ApiClient = class _ApiClient {
754
761
  req.end();
755
762
  });
756
763
  }
757
- async generatePlan(goal, runners2, workspace, sessionId = (0, import_core4.mintSessionId)()) {
764
+ async generatePlan(goal, runners2, workspace, sessionId = (0, import_core5.mintSessionId)()) {
758
765
  const route = `/api/plans/${sessionId}/generate`;
759
766
  const { plan, models, modelsByRunner, notes } = planOf(await this.turn("POST", route, "Plan generation failed", {
760
767
  goal,
@@ -820,6 +827,15 @@ var ApiClient = class _ApiClient {
820
827
  execute(sessionId, command) {
821
828
  return this.call("POST", `/api/plans/${sessionId}/execute-command`, "Command failed", command);
822
829
  }
830
+ /** What waits on a person now (ADR-0053). */
831
+ async escalations(sessionId) {
832
+ const res = await this.call("GET", `/api/plans/${sessionId}/escalations`, "Could not read what waits on you");
833
+ return res.escalations;
834
+ }
835
+ /** Answer an escalation by its id; a stale one is a {@link DaemonError} saying so. `confirmed` is the user's yes to the option's `confirm`. */
836
+ answerEscalation(sessionId, answer) {
837
+ return this.call("POST", `/api/plans/${sessionId}/escalations/answer`, "Could not answer", answer);
838
+ }
823
839
  /** Keep what waits on the planner's usage limit from being sent at the reset; `cancelled` is false when nothing waited. */
824
840
  cancelPlannerResend(sessionId) {
825
841
  return this.call("POST", `/api/plans/${sessionId}/converse/resend/cancel`, "Cancel resend failed");
@@ -902,6 +918,10 @@ var ApiClient = class _ApiClient {
902
918
  const res = await this.call("GET", `/api/plans/${sessionId}/tasks/${encodeURIComponent(taskId)}/merge-gate`, "Could not read the merge gate");
903
919
  return res.mergeGate ?? [];
904
920
  }
921
+ /** What reverting a task would do now (ADR-0054), which the user confirms before `execute` sends `revert_task`. */
922
+ revertPreview(sessionId, taskId) {
923
+ return this.call("GET", `/api/plans/${sessionId}/tasks/${encodeURIComponent(taskId)}/revert-preview`, "Could not read what the revert would take");
924
+ }
905
925
  async reviewRunDiff(sessionId) {
906
926
  const res = await this.call("GET", `/api/plans/${sessionId}/isolation/diff`, "Could not read the diff");
907
927
  return res.diff;
@@ -980,7 +1000,7 @@ var ApiClient = class _ApiClient {
980
1000
  const token = this.token();
981
1001
  const socket = new import_ws.default(
982
1002
  `ws://127.0.0.1:${this.port}/ws/session/${sessionId}`,
983
- token ? (0, import_core4.tokenSubprotocols)(token) : void 0
1003
+ token ? (0, import_core5.tokenSubprotocols)(token) : void 0
984
1004
  );
985
1005
  socket.on("unexpected-response", (_req, res) => {
986
1006
  const chunks = [];
@@ -1160,26 +1180,26 @@ var ApiClient = class _ApiClient {
1160
1180
  var import_readline2 = require("readline");
1161
1181
 
1162
1182
  // src/utils/tasks.ts
1163
- var import_core5 = require("@ordewell/core");
1183
+ var import_core6 = require("@ordewell/core");
1164
1184
  function allTasksOf(plan) {
1165
1185
  const tasks = plan?.pendingTasks || plan?.tasks || [];
1166
1186
  const executionLog = plan?.executionLog || [];
1167
- return (0, import_core5.rejoinPlanTree)(tasks, executionLog);
1187
+ return (0, import_core6.rejoinPlanTree)(tasks, executionLog);
1168
1188
  }
1169
1189
  function resolveTaskId(plan, identifier) {
1170
- return (0, import_core5.resolveUserTaskRef)(allTasksOf(plan), identifier)?.id;
1190
+ return (0, import_core6.resolveUserTaskRef)(allTasksOf(plan), identifier)?.id;
1171
1191
  }
1172
1192
 
1173
1193
  // src/utils/session.ts
1174
1194
  var import_fs3 = require("fs");
1175
1195
  var import_path3 = require("path");
1176
- var import_core6 = require("@ordewell/core");
1196
+ var import_core7 = require("@ordewell/core");
1177
1197
  var LAST_SESSION_FILENAME = "last-session.json";
1178
1198
  function lastSessionFile(workspace) {
1179
- return (0, import_path3.join)((0, import_core6.getStateDir)(workspace), LAST_SESSION_FILENAME);
1199
+ return (0, import_path3.join)((0, import_core7.getStateDir)(workspace), LAST_SESSION_FILENAME);
1180
1200
  }
1181
1201
  function saveLastSession(sessionId, goal, runners2, workspace) {
1182
- const dir = (0, import_core6.getStateDir)(workspace);
1202
+ const dir = (0, import_core7.getStateDir)(workspace);
1183
1203
  if (!(0, import_fs3.existsSync)(dir)) (0, import_fs3.mkdirSync)(dir, { recursive: true });
1184
1204
  (0, import_fs3.writeFileSync)(
1185
1205
  lastSessionFile(workspace),
@@ -1201,14 +1221,14 @@ function expandSessionId(id, workspace) {
1201
1221
  if (id.startsWith(SESSION_PREFIX)) return id;
1202
1222
  let files;
1203
1223
  try {
1204
- files = (0, import_fs3.readdirSync)((0, import_path3.join)((0, import_core6.getStateDir)(workspace), "sessions")).filter((f) => f.endsWith(".json"));
1224
+ files = (0, import_fs3.readdirSync)((0, import_path3.join)((0, import_core7.getStateDir)(workspace), "sessions")).filter((f) => f.endsWith(".json"));
1205
1225
  } catch {
1206
1226
  return id;
1207
1227
  }
1208
1228
  const matches = /* @__PURE__ */ new Set();
1209
1229
  for (const file of files) {
1210
1230
  try {
1211
- const full = JSON.parse((0, import_fs3.readFileSync)((0, import_path3.join)((0, import_core6.getStateDir)(workspace), "sessions", file), "utf8"))?.meta?.id;
1231
+ const full = JSON.parse((0, import_fs3.readFileSync)((0, import_path3.join)((0, import_core7.getStateDir)(workspace), "sessions", file), "utf8"))?.meta?.id;
1212
1232
  if (typeof full === "string" && (full.endsWith(id) || full.slice(SESSION_PREFIX.length).startsWith(id))) matches.add(full);
1213
1233
  } catch {
1214
1234
  }
@@ -1283,7 +1303,7 @@ ${describeApproval(event)}
1283
1303
  }
1284
1304
 
1285
1305
  // src/commands/researchLog.ts
1286
- var import_core7 = require("@ordewell/core");
1306
+ var import_core8 = require("@ordewell/core");
1287
1307
  var OUTCOME_MARK = {
1288
1308
  success: "\u2713",
1289
1309
  failure: "\u2717",
@@ -1295,7 +1315,7 @@ var RESULT_CHARS = 160;
1295
1315
  var VERBOSE_RESULT_CHARS = 600;
1296
1316
  var THINKING_CHARS = 300;
1297
1317
  function summarize(tool, args, toolLabel) {
1298
- const { name, keyArg } = (0, import_core7.toolHeadline)(tool, args, toolLabel);
1318
+ const { name, keyArg } = (0, import_core8.toolHeadline)(tool, args, toolLabel);
1299
1319
  return `${name}(${keyArg})`;
1300
1320
  }
1301
1321
  function oneLine(text, max) {
@@ -1311,9 +1331,9 @@ function formatStepLine(event, options = {}) {
1311
1331
  const step2 = event.step;
1312
1332
  if (!step2) return null;
1313
1333
  const mark = OUTCOME_MARK[step2.outcome] ?? "\u2713";
1314
- const sealed = step2.tool === "bash" && (0, import_core7.ranSealed)(step2.result ?? "");
1334
+ const sealed = step2.tool === "bash" && (0, import_core8.ranSealed)(step2.result ?? "");
1315
1335
  const summary = `${summarize(step2.tool, step2.args, step2.toolLabel)}${sealed ? " [sealed]" : ""}`;
1316
- const result = oneLine((0, import_core7.withoutSealedMark)(step2.result ?? ""), options.verbose ? VERBOSE_RESULT_CHARS : RESULT_CHARS);
1336
+ const result = oneLine((0, import_core8.withoutSealedMark)(step2.result ?? ""), options.verbose ? VERBOSE_RESULT_CHARS : RESULT_CHARS);
1317
1337
  const indent = event.subagentId ? " \u21B3 " : "";
1318
1338
  return result ? `${indent}${mark} ${summary} \u2192 ${result}` : `${indent}${mark} ${summary}`;
1319
1339
  }
@@ -1347,7 +1367,7 @@ function createStepLog(options = {}) {
1347
1367
  }
1348
1368
 
1349
1369
  // src/commands/plan.ts
1350
- var import_core9 = require("@ordewell/core");
1370
+ var import_core10 = require("@ordewell/core");
1351
1371
 
1352
1372
  // src/commands/shared.ts
1353
1373
  var import_readline = require("readline");
@@ -1398,7 +1418,7 @@ function normalizeCatalog(result) {
1398
1418
  }
1399
1419
 
1400
1420
  // src/commands/shared.ts
1401
- var import_core8 = require("@ordewell/core");
1421
+ var import_core9 = require("@ordewell/core");
1402
1422
  async function connect(subArgs, injected) {
1403
1423
  return injected ?? new ApiClient(await ensureDaemon(resolvePort(subArgs)));
1404
1424
  }
@@ -1416,7 +1436,7 @@ function headlessSignedOut(message) {
1416
1436
  );
1417
1437
  }
1418
1438
  function headlessPlannerRefusal(err) {
1419
- return err instanceof DaemonError && err.plannerNotReady ? (0, import_core8.describePlannerNotReady)(err.plannerNotReady, import_core8.HEADLESS_CLI_COMMANDS) : null;
1439
+ return err instanceof DaemonError && err.plannerNotReady ? (0, import_core9.describePlannerNotReady)(err.plannerNotReady, import_core9.HEADLESS_CLI_COMMANDS) : null;
1420
1440
  }
1421
1441
  function lostReplyHint(err, sessionId) {
1422
1442
  if (deliveryOf(err) !== "unknown") return null;
@@ -1564,16 +1584,16 @@ Plan: ${tasks.length} task${tasks.length === 1 ? "" : "s"} (${aiCount} AI${scrip
1564
1584
  for (const t of tasks) {
1565
1585
  const typeIcon = t.type === "user" ? "[MAN]" : t.type === "script" ? "[SCR]" : t.ops ? "[OPS]" : t.research ? "[RES]" : "[ AI]";
1566
1586
  const deps = t.dependencies?.length ? ` \u2190 ${t.dependencies.join(", ")}` : "";
1567
- console.log(` ${(0, import_core9.taskOrderLabel)(t).padStart(2)}. ${typeIcon} ${t.title}${modelSuffix(t)}${deps}`);
1587
+ console.log(` ${(0, import_core10.taskOrderLabel)(t).padStart(2)}. ${typeIcon} ${t.title}${modelSuffix(t)}${deps}`);
1568
1588
  if (t.type === "script") {
1569
- for (const line of (0, import_core9.scriptTaskText)({ ...t, type: "script" }).split("\n")) console.log(line ? ` ${line.replace(/\t/g, " ")}` : "");
1589
+ for (const line of (0, import_core10.scriptTaskText)({ ...t, type: "script" }).split("\n")) console.log(line ? ` ${line.replace(/\t/g, " ")}` : "");
1570
1590
  }
1571
- const loop = (0, import_core9.taskRowView)({ id: t.id, order: t.order, status: t.status ?? "pending", type: t.type === "user" || t.type === "script" ? t.type : "ai", dependencies: t.dependencies ?? [], loop: t.loop, loopRound: t.loopRound }, { tasks }).loop;
1572
- if (loop) console.log(` \u21BB ${(0, import_core9.loopRowParts)(loop).join(" \xB7 ")}`);
1591
+ const loop = (0, import_core10.taskRowView)({ id: t.id, order: t.order, status: t.status ?? "pending", type: t.type === "user" || t.type === "script" ? t.type : "ai", dependencies: t.dependencies ?? [], loop: t.loop, loopRound: t.loopRound }, { tasks }).loop;
1592
+ if (loop) console.log(` \u21BB ${(0, import_core10.loopRowParts)(loop).join(" \xB7 ")}`);
1573
1593
  for (const sub of t.subtasks || []) {
1574
1594
  const subTypeIcon = sub.type === "user" ? "[MAN]" : "[ AI]";
1575
1595
  const subDeps = sub.dependencies?.length ? ` \u2190 ${sub.dependencies.join(", ")}` : "";
1576
- console.log(` ${(0, import_core9.taskOrderLabel)(sub, t)}. ${subTypeIcon} ${sub.title}${modelSuffix(sub)}${subDeps}`);
1596
+ console.log(` ${(0, import_core10.taskOrderLabel)(sub, t)}. ${subTypeIcon} ${sub.title}${modelSuffix(sub)}${subDeps}`);
1577
1597
  }
1578
1598
  }
1579
1599
  if (manCount > 0) {
@@ -1637,7 +1657,7 @@ async function handlePlan(subArgs, deps = {}) {
1637
1657
  runners2 = runnerFlags;
1638
1658
  }
1639
1659
  console.error(`Generating plan for: "${goal}"...`);
1640
- const sessionId = (0, import_core9.mintSessionId)();
1660
+ const sessionId = (0, import_core10.mintSessionId)();
1641
1661
  const isTty = !!process.stderr.isTTY;
1642
1662
  let lastLineLength = 0;
1643
1663
  const stepLog = createStepLog({ verbose });
@@ -1750,9 +1770,9 @@ ${note}`);
1750
1770
  }
1751
1771
 
1752
1772
  // src/utils/output.ts
1753
- var import_core10 = require("@ordewell/core");
1773
+ var import_core11 = require("@ordewell/core");
1754
1774
  function iconFor(status) {
1755
- switch ((0, import_core10.taskStatusKind)({ status })) {
1775
+ switch ((0, import_core11.taskStatusKind)({ status })) {
1756
1776
  case "done":
1757
1777
  return "\x1B[32m\u2713\x1B[0m";
1758
1778
  case "running":
@@ -1772,12 +1792,12 @@ function iconFor(status) {
1772
1792
  }
1773
1793
 
1774
1794
  // src/commands/run.ts
1775
- var import_core12 = require("@ordewell/core");
1795
+ var import_core13 = require("@ordewell/core");
1776
1796
 
1777
1797
  // src/commands/taskHandoff.ts
1778
- var import_core11 = require("@ordewell/core");
1798
+ var import_core12 = require("@ordewell/core");
1779
1799
  function taskHandoffLines(handoff, indent) {
1780
- const preview = (0, import_core11.handoffPreviewLines)(handoff);
1800
+ const preview = (0, import_core12.handoffPreviewLines)(handoff);
1781
1801
  const [first = "", ...body] = preview;
1782
1802
  if (!handoff.summary.trim() || handoff.commit) return [`${indent}\u21B3 Handoff`, ...preview.map((line) => `${indent}${line}`)];
1783
1803
  return [`${indent}\u21B3 Handoff: ${first}`, ...body.map((line) => `${indent}${line}`)];
@@ -1790,12 +1810,12 @@ function loopNote(task, all) {
1790
1810
  if (task.loop) {
1791
1811
  const check2 = { id: task.id, status: task.status, loop: task.loop, loopRound: task.loopRound, ...task.verdict ? { verdict: task.verdict } : {} };
1792
1812
  const halted = task.loop.body.some((id) => ["failed", "skipped"].includes(all.find((t) => t.id === id)?.status ?? ""));
1793
- const state = (0, import_core12.loopCheckState)(check2, !halted);
1794
- return { text: (0, import_core12.loopCheckLabel)(check2, state), repeating: state === "repeating" };
1813
+ const state = (0, import_core13.loopCheckState)(check2, !halted && !task.loopStranded);
1814
+ return { text: (0, import_core13.loopCheckLabel)(check2, state), repeating: state === "repeating" };
1795
1815
  }
1796
1816
  const check = all.find((t) => t.loop?.body.includes(task.id));
1797
1817
  if (!check?.loop) return null;
1798
- return { text: (0, import_core12.loopBodyLabel)(refOf(check.id), (0, import_core12.loopBodyRound)({ status: check.status, loopRound: check.loopRound }, task.status), check.loop.maxRounds), repeating: false };
1818
+ return { text: (0, import_core13.loopBodyLabel)(refOf(check.id), (0, import_core13.loopBodyRound)({ status: check.status, loopRound: check.loopRound }, task.status), check.loop.maxRounds), repeating: false };
1799
1819
  }
1800
1820
  function resolveSessionId(subArgs) {
1801
1821
  const explicit = flag(subArgs, "--session-id");
@@ -1824,10 +1844,11 @@ async function followExecution(api, sessionId, start, onBlocked) {
1824
1844
  const loop = loopNote(t, entries);
1825
1845
  const icon = iconFor(loop?.repeating ? "pending" : t.status);
1826
1846
  const verdict = t.verdict ? `: ${t.verdict.outcome.toUpperCase()}: ${t.verdict.reason.slice(0, 60)}` : "";
1827
- const waits = t.waitsForApproval ? `: ${inLine(import_core12.WAITS_FOR_APPROVAL_LABEL)}: \`ordewell force-start <id>\` approves it, \`ordewell run\` approves all` : "";
1847
+ const waits = t.waitsForApproval ? `: ${inLine(import_core13.WAITS_FOR_APPROVAL_LABEL)}: \`ordewell force-start <id>\` approves it, \`ordewell run\` approves all` : "";
1828
1848
  lines.push(` ${icon} ${refOf(t.id)}${t.type === "script" ? " [SCR]" : t.research ? " [RES]" : ""} ${t.status}${verdict}${waits}${loop ? ` \xB7 \u21BB ${inLine(loop.text)}` : ""}`);
1829
- if (t.holdReason) lines.push(` ${(0, import_core12.holdLabel)(t.holdReason)}`);
1830
- if (t.status === "skipped") lines.push(` ${(0, import_core12.skippedLabel)(t.skipReason ?? null)}`);
1849
+ if (t.holdReason) lines.push(` ${(0, import_core13.holdLabel)(t.holdReason)}`);
1850
+ if (t.isolation?.state === "conflict" && t.isolation.repairWaits) lines.push(` ${(0, import_core13.repairWaitLabel)(t.isolation.repairWaits)}`);
1851
+ if (t.status === "skipped") lines.push(` ${(0, import_core13.skippedLabel)(t.skipReason ?? null)}`);
1831
1852
  if (t.handoff && (t.status === "completed" || t.status === "failed")) lines.push(...taskHandoffLines(t.handoff, " "));
1832
1853
  }
1833
1854
  const output = lines.join("\n");
@@ -1853,11 +1874,11 @@ async function followExecution(api, sessionId, start, onBlocked) {
1853
1874
  });
1854
1875
  const stream = api.streamExecution(sessionId, (event) => {
1855
1876
  if (event.type === "task_started") {
1856
- process.stderr.write(`\xB7 ${(0, import_core12.taskStartedNotice)(event.title, event.runner)}
1877
+ process.stderr.write(`\xB7 ${(0, import_core13.taskStartedNotice)(event.title, event.runner)}
1857
1878
  `);
1858
1879
  }
1859
1880
  if (event.type === "checkpoint") {
1860
- process.stderr.write(`\xB7 Checkpoint: ${event.taskTitle}: ${(0, import_core12.truncateCheckpointSummary)(event.summary)}: \`ordewell checkpoint <id> approve|reject [reason]\` answers it
1881
+ process.stderr.write(`\xB7 Checkpoint: ${event.taskTitle}: ${(0, import_core13.truncateCheckpointSummary)(event.summary)}: \`ordewell checkpoint <id> approve|reject [reason]\` answers it
1861
1882
  `);
1862
1883
  }
1863
1884
  if (event.type === "status_update" && event.tasks) {
@@ -1888,10 +1909,10 @@ Done. ${summary.completed || 0} completed, ${summary.failed || 0} failed, ${summ
1888
1909
  }
1889
1910
  if (event.type === "isolation_handoff") {
1890
1911
  const n = event.landed.length;
1891
- const where = (0, import_core12.isRepoGroup)(event) ? ` in ${event.repos.map((r) => r.path).join(", ")}` : "";
1912
+ const where = (0, import_core13.isRepoGroup)(event) ? ` in ${event.repos.map((r) => r.path).join(", ")}` : "";
1892
1913
  console.log(`
1893
- Run finished on ${(0, import_core12.handoffBranchName)(event)}${where}: ${n} task${n === 1 ? "" : "s"} landed.`);
1894
- if ((0, import_core12.isRepoGroup)(event)) for (const line of (0, import_core12.repoResultLines)(event)) console.log(` ${line}`);
1914
+ Run finished on ${(0, import_core13.handoffBranchName)(event)}${where}: ${n} task${n === 1 ? "" : "s"} landed.`);
1915
+ if ((0, import_core13.isRepoGroup)(event)) for (const line of (0, import_core13.repoResultLines)(event)) console.log(` ${line}`);
1895
1916
  if (event.deferredTo === "supervisor") console.log(" The supervisor may merge it.");
1896
1917
  console.log(" `ordewell handoff review|merge|discard|cleanup` to land it.");
1897
1918
  }
@@ -1925,7 +1946,7 @@ function printScriptBodies(plan) {
1925
1946
  for (const t of allTasksOf(plan)) {
1926
1947
  if (t.type !== "script" || t.status === "completed" || t.status === "skipped") continue;
1927
1948
  console.log(` Script ${refOf(t.id)} "${t.title}":`);
1928
- for (const line of (0, import_core12.scriptTaskText)(t).split("\n")) console.log(line ? ` ${line.replace(/\t/g, " ")}` : "");
1949
+ for (const line of (0, import_core13.scriptTaskText)(t).split("\n")) console.log(line ? ` ${line.replace(/\t/g, " ")}` : "");
1929
1950
  }
1930
1951
  }
1931
1952
  async function handleRun(subArgs, injectedApi) {
@@ -1954,7 +1975,7 @@ async function handleRun(subArgs, injectedApi) {
1954
1975
  }
1955
1976
 
1956
1977
  // src/tui/blocks.ts
1957
- var import_core14 = require("@ordewell/core");
1978
+ var import_core15 = require("@ordewell/core");
1958
1979
 
1959
1980
  // ../../node_modules/ansi-regex/index.js
1960
1981
  function ansiRegex({ onlyFirst = false } = {}) {
@@ -2367,9 +2388,9 @@ var style = {
2367
2388
  };
2368
2389
 
2369
2390
  // src/tui/markdown.ts
2370
- var import_core13 = require("@ordewell/core");
2391
+ var import_core14 = require("@ordewell/core");
2371
2392
  function renderMarkdown(source, cols) {
2372
- return blocks((0, import_core13.markdownTokens)(source), { room: Math.max(1, cols), base: [] });
2393
+ return blocks((0, import_core14.markdownTokens)(source), { room: Math.max(1, cols), base: [] });
2373
2394
  }
2374
2395
  function closeOf(tokens, open) {
2375
2396
  const { level } = tokens[open];
@@ -2629,7 +2650,7 @@ function messageLines(block2, cols) {
2629
2650
  }
2630
2651
  function reportUpdateLines(block2, cols, detailAll) {
2631
2652
  const shown = shownText(block2.text);
2632
- const items = (0, import_core14.wakeUpItems)(sanitize(shown.text));
2653
+ const items = (0, import_core15.wakeUpItems)(sanitize(shown.text));
2633
2654
  if (!items.some((item) => item.kind === "report")) return null;
2634
2655
  const rows = [style.yellow(`\u21AF Run update:`)];
2635
2656
  for (const item of items) {
@@ -2639,11 +2660,11 @@ function reportUpdateLines(block2, cols, detailAll) {
2639
2660
  }
2640
2661
  rows.push(...wrap(`- task ${item.task} ended, with this report:`, Math.max(1, cols - 2)).map((line) => style.yellow(` ${line}`)));
2641
2662
  const room = Math.max(1, cols - 4);
2642
- const shownLines = detailAll ? item.lines : item.lines.slice(0, import_core14.REPORT_PREVIEW_LINES);
2663
+ const shownLines = detailAll ? item.lines : item.lines.slice(0, import_core15.REPORT_PREVIEW_LINES);
2643
2664
  rows.push(...shownLines.flatMap((line) => wrap(line, room)).map((line) => ` ${line}`));
2644
2665
  const hidden = item.lines.length - shownLines.length;
2645
2666
  if (hidden > 0) rows.push(style.grey(` \u2026 +${countOf(hidden, "line")} of the report (ctrl+o to expand)`));
2646
- else if (detailAll && item.lines.length > import_core14.REPORT_PREVIEW_LINES) rows.push(style.grey(" \u2026 (ctrl+o to collapse)"));
2667
+ else if (detailAll && item.lines.length > import_core15.REPORT_PREVIEW_LINES) rows.push(style.grey(" \u2026 (ctrl+o to collapse)"));
2647
2668
  }
2648
2669
  if (shown.hiddenChars > 0) rows.push(textCutNote(shown.hiddenChars));
2649
2670
  return rows;
@@ -2653,7 +2674,7 @@ function onBand(rows, cols) {
2653
2674
  return style.enabled ? band.map((row) => style.band(pad(row, cols))) : band;
2654
2675
  }
2655
2676
  function skillPaintedLines(text, skills, room) {
2656
- const tokens = (0, import_core14.loadedSkillTokens)(text, skills);
2677
+ const tokens = (0, import_core15.loadedSkillTokens)(text, skills);
2657
2678
  return wrapLines(text, room).map(({ line, start }) => {
2658
2679
  let painted = "";
2659
2680
  let at = 0;
@@ -2747,7 +2768,7 @@ function headLines(output, maxLines, maxChars) {
2747
2768
  return lines;
2748
2769
  }
2749
2770
  function shownOutput(output, maxLines, maxChars) {
2750
- if (output.length <= FULL_OUTPUT_MAX) return (0, import_core14.outputPreview)(output, maxLines);
2771
+ if (output.length <= FULL_OUTPUT_MAX) return (0, import_core15.outputPreview)(output, maxLines);
2751
2772
  const lines = headLines(output, maxLines, maxChars);
2752
2773
  const total = output.length === 0 ? 0 : lineCountOf(output) + 1;
2753
2774
  return { lines, hiddenLineCount: Math.max(0, total - lines.length) };
@@ -2849,7 +2870,7 @@ var DIFF_PAINT = {
2849
2870
  var DIFF_SIGN = { added: "+", removed: "-", context: " ", gap: "" };
2850
2871
  function diffLines(block2, diff, room, detailAll) {
2851
2872
  const bounded = diffOutput(block2.output);
2852
- const rows = (0, import_core14.diffRows)(bounded.text);
2873
+ const rows = (0, import_core15.diffRows)(bounded.text);
2853
2874
  const gutter = Math.max(0, ...rows.map((row) => String(row.line ?? "").length));
2854
2875
  const draw = (row) => {
2855
2876
  const text = row.kind === "gap" ? `${" ".repeat(gutter)} \u22EE` : `${String(row.line ?? "").padStart(gutter)} ${DIFF_SIGN[row.kind]} ${sanitize(row.text)}`;
@@ -2861,7 +2882,7 @@ function diffLines(block2, diff, room, detailAll) {
2861
2882
  ...hidden > 0 ? [style.grey(`\u2026 +${countOf(hidden, "line")} (ctrl+o to expand)`)] : [],
2862
2883
  ...bounded.hiddenLineCount > 0 ? [style.grey(`\u2026 +${countOf(bounded.hiddenLineCount, "line")} not shown`)] : []
2863
2884
  ];
2864
- return [(0, import_core14.diffSummary)(diff), ...shown.flatMap(draw), ...more];
2885
+ return [(0, import_core15.diffSummary)(diff), ...shown.flatMap(draw), ...more];
2865
2886
  }
2866
2887
  function toolLines(block2, cols, detailAll) {
2867
2888
  const room = Math.max(1, cols - OUTPUT_INDENT.length);
@@ -2901,7 +2922,7 @@ function subagentLines(block2, cols, detailAll) {
2901
2922
  function approvalLine(block2, cols) {
2902
2923
  const [mark, paint] = block2.status === "pending" ? ["?", style.yellow] : block2.status === "granted" ? ["\u2713", style.green] : block2.status === "withdrawn" ? ["\xB7", style.grey] : ["\u2298", style.yellow];
2903
2924
  const note = block2.note ? `: \u201C${firstLine(block2.note)}\u201D` : "";
2904
- const line = `${mark} ${(0, import_core14.approvalStatusText)(block2)} \xB7 ${(0, import_core14.approvalKindText)(block2.kind)}: ${firstLine(block2.subject)}${note}`;
2925
+ const line = `${mark} ${(0, import_core15.approvalStatusText)(block2)} \xB7 ${(0, import_core15.approvalKindText)(block2.kind)}: ${firstLine(block2.subject)}${note}`;
2905
2926
  return `${paint(mark)}${truncate(line, cols).slice(mark.length)}`;
2906
2927
  }
2907
2928
  var SETTLED_BODY_LINES = 20;
@@ -2958,7 +2979,7 @@ function runnerCommandLines(block2, cols, detailAll) {
2958
2979
  }
2959
2980
  function compactionLines(block2, cols, detailAll) {
2960
2981
  const hint = block2.summary && !detailAll ? " (ctrl+o for the summary)" : "";
2961
- const head = style.grey(truncate(`\u2261 ${(0, import_core14.compactionText)(block2)}${hint}`, cols));
2982
+ const head = style.grey(truncate(`\u2261 ${(0, import_core15.compactionText)(block2)}${hint}`, cols));
2962
2983
  if (!block2.summary || !detailAll) return [head];
2963
2984
  const room = Math.max(1, cols - OUTPUT_INDENT.length);
2964
2985
  return [head, ...underHeader(block2.summary.trim().split("\n").flatMap((line) => wrap(sanitize(line), room)).map(style.grey))];
@@ -2995,7 +3016,7 @@ var HANDOFF_INDENT = " ";
2995
3016
  var FINDING_INDENT = " ";
2996
3017
  function findingHeadLines(f, cols) {
2997
3018
  const id = sanitize(f.id);
2998
- const severity = (0, import_core14.severityLabel)(f.severity);
3019
+ const severity = (0, import_core15.severityLabel)(f.severity);
2999
3020
  const label = `${id} \xB7 ${severity} \xB7 `;
3000
3021
  const room = Math.max(1, cols - width(HANDOFF_INDENT) - width(label));
3001
3022
  return wrap(sanitize(f.title), room).map((line, i) => i === 0 ? `${HANDOFF_INDENT}${style.grey(id)} \xB7 ${SEVERITY_PAINT[f.severity](severity)} \xB7 ${line}` : `${HANDOFF_INDENT}${" ".repeat(width(label))}${line}`);
@@ -3003,7 +3024,7 @@ function findingHeadLines(f, cols) {
3003
3024
  function findingLines(f, cols) {
3004
3025
  const room = Math.max(1, cols - FINDING_INDENT.length);
3005
3026
  const detail = f.detail ? sanitize(f.detail).replace(/\n+$/, "").split("\n").flatMap((line) => wrap(line, room)).map((line) => `${FINDING_INDENT}${line}`) : [];
3006
- const evidence = f.evidence?.length ? wrap(`Evidence: ${f.evidence.map((e) => sanitize((0, import_core14.handoffReferenceText)(e))).join("; ")}`, room).map((line) => `${FINDING_INDENT}${style.grey(line)}`) : [];
3027
+ const evidence = f.evidence?.length ? wrap(`Evidence: ${f.evidence.map((e) => sanitize((0, import_core15.handoffReferenceText)(e))).join("; ")}`, room).map((line) => `${FINDING_INDENT}${style.grey(line)}`) : [];
3007
3028
  return [...findingHeadLines(f, cols), ...detail, ...evidence];
3008
3029
  }
3009
3030
  function handoffArtifactLines(block2, cols) {
@@ -3012,14 +3033,14 @@ function handoffArtifactLines(block2, cols) {
3012
3033
  const room = Math.max(1, cols - FINDING_INDENT.length - 2);
3013
3034
  const rows = [`${HANDOFF_INDENT}${style.grey("Artifacts:")}`];
3014
3035
  for (const artifact of artifacts) {
3015
- const [first, ...rest] = wrap(sanitize((0, import_core14.handoffReferenceText)(artifact)), room);
3036
+ const [first, ...rest] = wrap(sanitize((0, import_core15.handoffReferenceText)(artifact)), room);
3016
3037
  rows.push(`${FINDING_INDENT}- ${first}`, ...rest.map((line) => `${FINDING_INDENT} ${line}`));
3017
3038
  }
3018
3039
  return rows;
3019
3040
  }
3020
3041
  function handoffPatchLines(block2, cols) {
3021
3042
  const room = Math.max(1, cols - FINDING_INDENT.length);
3022
- return (0, import_core14.savedPatchViews)(block2.handoff).flatMap((patch) => [
3043
+ return (0, import_core15.savedPatchViews)(block2.handoff).flatMap((patch) => [
3023
3044
  ...wrap(`Patch: ${patch.changes}, ${patch.base}`, Math.max(1, cols - HANDOFF_INDENT.length)).map((line, i) => `${HANDOFF_INDENT}${i === 0 ? style.grey(line) : line}`),
3024
3045
  ...wrap(patch.saved, room).map((line) => `${FINDING_INDENT}${style.grey(line)}`),
3025
3046
  ...patch.flags.flatMap((flag2) => wrap(flag2, room).map((line) => `${FINDING_INDENT}${style.grey(line)}`))
@@ -3027,7 +3048,7 @@ function handoffPatchLines(block2, cols) {
3027
3048
  }
3028
3049
  function handoffReportLines(report, cols, detailAll) {
3029
3050
  const all = sanitize(report).replace(/\n+$/, "").split("\n");
3030
- const shown = detailAll ? all : all.slice(0, import_core14.REPORT_PREVIEW_LINES);
3051
+ const shown = detailAll ? all : all.slice(0, import_core15.REPORT_PREVIEW_LINES);
3031
3052
  const room = Math.max(1, cols - FINDING_INDENT.length);
3032
3053
  const rows = [
3033
3054
  `${HANDOFF_INDENT}${style.grey("Report:")}`,
@@ -3035,13 +3056,13 @@ function handoffReportLines(report, cols, detailAll) {
3035
3056
  ];
3036
3057
  const hidden = all.length - shown.length;
3037
3058
  if (hidden > 0) rows.push(style.grey(`${FINDING_INDENT}\u2026 +${countOf(hidden, "line")} of the report (ctrl+o to expand)`));
3038
- else if (detailAll && all.length > import_core14.REPORT_PREVIEW_LINES) rows.push(style.grey(`${FINDING_INDENT}\u2026 (ctrl+o to collapse)`));
3059
+ else if (detailAll && all.length > import_core15.REPORT_PREVIEW_LINES) rows.push(style.grey(`${FINDING_INDENT}\u2026 (ctrl+o to collapse)`));
3039
3060
  return rows;
3040
3061
  }
3041
3062
  function handoffLines(block2, cols, detailAll) {
3042
3063
  const { handoff } = block2;
3043
- const severity = (0, import_core14.worstSeverity)(handoff.findings);
3044
- const summary = handoff.findingCount > 0 ? `${(0, import_core14.handoffFindingsText)(handoff.findingCount)}${severity ? ` \xB7 ${(0, import_core14.severityLabel)(severity).toLowerCase()}` : ""}` : null;
3064
+ const severity = (0, import_core15.worstSeverity)(handoff.findings);
3065
+ const summary = handoff.findingCount > 0 ? `${(0, import_core15.handoffFindingsText)(handoff.findingCount)}${severity ? ` \xB7 ${(0, import_core15.severityLabel)(severity).toLowerCase()}` : ""}` : null;
3045
3066
  const rows = [`${style.brand("\u25C6")} ${style.bold("Handoff")}${summary ? style.grey(` \xB7 ${summary}`) : ""}`];
3046
3067
  if (handoff.commit) {
3047
3068
  const room = Math.max(1, cols - HANDOFF_INDENT.length);
@@ -3062,7 +3083,7 @@ function handoffLines(block2, cols, detailAll) {
3062
3083
  return rows;
3063
3084
  }
3064
3085
  function planLine(block2, cols) {
3065
- const text = (0, import_core14.planMarkerParts)(block2).join(" \xB7 ");
3086
+ const text = (0, import_core15.planMarkerParts)(block2).join(" \xB7 ");
3066
3087
  return `${style.brand("\u25C7")}${truncate(`\u25C7 ${text}`, cols).slice(1)}`;
3067
3088
  }
3068
3089
  function blockLines(block2, cols, detailAll) {
@@ -3114,9 +3135,9 @@ function conversationLines(blocks2, cols, detailAll, answering) {
3114
3135
  }
3115
3136
  function tokenLine(blocks2, cols, limits = []) {
3116
3137
  const usage = blocks2[blocks2.length - 1];
3117
- const context = usage?.type === "usage" ? (0, import_core14.contextMeterText)(usage.contextFill) : null;
3118
- const cost = usage?.type === "usage" ? (0, import_core14.usageText)({ totals: usage.totals }) : null;
3119
- const text = [context ? `Planner ${context}` : null, ...limits.map(import_core14.accountLimitsText), cost].filter(Boolean).join(" \xB7 ");
3138
+ const context = usage?.type === "usage" ? (0, import_core15.contextMeterText)(usage.contextFill) : null;
3139
+ const cost = usage?.type === "usage" ? (0, import_core15.usageText)({ totals: usage.totals }) : null;
3140
+ const text = [context ? `Planner ${context}` : null, ...limits.map(import_core15.accountLimitsText), cost].filter(Boolean).join(" \xB7 ");
3120
3141
  return text ? style.grey(truncate(sanitize(text).replace(/\n/g, " "), cols)) : null;
3121
3142
  }
3122
3143
  function assignmentFlagText(row) {
@@ -3125,7 +3146,7 @@ function assignmentFlagText(row) {
3125
3146
 
3126
3147
  // src/commands/status.ts
3127
3148
  var import_fs4 = require("fs");
3128
- var import_core15 = require("@ordewell/core");
3149
+ var import_core16 = require("@ordewell/core");
3129
3150
  async function handleStatus(subArgs, injectedApi) {
3130
3151
  const sessionId = flag(subArgs, "--session-id");
3131
3152
  const workspace = flag(subArgs, "--workspace") || process.cwd();
@@ -3193,42 +3214,49 @@ async function assignmentScope(api) {
3193
3214
  }
3194
3215
  function printOneSession(sessionId, meta, plan, runnerScope, run) {
3195
3216
  const allTasks = allTasksOf(plan);
3196
- const everyTask = (0, import_core15.flattenTasks)(allTasks);
3197
- const { blocked, repeating } = (0, import_core15.planBlocks)(everyTask.map((t) => ({ ...t, subtasks: [], dependencies: t.dependencies ?? [] })));
3217
+ const everyTask = (0, import_core16.flattenTasks)(allTasks);
3218
+ const { blocked, repeating } = (0, import_core16.planBlocks)(everyTask.map((t) => ({ ...t, subtasks: [], dependencies: t.dependencies ?? [] })));
3198
3219
  const holds = /* @__PURE__ */ new Map();
3199
- for (const { task } of (0, import_core15.walkPlan)(run?.tasks ?? [])) {
3220
+ const repairs = /* @__PURE__ */ new Map();
3221
+ const stranded = /* @__PURE__ */ new Set();
3222
+ for (const { task } of (0, import_core16.walkPlan)(run?.tasks ?? [])) {
3200
3223
  if (task.holdReason) holds.set(task.id, task.holdReason);
3224
+ if (task.loopStranded) stranded.add(task.id);
3225
+ if (task.isolation?.state === "conflict" && task.isolation.repairWaits) repairs.set(task.id, (0, import_core16.repairWaitLabel)(task.isolation.repairWaits));
3201
3226
  }
3202
- const count = (status) => everyTask.filter((t) => t.status === status && !(status === "failed" && repeating.has(t.id)) || status === "blocked" && blocked.has(t.id)).length;
3227
+ if (!run) for (const id of repeating) stranded.add(id);
3228
+ const goesOn = (id) => repeating.has(id) && !stranded.has(id);
3229
+ const count = (status) => everyTask.filter((t) => t.status === status && !(status === "failed" && goesOn(t.id)) || status === "blocked" && blocked.has(t.id)).length;
3203
3230
  console.log(`Session: ${sessionId}`);
3204
3231
  console.log(`Goal: ${meta.goal}`);
3205
3232
  console.log(`Runner: ${(meta.runners || []).join(", ")} | Status: ${meta.status}`);
3206
3233
  console.log(`Phase: ${plan?.phase || "unknown"} | Created: ${meta.createdAt}`);
3207
- const isolation = (0, import_core15.isolationViewOfPlan)(plan);
3208
- if (isolation) console.log(`Base: ${(0, import_core15.handoffBaseLine)(isolation.handoff, 8)}`);
3234
+ const isolation = (0, import_core16.isolationViewOfPlan)(plan);
3235
+ if (isolation) console.log(`Base: ${(0, import_core16.handoffBaseLine)(isolation.handoff, 8)}`);
3209
3236
  console.log(`Tasks: ${everyTask.length} total | ${count("completed")} done | ${count("failed")} failed | ${count("in_progress")} running | ${count("blocked")} blocked | ${count("skipped")} skipped
3210
3237
  `);
3211
- const total = (0, import_core15.tasksTotalText)(allTasks);
3238
+ const total = (0, import_core16.tasksTotalText)(allTasks);
3212
3239
  if (total) console.log(total);
3213
3240
  const record = servedApprovalRecord(plan);
3214
3241
  const approval = (row, ref) => row.waitsForApproval ? `${row.waitsForApproval}: \`ordewell force-start ${ref}\` approves it, \`ordewell run\` approves all` : null;
3215
3242
  for (const t of allTasks) {
3216
3243
  const model = t.assignedModel ? ` (${t.assignedModel.modelLabel})` : "";
3217
3244
  const shortId = (t.id ?? "").slice(-8);
3218
- const row = (0, import_core15.taskRowView)({ ...t, dependencies: t.dependencies ?? [], waitsForApproval: (0, import_core15.waitsForApproval)(t, record), holdReason: holds.get(t.id) }, { tasks: allTasks, runnerScope });
3219
- const icon = iconFor(blocked.has(t.id) ? "blocked" : repeating.has(t.id) ? "pending" : t.status);
3220
- console.log(` ${(0, import_core15.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}]${row.kind === "script" ? " [SCR]" : row.research ? " [RES]" : ""} ${t.title}${model}`);
3245
+ const row = (0, import_core16.taskRowView)({ ...t, dependencies: t.dependencies ?? [], waitsForApproval: (0, import_core16.waitsForApproval)(t, record), holdReason: holds.get(t.id), loopStranded: stranded.has(t.id) }, { tasks: allTasks, runnerScope });
3246
+ const icon = iconFor(blocked.has(t.id) ? "blocked" : goesOn(t.id) ? "pending" : t.status);
3247
+ console.log(` ${(0, import_core16.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}]${row.kind === "script" ? " [SCR]" : row.research ? " [RES]" : ""} ${t.title}${model}`);
3221
3248
  if (t.type === "script") {
3222
- for (const line of (0, import_core15.scriptTaskText)(t).split("\n")) console.log(line ? ` ${line.replace(/\t/g, " ")}` : "");
3249
+ for (const line of (0, import_core16.scriptTaskText)(t).split("\n")) console.log(line ? ` ${line.replace(/\t/g, " ")}` : "");
3223
3250
  }
3224
- const waiting = approval(row, (0, import_core15.taskOrderLabel)(t));
3251
+ const waiting = approval(row, (0, import_core16.taskOrderLabel)(t));
3225
3252
  if (waiting) console.log(` ${waiting}`);
3226
- if (row.holdReason) console.log(` ${(0, import_core15.holdLabel)(row.holdReason)}`);
3253
+ if (row.holdReason) console.log(` ${(0, import_core16.holdLabel)(row.holdReason)}`);
3254
+ if (repairs.has(t.id)) console.log(` ${repairs.get(t.id)}`);
3227
3255
  if (row.assignmentFlag) console.log(` \u26A0 ${assignmentFlagText(row)}`);
3228
- if (row.blockedBy) console.log(` ${(0, import_core15.blockedByLabel)(row.blockedBy)}`);
3229
- if (row.loop) console.log(` \u21BB ${(0, import_core15.loopRowParts)(row.loop).join(" \xB7 ")}`);
3230
- if (row.status === "skipped") console.log(` ${(0, import_core15.skippedLabel)(row.skipReason)}`);
3231
- const usage = row.usage && (0, import_core15.usageText)(row.usage);
3256
+ if (row.blockedBy) console.log(` ${(0, import_core16.blockedByLabel)(row.blockedBy)}`);
3257
+ if (row.loop) console.log(` \u21BB ${(0, import_core16.loopRowParts)(row.loop).join(" \xB7 ")}`);
3258
+ if (row.status === "skipped") console.log(` ${(0, import_core16.skippedLabel)(row.skipReason)}`);
3259
+ const usage = row.usage && (0, import_core16.usageText)(row.usage);
3232
3260
  if (usage) console.log(` ${usage}`);
3233
3261
  if (t.handoff && (t.status === "completed" || t.status === "failed")) {
3234
3262
  for (const line of taskHandoffLines(t.handoff, " ")) console.log(line);
@@ -3237,15 +3265,16 @@ function printOneSession(sessionId, meta, plan, runnerScope, run) {
3237
3265
  const subIcon = iconFor(blocked.has(sub.id) ? "blocked" : sub.status);
3238
3266
  const subModel = sub.assignedModel ? ` (${sub.assignedModel.modelLabel})` : "";
3239
3267
  const subShortId = (sub.id ?? "").slice(-8);
3240
- console.log(` ${(0, import_core15.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
3241
- const subRow = (0, import_core15.taskRowView)({ ...sub, dependencies: sub.dependencies ?? [], waitsForApproval: (0, import_core15.waitsForApproval)(sub, record), holdReason: holds.get(sub.id) }, { parent: t, tasks: allTasks, runnerScope });
3242
- const subWaiting = approval(subRow, (0, import_core15.taskOrderLabel)(sub, t));
3268
+ console.log(` ${(0, import_core16.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
3269
+ const subRow = (0, import_core16.taskRowView)({ ...sub, dependencies: sub.dependencies ?? [], waitsForApproval: (0, import_core16.waitsForApproval)(sub, record), holdReason: holds.get(sub.id) }, { parent: t, tasks: allTasks, runnerScope });
3270
+ const subWaiting = approval(subRow, (0, import_core16.taskOrderLabel)(sub, t));
3243
3271
  if (subWaiting) console.log(` ${subWaiting}`);
3244
- if (subRow.holdReason) console.log(` ${(0, import_core15.holdLabel)(subRow.holdReason)}`);
3272
+ if (subRow.holdReason) console.log(` ${(0, import_core16.holdLabel)(subRow.holdReason)}`);
3273
+ if (repairs.has(sub.id)) console.log(` ${repairs.get(sub.id)}`);
3245
3274
  if (subRow.assignmentFlag) console.log(` \u26A0 ${assignmentFlagText(subRow)}`);
3246
- if (subRow.blockedBy) console.log(` ${(0, import_core15.blockedByLabel)(subRow.blockedBy)}`);
3247
- if (subRow.status === "skipped") console.log(` ${(0, import_core15.skippedLabel)(subRow.skipReason)}`);
3248
- const subUsage = subRow.usage && (0, import_core15.usageText)(subRow.usage);
3275
+ if (subRow.blockedBy) console.log(` ${(0, import_core16.blockedByLabel)(subRow.blockedBy)}`);
3276
+ if (subRow.status === "skipped") console.log(` ${(0, import_core16.skippedLabel)(subRow.skipReason)}`);
3277
+ const subUsage = subRow.usage && (0, import_core16.usageText)(subRow.usage);
3249
3278
  if (subUsage) console.log(` ${subUsage}`);
3250
3279
  if (sub.handoff && (sub.status === "completed" || sub.status === "failed")) {
3251
3280
  for (const line of taskHandoffLines(sub.handoff, " ")) console.log(line);
@@ -3304,28 +3333,28 @@ async function handleWeb(subArgs) {
3304
3333
  }
3305
3334
 
3306
3335
  // src/commands/models.ts
3307
- var import_core18 = require("@ordewell/core");
3336
+ var import_core19 = require("@ordewell/core");
3308
3337
 
3309
3338
  // src/commands/model.ts
3310
- var import_core17 = require("@ordewell/core");
3339
+ var import_core18 = require("@ordewell/core");
3311
3340
 
3312
3341
  // src/plannerModelSwitch.ts
3313
- var import_core16 = require("@ordewell/core");
3342
+ var import_core17 = require("@ordewell/core");
3314
3343
  function plannerSwitchRecall(settings) {
3315
3344
  if (settings.switchRecall) return settings.switchRecall;
3316
3345
  const model = settings.orchestratorModel;
3317
3346
  return model ? { model, effort: settings.plannerThinkingEffort, source: "remembered" } : { model: "", effort: "", source: "none" };
3318
3347
  }
3319
3348
  function vendorModelRefusal(provider, model, pickPlanner) {
3320
- const meta = (0, import_core16.getProviderMeta)(provider);
3321
- if (!(0, import_core16.runnerForProvider)(provider) || !meta || !(0, import_core16.isVendorModelFor)(meta.id, model)) return null;
3349
+ const meta = (0, import_core17.getProviderMeta)(provider);
3350
+ if (!(0, import_core17.runnerForProvider)(provider) || !meta || !(0, import_core17.isVendorModelFor)(meta.id, model)) return null;
3322
3351
  return `${model} is a vendor's model, which ${meta.label} cannot run. To plan with that vendor, switch planner with ${pickPlanner}.`;
3323
3352
  }
3324
3353
  function uninstalledPlannerRefusal(provider, installed, pickPlanner) {
3325
- const runner = (0, import_core16.runnerForProvider)(provider);
3354
+ const runner = (0, import_core17.runnerForProvider)(provider);
3326
3355
  if (!runner || installed.includes(runner)) return null;
3327
- const agents = import_core16.CLI_PROVIDERS.filter((id) => installed.includes((0, import_core16.runnerForProvider)(id) ?? ""));
3328
- const label = (0, import_core16.getProviderMeta)(provider)?.label ?? provider;
3356
+ const agents = import_core17.CLI_PROVIDERS.filter((id) => installed.includes((0, import_core17.runnerForProvider)(id) ?? ""));
3357
+ const label = (0, import_core17.getProviderMeta)(provider)?.label ?? provider;
3329
3358
  return agents.length > 0 ? `${label} is not installed or not on PATH. Installed coding agents: ${agents.join(", ")}. Switch with ${pickPlanner}.` : `${label} is not installed or not on PATH, and no other coding agent is. Install one, or plan with an API provider through ${pickPlanner}.`;
3330
3359
  }
3331
3360
 
@@ -3344,7 +3373,7 @@ async function handleModel(subArgs, injectedApi) {
3344
3373
  if (!modelId) fail(USAGE);
3345
3374
  const settings = await api.getSettings();
3346
3375
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
3347
- const runner = (0, import_core17.runnerForProvider)(provider);
3376
+ const runner = (0, import_core18.runnerForProvider)(provider);
3348
3377
  if (runner) {
3349
3378
  const known = (await fetchCatalog(api)).models.filter((m) => m.runners?.includes(runner));
3350
3379
  if (known.length > 0 && !known.some((m) => m.id === modelId)) {
@@ -3353,7 +3382,7 @@ async function handleModel(subArgs, injectedApi) {
3353
3382
  `Run \`ordewell model\` to see what ${runner} offers.`
3354
3383
  );
3355
3384
  }
3356
- const refusal = known.length === 0 ? vendorModelRefusal(provider, modelId, import_core17.HEADLESS_CLI_COMMANDS.pickPlanner) : null;
3385
+ const refusal = known.length === 0 ? vendorModelRefusal(provider, modelId, import_core18.HEADLESS_CLI_COMMANDS.pickPlanner) : null;
3357
3386
  if (refusal) fail(refusal);
3358
3387
  }
3359
3388
  const settled = await api.updateSettings({ orchestratorModel: modelId });
@@ -3369,7 +3398,7 @@ async function showModels(api) {
3369
3398
  const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
3370
3399
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
3371
3400
  const current = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
3372
- const runner = (0, import_core17.runnerForProvider)(provider);
3401
+ const runner = (0, import_core18.runnerForProvider)(provider);
3373
3402
  const options = runner ? catalog.models.filter((m) => m.runners?.includes(runner)) : catalog.orchestratorModels;
3374
3403
  console.log(`
3375
3404
  Planner model: ${current || "(backend default)"}`);
@@ -3403,8 +3432,8 @@ async function handleModels(subArgs) {
3403
3432
  const rest = subArgs.filter((_, i) => i !== setIdx && i !== setIdx + 1);
3404
3433
  return handleModel(["set", subArgs[setIdx + 1], ...rest]);
3405
3434
  }
3406
- const config = new import_core18.EnvConfig();
3407
- const providers = (0, import_core18.configuredProviders)(config);
3435
+ const config = new import_core19.EnvConfig();
3436
+ const providers = (0, import_core19.configuredProviders)(config);
3408
3437
  if (providers.length === 0) {
3409
3438
  console.error("No providers configured. Set an API key for a provider, e.g.:");
3410
3439
  console.error(" OPENROUTER_API_KEY=... (OpenRouter)");
@@ -3413,19 +3442,19 @@ async function handleModels(subArgs) {
3413
3442
  console.error(" OPENAI_COMPATIBLE_BASE_URL=... (self-hosted / OpenAI-compatible)");
3414
3443
  process.exit(1);
3415
3444
  }
3416
- const { apiKeys, baseUrls } = (0, import_core18.collectProviderCredentials)(config);
3445
+ const { apiKeys, baseUrls } = (0, import_core19.collectProviderCredentials)(config);
3417
3446
  let models;
3418
3447
  let errors;
3419
3448
  try {
3420
- const result = await (0, import_core18.fetchAllProviderModels)({ apiKeys, baseUrls });
3421
- models = (0, import_core18.toOrchestratorOptions)(result.models, import_core18.ORCHESTRATOR_SHORTCUTS);
3449
+ const result = await (0, import_core19.fetchAllProviderModels)({ apiKeys, baseUrls });
3450
+ models = (0, import_core19.toOrchestratorOptions)(result.models, import_core19.ORCHESTRATOR_SHORTCUTS);
3422
3451
  errors = result.errors;
3423
3452
  } catch (err) {
3424
3453
  console.error(`Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`);
3425
3454
  process.exit(1);
3426
3455
  return;
3427
3456
  }
3428
- const providerLabel = (p) => (0, import_core18.getProviderMeta)(p)?.label ?? p;
3457
+ const providerLabel = (p) => (0, import_core19.getProviderMeta)(p)?.label ?? p;
3429
3458
  console.log(
3430
3459
  `
3431
3460
  Available models (${models.length}) across ${providers.length} configured provider(s):`
@@ -3466,7 +3495,7 @@ ${providerLabel(provider)}`);
3466
3495
 
3467
3496
  // src/commands/setup.ts
3468
3497
  var import_fs6 = require("fs");
3469
- var import_core19 = require("@ordewell/core");
3498
+ var import_core20 = require("@ordewell/core");
3470
3499
  function handleSetup(subArgs) {
3471
3500
  const modelIdx = subArgs.indexOf("--model");
3472
3501
  const chosenModel = modelIdx !== -1 ? subArgs[modelIdx + 1] : null;
@@ -3538,13 +3567,13 @@ function handleSetup(subArgs) {
3538
3567
  # STEPFUN_API_KEY=...
3539
3568
  # SILICONFLOW_API_KEY=...
3540
3569
  `;
3541
- (0, import_core19.writePrivateFile)(envFile, content);
3570
+ (0, import_core20.writePrivateFile)(envFile, content);
3542
3571
  console.log(`Wrote: ${envFile}`);
3543
3572
  console.log("Review and edit the file, then run: ordewell tui");
3544
3573
  }
3545
3574
 
3546
3575
  // src/commands/skills.ts
3547
- var import_core20 = require("@ordewell/core");
3576
+ var import_core21 = require("@ordewell/core");
3548
3577
  var import_path5 = require("path");
3549
3578
  function invocation(skill) {
3550
3579
  if (skill.userInvocable && skill.modelInvocable) return "both";
@@ -3557,22 +3586,22 @@ function handleSkills(subArgs) {
3557
3586
  fail("Usage: ordewell skills [--workspace /path] [--json]");
3558
3587
  }
3559
3588
  const workspace = (0, import_path5.resolve)(flag(subArgs, "--workspace") || process.cwd());
3560
- const catalog = (0, import_core20.createSkillsService)(workspace, new import_core20.EnvConfig().workspaceRepos).readCatalog();
3589
+ const catalog = (0, import_core21.createSkillsService)(workspace, new import_core21.EnvConfig().workspaceRepos).readCatalog();
3561
3590
  const skills = catalog.skills.map((skill) => ({
3562
3591
  name: skill.name,
3563
3592
  scope: skill.source,
3564
3593
  appliesTo: skill.appliesTo,
3565
3594
  invocation: invocation(skill),
3566
- path: (0, import_core20.abbreviateHome)(skill.path)
3595
+ path: (0, import_core21.abbreviateHome)(skill.path)
3567
3596
  }));
3568
3597
  const shadowed = catalog.shadowed.map(({ skill, shadowedBy }) => ({
3569
3598
  name: skill.name,
3570
- path: (0, import_core20.abbreviateHome)(skill.path),
3571
- shadowedBy: { scope: shadowedBy.source, path: (0, import_core20.abbreviateHome)(shadowedBy.path) }
3599
+ path: (0, import_core21.abbreviateHome)(skill.path),
3600
+ shadowedBy: { scope: shadowedBy.source, path: (0, import_core21.abbreviateHome)(shadowedBy.path) }
3572
3601
  }));
3573
3602
  const invalid = catalog.invalid.map((skill) => ({
3574
3603
  name: skill.name,
3575
- path: (0, import_core20.abbreviateHome)(skill.path),
3604
+ path: (0, import_core21.abbreviateHome)(skill.path),
3576
3605
  reason: skill.reason
3577
3606
  }));
3578
3607
  if (hasFlag(subArgs, "--json")) {
@@ -3687,10 +3716,10 @@ async function handleShow(args, api) {
3687
3716
  }
3688
3717
 
3689
3718
  // src/commands/mark-complete.ts
3690
- var import_core22 = require("@ordewell/core");
3719
+ var import_core23 = require("@ordewell/core");
3691
3720
 
3692
3721
  // src/commands/task-control.ts
3693
- var import_core21 = require("@ordewell/core");
3722
+ var import_core22 = require("@ordewell/core");
3694
3723
  async function sessionAndRef(subArgs, usage, injectedApi) {
3695
3724
  const workspace = flag(subArgs, "--workspace") || process.cwd();
3696
3725
  let sessionId = flag(subArgs, "--session-id");
@@ -3735,7 +3764,7 @@ async function confirmPastGate(subArgs, api, sessionId, ref, confirm2) {
3735
3764
  const unmerged = await api.getMergeGate(sessionId, ref).catch(() => []);
3736
3765
  if (unmerged.length === 0) return false;
3737
3766
  if (hasFlag(subArgs, "--yes")) return true;
3738
- const question = `${(0, import_core21.pastGateConfirmation)(unmerged.map((d) => (0, import_core21.titledTaskRef)(d)))} Start it anyway?`;
3767
+ const question = `${(0, import_core22.pastGateConfirmation)(unmerged.map((d) => (0, import_core22.titledTaskRef)(d)))} Start it anyway?`;
3739
3768
  if (!await confirm2(question)) fail("Not started: nothing was changed. Pass --yes to start it without a prompt.");
3740
3769
  return true;
3741
3770
  }
@@ -3750,7 +3779,7 @@ function makeHandler(action, command = action) {
3750
3779
  const pastGate = PASSES_GATE.has(action) ? await confirmPastGate(subArgs, api, sessionId, ref, confirm2) : false;
3751
3780
  const control = pastGate && action === "run" ? "force-start" : action;
3752
3781
  try {
3753
- const { message } = await api.execute(sessionId, (0, import_core21.taskActionCommand)(control, ref));
3782
+ const { message } = await api.execute(sessionId, (0, import_core22.taskActionCommand)(control, ref));
3754
3783
  console.log(message);
3755
3784
  } catch (err) {
3756
3785
  console.error(`Failed to ${control} task: ${err.message}`);
@@ -3807,17 +3836,17 @@ var handleCancel = makeHandler("cancel");
3807
3836
 
3808
3837
  // src/commands/mark-complete.ts
3809
3838
  async function overrideCommand(direction, subArgs, api, sessionId, ref, confirm2) {
3810
- const command = (0, import_core22.taskActionCommand)(direction, ref);
3811
- if (hasFlag(subArgs, "--yes")) return (0, import_core22.confirmedOverride)(command);
3839
+ const command = (0, import_core23.taskActionCommand)(direction, ref);
3840
+ if (hasFlag(subArgs, "--yes")) return (0, import_core23.confirmedOverride)(command);
3812
3841
  const live = await api.getSession(sessionId, flag(subArgs, "--workspace") || process.cwd()).catch(() => null);
3813
3842
  const tasks = live ? allTasksOf(live.plan) : [];
3814
- const task = (0, import_core22.resolveUserTaskRef)(tasks, ref);
3815
- if (!task || !(0, import_core22.overrideNeedsConfirmation)(task)) return command;
3816
- const label = `task ${(0, import_core22.titledTaskRefOf)(tasks, task.id) ?? ref}`;
3817
- if (live?.run?.activity?.advancesOnItsOwn) fail((0, import_core22.manualOverrideStopFirst)(direction, label));
3818
- const question = `${(0, import_core22.manualOverrideConfirmation)(direction, label)} Go ahead?`;
3843
+ const task = (0, import_core23.resolveUserTaskRef)(tasks, ref);
3844
+ if (!task || !(0, import_core23.overrideNeedsConfirmation)(task)) return command;
3845
+ const label = `task ${(0, import_core23.titledTaskRefOf)(tasks, task.id) ?? ref}`;
3846
+ if (live?.run?.activity?.advancesOnItsOwn) fail((0, import_core23.manualOverrideStopFirst)(direction, label));
3847
+ const question = `${(0, import_core23.manualOverrideConfirmation)(direction, label)} Go ahead?`;
3819
3848
  if (!await confirm2(question)) fail("Nothing was changed. Pass --yes to confirm without a prompt.");
3820
- return (0, import_core22.confirmedOverride)(command, task.status);
3849
+ return (0, import_core23.confirmedOverride)(command, task.status);
3821
3850
  }
3822
3851
  async function handleMarkComplete(subArgs, api, confirm2 = askYesNo) {
3823
3852
  await withTaskRef(
@@ -3860,7 +3889,7 @@ async function handleSkip(subArgs, api) {
3860
3889
  api,
3861
3890
  async (client, sessionId, ref) => {
3862
3891
  try {
3863
- const done = await client.execute(sessionId, (0, import_core22.taskActionCommand)("skip", ref, flag(subArgs, "--reason")));
3892
+ const done = await client.execute(sessionId, (0, import_core23.taskActionCommand)("skip", ref, flag(subArgs, "--reason")));
3864
3893
  console.log(done.message);
3865
3894
  } catch (err) {
3866
3895
  console.error(`Failed to skip task: ${err.message}`);
@@ -3870,6 +3899,32 @@ async function handleSkip(subArgs, api) {
3870
3899
  );
3871
3900
  }
3872
3901
 
3902
+ // src/commands/revert.ts
3903
+ var import_core24 = require("@ordewell/core");
3904
+ async function handleRevert(subArgs, api, confirm2 = askYesNo) {
3905
+ await withTaskRef(
3906
+ subArgs,
3907
+ "Usage: ordewell revert <task-id-or-order> [--session-id <id>] [--workspace /path] [--yes]",
3908
+ api,
3909
+ async (client, sessionId, ref) => {
3910
+ try {
3911
+ const preview = await client.revertPreview(sessionId, ref);
3912
+ if (!preview.revertible) fail(preview.message);
3913
+ console.log(`${import_core24.revertEffect}
3914
+ ${preview.tasks.map((t) => ` ${(0, import_core24.revertListLine)(t)}`).join("\n")}`);
3915
+ if (!hasFlag(subArgs, "--yes") && !await confirm2("Revert these?")) {
3916
+ fail("Nothing was changed. Pass --yes to confirm without a prompt.");
3917
+ }
3918
+ const done = await client.execute(sessionId, { command: "revert_task", task: ref, confirmed: true, seen: preview.tasks.map((t) => t.taskId) });
3919
+ console.log(done.message);
3920
+ } catch (err) {
3921
+ console.error(`Failed to revert task: ${err.message}`);
3922
+ process.exit(1);
3923
+ }
3924
+ }
3925
+ );
3926
+ }
3927
+
3873
3928
  // src/commands/add-task.ts
3874
3929
  var import_plan_utils = require("@ordewell/core/plan-utils");
3875
3930
  async function handleAddTask(subArgs, injectedApi) {
@@ -4119,17 +4174,17 @@ async function handleResend(subArgs, injectedApi) {
4119
4174
  }
4120
4175
 
4121
4176
  // src/commands/handoff.ts
4122
- var import_core25 = require("@ordewell/core");
4177
+ var import_core27 = require("@ordewell/core");
4123
4178
 
4124
4179
  // src/tui/transcript.ts
4125
- var import_core23 = require("@ordewell/core");
4180
+ var import_core25 = require("@ordewell/core");
4126
4181
  function show(state, { view: conversation, gate: turnGate }) {
4127
4182
  const gated = turnGate === state.turnGate ? state : { ...state, turnGate };
4128
4183
  if (conversation === state.conversation) return gated;
4129
4184
  return { ...gated, conversation };
4130
4185
  }
4131
4186
  function fold(state, input) {
4132
- return show(state, { view: (0, import_core23.reduceConversation)(state.conversation, input), gate: state.turnGate });
4187
+ return show(state, { view: (0, import_core25.reduceConversation)(state.conversation, input), gate: state.turnGate });
4133
4188
  }
4134
4189
  function say(state, role, text, followTail = true) {
4135
4190
  const said = fold(state, { type: "local_entry", role, text });
@@ -4137,10 +4192,10 @@ function say(state, role, text, followTail = true) {
4137
4192
  return said.conversation.nextId > state.conversation.nextId ? { ...said, scroll: 0 } : said;
4138
4193
  }
4139
4194
  function hear(state, message) {
4140
- return show(state, (0, import_core23.followTurn)(state.conversation, state.turnGate, message));
4195
+ return show(state, (0, import_core25.followTurn)(state.conversation, state.turnGate, message));
4141
4196
  }
4142
4197
  function cutTurn(state) {
4143
- return show(state, (0, import_core23.stopTurn)(state.conversation, state.turnGate));
4198
+ return show(state, (0, import_core25.stopTurn)(state.conversation, state.turnGate));
4144
4199
  }
4145
4200
  function endRunningCompactions(state, outcome) {
4146
4201
  let next = state;
@@ -4158,7 +4213,7 @@ function wiped(view) {
4158
4213
  }
4159
4214
 
4160
4215
  // src/tui/handoff.ts
4161
- var import_core24 = require("@ordewell/core");
4216
+ var import_core26 = require("@ordewell/core");
4162
4217
  var HANDOFF_ACTIONS = [
4163
4218
  { id: "review", label: "Review diff", hint: "the branch against the commit it forked from" },
4164
4219
  { id: "merge", label: "Merge", hint: "into the branch you have checked out (asks first)" },
@@ -4166,7 +4221,7 @@ var HANDOFF_ACTIONS = [
4166
4221
  { id: "cleanup", label: "Clean up", hint: "remove worktrees and task branches, keep this branch" }
4167
4222
  ];
4168
4223
  function handoffActions(handoff) {
4169
- if (!(0, import_core24.isRepoGroup)(handoff)) return HANDOFF_ACTIONS;
4224
+ if (!(0, import_core26.isRepoGroup)(handoff)) return HANDOFF_ACTIONS;
4170
4225
  return [
4171
4226
  { id: "review", label: "Review diff", hint: "each repository's branch against the commit it forked from" },
4172
4227
  { id: "merge", label: "Merge all", hint: "every repository's branch into what you have checked out there (asks first)" },
@@ -4191,14 +4246,14 @@ function handoffCommand(state, arg) {
4191
4246
  }
4192
4247
  function handoffArrived(state, handoff) {
4193
4248
  const landed = handoff.landed.length;
4194
- const where = (0, import_core24.isRepoGroup)(handoff) ? ` in ${reposOf(handoff)}` : "";
4195
- const branch = (0, import_core24.handoffBranchName)(handoff);
4249
+ const where = (0, import_core26.isRepoGroup)(handoff) ? ` in ${reposOf(handoff)}` : "";
4250
+ const branch = (0, import_core26.handoffBranchName)(handoff);
4196
4251
  const tasks = `${landed} task${landed === 1 ? "" : "s"} landed`;
4197
4252
  const received = { ...state, handoff };
4198
4253
  if (handoff.deferredTo === "supervisor") {
4199
4254
  return say(received, "system", `Run finished on ${branch}${where}: ${tasks}. The supervisor may merge it; /handoff to review it yourself.`);
4200
4255
  }
4201
- const returned = state.handoff?.deferredTo === "supervisor" && (0, import_core24.handoffBranchName)(state.handoff) === branch;
4256
+ const returned = state.handoff?.deferredTo === "supervisor" && (0, import_core26.handoffBranchName)(state.handoff) === branch;
4202
4257
  const told = say(
4203
4258
  received,
4204
4259
  "system",
@@ -4215,7 +4270,7 @@ function runHandoffAction(state, id) {
4215
4270
  case "review":
4216
4271
  return { state, effects: [{ type: "isolationReviewDiff", sessionId }] };
4217
4272
  case "cleanup":
4218
- return { state: closed, effects: [{ type: "isolationCleanup", sessionId, branch: (0, import_core24.handoffBranchName)(handoff) }] };
4273
+ return { state: closed, effects: [{ type: "isolationCleanup", sessionId, branch: (0, import_core26.handoffBranchName)(handoff) }] };
4219
4274
  case "merge":
4220
4275
  if (midRun(state)) {
4221
4276
  return {
@@ -4223,8 +4278,8 @@ function runHandoffAction(state, id) {
4223
4278
  ...state,
4224
4279
  overlay: {
4225
4280
  kind: "confirm",
4226
- title: (0, import_core24.isRepoGroup)(handoff) ? "Merge what has landed into your branches?" : "Merge what has landed into your branch?",
4227
- message: `Merge ${(0, import_core24.handoffBranchName)(handoff)} as it stands now into ${(0, import_core24.mergeTarget)(handoff)}.${(0, import_core24.checkoutRule)(handoff)} The run goes on: tasks waiting for Merge all start once it has merged, and later tasks land on the same branch for the next Merge all. A merge that conflicts is aborted, and your ${(0, import_core24.isRepoGroup)(handoff) ? "trees stay" : "tree stays"} as ${(0, import_core24.isRepoGroup)(handoff) ? "they were" : "it was"}.${(0, import_core24.repairedNotice)(handoff)}`,
4281
+ title: (0, import_core26.isRepoGroup)(handoff) ? "Merge what has landed into your branches?" : "Merge what has landed into your branch?",
4282
+ message: `Merge ${(0, import_core26.handoffBranchName)(handoff)} as it stands now into ${(0, import_core26.mergeTarget)(handoff)}.${(0, import_core26.checkoutRule)(handoff)} The run goes on: tasks waiting for Merge all start once it has merged, and later tasks land on the same branch for the next Merge all. A merge that conflicts is aborted, and your ${(0, import_core26.isRepoGroup)(handoff) ? "trees stay" : "tree stays"} as ${(0, import_core26.isRepoGroup)(handoff) ? "they were" : "it was"}.${(0, import_core26.repairedNotice)(handoff)}`,
4228
4283
  action: { kind: "merge-run" }
4229
4284
  }
4230
4285
  },
@@ -4234,15 +4289,15 @@ function runHandoffAction(state, id) {
4234
4289
  return {
4235
4290
  state: {
4236
4291
  ...state,
4237
- overlay: (0, import_core24.isRepoGroup)(handoff) ? {
4292
+ overlay: (0, import_core26.isRepoGroup)(handoff) ? {
4238
4293
  kind: "confirm",
4239
4294
  title: "Merge all into your branches?",
4240
- message: `Merge ${(0, import_core24.handoffBranchName)(handoff)} into ${(0, import_core24.mergeTarget)(handoff)}.${(0, import_core24.checkoutRule)(handoff)} It checks every repository first and merges none unless every repository can take it; on git older than 2.38 it goes repository by repository and stops at the first that fails. A merge that conflicts is aborted, and your trees stay as they were. Once every repository has merged, the run's worktrees and branches are removed unless a task still has work in its worktree that has not landed: then the run stays, and so does that work.${(0, import_core24.repairedNotice)(handoff)}`,
4295
+ message: `Merge ${(0, import_core26.handoffBranchName)(handoff)} into ${(0, import_core26.mergeTarget)(handoff)}.${(0, import_core26.checkoutRule)(handoff)} It checks every repository first and merges none unless every repository can take it; on git older than 2.38 it goes repository by repository and stops at the first that fails. A merge that conflicts is aborted, and your trees stay as they were. Once every repository has merged, the run's worktrees and branches are removed unless a task still has work in its worktree that has not landed: then the run stays, and so does that work.${(0, import_core26.repairedNotice)(handoff)}`,
4241
4296
  action: { kind: "merge-run" }
4242
4297
  } : {
4243
4298
  kind: "confirm",
4244
4299
  title: "Merge into your branch?",
4245
- message: `Merge ${(0, import_core24.handoffBranchName)(handoff)} into ${(0, import_core24.mergeTarget)(handoff)}.${(0, import_core24.checkoutRule)(handoff)} If it conflicts the merge is aborted and your tree stays as it was. Once it has merged, the run's worktrees and branches are removed unless a task still has work in its worktree that has not landed: then the run stays, and so does that work.${(0, import_core24.repairedNotice)(handoff)}`,
4300
+ message: `Merge ${(0, import_core26.handoffBranchName)(handoff)} into ${(0, import_core26.mergeTarget)(handoff)}.${(0, import_core26.checkoutRule)(handoff)} If it conflicts the merge is aborted and your tree stays as it was. Once it has merged, the run's worktrees and branches are removed unless a task still has work in its worktree that has not landed: then the run stays, and so does that work.${(0, import_core26.repairedNotice)(handoff)}`,
4246
4301
  action: { kind: "merge-run" }
4247
4302
  }
4248
4303
  },
@@ -4255,7 +4310,7 @@ function runHandoffAction(state, id) {
4255
4310
  overlay: {
4256
4311
  kind: "confirm",
4257
4312
  title: "Discard this run?",
4258
- message: `Removes the run's worktrees and task branches and deletes ${(0, import_core24.handoffBranchName)(handoff)}${(0, import_core24.isRepoGroup)(handoff) ? ` in ${reposOf(handoff)}` : ""}. Tasks keep their status: mark one not done if you did not keep its work.`,
4313
+ message: `Removes the run's worktrees and task branches and deletes ${(0, import_core26.handoffBranchName)(handoff)}${(0, import_core26.isRepoGroup)(handoff) ? ` in ${reposOf(handoff)}` : ""}. Tasks keep their status: mark one not done if you did not keep its work.`,
4259
4314
  action: { kind: "discard-run" }
4260
4315
  }
4261
4316
  },
@@ -4268,19 +4323,19 @@ function confirmedHandoff(state, kind) {
4268
4323
  const handoff = currentHandoff(state);
4269
4324
  if (!state.sessionId || !handoff) return { state: closed, effects: [] };
4270
4325
  const { sessionId } = state;
4271
- const repaired = (0, import_core24.repairedLanded)(handoff);
4326
+ const repaired = (0, import_core26.repairedLanded)(handoff);
4272
4327
  const effect = kind === "merge-run" ? {
4273
4328
  type: "isolationMerge",
4274
4329
  sessionId,
4275
- branch: (0, import_core24.handoffBranchName)(handoff),
4276
- ...(0, import_core24.isRepoGroup)(handoff) ? { group: true } : {},
4330
+ branch: (0, import_core26.handoffBranchName)(handoff),
4331
+ ...(0, import_core26.isRepoGroup)(handoff) ? { group: true } : {},
4277
4332
  ...repaired.length > 0 ? { repaired } : {}
4278
- } : { type: "isolationDiscard", sessionId, branch: (0, import_core24.handoffBranchName)(handoff) };
4333
+ } : { type: "isolationDiscard", sessionId, branch: (0, import_core26.handoffBranchName)(handoff) };
4279
4334
  return { state: closed, effects: [effect] };
4280
4335
  }
4281
4336
  function showDiff(state, diff) {
4282
4337
  const handoff = currentHandoff(state);
4283
- if (diff.trim() === "") return say(state, "system", `Nothing differs from ${handoff ? (0, import_core24.handoffBaseLine)(handoff, 8) : "the base commit"}.`);
4338
+ if (diff.trim() === "") return say(state, "system", `Nothing differs from ${handoff ? (0, import_core26.handoffBaseLine)(handoff, 8) : "the base commit"}.`);
4284
4339
  const index = state.overlay?.kind === "handoff" ? state.overlay.index : 0;
4285
4340
  const lines = sanitize(diff.replace(/\t/g, " ")).split("\n");
4286
4341
  return { ...state, overlay: { kind: "handoff", index, diff: { lines, scroll: 0 } } };
@@ -4354,11 +4409,11 @@ async function handleHandoff(subArgs, injectedApi, confirm2 = askYesNo) {
4354
4409
  }
4355
4410
  if (!HANDOFF_ACTIONS.some((a) => a.id === action)) fail(`Unknown handoff action "${action}".`, USAGE4);
4356
4411
  const { api, sessionId, plan } = await adopted(subArgs, injectedApi);
4357
- const handoff = (0, import_core25.isolationViewOfPlan)(plan)?.handoff;
4412
+ const handoff = (0, import_core27.isolationViewOfPlan)(plan)?.handoff;
4358
4413
  if (!handoff) fail("This session has no isolated run to hand off.");
4359
- const branch = (0, import_core25.handoffBranchName)(handoff);
4360
- const group = (0, import_core25.isRepoGroup)(handoff);
4361
- if (group) for (const line of (0, import_core25.repoResultLines)(handoff)) console.error(line);
4414
+ const branch = (0, import_core27.handoffBranchName)(handoff);
4415
+ const group = (0, import_core27.isRepoGroup)(handoff);
4416
+ if (group) for (const line of (0, import_core27.repoResultLines)(handoff)) console.error(line);
4362
4417
  const asked = hasFlag(subArgs, "--yes");
4363
4418
  const confirmed = async (question) => {
4364
4419
  if (asked || await confirm2(question)) return;
@@ -4374,12 +4429,12 @@ async function handleHandoff(subArgs, injectedApi, confirm2 = askYesNo) {
4374
4429
  switch (action) {
4375
4430
  case "review": {
4376
4431
  const diff = await attempt(() => api.reviewRunDiff(sessionId));
4377
- console.log(diff.trim() === "" ? `Nothing differs from ${(0, import_core25.handoffBaseLine)(handoff, 8)}.` : diff);
4432
+ console.log(diff.trim() === "" ? `Nothing differs from ${(0, import_core27.handoffBaseLine)(handoff, 8)}.` : diff);
4378
4433
  return;
4379
4434
  }
4380
4435
  case "merge": {
4381
- await confirmed((group ? `Merge ${branch} into ${(0, import_core25.mergeTarget)(handoff)}? Nothing is merged unless every repository can take it${(0, import_core25.checkoutRule)(handoff) ? " and is on that branch" : ""}; once a finished run has merged everything, its worktrees and branches are removed.` : `Merge ${branch} into ${(0, import_core25.mergeTarget)(handoff)}?${(0, import_core25.checkoutRule)(handoff)} Once a finished run has merged everything, its worktrees and branches are removed.`) + (0, import_core25.repairedNotice)(handoff));
4382
- const { ok, message } = (0, import_core25.mergeOutcome)(await attempt(() => api.mergeRun(sessionId)), branch, group, (0, import_core25.repairedLanded)(handoff));
4436
+ await confirmed((group ? `Merge ${branch} into ${(0, import_core27.mergeTarget)(handoff)}? Nothing is merged unless every repository can take it${(0, import_core27.checkoutRule)(handoff) ? " and is on that branch" : ""}; once a finished run has merged everything, its worktrees and branches are removed.` : `Merge ${branch} into ${(0, import_core27.mergeTarget)(handoff)}?${(0, import_core27.checkoutRule)(handoff)} Once a finished run has merged everything, its worktrees and branches are removed.`) + (0, import_core27.repairedNotice)(handoff));
4437
+ const { ok, message } = (0, import_core27.mergeOutcome)(await attempt(() => api.mergeRun(sessionId)), branch, group, (0, import_core27.repairedLanded)(handoff));
4383
4438
  if (ok) console.log(message);
4384
4439
  else fail(message);
4385
4440
  return;
@@ -4397,7 +4452,7 @@ async function handleHandoff(subArgs, injectedApi, confirm2 = askYesNo) {
4397
4452
  }
4398
4453
 
4399
4454
  // src/commands/planner.ts
4400
- var import_core26 = require("@ordewell/core");
4455
+ var import_core28 = require("@ordewell/core");
4401
4456
  async function handlePlanner(subArgs, injectedApi) {
4402
4457
  const api = await connect(subArgs, injectedApi);
4403
4458
  const [wanted] = positionals(subArgs);
@@ -4406,13 +4461,13 @@ async function handlePlanner(subArgs, injectedApi) {
4406
4461
  return;
4407
4462
  }
4408
4463
  const provider = wanted.toLowerCase();
4409
- const meta = (0, import_core26.getProviderMeta)(provider);
4464
+ const meta = (0, import_core28.getProviderMeta)(provider);
4410
4465
  if (!meta) {
4411
4466
  fail(`Unknown planner: ${provider}`, "Run `ordewell planner` with no arguments to see the list.");
4412
4467
  }
4413
- if ((0, import_core26.isCliProvider)(provider)) {
4468
+ if ((0, import_core28.isCliProvider)(provider)) {
4414
4469
  const installed = (await api.getRunners()).runners.map((r) => r.id);
4415
- const refusal = uninstalledPlannerRefusal(provider, installed, import_core26.HEADLESS_CLI_COMMANDS.pickPlanner);
4470
+ const refusal = uninstalledPlannerRefusal(provider, installed, import_core28.HEADLESS_CLI_COMMANDS.pickPlanner);
4416
4471
  if (refusal) fail(refusal);
4417
4472
  }
4418
4473
  const settings = await persistEnv(api, { AI_PROVIDER: provider });
@@ -4420,10 +4475,10 @@ async function handlePlanner(subArgs, injectedApi) {
4420
4475
  writeEnv({ ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
4421
4476
  const catalog = await fetchCatalog(api);
4422
4477
  console.log(
4423
- (0, import_core26.isCliProvider)(provider) ? `Planning with ${meta.label}: no API key needed, it uses that agent's own subscription.` : `Planner set to ${meta.label}.`
4478
+ (0, import_core28.isCliProvider)(provider) ? `Planning with ${meta.label}: no API key needed, it uses that agent's own subscription.` : `Planner set to ${meta.label}.`
4424
4479
  );
4425
4480
  console.log(plannerModelLine(recall2));
4426
- if ((0, import_core26.isApiProvider)(provider) && !catalog.providers.includes(provider)) {
4481
+ if ((0, import_core28.isApiProvider)(provider) && !catalog.providers.includes(provider)) {
4427
4482
  console.log(` No ${meta.apiKeyEnvVar} configured yet: set one with \`ordewell key set ${provider} <key>\`.`);
4428
4483
  }
4429
4484
  }
@@ -4441,15 +4496,15 @@ async function showPlanners(api) {
4441
4496
  const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
4442
4497
  const current = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
4443
4498
  console.log("\nCoding agents (no API key; they use their own subscription)");
4444
- for (const id of import_core26.CLI_PROVIDERS) {
4445
- const runner = (0, import_core26.runnerForProvider)(id);
4499
+ for (const id of import_core28.CLI_PROVIDERS) {
4500
+ const runner = (0, import_core28.runnerForProvider)(id);
4446
4501
  const discovered = runner ? catalog.models.some((m) => m.runners?.includes(runner)) : false;
4447
- console.log(` ${marker(id === current)} ${id.padEnd(14)} ${(0, import_core26.getProviderMeta)(id)?.label ?? id}${discovered ? "" : " (not detected)"}`);
4502
+ console.log(` ${marker(id === current)} ${id.padEnd(14)} ${(0, import_core28.getProviderMeta)(id)?.label ?? id}${discovered ? "" : " (not detected)"}`);
4448
4503
  }
4449
4504
  console.log("\nAPI providers");
4450
- for (const id of import_core26.PROVIDER_PRIORITY) {
4505
+ for (const id of import_core28.PROVIDER_PRIORITY) {
4451
4506
  const configured = catalog.providers.includes(id);
4452
- const meta = (0, import_core26.getProviderMeta)(id);
4507
+ const meta = (0, import_core28.getProviderMeta)(id);
4453
4508
  console.log(` ${marker(id === current)} ${id.padEnd(14)} ${meta.label}${configured ? "" : ` (no ${meta.apiKeyEnvVar})`}`);
4454
4509
  }
4455
4510
  console.log(`
@@ -4466,7 +4521,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
4466
4521
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
4467
4522
  const modelId = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
4468
4523
  const currentEffort = typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : "";
4469
- const runner = (0, import_core26.runnerForProvider)(provider);
4524
+ const runner = (0, import_core28.runnerForProvider)(provider);
4470
4525
  if (!runner) {
4471
4526
  fail(
4472
4527
  "Thinking effort applies to a coding-agent planner.",
@@ -4506,7 +4561,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
4506
4561
  }
4507
4562
 
4508
4563
  // src/commands/parallel.ts
4509
- var import_core27 = require("@ordewell/core");
4564
+ var import_core29 = require("@ordewell/core");
4510
4565
  var USAGE5 = "Usage: ordewell parallel [<number of tasks, 1 or more>]";
4511
4566
  async function handleParallel(subArgs, injectedApi) {
4512
4567
  const [wanted] = positionals(subArgs);
@@ -4516,18 +4571,19 @@ async function handleParallel(subArgs, injectedApi) {
4516
4571
  console.log(`Up to ${String(settings.maxParallel ?? "?")} AI tasks run at once. Change with: ordewell parallel <n>`);
4517
4572
  return;
4518
4573
  }
4519
- const limit = (0, import_core27.parseMaxParallel)(wanted);
4574
+ const limit = (0, import_core29.parseMaxParallel)(wanted);
4520
4575
  if (limit === null) fail(`"${wanted}" is not a number of tasks (1 or more).`, USAGE5);
4521
4576
  await persistEnv(api, { ORDEWELL_MAX_PARALLEL: String(limit) });
4522
4577
  console.log(`Up to ${limit} AI task${limit === 1 ? "" : "s"} now run at once.`);
4523
4578
  }
4524
4579
 
4525
4580
  // src/commands/runSettings.ts
4526
- var import_core28 = require("@ordewell/core");
4581
+ var import_core30 = require("@ordewell/core");
4527
4582
  var SUPERVISOR_USAGE = "Usage: ordewell supervisor [off|watch|act] \xB7 ordewell supervisor retries <n> \xB7 ordewell supervisor reads <n>";
4528
4583
  var AUTOMERGE_USAGE = "Usage: ordewell automerge [on|off]";
4529
4584
  var TRAILER_USAGE = "Usage: ordewell trailer [on|off]";
4530
4585
  var AUTORUN_USAGE = "Usage: ordewell autorun [on|off]";
4586
+ var DELEGATION_USAGE = "Usage: ordewell delegation [off|<tokens>]";
4531
4587
  async function save(api, changes) {
4532
4588
  await api.updateRunSettings(changes);
4533
4589
  }
@@ -4535,28 +4591,28 @@ async function handleSupervisor(subArgs, injectedApi) {
4535
4591
  const [arg, value] = positionals(subArgs);
4536
4592
  let changes = null;
4537
4593
  if (arg === "off" || arg === "watch" || arg === "act") changes = { supervisorLevel: arg };
4538
- else if (arg === "retries") changes = maybe((0, import_core28.parseCount)(value, 0), (n) => ({ supervisorRetryLimit: n }));
4539
- else if (arg === "reads") changes = maybe((0, import_core28.parseCount)(value, 1), (n) => ({ wakeUpReadBudget: n }));
4594
+ else if (arg === "retries") changes = maybe((0, import_core30.parseCount)(value, 0), (n) => ({ supervisorRetryLimit: n }));
4595
+ else if (arg === "reads") changes = maybe((0, import_core30.parseCount)(value, 1), (n) => ({ wakeUpReadBudget: n }));
4540
4596
  if (arg !== void 0 && changes === null) fail(SUPERVISOR_USAGE);
4541
4597
  const api = await connect(subArgs, injectedApi);
4542
4598
  if (!changes) {
4543
- console.log((0, import_core28.supervisorSummary)((await api.getSettings()).run));
4599
+ console.log((0, import_core30.supervisorSummary)((await api.getSettings()).run));
4544
4600
  console.log(` ${SUPERVISOR_USAGE}`);
4545
4601
  return;
4546
4602
  }
4547
4603
  await save(api, changes);
4548
- if (changes.supervisorLevel) console.log(`Supervisor: ${import_core28.SUPERVISOR_LEVEL_LABEL[changes.supervisorLevel]}, for new sessions.`);
4549
- if (changes.supervisorLevel === "act") console.log(import_core28.ACT_RISK);
4604
+ if (changes.supervisorLevel) console.log(`Supervisor: ${import_core30.SUPERVISOR_LEVEL_LABEL[changes.supervisorLevel]}, for new sessions.`);
4605
+ if (changes.supervisorLevel === "act") console.log(import_core30.ACT_RISK);
4550
4606
  if (changes.supervisorRetryLimit !== void 0) console.log(`Supervisor retry limit: ${changes.supervisorRetryLimit} per task.`);
4551
4607
  if (changes.wakeUpReadBudget !== void 0) console.log(`Wake-up read budget: ${changes.wakeUpReadBudget} reads.`);
4552
4608
  }
4553
4609
  async function handleAutomerge(subArgs, injectedApi) {
4554
4610
  const [arg] = positionals(subArgs);
4555
- const enabled = arg === void 0 ? null : (0, import_core28.parseSwitch)(arg);
4611
+ const enabled = arg === void 0 ? null : (0, import_core30.parseSwitch)(arg);
4556
4612
  if (arg !== void 0 && enabled === null) fail(AUTOMERGE_USAGE);
4557
4613
  const api = await connect(subArgs, injectedApi);
4558
4614
  if (enabled === null) {
4559
- console.log((0, import_core28.automergeSummary)((await api.getSettings()).run));
4615
+ console.log((0, import_core30.automergeSummary)((await api.getSettings()).run));
4560
4616
  console.log(` ${AUTOMERGE_USAGE}`);
4561
4617
  return;
4562
4618
  }
@@ -4565,37 +4621,54 @@ async function handleAutomerge(subArgs, injectedApi) {
4565
4621
  }
4566
4622
  async function handleTrailer(subArgs, injectedApi) {
4567
4623
  const [arg] = positionals(subArgs);
4568
- const enabled = arg === void 0 ? null : (0, import_core28.parseSwitch)(arg);
4624
+ const enabled = arg === void 0 ? null : (0, import_core30.parseSwitch)(arg);
4569
4625
  if (arg !== void 0 && enabled === null) fail(TRAILER_USAGE);
4570
4626
  const api = await connect(subArgs, injectedApi);
4571
4627
  if (enabled === null) {
4572
- console.log((0, import_core28.trailerSummary)((await api.getSettings()).run));
4628
+ console.log((0, import_core30.trailerSummary)((await api.getSettings()).run));
4573
4629
  console.log(` ${TRAILER_USAGE}`);
4574
4630
  return;
4575
4631
  }
4576
4632
  await save(api, { plannedWithTrailer: enabled });
4577
- console.log(`${import_core28.PLANNED_WITH_LABEL}: ${enabled ? "on" : "off"}, from each session's next commit.`);
4633
+ console.log(`${import_core30.PLANNED_WITH_LABEL}: ${enabled ? "on" : "off"}, from each session's next commit.`);
4578
4634
  }
4579
4635
  async function handleAutorun(subArgs, injectedApi) {
4580
4636
  const [arg] = positionals(subArgs);
4581
- const enabled = arg === void 0 ? null : (0, import_core28.parseSwitch)(arg);
4637
+ const enabled = arg === void 0 ? null : (0, import_core30.parseSwitch)(arg);
4582
4638
  if (arg !== void 0 && enabled === null) fail(AUTORUN_USAGE);
4583
4639
  const api = await connect(subArgs, injectedApi);
4584
4640
  if (enabled === null) {
4585
- console.log((0, import_core28.autorunSummary)((await api.getSettings()).run));
4641
+ console.log((0, import_core30.autorunSummary)((await api.getSettings()).run));
4586
4642
  console.log(` ${AUTORUN_USAGE}`);
4587
4643
  return;
4588
4644
  }
4589
4645
  await save(api, { autorun: enabled });
4590
4646
  console.log(`Autorun: ${enabled ? "on: each plan the planner submits starts at once" : "off: plans wait for your review"}.`);
4591
- if (enabled) console.log(import_core28.AUTORUN_RISK);
4647
+ if (enabled) console.log(import_core30.AUTORUN_RISK);
4648
+ }
4649
+ async function handleDelegation(subArgs, injectedApi) {
4650
+ const [arg] = positionals(subArgs);
4651
+ const tokens = arg === void 0 ? null : (0, import_core30.parseDelegation)(arg);
4652
+ if (arg !== void 0 && tokens === null) fail(DELEGATION_USAGE);
4653
+ const api = await connect(subArgs, injectedApi);
4654
+ if (tokens === null) {
4655
+ console.log((0, import_core30.delegationSummary)((await api.getSettings()).run));
4656
+ console.log(` ${DELEGATION_USAGE}`);
4657
+ return;
4658
+ }
4659
+ await save(api, { delegationTokens: tokens });
4660
+ console.log(`${(0, import_core30.delegationSummary)({ ...(await api.getSettings()).run, delegationTokens: tokens })}, for new sessions.`);
4661
+ if (tokens > 0) console.log(import_core30.DELEGATION_RISK);
4592
4662
  }
4593
4663
  function maybe(n, wrap2) {
4594
4664
  return n === null ? null : wrap2(n);
4595
4665
  }
4596
4666
 
4667
+ // src/commands/registry.ts
4668
+ var import_core59 = require("@ordewell/core");
4669
+
4597
4670
  // src/commands/key.ts
4598
- var import_core29 = require("@ordewell/core");
4671
+ var import_core31 = require("@ordewell/core");
4599
4672
  var USAGE6 = "Usage: ordewell key [list | set <provider> <key>]";
4600
4673
  async function handleKey(subArgs, injectedApi) {
4601
4674
  const [action, provider, ...keyParts] = positionals(subArgs);
@@ -4606,8 +4679,8 @@ async function handleKey(subArgs, injectedApi) {
4606
4679
  if (action !== "set") {
4607
4680
  const catalog = await fetchCatalog(api);
4608
4681
  console.log("\nAPI provider keys\n");
4609
- for (const id2 of import_core29.PROVIDER_PRIORITY) {
4610
- const meta2 = (0, import_core29.getProviderMeta)(id2);
4682
+ for (const id2 of import_core31.PROVIDER_PRIORITY) {
4683
+ const meta2 = (0, import_core31.getProviderMeta)(id2);
4611
4684
  const configured = catalog.providers.includes(id2);
4612
4685
  console.log(` ${configured ? "\u2713" : " "} ${id2.padEnd(14)} ${meta2.apiKeyEnvVar.padEnd(28)} ${meta2.label}`);
4613
4686
  }
@@ -4617,10 +4690,10 @@ async function handleKey(subArgs, injectedApi) {
4617
4690
  }
4618
4691
  if (!provider) fail(USAGE6);
4619
4692
  const id = provider.toLowerCase();
4620
- if (!(0, import_core29.isApiProvider)(id)) {
4693
+ if (!(0, import_core31.isApiProvider)(id)) {
4621
4694
  fail(`Unknown provider: ${provider}`, "Run `ordewell key` to see the list.");
4622
4695
  }
4623
- const meta = (0, import_core29.getProviderMeta)(id);
4696
+ const meta = (0, import_core31.getProviderMeta)(id);
4624
4697
  const key = keyParts.join(" ").trim();
4625
4698
  if (!key) fail(`Usage: ordewell key set ${id} <key>`);
4626
4699
  await persistEnv(api, { [meta.apiKeyEnvVar]: key });
@@ -4628,7 +4701,7 @@ async function handleKey(subArgs, injectedApi) {
4628
4701
  }
4629
4702
 
4630
4703
  // src/commands/runners.ts
4631
- var import_core30 = require("@ordewell/core");
4704
+ var import_core32 = require("@ordewell/core");
4632
4705
  var USAGE7 = "Usage: ordewell runners [<runner-id> on|off]";
4633
4706
  async function handleRunners(subArgs, injectedApi) {
4634
4707
  const [runner, toggle] = positionals(subArgs);
@@ -4639,7 +4712,7 @@ async function handleRunners(subArgs, injectedApi) {
4639
4712
  for (const r of state2.runners) {
4640
4713
  console.log(` ${r.enabled ? "\u2713" : " "} ${r.id.padEnd(14)} ${r.name}${r.enabled ? "" : " (disabled)"}`);
4641
4714
  }
4642
- const notice = (0, import_core30.codexSandboxNotice)((await fetchCatalog(api).catch(() => null))?.modesByRunner);
4715
+ const notice = (0, import_core32.codexSandboxNotice)((await fetchCatalog(api).catch(() => null))?.modesByRunner);
4643
4716
  if (notice) console.log(`
4644
4717
  ${notice.line}
4645
4718
  ${notice.docs}`);
@@ -4660,17 +4733,17 @@ async function handleRunners(subArgs, injectedApi) {
4660
4733
  var AUTO_USAGE = "Usage: ordewell auto [full|guarded]";
4661
4734
  function handleAuto(subArgs) {
4662
4735
  const [arg] = positionals(subArgs);
4663
- const settings = new import_core30.SettingsService();
4736
+ const settings = new import_core32.SettingsService();
4664
4737
  const current = settings.autonomousMode();
4665
4738
  if (!arg) {
4666
- console.log(`Autonomy level: ${(0, import_core30.autonomyLevelLabel)(current)}`);
4739
+ console.log(`Autonomy level: ${(0, import_core32.autonomyLevelLabel)(current)}`);
4667
4740
  console.log(` ${AUTO_USAGE}`);
4668
4741
  return;
4669
4742
  }
4670
- const enabled = (0, import_core30.parseAutonomyLevel)(arg);
4743
+ const enabled = (0, import_core32.parseAutonomyLevel)(arg);
4671
4744
  if (enabled === null) fail(AUTO_USAGE);
4672
4745
  settings.setAutonomousMode(enabled);
4673
- console.log(settings.autonomousModeOverridden() ? `${(0, import_core30.autonomyLevelLabel)(enabled)} is saved, but ORDEWELL_AUTONOMOUS_MODE in the environment decides the level until it is unset.` : `Autonomy level: ${(0, import_core30.autonomyLevelLabel)(enabled)} for new plans.`);
4746
+ console.log(settings.autonomousModeOverridden() ? `${(0, import_core32.autonomyLevelLabel)(enabled)} is saved, but ORDEWELL_AUTONOMOUS_MODE in the environment decides the level until it is unset.` : `Autonomy level: ${(0, import_core32.autonomyLevelLabel)(enabled)} for new plans.`);
4674
4747
  }
4675
4748
  async function handleRefresh(subArgs, injectedApi) {
4676
4749
  const api = await connect(subArgs, injectedApi);
@@ -4701,7 +4774,7 @@ async function handleApprove(subArgs, injectedApi) {
4701
4774
  }
4702
4775
 
4703
4776
  // src/commands/task-assign.ts
4704
- var import_core32 = require("@ordewell/core");
4777
+ var import_core34 = require("@ordewell/core");
4705
4778
  var import_plan_utils4 = require("@ordewell/core/plan-utils");
4706
4779
 
4707
4780
  // src/tui/taskAssignment.ts
@@ -4743,7 +4816,7 @@ function modesForTask(modesByRunner, task) {
4743
4816
 
4744
4817
  // src/tui/state.ts
4745
4818
  var import_plan_utils3 = require("@ordewell/core/plan-utils");
4746
- var import_core31 = require("@ordewell/core");
4819
+ var import_core33 = require("@ordewell/core");
4747
4820
 
4748
4821
  // src/tui/editor.ts
4749
4822
  function emptyEditor() {
@@ -4904,7 +4977,6 @@ function queuedTarget(tv) {
4904
4977
  for (let i = queued.length - 1; i >= 0; i--) if (!queued[i].forced) return i;
4905
4978
  return -1;
4906
4979
  }
4907
- var anyTaskRunning = (state) => state.tasks.some(import_core31.isTaskRunning);
4908
4980
  var plannerInFlight = (state) => state.planner !== "idle";
4909
4981
  var planPaneOpen = (state) => state.focus === "plan" && !state.overlay && (!!state.taskEditor || !!state.expandedTaskId);
4910
4982
  function planRows(state) {
@@ -4929,10 +5001,10 @@ function waitingCheckpoint(task) {
4929
5001
  return task?.status === "awaiting_user" && task.awaitingReason === "checkpoint" ? task.checkpoint : void 0;
4930
5002
  }
4931
5003
  function continuesTask(task) {
4932
- return task ? (0, import_core31.logContinues)(task) : false;
5004
+ return task ? (0, import_core33.logContinues)(task) : false;
4933
5005
  }
4934
5006
  function resumesTask(task) {
4935
- return task ? (0, import_core31.logResumes)(task) : false;
5007
+ return task ? (0, import_core33.logResumes)(task) : false;
4936
5008
  }
4937
5009
  function findTask(tasks, id) {
4938
5010
  return (0, import_plan_utils3.taskPlacement)(tasks, id)?.task;
@@ -4941,10 +5013,10 @@ function scriptTaskView(state) {
4941
5013
  return state.taskView ? findTask(state.tasks, state.taskView.taskId)?.type === "script" : false;
4942
5014
  }
4943
5015
  function refFor(state, task) {
4944
- return (0, import_core31.taskRefOf)(state.tasks, task.id) ?? (0, import_core31.taskRef)(task);
5016
+ return (0, import_core33.taskRefOf)(state.tasks, task.id) ?? (0, import_core33.taskRef)(task);
4945
5017
  }
4946
5018
  function titledRefFor(state, task) {
4947
- return (0, import_core31.titledTaskRefOf)(state.tasks, task.id) ?? (0, import_core31.titledTaskRef)(task);
5019
+ return (0, import_core33.titledTaskRefOf)(state.tasks, task.id) ?? (0, import_core33.titledTaskRef)(task);
4948
5020
  }
4949
5021
  function asRunTasks(tasks) {
4950
5022
  return tasks.map((task) => ({ ...task, subtasks: asRunTasks(task.subtasks ?? []) }));
@@ -4952,7 +5024,7 @@ function asRunTasks(tasks) {
4952
5024
  function seedRun(overrides) {
4953
5025
  if (overrides.run) return overrides.run;
4954
5026
  return {
4955
- ...import_core31.EMPTY_RUN,
5027
+ ...import_core33.EMPTY_RUN,
4956
5028
  tasks: asRunTasks(overrides.tasks ?? []),
4957
5029
  handoff: overrides.handoff ?? null,
4958
5030
  runMerge: overrides.runMerge ? { waitingAtGate: overrides.runMerge.waitingAtGate, paused: overrides.runMerge.paused, repos: overrides.runMerge.handoff.repos, landed: overrides.runMerge.handoff.landed } : null
@@ -4961,11 +5033,11 @@ function seedRun(overrides) {
4961
5033
  function initialState(overrides = {}) {
4962
5034
  return {
4963
5035
  editor: emptyEditor(),
4964
- conversation: import_core31.EMPTY_CONVERSATION,
5036
+ conversation: import_core33.EMPTY_CONVERSATION,
4965
5037
  accountLimits: [],
4966
5038
  servedModels: {},
4967
5039
  taskView: null,
4968
- turnGate: import_core31.NO_TURN,
5040
+ turnGate: import_core33.NO_TURN,
4969
5041
  detailAll: false,
4970
5042
  status: "idle",
4971
5043
  planner: "idle",
@@ -4991,16 +5063,17 @@ function initialState(overrides = {}) {
4991
5063
  plannerProvider: "",
4992
5064
  plannerEffort: "",
4993
5065
  plannerModels: {},
4994
- maxParallel: import_core31.DEFAULT_MAX_PARALLEL,
5066
+ maxParallel: import_core33.DEFAULT_MAX_PARALLEL,
4995
5067
  configuredProviders: [],
4996
5068
  allowlist: {},
4997
5069
  autonomous: false,
4998
- runSettings: { ...import_core31.RUN_SETTINGS_DEFAULTS },
5070
+ runSettings: { ...import_core33.RUN_SETTINGS_DEFAULTS },
4999
5071
  runSettingsKnown: false,
5000
5072
  settingsRead: 0,
5001
5073
  runSettingsRead: 0,
5002
5074
  plannerKnown: false,
5003
5075
  runnersKnown: false,
5076
+ plannerDetecting: false,
5004
5077
  providersKnown: false,
5005
5078
  isolationPreview: void 0,
5006
5079
  isolationPreviewRead: 0,
@@ -5016,7 +5089,7 @@ function initialState(overrides = {}) {
5016
5089
  plannerPause: null,
5017
5090
  overlay: null,
5018
5091
  pendingApprovals: [],
5019
- queuedPrompts: import_core31.EMPTY_HOLD,
5092
+ queuedPrompts: import_core33.EMPTY_HOLD,
5020
5093
  stopArmed: false,
5021
5094
  stopArmToken: 0,
5022
5095
  stopRequested: false,
@@ -5026,6 +5099,7 @@ function initialState(overrides = {}) {
5026
5099
  cols: 80,
5027
5100
  exiting: false,
5028
5101
  spinnerFrame: 0,
5102
+ now: 0,
5029
5103
  ...overrides,
5030
5104
  run: seedRun(overrides)
5031
5105
  };
@@ -5152,7 +5226,7 @@ Mode \xB7 ${namedOf(tasks, task)}${task.assignedRunner ? ` (${task.assignedRunne
5152
5226
  for (const m of modes) {
5153
5227
  console.log(` ${m.id === task.taskMode ? "*" : " "} ${m.id.padEnd(14)} ${m.label}${m.unavailable ? ": unavailable on this host" : m.description ? `: ${m.description}` : ""}`);
5154
5228
  }
5155
- const notice = (0, import_core32.codexSandboxNotice)(catalog.modesByRunner);
5229
+ const notice = (0, import_core34.codexSandboxNotice)(catalog.modesByRunner);
5156
5230
  if (notice) console.log(`
5157
5231
  ${notice.line}
5158
5232
  ${notice.docs}`);
@@ -5202,7 +5276,7 @@ var SKILLS_USAGE = "Usage: ordewell task-skills <task-id-or-order> [<name> [<nam
5202
5276
  async function handleTaskSkills(subArgs, injectedApi) {
5203
5277
  await withTask(subArgs, SKILLS_USAGE, injectedApi, async (api, sessionId, task, tasks, value) => {
5204
5278
  if (task.type !== "ai") fail("Manual tasks do not run an executor, so they take no skills.");
5205
- const catalog = (0, import_core32.createSkillsService)(flag(subArgs, "--workspace") || process.cwd(), new import_core32.EnvConfig().workspaceRepos).listSkills().filter((s) => s.appliesTo === "task");
5279
+ const catalog = (0, import_core34.createSkillsService)(flag(subArgs, "--workspace") || process.cwd(), new import_core34.EnvConfig().workspaceRepos).listSkills().filter((s) => s.appliesTo === "task");
5206
5280
  if (!value) {
5207
5281
  console.log(`
5208
5282
  Skills \xB7 ${namedOf(tasks, task)}
@@ -5249,10 +5323,10 @@ ${namedOf(tasks, task)} is ${task.ops ? "an ops task: it runs in your checkout o
5249
5323
  // src/tui/index.ts
5250
5324
  var import_child_process4 = require("child_process");
5251
5325
  var import_path7 = require("path");
5252
- var import_core55 = require("@ordewell/core");
5326
+ var import_core58 = require("@ordewell/core");
5253
5327
 
5254
5328
  // src/tui/slash.ts
5255
- var import_core33 = require("@ordewell/core");
5329
+ var import_core35 = require("@ordewell/core");
5256
5330
  var SLASH_COMMANDS = [
5257
5331
  // Planning. There is deliberately no `/plan`: typing the goal *is* the way to
5258
5332
  // start planning, and an alias for it only shadowed `/planner` on completion.
@@ -5265,6 +5339,7 @@ var SLASH_COMMANDS = [
5265
5339
  { name: "add-task", usage: "/add-task <title>", description: "Add a task to the current plan", category: "tasks" },
5266
5340
  { name: "remove-task", usage: "/remove-task <id>", description: "Remove a task from the plan", category: "tasks" },
5267
5341
  { name: "complete", usage: "/complete <id>", description: "Mark a task complete (asks first)", category: "tasks" },
5342
+ { name: "revert", usage: "/revert <id>", description: "Take a task's landed work back off the integration branch (asks first)", category: "tasks" },
5268
5343
  { name: "uncomplete", usage: "/uncomplete <id>", description: "Mark a completed task not done (asks first)", category: "tasks" },
5269
5344
  { name: "skip", usage: "/skip <id>", description: "Skip a task", category: "tasks" },
5270
5345
  { name: "retry", usage: "/retry <id>", description: "Re-run a failed task", category: "tasks" },
@@ -5273,6 +5348,7 @@ var SLASH_COMMANDS = [
5273
5348
  { name: "continue", usage: "/continue <id> <message>", description: "Continue a finished task in its saved runner session, with your message", category: "tasks" },
5274
5349
  { name: "open", usage: "/open <id>", description: "Open a started task's chat: its view, and the keys that answer its checkpoint or tool request", category: "tasks" },
5275
5350
  { name: "checkpoint", usage: "/checkpoint <id> approve|reject [reason]", description: "Answer the checkpoint a task waits at: approve lets it go on, reject tells it why not", category: "tasks" },
5351
+ { name: "inbox", usage: "/inbox", description: "List everything waiting on you, and answer it or open its task", category: "tasks" },
5276
5352
  { name: "task-runner", usage: "/task-runner <id> [runner]", description: "Choose or set a task's executor (re-picks its model and mode)", category: "tasks" },
5277
5353
  { name: "task-model", usage: "/task-model <id> [model]", description: "Choose or set a task's executor model", category: "tasks" },
5278
5354
  { name: "task-effort", usage: "/task-effort <id> [level]", description: "Choose or set a task's thinking effort", category: "tasks" },
@@ -5286,7 +5362,7 @@ var SLASH_COMMANDS = [
5286
5362
  { name: "allowlist", usage: "/allowlist [set <runner> <ids> | clear <runner>]", description: "Limit which models a runner may use", category: "models" },
5287
5363
  { name: "runners", usage: "/runners [<id> on|off]", description: `Enable or disable runners (${runnerIdList()})`, category: "models" },
5288
5364
  { name: "login", usage: "/login [<runner>]", description: "Sign the active coding agent in, or the one named, in this terminal", category: "models" },
5289
- { name: "settings", usage: "/settings", description: "Run settings: autonomy, supervisor, merging, autorun and parallel tasks", category: "models" },
5365
+ { name: "settings", usage: "/settings", description: `Run settings: autonomy, supervisor, merging, autorun, parallel tasks${import_core35.DELEGATION_OFFERED ? " and delegation" : ""}`, category: "models" },
5290
5366
  { name: "refresh", usage: "/refresh", description: "Re-check runners; reread settings and cached models", category: "models" },
5291
5367
  // Sessions
5292
5368
  { name: "sessions", usage: "/sessions", description: "List saved sessions", category: "session" },
@@ -5386,7 +5462,7 @@ function parseRunnerCommand(text) {
5386
5462
  return match ? { name: match[1], args: (match[2] ?? "").trim() } : null;
5387
5463
  }
5388
5464
  function runnerCommandFor(commands, name) {
5389
- return (0, import_core33.findRunnerCommand)(commands, name) ?? (0, import_core33.findRunnerCommand)(commands, name.toLowerCase());
5465
+ return (0, import_core35.findRunnerCommand)(commands, name) ?? (0, import_core35.findRunnerCommand)(commands, name.toLowerCase());
5390
5466
  }
5391
5467
  function composerCompletions(token, runner) {
5392
5468
  if (runner) return token.start === 0 ? runner.filter((c) => c.name.toLowerCase().startsWith(token.name)).map((c) => c.name) : [];
@@ -5398,7 +5474,7 @@ function tokenCompletions(token) {
5398
5474
  }
5399
5475
 
5400
5476
  // src/tui/accountLimits.ts
5401
- var import_core34 = require("@ordewell/core");
5477
+ var import_core36 = require("@ordewell/core");
5402
5478
  function assignmentKey(task) {
5403
5479
  return `${task.assignedRunner}\0${task.assignedModel?.modelId ?? ""}`;
5404
5480
  }
@@ -5412,7 +5488,7 @@ function observeTaskServedModel(state, action) {
5412
5488
  state.taskView?.taskId === task.id ? state.taskView.attempts.at(-1) ?? 0 : 0
5413
5489
  );
5414
5490
  if (action.attempt < latest) return state;
5415
- const model = (0, import_core34.lastServedModel)(action.events);
5491
+ const model = (0, import_core36.lastServedModel)(action.events);
5416
5492
  if (!model && action.attempt === before?.attempt) return state;
5417
5493
  const served = { attempt: action.attempt, ...model ? { model, assignment: assignmentKey(task) } : {} };
5418
5494
  return { ...state, servedModels: { ...state.servedModels, [task.id]: served } };
@@ -5474,10 +5550,10 @@ function chatEditorRoomFor(state, active) {
5474
5550
  }
5475
5551
 
5476
5552
  // src/tui/layout.ts
5477
- var import_core37 = require("@ordewell/core");
5553
+ var import_core39 = require("@ordewell/core");
5478
5554
 
5479
5555
  // src/tui/reducers/pickers.ts
5480
- var import_core35 = require("@ordewell/core");
5556
+ var import_core37 = require("@ordewell/core");
5481
5557
  var import_plan_utils5 = require("@ordewell/core/plan-utils");
5482
5558
  var DEFAULT_EFFORT = "__runner_default__";
5483
5559
  function picker(title, items, action, extra = {}) {
@@ -5502,13 +5578,13 @@ function modelDetail(m) {
5502
5578
  return [m.provider, m.pricing].filter(Boolean).join(" \xB7 ") || void 0;
5503
5579
  }
5504
5580
  function plannerModelItems(state, provider, selectedId) {
5505
- const runner = (0, import_core35.runnerForProvider)(provider);
5581
+ const runner = (0, import_core37.runnerForProvider)(provider);
5506
5582
  if (!runner) {
5507
5583
  const failed = Object.entries(state.providerErrors ?? {});
5508
5584
  const unavailable = failed.length === 0 ? [] : [{
5509
5585
  id: "",
5510
5586
  label: "Some providers are unavailable",
5511
- detail: `\u26A0 ${failed.map(([id]) => (0, import_core35.getProviderMeta)(id)?.label ?? id).join(", ")}: key or fetch failed`,
5587
+ detail: `\u26A0 ${failed.map(([id]) => (0, import_core37.getProviderMeta)(id)?.label ?? id).join(", ")}: key or fetch failed`,
5512
5588
  disabled: true
5513
5589
  }];
5514
5590
  return [
@@ -5528,11 +5604,11 @@ function plannerModelItems(state, provider, selectedId) {
5528
5604
  return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
5529
5605
  }
5530
5606
  function plannerHarnessModel(state, provider, modelId) {
5531
- const runner = (0, import_core35.runnerForProvider)(provider);
5607
+ const runner = (0, import_core37.runnerForProvider)(provider);
5532
5608
  return runner ? state.models.find((m) => m.id === modelId && m.runners?.includes(runner)) : void 0;
5533
5609
  }
5534
5610
  function plannerEffortItems(state, provider, modelId, selectedEffort) {
5535
- if (!(0, import_core35.runnerForProvider)(provider)) {
5611
+ if (!(0, import_core37.runnerForProvider)(provider)) {
5536
5612
  return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
5537
5613
  }
5538
5614
  const model = plannerHarnessModel(state, provider, modelId);
@@ -5644,12 +5720,12 @@ function pickerItemsFor(state, action) {
5644
5720
  function plannerItems(state) {
5645
5721
  const installed = new Map(state.runners.map((runner) => [runner.id, runner]));
5646
5722
  const usability = {};
5647
- for (const id of import_core35.CLI_PROVIDERS) {
5648
- const runner = (0, import_core35.runnerForProvider)(id);
5723
+ for (const id of import_core37.CLI_PROVIDERS) {
5724
+ const runner = (0, import_core37.runnerForProvider)(id);
5649
5725
  const known = installed.get(runner);
5650
5726
  usability[runner] = known ? { usable: true, ...known.auth ? { auth: known.auth } : {} } : { usable: false, reason: "CLI not installed or not on PATH" };
5651
5727
  }
5652
- const offered2 = (0, import_core35.plannerBackendEntries)(usability, state.configuredProviders).filter((entry) => entry.usable);
5728
+ const offered2 = (0, import_core37.plannerBackendEntries)(usability, state.configuredProviders).filter((entry) => entry.usable);
5653
5729
  if (offered2.length === 0) {
5654
5730
  return [{ id: "", label: "No planner available", detail: `install a coding agent (${runnerNamesOr()}) or add a key with /key`, disabled: true }];
5655
5731
  }
@@ -5680,7 +5756,7 @@ function cliVersion() {
5680
5756
  }
5681
5757
 
5682
5758
  // src/tui/planKeys.ts
5683
- var import_core36 = require("@ordewell/core");
5759
+ var import_core38 = require("@ordewell/core");
5684
5760
  var PLAN_KEYS = {
5685
5761
  move: { keys: "up / down", meaning: "move between tasks", group: "Navigation" },
5686
5762
  scroll: { keys: "pgup / pgdn", meaning: "scroll the plan", group: "Navigation" },
@@ -5704,16 +5780,16 @@ var PLAN_KEYS = {
5704
5780
  meaning: "start the task, approving it if it waits for you; it starts once its dependencies finish",
5705
5781
  group: "Run",
5706
5782
  // Added after the plan was approved, it waits for this key; the row says the same.
5707
- hint: (task) => task && (0, import_core36.taskRowView)(task).waitsForApproval ? "f approve" : "f start"
5783
+ hint: (task) => task && (0, import_core38.taskRowView)(task).waitsForApproval ? "f approve" : "f start"
5708
5784
  },
5709
5785
  runPlan: { keys: "E", meaning: "run the whole plan", group: "Run", hint: "E run plan" },
5710
5786
  stop: { keys: "S", meaning: "stop the run", group: "Run", hint: "S stop" },
5711
5787
  cancel: {
5712
5788
  keys: "c",
5713
- meaning: "cancel the task's runner, or keep a task paused on a usage limit from resuming on its own",
5789
+ meaning: "cancel the task's runner, withdraw a conflict repair waiting to start, or keep a task paused on a usage limit from resuming on its own",
5714
5790
  group: "Run",
5715
5791
  // `c` cancels the resume while the task has one; it has no runner to cancel then.
5716
- hint: (task) => task && (0, import_core36.resumeLabel)(task) ? "c don't resume" : "c cancel"
5792
+ hint: (task) => task && (0, import_core38.resumeLabel)(task) ? "c don't resume" : task && (0, import_core38.taskRowView)(task).repairWaiting ? "c stop repair" : "c cancel"
5717
5793
  },
5718
5794
  resume: { keys: "r", meaning: "resume a task paused on a usage limit now, once the limit has reset", group: "Run", hint: "r resume now" },
5719
5795
  resolve: { keys: "x", meaning: "resolve a merge conflict", group: "Run", hint: "x resolve conflict" },
@@ -5723,7 +5799,7 @@ var PLAN_KEYS = {
5723
5799
  meaning: "mark the task done, or take it back",
5724
5800
  group: "Status",
5725
5801
  // `m` toggles, so the hint names the direction it will go for this task.
5726
- hint: (task) => task && (0, import_core36.markAction)(task) === "uncomplete" ? "m undone" : "m done"
5802
+ hint: (task) => task && (0, import_core38.markAction)(task) === "uncomplete" ? "m undone" : "m done"
5727
5803
  },
5728
5804
  skip: { keys: "s", meaning: "skip the task: none of its work lands, and what depends on it goes ahead", group: "Status", hint: "s skip" },
5729
5805
  remove: { keys: "d", meaning: "remove the task", group: "Status" },
@@ -5750,9 +5826,10 @@ function hintOf(id, task) {
5750
5826
  }
5751
5827
  function taskKeyIds(task, runActive) {
5752
5828
  if (!task) return ["add"];
5753
- const row = (0, import_core36.taskRowView)(task);
5829
+ const row = (0, import_core38.taskRowView)(task);
5754
5830
  const chat = (...ids) => row.chatAvailable ? ids : ids.filter((id) => id !== "log");
5755
- if (row.conflict) return chat("resolve", "log");
5831
+ if (row.conflict) return row.conflict.resolver ? chat("log") : chat("resolve", "log");
5832
+ if (row.repairWaiting) return chat("cancel", "log");
5756
5833
  if (row.actions.includes("resume-now") || row.resumes) {
5757
5834
  return chat(...row.actions.includes("resume-now") ? ["resume"] : [], ...row.resumes ? ["cancel"] : [], "skip", "log");
5758
5835
  }
@@ -5780,7 +5857,7 @@ function planFooterHints(task, runActive) {
5780
5857
  }
5781
5858
 
5782
5859
  // src/tui/layout.ts
5783
- var import_core38 = require("@ordewell/core");
5860
+ var import_core40 = require("@ordewell/core");
5784
5861
  var CHROME_ROWS = 1;
5785
5862
  var COMPOSER_WINDOW_CHARS = 8 * 1024;
5786
5863
  var COMPOSER_BAND_ROWS = 2;
@@ -5827,7 +5904,7 @@ function footerHints(state) {
5827
5904
  }
5828
5905
  return [...escHint ? [escHint] : [], ...planFooterHints(selected, state.status === "executing")];
5829
5906
  }
5830
- const detailHint = (0, import_core38.hasHiddenDetail)(state.taskView ? state.taskView.view.blocks : state.conversation.blocks) ? [state.detailAll ? "ctrl-o collapse all" : "ctrl-o expand all"] : [];
5907
+ const detailHint = (0, import_core40.hasHiddenDetail)(state.taskView ? state.taskView.view.blocks : state.conversation.blocks) ? [state.detailAll ? "ctrl-o collapse all" : "ctrl-o expand all"] : [];
5831
5908
  return [
5832
5909
  "/help",
5833
5910
  ...checkpointHints,
@@ -5902,12 +5979,13 @@ function logHeaderTask(task) {
5902
5979
  resumeAt: task?.resumeAt ?? null,
5903
5980
  idleSince: task?.idleSince ?? null,
5904
5981
  continuable: task?.continuable === true,
5982
+ ...task?.isolation?.state === "conflict" && task.isolation.repairWaits ? { repairWaits: task.isolation.repairWaits } : {},
5905
5983
  attemptUsage: task?.attemptUsage ?? []
5906
5984
  };
5907
5985
  }
5908
5986
  function logHeader(state, tv) {
5909
5987
  const task = findTask(state.tasks, tv.taskId);
5910
- return (0, import_core38.taskLogHeaderView)(logHeaderTask(task), tv, task?.awaitingApproval ?? 0, tv.attempt);
5988
+ return (0, import_core40.taskLogHeaderView)(logHeaderTask(task), tv, task?.awaitingApproval ?? 0, tv.attempt);
5911
5989
  }
5912
5990
  function taskHeaderLines(state, tv, cols) {
5913
5991
  const task = findTask(state.tasks, tv.taskId);
@@ -5915,7 +5993,7 @@ function taskHeaderLines(state, tv, cols) {
5915
5993
  const parts = [
5916
5994
  `\u2192 Task ${task ? refFor(state, task).slice(1) : "?"}`,
5917
5995
  task?.title ?? "(task removed)",
5918
- (task && (0, import_core38.assignedRunnerOf)(task)) ?? "",
5996
+ (task && (0, import_core40.assignedRunnerOf)(task)) ?? "",
5919
5997
  task ? inRow(header.state.label) : "",
5920
5998
  header.resumes ? inRow(header.resumes) : ""
5921
5999
  ].filter(Boolean);
@@ -5933,31 +6011,44 @@ function taskHeaderLines(state, tv, cols) {
5933
6011
  function stopHint(state) {
5934
6012
  return plannerInFlight(state) && state.stopArmed ? "Press Esc again to stop" : "";
5935
6013
  }
5936
- var DOT_TICKS = 4;
5937
6014
  var SPINNER_CYCLE = 40;
5938
6015
  function runnerStoppable(state) {
5939
6016
  const tv = state.taskView;
5940
- if (!tv || !chatActivity(state)?.busy) return false;
6017
+ const activity = chatActivity(state);
6018
+ if (!tv || !(activity?.busy || activity?.quiet)) return false;
5941
6019
  if (scriptTaskView(state)) return false;
5942
6020
  const task = findTask(state.tasks, tv.taskId);
5943
6021
  return !task?.awaitingApproval && !waitingApproval(tv);
5944
6022
  }
6023
+ function animating(state) {
6024
+ return state.tasks.some((t) => (0, import_core40.taskStatusKind)(t) === "running") || !!chatActivity(state)?.busy;
6025
+ }
6026
+ function activityTime(state, now) {
6027
+ return (0, import_core40.elapsedSince)(chatActivity(state)?.since, now);
6028
+ }
5945
6029
  function chatActivity(state) {
5946
6030
  const tv = state.taskView;
5947
6031
  if (!tv) {
5948
6032
  if (state.status === "starting") return { label: "Starting the plan\u2026", busy: true };
5949
6033
  if (!plannerInFlight(state)) return null;
5950
- const label = state.planner === "condensing" ? "Condensing\u2026" : state.planner === "compacting" ? "Compacting\u2026" : state.planner === "awaiting" ? "Waiting for the planner\u2026" : "Planning\u2026";
5951
- return { label, busy: true };
6034
+ const label = state.plannerDetecting ? DETECTING : state.planner === "condensing" ? "Condensing\u2026" : state.planner === "compacting" ? "Compacting\u2026" : state.planner === "awaiting" ? "Waiting for the planner\u2026" : "Planning\u2026";
6035
+ const since = state.turnGate.open !== null ? state.turnGate.openedAt : void 0;
6036
+ return since ? { label, busy: true, since } : { label, busy: true };
5952
6037
  }
5953
6038
  const own = findTask(state.tasks, tv.taskId);
5954
6039
  const task = state.tasks.find((t) => t.subtasks?.some((s) => s.id === tv.taskId)) ?? own;
5955
6040
  if (!task) return null;
5956
- const attaching = (0, import_core38.attachingLabel)(task);
6041
+ const attaching = (0, import_core40.attachingLabel)(task);
5957
6042
  if (attaching) return { label: `${attaching}\u2026`, busy: true };
5958
- if ((0, import_core31.isTaskRunning)(task) || own && (0, import_core31.isTaskRunning)(own)) return { label: "Working\u2026", busy: true };
6043
+ if ((0, import_core33.isTaskRunning)(task) || own && (0, import_core33.isTaskRunning)(own)) {
6044
+ const running2 = (0, import_core33.isTaskRunning)(task) ? task : own;
6045
+ const archived = tv.attempts.length > 0 && tv.attempt < Math.max(...tv.attempts);
6046
+ const activity = running2 && (0, import_core40.taskActivity)({ ...tv.view, archived }, running2);
6047
+ if (!activity) return null;
6048
+ return activity.busy ? activity : { ...activity, quiet: true };
6049
+ }
5959
6050
  const at = task.status === "awaiting_user" && task.awaitingReason === "usage-limit" && task.resumeAt ? new Date(task.resumeAt) : null;
5960
- if (at && !Number.isNaN(at.getTime())) return { label: `paused \xB7 resumes ${(0, import_core38.resumeTime)(at)}`, busy: false };
6051
+ if (at && !Number.isNaN(at.getTime())) return { label: `paused \xB7 resumes ${(0, import_core40.resumeTime)(at)}`, busy: false };
5961
6052
  return null;
5962
6053
  }
5963
6054
  function runHints(state) {
@@ -5967,6 +6058,10 @@ function runHints(state) {
5967
6058
  return state.runMerge?.paused ? ["paused for Merge all: /handoff merge", "paused \xB7 /handoff merge", "merge paused"] : [];
5968
6059
  }
5969
6060
  var runHint = (state) => runHints(state)[0] ?? "";
6061
+ function inboxHint(state) {
6062
+ const waiting = state.run.escalations?.length ?? 0;
6063
+ return waiting > 0 ? `${waiting} waiting: /inbox` : "";
6064
+ }
5970
6065
  function packHints(hints, cols) {
5971
6066
  const lines = [];
5972
6067
  let current = "";
@@ -6016,7 +6111,7 @@ function plannerResendRow(state, cols) {
6016
6111
  const at = plannerResend ? new Date(plannerResend.at) : null;
6017
6112
  if (!plannerResend || !at || Number.isNaN(at.getTime())) return [];
6018
6113
  const whose = plannerResend.runner ? `${plannerResend.runner}'s` : "the planner's";
6019
- return [style.yellow(truncate(`\u23F8 Resending at ${(0, import_core38.resumeTime)(at)}, once ${whose} usage limit resets \xB7 /resend cancel keeps it for your next message`, cols))];
6114
+ return [style.yellow(truncate(`\u23F8 Resending at ${(0, import_core40.resumeTime)(at)}, once ${whose} usage limit resets \xB7 /resend cancel keeps it for your next message`, cols))];
6020
6115
  }
6021
6116
  function plannerPauseRow(state, cols) {
6022
6117
  const { plannerPause } = state;
@@ -6024,18 +6119,18 @@ function plannerPauseRow(state, cols) {
6024
6119
  return [style.yellow(truncate(`\u23F8 Planner paused: its context does not fit ${plannerPause.model}'s window \xB7 /compact retry attempts recovery \xB7 /stop drops the turn`, cols))];
6025
6120
  }
6026
6121
  var tokenRow = (state, blocks2, cols) => {
6027
- const runner = (0, import_core38.runnerForProvider)(state.plannerProvider);
6028
- const limits = (0, import_core38.accountLimitsOf)(state.accountLimits, runner ?? void 0, state.orchestratorModel);
6122
+ const runner = (0, import_core40.runnerForProvider)(state.plannerProvider);
6123
+ const limits = (0, import_core40.accountLimitsOf)(state.accountLimits, runner ?? void 0, state.orchestratorModel);
6029
6124
  const line = tokenLine(blocks2, cols, limits);
6030
6125
  return line ? [line] : [];
6031
6126
  };
6032
6127
  function taskUsageFooter(state, tv, cols) {
6033
6128
  const task = findTask(state.tasks, tv.taskId);
6034
- const limits = (0, import_core38.accountLimitsOf)(state.accountLimits, task && (0, import_core38.assignedRunnerOf)(task), task && taskServedModel(state, task)).map(import_core38.accountLimitsText).filter(Boolean).join(" \xB7 ");
6129
+ const limits = (0, import_core40.accountLimitsOf)(state.accountLimits, task && (0, import_core40.assignedRunnerOf)(task), task && taskServedModel(state, task)).map(import_core40.accountLimitsText).filter(Boolean).join(" \xB7 ");
6035
6130
  const rows = logHeader(state, tv).usage;
6036
6131
  const text = rows.map((row, index) => {
6037
6132
  if (!limits || index !== rows.length - 1) return row.text;
6038
- const context = (0, import_core38.contextMeterText)(row.contextFill);
6133
+ const context = (0, import_core40.contextMeterText)(row.contextFill);
6039
6134
  return context ? row.text.replace(context, `${context} \xB7 ${limits}`) : `${limits} \xB7 ${row.text}`;
6040
6135
  });
6041
6136
  if (text.length === 0 && limits) text.push(limits);
@@ -6122,22 +6217,23 @@ function chatScrollMax(state) {
6122
6217
  }
6123
6218
  function plannerSetup(state) {
6124
6219
  const provider = state.plannerProvider;
6125
- const registration = (0, import_core38.getProviderMeta)(provider);
6220
+ const registration = (0, import_core40.getProviderMeta)(provider);
6126
6221
  if (!registration) return { label: "not set", ready: false };
6127
- const runner = (0, import_core38.runnerForProvider)(provider);
6128
- const ready = runner ? state.runners.some((r) => r.id === runner) : (0, import_core38.isApiProvider)(provider) && state.configuredProviders.includes(provider);
6222
+ const runner = (0, import_core40.runnerForProvider)(provider);
6223
+ const ready = runner ? state.runners.some((r) => r.id === runner) : (0, import_core40.isApiProvider)(provider) && state.configuredProviders.includes(provider);
6129
6224
  return { label: registration.label, ready };
6130
6225
  }
6131
6226
  var SETUP_KEY_COLS = 10;
6132
6227
  var SETUP_VALUE_COLS = 32;
6133
6228
  function runSettingsValue(state) {
6134
6229
  if (!state.runSettingsKnown) return style.grey("\u2026");
6135
- const { supervisorLevel, mergeAtGatesAutomatically, autorun } = state.runSettings;
6136
- const level = `supervisor ${import_core37.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}`;
6230
+ const { supervisorLevel, mergeAtGatesAutomatically, autorun, delegationTokens } = state.runSettings;
6231
+ const level = `supervisor ${import_core39.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}`;
6137
6232
  return [
6138
6233
  supervisorLevel === "act" ? style.yellow(level) : level,
6139
6234
  `auto-merge ${mergeAtGatesAutomatically ? "on" : "off"}`,
6140
- autorun ? style.yellow("autorun on") : "autorun off"
6235
+ autorun ? style.yellow("autorun on") : "autorun off",
6236
+ ...import_core39.DELEGATION_OFFERED && delegationTokens > 0 ? [style.yellow("delegation on")] : []
6141
6237
  ].join(" \xB7 ");
6142
6238
  }
6143
6239
  function plannerEffortLabel(state) {
@@ -6222,7 +6318,7 @@ function isolationPreviewLines(state, cols) {
6222
6318
  if (!preview) {
6223
6319
  return [head, ...wrap(style.grey(CHECKING_REPOSITORIES), room), ""];
6224
6320
  }
6225
- const notice = (0, import_core38.describeIsolationPreview)(preview);
6321
+ const notice = (0, import_core40.describeIsolationPreview)(preview);
6226
6322
  const important = preview.status === "dirty" || preview.status === "failed";
6227
6323
  const summary = important ? style.yellow(notice.summary) : notice.summary;
6228
6324
  return [
@@ -6236,7 +6332,7 @@ function isolationPreviewLines(state, cols) {
6236
6332
  }
6237
6333
  function signedOutPlannerLines(state, cols) {
6238
6334
  if (!state.plannerKnown || !state.runnersKnown) return [];
6239
- const planned = (0, import_core38.runnerForProvider)(state.plannerProvider);
6335
+ const planned = (0, import_core40.runnerForProvider)(state.plannerProvider);
6240
6336
  const flagged = state.runners.filter(signedOutWithLogin);
6241
6337
  const active = flagged.find((row) => row.id === planned);
6242
6338
  const room = Math.max(1, cols - 1);
@@ -6246,7 +6342,7 @@ function signedOutPlannerLines(state, cols) {
6246
6342
  ));
6247
6343
  }
6248
6344
  function plannerRowValue(state, label) {
6249
- const alarm = state.runners.find((row) => row.id === (0, import_core38.runnerForProvider)(state.plannerProvider));
6345
+ const alarm = state.runners.find((row) => row.id === (0, import_core40.runnerForProvider)(state.plannerProvider));
6250
6346
  const paint = alarm && signedOutWithLogin(alarm) ? style.red : (text) => text;
6251
6347
  const effort = plannerEffortLabel(state);
6252
6348
  const model = state.orchestratorModel ? paint(state.orchestratorModel) : style.yellow("no model");
@@ -6314,10 +6410,11 @@ var STATUS_ICON = {
6314
6410
  quiet: "~",
6315
6411
  todo: "\xB7"
6316
6412
  };
6413
+ var QUIET_MARK = STATUS_ICON.quiet;
6317
6414
  var inRow = (label) => label.charAt(0).toLowerCase() + label.slice(1);
6318
6415
  function planLayout(state, rows, cols) {
6319
6416
  const done = state.tasks.filter((t) => t.status === "completed").length;
6320
- const totalUsage = (0, import_core38.tasksTotalText)(state.tasks);
6417
+ const totalUsage = (0, import_core40.tasksTotalText)(state.tasks);
6321
6418
  const unmerged = state.runMerge?.handoff.landed.length ?? 0;
6322
6419
  const count = `${done}/${state.tasks.length}`;
6323
6420
  const hints = runHints(state);
@@ -6403,7 +6500,7 @@ function modelsByRunnerOf(models) {
6403
6500
  function taskLines(state, planRow, index, cols) {
6404
6501
  const { task, parent } = planRow;
6405
6502
  const runnerScope = state.runners.length > 0 ? { enabledRunners: state.runners.filter((r) => r.enabled).map((r) => r.id), allowlist: state.allowlist, models: modelsByRunnerOf(state.models) } : void 0;
6406
- const row = (0, import_core38.taskRowView)(task, { parent, tasks: state.tasks, modes: modesForTask(state.modesByRunner, task), runnerScope });
6503
+ const row = (0, import_core40.taskRowView)(task, { parent, tasks: state.tasks, modes: modesForTask(state.modesByRunner, task), runnerScope });
6407
6504
  const selected = state.focus === "plan" && index === state.selectedTask;
6408
6505
  const expanded = state.expandedTaskId === task.id;
6409
6506
  const rawIcon = row.status === "running" ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[row.status];
@@ -6442,7 +6539,12 @@ function taskLines(state, planRow, index, cols) {
6442
6539
  if (row.conflict) {
6443
6540
  const where = row.conflict.repo ? ` in ${row.conflict.repo}` : "";
6444
6541
  const files = row.conflict.files ? ` (${row.conflict.files})` : "";
6445
- lines.push(...statusLines(style.red, "\u26A0", `merge conflict${where}${files}: its work is kept on its own branch`, cols, bodyPad));
6542
+ const held = row.conflict.resolver ? `task ${row.conflict.resolver} is resolving it` : "its work is kept on its own branch";
6543
+ lines.push(...statusLines(style.red, "\u26A0", `merge conflict${where}${files}: ${held}`, cols, bodyPad));
6544
+ }
6545
+ if (row.repairWaiting) {
6546
+ const files = row.repairWaiting.files ? ` (${row.repairWaiting.files})` : "";
6547
+ lines.push(...statusLines(style.yellow, "\u23F8", `${inRow(row.repairWaiting.label)}${files}`, cols, bodyPad));
6446
6548
  }
6447
6549
  if (row.repairing) {
6448
6550
  const files = row.repairing.files ? ` in ${row.repairing.files}` : "";
@@ -6459,13 +6561,13 @@ function taskLines(state, planRow, index, cols) {
6459
6561
  lines.push(...statusLines(style.yellow, "\u23F8", `${inRow(row.waitsForApproval)} \xB7 f approves it \xB7 /run approves all`, cols, bodyPad));
6460
6562
  }
6461
6563
  if (row.holdReason) {
6462
- lines.push(...statusLines(style.yellow, "\u23F8", `${inRow((0, import_core38.holdLabel)(row.holdReason))} \xB7 retry or f start when ready`, cols, bodyPad));
6564
+ lines.push(...statusLines(style.yellow, "\u23F8", `${inRow((0, import_core40.holdLabel)(row.holdReason))} \xB7 retry or f start when ready`, cols, bodyPad));
6463
6565
  }
6464
6566
  if (row.blockedBy) {
6465
- lines.push(...statusLines(style.red, "\u2298", `${inRow((0, import_core38.blockedByLabel)(row.blockedBy))}: runs once ${row.blockedBy.length === 1 ? "it is retried and passes" : "they are retried and pass"}`, cols, bodyPad));
6567
+ lines.push(...statusLines(style.red, "\u2298", `${inRow((0, import_core40.blockedByLabel)(row.blockedBy))}: runs once ${row.blockedBy.length === 1 ? "it is retried and passes" : "they are retried and pass"}`, cols, bodyPad));
6466
6568
  }
6467
6569
  if (row.status === "skipped") {
6468
- lines.push(...statusLines(style.grey, "\xBB", `${inRow((0, import_core38.skippedLabel)(row.skipReason))}: nothing of it landed; m done lands its kept work`, cols, bodyPad));
6570
+ lines.push(...statusLines(style.grey, "\xBB", `${inRow((0, import_core40.skippedLabel)(row.skipReason))}: nothing of it landed; m done lands its kept work`, cols, bodyPad));
6469
6571
  }
6470
6572
  const resumable = row.actions.includes("resume-now");
6471
6573
  if (row.resumes) {
@@ -6482,11 +6584,11 @@ function taskLines(state, planRow, index, cols) {
6482
6584
  let editorLine;
6483
6585
  if (expanded) {
6484
6586
  if (row.handoff) {
6485
- lines.push(...taskText("Handoff", (0, import_core38.handoffPeekParts)(row.handoff).map(sanitize).join(" \xB7 "), cols, bodyPad));
6587
+ lines.push(...taskText("Handoff", (0, import_core40.handoffPeekParts)(row.handoff).map(sanitize).join(" \xB7 "), cols, bodyPad));
6486
6588
  }
6487
6589
  if (skills) lines.push(style.grey(truncate(`${bodyPad}skills: ${skills}`, cols)));
6488
6590
  if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
6489
- lines.push(style.grey(`${bodyPad}${STATUS_WORD[(0, import_core38.taskStatusKind)(task)]}`));
6591
+ lines.push(style.grey(`${bodyPad}${STATUS_WORD[(0, import_core40.taskStatusKind)(task)]}`));
6490
6592
  if (task.isolation && task.isolation.state !== "none" && task.isolation.branch) {
6491
6593
  lines.push(...taskText("Branch", task.isolation.branch, cols, bodyPad));
6492
6594
  if (row.repos.length > 0) lines.push(...taskText("Repos", row.repos.join(", "), cols, bodyPad));
@@ -6501,7 +6603,7 @@ function taskLines(state, planRow, index, cols) {
6501
6603
  lines.push(...taskText("Ops", "Runs in your checkout, not a worktree, once the work it depends on is merged. O makes it a change task.", cols, bodyPad));
6502
6604
  }
6503
6605
  if (row.kind === "script") {
6504
- lines.push(...taskText("Script", (0, import_core38.scriptTaskText)(task).replace(/\t/g, " "), cols, bodyPad));
6606
+ lines.push(...taskText("Script", (0, import_core40.scriptTaskText)(task).replace(/\t/g, " "), cols, bodyPad));
6505
6607
  lines.push(...taskText("Ops", "Runs in your checkout with no model, once the work it depends on is merged.", cols, bodyPad));
6506
6608
  }
6507
6609
  if (row.forcedPastGate) {
@@ -6545,7 +6647,7 @@ var LOOP_PAINT = {
6545
6647
  };
6546
6648
  function loopLines(loop, cols, pad2) {
6547
6649
  const paint = loop.role === "body" ? style.cyan : LOOP_PAINT[loop.state];
6548
- return statusLines(paint, "\u21BB", (0, import_core38.loopRowParts)(loop).map(inRow).join(" \xB7 "), cols, pad2);
6650
+ return statusLines(paint, "\u21BB", (0, import_core40.loopRowParts)(loop).map(inRow).join(" \xB7 "), cols, pad2);
6549
6651
  }
6550
6652
  function statusLines(paint, glyph, text, cols, pad2) {
6551
6653
  const lead = `${pad2}${glyph} `;
@@ -6688,8 +6790,8 @@ function helpScrollMax(state) {
6688
6790
  }
6689
6791
 
6690
6792
  // src/tui/render.ts
6691
- var import_core39 = require("@ordewell/core");
6692
- var import_core40 = require("@ordewell/core");
6793
+ var import_core41 = require("@ordewell/core");
6794
+ var import_core42 = require("@ordewell/core");
6693
6795
  function render(state) {
6694
6796
  const { rows, cols } = state;
6695
6797
  const rowsForBody = bodyRows(state);
@@ -6791,15 +6893,16 @@ function settingsBadge(state) {
6791
6893
  }
6792
6894
  function runSettingsBadge(state) {
6793
6895
  if (!state.runSettingsKnown) return style.grey("run settings \u2026");
6794
- const { supervisorLevel, mergeAtGatesAutomatically, autorun } = state.runSettings;
6896
+ const { supervisorLevel, mergeAtGatesAutomatically, autorun, delegationTokens } = state.runSettings;
6795
6897
  const paint = (text, warn) => warn ? style.yellow(text) : style.grey(text);
6796
6898
  const run = state.status === "executing" ? state.runSupervisor : null;
6797
- const actual = run?.unsupervised ? "unsupervised" : run && run.level !== supervisorLevel ? import_core40.SUPERVISOR_LEVEL_LABEL[run.level] : null;
6798
- const level = `supervisor ${import_core40.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}${actual ? ` \u2192 ${actual}` : ""}`;
6899
+ const actual = run?.unsupervised ? "unsupervised" : run && run.level !== supervisorLevel ? import_core42.SUPERVISOR_LEVEL_LABEL[run.level] : null;
6900
+ const level = `supervisor ${import_core42.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}${actual ? ` \u2192 ${actual}` : ""}`;
6799
6901
  return [
6800
6902
  paint(level, supervisorLevel === "act"),
6801
6903
  ...mergeAtGatesAutomatically ? [style.grey("auto-merge")] : [],
6802
- ...autorun ? [style.yellow("autorun")] : []
6904
+ ...autorun ? [style.yellow("autorun")] : [],
6905
+ ...import_core42.DELEGATION_OFFERED && delegationTokens > 0 ? [style.yellow("delegation")] : []
6803
6906
  ].join(style.grey(" \xB7 "));
6804
6907
  }
6805
6908
  function scrolledBackMark(state) {
@@ -6818,13 +6921,18 @@ function statusText(state, cols, panes) {
6818
6921
  if (armed) return truncate(style.red(armed), cols);
6819
6922
  const activity = chatActivity(state);
6820
6923
  const hint = !panes.plan || panes.plan.rows === 0 ? runHint(state) : "";
6821
- const notes = [mark, activity && !activity.busy ? activity.label : "", hint, state.toast].filter(Boolean).join(" \xB7 ");
6822
- if (activity?.busy) {
6823
- const lit = Math.floor(state.spinnerFrame / DOT_TICKS) % 2 === 0;
6824
- const dot = lit ? style.brand("\u25CF") : style.grey("\u25CF");
6924
+ const inbox = inboxHint(state);
6925
+ const notes = [mark, activity && !activity.busy ? activity.label : "", hint, inbox, state.toast].filter(Boolean).join(" \xB7 ");
6926
+ if (activity?.busy || activity?.quiet) {
6927
+ const glyph = activity.busy ? SPINNER[state.spinnerFrame % SPINNER.length] : QUIET_MARK;
6825
6928
  const scrolled = mark ? style.grey(`${mark} \xB7 `) : "";
6826
6929
  const stop = runnerStoppable(state) ? " \xB7 ctrl-x to stop" : "";
6827
- return truncate(`${scrolled}${dot} ${activity.label}${stop}`, cols);
6930
+ const elapsed = (0, import_core41.elapsedSince)(activity.since, state.now);
6931
+ const time = elapsed ? ` ${elapsed}` : "";
6932
+ const room = Math.max(1, cols - width(scrolled) - width(glyph) - 1 - width(time) - width(stop));
6933
+ const label = truncate(sanitize(activity.label).replace(/\s+/g, " ").trim(), room);
6934
+ const count = inbox && width(label) + width(inbox) + 3 <= room ? style.grey(` \xB7 ${inbox}`) : "";
6935
+ return truncate(`${scrolled}${style.brand(glyph)} ${label}${time}${count}${stop}`, cols);
6828
6936
  }
6829
6937
  return notes ? truncate(style.grey(notes), cols) : "";
6830
6938
  }
@@ -6883,10 +6991,10 @@ function composerLines(state, view) {
6883
6991
  const continuation = " ".repeat(width(prompt));
6884
6992
  if (!lines || !view) {
6885
6993
  const visible = text.replace(/\n/g, "\xB6").replace(/\t/g, " ");
6886
- const painted = paintSpans(visible, 0, highlightSpans(text, state.taskView ? (0, import_core39.taskLogCommands)(state.taskView.view) : null));
6994
+ const painted = paintSpans(visible, 0, highlightSpans(text, state.taskView ? (0, import_core41.taskLogCommands)(state.taskView.view) : null));
6887
6995
  return [prompt + painted + suggest(state, room - width(visible))];
6888
6996
  }
6889
- const spans = highlightSpans(view.text, state.taskView ? (0, import_core39.taskLogCommands)(state.taskView.view) : null);
6997
+ const spans = highlightSpans(view.text, state.taskView ? (0, import_core41.taskLogCommands)(state.taskView.view) : null);
6890
6998
  const { line: lineIndex, col } = cursorInLines(lines, cursor - view.base, view.text.length);
6891
6999
  return lines.map(({ line: raw, start: base }, i) => {
6892
7000
  const prefix = i === 0 ? prompt : continuation;
@@ -6905,7 +7013,7 @@ function suggest(state, room) {
6905
7013
  if (state.focus === "plan" || room <= 2) return "";
6906
7014
  const token = activeToken(state.editor.text, state.editor.cursor);
6907
7015
  if (!token) return "";
6908
- const matches = composerCompletions(token, state.taskView ? (0, import_core39.taskLogCommands)(state.taskView.view) : null);
7016
+ const matches = composerCompletions(token, state.taskView ? (0, import_core41.taskLogCommands)(state.taskView.view) : null);
6909
7017
  if (matches.length === 0) return "";
6910
7018
  const names = matches.join(" ");
6911
7019
  return style.bandGrey(` ${truncate(names, Math.max(0, room - 2))}`);
@@ -6971,23 +7079,23 @@ function renderHandoff(state, overlay, rows, cols) {
6971
7079
  const { lines, scroll } = overlay.diff;
6972
7080
  const shown = lines.slice(scroll, scroll + room).map((line) => paintDiffLine(truncate(line, Math.max(1, cols - 2))));
6973
7081
  const more = scroll + room < lines.length ? "\u2191\u2193 pgup/pgdn scroll \xB7 " : "";
6974
- return frame(`Diff: ${(0, import_core39.handoffBranchName)(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
7082
+ return frame(`Diff: ${(0, import_core41.handoffBranchName)(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
6975
7083
  }
6976
7084
  const landed = handoff.landed.length === 0 ? [style.grey("Nothing landed on it.")] : handoff.landed.map((task) => {
6977
- const repaired = task.repairedFiles?.length ? style.yellow(`: repaired (${(0, import_core39.capConflictFiles)(task.repairedFiles)})`) : "";
6978
- return ` ${style.green("\u2713")} ${(0, import_core39.titledTaskRefOf)(state.tasks, task.taskId) ?? (0, import_core39.titledTaskRef)(task)}${repaired}`;
7085
+ const repaired = task.repairedFiles?.length ? style.yellow(`: repaired (${(0, import_core41.capConflictFiles)(task.repairedFiles)})`) : "";
7086
+ return ` ${style.green("\u2713")} ${(0, import_core41.titledTaskRefOf)(state.tasks, task.taskId) ?? (0, import_core41.titledTaskRef)(task)}${repaired}`;
6979
7087
  });
6980
7088
  const actions = handoffActions(handoff).map((action, index) => {
6981
7089
  const active = index === overlay.index;
6982
7090
  const caret = active ? style.brand("\u276F") : " ";
6983
7091
  return `${caret} ${active ? style.bold(action.label) : action.label}${style.grey(`: ${action.hint}`)}`;
6984
7092
  });
6985
- const perRepo = (0, import_core39.isRepoGroup)(handoff) ? ["Per repository:", ...handoff.repos.map((repo, i) => ` ${repo.landed.length > 0 ? style.green("\u2713") : style.grey("\xB7")} ${(0, import_core39.repoResultLines)(handoff)[i]}`), ""] : [];
7093
+ const perRepo = (0, import_core41.isRepoGroup)(handoff) ? ["Per repository:", ...handoff.repos.map((repo, i) => ` ${repo.landed.length > 0 ? style.green("\u2713") : style.grey("\xB7")} ${(0, import_core41.repoResultLines)(handoff)[i]}`), ""] : [];
6986
7094
  return frame(
6987
7095
  "Run handoff",
6988
7096
  [
6989
- `Branch ${style.brand((0, import_core39.handoffBranchName)(handoff))}`,
6990
- style.grey(`Forked from ${(0, import_core39.handoffBaseLine)(handoff, 12)}`),
7097
+ `Branch ${style.brand((0, import_core41.handoffBranchName)(handoff))}`,
7098
+ style.grey(`Forked from ${(0, import_core41.handoffBaseLine)(handoff, 12)}`),
6991
7099
  "",
6992
7100
  ...perRepo,
6993
7101
  "Landed on it, in plan order:",
@@ -7149,13 +7257,13 @@ function frame(title, body, rows, cols) {
7149
7257
  }
7150
7258
 
7151
7259
  // src/tui/reducer.ts
7152
- var import_core52 = require("@ordewell/core");
7260
+ var import_core55 = require("@ordewell/core");
7153
7261
 
7154
7262
  // src/tui/reducers/overlays.ts
7155
- var import_core51 = require("@ordewell/core");
7263
+ var import_core53 = require("@ordewell/core");
7156
7264
 
7157
7265
  // src/tui/reducers/approvals.ts
7158
- var import_core41 = require("@ordewell/core");
7266
+ var import_core43 = require("@ordewell/core");
7159
7267
 
7160
7268
  // src/tui/reducers/shared.ts
7161
7269
  function step(state, effects = []) {
@@ -7294,7 +7402,7 @@ function syncApprovals(state, waiting) {
7294
7402
  const ids = new Set(waiting.map((request) => request.id));
7295
7403
  const open = state.overlay?.kind === "approval" ? [state.overlay.request] : [];
7296
7404
  const answered = [...open, ...state.pendingApprovals].filter((request) => !ids.has(request.id));
7297
- const dropped = answered.reduce((current, request) => dropApproval(current, request.id), { ...state, conversation: (0, import_core41.reconcileApprovals)(state.conversation, waiting) });
7405
+ const dropped = answered.reduce((current, request) => dropApproval(current, request.id), { ...state, conversation: (0, import_core43.reconcileApprovals)(state.conversation, waiting) });
7298
7406
  return waiting.reduce((current, { id, kind, subject, scope, detail, body }) => kind === "runner_tool" ? current : enqueueApproval(current, { id, kind, subject, scope, ...detail ? { detail } : {}, ...body ? { body } : {} }), dropped);
7299
7407
  }
7300
7408
  function showNextApproval(state) {
@@ -7319,10 +7427,10 @@ function scrollApproval(state, overlay, key) {
7319
7427
  }
7320
7428
 
7321
7429
  // src/tui/reducers/commands.ts
7322
- var import_core50 = require("@ordewell/core");
7430
+ var import_core52 = require("@ordewell/core");
7323
7431
 
7324
7432
  // src/tui/reducers/plannerStepper.ts
7325
- var import_core42 = require("@ordewell/core");
7433
+ var import_core44 = require("@ordewell/core");
7326
7434
  var offered = (items) => new Set(items.filter((i) => !i.disabled).map((i) => i.id));
7327
7435
  function preferredModel(state, provider) {
7328
7436
  const listed = offered(plannerModelItems(state, provider, ""));
@@ -7370,11 +7478,11 @@ var plannerStepper = {
7370
7478
  const model = plannerModelItems(state, provider, "").find((m) => m.id === modelId);
7371
7479
  const level = plannerEffortItems(state, provider, modelId, "").find((e) => e.id === effort);
7372
7480
  const parts = [
7373
- (0, import_core42.getProviderMeta)(provider)?.label ?? provider,
7481
+ (0, import_core44.getProviderMeta)(provider)?.label ?? provider,
7374
7482
  model?.label ?? modelId,
7375
7483
  ...effort ? [effortLabel(level?.label ?? effort)] : []
7376
7484
  ];
7377
- const active = state.runners.find((row) => row.id === (0, import_core42.runnerForProvider)(provider));
7485
+ const active = state.runners.find((row) => row.id === (0, import_core44.runnerForProvider)(provider));
7378
7486
  const noticeLevel = active && signedOutWithLogin(active) ? "error" : void 0;
7379
7487
  return step({
7380
7488
  ...state,
@@ -7386,8 +7494,284 @@ var plannerStepper = {
7386
7494
  }
7387
7495
  };
7388
7496
 
7497
+ // src/tui/reducers/taskView.ts
7498
+ var import_core45 = require("@ordewell/core");
7499
+ function openTaskView(state, sessionId, taskId) {
7500
+ const task = findTask(state.tasks, taskId);
7501
+ if (!task) return fail3(state, `No task ${taskId} in this plan.`);
7502
+ if (!(0, import_core45.taskRowView)(task).chatAvailable) {
7503
+ return fail3(state, "This task hasn\u2019t started yet. Its chat becomes available when it starts.");
7504
+ }
7505
+ const taskView = {
7506
+ taskId,
7507
+ ...import_core45.EMPTY_TASK_LOG_FOLLOW,
7508
+ queuedIndex: null
7509
+ };
7510
+ return step({ ...state, taskView, focus: "chat", scroll: 0 }, [{ type: "openTaskLog", sessionId, taskId }]);
7511
+ }
7512
+ function continueTaskStep(state, sessionId, taskId, text) {
7513
+ const opened = state.taskView?.taskId === taskId ? step({ ...state, taskView: { ...state.taskView, followLatest: true } }) : openTaskView(state, sessionId, taskId);
7514
+ const watch = state.status !== "executing";
7515
+ return step(opened.state, [...opened.effects, { type: "continueTask", sessionId, taskId, text, ...watch ? { watch: true } : {} }]);
7516
+ }
7517
+ function clampQueueIndex(tv) {
7518
+ if (tv.queuedIndex === null) return tv;
7519
+ if (tv.view.queued.length === 0) return { ...tv, queuedIndex: null };
7520
+ const max = tv.view.queued.length - 1;
7521
+ return tv.queuedIndex <= max ? tv : { ...tv, queuedIndex: max };
7522
+ }
7523
+ function taskLogAbandoned(state) {
7524
+ const tv = state.taskView;
7525
+ if (!tv || tv.loaded) return state;
7526
+ return { ...state, taskView: clampQueueIndex({ ...tv, ...(0, import_core45.followTaskLog)(tv, { type: "loaded", attempt: tv.attempt, events: [], abandoned: true }) }) };
7527
+ }
7528
+ function taskLogArrived(state, action) {
7529
+ state = observeTaskServedModel(state, action);
7530
+ const tv = state.taskView;
7531
+ if (!tv || tv.taskId !== action.taskId) return state;
7532
+ const next = (0, import_core45.followTaskLog)(tv, { ...action, type: "live" });
7533
+ return { ...state, taskView: next === tv ? tv : clampQueueIndex({ ...tv, ...next }) };
7534
+ }
7535
+ function announceApprovals(state, action) {
7536
+ if (state.taskView?.taskId === action.taskId) return state;
7537
+ const task = findTask(state.tasks, action.taskId);
7538
+ const way = task ? `tab to the plan, select task ${refFor(state, task)} and press enter (or /open ${refFor(state, task)})` : "tab to the plan, select it and press enter";
7539
+ const spoken = action.events.reduce((s, event) => event.type === "approval_requested" && !event.decided ? say(s, "system", `Task ${task ? refFor(state, task) : "?"} waits for approval: ${(0, import_core45.runnerToolSubject)(event.tool, event.args)}: ${way}, then ctrl-y allows or ctrl-g denies`) : s, state);
7540
+ return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
7541
+ }
7542
+ function announceCheckpoint(state, action) {
7543
+ const task = findTask(state.tasks, action.taskId) ?? state.tasks.find((t) => t.title === action.title);
7544
+ const label = task ? `Task ${refFor(state, task)}` : action.title;
7545
+ const where = task ? `tab to the plan, select task ${refFor(state, task)} and press enter (or /open ${refFor(state, task)}) to answer` : "tab to the plan, select it and press enter to answer";
7546
+ const spoken = say(state, "system", `${label} asks: ${(0, import_core45.truncateCheckpointSummary)(action.summary)}: ${where}`);
7547
+ return { ...spoken, scroll: state.scroll };
7548
+ }
7549
+ function taskLogLoaded(state, action) {
7550
+ state = observeTaskServedModel(state, action);
7551
+ const tv = state.taskView;
7552
+ if (!tv || tv.taskId !== action.taskId) return state;
7553
+ return { ...state, taskView: clampQueueIndex({ ...tv, ...(0, import_core45.followTaskLog)(tv, { ...action, type: "loaded" }) }) };
7554
+ }
7555
+ function answerApproval(state, tv, sessionId, decision) {
7556
+ const approval = waitingApproval(tv);
7557
+ if (!approval?.approvalId) return step(say(state, "system", "Nothing is waiting for approval."));
7558
+ if (decision === "allowForTask" && !approval.allowForTask) {
7559
+ return step(say(state, "system", "The runner offered no grant for the rest of this task: ctrl-y allows this call."));
7560
+ }
7561
+ const note = decision === "deny" ? state.editor.text.trim() : "";
7562
+ const answer = decision === "deny" ? { decision, ...note ? { note } : {} } : { decision };
7563
+ const next = note ? { ...state, editor: { ...state.editor, text: "", cursor: 0 } } : state;
7564
+ return step(next, [{ type: "answerTaskApproval", sessionId, approvalId: approval.approvalId, answer }]);
7565
+ }
7566
+ function answerCheckpoint(state, sessionId, taskId, answer, reason = "") {
7567
+ const task = findTask(state.tasks, taskId);
7568
+ if (!waitingCheckpoint(task)) return step(say(state, "system", `Task ${task?.order ?? taskId} is not waiting at a checkpoint.`));
7569
+ const note = answer === "reject" ? reason.trim() : "";
7570
+ return step(state, [{ type: "answerTaskCheckpoint", sessionId, taskId, answer, ...note ? { reason: note } : {} }]);
7571
+ }
7572
+ function answerWaiting(state, tv, sessionId, decision) {
7573
+ if (waitingApproval(tv) || !waitingCheckpoint(findTask(state.tasks, tv.taskId))) return answerApproval(state, tv, sessionId, decision);
7574
+ if (decision === "allow") return answerCheckpoint(state, sessionId, tv.taskId, "approve");
7575
+ const answered = answerCheckpoint(state, sessionId, tv.taskId, "reject", state.editor.text);
7576
+ return state.editor.text.trim() ? { ...answered, state: { ...answered.state, editor: { ...answered.state.editor, text: "", cursor: 0 } } } : answered;
7577
+ }
7578
+ function sendNow(state, tv, sessionId) {
7579
+ const text = state.editor.text.trim();
7580
+ if (text) {
7581
+ const task = findTask(state.tasks, tv.taskId);
7582
+ if (continuesTask(task)) {
7583
+ return step(say(state, "system", resumesTask(task) ? "The task is paused on a usage limit, so there is no turn to interrupt: enter resumes it." : "The task has finished, so there is no turn to interrupt: enter continues it."));
7584
+ }
7585
+ return step({ ...state, editor: commit(state.editor) }, [{ type: "forceSendTaskMessage", sessionId, taskId: tv.taskId, text }]);
7586
+ }
7587
+ const queued = tv.view.queued[queuedTarget(tv)];
7588
+ if (!queued) return step(say(state, "system", "Nothing to send now: type a message, or pick a queued one with ctrl-n/ctrl-p."));
7589
+ return step({ ...state, taskView: { ...tv, queuedIndex: null } }, [
7590
+ { type: "forceSendQueuedTaskMessage", sessionId, taskId: tv.taskId, messageId: queued.id }
7591
+ ]);
7592
+ }
7593
+ function handleTaskViewKey(state, key) {
7594
+ const tv = state.taskView;
7595
+ if (!tv || !state.sessionId) return null;
7596
+ if (key.name === "alt-left" || key.name === "alt-right") {
7597
+ const index = tv.attempts.indexOf(tv.loadingAttempt ?? tv.attempt);
7598
+ if (index < 0) return null;
7599
+ const target = key.name === "alt-left" ? index - 1 : index + 1;
7600
+ const attempt = tv.attempts[target];
7601
+ if (attempt === void 0) {
7602
+ return step(say(state, "system", target < 0 ? "This is the first attempt." : "This is the latest attempt."));
7603
+ }
7604
+ return step(
7605
+ { ...state, taskView: { ...tv, ...(0, import_core45.followTaskLog)(tv, { type: "picked", attempt }) } },
7606
+ [{ type: "loadTaskAttempt", sessionId: state.sessionId, taskId: tv.taskId, attempt }]
7607
+ );
7608
+ }
7609
+ if (scriptTaskView(state)) return null;
7610
+ if (key.name === "ctrl-r") {
7611
+ const queued = tv.view.queued[queuedTarget(tv)];
7612
+ if (!queued) return step(say(state, "system", "No queued message to remove."));
7613
+ if (queued.handedOver) return step(say(state, "system", "The runner already has that message; it can no longer be taken back."));
7614
+ return step(state, [{ type: "removeTaskMessage", sessionId: state.sessionId, taskId: tv.taskId, messageId: queued.id }]);
7615
+ }
7616
+ if (key.name === "ctrl-s") return sendNow(state, tv, state.sessionId);
7617
+ if (key.name === "ctrl-x") {
7618
+ const task = findTask(state.tasks, tv.taskId);
7619
+ if (task && (0, import_core45.resumeLabel)(task)) return step(state, [{ type: "taskAction", sessionId: state.sessionId, taskId: tv.taskId, action: "cancel-resume" }]);
7620
+ return step(state, [{ type: "interruptTask", sessionId: state.sessionId, taskId: tv.taskId }]);
7621
+ }
7622
+ if (key.name === "ctrl-y") return answerWaiting(state, tv, state.sessionId, "allow");
7623
+ if (key.name === "ctrl-t") return answerApproval(state, tv, state.sessionId, "allowForTask");
7624
+ if (key.name === "ctrl-g") return answerWaiting(state, tv, state.sessionId, "deny");
7625
+ if (key.name === "ctrl-n" || key.name === "ctrl-p") {
7626
+ const count = tv.view.queued.length;
7627
+ if (count === 0) return null;
7628
+ const delta = key.name === "ctrl-n" ? 1 : -1;
7629
+ const from = queuedTarget(tv);
7630
+ const queuedIndex = from < 0 ? count - 1 : Math.max(0, Math.min(count - 1, from + delta));
7631
+ return step({ ...state, taskView: { ...tv, queuedIndex } });
7632
+ }
7633
+ return null;
7634
+ }
7635
+
7636
+ // src/tui/reducers/inbox.ts
7637
+ var SPAWNS_RUNNER = /* @__PURE__ */ new Set(["retry", "resume", "continue", "force-start", "run"]);
7638
+ var OPEN_TASK = "__open-task";
7639
+ var NOTHING = "Nothing is waiting on you.";
7640
+ var GONE = "That is no longer waiting on you: the list changed. Run /inbox to see what is.";
7641
+ var listOf = (state) => state.run.escalations ?? [];
7642
+ var find = (state, id) => listOf(state).find((e) => e.id === id);
7643
+ var oneLine3 = (state, text) => truncate(sanitize(text).replace(/\s+/g, " ").trim(), Math.max(1, state.cols));
7644
+ var labelOf = (state, e) => e.task ? oneLine3(state, `${e.task.ref} ${e.task.title}`) : "The run";
7645
+ function inboxItems(state) {
7646
+ return listOf(state).map((e) => ({ id: e.id, label: labelOf(state, e), detail: oneLine3(state, e.reason) }));
7647
+ }
7648
+ function optionDetail(option) {
7649
+ if (option.confirm) return "asks first";
7650
+ if (option.note === "required") return "needs a note";
7651
+ if (option.note === "optional") return "takes a note";
7652
+ return void 0;
7653
+ }
7654
+ function answerItems(state, e) {
7655
+ const options = e.options.map((o) => ({ id: o.id, label: o.label, detail: optionDetail(o) }));
7656
+ return e.task ? [...options, { id: OPEN_TASK, label: "Open the task", detail: oneLine3(state, `${e.task.ref} ${e.task.title}`) }] : options;
7657
+ }
7658
+ function inboxPicker(state, index = 0) {
7659
+ return {
7660
+ title: "Waiting on you",
7661
+ items: inboxItems(state),
7662
+ filter: "",
7663
+ index,
7664
+ multi: false,
7665
+ chosen: [],
7666
+ filterable: false,
7667
+ action: { kind: "inbox" }
7668
+ };
7669
+ }
7670
+ function answerPicker(state, e) {
7671
+ return {
7672
+ title: labelOf(state, e),
7673
+ hint: oneLine3(state, e.reason),
7674
+ items: answerItems(state, e),
7675
+ filter: "",
7676
+ index: 0,
7677
+ multi: false,
7678
+ chosen: [],
7679
+ filterable: false,
7680
+ action: { kind: "inbox-answer", escalationId: e.id }
7681
+ };
7682
+ }
7683
+ function openInbox(state) {
7684
+ if (listOf(state).length === 0) return step(say(state, "system", NOTHING));
7685
+ return step({ ...state, overlay: { kind: "picker", picker: inboxPicker(state) } });
7686
+ }
7687
+ function chooseEscalation(state, id) {
7688
+ const e = find(state, id);
7689
+ if (!e) return gone(state);
7690
+ return step({ ...state, overlay: { kind: "picker", picker: answerPicker(state, e) } });
7691
+ }
7692
+ function chooseAnswer(state, escalationId, itemId) {
7693
+ const e = find(state, escalationId);
7694
+ if (!e) return gone(state);
7695
+ const closed = { ...state, overlay: null };
7696
+ if (itemId === OPEN_TASK) {
7697
+ return e.task && state.sessionId ? openTaskView(closed, state.sessionId, e.task.id) : step(closed);
7698
+ }
7699
+ const option = e.options.find((o) => o.id === itemId);
7700
+ if (!option) return gone(state);
7701
+ if (option.note) {
7702
+ return step({
7703
+ ...state,
7704
+ overlay: {
7705
+ kind: "prompt",
7706
+ title: `${option.label}: ${oneLine3(state, e.reason)}`,
7707
+ hint: option.note === "required" ? "A note is required. Enter sends it, esc cancels." : "A note is optional: enter alone sends none, esc cancels.",
7708
+ value: "",
7709
+ action: { kind: "escalation-note", escalationId, option: option.id, required: option.note === "required", ...option.confirm ? { confirm: option.confirm } : {} }
7710
+ }
7711
+ });
7712
+ }
7713
+ return option.confirm ? askFirst(state, escalationId, option) : submit(closed, escalationId, option.id);
7714
+ }
7715
+ function noteEntered(state, action, note) {
7716
+ if (!note && action.required) return step(state);
7717
+ const closed = { ...state, overlay: null };
7718
+ const option = find(state, action.escalationId)?.options.find((o) => o.id === action.option);
7719
+ if (!option) return gone(closed);
7720
+ return option.confirm ? askFirst(state, action.escalationId, option, note) : submit(closed, action.escalationId, option.id, note);
7721
+ }
7722
+ function askFirst(state, escalationId, option, note) {
7723
+ return step({
7724
+ ...state,
7725
+ overlay: {
7726
+ kind: "confirm",
7727
+ title: `${option.label}?`,
7728
+ message: sanitize(option.confirm ?? ""),
7729
+ action: { kind: "answer-escalation", escalationId, option: option.id, ...note ? { note } : {} }
7730
+ }
7731
+ });
7732
+ }
7733
+ function confirmedAnswer(state, escalationId, option, note) {
7734
+ return submit({ ...state, overlay: null }, escalationId, option, note, true);
7735
+ }
7736
+ function submit(state, escalationId, option, note, confirmed) {
7737
+ if (!state.sessionId) return fail3(state, "No active plan.");
7738
+ if (!find(state, escalationId)) return fail3(state, GONE);
7739
+ const effect = {
7740
+ type: "answerEscalation",
7741
+ sessionId: state.sessionId,
7742
+ id: escalationId,
7743
+ option,
7744
+ ...note ? { note } : {},
7745
+ ...confirmed ? { confirmed } : {},
7746
+ ...SPAWNS_RUNNER.has(option) && state.status !== "executing" ? { watch: true } : {}
7747
+ };
7748
+ return step(state, [effect]);
7749
+ }
7750
+ function gone(state) {
7751
+ const refused = fail3(state, GONE).state;
7752
+ return step(listOf(state).length === 0 ? { ...refused, overlay: null } : { ...refused, overlay: { kind: "picker", picker: inboxPicker(state) } });
7753
+ }
7754
+ function refillInbox(state) {
7755
+ const overlay = state.overlay;
7756
+ if (overlay?.kind !== "picker") return state;
7757
+ const { action } = overlay.picker;
7758
+ if (action.kind !== "inbox" && action.kind !== "inbox-answer") return state;
7759
+ if (action.kind === "inbox") {
7760
+ if (listOf(state).length === 0) return say({ ...state, overlay: null }, "system", NOTHING);
7761
+ const kept2 = overlay.picker.items[overlay.picker.index]?.id;
7762
+ const index2 = Math.max(0, listOf(state).findIndex((e2) => e2.id === kept2));
7763
+ return { ...state, overlay: { kind: "picker", picker: { ...inboxPicker(state, index2) } } };
7764
+ }
7765
+ const e = find(state, action.escalationId);
7766
+ if (!e) return gone(state).state;
7767
+ const kept = overlay.picker.items[overlay.picker.index]?.id;
7768
+ const items = answerItems(state, e);
7769
+ const index = Math.max(0, items.findIndex((i) => i.id === kept));
7770
+ return { ...state, overlay: { kind: "picker", picker: { ...answerPicker(state, e), index } } };
7771
+ }
7772
+
7389
7773
  // src/tui/reducers/stepper.ts
7390
- var import_core43 = require("@ordewell/core");
7774
+ var import_core46 = require("@ordewell/core");
7391
7775
  function openStepper(state, definition) {
7392
7776
  const { choices, lists } = settle(state, definition, {});
7393
7777
  const first = lists.findIndex((list2) => list2 !== null);
@@ -7410,7 +7794,7 @@ function handleStepperKey(state, stepper, key) {
7410
7794
  const reopen = (next) => step({ ...state, overlay: { kind: "stepper", stepper: next } });
7411
7795
  const { definition, current, entry, choices } = stepper;
7412
7796
  const def = definition.steps[current];
7413
- const typed = def.number && entry ? (0, import_core43.parseCount)(entry.text, def.number.min) : null;
7797
+ const typed = def.number && entry ? (0, import_core46.parseCount)(entry.text, def.number.min) : null;
7414
7798
  const { lists } = settle(state, definition, choices);
7415
7799
  const items = matchingItems(stepper.items, stepper.filter);
7416
7800
  const before = lastIndexBefore(lists, current);
@@ -7483,7 +7867,7 @@ function settle(state, definition, wanted) {
7483
7867
  const lists = definition.steps.map((def) => {
7484
7868
  if (def.skip?.(state, { ...choices })) return null;
7485
7869
  if (def.number) {
7486
- choices[def.id] = String((0, import_core43.parseCount)(wanted[def.id], def.number.min) ?? def.number.current(state, { ...choices }));
7870
+ choices[def.id] = String((0, import_core46.parseCount)(wanted[def.id], def.number.min) ?? def.number.current(state, { ...choices }));
7487
7871
  return [];
7488
7872
  }
7489
7873
  const items = def.items(state, { ...choices });
@@ -7522,149 +7906,10 @@ function lastIndexBefore(lists, index) {
7522
7906
  }
7523
7907
 
7524
7908
  // src/tui/reducers/planPane.ts
7525
- var import_core46 = require("@ordewell/core");
7526
-
7527
- // src/tui/reducers/taskView.ts
7528
- var import_core44 = require("@ordewell/core");
7529
- function openTaskView(state, sessionId, taskId) {
7530
- const task = findTask(state.tasks, taskId);
7531
- if (!task) return fail3(state, `No task ${taskId} in this plan.`);
7532
- if (!(0, import_core44.taskRowView)(task).chatAvailable) {
7533
- return fail3(state, "This task hasn\u2019t started yet. Its chat becomes available when it starts.");
7534
- }
7535
- const taskView = {
7536
- taskId,
7537
- ...import_core44.EMPTY_TASK_LOG_FOLLOW,
7538
- queuedIndex: null
7539
- };
7540
- return step({ ...state, taskView, focus: "chat", scroll: 0 }, [{ type: "openTaskLog", sessionId, taskId }]);
7541
- }
7542
- function continueTaskStep(state, sessionId, taskId, text) {
7543
- const opened = state.taskView?.taskId === taskId ? step({ ...state, taskView: { ...state.taskView, followLatest: true } }) : openTaskView(state, sessionId, taskId);
7544
- const watch = state.status !== "executing";
7545
- return step(opened.state, [...opened.effects, { type: "continueTask", sessionId, taskId, text, ...watch ? { watch: true } : {} }]);
7546
- }
7547
- function clampQueueIndex(tv) {
7548
- if (tv.queuedIndex === null) return tv;
7549
- if (tv.view.queued.length === 0) return { ...tv, queuedIndex: null };
7550
- const max = tv.view.queued.length - 1;
7551
- return tv.queuedIndex <= max ? tv : { ...tv, queuedIndex: max };
7552
- }
7553
- function taskLogAbandoned(state) {
7554
- const tv = state.taskView;
7555
- if (!tv || tv.loaded) return state;
7556
- return { ...state, taskView: clampQueueIndex({ ...tv, ...(0, import_core44.followTaskLog)(tv, { type: "loaded", attempt: tv.attempt, events: [], abandoned: true }) }) };
7557
- }
7558
- function taskLogArrived(state, action) {
7559
- state = observeTaskServedModel(state, action);
7560
- const tv = state.taskView;
7561
- if (!tv || tv.taskId !== action.taskId) return state;
7562
- const next = (0, import_core44.followTaskLog)(tv, { ...action, type: "live" });
7563
- return { ...state, taskView: next === tv ? tv : clampQueueIndex({ ...tv, ...next }) };
7564
- }
7565
- function announceApprovals(state, action) {
7566
- if (state.taskView?.taskId === action.taskId) return state;
7567
- const task = findTask(state.tasks, action.taskId);
7568
- const way = task ? `tab to the plan, select task ${refFor(state, task)} and press enter (or /open ${refFor(state, task)})` : "tab to the plan, select it and press enter";
7569
- const spoken = action.events.reduce((s, event) => event.type === "approval_requested" && !event.decided ? say(s, "system", `Task ${task ? refFor(state, task) : "?"} waits for approval: ${(0, import_core44.runnerToolSubject)(event.tool, event.args)}: ${way}, then ctrl-y allows or ctrl-g denies`) : s, state);
7570
- return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
7571
- }
7572
- function announceCheckpoint(state, action) {
7573
- const task = findTask(state.tasks, action.taskId) ?? state.tasks.find((t) => t.title === action.title);
7574
- const label = task ? `Task ${refFor(state, task)}` : action.title;
7575
- const where = task ? `tab to the plan, select task ${refFor(state, task)} and press enter (or /open ${refFor(state, task)}) to answer` : "tab to the plan, select it and press enter to answer";
7576
- const spoken = say(state, "system", `${label} asks: ${(0, import_core44.truncateCheckpointSummary)(action.summary)}: ${where}`);
7577
- return { ...spoken, scroll: state.scroll };
7578
- }
7579
- function taskLogLoaded(state, action) {
7580
- state = observeTaskServedModel(state, action);
7581
- const tv = state.taskView;
7582
- if (!tv || tv.taskId !== action.taskId) return state;
7583
- return { ...state, taskView: clampQueueIndex({ ...tv, ...(0, import_core44.followTaskLog)(tv, { ...action, type: "loaded" }) }) };
7584
- }
7585
- function answerApproval(state, tv, sessionId, decision) {
7586
- const approval = waitingApproval(tv);
7587
- if (!approval?.approvalId) return step(say(state, "system", "Nothing is waiting for approval."));
7588
- if (decision === "allowForTask" && !approval.allowForTask) {
7589
- return step(say(state, "system", "The runner offered no grant for the rest of this task: ctrl-y allows this call."));
7590
- }
7591
- const note = decision === "deny" ? state.editor.text.trim() : "";
7592
- const answer = decision === "deny" ? { decision, ...note ? { note } : {} } : { decision };
7593
- const next = note ? { ...state, editor: { ...state.editor, text: "", cursor: 0 } } : state;
7594
- return step(next, [{ type: "answerTaskApproval", sessionId, approvalId: approval.approvalId, answer }]);
7595
- }
7596
- function answerCheckpoint(state, sessionId, taskId, answer, reason = "") {
7597
- const task = findTask(state.tasks, taskId);
7598
- if (!waitingCheckpoint(task)) return step(say(state, "system", `Task ${task?.order ?? taskId} is not waiting at a checkpoint.`));
7599
- const note = answer === "reject" ? reason.trim() : "";
7600
- return step(state, [{ type: "answerTaskCheckpoint", sessionId, taskId, answer, ...note ? { reason: note } : {} }]);
7601
- }
7602
- function answerWaiting(state, tv, sessionId, decision) {
7603
- if (waitingApproval(tv) || !waitingCheckpoint(findTask(state.tasks, tv.taskId))) return answerApproval(state, tv, sessionId, decision);
7604
- if (decision === "allow") return answerCheckpoint(state, sessionId, tv.taskId, "approve");
7605
- const answered = answerCheckpoint(state, sessionId, tv.taskId, "reject", state.editor.text);
7606
- return state.editor.text.trim() ? { ...answered, state: { ...answered.state, editor: { ...answered.state.editor, text: "", cursor: 0 } } } : answered;
7607
- }
7608
- function sendNow(state, tv, sessionId) {
7609
- const text = state.editor.text.trim();
7610
- if (text) {
7611
- const task = findTask(state.tasks, tv.taskId);
7612
- if (continuesTask(task)) {
7613
- return step(say(state, "system", resumesTask(task) ? "The task is paused on a usage limit, so there is no turn to interrupt: enter resumes it." : "The task has finished, so there is no turn to interrupt: enter continues it."));
7614
- }
7615
- return step({ ...state, editor: commit(state.editor) }, [{ type: "forceSendTaskMessage", sessionId, taskId: tv.taskId, text }]);
7616
- }
7617
- const queued = tv.view.queued[queuedTarget(tv)];
7618
- if (!queued) return step(say(state, "system", "Nothing to send now: type a message, or pick a queued one with ctrl-n/ctrl-p."));
7619
- return step({ ...state, taskView: { ...tv, queuedIndex: null } }, [
7620
- { type: "forceSendQueuedTaskMessage", sessionId, taskId: tv.taskId, messageId: queued.id }
7621
- ]);
7622
- }
7623
- function handleTaskViewKey(state, key) {
7624
- const tv = state.taskView;
7625
- if (!tv || !state.sessionId) return null;
7626
- if (key.name === "alt-left" || key.name === "alt-right") {
7627
- const index = tv.attempts.indexOf(tv.loadingAttempt ?? tv.attempt);
7628
- if (index < 0) return null;
7629
- const target = key.name === "alt-left" ? index - 1 : index + 1;
7630
- const attempt = tv.attempts[target];
7631
- if (attempt === void 0) {
7632
- return step(say(state, "system", target < 0 ? "This is the first attempt." : "This is the latest attempt."));
7633
- }
7634
- return step(
7635
- { ...state, taskView: { ...tv, ...(0, import_core44.followTaskLog)(tv, { type: "picked", attempt }) } },
7636
- [{ type: "loadTaskAttempt", sessionId: state.sessionId, taskId: tv.taskId, attempt }]
7637
- );
7638
- }
7639
- if (scriptTaskView(state)) return null;
7640
- if (key.name === "ctrl-r") {
7641
- const queued = tv.view.queued[queuedTarget(tv)];
7642
- if (!queued) return step(say(state, "system", "No queued message to remove."));
7643
- if (queued.handedOver) return step(say(state, "system", "The runner already has that message; it can no longer be taken back."));
7644
- return step(state, [{ type: "removeTaskMessage", sessionId: state.sessionId, taskId: tv.taskId, messageId: queued.id }]);
7645
- }
7646
- if (key.name === "ctrl-s") return sendNow(state, tv, state.sessionId);
7647
- if (key.name === "ctrl-x") {
7648
- const task = findTask(state.tasks, tv.taskId);
7649
- if (task && (0, import_core44.resumeLabel)(task)) return step(state, [{ type: "taskAction", sessionId: state.sessionId, taskId: tv.taskId, action: "cancel-resume" }]);
7650
- return step(state, [{ type: "interruptTask", sessionId: state.sessionId, taskId: tv.taskId }]);
7651
- }
7652
- if (key.name === "ctrl-y") return answerWaiting(state, tv, state.sessionId, "allow");
7653
- if (key.name === "ctrl-t") return answerApproval(state, tv, state.sessionId, "allowForTask");
7654
- if (key.name === "ctrl-g") return answerWaiting(state, tv, state.sessionId, "deny");
7655
- if (key.name === "ctrl-n" || key.name === "ctrl-p") {
7656
- const count = tv.view.queued.length;
7657
- if (count === 0) return null;
7658
- const delta = key.name === "ctrl-n" ? 1 : -1;
7659
- const from = queuedTarget(tv);
7660
- const queuedIndex = from < 0 ? count - 1 : Math.max(0, Math.min(count - 1, from + delta));
7661
- return step({ ...state, taskView: { ...tv, queuedIndex } });
7662
- }
7663
- return null;
7664
- }
7909
+ var import_core48 = require("@ordewell/core");
7665
7910
 
7666
7911
  // src/tui/reducers/taskEdits.ts
7667
- var import_core45 = require("@ordewell/core");
7912
+ var import_core47 = require("@ordewell/core");
7668
7913
  var import_plan_utils6 = require("@ordewell/core/plan-utils");
7669
7914
  var noExecutor = (task, manual, script) => (0, import_plan_utils6.noExecutorRefusal)(task.type, manual, script);
7670
7915
  function openTaskRunnerPicker(state, task) {
@@ -7779,7 +8024,7 @@ function openTaskModePicker(state, task) {
7779
8024
  overlay: {
7780
8025
  kind: "picker",
7781
8026
  picker: picker(`Mode \xB7 ${refFor(state, task)}`, pickerItemsFor(state, action), action, {
7782
- hint: [`Modes declared by ${task.assignedRunner}.`, (0, import_core45.codexSandboxNotice)(state.modesByRunner)?.line].filter(Boolean).join(" ")
8027
+ hint: [`Modes declared by ${task.assignedRunner}.`, (0, import_core47.codexSandboxNotice)(state.modesByRunner)?.line].filter(Boolean).join(" ")
7783
8028
  })
7784
8029
  }
7785
8030
  }
@@ -7959,9 +8204,9 @@ var PLAN_SHORTCUTS = {
7959
8204
  f: "force-start",
7960
8205
  s: "skip"
7961
8206
  };
7962
- var SPAWNS_RUNNER = ["force-start", "retry", "resume-now"];
8207
+ var SPAWNS_RUNNER2 = ["force-start", "retry", "resume-now"];
7963
8208
  function taskActionEffect(state, sessionId, taskId, action, confirmed) {
7964
- const watch = SPAWNS_RUNNER.includes(action) && state.status !== "executing";
8209
+ const watch = SPAWNS_RUNNER2.includes(action) && state.status !== "executing";
7965
8210
  return {
7966
8211
  type: "taskAction",
7967
8212
  sessionId,
@@ -7972,15 +8217,15 @@ function taskActionEffect(state, sessionId, taskId, action, confirmed) {
7972
8217
  };
7973
8218
  }
7974
8219
  function confirmMark(state, task, direction, shortcut) {
7975
- const needsYes = (0, import_core46.overrideNeedsConfirmation)(task);
8220
+ const needsYes = (0, import_core48.overrideNeedsConfirmation)(task);
7976
8221
  if (!state.sessionId) return fail3(state, "No active plan.");
7977
8222
  if (!needsYes && !shortcut) return step(state, [taskActionEffect(state, state.sessionId, task.id, direction)]);
7978
8223
  const activity = state.run.activity;
7979
8224
  if (needsYes && activity?.advancesOnItsOwn) {
7980
- return fail3(state, (0, import_core46.manualOverrideStopFirst)(direction, `task ${titledRefFor(state, task)}`));
8225
+ return fail3(state, (0, import_core48.manualOverrideStopFirst)(direction, `task ${titledRefFor(state, task)}`));
7981
8226
  }
7982
8227
  const label = `task ${titledRefFor(state, task)}`;
7983
- const message = needsYes ? (0, import_core46.manualOverrideConfirmation)(direction, label) : direction === "complete" ? `Mark ${label} done? It is your own sign-off: what depends on it can start.` : `Mark ${label} not done? Your sign-off is taken back and it waits until you retry or start it.`;
8228
+ const message = needsYes ? (0, import_core48.manualOverrideConfirmation)(direction, label) : direction === "complete" ? `Mark ${label} done? It is your own sign-off: what depends on it can start.` : `Mark ${label} not done? Your sign-off is taken back and it waits until you retry or start it.`;
7984
8229
  return step({
7985
8230
  ...state,
7986
8231
  overlay: {
@@ -8033,11 +8278,11 @@ function handlePlanKey(state, key) {
8033
8278
  if (!state.sessionId || key.name !== "char") return step(state);
8034
8279
  if (key.char === "E") return startPlan(state, state.sessionId);
8035
8280
  if (key.char === "S") return step(state, [{ type: "stopExecution", sessionId: state.sessionId }]);
8036
- const shortcut = key.char === "c" && (0, import_core46.resumeLabel)(task) ? "cancel-resume" : PLAN_SHORTCUTS[key.char ?? ""];
8281
+ const shortcut = key.char === "c" && (0, import_core48.resumeLabel)(task) ? "cancel-resume" : PLAN_SHORTCUTS[key.char ?? ""];
8037
8282
  if (key.char === "r") {
8038
- return (0, import_core46.taskRowActions)(task).includes("resume-now") ? step(state, [taskActionEffect(state, state.sessionId, task.id, "resume-now")]) : step(state);
8283
+ return (0, import_core48.taskRowActions)(task).includes("resume-now") ? step(state, [taskActionEffect(state, state.sessionId, task.id, "resume-now")]) : step(state);
8039
8284
  }
8040
- if (key.char === "m") return confirmMark(state, task, (0, import_core46.markAction)(task), true);
8285
+ if (key.char === "m") return confirmMark(state, task, (0, import_core48.markAction)(task), true);
8041
8286
  const action = shortcut;
8042
8287
  if (action === "force-start" && task.mergeGate?.length) return confirmForceStartPastGate(state, task);
8043
8288
  if (action) {
@@ -8046,7 +8291,7 @@ function handlePlanKey(state, key) {
8046
8291
  if (key.char === "O") return toggleTaskOps(state, task, row.parent !== null);
8047
8292
  if (key.char === "d") return confirmRemoveTask(state, task);
8048
8293
  if (key.char === "x" && task.isolation?.state === "conflict") {
8049
- return step(state, [{ type: "resolveConflict", sessionId: state.sessionId, taskId: task.id }]);
8294
+ return step(state, [{ type: "resolveConflict", sessionId: state.sessionId, taskId: task.id, ...state.status === "executing" ? {} : { watch: true } }]);
8050
8295
  }
8051
8296
  if (key.char === "R") return openTaskRunnerPicker(state, task);
8052
8297
  if (key.char === "o") return openTaskModelPicker(state, task);
@@ -8078,9 +8323,9 @@ function commitTaskEdit(state, task, editor) {
8078
8323
  }
8079
8324
 
8080
8325
  // src/tui/reducers/turnQueue.ts
8081
- var import_core47 = require("@ordewell/core");
8326
+ var import_core49 = require("@ordewell/core");
8082
8327
  function drainQueue(state, settled) {
8083
- const next = (0, import_core47.drainNext)(state.queuedPrompts);
8328
+ const next = (0, import_core49.drainNext)(state.queuedPrompts);
8084
8329
  const ended = disarmStop({ ...settled, stopRequested: false, forceSendPending: null });
8085
8330
  if (!next) return step(ended);
8086
8331
  const spoken = say(ended, "user", next.text);
@@ -8095,7 +8340,7 @@ function restoreForced(state) {
8095
8340
  if (!forced) return state;
8096
8341
  const cleared = { ...state, forceSendPending: null };
8097
8342
  if (forced.queueIndex === void 0) {
8098
- const text = (0, import_core47.aheadOfDraft)(forced.text, state.editor.text);
8343
+ const text = (0, import_core49.aheadOfDraft)(forced.text, state.editor.text);
8099
8344
  return { ...cleared, editor: { ...state.editor, text, cursor: text.length } };
8100
8345
  }
8101
8346
  const queued = [...state.queuedPrompts];
@@ -8111,15 +8356,15 @@ function stopPlanning(state, sessionId) {
8111
8356
  return step(queueToEditor(cutTurn({ ...dismissed, stopArmed: false, stopRequested: true, forceSendPending: null })), [{ type: "cancelPlanning", sessionId }]);
8112
8357
  }
8113
8358
  function queueToEditor(state) {
8114
- const all = (0, import_core47.unsendAll)(state.queuedPrompts);
8359
+ const all = (0, import_core49.unsendAll)(state.queuedPrompts);
8115
8360
  if (!all) return state;
8116
- const text = (0, import_core47.aheadOfDraft)(all.text, state.editor.text);
8361
+ const text = (0, import_core49.aheadOfDraft)(all.text, state.editor.text);
8117
8362
  return { ...state, queuedPrompts: all.rest, editor: { ...state.editor, text, cursor: all.text.length } };
8118
8363
  }
8119
8364
  function unsendLatest(state) {
8120
- const latest = (0, import_core47.unsendLatest)(state.queuedPrompts);
8365
+ const latest = (0, import_core49.unsendLatest)(state.queuedPrompts);
8121
8366
  if (!latest) return step(state);
8122
- const text = (0, import_core47.aheadOfDraft)(latest.text, state.editor.text);
8367
+ const text = (0, import_core49.aheadOfDraft)(latest.text, state.editor.text);
8123
8368
  return step({
8124
8369
  ...state,
8125
8370
  queuedPrompts: latest.rest,
@@ -8135,7 +8380,7 @@ function forceSendPlanner(state) {
8135
8380
  { type: "forceSendPlannerMessage", sessionId: state.sessionId, message: text }
8136
8381
  ]);
8137
8382
  }
8138
- const latest = (0, import_core47.unsendLatest)(state.queuedPrompts);
8383
+ const latest = (0, import_core49.unsendLatest)(state.queuedPrompts);
8139
8384
  if (!latest) return step(say(state, "system", "Nothing to send now: type a message, or queue one with enter."));
8140
8385
  const forced = { text: latest.text, queueIndex: latest.rest.length };
8141
8386
  return step({ ...state, queuedPrompts: latest.rest, stopRequested: false, forceSendPending: forced }, [
@@ -8151,7 +8396,7 @@ function plannerEscape(state) {
8151
8396
  }
8152
8397
 
8153
8398
  // src/tui/reducers/incoming.ts
8154
- var import_core48 = require("@ordewell/core");
8399
+ var import_core50 = require("@ordewell/core");
8155
8400
  var planText = (value, fallback = "") => sanitize(String(value ?? fallback));
8156
8401
  var planLabel = (value, fallback = "") => planText(value, fallback).replace(/\n+/g, " ");
8157
8402
  var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
@@ -8206,7 +8451,7 @@ function runTaskToView(t) {
8206
8451
  ...Array.isArray(model.availableVariants) ? { availableVariants: model.availableVariants.map((variant) => planLabel(variant)) } : {}
8207
8452
  } : void 0,
8208
8453
  isolation: t.isolation,
8209
- awaitingReason: t.status === "awaiting_user" && (0, import_core48.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
8454
+ awaitingReason: t.status === "awaiting_user" && (0, import_core50.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
8210
8455
  ...t.status === "awaiting_user" && t.awaitingReason === "usage-limit" && typeof t.resumeAt === "string" ? { resumeAt: t.resumeAt } : {},
8211
8456
  ...t.status === "skipped" && typeof t.skipReason === "string" ? { skipReason: sanitize(t.skipReason) } : {},
8212
8457
  checkpoint: typeof t.checkpoint === "string" ? sanitize(t.checkpoint) : void 0,
@@ -8214,6 +8459,7 @@ function runTaskToView(t) {
8214
8459
  awaitingApproval: t.awaitingApproval,
8215
8460
  ...t.waitsForApproval === true ? { waitsForApproval: true } : {},
8216
8461
  ...typeof t.holdReason === "string" && t.holdReason.trim() ? { holdReason: sanitize(t.holdReason) } : {},
8462
+ ...t.loopStranded === true ? { loopStranded: true } : {},
8217
8463
  ...t.ops === true ? { ops: true } : {},
8218
8464
  ...t.research === true ? { research: true } : {},
8219
8465
  // Drawn through the same sanitize/wrap as any other plan text; keeping the
@@ -8233,13 +8479,13 @@ function supervisorOfRun(runSettings) {
8233
8479
  const unsupervised = typeof runSettings.unsupervised === "string" ? runSettings.unsupervised : void 0;
8234
8480
  return {
8235
8481
  runSupervisor: {
8236
- level: (0, import_core48.isSupervisorLevel)(runSettings.supervisorLevel) ? runSettings.supervisorLevel : "off",
8482
+ level: (0, import_core50.isSupervisorLevel)(runSettings.supervisorLevel) ? runSettings.supervisorLevel : "off",
8237
8483
  ...unsupervised ? { unsupervised } : {}
8238
8484
  }
8239
8485
  };
8240
8486
  }
8241
8487
  function withRun(state, run) {
8242
- return {
8488
+ return refillInbox({
8243
8489
  ...state,
8244
8490
  run,
8245
8491
  tasks: run.tasks.map(runTaskToView),
@@ -8247,10 +8493,10 @@ function withRun(state, run) {
8247
8493
  runMerge: run.runMerge ? { waitingAtGate: run.runMerge.waitingAtGate, paused: run.runMerge.paused, handoff: { repos: run.runMerge.repos, landed: run.runMerge.landed } } : null,
8248
8494
  runAutoMerge: run.runSettings?.mergeAtGatesAutomatically ?? null,
8249
8495
  ...supervisorOfRun(run.runSettings)
8250
- };
8496
+ });
8251
8497
  }
8252
8498
  function foldRun(state, input) {
8253
- const run = (0, import_core48.reduceRun)(state.run, input);
8499
+ const run = (0, import_core50.reduceRun)(state.run, input);
8254
8500
  return run === state.run ? state : withRun(state, run);
8255
8501
  }
8256
8502
  function applySettings(state, settings, applyRun = true) {
@@ -8269,6 +8515,7 @@ function applySettings(state, settings, applyRun = true) {
8269
8515
  }
8270
8516
  function followSession(state, message) {
8271
8517
  const heard = hear(state, message);
8518
+ if (message.type === "planner_turn_started") return { ...heard, plannerDetecting: false };
8272
8519
  if (message.type === "approval_request") {
8273
8520
  const { id, kind, subject, scope, detail, body } = message;
8274
8521
  if (kind === "runner_tool") return heard;
@@ -8278,7 +8525,7 @@ function followSession(state, message) {
8278
8525
  }
8279
8526
 
8280
8527
  // src/tui/reducers/settings.ts
8281
- var import_core49 = require("@ordewell/core");
8528
+ var import_core51 = require("@ordewell/core");
8282
8529
  var SUPERVISOR_DETAIL = {
8283
8530
  off: "The planner speaks only when you write",
8284
8531
  watch: "The planner follows your runs and suggests what to do",
@@ -8293,7 +8540,7 @@ var supervisorOff = (_state, choices) => choices.supervisor === "off";
8293
8540
  var SETTINGS = {
8294
8541
  title: "Settings",
8295
8542
  afterSave: "stay",
8296
- hint: "Supervisor, automerge and autorun apply to this session; your last change is the default for new ones. Autonomy and Parallel tasks apply to every session.",
8543
+ hint: `${import_core51.DELEGATION_OFFERED ? "Supervisor, automerge, autorun and delegation" : "Supervisor, automerge and autorun"} apply to this session; your last change is the default for new ones. Autonomy and Parallel tasks apply to every session.`,
8297
8544
  steps: [
8298
8545
  {
8299
8546
  id: "autonomy",
@@ -8303,7 +8550,7 @@ var SETTINGS = {
8303
8550
  {
8304
8551
  id: "guarded",
8305
8552
  label: "Guarded",
8306
- detail: [`New plans run tasks in each runner's safer mode`, (0, import_core49.codexSandboxNotice)(state.modesByRunner)?.line].filter(Boolean).join(" \xB7 "),
8553
+ detail: [`New plans run tasks in each runner's safer mode`, (0, import_core51.codexSandboxNotice)(state.modesByRunner)?.line].filter(Boolean).join(" \xB7 "),
8307
8554
  selected: !state.autonomous
8308
8555
  }
8309
8556
  ]
@@ -8313,7 +8560,7 @@ var SETTINGS = {
8313
8560
  label: "Supervisor",
8314
8561
  items: (state) => ["off", "watch", "act"].map((level) => ({
8315
8562
  id: level,
8316
- label: import_core49.SUPERVISOR_LEVEL_LABEL[level],
8563
+ label: import_core51.SUPERVISOR_LEVEL_LABEL[level],
8317
8564
  detail: SUPERVISOR_DETAIL[level],
8318
8565
  selected: state.runSettings.supervisorLevel === level
8319
8566
  }))
@@ -8348,7 +8595,14 @@ var SETTINGS = {
8348
8595
  label: "Parallel",
8349
8596
  hint: "How many AI tasks run at once, a live run included",
8350
8597
  number: { min: 1, current: (state) => state.maxParallel }
8351
- }
8598
+ },
8599
+ // Not offered until a delegated task exists (ADR-0055 D21).
8600
+ ...import_core51.DELEGATION_OFFERED ? [{
8601
+ id: "delegation",
8602
+ label: "Delegation",
8603
+ hint: "The token budget of a tree of plans; 0 keeps delegation off",
8604
+ number: { min: 0, current: (state) => state.runSettings.delegationTokens }
8605
+ }] : []
8352
8606
  ],
8353
8607
  save: saveChoices
8354
8608
  };
@@ -8360,12 +8614,12 @@ function saveChoices(state, choices) {
8360
8614
  const autonomous = choices.autonomy === "full";
8361
8615
  if (autonomous !== state.autonomous) {
8362
8616
  save2.autonomous = autonomous;
8363
- changed.push(`Autonomy ${(0, import_core49.autonomyLevelLabel)(autonomous)}`);
8617
+ changed.push(`Autonomy ${(0, import_core51.autonomyLevelLabel)(autonomous)}`);
8364
8618
  }
8365
8619
  const level = choices.supervisor;
8366
8620
  if (level !== state.runSettings.supervisorLevel) {
8367
8621
  run.supervisorLevel = level;
8368
- changed.push(`Supervisor ${import_core49.SUPERVISOR_LEVEL_LABEL[level]}`);
8622
+ changed.push(`Supervisor ${import_core51.SUPERVISOR_LEVEL_LABEL[level]}`);
8369
8623
  }
8370
8624
  if (choices.retries !== void 0 && Number(choices.retries) !== state.runSettings.supervisorRetryLimit) {
8371
8625
  run.supervisorRetryLimit = Number(choices.retries);
@@ -8386,9 +8640,15 @@ function saveChoices(state, choices) {
8386
8640
  save2.maxParallel = parallel;
8387
8641
  changed.push(`Parallel ${parallel}`);
8388
8642
  }
8643
+ const delegationTokens = Number(choices.delegation);
8644
+ if (choices.delegation !== void 0 && delegationTokens !== state.runSettings.delegationTokens) {
8645
+ run.delegationTokens = delegationTokens;
8646
+ changed.push(delegationTokens > 0 ? `Delegation on, ${delegationTokens} tokens` : "Delegation off");
8647
+ }
8389
8648
  if (changed.length === 0) return step(state);
8390
8649
  const effect = { ...save2, message: `Settings: ${changed.join(" \xB7 ")}` };
8391
- const risks = [...run.supervisorLevel === "act" ? [import_core49.ACT_RISK] : [], ...run.autorun ? [import_core49.AUTORUN_RISK] : []];
8650
+ const delegationOn = state.runSettings.delegationTokens === 0 && (run.delegationTokens ?? 0) > 0;
8651
+ const risks = [...run.supervisorLevel === "act" ? [import_core51.ACT_RISK] : [], ...run.autorun ? [import_core51.AUTORUN_RISK] : [], ...delegationOn ? [import_core51.DELEGATION_RISK] : []];
8392
8652
  if (risks.length === 0) return saveSettings(state, effect);
8393
8653
  return step({
8394
8654
  ...state,
@@ -8500,6 +8760,8 @@ function runCommand(state, parsed) {
8500
8760
  });
8501
8761
  case "open":
8502
8762
  return taskCommand(state, args[0], (sessionId, taskId) => openTaskView(state, sessionId, taskId));
8763
+ case "inbox":
8764
+ return openInbox(state);
8503
8765
  case "checkpoint":
8504
8766
  return taskCommand(state, args[0], (sessionId, taskId) => {
8505
8767
  const answer = args[1]?.toLowerCase();
@@ -8508,6 +8770,8 @@ function runCommand(state, parsed) {
8508
8770
  if (answer === "approve" && reason) return fail3(state, "Approving takes no note: reject with a reason to tell the task something.");
8509
8771
  return answerCheckpoint(state, sessionId, taskId, answer, reason);
8510
8772
  });
8773
+ case "revert":
8774
+ return taskCommand(state, args[0], (sessionId, taskId) => step(state, [{ type: "previewRevert", sessionId, taskId }]));
8511
8775
  case "task-runner":
8512
8776
  return taskRunnerCommand(state, args);
8513
8777
  case "task-model":
@@ -8577,8 +8841,8 @@ function newSession(state) {
8577
8841
  goal: "",
8578
8842
  taskView: null,
8579
8843
  planApproved: false,
8580
- conversation: import_core50.EMPTY_CONVERSATION,
8581
- turnGate: import_core50.NO_TURN,
8844
+ conversation: import_core52.EMPTY_CONVERSATION,
8845
+ turnGate: import_core52.NO_TURN,
8582
8846
  selectedTask: 0,
8583
8847
  expandedTaskId: null,
8584
8848
  taskEditor: null,
@@ -8591,16 +8855,16 @@ function newSession(state) {
8591
8855
  // Prompts belong to the session that raised them; the old planner is gone
8592
8856
  // and its pending requests deny on their own timeout.
8593
8857
  pendingApprovals: [],
8594
- queuedPrompts: import_core50.EMPTY_HOLD,
8858
+ queuedPrompts: import_core52.EMPTY_HOLD,
8595
8859
  stopArmed: false,
8596
8860
  forceSendPending: null,
8597
8861
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
8598
- }, import_core50.EMPTY_RUN), closeEffects);
8862
+ }, import_core52.EMPTY_RUN), closeEffects);
8599
8863
  }
8600
8864
  function setPlanner(state, args) {
8601
8865
  if (args[0]) {
8602
8866
  const id = args[0].toLowerCase();
8603
- if (!(0, import_core50.isPlannerBackend)(id)) {
8867
+ if (!(0, import_core52.isPlannerBackend)(id)) {
8604
8868
  return fail3(state, `Unknown planner: ${id}. Run /planner with no arguments to see the list.`);
8605
8869
  }
8606
8870
  return step(state, [{ type: "setPlanner", provider: id }]);
@@ -8611,7 +8875,7 @@ function setPlanner(state, args) {
8611
8875
  function setKey(state, args) {
8612
8876
  if (args[0] === "set" && args[1]) {
8613
8877
  const provider = args[1].toLowerCase();
8614
- if (!(0, import_core50.isApiProvider)(provider)) {
8878
+ if (!(0, import_core52.isApiProvider)(provider)) {
8615
8879
  return fail3(state, `Unknown provider: ${provider}. Run /key with no arguments to see the list.`);
8616
8880
  }
8617
8881
  if (!args[2]) {
@@ -8622,16 +8886,16 @@ function setKey(state, args) {
8622
8886
  }
8623
8887
  return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
8624
8888
  }
8625
- const items = import_core50.PROVIDER_PRIORITY.map((id) => ({
8889
+ const items = import_core52.PROVIDER_PRIORITY.map((id) => ({
8626
8890
  id,
8627
- label: (0, import_core50.getProviderMeta)(id).label,
8628
- detail: (0, import_core50.getProviderMeta)(id).apiKeyEnvVar,
8891
+ label: (0, import_core52.getProviderMeta)(id).label,
8892
+ detail: (0, import_core52.getProviderMeta)(id).apiKeyEnvVar,
8629
8893
  selected: state.configuredProviders.includes(id)
8630
8894
  }));
8631
8895
  return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
8632
8896
  }
8633
8897
  function keyPrompt(provider) {
8634
- const meta = (0, import_core50.getProviderMeta)(provider);
8898
+ const meta = (0, import_core52.getProviderMeta)(provider);
8635
8899
  return {
8636
8900
  kind: "prompt",
8637
8901
  title: `${meta.label} API key`,
@@ -8711,13 +8975,13 @@ function login(state, args) {
8711
8975
  return step(say(state, "system", "Still checking which coding agents are installed. Try /login again in a moment."), [{ type: "loadRunners" }]);
8712
8976
  }
8713
8977
  const named = args.join(" ").trim();
8714
- const runner = named ? matchRunner(state, named) : (0, import_core50.runnerForProvider)(state.plannerProvider);
8978
+ const runner = named ? matchRunner(state, named) : (0, import_core52.runnerForProvider)(state.plannerProvider);
8715
8979
  if (!runner) {
8716
8980
  return step(say(state, "system", named ? `No installed coding agent matches "${named}". ${signInHint(state)}` : `The active planner is not a coding agent, so there is no agent to sign in. ${signInHint(state)}`));
8717
8981
  }
8718
8982
  const row = state.runners.find((r) => r.id === runner);
8719
8983
  if (!row) {
8720
- const label = (0, import_core50.getProviderMeta)(state.plannerProvider)?.label ?? runner;
8984
+ const label = (0, import_core52.getProviderMeta)(state.plannerProvider)?.label ?? runner;
8721
8985
  return step(say(state, "system", `${label} is not installed, so there is nothing to sign in. /runners lists what is.`));
8722
8986
  }
8723
8987
  const command = row.auth?.loginCommand;
@@ -8838,9 +9102,14 @@ function runConfirm(state, overlay) {
8838
9102
  const { taskId, direction, seen } = overlay.action;
8839
9103
  const task = state.sessionId ? findTask(state.tasks, taskId) : void 0;
8840
9104
  if (!state.sessionId || !task) return fail3(closed, "That task is no longer in the plan: nothing was changed.");
8841
- return step(closed, [taskActionEffect(closed, state.sessionId, taskId, direction, (0, import_core51.overrideNeedsConfirmation)(task) ? { seen } : void 0)]);
9105
+ return step(closed, [taskActionEffect(closed, state.sessionId, taskId, direction, (0, import_core53.overrideNeedsConfirmation)(task) ? { seen } : void 0)]);
9106
+ }
9107
+ if (overlay.action.kind === "revert-task") {
9108
+ if (!state.sessionId) return step(closed);
9109
+ return step(closed, [{ type: "revertTask", sessionId: state.sessionId, taskId: overlay.action.taskId, seen: overlay.action.seen }]);
8842
9110
  }
8843
9111
  if (overlay.action.kind === "save-settings") return answerSaveSettings(state, overlay.action, true);
9112
+ if (overlay.action.kind === "answer-escalation") return confirmedAnswer(state, overlay.action.escalationId, overlay.action.option, overlay.action.note);
8844
9113
  if (overlay.action.kind === "merge-run" || overlay.action.kind === "discard-run") {
8845
9114
  return confirmedHandoff(state, overlay.action.kind);
8846
9115
  }
@@ -8868,6 +9137,7 @@ function handlePromptKey(state, overlay, key) {
8868
9137
  if (key.name === "enter") {
8869
9138
  const value2 = overlay.value.trim();
8870
9139
  const closed = { ...state, overlay: null };
9140
+ if (overlay.action.kind === "escalation-note") return noteEntered(state, overlay.action, value2);
8871
9141
  if (!value2) return step(closed);
8872
9142
  if (overlay.action.kind === "api-key") {
8873
9143
  return step(closed, [{ type: "setApiKey", provider: overlay.action.provider, key: value2 }]);
@@ -8944,12 +9214,16 @@ function choose2(state, picker2, item) {
8944
9214
  switch (picker2.action.kind) {
8945
9215
  case "load-session":
8946
9216
  return step(closed, [{ type: "loadSession", sessionId: item.id }]);
9217
+ case "inbox":
9218
+ return chooseEscalation(state, item.id);
9219
+ case "inbox-answer":
9220
+ return chooseAnswer(state, picker2.action.escalationId, item.id);
8947
9221
  case "rewind":
8948
9222
  return pickRewindTarget(state, Number(item.id));
8949
9223
  case "delete-session":
8950
9224
  return step(closed, [{ type: "deleteSession", sessionId: item.id }]);
8951
9225
  case "set-key":
8952
- return (0, import_core51.isApiProvider)(item.id) ? step({ ...state, overlay: keyPrompt(item.id) }) : step(closed);
9226
+ return (0, import_core53.isApiProvider)(item.id) ? step({ ...state, overlay: keyPrompt(item.id) }) : step(closed);
8953
9227
  case "choose-allowlist-runner": {
8954
9228
  const action = { kind: "set-allowlist", runner: item.id };
8955
9229
  const items = pickerItemsFor(state, action);
@@ -9005,6 +9279,24 @@ function choose2(state, picker2, item) {
9005
9279
  }
9006
9280
  }
9007
9281
 
9282
+ // src/tui/reducers/revert.ts
9283
+ var import_core54 = require("@ordewell/core");
9284
+ function revertPreviewed(state, taskId, preview) {
9285
+ if (!preview.revertible) return fail3(state, sanitize(preview.message));
9286
+ const asked = preview.tasks.find((t) => t.taskId === taskId) ?? preview.tasks.at(-1);
9287
+ const lines = preview.tasks.map((t) => ` ${sanitize((0, import_core54.revertListLine)(t))}`);
9288
+ return step({
9289
+ ...state,
9290
+ overlay: {
9291
+ kind: "confirm",
9292
+ title: `Revert ${asked ? `${(0, import_core54.revertTaskRef)(asked)} ${sanitize(asked.title)}` : "this task"}?`,
9293
+ message: import_core54.revertEffect,
9294
+ note: ["Reverting:", ...lines].join("\n"),
9295
+ action: { kind: "revert-task", taskId, seen: preview.tasks.map((t) => t.taskId) }
9296
+ }
9297
+ });
9298
+ }
9299
+
9008
9300
  // src/tui/reducers/compaction.ts
9009
9301
  var running = (state) => state.conversation.blocks.some((b) => b.type === "compaction" && b.status === "running");
9010
9302
  function followCompaction(state, message) {
@@ -9048,7 +9340,7 @@ function reduceAction(state, action) {
9048
9340
  // (`task-1`), so nothing of the last one's — a merge gate, a checkpoint, its
9049
9341
  // tasks — may carry over into this.
9050
9342
  case "sessionStarted":
9051
- return step(withRun({ ...state, sessionId: action.sessionId, goal: action.goal, accountLimits: [], servedModels: {}, taskView: null, status: "idle" }, import_core52.EMPTY_RUN));
9343
+ return step(withRun({ ...state, sessionId: action.sessionId, goal: action.goal, accountLimits: [], servedModels: {}, taskView: null, status: "idle" }, import_core55.EMPTY_RUN));
9052
9344
  // A loaded session is a different one: the turn the old one had out, the
9053
9345
  // prompts held behind it and a stop armed against it are not this one's.
9054
9346
  // Nor is an open prompt editor: task ids repeat across plans, so the
@@ -9069,15 +9361,15 @@ function reduceAction(state, action) {
9069
9361
  planner: "idle",
9070
9362
  plannerPause: null,
9071
9363
  plannerResend: null,
9072
- turnGate: import_core52.NO_TURN,
9364
+ turnGate: import_core55.NO_TURN,
9073
9365
  pendingApprovals: [],
9074
- queuedPrompts: import_core52.EMPTY_HOLD,
9366
+ queuedPrompts: import_core55.EMPTY_HOLD,
9075
9367
  stopArmed: false,
9076
9368
  stopRequested: false,
9077
9369
  forceSendPending: null,
9078
9370
  overlay: state.overlay?.kind === "approval" ? null : state.overlay,
9079
9371
  runSettingsKnown: false
9080
- }, import_core52.EMPTY_RUN));
9372
+ }, import_core55.EMPTY_RUN));
9081
9373
  // The daemon registers a session before its opening turn runs, and drops it
9082
9374
  // itself when it refuses the turn or the goal is discarded; any other
9083
9375
  // failure it answered leaves no conversation a reply could continue, so the
@@ -9097,20 +9389,20 @@ function reduceAction(state, action) {
9097
9389
  status: "idle",
9098
9390
  plannerPause: null,
9099
9391
  plannerResend: null,
9100
- turnGate: import_core52.NO_TURN,
9392
+ turnGate: import_core55.NO_TURN,
9101
9393
  pendingApprovals: [],
9102
9394
  stopArmed: false,
9103
9395
  forceSendPending: null,
9104
9396
  overlay: state.overlay?.kind === "approval" ? null : state.overlay,
9105
9397
  runSettingsKnown: action.unreached ? state.runSettingsKnown : false
9106
- }, import_core52.EMPTY_RUN));
9398
+ }, import_core55.EMPTY_RUN));
9107
9399
  // Deleting the session on screen stops it in the daemon; a view left on it
9108
9400
  // would answer every next message with a session-not-found.
9109
9401
  case "sessionDeleted":
9110
9402
  return action.sessionId === state.sessionId ? newSession(state) : step(state);
9111
9403
  case "chatRestored":
9112
9404
  if (stale(state, action.sessionId)) return step(state);
9113
- return step({ ...state, conversation: (0, import_core52.fromTranscript)(action.history, action.researchLog, action.plannerUsage, action.pendingTurns), scroll: 0 });
9405
+ return step({ ...state, conversation: (0, import_core55.fromTranscript)(action.history, action.researchLog, action.plannerUsage, action.pendingTurns), scroll: 0 });
9114
9406
  case "planRefreshed":
9115
9407
  if (stale(state, action.sessionId)) return step(state);
9116
9408
  return step(showPlan(state, action.plan));
@@ -9126,7 +9418,7 @@ function reduceAction(state, action) {
9126
9418
  return step({ ...settled, forceSendPending: null });
9127
9419
  }
9128
9420
  if (state.forceSendPending) return step({ ...settled, planner: "planning", forceSendPending: null });
9129
- return drainQueue(state, settled);
9421
+ return drainQueue(state, { ...settled, turnGate: (0, import_core55.closeTurn)(settled.turnGate) });
9130
9422
  }
9131
9423
  // The call that carried the turn's reply died, so nothing will say the turn
9132
9424
  // is over but the daemon. The prompts held behind it, and the turn itself,
@@ -9153,7 +9445,7 @@ function reduceAction(state, action) {
9153
9445
  const gate = action.plannerTurnId === state.turnGate.open ? state.turnGate : { open: action.plannerTurnId, stopped: null };
9154
9446
  return step(showPlan({ ...state, turnGate: gate }, action.plan));
9155
9447
  }
9156
- const redrawn = reduceAction({ ...state, turnGate: import_core52.NO_TURN }, action.chat).state;
9448
+ const redrawn = reduceAction({ ...state, turnGate: import_core55.NO_TURN }, action.chat).state;
9157
9449
  const settled = reduceAction(redrawn, { type: "planUpdated", plan: action.plan, sessionId: action.sessionId });
9158
9450
  if (state.planner === "joined") return settled;
9159
9451
  return { ...settled, state: say(settled.state, "system", `The reply to your last message was lost on the way${because(action.reason)}; this is what the server holds.`) };
@@ -9171,6 +9463,7 @@ function reduceAction(state, action) {
9171
9463
  case "sessionMessage": {
9172
9464
  if (stale(state, action.sessionId)) return step(state);
9173
9465
  if (action.message.type === "planner_compaction") return followCompaction(state, action.message);
9466
+ if (action.message.type === "planner_detection") return step({ ...state, plannerDetecting: action.message.waiting });
9174
9467
  const next = followSession(state, action.message);
9175
9468
  const { sessionId } = state;
9176
9469
  const gateClosed = action.message.type === "planner_turn_ended" && next.turnGate.open === null;
@@ -9205,15 +9498,15 @@ function reduceAction(state, action) {
9205
9498
  case "taskStarted": {
9206
9499
  if (stale(state, action.sessionId)) return step(state);
9207
9500
  const existing = findTask(state.tasks, action.taskId);
9208
- if (existing && (0, import_core31.isTaskRunning)(existing)) return step(state);
9501
+ if (existing && (0, import_core33.isTaskRunning)(existing)) return step(state);
9209
9502
  const folded = foldRun(state, {
9210
9503
  type: "task_started",
9211
9504
  taskId: action.taskId,
9212
9505
  order: action.order ?? 0,
9213
9506
  title: action.title,
9214
- runner: action.runner ?? import_core52.DEFAULT_RUNNERS[0]
9507
+ runner: action.runner ?? import_core55.DEFAULT_RUNNERS[0]
9215
9508
  });
9216
- const spoken = say(folded, "system", (0, import_core52.taskStartedNotice)(action.title, action.runner));
9509
+ const spoken = say(folded, "system", (0, import_core55.taskStartedNotice)(action.title, action.runner));
9217
9510
  return step({ ...spoken, status: "executing" });
9218
9511
  }
9219
9512
  case "taskCheckpoint":
@@ -9231,7 +9524,8 @@ function reduceAction(state, action) {
9231
9524
  tasks: action.tasks,
9232
9525
  ...action.runMerge !== void 0 ? { runMerge: action.runMerge } : {},
9233
9526
  ...action.usage !== void 0 ? { usage: action.usage } : {},
9234
- ...action.activity !== void 0 ? { activity: action.activity } : {}
9527
+ ...action.activity !== void 0 ? { activity: action.activity } : {},
9528
+ ...action.escalations !== void 0 ? { escalations: action.escalations } : {}
9235
9529
  });
9236
9530
  const status = runStatus(next, next.tasks);
9237
9531
  return step({ ...next, status });
@@ -9266,7 +9560,7 @@ function reduceAction(state, action) {
9266
9560
  completed: state.tasks.filter((t) => t.status === "completed").length,
9267
9561
  total: state.tasks.length,
9268
9562
  failed: state.tasks.filter((t) => t.status === "failed").length,
9269
- blocked: (0, import_core52.blockingFailures)(state.tasks).size,
9563
+ blocked: (0, import_core55.blockingFailures)(state.tasks).size,
9270
9564
  skipped: state.tasks.filter((t) => t.status === "skipped").length
9271
9565
  };
9272
9566
  const failures = `${failed > 0 ? ` \xB7 ${failed} failed` : ""}${blocked ? ` \xB7 ${blocked} blocked` : ""}${skipped ? ` \xB7 ${skipped} skipped` : ""}`;
@@ -9334,6 +9628,8 @@ function reduceAction(state, action) {
9334
9628
  ));
9335
9629
  case "sessionsLoaded":
9336
9630
  return step(refillPicker({ ...state, sessions: action.sessions }, ["load-session", "delete-session"]));
9631
+ case "revertPreviewed":
9632
+ return stale(state, action.sessionId) ? step(state) : revertPreviewed(state, action.taskId, action.preview);
9337
9633
  case "rewindTargetsLoaded":
9338
9634
  if (stale(state, action.sessionId)) return step(state);
9339
9635
  if (action.pick !== void 0) return pickRewindTarget({ ...state, rewindTargets: action.targets }, action.pick);
@@ -9353,16 +9649,16 @@ function reduceAction(state, action) {
9353
9649
  taskEditor: null,
9354
9650
  status: "idle",
9355
9651
  planner: "idle",
9356
- turnGate: import_core52.NO_TURN,
9652
+ turnGate: import_core55.NO_TURN,
9357
9653
  planApproved: false,
9358
9654
  pendingApprovals: [],
9359
- queuedPrompts: import_core52.EMPTY_HOLD,
9655
+ queuedPrompts: import_core55.EMPTY_HOLD,
9360
9656
  stopArmed: false,
9361
9657
  stopRequested: false,
9362
9658
  forceSendPending: null,
9363
9659
  overlay: state.overlay?.kind === "approval" ? null : state.overlay,
9364
9660
  runSettingsKnown: false
9365
- }, import_core52.EMPTY_RUN));
9661
+ }, import_core55.EMPTY_RUN));
9366
9662
  // The rewound message comes back as a draft to edit and resend, so it lands
9367
9663
  // as if just typed: history is not being browsed, and nothing is parked.
9368
9664
  case "inputPrefilled":
@@ -9418,7 +9714,7 @@ function reduceAction(state, action) {
9418
9714
  status: state.status === "starting" ? "idle" : state.status
9419
9715
  };
9420
9716
  if (!plannerInFlight(state)) return step(reported);
9421
- return drainQueue(state, reported);
9717
+ return drainQueue(state, { ...reported, turnGate: (0, import_core55.closeTurn)(reported.turnGate) });
9422
9718
  }
9423
9719
  // As `failed`: an opening that let itself go asks on the welcome it left,
9424
9720
  // never over a session loaded meanwhile.
@@ -9427,6 +9723,7 @@ function reduceAction(state, action) {
9427
9723
  return step({
9428
9724
  ...state,
9429
9725
  planner: "idle",
9726
+ turnGate: plannerInFlight(state) ? (0, import_core55.closeTurn)(state.turnGate) : state.turnGate,
9430
9727
  overlay: {
9431
9728
  kind: "confirm",
9432
9729
  title: "Initialize new workspace?",
@@ -9459,7 +9756,7 @@ function reduceAction(state, action) {
9459
9756
  // Whether there is anything to animate is the app loop's call (it owns the
9460
9757
  // timer); a tick that arrives simply advances the frame.
9461
9758
  case "spinnerTick":
9462
- return step({ ...state, spinnerFrame: (state.spinnerFrame + (action.by ?? 1)) % SPINNER_CYCLE });
9759
+ return step({ ...state, spinnerFrame: (state.spinnerFrame + 1) % SPINNER_CYCLE });
9463
9760
  // The scheduled expiry of an armed stop; a token that is no longer the live
9464
9761
  // arm is a stale timer (the user disarmed and re-armed in between) and is
9465
9762
  // ignored, so it cannot cut a newer arm short.
@@ -9501,7 +9798,7 @@ function runStatus(state, tasks) {
9501
9798
  const activity = state.run.activity;
9502
9799
  if (activity?.starting) return "starting";
9503
9800
  if (activity?.running) return "executing";
9504
- if (tasks.some(import_core31.isTaskRunning)) return "executing";
9801
+ if (tasks.some(import_core33.isTaskRunning)) return "executing";
9505
9802
  if (tasks.some((t) => t.status === "awaiting_user") || state.runMerge?.paused) return "executing";
9506
9803
  return "idle";
9507
9804
  }
@@ -9533,7 +9830,7 @@ function handleKey2(state, key) {
9533
9830
  if (key.name === "wheelignored") return step(state);
9534
9831
  if (key.name === "tab" && state.focus === "chat") {
9535
9832
  const token = activeToken(state.editor.text, state.editor.cursor);
9536
- const matches = token ? composerCompletions(token, state.taskView ? (0, import_core52.taskLogCommands)(state.taskView.view) : null) : [];
9833
+ const matches = token ? composerCompletions(token, state.taskView ? (0, import_core55.taskLogCommands)(state.taskView.view) : null) : [];
9537
9834
  if (token && matches.length > 0) {
9538
9835
  const { text: full } = state.editor;
9539
9836
  const completed = `/${matches[0]} `;
@@ -9554,9 +9851,9 @@ function handleKey2(state, key) {
9554
9851
  return scroll2 !== null ? scrollChat(state, scroll2) : step(state);
9555
9852
  }
9556
9853
  }
9557
- if (key.name === "enter") return submit(state);
9854
+ if (key.name === "enter") return submit2(state);
9558
9855
  if (key.name === "ctrl-s") {
9559
- return (plannerInFlight(state) || state.turnGate.open !== null) && !builtInCommand(state.editor.text) ? forceSendPlanner(state) : submit(state);
9856
+ return (plannerInFlight(state) || state.turnGate.open !== null) && !builtInCommand(state.editor.text) ? forceSendPlanner(state) : submit2(state);
9560
9857
  }
9561
9858
  if (key.name === "escape") return step({ ...state, editor: { ...state.editor, text: "", cursor: 0 } });
9562
9859
  const scroll = scrollDelta(key, state, "chat");
@@ -9570,7 +9867,7 @@ function builtInCommand(text) {
9570
9867
  const known = command && findCommand(command.name);
9571
9868
  return command && known && known.source !== "skill" ? command : null;
9572
9869
  }
9573
- function submit(state) {
9870
+ function submit2(state) {
9574
9871
  const text = state.editor.text.trim();
9575
9872
  if (!text) return step(state);
9576
9873
  const cleared = { ...state, editor: commit(state.editor) };
@@ -9579,7 +9876,7 @@ function submit(state) {
9579
9876
  const taskId = state.taskView.taskId;
9580
9877
  const task = findTask(state.tasks, taskId);
9581
9878
  const command = parseRunnerCommand(text);
9582
- const offered2 = (0, import_core52.taskLogCommands)(state.taskView.view);
9879
+ const offered2 = (0, import_core55.taskLogCommands)(state.taskView.view);
9583
9880
  if (command && offered2.length > 0 && !runnerCommandFor(offered2, command.name)) {
9584
9881
  const notice = `/${command.name} is not a command ${task?.assignedRunner ?? "the runner"} offers this task: type / to see them.`;
9585
9882
  return step({ ...cleared, taskView: { ...state.taskView, notice } });
@@ -9598,7 +9895,7 @@ function submit(state) {
9598
9895
  const builtIn = builtInCommand(text);
9599
9896
  if (builtIn) return runCommand(cleared, builtIn);
9600
9897
  if (plannerInFlight(state)) {
9601
- return step({ ...cleared, queuedPrompts: (0, import_core52.holdPrompt)(state.queuedPrompts, text) });
9898
+ return step({ ...cleared, queuedPrompts: (0, import_core55.holdPrompt)(state.queuedPrompts, text) });
9602
9899
  }
9603
9900
  const spoken = say(cleared, "user", text);
9604
9901
  const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
@@ -9618,15 +9915,9 @@ var FRAME_INTERVAL_MS = 33;
9618
9915
  function createApp(deps) {
9619
9916
  let state = initialState(deps.initial);
9620
9917
  let exited = false;
9621
- let dotTicks = 0;
9918
+ let shownTime = "";
9622
9919
  const spinnerInterval = setInterval(() => {
9623
- if (anyTaskRunning(state)) {
9624
- dispatch({ type: "spinnerTick" });
9625
- return;
9626
- }
9627
- if (style.enabled && chatActivity(state)?.busy && ++dotTicks % DOT_TICKS === 0) {
9628
- dispatch({ type: "spinnerTick", by: DOT_TICKS });
9629
- }
9920
+ if (animating(state) || activityTime(state, Date.now()) !== shownTime) dispatch({ type: "spinnerTick" });
9630
9921
  }, 200);
9631
9922
  let renderPending = false;
9632
9923
  let renderTimer = null;
@@ -9640,7 +9931,8 @@ function createApp(deps) {
9640
9931
  }
9641
9932
  renderPending = false;
9642
9933
  lastPaint = Date.now();
9643
- deps.draw(render(state));
9934
+ shownTime = activityTime(state, lastPaint);
9935
+ deps.draw(render({ ...state, now: lastPaint }));
9644
9936
  }
9645
9937
  function paintKey() {
9646
9938
  if (keyBurst) {
@@ -9712,7 +10004,7 @@ function createApp(deps) {
9712
10004
 
9713
10005
  // src/tui/effects.ts
9714
10006
  var import_child_process3 = require("child_process");
9715
- var import_core53 = require("@ordewell/core");
10007
+ var import_core56 = require("@ordewell/core");
9716
10008
 
9717
10009
  // src/tui/inbound.ts
9718
10010
  var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
@@ -9732,6 +10024,7 @@ var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
9732
10024
  "planner_usage",
9733
10025
  "planner_liveness",
9734
10026
  "planner_compaction",
10027
+ "planner_detection",
9735
10028
  "approval_request",
9736
10029
  "approval_settled",
9737
10030
  "approval_decided",
@@ -10029,6 +10322,7 @@ function dispatchLifecycle(dispatch, event, sessionId) {
10029
10322
  ...event.runMerge ? { runMerge: event.runMerge } : {},
10030
10323
  ...event.usage ? { usage: event.usage } : {},
10031
10324
  ...event.activity ? { activity: event.activity } : {},
10325
+ ...event.escalations ? { escalations: event.escalations } : {},
10032
10326
  sessionId
10033
10327
  });
10034
10328
  return;
@@ -10234,14 +10528,35 @@ async function perform(effect, deps) {
10234
10528
  }
10235
10529
  case "taskAction": {
10236
10530
  const { sessionId, taskId, action } = effect;
10237
- const command = (0, import_core53.taskActionCommand)(action, taskId);
10238
- const request = () => api.execute(sessionId, effect.confirmed ? (0, import_core53.confirmedOverride)(command, effect.confirmed.seen) : command);
10531
+ const command = (0, import_core56.taskActionCommand)(action, taskId);
10532
+ const request = () => api.execute(sessionId, effect.confirmed ? (0, import_core56.confirmedOverride)(command, effect.confirmed.seen) : command);
10239
10533
  await (effect.watch ? withExecutionStream(deps, sessionId, request) : request());
10240
10534
  await refreshPlan(deps, sessionId);
10241
10535
  return;
10242
10536
  }
10537
+ case "previewRevert":
10538
+ dispatch({ type: "revertPreviewed", sessionId: effect.sessionId, taskId: effect.taskId, preview: await api.revertPreview(effect.sessionId, effect.taskId) });
10539
+ return;
10540
+ // A refusal at this point (a plan that changed, a conflict) comes back as the failure the seam worded.
10541
+ case "revertTask": {
10542
+ const { message } = await api.execute(effect.sessionId, { command: "revert_task", task: effect.taskId, confirmed: true, seen: effect.seen });
10543
+ await refreshPlan(deps, effect.sessionId);
10544
+ dispatch({ type: "notice", message });
10545
+ return;
10546
+ }
10547
+ case "answerEscalation": {
10548
+ const { sessionId, id, option, note, confirmed } = effect;
10549
+ let said = "";
10550
+ const request = async () => {
10551
+ said = (await api.answerEscalation(sessionId, { id, option, ...note ? { note } : {}, ...confirmed ? { confirmed } : {} })).message;
10552
+ };
10553
+ await (effect.watch ? withExecutionStream(deps, sessionId, request) : request());
10554
+ await refreshPlan(deps, sessionId);
10555
+ if (said) dispatch({ type: "notice", message: said });
10556
+ return;
10557
+ }
10243
10558
  case "addTask": {
10244
- const fields = (0, import_core53.newTaskFields)(effect.title);
10559
+ const fields = (0, import_core56.newTaskFields)(effect.title);
10245
10560
  if (!fields) return;
10246
10561
  await api.addTask(effect.sessionId, { ...fields });
10247
10562
  await refreshPlan(deps, effect.sessionId);
@@ -10331,11 +10646,11 @@ async function perform(effect, deps) {
10331
10646
  await api.execute(effect.sessionId, { command: "interrupt_task", task: effect.taskId });
10332
10647
  return;
10333
10648
  case "setPlanner": {
10334
- const meta = (0, import_core53.getProviderMeta)(effect.provider);
10649
+ const meta = (0, import_core56.getProviderMeta)(effect.provider);
10335
10650
  if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
10336
- if ((0, import_core53.isCliProvider)(effect.provider)) {
10651
+ if ((0, import_core56.isCliProvider)(effect.provider)) {
10337
10652
  const installed = ((await api.getRunners()).runners ?? []).map((r) => r.id);
10338
- const refusal = uninstalledPlannerRefusal(effect.provider, installed, import_core53.TERMINAL_UI_COMMANDS.pickPlanner);
10653
+ const refusal = uninstalledPlannerRefusal(effect.provider, installed, import_core56.TERMINAL_UI_COMMANDS.pickPlanner);
10339
10654
  if (refusal) throw new Error(refusal);
10340
10655
  }
10341
10656
  const env = { AI_PROVIDER: effect.provider };
@@ -10345,7 +10660,7 @@ async function perform(effect, deps) {
10345
10660
  dispatchSettings(deps, { aiProvider: effect.provider, orchestratorModel: recall2.model, plannerThinkingEffort: recall2.effort });
10346
10661
  dispatch({
10347
10662
  type: "notice",
10348
- message: `${(0, import_core53.isCliProvider)(effect.provider) ? `Planning with ${meta.label}: no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
10663
+ message: `${(0, import_core56.isCliProvider)(effect.provider) ? `Planning with ${meta.label}: no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
10349
10664
  });
10350
10665
  await loadModels(deps);
10351
10666
  return;
@@ -10364,7 +10679,7 @@ async function perform(effect, deps) {
10364
10679
  return;
10365
10680
  }
10366
10681
  case "setApiKey": {
10367
- const meta = (0, import_core53.isApiProvider)(effect.provider) ? (0, import_core53.getProviderMeta)(effect.provider) : void 0;
10682
+ const meta = (0, import_core56.isApiProvider)(effect.provider) ? (0, import_core56.getProviderMeta)(effect.provider) : void 0;
10368
10683
  if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
10369
10684
  await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
10370
10685
  deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
@@ -10552,7 +10867,7 @@ async function perform(effect, deps) {
10552
10867
  // cleared the run up and says so on the stream (`run_closed`); one
10553
10868
  // mid-run leaves the run going (ADR-0020).
10554
10869
  case "isolationMerge": {
10555
- const { ok, message } = (0, import_core53.mergeOutcome)(await api.mergeRun(effect.sessionId), effect.branch, effect.group === true, effect.repaired ?? []);
10870
+ const { ok, message } = (0, import_core56.mergeOutcome)(await api.mergeRun(effect.sessionId), effect.branch, effect.group === true, effect.repaired ?? []);
10556
10871
  dispatch({ type: ok ? "notice" : "failed", message });
10557
10872
  return;
10558
10873
  }
@@ -10573,11 +10888,15 @@ async function perform(effect, deps) {
10573
10888
  await refreshPlan(deps, sessionId);
10574
10889
  return;
10575
10890
  }
10576
- case "resolveConflict":
10577
- await api.resolveConflictAsTask(effect.sessionId, effect.taskId);
10578
- await refreshPlan(deps, effect.sessionId);
10579
- dispatch({ type: "notice", message: "Added a task that merges the conflicted branch by hand. Run it with E (run plan); when it lands, the conflicted task lands through it." });
10891
+ // A refusal, a second resolver among them, comes back in the daemon's words.
10892
+ case "resolveConflict": {
10893
+ const { sessionId, taskId } = effect;
10894
+ const request = () => api.resolveConflictAsTask(sessionId, taskId);
10895
+ await (effect.watch ? withExecutionStream(deps, sessionId, request) : request());
10896
+ await refreshPlan(deps, sessionId);
10897
+ dispatch({ type: "notice", message: "Added a task that merges the conflicted branch by hand. It starts once a slot is free; when it lands, the conflicted task lands through it." });
10580
10898
  return;
10899
+ }
10581
10900
  case "deleteSession": {
10582
10901
  const { ok } = await api.deleteSession(effect.sessionId, workspace);
10583
10902
  await loadSessions(deps);
@@ -10630,7 +10949,7 @@ async function saveMaxParallel(deps, limit) {
10630
10949
  function copySelection(deps, text) {
10631
10950
  if (!text) return;
10632
10951
  const lines = text.split("\n").length;
10633
- const command = (0, import_core53.clipboardCopyCommand)(deps.hasBin);
10952
+ const command = (0, import_core56.clipboardCopyCommand)(deps.hasBin);
10634
10953
  if (command) {
10635
10954
  try {
10636
10955
  (deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
@@ -10857,7 +11176,7 @@ function turnReplies(plan) {
10857
11176
  const replies = [];
10858
11177
  for (let i = history.length - 1; i >= 0; i--) {
10859
11178
  const entry = history[i];
10860
- if ((0, import_core53.isUserMessage)(entry)) break;
11179
+ if ((0, import_core56.isUserMessage)(entry)) break;
10861
11180
  if (entry.role === "assistant" && entry.kind === void 0) {
10862
11181
  const { content, timestamp, runs } = entry;
10863
11182
  replies.unshift({ content, ...timestamp ? { timestamp } : {}, ...runs ? { runs } : {} });
@@ -11293,9 +11612,9 @@ function openTerminal(options) {
11293
11612
  }
11294
11613
 
11295
11614
  // src/versionCheck.ts
11296
- var import_core54 = require("@ordewell/core");
11615
+ var import_core57 = require("@ordewell/core");
11297
11616
  function startCliVersionCheck(show3, options = {}) {
11298
- return (0, import_core54.startVersionCheck)({ version: cliVersion(), ...options, ...show3 });
11617
+ return (0, import_core57.startVersionCheck)({ version: cliVersion(), ...options, ...show3 });
11299
11618
  }
11300
11619
  var writeLine = (line) => console.error(line);
11301
11620
  var toStderr = { onNotice: writeLine, onUpdate: writeLine };
@@ -11303,7 +11622,7 @@ var toStderr = { onNotice: writeLine, onUpdate: writeLine };
11303
11622
  // src/tui/index.ts
11304
11623
  var DAEMON_STOP_GRACE_MS = 3e3;
11305
11624
  async function runLogin(start, command, cwd, env) {
11306
- const plan = await (0, import_core55.planDirectLaunch)(command[0], command.slice(1));
11625
+ const plan = await (0, import_core58.planDirectLaunch)(command[0], command.slice(1));
11307
11626
  return new Promise((resolve6, reject) => {
11308
11627
  const child = start(plan.file, plan.args, {
11309
11628
  stdio: "inherit",
@@ -11322,7 +11641,7 @@ async function handleTui(subArgs, options = {}) {
11322
11641
  }
11323
11642
  const workspace = (0, import_path7.resolve)(flag(subArgs, "--workspace") || process.cwd());
11324
11643
  try {
11325
- (0, import_core55.assertWorkspaceExists)(workspace);
11644
+ (0, import_core58.assertWorkspaceExists)(workspace);
11326
11645
  } catch {
11327
11646
  console.error(
11328
11647
  `Workspace "${workspace}" does not exist.
@@ -11331,7 +11650,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
11331
11650
  );
11332
11651
  process.exit(1);
11333
11652
  }
11334
- const discoveredSkills = (0, import_core55.createSkillsService)(workspace, new import_core55.EnvConfig().workspaceRepos).listSkills();
11653
+ const discoveredSkills = (0, import_core58.createSkillsService)(workspace, new import_core58.EnvConfig().workspaceRepos).listSkills();
11335
11654
  registerSkillCommands(discoveredSkills);
11336
11655
  const taskSkills = discoveredSkills.filter((s) => s.appliesTo === "task").map((s) => ({ name: s.name, description: s.description }));
11337
11656
  const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
@@ -11350,7 +11669,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
11350
11669
  },
11351
11670
  onUpdate: (line) => app.dispatch({ type: "notice", message: line })
11352
11671
  });
11353
- const settings = new import_core55.SettingsService();
11672
+ const settings = new import_core58.SettingsService();
11354
11673
  const app = createApp({
11355
11674
  // The level the daemon plans with: the saved one, unless the environment overrides it (ADR-0030).
11356
11675
  // The run settings are seeded from the same file because the first frame is
@@ -11373,13 +11692,13 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
11373
11692
  workspace,
11374
11693
  port,
11375
11694
  dispatch: (action) => app.dispatch(action),
11376
- newSessionId: () => (0, import_core55.mintSessionId)(),
11695
+ newSessionId: () => (0, import_core58.mintSessionId)(),
11377
11696
  setEnvVar: (key, value) => {
11378
11697
  writeEnvVar(findEnvFile(), key, value);
11379
11698
  process.env[key] = value;
11380
11699
  },
11381
11700
  saveAutonomy: (enabled) => {
11382
- const settings2 = new import_core55.SettingsService();
11701
+ const settings2 = new import_core58.SettingsService();
11383
11702
  settings2.setAutonomousMode(enabled);
11384
11703
  return { overridden: settings2.autonomousModeOverridden() };
11385
11704
  },
@@ -11394,7 +11713,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
11394
11713
  login: async (command) => {
11395
11714
  let handedOver = false;
11396
11715
  try {
11397
- const env = (0, import_core55.withPath)(process.env, await (0, import_core55.augmentedPath)(), await (0, import_core55.workspaceEnvOf)(workspace));
11716
+ const env = (0, import_core58.withPath)(process.env, await (0, import_core58.augmentedPath)(), await (0, import_core58.workspaceEnvOf)(workspace));
11398
11717
  loginRunning = true;
11399
11718
  handedOver = true;
11400
11719
  terminal?.suspend();
@@ -11505,12 +11824,15 @@ var COMMANDS = {
11505
11824
  automerge: handleAutomerge,
11506
11825
  trailer: handleTrailer,
11507
11826
  autorun: handleAutorun,
11827
+ // Not offered until a delegated task exists (ADR-0055 D21).
11828
+ ...import_core59.DELEGATION_OFFERED ? { delegation: handleDelegation } : {},
11508
11829
  refresh: handleRefresh,
11509
11830
  setup: handleSetup,
11510
11831
  skills: handleSkills,
11511
11832
  "mark-complete": handleMarkComplete,
11512
11833
  complete: handleMarkComplete,
11513
11834
  uncomplete: handleUncomplete,
11835
+ revert: handleRevert,
11514
11836
  skip: handleSkip,
11515
11837
  "run-task": handleRunTask,
11516
11838
  "force-start": handleForceStart,
@@ -11539,7 +11861,7 @@ var COMMANDS = {
11539
11861
  // src/main.ts
11540
11862
  var TUI_FLAGS = /* @__PURE__ */ new Set(["--workspace", "--port"]);
11541
11863
  async function main() {
11542
- (0, import_core56.migrateOldConfigDir)();
11864
+ (0, import_core60.migrateOldConfigDir)();
11543
11865
  moveAutonomyToSettings();
11544
11866
  loadEnvFile();
11545
11867
  const argv = process.argv.slice(2);