@ordewell/cli 0.10.2 → 0.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/main.js +697 -497
  3. package/package.json +3 -3
package/dist/main.js CHANGED
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/main.ts
27
- var import_core49 = require("@ordewell/core");
27
+ var import_core52 = require("@ordewell/core");
28
28
 
29
29
  // src/utils/env.ts
30
30
  var import_fs = require("fs");
@@ -127,7 +127,7 @@ function runnerNamesOr() {
127
127
  // src/help.ts
128
128
  function printHelp() {
129
129
  console.log(`
130
- Ordewell \u2014 task orchestration for coding agents
130
+ Ordewell: task orchestration for coding agents
131
131
 
132
132
  Run "ordewell" with no arguments for the TUI. Every command below has a TUI
133
133
  slash command of the same name, where each one opens a picker instead of taking
@@ -135,7 +135,7 @@ an id.
135
135
 
136
136
  Planning:
137
137
  ordewell Full-screen terminal UI (everything the VS Code extension does)
138
- ordewell tui The same thing, named \u2014 for scripts and aliases
138
+ ordewell tui Explicit TUI command for scripts and aliases
139
139
  ordewell plan --goal "text" Plan a goal conversationally
140
140
  ordewell run Execute the last generated plan (streams status)
141
141
  ordewell approve Sign off a plan paused for review, and continue it
@@ -170,7 +170,7 @@ Per-task assignment (omit the value to list the options):
170
170
  ordewell task-ops <id> [on|off] Make a task an ops task (runs in your checkout once its dependencies are merged) or a change task
171
171
 
172
172
  Planner, models and runners:
173
- ordewell planner [<provider>] Choose who plans \u2014 an API provider or a coding agent (no API key)
173
+ ordewell planner [<provider>] Choose who plans: an API provider or a coding agent (no API key)
174
174
  ordewell model [set <id>] Show or set the planner model (applies without a restart)
175
175
  ordewell planner-effort [lvl] Thinking effort for a coding-agent planner
176
176
  ordewell key [set <p> <key>] Show which providers have a key, or store one
@@ -249,17 +249,20 @@ Global options:
249
249
  --session-id <id> Session to act on (default: last planned session)
250
250
 
251
251
  Quick Start:
252
- Set OPENROUTER_API_KEY or GEMINI_API_KEY in your environment,
253
- then run: ordewell plan --goal "Your task description"
252
+ Run "ordewell", then use /planner to choose a coding agent,
253
+ or /key to choose an API provider and add its key.
254
+
255
+ To plan from the command line after setup:
256
+ ordewell plan --goal "Your task description"
254
257
 
255
258
  No API key? Plan with a coding agent you already pay for:
256
259
  AI_PROVIDER=<runner> ordewell plan --goal "Your task description"
257
260
 
258
261
  Environment:
259
- OPENROUTER_API_KEY OpenRouter API key (recommended)
260
- GEMINI_API_KEY Google Gemini API key (alternative)
261
- AI_PROVIDER openrouter, google \u2014 auto-detected from keys
262
- ${runnerIdList()} \u2014 plan with that CLI,
262
+ OPENROUTER_API_KEY OpenRouter API key
263
+ GEMINI_API_KEY Google Gemini API key
264
+ AI_PROVIDER Provider ID (auto-detected from keys); list with "ordewell planner"
265
+ ${runnerIdList()}: plan with that CLI,
263
266
  no API key (uses its own subscription)
264
267
  ORDEWELL_PLANNER_EFFORT Thinking effort for a coding-agent planner
265
268
  ORCHESTRATOR_MODEL Default: deepseek/deepseek-v4-flash
@@ -433,7 +436,7 @@ function isConnectionRefused(err) {
433
436
  return message.includes("ECONNREFUSED");
434
437
  }
435
438
  function describeConnectionRefused(port) {
436
- return `No Ordewell server is listening on port ${port}. It may have been stopped, or it may have crashed \u2014 check ${(0, import_path2.join)(CONFIG_DIR, "server.log")}. Start one with \`ordewell web --daemon\`.`;
439
+ return `No Ordewell server is listening on port ${port}. It may have been stopped, or it may have crashed: check ${(0, import_path2.join)(CONFIG_DIR, "server.log")}. Start one with \`ordewell web --daemon\`.`;
437
440
  }
438
441
  function findFreePort() {
439
442
  return new Promise((resolvePromise, reject) => {
@@ -464,14 +467,16 @@ var import_ws = __toESM(require("ws"));
464
467
  var import_core4 = require("@ordewell/core");
465
468
  var DEFAULT_HTTP_TIMEOUT_MS = 15 * 60 * 1e3;
466
469
  var DaemonError = class extends Error {
467
- constructor(message, status, code) {
470
+ constructor(message, status, code, plannerNotReady) {
468
471
  super(message);
469
472
  this.status = status;
470
473
  this.code = code;
474
+ this.plannerNotReady = plannerNotReady;
471
475
  this.name = "DaemonError";
472
476
  }
473
477
  status;
474
478
  code;
479
+ plannerNotReady;
475
480
  };
476
481
  var WorkspaceInitNeededError = class extends DaemonError {
477
482
  constructor(message, workspace) {
@@ -483,9 +488,9 @@ var WorkspaceInitNeededError = class extends DaemonError {
483
488
  };
484
489
  var SESSION_PATH = /^\/api\/(?:plans|sessions)\/([^/?]+)/;
485
490
  function daemonError(status, data, fallback) {
486
- const { error, code, workspace } = typeof data === "object" && data !== null ? data : {};
491
+ const { error, code, workspace, plannerNotReady } = typeof data === "object" && data !== null ? data : {};
487
492
  if (code === "workspace_not_a_project") return new WorkspaceInitNeededError(error || "Not a project directory", workspace ?? "");
488
- return new DaemonError(error || fallback, status, code);
493
+ return new DaemonError(error || fallback, status, code, plannerNotReady);
489
494
  }
490
495
  var ApiClient = class _ApiClient {
491
496
  /** Where a CLI invocation's sessions live, when a client is built without one: `--workspace`, else the cwd. */
@@ -628,7 +633,7 @@ var ApiClient = class _ApiClient {
628
633
  markTaskComplete(sessionId, taskId) {
629
634
  return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/complete`, "Mark complete failed");
630
635
  }
631
- /** Skip: end the task without landing its work, so what depends on it goes ahead; never a mark complete. */
636
+ /** Skip: end the task without landing its work, so what depends on it goes ahead once the work it waited on is done; never a mark complete. */
632
637
  skipTask(sessionId, taskId, reason) {
633
638
  return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/skip`, "Skip failed", reason ? { reason } : {});
634
639
  }
@@ -728,7 +733,7 @@ var ApiClient = class _ApiClient {
728
733
  compactConversation(sessionId) {
729
734
  return this.call("POST", `/api/plans/${sessionId}/conversation/compact`, "Compaction failed");
730
735
  }
731
- /** The dependencies a task waits on at its merge gate (ADR-0020); empty when nothing gates it. */
736
+ /** The ancestors a task waits on at its merge gate (ADR-0020); empty when nothing gates it. */
732
737
  async getMergeGate(sessionId, taskId) {
733
738
  const res = await this.call("GET", `/api/plans/${sessionId}/tasks/${taskId}/merge-gate`, "Could not read the merge gate");
734
739
  return res.mergeGate ?? [];
@@ -979,7 +984,7 @@ var ApiClient = class _ApiClient {
979
984
  };
980
985
 
981
986
  // src/commands/plan.ts
982
- var import_readline = require("readline");
987
+ var import_readline2 = require("readline");
983
988
 
984
989
  // src/utils/args.ts
985
990
  function flag(args, name) {
@@ -1140,7 +1145,7 @@ ${describeApproval(event)}
1140
1145
  deps.write(" Approved automatically (--yes).\n");
1141
1146
  } else if (!interactive) {
1142
1147
  granted = false;
1143
- deps.write(" Denied: no interactive terminal. Re-run with --yes, or pre-approve via ORDEWELL_APPROVAL_ALLOW.\n");
1148
+ deps.write(event.kind === "task_start" ? " Not started: there is no terminal here to ask you. Start it yourself with `ordewell run`, or Execute plan in the TUI or VS Code.\n" : " Denied: no interactive terminal. Re-run with --yes, or pre-approve via ORDEWELL_APPROVAL_ALLOW.\n");
1144
1149
  } else {
1145
1150
  const answer = (await deps.ask(" Allow? [y/N] ")).trim().toLowerCase();
1146
1151
  granted = answer === "y" || answer === "yes";
@@ -1148,7 +1153,7 @@ ${describeApproval(event)}
1148
1153
  try {
1149
1154
  await deps.respond(sessionId, id, granted);
1150
1155
  } catch {
1151
- deps.write(" (could not deliver the answer \u2014 the planner will treat it as denied)\n");
1156
+ deps.write(" (could not deliver the answer: the planner will treat it as denied)\n");
1152
1157
  }
1153
1158
  }
1154
1159
 
@@ -1215,7 +1220,118 @@ function createStepLog(options = {}) {
1215
1220
  }
1216
1221
 
1217
1222
  // src/commands/plan.ts
1223
+ var import_core9 = require("@ordewell/core");
1224
+
1225
+ // src/commands/shared.ts
1226
+ var import_readline = require("readline");
1227
+
1228
+ // src/catalog.ts
1229
+ function normalizeCatalog(result) {
1230
+ const runnersByModel = /* @__PURE__ */ new Map();
1231
+ for (const [runner, entries] of Object.entries(result.modelsByRunner ?? {})) {
1232
+ for (const entry of entries ?? []) {
1233
+ const id = String(entry.modelId);
1234
+ runnersByModel.set(id, [.../* @__PURE__ */ new Set([...runnersByModel.get(id) ?? [], runner])]);
1235
+ }
1236
+ }
1237
+ const models = (result.models ?? []).map((m) => {
1238
+ const id = String(m.modelId);
1239
+ return {
1240
+ id,
1241
+ label: String(m.modelLabel ?? id),
1242
+ provider: String(m.runnerProviderLabel ?? m.runnerProvider ?? ""),
1243
+ variants: Array.isArray(m.variants) ? m.variants.map((v) => ({ id: String(v.id), label: String(v.label ?? v.id) })) : [],
1244
+ runners: runnersByModel.get(id) ?? []
1245
+ };
1246
+ });
1247
+ const orchestratorModels = (result.orchestratorModels ?? []).map((m) => ({
1248
+ id: String(m.id),
1249
+ label: String(m.label ?? m.id),
1250
+ provider: String(m.provider ?? ""),
1251
+ pricing: m.pricing ? `$${m.pricing}/MTok` : void 0
1252
+ }));
1253
+ return {
1254
+ models,
1255
+ orchestratorModels,
1256
+ providers: result.providers ?? [],
1257
+ providerErrors: result.providerErrors ?? {},
1258
+ modesByRunner: Object.fromEntries(
1259
+ Object.entries(result.modesByRunner ?? {}).map(([runner, modes]) => [
1260
+ runner,
1261
+ (modes ?? []).map((m) => ({
1262
+ id: String(m.id),
1263
+ label: String(m.label ?? m.id),
1264
+ description: m.description ? String(m.description) : void 0,
1265
+ autonomous: m.autonomous ? true : void 0,
1266
+ unavailable: m.unavailable ? String(m.unavailable) : void 0
1267
+ }))
1268
+ ])
1269
+ )
1270
+ };
1271
+ }
1272
+
1273
+ // src/commands/shared.ts
1218
1274
  var import_core8 = require("@ordewell/core");
1275
+ async function connect(subArgs, injected) {
1276
+ return injected ?? new ApiClient(await ensureDaemon(resolvePort(subArgs)));
1277
+ }
1278
+ async function fetchCatalog(api) {
1279
+ return normalizeCatalog(await api.getModels());
1280
+ }
1281
+ function fail(...lines) {
1282
+ for (const line of lines) console.error(line);
1283
+ process.exit(1);
1284
+ }
1285
+ function headlessPlannerRefusal(err) {
1286
+ return err instanceof DaemonError && err.plannerNotReady ? (0, import_core8.describePlannerNotReady)(err.plannerNotReady, import_core8.HEADLESS_CLI_COMMANDS) : null;
1287
+ }
1288
+ async function askYesNo(question) {
1289
+ if (!process.stdin.isTTY) return false;
1290
+ const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stderr });
1291
+ try {
1292
+ const answer = await new Promise((resolve4) => rl.question(`${question} [y/N] `, resolve4));
1293
+ return /^y(es)?$/i.test(answer.trim());
1294
+ } finally {
1295
+ rl.close();
1296
+ }
1297
+ }
1298
+ function writeEnv(env) {
1299
+ const envFile = findEnvFile();
1300
+ for (const [key, value] of Object.entries(env)) {
1301
+ writeEnvVar(envFile, key, value);
1302
+ process.env[key] = value;
1303
+ }
1304
+ }
1305
+ async function persistEnv(api, env) {
1306
+ const settings = await api.updateSettings({ env });
1307
+ writeEnv(env);
1308
+ return settings;
1309
+ }
1310
+ function toTaskView(task) {
1311
+ return {
1312
+ id: task.id,
1313
+ order: task.order,
1314
+ title: task.title,
1315
+ description: task.description,
1316
+ prompt: task.prompt ?? void 0,
1317
+ type: task.type === "user" ? "user" : "ai",
1318
+ status: task.status ?? "pending",
1319
+ dependencies: task.dependencies ?? [],
1320
+ assignedRunner: task.assignedRunner || void 0,
1321
+ taskMode: task.taskMode || void 0,
1322
+ assignedModel: task.assignedModel ?? void 0,
1323
+ subtasks: [...task.subtasks ?? []].sort((a, b) => a.order - b.order).map(toTaskView),
1324
+ ...task.ops ? { ops: true } : {},
1325
+ ...task.skills?.length ? { skills: [...task.skills] } : {},
1326
+ ...task.addedBy ? { addedBy: task.addedBy } : {}
1327
+ };
1328
+ }
1329
+ function taskViews(plan) {
1330
+ const tasks = allTasksOf(plan);
1331
+ return [...tasks].sort((a, b) => a.order - b.order).map(toTaskView);
1332
+ }
1333
+
1334
+ // src/commands/plan.ts
1219
1335
  function planTasks(plan) {
1220
1336
  return plan ? allTasksOf(plan) : [];
1221
1337
  }
@@ -1237,8 +1353,16 @@ function commitNotes(plan) {
1237
1353
  const marker2 = history.map((e) => e.kind).lastIndexOf("plan_generated");
1238
1354
  return marker2 < 0 ? [] : history.slice(marker2 + 1).filter((e) => e.kind === "system").map((e) => e.content);
1239
1355
  }
1356
+ async function nextStep(api, plan) {
1357
+ const approved = "approved" in plan && plan.approved === true;
1358
+ const moving = planTasks(plan).some((t) => t.status !== void 0 && t.status !== "pending" && t.status !== "approved");
1359
+ if (!approved && !moving) return `Run 'ordewell run' to execute, 'ordewell status' to inspect, or 'ordewell tui' for the full UI.`;
1360
+ const follow2 = `Follow it with 'ordewell status', or 'ordewell tui' for the full UI.`;
1361
+ const autorun = await api.getSettings().then((settings) => settings.run?.autorun === true, () => false);
1362
+ return autorun ? `Autorun started the plan. ${follow2}` : `The plan is running. ${follow2}`;
1363
+ }
1240
1364
  function stdinReader(input = process.stdin) {
1241
- const rl = (0, import_readline.createInterface)({ input, output: process.stderr });
1365
+ const rl = (0, import_readline2.createInterface)({ input, output: process.stderr });
1242
1366
  const buffered = [];
1243
1367
  let waiting = null;
1244
1368
  let ended = false;
@@ -1293,35 +1417,36 @@ function printPlan(plan, sessionId, runners2, models) {
1293
1417
  return rp ? ` (${task.assignedModel.modelLabel} \xB7 ${label})` : ` (${task.assignedModel.modelLabel})`;
1294
1418
  };
1295
1419
  console.log(`
1296
- Plan: ${tasks.length} task${tasks.length === 1 ? "" : "s"} (${aiCount} AI, ${manCount} Manual) \u2014 ${runnersOf(plan, runners2).join(", ")}`);
1420
+ Plan: ${tasks.length} task${tasks.length === 1 ? "" : "s"} (${aiCount} AI, ${manCount} Manual): ${runnersOf(plan, runners2).join(", ")}`);
1297
1421
  console.log(`Session: ${sessionId}
1298
1422
  `);
1299
1423
  for (const t of tasks) {
1300
1424
  const typeIcon = t.type === "user" ? "[MAN]" : t.ops ? "[OPS]" : "[ AI]";
1301
1425
  const deps = t.dependencies?.length ? ` \u2190 ${t.dependencies.join(", ")}` : "";
1302
- console.log(` ${(0, import_core8.taskOrderLabel)(t).padStart(2)}. ${typeIcon} ${t.title}${modelSuffix(t)}${deps}`);
1426
+ console.log(` ${(0, import_core9.taskOrderLabel)(t).padStart(2)}. ${typeIcon} ${t.title}${modelSuffix(t)}${deps}`);
1303
1427
  for (const sub of t.subtasks || []) {
1304
1428
  const subTypeIcon = sub.type === "user" ? "[MAN]" : "[ AI]";
1305
1429
  const subDeps = sub.dependencies?.length ? ` \u2190 ${sub.dependencies.join(", ")}` : "";
1306
- console.log(` ${(0, import_core8.taskOrderLabel)(sub, t)}. ${subTypeIcon} ${sub.title}${modelSuffix(sub)}${subDeps}`);
1430
+ console.log(` ${(0, import_core9.taskOrderLabel)(sub, t)}. ${subTypeIcon} ${sub.title}${modelSuffix(sub)}${subDeps}`);
1307
1431
  }
1308
1432
  }
1309
1433
  if (manCount > 0) {
1310
- console.log("\n [MAN] = manual step \u2014 run `ordewell tui` to work through it");
1434
+ console.log("\n [MAN] = manual step: run `ordewell tui` to work through it");
1311
1435
  }
1312
1436
  if (tasks.some((t) => t.ops)) {
1313
- console.log(" [OPS] = ops task \u2014 runs in your checkout once the work it depends on is merged (`ordewell handoff merge`)");
1437
+ console.log(" [OPS] = ops task: runs in your checkout once the work it depends on is merged (`ordewell handoff merge`)");
1314
1438
  }
1315
1439
  }
1316
- var PLANNER_ENV = ["AI_PROVIDER", "ORCHESTRATOR_MODEL", "ORDEWELL_PLANNER_EFFORT"];
1440
+ var INHERITED_PLANNER_ENV = ["ORCHESTRATOR_MODEL", "ORDEWELL_PLANNER_EFFORT"];
1317
1441
  async function forwardPlannerEnv(api) {
1442
+ const provider = process.env.AI_PROVIDER;
1443
+ if (provider) await api.updateSettings({ env: { AI_PROVIDER: provider } });
1318
1444
  const env = {};
1319
- for (const key of PLANNER_ENV) {
1445
+ for (const key of INHERITED_PLANNER_ENV) {
1320
1446
  const value = process.env[key];
1321
1447
  if (value) env[key] = value;
1322
1448
  }
1323
- if (Object.keys(env).length === 0) return;
1324
- await api.updateSettings({ env });
1449
+ if (Object.keys(env).length > 0) await api.updateSettings({ env, inherited: true });
1325
1450
  }
1326
1451
  async function handlePlan(subArgs, deps = {}) {
1327
1452
  const injectedApi = deps.api;
@@ -1340,20 +1465,27 @@ async function handlePlan(subArgs, deps = {}) {
1340
1465
  await forwardPlannerEnv(api);
1341
1466
  const state = await api.getRunners();
1342
1467
  const enabledIds = state.runners.filter((r) => r.enabled).map((r) => r.id);
1468
+ const enableHint = (id) => `Enable it with: ordewell runners ${id} on`;
1343
1469
  let runners2;
1344
1470
  if (runnerFlags.length === 0) {
1471
+ if (enabledIds.length === 0) {
1472
+ const installed = state.runners.map((r) => r.id);
1473
+ fail(
1474
+ "No runner is enabled.",
1475
+ installed.length > 0 ? `Installed: ${installed.join(", ")}. ${enableHint(installed[0])}` : "No supported coding agent is installed or on PATH."
1476
+ );
1477
+ }
1345
1478
  runners2 = enabledIds;
1346
1479
  } else {
1347
1480
  for (const r of runnerFlags) {
1348
- if (!enabledIds.includes(r)) {
1349
- console.error(`Unknown runner: "${r}". Enabled runners: ${enabledIds.join(", ")}`);
1350
- process.exit(1);
1351
- }
1481
+ if (enabledIds.includes(r)) continue;
1482
+ if (state.runners.some((known) => known.id === r)) fail(`${r} is installed but not enabled. ${enableHint(r)}`);
1483
+ fail(`Unknown runner: "${r}". Enabled runners: ${enabledIds.join(", ")}`);
1352
1484
  }
1353
1485
  runners2 = runnerFlags;
1354
1486
  }
1355
1487
  console.error(`Generating plan for: "${goal}"...`);
1356
- const sessionId = (0, import_core8.mintSessionId)();
1488
+ const sessionId = (0, import_core9.mintSessionId)();
1357
1489
  const isTty = !!process.stderr.isTTY;
1358
1490
  let lastLineLength = 0;
1359
1491
  const stepLog = createStepLog({ verbose });
@@ -1447,19 +1579,24 @@ ${lastPlannerMessage(plan)}
1447
1579
  ${note}`);
1448
1580
  saveLastSession(sessionId, goal, runnersOf(plan, runners2), workspace);
1449
1581
  console.log(`
1450
- Run 'ordewell run' to execute, 'ordewell status' to inspect, or 'ordewell tui' for the full UI.`);
1582
+ ${await nextStep(api, plan)}`);
1451
1583
  } catch (err) {
1452
1584
  reader?.close();
1453
1585
  closeStream();
1454
1586
  if (isTty && lastLineLength > 0) process.stderr.write("\r\x1B[2K");
1455
1587
  process.stderr.write("\n");
1456
- console.error(`Plan generation failed: ${err instanceof Error ? err.message : String(err)}`);
1588
+ const refusal = headlessPlannerRefusal(err);
1589
+ if (refusal) {
1590
+ console.error(refusal);
1591
+ } else {
1592
+ console.error(`Plan generation failed: ${err instanceof Error ? err.message : String(err)}`);
1593
+ }
1457
1594
  process.exit(1);
1458
1595
  }
1459
1596
  }
1460
1597
 
1461
1598
  // src/isolation.ts
1462
- var import_core9 = require("@ordewell/core");
1599
+ var import_core10 = require("@ordewell/core");
1463
1600
  var STATE_OF = {
1464
1601
  active: "active",
1465
1602
  merged: "integrated",
@@ -1543,7 +1680,7 @@ function repairedLanded(handoff) {
1543
1680
  function repairedNotice(handoff) {
1544
1681
  const repaired = repairedLanded(handoff);
1545
1682
  if (repaired.length === 0) return "";
1546
- const named = repaired.map((t) => `${t.title} (${(0, import_core9.capConflictFiles)(t.repairedFiles)})`);
1683
+ const named = repaired.map((t) => `${t.title} (${(0, import_core10.capConflictFiles)(t.repairedFiles)})`);
1547
1684
  const list2 = named.length === 1 ? named[0] : `${named.slice(0, -1).join(", ")} and ${named[named.length - 1]}`;
1548
1685
  return ` ${list2} landed through ${repaired.length === 1 ? "a conflict repair" : "conflict repairs"}.`;
1549
1686
  }
@@ -1556,16 +1693,16 @@ function repoResultLines(handoff) {
1556
1693
  function mergeOutcome(result, branch, group, repaired = []) {
1557
1694
  const ok = result.outcome === "merged";
1558
1695
  if (!group && result.outcome === "conflict") {
1559
- return { ok, message: `Merging ${branch} conflicted, so it was aborted \u2014 your tree is as it was. Merge it with git and resolve the conflict there.` };
1696
+ return { ok, message: `Merging ${branch} conflicted, so it was aborted: your tree is as it was. Merge it with git and resolve the conflict there.` };
1560
1697
  }
1561
- const { message } = (0, import_core9.describeMergeResult)(result, branch, group, repaired);
1698
+ const { message } = (0, import_core10.describeMergeResult)(result, branch, group, repaired);
1562
1699
  return { ok, message: ok || !group ? message : `${message} Each repository's ${branch} is a plain branch you can merge by hand.` };
1563
1700
  }
1564
1701
 
1565
1702
  // src/utils/output.ts
1566
- var import_core10 = require("@ordewell/core");
1703
+ var import_core11 = require("@ordewell/core");
1567
1704
  function iconFor(status) {
1568
- switch ((0, import_core10.taskStatusKind)({ status })) {
1705
+ switch ((0, import_core11.taskStatusKind)({ status })) {
1569
1706
  case "done":
1570
1707
  return "\x1B[32m\u2713\x1B[0m";
1571
1708
  case "running":
@@ -1585,114 +1722,8 @@ function iconFor(status) {
1585
1722
  }
1586
1723
 
1587
1724
  // src/commands/run.ts
1588
- var import_core11 = require("@ordewell/core");
1589
-
1590
- // src/commands/shared.ts
1591
- var import_readline2 = require("readline");
1592
-
1593
- // src/catalog.ts
1594
- function normalizeCatalog(result) {
1595
- const runnersByModel = /* @__PURE__ */ new Map();
1596
- for (const [runner, entries] of Object.entries(result.modelsByRunner ?? {})) {
1597
- for (const entry of entries ?? []) {
1598
- const id = String(entry.modelId);
1599
- runnersByModel.set(id, [.../* @__PURE__ */ new Set([...runnersByModel.get(id) ?? [], runner])]);
1600
- }
1601
- }
1602
- const models = (result.models ?? []).map((m) => {
1603
- const id = String(m.modelId);
1604
- return {
1605
- id,
1606
- label: String(m.modelLabel ?? id),
1607
- provider: String(m.runnerProviderLabel ?? m.runnerProvider ?? ""),
1608
- variants: Array.isArray(m.variants) ? m.variants.map((v) => ({ id: String(v.id), label: String(v.label ?? v.id) })) : [],
1609
- runners: runnersByModel.get(id) ?? []
1610
- };
1611
- });
1612
- const orchestratorModels = (result.orchestratorModels ?? []).map((m) => ({
1613
- id: String(m.id),
1614
- label: String(m.label ?? m.id),
1615
- provider: String(m.provider ?? ""),
1616
- pricing: m.pricing ? `$${m.pricing}/MTok` : void 0
1617
- }));
1618
- return {
1619
- models,
1620
- orchestratorModels,
1621
- providers: result.providers ?? [],
1622
- providerErrors: result.providerErrors ?? {},
1623
- modesByRunner: Object.fromEntries(
1624
- Object.entries(result.modesByRunner ?? {}).map(([runner, modes]) => [
1625
- runner,
1626
- (modes ?? []).map((m) => ({
1627
- id: String(m.id),
1628
- label: String(m.label ?? m.id),
1629
- description: m.description ? String(m.description) : void 0,
1630
- autonomous: m.autonomous ? true : void 0,
1631
- unavailable: m.unavailable ? String(m.unavailable) : void 0
1632
- }))
1633
- ])
1634
- )
1635
- };
1636
- }
1637
-
1638
- // src/commands/shared.ts
1639
- async function connect(subArgs, injected) {
1640
- return injected ?? new ApiClient(await ensureDaemon(resolvePort(subArgs)));
1641
- }
1642
- async function fetchCatalog(api) {
1643
- return normalizeCatalog(await api.getModels());
1644
- }
1645
- function fail(...lines) {
1646
- for (const line of lines) console.error(line);
1647
- process.exit(1);
1648
- }
1649
- async function askYesNo(question) {
1650
- if (!process.stdin.isTTY) return false;
1651
- const rl = (0, import_readline2.createInterface)({ input: process.stdin, output: process.stderr });
1652
- try {
1653
- const answer = await new Promise((resolve4) => rl.question(`${question} [y/N] `, resolve4));
1654
- return /^y(es)?$/i.test(answer.trim());
1655
- } finally {
1656
- rl.close();
1657
- }
1658
- }
1659
- function writeEnv(env) {
1660
- const envFile = findEnvFile();
1661
- for (const [key, value] of Object.entries(env)) {
1662
- writeEnvVar(envFile, key, value);
1663
- process.env[key] = value;
1664
- }
1665
- }
1666
- async function persistEnv(api, env) {
1667
- const settings = await api.updateSettings({ env });
1668
- writeEnv(env);
1669
- return settings;
1670
- }
1671
- function toTaskView(task) {
1672
- return {
1673
- id: task.id,
1674
- order: task.order,
1675
- title: task.title,
1676
- description: task.description,
1677
- prompt: task.prompt ?? void 0,
1678
- type: task.type === "user" ? "user" : "ai",
1679
- status: task.status ?? "pending",
1680
- dependencies: task.dependencies ?? [],
1681
- assignedRunner: task.assignedRunner || void 0,
1682
- taskMode: task.taskMode || void 0,
1683
- assignedModel: task.assignedModel ?? void 0,
1684
- subtasks: [...task.subtasks ?? []].sort((a, b) => a.order - b.order).map(toTaskView),
1685
- ...task.ops ? { ops: true } : {},
1686
- ...task.skills?.length ? { skills: [...task.skills] } : {},
1687
- ...task.addedBy ? { addedBy: task.addedBy } : {}
1688
- };
1689
- }
1690
- function taskViews(plan) {
1691
- const tasks = allTasksOf(plan);
1692
- return [...tasks].sort((a, b) => a.order - b.order).map(toTaskView);
1693
- }
1694
-
1695
- // src/commands/run.ts
1725
+ var import_core12 = require("@ordewell/core");
1726
+ var inLine = (label) => label.charAt(0).toLowerCase() + label.slice(1);
1696
1727
  function resolveSessionId(subArgs) {
1697
1728
  const explicit = flag(subArgs, "--session-id");
1698
1729
  if (explicit) return explicit;
@@ -1718,8 +1749,9 @@ async function followExecution(api, sessionId, start, onBlocked) {
1718
1749
  const lines = [];
1719
1750
  for (const t of entries) {
1720
1751
  const icon = iconFor(t.status);
1721
- const verdict = t.verdict ? ` \u2014 ${t.verdict.outcome.toUpperCase()}: ${t.verdict.reason.slice(0, 60)}` : "";
1722
- lines.push(` ${icon} #${t.id.slice(-4)} ${t.status}${verdict}`);
1752
+ const verdict = t.verdict ? `: ${t.verdict.outcome.toUpperCase()}: ${t.verdict.reason.slice(0, 60)}` : "";
1753
+ const waits = t.waitsForApproval ? `: ${inLine(import_core12.WAITS_FOR_APPROVAL_LABEL)}: \`ordewell run-task <id>\` starts it` : "";
1754
+ lines.push(` ${icon} #${t.id.slice(-4)} ${t.status}${verdict}${waits}`);
1723
1755
  }
1724
1756
  const output = lines.join("\n");
1725
1757
  if (output !== lastPrinted && output.trim()) {
@@ -1744,21 +1776,21 @@ async function followExecution(api, sessionId, start, onBlocked) {
1744
1776
  });
1745
1777
  const stream = api.streamExecution(sessionId, (event) => {
1746
1778
  if (event.type === "task_started") {
1747
- process.stderr.write(`\xB7 ${(0, import_core11.taskStartedNotice)(event.title, event.runner)}
1779
+ process.stderr.write(`\xB7 ${(0, import_core12.taskStartedNotice)(event.title, event.runner)}
1748
1780
  `);
1749
1781
  }
1750
1782
  if (event.type === "checkpoint") {
1751
- process.stderr.write(`\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core11.truncateCheckpointSummary)(event.summary)} \u2014 \`ordewell checkpoint <id> approve|reject [reason]\` answers it
1783
+ process.stderr.write(`\xB7 Checkpoint: ${event.taskTitle}: ${(0, import_core12.truncateCheckpointSummary)(event.summary)}: \`ordewell checkpoint <id> approve|reject [reason]\` answers it
1752
1784
  `);
1753
1785
  }
1754
1786
  if (event.type === "status_update" && event.tasks) {
1755
1787
  printStatus(event.tasks);
1756
1788
  const paused = event.runMerge?.paused === true;
1757
- if (paused && !pausedAtGate) process.stderr.write("\xB7 Paused for Merge all \u2014 `ordewell handoff merge` merges what has landed, and the waiting tasks start.\n");
1789
+ if (paused && !pausedAtGate) process.stderr.write("\xB7 Paused for Merge all: `ordewell handoff merge` merges what has landed, and the waiting tasks start.\n");
1758
1790
  pausedAtGate = paused;
1759
1791
  }
1760
1792
  if (event.type === "review_needed") {
1761
- console.log("\nPlan needs your sign-off \u2014 run `ordewell approve` to continue.");
1793
+ console.log("\nPlan needs your sign-off: run `ordewell approve` to continue.");
1762
1794
  }
1763
1795
  if (event.type === "execution_complete") {
1764
1796
  const s = event;
@@ -1782,7 +1814,7 @@ Done. ${summary.completed || 0} completed, ${summary.failed || 0} failed, ${summ
1782
1814
  const n = event.landed.length;
1783
1815
  const where = isRepoGroup(event) ? ` in ${event.repos.map((r) => r.path).join(", ")}` : "";
1784
1816
  console.log(`
1785
- Run finished on ${handoffBranch(event)}${where} \u2014 ${n} task${n === 1 ? "" : "s"} landed.`);
1817
+ Run finished on ${handoffBranch(event)}${where}: ${n} task${n === 1 ? "" : "s"} landed.`);
1786
1818
  if (isRepoGroup(event)) for (const line of repoResultLines(event)) console.log(` ${line}`);
1787
1819
  if (event.deferredTo === "supervisor") console.log(" The supervisor may merge it.");
1788
1820
  console.log(" `ordewell handoff review|merge|discard|cleanup` to land it.");
@@ -1800,7 +1832,7 @@ Run finished on ${handoffBranch(event)}${where} \u2014 ${n} task${n === 1 ? "" :
1800
1832
  await start();
1801
1833
  const outcome = await Promise.race([stream, blockedSeen]).catch(streamFailed);
1802
1834
  if (outcome === "lost") {
1803
- console.error("Lost the connection to the daemon \u2014 the run is no longer being followed, but the daemon may still be running it. `ordewell status` shows where it stands.");
1835
+ console.error("Lost the connection to the daemon: the run is no longer being followed, but the daemon may still be running it. `ordewell status` shows where it stands.");
1804
1836
  process.exit(1);
1805
1837
  }
1806
1838
  if (blocked === null) return;
@@ -1829,7 +1861,7 @@ async function handleRun(subArgs, injectedApi) {
1829
1861
  }
1830
1862
 
1831
1863
  // src/tui/blocks.ts
1832
- var import_core13 = require("@ordewell/core");
1864
+ var import_core14 = require("@ordewell/core");
1833
1865
 
1834
1866
  // src/tui/ansi.ts
1835
1867
  var import_string_width = __toESM(require("string-width"));
@@ -2008,6 +2040,7 @@ var style = {
2008
2040
  dim: colour("2"),
2009
2041
  italic: colour("3"),
2010
2042
  underline: colour("4"),
2043
+ strike: colour("9"),
2011
2044
  inverse: colour("7"),
2012
2045
  red: colour("31"),
2013
2046
  green: colour("32"),
@@ -2044,9 +2077,9 @@ var style = {
2044
2077
  };
2045
2078
 
2046
2079
  // src/tui/markdown.ts
2047
- var import_core12 = require("@ordewell/core");
2080
+ var import_core13 = require("@ordewell/core");
2048
2081
  function renderMarkdown(source, cols) {
2049
- return blocks((0, import_core12.markdownTokens)(source), { room: Math.max(1, cols), base: [] });
2082
+ return blocks((0, import_core13.markdownTokens)(source), { room: Math.max(1, cols), base: [] });
2050
2083
  }
2051
2084
  function closeOf(tokens, open) {
2052
2085
  const { level } = tokens[open];
@@ -2297,7 +2330,7 @@ function onBand(rows, cols) {
2297
2330
  return style.enabled ? band.map((row) => style.band(pad(row, cols))) : band;
2298
2331
  }
2299
2332
  function skillPaintedLines(text, skills, room) {
2300
- const tokens = (0, import_core13.loadedSkillTokens)(text, skills);
2333
+ const tokens = (0, import_core14.loadedSkillTokens)(text, skills);
2301
2334
  return wrapLines(text, room).map(({ line, start }) => {
2302
2335
  let painted = "";
2303
2336
  let at = 0;
@@ -2393,16 +2426,16 @@ function resultLines(block2, room, detailAll) {
2393
2426
  const fit2 = (line) => detailAll ? wrap(line, room) : [truncate(line, room)];
2394
2427
  const outcome = unsettledOutcome(block2);
2395
2428
  if (outcome) {
2396
- const [reason, ...rest] = (0, import_core13.outputLines)(block2.output).map(sanitize);
2429
+ const [reason, ...rest] = (0, import_core14.outputLines)(block2.output).map(sanitize);
2397
2430
  const status = fit2(reason ? `${outcome.label}: ${reason}` : outcome.label).map(outcome.paint);
2398
2431
  return detailAll ? [...status, ...rest.flatMap(fit2).map(style.grey)] : status;
2399
2432
  }
2400
2433
  if (detailAll) {
2401
- const lines2 = (0, import_core13.outputLines)(block2.output).map(sanitize);
2434
+ const lines2 = (0, import_core14.outputLines)(block2.output).map(sanitize);
2402
2435
  const note = lines2.length > PREVIEW_LINES ? ["\u2026 (ctrl+o to collapse)"] : [];
2403
2436
  return lines2.length > 0 ? [...lines2.flatMap(fit2), ...note].map(style.grey) : [style.grey("(no output)")];
2404
2437
  }
2405
- const { lines, hiddenLineCount } = (0, import_core13.outputPreview)(block2.output, PREVIEW_LINES);
2438
+ const { lines, hiddenLineCount } = (0, import_core14.outputPreview)(block2.output, PREVIEW_LINES);
2406
2439
  if (lines.length === 0) return [style.grey("(no output)")];
2407
2440
  const more = hiddenLineCount > 0 ? [`\u2026 +${countOf(hiddenLineCount, "line")} (ctrl+o to expand)`] : [];
2408
2441
  return [...lines.map(sanitize), ...more].map((line) => style.grey(truncate(line, room)));
@@ -2416,7 +2449,7 @@ var DIFF_PAINT = {
2416
2449
  };
2417
2450
  var DIFF_SIGN = { added: "+", removed: "-", context: " ", gap: "" };
2418
2451
  function diffLines(block2, diff, room, detailAll) {
2419
- const rows = (0, import_core13.diffRows)(block2.output);
2452
+ const rows = (0, import_core14.diffRows)(block2.output);
2420
2453
  const gutter = Math.max(0, ...rows.map((row) => String(row.line ?? "").length));
2421
2454
  const draw = (row) => {
2422
2455
  const text = row.kind === "gap" ? `${" ".repeat(gutter)} \u22EE` : `${String(row.line ?? "").padStart(gutter)} ${DIFF_SIGN[row.kind]} ${sanitize(row.text)}`;
@@ -2425,7 +2458,7 @@ function diffLines(block2, diff, room, detailAll) {
2425
2458
  const shown = detailAll ? rows : rows.slice(0, DIFF_PREVIEW_ROWS);
2426
2459
  const hidden = rows.length - shown.length;
2427
2460
  const more = hidden > 0 ? [style.grey(`\u2026 +${countOf(hidden, "line")} (ctrl+o to expand)`)] : [];
2428
- return [(0, import_core13.diffSummary)(diff), ...shown.flatMap(draw), ...more];
2461
+ return [(0, import_core14.diffSummary)(diff), ...shown.flatMap(draw), ...more];
2429
2462
  }
2430
2463
  function toolLines(block2, cols, detailAll) {
2431
2464
  const room = Math.max(1, cols - OUTPUT_INDENT.length);
@@ -2486,7 +2519,7 @@ var DECIDED_BY = {
2486
2519
  function approvalLine(block2, cols) {
2487
2520
  const policy = block2.decidedBy && block2.decidedBy !== "asked" ? DECIDED_BY[block2.decidedBy] : void 0;
2488
2521
  const [mark, status, paint] = block2.status === "pending" ? ["?", "Waiting for you", style.yellow] : block2.status === "granted" ? ["\u2713", policy ? `Auto-approved (${policy})` : block2.forTask ? "Approved for this task" : "Approved", style.green] : block2.status === "withdrawn" ? ["\xB7", "Withdrawn", style.grey] : ["\u2298", policy ? `Auto-denied (${policy})` : "Denied", style.yellow];
2489
- const note = block2.note ? ` \u2014 \u201C${firstLine(block2.note)}\u201D` : "";
2522
+ const note = block2.note ? `: \u201C${firstLine(block2.note)}\u201D` : "";
2490
2523
  const line = `${mark} ${status} \xB7 ${APPROVAL_KIND[block2.kind]}: ${firstLine(block2.subject)}${note}`;
2491
2524
  return `${paint(mark)}${truncate(line, cols).slice(mark.length)}`;
2492
2525
  }
@@ -2529,7 +2562,7 @@ function runnerCommandLines(block2, cols, detailAll) {
2529
2562
  const status = ` ${block2.status}`;
2530
2563
  const head = `${style.accent("\u276F")} ${truncate(typed, Math.max(1, cols - 2 - width(status)))}${COMMAND_PAINT[block2.status](status)}`;
2531
2564
  const room = Math.max(1, cols - OUTPUT_INDENT.length);
2532
- const printed = (0, import_core13.outputLines)(block2.output).map(sanitize);
2565
+ const printed = (0, import_core14.outputLines)(block2.output).map(sanitize);
2533
2566
  const shown = detailAll ? printed.flatMap((line) => wrap(line, room)) : printed.slice(0, PREVIEW_LINES).map((line) => truncate(line, room));
2534
2567
  return [head, ...underHeader(shown.map(style.grey))];
2535
2568
  }
@@ -2647,7 +2680,7 @@ function taskUsageText(usage) {
2647
2680
 
2648
2681
  // src/commands/status.ts
2649
2682
  var import_fs4 = require("fs");
2650
- var import_core14 = require("@ordewell/core");
2683
+ var import_core15 = require("@ordewell/core");
2651
2684
  async function handleStatus(subArgs, injectedApi) {
2652
2685
  const sessionId = flag(subArgs, "--session-id");
2653
2686
  const workspace = flag(subArgs, "--workspace") || process.cwd();
@@ -2702,8 +2735,8 @@ function emitJson(payload, outputPath) {
2702
2735
  }
2703
2736
  function printOneSession(sessionId, meta, plan) {
2704
2737
  const allTasks = allTasksOf(plan);
2705
- const everyTask = (0, import_core14.flattenTasks)(allTasks);
2706
- const blocked = (0, import_core14.blockingFailures)(everyTask.map((t) => ({ ...t, subtasks: [], dependencies: t.dependencies ?? [] })));
2738
+ const everyTask = (0, import_core15.flattenTasks)(allTasks);
2739
+ const blocked = (0, import_core15.blockingFailures)(everyTask.map((t) => ({ ...t, subtasks: [], dependencies: t.dependencies ?? [] })));
2707
2740
  const count = (status) => everyTask.filter((t) => t.status === status || status === "blocked" && blocked.has(t.id)).length;
2708
2741
  console.log(`Session: ${sessionId}`);
2709
2742
  console.log(`Goal: ${meta.goal}`);
@@ -2711,29 +2744,39 @@ function printOneSession(sessionId, meta, plan) {
2711
2744
  console.log(`Phase: ${plan?.phase || "unknown"} | Created: ${meta.createdAt}`);
2712
2745
  console.log(`Tasks: ${everyTask.length} total | ${count("completed")} done | ${count("failed")} failed | ${count("in_progress")} running | ${count("blocked")} blocked | ${count("skipped")} skipped
2713
2746
  `);
2714
- const usage = (0, import_core14.planUsage)(allTasks);
2747
+ const usage = (0, import_core15.planUsage)(allTasks);
2715
2748
  if (usage.attempts) console.log(`Tasks total: ${taskUsageText(usage)}`);
2749
+ const record = servedApprovalRecord(plan);
2750
+ const approval = (row, ref) => row.waitsForApproval ? `${row.waitsForApproval}: \`ordewell run-task ${ref}\` starts it, or \`ordewell run\` runs the plan` : null;
2716
2751
  for (const t of allTasks) {
2717
2752
  const icon = iconFor(blocked.has(t.id) ? "blocked" : t.status);
2718
2753
  const model = t.assignedModel ? ` (${t.assignedModel.modelLabel})` : "";
2719
2754
  const shortId = (t.id ?? "").slice(-8);
2720
- console.log(` ${(0, import_core14.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}] ${t.title}${model}`);
2721
- const row = (0, import_core14.taskRowView)({ ...t, dependencies: t.dependencies ?? [] }, { tasks: allTasks });
2722
- if (row.blockedBy) console.log(` ${(0, import_core14.blockedByLabel)(row.blockedBy)}`);
2723
- if (row.status === "skipped") console.log(` ${(0, import_core14.skippedLabel)(row.skipReason)}`);
2755
+ console.log(` ${(0, import_core15.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}] ${t.title}${model}`);
2756
+ const row = (0, import_core15.taskRowView)({ ...t, dependencies: t.dependencies ?? [], waitsForApproval: (0, import_core15.waitsForApproval)(t, record) }, { tasks: allTasks });
2757
+ const waiting = approval(row, (0, import_core15.taskOrderLabel)(t));
2758
+ if (waiting) console.log(` ${waiting}`);
2759
+ if (row.blockedBy) console.log(` ${(0, import_core15.blockedByLabel)(row.blockedBy)}`);
2760
+ if (row.status === "skipped") console.log(` ${(0, import_core15.skippedLabel)(row.skipReason)}`);
2724
2761
  if (row.usage) console.log(` ${taskUsageText(row.usage)}`);
2725
2762
  for (const sub of t.subtasks || []) {
2726
2763
  const subIcon = iconFor(blocked.has(sub.id) ? "blocked" : sub.status);
2727
2764
  const subModel = sub.assignedModel ? ` (${sub.assignedModel.modelLabel})` : "";
2728
2765
  const subShortId = (sub.id ?? "").slice(-8);
2729
- console.log(` ${(0, import_core14.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
2730
- const subRow = (0, import_core14.taskRowView)({ ...sub, dependencies: sub.dependencies ?? [] }, { parent: t, tasks: allTasks });
2731
- if (subRow.blockedBy) console.log(` ${(0, import_core14.blockedByLabel)(subRow.blockedBy)}`);
2732
- if (subRow.status === "skipped") console.log(` ${(0, import_core14.skippedLabel)(subRow.skipReason)}`);
2766
+ console.log(` ${(0, import_core15.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
2767
+ const subRow = (0, import_core15.taskRowView)({ ...sub, dependencies: sub.dependencies ?? [], waitsForApproval: (0, import_core15.waitsForApproval)(sub, record) }, { parent: t, tasks: allTasks });
2768
+ const subWaiting = approval(subRow, (0, import_core15.taskOrderLabel)(sub, t));
2769
+ if (subWaiting) console.log(` ${subWaiting}`);
2770
+ if (subRow.blockedBy) console.log(` ${(0, import_core15.blockedByLabel)(subRow.blockedBy)}`);
2771
+ if (subRow.status === "skipped") console.log(` ${(0, import_core15.skippedLabel)(subRow.skipReason)}`);
2733
2772
  if (subRow.usage) console.log(` ${taskUsageText(subRow.usage)}`);
2734
2773
  }
2735
2774
  }
2736
2775
  }
2776
+ function servedApprovalRecord(plan) {
2777
+ const approvedTasks = Array.isArray(plan?.approvedTasks) ? plan.approvedTasks.filter((id) => typeof id === "string") : void 0;
2778
+ return { approved: plan?.approved === true, ...approvedTasks ? { approvedTasks } : {} };
2779
+ }
2737
2780
 
2738
2781
  // src/commands/stop.ts
2739
2782
  async function handleStop(subArgs) {
@@ -2782,10 +2825,32 @@ async function handleWeb(subArgs) {
2782
2825
  }
2783
2826
 
2784
2827
  // src/commands/models.ts
2828
+ var import_core18 = require("@ordewell/core");
2829
+
2830
+ // src/commands/model.ts
2831
+ var import_core17 = require("@ordewell/core");
2832
+
2833
+ // src/plannerModelSwitch.ts
2785
2834
  var import_core16 = require("@ordewell/core");
2835
+ function plannerSwitchRecall(settings) {
2836
+ if (settings.switchRecall) return settings.switchRecall;
2837
+ const model = settings.orchestratorModel;
2838
+ return model ? { model, effort: settings.plannerThinkingEffort, source: "remembered" } : { model: "", effort: "", source: "none" };
2839
+ }
2840
+ function vendorModelRefusal(provider, model, pickPlanner) {
2841
+ const meta = (0, import_core16.getProviderMeta)(provider);
2842
+ if (!(0, import_core16.runnerForProvider)(provider) || !meta || !(0, import_core16.isVendorModelFor)(meta.id, model)) return null;
2843
+ return `${model} is a vendor's model, which ${meta.label} cannot run. To plan with that vendor, switch planner with ${pickPlanner}.`;
2844
+ }
2845
+ function uninstalledPlannerRefusal(provider, installed, pickPlanner) {
2846
+ const runner = (0, import_core16.runnerForProvider)(provider);
2847
+ if (!runner || installed.includes(runner)) return null;
2848
+ const agents = import_core16.CLI_PROVIDERS.filter((id) => installed.includes((0, import_core16.runnerForProvider)(id) ?? ""));
2849
+ const label = (0, import_core16.getProviderMeta)(provider)?.label ?? provider;
2850
+ 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}.`;
2851
+ }
2786
2852
 
2787
2853
  // src/commands/model.ts
2788
- var import_core15 = require("@ordewell/core");
2789
2854
  var USAGE = "Usage: ordewell model [show | set <model-id>]";
2790
2855
  async function handleModel(subArgs, injectedApi) {
2791
2856
  const [action, modelId] = positionals(subArgs);
@@ -2800,7 +2865,7 @@ async function handleModel(subArgs, injectedApi) {
2800
2865
  if (!modelId) fail(USAGE);
2801
2866
  const settings = await api.getSettings();
2802
2867
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
2803
- const runner = (0, import_core15.runnerForProvider)(provider);
2868
+ const runner = (0, import_core17.runnerForProvider)(provider);
2804
2869
  if (runner) {
2805
2870
  const known = (await fetchCatalog(api)).models.filter((m) => m.runners?.includes(runner));
2806
2871
  if (known.length > 0 && !known.some((m) => m.id === modelId)) {
@@ -2809,6 +2874,8 @@ async function handleModel(subArgs, injectedApi) {
2809
2874
  `Run \`ordewell model\` to see what ${runner} offers.`
2810
2875
  );
2811
2876
  }
2877
+ const refusal = known.length === 0 ? vendorModelRefusal(provider, modelId, import_core17.HEADLESS_CLI_COMMANDS.pickPlanner) : null;
2878
+ if (refusal) fail(refusal);
2812
2879
  }
2813
2880
  const settled = await api.updateSettings({ orchestratorModel: modelId });
2814
2881
  const effort = typeof settled.plannerThinkingEffort === "string" ? settled.plannerThinkingEffort : "";
@@ -2823,14 +2890,14 @@ async function showModels(api) {
2823
2890
  const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
2824
2891
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
2825
2892
  const current = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
2826
- const runner = (0, import_core15.runnerForProvider)(provider);
2893
+ const runner = (0, import_core17.runnerForProvider)(provider);
2827
2894
  const options = runner ? catalog.models.filter((m) => m.runners?.includes(runner)) : catalog.orchestratorModels;
2828
2895
  console.log(`
2829
2896
  Planner model: ${current || "(backend default)"}`);
2830
- console.log(`Planner backend: ${provider || "(default)"}${runner ? ` \u2014 showing ${runner} models only` : ""}
2897
+ console.log(`Planner backend: ${provider || "(default)"}${runner ? `: showing ${runner} models only` : ""}
2831
2898
  `);
2832
2899
  if (options.length === 0) {
2833
- console.log(runner ? ` No ${runner} models discovered yet \u2014 try \`ordewell refresh\`.` : " No models discovered. Configure a provider key with `ordewell key set <provider> <key>`.");
2900
+ console.log(runner ? ` No ${runner} models discovered yet: try \`ordewell refresh\`.` : " No models discovered. Configure a provider key with `ordewell key set <provider> <key>`.");
2834
2901
  }
2835
2902
  for (const m of options) {
2836
2903
  const detail = [
@@ -2839,7 +2906,7 @@ Planner model: ${current || "(backend default)"}`);
2839
2906
  m.variants?.length ? `${m.variants.length} effort level${m.variants.length === 1 ? "" : "s"}` : void 0
2840
2907
  ].filter(Boolean).join(" \xB7 ");
2841
2908
  console.log(` ${m.id === current ? "*" : " "} ${m.id}`);
2842
- if (detail) console.log(` ${m.label} \u2014 ${detail}`);
2909
+ if (detail) console.log(` ${m.label}: ${detail}`);
2843
2910
  }
2844
2911
  const failed = Object.entries(catalog.providerErrors);
2845
2912
  for (const [id, message] of failed) {
@@ -2857,8 +2924,8 @@ async function handleModels(subArgs) {
2857
2924
  const rest = subArgs.filter((_, i) => i !== setIdx && i !== setIdx + 1);
2858
2925
  return handleModel(["set", subArgs[setIdx + 1], ...rest]);
2859
2926
  }
2860
- const config = new import_core16.EnvConfig();
2861
- const providers = (0, import_core16.configuredProviders)(config);
2927
+ const config = new import_core18.EnvConfig();
2928
+ const providers = (0, import_core18.configuredProviders)(config);
2862
2929
  if (providers.length === 0) {
2863
2930
  console.error("No providers configured. Set an API key for a provider, e.g.:");
2864
2931
  console.error(" OPENROUTER_API_KEY=... (OpenRouter)");
@@ -2867,19 +2934,19 @@ async function handleModels(subArgs) {
2867
2934
  console.error(" OPENAI_COMPATIBLE_BASE_URL=... (self-hosted / OpenAI-compatible)");
2868
2935
  process.exit(1);
2869
2936
  }
2870
- const { apiKeys, baseUrls } = (0, import_core16.collectProviderCredentials)(config);
2937
+ const { apiKeys, baseUrls } = (0, import_core18.collectProviderCredentials)(config);
2871
2938
  let models;
2872
2939
  let errors;
2873
2940
  try {
2874
- const result = await (0, import_core16.fetchAllProviderModels)({ apiKeys, baseUrls });
2875
- models = (0, import_core16.toOrchestratorOptions)(result.models, import_core16.ORCHESTRATOR_SHORTCUTS);
2941
+ const result = await (0, import_core18.fetchAllProviderModels)({ apiKeys, baseUrls });
2942
+ models = (0, import_core18.toOrchestratorOptions)(result.models, import_core18.ORCHESTRATOR_SHORTCUTS);
2876
2943
  errors = result.errors;
2877
2944
  } catch (err) {
2878
2945
  console.error(`Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`);
2879
2946
  process.exit(1);
2880
2947
  return;
2881
2948
  }
2882
- const providerLabel = (p) => (0, import_core16.getProviderMeta)(p)?.label ?? p;
2949
+ const providerLabel = (p) => (0, import_core18.getProviderMeta)(p)?.label ?? p;
2883
2950
  console.log(
2884
2951
  `
2885
2952
  Available models (${models.length}) across ${providers.length} configured provider(s):`
@@ -2919,7 +2986,7 @@ ${providerLabel(provider)}`);
2919
2986
 
2920
2987
  // src/commands/setup.ts
2921
2988
  var import_fs6 = require("fs");
2922
- var import_core17 = require("@ordewell/core");
2989
+ var import_core19 = require("@ordewell/core");
2923
2990
  function handleSetup(subArgs) {
2924
2991
  const modelIdx = subArgs.indexOf("--model");
2925
2992
  const chosenModel = modelIdx !== -1 ? subArgs[modelIdx + 1] : null;
@@ -2962,7 +3029,7 @@ function handleSetup(subArgs) {
2962
3029
  # OPENAI_COMPATIBLE_BASE_URL=http://localhost:11434/v1
2963
3030
  # OPENAI_COMPATIBLE_API_KEY=your-key-here
2964
3031
 
2965
- # Or plan with a coding agent you already have installed \u2014 no API key needed,
3032
+ # Or plan with a coding agent you already have installed: no API key needed,
2966
3033
  # it uses that agent's own subscription. Set the model to one of its own.
2967
3034
  # AI_PROVIDER=${runnerIdChoice()}
2968
3035
  # ORCHESTRATOR_MODEL=haiku
@@ -2991,13 +3058,13 @@ function handleSetup(subArgs) {
2991
3058
  # STEPFUN_API_KEY=...
2992
3059
  # SILICONFLOW_API_KEY=...
2993
3060
  `;
2994
- (0, import_core17.writePrivateFile)(envFile, content);
3061
+ (0, import_core19.writePrivateFile)(envFile, content);
2995
3062
  console.log(`Wrote: ${envFile}`);
2996
3063
  console.log("Review and edit the file, then run: ordewell tui");
2997
3064
  }
2998
3065
 
2999
3066
  // src/commands/skills.ts
3000
- var import_core18 = require("@ordewell/core");
3067
+ var import_core20 = require("@ordewell/core");
3001
3068
  var import_path5 = require("path");
3002
3069
  function invocation(skill) {
3003
3070
  if (skill.userInvocable && skill.modelInvocable) return "both";
@@ -3010,22 +3077,22 @@ function handleSkills(subArgs) {
3010
3077
  fail("Usage: ordewell skills [--workspace /path] [--json]");
3011
3078
  }
3012
3079
  const workspace = (0, import_path5.resolve)(flag(subArgs, "--workspace") || process.cwd());
3013
- const catalog = (0, import_core18.createSkillsService)(workspace, new import_core18.EnvConfig().workspaceRepos).readCatalog();
3080
+ const catalog = (0, import_core20.createSkillsService)(workspace, new import_core20.EnvConfig().workspaceRepos).readCatalog();
3014
3081
  const skills = catalog.skills.map((skill) => ({
3015
3082
  name: skill.name,
3016
3083
  scope: skill.source,
3017
3084
  appliesTo: skill.appliesTo,
3018
3085
  invocation: invocation(skill),
3019
- path: (0, import_core18.abbreviateHome)(skill.path)
3086
+ path: (0, import_core20.abbreviateHome)(skill.path)
3020
3087
  }));
3021
3088
  const shadowed = catalog.shadowed.map(({ skill, shadowedBy }) => ({
3022
3089
  name: skill.name,
3023
- path: (0, import_core18.abbreviateHome)(skill.path),
3024
- shadowedBy: { scope: shadowedBy.source, path: (0, import_core18.abbreviateHome)(shadowedBy.path) }
3090
+ path: (0, import_core20.abbreviateHome)(skill.path),
3091
+ shadowedBy: { scope: shadowedBy.source, path: (0, import_core20.abbreviateHome)(shadowedBy.path) }
3025
3092
  }));
3026
3093
  const invalid = catalog.invalid.map((skill) => ({
3027
3094
  name: skill.name,
3028
- path: (0, import_core18.abbreviateHome)(skill.path),
3095
+ path: (0, import_core20.abbreviateHome)(skill.path),
3029
3096
  reason: skill.reason
3030
3097
  }));
3031
3098
  if (hasFlag(subArgs, "--json")) {
@@ -3140,7 +3207,7 @@ async function handleShow(args, api) {
3140
3207
  }
3141
3208
 
3142
3209
  // src/commands/task-control.ts
3143
- var import_core19 = require("@ordewell/core");
3210
+ var import_core21 = require("@ordewell/core");
3144
3211
  var PAST_TENSE = {
3145
3212
  run: "Task started.",
3146
3213
  "force-start": "Task force-started.",
@@ -3183,8 +3250,8 @@ async function confirmPastGate(subArgs, api, sessionId, taskId, confirm2) {
3183
3250
  if (hasFlag(subArgs, "--yes")) return;
3184
3251
  const unmerged = await api.getMergeGate(sessionId, taskId).catch(() => []);
3185
3252
  if (unmerged.length === 0) return;
3186
- const question = `${(0, import_core19.pastGateConfirmation)(unmerged.map((d) => (0, import_core19.titledTaskRef)(d)))} Start it anyway?`;
3187
- if (!await confirm2(question)) fail("Not started \u2014 nothing was changed. Pass --yes to start it without a prompt.");
3253
+ const question = `${(0, import_core21.pastGateConfirmation)(unmerged.map((d) => (0, import_core21.titledTaskRef)(d)))} Start it anyway?`;
3254
+ if (!await confirm2(question)) fail("Not started: nothing was changed. Pass --yes to start it without a prompt.");
3188
3255
  }
3189
3256
  function makeHandler(action, command = action) {
3190
3257
  const yes = PASSES_GATE.has(action) ? " [--yes]" : "";
@@ -3232,7 +3299,7 @@ async function handleCheckpoint(subArgs, injectedApi) {
3232
3299
  process.exit(1);
3233
3300
  }
3234
3301
  if (answer === "approve" && reason) {
3235
- console.error(`Approving takes no note \u2014 reject with a reason to tell the task something.
3302
+ console.error(`Approving takes no note: reject with a reason to tell the task something.
3236
3303
  ${usage}`);
3237
3304
  process.exit(1);
3238
3305
  }
@@ -3293,7 +3360,7 @@ async function handleSkip(subArgs, api) {
3293
3360
  async (client, sessionId, taskId) => {
3294
3361
  try {
3295
3362
  await client.skipTask(sessionId, taskId, flag(subArgs, "--reason"));
3296
- console.log("Task skipped: none of its work lands, and the tasks that depend on it go ahead without it.");
3363
+ console.log("Task skipped: none of its work lands, and the tasks that depend on it go ahead without it once the work it waited on is done.");
3297
3364
  } catch (err) {
3298
3365
  console.error(`Failed to skip task: ${err.message}`);
3299
3366
  process.exit(1);
@@ -3401,7 +3468,7 @@ async function handleSessions(subArgs, injectedApi) {
3401
3468
  }
3402
3469
  const { meta } = session;
3403
3470
  saveLastSession(meta.id, meta.goal, meta.runners || [], workspace);
3404
- console.log(`Loaded session ${meta.id} ("${meta.goal}") \u2014 ${meta.taskCount} tasks, status ${meta.status}.`);
3471
+ console.log(`Loaded session ${meta.id} ("${meta.goal}"): ${meta.taskCount} tasks, status ${meta.status}.`);
3405
3472
  console.log(" `ordewell status` for detail, `ordewell run` to execute.");
3406
3473
  return;
3407
3474
  }
@@ -3425,7 +3492,7 @@ async function handleSessions(subArgs, injectedApi) {
3425
3492
  }
3426
3493
 
3427
3494
  // src/commands/conversation.ts
3428
- var REWIND_USAGE = "Usage: ordewell rewind [<n>] [--session-id <id>] [--workspace /path] \u2014 with no <n>, lists the messages you can rewind to.";
3495
+ var REWIND_USAGE = "Usage: ordewell rewind [<n>] [--session-id <id>] [--workspace /path]: with no <n>, lists the messages you can rewind to.";
3429
3496
  async function adopted(subArgs, injectedApi) {
3430
3497
  const workspace = flag(subArgs, "--workspace") || process.cwd();
3431
3498
  const sessionId = resolveSessionId(subArgs);
@@ -3447,7 +3514,7 @@ async function handleFork(subArgs, injectedApi) {
3447
3514
  fail(`Failed to fork: ${err.message}`);
3448
3515
  }
3449
3516
  saveLastSession(fork.sessionId, fork.goal, fork.plan.runners ?? [], workspace);
3450
- console.log(`Forked ${sessionId} into ${fork.sessionId} \u2014 the fork is now the current session.`);
3517
+ console.log(`Forked ${sessionId} into ${fork.sessionId}: the fork is now the current session.`);
3451
3518
  console.log(` \`ordewell sessions load ${sessionId}\` to go back to the original.`);
3452
3519
  }
3453
3520
  async function handleCompact(subArgs, injectedApi) {
@@ -3456,7 +3523,7 @@ async function handleCompact(subArgs, injectedApi) {
3456
3523
  try {
3457
3524
  result = await api.compactConversation(sessionId);
3458
3525
  } catch (err) {
3459
- fail(`Failed to condense: ${err.message}`);
3526
+ fail(`Failed to condense: ${headlessPlannerRefusal(err) ?? err.message}`);
3460
3527
  }
3461
3528
  console.log(`Condensed ${sessionId}. The last two exchanges were kept as they were; the tasks are unchanged.
3462
3529
  `);
@@ -3474,7 +3541,7 @@ async function handleRewind(subArgs, injectedApi) {
3474
3541
  fail(`Failed to list messages: ${err.message}`);
3475
3542
  }
3476
3543
  if (targets.length === 0) {
3477
- console.log("Nothing to rewind to yet \u2014 the only message so far is the goal.");
3544
+ console.log("Nothing to rewind to yet: the only message so far is the goal.");
3478
3545
  return;
3479
3546
  }
3480
3547
  console.log("Your messages, most recent first:\n");
@@ -3489,31 +3556,31 @@ async function handleRewind(subArgs, injectedApi) {
3489
3556
  fail(`Failed to rewind: ${err.message}`);
3490
3557
  }
3491
3558
  saveLastSession(fork.sessionId, fork.goal, fork.plan.runners ?? [], workspace);
3492
- console.log(`Forked ${sessionId} into ${fork.sessionId} from just before message ${arg} \u2014 the fork is now the current session.`);
3559
+ console.log(`Forked ${sessionId} into ${fork.sessionId} from just before message ${arg}. The fork is now the current session.`);
3493
3560
  console.log(` \`ordewell sessions load ${sessionId}\` to go back to the original.`);
3494
3561
  console.log("\nThe message you rewound to, to resend or edit:\n");
3495
3562
  console.log(fork.rewoundMessage);
3496
3563
  }
3497
3564
 
3498
3565
  // src/tui/transcript.ts
3499
- var import_core20 = require("@ordewell/core");
3566
+ var import_core22 = require("@ordewell/core");
3500
3567
  function show(state, { view: conversation, gate: turnGate }) {
3501
3568
  const gated = turnGate === state.turnGate ? state : { ...state, turnGate };
3502
3569
  if (conversation === state.conversation) return gated;
3503
3570
  return { ...gated, conversation };
3504
3571
  }
3505
3572
  function fold(state, input) {
3506
- return show(state, { view: (0, import_core20.reduceConversation)(state.conversation, input), gate: state.turnGate });
3573
+ return show(state, { view: (0, import_core22.reduceConversation)(state.conversation, input), gate: state.turnGate });
3507
3574
  }
3508
3575
  function say(state, role, text) {
3509
3576
  const said = fold(state, { type: "local_entry", role, text });
3510
3577
  return said.conversation.nextId > state.conversation.nextId ? { ...said, scroll: 0 } : said;
3511
3578
  }
3512
3579
  function hear(state, message) {
3513
- return show(state, (0, import_core20.followTurn)(state.conversation, state.turnGate, message));
3580
+ return show(state, (0, import_core22.followTurn)(state.conversation, state.turnGate, message));
3514
3581
  }
3515
3582
  function cutTurn(state) {
3516
- return show(state, (0, import_core20.stopTurn)(state.conversation, state.turnGate));
3583
+ return show(state, (0, import_core22.stopTurn)(state.conversation, state.turnGate));
3517
3584
  }
3518
3585
  function wiped(view) {
3519
3586
  return { ...view, blocks: view.blocks.filter((b) => b.type === "usage") };
@@ -3522,16 +3589,16 @@ function wiped(view) {
3522
3589
  // src/tui/handoff.ts
3523
3590
  var HANDOFF_ACTIONS = [
3524
3591
  { id: "review", label: "Review diff", hint: "the branch against the commit it forked from" },
3525
- { id: "merge", label: "Merge", hint: "into the branch you have checked out \u2014 asks first" },
3526
- { id: "discard", label: "Discard", hint: "the run, its worktrees and this branch \u2014 asks first" },
3592
+ { id: "merge", label: "Merge", hint: "into the branch you have checked out (asks first)" },
3593
+ { id: "discard", label: "Discard", hint: "the run, its worktrees and this branch (asks first)" },
3527
3594
  { id: "cleanup", label: "Clean up", hint: "remove worktrees and task branches, keep this branch" }
3528
3595
  ];
3529
3596
  function handoffActions(handoff) {
3530
3597
  if (!isRepoGroup(handoff)) return HANDOFF_ACTIONS;
3531
3598
  return [
3532
3599
  { id: "review", label: "Review diff", hint: "each repository's branch against the commit it forked from" },
3533
- { id: "merge", label: "Merge all", hint: "every repository's branch into what you have checked out there \u2014 asks first" },
3534
- { id: "discard", label: "Discard", hint: "the run, its worktrees and every repository's branch \u2014 asks first" },
3600
+ { id: "merge", label: "Merge all", hint: "every repository's branch into what you have checked out there (asks first)" },
3601
+ { id: "discard", label: "Discard", hint: "the run, its worktrees and every repository's branch (asks first)" },
3535
3602
  { id: "cleanup", label: "Clean up", hint: "remove worktrees and task branches everywhere, keep the integration branches" }
3536
3603
  ];
3537
3604
  }
@@ -3544,7 +3611,7 @@ function currentHandoff(state) {
3544
3611
  }
3545
3612
  var midRun = (state) => state.status === "executing";
3546
3613
  function handoffCommand(state, arg) {
3547
- if (!state.sessionId || !currentHandoff(state)) return fail2(state, "No isolated run to hand off \u2014 a plan that ran in worktrees leaves one here.");
3614
+ if (!state.sessionId || !currentHandoff(state)) return fail2(state, "No isolated run to hand off: a plan that ran in worktrees leaves one here.");
3548
3615
  if (arg === void 0) return { state: { ...state, overlay: { kind: "handoff", index: 0, diff: null } }, effects: [] };
3549
3616
  const action = HANDOFF_ACTIONS.find((a) => a.id === arg.toLowerCase());
3550
3617
  if (!action) return fail2(state, `Usage: ${HANDOFF_USAGE}`);
@@ -3557,13 +3624,13 @@ function handoffArrived(state, handoff) {
3557
3624
  const tasks = `${landed} task${landed === 1 ? "" : "s"} landed`;
3558
3625
  const received = { ...state, handoff };
3559
3626
  if (handoff.deferredTo === "supervisor") {
3560
- return say(received, "system", `Run finished on ${branch}${where} \u2014 ${tasks}. The supervisor may merge it; /handoff to review it yourself.`);
3627
+ return say(received, "system", `Run finished on ${branch}${where}: ${tasks}. The supervisor may merge it; /handoff to review it yourself.`);
3561
3628
  }
3562
3629
  const returned = state.handoff?.deferredTo === "supervisor" && handoffBranch(state.handoff) === branch;
3563
3630
  const told = say(
3564
3631
  received,
3565
3632
  "system",
3566
- returned ? `The supervisor left ${branch}${where} to you \u2014 ${tasks}. /handoff to review and land it.` : `Run finished on ${branch}${where} \u2014 ${tasks}. /handoff to review and land it.`
3633
+ returned ? `The supervisor left ${branch}${where} to you: ${tasks}. /handoff to review and land it.` : `Run finished on ${branch}${where}: ${tasks}. /handoff to review and land it.`
3567
3634
  );
3568
3635
  return state.overlay ? told : { ...told, overlay: { kind: "handoff", index: 0, diff: null } };
3569
3636
  }
@@ -3601,12 +3668,12 @@ function runHandoffAction(state, id) {
3601
3668
  overlay: isRepoGroup(handoff) ? {
3602
3669
  kind: "confirm",
3603
3670
  title: "Merge all into your branches?",
3604
- message: `Merge ${handoffBranch(handoff)} into whatever each of ${reposWithWork(handoff).join(", ")} has checked out. 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 \u2014 unless a task still has work in its worktree that has not landed: then the run stays, and so does that work.${repairedNotice(handoff)}`,
3671
+ message: `Merge ${handoffBranch(handoff)} into whatever each of ${reposWithWork(handoff).join(", ")} has checked out. 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.${repairedNotice(handoff)}`,
3605
3672
  action: { kind: "merge-run" }
3606
3673
  } : {
3607
3674
  kind: "confirm",
3608
3675
  title: "Merge into your branch?",
3609
- message: `Merge ${handoffBranch(handoff)} into whatever you have checked out. 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 \u2014 unless a task still has work in its worktree that has not landed: then the run stays, and so does that work.${repairedNotice(handoff)}`,
3676
+ message: `Merge ${handoffBranch(handoff)} into whatever you have checked out. 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.${repairedNotice(handoff)}`,
3610
3677
  action: { kind: "merge-run" }
3611
3678
  }
3612
3679
  },
@@ -3619,7 +3686,7 @@ function runHandoffAction(state, id) {
3619
3686
  overlay: {
3620
3687
  kind: "confirm",
3621
3688
  title: "Discard this run?",
3622
- message: `Removes the run's worktrees and task branches and deletes ${handoffBranch(handoff)}${isRepoGroup(handoff) ? ` in ${reposOf2(handoff)}` : ""}. Tasks keep their status \u2014 mark one not done if you did not keep its work.`,
3689
+ message: `Removes the run's worktrees and task branches and deletes ${handoffBranch(handoff)}${isRepoGroup(handoff) ? ` in ${reposOf2(handoff)}` : ""}. Tasks keep their status: mark one not done if you did not keep its work.`,
3623
3690
  action: { kind: "discard-run" }
3624
3691
  }
3625
3692
  },
@@ -3714,7 +3781,7 @@ async function handleHandoff(subArgs, injectedApi, confirm2 = askYesNo) {
3714
3781
  if (action === void 0) {
3715
3782
  console.log(`${USAGE4}
3716
3783
  `);
3717
- for (const a of HANDOFF_ACTIONS) console.log(` ${a.id.padEnd(8)} ${a.label} \u2014 ${a.hint}`);
3784
+ for (const a of HANDOFF_ACTIONS) console.log(` ${a.id.padEnd(8)} ${a.label}: ${a.hint}`);
3718
3785
  return;
3719
3786
  }
3720
3787
  if (!HANDOFF_ACTIONS.some((a) => a.id === action)) fail(`Unknown handoff action "${action}".`, USAGE4);
@@ -3727,7 +3794,7 @@ async function handleHandoff(subArgs, injectedApi, confirm2 = askYesNo) {
3727
3794
  const asked = hasFlag(subArgs, "--yes");
3728
3795
  const confirmed = async (question) => {
3729
3796
  if (asked || await confirm2(question)) return;
3730
- fail("Not confirmed \u2014 nothing was changed. Pass --yes to confirm without a prompt.");
3797
+ fail("Not confirmed: nothing was changed. Pass --yes to confirm without a prompt.");
3731
3798
  };
3732
3799
  const attempt = async (work) => {
3733
3800
  try {
@@ -3762,16 +3829,7 @@ async function handleHandoff(subArgs, injectedApi, confirm2 = askYesNo) {
3762
3829
  }
3763
3830
 
3764
3831
  // src/commands/planner.ts
3765
- var import_core21 = require("@ordewell/core");
3766
-
3767
- // src/plannerModelSwitch.ts
3768
- function plannerSwitchRecall(settings) {
3769
- if (settings.switchRecall) return settings.switchRecall;
3770
- const model = settings.orchestratorModel;
3771
- return model ? { model, effort: settings.plannerThinkingEffort, source: "remembered" } : { model: "", effort: "", source: "none" };
3772
- }
3773
-
3774
- // src/commands/planner.ts
3832
+ var import_core23 = require("@ordewell/core");
3775
3833
  async function handlePlanner(subArgs, injectedApi) {
3776
3834
  const api = await connect(subArgs, injectedApi);
3777
3835
  const [wanted] = positionals(subArgs);
@@ -3780,20 +3838,25 @@ async function handlePlanner(subArgs, injectedApi) {
3780
3838
  return;
3781
3839
  }
3782
3840
  const provider = wanted.toLowerCase();
3783
- const meta = (0, import_core21.getProviderMeta)(provider);
3841
+ const meta = (0, import_core23.getProviderMeta)(provider);
3784
3842
  if (!meta) {
3785
3843
  fail(`Unknown planner: ${provider}`, "Run `ordewell planner` with no arguments to see the list.");
3786
3844
  }
3845
+ if ((0, import_core23.isCliProvider)(provider)) {
3846
+ const installed = (await api.getRunners()).runners.map((r) => r.id);
3847
+ const refusal = uninstalledPlannerRefusal(provider, installed, import_core23.HEADLESS_CLI_COMMANDS.pickPlanner);
3848
+ if (refusal) fail(refusal);
3849
+ }
3787
3850
  const settings = await persistEnv(api, { AI_PROVIDER: provider });
3788
3851
  const recall2 = plannerSwitchRecall(settings);
3789
3852
  writeEnv({ ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
3790
3853
  const catalog = await fetchCatalog(api);
3791
3854
  console.log(
3792
- (0, import_core21.isCliProvider)(provider) ? `Planning with ${meta.label} \u2014 no API key needed, it uses that agent's own subscription.` : `Planner set to ${meta.label}.`
3855
+ (0, import_core23.isCliProvider)(provider) ? `Planning with ${meta.label}: no API key needed, it uses that agent's own subscription.` : `Planner set to ${meta.label}.`
3793
3856
  );
3794
3857
  console.log(plannerModelLine(recall2));
3795
- if ((0, import_core21.isApiProvider)(provider) && !catalog.providers.includes(provider)) {
3796
- console.log(` No ${meta.apiKeyEnvVar} configured yet \u2014 set one with \`ordewell key set ${provider} <key>\`.`);
3858
+ if ((0, import_core23.isApiProvider)(provider) && !catalog.providers.includes(provider)) {
3859
+ console.log(` No ${meta.apiKeyEnvVar} configured yet: set one with \`ordewell key set ${provider} <key>\`.`);
3797
3860
  }
3798
3861
  }
3799
3862
  function plannerModelLine(recall2) {
@@ -3801,7 +3864,7 @@ function plannerModelLine(recall2) {
3801
3864
  case "remembered":
3802
3865
  return ` Restored its planner model: ${recall2.model}.`;
3803
3866
  case "catalog-default":
3804
- return ` Using ${recall2.model}, its default model \u2014 pick another with \`ordewell model set <id>\`.`;
3867
+ return ` Using ${recall2.model}, its default model: pick another with \`ordewell model set <id>\`.`;
3805
3868
  case "none":
3806
3869
  return " Pick a model with `ordewell model set <id>`, or leave it on the default.";
3807
3870
  }
@@ -3809,16 +3872,16 @@ function plannerModelLine(recall2) {
3809
3872
  async function showPlanners(api) {
3810
3873
  const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
3811
3874
  const current = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
3812
- console.log("\nCoding agents (no API key \u2014 they use their own subscription)");
3813
- for (const id of import_core21.CLI_PROVIDERS) {
3814
- const runner = (0, import_core21.runnerForProvider)(id);
3875
+ console.log("\nCoding agents (no API key; they use their own subscription)");
3876
+ for (const id of import_core23.CLI_PROVIDERS) {
3877
+ const runner = (0, import_core23.runnerForProvider)(id);
3815
3878
  const discovered = runner ? catalog.models.some((m) => m.runners?.includes(runner)) : false;
3816
- console.log(` ${marker(id === current)} ${id.padEnd(14)} ${(0, import_core21.getProviderMeta)(id)?.label ?? id}${discovered ? "" : " (not detected)"}`);
3879
+ console.log(` ${marker(id === current)} ${id.padEnd(14)} ${(0, import_core23.getProviderMeta)(id)?.label ?? id}${discovered ? "" : " (not detected)"}`);
3817
3880
  }
3818
3881
  console.log("\nAPI providers");
3819
- for (const id of import_core21.PROVIDER_PRIORITY) {
3882
+ for (const id of import_core23.PROVIDER_PRIORITY) {
3820
3883
  const configured = catalog.providers.includes(id);
3821
- const meta = (0, import_core21.getProviderMeta)(id);
3884
+ const meta = (0, import_core23.getProviderMeta)(id);
3822
3885
  console.log(` ${marker(id === current)} ${id.padEnd(14)} ${meta.label}${configured ? "" : ` (no ${meta.apiKeyEnvVar})`}`);
3823
3886
  }
3824
3887
  console.log(`
@@ -3835,7 +3898,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
3835
3898
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
3836
3899
  const modelId = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
3837
3900
  const currentEffort = typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : "";
3838
- const runner = (0, import_core21.runnerForProvider)(provider);
3901
+ const runner = (0, import_core23.runnerForProvider)(provider);
3839
3902
  if (!runner) {
3840
3903
  fail(
3841
3904
  "Thinking effort applies to a coding-agent planner.",
@@ -3847,7 +3910,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
3847
3910
  if (!wanted || wanted === "show") {
3848
3911
  console.log(`Planner effort: ${currentEffort || "runner default"}`);
3849
3912
  if (!model) {
3850
- console.log(` No planner model selected for ${runner} \u2014 pick one with \`ordewell model set <id>\`.`);
3913
+ console.log(` No planner model selected for ${runner}: pick one with \`ordewell model set <id>\`.`);
3851
3914
  } else if (variants.length === 0) {
3852
3915
  console.log(` ${model.label} exposes no effort levels; it always runs at the agent's default.`);
3853
3916
  } else {
@@ -3875,7 +3938,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
3875
3938
  }
3876
3939
 
3877
3940
  // src/commands/parallel.ts
3878
- var import_core22 = require("@ordewell/core");
3941
+ var import_core24 = require("@ordewell/core");
3879
3942
  var USAGE5 = "Usage: ordewell parallel [<number of tasks, 1 or more>]";
3880
3943
  async function handleParallel(subArgs, injectedApi) {
3881
3944
  const [wanted] = positionals(subArgs);
@@ -3885,14 +3948,14 @@ async function handleParallel(subArgs, injectedApi) {
3885
3948
  console.log(`Up to ${String(settings.maxParallel ?? "?")} AI tasks run at once. Change with: ordewell parallel <n>`);
3886
3949
  return;
3887
3950
  }
3888
- const limit = (0, import_core22.parseMaxParallel)(wanted);
3951
+ const limit = (0, import_core24.parseMaxParallel)(wanted);
3889
3952
  if (limit === null) fail(`"${wanted}" is not a number of tasks (1 or more).`, USAGE5);
3890
3953
  await persistEnv(api, { ORDEWELL_MAX_PARALLEL: String(limit) });
3891
3954
  console.log(`Up to ${limit} AI task${limit === 1 ? "" : "s"} now run at once.`);
3892
3955
  }
3893
3956
 
3894
3957
  // src/commands/runSettings.ts
3895
- var import_core23 = require("@ordewell/core");
3958
+ var import_core25 = require("@ordewell/core");
3896
3959
  var SUPERVISOR_USAGE = "Usage: ordewell supervisor [off|watch|act] \xB7 ordewell supervisor retries <n> \xB7 ordewell supervisor reads <n>";
3897
3960
  var AUTOMERGE_USAGE = "Usage: ordewell automerge [on|off]";
3898
3961
  var AUTORUN_USAGE = "Usage: ordewell autorun [on|off]";
@@ -3903,28 +3966,28 @@ async function handleSupervisor(subArgs, injectedApi) {
3903
3966
  const [arg, value] = positionals(subArgs);
3904
3967
  let changes = null;
3905
3968
  if (arg === "off" || arg === "watch" || arg === "act") changes = { supervisorLevel: arg };
3906
- else if (arg === "retries") changes = maybe((0, import_core23.parseCount)(value, 0), (n) => ({ supervisorRetryLimit: n }));
3907
- else if (arg === "reads") changes = maybe((0, import_core23.parseCount)(value, 1), (n) => ({ wakeUpReadBudget: n }));
3969
+ else if (arg === "retries") changes = maybe((0, import_core25.parseCount)(value, 0), (n) => ({ supervisorRetryLimit: n }));
3970
+ else if (arg === "reads") changes = maybe((0, import_core25.parseCount)(value, 1), (n) => ({ wakeUpReadBudget: n }));
3908
3971
  if (arg !== void 0 && changes === null) fail(SUPERVISOR_USAGE);
3909
3972
  const api = await connect(subArgs, injectedApi);
3910
3973
  if (!changes) {
3911
- console.log((0, import_core23.supervisorSummary)((await api.getSettings()).run));
3974
+ console.log((0, import_core25.supervisorSummary)((await api.getSettings()).run));
3912
3975
  console.log(` ${SUPERVISOR_USAGE}`);
3913
3976
  return;
3914
3977
  }
3915
3978
  await save(api, changes);
3916
- if (changes.supervisorLevel) console.log(`Supervisor: ${import_core23.SUPERVISOR_LEVEL_LABEL[changes.supervisorLevel]}, from each session's next run.`);
3917
- if (changes.supervisorLevel === "act") console.log(import_core23.ACT_RISK);
3979
+ if (changes.supervisorLevel) console.log(`Supervisor: ${import_core25.SUPERVISOR_LEVEL_LABEL[changes.supervisorLevel]}, from each session's next run.`);
3980
+ if (changes.supervisorLevel === "act") console.log(import_core25.ACT_RISK);
3918
3981
  if (changes.supervisorRetryLimit !== void 0) console.log(`Supervisor retry limit: ${changes.supervisorRetryLimit} per task.`);
3919
3982
  if (changes.wakeUpReadBudget !== void 0) console.log(`Wake-up read budget: ${changes.wakeUpReadBudget} reads.`);
3920
3983
  }
3921
3984
  async function handleAutomerge(subArgs, injectedApi) {
3922
3985
  const [arg] = positionals(subArgs);
3923
- const enabled = arg === void 0 ? null : (0, import_core23.parseSwitch)(arg);
3986
+ const enabled = arg === void 0 ? null : (0, import_core25.parseSwitch)(arg);
3924
3987
  if (arg !== void 0 && enabled === null) fail(AUTOMERGE_USAGE);
3925
3988
  const api = await connect(subArgs, injectedApi);
3926
3989
  if (enabled === null) {
3927
- console.log((0, import_core23.automergeSummary)((await api.getSettings()).run));
3990
+ console.log((0, import_core25.automergeSummary)((await api.getSettings()).run));
3928
3991
  console.log(` ${AUTOMERGE_USAGE}`);
3929
3992
  return;
3930
3993
  }
@@ -3933,24 +3996,24 @@ async function handleAutomerge(subArgs, injectedApi) {
3933
3996
  }
3934
3997
  async function handleAutorun(subArgs, injectedApi) {
3935
3998
  const [arg] = positionals(subArgs);
3936
- const enabled = arg === void 0 ? null : (0, import_core23.parseSwitch)(arg);
3999
+ const enabled = arg === void 0 ? null : (0, import_core25.parseSwitch)(arg);
3937
4000
  if (arg !== void 0 && enabled === null) fail(AUTORUN_USAGE);
3938
4001
  const api = await connect(subArgs, injectedApi);
3939
4002
  if (enabled === null) {
3940
- console.log((0, import_core23.autorunSummary)((await api.getSettings()).run));
4003
+ console.log((0, import_core25.autorunSummary)((await api.getSettings()).run));
3941
4004
  console.log(` ${AUTORUN_USAGE}`);
3942
4005
  return;
3943
4006
  }
3944
4007
  await save(api, { autorun: enabled });
3945
- console.log(`Autorun: ${enabled ? "on \u2014 each plan the planner submits starts at once" : "off \u2014 plans wait for your review"}.`);
3946
- if (enabled) console.log(import_core23.AUTORUN_RISK);
4008
+ console.log(`Autorun: ${enabled ? "on: each plan the planner submits starts at once" : "off: plans wait for your review"}.`);
4009
+ if (enabled) console.log(import_core25.AUTORUN_RISK);
3947
4010
  }
3948
4011
  function maybe(n, wrap2) {
3949
4012
  return n === null ? null : wrap2(n);
3950
4013
  }
3951
4014
 
3952
4015
  // src/commands/key.ts
3953
- var import_core24 = require("@ordewell/core");
4016
+ var import_core26 = require("@ordewell/core");
3954
4017
  var USAGE6 = "Usage: ordewell key [list | set <provider> <key>]";
3955
4018
  async function handleKey(subArgs, injectedApi) {
3956
4019
  const [action, provider, ...keyParts] = positionals(subArgs);
@@ -3961,8 +4024,8 @@ async function handleKey(subArgs, injectedApi) {
3961
4024
  if (action !== "set") {
3962
4025
  const catalog = await fetchCatalog(api);
3963
4026
  console.log("\nAPI provider keys\n");
3964
- for (const id2 of import_core24.PROVIDER_PRIORITY) {
3965
- const meta2 = (0, import_core24.getProviderMeta)(id2);
4027
+ for (const id2 of import_core26.PROVIDER_PRIORITY) {
4028
+ const meta2 = (0, import_core26.getProviderMeta)(id2);
3966
4029
  const configured = catalog.providers.includes(id2);
3967
4030
  console.log(` ${configured ? "\u2713" : " "} ${id2.padEnd(14)} ${meta2.apiKeyEnvVar.padEnd(28)} ${meta2.label}`);
3968
4031
  }
@@ -3972,10 +4035,10 @@ async function handleKey(subArgs, injectedApi) {
3972
4035
  }
3973
4036
  if (!provider) fail(USAGE6);
3974
4037
  const id = provider.toLowerCase();
3975
- if (!(0, import_core24.isApiProvider)(id)) {
4038
+ if (!(0, import_core26.isApiProvider)(id)) {
3976
4039
  fail(`Unknown provider: ${provider}`, "Run `ordewell key` to see the list.");
3977
4040
  }
3978
- const meta = (0, import_core24.getProviderMeta)(id);
4041
+ const meta = (0, import_core26.getProviderMeta)(id);
3979
4042
  const key = keyParts.join(" ").trim();
3980
4043
  if (!key) fail(`Usage: ordewell key set ${id} <key>`);
3981
4044
  await persistEnv(api, { [meta.apiKeyEnvVar]: key });
@@ -3983,7 +4046,7 @@ async function handleKey(subArgs, injectedApi) {
3983
4046
  }
3984
4047
 
3985
4048
  // src/commands/runners.ts
3986
- var import_core25 = require("@ordewell/core");
4049
+ var import_core27 = require("@ordewell/core");
3987
4050
  var USAGE7 = "Usage: ordewell runners [<runner-id> on|off]";
3988
4051
  async function handleRunners(subArgs, injectedApi) {
3989
4052
  const [runner, toggle] = positionals(subArgs);
@@ -3994,7 +4057,7 @@ async function handleRunners(subArgs, injectedApi) {
3994
4057
  for (const r of state2.runners) {
3995
4058
  console.log(` ${r.enabled ? "\u2713" : " "} ${r.id.padEnd(14)} ${r.name}${r.enabled ? "" : " (disabled)"}`);
3996
4059
  }
3997
- const notice = (0, import_core25.codexSandboxNotice)((await fetchCatalog(api).catch(() => null))?.modesByRunner);
4060
+ const notice = (0, import_core27.codexSandboxNotice)((await fetchCatalog(api).catch(() => null))?.modesByRunner);
3998
4061
  if (notice) console.log(`
3999
4062
  ${notice.line}
4000
4063
  ${notice.docs}`);
@@ -4015,17 +4078,17 @@ async function handleRunners(subArgs, injectedApi) {
4015
4078
  var AUTO_USAGE = "Usage: ordewell auto [full|guarded]";
4016
4079
  function handleAuto(subArgs) {
4017
4080
  const [arg] = positionals(subArgs);
4018
- const settings = new import_core25.SettingsService();
4081
+ const settings = new import_core27.SettingsService();
4019
4082
  const current = settings.autonomousMode();
4020
4083
  if (!arg) {
4021
- console.log(`Autonomy level: ${(0, import_core25.autonomyLevelLabel)(current)}`);
4084
+ console.log(`Autonomy level: ${(0, import_core27.autonomyLevelLabel)(current)}`);
4022
4085
  console.log(` ${AUTO_USAGE}`);
4023
4086
  return;
4024
4087
  }
4025
- const enabled = (0, import_core25.parseAutonomyLevel)(arg);
4088
+ const enabled = (0, import_core27.parseAutonomyLevel)(arg);
4026
4089
  if (enabled === null) fail(AUTO_USAGE);
4027
4090
  settings.setAutonomousMode(enabled);
4028
- console.log(settings.autonomousModeOverridden() ? `${(0, import_core25.autonomyLevelLabel)(enabled)} is saved, but ORDEWELL_AUTONOMOUS_MODE in the environment decides the level until it is unset.` : `Autonomy level: ${(0, import_core25.autonomyLevelLabel)(enabled)} for new plans.`);
4091
+ console.log(settings.autonomousModeOverridden() ? `${(0, import_core27.autonomyLevelLabel)(enabled)} is saved, but ORDEWELL_AUTONOMOUS_MODE in the environment decides the level until it is unset.` : `Autonomy level: ${(0, import_core27.autonomyLevelLabel)(enabled)} for new plans.`);
4029
4092
  }
4030
4093
  async function handleRefresh(subArgs, injectedApi) {
4031
4094
  const api = await connect(subArgs, injectedApi);
@@ -4050,12 +4113,12 @@ async function handleApprove(subArgs, injectedApi) {
4050
4113
  console.error(`Failed to approve: ${err.message}`);
4051
4114
  process.exit(1);
4052
4115
  }
4053
- console.error("Plan approved \u2014 continuing execution.");
4116
+ console.error("Plan approved: continuing execution.");
4054
4117
  });
4055
4118
  }
4056
4119
 
4057
4120
  // src/commands/task-assign.ts
4058
- var import_core27 = require("@ordewell/core");
4121
+ var import_core29 = require("@ordewell/core");
4059
4122
  var import_plan_utils = require("@ordewell/core/plan-utils");
4060
4123
 
4061
4124
  // src/tui/taskAssignment.ts
@@ -4095,7 +4158,7 @@ function modesForTask(modesByRunner, task) {
4095
4158
  }
4096
4159
 
4097
4160
  // src/tui/state.ts
4098
- var import_core26 = require("@ordewell/core");
4161
+ var import_core28 = require("@ordewell/core");
4099
4162
 
4100
4163
  // src/tui/editor.ts
4101
4164
  function emptyEditor() {
@@ -4253,7 +4316,7 @@ function queuedTarget(tv) {
4253
4316
  for (let i = queued.length - 1; i >= 0; i--) if (!queued[i].forced) return i;
4254
4317
  return -1;
4255
4318
  }
4256
- var anyTaskRunning = (state) => state.tasks.some(import_core26.isTaskRunning);
4319
+ var anyTaskRunning = (state) => state.tasks.some(import_core28.isTaskRunning);
4257
4320
  var plannerInFlight = (state) => state.planner !== "idle";
4258
4321
  var planPaneOpen = (state) => state.focus === "plan" && !state.overlay && (!!state.taskEditor || !!state.expandedTaskId);
4259
4322
  function planRows(state) {
@@ -4299,10 +4362,10 @@ function findTask(tasks, id) {
4299
4362
  return void 0;
4300
4363
  }
4301
4364
  function refFor(state, task) {
4302
- return (0, import_core26.taskRefOf)(state.tasks, task.id) ?? (0, import_core26.taskRef)(task);
4365
+ return (0, import_core28.taskRefOf)(state.tasks, task.id) ?? (0, import_core28.taskRef)(task);
4303
4366
  }
4304
4367
  function titledRefFor(state, task) {
4305
- return (0, import_core26.titledTaskRefOf)(state.tasks, task.id) ?? (0, import_core26.titledTaskRef)(task);
4368
+ return (0, import_core28.titledTaskRefOf)(state.tasks, task.id) ?? (0, import_core28.titledTaskRef)(task);
4306
4369
  }
4307
4370
  function asRunTasks(tasks) {
4308
4371
  return tasks.map((task) => ({ ...task, subtasks: asRunTasks(task.subtasks ?? []) }));
@@ -4310,7 +4373,7 @@ function asRunTasks(tasks) {
4310
4373
  function seedRun(overrides) {
4311
4374
  if (overrides.run) return overrides.run;
4312
4375
  return {
4313
- ...import_core26.EMPTY_RUN,
4376
+ ...import_core28.EMPTY_RUN,
4314
4377
  tasks: asRunTasks(overrides.tasks ?? []),
4315
4378
  handoff: overrides.handoff ?? null,
4316
4379
  runMerge: overrides.runMerge ? { waitingAtGate: overrides.runMerge.waitingAtGate, paused: overrides.runMerge.paused, repos: overrides.runMerge.handoff.repos, landed: overrides.runMerge.handoff.landed } : null
@@ -4319,9 +4382,9 @@ function seedRun(overrides) {
4319
4382
  function initialState(overrides = {}) {
4320
4383
  return {
4321
4384
  editor: emptyEditor(),
4322
- conversation: import_core26.EMPTY_CONVERSATION,
4385
+ conversation: import_core28.EMPTY_CONVERSATION,
4323
4386
  taskView: null,
4324
- turnGate: import_core26.NO_TURN,
4387
+ turnGate: import_core28.NO_TURN,
4325
4388
  detailAll: false,
4326
4389
  status: "idle",
4327
4390
  planner: "idle",
@@ -4347,11 +4410,15 @@ function initialState(overrides = {}) {
4347
4410
  plannerProvider: "",
4348
4411
  plannerEffort: "",
4349
4412
  plannerModels: {},
4350
- maxParallel: import_core26.DEFAULT_MAX_PARALLEL,
4413
+ maxParallel: import_core28.DEFAULT_MAX_PARALLEL,
4351
4414
  configuredProviders: [],
4352
4415
  allowlist: {},
4353
4416
  autonomous: true,
4354
- runSettings: { ...import_core26.RUN_SETTINGS_DEFAULTS },
4417
+ runSettings: { ...import_core28.RUN_SETTINGS_DEFAULTS },
4418
+ runSettingsKnown: false,
4419
+ plannerKnown: false,
4420
+ runnersKnown: false,
4421
+ providersKnown: false,
4355
4422
  runAutoMerge: null,
4356
4423
  runSupervisor: null,
4357
4424
  mouseCapture: true,
@@ -4361,7 +4428,7 @@ function initialState(overrides = {}) {
4361
4428
  runMerge: null,
4362
4429
  overlay: null,
4363
4430
  pendingApprovals: [],
4364
- queuedPrompts: import_core26.EMPTY_HOLD,
4431
+ queuedPrompts: import_core28.EMPTY_HOLD,
4365
4432
  stopArmed: false,
4366
4433
  stopArmToken: 0,
4367
4434
  stopRequested: false,
@@ -4411,7 +4478,7 @@ Runner \xB7 ${namedOf(tasks, task)}
4411
4478
  fail(`Unknown runner: ${runner}`, `This daemon knows: ${state.runners.map((r) => r.id).join(", ")}`);
4412
4479
  }
4413
4480
  await api.updateTask(sessionId, task.id, { assignedRunner: runner });
4414
- console.log(`Task ${refOf(tasks, task)} runner set to ${runner} \u2014 its model, effort and mode were re-picked for it.`);
4481
+ console.log(`Task ${refOf(tasks, task)} runner set to ${runner}: its model, effort and mode were re-picked for it.`);
4415
4482
  });
4416
4483
  }
4417
4484
  var MODEL_USAGE = "Usage: ordewell task-model <task-id-or-order> [<model-id>] [--session-id <id>]";
@@ -4423,11 +4490,11 @@ async function handleTaskModel(subArgs, injectedApi) {
4423
4490
  console.log(`
4424
4491
  Model \xB7 ${namedOf(tasks, task)}${task.assignedRunner ? ` (${task.assignedRunner})` : ""}
4425
4492
  `);
4426
- if (options.length === 0) console.log(" No models discovered for this runner \u2014 try `ordewell refresh`.");
4493
+ if (options.length === 0) console.log(" No models discovered for this runner: try `ordewell refresh`.");
4427
4494
  for (const m of options) {
4428
4495
  const detail = [m.provider, m.variants?.length ? `${m.variants.length} effort levels` : "runner default effort"].filter(Boolean).join(" \xB7 ");
4429
4496
  console.log(` ${m.id === task.assignedModel?.modelId ? "*" : " "} ${m.id}`);
4430
- console.log(` ${m.label} \u2014 ${detail}`);
4497
+ console.log(` ${m.label}: ${detail}`);
4431
4498
  }
4432
4499
  console.log(`
4433
4500
  ${MODEL_USAGE}`);
@@ -4493,9 +4560,9 @@ Mode \xB7 ${namedOf(tasks, task)}${task.assignedRunner ? ` (${task.assignedRunne
4493
4560
  console.log(" This task has no runner, or its runner declares no modes.");
4494
4561
  }
4495
4562
  for (const m of modes) {
4496
- console.log(` ${m.id === task.taskMode ? "*" : " "} ${m.id.padEnd(14)} ${m.label}${m.unavailable ? " \u2014 unavailable on this host" : m.description ? ` \u2014 ${m.description}` : ""}`);
4563
+ console.log(` ${m.id === task.taskMode ? "*" : " "} ${m.id.padEnd(14)} ${m.label}${m.unavailable ? ": unavailable on this host" : m.description ? `: ${m.description}` : ""}`);
4497
4564
  }
4498
- const notice = (0, import_core27.codexSandboxNotice)(catalog.modesByRunner);
4565
+ const notice = (0, import_core29.codexSandboxNotice)(catalog.modesByRunner);
4499
4566
  if (notice) console.log(`
4500
4567
  ${notice.line}
4501
4568
  ${notice.docs}`);
@@ -4544,7 +4611,7 @@ var SKILLS_USAGE = "Usage: ordewell task-skills <task-id-or-order> [<name> [<nam
4544
4611
  async function handleTaskSkills(subArgs, injectedApi) {
4545
4612
  await withTask(subArgs, SKILLS_USAGE, injectedApi, async (api, sessionId, task, tasks, value) => {
4546
4613
  if (task.type !== "ai") fail("Manual tasks do not run an executor, so they take no skills.");
4547
- const catalog = (0, import_core27.createSkillsService)(flag(subArgs, "--workspace") || process.cwd(), new import_core27.EnvConfig().workspaceRepos).listSkills().filter((s) => s.appliesTo === "task");
4614
+ const catalog = (0, import_core29.createSkillsService)(flag(subArgs, "--workspace") || process.cwd(), new import_core29.EnvConfig().workspaceRepos).listSkills().filter((s) => s.appliesTo === "task");
4548
4615
  if (!value) {
4549
4616
  console.log(`
4550
4617
  Skills \xB7 ${namedOf(tasks, task)}
@@ -4581,7 +4648,7 @@ ${namedOf(tasks, task)} is ${task.ops ? "an ops task: it runs in your checkout o
4581
4648
  }
4582
4649
  const to = value.toLowerCase();
4583
4650
  if (to !== "on" && to !== "off") fail(OPS_USAGE);
4584
- if (task.type !== "ai") fail("Only an AI task can be an ops task \u2014 a manual task already runs outside any worktree.");
4651
+ if (task.type !== "ai") fail("Only an AI task can be an ops task: a manual task already runs outside any worktree.");
4585
4652
  if (!tasks.some((t) => t.id === task.id)) fail("A subtask runs with its parent; make the parent an ops task instead.");
4586
4653
  await api.updateTask(sessionId, task.id, { ops: to === "on" });
4587
4654
  console.log(to === "on" ? `Task ${refOf(tasks, task)} is an ops task: it runs in your checkout once the work it depends on is merged.` : `Task ${refOf(tasks, task)} is a change task: it runs in its own worktree.`);
@@ -4589,10 +4656,10 @@ ${namedOf(tasks, task)} is ${task.ops ? "an ops task: it runs in your checkout o
4589
4656
  }
4590
4657
 
4591
4658
  // src/tui/index.ts
4592
- var import_core48 = require("@ordewell/core");
4659
+ var import_core51 = require("@ordewell/core");
4593
4660
 
4594
4661
  // src/tui/slash.ts
4595
- var import_core28 = require("@ordewell/core");
4662
+ var import_core30 = require("@ordewell/core");
4596
4663
  var SLASH_COMMANDS = [
4597
4664
  // Planning. There is deliberately no `/plan`: typing the goal *is* the way to
4598
4665
  // start planning, and an alias for it only shadowed `/planner` on completion.
@@ -4618,9 +4685,9 @@ var SLASH_COMMANDS = [
4618
4685
  { name: "task-mode", usage: "/task-mode <id> [mode]", description: "Choose or set a task's runner mode", category: "tasks" },
4619
4686
  { name: "task-deps", usage: "/task-deps <id>", description: "Edit which earlier tasks a task waits for", category: "tasks" },
4620
4687
  { name: "task-skills", usage: "/task-skills <id> [a,b|none]", description: "Choose or set the skills attached to a task; none clears them", category: "tasks" },
4621
- { name: "task-ops", usage: "/task-ops <id> [on|off]", description: "Make a task an ops task \u2014 run in your checkout once its dependencies are merged \u2014 or a change task", category: "tasks" },
4688
+ { name: "task-ops", usage: "/task-ops <id> [on|off]", description: "Choose between an ops task (runs in your checkout once its dependencies are merged) and a change task", category: "tasks" },
4622
4689
  // Models & providers
4623
- { name: "planner", usage: "/planner [<provider>]", description: "Pick the planner \u2014 harness, model and effort in one go", category: "models" },
4690
+ { name: "planner", usage: "/planner [<provider>]", description: "Pick the planner: harness, model and effort in one go", category: "models" },
4624
4691
  { name: "model", usage: "/model [set <id>]", description: "Show or set the planner model", category: "models" },
4625
4692
  { name: "planner-effort", usage: "/planner-effort [<level>]", description: "Thinking effort for a coding-agent planner", category: "models" },
4626
4693
  { name: "key", usage: "/key [set <provider> <key>]", description: "Configure API provider keys", category: "models" },
@@ -4711,7 +4778,7 @@ function parseRunnerCommand(text) {
4711
4778
  return match ? { name: match[1], args: (match[2] ?? "").trim() } : null;
4712
4779
  }
4713
4780
  function runnerCommandFor(commands, name) {
4714
- return (0, import_core28.findRunnerCommand)(commands, name) ?? (0, import_core28.findRunnerCommand)(commands, name.toLowerCase());
4781
+ return (0, import_core30.findRunnerCommand)(commands, name) ?? (0, import_core30.findRunnerCommand)(commands, name.toLowerCase());
4715
4782
  }
4716
4783
  function composerCompletions(token, runner) {
4717
4784
  if (runner) return token.start === 0 ? runner.filter((c) => c.name.toLowerCase().startsWith(token.name)).map((c) => c.name) : [];
@@ -4758,10 +4825,10 @@ function chatEditorRoomFor(state, active) {
4758
4825
  }
4759
4826
 
4760
4827
  // src/tui/layout.ts
4761
- var import_core31 = require("@ordewell/core");
4828
+ var import_core33 = require("@ordewell/core");
4762
4829
 
4763
4830
  // src/tui/reducers/pickers.ts
4764
- var import_core29 = require("@ordewell/core");
4831
+ var import_core31 = require("@ordewell/core");
4765
4832
  var import_plan_utils2 = require("@ordewell/core/plan-utils");
4766
4833
  var DEFAULT_EFFORT = "__runner_default__";
4767
4834
  function picker(title, items, action, extra = {}) {
@@ -4780,11 +4847,11 @@ function modelDetail(m) {
4780
4847
  function providerErrorHint(state) {
4781
4848
  const failed = Object.keys(state.providerErrors ?? {});
4782
4849
  if (failed.length === 0) return void 0;
4783
- const names = failed.map((p) => (0, import_core29.getProviderMeta)(p)?.label ?? p);
4784
- return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")} \u2014 showing working providers only.`;
4850
+ const names = failed.map((p) => (0, import_core31.getProviderMeta)(p)?.label ?? p);
4851
+ return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")}. Showing working providers only.`;
4785
4852
  }
4786
4853
  function plannerModelItems(state, provider, selectedId) {
4787
- const runner = (0, import_core29.runnerForProvider)(provider);
4854
+ const runner = (0, import_core31.runnerForProvider)(provider);
4788
4855
  if (!runner) {
4789
4856
  return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m), selected: m.id === selectedId }));
4790
4857
  }
@@ -4800,11 +4867,11 @@ function plannerModelItems(state, provider, selectedId) {
4800
4867
  return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
4801
4868
  }
4802
4869
  function plannerHarnessModel(state, provider, modelId) {
4803
- const runner = (0, import_core29.runnerForProvider)(provider);
4870
+ const runner = (0, import_core31.runnerForProvider)(provider);
4804
4871
  return runner ? state.models.find((m) => m.id === modelId && m.runners?.includes(runner)) : void 0;
4805
4872
  }
4806
4873
  function plannerEffortItems(state, provider, modelId, selectedEffort) {
4807
- if (!(0, import_core29.runnerForProvider)(provider)) {
4874
+ if (!(0, import_core31.runnerForProvider)(provider)) {
4808
4875
  return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
4809
4876
  }
4810
4877
  const model = plannerHarnessModel(state, provider, modelId);
@@ -4922,19 +4989,21 @@ function pickerItemsFor(state, action) {
4922
4989
  function plannerItems(state) {
4923
4990
  const installed = new Set(state.runners.map((r) => r.id));
4924
4991
  const usability = {};
4925
- for (const id of import_core29.CLI_PROVIDERS) {
4926
- const runner = (0, import_core29.runnerForProvider)(id);
4992
+ for (const id of import_core31.CLI_PROVIDERS) {
4993
+ const runner = (0, import_core31.runnerForProvider)(id);
4927
4994
  usability[runner] = installed.has(runner) ? { usable: true } : { usable: false, reason: "CLI not installed or not on PATH" };
4928
4995
  }
4929
- const entries = (0, import_core29.plannerBackendEntries)(usability, state.configuredProviders);
4930
- const items = entries.map((entry) => ({
4996
+ const offered2 = (0, import_core31.plannerBackendEntries)(usability, state.configuredProviders).filter((entry) => entry.usable);
4997
+ if (offered2.length === 0) {
4998
+ return [{ id: "", label: "No planner available", detail: "install a coding agent or add a key with /key", disabled: true }];
4999
+ }
5000
+ const items = offered2.map((entry) => ({
4931
5001
  id: entry.id,
4932
5002
  label: entry.label,
4933
5003
  detail: entry.reason,
4934
- selected: state.plannerProvider === entry.id,
4935
- disabled: !entry.usable
5004
+ selected: state.plannerProvider === entry.id
4936
5005
  }));
4937
- return entries.some((entry) => entry.kind === "vendor") ? items : [...items, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
5006
+ return offered2.some((entry) => entry.kind === "vendor") ? items : [...items, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
4938
5007
  }
4939
5008
 
4940
5009
  // src/version.ts
@@ -4951,14 +5020,14 @@ function cliVersion() {
4951
5020
  }
4952
5021
 
4953
5022
  // src/tui/planKeys.ts
4954
- var import_core30 = require("@ordewell/core");
5023
+ var import_core32 = require("@ordewell/core");
4955
5024
  var PLAN_KEYS = {
4956
5025
  move: { keys: "up / down", meaning: "move between tasks", group: "Navigation" },
4957
5026
  scroll: { keys: "pgup / pgdn", meaning: "scroll the plan", group: "Navigation" },
4958
5027
  expand: { keys: "\u2192", meaning: "reveal a task's subtasks, then edit its prompt", group: "Navigation", hint: "\u2192 expand" },
4959
5028
  log: {
4960
5029
  keys: "enter",
4961
- meaning: "open the task's log and message its runner \u2014 or, at a checkpoint, answer it",
5030
+ meaning: "open the task's log and message its runner or, at a checkpoint, answer it",
4962
5031
  group: "Navigation",
4963
5032
  hint: (task) => task?.status === "awaiting_user" && task.awaitingReason === "checkpoint" ? "enter answer" : "enter log"
4964
5033
  },
@@ -4978,7 +5047,7 @@ var PLAN_KEYS = {
4978
5047
  meaning: "cancel the task's runner, or keep a task paused on a usage limit from resuming on its own",
4979
5048
  group: "Run",
4980
5049
  // `c` cancels the resume while the task has one; it has no runner to cancel then.
4981
- hint: (task) => task && (0, import_core30.resumeLabel)(task) ? "c don't resume" : "c cancel"
5050
+ hint: (task) => task && (0, import_core32.resumeLabel)(task) ? "c don't resume" : "c cancel"
4982
5051
  },
4983
5052
  resume: { keys: "r", meaning: "resume a task paused on a usage limit now, once the limit has reset", group: "Run", hint: "r resume now" },
4984
5053
  resolve: { keys: "x", meaning: "resolve a merge conflict", group: "Run", hint: "x resolve conflict" },
@@ -4988,7 +5057,7 @@ var PLAN_KEYS = {
4988
5057
  meaning: "mark the task done, or take it back",
4989
5058
  group: "Status",
4990
5059
  // `m` toggles, so the hint names the direction it will go for this task.
4991
- hint: (task) => task && (0, import_core30.markAction)(task) === "uncomplete" ? "m undone" : "m done"
5060
+ hint: (task) => task && (0, import_core32.markAction)(task) === "uncomplete" ? "m undone" : "m done"
4992
5061
  },
4993
5062
  skip: { keys: "s", meaning: "skip the task: none of its work lands, and what depends on it goes ahead", group: "Status", hint: "s skip" },
4994
5063
  remove: { keys: "d", meaning: "remove the task", group: "Status" },
@@ -5015,7 +5084,7 @@ function hintOf(id, task) {
5015
5084
  }
5016
5085
  function taskKeyIds(task, runActive) {
5017
5086
  if (!task) return ["add"];
5018
- const row = (0, import_core30.taskRowView)(task);
5087
+ const row = (0, import_core32.taskRowView)(task);
5019
5088
  if (row.conflict) return ["resolve", "log"];
5020
5089
  if (row.actions.includes("resume-now") || row.resumes) {
5021
5090
  return [...row.actions.includes("resume-now") ? ["resume"] : [], ...row.resumes ? ["cancel"] : [], "skip", "log"];
@@ -5043,7 +5112,7 @@ function planFooterHints(task, runActive) {
5043
5112
  }
5044
5113
 
5045
5114
  // src/tui/layout.ts
5046
- var import_core32 = require("@ordewell/core");
5115
+ var import_core34 = require("@ordewell/core");
5047
5116
  var CHROME_ROWS = 1;
5048
5117
  function chatInputWrap(state) {
5049
5118
  const active = state.focus === "chat" && !state.overlay;
@@ -5069,7 +5138,7 @@ function footerHints(state) {
5069
5138
  }
5070
5139
  return [...escHint ? [escHint] : [], ...planFooterHints(selected, state.status === "executing")];
5071
5140
  }
5072
- const detailHint = (0, import_core32.hasHiddenDetail)(state.taskView ? state.taskView.view.blocks : state.conversation.blocks) ? [state.detailAll ? "ctrl-o collapse all" : "ctrl-o expand all"] : [];
5141
+ const detailHint = (0, import_core34.hasHiddenDetail)(state.taskView ? state.taskView.view.blocks : state.conversation.blocks) ? [state.detailAll ? "ctrl-o collapse all" : "ctrl-o expand all"] : [];
5073
5142
  return [
5074
5143
  "/help",
5075
5144
  ...checkpointHints,
@@ -5128,12 +5197,12 @@ function queuedTaskBubble(message, target, sends, cols) {
5128
5197
  }
5129
5198
  function taskActivity(task, working) {
5130
5199
  if (task?.status === "completed" || task?.status === "failed") return task.status;
5131
- const approvals = (0, import_core32.approvalLabel)(task?.awaitingApproval);
5200
+ const approvals = (0, import_core34.approvalLabel)(task?.awaitingApproval);
5132
5201
  if (approvals) return inRow(approvals);
5133
5202
  if (working) return "working";
5134
5203
  if (task?.status === "awaiting_user") {
5135
- const resumes = (0, import_core32.resumeLabel)(task);
5136
- return [inRow((0, import_core32.awaitingLabel)(task) ?? "Waiting for your input"), resumes ? inRow(resumes) : ""].filter(Boolean).join(" \xB7 ");
5204
+ const resumes = (0, import_core34.resumeLabel)(task);
5205
+ return [inRow((0, import_core34.awaitingLabel)(task) ?? "Waiting for your input"), resumes ? inRow(resumes) : ""].filter(Boolean).join(" \xB7 ");
5137
5206
  }
5138
5207
  return task?.status ?? "";
5139
5208
  }
@@ -5146,7 +5215,7 @@ function taskHeaderLines(state, tv, cols) {
5146
5215
  taskActivity(task, tv.view.working)
5147
5216
  ].filter(Boolean);
5148
5217
  const approval = waitingApproval(tv);
5149
- const hint = approval ? ["ctrl-y allow", ...approval.allowForTask ? ["ctrl-t allow for task"] : [], "ctrl-g deny (composer text is the note)"] : waitingCheckpoint(task) ? ["enter reply", "ctrl-y approve", "ctrl-g reject", "ctrl-x stop runner"] : task && (0, import_core32.resumeLabel)(task) ? ["ctrl-x don't resume"] : [...runnerStoppable(state) ? ["ctrl-x stop runner"] : [], "ctrl-r remove queued", "ctrl-s send now"];
5218
+ const hint = approval ? ["ctrl-y allow", ...approval.allowForTask ? ["ctrl-t allow for task"] : [], "ctrl-g deny (composer text is the note)"] : waitingCheckpoint(task) ? ["enter reply", "ctrl-y approve", "ctrl-g reject", "ctrl-x stop runner"] : task && (0, import_core34.resumeLabel)(task) ? ["ctrl-x don't resume"] : [...runnerStoppable(state) ? ["ctrl-x stop runner"] : [], "ctrl-r remove queued", "ctrl-s send now"];
5150
5219
  if (approval && waitingCheckpoint(task)) hint.push("then the checkpoint");
5151
5220
  const index = tv.attempts.indexOf(tv.attempt);
5152
5221
  if (tv.attempts.length > 1) hint.push(`alt\u2190/\u2192 attempt ${index >= 0 ? index + 1 : 1}/${tv.attempts.length}`);
@@ -5176,15 +5245,15 @@ function chatActivity(state) {
5176
5245
  const own = findTask(state.tasks, tv.taskId);
5177
5246
  const task = state.tasks.find((t) => t.subtasks?.some((s) => s.id === tv.taskId)) ?? own;
5178
5247
  if (!task) return null;
5179
- if ((0, import_core26.isTaskRunning)(task) || own && (0, import_core26.isTaskRunning)(own)) return { label: "Working\u2026", busy: true };
5248
+ if ((0, import_core28.isTaskRunning)(task) || own && (0, import_core28.isTaskRunning)(own)) return { label: "Working\u2026", busy: true };
5180
5249
  const at = task.status === "awaiting_user" && task.awaitingReason === "usage-limit" && task.resumeAt ? new Date(task.resumeAt) : null;
5181
- if (at && !Number.isNaN(at.getTime())) return { label: `paused \xB7 resumes ${(0, import_core32.resumeTime)(at)}`, busy: false };
5250
+ if (at && !Number.isNaN(at.getTime())) return { label: `paused \xB7 resumes ${(0, import_core34.resumeTime)(at)}`, busy: false };
5182
5251
  return null;
5183
5252
  }
5184
5253
  function runHints(state) {
5185
5254
  const waiting = state.tasks.filter((t) => t.status === "awaiting_user").length;
5186
5255
  if (waiting > 0) return [waiting === 1 ? "1 task waits for you" : `${waiting} tasks wait for you`, `${waiting} waiting`];
5187
- return state.runMerge?.paused ? ["paused for Merge all \u2014 /handoff merge", "paused \xB7 /handoff merge", "merge paused"] : [];
5256
+ return state.runMerge?.paused ? ["paused for Merge all: /handoff merge", "paused \xB7 /handoff merge", "merge paused"] : [];
5188
5257
  }
5189
5258
  var runHint = (state) => runHints(state)[0] ?? "";
5190
5259
  function packHints(hints, cols) {
@@ -5240,7 +5309,7 @@ function taskUsageFooter(state, tv, blocks2, cols) {
5240
5309
  const reported = task?.attemptUsage ?? [];
5241
5310
  const onScreen = tv.attempt > 0 ? reported.find((a) => a.attempt === tv.attempt) : reported.at(-1);
5242
5311
  if (!task || !onScreen) return tokenRow(blocks2, cols);
5243
- const usage = (0, import_core32.taskRowView)({ ...task, attemptUsage: [onScreen] }).usage;
5312
+ const usage = (0, import_core34.taskRowView)({ ...task, attemptUsage: [onScreen] }).usage;
5244
5313
  if (!usage) return tokenRow(blocks2, cols);
5245
5314
  const models = usage.byModel ?? [];
5246
5315
  return [
@@ -5253,17 +5322,17 @@ function chatScrollMax(state) {
5253
5322
  }
5254
5323
  function plannerSetup(state) {
5255
5324
  const provider = state.plannerProvider;
5256
- const registration = (0, import_core32.getProviderMeta)(provider);
5325
+ const registration = (0, import_core34.getProviderMeta)(provider);
5257
5326
  if (!registration) return { label: "not set", ready: false };
5258
- const runner = (0, import_core32.runnerForProvider)(provider);
5259
- const ready = runner ? state.runners.some((r) => r.id === runner) : (0, import_core32.isApiProvider)(provider) && state.configuredProviders.includes(provider);
5327
+ const runner = (0, import_core34.runnerForProvider)(provider);
5328
+ const ready = runner ? state.runners.some((r) => r.id === runner) : (0, import_core34.isApiProvider)(provider) && state.configuredProviders.includes(provider);
5260
5329
  return { label: registration.label, ready };
5261
5330
  }
5262
5331
  var SETUP_KEY_COLS = 10;
5263
5332
  var SETUP_VALUE_COLS = 32;
5264
5333
  function runSettingsValue(state) {
5265
5334
  const { supervisorLevel, mergeAtGatesAutomatically, autorun } = state.runSettings;
5266
- const level = `supervisor ${import_core31.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}`;
5335
+ const level = `supervisor ${import_core33.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}`;
5267
5336
  return [
5268
5337
  supervisorLevel === "act" ? style.yellow(level) : level,
5269
5338
  `auto-merge ${mergeAtGatesAutomatically ? "on" : "off"}`,
@@ -5343,18 +5412,22 @@ var twoLines = ({ orde, well }) => [...orde, ...well.map((row) => style.brand(ro
5343
5412
  function welcomeLogo(restRows, rows, cols) {
5344
5413
  return [bannerLines(), oneLine2(BANNER_WORDMARK), twoLines(BANNER_WORDMARK)].find((lines) => lines.every((line) => width(line) <= cols - 1) && lines.length + restRows <= rows) ?? [];
5345
5414
  }
5415
+ var DETECTING = "Detecting coding agents\u2026";
5346
5416
  function welcomeLines(state, rows, cols) {
5347
5417
  const planner = plannerSetup(state);
5348
5418
  const enabledRunners = state.runners.filter((r) => r.enabled).map((r) => r.name);
5349
5419
  const canPlan = planner.ready || state.runners.length > 0 || state.configuredProviders.length > 0;
5350
- const plannerValue = planner.ready ? [planner.label, state.orchestratorModel || style.yellow("no model"), plannerEffortLabel(state)].filter(Boolean).join(style.grey(" \xB7 ")) : style.yellow(planner.label === "not set" ? "not set" : `${planner.label} (unavailable)`);
5420
+ const detecting = !state.plannerKnown || !state.runnersKnown;
5421
+ const plannerValue = detecting ? style.grey(DETECTING) : planner.ready ? [planner.label, state.orchestratorModel || style.yellow("no model"), plannerEffortLabel(state)].filter(Boolean).join(style.grey(" \xB7 ")) : style.yellow(planner.label === "not set" ? "not set" : `${planner.label} (unavailable)`);
5351
5422
  const prompt = wrap(style.bold("Describe a goal to start planning."), Math.max(1, cols - 1));
5423
+ const approvalText = !state.runSettingsKnown ? "" : state.runSettings.autorun ? style.yellow("Autorun is on. A plan starts as soon as the planner submits it. Change it in /settings.") : style.grey("Nothing runs until you approve the plan.");
5424
+ const approval = approvalText ? wrap(approvalText, Math.max(1, cols - 1)) : [];
5352
5425
  const setup = [
5353
5426
  style.bold("Setup"),
5354
5427
  setupRow("Planner", plannerValue, "/planner", cols),
5355
5428
  setupRow(
5356
5429
  "Runners",
5357
- enabledRunners.length > 0 ? enabledRunners.join(", ") : style.yellow("none enabled"),
5430
+ detecting ? style.grey(DETECTING) : enabledRunners.length > 0 ? enabledRunners.join(", ") : style.yellow("none enabled"),
5358
5431
  "/runners",
5359
5432
  cols
5360
5433
  ),
@@ -5362,15 +5435,15 @@ function welcomeLines(state, rows, cols) {
5362
5435
  setupRow("Run", runSettingsValue(state), "/settings", cols),
5363
5436
  ""
5364
5437
  ];
5365
- const notice = canPlan ? [] : [
5438
+ const notice = canPlan || detecting || !state.providersKnown ? [] : [
5366
5439
  ...wrap(
5367
- style.yellow(`Ordewell needs one coding agent installed (${runnerNamesOr()}) \u2014 or an API key from a provider such as OpenRouter, added with /key.`),
5440
+ style.yellow(`Install a coding agent (${runnerNamesOr()}) and pick it with /planner. No API key needed. Or add an API key with /key and choose your provider.`),
5368
5441
  Math.max(1, cols - 1)
5369
5442
  ),
5370
5443
  ""
5371
5444
  ];
5372
5445
  const tail = [...notice, style.grey("/help all commands"), workspaceLine(state.workspace, cols)];
5373
- const rest = ["", ...prompt, "", ...setup, ...tail];
5446
+ const rest = ["", ...prompt, ...approval, "", ...setup, ...tail];
5374
5447
  const logo = welcomeLogo(rest.length, rows, cols);
5375
5448
  return logo.length > 0 ? [...logo, ...rest] : rest.slice(1);
5376
5449
  }
@@ -5398,7 +5471,7 @@ var STATUS_ICON = {
5398
5471
  var inRow = (label) => label.charAt(0).toLowerCase() + label.slice(1);
5399
5472
  function planLayout(state, rows, cols) {
5400
5473
  const done = state.tasks.filter((t) => t.status === "completed").length;
5401
- const totalUsage = (0, import_core32.planUsage)(state.tasks);
5474
+ const totalUsage = (0, import_core34.planUsage)(state.tasks);
5402
5475
  const unmerged = state.runMerge?.handoff.landed.length ?? 0;
5403
5476
  const count = `${done}/${state.tasks.length}`;
5404
5477
  const hints = runHints(state);
@@ -5408,7 +5481,7 @@ function planLayout(state, rows, cols) {
5408
5481
  totalUsage.attempts ? style.grey(truncate(`Tasks total: ${taskUsageText(totalUsage)}`, cols)) : ""
5409
5482
  ];
5410
5483
  if (unmerged > 0) {
5411
- const landed = style.dim(truncate(`${unmerged} landed task${unmerged === 1 ? "" : "s"} not merged \u2014 /handoff merge`, cols));
5484
+ const landed = style.dim(truncate(`${unmerged} landed task${unmerged === 1 ? "" : "s"} not merged: /handoff merge`, cols));
5412
5485
  if (totalUsage.attempts) lines.push(landed);
5413
5486
  else lines[1] = landed;
5414
5487
  }
@@ -5425,10 +5498,21 @@ function planLayout(state, rows, cols) {
5425
5498
  lines.push(...renderedTask.lines);
5426
5499
  rowSpans.push({ start: taskStart, end: lines.length - 1 });
5427
5500
  });
5501
+ lines.push(...heldEditLines(state, cols));
5428
5502
  const selected = rowSpans[state.selectedTask] ?? { start: firstRowLine, end: firstRowLine };
5429
5503
  const anchor = editorLine === void 0 ? selected : { start: editorLine, end: editorLine };
5430
5504
  return { lines, rowSpans, anchor, rows, maxScroll: Math.max(0, lines.length - rows) };
5431
5505
  }
5506
+ var heldMark = (mark) => mark === "added" ? style.green("+") : mark === "changed" ? style.yellow("~") : style.red("-");
5507
+ function heldEditLines(state, cols) {
5508
+ const rows = (state.run.heldEdit?.tasks ?? []).flatMap(({ label, mark, task }) => {
5509
+ if (!mark) return [];
5510
+ const title = mark === "removed" ? style.strike(task.title) : task.title;
5511
+ return [truncate(` ${heldMark(mark)} ${style.grey(label.padStart(2))} ${title}`, cols)];
5512
+ });
5513
+ if (rows.length === 0) return [];
5514
+ return ["", style.dim(truncate("Proposed: lands when this reply ends", cols)), ...rows];
5515
+ }
5432
5516
  var clampOffset = (layout, offset) => Math.max(0, Math.min(layout.maxScroll, offset));
5433
5517
  function revealOffset(layout, offset, span) {
5434
5518
  const at = clampOffset(layout, offset);
@@ -5476,7 +5560,7 @@ function planScrollExtent(state) {
5476
5560
  }
5477
5561
  function taskLines(state, planRow, index, cols) {
5478
5562
  const { task, parent } = planRow;
5479
- const row = (0, import_core32.taskRowView)(task, { parent, tasks: state.tasks, modes: modesForTask(state.modesByRunner, task) });
5563
+ const row = (0, import_core34.taskRowView)(task, { parent, tasks: state.tasks, modes: modesForTask(state.modesByRunner, task) });
5480
5564
  const selected = state.focus === "plan" && index === state.selectedTask;
5481
5565
  const expanded = state.expandedTaskId === task.id;
5482
5566
  const rawIcon = row.status === "running" ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[row.status];
@@ -5504,10 +5588,9 @@ function taskLines(state, planRow, index, cols) {
5504
5588
  const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${row.autonomous ? style.yellow(" \u26A1") : ""}` : "";
5505
5589
  const runner = task.assignedRunner ?? "";
5506
5590
  const bodyPad = parent ? " " : " ";
5507
- const activity = row.approvals ? `${inRow(row.approvals)} \u2014 enter opens it` : row.awaiting ? inRow(row.awaiting) : "";
5591
+ const activity = row.approvals ? `${inRow(row.approvals)}: enter opens it` : row.awaiting ? inRow(row.awaiting) : "";
5508
5592
  const skills = (task.skills ?? []).join(" \xB7 ");
5509
- const origin = task.addedBy === "supervisor" ? "added by supervisor" : "";
5510
- const meta = [activity, origin, runner, model];
5593
+ const meta = [activity, runner, model];
5511
5594
  const variant = task.type === "ai" ? sanitize(task.assignedModel?.thinkingEffort ?? "") : "";
5512
5595
  const withVariant = variant ? [...meta, variant] : meta;
5513
5596
  const metaLine = variant && width(`${continuation}${withVariant.filter(Boolean).join(" \xB7 ")}`) <= cols ? withVariant : meta;
@@ -5516,7 +5599,7 @@ function taskLines(state, planRow, index, cols) {
5516
5599
  if (row.conflict) {
5517
5600
  const where = row.conflict.repo ? ` in ${row.conflict.repo}` : "";
5518
5601
  const files = row.conflict.files ? ` (${row.conflict.files})` : "";
5519
- lines.push(style.red(truncate(`${bodyPad}\u26A0 merge conflict${where}${files} \u2014 its work is kept on its own branch`, cols)));
5602
+ lines.push(style.red(truncate(`${bodyPad}\u26A0 merge conflict${where}${files}: its work is kept on its own branch`, cols)));
5520
5603
  }
5521
5604
  if (row.repairing) {
5522
5605
  const files = row.repairing.files ? ` in ${row.repairing.files}` : "";
@@ -5524,22 +5607,25 @@ function taskLines(state, planRow, index, cols) {
5524
5607
  lines.push(style.yellow(truncate(`${bodyPad}\u21BB repairing conflict${files}${attempt}`, cols)));
5525
5608
  }
5526
5609
  if (row.mergeGate) {
5527
- lines.push(style.yellow(truncate(`${bodyPad}\u23F8 waits for Merge all \u2014 ${row.mergeGate.join(", ")} not merged into your branch yet`, cols)));
5610
+ lines.push(style.yellow(truncate(`${bodyPad}\u23F8 waits for Merge all: ${row.mergeGate.join(", ")} not merged into your branch yet`, cols)));
5611
+ }
5612
+ if (row.waitsForApproval) {
5613
+ lines.push(style.yellow(truncate(`${bodyPad}\u23F8 ${inRow(row.waitsForApproval)}: added after you approved the plan; f starts it, or /run runs the plan`, cols)));
5528
5614
  }
5529
5615
  if (row.blockedBy) {
5530
- lines.push(style.red(truncate(`${bodyPad}\u2298 ${inRow((0, import_core32.blockedByLabel)(row.blockedBy))} \u2014 runs once ${row.blockedBy.length === 1 ? "it is retried and passes" : "they are retried and pass"}`, cols)));
5616
+ lines.push(style.red(truncate(`${bodyPad}\u2298 ${inRow((0, import_core34.blockedByLabel)(row.blockedBy))}: runs once ${row.blockedBy.length === 1 ? "it is retried and passes" : "they are retried and pass"}`, cols)));
5531
5617
  }
5532
5618
  if (row.status === "skipped") {
5533
- lines.push(style.grey(truncate(`${bodyPad}\xBB ${inRow((0, import_core32.skippedLabel)(row.skipReason))} \u2014 nothing of it landed; m done lands its kept work`, cols)));
5619
+ lines.push(style.grey(truncate(`${bodyPad}\xBB ${inRow((0, import_core34.skippedLabel)(row.skipReason))}: nothing of it landed; m done lands its kept work`, cols)));
5534
5620
  }
5535
5621
  const resumable = row.actions.includes("resume-now");
5536
5622
  if (row.resumes) {
5537
- lines.push(style.yellow(truncate(`${bodyPad}\u23F8 ${inRow(row.resumes)}, once the usage limit resets \u2014 ${resumable ? "r resume now \xB7 " : ""}c don't resume`, cols)));
5623
+ lines.push(style.yellow(truncate(`${bodyPad}\u23F8 ${inRow(row.resumes)}, once the usage limit resets: ${resumable ? "r resume now \xB7 " : ""}c don't resume`, cols)));
5538
5624
  } else if (resumable) {
5539
- lines.push(style.yellow(truncate(`${bodyPad}\u23F8 waits for the usage limit to reset \u2014 r resume now`, cols)));
5625
+ lines.push(style.yellow(truncate(`${bodyPad}\u23F8 waits for the usage limit to reset: r resume now`, cols)));
5540
5626
  }
5541
5627
  if (row.opsChangedFiles) {
5542
- lines.push(style.yellow(truncate(`${bodyPad}\u26A0 an ops task changed tracked files \u2014 check them, then m done or /retry`, cols)));
5628
+ lines.push(style.yellow(truncate(`${bodyPad}\u26A0 an ops task changed tracked files: check them, then m done or /retry`, cols)));
5543
5629
  }
5544
5630
  if (row.repaired?.landed) {
5545
5631
  lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${row.repaired.files}`, cols)));
@@ -5639,14 +5725,14 @@ function commandSheetBody(cols) {
5639
5725
  body.push(
5640
5726
  style.grey("tab switches panes \xB7 pgup/pgdn scroll \xB7 ctrl-o toggles full detail \xB7 esc takes back a queued prompt, otherwise esc twice stops \xB7 ctrl-l clears \xB7 ctrl-c quits"),
5641
5727
  style.grey("alt-enter inserts a newline \xB7 ? lists the plan pane's keys, once tab has put you there"),
5642
- style.grey(" in the planner chat, ctrl-s sends now: stops the running planner turn and sends the composer text next \u2014 or, with it empty, the newest queued prompt")
5728
+ style.grey(" in the planner chat, ctrl-s sends now: stops the running planner turn and sends the composer text next: or, with it empty, the newest queued prompt")
5643
5729
  );
5644
5730
  body.push(
5645
5731
  "",
5646
5732
  style.bold("In a task view (enter on a task)"),
5647
- style.grey(" ctrl-s sends now: interrupts the running step, then delivers the composer text \u2014 or, with it empty, the highlighted pending message"),
5733
+ style.grey(" ctrl-s sends now: interrupts the running step, then delivers the composer text or, with it empty, the highlighted pending message"),
5648
5734
  style.grey(" ctrl-n/ctrl-p pick a pending message (the newest until you do) \xB7 ctrl-r removes a queued one"),
5649
- style.grey(" ctrl-x stops the runner's running turn, and it then waits for your next message \u2014 on a task paused on a usage limit, it keeps it from resuming"),
5735
+ style.grey(" ctrl-x stops the runner's running turn, and it then waits for your next message: on a task paused on a usage limit, it keeps it from resuming"),
5650
5736
  style.grey(" ctrl-y allows a tool request (its keys sit under it) \xB7 ctrl-t allows it for the task \xB7 ctrl-g denies it, with the composer text as the note"),
5651
5737
  style.grey(" at a checkpoint, enter replies with what you typed, ctrl-y approves, ctrl-g rejects (the text is its reason) \xB7 /checkpoint <id> approve|reject [reason] from anywhere"),
5652
5738
  style.grey(" alt\u2190/\u2192 changes attempt \xB7 esc returns")
@@ -5665,8 +5751,8 @@ function helpScrollMax(state) {
5665
5751
  }
5666
5752
 
5667
5753
  // src/tui/render.ts
5668
- var import_core33 = require("@ordewell/core");
5669
- var import_core34 = require("@ordewell/core");
5754
+ var import_core35 = require("@ordewell/core");
5755
+ var import_core36 = require("@ordewell/core");
5670
5756
  function render(state) {
5671
5757
  const { rows, cols } = state;
5672
5758
  const rowsForBody = bodyRows(state);
@@ -5765,8 +5851,8 @@ function runSettingsBadge(state) {
5765
5851
  const { supervisorLevel, mergeAtGatesAutomatically, autorun } = state.runSettings;
5766
5852
  const paint = (text, warn) => warn ? style.yellow(text) : style.grey(text);
5767
5853
  const run = state.status === "executing" ? state.runSupervisor : null;
5768
- const actual = run?.unsupervised ? "unsupervised" : run && run.level !== supervisorLevel ? import_core34.SUPERVISOR_LEVEL_LABEL[run.level] : null;
5769
- const level = `supervisor ${import_core34.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}${actual ? ` \u2192 ${actual}` : ""}`;
5854
+ const actual = run?.unsupervised ? "unsupervised" : run && run.level !== supervisorLevel ? import_core36.SUPERVISOR_LEVEL_LABEL[run.level] : null;
5855
+ const level = `supervisor ${import_core36.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}${actual ? ` \u2192 ${actual}` : ""}`;
5770
5856
  return [
5771
5857
  paint(level, supervisorLevel === "act"),
5772
5858
  ...mergeAtGatesAutomatically ? [style.grey("auto-merge")] : [],
@@ -5960,16 +6046,16 @@ function renderHandoff(state, overlay, rows, cols) {
5960
6046
  const { lines, scroll } = overlay.diff;
5961
6047
  const shown = lines.slice(scroll, scroll + room).map((line) => paintDiffLine(truncate(line, Math.max(1, cols - 2))));
5962
6048
  const more = scroll + room < lines.length ? "\u2191\u2193 pgup/pgdn scroll \xB7 " : "";
5963
- return frame(`Diff \u2014 ${handoffBranch(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
6049
+ return frame(`Diff: ${handoffBranch(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
5964
6050
  }
5965
6051
  const landed = handoff.landed.length === 0 ? [style.grey("Nothing landed on it.")] : handoff.landed.map((task) => {
5966
- const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0, import_core33.capConflictFiles)(task.repairedFiles)})`) : "";
5967
- return ` ${style.green("\u2713")} ${(0, import_core33.titledTaskRefOf)(state.tasks, task.taskId) ?? (0, import_core33.titledTaskRef)(task)}${repaired}`;
6052
+ const repaired = task.repairedFiles?.length ? style.yellow(`: repaired (${(0, import_core35.capConflictFiles)(task.repairedFiles)})`) : "";
6053
+ return ` ${style.green("\u2713")} ${(0, import_core35.titledTaskRefOf)(state.tasks, task.taskId) ?? (0, import_core35.titledTaskRef)(task)}${repaired}`;
5968
6054
  });
5969
6055
  const actions = handoffActions(handoff).map((action, index) => {
5970
6056
  const active = index === overlay.index;
5971
6057
  const caret = active ? style.brand("\u276F") : " ";
5972
- return `${caret} ${active ? style.bold(action.label) : action.label}${style.grey(` \u2014 ${action.hint}`)}`;
6058
+ return `${caret} ${active ? style.bold(action.label) : action.label}${style.grey(`: ${action.hint}`)}`;
5973
6059
  });
5974
6060
  const perRepo = isRepoGroup(handoff) ? ["Per repository:", ...handoff.repos.map((repo, i) => ` ${repo.landed.length > 0 ? style.green("\u2713") : style.grey("\xB7")} ${repoResultLines(handoff)[i]}`), ""] : [];
5975
6061
  return frame(
@@ -6138,10 +6224,13 @@ function frame(title, body, rows, cols) {
6138
6224
  }
6139
6225
 
6140
6226
  // src/tui/reducer.ts
6141
- var import_core46 = require("@ordewell/core");
6227
+ var import_core49 = require("@ordewell/core");
6142
6228
 
6143
6229
  // src/tui/reducers/overlays.ts
6144
- var import_core45 = require("@ordewell/core");
6230
+ var import_core48 = require("@ordewell/core");
6231
+
6232
+ // src/tui/reducers/approvals.ts
6233
+ var import_core37 = require("@ordewell/core");
6145
6234
 
6146
6235
  // src/tui/reducers/shared.ts
6147
6236
  function step(state, effects = []) {
@@ -6157,7 +6246,7 @@ function clampSelection(index, rows) {
6157
6246
  }
6158
6247
  function withSession(state, run) {
6159
6248
  if (!state.sessionId) {
6160
- return fail3(state, "No active plan \u2014 describe a goal first, or load a session with /sessions.");
6249
+ return fail3(state, "No active plan: describe a goal first, or load a session with /sessions.");
6161
6250
  }
6162
6251
  return run(state.sessionId);
6163
6252
  }
@@ -6186,6 +6275,13 @@ function dropApproval(state, approvalId) {
6186
6275
  if (open?.id !== approvalId) return { ...state, pendingApprovals: pending };
6187
6276
  return showNextApproval({ ...state, pendingApprovals: pending, overlay: null });
6188
6277
  }
6278
+ function syncApprovals(state, waiting) {
6279
+ const ids = new Set(waiting.map((request) => request.id));
6280
+ const open = state.overlay?.kind === "approval" ? [state.overlay.request] : [];
6281
+ const answered = [...open, ...state.pendingApprovals].filter((request) => !ids.has(request.id));
6282
+ const dropped = answered.reduce((current, request) => dropApproval(current, request.id), { ...state, conversation: (0, import_core37.reconcileApprovals)(state.conversation, waiting) });
6283
+ return waiting.reduce((current, { id, kind, subject, scope, detail }) => kind === "runner_tool" ? current : enqueueApproval(current, { id, kind, subject, scope, ...detail ? { detail } : {} }), dropped);
6284
+ }
6189
6285
  function showNextApproval(state) {
6190
6286
  const [next, ...rest] = state.pendingApprovals;
6191
6287
  if (!next) return { ...state, overlay: null };
@@ -6200,10 +6296,10 @@ function handleApprovalKey(state, overlay, key) {
6200
6296
  }
6201
6297
 
6202
6298
  // src/tui/reducers/commands.ts
6203
- var import_core44 = require("@ordewell/core");
6299
+ var import_core47 = require("@ordewell/core");
6204
6300
 
6205
6301
  // src/tui/reducers/plannerStepper.ts
6206
- var import_core35 = require("@ordewell/core");
6302
+ var import_core38 = require("@ordewell/core");
6207
6303
  var offered = (items) => new Set(items.filter((i) => !i.disabled).map((i) => i.id));
6208
6304
  function preferredModel(state, provider) {
6209
6305
  const listed = offered(plannerModelItems(state, provider, ""));
@@ -6222,7 +6318,7 @@ var effortLabel = (label) => /\beffort$/i.test(label) ? label : `${label} effort
6222
6318
  var plannerStepper = {
6223
6319
  title: "Planner",
6224
6320
  advanceOnEnter: true,
6225
- hint: "Who researches your goal and writes the plan. Coding agents use their own subscription \u2014 no API key.",
6321
+ hint: "Who researches your goal and writes the plan. Coding agents use their own subscription. No API key.",
6226
6322
  steps: [
6227
6323
  {
6228
6324
  id: "harness",
@@ -6251,7 +6347,7 @@ var plannerStepper = {
6251
6347
  const model = plannerModelItems(state, provider, "").find((m) => m.id === modelId);
6252
6348
  const level = plannerEffortItems(state, provider, modelId, "").find((e) => e.id === effort);
6253
6349
  const parts = [
6254
- (0, import_core35.getProviderMeta)(provider)?.label ?? provider,
6350
+ (0, import_core38.getProviderMeta)(provider)?.label ?? provider,
6255
6351
  model?.label ?? modelId,
6256
6352
  ...effort ? [effortLabel(level?.label ?? effort)] : []
6257
6353
  ];
@@ -6266,7 +6362,7 @@ var plannerStepper = {
6266
6362
  };
6267
6363
 
6268
6364
  // src/tui/reducers/stepper.ts
6269
- var import_core36 = require("@ordewell/core");
6365
+ var import_core39 = require("@ordewell/core");
6270
6366
  function openStepper(state, definition) {
6271
6367
  const { choices, lists } = settle(state, definition, {});
6272
6368
  const first = lists.findIndex((list2) => list2 !== null);
@@ -6289,7 +6385,7 @@ function handleStepperKey(state, stepper, key) {
6289
6385
  const reopen = (next) => step({ ...state, overlay: { kind: "stepper", stepper: next } });
6290
6386
  const { definition, current, entry, choices } = stepper;
6291
6387
  const def = definition.steps[current];
6292
- const typed = def.number && entry ? (0, import_core36.parseCount)(entry.text, def.number.min) : null;
6388
+ const typed = def.number && entry ? (0, import_core39.parseCount)(entry.text, def.number.min) : null;
6293
6389
  const { lists } = settle(state, definition, choices);
6294
6390
  const items = matchingItems(stepper.items, stepper.filter);
6295
6391
  const before = lastIndexBefore(lists, current);
@@ -6326,7 +6422,7 @@ function handleStepperKey(state, stepper, key) {
6326
6422
  function confirm(state, stepper, item) {
6327
6423
  if (!item) return step(state);
6328
6424
  if (item.disabled) {
6329
- return step(say(state, "error", item.detail ? `${item.label} \u2014 ${item.detail}` : `${item.label} is not available.`));
6425
+ return step(say(state, "error", item.detail ? `${item.label}: ${item.detail}` : `${item.label} is not available.`));
6330
6426
  }
6331
6427
  return choose(state, stepper, item.id);
6332
6428
  }
@@ -6355,7 +6451,7 @@ function settle(state, definition, wanted) {
6355
6451
  const lists = definition.steps.map((def) => {
6356
6452
  if (def.skip?.(state, { ...choices })) return null;
6357
6453
  if (def.number) {
6358
- choices[def.id] = String((0, import_core36.parseCount)(wanted[def.id], def.number.min) ?? def.number.current(state, { ...choices }));
6454
+ choices[def.id] = String((0, import_core39.parseCount)(wanted[def.id], def.number.min) ?? def.number.current(state, { ...choices }));
6359
6455
  return [];
6360
6456
  }
6361
6457
  const items = def.items(state, { ...choices });
@@ -6394,7 +6490,7 @@ function lastIndexBefore(lists, index) {
6394
6490
  }
6395
6491
 
6396
6492
  // src/tui/reducers/planPane.ts
6397
- var import_core39 = require("@ordewell/core");
6493
+ var import_core42 = require("@ordewell/core");
6398
6494
 
6399
6495
  // src/tui/reducers/pointer.ts
6400
6496
  var WHEEL_NOTCH = 3;
@@ -6472,13 +6568,13 @@ function handleMouseSelect(state, key) {
6472
6568
  }
6473
6569
 
6474
6570
  // src/tui/reducers/taskView.ts
6475
- var import_core37 = require("@ordewell/core");
6571
+ var import_core40 = require("@ordewell/core");
6476
6572
  function openTaskView(state, sessionId, taskId) {
6477
6573
  const task = findTask(state.tasks, taskId);
6478
6574
  if (!task) return step(say(state, "system", `No task ${taskId} in this plan.`));
6479
6575
  const taskView = {
6480
6576
  taskId,
6481
- view: import_core37.EMPTY_TASK_LOG,
6577
+ view: import_core40.EMPTY_TASK_LOG,
6482
6578
  attempts: [],
6483
6579
  attempt: 0,
6484
6580
  pending: [],
@@ -6508,13 +6604,13 @@ function applyLive(tv, attempt, events) {
6508
6604
  }
6509
6605
  next = {
6510
6606
  ...next,
6511
- view: import_core37.EMPTY_TASK_LOG,
6607
+ view: import_core40.EMPTY_TASK_LOG,
6512
6608
  attempt,
6513
6609
  attempts: next.attempts.includes(attempt) ? next.attempts : [...next.attempts, attempt]
6514
6610
  };
6515
6611
  }
6516
6612
  if (attempt < next.attempt) return next;
6517
- return clampQueueIndex({ ...next, view: events.reduce(import_core37.reduceTaskLog, next.view) });
6613
+ return clampQueueIndex({ ...next, view: events.reduce(import_core40.reduceTaskLog, next.view) });
6518
6614
  }
6519
6615
  function taskLogAbandoned(state) {
6520
6616
  const tv = state.taskView;
@@ -6535,14 +6631,14 @@ function announceApprovals(state, action) {
6535
6631
  if (state.taskView?.taskId === action.taskId) return state;
6536
6632
  const task = findTask(state.tasks, action.taskId);
6537
6633
  const way = task ? `tab to the plan, select task ${task.order} and press enter (or /open ${task.order})` : "tab to the plan, select it and press enter";
6538
- const spoken = action.events.reduce((s, event) => event.type === "approval_requested" && !event.decided ? say(s, "system", `Task ${task?.order ?? "?"} waits for approval: ${(0, import_core37.runnerToolSubject)(event.tool, event.args)} \u2014 ${way}, then ctrl-y allows or ctrl-g denies`) : s, state);
6634
+ const spoken = action.events.reduce((s, event) => event.type === "approval_requested" && !event.decided ? say(s, "system", `Task ${task?.order ?? "?"} waits for approval: ${(0, import_core40.runnerToolSubject)(event.tool, event.args)}: ${way}, then ctrl-y allows or ctrl-g denies`) : s, state);
6539
6635
  return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
6540
6636
  }
6541
6637
  function announceCheckpoint(state, action) {
6542
6638
  const task = findTask(state.tasks, action.taskId) ?? state.tasks.find((t) => t.title === action.title);
6543
6639
  const label = task ? `Task ${task.order}` : action.title;
6544
6640
  const where = task ? `tab to the plan, select task ${task.order} and press enter (or /open ${task.order}) to answer` : "tab to the plan, select it and press enter to answer";
6545
- const spoken = say(state, "system", `${label} asks: ${(0, import_core37.truncateCheckpointSummary)(action.summary)} \u2014 ${where}`);
6641
+ const spoken = say(state, "system", `${label} asks: ${(0, import_core40.truncateCheckpointSummary)(action.summary)}: ${where}`);
6546
6642
  return { ...spoken, scroll: state.scroll };
6547
6643
  }
6548
6644
  function taskLogLoaded(state, action) {
@@ -6550,7 +6646,7 @@ function taskLogLoaded(state, action) {
6550
6646
  if (!tv || tv.taskId !== action.taskId) return state;
6551
6647
  const loaded = action.attempt >= 1 ? {
6552
6648
  ...tv,
6553
- view: (0, import_core37.replayTaskLog)(action.events),
6649
+ view: (0, import_core40.replayTaskLog)(action.events),
6554
6650
  attempts: action.attempts ?? tv.attempts,
6555
6651
  attempt: action.attempt
6556
6652
  } : { ...tv, attempts: action.attempts ?? tv.attempts };
@@ -6565,7 +6661,7 @@ function answerApproval(state, tv, sessionId, decision) {
6565
6661
  const approval = waitingApproval(tv);
6566
6662
  if (!approval?.approvalId) return step(say(state, "system", "Nothing is waiting for approval."));
6567
6663
  if (decision === "allowForTask" && !approval.allowForTask) {
6568
- return step(say(state, "system", "The runner offered no grant for the rest of this task \u2014 ctrl-y allows this call."));
6664
+ return step(say(state, "system", "The runner offered no grant for the rest of this task: ctrl-y allows this call."));
6569
6665
  }
6570
6666
  const note = decision === "deny" ? state.editor.text.trim() : "";
6571
6667
  const answer = decision === "deny" ? { decision, ...note ? { note } : {} } : { decision };
@@ -6589,7 +6685,7 @@ function sendNow(state, tv, sessionId) {
6589
6685
  if (text) {
6590
6686
  const task = findTask(state.tasks, tv.taskId);
6591
6687
  if (continuesTask(task)) {
6592
- return step(say(state, "system", resumesTask(task) ? "The task is paused on a usage limit, so there is no turn to interrupt \u2014 enter resumes it." : "The task has finished, so there is no turn to interrupt \u2014 enter continues it."));
6688
+ 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."));
6593
6689
  }
6594
6690
  return step({ ...state, editor: commit(state.editor) }, [{ type: "forceSendTaskMessage", sessionId, taskId: tv.taskId, text }]);
6595
6691
  }
@@ -6624,7 +6720,7 @@ function handleTaskViewKey(state, key) {
6624
6720
  if (key.name === "ctrl-s") return sendNow(state, tv, state.sessionId);
6625
6721
  if (key.name === "ctrl-x") {
6626
6722
  const task = findTask(state.tasks, tv.taskId);
6627
- if (task && (0, import_core37.resumeLabel)(task)) return step(state, [{ type: "taskAction", sessionId: state.sessionId, taskId: tv.taskId, action: "cancel-resume" }]);
6723
+ if (task && (0, import_core40.resumeLabel)(task)) return step(state, [{ type: "taskAction", sessionId: state.sessionId, taskId: tv.taskId, action: "cancel-resume" }]);
6628
6724
  return step(state, [{ type: "interruptTask", sessionId: state.sessionId, taskId: tv.taskId }]);
6629
6725
  }
6630
6726
  if (key.name === "ctrl-y") return answerWaiting(state, tv, state.sessionId, "allow");
@@ -6642,7 +6738,7 @@ function handleTaskViewKey(state, key) {
6642
6738
  }
6643
6739
 
6644
6740
  // src/tui/reducers/taskEdits.ts
6645
- var import_core38 = require("@ordewell/core");
6741
+ var import_core41 = require("@ordewell/core");
6646
6742
  var import_plan_utils3 = require("@ordewell/core/plan-utils");
6647
6743
  function openTaskRunnerPicker(state, task) {
6648
6744
  if (task.type !== "ai") return fail3(state, "Manual tasks do not run on an executor, so they have no runner.");
@@ -6672,7 +6768,7 @@ function confirmRemoveTask(state, task) {
6672
6768
  }
6673
6769
  function toggleTaskOps(state, task, subtask, to = !task.ops) {
6674
6770
  if (!state.sessionId) return fail3(state, "No active plan.");
6675
- if (task.type !== "ai") return fail3(state, "Only an AI task can be an ops task \u2014 a manual task already runs outside any worktree.");
6771
+ if (task.type !== "ai") return fail3(state, "Only an AI task can be an ops task: a manual task already runs outside any worktree.");
6676
6772
  if (subtask) return fail3(state, "A subtask runs with its parent; make the parent an ops task instead.");
6677
6773
  if (!!task.ops === to) return fail3(state, `${refFor(state, task)} is already ${to ? "an ops" : "a change"} task.`);
6678
6774
  return step(state, [{
@@ -6753,7 +6849,7 @@ function openTaskModePicker(state, task) {
6753
6849
  overlay: {
6754
6850
  kind: "picker",
6755
6851
  picker: picker(`Mode \xB7 ${refFor(state, task)}`, pickerItemsFor(state, action), action, {
6756
- hint: [`Modes declared by ${task.assignedRunner}.`, (0, import_core38.codexSandboxNotice)(state.modesByRunner)?.line].filter(Boolean).join(" ")
6852
+ hint: [`Modes declared by ${task.assignedRunner}.`, (0, import_core41.codexSandboxNotice)(state.modesByRunner)?.line].filter(Boolean).join(" ")
6757
6853
  })
6758
6854
  }
6759
6855
  }
@@ -6905,7 +7001,7 @@ function taskOpsCommand(state, args) {
6905
7001
  }
6906
7002
  function addTask(state, title) {
6907
7003
  if (!state.sessionId) {
6908
- return fail3(state, "No active plan to add a task to \u2014 describe a goal first, then /add-task <title>.");
7004
+ return fail3(state, "No active plan to add a task to: describe a goal first, then /add-task <title>.");
6909
7005
  }
6910
7006
  if (!title) {
6911
7007
  return step({
@@ -6981,11 +7077,11 @@ function handlePlanKey(state, key) {
6981
7077
  if (!state.sessionId || key.name !== "char") return step(state);
6982
7078
  if (key.char === "E") return step(state, [{ type: "execute", sessionId: state.sessionId }]);
6983
7079
  if (key.char === "S") return step(state, [{ type: "stopExecution", sessionId: state.sessionId }]);
6984
- const shortcut = key.char === "c" && (0, import_core39.resumeLabel)(task) ? "cancel-resume" : PLAN_SHORTCUTS[key.char ?? ""];
7080
+ const shortcut = key.char === "c" && (0, import_core42.resumeLabel)(task) ? "cancel-resume" : PLAN_SHORTCUTS[key.char ?? ""];
6985
7081
  if (key.char === "r") {
6986
- return (0, import_core39.taskRowActions)(task).includes("resume-now") ? step(state, [taskActionEffect(state, state.sessionId, task.id, "resume-now")]) : step(state);
7082
+ return (0, import_core42.taskRowActions)(task).includes("resume-now") ? step(state, [taskActionEffect(state, state.sessionId, task.id, "resume-now")]) : step(state);
6987
7083
  }
6988
- const action = key.char === "m" ? (0, import_core39.markAction)(task) : shortcut;
7084
+ const action = key.char === "m" ? (0, import_core42.markAction)(task) : shortcut;
6989
7085
  if (action === "force-start" && task.mergeGate?.length) return confirmForceStartPastGate(state, task);
6990
7086
  if (action) {
6991
7087
  return step(state, [taskActionEffect(state, state.sessionId, task.id, action)]);
@@ -7025,9 +7121,9 @@ function commitTaskEdit(state, task, editor) {
7025
7121
  }
7026
7122
 
7027
7123
  // src/tui/reducers/turnQueue.ts
7028
- var import_core40 = require("@ordewell/core");
7124
+ var import_core43 = require("@ordewell/core");
7029
7125
  function drainQueue(state, settled) {
7030
- const next = (0, import_core40.drainNext)(state.queuedPrompts);
7126
+ const next = (0, import_core43.drainNext)(state.queuedPrompts);
7031
7127
  const ended = disarmStop({ ...settled, stopRequested: false, forceSendPending: null });
7032
7128
  if (!next) return step(ended);
7033
7129
  const spoken = say(ended, "user", next.text);
@@ -7042,7 +7138,7 @@ function restoreForced(state) {
7042
7138
  if (!forced) return state;
7043
7139
  const cleared = { ...state, forceSendPending: null };
7044
7140
  if (forced.queueIndex === void 0) {
7045
- const text = (0, import_core40.aheadOfDraft)(forced.text, state.editor.text);
7141
+ const text = (0, import_core43.aheadOfDraft)(forced.text, state.editor.text);
7046
7142
  return { ...cleared, editor: { ...state.editor, text, cursor: text.length } };
7047
7143
  }
7048
7144
  const queued = [...state.queuedPrompts];
@@ -7058,14 +7154,14 @@ function stopPlanning(state, sessionId) {
7058
7154
  return step(queueToEditor(cutTurn({ ...dismissed, stopArmed: false, stopRequested: true, forceSendPending: null })), [{ type: "cancelPlanning", sessionId }]);
7059
7155
  }
7060
7156
  function queueToEditor(state) {
7061
- const all = (0, import_core40.unsendAll)(state.queuedPrompts);
7157
+ const all = (0, import_core43.unsendAll)(state.queuedPrompts);
7062
7158
  if (!all) return state;
7063
7159
  return { ...state, queuedPrompts: all.rest, editor: { ...state.editor, text: all.text, cursor: all.text.length } };
7064
7160
  }
7065
7161
  function unsendLatest(state) {
7066
- const latest = (0, import_core40.unsendLatest)(state.queuedPrompts);
7162
+ const latest = (0, import_core43.unsendLatest)(state.queuedPrompts);
7067
7163
  if (!latest) return step(state);
7068
- const text = (0, import_core40.aheadOfDraft)(latest.text, state.editor.text);
7164
+ const text = (0, import_core43.aheadOfDraft)(latest.text, state.editor.text);
7069
7165
  return step({
7070
7166
  ...state,
7071
7167
  queuedPrompts: latest.rest,
@@ -7081,7 +7177,7 @@ function forceSendPlanner(state) {
7081
7177
  { type: "forceSendPlannerMessage", sessionId: state.sessionId, message: text }
7082
7178
  ]);
7083
7179
  }
7084
- const latest = (0, import_core40.unsendLatest)(state.queuedPrompts);
7180
+ const latest = (0, import_core43.unsendLatest)(state.queuedPrompts);
7085
7181
  if (!latest) return step(say(state, "system", "Nothing to send now: type a message, or queue one with enter."));
7086
7182
  const forced = { text: latest.text, queueIndex: latest.rest.length };
7087
7183
  return step({ ...state, queuedPrompts: latest.rest, stopRequested: false, forceSendPending: forced }, [
@@ -7097,7 +7193,7 @@ function plannerEscape(state) {
7097
7193
  }
7098
7194
 
7099
7195
  // src/tui/reducers/incoming.ts
7100
- var import_core41 = require("@ordewell/core");
7196
+ var import_core44 = require("@ordewell/core");
7101
7197
  var planText = (value, fallback = "") => sanitize(String(value ?? fallback));
7102
7198
  var planLabel = (value, fallback = "") => planText(value, fallback).replace(/\n+/g, " ");
7103
7199
  var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
@@ -7136,12 +7232,13 @@ function runTaskToView(t) {
7136
7232
  ...Array.isArray(model.availableVariants) ? { availableVariants: model.availableVariants.map((variant) => planLabel(variant)) } : {}
7137
7233
  } : void 0,
7138
7234
  isolation: t.isolation,
7139
- awaitingReason: t.status === "awaiting_user" && (0, import_core41.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
7235
+ awaitingReason: t.status === "awaiting_user" && (0, import_core44.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
7140
7236
  ...t.status === "awaiting_user" && t.awaitingReason === "usage-limit" && typeof t.resumeAt === "string" ? { resumeAt: t.resumeAt } : {},
7141
7237
  ...t.status === "skipped" && typeof t.skipReason === "string" ? { skipReason: sanitize(t.skipReason) } : {},
7142
7238
  checkpoint: typeof t.checkpoint === "string" ? sanitize(t.checkpoint) : void 0,
7143
7239
  continuable: t.continuable === true,
7144
7240
  awaitingApproval: t.awaitingApproval,
7241
+ ...t.waitsForApproval === true ? { waitsForApproval: true } : {},
7145
7242
  ...t.ops === true ? { ops: true } : {},
7146
7243
  ...Array.isArray(t.skills) && t.skills.length > 0 ? { skills: t.skills.map((name) => planLabel(name)) } : {},
7147
7244
  ...t.addedBy ? { addedBy: t.addedBy } : {},
@@ -7155,7 +7252,7 @@ function supervisorOfRun(runSettings) {
7155
7252
  const unsupervised = typeof runSettings.unsupervised === "string" ? runSettings.unsupervised : void 0;
7156
7253
  return {
7157
7254
  runSupervisor: {
7158
- level: (0, import_core41.isSupervisorLevel)(runSettings.supervisorLevel) ? runSettings.supervisorLevel : "off",
7255
+ level: (0, import_core44.isSupervisorLevel)(runSettings.supervisorLevel) ? runSettings.supervisorLevel : "off",
7159
7256
  ...unsupervised ? { unsupervised } : {}
7160
7257
  }
7161
7258
  };
@@ -7172,7 +7269,7 @@ function withRun(state, run) {
7172
7269
  };
7173
7270
  }
7174
7271
  function foldRun(state, input) {
7175
- const run = (0, import_core41.reduceRun)(state.run, input);
7272
+ const run = (0, import_core44.reduceRun)(state.run, input);
7176
7273
  return run === state.run ? state : withRun(state, run);
7177
7274
  }
7178
7275
  function mapRunTasks(tasks, map) {
@@ -7202,11 +7299,13 @@ function applySettings(state, settings) {
7202
7299
  ...state,
7203
7300
  orchestratorModel: typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : state.orchestratorModel,
7204
7301
  plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
7302
+ plannerKnown: state.plannerKnown || typeof settings.aiProvider === "string",
7205
7303
  plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
7206
7304
  plannerModels: settings.plannerModels && typeof settings.plannerModels === "object" ? settings.plannerModels : state.plannerModels,
7207
7305
  maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
7208
7306
  allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist,
7209
- runSettings: settings.run ?? state.runSettings
7307
+ runSettings: settings.run ?? state.runSettings,
7308
+ runSettingsKnown: state.runSettingsKnown || settings.run !== void 0
7210
7309
  };
7211
7310
  }
7212
7311
  function followSession(state, message) {
@@ -7220,21 +7319,21 @@ function followSession(state, message) {
7220
7319
  }
7221
7320
 
7222
7321
  // src/tui/reducers/runSettings.ts
7223
- var import_core42 = require("@ordewell/core");
7322
+ var import_core45 = require("@ordewell/core");
7224
7323
  function runSettingsEffect(state, changes) {
7225
7324
  return { type: "setRunSettings", changes, ...state.sessionId ? { sessionId: state.sessionId } : {} };
7226
7325
  }
7227
7326
  function supervisorCommand(state, args) {
7228
7327
  const [arg, value] = args;
7229
7328
  if (arg === void 0) {
7230
- return step(say(state, "system", `${(0, import_core42.supervisorSummary)(state.runSettings)} \u2014 /supervisor off|watch|act, /supervisor retries <n>, /supervisor reads <n>.`));
7329
+ return step(say(state, "system", `${(0, import_core45.supervisorSummary)(state.runSettings)}: /supervisor off|watch|act, /supervisor retries <n>, /supervisor reads <n>.`));
7231
7330
  }
7232
7331
  if (arg === "retries") {
7233
- const n = (0, import_core42.parseCount)(value, 0);
7332
+ const n = (0, import_core45.parseCount)(value, 0);
7234
7333
  return n === null ? fail3(state, "Usage: /supervisor retries <whole number, 0 or more>") : step(state, [runSettingsEffect(state, { supervisorRetryLimit: n })]);
7235
7334
  }
7236
7335
  if (arg === "reads") {
7237
- const n = (0, import_core42.parseCount)(value, 1);
7336
+ const n = (0, import_core45.parseCount)(value, 1);
7238
7337
  return n === null ? fail3(state, "Usage: /supervisor reads <whole number, 1 or more>") : step(state, [runSettingsEffect(state, { wakeUpReadBudget: n })]);
7239
7338
  }
7240
7339
  if (arg === "act") return confirmSupervisorAct(state);
@@ -7243,17 +7342,17 @@ function supervisorCommand(state, args) {
7243
7342
  }
7244
7343
  function automergeCommand(state, arg) {
7245
7344
  if (arg === void 0) {
7246
- return step(say(state, "system", `${(0, import_core42.automergeSummary)(state.runSettings)} \u2014 /automerge on|off.`));
7345
+ return step(say(state, "system", `${(0, import_core45.automergeSummary)(state.runSettings)}: /automerge on|off.`));
7247
7346
  }
7248
- const enabled = (0, import_core42.parseSwitch)(arg);
7347
+ const enabled = (0, import_core45.parseSwitch)(arg);
7249
7348
  if (enabled === null) return fail3(state, "Usage: /automerge [on|off]");
7250
7349
  return step(state, [runSettingsEffect(state, { mergeAtGatesAutomatically: enabled })]);
7251
7350
  }
7252
7351
  function autorunCommand(state, arg) {
7253
7352
  if (arg === void 0) {
7254
- return step(say(state, "system", `${(0, import_core42.autorunSummary)(state.runSettings)} \u2014 /autorun on starts each plan the planner submits, without review.`));
7353
+ return step(say(state, "system", `${(0, import_core45.autorunSummary)(state.runSettings)}: /autorun on starts each plan the planner submits, without review.`));
7255
7354
  }
7256
- const enabled = (0, import_core42.parseSwitch)(arg);
7355
+ const enabled = (0, import_core45.parseSwitch)(arg);
7257
7356
  if (enabled === null) return fail3(state, "Usage: /autorun [on|off]");
7258
7357
  if (enabled) return confirmAutorun(state);
7259
7358
  return step(state, [runSettingsEffect(state, { autorun: false })]);
@@ -7265,7 +7364,7 @@ function confirmSupervisorAct(state) {
7265
7364
  kind: "confirm",
7266
7365
  title: "Let the supervisor act?",
7267
7366
  message: "At Act the supervisor runs, stops and retries tasks, answers their requests (never allowing), edits the plan and merges, without asking you.",
7268
- note: `${import_core42.ACT_RISK}
7367
+ note: `${import_core45.ACT_RISK}
7269
7368
  It applies to this session's run now, and to every later run.`,
7270
7369
  action: { kind: "supervisor-act" }
7271
7370
  }
@@ -7278,14 +7377,14 @@ function confirmAutorun(state) {
7278
7377
  kind: "confirm",
7279
7378
  title: "Start plans without review?",
7280
7379
  message: "With autorun on, each plan the planner submits starts at once, as if you had pressed Execute. The planner still asks when something is unclear.",
7281
- note: import_core42.AUTORUN_RISK,
7380
+ note: import_core45.AUTORUN_RISK,
7282
7381
  action: { kind: "autorun-on" }
7283
7382
  }
7284
7383
  });
7285
7384
  }
7286
7385
 
7287
7386
  // src/tui/reducers/settings.ts
7288
- var import_core43 = require("@ordewell/core");
7387
+ var import_core46 = require("@ordewell/core");
7289
7388
  var SUPERVISOR_DETAIL = {
7290
7389
  off: "The planner speaks only when you write",
7291
7390
  watch: "The planner follows your runs and suggests what to do",
@@ -7310,7 +7409,7 @@ var SETTINGS = {
7310
7409
  {
7311
7410
  id: "guarded",
7312
7411
  label: "Guarded",
7313
- detail: [`New plans run tasks in each runner's safer mode`, (0, import_core43.codexSandboxNotice)(state.modesByRunner)?.line].filter(Boolean).join(" \xB7 "),
7412
+ detail: [`New plans run tasks in each runner's safer mode`, (0, import_core46.codexSandboxNotice)(state.modesByRunner)?.line].filter(Boolean).join(" \xB7 "),
7314
7413
  selected: !state.autonomous
7315
7414
  }
7316
7415
  ]
@@ -7320,7 +7419,7 @@ var SETTINGS = {
7320
7419
  label: "Supervisor",
7321
7420
  items: (state) => ["off", "watch", "act"].map((level) => ({
7322
7421
  id: level,
7323
- label: import_core43.SUPERVISOR_LEVEL_LABEL[level],
7422
+ label: import_core46.SUPERVISOR_LEVEL_LABEL[level],
7324
7423
  detail: SUPERVISOR_DETAIL[level],
7325
7424
  selected: state.runSettings.supervisorLevel === level
7326
7425
  }))
@@ -7374,12 +7473,12 @@ function saveChoices(state, choices) {
7374
7473
  const autonomous = choices.autonomy === "full";
7375
7474
  if (autonomous !== state.autonomous) {
7376
7475
  save2.autonomous = autonomous;
7377
- changed.push(`Autonomy ${(0, import_core43.autonomyLevelLabel)(autonomous)}`);
7476
+ changed.push(`Autonomy ${(0, import_core46.autonomyLevelLabel)(autonomous)}`);
7378
7477
  }
7379
7478
  const level = choices.supervisor;
7380
7479
  if (level !== state.runSettings.supervisorLevel) {
7381
7480
  run.supervisorLevel = level;
7382
- changed.push(`Supervisor ${import_core43.SUPERVISOR_LEVEL_LABEL[level]}`);
7481
+ changed.push(`Supervisor ${import_core46.SUPERVISOR_LEVEL_LABEL[level]}`);
7383
7482
  }
7384
7483
  if (choices.retries !== void 0 && Number(choices.retries) !== state.runSettings.supervisorRetryLimit) {
7385
7484
  run.supervisorRetryLimit = Number(choices.retries);
@@ -7406,7 +7505,7 @@ function saveChoices(state, choices) {
7406
7505
  }
7407
7506
  if (changed.length === 0) return step(state);
7408
7507
  const effect = { ...save2, message: `Settings: ${changed.join(" \xB7 ")}` };
7409
- const risks = [...run.supervisorLevel === "act" ? [import_core43.ACT_RISK] : [], ...run.autorun ? [import_core43.AUTORUN_RISK] : []];
7508
+ const risks = [...run.supervisorLevel === "act" ? [import_core46.ACT_RISK] : [], ...run.autorun ? [import_core46.AUTORUN_RISK] : []];
7410
7509
  if (risks.length === 0) return saveSettings(state, effect);
7411
7510
  return step({
7412
7511
  ...state,
@@ -7435,7 +7534,7 @@ function saveSettings(state, effect) {
7435
7534
  }
7436
7535
 
7437
7536
  // src/tui/reducers/commands.ts
7438
- var unknownCommand = (state, name) => fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
7537
+ var unknownCommand = (state, name) => fail3(state, `Unknown command: /${name}. Type /help to see what's available.`);
7439
7538
  function runCommand(state, { name, args }) {
7440
7539
  if (!findCommand(name)) return unknownCommand(state, name);
7441
7540
  switch (name) {
@@ -7504,7 +7603,7 @@ function runCommand(state, { name, args }) {
7504
7603
  case "save":
7505
7604
  return withSession(state, (sessionId) => step(state, [{ type: "saveSession", sessionId }]));
7506
7605
  case "load":
7507
- return args[0] ? step(state, [{ type: "loadSession", sessionId: args[0] }]) : fail3(state, "Usage: /load <session-id> \u2014 or run /sessions to pick one.");
7606
+ return args[0] ? step(state, [{ type: "loadSession", sessionId: args[0] }]) : fail3(state, "Usage: /load <session-id>: or run /sessions to pick one.");
7508
7607
  case "delete":
7509
7608
  return args[0] ? step(state, [{ type: "deleteSession", sessionId: args[0] }]) : fail3(state, "Usage: /delete <session-id>");
7510
7609
  case "add-task":
@@ -7528,7 +7627,7 @@ function runCommand(state, { name, args }) {
7528
7627
  const answer = args[1]?.toLowerCase();
7529
7628
  const reason = args.slice(2).join(" ").trim();
7530
7629
  if (answer !== "approve" && answer !== "reject") return fail3(state, "Usage: /checkpoint <id> approve|reject [reason]");
7531
- if (answer === "approve" && reason) return fail3(state, "Approving takes no note \u2014 reject with a reason to tell the task something.");
7630
+ if (answer === "approve" && reason) return fail3(state, "Approving takes no note: reject with a reason to tell the task something.");
7532
7631
  return answerCheckpoint(state, sessionId, taskId, answer, reason);
7533
7632
  });
7534
7633
  case "task-runner":
@@ -7566,7 +7665,7 @@ function runCommand(state, { name, args }) {
7566
7665
  function withIdlePlanner(state, run) {
7567
7666
  return withSession(
7568
7667
  state,
7569
- (sessionId) => plannerInFlight(state) ? fail3(state, "The planner is still answering \u2014 wait for its reply, or /stop it first.") : run(sessionId)
7668
+ (sessionId) => plannerInFlight(state) ? fail3(state, "The planner is still answering: wait for its reply, or /stop it first.") : run(sessionId)
7570
7669
  );
7571
7670
  }
7572
7671
  function resolveToggle(arg, current) {
@@ -7597,8 +7696,8 @@ function newSession(state) {
7597
7696
  goal: "",
7598
7697
  taskView: null,
7599
7698
  planApproved: false,
7600
- conversation: import_core44.EMPTY_CONVERSATION,
7601
- turnGate: import_core44.NO_TURN,
7699
+ conversation: import_core47.EMPTY_CONVERSATION,
7700
+ turnGate: import_core47.NO_TURN,
7602
7701
  selectedTask: 0,
7603
7702
  expandedTaskId: null,
7604
7703
  taskEditor: null,
@@ -7609,19 +7708,21 @@ function newSession(state) {
7609
7708
  // Prompts belong to the session that raised them; the old planner is gone
7610
7709
  // and its pending requests deny on their own timeout.
7611
7710
  pendingApprovals: [],
7612
- queuedPrompts: import_core44.EMPTY_HOLD,
7711
+ queuedPrompts: import_core47.EMPTY_HOLD,
7613
7712
  stopArmed: false,
7614
7713
  forceSendPending: null,
7615
7714
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
7616
- }, import_core44.EMPTY_RUN), closeEffects);
7715
+ }, import_core47.EMPTY_RUN), closeEffects);
7617
7716
  }
7618
7717
  function setModel(state, args) {
7619
7718
  if (args[0] === "set" && args[1]) {
7620
- const runner = (0, import_core44.runnerForProvider)(state.plannerProvider);
7719
+ const runner = (0, import_core47.runnerForProvider)(state.plannerProvider);
7621
7720
  const known = runner ? modelsForRunner(state.models, runner) : [];
7622
7721
  if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
7623
7722
  return fail3(state, `${args[1]} was not discovered for ${runner}.`);
7624
7723
  }
7724
+ const refusal = known.length === 0 ? vendorModelRefusal(state.plannerProvider, args[1], import_core47.TERMINAL_UI_COMMANDS.pickPlanner) : null;
7725
+ if (refusal) return fail3(state, refusal);
7625
7726
  return step(state, [{ type: "setModel", modelId: args[1] }]);
7626
7727
  }
7627
7728
  if (args.length > 0 && args[0] !== "set") return fail3(state, "Usage: /model [set <model-id>]");
@@ -7640,7 +7741,7 @@ function setModel(state, args) {
7640
7741
  function setPlanner(state, args) {
7641
7742
  if (args[0]) {
7642
7743
  const id = args[0].toLowerCase();
7643
- if (!(0, import_core44.isPlannerBackend)(id)) {
7744
+ if (!(0, import_core47.isPlannerBackend)(id)) {
7644
7745
  return fail3(state, `Unknown planner: ${id}. Run /planner with no arguments to see the list.`);
7645
7746
  }
7646
7747
  return step(state, [{ type: "setPlanner", provider: id }]);
@@ -7672,7 +7773,7 @@ function setPlannerEffort(state, args) {
7672
7773
  function setKey(state, args) {
7673
7774
  if (args[0] === "set" && args[1]) {
7674
7775
  const provider = args[1].toLowerCase();
7675
- if (!(0, import_core44.isApiProvider)(provider)) {
7776
+ if (!(0, import_core47.isApiProvider)(provider)) {
7676
7777
  return fail3(state, `Unknown provider: ${provider}. Run /key with no arguments to see the list.`);
7677
7778
  }
7678
7779
  if (!args[2]) {
@@ -7683,16 +7784,16 @@ function setKey(state, args) {
7683
7784
  }
7684
7785
  return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
7685
7786
  }
7686
- const items = import_core44.PROVIDER_PRIORITY.map((id) => ({
7787
+ const items = import_core47.PROVIDER_PRIORITY.map((id) => ({
7687
7788
  id,
7688
- label: (0, import_core44.getProviderMeta)(id).label,
7689
- detail: (0, import_core44.getProviderMeta)(id).apiKeyEnvVar,
7789
+ label: (0, import_core47.getProviderMeta)(id).label,
7790
+ detail: (0, import_core47.getProviderMeta)(id).apiKeyEnvVar,
7690
7791
  selected: state.configuredProviders.includes(id)
7691
7792
  }));
7692
7793
  return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
7693
7794
  }
7694
7795
  function keyPrompt(provider) {
7695
- const meta = (0, import_core44.getProviderMeta)(provider);
7796
+ const meta = (0, import_core47.getProviderMeta)(provider);
7696
7797
  return {
7697
7798
  kind: "prompt",
7698
7799
  title: `${meta.label} API key`,
@@ -7768,14 +7869,14 @@ function runners(state, args) {
7768
7869
  } });
7769
7870
  }
7770
7871
  function setMaxParallel(state, arg) {
7771
- if (!arg) return step(say(state, "system", `Up to ${state.maxParallel} AI task${state.maxParallel === 1 ? "" : "s"} run at once \u2014 /parallel <n> changes it.`));
7772
- const limit = (0, import_core44.parseMaxParallel)(arg);
7872
+ if (!arg) return step(say(state, "system", `Up to ${state.maxParallel} AI task${state.maxParallel === 1 ? "" : "s"} run at once: /parallel <n> changes it.`));
7873
+ const limit = (0, import_core47.parseMaxParallel)(arg);
7773
7874
  if (limit === null) return fail3(state, "Usage: /parallel [<number of tasks, 1 or more>]");
7774
7875
  return step({ ...state, maxParallel: limit }, [{ type: "setMaxParallel", limit }]);
7775
7876
  }
7776
7877
  function setAutonomous(state, arg) {
7777
- if (arg === void 0) return step(say(state, "system", `Autonomy level: ${(0, import_core44.autonomyLevelLabel)(state.autonomous)} \u2014 /auto full or /auto guarded changes it for new plans.`));
7778
- const enabled = (0, import_core44.parseAutonomyLevel)(arg);
7878
+ if (arg === void 0) return step(say(state, "system", `Autonomy level: ${(0, import_core47.autonomyLevelLabel)(state.autonomous)}. /auto full or /auto guarded changes it for new plans.`));
7879
+ const enabled = (0, import_core47.parseAutonomyLevel)(arg);
7779
7880
  if (enabled === null) return fail3(state, "Usage: /auto [full|guarded]");
7780
7881
  return step({ ...state, autonomous: enabled }, [{ type: "setAutonomous", enabled }]);
7781
7882
  }
@@ -7788,7 +7889,7 @@ function setMouseCapture(state, arg) {
7788
7889
  // move it.
7789
7890
  { ...state, mouseCapture: enabled, selection: null },
7790
7891
  "system",
7791
- enabled ? "Mouse capture on \u2014 the wheel scrolls, and dragging selects within one pane and copies on release." : "Mouse capture off \u2014 the terminal's own drag-select is back, across both panes; scroll with pgup/pgdn."
7892
+ enabled ? "Mouse capture on: the wheel scrolls, and dragging selects within one pane and copies on release." : "Mouse capture off: the terminal's own drag-select is back, across both panes; scroll with pgup/pgdn."
7792
7893
  );
7793
7894
  return step(noted, [{ type: "setMouseCapture", enabled }]);
7794
7895
  }
@@ -7800,7 +7901,7 @@ function rewind(state, arg) {
7800
7901
  [{ type: "loadRewindTargets", sessionId }]
7801
7902
  );
7802
7903
  }
7803
- if (!/^\d+$/.test(arg)) return fail3(state, "Usage: /rewind [<message>] \u2014 or /rewind alone to pick one.");
7904
+ if (!/^\d+$/.test(arg)) return fail3(state, "Usage: /rewind [<message>]: or /rewind alone to pick one.");
7804
7905
  return step(state, [{ type: "loadRewindTargets", sessionId, pick: Number(arg) }]);
7805
7906
  });
7806
7907
  }
@@ -7811,7 +7912,7 @@ var REWIND_OPTIONS = [
7811
7912
  function pickRewindTarget(state, index) {
7812
7913
  return withIdlePlanner({ ...state, overlay: null }, () => {
7813
7914
  const target = state.rewindTargets?.find((t) => t.index === index);
7814
- if (!target) return fail3({ ...state, overlay: null }, `No message ${index} to rewind to \u2014 /rewind alone lists them.`);
7915
+ if (!target) return fail3({ ...state, overlay: null }, `No message ${index} to rewind to: /rewind alone lists them.`);
7815
7916
  return step({
7816
7917
  ...state,
7817
7918
  overlay: {
@@ -7903,7 +8004,7 @@ function handlePromptKey(state, overlay, key) {
7903
8004
  if (overlay.action.kind === "api-key") {
7904
8005
  return step(closed, [{ type: "setApiKey", provider: overlay.action.provider, key: value2 }]);
7905
8006
  }
7906
- if (!state.sessionId) return fail3(closed, "No active plan to add a task to \u2014 describe a goal first, then /add-task <title>.");
8007
+ if (!state.sessionId) return fail3(closed, "No active plan to add a task to: describe a goal first, then /add-task <title>.");
7907
8008
  return step(closed, [{ type: "addTask", sessionId: state.sessionId, title: value2 }]);
7908
8009
  }
7909
8010
  const value = editText(overlay.value, key);
@@ -7948,7 +8049,7 @@ function choose2(state, picker2, item) {
7948
8049
  return step(closed, [{
7949
8050
  type: "setRunners",
7950
8051
  changes,
7951
- message: names.length > 0 ? `Runners enabled: ${names.join(", ")}.` : "No runners enabled \u2014 the planner has nothing to assign work to."
8052
+ message: names.length > 0 ? `Runners enabled: ${names.join(", ")}.` : "No runners enabled: the planner has nothing to assign work to."
7952
8053
  }]);
7953
8054
  }
7954
8055
  if (picker2.action.kind === "set-task-deps") {
@@ -7970,7 +8071,7 @@ function choose2(state, picker2, item) {
7970
8071
  }
7971
8072
  if (!item) return step(state);
7972
8073
  if (item.disabled) {
7973
- return step(say(state, "error", item.detail ? `${item.label} \u2014 ${item.detail}` : `${item.label} is not available.`));
8074
+ return step(say(state, "error", item.detail ? `${item.label}: ${item.detail}` : `${item.label} is not available.`));
7974
8075
  }
7975
8076
  switch (picker2.action.kind) {
7976
8077
  case "set-model":
@@ -7984,7 +8085,7 @@ function choose2(state, picker2, item) {
7984
8085
  case "delete-session":
7985
8086
  return step(closed, [{ type: "deleteSession", sessionId: item.id }]);
7986
8087
  case "set-key":
7987
- return (0, import_core45.isApiProvider)(item.id) ? step({ ...state, overlay: keyPrompt(item.id) }) : step(closed);
8088
+ return (0, import_core48.isApiProvider)(item.id) ? step({ ...state, overlay: keyPrompt(item.id) }) : step(closed);
7988
8089
  case "choose-allowlist-runner": {
7989
8090
  const action = { kind: "set-allowlist", runner: item.id };
7990
8091
  const items = pickerItemsFor(state, action);
@@ -8060,7 +8161,7 @@ function reduceAction(state, action) {
8060
8161
  // (`task-1`), so nothing of the last one's — a merge gate, a checkpoint, its
8061
8162
  // tasks — may carry over into this.
8062
8163
  case "sessionStarted":
8063
- return step(withRun({ ...state, sessionId: action.sessionId, goal: action.goal, taskView: null }, import_core46.EMPTY_RUN));
8164
+ return step(withRun({ ...state, sessionId: action.sessionId, goal: action.goal, taskView: null }, import_core49.EMPTY_RUN));
8064
8165
  // The daemon registers a session only once planning succeeds; when it does
8065
8166
  // not, holding on to the id would send every next message to a session the
8066
8167
  // server never had.
@@ -8071,18 +8172,18 @@ function reduceAction(state, action) {
8071
8172
  goal: "",
8072
8173
  taskView: null,
8073
8174
  pendingApprovals: [],
8074
- queuedPrompts: import_core46.EMPTY_HOLD,
8175
+ queuedPrompts: import_core49.EMPTY_HOLD,
8075
8176
  stopArmed: false,
8076
8177
  forceSendPending: null,
8077
8178
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
8078
- }, import_core46.EMPTY_RUN));
8179
+ }, import_core49.EMPTY_RUN));
8079
8180
  // Deleting the session on screen stops it in the daemon; a view left on it
8080
8181
  // would answer every next message with a session-not-found.
8081
8182
  case "sessionDeleted":
8082
8183
  return action.sessionId === state.sessionId ? newSession(state) : step(state);
8083
8184
  case "chatRestored":
8084
8185
  if (stale(state, action.sessionId)) return step(state);
8085
- return step({ ...state, conversation: (0, import_core46.fromTranscript)(action.history, action.researchLog, action.plannerUsage, action.pendingTurns), scroll: 0 });
8186
+ return step({ ...state, conversation: (0, import_core49.fromTranscript)(action.history, action.researchLog, action.plannerUsage, action.pendingTurns), scroll: 0 });
8086
8187
  case "planUpdated": {
8087
8188
  if (stale(state, action.sessionId)) return step(state);
8088
8189
  const settled = { ...showPlan(state, action.plan), planner: "idle" };
@@ -8103,10 +8204,14 @@ function reduceAction(state, action) {
8103
8204
  // its status line with it, as a status update would have set it.
8104
8205
  case "runSnapshot": {
8105
8206
  if (stale(state, action.sessionId)) return step(state);
8106
- const next = withRun(state, action.view);
8207
+ const run = withRun(state, action.view);
8208
+ const next = action.approvals ? syncApprovals(run, action.approvals) : run;
8107
8209
  const status = runStatus(next, next.tasks);
8108
8210
  return step({ ...next, status });
8109
8211
  }
8212
+ case "heldEdit":
8213
+ if (stale(state, action.sessionId)) return step(state);
8214
+ return step(foldRun(state, { type: "plan_edit_held", held: action.held }));
8110
8215
  case "taskLog":
8111
8216
  if (stale(state, action.sessionId)) return step(state);
8112
8217
  return step(announceApprovals(taskLogArrived(state, action), action));
@@ -8122,15 +8227,15 @@ function reduceAction(state, action) {
8122
8227
  case "taskStarted": {
8123
8228
  if (stale(state, action.sessionId)) return step(state);
8124
8229
  const existing = findTask(state.tasks, action.taskId);
8125
- if (existing && (0, import_core26.isTaskRunning)(existing)) return step(state);
8230
+ if (existing && (0, import_core28.isTaskRunning)(existing)) return step(state);
8126
8231
  const folded = foldRun(state, {
8127
8232
  type: "task_started",
8128
8233
  taskId: action.taskId,
8129
8234
  order: action.order ?? 0,
8130
8235
  title: action.title,
8131
- runner: action.runner ?? import_core46.DEFAULT_RUNNERS[0]
8236
+ runner: action.runner ?? import_core49.DEFAULT_RUNNERS[0]
8132
8237
  });
8133
- const spoken = say(folded, "system", (0, import_core46.taskStartedNotice)(action.title, action.runner));
8238
+ const spoken = say(folded, "system", (0, import_core49.taskStartedNotice)(action.title, action.runner));
8134
8239
  return step({ ...spoken, status: "executing" });
8135
8240
  }
8136
8241
  case "taskCheckpoint":
@@ -8176,13 +8281,13 @@ function reduceAction(state, action) {
8176
8281
  completed: state.tasks.filter((t) => t.status === "completed").length,
8177
8282
  total: state.tasks.length,
8178
8283
  failed: state.tasks.filter((t) => t.status === "failed").length,
8179
- blocked: (0, import_core46.blockingFailures)(state.tasks).size,
8284
+ blocked: (0, import_core49.blockingFailures)(state.tasks).size,
8180
8285
  skipped: state.tasks.filter((t) => t.status === "skipped").length
8181
8286
  };
8182
8287
  const failures = `${failed > 0 ? ` \xB7 ${failed} failed` : ""}${blocked ? ` \xB7 ${blocked} blocked` : ""}${skipped ? ` \xB7 ${skipped} skipped` : ""}`;
8183
8288
  const verb = action.stopped ? "stopped" : "finished";
8184
8289
  return step({
8185
- ...say(state, "system", `Execution ${verb} \u2014 ${completed}/${total} tasks complete${failures}.`),
8290
+ ...say(state, "system", `Execution ${verb}: ${completed}/${total} tasks complete${failures}.`),
8186
8291
  status: "idle"
8187
8292
  });
8188
8293
  }
@@ -8201,7 +8306,7 @@ function reduceAction(state, action) {
8201
8306
  case "executionLost": {
8202
8307
  if (stale(state, action.sessionId)) return step(state);
8203
8308
  return step({
8204
- ...say(state, "error", "Lost the connection to the daemon \u2014 this view is no longer following the run, but the daemon may still be running it. /load this session again to see where it stands."),
8309
+ ...say(state, "error", "Lost the connection to the daemon: this view is no longer following the run, but the daemon may still be running it. /load this session again to see where it stands."),
8205
8310
  status: "idle"
8206
8311
  });
8207
8312
  }
@@ -8215,6 +8320,7 @@ function reduceAction(state, action) {
8215
8320
  models: action.models,
8216
8321
  orchestratorModels: action.orchestratorModels ?? state.orchestratorModels,
8217
8322
  configuredProviders: action.providers ?? state.configuredProviders,
8323
+ providersKnown: state.providersKnown || action.providers !== void 0,
8218
8324
  providerErrors: action.providerErrors ?? state.providerErrors,
8219
8325
  modesByRunner: action.modesByRunner ?? state.modesByRunner
8220
8326
  },
@@ -8240,11 +8346,11 @@ function reduceAction(state, action) {
8240
8346
  planner: "idle",
8241
8347
  planApproved: false,
8242
8348
  pendingApprovals: [],
8243
- queuedPrompts: import_core46.EMPTY_HOLD,
8349
+ queuedPrompts: import_core49.EMPTY_HOLD,
8244
8350
  stopArmed: false,
8245
8351
  forceSendPending: null,
8246
8352
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
8247
- }, import_core46.EMPTY_RUN));
8353
+ }, import_core49.EMPTY_RUN));
8248
8354
  // The rewound message comes back as a draft to edit and resend, so it lands
8249
8355
  // as if just typed: history is not being browsed, and nothing is parked.
8250
8356
  case "inputPrefilled":
@@ -8258,6 +8364,7 @@ function reduceAction(state, action) {
8258
8364
  return step(refreshStepper(refillPicker({
8259
8365
  ...state,
8260
8366
  runners: action.runners,
8367
+ runnersKnown: true,
8261
8368
  orchestratorModel: action.orchestratorModel ?? state.orchestratorModel
8262
8369
  }, ["set-task-runner"])));
8263
8370
  case "failed": {
@@ -8316,7 +8423,7 @@ function showPlan(state, plan) {
8316
8423
  });
8317
8424
  }
8318
8425
  function runStatus(state, tasks) {
8319
- if (tasks.some(import_core26.isTaskRunning)) return "executing";
8426
+ if (tasks.some(import_core28.isTaskRunning)) return "executing";
8320
8427
  if (tasks.some((t) => t.status === "awaiting_user") || state.runMerge?.paused) return "executing";
8321
8428
  return "idle";
8322
8429
  }
@@ -8391,13 +8498,13 @@ function submit(state) {
8391
8498
  const command = parseRunnerCommand(text);
8392
8499
  const offered2 = runnerCommands(state.taskView);
8393
8500
  if (command && offered2.length > 0 && !runnerCommandFor(offered2, command.name)) {
8394
- const notice = `/${command.name} is not a command ${task?.assignedRunner ?? "the runner"} offers this task \u2014 type / to see them.`;
8501
+ const notice = `/${command.name} is not a command ${task?.assignedRunner ?? "the runner"} offers this task: type / to see them.`;
8395
8502
  return step({ ...cleared, taskView: { ...state.taskView, notice } });
8396
8503
  }
8397
8504
  if (state.taskView.notice) cleared.taskView = { ...state.taskView, notice: void 0 };
8398
8505
  if (!command && waitingCheckpoint(task)) {
8399
8506
  if (waitingApproval(state.taskView)) {
8400
- return step(say(state, "system", "A tool request is waiting ahead of the checkpoint \u2014 answer it first (ctrl-y to allow, ctrl-g to deny), then enter replies to the checkpoint."));
8507
+ return step(say(state, "system", "A tool request is waiting ahead of the checkpoint: answer it first (ctrl-y to allow, ctrl-g to deny), then enter replies to the checkpoint."));
8401
8508
  }
8402
8509
  return answerCheckpoint(cleared, state.sessionId, taskId, "reject", text);
8403
8510
  }
@@ -8408,7 +8515,7 @@ function submit(state) {
8408
8515
  const builtIn = builtInCommand(text);
8409
8516
  if (builtIn) return runCommand(cleared, builtIn);
8410
8517
  if (plannerInFlight(state)) {
8411
- return step({ ...cleared, queuedPrompts: (0, import_core46.holdPrompt)(state.queuedPrompts, text) });
8518
+ return step({ ...cleared, queuedPrompts: (0, import_core49.holdPrompt)(state.queuedPrompts, text) });
8412
8519
  }
8413
8520
  const spoken = say(cleared, "user", text);
8414
8521
  const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
@@ -8483,7 +8590,7 @@ function createApp(deps) {
8483
8590
 
8484
8591
  // src/tui/effects.ts
8485
8592
  var import_child_process3 = require("child_process");
8486
- var import_core47 = require("@ordewell/core");
8593
+ var import_core50 = require("@ordewell/core");
8487
8594
 
8488
8595
  // src/tui/inbound.ts
8489
8596
  var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
@@ -8534,6 +8641,52 @@ function identity(event) {
8534
8641
  function replyKey(content, timestamp) {
8535
8642
  return timestamp ? `planner_message:${timestamp}:${content}` : `planner_message:content:${content}`;
8536
8643
  }
8644
+ function stampOf(frame2) {
8645
+ return frame2.stream !== void 0 && frame2.seq !== void 0 ? { stream: frame2.stream, seq: frame2.seq } : null;
8646
+ }
8647
+ var COVERED = /* @__PURE__ */ new Set(["plan_edit_held", "status_update", "task_updated", "approval_request", "approval_settled"]);
8648
+ var Delivered = class {
8649
+ constructor(window) {
8650
+ this.window = window;
8651
+ }
8652
+ window;
8653
+ stream = null;
8654
+ highest = 0;
8655
+ snapshot = 0;
8656
+ seen = /* @__PURE__ */ new Set();
8657
+ first({ stream, seq }) {
8658
+ this.follow(stream);
8659
+ if (seq <= this.highest - this.window || this.seen.has(seq)) return false;
8660
+ this.seen.add(seq);
8661
+ if (seq > this.highest) {
8662
+ this.highest = seq;
8663
+ for (const old of this.seen) if (old <= seq - this.window) this.seen.delete(old);
8664
+ }
8665
+ return true;
8666
+ }
8667
+ covered({ seq }) {
8668
+ return seq <= this.snapshot;
8669
+ }
8670
+ /** Whether a snapshot is as new as everything delivered, and so adopted; an older one would undo what came after it. */
8671
+ adopt({ stream, seq }) {
8672
+ this.follow(stream);
8673
+ if (seq < this.highest) return false;
8674
+ this.snapshot = seq;
8675
+ this.highest = seq;
8676
+ return true;
8677
+ }
8678
+ // A new stream is a restarted daemon: its numbers start over and mean nothing against the last one's.
8679
+ follow(stream) {
8680
+ if (stream === this.stream) return;
8681
+ this.stream = stream;
8682
+ this.highest = 0;
8683
+ this.snapshot = 0;
8684
+ this.seen.clear();
8685
+ }
8686
+ };
8687
+ function isClear(event) {
8688
+ return event.type === "plan_edit_held" && event.held === null || event.type === "approval_settled";
8689
+ }
8537
8690
  var Copies = class {
8538
8691
  constructor(cap) {
8539
8692
  this.cap = cap;
@@ -8604,6 +8757,7 @@ function inboundFor(api, dispatch, sessionId) {
8604
8757
  return inbound;
8605
8758
  }
8606
8759
  function createInbound(dispatch, sessionId) {
8760
+ const delivered = new Delivered(256);
8607
8761
  const copies = new Copies(64);
8608
8762
  const shown = new WindowSet(64);
8609
8763
  const pending = new WindowSet(16);
@@ -8612,6 +8766,7 @@ function createInbound(dispatch, sessionId) {
8612
8766
  let timer = null;
8613
8767
  let holdingSnapshot = false;
8614
8768
  let heldSnapshot = null;
8769
+ let heldAfter = [];
8615
8770
  const flush = () => {
8616
8771
  if (timer !== null) {
8617
8772
  clearTimeout(timer);
@@ -8636,8 +8791,17 @@ function createInbound(dispatch, sessionId) {
8636
8791
  shown.add(message.content);
8637
8792
  dispatch({ type: "sessionMessage", message, sessionId });
8638
8793
  };
8794
+ const fresh = (id, event) => {
8795
+ const stamp = stampOf(event);
8796
+ if (stamp) return delivered.first(stamp) && !(COVERED.has(event.type) && delivered.covered(stamp));
8797
+ return isClear(event) || copies.first(id, identity(event));
8798
+ };
8639
8799
  const acceptReply = (id, event) => {
8640
8800
  const key = replyKey(event.content, event.timestamp);
8801
+ if (stampOf(event)) {
8802
+ if (fresh(id, event) && !pending.take(key)) showReply(event);
8803
+ return;
8804
+ }
8641
8805
  if (pending.take(key)) {
8642
8806
  copies.credit(id, key);
8643
8807
  return;
@@ -8645,12 +8809,15 @@ function createInbound(dispatch, sessionId) {
8645
8809
  if (!copies.first(id, key)) return;
8646
8810
  showReply(event);
8647
8811
  };
8812
+ const deliverSnapshot = ({ view, approvals }) => {
8813
+ dispatch({ type: "runSnapshot", view, ...approvals ? { approvals } : {}, sessionId });
8814
+ };
8648
8815
  const deliverConversation = (id, event) => {
8649
8816
  if (event.type === "planner_message") {
8650
8817
  acceptReply(id, event);
8651
8818
  return;
8652
8819
  }
8653
- if (!copies.first(id, identity(event))) return;
8820
+ if (!fresh(id, event)) return;
8654
8821
  if (event.type === "plan_generated") {
8655
8822
  dispatch({ type: "sessionMessage", message: event, sessionId });
8656
8823
  dispatch({ type: "planBroadcast", plan: event.plan, sessionId });
@@ -8661,12 +8828,18 @@ function createInbound(dispatch, sessionId) {
8661
8828
  const receive = (kind, id, event) => {
8662
8829
  if (event.type === "run_snapshot") {
8663
8830
  flush();
8664
- if (holdingSnapshot) heldSnapshot = event.view;
8665
- else dispatch({ type: "runSnapshot", view: event.view, sessionId });
8831
+ const stamp = stampOf(event);
8832
+ if (stamp && !delivered.adopt(stamp)) return;
8833
+ if (holdingSnapshot) heldSnapshot = event;
8834
+ else deliverSnapshot(event);
8835
+ return;
8836
+ }
8837
+ if (holdingSnapshot) {
8838
+ heldAfter.push(() => receive(kind, id, event));
8666
8839
  return;
8667
8840
  }
8668
8841
  if (isStreamed(event)) {
8669
- if (kind === "planning") hold(event);
8842
+ if (kind === "planning" && (!stampOf(event) || fresh(id, event))) hold(event);
8670
8843
  return;
8671
8844
  }
8672
8845
  flush();
@@ -8674,7 +8847,7 @@ function createInbound(dispatch, sessionId) {
8674
8847
  deliverConversation(id, event);
8675
8848
  return;
8676
8849
  }
8677
- if (!copies.first(id, identity(event))) return;
8850
+ if (!fresh(id, event)) return;
8678
8851
  dispatchLifecycle(dispatch, event, sessionId);
8679
8852
  };
8680
8853
  let subscription = 0;
@@ -8701,12 +8874,17 @@ function createInbound(dispatch, sessionId) {
8701
8874
  holdSnapshot: () => {
8702
8875
  holdingSnapshot = true;
8703
8876
  heldSnapshot = null;
8877
+ heldAfter = [];
8704
8878
  },
8705
8879
  releaseSnapshot: (deliver) => {
8706
8880
  holdingSnapshot = false;
8707
- const view = heldSnapshot;
8881
+ const snapshot = heldSnapshot;
8882
+ const after = heldAfter;
8708
8883
  heldSnapshot = null;
8709
- if (deliver && view) dispatch({ type: "runSnapshot", view, sessionId });
8884
+ heldAfter = [];
8885
+ if (!deliver) return;
8886
+ if (snapshot) deliverSnapshot(snapshot);
8887
+ for (const next of after) next();
8710
8888
  }
8711
8889
  };
8712
8890
  }
@@ -8730,7 +8908,7 @@ function dispatchLifecycle(dispatch, event, sessionId) {
8730
8908
  dispatch({ type: "taskCheckpoint", taskId: String(event.taskId), title: String(event.taskTitle), summary: event.summary, sessionId });
8731
8909
  return;
8732
8910
  case "review_needed":
8733
- dispatch({ type: "notice", message: "Plan needs your sign-off \u2014 /approve to continue." });
8911
+ dispatch({ type: "notice", message: "Plan needs your sign-off: /approve to continue." });
8734
8912
  return;
8735
8913
  case "review_approved":
8736
8914
  dispatch({ type: "notice", message: "Plan approved." });
@@ -8750,6 +8928,10 @@ function dispatchLifecycle(dispatch, event, sessionId) {
8750
8928
  case "notice":
8751
8929
  dispatch({ type: "notice", message: event.message, level: event.level });
8752
8930
  return;
8931
+ // The pane draws it apart from the plan's rows; the reducer folds it into the run view.
8932
+ case "plan_edit_held":
8933
+ dispatch({ type: "heldEdit", held: event.held, sessionId });
8934
+ return;
8753
8935
  case "isolation_handoff":
8754
8936
  dispatch({ type: "isolationHandoff", handoff: { repos: event.repos, landed: event.landed, ...event.deferredTo ? { deferredTo: event.deferredTo } : {} }, sessionId });
8755
8937
  return;
@@ -8814,7 +8996,7 @@ function isPlannerTurn(effect) {
8814
8996
  }
8815
8997
  function explain(err) {
8816
8998
  if (err instanceof DaemonError && err.code === "session_not_found") {
8817
- return "This server is no longer holding that session \u2014 it was deleted, or the server restarted. Reload it with /sessions if it is still listed.";
8999
+ return "This server is no longer holding that session: it was deleted, or the server restarted. Reload it with /sessions if it is still listed.";
8818
9000
  }
8819
9001
  return err instanceof Error ? err.message : String(err);
8820
9002
  }
@@ -8830,7 +9012,7 @@ function plannerModelNotice(recall2) {
8830
9012
  case "remembered":
8831
9013
  return `Restored ${recall2.model}.`;
8832
9014
  case "catalog-default":
8833
- return `Using its default model, ${recall2.model} \u2014 pick another with /model.`;
9015
+ return `Using its default model, ${recall2.model}. Pick another with /model.`;
8834
9016
  case "none":
8835
9017
  return "Pick a model with /model.";
8836
9018
  }
@@ -8892,13 +9074,13 @@ async function perform(effect, deps) {
8892
9074
  await refreshPlan(deps, sessionId);
8893
9075
  return;
8894
9076
  }
8895
- const request = action === "cancel" || action === "retry" || action === "force-start" ? () => api.taskControl(sessionId, taskId, action) : action === "resume-now" ? () => api.resumeNow(sessionId, taskId) : action === "skip" ? () => api.skipTask(sessionId, taskId) : (0, import_core47.markRequestFor)(action) === "uncomplete" ? () => api.markTaskIncomplete(sessionId, taskId) : () => api.markTaskComplete(sessionId, taskId);
9077
+ const request = action === "cancel" || action === "retry" || action === "force-start" ? () => api.taskControl(sessionId, taskId, action) : action === "resume-now" ? () => api.resumeNow(sessionId, taskId) : action === "skip" ? () => api.skipTask(sessionId, taskId) : (0, import_core50.markRequestFor)(action) === "uncomplete" ? () => api.markTaskIncomplete(sessionId, taskId) : () => api.markTaskComplete(sessionId, taskId);
8896
9078
  await (effect.watch ? withExecutionStream(deps, sessionId, request) : request());
8897
9079
  await refreshPlan(deps, sessionId);
8898
9080
  return;
8899
9081
  }
8900
9082
  case "addTask": {
8901
- const fields = (0, import_core47.newTaskFields)(effect.title);
9083
+ const fields = (0, import_core50.newTaskFields)(effect.title);
8902
9084
  if (!fields) return;
8903
9085
  await api.addTask(effect.sessionId, { ...fields });
8904
9086
  await refreshPlan(deps, effect.sessionId);
@@ -8923,7 +9105,7 @@ async function perform(effect, deps) {
8923
9105
  try {
8924
9106
  await api.respondToApproval(effect.sessionId, effect.approvalId, effect.granted);
8925
9107
  } catch {
8926
- dispatch({ type: "failed", message: "Could not deliver the approval answer \u2014 the planner will treat it as denied." });
9108
+ dispatch({ type: "failed", message: "Could not deliver the approval answer: the planner will treat it as denied." });
8927
9109
  }
8928
9110
  return;
8929
9111
  }
@@ -8932,7 +9114,7 @@ async function perform(effect, deps) {
8932
9114
  try {
8933
9115
  await api.respondToApproval(effect.sessionId, effect.approvalId, effect.answer);
8934
9116
  } catch {
8935
- dispatch({ type: "failed", message: "Could not deliver the answer \u2014 the task is still waiting for approval." });
9117
+ dispatch({ type: "failed", message: "Could not deliver the answer: the task is still waiting for approval." });
8936
9118
  }
8937
9119
  return;
8938
9120
  }
@@ -9006,8 +9188,13 @@ async function perform(effect, deps) {
9006
9188
  return;
9007
9189
  }
9008
9190
  case "setPlanner": {
9009
- const meta = (0, import_core47.getProviderMeta)(effect.provider);
9191
+ const meta = (0, import_core50.getProviderMeta)(effect.provider);
9010
9192
  if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
9193
+ if ((0, import_core50.isCliProvider)(effect.provider)) {
9194
+ const installed = ((await api.getRunners()).runners ?? []).map((r) => r.id);
9195
+ const refusal = uninstalledPlannerRefusal(effect.provider, installed, import_core50.TERMINAL_UI_COMMANDS.pickPlanner);
9196
+ if (refusal) throw new Error(refusal);
9197
+ }
9011
9198
  const env = { AI_PROVIDER: effect.provider };
9012
9199
  const settings = await api.updateSettings({ env });
9013
9200
  const recall2 = plannerSwitchRecall(settings);
@@ -9018,7 +9205,7 @@ async function perform(effect, deps) {
9018
9205
  });
9019
9206
  dispatch({
9020
9207
  type: "notice",
9021
- message: `${(0, import_core47.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
9208
+ message: `${(0, import_core50.isCliProvider)(effect.provider) ? `Planning with ${meta.label}: no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
9022
9209
  });
9023
9210
  await loadModels(deps);
9024
9211
  return;
@@ -9046,7 +9233,7 @@ async function perform(effect, deps) {
9046
9233
  dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
9047
9234
  return;
9048
9235
  case "setApiKey": {
9049
- const meta = (0, import_core47.isApiProvider)(effect.provider) ? (0, import_core47.getProviderMeta)(effect.provider) : void 0;
9236
+ const meta = (0, import_core50.isApiProvider)(effect.provider) ? (0, import_core50.getProviderMeta)(effect.provider) : void 0;
9050
9237
  if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
9051
9238
  await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
9052
9239
  deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
@@ -9089,12 +9276,12 @@ async function perform(effect, deps) {
9089
9276
  }
9090
9277
  if (effect.maxParallel !== void 0) await saveMaxParallel(deps, effect.maxParallel);
9091
9278
  const overridden = effect.autonomous !== void 0 && deps.saveAutonomy(effect.autonomous).overridden;
9092
- dispatch({ type: "notice", message: overridden ? `${effect.message} \u2014 ORDEWELL_AUTONOMOUS_MODE in the environment decides the autonomy level until it is unset.` : effect.message });
9279
+ dispatch({ type: "notice", message: overridden ? `${effect.message}: ORDEWELL_AUTONOMOUS_MODE in the environment decides the autonomy level until it is unset.` : effect.message });
9093
9280
  return;
9094
9281
  }
9095
9282
  case "setAutonomous": {
9096
9283
  const { overridden } = deps.saveAutonomy(effect.enabled);
9097
- const level = (0, import_core47.autonomyLevelLabel)(effect.enabled);
9284
+ const level = (0, import_core50.autonomyLevelLabel)(effect.enabled);
9098
9285
  dispatch({ type: "notice", message: overridden ? `${level} is saved, but ORDEWELL_AUTONOMOUS_MODE in the environment decides the level until it is unset.` : `Autonomy level: ${level} for new plans.` });
9099
9286
  return;
9100
9287
  }
@@ -9132,8 +9319,8 @@ async function perform(effect, deps) {
9132
9319
  }
9133
9320
  const { plan, goal } = adopted2;
9134
9321
  dispatch({ type: "sessionStarted", sessionId: effect.sessionId, goal });
9135
- inbound.releaseSnapshot(true);
9136
9322
  dispatch(restoredChat(plan, effect.sessionId));
9323
+ inbound.releaseSnapshot(true);
9137
9324
  dispatch({ type: "planUpdated", plan, sessionId: effect.sessionId });
9138
9325
  dispatch({ type: "notice", message: `Loaded "${goal || effect.sessionId}".` });
9139
9326
  return;
@@ -9146,7 +9333,7 @@ async function perform(effect, deps) {
9146
9333
  dispatch({ type: "sessionForked", sessionId: fork.sessionId, goal: fork.goal });
9147
9334
  dispatch(restoredChat(fork.plan, fork.sessionId));
9148
9335
  dispatch({ type: "planUpdated", plan: fork.plan, sessionId: fork.sessionId });
9149
- dispatch({ type: "notice", message: `Forked ${effect.sessionId} into ${fork.sessionId} \u2014 you are in the fork now. /sessions to go back.` });
9336
+ dispatch({ type: "notice", message: `Forked ${effect.sessionId} into ${fork.sessionId}: you are in the fork now. /sessions to go back.` });
9150
9337
  return;
9151
9338
  }
9152
9339
  case "loadRewindTargets":
@@ -9167,7 +9354,7 @@ async function perform(effect, deps) {
9167
9354
  dispatch(restoredChat(fork.plan, fork.sessionId));
9168
9355
  dispatch({ type: "planUpdated", plan: fork.plan, sessionId: fork.sessionId });
9169
9356
  dispatch({ type: "inputPrefilled", text: fork.rewoundMessage, sessionId: fork.sessionId });
9170
- dispatch({ type: "notice", message: `Forked ${effect.sessionId} into ${fork.sessionId} from before that message \u2014 the original is kept (/sessions to go back). The message is ready to edit and resend.` });
9357
+ dispatch({ type: "notice", message: `Forked ${effect.sessionId} into ${fork.sessionId} from before that message: the original is kept (/sessions to go back). The message is ready to edit and resend.` });
9171
9358
  return;
9172
9359
  }
9173
9360
  // The redrawn transcript opens with the summary entry, which is what the
@@ -9258,7 +9445,7 @@ async function saveMaxParallel(deps, limit) {
9258
9445
  function copySelection(deps, text) {
9259
9446
  if (!text) return;
9260
9447
  const lines = text.split("\n").length;
9261
- const command = (0, import_core47.clipboardCopyCommand)(deps.hasBin);
9448
+ const command = (0, import_core50.clipboardCopyCommand)(deps.hasBin);
9262
9449
  if (command) {
9263
9450
  try {
9264
9451
  (deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
@@ -9270,7 +9457,7 @@ function copySelection(deps, text) {
9270
9457
  deps.writeTerminal(osc52(text));
9271
9458
  deps.dispatch({
9272
9459
  type: "notice",
9273
- message: `Copied ${lines} ${lines === 1 ? "line" : "lines"} through the terminal \u2014 no clipboard tool here (pbcopy, xclip, xsel or wl-copy), so it only lands if your terminal supports OSC 52.`
9460
+ message: `Copied ${lines} ${lines === 1 ? "line" : "lines"} through the terminal: no clipboard tool here (pbcopy, xclip, xsel or wl-copy), so it only lands if your terminal supports OSC 52.`
9274
9461
  });
9275
9462
  }
9276
9463
  async function withExecutionStream(deps, sessionId, request) {
@@ -9363,7 +9550,7 @@ function lastAssistantMessage(plan) {
9363
9550
  const history = plan?.conversationHistory ?? [];
9364
9551
  for (let i = history.length - 1; i >= 0; i--) {
9365
9552
  const entry = history[i];
9366
- if ((0, import_core47.isUserMessage)(entry)) return null;
9553
+ if ((0, import_core50.isUserMessage)(entry)) return null;
9367
9554
  if (entry.role === "assistant" && entry.kind === void 0) {
9368
9555
  const { content, timestamp } = entry;
9369
9556
  return { content, ...timestamp ? { timestamp } : {} };
@@ -9718,7 +9905,7 @@ async function handleTui(subArgs) {
9718
9905
  }
9719
9906
  const workspace = flag(subArgs, "--workspace") || process.cwd();
9720
9907
  try {
9721
- (0, import_core48.assertWorkspaceExists)(workspace);
9908
+ (0, import_core51.assertWorkspaceExists)(workspace);
9722
9909
  } catch {
9723
9910
  console.error(
9724
9911
  `Workspace "${workspace}" does not exist.
@@ -9727,7 +9914,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
9727
9914
  );
9728
9915
  process.exit(1);
9729
9916
  }
9730
- const discoveredSkills = (0, import_core48.createSkillsService)(workspace, new import_core48.EnvConfig().workspaceRepos).listSkills();
9917
+ const discoveredSkills = (0, import_core51.createSkillsService)(workspace, new import_core51.EnvConfig().workspaceRepos).listSkills();
9731
9918
  registerSkillCommands(discoveredSkills);
9732
9919
  const taskSkills = discoveredSkills.filter((s) => s.appliesTo === "task").map((s) => ({ name: s.name, description: s.description }));
9733
9920
  const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
@@ -9737,24 +9924,37 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
9737
9924
  const api = new ApiClient(port, workspace);
9738
9925
  let terminal;
9739
9926
  const mouseCapture = process.env.ORDEWELL_TUI_MOUSE !== "false" && process.env.ORDEWELL_TUI_MOUSE !== "0";
9927
+ const settings = new import_core51.SettingsService();
9740
9928
  const app = createApp({
9741
9929
  // The level the daemon plans with: the saved one, unless the environment overrides it (ADR-0030).
9742
- initial: { workspace, autonomous: new import_core48.SettingsService().autonomousMode(), mouseCapture, taskSkills },
9930
+ // The run settings are seeded from the same file because the first frame is
9931
+ // drawn before the daemon answers, and the welcome's autorun line is only
9932
+ // truthful once they are known. That holds only for a daemon this process
9933
+ // spawned, which inherits its settings path; an attached one may read a
9934
+ // different file (ORDEWELL_SETTINGS_PATH), and nothing it reports names that
9935
+ // file, so the welcome stays silent until its own settings arrive.
9936
+ initial: {
9937
+ workspace,
9938
+ autonomous: settings.autonomousMode(),
9939
+ ...ownedAtLaunch ? { runSettings: settings.getRunSettings(), runSettingsKnown: true } : {},
9940
+ mouseCapture,
9941
+ taskSkills
9942
+ },
9743
9943
  draw: (frame2) => terminal?.draw(frame2),
9744
9944
  perform: (effect) => runEffect(effect, {
9745
9945
  api,
9746
9946
  workspace,
9747
9947
  port,
9748
9948
  dispatch: (action) => app.dispatch(action),
9749
- newSessionId: () => (0, import_core48.mintSessionId)(),
9949
+ newSessionId: () => (0, import_core51.mintSessionId)(),
9750
9950
  setEnvVar: (key, value) => {
9751
9951
  writeEnvVar(findEnvFile(), key, value);
9752
9952
  process.env[key] = value;
9753
9953
  },
9754
9954
  saveAutonomy: (enabled) => {
9755
- const settings = new import_core48.SettingsService();
9756
- settings.setAutonomousMode(enabled);
9757
- return { overridden: settings.autonomousModeOverridden() };
9955
+ const settings2 = new import_core51.SettingsService();
9956
+ settings2.setAutonomousMode(enabled);
9957
+ return { overridden: settings2.autonomousModeOverridden() };
9758
9958
  },
9759
9959
  setMouseCapture: (enabled) => terminal?.setMouse(enabled),
9760
9960
  // The alt screen is up with the cursor hidden, so an OSC 52 write shows
@@ -9866,7 +10066,7 @@ var COMMANDS = {
9866
10066
  // src/main.ts
9867
10067
  var TUI_FLAGS = /* @__PURE__ */ new Set(["--workspace", "--port"]);
9868
10068
  async function main() {
9869
- (0, import_core49.migrateOldConfigDir)();
10069
+ (0, import_core52.migrateOldConfigDir)();
9870
10070
  moveAutonomyToSettings();
9871
10071
  loadEnvFile();
9872
10072
  const argv = process.argv.slice(2);