@ordewell/cli 0.10.2 → 0.10.3

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 +691 -493
  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,11 +4989,11 @@ 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);
4996
+ const entries = (0, import_core31.plannerBackendEntries)(usability, state.configuredProviders);
4930
4997
  const items = entries.map((entry) => ({
4931
4998
  id: entry.id,
4932
4999
  label: entry.label,
@@ -4951,14 +5018,14 @@ function cliVersion() {
4951
5018
  }
4952
5019
 
4953
5020
  // src/tui/planKeys.ts
4954
- var import_core30 = require("@ordewell/core");
5021
+ var import_core32 = require("@ordewell/core");
4955
5022
  var PLAN_KEYS = {
4956
5023
  move: { keys: "up / down", meaning: "move between tasks", group: "Navigation" },
4957
5024
  scroll: { keys: "pgup / pgdn", meaning: "scroll the plan", group: "Navigation" },
4958
5025
  expand: { keys: "\u2192", meaning: "reveal a task's subtasks, then edit its prompt", group: "Navigation", hint: "\u2192 expand" },
4959
5026
  log: {
4960
5027
  keys: "enter",
4961
- meaning: "open the task's log and message its runner \u2014 or, at a checkpoint, answer it",
5028
+ meaning: "open the task's log and message its runner or, at a checkpoint, answer it",
4962
5029
  group: "Navigation",
4963
5030
  hint: (task) => task?.status === "awaiting_user" && task.awaitingReason === "checkpoint" ? "enter answer" : "enter log"
4964
5031
  },
@@ -4978,7 +5045,7 @@ var PLAN_KEYS = {
4978
5045
  meaning: "cancel the task's runner, or keep a task paused on a usage limit from resuming on its own",
4979
5046
  group: "Run",
4980
5047
  // `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"
5048
+ hint: (task) => task && (0, import_core32.resumeLabel)(task) ? "c don't resume" : "c cancel"
4982
5049
  },
4983
5050
  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
5051
  resolve: { keys: "x", meaning: "resolve a merge conflict", group: "Run", hint: "x resolve conflict" },
@@ -4988,7 +5055,7 @@ var PLAN_KEYS = {
4988
5055
  meaning: "mark the task done, or take it back",
4989
5056
  group: "Status",
4990
5057
  // `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"
5058
+ hint: (task) => task && (0, import_core32.markAction)(task) === "uncomplete" ? "m undone" : "m done"
4992
5059
  },
4993
5060
  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
5061
  remove: { keys: "d", meaning: "remove the task", group: "Status" },
@@ -5015,7 +5082,7 @@ function hintOf(id, task) {
5015
5082
  }
5016
5083
  function taskKeyIds(task, runActive) {
5017
5084
  if (!task) return ["add"];
5018
- const row = (0, import_core30.taskRowView)(task);
5085
+ const row = (0, import_core32.taskRowView)(task);
5019
5086
  if (row.conflict) return ["resolve", "log"];
5020
5087
  if (row.actions.includes("resume-now") || row.resumes) {
5021
5088
  return [...row.actions.includes("resume-now") ? ["resume"] : [], ...row.resumes ? ["cancel"] : [], "skip", "log"];
@@ -5043,7 +5110,7 @@ function planFooterHints(task, runActive) {
5043
5110
  }
5044
5111
 
5045
5112
  // src/tui/layout.ts
5046
- var import_core32 = require("@ordewell/core");
5113
+ var import_core34 = require("@ordewell/core");
5047
5114
  var CHROME_ROWS = 1;
5048
5115
  function chatInputWrap(state) {
5049
5116
  const active = state.focus === "chat" && !state.overlay;
@@ -5069,7 +5136,7 @@ function footerHints(state) {
5069
5136
  }
5070
5137
  return [...escHint ? [escHint] : [], ...planFooterHints(selected, state.status === "executing")];
5071
5138
  }
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"] : [];
5139
+ 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
5140
  return [
5074
5141
  "/help",
5075
5142
  ...checkpointHints,
@@ -5128,12 +5195,12 @@ function queuedTaskBubble(message, target, sends, cols) {
5128
5195
  }
5129
5196
  function taskActivity(task, working) {
5130
5197
  if (task?.status === "completed" || task?.status === "failed") return task.status;
5131
- const approvals = (0, import_core32.approvalLabel)(task?.awaitingApproval);
5198
+ const approvals = (0, import_core34.approvalLabel)(task?.awaitingApproval);
5132
5199
  if (approvals) return inRow(approvals);
5133
5200
  if (working) return "working";
5134
5201
  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 ");
5202
+ const resumes = (0, import_core34.resumeLabel)(task);
5203
+ return [inRow((0, import_core34.awaitingLabel)(task) ?? "Waiting for your input"), resumes ? inRow(resumes) : ""].filter(Boolean).join(" \xB7 ");
5137
5204
  }
5138
5205
  return task?.status ?? "";
5139
5206
  }
@@ -5146,7 +5213,7 @@ function taskHeaderLines(state, tv, cols) {
5146
5213
  taskActivity(task, tv.view.working)
5147
5214
  ].filter(Boolean);
5148
5215
  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"];
5216
+ 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
5217
  if (approval && waitingCheckpoint(task)) hint.push("then the checkpoint");
5151
5218
  const index = tv.attempts.indexOf(tv.attempt);
5152
5219
  if (tv.attempts.length > 1) hint.push(`alt\u2190/\u2192 attempt ${index >= 0 ? index + 1 : 1}/${tv.attempts.length}`);
@@ -5176,15 +5243,15 @@ function chatActivity(state) {
5176
5243
  const own = findTask(state.tasks, tv.taskId);
5177
5244
  const task = state.tasks.find((t) => t.subtasks?.some((s) => s.id === tv.taskId)) ?? own;
5178
5245
  if (!task) return null;
5179
- if ((0, import_core26.isTaskRunning)(task) || own && (0, import_core26.isTaskRunning)(own)) return { label: "Working\u2026", busy: true };
5246
+ if ((0, import_core28.isTaskRunning)(task) || own && (0, import_core28.isTaskRunning)(own)) return { label: "Working\u2026", busy: true };
5180
5247
  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 };
5248
+ if (at && !Number.isNaN(at.getTime())) return { label: `paused \xB7 resumes ${(0, import_core34.resumeTime)(at)}`, busy: false };
5182
5249
  return null;
5183
5250
  }
5184
5251
  function runHints(state) {
5185
5252
  const waiting = state.tasks.filter((t) => t.status === "awaiting_user").length;
5186
5253
  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"] : [];
5254
+ return state.runMerge?.paused ? ["paused for Merge all: /handoff merge", "paused \xB7 /handoff merge", "merge paused"] : [];
5188
5255
  }
5189
5256
  var runHint = (state) => runHints(state)[0] ?? "";
5190
5257
  function packHints(hints, cols) {
@@ -5240,7 +5307,7 @@ function taskUsageFooter(state, tv, blocks2, cols) {
5240
5307
  const reported = task?.attemptUsage ?? [];
5241
5308
  const onScreen = tv.attempt > 0 ? reported.find((a) => a.attempt === tv.attempt) : reported.at(-1);
5242
5309
  if (!task || !onScreen) return tokenRow(blocks2, cols);
5243
- const usage = (0, import_core32.taskRowView)({ ...task, attemptUsage: [onScreen] }).usage;
5310
+ const usage = (0, import_core34.taskRowView)({ ...task, attemptUsage: [onScreen] }).usage;
5244
5311
  if (!usage) return tokenRow(blocks2, cols);
5245
5312
  const models = usage.byModel ?? [];
5246
5313
  return [
@@ -5253,17 +5320,17 @@ function chatScrollMax(state) {
5253
5320
  }
5254
5321
  function plannerSetup(state) {
5255
5322
  const provider = state.plannerProvider;
5256
- const registration = (0, import_core32.getProviderMeta)(provider);
5323
+ const registration = (0, import_core34.getProviderMeta)(provider);
5257
5324
  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);
5325
+ const runner = (0, import_core34.runnerForProvider)(provider);
5326
+ const ready = runner ? state.runners.some((r) => r.id === runner) : (0, import_core34.isApiProvider)(provider) && state.configuredProviders.includes(provider);
5260
5327
  return { label: registration.label, ready };
5261
5328
  }
5262
5329
  var SETUP_KEY_COLS = 10;
5263
5330
  var SETUP_VALUE_COLS = 32;
5264
5331
  function runSettingsValue(state) {
5265
5332
  const { supervisorLevel, mergeAtGatesAutomatically, autorun } = state.runSettings;
5266
- const level = `supervisor ${import_core31.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}`;
5333
+ const level = `supervisor ${import_core33.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}`;
5267
5334
  return [
5268
5335
  supervisorLevel === "act" ? style.yellow(level) : level,
5269
5336
  `auto-merge ${mergeAtGatesAutomatically ? "on" : "off"}`,
@@ -5343,18 +5410,22 @@ var twoLines = ({ orde, well }) => [...orde, ...well.map((row) => style.brand(ro
5343
5410
  function welcomeLogo(restRows, rows, cols) {
5344
5411
  return [bannerLines(), oneLine2(BANNER_WORDMARK), twoLines(BANNER_WORDMARK)].find((lines) => lines.every((line) => width(line) <= cols - 1) && lines.length + restRows <= rows) ?? [];
5345
5412
  }
5413
+ var DETECTING = "Detecting coding agents\u2026";
5346
5414
  function welcomeLines(state, rows, cols) {
5347
5415
  const planner = plannerSetup(state);
5348
5416
  const enabledRunners = state.runners.filter((r) => r.enabled).map((r) => r.name);
5349
5417
  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)`);
5418
+ const detecting = !state.plannerKnown || !state.runnersKnown;
5419
+ 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
5420
  const prompt = wrap(style.bold("Describe a goal to start planning."), Math.max(1, cols - 1));
5421
+ 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.");
5422
+ const approval = approvalText ? wrap(approvalText, Math.max(1, cols - 1)) : [];
5352
5423
  const setup = [
5353
5424
  style.bold("Setup"),
5354
5425
  setupRow("Planner", plannerValue, "/planner", cols),
5355
5426
  setupRow(
5356
5427
  "Runners",
5357
- enabledRunners.length > 0 ? enabledRunners.join(", ") : style.yellow("none enabled"),
5428
+ detecting ? style.grey(DETECTING) : enabledRunners.length > 0 ? enabledRunners.join(", ") : style.yellow("none enabled"),
5358
5429
  "/runners",
5359
5430
  cols
5360
5431
  ),
@@ -5362,15 +5433,15 @@ function welcomeLines(state, rows, cols) {
5362
5433
  setupRow("Run", runSettingsValue(state), "/settings", cols),
5363
5434
  ""
5364
5435
  ];
5365
- const notice = canPlan ? [] : [
5436
+ const notice = canPlan || detecting || !state.providersKnown ? [] : [
5366
5437
  ...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.`),
5438
+ 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
5439
  Math.max(1, cols - 1)
5369
5440
  ),
5370
5441
  ""
5371
5442
  ];
5372
5443
  const tail = [...notice, style.grey("/help all commands"), workspaceLine(state.workspace, cols)];
5373
- const rest = ["", ...prompt, "", ...setup, ...tail];
5444
+ const rest = ["", ...prompt, ...approval, "", ...setup, ...tail];
5374
5445
  const logo = welcomeLogo(rest.length, rows, cols);
5375
5446
  return logo.length > 0 ? [...logo, ...rest] : rest.slice(1);
5376
5447
  }
@@ -5398,7 +5469,7 @@ var STATUS_ICON = {
5398
5469
  var inRow = (label) => label.charAt(0).toLowerCase() + label.slice(1);
5399
5470
  function planLayout(state, rows, cols) {
5400
5471
  const done = state.tasks.filter((t) => t.status === "completed").length;
5401
- const totalUsage = (0, import_core32.planUsage)(state.tasks);
5472
+ const totalUsage = (0, import_core34.planUsage)(state.tasks);
5402
5473
  const unmerged = state.runMerge?.handoff.landed.length ?? 0;
5403
5474
  const count = `${done}/${state.tasks.length}`;
5404
5475
  const hints = runHints(state);
@@ -5408,7 +5479,7 @@ function planLayout(state, rows, cols) {
5408
5479
  totalUsage.attempts ? style.grey(truncate(`Tasks total: ${taskUsageText(totalUsage)}`, cols)) : ""
5409
5480
  ];
5410
5481
  if (unmerged > 0) {
5411
- const landed = style.dim(truncate(`${unmerged} landed task${unmerged === 1 ? "" : "s"} not merged \u2014 /handoff merge`, cols));
5482
+ const landed = style.dim(truncate(`${unmerged} landed task${unmerged === 1 ? "" : "s"} not merged: /handoff merge`, cols));
5412
5483
  if (totalUsage.attempts) lines.push(landed);
5413
5484
  else lines[1] = landed;
5414
5485
  }
@@ -5425,10 +5496,21 @@ function planLayout(state, rows, cols) {
5425
5496
  lines.push(...renderedTask.lines);
5426
5497
  rowSpans.push({ start: taskStart, end: lines.length - 1 });
5427
5498
  });
5499
+ lines.push(...heldEditLines(state, cols));
5428
5500
  const selected = rowSpans[state.selectedTask] ?? { start: firstRowLine, end: firstRowLine };
5429
5501
  const anchor = editorLine === void 0 ? selected : { start: editorLine, end: editorLine };
5430
5502
  return { lines, rowSpans, anchor, rows, maxScroll: Math.max(0, lines.length - rows) };
5431
5503
  }
5504
+ var heldMark = (mark) => mark === "added" ? style.green("+") : mark === "changed" ? style.yellow("~") : style.red("-");
5505
+ function heldEditLines(state, cols) {
5506
+ const rows = (state.run.heldEdit?.tasks ?? []).flatMap(({ label, mark, task }) => {
5507
+ if (!mark) return [];
5508
+ const title = mark === "removed" ? style.strike(task.title) : task.title;
5509
+ return [truncate(` ${heldMark(mark)} ${style.grey(label.padStart(2))} ${title}`, cols)];
5510
+ });
5511
+ if (rows.length === 0) return [];
5512
+ return ["", style.dim(truncate("Proposed: lands when this reply ends", cols)), ...rows];
5513
+ }
5432
5514
  var clampOffset = (layout, offset) => Math.max(0, Math.min(layout.maxScroll, offset));
5433
5515
  function revealOffset(layout, offset, span) {
5434
5516
  const at = clampOffset(layout, offset);
@@ -5476,7 +5558,7 @@ function planScrollExtent(state) {
5476
5558
  }
5477
5559
  function taskLines(state, planRow, index, cols) {
5478
5560
  const { task, parent } = planRow;
5479
- const row = (0, import_core32.taskRowView)(task, { parent, tasks: state.tasks, modes: modesForTask(state.modesByRunner, task) });
5561
+ const row = (0, import_core34.taskRowView)(task, { parent, tasks: state.tasks, modes: modesForTask(state.modesByRunner, task) });
5480
5562
  const selected = state.focus === "plan" && index === state.selectedTask;
5481
5563
  const expanded = state.expandedTaskId === task.id;
5482
5564
  const rawIcon = row.status === "running" ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[row.status];
@@ -5504,10 +5586,9 @@ function taskLines(state, planRow, index, cols) {
5504
5586
  const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${row.autonomous ? style.yellow(" \u26A1") : ""}` : "";
5505
5587
  const runner = task.assignedRunner ?? "";
5506
5588
  const bodyPad = parent ? " " : " ";
5507
- const activity = row.approvals ? `${inRow(row.approvals)} \u2014 enter opens it` : row.awaiting ? inRow(row.awaiting) : "";
5589
+ const activity = row.approvals ? `${inRow(row.approvals)}: enter opens it` : row.awaiting ? inRow(row.awaiting) : "";
5508
5590
  const skills = (task.skills ?? []).join(" \xB7 ");
5509
- const origin = task.addedBy === "supervisor" ? "added by supervisor" : "";
5510
- const meta = [activity, origin, runner, model];
5591
+ const meta = [activity, runner, model];
5511
5592
  const variant = task.type === "ai" ? sanitize(task.assignedModel?.thinkingEffort ?? "") : "";
5512
5593
  const withVariant = variant ? [...meta, variant] : meta;
5513
5594
  const metaLine = variant && width(`${continuation}${withVariant.filter(Boolean).join(" \xB7 ")}`) <= cols ? withVariant : meta;
@@ -5516,7 +5597,7 @@ function taskLines(state, planRow, index, cols) {
5516
5597
  if (row.conflict) {
5517
5598
  const where = row.conflict.repo ? ` in ${row.conflict.repo}` : "";
5518
5599
  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)));
5600
+ lines.push(style.red(truncate(`${bodyPad}\u26A0 merge conflict${where}${files}: its work is kept on its own branch`, cols)));
5520
5601
  }
5521
5602
  if (row.repairing) {
5522
5603
  const files = row.repairing.files ? ` in ${row.repairing.files}` : "";
@@ -5524,22 +5605,25 @@ function taskLines(state, planRow, index, cols) {
5524
5605
  lines.push(style.yellow(truncate(`${bodyPad}\u21BB repairing conflict${files}${attempt}`, cols)));
5525
5606
  }
5526
5607
  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)));
5608
+ lines.push(style.yellow(truncate(`${bodyPad}\u23F8 waits for Merge all: ${row.mergeGate.join(", ")} not merged into your branch yet`, cols)));
5609
+ }
5610
+ if (row.waitsForApproval) {
5611
+ 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
5612
  }
5529
5613
  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)));
5614
+ 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
5615
  }
5532
5616
  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)));
5617
+ 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
5618
  }
5535
5619
  const resumable = row.actions.includes("resume-now");
5536
5620
  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)));
5621
+ 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
5622
  } else if (resumable) {
5539
- lines.push(style.yellow(truncate(`${bodyPad}\u23F8 waits for the usage limit to reset \u2014 r resume now`, cols)));
5623
+ lines.push(style.yellow(truncate(`${bodyPad}\u23F8 waits for the usage limit to reset: r resume now`, cols)));
5540
5624
  }
5541
5625
  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)));
5626
+ lines.push(style.yellow(truncate(`${bodyPad}\u26A0 an ops task changed tracked files: check them, then m done or /retry`, cols)));
5543
5627
  }
5544
5628
  if (row.repaired?.landed) {
5545
5629
  lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${row.repaired.files}`, cols)));
@@ -5639,14 +5723,14 @@ function commandSheetBody(cols) {
5639
5723
  body.push(
5640
5724
  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
5725
  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")
5726
+ 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
5727
  );
5644
5728
  body.push(
5645
5729
  "",
5646
5730
  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"),
5731
+ style.grey(" ctrl-s sends now: interrupts the running step, then delivers the composer text or, with it empty, the highlighted pending message"),
5648
5732
  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"),
5733
+ 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
5734
  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
5735
  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
5736
  style.grey(" alt\u2190/\u2192 changes attempt \xB7 esc returns")
@@ -5665,8 +5749,8 @@ function helpScrollMax(state) {
5665
5749
  }
5666
5750
 
5667
5751
  // src/tui/render.ts
5668
- var import_core33 = require("@ordewell/core");
5669
- var import_core34 = require("@ordewell/core");
5752
+ var import_core35 = require("@ordewell/core");
5753
+ var import_core36 = require("@ordewell/core");
5670
5754
  function render(state) {
5671
5755
  const { rows, cols } = state;
5672
5756
  const rowsForBody = bodyRows(state);
@@ -5765,8 +5849,8 @@ function runSettingsBadge(state) {
5765
5849
  const { supervisorLevel, mergeAtGatesAutomatically, autorun } = state.runSettings;
5766
5850
  const paint = (text, warn) => warn ? style.yellow(text) : style.grey(text);
5767
5851
  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}` : ""}`;
5852
+ const actual = run?.unsupervised ? "unsupervised" : run && run.level !== supervisorLevel ? import_core36.SUPERVISOR_LEVEL_LABEL[run.level] : null;
5853
+ const level = `supervisor ${import_core36.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}${actual ? ` \u2192 ${actual}` : ""}`;
5770
5854
  return [
5771
5855
  paint(level, supervisorLevel === "act"),
5772
5856
  ...mergeAtGatesAutomatically ? [style.grey("auto-merge")] : [],
@@ -5960,16 +6044,16 @@ function renderHandoff(state, overlay, rows, cols) {
5960
6044
  const { lines, scroll } = overlay.diff;
5961
6045
  const shown = lines.slice(scroll, scroll + room).map((line) => paintDiffLine(truncate(line, Math.max(1, cols - 2))));
5962
6046
  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);
6047
+ return frame(`Diff: ${handoffBranch(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
5964
6048
  }
5965
6049
  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}`;
6050
+ const repaired = task.repairedFiles?.length ? style.yellow(`: repaired (${(0, import_core35.capConflictFiles)(task.repairedFiles)})`) : "";
6051
+ return ` ${style.green("\u2713")} ${(0, import_core35.titledTaskRefOf)(state.tasks, task.taskId) ?? (0, import_core35.titledTaskRef)(task)}${repaired}`;
5968
6052
  });
5969
6053
  const actions = handoffActions(handoff).map((action, index) => {
5970
6054
  const active = index === overlay.index;
5971
6055
  const caret = active ? style.brand("\u276F") : " ";
5972
- return `${caret} ${active ? style.bold(action.label) : action.label}${style.grey(` \u2014 ${action.hint}`)}`;
6056
+ return `${caret} ${active ? style.bold(action.label) : action.label}${style.grey(`: ${action.hint}`)}`;
5973
6057
  });
5974
6058
  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
6059
  return frame(
@@ -6138,10 +6222,13 @@ function frame(title, body, rows, cols) {
6138
6222
  }
6139
6223
 
6140
6224
  // src/tui/reducer.ts
6141
- var import_core46 = require("@ordewell/core");
6225
+ var import_core49 = require("@ordewell/core");
6142
6226
 
6143
6227
  // src/tui/reducers/overlays.ts
6144
- var import_core45 = require("@ordewell/core");
6228
+ var import_core48 = require("@ordewell/core");
6229
+
6230
+ // src/tui/reducers/approvals.ts
6231
+ var import_core37 = require("@ordewell/core");
6145
6232
 
6146
6233
  // src/tui/reducers/shared.ts
6147
6234
  function step(state, effects = []) {
@@ -6157,7 +6244,7 @@ function clampSelection(index, rows) {
6157
6244
  }
6158
6245
  function withSession(state, run) {
6159
6246
  if (!state.sessionId) {
6160
- return fail3(state, "No active plan \u2014 describe a goal first, or load a session with /sessions.");
6247
+ return fail3(state, "No active plan: describe a goal first, or load a session with /sessions.");
6161
6248
  }
6162
6249
  return run(state.sessionId);
6163
6250
  }
@@ -6186,6 +6273,13 @@ function dropApproval(state, approvalId) {
6186
6273
  if (open?.id !== approvalId) return { ...state, pendingApprovals: pending };
6187
6274
  return showNextApproval({ ...state, pendingApprovals: pending, overlay: null });
6188
6275
  }
6276
+ function syncApprovals(state, waiting) {
6277
+ const ids = new Set(waiting.map((request) => request.id));
6278
+ const open = state.overlay?.kind === "approval" ? [state.overlay.request] : [];
6279
+ const answered = [...open, ...state.pendingApprovals].filter((request) => !ids.has(request.id));
6280
+ const dropped = answered.reduce((current, request) => dropApproval(current, request.id), { ...state, conversation: (0, import_core37.reconcileApprovals)(state.conversation, waiting) });
6281
+ return waiting.reduce((current, { id, kind, subject, scope, detail }) => kind === "runner_tool" ? current : enqueueApproval(current, { id, kind, subject, scope, ...detail ? { detail } : {} }), dropped);
6282
+ }
6189
6283
  function showNextApproval(state) {
6190
6284
  const [next, ...rest] = state.pendingApprovals;
6191
6285
  if (!next) return { ...state, overlay: null };
@@ -6200,10 +6294,10 @@ function handleApprovalKey(state, overlay, key) {
6200
6294
  }
6201
6295
 
6202
6296
  // src/tui/reducers/commands.ts
6203
- var import_core44 = require("@ordewell/core");
6297
+ var import_core47 = require("@ordewell/core");
6204
6298
 
6205
6299
  // src/tui/reducers/plannerStepper.ts
6206
- var import_core35 = require("@ordewell/core");
6300
+ var import_core38 = require("@ordewell/core");
6207
6301
  var offered = (items) => new Set(items.filter((i) => !i.disabled).map((i) => i.id));
6208
6302
  function preferredModel(state, provider) {
6209
6303
  const listed = offered(plannerModelItems(state, provider, ""));
@@ -6222,7 +6316,7 @@ var effortLabel = (label) => /\beffort$/i.test(label) ? label : `${label} effort
6222
6316
  var plannerStepper = {
6223
6317
  title: "Planner",
6224
6318
  advanceOnEnter: true,
6225
- hint: "Who researches your goal and writes the plan. Coding agents use their own subscription \u2014 no API key.",
6319
+ hint: "Who researches your goal and writes the plan. Coding agents use their own subscription. No API key.",
6226
6320
  steps: [
6227
6321
  {
6228
6322
  id: "harness",
@@ -6251,7 +6345,7 @@ var plannerStepper = {
6251
6345
  const model = plannerModelItems(state, provider, "").find((m) => m.id === modelId);
6252
6346
  const level = plannerEffortItems(state, provider, modelId, "").find((e) => e.id === effort);
6253
6347
  const parts = [
6254
- (0, import_core35.getProviderMeta)(provider)?.label ?? provider,
6348
+ (0, import_core38.getProviderMeta)(provider)?.label ?? provider,
6255
6349
  model?.label ?? modelId,
6256
6350
  ...effort ? [effortLabel(level?.label ?? effort)] : []
6257
6351
  ];
@@ -6266,7 +6360,7 @@ var plannerStepper = {
6266
6360
  };
6267
6361
 
6268
6362
  // src/tui/reducers/stepper.ts
6269
- var import_core36 = require("@ordewell/core");
6363
+ var import_core39 = require("@ordewell/core");
6270
6364
  function openStepper(state, definition) {
6271
6365
  const { choices, lists } = settle(state, definition, {});
6272
6366
  const first = lists.findIndex((list2) => list2 !== null);
@@ -6289,7 +6383,7 @@ function handleStepperKey(state, stepper, key) {
6289
6383
  const reopen = (next) => step({ ...state, overlay: { kind: "stepper", stepper: next } });
6290
6384
  const { definition, current, entry, choices } = stepper;
6291
6385
  const def = definition.steps[current];
6292
- const typed = def.number && entry ? (0, import_core36.parseCount)(entry.text, def.number.min) : null;
6386
+ const typed = def.number && entry ? (0, import_core39.parseCount)(entry.text, def.number.min) : null;
6293
6387
  const { lists } = settle(state, definition, choices);
6294
6388
  const items = matchingItems(stepper.items, stepper.filter);
6295
6389
  const before = lastIndexBefore(lists, current);
@@ -6326,7 +6420,7 @@ function handleStepperKey(state, stepper, key) {
6326
6420
  function confirm(state, stepper, item) {
6327
6421
  if (!item) return step(state);
6328
6422
  if (item.disabled) {
6329
- return step(say(state, "error", item.detail ? `${item.label} \u2014 ${item.detail}` : `${item.label} is not available.`));
6423
+ return step(say(state, "error", item.detail ? `${item.label}: ${item.detail}` : `${item.label} is not available.`));
6330
6424
  }
6331
6425
  return choose(state, stepper, item.id);
6332
6426
  }
@@ -6355,7 +6449,7 @@ function settle(state, definition, wanted) {
6355
6449
  const lists = definition.steps.map((def) => {
6356
6450
  if (def.skip?.(state, { ...choices })) return null;
6357
6451
  if (def.number) {
6358
- choices[def.id] = String((0, import_core36.parseCount)(wanted[def.id], def.number.min) ?? def.number.current(state, { ...choices }));
6452
+ choices[def.id] = String((0, import_core39.parseCount)(wanted[def.id], def.number.min) ?? def.number.current(state, { ...choices }));
6359
6453
  return [];
6360
6454
  }
6361
6455
  const items = def.items(state, { ...choices });
@@ -6394,7 +6488,7 @@ function lastIndexBefore(lists, index) {
6394
6488
  }
6395
6489
 
6396
6490
  // src/tui/reducers/planPane.ts
6397
- var import_core39 = require("@ordewell/core");
6491
+ var import_core42 = require("@ordewell/core");
6398
6492
 
6399
6493
  // src/tui/reducers/pointer.ts
6400
6494
  var WHEEL_NOTCH = 3;
@@ -6472,13 +6566,13 @@ function handleMouseSelect(state, key) {
6472
6566
  }
6473
6567
 
6474
6568
  // src/tui/reducers/taskView.ts
6475
- var import_core37 = require("@ordewell/core");
6569
+ var import_core40 = require("@ordewell/core");
6476
6570
  function openTaskView(state, sessionId, taskId) {
6477
6571
  const task = findTask(state.tasks, taskId);
6478
6572
  if (!task) return step(say(state, "system", `No task ${taskId} in this plan.`));
6479
6573
  const taskView = {
6480
6574
  taskId,
6481
- view: import_core37.EMPTY_TASK_LOG,
6575
+ view: import_core40.EMPTY_TASK_LOG,
6482
6576
  attempts: [],
6483
6577
  attempt: 0,
6484
6578
  pending: [],
@@ -6508,13 +6602,13 @@ function applyLive(tv, attempt, events) {
6508
6602
  }
6509
6603
  next = {
6510
6604
  ...next,
6511
- view: import_core37.EMPTY_TASK_LOG,
6605
+ view: import_core40.EMPTY_TASK_LOG,
6512
6606
  attempt,
6513
6607
  attempts: next.attempts.includes(attempt) ? next.attempts : [...next.attempts, attempt]
6514
6608
  };
6515
6609
  }
6516
6610
  if (attempt < next.attempt) return next;
6517
- return clampQueueIndex({ ...next, view: events.reduce(import_core37.reduceTaskLog, next.view) });
6611
+ return clampQueueIndex({ ...next, view: events.reduce(import_core40.reduceTaskLog, next.view) });
6518
6612
  }
6519
6613
  function taskLogAbandoned(state) {
6520
6614
  const tv = state.taskView;
@@ -6535,14 +6629,14 @@ function announceApprovals(state, action) {
6535
6629
  if (state.taskView?.taskId === action.taskId) return state;
6536
6630
  const task = findTask(state.tasks, action.taskId);
6537
6631
  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);
6632
+ 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
6633
  return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
6540
6634
  }
6541
6635
  function announceCheckpoint(state, action) {
6542
6636
  const task = findTask(state.tasks, action.taskId) ?? state.tasks.find((t) => t.title === action.title);
6543
6637
  const label = task ? `Task ${task.order}` : action.title;
6544
6638
  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}`);
6639
+ const spoken = say(state, "system", `${label} asks: ${(0, import_core40.truncateCheckpointSummary)(action.summary)}: ${where}`);
6546
6640
  return { ...spoken, scroll: state.scroll };
6547
6641
  }
6548
6642
  function taskLogLoaded(state, action) {
@@ -6550,7 +6644,7 @@ function taskLogLoaded(state, action) {
6550
6644
  if (!tv || tv.taskId !== action.taskId) return state;
6551
6645
  const loaded = action.attempt >= 1 ? {
6552
6646
  ...tv,
6553
- view: (0, import_core37.replayTaskLog)(action.events),
6647
+ view: (0, import_core40.replayTaskLog)(action.events),
6554
6648
  attempts: action.attempts ?? tv.attempts,
6555
6649
  attempt: action.attempt
6556
6650
  } : { ...tv, attempts: action.attempts ?? tv.attempts };
@@ -6565,7 +6659,7 @@ function answerApproval(state, tv, sessionId, decision) {
6565
6659
  const approval = waitingApproval(tv);
6566
6660
  if (!approval?.approvalId) return step(say(state, "system", "Nothing is waiting for approval."));
6567
6661
  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."));
6662
+ return step(say(state, "system", "The runner offered no grant for the rest of this task: ctrl-y allows this call."));
6569
6663
  }
6570
6664
  const note = decision === "deny" ? state.editor.text.trim() : "";
6571
6665
  const answer = decision === "deny" ? { decision, ...note ? { note } : {} } : { decision };
@@ -6589,7 +6683,7 @@ function sendNow(state, tv, sessionId) {
6589
6683
  if (text) {
6590
6684
  const task = findTask(state.tasks, tv.taskId);
6591
6685
  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."));
6686
+ 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
6687
  }
6594
6688
  return step({ ...state, editor: commit(state.editor) }, [{ type: "forceSendTaskMessage", sessionId, taskId: tv.taskId, text }]);
6595
6689
  }
@@ -6624,7 +6718,7 @@ function handleTaskViewKey(state, key) {
6624
6718
  if (key.name === "ctrl-s") return sendNow(state, tv, state.sessionId);
6625
6719
  if (key.name === "ctrl-x") {
6626
6720
  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" }]);
6721
+ if (task && (0, import_core40.resumeLabel)(task)) return step(state, [{ type: "taskAction", sessionId: state.sessionId, taskId: tv.taskId, action: "cancel-resume" }]);
6628
6722
  return step(state, [{ type: "interruptTask", sessionId: state.sessionId, taskId: tv.taskId }]);
6629
6723
  }
6630
6724
  if (key.name === "ctrl-y") return answerWaiting(state, tv, state.sessionId, "allow");
@@ -6642,7 +6736,7 @@ function handleTaskViewKey(state, key) {
6642
6736
  }
6643
6737
 
6644
6738
  // src/tui/reducers/taskEdits.ts
6645
- var import_core38 = require("@ordewell/core");
6739
+ var import_core41 = require("@ordewell/core");
6646
6740
  var import_plan_utils3 = require("@ordewell/core/plan-utils");
6647
6741
  function openTaskRunnerPicker(state, task) {
6648
6742
  if (task.type !== "ai") return fail3(state, "Manual tasks do not run on an executor, so they have no runner.");
@@ -6672,7 +6766,7 @@ function confirmRemoveTask(state, task) {
6672
6766
  }
6673
6767
  function toggleTaskOps(state, task, subtask, to = !task.ops) {
6674
6768
  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.");
6769
+ 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
6770
  if (subtask) return fail3(state, "A subtask runs with its parent; make the parent an ops task instead.");
6677
6771
  if (!!task.ops === to) return fail3(state, `${refFor(state, task)} is already ${to ? "an ops" : "a change"} task.`);
6678
6772
  return step(state, [{
@@ -6753,7 +6847,7 @@ function openTaskModePicker(state, task) {
6753
6847
  overlay: {
6754
6848
  kind: "picker",
6755
6849
  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(" ")
6850
+ hint: [`Modes declared by ${task.assignedRunner}.`, (0, import_core41.codexSandboxNotice)(state.modesByRunner)?.line].filter(Boolean).join(" ")
6757
6851
  })
6758
6852
  }
6759
6853
  }
@@ -6905,7 +6999,7 @@ function taskOpsCommand(state, args) {
6905
6999
  }
6906
7000
  function addTask(state, title) {
6907
7001
  if (!state.sessionId) {
6908
- return fail3(state, "No active plan to add a task to \u2014 describe a goal first, then /add-task <title>.");
7002
+ return fail3(state, "No active plan to add a task to: describe a goal first, then /add-task <title>.");
6909
7003
  }
6910
7004
  if (!title) {
6911
7005
  return step({
@@ -6981,11 +7075,11 @@ function handlePlanKey(state, key) {
6981
7075
  if (!state.sessionId || key.name !== "char") return step(state);
6982
7076
  if (key.char === "E") return step(state, [{ type: "execute", sessionId: state.sessionId }]);
6983
7077
  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 ?? ""];
7078
+ const shortcut = key.char === "c" && (0, import_core42.resumeLabel)(task) ? "cancel-resume" : PLAN_SHORTCUTS[key.char ?? ""];
6985
7079
  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);
7080
+ return (0, import_core42.taskRowActions)(task).includes("resume-now") ? step(state, [taskActionEffect(state, state.sessionId, task.id, "resume-now")]) : step(state);
6987
7081
  }
6988
- const action = key.char === "m" ? (0, import_core39.markAction)(task) : shortcut;
7082
+ const action = key.char === "m" ? (0, import_core42.markAction)(task) : shortcut;
6989
7083
  if (action === "force-start" && task.mergeGate?.length) return confirmForceStartPastGate(state, task);
6990
7084
  if (action) {
6991
7085
  return step(state, [taskActionEffect(state, state.sessionId, task.id, action)]);
@@ -7025,9 +7119,9 @@ function commitTaskEdit(state, task, editor) {
7025
7119
  }
7026
7120
 
7027
7121
  // src/tui/reducers/turnQueue.ts
7028
- var import_core40 = require("@ordewell/core");
7122
+ var import_core43 = require("@ordewell/core");
7029
7123
  function drainQueue(state, settled) {
7030
- const next = (0, import_core40.drainNext)(state.queuedPrompts);
7124
+ const next = (0, import_core43.drainNext)(state.queuedPrompts);
7031
7125
  const ended = disarmStop({ ...settled, stopRequested: false, forceSendPending: null });
7032
7126
  if (!next) return step(ended);
7033
7127
  const spoken = say(ended, "user", next.text);
@@ -7042,7 +7136,7 @@ function restoreForced(state) {
7042
7136
  if (!forced) return state;
7043
7137
  const cleared = { ...state, forceSendPending: null };
7044
7138
  if (forced.queueIndex === void 0) {
7045
- const text = (0, import_core40.aheadOfDraft)(forced.text, state.editor.text);
7139
+ const text = (0, import_core43.aheadOfDraft)(forced.text, state.editor.text);
7046
7140
  return { ...cleared, editor: { ...state.editor, text, cursor: text.length } };
7047
7141
  }
7048
7142
  const queued = [...state.queuedPrompts];
@@ -7058,14 +7152,14 @@ function stopPlanning(state, sessionId) {
7058
7152
  return step(queueToEditor(cutTurn({ ...dismissed, stopArmed: false, stopRequested: true, forceSendPending: null })), [{ type: "cancelPlanning", sessionId }]);
7059
7153
  }
7060
7154
  function queueToEditor(state) {
7061
- const all = (0, import_core40.unsendAll)(state.queuedPrompts);
7155
+ const all = (0, import_core43.unsendAll)(state.queuedPrompts);
7062
7156
  if (!all) return state;
7063
7157
  return { ...state, queuedPrompts: all.rest, editor: { ...state.editor, text: all.text, cursor: all.text.length } };
7064
7158
  }
7065
7159
  function unsendLatest(state) {
7066
- const latest = (0, import_core40.unsendLatest)(state.queuedPrompts);
7160
+ const latest = (0, import_core43.unsendLatest)(state.queuedPrompts);
7067
7161
  if (!latest) return step(state);
7068
- const text = (0, import_core40.aheadOfDraft)(latest.text, state.editor.text);
7162
+ const text = (0, import_core43.aheadOfDraft)(latest.text, state.editor.text);
7069
7163
  return step({
7070
7164
  ...state,
7071
7165
  queuedPrompts: latest.rest,
@@ -7081,7 +7175,7 @@ function forceSendPlanner(state) {
7081
7175
  { type: "forceSendPlannerMessage", sessionId: state.sessionId, message: text }
7082
7176
  ]);
7083
7177
  }
7084
- const latest = (0, import_core40.unsendLatest)(state.queuedPrompts);
7178
+ const latest = (0, import_core43.unsendLatest)(state.queuedPrompts);
7085
7179
  if (!latest) return step(say(state, "system", "Nothing to send now: type a message, or queue one with enter."));
7086
7180
  const forced = { text: latest.text, queueIndex: latest.rest.length };
7087
7181
  return step({ ...state, queuedPrompts: latest.rest, stopRequested: false, forceSendPending: forced }, [
@@ -7097,7 +7191,7 @@ function plannerEscape(state) {
7097
7191
  }
7098
7192
 
7099
7193
  // src/tui/reducers/incoming.ts
7100
- var import_core41 = require("@ordewell/core");
7194
+ var import_core44 = require("@ordewell/core");
7101
7195
  var planText = (value, fallback = "") => sanitize(String(value ?? fallback));
7102
7196
  var planLabel = (value, fallback = "") => planText(value, fallback).replace(/\n+/g, " ");
7103
7197
  var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
@@ -7136,12 +7230,13 @@ function runTaskToView(t) {
7136
7230
  ...Array.isArray(model.availableVariants) ? { availableVariants: model.availableVariants.map((variant) => planLabel(variant)) } : {}
7137
7231
  } : void 0,
7138
7232
  isolation: t.isolation,
7139
- awaitingReason: t.status === "awaiting_user" && (0, import_core41.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
7233
+ awaitingReason: t.status === "awaiting_user" && (0, import_core44.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
7140
7234
  ...t.status === "awaiting_user" && t.awaitingReason === "usage-limit" && typeof t.resumeAt === "string" ? { resumeAt: t.resumeAt } : {},
7141
7235
  ...t.status === "skipped" && typeof t.skipReason === "string" ? { skipReason: sanitize(t.skipReason) } : {},
7142
7236
  checkpoint: typeof t.checkpoint === "string" ? sanitize(t.checkpoint) : void 0,
7143
7237
  continuable: t.continuable === true,
7144
7238
  awaitingApproval: t.awaitingApproval,
7239
+ ...t.waitsForApproval === true ? { waitsForApproval: true } : {},
7145
7240
  ...t.ops === true ? { ops: true } : {},
7146
7241
  ...Array.isArray(t.skills) && t.skills.length > 0 ? { skills: t.skills.map((name) => planLabel(name)) } : {},
7147
7242
  ...t.addedBy ? { addedBy: t.addedBy } : {},
@@ -7155,7 +7250,7 @@ function supervisorOfRun(runSettings) {
7155
7250
  const unsupervised = typeof runSettings.unsupervised === "string" ? runSettings.unsupervised : void 0;
7156
7251
  return {
7157
7252
  runSupervisor: {
7158
- level: (0, import_core41.isSupervisorLevel)(runSettings.supervisorLevel) ? runSettings.supervisorLevel : "off",
7253
+ level: (0, import_core44.isSupervisorLevel)(runSettings.supervisorLevel) ? runSettings.supervisorLevel : "off",
7159
7254
  ...unsupervised ? { unsupervised } : {}
7160
7255
  }
7161
7256
  };
@@ -7172,7 +7267,7 @@ function withRun(state, run) {
7172
7267
  };
7173
7268
  }
7174
7269
  function foldRun(state, input) {
7175
- const run = (0, import_core41.reduceRun)(state.run, input);
7270
+ const run = (0, import_core44.reduceRun)(state.run, input);
7176
7271
  return run === state.run ? state : withRun(state, run);
7177
7272
  }
7178
7273
  function mapRunTasks(tasks, map) {
@@ -7202,11 +7297,13 @@ function applySettings(state, settings) {
7202
7297
  ...state,
7203
7298
  orchestratorModel: typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : state.orchestratorModel,
7204
7299
  plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
7300
+ plannerKnown: state.plannerKnown || typeof settings.aiProvider === "string",
7205
7301
  plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
7206
7302
  plannerModels: settings.plannerModels && typeof settings.plannerModels === "object" ? settings.plannerModels : state.plannerModels,
7207
7303
  maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
7208
7304
  allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist,
7209
- runSettings: settings.run ?? state.runSettings
7305
+ runSettings: settings.run ?? state.runSettings,
7306
+ runSettingsKnown: state.runSettingsKnown || settings.run !== void 0
7210
7307
  };
7211
7308
  }
7212
7309
  function followSession(state, message) {
@@ -7220,21 +7317,21 @@ function followSession(state, message) {
7220
7317
  }
7221
7318
 
7222
7319
  // src/tui/reducers/runSettings.ts
7223
- var import_core42 = require("@ordewell/core");
7320
+ var import_core45 = require("@ordewell/core");
7224
7321
  function runSettingsEffect(state, changes) {
7225
7322
  return { type: "setRunSettings", changes, ...state.sessionId ? { sessionId: state.sessionId } : {} };
7226
7323
  }
7227
7324
  function supervisorCommand(state, args) {
7228
7325
  const [arg, value] = args;
7229
7326
  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>.`));
7327
+ return step(say(state, "system", `${(0, import_core45.supervisorSummary)(state.runSettings)}: /supervisor off|watch|act, /supervisor retries <n>, /supervisor reads <n>.`));
7231
7328
  }
7232
7329
  if (arg === "retries") {
7233
- const n = (0, import_core42.parseCount)(value, 0);
7330
+ const n = (0, import_core45.parseCount)(value, 0);
7234
7331
  return n === null ? fail3(state, "Usage: /supervisor retries <whole number, 0 or more>") : step(state, [runSettingsEffect(state, { supervisorRetryLimit: n })]);
7235
7332
  }
7236
7333
  if (arg === "reads") {
7237
- const n = (0, import_core42.parseCount)(value, 1);
7334
+ const n = (0, import_core45.parseCount)(value, 1);
7238
7335
  return n === null ? fail3(state, "Usage: /supervisor reads <whole number, 1 or more>") : step(state, [runSettingsEffect(state, { wakeUpReadBudget: n })]);
7239
7336
  }
7240
7337
  if (arg === "act") return confirmSupervisorAct(state);
@@ -7243,17 +7340,17 @@ function supervisorCommand(state, args) {
7243
7340
  }
7244
7341
  function automergeCommand(state, arg) {
7245
7342
  if (arg === void 0) {
7246
- return step(say(state, "system", `${(0, import_core42.automergeSummary)(state.runSettings)} \u2014 /automerge on|off.`));
7343
+ return step(say(state, "system", `${(0, import_core45.automergeSummary)(state.runSettings)}: /automerge on|off.`));
7247
7344
  }
7248
- const enabled = (0, import_core42.parseSwitch)(arg);
7345
+ const enabled = (0, import_core45.parseSwitch)(arg);
7249
7346
  if (enabled === null) return fail3(state, "Usage: /automerge [on|off]");
7250
7347
  return step(state, [runSettingsEffect(state, { mergeAtGatesAutomatically: enabled })]);
7251
7348
  }
7252
7349
  function autorunCommand(state, arg) {
7253
7350
  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.`));
7351
+ return step(say(state, "system", `${(0, import_core45.autorunSummary)(state.runSettings)}: /autorun on starts each plan the planner submits, without review.`));
7255
7352
  }
7256
- const enabled = (0, import_core42.parseSwitch)(arg);
7353
+ const enabled = (0, import_core45.parseSwitch)(arg);
7257
7354
  if (enabled === null) return fail3(state, "Usage: /autorun [on|off]");
7258
7355
  if (enabled) return confirmAutorun(state);
7259
7356
  return step(state, [runSettingsEffect(state, { autorun: false })]);
@@ -7265,7 +7362,7 @@ function confirmSupervisorAct(state) {
7265
7362
  kind: "confirm",
7266
7363
  title: "Let the supervisor act?",
7267
7364
  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}
7365
+ note: `${import_core45.ACT_RISK}
7269
7366
  It applies to this session's run now, and to every later run.`,
7270
7367
  action: { kind: "supervisor-act" }
7271
7368
  }
@@ -7278,14 +7375,14 @@ function confirmAutorun(state) {
7278
7375
  kind: "confirm",
7279
7376
  title: "Start plans without review?",
7280
7377
  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,
7378
+ note: import_core45.AUTORUN_RISK,
7282
7379
  action: { kind: "autorun-on" }
7283
7380
  }
7284
7381
  });
7285
7382
  }
7286
7383
 
7287
7384
  // src/tui/reducers/settings.ts
7288
- var import_core43 = require("@ordewell/core");
7385
+ var import_core46 = require("@ordewell/core");
7289
7386
  var SUPERVISOR_DETAIL = {
7290
7387
  off: "The planner speaks only when you write",
7291
7388
  watch: "The planner follows your runs and suggests what to do",
@@ -7310,7 +7407,7 @@ var SETTINGS = {
7310
7407
  {
7311
7408
  id: "guarded",
7312
7409
  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 "),
7410
+ detail: [`New plans run tasks in each runner's safer mode`, (0, import_core46.codexSandboxNotice)(state.modesByRunner)?.line].filter(Boolean).join(" \xB7 "),
7314
7411
  selected: !state.autonomous
7315
7412
  }
7316
7413
  ]
@@ -7320,7 +7417,7 @@ var SETTINGS = {
7320
7417
  label: "Supervisor",
7321
7418
  items: (state) => ["off", "watch", "act"].map((level) => ({
7322
7419
  id: level,
7323
- label: import_core43.SUPERVISOR_LEVEL_LABEL[level],
7420
+ label: import_core46.SUPERVISOR_LEVEL_LABEL[level],
7324
7421
  detail: SUPERVISOR_DETAIL[level],
7325
7422
  selected: state.runSettings.supervisorLevel === level
7326
7423
  }))
@@ -7374,12 +7471,12 @@ function saveChoices(state, choices) {
7374
7471
  const autonomous = choices.autonomy === "full";
7375
7472
  if (autonomous !== state.autonomous) {
7376
7473
  save2.autonomous = autonomous;
7377
- changed.push(`Autonomy ${(0, import_core43.autonomyLevelLabel)(autonomous)}`);
7474
+ changed.push(`Autonomy ${(0, import_core46.autonomyLevelLabel)(autonomous)}`);
7378
7475
  }
7379
7476
  const level = choices.supervisor;
7380
7477
  if (level !== state.runSettings.supervisorLevel) {
7381
7478
  run.supervisorLevel = level;
7382
- changed.push(`Supervisor ${import_core43.SUPERVISOR_LEVEL_LABEL[level]}`);
7479
+ changed.push(`Supervisor ${import_core46.SUPERVISOR_LEVEL_LABEL[level]}`);
7383
7480
  }
7384
7481
  if (choices.retries !== void 0 && Number(choices.retries) !== state.runSettings.supervisorRetryLimit) {
7385
7482
  run.supervisorRetryLimit = Number(choices.retries);
@@ -7406,7 +7503,7 @@ function saveChoices(state, choices) {
7406
7503
  }
7407
7504
  if (changed.length === 0) return step(state);
7408
7505
  const effect = { ...save2, message: `Settings: ${changed.join(" \xB7 ")}` };
7409
- const risks = [...run.supervisorLevel === "act" ? [import_core43.ACT_RISK] : [], ...run.autorun ? [import_core43.AUTORUN_RISK] : []];
7506
+ const risks = [...run.supervisorLevel === "act" ? [import_core46.ACT_RISK] : [], ...run.autorun ? [import_core46.AUTORUN_RISK] : []];
7410
7507
  if (risks.length === 0) return saveSettings(state, effect);
7411
7508
  return step({
7412
7509
  ...state,
@@ -7435,7 +7532,7 @@ function saveSettings(state, effect) {
7435
7532
  }
7436
7533
 
7437
7534
  // src/tui/reducers/commands.ts
7438
- var unknownCommand = (state, name) => fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
7535
+ var unknownCommand = (state, name) => fail3(state, `Unknown command: /${name}. Type /help to see what's available.`);
7439
7536
  function runCommand(state, { name, args }) {
7440
7537
  if (!findCommand(name)) return unknownCommand(state, name);
7441
7538
  switch (name) {
@@ -7504,7 +7601,7 @@ function runCommand(state, { name, args }) {
7504
7601
  case "save":
7505
7602
  return withSession(state, (sessionId) => step(state, [{ type: "saveSession", sessionId }]));
7506
7603
  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.");
7604
+ return args[0] ? step(state, [{ type: "loadSession", sessionId: args[0] }]) : fail3(state, "Usage: /load <session-id>: or run /sessions to pick one.");
7508
7605
  case "delete":
7509
7606
  return args[0] ? step(state, [{ type: "deleteSession", sessionId: args[0] }]) : fail3(state, "Usage: /delete <session-id>");
7510
7607
  case "add-task":
@@ -7528,7 +7625,7 @@ function runCommand(state, { name, args }) {
7528
7625
  const answer = args[1]?.toLowerCase();
7529
7626
  const reason = args.slice(2).join(" ").trim();
7530
7627
  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.");
7628
+ if (answer === "approve" && reason) return fail3(state, "Approving takes no note: reject with a reason to tell the task something.");
7532
7629
  return answerCheckpoint(state, sessionId, taskId, answer, reason);
7533
7630
  });
7534
7631
  case "task-runner":
@@ -7566,7 +7663,7 @@ function runCommand(state, { name, args }) {
7566
7663
  function withIdlePlanner(state, run) {
7567
7664
  return withSession(
7568
7665
  state,
7569
- (sessionId) => plannerInFlight(state) ? fail3(state, "The planner is still answering \u2014 wait for its reply, or /stop it first.") : run(sessionId)
7666
+ (sessionId) => plannerInFlight(state) ? fail3(state, "The planner is still answering: wait for its reply, or /stop it first.") : run(sessionId)
7570
7667
  );
7571
7668
  }
7572
7669
  function resolveToggle(arg, current) {
@@ -7597,8 +7694,8 @@ function newSession(state) {
7597
7694
  goal: "",
7598
7695
  taskView: null,
7599
7696
  planApproved: false,
7600
- conversation: import_core44.EMPTY_CONVERSATION,
7601
- turnGate: import_core44.NO_TURN,
7697
+ conversation: import_core47.EMPTY_CONVERSATION,
7698
+ turnGate: import_core47.NO_TURN,
7602
7699
  selectedTask: 0,
7603
7700
  expandedTaskId: null,
7604
7701
  taskEditor: null,
@@ -7609,19 +7706,21 @@ function newSession(state) {
7609
7706
  // Prompts belong to the session that raised them; the old planner is gone
7610
7707
  // and its pending requests deny on their own timeout.
7611
7708
  pendingApprovals: [],
7612
- queuedPrompts: import_core44.EMPTY_HOLD,
7709
+ queuedPrompts: import_core47.EMPTY_HOLD,
7613
7710
  stopArmed: false,
7614
7711
  forceSendPending: null,
7615
7712
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
7616
- }, import_core44.EMPTY_RUN), closeEffects);
7713
+ }, import_core47.EMPTY_RUN), closeEffects);
7617
7714
  }
7618
7715
  function setModel(state, args) {
7619
7716
  if (args[0] === "set" && args[1]) {
7620
- const runner = (0, import_core44.runnerForProvider)(state.plannerProvider);
7717
+ const runner = (0, import_core47.runnerForProvider)(state.plannerProvider);
7621
7718
  const known = runner ? modelsForRunner(state.models, runner) : [];
7622
7719
  if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
7623
7720
  return fail3(state, `${args[1]} was not discovered for ${runner}.`);
7624
7721
  }
7722
+ const refusal = known.length === 0 ? vendorModelRefusal(state.plannerProvider, args[1], import_core47.TERMINAL_UI_COMMANDS.pickPlanner) : null;
7723
+ if (refusal) return fail3(state, refusal);
7625
7724
  return step(state, [{ type: "setModel", modelId: args[1] }]);
7626
7725
  }
7627
7726
  if (args.length > 0 && args[0] !== "set") return fail3(state, "Usage: /model [set <model-id>]");
@@ -7640,7 +7739,7 @@ function setModel(state, args) {
7640
7739
  function setPlanner(state, args) {
7641
7740
  if (args[0]) {
7642
7741
  const id = args[0].toLowerCase();
7643
- if (!(0, import_core44.isPlannerBackend)(id)) {
7742
+ if (!(0, import_core47.isPlannerBackend)(id)) {
7644
7743
  return fail3(state, `Unknown planner: ${id}. Run /planner with no arguments to see the list.`);
7645
7744
  }
7646
7745
  return step(state, [{ type: "setPlanner", provider: id }]);
@@ -7672,7 +7771,7 @@ function setPlannerEffort(state, args) {
7672
7771
  function setKey(state, args) {
7673
7772
  if (args[0] === "set" && args[1]) {
7674
7773
  const provider = args[1].toLowerCase();
7675
- if (!(0, import_core44.isApiProvider)(provider)) {
7774
+ if (!(0, import_core47.isApiProvider)(provider)) {
7676
7775
  return fail3(state, `Unknown provider: ${provider}. Run /key with no arguments to see the list.`);
7677
7776
  }
7678
7777
  if (!args[2]) {
@@ -7683,16 +7782,16 @@ function setKey(state, args) {
7683
7782
  }
7684
7783
  return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
7685
7784
  }
7686
- const items = import_core44.PROVIDER_PRIORITY.map((id) => ({
7785
+ const items = import_core47.PROVIDER_PRIORITY.map((id) => ({
7687
7786
  id,
7688
- label: (0, import_core44.getProviderMeta)(id).label,
7689
- detail: (0, import_core44.getProviderMeta)(id).apiKeyEnvVar,
7787
+ label: (0, import_core47.getProviderMeta)(id).label,
7788
+ detail: (0, import_core47.getProviderMeta)(id).apiKeyEnvVar,
7690
7789
  selected: state.configuredProviders.includes(id)
7691
7790
  }));
7692
7791
  return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
7693
7792
  }
7694
7793
  function keyPrompt(provider) {
7695
- const meta = (0, import_core44.getProviderMeta)(provider);
7794
+ const meta = (0, import_core47.getProviderMeta)(provider);
7696
7795
  return {
7697
7796
  kind: "prompt",
7698
7797
  title: `${meta.label} API key`,
@@ -7768,14 +7867,14 @@ function runners(state, args) {
7768
7867
  } });
7769
7868
  }
7770
7869
  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);
7870
+ if (!arg) return step(say(state, "system", `Up to ${state.maxParallel} AI task${state.maxParallel === 1 ? "" : "s"} run at once: /parallel <n> changes it.`));
7871
+ const limit = (0, import_core47.parseMaxParallel)(arg);
7773
7872
  if (limit === null) return fail3(state, "Usage: /parallel [<number of tasks, 1 or more>]");
7774
7873
  return step({ ...state, maxParallel: limit }, [{ type: "setMaxParallel", limit }]);
7775
7874
  }
7776
7875
  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);
7876
+ 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.`));
7877
+ const enabled = (0, import_core47.parseAutonomyLevel)(arg);
7779
7878
  if (enabled === null) return fail3(state, "Usage: /auto [full|guarded]");
7780
7879
  return step({ ...state, autonomous: enabled }, [{ type: "setAutonomous", enabled }]);
7781
7880
  }
@@ -7788,7 +7887,7 @@ function setMouseCapture(state, arg) {
7788
7887
  // move it.
7789
7888
  { ...state, mouseCapture: enabled, selection: null },
7790
7889
  "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."
7890
+ 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
7891
  );
7793
7892
  return step(noted, [{ type: "setMouseCapture", enabled }]);
7794
7893
  }
@@ -7800,7 +7899,7 @@ function rewind(state, arg) {
7800
7899
  [{ type: "loadRewindTargets", sessionId }]
7801
7900
  );
7802
7901
  }
7803
- if (!/^\d+$/.test(arg)) return fail3(state, "Usage: /rewind [<message>] \u2014 or /rewind alone to pick one.");
7902
+ if (!/^\d+$/.test(arg)) return fail3(state, "Usage: /rewind [<message>]: or /rewind alone to pick one.");
7804
7903
  return step(state, [{ type: "loadRewindTargets", sessionId, pick: Number(arg) }]);
7805
7904
  });
7806
7905
  }
@@ -7811,7 +7910,7 @@ var REWIND_OPTIONS = [
7811
7910
  function pickRewindTarget(state, index) {
7812
7911
  return withIdlePlanner({ ...state, overlay: null }, () => {
7813
7912
  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.`);
7913
+ if (!target) return fail3({ ...state, overlay: null }, `No message ${index} to rewind to: /rewind alone lists them.`);
7815
7914
  return step({
7816
7915
  ...state,
7817
7916
  overlay: {
@@ -7903,7 +8002,7 @@ function handlePromptKey(state, overlay, key) {
7903
8002
  if (overlay.action.kind === "api-key") {
7904
8003
  return step(closed, [{ type: "setApiKey", provider: overlay.action.provider, key: value2 }]);
7905
8004
  }
7906
- if (!state.sessionId) return fail3(closed, "No active plan to add a task to \u2014 describe a goal first, then /add-task <title>.");
8005
+ if (!state.sessionId) return fail3(closed, "No active plan to add a task to: describe a goal first, then /add-task <title>.");
7907
8006
  return step(closed, [{ type: "addTask", sessionId: state.sessionId, title: value2 }]);
7908
8007
  }
7909
8008
  const value = editText(overlay.value, key);
@@ -7948,7 +8047,7 @@ function choose2(state, picker2, item) {
7948
8047
  return step(closed, [{
7949
8048
  type: "setRunners",
7950
8049
  changes,
7951
- message: names.length > 0 ? `Runners enabled: ${names.join(", ")}.` : "No runners enabled \u2014 the planner has nothing to assign work to."
8050
+ message: names.length > 0 ? `Runners enabled: ${names.join(", ")}.` : "No runners enabled: the planner has nothing to assign work to."
7952
8051
  }]);
7953
8052
  }
7954
8053
  if (picker2.action.kind === "set-task-deps") {
@@ -7970,7 +8069,7 @@ function choose2(state, picker2, item) {
7970
8069
  }
7971
8070
  if (!item) return step(state);
7972
8071
  if (item.disabled) {
7973
- return step(say(state, "error", item.detail ? `${item.label} \u2014 ${item.detail}` : `${item.label} is not available.`));
8072
+ return step(say(state, "error", item.detail ? `${item.label}: ${item.detail}` : `${item.label} is not available.`));
7974
8073
  }
7975
8074
  switch (picker2.action.kind) {
7976
8075
  case "set-model":
@@ -7984,7 +8083,7 @@ function choose2(state, picker2, item) {
7984
8083
  case "delete-session":
7985
8084
  return step(closed, [{ type: "deleteSession", sessionId: item.id }]);
7986
8085
  case "set-key":
7987
- return (0, import_core45.isApiProvider)(item.id) ? step({ ...state, overlay: keyPrompt(item.id) }) : step(closed);
8086
+ return (0, import_core48.isApiProvider)(item.id) ? step({ ...state, overlay: keyPrompt(item.id) }) : step(closed);
7988
8087
  case "choose-allowlist-runner": {
7989
8088
  const action = { kind: "set-allowlist", runner: item.id };
7990
8089
  const items = pickerItemsFor(state, action);
@@ -8060,7 +8159,7 @@ function reduceAction(state, action) {
8060
8159
  // (`task-1`), so nothing of the last one's — a merge gate, a checkpoint, its
8061
8160
  // tasks — may carry over into this.
8062
8161
  case "sessionStarted":
8063
- return step(withRun({ ...state, sessionId: action.sessionId, goal: action.goal, taskView: null }, import_core46.EMPTY_RUN));
8162
+ return step(withRun({ ...state, sessionId: action.sessionId, goal: action.goal, taskView: null }, import_core49.EMPTY_RUN));
8064
8163
  // The daemon registers a session only once planning succeeds; when it does
8065
8164
  // not, holding on to the id would send every next message to a session the
8066
8165
  // server never had.
@@ -8071,18 +8170,18 @@ function reduceAction(state, action) {
8071
8170
  goal: "",
8072
8171
  taskView: null,
8073
8172
  pendingApprovals: [],
8074
- queuedPrompts: import_core46.EMPTY_HOLD,
8173
+ queuedPrompts: import_core49.EMPTY_HOLD,
8075
8174
  stopArmed: false,
8076
8175
  forceSendPending: null,
8077
8176
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
8078
- }, import_core46.EMPTY_RUN));
8177
+ }, import_core49.EMPTY_RUN));
8079
8178
  // Deleting the session on screen stops it in the daemon; a view left on it
8080
8179
  // would answer every next message with a session-not-found.
8081
8180
  case "sessionDeleted":
8082
8181
  return action.sessionId === state.sessionId ? newSession(state) : step(state);
8083
8182
  case "chatRestored":
8084
8183
  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 });
8184
+ return step({ ...state, conversation: (0, import_core49.fromTranscript)(action.history, action.researchLog, action.plannerUsage, action.pendingTurns), scroll: 0 });
8086
8185
  case "planUpdated": {
8087
8186
  if (stale(state, action.sessionId)) return step(state);
8088
8187
  const settled = { ...showPlan(state, action.plan), planner: "idle" };
@@ -8103,10 +8202,14 @@ function reduceAction(state, action) {
8103
8202
  // its status line with it, as a status update would have set it.
8104
8203
  case "runSnapshot": {
8105
8204
  if (stale(state, action.sessionId)) return step(state);
8106
- const next = withRun(state, action.view);
8205
+ const run = withRun(state, action.view);
8206
+ const next = action.approvals ? syncApprovals(run, action.approvals) : run;
8107
8207
  const status = runStatus(next, next.tasks);
8108
8208
  return step({ ...next, status });
8109
8209
  }
8210
+ case "heldEdit":
8211
+ if (stale(state, action.sessionId)) return step(state);
8212
+ return step(foldRun(state, { type: "plan_edit_held", held: action.held }));
8110
8213
  case "taskLog":
8111
8214
  if (stale(state, action.sessionId)) return step(state);
8112
8215
  return step(announceApprovals(taskLogArrived(state, action), action));
@@ -8122,15 +8225,15 @@ function reduceAction(state, action) {
8122
8225
  case "taskStarted": {
8123
8226
  if (stale(state, action.sessionId)) return step(state);
8124
8227
  const existing = findTask(state.tasks, action.taskId);
8125
- if (existing && (0, import_core26.isTaskRunning)(existing)) return step(state);
8228
+ if (existing && (0, import_core28.isTaskRunning)(existing)) return step(state);
8126
8229
  const folded = foldRun(state, {
8127
8230
  type: "task_started",
8128
8231
  taskId: action.taskId,
8129
8232
  order: action.order ?? 0,
8130
8233
  title: action.title,
8131
- runner: action.runner ?? import_core46.DEFAULT_RUNNERS[0]
8234
+ runner: action.runner ?? import_core49.DEFAULT_RUNNERS[0]
8132
8235
  });
8133
- const spoken = say(folded, "system", (0, import_core46.taskStartedNotice)(action.title, action.runner));
8236
+ const spoken = say(folded, "system", (0, import_core49.taskStartedNotice)(action.title, action.runner));
8134
8237
  return step({ ...spoken, status: "executing" });
8135
8238
  }
8136
8239
  case "taskCheckpoint":
@@ -8176,13 +8279,13 @@ function reduceAction(state, action) {
8176
8279
  completed: state.tasks.filter((t) => t.status === "completed").length,
8177
8280
  total: state.tasks.length,
8178
8281
  failed: state.tasks.filter((t) => t.status === "failed").length,
8179
- blocked: (0, import_core46.blockingFailures)(state.tasks).size,
8282
+ blocked: (0, import_core49.blockingFailures)(state.tasks).size,
8180
8283
  skipped: state.tasks.filter((t) => t.status === "skipped").length
8181
8284
  };
8182
8285
  const failures = `${failed > 0 ? ` \xB7 ${failed} failed` : ""}${blocked ? ` \xB7 ${blocked} blocked` : ""}${skipped ? ` \xB7 ${skipped} skipped` : ""}`;
8183
8286
  const verb = action.stopped ? "stopped" : "finished";
8184
8287
  return step({
8185
- ...say(state, "system", `Execution ${verb} \u2014 ${completed}/${total} tasks complete${failures}.`),
8288
+ ...say(state, "system", `Execution ${verb}: ${completed}/${total} tasks complete${failures}.`),
8186
8289
  status: "idle"
8187
8290
  });
8188
8291
  }
@@ -8201,7 +8304,7 @@ function reduceAction(state, action) {
8201
8304
  case "executionLost": {
8202
8305
  if (stale(state, action.sessionId)) return step(state);
8203
8306
  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."),
8307
+ ...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
8308
  status: "idle"
8206
8309
  });
8207
8310
  }
@@ -8215,6 +8318,7 @@ function reduceAction(state, action) {
8215
8318
  models: action.models,
8216
8319
  orchestratorModels: action.orchestratorModels ?? state.orchestratorModels,
8217
8320
  configuredProviders: action.providers ?? state.configuredProviders,
8321
+ providersKnown: state.providersKnown || action.providers !== void 0,
8218
8322
  providerErrors: action.providerErrors ?? state.providerErrors,
8219
8323
  modesByRunner: action.modesByRunner ?? state.modesByRunner
8220
8324
  },
@@ -8240,11 +8344,11 @@ function reduceAction(state, action) {
8240
8344
  planner: "idle",
8241
8345
  planApproved: false,
8242
8346
  pendingApprovals: [],
8243
- queuedPrompts: import_core46.EMPTY_HOLD,
8347
+ queuedPrompts: import_core49.EMPTY_HOLD,
8244
8348
  stopArmed: false,
8245
8349
  forceSendPending: null,
8246
8350
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
8247
- }, import_core46.EMPTY_RUN));
8351
+ }, import_core49.EMPTY_RUN));
8248
8352
  // The rewound message comes back as a draft to edit and resend, so it lands
8249
8353
  // as if just typed: history is not being browsed, and nothing is parked.
8250
8354
  case "inputPrefilled":
@@ -8258,6 +8362,7 @@ function reduceAction(state, action) {
8258
8362
  return step(refreshStepper(refillPicker({
8259
8363
  ...state,
8260
8364
  runners: action.runners,
8365
+ runnersKnown: true,
8261
8366
  orchestratorModel: action.orchestratorModel ?? state.orchestratorModel
8262
8367
  }, ["set-task-runner"])));
8263
8368
  case "failed": {
@@ -8316,7 +8421,7 @@ function showPlan(state, plan) {
8316
8421
  });
8317
8422
  }
8318
8423
  function runStatus(state, tasks) {
8319
- if (tasks.some(import_core26.isTaskRunning)) return "executing";
8424
+ if (tasks.some(import_core28.isTaskRunning)) return "executing";
8320
8425
  if (tasks.some((t) => t.status === "awaiting_user") || state.runMerge?.paused) return "executing";
8321
8426
  return "idle";
8322
8427
  }
@@ -8391,13 +8496,13 @@ function submit(state) {
8391
8496
  const command = parseRunnerCommand(text);
8392
8497
  const offered2 = runnerCommands(state.taskView);
8393
8498
  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.`;
8499
+ const notice = `/${command.name} is not a command ${task?.assignedRunner ?? "the runner"} offers this task: type / to see them.`;
8395
8500
  return step({ ...cleared, taskView: { ...state.taskView, notice } });
8396
8501
  }
8397
8502
  if (state.taskView.notice) cleared.taskView = { ...state.taskView, notice: void 0 };
8398
8503
  if (!command && waitingCheckpoint(task)) {
8399
8504
  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."));
8505
+ 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
8506
  }
8402
8507
  return answerCheckpoint(cleared, state.sessionId, taskId, "reject", text);
8403
8508
  }
@@ -8408,7 +8513,7 @@ function submit(state) {
8408
8513
  const builtIn = builtInCommand(text);
8409
8514
  if (builtIn) return runCommand(cleared, builtIn);
8410
8515
  if (plannerInFlight(state)) {
8411
- return step({ ...cleared, queuedPrompts: (0, import_core46.holdPrompt)(state.queuedPrompts, text) });
8516
+ return step({ ...cleared, queuedPrompts: (0, import_core49.holdPrompt)(state.queuedPrompts, text) });
8412
8517
  }
8413
8518
  const spoken = say(cleared, "user", text);
8414
8519
  const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
@@ -8483,7 +8588,7 @@ function createApp(deps) {
8483
8588
 
8484
8589
  // src/tui/effects.ts
8485
8590
  var import_child_process3 = require("child_process");
8486
- var import_core47 = require("@ordewell/core");
8591
+ var import_core50 = require("@ordewell/core");
8487
8592
 
8488
8593
  // src/tui/inbound.ts
8489
8594
  var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
@@ -8534,6 +8639,52 @@ function identity(event) {
8534
8639
  function replyKey(content, timestamp) {
8535
8640
  return timestamp ? `planner_message:${timestamp}:${content}` : `planner_message:content:${content}`;
8536
8641
  }
8642
+ function stampOf(frame2) {
8643
+ return frame2.stream !== void 0 && frame2.seq !== void 0 ? { stream: frame2.stream, seq: frame2.seq } : null;
8644
+ }
8645
+ var COVERED = /* @__PURE__ */ new Set(["plan_edit_held", "status_update", "task_updated", "approval_request", "approval_settled"]);
8646
+ var Delivered = class {
8647
+ constructor(window) {
8648
+ this.window = window;
8649
+ }
8650
+ window;
8651
+ stream = null;
8652
+ highest = 0;
8653
+ snapshot = 0;
8654
+ seen = /* @__PURE__ */ new Set();
8655
+ first({ stream, seq }) {
8656
+ this.follow(stream);
8657
+ if (seq <= this.highest - this.window || this.seen.has(seq)) return false;
8658
+ this.seen.add(seq);
8659
+ if (seq > this.highest) {
8660
+ this.highest = seq;
8661
+ for (const old of this.seen) if (old <= seq - this.window) this.seen.delete(old);
8662
+ }
8663
+ return true;
8664
+ }
8665
+ covered({ seq }) {
8666
+ return seq <= this.snapshot;
8667
+ }
8668
+ /** Whether a snapshot is as new as everything delivered, and so adopted; an older one would undo what came after it. */
8669
+ adopt({ stream, seq }) {
8670
+ this.follow(stream);
8671
+ if (seq < this.highest) return false;
8672
+ this.snapshot = seq;
8673
+ this.highest = seq;
8674
+ return true;
8675
+ }
8676
+ // A new stream is a restarted daemon: its numbers start over and mean nothing against the last one's.
8677
+ follow(stream) {
8678
+ if (stream === this.stream) return;
8679
+ this.stream = stream;
8680
+ this.highest = 0;
8681
+ this.snapshot = 0;
8682
+ this.seen.clear();
8683
+ }
8684
+ };
8685
+ function isClear(event) {
8686
+ return event.type === "plan_edit_held" && event.held === null || event.type === "approval_settled";
8687
+ }
8537
8688
  var Copies = class {
8538
8689
  constructor(cap) {
8539
8690
  this.cap = cap;
@@ -8604,6 +8755,7 @@ function inboundFor(api, dispatch, sessionId) {
8604
8755
  return inbound;
8605
8756
  }
8606
8757
  function createInbound(dispatch, sessionId) {
8758
+ const delivered = new Delivered(256);
8607
8759
  const copies = new Copies(64);
8608
8760
  const shown = new WindowSet(64);
8609
8761
  const pending = new WindowSet(16);
@@ -8612,6 +8764,7 @@ function createInbound(dispatch, sessionId) {
8612
8764
  let timer = null;
8613
8765
  let holdingSnapshot = false;
8614
8766
  let heldSnapshot = null;
8767
+ let heldAfter = [];
8615
8768
  const flush = () => {
8616
8769
  if (timer !== null) {
8617
8770
  clearTimeout(timer);
@@ -8636,8 +8789,17 @@ function createInbound(dispatch, sessionId) {
8636
8789
  shown.add(message.content);
8637
8790
  dispatch({ type: "sessionMessage", message, sessionId });
8638
8791
  };
8792
+ const fresh = (id, event) => {
8793
+ const stamp = stampOf(event);
8794
+ if (stamp) return delivered.first(stamp) && !(COVERED.has(event.type) && delivered.covered(stamp));
8795
+ return isClear(event) || copies.first(id, identity(event));
8796
+ };
8639
8797
  const acceptReply = (id, event) => {
8640
8798
  const key = replyKey(event.content, event.timestamp);
8799
+ if (stampOf(event)) {
8800
+ if (fresh(id, event) && !pending.take(key)) showReply(event);
8801
+ return;
8802
+ }
8641
8803
  if (pending.take(key)) {
8642
8804
  copies.credit(id, key);
8643
8805
  return;
@@ -8645,12 +8807,15 @@ function createInbound(dispatch, sessionId) {
8645
8807
  if (!copies.first(id, key)) return;
8646
8808
  showReply(event);
8647
8809
  };
8810
+ const deliverSnapshot = ({ view, approvals }) => {
8811
+ dispatch({ type: "runSnapshot", view, ...approvals ? { approvals } : {}, sessionId });
8812
+ };
8648
8813
  const deliverConversation = (id, event) => {
8649
8814
  if (event.type === "planner_message") {
8650
8815
  acceptReply(id, event);
8651
8816
  return;
8652
8817
  }
8653
- if (!copies.first(id, identity(event))) return;
8818
+ if (!fresh(id, event)) return;
8654
8819
  if (event.type === "plan_generated") {
8655
8820
  dispatch({ type: "sessionMessage", message: event, sessionId });
8656
8821
  dispatch({ type: "planBroadcast", plan: event.plan, sessionId });
@@ -8661,12 +8826,18 @@ function createInbound(dispatch, sessionId) {
8661
8826
  const receive = (kind, id, event) => {
8662
8827
  if (event.type === "run_snapshot") {
8663
8828
  flush();
8664
- if (holdingSnapshot) heldSnapshot = event.view;
8665
- else dispatch({ type: "runSnapshot", view: event.view, sessionId });
8829
+ const stamp = stampOf(event);
8830
+ if (stamp && !delivered.adopt(stamp)) return;
8831
+ if (holdingSnapshot) heldSnapshot = event;
8832
+ else deliverSnapshot(event);
8833
+ return;
8834
+ }
8835
+ if (holdingSnapshot) {
8836
+ heldAfter.push(() => receive(kind, id, event));
8666
8837
  return;
8667
8838
  }
8668
8839
  if (isStreamed(event)) {
8669
- if (kind === "planning") hold(event);
8840
+ if (kind === "planning" && (!stampOf(event) || fresh(id, event))) hold(event);
8670
8841
  return;
8671
8842
  }
8672
8843
  flush();
@@ -8674,7 +8845,7 @@ function createInbound(dispatch, sessionId) {
8674
8845
  deliverConversation(id, event);
8675
8846
  return;
8676
8847
  }
8677
- if (!copies.first(id, identity(event))) return;
8848
+ if (!fresh(id, event)) return;
8678
8849
  dispatchLifecycle(dispatch, event, sessionId);
8679
8850
  };
8680
8851
  let subscription = 0;
@@ -8701,12 +8872,17 @@ function createInbound(dispatch, sessionId) {
8701
8872
  holdSnapshot: () => {
8702
8873
  holdingSnapshot = true;
8703
8874
  heldSnapshot = null;
8875
+ heldAfter = [];
8704
8876
  },
8705
8877
  releaseSnapshot: (deliver) => {
8706
8878
  holdingSnapshot = false;
8707
- const view = heldSnapshot;
8879
+ const snapshot = heldSnapshot;
8880
+ const after = heldAfter;
8708
8881
  heldSnapshot = null;
8709
- if (deliver && view) dispatch({ type: "runSnapshot", view, sessionId });
8882
+ heldAfter = [];
8883
+ if (!deliver) return;
8884
+ if (snapshot) deliverSnapshot(snapshot);
8885
+ for (const next of after) next();
8710
8886
  }
8711
8887
  };
8712
8888
  }
@@ -8730,7 +8906,7 @@ function dispatchLifecycle(dispatch, event, sessionId) {
8730
8906
  dispatch({ type: "taskCheckpoint", taskId: String(event.taskId), title: String(event.taskTitle), summary: event.summary, sessionId });
8731
8907
  return;
8732
8908
  case "review_needed":
8733
- dispatch({ type: "notice", message: "Plan needs your sign-off \u2014 /approve to continue." });
8909
+ dispatch({ type: "notice", message: "Plan needs your sign-off: /approve to continue." });
8734
8910
  return;
8735
8911
  case "review_approved":
8736
8912
  dispatch({ type: "notice", message: "Plan approved." });
@@ -8750,6 +8926,10 @@ function dispatchLifecycle(dispatch, event, sessionId) {
8750
8926
  case "notice":
8751
8927
  dispatch({ type: "notice", message: event.message, level: event.level });
8752
8928
  return;
8929
+ // The pane draws it apart from the plan's rows; the reducer folds it into the run view.
8930
+ case "plan_edit_held":
8931
+ dispatch({ type: "heldEdit", held: event.held, sessionId });
8932
+ return;
8753
8933
  case "isolation_handoff":
8754
8934
  dispatch({ type: "isolationHandoff", handoff: { repos: event.repos, landed: event.landed, ...event.deferredTo ? { deferredTo: event.deferredTo } : {} }, sessionId });
8755
8935
  return;
@@ -8814,7 +8994,7 @@ function isPlannerTurn(effect) {
8814
8994
  }
8815
8995
  function explain(err) {
8816
8996
  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.";
8997
+ 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
8998
  }
8819
8999
  return err instanceof Error ? err.message : String(err);
8820
9000
  }
@@ -8830,7 +9010,7 @@ function plannerModelNotice(recall2) {
8830
9010
  case "remembered":
8831
9011
  return `Restored ${recall2.model}.`;
8832
9012
  case "catalog-default":
8833
- return `Using its default model, ${recall2.model} \u2014 pick another with /model.`;
9013
+ return `Using its default model, ${recall2.model}. Pick another with /model.`;
8834
9014
  case "none":
8835
9015
  return "Pick a model with /model.";
8836
9016
  }
@@ -8892,13 +9072,13 @@ async function perform(effect, deps) {
8892
9072
  await refreshPlan(deps, sessionId);
8893
9073
  return;
8894
9074
  }
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);
9075
+ 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
9076
  await (effect.watch ? withExecutionStream(deps, sessionId, request) : request());
8897
9077
  await refreshPlan(deps, sessionId);
8898
9078
  return;
8899
9079
  }
8900
9080
  case "addTask": {
8901
- const fields = (0, import_core47.newTaskFields)(effect.title);
9081
+ const fields = (0, import_core50.newTaskFields)(effect.title);
8902
9082
  if (!fields) return;
8903
9083
  await api.addTask(effect.sessionId, { ...fields });
8904
9084
  await refreshPlan(deps, effect.sessionId);
@@ -8923,7 +9103,7 @@ async function perform(effect, deps) {
8923
9103
  try {
8924
9104
  await api.respondToApproval(effect.sessionId, effect.approvalId, effect.granted);
8925
9105
  } catch {
8926
- dispatch({ type: "failed", message: "Could not deliver the approval answer \u2014 the planner will treat it as denied." });
9106
+ dispatch({ type: "failed", message: "Could not deliver the approval answer: the planner will treat it as denied." });
8927
9107
  }
8928
9108
  return;
8929
9109
  }
@@ -8932,7 +9112,7 @@ async function perform(effect, deps) {
8932
9112
  try {
8933
9113
  await api.respondToApproval(effect.sessionId, effect.approvalId, effect.answer);
8934
9114
  } catch {
8935
- dispatch({ type: "failed", message: "Could not deliver the answer \u2014 the task is still waiting for approval." });
9115
+ dispatch({ type: "failed", message: "Could not deliver the answer: the task is still waiting for approval." });
8936
9116
  }
8937
9117
  return;
8938
9118
  }
@@ -9006,8 +9186,13 @@ async function perform(effect, deps) {
9006
9186
  return;
9007
9187
  }
9008
9188
  case "setPlanner": {
9009
- const meta = (0, import_core47.getProviderMeta)(effect.provider);
9189
+ const meta = (0, import_core50.getProviderMeta)(effect.provider);
9010
9190
  if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
9191
+ if ((0, import_core50.isCliProvider)(effect.provider)) {
9192
+ const installed = ((await api.getRunners()).runners ?? []).map((r) => r.id);
9193
+ const refusal = uninstalledPlannerRefusal(effect.provider, installed, import_core50.TERMINAL_UI_COMMANDS.pickPlanner);
9194
+ if (refusal) throw new Error(refusal);
9195
+ }
9011
9196
  const env = { AI_PROVIDER: effect.provider };
9012
9197
  const settings = await api.updateSettings({ env });
9013
9198
  const recall2 = plannerSwitchRecall(settings);
@@ -9018,7 +9203,7 @@ async function perform(effect, deps) {
9018
9203
  });
9019
9204
  dispatch({
9020
9205
  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)}`
9206
+ message: `${(0, import_core50.isCliProvider)(effect.provider) ? `Planning with ${meta.label}: no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
9022
9207
  });
9023
9208
  await loadModels(deps);
9024
9209
  return;
@@ -9046,7 +9231,7 @@ async function perform(effect, deps) {
9046
9231
  dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
9047
9232
  return;
9048
9233
  case "setApiKey": {
9049
- const meta = (0, import_core47.isApiProvider)(effect.provider) ? (0, import_core47.getProviderMeta)(effect.provider) : void 0;
9234
+ const meta = (0, import_core50.isApiProvider)(effect.provider) ? (0, import_core50.getProviderMeta)(effect.provider) : void 0;
9050
9235
  if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
9051
9236
  await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
9052
9237
  deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
@@ -9089,12 +9274,12 @@ async function perform(effect, deps) {
9089
9274
  }
9090
9275
  if (effect.maxParallel !== void 0) await saveMaxParallel(deps, effect.maxParallel);
9091
9276
  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 });
9277
+ dispatch({ type: "notice", message: overridden ? `${effect.message}: ORDEWELL_AUTONOMOUS_MODE in the environment decides the autonomy level until it is unset.` : effect.message });
9093
9278
  return;
9094
9279
  }
9095
9280
  case "setAutonomous": {
9096
9281
  const { overridden } = deps.saveAutonomy(effect.enabled);
9097
- const level = (0, import_core47.autonomyLevelLabel)(effect.enabled);
9282
+ const level = (0, import_core50.autonomyLevelLabel)(effect.enabled);
9098
9283
  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
9284
  return;
9100
9285
  }
@@ -9132,8 +9317,8 @@ async function perform(effect, deps) {
9132
9317
  }
9133
9318
  const { plan, goal } = adopted2;
9134
9319
  dispatch({ type: "sessionStarted", sessionId: effect.sessionId, goal });
9135
- inbound.releaseSnapshot(true);
9136
9320
  dispatch(restoredChat(plan, effect.sessionId));
9321
+ inbound.releaseSnapshot(true);
9137
9322
  dispatch({ type: "planUpdated", plan, sessionId: effect.sessionId });
9138
9323
  dispatch({ type: "notice", message: `Loaded "${goal || effect.sessionId}".` });
9139
9324
  return;
@@ -9146,7 +9331,7 @@ async function perform(effect, deps) {
9146
9331
  dispatch({ type: "sessionForked", sessionId: fork.sessionId, goal: fork.goal });
9147
9332
  dispatch(restoredChat(fork.plan, fork.sessionId));
9148
9333
  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.` });
9334
+ dispatch({ type: "notice", message: `Forked ${effect.sessionId} into ${fork.sessionId}: you are in the fork now. /sessions to go back.` });
9150
9335
  return;
9151
9336
  }
9152
9337
  case "loadRewindTargets":
@@ -9167,7 +9352,7 @@ async function perform(effect, deps) {
9167
9352
  dispatch(restoredChat(fork.plan, fork.sessionId));
9168
9353
  dispatch({ type: "planUpdated", plan: fork.plan, sessionId: fork.sessionId });
9169
9354
  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.` });
9355
+ 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
9356
  return;
9172
9357
  }
9173
9358
  // The redrawn transcript opens with the summary entry, which is what the
@@ -9258,7 +9443,7 @@ async function saveMaxParallel(deps, limit) {
9258
9443
  function copySelection(deps, text) {
9259
9444
  if (!text) return;
9260
9445
  const lines = text.split("\n").length;
9261
- const command = (0, import_core47.clipboardCopyCommand)(deps.hasBin);
9446
+ const command = (0, import_core50.clipboardCopyCommand)(deps.hasBin);
9262
9447
  if (command) {
9263
9448
  try {
9264
9449
  (deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
@@ -9270,7 +9455,7 @@ function copySelection(deps, text) {
9270
9455
  deps.writeTerminal(osc52(text));
9271
9456
  deps.dispatch({
9272
9457
  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.`
9458
+ 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
9459
  });
9275
9460
  }
9276
9461
  async function withExecutionStream(deps, sessionId, request) {
@@ -9363,7 +9548,7 @@ function lastAssistantMessage(plan) {
9363
9548
  const history = plan?.conversationHistory ?? [];
9364
9549
  for (let i = history.length - 1; i >= 0; i--) {
9365
9550
  const entry = history[i];
9366
- if ((0, import_core47.isUserMessage)(entry)) return null;
9551
+ if ((0, import_core50.isUserMessage)(entry)) return null;
9367
9552
  if (entry.role === "assistant" && entry.kind === void 0) {
9368
9553
  const { content, timestamp } = entry;
9369
9554
  return { content, ...timestamp ? { timestamp } : {} };
@@ -9718,7 +9903,7 @@ async function handleTui(subArgs) {
9718
9903
  }
9719
9904
  const workspace = flag(subArgs, "--workspace") || process.cwd();
9720
9905
  try {
9721
- (0, import_core48.assertWorkspaceExists)(workspace);
9906
+ (0, import_core51.assertWorkspaceExists)(workspace);
9722
9907
  } catch {
9723
9908
  console.error(
9724
9909
  `Workspace "${workspace}" does not exist.
@@ -9727,7 +9912,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
9727
9912
  );
9728
9913
  process.exit(1);
9729
9914
  }
9730
- const discoveredSkills = (0, import_core48.createSkillsService)(workspace, new import_core48.EnvConfig().workspaceRepos).listSkills();
9915
+ const discoveredSkills = (0, import_core51.createSkillsService)(workspace, new import_core51.EnvConfig().workspaceRepos).listSkills();
9731
9916
  registerSkillCommands(discoveredSkills);
9732
9917
  const taskSkills = discoveredSkills.filter((s) => s.appliesTo === "task").map((s) => ({ name: s.name, description: s.description }));
9733
9918
  const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
@@ -9737,24 +9922,37 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
9737
9922
  const api = new ApiClient(port, workspace);
9738
9923
  let terminal;
9739
9924
  const mouseCapture = process.env.ORDEWELL_TUI_MOUSE !== "false" && process.env.ORDEWELL_TUI_MOUSE !== "0";
9925
+ const settings = new import_core51.SettingsService();
9740
9926
  const app = createApp({
9741
9927
  // 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 },
9928
+ // The run settings are seeded from the same file because the first frame is
9929
+ // drawn before the daemon answers, and the welcome's autorun line is only
9930
+ // truthful once they are known. That holds only for a daemon this process
9931
+ // spawned, which inherits its settings path; an attached one may read a
9932
+ // different file (ORDEWELL_SETTINGS_PATH), and nothing it reports names that
9933
+ // file, so the welcome stays silent until its own settings arrive.
9934
+ initial: {
9935
+ workspace,
9936
+ autonomous: settings.autonomousMode(),
9937
+ ...ownedAtLaunch ? { runSettings: settings.getRunSettings(), runSettingsKnown: true } : {},
9938
+ mouseCapture,
9939
+ taskSkills
9940
+ },
9743
9941
  draw: (frame2) => terminal?.draw(frame2),
9744
9942
  perform: (effect) => runEffect(effect, {
9745
9943
  api,
9746
9944
  workspace,
9747
9945
  port,
9748
9946
  dispatch: (action) => app.dispatch(action),
9749
- newSessionId: () => (0, import_core48.mintSessionId)(),
9947
+ newSessionId: () => (0, import_core51.mintSessionId)(),
9750
9948
  setEnvVar: (key, value) => {
9751
9949
  writeEnvVar(findEnvFile(), key, value);
9752
9950
  process.env[key] = value;
9753
9951
  },
9754
9952
  saveAutonomy: (enabled) => {
9755
- const settings = new import_core48.SettingsService();
9756
- settings.setAutonomousMode(enabled);
9757
- return { overridden: settings.autonomousModeOverridden() };
9953
+ const settings2 = new import_core51.SettingsService();
9954
+ settings2.setAutonomousMode(enabled);
9955
+ return { overridden: settings2.autonomousModeOverridden() };
9758
9956
  },
9759
9957
  setMouseCapture: (enabled) => terminal?.setMouse(enabled),
9760
9958
  // The alt screen is up with the cursor hidden, so an OSC 52 write shows
@@ -9866,7 +10064,7 @@ var COMMANDS = {
9866
10064
  // src/main.ts
9867
10065
  var TUI_FLAGS = /* @__PURE__ */ new Set(["--workspace", "--port"]);
9868
10066
  async function main() {
9869
- (0, import_core49.migrateOldConfigDir)();
10067
+ (0, import_core52.migrateOldConfigDir)();
9870
10068
  moveAutonomyToSettings();
9871
10069
  loadEnvFile();
9872
10070
  const argv = process.argv.slice(2);