@ordewell/cli 0.10.1 → 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 +1034 -724
  3. package/package.json +5 -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_core46 = 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");
@@ -107,10 +107,27 @@ function moveAutonomyToSettings() {
107
107
  }
108
108
  }
109
109
 
110
+ // src/runnerNames.ts
111
+ var import_core2 = require("@ordewell/core");
112
+ function manifests() {
113
+ return new import_core2.RunnerRegistry().list().map((entry) => entry.manifest);
114
+ }
115
+ function runnerIdList() {
116
+ return manifests().map((m) => m.name).join(", ");
117
+ }
118
+ function runnerIdChoice() {
119
+ return manifests().map((m) => m.name).join("|");
120
+ }
121
+ function runnerNamesOr() {
122
+ const names = manifests().map((m) => m.displayName);
123
+ if (names.length < 2) return names.join("");
124
+ return `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}`;
125
+ }
126
+
110
127
  // src/help.ts
111
128
  function printHelp() {
112
129
  console.log(`
113
- Ordewell \u2014 task orchestration for coding agents
130
+ Ordewell: task orchestration for coding agents
114
131
 
115
132
  Run "ordewell" with no arguments for the TUI. Every command below has a TUI
116
133
  slash command of the same name, where each one opens a picker instead of taking
@@ -118,7 +135,7 @@ an id.
118
135
 
119
136
  Planning:
120
137
  ordewell Full-screen terminal UI (everything the VS Code extension does)
121
- ordewell tui The same thing, named \u2014 for scripts and aliases
138
+ ordewell tui Explicit TUI command for scripts and aliases
122
139
  ordewell plan --goal "text" Plan a goal conversationally
123
140
  ordewell run Execute the last generated plan (streams status)
124
141
  ordewell approve Sign off a plan paused for review, and continue it
@@ -153,11 +170,11 @@ Per-task assignment (omit the value to list the options):
153
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
154
171
 
155
172
  Planner, models and runners:
156
- 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)
157
174
  ordewell model [set <id>] Show or set the planner model (applies without a restart)
158
175
  ordewell planner-effort [lvl] Thinking effort for a coding-agent planner
159
176
  ordewell key [set <p> <key>] Show which providers have a key, or store one
160
- ordewell runners [<id> on|off] Enable or disable runners (claude-code, opencode, codex)
177
+ ordewell runners [<id> on|off] Enable or disable runners (${runnerIdList()})
161
178
  ordewell allowlist set|clear|show Limit which models a runner may use
162
179
  ordewell auto [full|guarded] Autonomy level for new plans: Full or Guarded
163
180
  ordewell supervisor [off|watch|act] Whether the planner watches or acts on your runs (default watch);
@@ -232,17 +249,20 @@ Global options:
232
249
  --session-id <id> Session to act on (default: last planned session)
233
250
 
234
251
  Quick Start:
235
- Set OPENROUTER_API_KEY or GEMINI_API_KEY in your environment,
236
- 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"
237
257
 
238
258
  No API key? Plan with a coding agent you already pay for:
239
- AI_PROVIDER=claude-code ordewell plan --goal "Your task description"
259
+ AI_PROVIDER=<runner> ordewell plan --goal "Your task description"
240
260
 
241
261
  Environment:
242
- OPENROUTER_API_KEY OpenRouter API key (recommended)
243
- GEMINI_API_KEY Google Gemini API key (alternative)
244
- AI_PROVIDER openrouter, google \u2014 auto-detected from keys
245
- claude-code, codex, opencode \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,
246
266
  no API key (uses its own subscription)
247
267
  ORDEWELL_PLANNER_EFFORT Thinking effort for a coding-agent planner
248
268
  ORCHESTRATOR_MODEL Default: deepseek/deepseek-v4-flash
@@ -275,9 +295,9 @@ var import_os = require("os");
275
295
  var import_path2 = require("path");
276
296
  var import_http = __toESM(require("http"));
277
297
  var import_net = require("net");
278
- var import_core2 = require("@ordewell/core");
298
+ var import_core3 = require("@ordewell/core");
279
299
  var DEFAULT_PORT = 3742;
280
- var CONFIG_DIR = (0, import_core2.globalDataDir)();
300
+ var CONFIG_DIR = (0, import_core3.globalDataDir)();
281
301
  var LEGACY_PID_FILE = (0, import_path2.join)(CONFIG_DIR, "server.pid");
282
302
  var OLD_CONFIG_DIR = (0, import_path2.join)((0, import_os.homedir)(), ".config", "ordewell");
283
303
  function pidFileFor(port) {
@@ -317,12 +337,12 @@ function getWebServerPath() {
317
337
  );
318
338
  }
319
339
  async function isDaemonRunning(port = DEFAULT_PORT) {
320
- const token = (0, import_core2.readDaemonToken)(port);
340
+ const token = (0, import_core3.readDaemonToken)(port);
321
341
  try {
322
342
  const res = await httpGet(
323
343
  `http://127.0.0.1:${port}/api/workspaces`,
324
344
  2e3,
325
- token ? { Authorization: (0, import_core2.bearerHeaderValue)(token) } : {}
345
+ token ? { Authorization: (0, import_core3.bearerHeaderValue)(token) } : {}
326
346
  );
327
347
  return res.status === 200 || res.status === 401;
328
348
  } catch {
@@ -378,7 +398,7 @@ async function stopDaemon(port = DEFAULT_PORT) {
378
398
  const pid = parseInt(pidStr, 10);
379
399
  if (!pid || isNaN(pid)) {
380
400
  (0, import_fs2.unlinkSync)(pidFile);
381
- (0, import_core2.clearDaemonToken)(port);
401
+ (0, import_core3.clearDaemonToken)(port);
382
402
  return false;
383
403
  }
384
404
  process.kill(pid, "SIGTERM");
@@ -391,7 +411,7 @@ async function stopDaemon(port = DEFAULT_PORT) {
391
411
  (0, import_fs2.unlinkSync)(pidFile);
392
412
  } catch {
393
413
  }
394
- (0, import_core2.clearDaemonToken)(port);
414
+ (0, import_core3.clearDaemonToken)(port);
395
415
  console.log("Server was not running (stale PID removed).");
396
416
  return true;
397
417
  }
@@ -416,7 +436,7 @@ function isConnectionRefused(err) {
416
436
  return message.includes("ECONNREFUSED");
417
437
  }
418
438
  function describeConnectionRefused(port) {
419
- 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\`.`;
420
440
  }
421
441
  function findFreePort() {
422
442
  return new Promise((resolvePromise, reject) => {
@@ -444,17 +464,19 @@ function resolvePort(args = []) {
444
464
  // src/apiClient.ts
445
465
  var import_http2 = __toESM(require("http"));
446
466
  var import_ws = __toESM(require("ws"));
447
- var import_core3 = require("@ordewell/core");
467
+ var import_core4 = require("@ordewell/core");
448
468
  var DEFAULT_HTTP_TIMEOUT_MS = 15 * 60 * 1e3;
449
469
  var DaemonError = class extends Error {
450
- constructor(message, status, code) {
470
+ constructor(message, status, code, plannerNotReady) {
451
471
  super(message);
452
472
  this.status = status;
453
473
  this.code = code;
474
+ this.plannerNotReady = plannerNotReady;
454
475
  this.name = "DaemonError";
455
476
  }
456
477
  status;
457
478
  code;
479
+ plannerNotReady;
458
480
  };
459
481
  var WorkspaceInitNeededError = class extends DaemonError {
460
482
  constructor(message, workspace) {
@@ -466,9 +488,9 @@ var WorkspaceInitNeededError = class extends DaemonError {
466
488
  };
467
489
  var SESSION_PATH = /^\/api\/(?:plans|sessions)\/([^/?]+)/;
468
490
  function daemonError(status, data, fallback) {
469
- const { error, code, workspace } = typeof data === "object" && data !== null ? data : {};
491
+ const { error, code, workspace, plannerNotReady } = typeof data === "object" && data !== null ? data : {};
470
492
  if (code === "workspace_not_a_project") return new WorkspaceInitNeededError(error || "Not a project directory", workspace ?? "");
471
- return new DaemonError(error || fallback, status, code);
493
+ return new DaemonError(error || fallback, status, code, plannerNotReady);
472
494
  }
473
495
  var ApiClient = class _ApiClient {
474
496
  /** Where a CLI invocation's sessions live, when a client is built without one: `--workspace`, else the cwd. */
@@ -488,7 +510,7 @@ var ApiClient = class _ApiClient {
488
510
  * A missing file is not an error here — the daemon's own 401 names the path.
489
511
  */
490
512
  token() {
491
- return (0, import_core3.readDaemonToken)(this.port);
513
+ return (0, import_core4.readDaemonToken)(this.port);
492
514
  }
493
515
  /**
494
516
  * A daemon holds only the sessions adopted since it started, so after a
@@ -524,7 +546,7 @@ var ApiClient = class _ApiClient {
524
546
  method,
525
547
  headers: {
526
548
  ...body ? { "Content-Type": "application/json" } : {},
527
- ...token ? { Authorization: (0, import_core3.bearerHeaderValue)(token) } : {}
549
+ ...token ? { Authorization: (0, import_core4.bearerHeaderValue)(token) } : {}
528
550
  },
529
551
  // Planner research on a real repository regularly exceeds two minutes.
530
552
  // The benchmark harness uses the same 15-minute default explicitly;
@@ -554,7 +576,7 @@ var ApiClient = class _ApiClient {
554
576
  req.end();
555
577
  });
556
578
  }
557
- async generatePlan(goal, runners2, workspace, sessionId = (0, import_core3.mintSessionId)()) {
579
+ async generatePlan(goal, runners2, workspace, sessionId = (0, import_core4.mintSessionId)()) {
558
580
  const { plan, models, modelsByRunner, notes } = await this.call("POST", `/api/plans/${sessionId}/generate`, "Plan generation failed", {
559
581
  goal,
560
582
  runners: runners2 || void 0,
@@ -611,7 +633,7 @@ var ApiClient = class _ApiClient {
611
633
  markTaskComplete(sessionId, taskId) {
612
634
  return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/complete`, "Mark complete failed");
613
635
  }
614
- /** 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. */
615
637
  skipTask(sessionId, taskId, reason) {
616
638
  return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/skip`, "Skip failed", reason ? { reason } : {});
617
639
  }
@@ -711,7 +733,7 @@ var ApiClient = class _ApiClient {
711
733
  compactConversation(sessionId) {
712
734
  return this.call("POST", `/api/plans/${sessionId}/conversation/compact`, "Compaction failed");
713
735
  }
714
- /** 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. */
715
737
  async getMergeGate(sessionId, taskId) {
716
738
  const res = await this.call("GET", `/api/plans/${sessionId}/tasks/${taskId}/merge-gate`, "Could not read the merge gate");
717
739
  return res.mergeGate ?? [];
@@ -789,7 +811,7 @@ var ApiClient = class _ApiClient {
789
811
  const token = this.token();
790
812
  const socket = new import_ws.default(
791
813
  `ws://127.0.0.1:${this.port}/ws/session/${sessionId}`,
792
- token ? (0, import_core3.tokenSubprotocols)(token) : void 0
814
+ token ? (0, import_core4.tokenSubprotocols)(token) : void 0
793
815
  );
794
816
  socket.on("unexpected-response", (_req, res) => {
795
817
  const chunks = [];
@@ -962,7 +984,7 @@ var ApiClient = class _ApiClient {
962
984
  };
963
985
 
964
986
  // src/commands/plan.ts
965
- var import_readline = require("readline");
987
+ var import_readline2 = require("readline");
966
988
 
967
989
  // src/utils/args.ts
968
990
  function flag(args, name) {
@@ -1012,15 +1034,15 @@ function positionals(args) {
1012
1034
  }
1013
1035
 
1014
1036
  // src/utils/tasks.ts
1015
- var import_core4 = require("@ordewell/core");
1037
+ var import_core5 = require("@ordewell/core");
1016
1038
  function allTasksOf(plan) {
1017
1039
  const tasks = plan?.pendingTasks || plan?.tasks || [];
1018
1040
  const executionLog = plan?.executionLog || [];
1019
- return (0, import_core4.rejoinPlanTree)(tasks, executionLog);
1041
+ return (0, import_core5.rejoinPlanTree)(tasks, executionLog);
1020
1042
  }
1021
1043
  function resolveTaskId(plan, identifier) {
1022
1044
  const allTasks = allTasksOf(plan);
1023
- const byRef = (0, import_core4.resolveTaskRef)(allTasks, identifier);
1045
+ const byRef = (0, import_core5.resolveTaskRef)(allTasks, identifier);
1024
1046
  if (byRef) return byRef.id;
1025
1047
  const prefixMatches = allTasks.filter((t) => typeof t.id === "string" && t.id.startsWith(identifier));
1026
1048
  if (prefixMatches.length === 1) return prefixMatches[0].id;
@@ -1030,13 +1052,13 @@ function resolveTaskId(plan, identifier) {
1030
1052
  // src/utils/session.ts
1031
1053
  var import_fs3 = require("fs");
1032
1054
  var import_path3 = require("path");
1033
- var import_core5 = require("@ordewell/core");
1055
+ var import_core6 = require("@ordewell/core");
1034
1056
  var LAST_SESSION_FILENAME = "last-session.json";
1035
1057
  function lastSessionFile(workspace) {
1036
- return (0, import_path3.join)((0, import_core5.getStateDir)(workspace), LAST_SESSION_FILENAME);
1058
+ return (0, import_path3.join)((0, import_core6.getStateDir)(workspace), LAST_SESSION_FILENAME);
1037
1059
  }
1038
1060
  function saveLastSession(sessionId, goal, runners2, workspace) {
1039
- const dir = (0, import_core5.getStateDir)(workspace);
1061
+ const dir = (0, import_core6.getStateDir)(workspace);
1040
1062
  if (!(0, import_fs3.existsSync)(dir)) (0, import_fs3.mkdirSync)(dir, { recursive: true });
1041
1063
  (0, import_fs3.writeFileSync)(
1042
1064
  lastSessionFile(workspace),
@@ -1058,14 +1080,14 @@ function expandSessionId(id, workspace) {
1058
1080
  if (id.startsWith(SESSION_PREFIX)) return id;
1059
1081
  let files;
1060
1082
  try {
1061
- files = (0, import_fs3.readdirSync)((0, import_path3.join)((0, import_core5.getStateDir)(workspace), "sessions")).filter((f) => f.endsWith(".json"));
1083
+ files = (0, import_fs3.readdirSync)((0, import_path3.join)((0, import_core6.getStateDir)(workspace), "sessions")).filter((f) => f.endsWith(".json"));
1062
1084
  } catch {
1063
1085
  return id;
1064
1086
  }
1065
1087
  const matches = /* @__PURE__ */ new Set();
1066
1088
  for (const file of files) {
1067
1089
  try {
1068
- const full = JSON.parse((0, import_fs3.readFileSync)((0, import_path3.join)((0, import_core5.getStateDir)(workspace), "sessions", file), "utf8"))?.meta?.id;
1090
+ const full = JSON.parse((0, import_fs3.readFileSync)((0, import_path3.join)((0, import_core6.getStateDir)(workspace), "sessions", file), "utf8"))?.meta?.id;
1069
1091
  if (typeof full === "string" && (full.endsWith(id) || full.slice(SESSION_PREFIX.length).startsWith(id))) matches.add(full);
1070
1092
  } catch {
1071
1093
  }
@@ -1123,7 +1145,7 @@ ${describeApproval(event)}
1123
1145
  deps.write(" Approved automatically (--yes).\n");
1124
1146
  } else if (!interactive) {
1125
1147
  granted = false;
1126
- 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");
1127
1149
  } else {
1128
1150
  const answer = (await deps.ask(" Allow? [y/N] ")).trim().toLowerCase();
1129
1151
  granted = answer === "y" || answer === "yes";
@@ -1131,12 +1153,12 @@ ${describeApproval(event)}
1131
1153
  try {
1132
1154
  await deps.respond(sessionId, id, granted);
1133
1155
  } catch {
1134
- 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");
1135
1157
  }
1136
1158
  }
1137
1159
 
1138
1160
  // src/commands/researchLog.ts
1139
- var import_core6 = require("@ordewell/core");
1161
+ var import_core7 = require("@ordewell/core");
1140
1162
  var OUTCOME_MARK = {
1141
1163
  success: "\u2713",
1142
1164
  failure: "\u2717",
@@ -1148,7 +1170,7 @@ var RESULT_CHARS = 160;
1148
1170
  var VERBOSE_RESULT_CHARS = 600;
1149
1171
  var THINKING_CHARS = 300;
1150
1172
  function summarize(tool, args, toolLabel) {
1151
- const { name, keyArg } = (0, import_core6.toolHeadline)(tool, args, toolLabel);
1173
+ const { name, keyArg } = (0, import_core7.toolHeadline)(tool, args, toolLabel);
1152
1174
  return `${name}(${keyArg})`;
1153
1175
  }
1154
1176
  function oneLine(text, max) {
@@ -1198,7 +1220,118 @@ function createStepLog(options = {}) {
1198
1220
  }
1199
1221
 
1200
1222
  // src/commands/plan.ts
1201
- var import_core7 = require("@ordewell/core");
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
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
1202
1335
  function planTasks(plan) {
1203
1336
  return plan ? allTasksOf(plan) : [];
1204
1337
  }
@@ -1220,8 +1353,16 @@ function commitNotes(plan) {
1220
1353
  const marker2 = history.map((e) => e.kind).lastIndexOf("plan_generated");
1221
1354
  return marker2 < 0 ? [] : history.slice(marker2 + 1).filter((e) => e.kind === "system").map((e) => e.content);
1222
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
+ }
1223
1364
  function stdinReader(input = process.stdin) {
1224
- const rl = (0, import_readline.createInterface)({ input, output: process.stderr });
1365
+ const rl = (0, import_readline2.createInterface)({ input, output: process.stderr });
1225
1366
  const buffered = [];
1226
1367
  let waiting = null;
1227
1368
  let ended = false;
@@ -1276,35 +1417,36 @@ function printPlan(plan, sessionId, runners2, models) {
1276
1417
  return rp ? ` (${task.assignedModel.modelLabel} \xB7 ${label})` : ` (${task.assignedModel.modelLabel})`;
1277
1418
  };
1278
1419
  console.log(`
1279
- 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(", ")}`);
1280
1421
  console.log(`Session: ${sessionId}
1281
1422
  `);
1282
1423
  for (const t of tasks) {
1283
1424
  const typeIcon = t.type === "user" ? "[MAN]" : t.ops ? "[OPS]" : "[ AI]";
1284
1425
  const deps = t.dependencies?.length ? ` \u2190 ${t.dependencies.join(", ")}` : "";
1285
- console.log(` ${(0, import_core7.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}`);
1286
1427
  for (const sub of t.subtasks || []) {
1287
1428
  const subTypeIcon = sub.type === "user" ? "[MAN]" : "[ AI]";
1288
1429
  const subDeps = sub.dependencies?.length ? ` \u2190 ${sub.dependencies.join(", ")}` : "";
1289
- console.log(` ${(0, import_core7.taskOrderLabel)(sub, t)}. ${subTypeIcon} ${sub.title}${modelSuffix(sub)}${subDeps}`);
1430
+ console.log(` ${(0, import_core9.taskOrderLabel)(sub, t)}. ${subTypeIcon} ${sub.title}${modelSuffix(sub)}${subDeps}`);
1290
1431
  }
1291
1432
  }
1292
1433
  if (manCount > 0) {
1293
- 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");
1294
1435
  }
1295
1436
  if (tasks.some((t) => t.ops)) {
1296
- 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`)");
1297
1438
  }
1298
1439
  }
1299
- var PLANNER_ENV = ["AI_PROVIDER", "ORCHESTRATOR_MODEL", "ORDEWELL_PLANNER_EFFORT"];
1440
+ var INHERITED_PLANNER_ENV = ["ORCHESTRATOR_MODEL", "ORDEWELL_PLANNER_EFFORT"];
1300
1441
  async function forwardPlannerEnv(api) {
1442
+ const provider = process.env.AI_PROVIDER;
1443
+ if (provider) await api.updateSettings({ env: { AI_PROVIDER: provider } });
1301
1444
  const env = {};
1302
- for (const key of PLANNER_ENV) {
1445
+ for (const key of INHERITED_PLANNER_ENV) {
1303
1446
  const value = process.env[key];
1304
1447
  if (value) env[key] = value;
1305
1448
  }
1306
- if (Object.keys(env).length === 0) return;
1307
- await api.updateSettings({ env });
1449
+ if (Object.keys(env).length > 0) await api.updateSettings({ env, inherited: true });
1308
1450
  }
1309
1451
  async function handlePlan(subArgs, deps = {}) {
1310
1452
  const injectedApi = deps.api;
@@ -1323,20 +1465,27 @@ async function handlePlan(subArgs, deps = {}) {
1323
1465
  await forwardPlannerEnv(api);
1324
1466
  const state = await api.getRunners();
1325
1467
  const enabledIds = state.runners.filter((r) => r.enabled).map((r) => r.id);
1468
+ const enableHint = (id) => `Enable it with: ordewell runners ${id} on`;
1326
1469
  let runners2;
1327
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
+ }
1328
1478
  runners2 = enabledIds;
1329
1479
  } else {
1330
1480
  for (const r of runnerFlags) {
1331
- if (!enabledIds.includes(r)) {
1332
- console.error(`Unknown runner: "${r}". Enabled runners: ${enabledIds.join(", ")}`);
1333
- process.exit(1);
1334
- }
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(", ")}`);
1335
1484
  }
1336
1485
  runners2 = runnerFlags;
1337
1486
  }
1338
1487
  console.error(`Generating plan for: "${goal}"...`);
1339
- const sessionId = (0, import_core7.mintSessionId)();
1488
+ const sessionId = (0, import_core9.mintSessionId)();
1340
1489
  const isTty = !!process.stderr.isTTY;
1341
1490
  let lastLineLength = 0;
1342
1491
  const stepLog = createStepLog({ verbose });
@@ -1430,19 +1579,24 @@ ${lastPlannerMessage(plan)}
1430
1579
  ${note}`);
1431
1580
  saveLastSession(sessionId, goal, runnersOf(plan, runners2), workspace);
1432
1581
  console.log(`
1433
- Run 'ordewell run' to execute, 'ordewell status' to inspect, or 'ordewell tui' for the full UI.`);
1582
+ ${await nextStep(api, plan)}`);
1434
1583
  } catch (err) {
1435
1584
  reader?.close();
1436
1585
  closeStream();
1437
1586
  if (isTty && lastLineLength > 0) process.stderr.write("\r\x1B[2K");
1438
1587
  process.stderr.write("\n");
1439
- 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
+ }
1440
1594
  process.exit(1);
1441
1595
  }
1442
1596
  }
1443
1597
 
1444
1598
  // src/isolation.ts
1445
- var import_core8 = require("@ordewell/core");
1599
+ var import_core10 = require("@ordewell/core");
1446
1600
  var STATE_OF = {
1447
1601
  active: "active",
1448
1602
  merged: "integrated",
@@ -1526,9 +1680,9 @@ function repairedLanded(handoff) {
1526
1680
  function repairedNotice(handoff) {
1527
1681
  const repaired = repairedLanded(handoff);
1528
1682
  if (repaired.length === 0) return "";
1529
- const named = repaired.map((t) => `${t.title} (${(0, import_core8.capConflictFiles)(t.repairedFiles)})`);
1530
- const list = named.length === 1 ? named[0] : `${named.slice(0, -1).join(", ")} and ${named[named.length - 1]}`;
1531
- return ` ${list} landed through ${repaired.length === 1 ? "a conflict repair" : "conflict repairs"}.`;
1683
+ const named = repaired.map((t) => `${t.title} (${(0, import_core10.capConflictFiles)(t.repairedFiles)})`);
1684
+ const list2 = named.length === 1 ? named[0] : `${named.slice(0, -1).join(", ")} and ${named[named.length - 1]}`;
1685
+ return ` ${list2} landed through ${repaired.length === 1 ? "a conflict repair" : "conflict repairs"}.`;
1532
1686
  }
1533
1687
  function repoResultLines(handoff) {
1534
1688
  return handoff.repos.map(({ path, landed }) => {
@@ -1539,16 +1693,16 @@ function repoResultLines(handoff) {
1539
1693
  function mergeOutcome(result, branch, group, repaired = []) {
1540
1694
  const ok = result.outcome === "merged";
1541
1695
  if (!group && result.outcome === "conflict") {
1542
- 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.` };
1543
1697
  }
1544
- const { message } = (0, import_core8.describeMergeResult)(result, branch, group, repaired);
1698
+ const { message } = (0, import_core10.describeMergeResult)(result, branch, group, repaired);
1545
1699
  return { ok, message: ok || !group ? message : `${message} Each repository's ${branch} is a plain branch you can merge by hand.` };
1546
1700
  }
1547
1701
 
1548
1702
  // src/utils/output.ts
1549
- var import_core9 = require("@ordewell/core");
1703
+ var import_core11 = require("@ordewell/core");
1550
1704
  function iconFor(status) {
1551
- switch ((0, import_core9.taskStatusKind)({ status })) {
1705
+ switch ((0, import_core11.taskStatusKind)({ status })) {
1552
1706
  case "done":
1553
1707
  return "\x1B[32m\u2713\x1B[0m";
1554
1708
  case "running":
@@ -1568,114 +1722,8 @@ function iconFor(status) {
1568
1722
  }
1569
1723
 
1570
1724
  // src/commands/run.ts
1571
- var import_core10 = require("@ordewell/core");
1572
-
1573
- // src/commands/shared.ts
1574
- var import_readline2 = require("readline");
1575
-
1576
- // src/catalog.ts
1577
- function normalizeCatalog(result) {
1578
- const runnersByModel = /* @__PURE__ */ new Map();
1579
- for (const [runner, entries] of Object.entries(result.modelsByRunner ?? {})) {
1580
- for (const entry of entries ?? []) {
1581
- const id = String(entry.modelId);
1582
- runnersByModel.set(id, [.../* @__PURE__ */ new Set([...runnersByModel.get(id) ?? [], runner])]);
1583
- }
1584
- }
1585
- const models = (result.models ?? []).map((m) => {
1586
- const id = String(m.modelId);
1587
- return {
1588
- id,
1589
- label: String(m.modelLabel ?? id),
1590
- provider: String(m.runnerProviderLabel ?? m.runnerProvider ?? ""),
1591
- variants: Array.isArray(m.variants) ? m.variants.map((v) => ({ id: String(v.id), label: String(v.label ?? v.id) })) : [],
1592
- runners: runnersByModel.get(id) ?? []
1593
- };
1594
- });
1595
- const orchestratorModels = (result.orchestratorModels ?? []).map((m) => ({
1596
- id: String(m.id),
1597
- label: String(m.label ?? m.id),
1598
- provider: String(m.provider ?? ""),
1599
- pricing: m.pricing ? `$${m.pricing}/MTok` : void 0
1600
- }));
1601
- return {
1602
- models,
1603
- orchestratorModels,
1604
- providers: result.providers ?? [],
1605
- providerErrors: result.providerErrors ?? {},
1606
- modesByRunner: Object.fromEntries(
1607
- Object.entries(result.modesByRunner ?? {}).map(([runner, modes]) => [
1608
- runner,
1609
- (modes ?? []).map((m) => ({
1610
- id: String(m.id),
1611
- label: String(m.label ?? m.id),
1612
- description: m.description ? String(m.description) : void 0,
1613
- autonomous: m.autonomous ? true : void 0,
1614
- unavailable: m.unavailable ? String(m.unavailable) : void 0
1615
- }))
1616
- ])
1617
- )
1618
- };
1619
- }
1620
-
1621
- // src/commands/shared.ts
1622
- async function connect(subArgs, injected) {
1623
- return injected ?? new ApiClient(await ensureDaemon(resolvePort(subArgs)));
1624
- }
1625
- async function fetchCatalog(api) {
1626
- return normalizeCatalog(await api.getModels());
1627
- }
1628
- function fail(...lines) {
1629
- for (const line of lines) console.error(line);
1630
- process.exit(1);
1631
- }
1632
- async function askYesNo(question) {
1633
- if (!process.stdin.isTTY) return false;
1634
- const rl = (0, import_readline2.createInterface)({ input: process.stdin, output: process.stderr });
1635
- try {
1636
- const answer = await new Promise((resolve4) => rl.question(`${question} [y/N] `, resolve4));
1637
- return /^y(es)?$/i.test(answer.trim());
1638
- } finally {
1639
- rl.close();
1640
- }
1641
- }
1642
- function writeEnv(env) {
1643
- const envFile = findEnvFile();
1644
- for (const [key, value] of Object.entries(env)) {
1645
- writeEnvVar(envFile, key, value);
1646
- process.env[key] = value;
1647
- }
1648
- }
1649
- async function persistEnv(api, env) {
1650
- const settings = await api.updateSettings({ env });
1651
- writeEnv(env);
1652
- return settings;
1653
- }
1654
- function toTaskView(task) {
1655
- return {
1656
- id: task.id,
1657
- order: task.order,
1658
- title: task.title,
1659
- description: task.description,
1660
- prompt: task.prompt ?? void 0,
1661
- type: task.type === "user" ? "user" : "ai",
1662
- status: task.status ?? "pending",
1663
- dependencies: task.dependencies ?? [],
1664
- assignedRunner: task.assignedRunner || void 0,
1665
- taskMode: task.taskMode || void 0,
1666
- assignedModel: task.assignedModel ?? void 0,
1667
- subtasks: [...task.subtasks ?? []].sort((a, b) => a.order - b.order).map(toTaskView),
1668
- ...task.ops ? { ops: true } : {},
1669
- ...task.skills?.length ? { skills: [...task.skills] } : {},
1670
- ...task.addedBy ? { addedBy: task.addedBy } : {}
1671
- };
1672
- }
1673
- function taskViews(plan) {
1674
- const tasks = allTasksOf(plan);
1675
- return [...tasks].sort((a, b) => a.order - b.order).map(toTaskView);
1676
- }
1677
-
1678
- // src/commands/run.ts
1725
+ var import_core12 = require("@ordewell/core");
1726
+ var inLine = (label) => label.charAt(0).toLowerCase() + label.slice(1);
1679
1727
  function resolveSessionId(subArgs) {
1680
1728
  const explicit = flag(subArgs, "--session-id");
1681
1729
  if (explicit) return explicit;
@@ -1701,8 +1749,9 @@ async function followExecution(api, sessionId, start, onBlocked) {
1701
1749
  const lines = [];
1702
1750
  for (const t of entries) {
1703
1751
  const icon = iconFor(t.status);
1704
- const verdict = t.verdict ? ` \u2014 ${t.verdict.outcome.toUpperCase()}: ${t.verdict.reason.slice(0, 60)}` : "";
1705
- 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}`);
1706
1755
  }
1707
1756
  const output = lines.join("\n");
1708
1757
  if (output !== lastPrinted && output.trim()) {
@@ -1727,21 +1776,21 @@ async function followExecution(api, sessionId, start, onBlocked) {
1727
1776
  });
1728
1777
  const stream = api.streamExecution(sessionId, (event) => {
1729
1778
  if (event.type === "task_started") {
1730
- process.stderr.write(`\xB7 ${(0, import_core10.taskStartedNotice)(event.title, event.runner)}
1779
+ process.stderr.write(`\xB7 ${(0, import_core12.taskStartedNotice)(event.title, event.runner)}
1731
1780
  `);
1732
1781
  }
1733
1782
  if (event.type === "checkpoint") {
1734
- process.stderr.write(`\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core10.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
1735
1784
  `);
1736
1785
  }
1737
1786
  if (event.type === "status_update" && event.tasks) {
1738
1787
  printStatus(event.tasks);
1739
1788
  const paused = event.runMerge?.paused === true;
1740
- 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");
1741
1790
  pausedAtGate = paused;
1742
1791
  }
1743
1792
  if (event.type === "review_needed") {
1744
- 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.");
1745
1794
  }
1746
1795
  if (event.type === "execution_complete") {
1747
1796
  const s = event;
@@ -1765,7 +1814,7 @@ Done. ${summary.completed || 0} completed, ${summary.failed || 0} failed, ${summ
1765
1814
  const n = event.landed.length;
1766
1815
  const where = isRepoGroup(event) ? ` in ${event.repos.map((r) => r.path).join(", ")}` : "";
1767
1816
  console.log(`
1768
- 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.`);
1769
1818
  if (isRepoGroup(event)) for (const line of repoResultLines(event)) console.log(` ${line}`);
1770
1819
  if (event.deferredTo === "supervisor") console.log(" The supervisor may merge it.");
1771
1820
  console.log(" `ordewell handoff review|merge|discard|cleanup` to land it.");
@@ -1783,7 +1832,7 @@ Run finished on ${handoffBranch(event)}${where} \u2014 ${n} task${n === 1 ? "" :
1783
1832
  await start();
1784
1833
  const outcome = await Promise.race([stream, blockedSeen]).catch(streamFailed);
1785
1834
  if (outcome === "lost") {
1786
- 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.");
1787
1836
  process.exit(1);
1788
1837
  }
1789
1838
  if (blocked === null) return;
@@ -1812,7 +1861,7 @@ async function handleRun(subArgs, injectedApi) {
1812
1861
  }
1813
1862
 
1814
1863
  // src/tui/blocks.ts
1815
- var import_core11 = require("@ordewell/core");
1864
+ var import_core14 = require("@ordewell/core");
1816
1865
 
1817
1866
  // src/tui/ansi.ts
1818
1867
  var import_string_width = __toESM(require("string-width"));
@@ -1991,6 +2040,7 @@ var style = {
1991
2040
  dim: colour("2"),
1992
2041
  italic: colour("3"),
1993
2042
  underline: colour("4"),
2043
+ strike: colour("9"),
1994
2044
  inverse: colour("7"),
1995
2045
  red: colour("31"),
1996
2046
  green: colour("32"),
@@ -2027,94 +2077,191 @@ var style = {
2027
2077
  };
2028
2078
 
2029
2079
  // src/tui/markdown.ts
2080
+ var import_core13 = require("@ordewell/core");
2030
2081
  function renderMarkdown(source, cols) {
2031
- const lines = source.replace(/\r/g, "").replace(/^\n+|\n+$/g, "").split("\n");
2082
+ return blocks((0, import_core13.markdownTokens)(source), { room: Math.max(1, cols), base: [] });
2083
+ }
2084
+ function closeOf(tokens, open) {
2085
+ const { level } = tokens[open];
2086
+ for (let i = open + 1; i < tokens.length; i += 1) {
2087
+ if (tokens[i].nesting === -1 && tokens[i].level === level) return i;
2088
+ }
2089
+ return tokens.length - 1;
2090
+ }
2091
+ function lastLine(tokens) {
2092
+ let last = null;
2093
+ for (const token of tokens) {
2094
+ if (token.nesting === 0 && token.map && (last === null || token.map[1] > last)) last = token.map[1];
2095
+ }
2096
+ return last ?? tokens[0]?.map?.[1] ?? null;
2097
+ }
2098
+ function blocks(tokens, ctx) {
2032
2099
  const out = [];
2033
- let inCodeBlock = false;
2034
- for (let i = 0; i < lines.length; i += 1) {
2035
- const line = lines[i];
2036
- if (/^\s*```/.test(line)) {
2037
- inCodeBlock = !inCodeBlock;
2038
- continue;
2039
- }
2040
- if (inCodeBlock) {
2041
- out.push(...wrap(style.grey(` ${line}`), cols));
2042
- continue;
2043
- }
2044
- const table = tableAt(lines, i);
2045
- if (table) {
2046
- out.push(...renderTable(table.headers, table.rows, cols));
2047
- i = table.end;
2048
- continue;
2049
- }
2050
- const heading = line.match(/^\s{0,3}(#{1,6})\s+(.+?)\s*#*\s*$/);
2051
- if (heading) {
2052
- const paint = heading[1].length <= 2 ? (text) => style.bold(style.brand(text)) : style.bold;
2053
- out.push(...wrap(paint(inline(heading[2])), cols));
2054
- continue;
2055
- }
2056
- if (/^\s{0,3}(?:[-*_]\s*){3,}$/.test(line)) {
2057
- out.push(style.grey("\u2500".repeat(Math.max(1, cols))));
2058
- continue;
2059
- }
2060
- const bullet = line.match(/^(\s*)[-*+]\s+(.+)$/);
2061
- if (bullet) {
2062
- out.push(...wrap(`${bullet[1]}\u2022 ${inline(bullet[2])}`, cols));
2063
- continue;
2064
- }
2065
- const quote = line.match(/^\s*>\s?(.*)$/);
2066
- if (quote) {
2067
- out.push(...wrap(style.grey(`\u2502 ${inline(quote[1])}`), cols));
2068
- continue;
2069
- }
2070
- out.push(...wrap(inline(line), cols));
2100
+ let previousEnd = null;
2101
+ for (let i = 0; i < tokens.length; i += 1) {
2102
+ const token = tokens[i];
2103
+ if (token.nesting === -1) continue;
2104
+ const end = token.nesting === 1 ? closeOf(tokens, i) : i;
2105
+ const children = tokens.slice(i + 1, end);
2106
+ const lines = block(token, children, ctx);
2107
+ const last = lastLine([token, ...children]);
2108
+ i = end;
2109
+ if (lines === null) continue;
2110
+ const start = token.map?.[0] ?? null;
2111
+ if (previousEnd !== null && start !== null && start > previousEnd && out.length > 0) out.push("");
2112
+ previousEnd = last ?? previousEnd;
2113
+ out.push(...lines);
2071
2114
  }
2072
2115
  return out;
2073
2116
  }
2074
- function tableAt(lines, start) {
2075
- if (!lines[start]?.includes("|") || !isTableRule(lines[start + 1] ?? "")) return null;
2076
- const headers = tableCells(lines[start]);
2077
- if (headers.length < 2) return null;
2078
- const rows = [];
2079
- let end = start + 1;
2080
- for (let i = start + 2; i < lines.length && lines[i].includes("|"); i += 1) {
2081
- const cells2 = tableCells(lines[i]);
2082
- if (cells2.length !== headers.length) break;
2083
- rows.push(cells2);
2084
- end = i;
2117
+ function block(token, children, ctx) {
2118
+ switch (token.type) {
2119
+ case "paragraph_open":
2120
+ return wrap(inline(children[0]?.children ?? [], ctx.base, "\n"), ctx.room);
2121
+ case "heading_open": {
2122
+ const level = Number(token.tag.slice(1));
2123
+ const base = level <= 2 ? [style.bold, style.brand] : [style.bold];
2124
+ return wrap(inline(children[0]?.children ?? [], [...ctx.base, ...base], "\n"), ctx.room);
2125
+ }
2126
+ case "fence":
2127
+ case "code_block":
2128
+ return token.content.replace(/\n$/, "").split("\n").flatMap((line) => wrap(` ${line}`, ctx.room)).map(style.grey);
2129
+ case "hr":
2130
+ return [style.grey("\u2500".repeat(ctx.room))];
2131
+ case "blockquote_open": {
2132
+ const inner = blocks(children, { room: Math.max(1, ctx.room - 2), base: [...ctx.base, style.grey] });
2133
+ return inner.map((line) => `${style.grey("\u2502")} ${line}`);
2134
+ }
2135
+ case "bullet_list_open":
2136
+ return list(children, ctx, () => "\u2022");
2137
+ case "ordered_list_open": {
2138
+ const start = Number(token.attrs?.find(([name]) => name === "start")?.[1] ?? 1);
2139
+ let n = start - 1;
2140
+ return list(children, ctx, (item) => {
2141
+ n += 1;
2142
+ return `${item.info || n}${item.markup || "."}`;
2143
+ });
2144
+ }
2145
+ case "table_open":
2146
+ return table(children, ctx);
2147
+ default:
2148
+ return null;
2085
2149
  }
2086
- return { headers, rows, end };
2087
2150
  }
2088
- function isTableRule(line) {
2089
- const cells2 = tableCells(line);
2090
- return cells2.length >= 2 && cells2.every((cell) => /^:?-{3,}:?$/.test(cell));
2151
+ function list(items, ctx, marker2) {
2152
+ const out = [];
2153
+ let previousEnd = null;
2154
+ for (let i = 0; i < items.length; i += 1) {
2155
+ const item = items[i];
2156
+ if (item.type !== "list_item_open") continue;
2157
+ const end = closeOf(items, i);
2158
+ const mark = marker2(item);
2159
+ const hang = " ".repeat(width(mark) + 1);
2160
+ const content = items.slice(i + 1, end);
2161
+ const inner = blocks(content, { ...ctx, room: Math.max(1, ctx.room - hang.length) });
2162
+ i = end;
2163
+ const start = item.map?.[0] ?? null;
2164
+ if (previousEnd !== null && start !== null && start > previousEnd) out.push("");
2165
+ previousEnd = lastLine(content) ?? previousEnd;
2166
+ (inner.length > 0 ? inner : [""]).forEach((line, row) => out.push(row === 0 ? `${mark} ${line}` : line && `${hang}${line}`));
2167
+ }
2168
+ return out;
2091
2169
  }
2092
- function tableCells(line) {
2093
- return line.trim().replace(/^\||\|$/g, "").split("|").map((cell) => cell.trim());
2170
+ function inline(tokens, base, lineBreak) {
2171
+ const active = [...base];
2172
+ const paint = (text) => text.split(" ").map((word) => word ? active.reduce((acc, apply) => apply(acc), word) : word).join(" ");
2173
+ let out = "";
2174
+ let href = null;
2175
+ let linkText = "";
2176
+ const emit = (text) => {
2177
+ out += paint(text);
2178
+ if (href !== null) linkText += text;
2179
+ };
2180
+ for (const token of tokens) {
2181
+ switch (token.type) {
2182
+ case "text":
2183
+ case "image":
2184
+ emit(token.content);
2185
+ break;
2186
+ case "code_inline":
2187
+ active.push(style.brand);
2188
+ emit(token.content);
2189
+ active.pop();
2190
+ break;
2191
+ case "softbreak":
2192
+ case "hardbreak":
2193
+ out += lineBreak;
2194
+ break;
2195
+ case "strong_open":
2196
+ active.push(style.bold);
2197
+ break;
2198
+ case "em_open":
2199
+ active.push(style.italic);
2200
+ break;
2201
+ case "s_open":
2202
+ active.push(style.dim);
2203
+ break;
2204
+ case "link_open":
2205
+ href = token.attrs?.find(([name]) => name === "href")?.[1] ?? null;
2206
+ linkText = "";
2207
+ if (href !== null) active.push(style.underline);
2208
+ break;
2209
+ case "link_close":
2210
+ if (href !== null) {
2211
+ active.pop();
2212
+ if (linkText !== href) out += paint(" ") + style.grey(`(${href})`);
2213
+ }
2214
+ href = null;
2215
+ break;
2216
+ case "strong_close":
2217
+ case "em_close":
2218
+ case "s_close":
2219
+ active.pop();
2220
+ break;
2221
+ default:
2222
+ if (token.content) emit(token.content);
2223
+ }
2224
+ }
2225
+ return out;
2226
+ }
2227
+ function table(tokens, ctx) {
2228
+ const headers = [];
2229
+ const rows = [];
2230
+ let inHead = false;
2231
+ let row = null;
2232
+ for (const token of tokens) {
2233
+ if (token.type === "thead_open") inHead = true;
2234
+ else if (token.type === "thead_close") inHead = false;
2235
+ else if (token.type === "tr_open") row = [];
2236
+ else if (token.type === "tr_close" && row) {
2237
+ if (inHead) headers.push(...row);
2238
+ else rows.push(row);
2239
+ row = null;
2240
+ } else if (token.type === "inline" && row) {
2241
+ row.push(inline(token.children ?? [], inHead ? [style.bold] : [], " "));
2242
+ }
2243
+ }
2244
+ return renderTable(headers, rows, ctx.room);
2094
2245
  }
2095
2246
  function renderTable(headers, rows, cols) {
2096
2247
  const separator = " \u2502 ";
2097
2248
  const separatorWidth = width(separator);
2098
2249
  const available = Math.max(headers.length, cols - separatorWidth * (headers.length - 1));
2099
2250
  const natural = headers.map(
2100
- (header, column) => Math.max(width(inline(header)), ...rows.map((row) => width(inline(row[column] ?? ""))))
2251
+ (header, column) => Math.max(width(header), ...rows.map((row) => width(row[column] ?? "")))
2101
2252
  );
2102
2253
  const columnWidths = fitTableColumns(natural, available);
2103
- if (columnWidths.some((size) => size < 4)) return renderStackedTable(headers, rows, cols);
2104
- const renderRow = (cells2, paint = (text) => text) => {
2105
- const wrapped = cells2.map((cell, index) => wrap(inline(cell), columnWidths[index]));
2254
+ if (columnWidths.some((size, index) => size < Math.min(4, natural[index]))) return renderRecords(headers, rows, cols);
2255
+ const renderRow = (cells2) => {
2256
+ const wrapped = headers.map((_, index) => wrap(cells2[index] ?? "", columnWidths[index]));
2106
2257
  const height = Math.max(...wrapped.map((cell) => cell.length));
2107
2258
  return Array.from(
2108
2259
  { length: height },
2109
- (_, line) => wrapped.map((cell, column) => pad(paint(cell[line] ?? ""), columnWidths[column])).join(separator)
2260
+ (_, line) => wrapped.map((cell, column) => pad(cell[line] ?? "", columnWidths[column])).join(separator)
2110
2261
  );
2111
2262
  };
2112
2263
  const rule = columnWidths.map((size) => style.grey("\u2500".repeat(size))).join(style.grey("\u2500\u253C\u2500"));
2113
- return [
2114
- ...renderRow(headers, style.bold),
2115
- rule,
2116
- ...rows.flatMap((row) => renderRow(row))
2117
- ];
2264
+ return [...renderRow(headers), rule, ...rows.flatMap(renderRow)];
2118
2265
  }
2119
2266
  function fitTableColumns(natural, available) {
2120
2267
  const widths = [...natural];
@@ -2127,15 +2274,12 @@ function fitTableColumns(natural, available) {
2127
2274
  }
2128
2275
  return widths;
2129
2276
  }
2130
- function renderStackedTable(headers, rows, cols) {
2277
+ function renderRecords(headers, rows, cols) {
2131
2278
  return rows.flatMap((row, rowIndex) => [
2132
- ...rowIndex === 0 ? [] : [""],
2133
- ...row.flatMap((cell, column) => wrap(`${style.bold(inline(headers[column]))}: ${inline(cell)}`, cols))
2279
+ ...rowIndex === 0 ? [] : [style.grey("\u2500".repeat(cols))],
2280
+ ...headers.flatMap((header, column) => wrap(`${header}: ${row[column] ?? ""}`, cols))
2134
2281
  ]);
2135
2282
  }
2136
- function inline(text) {
2137
- return text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\[([^\]]+)\]\(([^\s)]+)(?:\s+[^)]*)?\)/g, "$1 ($2)").replace(/\*\*([^*]+)\*\*/g, (_, bold) => style.bold(bold)).replace(/__([^_]+)__/g, (_, bold) => style.bold(bold)).replace(/(^|[^\w])\*([^*]+)\*(?![\w*])/g, (_, prefix, italic) => `${prefix}${style.italic(italic)}`).replace(/(^|[^\w])_([^_]+)_(?![\w_])/g, (_, prefix, italic) => `${prefix}${style.italic(italic)}`).replace(/`([^`]+)`/g, (_, code) => style.brand(code));
2138
- }
2139
2283
 
2140
2284
  // src/tui/blocks.ts
2141
2285
  function countOf(n, noun) {
@@ -2168,25 +2312,25 @@ var ROLE_CONTINUATION = {
2168
2312
  error: style.red,
2169
2313
  wake_up: style.yellow
2170
2314
  };
2171
- function messageLines(block, cols) {
2172
- const text = sanitize(block.text);
2315
+ function messageLines(block2, cols) {
2316
+ const text = sanitize(block2.text);
2173
2317
  const room = Math.max(1, cols - 2);
2174
- const wrapped = block.role === "planner" || block.role === "agent" || block.role === "supervisor" ? renderMarkdown(text, room) : block.skills ? skillPaintedLines(text, block.skills, room) : wrap(text, room);
2175
- const rest = ROLE_CONTINUATION[block.role] ?? ((t) => t);
2176
- const rows = wrapped.map((line, i) => i === 0 ? ROLE_PREFIX[block.role](line) : rest(` ${line}`));
2177
- if (block.role === "user" && block.forced) {
2318
+ const wrapped = block2.role === "planner" || block2.role === "agent" || block2.role === "supervisor" ? renderMarkdown(text, room) : block2.skills ? skillPaintedLines(text, block2.skills, room) : wrap(text, room);
2319
+ const rest = ROLE_CONTINUATION[block2.role] ?? ((t) => t);
2320
+ const rows = wrapped.map((line, i) => i === 0 ? ROLE_PREFIX[block2.role](line) : rest(` ${line}`));
2321
+ if (block2.role === "user" && block2.forced) {
2178
2322
  const note = style.grey(" \xB7 sent now");
2179
2323
  if (width(rows[0]) + width(note) <= cols) rows[0] += note;
2180
2324
  else rows.push(` ${note}`);
2181
2325
  }
2182
- return block.role === "user" ? onBand(rows, cols) : rows;
2326
+ return block2.role === "user" ? onBand(rows, cols) : rows;
2183
2327
  }
2184
2328
  function onBand(rows, cols) {
2185
2329
  const band = ["", ...rows, ""];
2186
2330
  return style.enabled ? band.map((row) => style.band(pad(row, cols))) : band;
2187
2331
  }
2188
2332
  function skillPaintedLines(text, skills, room) {
2189
- const tokens = (0, import_core11.loadedSkillTokens)(text, skills);
2333
+ const tokens = (0, import_core14.loadedSkillTokens)(text, skills);
2190
2334
  return wrapLines(text, room).map(({ line, start }) => {
2191
2335
  let painted = "";
2192
2336
  let at = 0;
@@ -2200,19 +2344,19 @@ function skillPaintedLines(text, skills, room) {
2200
2344
  return painted + line.slice(at);
2201
2345
  });
2202
2346
  }
2203
- function skillLoadLine(block, cols) {
2204
- const head = block.attaches ? `\u25CF /${sanitize(block.name)} will be attached to fitting tasks \xB7 ` : block.invokedBy === "planner" ? `\u25CF ${sanitize(block.name)} skill loaded by planner \xB7 ` : `\u25CF /${sanitize(block.name)} skill loaded \xB7 `;
2205
- const line = truncate(`${head}${truncatePath(sanitize(block.path), Math.max(1, cols - width(head)))}`, cols);
2347
+ function skillLoadLine(block2, cols) {
2348
+ const head = block2.attaches ? `\u25CF /${sanitize(block2.name)} will be attached to fitting tasks \xB7 ` : block2.invokedBy === "planner" ? `\u25CF ${sanitize(block2.name)} skill loaded by planner \xB7 ` : `\u25CF /${sanitize(block2.name)} skill loaded \xB7 `;
2349
+ const line = truncate(`${head}${truncatePath(sanitize(block2.path), Math.max(1, cols - width(head)))}`, cols);
2206
2350
  return `${style.green("\u25CF")}${style.grey(line.slice(1))}`;
2207
2351
  }
2208
- function thinkingLines(block, cols, detailAll) {
2209
- const text = sanitize(block.text).trim();
2352
+ function thinkingLines(block2, cols, detailAll) {
2353
+ const text = sanitize(block2.text).trim();
2210
2354
  const head = `\u2234 Thinking (${countOf(text ? text.split(/\s+/).length : 0, "word")})`;
2211
2355
  if (detailAll) {
2212
2356
  const body = text ? wrap(text, Math.max(1, cols - 2)).map((line) => style.grey(` ${line}`)) : [];
2213
2357
  return [style.grey(truncate(head, cols)), ...body];
2214
2358
  }
2215
- const latest = block.streaming ? (text.split("\n").at(-1) ?? "").trim() : "";
2359
+ const latest = block2.streaming ? (text.split("\n").at(-1) ?? "").trim() : "";
2216
2360
  const room = cols - width(`${head} \xB7 `);
2217
2361
  if (!latest || room < 2) return [style.grey(truncate(head, cols))];
2218
2362
  const shown = width(latest) > room ? `\u2026${tailOf(latest, room - 1)}` : latest;
@@ -2224,21 +2368,21 @@ var PREVIEW_LINES = 3;
2224
2368
  function underHeader(lines) {
2225
2369
  return lines.map((line, i) => `${i === 0 ? style.grey(OUTPUT_HEAD) : OUTPUT_INDENT}${line}`);
2226
2370
  }
2227
- function unsettledOutcome(block) {
2228
- switch (block.status) {
2371
+ function unsettledOutcome(block2) {
2372
+ switch (block2.status) {
2229
2373
  case "error":
2230
2374
  return { label: "Error", paint: style.red };
2231
2375
  case "denied":
2232
- return { label: block.outcome === "refused" ? "Refused" : "Denied", paint: style.yellow };
2376
+ return { label: block2.outcome === "refused" ? "Refused" : "Denied", paint: style.yellow };
2233
2377
  case "interrupted":
2234
2378
  return { label: "Interrupted", paint: style.grey };
2235
2379
  default:
2236
2380
  return null;
2237
2381
  }
2238
2382
  }
2239
- function commandMark(block) {
2240
- if (block.status === "pending") return style.grey("\u25CB");
2241
- return (unsettledOutcome(block)?.paint ?? style.green)("\u25CF");
2383
+ function commandMark(block2) {
2384
+ if (block2.status === "pending") return style.grey("\u25CB");
2385
+ return (unsettledOutcome(block2)?.paint ?? style.green)("\u25CF");
2242
2386
  }
2243
2387
  var PATH_LIKE = /^(?![a-z][a-z0-9+.-]*:\/\/)\S*\/\S*$/i;
2244
2388
  function truncatePath(path, max) {
@@ -2251,17 +2395,17 @@ function truncatePath(path, max) {
2251
2395
  const slash = tail.search(/[\\/]/);
2252
2396
  return `\u2026${slash > 0 ? tail.slice(slash) : tail}`;
2253
2397
  }
2254
- function commandHeader(block, cols, detailAll) {
2255
- const name = sanitize(block.headline.name);
2256
- const keyArg = sanitize(block.headline.keyArg);
2398
+ function commandHeader(block2, cols, detailAll) {
2399
+ const name = sanitize(block2.headline.name);
2400
+ const keyArg = sanitize(block2.headline.keyArg);
2257
2401
  if (detailAll) {
2258
- return wrap(`${name}(${keyArg})`, Math.max(1, cols - 2)).map((line, i) => i === 0 ? `${commandMark(block)} ${line}` : ` ${line}`);
2402
+ return wrap(`${name}(${keyArg})`, Math.max(1, cols - 2)).map((line, i) => i === 0 ? `${commandMark(block2)} ${line}` : ` ${line}`);
2259
2403
  }
2260
2404
  const open = `\u25CF ${name}(`;
2261
2405
  const room = cols - width(open) - 1;
2262
2406
  const cut = PATH_LIKE.test(keyArg) ? truncatePath(keyArg, room) : truncate(keyArg, room);
2263
2407
  const text = room > 1 ? `${open}${cut})` : truncate(`${open}${keyArg})`, cols);
2264
- return [`${commandMark(block)}${text.slice(1)}`];
2408
+ return [`${commandMark(block2)}${text.slice(1)}`];
2265
2409
  }
2266
2410
  function argumentLines(args) {
2267
2411
  if (!args.trim()) return [];
@@ -2277,21 +2421,21 @@ function argumentLines(args) {
2277
2421
  return [`${key}: ${first}`, ...rest];
2278
2422
  });
2279
2423
  }
2280
- function resultLines(block, room, detailAll) {
2281
- if (block.status === "pending") return [style.grey("Running\u2026")];
2424
+ function resultLines(block2, room, detailAll) {
2425
+ if (block2.status === "pending") return [style.grey("Running\u2026")];
2282
2426
  const fit2 = (line) => detailAll ? wrap(line, room) : [truncate(line, room)];
2283
- const outcome = unsettledOutcome(block);
2427
+ const outcome = unsettledOutcome(block2);
2284
2428
  if (outcome) {
2285
- const [reason, ...rest] = (0, import_core11.outputLines)(block.output).map(sanitize);
2429
+ const [reason, ...rest] = (0, import_core14.outputLines)(block2.output).map(sanitize);
2286
2430
  const status = fit2(reason ? `${outcome.label}: ${reason}` : outcome.label).map(outcome.paint);
2287
2431
  return detailAll ? [...status, ...rest.flatMap(fit2).map(style.grey)] : status;
2288
2432
  }
2289
2433
  if (detailAll) {
2290
- const lines2 = (0, import_core11.outputLines)(block.output).map(sanitize);
2434
+ const lines2 = (0, import_core14.outputLines)(block2.output).map(sanitize);
2291
2435
  const note = lines2.length > PREVIEW_LINES ? ["\u2026 (ctrl+o to collapse)"] : [];
2292
2436
  return lines2.length > 0 ? [...lines2.flatMap(fit2), ...note].map(style.grey) : [style.grey("(no output)")];
2293
2437
  }
2294
- const { lines, hiddenLineCount } = (0, import_core11.outputPreview)(block.output, PREVIEW_LINES);
2438
+ const { lines, hiddenLineCount } = (0, import_core14.outputPreview)(block2.output, PREVIEW_LINES);
2295
2439
  if (lines.length === 0) return [style.grey("(no output)")];
2296
2440
  const more = hiddenLineCount > 0 ? [`\u2026 +${countOf(hiddenLineCount, "line")} (ctrl+o to expand)`] : [];
2297
2441
  return [...lines.map(sanitize), ...more].map((line) => style.grey(truncate(line, room)));
@@ -2304,8 +2448,8 @@ var DIFF_PAINT = {
2304
2448
  gap: style.grey
2305
2449
  };
2306
2450
  var DIFF_SIGN = { added: "+", removed: "-", context: " ", gap: "" };
2307
- function diffLines(block, diff, room, detailAll) {
2308
- const rows = (0, import_core11.diffRows)(block.output);
2451
+ function diffLines(block2, diff, room, detailAll) {
2452
+ const rows = (0, import_core14.diffRows)(block2.output);
2309
2453
  const gutter = Math.max(0, ...rows.map((row) => String(row.line ?? "").length));
2310
2454
  const draw = (row) => {
2311
2455
  const text = row.kind === "gap" ? `${" ".repeat(gutter)} \u22EE` : `${String(row.line ?? "").padStart(gutter)} ${DIFF_SIGN[row.kind]} ${sanitize(row.text)}`;
@@ -2314,14 +2458,14 @@ function diffLines(block, diff, room, detailAll) {
2314
2458
  const shown = detailAll ? rows : rows.slice(0, DIFF_PREVIEW_ROWS);
2315
2459
  const hidden = rows.length - shown.length;
2316
2460
  const more = hidden > 0 ? [style.grey(`\u2026 +${countOf(hidden, "line")} (ctrl+o to expand)`)] : [];
2317
- return [(0, import_core11.diffSummary)(diff), ...shown.flatMap(draw), ...more];
2461
+ return [(0, import_core14.diffSummary)(diff), ...shown.flatMap(draw), ...more];
2318
2462
  }
2319
- function toolLines(block, cols, detailAll) {
2463
+ function toolLines(block2, cols, detailAll) {
2320
2464
  const room = Math.max(1, cols - OUTPUT_INDENT.length);
2321
- const diff = block.diff && block.status === "ok" ? block.diff : null;
2322
- const args = detailAll && !diff ? argumentLines(block.args).flatMap((line) => wrap(line, room)).map((line) => OUTPUT_INDENT + style.grey(line)) : [];
2323
- const result = diff ? diffLines(block, diff, room, detailAll) : resultLines(block, room, detailAll);
2324
- return [...commandHeader(block, cols, detailAll), ...args, ...underHeader(result)];
2465
+ const diff = block2.diff && block2.status === "ok" ? block2.diff : null;
2466
+ const args = detailAll && !diff ? argumentLines(block2.args).flatMap((line) => wrap(line, room)).map((line) => OUTPUT_INDENT + style.grey(line)) : [];
2467
+ const result = diff ? diffLines(block2, diff, room, detailAll) : resultLines(block2, room, detailAll);
2468
+ return [...commandHeader(block2, cols, detailAll), ...args, ...underHeader(result)];
2325
2469
  }
2326
2470
  var SUBAGENT_PAINT = {
2327
2471
  running: style.yellow,
@@ -2330,24 +2474,24 @@ var SUBAGENT_PAINT = {
2330
2474
  stopped: style.grey
2331
2475
  };
2332
2476
  var CHILD_INDENT = " ";
2333
- function subagentHeader(block, cols) {
2334
- const brief = firstLine(block.brief);
2335
- const steps = block.children.filter((c) => c.type === "tool").length;
2336
- const status = block.status === "running" && steps > 0 ? `running \xB7 ${countOf(steps, "step")}` : block.status;
2477
+ function subagentHeader(block2, cols) {
2478
+ const brief = firstLine(block2.brief);
2479
+ const steps = block2.children.filter((c) => c.type === "tool").length;
2480
+ const status = block2.status === "running" && steps > 0 ? `running \xB7 ${countOf(steps, "step")}` : block2.status;
2337
2481
  const head = brief ? " Agent: " : " Agent";
2338
2482
  const briefRoom = cols - width(`\u25C6${head} ${status}`);
2339
2483
  if (briefRoom < 4) return truncate(`\u25C6${head}${brief} ${status}`, cols);
2340
- const named = `${style.brand("\u25C6")}${head}${truncate(brief, briefRoom)} ${SUBAGENT_PAINT[block.status](status)}`;
2341
- const digest = firstLine(block.digest);
2484
+ const named = `${style.brand("\u25C6")}${head}${truncate(brief, briefRoom)} ${SUBAGENT_PAINT[block2.status](status)}`;
2485
+ const digest = firstLine(block2.digest);
2342
2486
  const digestRoom = cols - width(named) - width(" \xB7 ");
2343
2487
  return digest && digestRoom >= 4 ? `${named}${style.grey(` \xB7 ${truncate(digest, digestRoom)}`)}` : named;
2344
2488
  }
2345
- function subagentLines(block, cols, detailAll) {
2346
- const header = subagentHeader(block, cols);
2489
+ function subagentLines(block2, cols, detailAll) {
2490
+ const header = subagentHeader(block2, cols);
2347
2491
  if (!detailAll) return [header];
2348
2492
  const childCols = Math.max(1, cols - CHILD_INDENT.length);
2349
- const children = block.children.flatMap((child) => blockLines(child, childCols, true).map((line) => CHILD_INDENT + line));
2350
- const digest = sanitize(block.digest).trim();
2493
+ const children = block2.children.flatMap((child) => blockLines(child, childCols, true).map((line) => CHILD_INDENT + line));
2494
+ const digest = sanitize(block2.digest).trim();
2351
2495
  const handedBack = digest ? underHeader(wrap(digest, Math.max(1, cols - OUTPUT_INDENT.length)).map(style.grey)) : [];
2352
2496
  return [header, ...children, ...handedBack];
2353
2497
  }
@@ -2372,15 +2516,15 @@ var DECIDED_BY = {
2372
2516
  budget: "web-read budget",
2373
2517
  unsupported: "not usable on this runner version"
2374
2518
  };
2375
- function approvalLine(block, cols) {
2376
- const policy = block.decidedBy && block.decidedBy !== "asked" ? DECIDED_BY[block.decidedBy] : void 0;
2377
- const [mark, status, paint] = block.status === "pending" ? ["?", "Waiting for you", style.yellow] : block.status === "granted" ? ["\u2713", policy ? `Auto-approved (${policy})` : block.forTask ? "Approved for this task" : "Approved", style.green] : block.status === "withdrawn" ? ["\xB7", "Withdrawn", style.grey] : ["\u2298", policy ? `Auto-denied (${policy})` : "Denied", style.yellow];
2378
- const note = block.note ? ` \u2014 \u201C${firstLine(block.note)}\u201D` : "";
2379
- const line = `${mark} ${status} \xB7 ${APPROVAL_KIND[block.kind]}: ${firstLine(block.subject)}${note}`;
2519
+ function approvalLine(block2, cols) {
2520
+ const policy = block2.decidedBy && block2.decidedBy !== "asked" ? DECIDED_BY[block2.decidedBy] : void 0;
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];
2522
+ const note = block2.note ? `: \u201C${firstLine(block2.note)}\u201D` : "";
2523
+ const line = `${mark} ${status} \xB7 ${APPROVAL_KIND[block2.kind]}: ${firstLine(block2.subject)}${note}`;
2380
2524
  return `${paint(mark)}${truncate(line, cols).slice(mark.length)}`;
2381
2525
  }
2382
- function approvalKeysLine(block, cols) {
2383
- const keys = ["ctrl-y allow", ...block.allowForTask ? ["ctrl-t allow for task"] : [], "ctrl-g deny (composer text is the note)"];
2526
+ function approvalKeysLine(block2, cols) {
2527
+ const keys = ["ctrl-y allow", ...block2.allowForTask ? ["ctrl-t allow for task"] : [], "ctrl-g deny (composer text is the note)"];
2384
2528
  return style.grey(truncate(` ${keys.join(" \xB7 ")}`, cols));
2385
2529
  }
2386
2530
  function checkpointCardLines(order, question, cols, approvalFirst) {
@@ -2413,22 +2557,22 @@ var COMMAND_PAINT = {
2413
2557
  failed: style.red,
2414
2558
  stopped: style.grey
2415
2559
  };
2416
- function runnerCommandLines(block, cols, detailAll) {
2417
- const typed = sanitize(`/${block.name}${block.args ? ` ${block.args}` : ""}`);
2418
- const status = ` ${block.status}`;
2419
- const head = `${style.accent("\u276F")} ${truncate(typed, Math.max(1, cols - 2 - width(status)))}${COMMAND_PAINT[block.status](status)}`;
2560
+ function runnerCommandLines(block2, cols, detailAll) {
2561
+ const typed = sanitize(`/${block2.name}${block2.args ? ` ${block2.args}` : ""}`);
2562
+ const status = ` ${block2.status}`;
2563
+ const head = `${style.accent("\u276F")} ${truncate(typed, Math.max(1, cols - 2 - width(status)))}${COMMAND_PAINT[block2.status](status)}`;
2420
2564
  const room = Math.max(1, cols - OUTPUT_INDENT.length);
2421
- const printed = (0, import_core11.outputLines)(block.output).map(sanitize);
2565
+ const printed = (0, import_core14.outputLines)(block2.output).map(sanitize);
2422
2566
  const shown = detailAll ? printed.flatMap((line) => wrap(line, room)) : printed.slice(0, PREVIEW_LINES).map((line) => truncate(line, room));
2423
2567
  return [head, ...underHeader(shown.map(style.grey))];
2424
2568
  }
2425
- function compactionLine(block, cols) {
2426
- const sizes = block.preTokens !== void 0 && block.postTokens !== void 0 ? ` \xB7 ${compactCount(block.preTokens)} \u2192 ${compactCount(block.postTokens)} tokens` : "";
2427
- const text = block.trigger === "auto" && !sizes ? "\u2261 Context compacted by the runner on its own" : `\u2261 Context compacted${sizes}`;
2569
+ function compactionLine(block2, cols) {
2570
+ const sizes = block2.preTokens !== void 0 && block2.postTokens !== void 0 ? ` \xB7 ${compactCount(block2.preTokens)} \u2192 ${compactCount(block2.postTokens)} tokens` : "";
2571
+ const text = block2.trigger === "auto" && !sizes ? "\u2261 Context compacted by the runner on its own" : `\u2261 Context compacted${sizes}`;
2428
2572
  return style.grey(truncate(text, cols));
2429
2573
  }
2430
- function runnerLoadedLines(block, cols, detailAll) {
2431
- const of = (kind) => block.commands.filter((c) => c.kind === kind).map((c) => `/${sanitize(c.name)}`);
2574
+ function runnerLoadedLines(block2, cols, detailAll) {
2575
+ const of = (kind) => block2.commands.filter((c) => c.kind === kind).map((c) => `/${sanitize(c.name)}`);
2432
2576
  const skills = of("skill");
2433
2577
  const commands = of("command");
2434
2578
  const builtins = of("builtin");
@@ -2436,66 +2580,66 @@ function runnerLoadedLines(block, cols, detailAll) {
2436
2580
  countOf(skills.length, "skill"),
2437
2581
  countOf(commands.length, "command"),
2438
2582
  countOf(builtins.length, "built-in command"),
2439
- countOf(block.mcpServers.length, "MCP server")
2583
+ countOf(block2.mcpServers.length, "MCP server")
2440
2584
  ];
2441
2585
  const head = truncate(`\u25CF Runner loaded ${counts.join(", ")}`, cols);
2442
2586
  const lines = [`${style.green("\u25CF")}${style.grey(head.slice(1))}`];
2443
2587
  if (!detailAll) return lines;
2444
2588
  const room = Math.max(1, cols - OUTPUT_INDENT.length);
2445
- const list = (label, names) => names.length > 0 ? wrap(`${label}: ${names.join(" ")}`, room) : [];
2446
- const servers = block.mcpServers.map((m) => `${sanitize(m.name)}${m.status ? ` (${sanitize(m.status)})` : ""}`);
2589
+ const list2 = (label, names) => names.length > 0 ? wrap(`${label}: ${names.join(" ")}`, room) : [];
2590
+ const servers = block2.mcpServers.map((m) => `${sanitize(m.name)}${m.status ? ` (${sanitize(m.status)})` : ""}`);
2447
2591
  return [
2448
2592
  ...lines,
2449
- ...[...list("skills", skills), ...list("commands", commands), ...list("built-in", builtins), ...servers.length > 0 ? wrap(`MCP servers: ${servers.join(", ")}`, room) : []].map((line) => OUTPUT_INDENT + style.grey(line))
2593
+ ...[...list2("skills", skills), ...list2("commands", commands), ...list2("built-in", builtins), ...servers.length > 0 ? wrap(`MCP servers: ${servers.join(", ")}`, room) : []].map((line) => OUTPUT_INDENT + style.grey(line))
2450
2594
  ];
2451
2595
  }
2452
- function planLine(block, cols) {
2453
- const count = block.taskCount === void 0 ? "" : ` (${countOf(block.taskCount, "task")})`;
2454
- const text = block.status === "building" ? "Building plan\u2026" : `Plan ${block.status}${count}`;
2596
+ function planLine(block2, cols) {
2597
+ const count = block2.taskCount === void 0 ? "" : ` (${countOf(block2.taskCount, "task")})`;
2598
+ const text = block2.status === "building" ? "Building plan\u2026" : `Plan ${block2.status}${count}`;
2455
2599
  return `${style.brand("\u25C7")}${truncate(`\u25C7 ${text}`, cols).slice(1)}`;
2456
2600
  }
2457
- function blockLines(block, cols, detailAll) {
2458
- switch (block.type) {
2601
+ function blockLines(block2, cols, detailAll) {
2602
+ switch (block2.type) {
2459
2603
  case "message":
2460
- return messageLines(block, cols);
2604
+ return messageLines(block2, cols);
2461
2605
  case "thinking":
2462
- return thinkingLines(block, cols, detailAll);
2606
+ return thinkingLines(block2, cols, detailAll);
2463
2607
  case "tool":
2464
- return toolLines(block, cols, detailAll);
2608
+ return toolLines(block2, cols, detailAll);
2465
2609
  case "subagent":
2466
- return subagentLines(block, cols, detailAll);
2610
+ return subagentLines(block2, cols, detailAll);
2467
2611
  case "approval":
2468
- return [approvalLine(block, cols)];
2612
+ return [approvalLine(block2, cols)];
2469
2613
  case "plan":
2470
- return [planLine(block, cols)];
2614
+ return [planLine(block2, cols)];
2471
2615
  case "skill_load":
2472
- return [skillLoadLine(block, cols)];
2616
+ return [skillLoadLine(block2, cols)];
2473
2617
  case "command":
2474
- return runnerCommandLines(block, cols, detailAll);
2618
+ return runnerCommandLines(block2, cols, detailAll);
2475
2619
  case "compaction":
2476
- return [compactionLine(block, cols)];
2620
+ return [compactionLine(block2, cols)];
2477
2621
  case "runner_loaded":
2478
- return runnerLoadedLines(block, cols, detailAll);
2622
+ return runnerLoadedLines(block2, cols, detailAll);
2479
2623
  // Pinned under the pane rather than drawn in it — see `tokenLine`.
2480
2624
  case "usage":
2481
2625
  return [];
2482
2626
  }
2483
2627
  }
2484
2628
  var drawnBlocks = /* @__PURE__ */ new WeakMap();
2485
- function drawn(block, cols, detailAll) {
2486
- const cached = drawnBlocks.get(block);
2629
+ function drawn(block2, cols, detailAll) {
2630
+ const cached = drawnBlocks.get(block2);
2487
2631
  if (cached && cached.cols === cols && cached.detailAll === detailAll) return cached.lines;
2488
- const lines = blockLines(block, cols, detailAll);
2489
- drawnBlocks.set(block, { cols, detailAll, lines });
2632
+ const lines = blockLines(block2, cols, detailAll);
2633
+ drawnBlocks.set(block2, { cols, detailAll, lines });
2490
2634
  return lines;
2491
2635
  }
2492
- function conversationLines(blocks, cols, detailAll, answering) {
2636
+ function conversationLines(blocks2, cols, detailAll, answering) {
2493
2637
  const lines = [];
2494
- for (const [i, block] of blocks.entries()) {
2495
- if (block.type === "usage") continue;
2496
- lines.push(...drawn(block, cols, detailAll));
2497
- if (block === answering) lines.push(approvalKeysLine(block, cols));
2498
- if (blocks[i + 1]?.type !== "skill_load") lines.push("");
2638
+ for (const [i, block2] of blocks2.entries()) {
2639
+ if (block2.type === "usage") continue;
2640
+ lines.push(...drawn(block2, cols, detailAll));
2641
+ if (block2 === answering) lines.push(approvalKeysLine(block2, cols));
2642
+ if (blocks2[i + 1]?.type !== "skill_load") lines.push("");
2499
2643
  }
2500
2644
  return lines;
2501
2645
  }
@@ -2509,8 +2653,8 @@ function formatCost(currency, amount) {
2509
2653
  const value = `${whole}.${frac.padEnd(2, "0")}`;
2510
2654
  return currency.toLowerCase() === "usd" ? `$${value}` : `${value} ${currency.toUpperCase()}`;
2511
2655
  }
2512
- function tokenLine(blocks, cols) {
2513
- const usage = blocks[blocks.length - 1];
2656
+ function tokenLine(blocks2, cols) {
2657
+ const usage = blocks2[blocks2.length - 1];
2514
2658
  if (usage?.type !== "usage") return null;
2515
2659
  const parts = usageMeasures(usage.totals, false);
2516
2660
  if (usage.model && !usage.contextFill) parts.push("ctx unknown");
@@ -2536,7 +2680,7 @@ function taskUsageText(usage) {
2536
2680
 
2537
2681
  // src/commands/status.ts
2538
2682
  var import_fs4 = require("fs");
2539
- var import_core12 = require("@ordewell/core");
2683
+ var import_core15 = require("@ordewell/core");
2540
2684
  async function handleStatus(subArgs, injectedApi) {
2541
2685
  const sessionId = flag(subArgs, "--session-id");
2542
2686
  const workspace = flag(subArgs, "--workspace") || process.cwd();
@@ -2591,8 +2735,8 @@ function emitJson(payload, outputPath) {
2591
2735
  }
2592
2736
  function printOneSession(sessionId, meta, plan) {
2593
2737
  const allTasks = allTasksOf(plan);
2594
- const everyTask = (0, import_core12.flattenTasks)(allTasks);
2595
- const blocked = (0, import_core12.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 ?? [] })));
2596
2740
  const count = (status) => everyTask.filter((t) => t.status === status || status === "blocked" && blocked.has(t.id)).length;
2597
2741
  console.log(`Session: ${sessionId}`);
2598
2742
  console.log(`Goal: ${meta.goal}`);
@@ -2600,29 +2744,39 @@ function printOneSession(sessionId, meta, plan) {
2600
2744
  console.log(`Phase: ${plan?.phase || "unknown"} | Created: ${meta.createdAt}`);
2601
2745
  console.log(`Tasks: ${everyTask.length} total | ${count("completed")} done | ${count("failed")} failed | ${count("in_progress")} running | ${count("blocked")} blocked | ${count("skipped")} skipped
2602
2746
  `);
2603
- const usage = (0, import_core12.planUsage)(allTasks);
2747
+ const usage = (0, import_core15.planUsage)(allTasks);
2604
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;
2605
2751
  for (const t of allTasks) {
2606
2752
  const icon = iconFor(blocked.has(t.id) ? "blocked" : t.status);
2607
2753
  const model = t.assignedModel ? ` (${t.assignedModel.modelLabel})` : "";
2608
2754
  const shortId = (t.id ?? "").slice(-8);
2609
- console.log(` ${(0, import_core12.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}] ${t.title}${model}`);
2610
- const row = (0, import_core12.taskRowView)({ ...t, dependencies: t.dependencies ?? [] }, { tasks: allTasks });
2611
- if (row.blockedBy) console.log(` ${(0, import_core12.blockedByLabel)(row.blockedBy)}`);
2612
- if (row.status === "skipped") console.log(` ${(0, import_core12.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)}`);
2613
2761
  if (row.usage) console.log(` ${taskUsageText(row.usage)}`);
2614
2762
  for (const sub of t.subtasks || []) {
2615
2763
  const subIcon = iconFor(blocked.has(sub.id) ? "blocked" : sub.status);
2616
2764
  const subModel = sub.assignedModel ? ` (${sub.assignedModel.modelLabel})` : "";
2617
2765
  const subShortId = (sub.id ?? "").slice(-8);
2618
- console.log(` ${(0, import_core12.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
2619
- const subRow = (0, import_core12.taskRowView)({ ...sub, dependencies: sub.dependencies ?? [] }, { parent: t, tasks: allTasks });
2620
- if (subRow.blockedBy) console.log(` ${(0, import_core12.blockedByLabel)(subRow.blockedBy)}`);
2621
- if (subRow.status === "skipped") console.log(` ${(0, import_core12.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)}`);
2622
2772
  if (subRow.usage) console.log(` ${taskUsageText(subRow.usage)}`);
2623
2773
  }
2624
2774
  }
2625
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
+ }
2626
2780
 
2627
2781
  // src/commands/stop.ts
2628
2782
  async function handleStop(subArgs) {
@@ -2671,10 +2825,32 @@ async function handleWeb(subArgs) {
2671
2825
  }
2672
2826
 
2673
2827
  // src/commands/models.ts
2674
- var import_core14 = require("@ordewell/core");
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
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
+ }
2675
2852
 
2676
2853
  // src/commands/model.ts
2677
- var import_core13 = require("@ordewell/core");
2678
2854
  var USAGE = "Usage: ordewell model [show | set <model-id>]";
2679
2855
  async function handleModel(subArgs, injectedApi) {
2680
2856
  const [action, modelId] = positionals(subArgs);
@@ -2689,7 +2865,7 @@ async function handleModel(subArgs, injectedApi) {
2689
2865
  if (!modelId) fail(USAGE);
2690
2866
  const settings = await api.getSettings();
2691
2867
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
2692
- const runner = (0, import_core13.runnerForProvider)(provider);
2868
+ const runner = (0, import_core17.runnerForProvider)(provider);
2693
2869
  if (runner) {
2694
2870
  const known = (await fetchCatalog(api)).models.filter((m) => m.runners?.includes(runner));
2695
2871
  if (known.length > 0 && !known.some((m) => m.id === modelId)) {
@@ -2698,6 +2874,8 @@ async function handleModel(subArgs, injectedApi) {
2698
2874
  `Run \`ordewell model\` to see what ${runner} offers.`
2699
2875
  );
2700
2876
  }
2877
+ const refusal = known.length === 0 ? vendorModelRefusal(provider, modelId, import_core17.HEADLESS_CLI_COMMANDS.pickPlanner) : null;
2878
+ if (refusal) fail(refusal);
2701
2879
  }
2702
2880
  const settled = await api.updateSettings({ orchestratorModel: modelId });
2703
2881
  const effort = typeof settled.plannerThinkingEffort === "string" ? settled.plannerThinkingEffort : "";
@@ -2712,14 +2890,14 @@ async function showModels(api) {
2712
2890
  const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
2713
2891
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
2714
2892
  const current = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
2715
- const runner = (0, import_core13.runnerForProvider)(provider);
2893
+ const runner = (0, import_core17.runnerForProvider)(provider);
2716
2894
  const options = runner ? catalog.models.filter((m) => m.runners?.includes(runner)) : catalog.orchestratorModels;
2717
2895
  console.log(`
2718
2896
  Planner model: ${current || "(backend default)"}`);
2719
- console.log(`Planner backend: ${provider || "(default)"}${runner ? ` \u2014 showing ${runner} models only` : ""}
2897
+ console.log(`Planner backend: ${provider || "(default)"}${runner ? `: showing ${runner} models only` : ""}
2720
2898
  `);
2721
2899
  if (options.length === 0) {
2722
- 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>`.");
2723
2901
  }
2724
2902
  for (const m of options) {
2725
2903
  const detail = [
@@ -2728,7 +2906,7 @@ Planner model: ${current || "(backend default)"}`);
2728
2906
  m.variants?.length ? `${m.variants.length} effort level${m.variants.length === 1 ? "" : "s"}` : void 0
2729
2907
  ].filter(Boolean).join(" \xB7 ");
2730
2908
  console.log(` ${m.id === current ? "*" : " "} ${m.id}`);
2731
- if (detail) console.log(` ${m.label} \u2014 ${detail}`);
2909
+ if (detail) console.log(` ${m.label}: ${detail}`);
2732
2910
  }
2733
2911
  const failed = Object.entries(catalog.providerErrors);
2734
2912
  for (const [id, message] of failed) {
@@ -2746,8 +2924,8 @@ async function handleModels(subArgs) {
2746
2924
  const rest = subArgs.filter((_, i) => i !== setIdx && i !== setIdx + 1);
2747
2925
  return handleModel(["set", subArgs[setIdx + 1], ...rest]);
2748
2926
  }
2749
- const config = new import_core14.EnvConfig();
2750
- const providers = (0, import_core14.configuredProviders)(config);
2927
+ const config = new import_core18.EnvConfig();
2928
+ const providers = (0, import_core18.configuredProviders)(config);
2751
2929
  if (providers.length === 0) {
2752
2930
  console.error("No providers configured. Set an API key for a provider, e.g.:");
2753
2931
  console.error(" OPENROUTER_API_KEY=... (OpenRouter)");
@@ -2756,19 +2934,19 @@ async function handleModels(subArgs) {
2756
2934
  console.error(" OPENAI_COMPATIBLE_BASE_URL=... (self-hosted / OpenAI-compatible)");
2757
2935
  process.exit(1);
2758
2936
  }
2759
- const { apiKeys, baseUrls } = (0, import_core14.collectProviderCredentials)(config);
2937
+ const { apiKeys, baseUrls } = (0, import_core18.collectProviderCredentials)(config);
2760
2938
  let models;
2761
2939
  let errors;
2762
2940
  try {
2763
- const result = await (0, import_core14.fetchAllProviderModels)({ apiKeys, baseUrls });
2764
- models = (0, import_core14.toOrchestratorOptions)(result.models, import_core14.ORCHESTRATOR_SHORTCUTS);
2941
+ const result = await (0, import_core18.fetchAllProviderModels)({ apiKeys, baseUrls });
2942
+ models = (0, import_core18.toOrchestratorOptions)(result.models, import_core18.ORCHESTRATOR_SHORTCUTS);
2765
2943
  errors = result.errors;
2766
2944
  } catch (err) {
2767
2945
  console.error(`Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`);
2768
2946
  process.exit(1);
2769
2947
  return;
2770
2948
  }
2771
- const providerLabel = (p) => (0, import_core14.getProviderMeta)(p)?.label ?? p;
2949
+ const providerLabel = (p) => (0, import_core18.getProviderMeta)(p)?.label ?? p;
2772
2950
  console.log(
2773
2951
  `
2774
2952
  Available models (${models.length}) across ${providers.length} configured provider(s):`
@@ -2808,7 +2986,7 @@ ${providerLabel(provider)}`);
2808
2986
 
2809
2987
  // src/commands/setup.ts
2810
2988
  var import_fs6 = require("fs");
2811
- var import_core15 = require("@ordewell/core");
2989
+ var import_core19 = require("@ordewell/core");
2812
2990
  function handleSetup(subArgs) {
2813
2991
  const modelIdx = subArgs.indexOf("--model");
2814
2992
  const chosenModel = modelIdx !== -1 ? subArgs[modelIdx + 1] : null;
@@ -2851,9 +3029,9 @@ function handleSetup(subArgs) {
2851
3029
  # OPENAI_COMPATIBLE_BASE_URL=http://localhost:11434/v1
2852
3030
  # OPENAI_COMPATIBLE_API_KEY=your-key-here
2853
3031
 
2854
- # 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,
2855
3033
  # it uses that agent's own subscription. Set the model to one of its own.
2856
- # AI_PROVIDER=claude-code|codex|opencode
3034
+ # AI_PROVIDER=${runnerIdChoice()}
2857
3035
  # ORCHESTRATOR_MODEL=haiku
2858
3036
  # ORDEWELL_PLANNER_EFFORT=low
2859
3037
 
@@ -2880,13 +3058,13 @@ function handleSetup(subArgs) {
2880
3058
  # STEPFUN_API_KEY=...
2881
3059
  # SILICONFLOW_API_KEY=...
2882
3060
  `;
2883
- (0, import_core15.writePrivateFile)(envFile, content);
3061
+ (0, import_core19.writePrivateFile)(envFile, content);
2884
3062
  console.log(`Wrote: ${envFile}`);
2885
3063
  console.log("Review and edit the file, then run: ordewell tui");
2886
3064
  }
2887
3065
 
2888
3066
  // src/commands/skills.ts
2889
- var import_core16 = require("@ordewell/core");
3067
+ var import_core20 = require("@ordewell/core");
2890
3068
  var import_path5 = require("path");
2891
3069
  function invocation(skill) {
2892
3070
  if (skill.userInvocable && skill.modelInvocable) return "both";
@@ -2899,22 +3077,22 @@ function handleSkills(subArgs) {
2899
3077
  fail("Usage: ordewell skills [--workspace /path] [--json]");
2900
3078
  }
2901
3079
  const workspace = (0, import_path5.resolve)(flag(subArgs, "--workspace") || process.cwd());
2902
- const catalog = (0, import_core16.createSkillsService)(workspace, new import_core16.EnvConfig().workspaceRepos).readCatalog();
3080
+ const catalog = (0, import_core20.createSkillsService)(workspace, new import_core20.EnvConfig().workspaceRepos).readCatalog();
2903
3081
  const skills = catalog.skills.map((skill) => ({
2904
3082
  name: skill.name,
2905
3083
  scope: skill.source,
2906
3084
  appliesTo: skill.appliesTo,
2907
3085
  invocation: invocation(skill),
2908
- path: (0, import_core16.abbreviateHome)(skill.path)
3086
+ path: (0, import_core20.abbreviateHome)(skill.path)
2909
3087
  }));
2910
3088
  const shadowed = catalog.shadowed.map(({ skill, shadowedBy }) => ({
2911
3089
  name: skill.name,
2912
- path: (0, import_core16.abbreviateHome)(skill.path),
2913
- shadowedBy: { scope: shadowedBy.source, path: (0, import_core16.abbreviateHome)(shadowedBy.path) }
3090
+ path: (0, import_core20.abbreviateHome)(skill.path),
3091
+ shadowedBy: { scope: shadowedBy.source, path: (0, import_core20.abbreviateHome)(shadowedBy.path) }
2914
3092
  }));
2915
3093
  const invalid = catalog.invalid.map((skill) => ({
2916
3094
  name: skill.name,
2917
- path: (0, import_core16.abbreviateHome)(skill.path),
3095
+ path: (0, import_core20.abbreviateHome)(skill.path),
2918
3096
  reason: skill.reason
2919
3097
  }));
2920
3098
  if (hasFlag(subArgs, "--json")) {
@@ -3029,7 +3207,7 @@ async function handleShow(args, api) {
3029
3207
  }
3030
3208
 
3031
3209
  // src/commands/task-control.ts
3032
- var import_core17 = require("@ordewell/core");
3210
+ var import_core21 = require("@ordewell/core");
3033
3211
  var PAST_TENSE = {
3034
3212
  run: "Task started.",
3035
3213
  "force-start": "Task force-started.",
@@ -3072,8 +3250,8 @@ async function confirmPastGate(subArgs, api, sessionId, taskId, confirm2) {
3072
3250
  if (hasFlag(subArgs, "--yes")) return;
3073
3251
  const unmerged = await api.getMergeGate(sessionId, taskId).catch(() => []);
3074
3252
  if (unmerged.length === 0) return;
3075
- const question = `${(0, import_core17.pastGateConfirmation)(unmerged.map((d) => (0, import_core17.titledTaskRef)(d)))} Start it anyway?`;
3076
- 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.");
3077
3255
  }
3078
3256
  function makeHandler(action, command = action) {
3079
3257
  const yes = PASSES_GATE.has(action) ? " [--yes]" : "";
@@ -3121,7 +3299,7 @@ async function handleCheckpoint(subArgs, injectedApi) {
3121
3299
  process.exit(1);
3122
3300
  }
3123
3301
  if (answer === "approve" && reason) {
3124
- 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.
3125
3303
  ${usage}`);
3126
3304
  process.exit(1);
3127
3305
  }
@@ -3182,7 +3360,7 @@ async function handleSkip(subArgs, api) {
3182
3360
  async (client, sessionId, taskId) => {
3183
3361
  try {
3184
3362
  await client.skipTask(sessionId, taskId, flag(subArgs, "--reason"));
3185
- 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.");
3186
3364
  } catch (err) {
3187
3365
  console.error(`Failed to skip task: ${err.message}`);
3188
3366
  process.exit(1);
@@ -3290,7 +3468,7 @@ async function handleSessions(subArgs, injectedApi) {
3290
3468
  }
3291
3469
  const { meta } = session;
3292
3470
  saveLastSession(meta.id, meta.goal, meta.runners || [], workspace);
3293
- 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}.`);
3294
3472
  console.log(" `ordewell status` for detail, `ordewell run` to execute.");
3295
3473
  return;
3296
3474
  }
@@ -3314,7 +3492,7 @@ async function handleSessions(subArgs, injectedApi) {
3314
3492
  }
3315
3493
 
3316
3494
  // src/commands/conversation.ts
3317
- 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.";
3318
3496
  async function adopted(subArgs, injectedApi) {
3319
3497
  const workspace = flag(subArgs, "--workspace") || process.cwd();
3320
3498
  const sessionId = resolveSessionId(subArgs);
@@ -3336,7 +3514,7 @@ async function handleFork(subArgs, injectedApi) {
3336
3514
  fail(`Failed to fork: ${err.message}`);
3337
3515
  }
3338
3516
  saveLastSession(fork.sessionId, fork.goal, fork.plan.runners ?? [], workspace);
3339
- 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.`);
3340
3518
  console.log(` \`ordewell sessions load ${sessionId}\` to go back to the original.`);
3341
3519
  }
3342
3520
  async function handleCompact(subArgs, injectedApi) {
@@ -3345,7 +3523,7 @@ async function handleCompact(subArgs, injectedApi) {
3345
3523
  try {
3346
3524
  result = await api.compactConversation(sessionId);
3347
3525
  } catch (err) {
3348
- fail(`Failed to condense: ${err.message}`);
3526
+ fail(`Failed to condense: ${headlessPlannerRefusal(err) ?? err.message}`);
3349
3527
  }
3350
3528
  console.log(`Condensed ${sessionId}. The last two exchanges were kept as they were; the tasks are unchanged.
3351
3529
  `);
@@ -3363,7 +3541,7 @@ async function handleRewind(subArgs, injectedApi) {
3363
3541
  fail(`Failed to list messages: ${err.message}`);
3364
3542
  }
3365
3543
  if (targets.length === 0) {
3366
- 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.");
3367
3545
  return;
3368
3546
  }
3369
3547
  console.log("Your messages, most recent first:\n");
@@ -3378,31 +3556,31 @@ async function handleRewind(subArgs, injectedApi) {
3378
3556
  fail(`Failed to rewind: ${err.message}`);
3379
3557
  }
3380
3558
  saveLastSession(fork.sessionId, fork.goal, fork.plan.runners ?? [], workspace);
3381
- 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.`);
3382
3560
  console.log(` \`ordewell sessions load ${sessionId}\` to go back to the original.`);
3383
3561
  console.log("\nThe message you rewound to, to resend or edit:\n");
3384
3562
  console.log(fork.rewoundMessage);
3385
3563
  }
3386
3564
 
3387
3565
  // src/tui/transcript.ts
3388
- var import_core18 = require("@ordewell/core");
3566
+ var import_core22 = require("@ordewell/core");
3389
3567
  function show(state, { view: conversation, gate: turnGate }) {
3390
3568
  const gated = turnGate === state.turnGate ? state : { ...state, turnGate };
3391
3569
  if (conversation === state.conversation) return gated;
3392
3570
  return { ...gated, conversation };
3393
3571
  }
3394
3572
  function fold(state, input) {
3395
- return show(state, { view: (0, import_core18.reduceConversation)(state.conversation, input), gate: state.turnGate });
3573
+ return show(state, { view: (0, import_core22.reduceConversation)(state.conversation, input), gate: state.turnGate });
3396
3574
  }
3397
3575
  function say(state, role, text) {
3398
3576
  const said = fold(state, { type: "local_entry", role, text });
3399
3577
  return said.conversation.nextId > state.conversation.nextId ? { ...said, scroll: 0 } : said;
3400
3578
  }
3401
3579
  function hear(state, message) {
3402
- return show(state, (0, import_core18.followTurn)(state.conversation, state.turnGate, message));
3580
+ return show(state, (0, import_core22.followTurn)(state.conversation, state.turnGate, message));
3403
3581
  }
3404
3582
  function cutTurn(state) {
3405
- return show(state, (0, import_core18.stopTurn)(state.conversation, state.turnGate));
3583
+ return show(state, (0, import_core22.stopTurn)(state.conversation, state.turnGate));
3406
3584
  }
3407
3585
  function wiped(view) {
3408
3586
  return { ...view, blocks: view.blocks.filter((b) => b.type === "usage") };
@@ -3411,16 +3589,16 @@ function wiped(view) {
3411
3589
  // src/tui/handoff.ts
3412
3590
  var HANDOFF_ACTIONS = [
3413
3591
  { id: "review", label: "Review diff", hint: "the branch against the commit it forked from" },
3414
- { id: "merge", label: "Merge", hint: "into the branch you have checked out \u2014 asks first" },
3415
- { 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)" },
3416
3594
  { id: "cleanup", label: "Clean up", hint: "remove worktrees and task branches, keep this branch" }
3417
3595
  ];
3418
3596
  function handoffActions(handoff) {
3419
3597
  if (!isRepoGroup(handoff)) return HANDOFF_ACTIONS;
3420
3598
  return [
3421
3599
  { id: "review", label: "Review diff", hint: "each repository's branch against the commit it forked from" },
3422
- { id: "merge", label: "Merge all", hint: "every repository's branch into what you have checked out there \u2014 asks first" },
3423
- { 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)" },
3424
3602
  { id: "cleanup", label: "Clean up", hint: "remove worktrees and task branches everywhere, keep the integration branches" }
3425
3603
  ];
3426
3604
  }
@@ -3433,7 +3611,7 @@ function currentHandoff(state) {
3433
3611
  }
3434
3612
  var midRun = (state) => state.status === "executing";
3435
3613
  function handoffCommand(state, arg) {
3436
- 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.");
3437
3615
  if (arg === void 0) return { state: { ...state, overlay: { kind: "handoff", index: 0, diff: null } }, effects: [] };
3438
3616
  const action = HANDOFF_ACTIONS.find((a) => a.id === arg.toLowerCase());
3439
3617
  if (!action) return fail2(state, `Usage: ${HANDOFF_USAGE}`);
@@ -3446,13 +3624,13 @@ function handoffArrived(state, handoff) {
3446
3624
  const tasks = `${landed} task${landed === 1 ? "" : "s"} landed`;
3447
3625
  const received = { ...state, handoff };
3448
3626
  if (handoff.deferredTo === "supervisor") {
3449
- 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.`);
3450
3628
  }
3451
3629
  const returned = state.handoff?.deferredTo === "supervisor" && handoffBranch(state.handoff) === branch;
3452
3630
  const told = say(
3453
3631
  received,
3454
3632
  "system",
3455
- 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.`
3456
3634
  );
3457
3635
  return state.overlay ? told : { ...told, overlay: { kind: "handoff", index: 0, diff: null } };
3458
3636
  }
@@ -3490,12 +3668,12 @@ function runHandoffAction(state, id) {
3490
3668
  overlay: isRepoGroup(handoff) ? {
3491
3669
  kind: "confirm",
3492
3670
  title: "Merge all into your branches?",
3493
- 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)}`,
3494
3672
  action: { kind: "merge-run" }
3495
3673
  } : {
3496
3674
  kind: "confirm",
3497
3675
  title: "Merge into your branch?",
3498
- 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)}`,
3499
3677
  action: { kind: "merge-run" }
3500
3678
  }
3501
3679
  },
@@ -3508,7 +3686,7 @@ function runHandoffAction(state, id) {
3508
3686
  overlay: {
3509
3687
  kind: "confirm",
3510
3688
  title: "Discard this run?",
3511
- 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.`,
3512
3690
  action: { kind: "discard-run" }
3513
3691
  }
3514
3692
  },
@@ -3603,7 +3781,7 @@ async function handleHandoff(subArgs, injectedApi, confirm2 = askYesNo) {
3603
3781
  if (action === void 0) {
3604
3782
  console.log(`${USAGE4}
3605
3783
  `);
3606
- 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}`);
3607
3785
  return;
3608
3786
  }
3609
3787
  if (!HANDOFF_ACTIONS.some((a) => a.id === action)) fail(`Unknown handoff action "${action}".`, USAGE4);
@@ -3616,7 +3794,7 @@ async function handleHandoff(subArgs, injectedApi, confirm2 = askYesNo) {
3616
3794
  const asked = hasFlag(subArgs, "--yes");
3617
3795
  const confirmed = async (question) => {
3618
3796
  if (asked || await confirm2(question)) return;
3619
- 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.");
3620
3798
  };
3621
3799
  const attempt = async (work) => {
3622
3800
  try {
@@ -3651,17 +3829,7 @@ async function handleHandoff(subArgs, injectedApi, confirm2 = askYesNo) {
3651
3829
  }
3652
3830
 
3653
3831
  // src/commands/planner.ts
3654
- var import_core19 = require("@ordewell/core");
3655
-
3656
- // src/plannerModelSwitch.ts
3657
- function plannerSwitchRecall(settings) {
3658
- if (settings.switchRecall) return settings.switchRecall;
3659
- const model = settings.orchestratorModel;
3660
- return model ? { model, effort: settings.plannerThinkingEffort, source: "remembered" } : { model: "", effort: "", source: "none" };
3661
- }
3662
-
3663
- // src/commands/planner.ts
3664
- var isAiProvider = (id) => Object.hasOwn(import_core19.ALL_PROVIDERS, id);
3832
+ var import_core23 = require("@ordewell/core");
3665
3833
  async function handlePlanner(subArgs, injectedApi) {
3666
3834
  const api = await connect(subArgs, injectedApi);
3667
3835
  const [wanted] = positionals(subArgs);
@@ -3670,20 +3838,25 @@ async function handlePlanner(subArgs, injectedApi) {
3670
3838
  return;
3671
3839
  }
3672
3840
  const provider = wanted.toLowerCase();
3673
- if (!isAiProvider(provider)) {
3841
+ const meta = (0, import_core23.getProviderMeta)(provider);
3842
+ if (!meta) {
3674
3843
  fail(`Unknown planner: ${provider}`, "Run `ordewell planner` with no arguments to see the list.");
3675
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
+ }
3676
3850
  const settings = await persistEnv(api, { AI_PROVIDER: provider });
3677
3851
  const recall2 = plannerSwitchRecall(settings);
3678
3852
  writeEnv({ ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
3679
- const meta = import_core19.ALL_PROVIDERS[provider];
3680
3853
  const catalog = await fetchCatalog(api);
3681
3854
  console.log(
3682
- (0, import_core19.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}.`
3683
3856
  );
3684
3857
  console.log(plannerModelLine(recall2));
3685
- if (!(0, import_core19.isCliProvider)(provider) && !catalog.providers.includes(provider)) {
3686
- 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>\`.`);
3687
3860
  }
3688
3861
  }
3689
3862
  function plannerModelLine(recall2) {
@@ -3691,7 +3864,7 @@ function plannerModelLine(recall2) {
3691
3864
  case "remembered":
3692
3865
  return ` Restored its planner model: ${recall2.model}.`;
3693
3866
  case "catalog-default":
3694
- 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>\`.`;
3695
3868
  case "none":
3696
3869
  return " Pick a model with `ordewell model set <id>`, or leave it on the default.";
3697
3870
  }
@@ -3699,16 +3872,16 @@ function plannerModelLine(recall2) {
3699
3872
  async function showPlanners(api) {
3700
3873
  const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
3701
3874
  const current = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
3702
- console.log("\nCoding agents (no API key \u2014 they use their own subscription)");
3703
- for (const id of import_core19.CLI_PROVIDERS) {
3704
- const runner = (0, import_core19.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);
3705
3878
  const discovered = runner ? catalog.models.some((m) => m.runners?.includes(runner)) : false;
3706
- console.log(` ${marker(id === current)} ${id.padEnd(14)} ${import_core19.ALL_PROVIDERS[id].label}${discovered ? "" : " (not detected)"}`);
3879
+ console.log(` ${marker(id === current)} ${id.padEnd(14)} ${(0, import_core23.getProviderMeta)(id)?.label ?? id}${discovered ? "" : " (not detected)"}`);
3707
3880
  }
3708
3881
  console.log("\nAPI providers");
3709
- for (const id of import_core19.PROVIDER_PRIORITY) {
3882
+ for (const id of import_core23.PROVIDER_PRIORITY) {
3710
3883
  const configured = catalog.providers.includes(id);
3711
- const meta = import_core19.ALL_PROVIDERS[id];
3884
+ const meta = (0, import_core23.getProviderMeta)(id);
3712
3885
  console.log(` ${marker(id === current)} ${id.padEnd(14)} ${meta.label}${configured ? "" : ` (no ${meta.apiKeyEnvVar})`}`);
3713
3886
  }
3714
3887
  console.log(`
@@ -3725,11 +3898,11 @@ async function handlePlannerEffort(subArgs, injectedApi) {
3725
3898
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
3726
3899
  const modelId = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
3727
3900
  const currentEffort = typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : "";
3728
- const runner = isAiProvider(provider) ? (0, import_core19.runnerForProvider)(provider) : null;
3901
+ const runner = (0, import_core23.runnerForProvider)(provider);
3729
3902
  if (!runner) {
3730
3903
  fail(
3731
3904
  "Thinking effort applies to a coding-agent planner.",
3732
- "Switch with: ordewell planner claude-code|codex|opencode"
3905
+ `Switch with: ordewell planner ${runnerIdChoice()}`
3733
3906
  );
3734
3907
  }
3735
3908
  const model = catalog.models.find((m) => m.id === modelId && m.runners?.includes(runner));
@@ -3737,7 +3910,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
3737
3910
  if (!wanted || wanted === "show") {
3738
3911
  console.log(`Planner effort: ${currentEffort || "runner default"}`);
3739
3912
  if (!model) {
3740
- 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>\`.`);
3741
3914
  } else if (variants.length === 0) {
3742
3915
  console.log(` ${model.label} exposes no effort levels; it always runs at the agent's default.`);
3743
3916
  } else {
@@ -3765,7 +3938,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
3765
3938
  }
3766
3939
 
3767
3940
  // src/commands/parallel.ts
3768
- var import_core20 = require("@ordewell/core");
3941
+ var import_core24 = require("@ordewell/core");
3769
3942
  var USAGE5 = "Usage: ordewell parallel [<number of tasks, 1 or more>]";
3770
3943
  async function handleParallel(subArgs, injectedApi) {
3771
3944
  const [wanted] = positionals(subArgs);
@@ -3775,14 +3948,14 @@ async function handleParallel(subArgs, injectedApi) {
3775
3948
  console.log(`Up to ${String(settings.maxParallel ?? "?")} AI tasks run at once. Change with: ordewell parallel <n>`);
3776
3949
  return;
3777
3950
  }
3778
- const limit = (0, import_core20.parseMaxParallel)(wanted);
3951
+ const limit = (0, import_core24.parseMaxParallel)(wanted);
3779
3952
  if (limit === null) fail(`"${wanted}" is not a number of tasks (1 or more).`, USAGE5);
3780
3953
  await persistEnv(api, { ORDEWELL_MAX_PARALLEL: String(limit) });
3781
3954
  console.log(`Up to ${limit} AI task${limit === 1 ? "" : "s"} now run at once.`);
3782
3955
  }
3783
3956
 
3784
3957
  // src/commands/runSettings.ts
3785
- var import_core21 = require("@ordewell/core");
3958
+ var import_core25 = require("@ordewell/core");
3786
3959
  var SUPERVISOR_USAGE = "Usage: ordewell supervisor [off|watch|act] \xB7 ordewell supervisor retries <n> \xB7 ordewell supervisor reads <n>";
3787
3960
  var AUTOMERGE_USAGE = "Usage: ordewell automerge [on|off]";
3788
3961
  var AUTORUN_USAGE = "Usage: ordewell autorun [on|off]";
@@ -3793,28 +3966,28 @@ async function handleSupervisor(subArgs, injectedApi) {
3793
3966
  const [arg, value] = positionals(subArgs);
3794
3967
  let changes = null;
3795
3968
  if (arg === "off" || arg === "watch" || arg === "act") changes = { supervisorLevel: arg };
3796
- else if (arg === "retries") changes = maybe((0, import_core21.parseCount)(value, 0), (n) => ({ supervisorRetryLimit: n }));
3797
- else if (arg === "reads") changes = maybe((0, import_core21.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 }));
3798
3971
  if (arg !== void 0 && changes === null) fail(SUPERVISOR_USAGE);
3799
3972
  const api = await connect(subArgs, injectedApi);
3800
3973
  if (!changes) {
3801
- console.log((0, import_core21.supervisorSummary)((await api.getSettings()).run));
3974
+ console.log((0, import_core25.supervisorSummary)((await api.getSettings()).run));
3802
3975
  console.log(` ${SUPERVISOR_USAGE}`);
3803
3976
  return;
3804
3977
  }
3805
3978
  await save(api, changes);
3806
- if (changes.supervisorLevel) console.log(`Supervisor: ${import_core21.SUPERVISOR_LEVEL_LABEL[changes.supervisorLevel]}, from each session's next run.`);
3807
- if (changes.supervisorLevel === "act") console.log(import_core21.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);
3808
3981
  if (changes.supervisorRetryLimit !== void 0) console.log(`Supervisor retry limit: ${changes.supervisorRetryLimit} per task.`);
3809
3982
  if (changes.wakeUpReadBudget !== void 0) console.log(`Wake-up read budget: ${changes.wakeUpReadBudget} reads.`);
3810
3983
  }
3811
3984
  async function handleAutomerge(subArgs, injectedApi) {
3812
3985
  const [arg] = positionals(subArgs);
3813
- const enabled = arg === void 0 ? null : (0, import_core21.parseSwitch)(arg);
3986
+ const enabled = arg === void 0 ? null : (0, import_core25.parseSwitch)(arg);
3814
3987
  if (arg !== void 0 && enabled === null) fail(AUTOMERGE_USAGE);
3815
3988
  const api = await connect(subArgs, injectedApi);
3816
3989
  if (enabled === null) {
3817
- console.log((0, import_core21.automergeSummary)((await api.getSettings()).run));
3990
+ console.log((0, import_core25.automergeSummary)((await api.getSettings()).run));
3818
3991
  console.log(` ${AUTOMERGE_USAGE}`);
3819
3992
  return;
3820
3993
  }
@@ -3823,24 +3996,24 @@ async function handleAutomerge(subArgs, injectedApi) {
3823
3996
  }
3824
3997
  async function handleAutorun(subArgs, injectedApi) {
3825
3998
  const [arg] = positionals(subArgs);
3826
- const enabled = arg === void 0 ? null : (0, import_core21.parseSwitch)(arg);
3999
+ const enabled = arg === void 0 ? null : (0, import_core25.parseSwitch)(arg);
3827
4000
  if (arg !== void 0 && enabled === null) fail(AUTORUN_USAGE);
3828
4001
  const api = await connect(subArgs, injectedApi);
3829
4002
  if (enabled === null) {
3830
- console.log((0, import_core21.autorunSummary)((await api.getSettings()).run));
4003
+ console.log((0, import_core25.autorunSummary)((await api.getSettings()).run));
3831
4004
  console.log(` ${AUTORUN_USAGE}`);
3832
4005
  return;
3833
4006
  }
3834
4007
  await save(api, { autorun: enabled });
3835
- console.log(`Autorun: ${enabled ? "on \u2014 each plan the planner submits starts at once" : "off \u2014 plans wait for your review"}.`);
3836
- if (enabled) console.log(import_core21.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);
3837
4010
  }
3838
4011
  function maybe(n, wrap2) {
3839
4012
  return n === null ? null : wrap2(n);
3840
4013
  }
3841
4014
 
3842
4015
  // src/commands/key.ts
3843
- var import_core22 = require("@ordewell/core");
4016
+ var import_core26 = require("@ordewell/core");
3844
4017
  var USAGE6 = "Usage: ordewell key [list | set <provider> <key>]";
3845
4018
  async function handleKey(subArgs, injectedApi) {
3846
4019
  const [action, provider, ...keyParts] = positionals(subArgs);
@@ -3851,8 +4024,8 @@ async function handleKey(subArgs, injectedApi) {
3851
4024
  if (action !== "set") {
3852
4025
  const catalog = await fetchCatalog(api);
3853
4026
  console.log("\nAPI provider keys\n");
3854
- for (const id2 of import_core22.PROVIDER_PRIORITY) {
3855
- const meta2 = import_core22.ALL_PROVIDERS[id2];
4027
+ for (const id2 of import_core26.PROVIDER_PRIORITY) {
4028
+ const meta2 = (0, import_core26.getProviderMeta)(id2);
3856
4029
  const configured = catalog.providers.includes(id2);
3857
4030
  console.log(` ${configured ? "\u2713" : " "} ${id2.padEnd(14)} ${meta2.apiKeyEnvVar.padEnd(28)} ${meta2.label}`);
3858
4031
  }
@@ -3862,10 +4035,10 @@ async function handleKey(subArgs, injectedApi) {
3862
4035
  }
3863
4036
  if (!provider) fail(USAGE6);
3864
4037
  const id = provider.toLowerCase();
3865
- const meta = import_core22.ALL_PROVIDERS[id];
3866
- if (!meta) {
4038
+ if (!(0, import_core26.isApiProvider)(id)) {
3867
4039
  fail(`Unknown provider: ${provider}`, "Run `ordewell key` to see the list.");
3868
4040
  }
4041
+ const meta = (0, import_core26.getProviderMeta)(id);
3869
4042
  const key = keyParts.join(" ").trim();
3870
4043
  if (!key) fail(`Usage: ordewell key set ${id} <key>`);
3871
4044
  await persistEnv(api, { [meta.apiKeyEnvVar]: key });
@@ -3873,7 +4046,7 @@ async function handleKey(subArgs, injectedApi) {
3873
4046
  }
3874
4047
 
3875
4048
  // src/commands/runners.ts
3876
- var import_core23 = require("@ordewell/core");
4049
+ var import_core27 = require("@ordewell/core");
3877
4050
  var USAGE7 = "Usage: ordewell runners [<runner-id> on|off]";
3878
4051
  async function handleRunners(subArgs, injectedApi) {
3879
4052
  const [runner, toggle] = positionals(subArgs);
@@ -3884,7 +4057,7 @@ async function handleRunners(subArgs, injectedApi) {
3884
4057
  for (const r of state2.runners) {
3885
4058
  console.log(` ${r.enabled ? "\u2713" : " "} ${r.id.padEnd(14)} ${r.name}${r.enabled ? "" : " (disabled)"}`);
3886
4059
  }
3887
- const notice = (0, import_core23.codexSandboxNotice)((await fetchCatalog(api).catch(() => null))?.modesByRunner);
4060
+ const notice = (0, import_core27.codexSandboxNotice)((await fetchCatalog(api).catch(() => null))?.modesByRunner);
3888
4061
  if (notice) console.log(`
3889
4062
  ${notice.line}
3890
4063
  ${notice.docs}`);
@@ -3905,17 +4078,17 @@ async function handleRunners(subArgs, injectedApi) {
3905
4078
  var AUTO_USAGE = "Usage: ordewell auto [full|guarded]";
3906
4079
  function handleAuto(subArgs) {
3907
4080
  const [arg] = positionals(subArgs);
3908
- const settings = new import_core23.SettingsService();
4081
+ const settings = new import_core27.SettingsService();
3909
4082
  const current = settings.autonomousMode();
3910
4083
  if (!arg) {
3911
- console.log(`Autonomy level: ${(0, import_core23.autonomyLevelLabel)(current)}`);
4084
+ console.log(`Autonomy level: ${(0, import_core27.autonomyLevelLabel)(current)}`);
3912
4085
  console.log(` ${AUTO_USAGE}`);
3913
4086
  return;
3914
4087
  }
3915
- const enabled = (0, import_core23.parseAutonomyLevel)(arg);
4088
+ const enabled = (0, import_core27.parseAutonomyLevel)(arg);
3916
4089
  if (enabled === null) fail(AUTO_USAGE);
3917
4090
  settings.setAutonomousMode(enabled);
3918
- console.log(settings.autonomousModeOverridden() ? `${(0, import_core23.autonomyLevelLabel)(enabled)} is saved, but ORDEWELL_AUTONOMOUS_MODE in the environment decides the level until it is unset.` : `Autonomy level: ${(0, import_core23.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.`);
3919
4092
  }
3920
4093
  async function handleRefresh(subArgs, injectedApi) {
3921
4094
  const api = await connect(subArgs, injectedApi);
@@ -3940,12 +4113,12 @@ async function handleApprove(subArgs, injectedApi) {
3940
4113
  console.error(`Failed to approve: ${err.message}`);
3941
4114
  process.exit(1);
3942
4115
  }
3943
- console.error("Plan approved \u2014 continuing execution.");
4116
+ console.error("Plan approved: continuing execution.");
3944
4117
  });
3945
4118
  }
3946
4119
 
3947
4120
  // src/commands/task-assign.ts
3948
- var import_core25 = require("@ordewell/core");
4121
+ var import_core29 = require("@ordewell/core");
3949
4122
  var import_plan_utils = require("@ordewell/core/plan-utils");
3950
4123
 
3951
4124
  // src/tui/taskAssignment.ts
@@ -3985,7 +4158,7 @@ function modesForTask(modesByRunner, task) {
3985
4158
  }
3986
4159
 
3987
4160
  // src/tui/state.ts
3988
- var import_core24 = require("@ordewell/core");
4161
+ var import_core28 = require("@ordewell/core");
3989
4162
 
3990
4163
  // src/tui/editor.ts
3991
4164
  function emptyEditor() {
@@ -4143,7 +4316,7 @@ function queuedTarget(tv) {
4143
4316
  for (let i = queued.length - 1; i >= 0; i--) if (!queued[i].forced) return i;
4144
4317
  return -1;
4145
4318
  }
4146
- var anyTaskRunning = (state) => state.tasks.some(import_core24.isTaskRunning);
4319
+ var anyTaskRunning = (state) => state.tasks.some(import_core28.isTaskRunning);
4147
4320
  var plannerInFlight = (state) => state.planner !== "idle";
4148
4321
  var planPaneOpen = (state) => state.focus === "plan" && !state.overlay && (!!state.taskEditor || !!state.expandedTaskId);
4149
4322
  function planRows(state) {
@@ -4163,8 +4336,8 @@ function selectedPlanRow(state) {
4163
4336
  }
4164
4337
  function runnerCommands(tv) {
4165
4338
  for (let i = tv.view.blocks.length - 1; i >= 0; i--) {
4166
- const block = tv.view.blocks[i];
4167
- if (block.type === "runner_loaded") return block.commands;
4339
+ const block2 = tv.view.blocks[i];
4340
+ if (block2.type === "runner_loaded") return block2.commands;
4168
4341
  }
4169
4342
  return [];
4170
4343
  }
@@ -4189,10 +4362,10 @@ function findTask(tasks, id) {
4189
4362
  return void 0;
4190
4363
  }
4191
4364
  function refFor(state, task) {
4192
- return (0, import_core24.taskRefOf)(state.tasks, task.id) ?? (0, import_core24.taskRef)(task);
4365
+ return (0, import_core28.taskRefOf)(state.tasks, task.id) ?? (0, import_core28.taskRef)(task);
4193
4366
  }
4194
4367
  function titledRefFor(state, task) {
4195
- return (0, import_core24.titledTaskRefOf)(state.tasks, task.id) ?? (0, import_core24.titledTaskRef)(task);
4368
+ return (0, import_core28.titledTaskRefOf)(state.tasks, task.id) ?? (0, import_core28.titledTaskRef)(task);
4196
4369
  }
4197
4370
  function asRunTasks(tasks) {
4198
4371
  return tasks.map((task) => ({ ...task, subtasks: asRunTasks(task.subtasks ?? []) }));
@@ -4200,7 +4373,7 @@ function asRunTasks(tasks) {
4200
4373
  function seedRun(overrides) {
4201
4374
  if (overrides.run) return overrides.run;
4202
4375
  return {
4203
- ...import_core24.EMPTY_RUN,
4376
+ ...import_core28.EMPTY_RUN,
4204
4377
  tasks: asRunTasks(overrides.tasks ?? []),
4205
4378
  handoff: overrides.handoff ?? null,
4206
4379
  runMerge: overrides.runMerge ? { waitingAtGate: overrides.runMerge.waitingAtGate, paused: overrides.runMerge.paused, repos: overrides.runMerge.handoff.repos, landed: overrides.runMerge.handoff.landed } : null
@@ -4209,9 +4382,9 @@ function seedRun(overrides) {
4209
4382
  function initialState(overrides = {}) {
4210
4383
  return {
4211
4384
  editor: emptyEditor(),
4212
- conversation: import_core24.EMPTY_CONVERSATION,
4385
+ conversation: import_core28.EMPTY_CONVERSATION,
4213
4386
  taskView: null,
4214
- turnGate: import_core24.NO_TURN,
4387
+ turnGate: import_core28.NO_TURN,
4215
4388
  detailAll: false,
4216
4389
  status: "idle",
4217
4390
  planner: "idle",
@@ -4237,11 +4410,15 @@ function initialState(overrides = {}) {
4237
4410
  plannerProvider: "",
4238
4411
  plannerEffort: "",
4239
4412
  plannerModels: {},
4240
- maxParallel: import_core24.DEFAULT_MAX_PARALLEL,
4413
+ maxParallel: import_core28.DEFAULT_MAX_PARALLEL,
4241
4414
  configuredProviders: [],
4242
4415
  allowlist: {},
4243
4416
  autonomous: true,
4244
- runSettings: { ...import_core24.RUN_SETTINGS_DEFAULTS },
4417
+ runSettings: { ...import_core28.RUN_SETTINGS_DEFAULTS },
4418
+ runSettingsKnown: false,
4419
+ plannerKnown: false,
4420
+ runnersKnown: false,
4421
+ providersKnown: false,
4245
4422
  runAutoMerge: null,
4246
4423
  runSupervisor: null,
4247
4424
  mouseCapture: true,
@@ -4251,7 +4428,7 @@ function initialState(overrides = {}) {
4251
4428
  runMerge: null,
4252
4429
  overlay: null,
4253
4430
  pendingApprovals: [],
4254
- queuedPrompts: import_core24.EMPTY_HOLD,
4431
+ queuedPrompts: import_core28.EMPTY_HOLD,
4255
4432
  stopArmed: false,
4256
4433
  stopArmToken: 0,
4257
4434
  stopRequested: false,
@@ -4301,7 +4478,7 @@ Runner \xB7 ${namedOf(tasks, task)}
4301
4478
  fail(`Unknown runner: ${runner}`, `This daemon knows: ${state.runners.map((r) => r.id).join(", ")}`);
4302
4479
  }
4303
4480
  await api.updateTask(sessionId, task.id, { assignedRunner: runner });
4304
- 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.`);
4305
4482
  });
4306
4483
  }
4307
4484
  var MODEL_USAGE = "Usage: ordewell task-model <task-id-or-order> [<model-id>] [--session-id <id>]";
@@ -4313,11 +4490,11 @@ async function handleTaskModel(subArgs, injectedApi) {
4313
4490
  console.log(`
4314
4491
  Model \xB7 ${namedOf(tasks, task)}${task.assignedRunner ? ` (${task.assignedRunner})` : ""}
4315
4492
  `);
4316
- 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`.");
4317
4494
  for (const m of options) {
4318
4495
  const detail = [m.provider, m.variants?.length ? `${m.variants.length} effort levels` : "runner default effort"].filter(Boolean).join(" \xB7 ");
4319
4496
  console.log(` ${m.id === task.assignedModel?.modelId ? "*" : " "} ${m.id}`);
4320
- console.log(` ${m.label} \u2014 ${detail}`);
4497
+ console.log(` ${m.label}: ${detail}`);
4321
4498
  }
4322
4499
  console.log(`
4323
4500
  ${MODEL_USAGE}`);
@@ -4383,9 +4560,9 @@ Mode \xB7 ${namedOf(tasks, task)}${task.assignedRunner ? ` (${task.assignedRunne
4383
4560
  console.log(" This task has no runner, or its runner declares no modes.");
4384
4561
  }
4385
4562
  for (const m of modes) {
4386
- 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}` : ""}`);
4387
4564
  }
4388
- const notice = (0, import_core25.codexSandboxNotice)(catalog.modesByRunner);
4565
+ const notice = (0, import_core29.codexSandboxNotice)(catalog.modesByRunner);
4389
4566
  if (notice) console.log(`
4390
4567
  ${notice.line}
4391
4568
  ${notice.docs}`);
@@ -4434,7 +4611,7 @@ var SKILLS_USAGE = "Usage: ordewell task-skills <task-id-or-order> [<name> [<nam
4434
4611
  async function handleTaskSkills(subArgs, injectedApi) {
4435
4612
  await withTask(subArgs, SKILLS_USAGE, injectedApi, async (api, sessionId, task, tasks, value) => {
4436
4613
  if (task.type !== "ai") fail("Manual tasks do not run an executor, so they take no skills.");
4437
- const catalog = (0, import_core25.createSkillsService)(flag(subArgs, "--workspace") || process.cwd(), new import_core25.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");
4438
4615
  if (!value) {
4439
4616
  console.log(`
4440
4617
  Skills \xB7 ${namedOf(tasks, task)}
@@ -4471,7 +4648,7 @@ ${namedOf(tasks, task)} is ${task.ops ? "an ops task: it runs in your checkout o
4471
4648
  }
4472
4649
  const to = value.toLowerCase();
4473
4650
  if (to !== "on" && to !== "off") fail(OPS_USAGE);
4474
- 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.");
4475
4652
  if (!tasks.some((t) => t.id === task.id)) fail("A subtask runs with its parent; make the parent an ops task instead.");
4476
4653
  await api.updateTask(sessionId, task.id, { ops: to === "on" });
4477
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.`);
@@ -4479,10 +4656,10 @@ ${namedOf(tasks, task)} is ${task.ops ? "an ops task: it runs in your checkout o
4479
4656
  }
4480
4657
 
4481
4658
  // src/tui/index.ts
4482
- var import_core45 = require("@ordewell/core");
4659
+ var import_core51 = require("@ordewell/core");
4483
4660
 
4484
4661
  // src/tui/slash.ts
4485
- var import_core26 = require("@ordewell/core");
4662
+ var import_core30 = require("@ordewell/core");
4486
4663
  var SLASH_COMMANDS = [
4487
4664
  // Planning. There is deliberately no `/plan`: typing the goal *is* the way to
4488
4665
  // start planning, and an alias for it only shadowed `/planner` on completion.
@@ -4508,14 +4685,14 @@ var SLASH_COMMANDS = [
4508
4685
  { name: "task-mode", usage: "/task-mode <id> [mode]", description: "Choose or set a task's runner mode", category: "tasks" },
4509
4686
  { name: "task-deps", usage: "/task-deps <id>", description: "Edit which earlier tasks a task waits for", category: "tasks" },
4510
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" },
4511
- { 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" },
4512
4689
  // Models & providers
4513
- { 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" },
4514
4691
  { name: "model", usage: "/model [set <id>]", description: "Show or set the planner model", category: "models" },
4515
4692
  { name: "planner-effort", usage: "/planner-effort [<level>]", description: "Thinking effort for a coding-agent planner", category: "models" },
4516
4693
  { name: "key", usage: "/key [set <provider> <key>]", description: "Configure API provider keys", category: "models" },
4517
4694
  { name: "allowlist", usage: "/allowlist [set <runner> <ids> | clear <runner>]", description: "Limit which models a runner may use", category: "models" },
4518
- { name: "runners", usage: "/runners [<id> on|off]", description: "Enable or disable runners (claude-code, opencode, codex)", category: "models" },
4695
+ { name: "runners", usage: "/runners [<id> on|off]", description: `Enable or disable runners (${runnerIdList()})`, category: "models" },
4519
4696
  { name: "auto", usage: "/auto [full|guarded]", description: "Autonomy level for new plans: Full or Guarded", category: "models" },
4520
4697
  { name: "supervisor", usage: "/supervisor [off|watch|act] \xB7 retries <n> \xB7 reads <n>", description: "Whether the planner watches or acts on your runs, now and from then on", category: "models" },
4521
4698
  { name: "automerge", usage: "/automerge [on|off]", description: "Merge into your branch by itself at merge gates and at the end of a run", category: "models" },
@@ -4601,7 +4778,7 @@ function parseRunnerCommand(text) {
4601
4778
  return match ? { name: match[1], args: (match[2] ?? "").trim() } : null;
4602
4779
  }
4603
4780
  function runnerCommandFor(commands, name) {
4604
- return (0, import_core26.findRunnerCommand)(commands, name) ?? (0, import_core26.findRunnerCommand)(commands, name.toLowerCase());
4781
+ return (0, import_core30.findRunnerCommand)(commands, name) ?? (0, import_core30.findRunnerCommand)(commands, name.toLowerCase());
4605
4782
  }
4606
4783
  function composerCompletions(token, runner) {
4607
4784
  if (runner) return token.start === 0 ? runner.filter((c) => c.name.toLowerCase().startsWith(token.name)).map((c) => c.name) : [];
@@ -4648,10 +4825,10 @@ function chatEditorRoomFor(state, active) {
4648
4825
  }
4649
4826
 
4650
4827
  // src/tui/layout.ts
4651
- var import_core29 = require("@ordewell/core");
4828
+ var import_core33 = require("@ordewell/core");
4652
4829
 
4653
4830
  // src/tui/reducers/pickers.ts
4654
- var import_core27 = require("@ordewell/core");
4831
+ var import_core31 = require("@ordewell/core");
4655
4832
  var import_plan_utils2 = require("@ordewell/core/plan-utils");
4656
4833
  var DEFAULT_EFFORT = "__runner_default__";
4657
4834
  function picker(title, items, action, extra = {}) {
@@ -4670,11 +4847,11 @@ function modelDetail(m) {
4670
4847
  function providerErrorHint(state) {
4671
4848
  const failed = Object.keys(state.providerErrors ?? {});
4672
4849
  if (failed.length === 0) return void 0;
4673
- const names = failed.map((p) => import_core27.ALL_PROVIDERS[p]?.label ?? p);
4674
- 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.`;
4675
4852
  }
4676
4853
  function plannerModelItems(state, provider, selectedId) {
4677
- const runner = (0, import_core27.runnerForProvider)(provider);
4854
+ const runner = (0, import_core31.runnerForProvider)(provider);
4678
4855
  if (!runner) {
4679
4856
  return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m), selected: m.id === selectedId }));
4680
4857
  }
@@ -4690,11 +4867,11 @@ function plannerModelItems(state, provider, selectedId) {
4690
4867
  return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
4691
4868
  }
4692
4869
  function plannerHarnessModel(state, provider, modelId) {
4693
- const runner = (0, import_core27.runnerForProvider)(provider);
4870
+ const runner = (0, import_core31.runnerForProvider)(provider);
4694
4871
  return runner ? state.models.find((m) => m.id === modelId && m.runners?.includes(runner)) : void 0;
4695
4872
  }
4696
4873
  function plannerEffortItems(state, provider, modelId, selectedEffort) {
4697
- if (!(0, import_core27.runnerForProvider)(provider)) {
4874
+ if (!(0, import_core31.runnerForProvider)(provider)) {
4698
4875
  return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
4699
4876
  }
4700
4877
  const model = plannerHarnessModel(state, provider, modelId);
@@ -4812,11 +4989,11 @@ function pickerItemsFor(state, action) {
4812
4989
  function plannerItems(state) {
4813
4990
  const installed = new Set(state.runners.map((r) => r.id));
4814
4991
  const usability = {};
4815
- for (const id of import_core27.CLI_PROVIDERS) {
4816
- const runner = (0, import_core27.runnerForProvider)(id);
4992
+ for (const id of import_core31.CLI_PROVIDERS) {
4993
+ const runner = (0, import_core31.runnerForProvider)(id);
4817
4994
  usability[runner] = installed.has(runner) ? { usable: true } : { usable: false, reason: "CLI not installed or not on PATH" };
4818
4995
  }
4819
- const entries = (0, import_core27.plannerBackendEntries)(usability, state.configuredProviders);
4996
+ const entries = (0, import_core31.plannerBackendEntries)(usability, state.configuredProviders);
4820
4997
  const items = entries.map((entry) => ({
4821
4998
  id: entry.id,
4822
4999
  label: entry.label,
@@ -4841,14 +5018,14 @@ function cliVersion() {
4841
5018
  }
4842
5019
 
4843
5020
  // src/tui/planKeys.ts
4844
- var import_core28 = require("@ordewell/core");
5021
+ var import_core32 = require("@ordewell/core");
4845
5022
  var PLAN_KEYS = {
4846
5023
  move: { keys: "up / down", meaning: "move between tasks", group: "Navigation" },
4847
5024
  scroll: { keys: "pgup / pgdn", meaning: "scroll the plan", group: "Navigation" },
4848
5025
  expand: { keys: "\u2192", meaning: "reveal a task's subtasks, then edit its prompt", group: "Navigation", hint: "\u2192 expand" },
4849
5026
  log: {
4850
5027
  keys: "enter",
4851
- 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",
4852
5029
  group: "Navigation",
4853
5030
  hint: (task) => task?.status === "awaiting_user" && task.awaitingReason === "checkpoint" ? "enter answer" : "enter log"
4854
5031
  },
@@ -4868,7 +5045,7 @@ var PLAN_KEYS = {
4868
5045
  meaning: "cancel the task's runner, or keep a task paused on a usage limit from resuming on its own",
4869
5046
  group: "Run",
4870
5047
  // `c` cancels the resume while the task has one; it has no runner to cancel then.
4871
- hint: (task) => task && (0, import_core28.resumeLabel)(task) ? "c don't resume" : "c cancel"
5048
+ hint: (task) => task && (0, import_core32.resumeLabel)(task) ? "c don't resume" : "c cancel"
4872
5049
  },
4873
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" },
4874
5051
  resolve: { keys: "x", meaning: "resolve a merge conflict", group: "Run", hint: "x resolve conflict" },
@@ -4878,7 +5055,7 @@ var PLAN_KEYS = {
4878
5055
  meaning: "mark the task done, or take it back",
4879
5056
  group: "Status",
4880
5057
  // `m` toggles, so the hint names the direction it will go for this task.
4881
- hint: (task) => task && (0, import_core28.markAction)(task) === "uncomplete" ? "m undone" : "m done"
5058
+ hint: (task) => task && (0, import_core32.markAction)(task) === "uncomplete" ? "m undone" : "m done"
4882
5059
  },
4883
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" },
4884
5061
  remove: { keys: "d", meaning: "remove the task", group: "Status" },
@@ -4905,7 +5082,7 @@ function hintOf(id, task) {
4905
5082
  }
4906
5083
  function taskKeyIds(task, runActive) {
4907
5084
  if (!task) return ["add"];
4908
- const row = (0, import_core28.taskRowView)(task);
5085
+ const row = (0, import_core32.taskRowView)(task);
4909
5086
  if (row.conflict) return ["resolve", "log"];
4910
5087
  if (row.actions.includes("resume-now") || row.resumes) {
4911
5088
  return [...row.actions.includes("resume-now") ? ["resume"] : [], ...row.resumes ? ["cancel"] : [], "skip", "log"];
@@ -4933,7 +5110,7 @@ function planFooterHints(task, runActive) {
4933
5110
  }
4934
5111
 
4935
5112
  // src/tui/layout.ts
4936
- var import_core30 = require("@ordewell/core");
5113
+ var import_core34 = require("@ordewell/core");
4937
5114
  var CHROME_ROWS = 1;
4938
5115
  function chatInputWrap(state) {
4939
5116
  const active = state.focus === "chat" && !state.overlay;
@@ -4959,7 +5136,7 @@ function footerHints(state) {
4959
5136
  }
4960
5137
  return [...escHint ? [escHint] : [], ...planFooterHints(selected, state.status === "executing")];
4961
5138
  }
4962
- const detailHint = (0, import_core30.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"] : [];
4963
5140
  return [
4964
5141
  "/help",
4965
5142
  ...checkpointHints,
@@ -5018,12 +5195,12 @@ function queuedTaskBubble(message, target, sends, cols) {
5018
5195
  }
5019
5196
  function taskActivity(task, working) {
5020
5197
  if (task?.status === "completed" || task?.status === "failed") return task.status;
5021
- const approvals = (0, import_core30.approvalLabel)(task?.awaitingApproval);
5198
+ const approvals = (0, import_core34.approvalLabel)(task?.awaitingApproval);
5022
5199
  if (approvals) return inRow(approvals);
5023
5200
  if (working) return "working";
5024
5201
  if (task?.status === "awaiting_user") {
5025
- const resumes = (0, import_core30.resumeLabel)(task);
5026
- return [inRow((0, import_core30.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 ");
5027
5204
  }
5028
5205
  return task?.status ?? "";
5029
5206
  }
@@ -5036,7 +5213,7 @@ function taskHeaderLines(state, tv, cols) {
5036
5213
  taskActivity(task, tv.view.working)
5037
5214
  ].filter(Boolean);
5038
5215
  const approval = waitingApproval(tv);
5039
- 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_core30.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"];
5040
5217
  if (approval && waitingCheckpoint(task)) hint.push("then the checkpoint");
5041
5218
  const index = tv.attempts.indexOf(tv.attempt);
5042
5219
  if (tv.attempts.length > 1) hint.push(`alt\u2190/\u2192 attempt ${index >= 0 ? index + 1 : 1}/${tv.attempts.length}`);
@@ -5066,15 +5243,15 @@ function chatActivity(state) {
5066
5243
  const own = findTask(state.tasks, tv.taskId);
5067
5244
  const task = state.tasks.find((t) => t.subtasks?.some((s) => s.id === tv.taskId)) ?? own;
5068
5245
  if (!task) return null;
5069
- if ((0, import_core24.isTaskRunning)(task) || own && (0, import_core24.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 };
5070
5247
  const at = task.status === "awaiting_user" && task.awaitingReason === "usage-limit" && task.resumeAt ? new Date(task.resumeAt) : null;
5071
- if (at && !Number.isNaN(at.getTime())) return { label: `paused \xB7 resumes ${(0, import_core30.resumeTime)(at)}`, busy: false };
5248
+ if (at && !Number.isNaN(at.getTime())) return { label: `paused \xB7 resumes ${(0, import_core34.resumeTime)(at)}`, busy: false };
5072
5249
  return null;
5073
5250
  }
5074
5251
  function runHints(state) {
5075
5252
  const waiting = state.tasks.filter((t) => t.status === "awaiting_user").length;
5076
5253
  if (waiting > 0) return [waiting === 1 ? "1 task waits for you" : `${waiting} tasks wait for you`, `${waiting} waiting`];
5077
- 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"] : [];
5078
5255
  }
5079
5256
  var runHint = (state) => runHints(state)[0] ?? "";
5080
5257
  function packHints(hints, cols) {
@@ -5097,41 +5274,41 @@ function bodyRows(state) {
5097
5274
  return Math.max(0, state.rows - CHROME_ROWS - composerRows(state) - footer);
5098
5275
  }
5099
5276
  var chatBodyMemo = null;
5100
- function chatBodyLines(blocks, cols, detailAll, answering) {
5277
+ function chatBodyLines(blocks2, cols, detailAll, answering) {
5101
5278
  const memo = chatBodyMemo;
5102
- if (memo && memo.blocks === blocks && memo.cols === cols && memo.detailAll === detailAll) return memo.lines;
5103
- const lines = conversationLines(blocks, cols, detailAll, answering);
5104
- chatBodyMemo = { blocks, cols, detailAll, lines };
5279
+ if (memo && memo.blocks === blocks2 && memo.cols === cols && memo.detailAll === detailAll) return memo.lines;
5280
+ const lines = conversationLines(blocks2, cols, detailAll, answering);
5281
+ chatBodyMemo = { blocks: blocks2, cols, detailAll, lines };
5105
5282
  return lines;
5106
5283
  }
5107
5284
  function chatLayout(state, rows, cols) {
5108
5285
  const tv = state.taskView;
5109
- const blocks = tv ? tv.view.blocks : state.conversation.blocks;
5286
+ const blocks2 = tv ? tv.view.blocks : state.conversation.blocks;
5110
5287
  const welcome = !tv && state.tasks.length === 0;
5111
5288
  const checkpoint = tv ? waitingCheckpoint(findTask(state.tasks, tv.taskId)) : void 0;
5112
5289
  const card = tv && checkpoint ? checkpointCardLines(findTask(state.tasks, tv.taskId)?.order ?? 0, checkpoint, cols, waitingApproval(tv) !== void 0) : [];
5113
- const answering = chatBodyLines(blocks, cols, state.detailAll, tv ? waitingApproval(tv) : void 0);
5290
+ const answering = chatBodyLines(blocks2, cols, state.detailAll, tv ? waitingApproval(tv) : void 0);
5114
5291
  const body = card.length > 0 ? [...answering, ...card] : answering;
5115
5292
  const transcript = !welcome ? body : body.length === 0 ? welcomeLines(state, rows, cols) : [...welcomeLines(state, rows, cols), "", ...body];
5116
5293
  const bubbles = tv ? taskQueuedRows(state, cols) : queuedPromptRows(state, cols);
5117
5294
  const content = [...transcript, ...bubbles.length > 0 ? ["", ...bubbles] : []];
5118
5295
  const taskHeader = tv ? taskHeaderLines(state, tv, cols) : [];
5119
- const usage = rows <= 1 ? [] : tv ? taskUsageFooter(state, tv, blocks, cols) : tokenRow(blocks, cols);
5296
+ const usage = rows <= 1 ? [] : tv ? taskUsageFooter(state, tv, blocks2, cols) : tokenRow(blocks2, cols);
5120
5297
  const footer = usage.slice(0, Math.max(1, rows - taskHeader.length - 1));
5121
5298
  const space = Math.max(0, rows - taskHeader.length - footer.length);
5122
5299
  return { lines: content, header: taskHeader, footer, anchor: content.length > space ? "bottom" : "top", maxScroll: Math.max(0, content.length - space) };
5123
5300
  }
5124
- var tokenRow = (blocks, cols) => {
5125
- const line = tokenLine(blocks, cols);
5301
+ var tokenRow = (blocks2, cols) => {
5302
+ const line = tokenLine(blocks2, cols);
5126
5303
  return line ? [line] : [];
5127
5304
  };
5128
- function taskUsageFooter(state, tv, blocks, cols) {
5305
+ function taskUsageFooter(state, tv, blocks2, cols) {
5129
5306
  const task = findTask(state.tasks, tv.taskId);
5130
5307
  const reported = task?.attemptUsage ?? [];
5131
5308
  const onScreen = tv.attempt > 0 ? reported.find((a) => a.attempt === tv.attempt) : reported.at(-1);
5132
- if (!task || !onScreen) return tokenRow(blocks, cols);
5133
- const usage = (0, import_core30.taskRowView)({ ...task, attemptUsage: [onScreen] }).usage;
5134
- if (!usage) return tokenRow(blocks, cols);
5309
+ if (!task || !onScreen) return tokenRow(blocks2, cols);
5310
+ const usage = (0, import_core34.taskRowView)({ ...task, attemptUsage: [onScreen] }).usage;
5311
+ if (!usage) return tokenRow(blocks2, cols);
5135
5312
  const models = usage.byModel ?? [];
5136
5313
  return [
5137
5314
  style.grey(truncate(taskUsageText(usage), cols)),
@@ -5143,17 +5320,17 @@ function chatScrollMax(state) {
5143
5320
  }
5144
5321
  function plannerSetup(state) {
5145
5322
  const provider = state.plannerProvider;
5146
- const registration = import_core30.ALL_PROVIDERS[provider];
5323
+ const registration = (0, import_core34.getProviderMeta)(provider);
5147
5324
  if (!registration) return { label: "not set", ready: false };
5148
- const runner = (0, import_core30.runnerForProvider)(provider);
5149
- const ready = runner ? state.runners.some((r) => r.id === runner) : 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);
5150
5327
  return { label: registration.label, ready };
5151
5328
  }
5152
5329
  var SETUP_KEY_COLS = 10;
5153
5330
  var SETUP_VALUE_COLS = 32;
5154
5331
  function runSettingsValue(state) {
5155
5332
  const { supervisorLevel, mergeAtGatesAutomatically, autorun } = state.runSettings;
5156
- const level = `supervisor ${import_core29.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}`;
5333
+ const level = `supervisor ${import_core33.SUPERVISOR_LEVEL_LABEL[supervisorLevel]}`;
5157
5334
  return [
5158
5335
  supervisorLevel === "act" ? style.yellow(level) : level,
5159
5336
  `auto-merge ${mergeAtGatesAutomatically ? "on" : "off"}`,
@@ -5233,18 +5410,22 @@ var twoLines = ({ orde, well }) => [...orde, ...well.map((row) => style.brand(ro
5233
5410
  function welcomeLogo(restRows, rows, cols) {
5234
5411
  return [bannerLines(), oneLine2(BANNER_WORDMARK), twoLines(BANNER_WORDMARK)].find((lines) => lines.every((line) => width(line) <= cols - 1) && lines.length + restRows <= rows) ?? [];
5235
5412
  }
5413
+ var DETECTING = "Detecting coding agents\u2026";
5236
5414
  function welcomeLines(state, rows, cols) {
5237
5415
  const planner = plannerSetup(state);
5238
5416
  const enabledRunners = state.runners.filter((r) => r.enabled).map((r) => r.name);
5239
5417
  const canPlan = planner.ready || state.runners.length > 0 || state.configuredProviders.length > 0;
5240
- 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)`);
5241
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)) : [];
5242
5423
  const setup = [
5243
5424
  style.bold("Setup"),
5244
5425
  setupRow("Planner", plannerValue, "/planner", cols),
5245
5426
  setupRow(
5246
5427
  "Runners",
5247
- enabledRunners.length > 0 ? enabledRunners.join(", ") : style.yellow("none enabled"),
5428
+ detecting ? style.grey(DETECTING) : enabledRunners.length > 0 ? enabledRunners.join(", ") : style.yellow("none enabled"),
5248
5429
  "/runners",
5249
5430
  cols
5250
5431
  ),
@@ -5252,15 +5433,15 @@ function welcomeLines(state, rows, cols) {
5252
5433
  setupRow("Run", runSettingsValue(state), "/settings", cols),
5253
5434
  ""
5254
5435
  ];
5255
- const notice = canPlan ? [] : [
5436
+ const notice = canPlan || detecting || !state.providersKnown ? [] : [
5256
5437
  ...wrap(
5257
- style.yellow("Ordewell needs one coding agent installed (Claude Code, Codex, OpenCode) \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.`),
5258
5439
  Math.max(1, cols - 1)
5259
5440
  ),
5260
5441
  ""
5261
5442
  ];
5262
5443
  const tail = [...notice, style.grey("/help all commands"), workspaceLine(state.workspace, cols)];
5263
- const rest = ["", ...prompt, "", ...setup, ...tail];
5444
+ const rest = ["", ...prompt, ...approval, "", ...setup, ...tail];
5264
5445
  const logo = welcomeLogo(rest.length, rows, cols);
5265
5446
  return logo.length > 0 ? [...logo, ...rest] : rest.slice(1);
5266
5447
  }
@@ -5288,7 +5469,7 @@ var STATUS_ICON = {
5288
5469
  var inRow = (label) => label.charAt(0).toLowerCase() + label.slice(1);
5289
5470
  function planLayout(state, rows, cols) {
5290
5471
  const done = state.tasks.filter((t) => t.status === "completed").length;
5291
- const totalUsage = (0, import_core30.planUsage)(state.tasks);
5472
+ const totalUsage = (0, import_core34.planUsage)(state.tasks);
5292
5473
  const unmerged = state.runMerge?.handoff.landed.length ?? 0;
5293
5474
  const count = `${done}/${state.tasks.length}`;
5294
5475
  const hints = runHints(state);
@@ -5298,7 +5479,7 @@ function planLayout(state, rows, cols) {
5298
5479
  totalUsage.attempts ? style.grey(truncate(`Tasks total: ${taskUsageText(totalUsage)}`, cols)) : ""
5299
5480
  ];
5300
5481
  if (unmerged > 0) {
5301
- 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));
5302
5483
  if (totalUsage.attempts) lines.push(landed);
5303
5484
  else lines[1] = landed;
5304
5485
  }
@@ -5315,10 +5496,21 @@ function planLayout(state, rows, cols) {
5315
5496
  lines.push(...renderedTask.lines);
5316
5497
  rowSpans.push({ start: taskStart, end: lines.length - 1 });
5317
5498
  });
5499
+ lines.push(...heldEditLines(state, cols));
5318
5500
  const selected = rowSpans[state.selectedTask] ?? { start: firstRowLine, end: firstRowLine };
5319
5501
  const anchor = editorLine === void 0 ? selected : { start: editorLine, end: editorLine };
5320
5502
  return { lines, rowSpans, anchor, rows, maxScroll: Math.max(0, lines.length - rows) };
5321
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
+ }
5322
5514
  var clampOffset = (layout, offset) => Math.max(0, Math.min(layout.maxScroll, offset));
5323
5515
  function revealOffset(layout, offset, span) {
5324
5516
  const at = clampOffset(layout, offset);
@@ -5366,7 +5558,7 @@ function planScrollExtent(state) {
5366
5558
  }
5367
5559
  function taskLines(state, planRow, index, cols) {
5368
5560
  const { task, parent } = planRow;
5369
- const row = (0, import_core30.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) });
5370
5562
  const selected = state.focus === "plan" && index === state.selectedTask;
5371
5563
  const expanded = state.expandedTaskId === task.id;
5372
5564
  const rawIcon = row.status === "running" ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[row.status];
@@ -5394,10 +5586,9 @@ function taskLines(state, planRow, index, cols) {
5394
5586
  const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${row.autonomous ? style.yellow(" \u26A1") : ""}` : "";
5395
5587
  const runner = task.assignedRunner ?? "";
5396
5588
  const bodyPad = parent ? " " : " ";
5397
- 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) : "";
5398
5590
  const skills = (task.skills ?? []).join(" \xB7 ");
5399
- const origin = task.addedBy === "supervisor" ? "added by supervisor" : "";
5400
- const meta = [activity, origin, runner, model];
5591
+ const meta = [activity, runner, model];
5401
5592
  const variant = task.type === "ai" ? sanitize(task.assignedModel?.thinkingEffort ?? "") : "";
5402
5593
  const withVariant = variant ? [...meta, variant] : meta;
5403
5594
  const metaLine = variant && width(`${continuation}${withVariant.filter(Boolean).join(" \xB7 ")}`) <= cols ? withVariant : meta;
@@ -5406,7 +5597,7 @@ function taskLines(state, planRow, index, cols) {
5406
5597
  if (row.conflict) {
5407
5598
  const where = row.conflict.repo ? ` in ${row.conflict.repo}` : "";
5408
5599
  const files = row.conflict.files ? ` (${row.conflict.files})` : "";
5409
- 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)));
5410
5601
  }
5411
5602
  if (row.repairing) {
5412
5603
  const files = row.repairing.files ? ` in ${row.repairing.files}` : "";
@@ -5414,22 +5605,25 @@ function taskLines(state, planRow, index, cols) {
5414
5605
  lines.push(style.yellow(truncate(`${bodyPad}\u21BB repairing conflict${files}${attempt}`, cols)));
5415
5606
  }
5416
5607
  if (row.mergeGate) {
5417
- 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)));
5418
5612
  }
5419
5613
  if (row.blockedBy) {
5420
- lines.push(style.red(truncate(`${bodyPad}\u2298 ${inRow((0, import_core30.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)));
5421
5615
  }
5422
5616
  if (row.status === "skipped") {
5423
- lines.push(style.grey(truncate(`${bodyPad}\xBB ${inRow((0, import_core30.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)));
5424
5618
  }
5425
5619
  const resumable = row.actions.includes("resume-now");
5426
5620
  if (row.resumes) {
5427
- 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)));
5428
5622
  } else if (resumable) {
5429
- 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)));
5430
5624
  }
5431
5625
  if (row.opsChangedFiles) {
5432
- 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)));
5433
5627
  }
5434
5628
  if (row.repaired?.landed) {
5435
5629
  lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${row.repaired.files}`, cols)));
@@ -5529,14 +5723,14 @@ function commandSheetBody(cols) {
5529
5723
  body.push(
5530
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"),
5531
5725
  style.grey("alt-enter inserts a newline \xB7 ? lists the plan pane's keys, once tab has put you there"),
5532
- 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")
5533
5727
  );
5534
5728
  body.push(
5535
5729
  "",
5536
5730
  style.bold("In a task view (enter on a task)"),
5537
- 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"),
5538
5732
  style.grey(" ctrl-n/ctrl-p pick a pending message (the newest until you do) \xB7 ctrl-r removes a queued one"),
5539
- 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"),
5540
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"),
5541
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"),
5542
5736
  style.grey(" alt\u2190/\u2192 changes attempt \xB7 esc returns")
@@ -5555,8 +5749,8 @@ function helpScrollMax(state) {
5555
5749
  }
5556
5750
 
5557
5751
  // src/tui/render.ts
5558
- var import_core31 = require("@ordewell/core");
5559
- var import_core32 = require("@ordewell/core");
5752
+ var import_core35 = require("@ordewell/core");
5753
+ var import_core36 = require("@ordewell/core");
5560
5754
  function render(state) {
5561
5755
  const { rows, cols } = state;
5562
5756
  const rowsForBody = bodyRows(state);
@@ -5655,8 +5849,8 @@ function runSettingsBadge(state) {
5655
5849
  const { supervisorLevel, mergeAtGatesAutomatically, autorun } = state.runSettings;
5656
5850
  const paint = (text, warn) => warn ? style.yellow(text) : style.grey(text);
5657
5851
  const run = state.status === "executing" ? state.runSupervisor : null;
5658
- const actual = run?.unsupervised ? "unsupervised" : run && run.level !== supervisorLevel ? import_core32.SUPERVISOR_LEVEL_LABEL[run.level] : null;
5659
- const level = `supervisor ${import_core32.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}` : ""}`;
5660
5854
  return [
5661
5855
  paint(level, supervisorLevel === "act"),
5662
5856
  ...mergeAtGatesAutomatically ? [style.grey("auto-merge")] : [],
@@ -5850,16 +6044,16 @@ function renderHandoff(state, overlay, rows, cols) {
5850
6044
  const { lines, scroll } = overlay.diff;
5851
6045
  const shown = lines.slice(scroll, scroll + room).map((line) => paintDiffLine(truncate(line, Math.max(1, cols - 2))));
5852
6046
  const more = scroll + room < lines.length ? "\u2191\u2193 pgup/pgdn scroll \xB7 " : "";
5853
- 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);
5854
6048
  }
5855
6049
  const landed = handoff.landed.length === 0 ? [style.grey("Nothing landed on it.")] : handoff.landed.map((task) => {
5856
- const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0, import_core31.capConflictFiles)(task.repairedFiles)})`) : "";
5857
- return ` ${style.green("\u2713")} ${(0, import_core31.titledTaskRefOf)(state.tasks, task.taskId) ?? (0, import_core31.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}`;
5858
6052
  });
5859
6053
  const actions = handoffActions(handoff).map((action, index) => {
5860
6054
  const active = index === overlay.index;
5861
6055
  const caret = active ? style.brand("\u276F") : " ";
5862
- 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}`)}`;
5863
6057
  });
5864
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]}`), ""] : [];
5865
6059
  return frame(
@@ -5902,8 +6096,8 @@ function renderPicker(picker2, rows, cols) {
5902
6096
  ...picker2.filterable ? [["type", "to filter"]] : []
5903
6097
  ]);
5904
6098
  const room = Math.max(1, rows - head.length - 3);
5905
- const list = items.length === 0 ? [style.grey(picker2.filter ? "Nothing matches that filter." : "Nothing to show yet\u2026")] : pickerRows({ items, index: picker2.index, numbered: !picker2.filterable, chosen: picker2.multi ? picker2.chosen : void 0 }, room, inner);
5906
- return frame(picker2.title, [...head, ...list, "", footer], rows, cols);
6099
+ const list2 = items.length === 0 ? [style.grey(picker2.filter ? "Nothing matches that filter." : "Nothing to show yet\u2026")] : pickerRows({ items, index: picker2.index, numbered: !picker2.filterable, chosen: picker2.multi ? picker2.chosen : void 0 }, room, inner);
6100
+ return frame(picker2.title, [...head, ...list2, "", footer], rows, cols);
5907
6101
  }
5908
6102
  function renderStepper(stepper, rows, cols) {
5909
6103
  const { definition, trail } = stepper;
@@ -5934,13 +6128,13 @@ function renderStepper(stepper, rows, cols) {
5934
6128
  cancel
5935
6129
  ]);
5936
6130
  const room = Math.max(1, rows - head.length - 3);
5937
- const list = items.length === 0 ? [style.grey(stepper.filter ? "Nothing matches that filter." : "Nothing to show yet\u2026")] : pickerRows({ items, index: stepper.index, numbered: !active.filterable }, room, inner);
5938
- return frame(definition.title, [...head, ...list, "", footer], rows, cols);
6131
+ const list2 = items.length === 0 ? [style.grey(stepper.filter ? "Nothing matches that filter." : "Nothing to show yet\u2026")] : pickerRows({ items, index: stepper.index, numbered: !active.filterable }, room, inner);
6132
+ return frame(definition.title, [...head, ...list2, "", footer], rows, cols);
5939
6133
  }
5940
6134
  var DETAIL_GAP = 2;
5941
- function pickerRows(list, room, inner) {
5942
- const { items, numbered, chosen } = list;
5943
- const index = Math.max(0, Math.min(list.index, items.length - 1));
6135
+ function pickerRows(list2, room, inner) {
6136
+ const { items, numbered, chosen } = list2;
6137
+ const index = Math.max(0, Math.min(list2.index, items.length - 1));
5944
6138
  const digits = String(items.length).length;
5945
6139
  const prefixWidth = 2 + (numbered ? digits + 2 : 0) + (chosen ? 4 : 0);
5946
6140
  const rowWidth = Math.max(1, inner - prefixWidth);
@@ -5949,7 +6143,7 @@ function pickerRows(list, room, inner) {
5949
6143
  const labelWidth = items.some((item) => item.detail) ? Math.min(widest, Math.max(8, rowWidth - DETAIL_GAP - Math.max(16, Math.floor(rowWidth / 3)))) : Math.min(widest, rowWidth);
5950
6144
  const detailWidth = Math.max(1, rowWidth - labelWidth - DETAIL_GAP);
5951
6145
  const detailIndent = " ".repeat(prefixWidth + labelWidth + DETAIL_GAP);
5952
- const block = (item, i) => {
6146
+ const block2 = (item, i) => {
5953
6147
  const active = i === index;
5954
6148
  const current = isCurrent(item);
5955
6149
  const number = numbered ? `${String(i + 1).padStart(digits)}. ` : "";
@@ -5966,29 +6160,29 @@ function pickerRows(list, room, inner) {
5966
6160
  ];
5967
6161
  return active ? lines.map((line) => style.highlight(pad(line, inner))) : lines;
5968
6162
  };
5969
- const blocks = items.map(block);
5970
- if (blocks.reduce((n, b) => n + b.length, 0) <= room) return blocks.flat();
6163
+ const blocks2 = items.map(block2);
6164
+ if (blocks2.reduce((n, b) => n + b.length, 0) <= room) return blocks2.flat();
5971
6165
  const fit2 = Math.max(1, room - 2);
5972
6166
  let lo = index;
5973
6167
  let hi = index;
5974
- let used = blocks[index].length;
6168
+ let used = blocks2[index].length;
5975
6169
  for (let grew = true; grew; ) {
5976
6170
  grew = false;
5977
- if (hi + 1 < blocks.length && used + blocks[hi + 1].length <= fit2) {
6171
+ if (hi + 1 < blocks2.length && used + blocks2[hi + 1].length <= fit2) {
5978
6172
  hi += 1;
5979
- used += blocks[hi].length;
6173
+ used += blocks2[hi].length;
5980
6174
  grew = true;
5981
6175
  }
5982
- if (lo > 0 && used + blocks[lo - 1].length <= fit2) {
6176
+ if (lo > 0 && used + blocks2[lo - 1].length <= fit2) {
5983
6177
  lo -= 1;
5984
- used += blocks[lo].length;
6178
+ used += blocks2[lo].length;
5985
6179
  grew = true;
5986
6180
  }
5987
6181
  }
5988
6182
  return [
5989
6183
  ...lo > 0 ? [style.grey(` \u2191 \u2026 +${lo} more`)] : [],
5990
- ...blocks.slice(lo, hi + 1).flat().slice(0, fit2),
5991
- ...hi < blocks.length - 1 ? [style.grey(` \u2193 \u2026 +${blocks.length - 1 - hi} more`)] : []
6184
+ ...blocks2.slice(lo, hi + 1).flat().slice(0, fit2),
6185
+ ...hi < blocks2.length - 1 ? [style.grey(` \u2193 \u2026 +${blocks2.length - 1 - hi} more`)] : []
5992
6186
  ];
5993
6187
  }
5994
6188
  function keyHints(hints) {
@@ -6028,7 +6222,13 @@ function frame(title, body, rows, cols) {
6028
6222
  }
6029
6223
 
6030
6224
  // src/tui/reducer.ts
6031
- var import_core43 = require("@ordewell/core");
6225
+ var import_core49 = require("@ordewell/core");
6226
+
6227
+ // src/tui/reducers/overlays.ts
6228
+ var import_core48 = require("@ordewell/core");
6229
+
6230
+ // src/tui/reducers/approvals.ts
6231
+ var import_core37 = require("@ordewell/core");
6032
6232
 
6033
6233
  // src/tui/reducers/shared.ts
6034
6234
  function step(state, effects = []) {
@@ -6044,7 +6244,7 @@ function clampSelection(index, rows) {
6044
6244
  }
6045
6245
  function withSession(state, run) {
6046
6246
  if (!state.sessionId) {
6047
- 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.");
6048
6248
  }
6049
6249
  return run(state.sessionId);
6050
6250
  }
@@ -6073,6 +6273,13 @@ function dropApproval(state, approvalId) {
6073
6273
  if (open?.id !== approvalId) return { ...state, pendingApprovals: pending };
6074
6274
  return showNextApproval({ ...state, pendingApprovals: pending, overlay: null });
6075
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
+ }
6076
6283
  function showNextApproval(state) {
6077
6284
  const [next, ...rest] = state.pendingApprovals;
6078
6285
  if (!next) return { ...state, overlay: null };
@@ -6087,10 +6294,10 @@ function handleApprovalKey(state, overlay, key) {
6087
6294
  }
6088
6295
 
6089
6296
  // src/tui/reducers/commands.ts
6090
- var import_core42 = require("@ordewell/core");
6297
+ var import_core47 = require("@ordewell/core");
6091
6298
 
6092
6299
  // src/tui/reducers/plannerStepper.ts
6093
- var import_core33 = require("@ordewell/core");
6300
+ var import_core38 = require("@ordewell/core");
6094
6301
  var offered = (items) => new Set(items.filter((i) => !i.disabled).map((i) => i.id));
6095
6302
  function preferredModel(state, provider) {
6096
6303
  const listed = offered(plannerModelItems(state, provider, ""));
@@ -6109,7 +6316,7 @@ var effortLabel = (label) => /\beffort$/i.test(label) ? label : `${label} effort
6109
6316
  var plannerStepper = {
6110
6317
  title: "Planner",
6111
6318
  advanceOnEnter: true,
6112
- 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.",
6113
6320
  steps: [
6114
6321
  {
6115
6322
  id: "harness",
@@ -6138,7 +6345,7 @@ var plannerStepper = {
6138
6345
  const model = plannerModelItems(state, provider, "").find((m) => m.id === modelId);
6139
6346
  const level = plannerEffortItems(state, provider, modelId, "").find((e) => e.id === effort);
6140
6347
  const parts = [
6141
- import_core33.ALL_PROVIDERS[provider]?.label ?? provider,
6348
+ (0, import_core38.getProviderMeta)(provider)?.label ?? provider,
6142
6349
  model?.label ?? modelId,
6143
6350
  ...effort ? [effortLabel(level?.label ?? effort)] : []
6144
6351
  ];
@@ -6153,10 +6360,10 @@ var plannerStepper = {
6153
6360
  };
6154
6361
 
6155
6362
  // src/tui/reducers/stepper.ts
6156
- var import_core34 = require("@ordewell/core");
6363
+ var import_core39 = require("@ordewell/core");
6157
6364
  function openStepper(state, definition) {
6158
6365
  const { choices, lists } = settle(state, definition, {});
6159
- const first = lists.findIndex((list) => list !== null);
6366
+ const first = lists.findIndex((list2) => list2 !== null);
6160
6367
  if (first === -1) return definition.save(state, choices);
6161
6368
  return step({ ...state, overlay: { kind: "stepper", stepper: show2(state, definition, choices, first) } });
6162
6369
  }
@@ -6176,11 +6383,11 @@ function handleStepperKey(state, stepper, key) {
6176
6383
  const reopen = (next) => step({ ...state, overlay: { kind: "stepper", stepper: next } });
6177
6384
  const { definition, current, entry, choices } = stepper;
6178
6385
  const def = definition.steps[current];
6179
- const typed = def.number && entry ? (0, import_core34.parseCount)(entry.text, def.number.min) : null;
6386
+ const typed = def.number && entry ? (0, import_core39.parseCount)(entry.text, def.number.min) : null;
6180
6387
  const { lists } = settle(state, definition, choices);
6181
6388
  const items = matchingItems(stepper.items, stepper.filter);
6182
6389
  const before = lastIndexBefore(lists, current);
6183
- const after = lists.findIndex((list, i) => i > current && list !== null);
6390
+ const after = lists.findIndex((list2, i) => i > current && list2 !== null);
6184
6391
  if (key.name === "escape") return step({ ...state, overlay: null });
6185
6392
  if (key.name === "tab" || key.name === "right") return after === -1 ? reopen(stepper) : reopen(show2(state, definition, choices, after));
6186
6393
  if (key.name === "shift-tab" || key.name === "left") return before === -1 ? reopen(stepper) : reopen(show2(state, definition, choices, before));
@@ -6213,7 +6420,7 @@ function handleStepperKey(state, stepper, key) {
6213
6420
  function confirm(state, stepper, item) {
6214
6421
  if (!item) return step(state);
6215
6422
  if (item.disabled) {
6216
- 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.`));
6217
6424
  }
6218
6425
  return choose(state, stepper, item.id);
6219
6426
  }
@@ -6226,14 +6433,14 @@ function choose(state, stepper, value) {
6226
6433
  const saved = definition.save(open, choices);
6227
6434
  if (saved.state.overlay !== open.overlay) return saved;
6228
6435
  const { lists } = settle(saved.state, definition, choices);
6229
- const next = !definition.advanceOnEnter ? current : lists.findIndex((list, i) => i > current && list !== null);
6436
+ const next = !definition.advanceOnEnter ? current : lists.findIndex((list2, i) => i > current && list2 !== null);
6230
6437
  if (next === -1 || lists[next] === null) return { ...saved, state: { ...saved.state, overlay: null } };
6231
6438
  return { ...saved, state: { ...saved.state, overlay: { kind: "stepper", stepper: show2(saved.state, definition, choices, next) } } };
6232
6439
  }
6233
6440
  function reopenStepper(state, stepper) {
6234
6441
  const { definition, current } = stepper;
6235
6442
  const { lists } = settle(state, definition, {});
6236
- const at = lists[current] !== null ? current : lists.findIndex((list) => list !== null);
6443
+ const at = lists[current] !== null ? current : lists.findIndex((list2) => list2 !== null);
6237
6444
  if (at === -1) return { ...state, overlay: null };
6238
6445
  return { ...state, overlay: { kind: "stepper", stepper: show2(state, definition, {}, at) } };
6239
6446
  }
@@ -6242,7 +6449,7 @@ function settle(state, definition, wanted) {
6242
6449
  const lists = definition.steps.map((def) => {
6243
6450
  if (def.skip?.(state, { ...choices })) return null;
6244
6451
  if (def.number) {
6245
- choices[def.id] = String((0, import_core34.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 }));
6246
6453
  return [];
6247
6454
  }
6248
6455
  const items = def.items(state, { ...choices });
@@ -6256,10 +6463,10 @@ function settle(state, definition, wanted) {
6256
6463
  function show2(state, definition, wanted, current) {
6257
6464
  const { choices, lists } = settle(state, definition, wanted);
6258
6465
  const items = lists[current] ?? [];
6259
- const trail = lists.flatMap((list, i) => {
6260
- if (list === null) return [];
6466
+ const trail = lists.flatMap((list2, i) => {
6467
+ if (list2 === null) return [];
6261
6468
  const def2 = definition.steps[i];
6262
- const value = i >= current ? void 0 : def2.number ? choices[def2.id] : list.find((item) => item.id === choices[def2.id])?.label;
6469
+ const value = i >= current ? void 0 : def2.number ? choices[def2.id] : list2.find((item) => item.id === choices[def2.id])?.label;
6263
6470
  return [{ label: def2.label, active: i === current, ...value !== void 0 ? { value } : {} }];
6264
6471
  });
6265
6472
  const def = definition.steps[current];
@@ -6281,7 +6488,7 @@ function lastIndexBefore(lists, index) {
6281
6488
  }
6282
6489
 
6283
6490
  // src/tui/reducers/planPane.ts
6284
- var import_core37 = require("@ordewell/core");
6491
+ var import_core42 = require("@ordewell/core");
6285
6492
 
6286
6493
  // src/tui/reducers/pointer.ts
6287
6494
  var WHEEL_NOTCH = 3;
@@ -6359,13 +6566,13 @@ function handleMouseSelect(state, key) {
6359
6566
  }
6360
6567
 
6361
6568
  // src/tui/reducers/taskView.ts
6362
- var import_core35 = require("@ordewell/core");
6569
+ var import_core40 = require("@ordewell/core");
6363
6570
  function openTaskView(state, sessionId, taskId) {
6364
6571
  const task = findTask(state.tasks, taskId);
6365
6572
  if (!task) return step(say(state, "system", `No task ${taskId} in this plan.`));
6366
6573
  const taskView = {
6367
6574
  taskId,
6368
- view: import_core35.EMPTY_TASK_LOG,
6575
+ view: import_core40.EMPTY_TASK_LOG,
6369
6576
  attempts: [],
6370
6577
  attempt: 0,
6371
6578
  pending: [],
@@ -6395,13 +6602,13 @@ function applyLive(tv, attempt, events) {
6395
6602
  }
6396
6603
  next = {
6397
6604
  ...next,
6398
- view: import_core35.EMPTY_TASK_LOG,
6605
+ view: import_core40.EMPTY_TASK_LOG,
6399
6606
  attempt,
6400
6607
  attempts: next.attempts.includes(attempt) ? next.attempts : [...next.attempts, attempt]
6401
6608
  };
6402
6609
  }
6403
6610
  if (attempt < next.attempt) return next;
6404
- return clampQueueIndex({ ...next, view: events.reduce(import_core35.reduceTaskLog, next.view) });
6611
+ return clampQueueIndex({ ...next, view: events.reduce(import_core40.reduceTaskLog, next.view) });
6405
6612
  }
6406
6613
  function taskLogAbandoned(state) {
6407
6614
  const tv = state.taskView;
@@ -6422,14 +6629,14 @@ function announceApprovals(state, action) {
6422
6629
  if (state.taskView?.taskId === action.taskId) return state;
6423
6630
  const task = findTask(state.tasks, action.taskId);
6424
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";
6425
- const spoken = action.events.reduce((s, event) => event.type === "approval_requested" && !event.decided ? say(s, "system", `Task ${task?.order ?? "?"} waits for approval: ${(0, import_core35.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);
6426
6633
  return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
6427
6634
  }
6428
6635
  function announceCheckpoint(state, action) {
6429
6636
  const task = findTask(state.tasks, action.taskId) ?? state.tasks.find((t) => t.title === action.title);
6430
6637
  const label = task ? `Task ${task.order}` : action.title;
6431
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";
6432
- const spoken = say(state, "system", `${label} asks: ${(0, import_core35.truncateCheckpointSummary)(action.summary)} \u2014 ${where}`);
6639
+ const spoken = say(state, "system", `${label} asks: ${(0, import_core40.truncateCheckpointSummary)(action.summary)}: ${where}`);
6433
6640
  return { ...spoken, scroll: state.scroll };
6434
6641
  }
6435
6642
  function taskLogLoaded(state, action) {
@@ -6437,7 +6644,7 @@ function taskLogLoaded(state, action) {
6437
6644
  if (!tv || tv.taskId !== action.taskId) return state;
6438
6645
  const loaded = action.attempt >= 1 ? {
6439
6646
  ...tv,
6440
- view: (0, import_core35.replayTaskLog)(action.events),
6647
+ view: (0, import_core40.replayTaskLog)(action.events),
6441
6648
  attempts: action.attempts ?? tv.attempts,
6442
6649
  attempt: action.attempt
6443
6650
  } : { ...tv, attempts: action.attempts ?? tv.attempts };
@@ -6452,7 +6659,7 @@ function answerApproval(state, tv, sessionId, decision) {
6452
6659
  const approval = waitingApproval(tv);
6453
6660
  if (!approval?.approvalId) return step(say(state, "system", "Nothing is waiting for approval."));
6454
6661
  if (decision === "allowForTask" && !approval.allowForTask) {
6455
- 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."));
6456
6663
  }
6457
6664
  const note = decision === "deny" ? state.editor.text.trim() : "";
6458
6665
  const answer = decision === "deny" ? { decision, ...note ? { note } : {} } : { decision };
@@ -6476,7 +6683,7 @@ function sendNow(state, tv, sessionId) {
6476
6683
  if (text) {
6477
6684
  const task = findTask(state.tasks, tv.taskId);
6478
6685
  if (continuesTask(task)) {
6479
- 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."));
6480
6687
  }
6481
6688
  return step({ ...state, editor: commit(state.editor) }, [{ type: "forceSendTaskMessage", sessionId, taskId: tv.taskId, text }]);
6482
6689
  }
@@ -6511,7 +6718,7 @@ function handleTaskViewKey(state, key) {
6511
6718
  if (key.name === "ctrl-s") return sendNow(state, tv, state.sessionId);
6512
6719
  if (key.name === "ctrl-x") {
6513
6720
  const task = findTask(state.tasks, tv.taskId);
6514
- if (task && (0, import_core35.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" }]);
6515
6722
  return step(state, [{ type: "interruptTask", sessionId: state.sessionId, taskId: tv.taskId }]);
6516
6723
  }
6517
6724
  if (key.name === "ctrl-y") return answerWaiting(state, tv, state.sessionId, "allow");
@@ -6529,7 +6736,7 @@ function handleTaskViewKey(state, key) {
6529
6736
  }
6530
6737
 
6531
6738
  // src/tui/reducers/taskEdits.ts
6532
- var import_core36 = require("@ordewell/core");
6739
+ var import_core41 = require("@ordewell/core");
6533
6740
  var import_plan_utils3 = require("@ordewell/core/plan-utils");
6534
6741
  function openTaskRunnerPicker(state, task) {
6535
6742
  if (task.type !== "ai") return fail3(state, "Manual tasks do not run on an executor, so they have no runner.");
@@ -6559,7 +6766,7 @@ function confirmRemoveTask(state, task) {
6559
6766
  }
6560
6767
  function toggleTaskOps(state, task, subtask, to = !task.ops) {
6561
6768
  if (!state.sessionId) return fail3(state, "No active plan.");
6562
- 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.");
6563
6770
  if (subtask) return fail3(state, "A subtask runs with its parent; make the parent an ops task instead.");
6564
6771
  if (!!task.ops === to) return fail3(state, `${refFor(state, task)} is already ${to ? "an ops" : "a change"} task.`);
6565
6772
  return step(state, [{
@@ -6640,7 +6847,7 @@ function openTaskModePicker(state, task) {
6640
6847
  overlay: {
6641
6848
  kind: "picker",
6642
6849
  picker: picker(`Mode \xB7 ${refFor(state, task)}`, pickerItemsFor(state, action), action, {
6643
- hint: [`Modes declared by ${task.assignedRunner}.`, (0, import_core36.codexSandboxNotice)(state.modesByRunner)?.line].filter(Boolean).join(" ")
6850
+ hint: [`Modes declared by ${task.assignedRunner}.`, (0, import_core41.codexSandboxNotice)(state.modesByRunner)?.line].filter(Boolean).join(" ")
6644
6851
  })
6645
6852
  }
6646
6853
  }
@@ -6792,7 +6999,7 @@ function taskOpsCommand(state, args) {
6792
6999
  }
6793
7000
  function addTask(state, title) {
6794
7001
  if (!state.sessionId) {
6795
- 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>.");
6796
7003
  }
6797
7004
  if (!title) {
6798
7005
  return step({
@@ -6868,11 +7075,11 @@ function handlePlanKey(state, key) {
6868
7075
  if (!state.sessionId || key.name !== "char") return step(state);
6869
7076
  if (key.char === "E") return step(state, [{ type: "execute", sessionId: state.sessionId }]);
6870
7077
  if (key.char === "S") return step(state, [{ type: "stopExecution", sessionId: state.sessionId }]);
6871
- const shortcut = key.char === "c" && (0, import_core37.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 ?? ""];
6872
7079
  if (key.char === "r") {
6873
- return (0, import_core37.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);
6874
7081
  }
6875
- const action = key.char === "m" ? (0, import_core37.markAction)(task) : shortcut;
7082
+ const action = key.char === "m" ? (0, import_core42.markAction)(task) : shortcut;
6876
7083
  if (action === "force-start" && task.mergeGate?.length) return confirmForceStartPastGate(state, task);
6877
7084
  if (action) {
6878
7085
  return step(state, [taskActionEffect(state, state.sessionId, task.id, action)]);
@@ -6912,9 +7119,9 @@ function commitTaskEdit(state, task, editor) {
6912
7119
  }
6913
7120
 
6914
7121
  // src/tui/reducers/turnQueue.ts
6915
- var import_core38 = require("@ordewell/core");
7122
+ var import_core43 = require("@ordewell/core");
6916
7123
  function drainQueue(state, settled) {
6917
- const next = (0, import_core38.drainNext)(state.queuedPrompts);
7124
+ const next = (0, import_core43.drainNext)(state.queuedPrompts);
6918
7125
  const ended = disarmStop({ ...settled, stopRequested: false, forceSendPending: null });
6919
7126
  if (!next) return step(ended);
6920
7127
  const spoken = say(ended, "user", next.text);
@@ -6929,7 +7136,7 @@ function restoreForced(state) {
6929
7136
  if (!forced) return state;
6930
7137
  const cleared = { ...state, forceSendPending: null };
6931
7138
  if (forced.queueIndex === void 0) {
6932
- const text = (0, import_core38.aheadOfDraft)(forced.text, state.editor.text);
7139
+ const text = (0, import_core43.aheadOfDraft)(forced.text, state.editor.text);
6933
7140
  return { ...cleared, editor: { ...state.editor, text, cursor: text.length } };
6934
7141
  }
6935
7142
  const queued = [...state.queuedPrompts];
@@ -6945,14 +7152,14 @@ function stopPlanning(state, sessionId) {
6945
7152
  return step(queueToEditor(cutTurn({ ...dismissed, stopArmed: false, stopRequested: true, forceSendPending: null })), [{ type: "cancelPlanning", sessionId }]);
6946
7153
  }
6947
7154
  function queueToEditor(state) {
6948
- const all = (0, import_core38.unsendAll)(state.queuedPrompts);
7155
+ const all = (0, import_core43.unsendAll)(state.queuedPrompts);
6949
7156
  if (!all) return state;
6950
7157
  return { ...state, queuedPrompts: all.rest, editor: { ...state.editor, text: all.text, cursor: all.text.length } };
6951
7158
  }
6952
7159
  function unsendLatest(state) {
6953
- const latest = (0, import_core38.unsendLatest)(state.queuedPrompts);
7160
+ const latest = (0, import_core43.unsendLatest)(state.queuedPrompts);
6954
7161
  if (!latest) return step(state);
6955
- const text = (0, import_core38.aheadOfDraft)(latest.text, state.editor.text);
7162
+ const text = (0, import_core43.aheadOfDraft)(latest.text, state.editor.text);
6956
7163
  return step({
6957
7164
  ...state,
6958
7165
  queuedPrompts: latest.rest,
@@ -6968,7 +7175,7 @@ function forceSendPlanner(state) {
6968
7175
  { type: "forceSendPlannerMessage", sessionId: state.sessionId, message: text }
6969
7176
  ]);
6970
7177
  }
6971
- const latest = (0, import_core38.unsendLatest)(state.queuedPrompts);
7178
+ const latest = (0, import_core43.unsendLatest)(state.queuedPrompts);
6972
7179
  if (!latest) return step(say(state, "system", "Nothing to send now: type a message, or queue one with enter."));
6973
7180
  const forced = { text: latest.text, queueIndex: latest.rest.length };
6974
7181
  return step({ ...state, queuedPrompts: latest.rest, stopRequested: false, forceSendPending: forced }, [
@@ -6984,7 +7191,7 @@ function plannerEscape(state) {
6984
7191
  }
6985
7192
 
6986
7193
  // src/tui/reducers/incoming.ts
6987
- var import_core39 = require("@ordewell/core");
7194
+ var import_core44 = require("@ordewell/core");
6988
7195
  var planText = (value, fallback = "") => sanitize(String(value ?? fallback));
6989
7196
  var planLabel = (value, fallback = "") => planText(value, fallback).replace(/\n+/g, " ");
6990
7197
  var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
@@ -7023,12 +7230,13 @@ function runTaskToView(t) {
7023
7230
  ...Array.isArray(model.availableVariants) ? { availableVariants: model.availableVariants.map((variant) => planLabel(variant)) } : {}
7024
7231
  } : void 0,
7025
7232
  isolation: t.isolation,
7026
- awaitingReason: t.status === "awaiting_user" && (0, import_core39.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
7233
+ awaitingReason: t.status === "awaiting_user" && (0, import_core44.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
7027
7234
  ...t.status === "awaiting_user" && t.awaitingReason === "usage-limit" && typeof t.resumeAt === "string" ? { resumeAt: t.resumeAt } : {},
7028
7235
  ...t.status === "skipped" && typeof t.skipReason === "string" ? { skipReason: sanitize(t.skipReason) } : {},
7029
7236
  checkpoint: typeof t.checkpoint === "string" ? sanitize(t.checkpoint) : void 0,
7030
7237
  continuable: t.continuable === true,
7031
7238
  awaitingApproval: t.awaitingApproval,
7239
+ ...t.waitsForApproval === true ? { waitsForApproval: true } : {},
7032
7240
  ...t.ops === true ? { ops: true } : {},
7033
7241
  ...Array.isArray(t.skills) && t.skills.length > 0 ? { skills: t.skills.map((name) => planLabel(name)) } : {},
7034
7242
  ...t.addedBy ? { addedBy: t.addedBy } : {},
@@ -7042,7 +7250,7 @@ function supervisorOfRun(runSettings) {
7042
7250
  const unsupervised = typeof runSettings.unsupervised === "string" ? runSettings.unsupervised : void 0;
7043
7251
  return {
7044
7252
  runSupervisor: {
7045
- level: (0, import_core39.isSupervisorLevel)(runSettings.supervisorLevel) ? runSettings.supervisorLevel : "off",
7253
+ level: (0, import_core44.isSupervisorLevel)(runSettings.supervisorLevel) ? runSettings.supervisorLevel : "off",
7046
7254
  ...unsupervised ? { unsupervised } : {}
7047
7255
  }
7048
7256
  };
@@ -7059,7 +7267,7 @@ function withRun(state, run) {
7059
7267
  };
7060
7268
  }
7061
7269
  function foldRun(state, input) {
7062
- const run = (0, import_core39.reduceRun)(state.run, input);
7270
+ const run = (0, import_core44.reduceRun)(state.run, input);
7063
7271
  return run === state.run ? state : withRun(state, run);
7064
7272
  }
7065
7273
  function mapRunTasks(tasks, map) {
@@ -7089,11 +7297,13 @@ function applySettings(state, settings) {
7089
7297
  ...state,
7090
7298
  orchestratorModel: typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : state.orchestratorModel,
7091
7299
  plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
7300
+ plannerKnown: state.plannerKnown || typeof settings.aiProvider === "string",
7092
7301
  plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
7093
7302
  plannerModels: settings.plannerModels && typeof settings.plannerModels === "object" ? settings.plannerModels : state.plannerModels,
7094
7303
  maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
7095
7304
  allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist,
7096
- runSettings: settings.run ?? state.runSettings
7305
+ runSettings: settings.run ?? state.runSettings,
7306
+ runSettingsKnown: state.runSettingsKnown || settings.run !== void 0
7097
7307
  };
7098
7308
  }
7099
7309
  function followSession(state, message) {
@@ -7107,21 +7317,21 @@ function followSession(state, message) {
7107
7317
  }
7108
7318
 
7109
7319
  // src/tui/reducers/runSettings.ts
7110
- var import_core40 = require("@ordewell/core");
7320
+ var import_core45 = require("@ordewell/core");
7111
7321
  function runSettingsEffect(state, changes) {
7112
7322
  return { type: "setRunSettings", changes, ...state.sessionId ? { sessionId: state.sessionId } : {} };
7113
7323
  }
7114
7324
  function supervisorCommand(state, args) {
7115
7325
  const [arg, value] = args;
7116
7326
  if (arg === void 0) {
7117
- return step(say(state, "system", `${(0, import_core40.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>.`));
7118
7328
  }
7119
7329
  if (arg === "retries") {
7120
- const n = (0, import_core40.parseCount)(value, 0);
7330
+ const n = (0, import_core45.parseCount)(value, 0);
7121
7331
  return n === null ? fail3(state, "Usage: /supervisor retries <whole number, 0 or more>") : step(state, [runSettingsEffect(state, { supervisorRetryLimit: n })]);
7122
7332
  }
7123
7333
  if (arg === "reads") {
7124
- const n = (0, import_core40.parseCount)(value, 1);
7334
+ const n = (0, import_core45.parseCount)(value, 1);
7125
7335
  return n === null ? fail3(state, "Usage: /supervisor reads <whole number, 1 or more>") : step(state, [runSettingsEffect(state, { wakeUpReadBudget: n })]);
7126
7336
  }
7127
7337
  if (arg === "act") return confirmSupervisorAct(state);
@@ -7130,17 +7340,17 @@ function supervisorCommand(state, args) {
7130
7340
  }
7131
7341
  function automergeCommand(state, arg) {
7132
7342
  if (arg === void 0) {
7133
- return step(say(state, "system", `${(0, import_core40.automergeSummary)(state.runSettings)} \u2014 /automerge on|off.`));
7343
+ return step(say(state, "system", `${(0, import_core45.automergeSummary)(state.runSettings)}: /automerge on|off.`));
7134
7344
  }
7135
- const enabled = (0, import_core40.parseSwitch)(arg);
7345
+ const enabled = (0, import_core45.parseSwitch)(arg);
7136
7346
  if (enabled === null) return fail3(state, "Usage: /automerge [on|off]");
7137
7347
  return step(state, [runSettingsEffect(state, { mergeAtGatesAutomatically: enabled })]);
7138
7348
  }
7139
7349
  function autorunCommand(state, arg) {
7140
7350
  if (arg === void 0) {
7141
- return step(say(state, "system", `${(0, import_core40.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.`));
7142
7352
  }
7143
- const enabled = (0, import_core40.parseSwitch)(arg);
7353
+ const enabled = (0, import_core45.parseSwitch)(arg);
7144
7354
  if (enabled === null) return fail3(state, "Usage: /autorun [on|off]");
7145
7355
  if (enabled) return confirmAutorun(state);
7146
7356
  return step(state, [runSettingsEffect(state, { autorun: false })]);
@@ -7152,7 +7362,7 @@ function confirmSupervisorAct(state) {
7152
7362
  kind: "confirm",
7153
7363
  title: "Let the supervisor act?",
7154
7364
  message: "At Act the supervisor runs, stops and retries tasks, answers their requests (never allowing), edits the plan and merges, without asking you.",
7155
- note: `${import_core40.ACT_RISK}
7365
+ note: `${import_core45.ACT_RISK}
7156
7366
  It applies to this session's run now, and to every later run.`,
7157
7367
  action: { kind: "supervisor-act" }
7158
7368
  }
@@ -7165,14 +7375,14 @@ function confirmAutorun(state) {
7165
7375
  kind: "confirm",
7166
7376
  title: "Start plans without review?",
7167
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.",
7168
- note: import_core40.AUTORUN_RISK,
7378
+ note: import_core45.AUTORUN_RISK,
7169
7379
  action: { kind: "autorun-on" }
7170
7380
  }
7171
7381
  });
7172
7382
  }
7173
7383
 
7174
7384
  // src/tui/reducers/settings.ts
7175
- var import_core41 = require("@ordewell/core");
7385
+ var import_core46 = require("@ordewell/core");
7176
7386
  var SUPERVISOR_DETAIL = {
7177
7387
  off: "The planner speaks only when you write",
7178
7388
  watch: "The planner follows your runs and suggests what to do",
@@ -7197,7 +7407,7 @@ var SETTINGS = {
7197
7407
  {
7198
7408
  id: "guarded",
7199
7409
  label: "Guarded",
7200
- detail: [`New plans run tasks in each runner's safer mode`, (0, import_core41.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 "),
7201
7411
  selected: !state.autonomous
7202
7412
  }
7203
7413
  ]
@@ -7207,7 +7417,7 @@ var SETTINGS = {
7207
7417
  label: "Supervisor",
7208
7418
  items: (state) => ["off", "watch", "act"].map((level) => ({
7209
7419
  id: level,
7210
- label: import_core41.SUPERVISOR_LEVEL_LABEL[level],
7420
+ label: import_core46.SUPERVISOR_LEVEL_LABEL[level],
7211
7421
  detail: SUPERVISOR_DETAIL[level],
7212
7422
  selected: state.runSettings.supervisorLevel === level
7213
7423
  }))
@@ -7261,12 +7471,12 @@ function saveChoices(state, choices) {
7261
7471
  const autonomous = choices.autonomy === "full";
7262
7472
  if (autonomous !== state.autonomous) {
7263
7473
  save2.autonomous = autonomous;
7264
- changed.push(`Autonomy ${(0, import_core41.autonomyLevelLabel)(autonomous)}`);
7474
+ changed.push(`Autonomy ${(0, import_core46.autonomyLevelLabel)(autonomous)}`);
7265
7475
  }
7266
7476
  const level = choices.supervisor;
7267
7477
  if (level !== state.runSettings.supervisorLevel) {
7268
7478
  run.supervisorLevel = level;
7269
- changed.push(`Supervisor ${import_core41.SUPERVISOR_LEVEL_LABEL[level]}`);
7479
+ changed.push(`Supervisor ${import_core46.SUPERVISOR_LEVEL_LABEL[level]}`);
7270
7480
  }
7271
7481
  if (choices.retries !== void 0 && Number(choices.retries) !== state.runSettings.supervisorRetryLimit) {
7272
7482
  run.supervisorRetryLimit = Number(choices.retries);
@@ -7293,7 +7503,7 @@ function saveChoices(state, choices) {
7293
7503
  }
7294
7504
  if (changed.length === 0) return step(state);
7295
7505
  const effect = { ...save2, message: `Settings: ${changed.join(" \xB7 ")}` };
7296
- const risks = [...run.supervisorLevel === "act" ? [import_core41.ACT_RISK] : [], ...run.autorun ? [import_core41.AUTORUN_RISK] : []];
7506
+ const risks = [...run.supervisorLevel === "act" ? [import_core46.ACT_RISK] : [], ...run.autorun ? [import_core46.AUTORUN_RISK] : []];
7297
7507
  if (risks.length === 0) return saveSettings(state, effect);
7298
7508
  return step({
7299
7509
  ...state,
@@ -7322,8 +7532,7 @@ function saveSettings(state, effect) {
7322
7532
  }
7323
7533
 
7324
7534
  // src/tui/reducers/commands.ts
7325
- var KNOWN_PROVIDERS = Object.keys(import_core42.ALL_PROVIDERS);
7326
- 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.`);
7327
7536
  function runCommand(state, { name, args }) {
7328
7537
  if (!findCommand(name)) return unknownCommand(state, name);
7329
7538
  switch (name) {
@@ -7392,7 +7601,7 @@ function runCommand(state, { name, args }) {
7392
7601
  case "save":
7393
7602
  return withSession(state, (sessionId) => step(state, [{ type: "saveSession", sessionId }]));
7394
7603
  case "load":
7395
- 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.");
7396
7605
  case "delete":
7397
7606
  return args[0] ? step(state, [{ type: "deleteSession", sessionId: args[0] }]) : fail3(state, "Usage: /delete <session-id>");
7398
7607
  case "add-task":
@@ -7416,7 +7625,7 @@ function runCommand(state, { name, args }) {
7416
7625
  const answer = args[1]?.toLowerCase();
7417
7626
  const reason = args.slice(2).join(" ").trim();
7418
7627
  if (answer !== "approve" && answer !== "reject") return fail3(state, "Usage: /checkpoint <id> approve|reject [reason]");
7419
- 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.");
7420
7629
  return answerCheckpoint(state, sessionId, taskId, answer, reason);
7421
7630
  });
7422
7631
  case "task-runner":
@@ -7454,7 +7663,7 @@ function runCommand(state, { name, args }) {
7454
7663
  function withIdlePlanner(state, run) {
7455
7664
  return withSession(
7456
7665
  state,
7457
- (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)
7458
7667
  );
7459
7668
  }
7460
7669
  function resolveToggle(arg, current) {
@@ -7485,8 +7694,8 @@ function newSession(state) {
7485
7694
  goal: "",
7486
7695
  taskView: null,
7487
7696
  planApproved: false,
7488
- conversation: import_core42.EMPTY_CONVERSATION,
7489
- turnGate: import_core42.NO_TURN,
7697
+ conversation: import_core47.EMPTY_CONVERSATION,
7698
+ turnGate: import_core47.NO_TURN,
7490
7699
  selectedTask: 0,
7491
7700
  expandedTaskId: null,
7492
7701
  taskEditor: null,
@@ -7497,19 +7706,21 @@ function newSession(state) {
7497
7706
  // Prompts belong to the session that raised them; the old planner is gone
7498
7707
  // and its pending requests deny on their own timeout.
7499
7708
  pendingApprovals: [],
7500
- queuedPrompts: import_core42.EMPTY_HOLD,
7709
+ queuedPrompts: import_core47.EMPTY_HOLD,
7501
7710
  stopArmed: false,
7502
7711
  forceSendPending: null,
7503
7712
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
7504
- }, import_core42.EMPTY_RUN), closeEffects);
7713
+ }, import_core47.EMPTY_RUN), closeEffects);
7505
7714
  }
7506
7715
  function setModel(state, args) {
7507
7716
  if (args[0] === "set" && args[1]) {
7508
- const runner = (0, import_core42.runnerForProvider)(state.plannerProvider);
7717
+ const runner = (0, import_core47.runnerForProvider)(state.plannerProvider);
7509
7718
  const known = runner ? modelsForRunner(state.models, runner) : [];
7510
7719
  if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
7511
7720
  return fail3(state, `${args[1]} was not discovered for ${runner}.`);
7512
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);
7513
7724
  return step(state, [{ type: "setModel", modelId: args[1] }]);
7514
7725
  }
7515
7726
  if (args.length > 0 && args[0] !== "set") return fail3(state, "Usage: /model [set <model-id>]");
@@ -7528,7 +7739,7 @@ function setModel(state, args) {
7528
7739
  function setPlanner(state, args) {
7529
7740
  if (args[0]) {
7530
7741
  const id = args[0].toLowerCase();
7531
- if (!KNOWN_PROVIDERS.includes(id)) {
7742
+ if (!(0, import_core47.isPlannerBackend)(id)) {
7532
7743
  return fail3(state, `Unknown planner: ${id}. Run /planner with no arguments to see the list.`);
7533
7744
  }
7534
7745
  return step(state, [{ type: "setPlanner", provider: id }]);
@@ -7560,7 +7771,7 @@ function setPlannerEffort(state, args) {
7560
7771
  function setKey(state, args) {
7561
7772
  if (args[0] === "set" && args[1]) {
7562
7773
  const provider = args[1].toLowerCase();
7563
- if (!KNOWN_PROVIDERS.includes(provider)) {
7774
+ if (!(0, import_core47.isApiProvider)(provider)) {
7564
7775
  return fail3(state, `Unknown provider: ${provider}. Run /key with no arguments to see the list.`);
7565
7776
  }
7566
7777
  if (!args[2]) {
@@ -7571,16 +7782,16 @@ function setKey(state, args) {
7571
7782
  }
7572
7783
  return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
7573
7784
  }
7574
- const items = import_core42.PROVIDER_PRIORITY.map((id) => ({
7785
+ const items = import_core47.PROVIDER_PRIORITY.map((id) => ({
7575
7786
  id,
7576
- label: import_core42.ALL_PROVIDERS[id].label,
7577
- detail: import_core42.ALL_PROVIDERS[id].apiKeyEnvVar,
7787
+ label: (0, import_core47.getProviderMeta)(id).label,
7788
+ detail: (0, import_core47.getProviderMeta)(id).apiKeyEnvVar,
7578
7789
  selected: state.configuredProviders.includes(id)
7579
7790
  }));
7580
7791
  return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
7581
7792
  }
7582
7793
  function keyPrompt(provider) {
7583
- const meta = import_core42.ALL_PROVIDERS[provider];
7794
+ const meta = (0, import_core47.getProviderMeta)(provider);
7584
7795
  return {
7585
7796
  kind: "prompt",
7586
7797
  title: `${meta.label} API key`,
@@ -7656,14 +7867,14 @@ function runners(state, args) {
7656
7867
  } });
7657
7868
  }
7658
7869
  function setMaxParallel(state, arg) {
7659
- 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.`));
7660
- const limit = (0, import_core42.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);
7661
7872
  if (limit === null) return fail3(state, "Usage: /parallel [<number of tasks, 1 or more>]");
7662
7873
  return step({ ...state, maxParallel: limit }, [{ type: "setMaxParallel", limit }]);
7663
7874
  }
7664
7875
  function setAutonomous(state, arg) {
7665
- if (arg === void 0) return step(say(state, "system", `Autonomy level: ${(0, import_core42.autonomyLevelLabel)(state.autonomous)} \u2014 /auto full or /auto guarded changes it for new plans.`));
7666
- const enabled = (0, import_core42.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);
7667
7878
  if (enabled === null) return fail3(state, "Usage: /auto [full|guarded]");
7668
7879
  return step({ ...state, autonomous: enabled }, [{ type: "setAutonomous", enabled }]);
7669
7880
  }
@@ -7676,7 +7887,7 @@ function setMouseCapture(state, arg) {
7676
7887
  // move it.
7677
7888
  { ...state, mouseCapture: enabled, selection: null },
7678
7889
  "system",
7679
- 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."
7680
7891
  );
7681
7892
  return step(noted, [{ type: "setMouseCapture", enabled }]);
7682
7893
  }
@@ -7688,7 +7899,7 @@ function rewind(state, arg) {
7688
7899
  [{ type: "loadRewindTargets", sessionId }]
7689
7900
  );
7690
7901
  }
7691
- 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.");
7692
7903
  return step(state, [{ type: "loadRewindTargets", sessionId, pick: Number(arg) }]);
7693
7904
  });
7694
7905
  }
@@ -7699,7 +7910,7 @@ var REWIND_OPTIONS = [
7699
7910
  function pickRewindTarget(state, index) {
7700
7911
  return withIdlePlanner({ ...state, overlay: null }, () => {
7701
7912
  const target = state.rewindTargets?.find((t) => t.index === index);
7702
- 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.`);
7703
7914
  return step({
7704
7915
  ...state,
7705
7916
  overlay: {
@@ -7791,7 +8002,7 @@ function handlePromptKey(state, overlay, key) {
7791
8002
  if (overlay.action.kind === "api-key") {
7792
8003
  return step(closed, [{ type: "setApiKey", provider: overlay.action.provider, key: value2 }]);
7793
8004
  }
7794
- 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>.");
7795
8006
  return step(closed, [{ type: "addTask", sessionId: state.sessionId, title: value2 }]);
7796
8007
  }
7797
8008
  const value = editText(overlay.value, key);
@@ -7836,7 +8047,7 @@ function choose2(state, picker2, item) {
7836
8047
  return step(closed, [{
7837
8048
  type: "setRunners",
7838
8049
  changes,
7839
- 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."
7840
8051
  }]);
7841
8052
  }
7842
8053
  if (picker2.action.kind === "set-task-deps") {
@@ -7858,7 +8069,7 @@ function choose2(state, picker2, item) {
7858
8069
  }
7859
8070
  if (!item) return step(state);
7860
8071
  if (item.disabled) {
7861
- 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.`));
7862
8073
  }
7863
8074
  switch (picker2.action.kind) {
7864
8075
  case "set-model":
@@ -7872,7 +8083,7 @@ function choose2(state, picker2, item) {
7872
8083
  case "delete-session":
7873
8084
  return step(closed, [{ type: "deleteSession", sessionId: item.id }]);
7874
8085
  case "set-key":
7875
- return step({ ...state, overlay: keyPrompt(item.id) });
8086
+ return (0, import_core48.isApiProvider)(item.id) ? step({ ...state, overlay: keyPrompt(item.id) }) : step(closed);
7876
8087
  case "choose-allowlist-runner": {
7877
8088
  const action = { kind: "set-allowlist", runner: item.id };
7878
8089
  const items = pickerItemsFor(state, action);
@@ -7948,7 +8159,7 @@ function reduceAction(state, action) {
7948
8159
  // (`task-1`), so nothing of the last one's — a merge gate, a checkpoint, its
7949
8160
  // tasks — may carry over into this.
7950
8161
  case "sessionStarted":
7951
- return step(withRun({ ...state, sessionId: action.sessionId, goal: action.goal, taskView: null }, import_core43.EMPTY_RUN));
8162
+ return step(withRun({ ...state, sessionId: action.sessionId, goal: action.goal, taskView: null }, import_core49.EMPTY_RUN));
7952
8163
  // The daemon registers a session only once planning succeeds; when it does
7953
8164
  // not, holding on to the id would send every next message to a session the
7954
8165
  // server never had.
@@ -7959,18 +8170,18 @@ function reduceAction(state, action) {
7959
8170
  goal: "",
7960
8171
  taskView: null,
7961
8172
  pendingApprovals: [],
7962
- queuedPrompts: import_core43.EMPTY_HOLD,
8173
+ queuedPrompts: import_core49.EMPTY_HOLD,
7963
8174
  stopArmed: false,
7964
8175
  forceSendPending: null,
7965
8176
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
7966
- }, import_core43.EMPTY_RUN));
8177
+ }, import_core49.EMPTY_RUN));
7967
8178
  // Deleting the session on screen stops it in the daemon; a view left on it
7968
8179
  // would answer every next message with a session-not-found.
7969
8180
  case "sessionDeleted":
7970
8181
  return action.sessionId === state.sessionId ? newSession(state) : step(state);
7971
8182
  case "chatRestored":
7972
8183
  if (stale(state, action.sessionId)) return step(state);
7973
- return step({ ...state, conversation: (0, import_core43.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 });
7974
8185
  case "planUpdated": {
7975
8186
  if (stale(state, action.sessionId)) return step(state);
7976
8187
  const settled = { ...showPlan(state, action.plan), planner: "idle" };
@@ -7991,10 +8202,14 @@ function reduceAction(state, action) {
7991
8202
  // its status line with it, as a status update would have set it.
7992
8203
  case "runSnapshot": {
7993
8204
  if (stale(state, action.sessionId)) return step(state);
7994
- const next = withRun(state, action.view);
8205
+ const run = withRun(state, action.view);
8206
+ const next = action.approvals ? syncApprovals(run, action.approvals) : run;
7995
8207
  const status = runStatus(next, next.tasks);
7996
8208
  return step({ ...next, status });
7997
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 }));
7998
8213
  case "taskLog":
7999
8214
  if (stale(state, action.sessionId)) return step(state);
8000
8215
  return step(announceApprovals(taskLogArrived(state, action), action));
@@ -8010,15 +8225,15 @@ function reduceAction(state, action) {
8010
8225
  case "taskStarted": {
8011
8226
  if (stale(state, action.sessionId)) return step(state);
8012
8227
  const existing = findTask(state.tasks, action.taskId);
8013
- if (existing && (0, import_core24.isTaskRunning)(existing)) return step(state);
8228
+ if (existing && (0, import_core28.isTaskRunning)(existing)) return step(state);
8014
8229
  const folded = foldRun(state, {
8015
8230
  type: "task_started",
8016
8231
  taskId: action.taskId,
8017
8232
  order: action.order ?? 0,
8018
8233
  title: action.title,
8019
- runner: action.runner ?? "claude-code"
8234
+ runner: action.runner ?? import_core49.DEFAULT_RUNNERS[0]
8020
8235
  });
8021
- const spoken = say(folded, "system", (0, import_core43.taskStartedNotice)(action.title, action.runner));
8236
+ const spoken = say(folded, "system", (0, import_core49.taskStartedNotice)(action.title, action.runner));
8022
8237
  return step({ ...spoken, status: "executing" });
8023
8238
  }
8024
8239
  case "taskCheckpoint":
@@ -8064,13 +8279,13 @@ function reduceAction(state, action) {
8064
8279
  completed: state.tasks.filter((t) => t.status === "completed").length,
8065
8280
  total: state.tasks.length,
8066
8281
  failed: state.tasks.filter((t) => t.status === "failed").length,
8067
- blocked: (0, import_core43.blockingFailures)(state.tasks).size,
8282
+ blocked: (0, import_core49.blockingFailures)(state.tasks).size,
8068
8283
  skipped: state.tasks.filter((t) => t.status === "skipped").length
8069
8284
  };
8070
8285
  const failures = `${failed > 0 ? ` \xB7 ${failed} failed` : ""}${blocked ? ` \xB7 ${blocked} blocked` : ""}${skipped ? ` \xB7 ${skipped} skipped` : ""}`;
8071
8286
  const verb = action.stopped ? "stopped" : "finished";
8072
8287
  return step({
8073
- ...say(state, "system", `Execution ${verb} \u2014 ${completed}/${total} tasks complete${failures}.`),
8288
+ ...say(state, "system", `Execution ${verb}: ${completed}/${total} tasks complete${failures}.`),
8074
8289
  status: "idle"
8075
8290
  });
8076
8291
  }
@@ -8089,7 +8304,7 @@ function reduceAction(state, action) {
8089
8304
  case "executionLost": {
8090
8305
  if (stale(state, action.sessionId)) return step(state);
8091
8306
  return step({
8092
- ...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."),
8093
8308
  status: "idle"
8094
8309
  });
8095
8310
  }
@@ -8103,6 +8318,7 @@ function reduceAction(state, action) {
8103
8318
  models: action.models,
8104
8319
  orchestratorModels: action.orchestratorModels ?? state.orchestratorModels,
8105
8320
  configuredProviders: action.providers ?? state.configuredProviders,
8321
+ providersKnown: state.providersKnown || action.providers !== void 0,
8106
8322
  providerErrors: action.providerErrors ?? state.providerErrors,
8107
8323
  modesByRunner: action.modesByRunner ?? state.modesByRunner
8108
8324
  },
@@ -8128,11 +8344,11 @@ function reduceAction(state, action) {
8128
8344
  planner: "idle",
8129
8345
  planApproved: false,
8130
8346
  pendingApprovals: [],
8131
- queuedPrompts: import_core43.EMPTY_HOLD,
8347
+ queuedPrompts: import_core49.EMPTY_HOLD,
8132
8348
  stopArmed: false,
8133
8349
  forceSendPending: null,
8134
8350
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
8135
- }, import_core43.EMPTY_RUN));
8351
+ }, import_core49.EMPTY_RUN));
8136
8352
  // The rewound message comes back as a draft to edit and resend, so it lands
8137
8353
  // as if just typed: history is not being browsed, and nothing is parked.
8138
8354
  case "inputPrefilled":
@@ -8146,6 +8362,7 @@ function reduceAction(state, action) {
8146
8362
  return step(refreshStepper(refillPicker({
8147
8363
  ...state,
8148
8364
  runners: action.runners,
8365
+ runnersKnown: true,
8149
8366
  orchestratorModel: action.orchestratorModel ?? state.orchestratorModel
8150
8367
  }, ["set-task-runner"])));
8151
8368
  case "failed": {
@@ -8204,7 +8421,7 @@ function showPlan(state, plan) {
8204
8421
  });
8205
8422
  }
8206
8423
  function runStatus(state, tasks) {
8207
- if (tasks.some(import_core24.isTaskRunning)) return "executing";
8424
+ if (tasks.some(import_core28.isTaskRunning)) return "executing";
8208
8425
  if (tasks.some((t) => t.status === "awaiting_user") || state.runMerge?.paused) return "executing";
8209
8426
  return "idle";
8210
8427
  }
@@ -8279,13 +8496,13 @@ function submit(state) {
8279
8496
  const command = parseRunnerCommand(text);
8280
8497
  const offered2 = runnerCommands(state.taskView);
8281
8498
  if (command && offered2.length > 0 && !runnerCommandFor(offered2, command.name)) {
8282
- 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.`;
8283
8500
  return step({ ...cleared, taskView: { ...state.taskView, notice } });
8284
8501
  }
8285
8502
  if (state.taskView.notice) cleared.taskView = { ...state.taskView, notice: void 0 };
8286
8503
  if (!command && waitingCheckpoint(task)) {
8287
8504
  if (waitingApproval(state.taskView)) {
8288
- 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."));
8289
8506
  }
8290
8507
  return answerCheckpoint(cleared, state.sessionId, taskId, "reject", text);
8291
8508
  }
@@ -8296,7 +8513,7 @@ function submit(state) {
8296
8513
  const builtIn = builtInCommand(text);
8297
8514
  if (builtIn) return runCommand(cleared, builtIn);
8298
8515
  if (plannerInFlight(state)) {
8299
- return step({ ...cleared, queuedPrompts: (0, import_core43.holdPrompt)(state.queuedPrompts, text) });
8516
+ return step({ ...cleared, queuedPrompts: (0, import_core49.holdPrompt)(state.queuedPrompts, text) });
8300
8517
  }
8301
8518
  const spoken = say(cleared, "user", text);
8302
8519
  const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
@@ -8371,7 +8588,7 @@ function createApp(deps) {
8371
8588
 
8372
8589
  // src/tui/effects.ts
8373
8590
  var import_child_process3 = require("child_process");
8374
- var import_core44 = require("@ordewell/core");
8591
+ var import_core50 = require("@ordewell/core");
8375
8592
 
8376
8593
  // src/tui/inbound.ts
8377
8594
  var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
@@ -8422,6 +8639,52 @@ function identity(event) {
8422
8639
  function replyKey(content, timestamp) {
8423
8640
  return timestamp ? `planner_message:${timestamp}:${content}` : `planner_message:content:${content}`;
8424
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
+ }
8425
8688
  var Copies = class {
8426
8689
  constructor(cap) {
8427
8690
  this.cap = cap;
@@ -8492,6 +8755,7 @@ function inboundFor(api, dispatch, sessionId) {
8492
8755
  return inbound;
8493
8756
  }
8494
8757
  function createInbound(dispatch, sessionId) {
8758
+ const delivered = new Delivered(256);
8495
8759
  const copies = new Copies(64);
8496
8760
  const shown = new WindowSet(64);
8497
8761
  const pending = new WindowSet(16);
@@ -8500,6 +8764,7 @@ function createInbound(dispatch, sessionId) {
8500
8764
  let timer = null;
8501
8765
  let holdingSnapshot = false;
8502
8766
  let heldSnapshot = null;
8767
+ let heldAfter = [];
8503
8768
  const flush = () => {
8504
8769
  if (timer !== null) {
8505
8770
  clearTimeout(timer);
@@ -8524,8 +8789,17 @@ function createInbound(dispatch, sessionId) {
8524
8789
  shown.add(message.content);
8525
8790
  dispatch({ type: "sessionMessage", message, sessionId });
8526
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
+ };
8527
8797
  const acceptReply = (id, event) => {
8528
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
+ }
8529
8803
  if (pending.take(key)) {
8530
8804
  copies.credit(id, key);
8531
8805
  return;
@@ -8533,12 +8807,15 @@ function createInbound(dispatch, sessionId) {
8533
8807
  if (!copies.first(id, key)) return;
8534
8808
  showReply(event);
8535
8809
  };
8810
+ const deliverSnapshot = ({ view, approvals }) => {
8811
+ dispatch({ type: "runSnapshot", view, ...approvals ? { approvals } : {}, sessionId });
8812
+ };
8536
8813
  const deliverConversation = (id, event) => {
8537
8814
  if (event.type === "planner_message") {
8538
8815
  acceptReply(id, event);
8539
8816
  return;
8540
8817
  }
8541
- if (!copies.first(id, identity(event))) return;
8818
+ if (!fresh(id, event)) return;
8542
8819
  if (event.type === "plan_generated") {
8543
8820
  dispatch({ type: "sessionMessage", message: event, sessionId });
8544
8821
  dispatch({ type: "planBroadcast", plan: event.plan, sessionId });
@@ -8549,12 +8826,18 @@ function createInbound(dispatch, sessionId) {
8549
8826
  const receive = (kind, id, event) => {
8550
8827
  if (event.type === "run_snapshot") {
8551
8828
  flush();
8552
- if (holdingSnapshot) heldSnapshot = event.view;
8553
- 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));
8554
8837
  return;
8555
8838
  }
8556
8839
  if (isStreamed(event)) {
8557
- if (kind === "planning") hold(event);
8840
+ if (kind === "planning" && (!stampOf(event) || fresh(id, event))) hold(event);
8558
8841
  return;
8559
8842
  }
8560
8843
  flush();
@@ -8562,7 +8845,7 @@ function createInbound(dispatch, sessionId) {
8562
8845
  deliverConversation(id, event);
8563
8846
  return;
8564
8847
  }
8565
- if (!copies.first(id, identity(event))) return;
8848
+ if (!fresh(id, event)) return;
8566
8849
  dispatchLifecycle(dispatch, event, sessionId);
8567
8850
  };
8568
8851
  let subscription = 0;
@@ -8589,12 +8872,17 @@ function createInbound(dispatch, sessionId) {
8589
8872
  holdSnapshot: () => {
8590
8873
  holdingSnapshot = true;
8591
8874
  heldSnapshot = null;
8875
+ heldAfter = [];
8592
8876
  },
8593
8877
  releaseSnapshot: (deliver) => {
8594
8878
  holdingSnapshot = false;
8595
- const view = heldSnapshot;
8879
+ const snapshot = heldSnapshot;
8880
+ const after = heldAfter;
8596
8881
  heldSnapshot = null;
8597
- 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();
8598
8886
  }
8599
8887
  };
8600
8888
  }
@@ -8618,7 +8906,7 @@ function dispatchLifecycle(dispatch, event, sessionId) {
8618
8906
  dispatch({ type: "taskCheckpoint", taskId: String(event.taskId), title: String(event.taskTitle), summary: event.summary, sessionId });
8619
8907
  return;
8620
8908
  case "review_needed":
8621
- 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." });
8622
8910
  return;
8623
8911
  case "review_approved":
8624
8912
  dispatch({ type: "notice", message: "Plan approved." });
@@ -8638,6 +8926,10 @@ function dispatchLifecycle(dispatch, event, sessionId) {
8638
8926
  case "notice":
8639
8927
  dispatch({ type: "notice", message: event.message, level: event.level });
8640
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;
8641
8933
  case "isolation_handoff":
8642
8934
  dispatch({ type: "isolationHandoff", handoff: { repos: event.repos, landed: event.landed, ...event.deferredTo ? { deferredTo: event.deferredTo } : {} }, sessionId });
8643
8935
  return;
@@ -8702,7 +8994,7 @@ function isPlannerTurn(effect) {
8702
8994
  }
8703
8995
  function explain(err) {
8704
8996
  if (err instanceof DaemonError && err.code === "session_not_found") {
8705
- 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.";
8706
8998
  }
8707
8999
  return err instanceof Error ? err.message : String(err);
8708
9000
  }
@@ -8718,7 +9010,7 @@ function plannerModelNotice(recall2) {
8718
9010
  case "remembered":
8719
9011
  return `Restored ${recall2.model}.`;
8720
9012
  case "catalog-default":
8721
- return `Using its default model, ${recall2.model} \u2014 pick another with /model.`;
9013
+ return `Using its default model, ${recall2.model}. Pick another with /model.`;
8722
9014
  case "none":
8723
9015
  return "Pick a model with /model.";
8724
9016
  }
@@ -8780,13 +9072,13 @@ async function perform(effect, deps) {
8780
9072
  await refreshPlan(deps, sessionId);
8781
9073
  return;
8782
9074
  }
8783
- 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_core44.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);
8784
9076
  await (effect.watch ? withExecutionStream(deps, sessionId, request) : request());
8785
9077
  await refreshPlan(deps, sessionId);
8786
9078
  return;
8787
9079
  }
8788
9080
  case "addTask": {
8789
- const fields = (0, import_core44.newTaskFields)(effect.title);
9081
+ const fields = (0, import_core50.newTaskFields)(effect.title);
8790
9082
  if (!fields) return;
8791
9083
  await api.addTask(effect.sessionId, { ...fields });
8792
9084
  await refreshPlan(deps, effect.sessionId);
@@ -8811,7 +9103,7 @@ async function perform(effect, deps) {
8811
9103
  try {
8812
9104
  await api.respondToApproval(effect.sessionId, effect.approvalId, effect.granted);
8813
9105
  } catch {
8814
- 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." });
8815
9107
  }
8816
9108
  return;
8817
9109
  }
@@ -8820,7 +9112,7 @@ async function perform(effect, deps) {
8820
9112
  try {
8821
9113
  await api.respondToApproval(effect.sessionId, effect.approvalId, effect.answer);
8822
9114
  } catch {
8823
- 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." });
8824
9116
  }
8825
9117
  return;
8826
9118
  }
@@ -8894,8 +9186,13 @@ async function perform(effect, deps) {
8894
9186
  return;
8895
9187
  }
8896
9188
  case "setPlanner": {
8897
- const meta = import_core44.ALL_PROVIDERS[effect.provider];
9189
+ const meta = (0, import_core50.getProviderMeta)(effect.provider);
8898
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
+ }
8899
9196
  const env = { AI_PROVIDER: effect.provider };
8900
9197
  const settings = await api.updateSettings({ env });
8901
9198
  const recall2 = plannerSwitchRecall(settings);
@@ -8906,7 +9203,7 @@ async function perform(effect, deps) {
8906
9203
  });
8907
9204
  dispatch({
8908
9205
  type: "notice",
8909
- message: `${(0, import_core44.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)}`
8910
9207
  });
8911
9208
  await loadModels(deps);
8912
9209
  return;
@@ -8934,7 +9231,7 @@ async function perform(effect, deps) {
8934
9231
  dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
8935
9232
  return;
8936
9233
  case "setApiKey": {
8937
- const meta = import_core44.ALL_PROVIDERS[effect.provider];
9234
+ const meta = (0, import_core50.isApiProvider)(effect.provider) ? (0, import_core50.getProviderMeta)(effect.provider) : void 0;
8938
9235
  if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
8939
9236
  await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
8940
9237
  deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
@@ -8977,12 +9274,12 @@ async function perform(effect, deps) {
8977
9274
  }
8978
9275
  if (effect.maxParallel !== void 0) await saveMaxParallel(deps, effect.maxParallel);
8979
9276
  const overridden = effect.autonomous !== void 0 && deps.saveAutonomy(effect.autonomous).overridden;
8980
- 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 });
8981
9278
  return;
8982
9279
  }
8983
9280
  case "setAutonomous": {
8984
9281
  const { overridden } = deps.saveAutonomy(effect.enabled);
8985
- const level = (0, import_core44.autonomyLevelLabel)(effect.enabled);
9282
+ const level = (0, import_core50.autonomyLevelLabel)(effect.enabled);
8986
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.` });
8987
9284
  return;
8988
9285
  }
@@ -9020,8 +9317,8 @@ async function perform(effect, deps) {
9020
9317
  }
9021
9318
  const { plan, goal } = adopted2;
9022
9319
  dispatch({ type: "sessionStarted", sessionId: effect.sessionId, goal });
9023
- inbound.releaseSnapshot(true);
9024
9320
  dispatch(restoredChat(plan, effect.sessionId));
9321
+ inbound.releaseSnapshot(true);
9025
9322
  dispatch({ type: "planUpdated", plan, sessionId: effect.sessionId });
9026
9323
  dispatch({ type: "notice", message: `Loaded "${goal || effect.sessionId}".` });
9027
9324
  return;
@@ -9034,7 +9331,7 @@ async function perform(effect, deps) {
9034
9331
  dispatch({ type: "sessionForked", sessionId: fork.sessionId, goal: fork.goal });
9035
9332
  dispatch(restoredChat(fork.plan, fork.sessionId));
9036
9333
  dispatch({ type: "planUpdated", plan: fork.plan, sessionId: fork.sessionId });
9037
- 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.` });
9038
9335
  return;
9039
9336
  }
9040
9337
  case "loadRewindTargets":
@@ -9055,7 +9352,7 @@ async function perform(effect, deps) {
9055
9352
  dispatch(restoredChat(fork.plan, fork.sessionId));
9056
9353
  dispatch({ type: "planUpdated", plan: fork.plan, sessionId: fork.sessionId });
9057
9354
  dispatch({ type: "inputPrefilled", text: fork.rewoundMessage, sessionId: fork.sessionId });
9058
- 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.` });
9059
9356
  return;
9060
9357
  }
9061
9358
  // The redrawn transcript opens with the summary entry, which is what the
@@ -9146,7 +9443,7 @@ async function saveMaxParallel(deps, limit) {
9146
9443
  function copySelection(deps, text) {
9147
9444
  if (!text) return;
9148
9445
  const lines = text.split("\n").length;
9149
- const command = (0, import_core44.clipboardCopyCommand)(deps.hasBin);
9446
+ const command = (0, import_core50.clipboardCopyCommand)(deps.hasBin);
9150
9447
  if (command) {
9151
9448
  try {
9152
9449
  (deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
@@ -9158,7 +9455,7 @@ function copySelection(deps, text) {
9158
9455
  deps.writeTerminal(osc52(text));
9159
9456
  deps.dispatch({
9160
9457
  type: "notice",
9161
- 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.`
9162
9459
  });
9163
9460
  }
9164
9461
  async function withExecutionStream(deps, sessionId, request) {
@@ -9251,7 +9548,7 @@ function lastAssistantMessage(plan) {
9251
9548
  const history = plan?.conversationHistory ?? [];
9252
9549
  for (let i = history.length - 1; i >= 0; i--) {
9253
9550
  const entry = history[i];
9254
- if ((0, import_core44.isUserMessage)(entry)) return null;
9551
+ if ((0, import_core50.isUserMessage)(entry)) return null;
9255
9552
  if (entry.role === "assistant" && entry.kind === void 0) {
9256
9553
  const { content, timestamp } = entry;
9257
9554
  return { content, ...timestamp ? { timestamp } : {} };
@@ -9273,14 +9570,14 @@ async function loadModels(deps) {
9273
9570
  }
9274
9571
  async function loadSessions(deps) {
9275
9572
  const sessions = await deps.api.getSessions(deps.workspace);
9276
- const list = sessions.map((s) => ({
9573
+ const list2 = sessions.map((s) => ({
9277
9574
  id: String(s.id),
9278
9575
  goal: String(s.goal ?? ""),
9279
9576
  taskCount: Number(s.taskCount ?? 0),
9280
9577
  status: String(s.status ?? ""),
9281
9578
  createdAt: String(s.createdAt ?? "")
9282
9579
  }));
9283
- deps.dispatch({ type: "sessionsLoaded", sessions: list });
9580
+ deps.dispatch({ type: "sessionsLoaded", sessions: list2 });
9284
9581
  }
9285
9582
  async function loadRunners(deps) {
9286
9583
  const state = await deps.api.getRunners();
@@ -9606,7 +9903,7 @@ async function handleTui(subArgs) {
9606
9903
  }
9607
9904
  const workspace = flag(subArgs, "--workspace") || process.cwd();
9608
9905
  try {
9609
- (0, import_core45.assertWorkspaceExists)(workspace);
9906
+ (0, import_core51.assertWorkspaceExists)(workspace);
9610
9907
  } catch {
9611
9908
  console.error(
9612
9909
  `Workspace "${workspace}" does not exist.
@@ -9615,7 +9912,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
9615
9912
  );
9616
9913
  process.exit(1);
9617
9914
  }
9618
- const discoveredSkills = (0, import_core45.createSkillsService)(workspace, new import_core45.EnvConfig().workspaceRepos).listSkills();
9915
+ const discoveredSkills = (0, import_core51.createSkillsService)(workspace, new import_core51.EnvConfig().workspaceRepos).listSkills();
9619
9916
  registerSkillCommands(discoveredSkills);
9620
9917
  const taskSkills = discoveredSkills.filter((s) => s.appliesTo === "task").map((s) => ({ name: s.name, description: s.description }));
9621
9918
  const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
@@ -9625,24 +9922,37 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
9625
9922
  const api = new ApiClient(port, workspace);
9626
9923
  let terminal;
9627
9924
  const mouseCapture = process.env.ORDEWELL_TUI_MOUSE !== "false" && process.env.ORDEWELL_TUI_MOUSE !== "0";
9925
+ const settings = new import_core51.SettingsService();
9628
9926
  const app = createApp({
9629
9927
  // The level the daemon plans with: the saved one, unless the environment overrides it (ADR-0030).
9630
- initial: { workspace, autonomous: new import_core45.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
+ },
9631
9941
  draw: (frame2) => terminal?.draw(frame2),
9632
9942
  perform: (effect) => runEffect(effect, {
9633
9943
  api,
9634
9944
  workspace,
9635
9945
  port,
9636
9946
  dispatch: (action) => app.dispatch(action),
9637
- newSessionId: () => (0, import_core45.mintSessionId)(),
9947
+ newSessionId: () => (0, import_core51.mintSessionId)(),
9638
9948
  setEnvVar: (key, value) => {
9639
9949
  writeEnvVar(findEnvFile(), key, value);
9640
9950
  process.env[key] = value;
9641
9951
  },
9642
9952
  saveAutonomy: (enabled) => {
9643
- const settings = new import_core45.SettingsService();
9644
- settings.setAutonomousMode(enabled);
9645
- return { overridden: settings.autonomousModeOverridden() };
9953
+ const settings2 = new import_core51.SettingsService();
9954
+ settings2.setAutonomousMode(enabled);
9955
+ return { overridden: settings2.autonomousModeOverridden() };
9646
9956
  },
9647
9957
  setMouseCapture: (enabled) => terminal?.setMouse(enabled),
9648
9958
  // The alt screen is up with the cursor hidden, so an OSC 52 write shows
@@ -9754,7 +10064,7 @@ var COMMANDS = {
9754
10064
  // src/main.ts
9755
10065
  var TUI_FLAGS = /* @__PURE__ */ new Set(["--workspace", "--port"]);
9756
10066
  async function main() {
9757
- (0, import_core46.migrateOldConfigDir)();
10067
+ (0, import_core52.migrateOldConfigDir)();
9758
10068
  moveAutonomyToSettings();
9759
10069
  loadEnvFile();
9760
10070
  const argv = process.argv.slice(2);