@ordewell/cli 0.4.9 → 0.4.11

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 (2) hide show
  1. package/dist/main.js +381 -323
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -23,12 +23,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  mod
24
24
  ));
25
25
 
26
+ // src/main.ts
27
+ var import_core18 = require("@ordewell/core");
28
+
26
29
  // src/utils/env.ts
27
30
  var import_fs = require("fs");
28
31
  var import_path = require("path");
29
- var import_os = require("os");
32
+ var import_core = require("@ordewell/core");
30
33
  function findEnvFile() {
31
- return (0, import_path.join)((0, import_os.homedir)(), ".config", "ordewell", ".env");
34
+ return (0, import_path.join)((0, import_core.globalDataDir)(), ".env");
32
35
  }
33
36
  function loadEnvFile() {
34
37
  const filePath = findEnvFile();
@@ -83,7 +86,7 @@ an id.
83
86
  Planning:
84
87
  ordewell Full-screen terminal UI (everything the VS Code extension does)
85
88
  ordewell tui The same thing, named \u2014 for scripts and aliases
86
- ordewell plan --goal "text" Plan a goal conversationally (honours grill-me/PRD/review)
89
+ ordewell plan --goal "text" Plan a goal conversationally (honours verify)
87
90
  ordewell run Execute the last generated plan (streams status)
88
91
  ordewell approve Sign off a plan paused for review, and continue it
89
92
  ordewell stop Stop execution of the last session (or --server)
@@ -121,12 +124,8 @@ Planner, models and runners:
121
124
  ordewell models List every provider's catalog (works without a server)
122
125
 
123
126
  Modes:
124
- ordewell grill-me [on|off] Toggle Grill-Me challenge mode (or show status)
125
127
  ordewell tdd [on|off] Toggle Test-Driven Development mode (or show status)
126
- ordewell prd [on|off] Toggle PRD mode (planner writes a PRD before the plan)
127
- ordewell review [on|off] Toggle review mode \u2014 adds a final opinion-review task, human sign-off (or show status)
128
128
  ordewell verify [on|off] Toggle verification mode \u2014 adds a final evidence-based task that runs the full suite (or show status)
129
- ordewell research-subagents [on|off] Toggle parallel read-only research subagents during planning (or show status)
130
129
 
131
130
  Other:
132
131
  ordewell web Start the API server (foreground; --daemon for background)
@@ -202,16 +201,21 @@ Environment:
202
201
  // src/daemon.ts
203
202
  var import_child_process = require("child_process");
204
203
  var import_fs2 = require("fs");
205
- var import_os2 = require("os");
204
+ var import_os = require("os");
206
205
  var import_path2 = require("path");
207
206
  var import_http = __toESM(require("http"));
208
- var import_core = require("@ordewell/core");
207
+ var import_net = require("net");
208
+ var import_core2 = require("@ordewell/core");
209
209
  var DEFAULT_PORT = 3742;
210
- var CONFIG_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".config", "ordewell");
210
+ var CONFIG_DIR = (0, import_core2.globalDataDir)();
211
211
  var LEGACY_PID_FILE = (0, import_path2.join)(CONFIG_DIR, "server.pid");
212
+ var OLD_CONFIG_DIR = (0, import_path2.join)((0, import_os.homedir)(), ".config", "ordewell");
212
213
  function pidFileFor(port) {
213
214
  return port === DEFAULT_PORT ? LEGACY_PID_FILE : (0, import_path2.join)(CONFIG_DIR, `server-${port}.pid`);
214
215
  }
216
+ function oldPidFileFor(port) {
217
+ return port === DEFAULT_PORT ? (0, import_path2.join)(OLD_CONFIG_DIR, "server.pid") : (0, import_path2.join)(OLD_CONFIG_DIR, `server-${port}.pid`);
218
+ }
215
219
  function httpGet(url, timeoutMs = 2e3, headers = {}) {
216
220
  return new Promise((resolve3, reject) => {
217
221
  const req = import_http.default.get(url, { timeout: timeoutMs, headers }, (res) => {
@@ -243,12 +247,12 @@ function getWebServerPath() {
243
247
  );
244
248
  }
245
249
  async function isDaemonRunning(port = DEFAULT_PORT) {
246
- const token = (0, import_core.readDaemonToken)(port);
250
+ const token = (0, import_core2.readDaemonToken)(port);
247
251
  try {
248
252
  const res = await httpGet(
249
253
  `http://127.0.0.1:${port}/api/workspaces`,
250
254
  2e3,
251
- token ? { Authorization: (0, import_core.bearerHeaderValue)(token) } : {}
255
+ token ? { Authorization: (0, import_core2.bearerHeaderValue)(token) } : {}
252
256
  );
253
257
  return res.status === 200 || res.status === 401;
254
258
  } catch {
@@ -268,7 +272,12 @@ async function startDaemon(port = DEFAULT_PORT, opts) {
268
272
  }
269
273
  const logFd = (0, import_fs2.openSync)((0, import_path2.join)(CONFIG_DIR, "server.log"), "a");
270
274
  const detached = opts?.detached ?? true;
271
- const child = (0, import_child_process.spawn)(process.execPath, [serverPath, "--port", String(port)], {
275
+ const child = (0, import_child_process.spawn)(process.execPath, [
276
+ serverPath,
277
+ "--port",
278
+ String(port),
279
+ ...detached ? [] : ["--watch-parent", String(process.pid)]
280
+ ], {
272
281
  detached,
273
282
  stdio: ["ignore", logFd, logFd],
274
283
  env: { ...process.env }
@@ -288,7 +297,8 @@ async function startDaemon(port = DEFAULT_PORT, opts) {
288
297
  process.exit(1);
289
298
  }
290
299
  async function stopDaemon(port = DEFAULT_PORT) {
291
- const pidFile = pidFileFor(port);
300
+ let pidFile = pidFileFor(port);
301
+ if (!(0, import_fs2.existsSync)(pidFile) && (0, import_fs2.existsSync)(oldPidFileFor(port))) pidFile = oldPidFileFor(port);
292
302
  if (!(0, import_fs2.existsSync)(pidFile)) {
293
303
  console.error(`No server PID file found for port ${port}. Is the server running?`);
294
304
  return false;
@@ -298,7 +308,7 @@ async function stopDaemon(port = DEFAULT_PORT) {
298
308
  const pid = parseInt(pidStr, 10);
299
309
  if (!pid || isNaN(pid)) {
300
310
  (0, import_fs2.unlinkSync)(pidFile);
301
- (0, import_core.clearDaemonToken)(port);
311
+ (0, import_core2.clearDaemonToken)(port);
302
312
  return false;
303
313
  }
304
314
  process.kill(pid, "SIGTERM");
@@ -311,7 +321,7 @@ async function stopDaemon(port = DEFAULT_PORT) {
311
321
  (0, import_fs2.unlinkSync)(pidFile);
312
322
  } catch {
313
323
  }
314
- (0, import_core.clearDaemonToken)(port);
324
+ (0, import_core2.clearDaemonToken)(port);
315
325
  console.log("Server was not running (stale PID removed).");
316
326
  return true;
317
327
  }
@@ -338,6 +348,18 @@ function isConnectionRefused(err) {
338
348
  function describeConnectionRefused(port) {
339
349
  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\`.`;
340
350
  }
351
+ function findFreePort() {
352
+ return new Promise((resolvePromise, reject) => {
353
+ const srv = (0, import_net.createServer)();
354
+ srv.unref();
355
+ srv.on("error", reject);
356
+ srv.listen(0, "127.0.0.1", () => {
357
+ const address = srv.address();
358
+ const port = typeof address === "object" && address ? address.port : 0;
359
+ srv.close(() => resolvePromise(port));
360
+ });
361
+ });
362
+ }
341
363
  function resolvePort(args = []) {
342
364
  const idx = args.indexOf("--port");
343
365
  if (idx !== -1) {
@@ -352,7 +374,7 @@ function resolvePort(args = []) {
352
374
  // src/apiClient.ts
353
375
  var import_http2 = __toESM(require("http"));
354
376
  var import_ws = __toESM(require("ws"));
355
- var import_core2 = require("@ordewell/core");
377
+ var import_core3 = require("@ordewell/core");
356
378
  var DEFAULT_HTTP_TIMEOUT_MS = 15 * 60 * 1e3;
357
379
  var ApiClient = class {
358
380
  port;
@@ -365,7 +387,7 @@ var ApiClient = class {
365
387
  * A missing file is not an error here — the daemon's own 401 names the path.
366
388
  */
367
389
  token() {
368
- return (0, import_core2.readDaemonToken)(this.port);
390
+ return (0, import_core3.readDaemonToken)(this.port);
369
391
  }
370
392
  httpRequest(method, urlPath, body) {
371
393
  return new Promise((resolve3, reject) => {
@@ -379,7 +401,7 @@ var ApiClient = class {
379
401
  method,
380
402
  headers: {
381
403
  ...body ? { "Content-Type": "application/json" } : {},
382
- ...token ? { Authorization: (0, import_core2.bearerHeaderValue)(token) } : {}
404
+ ...token ? { Authorization: (0, import_core3.bearerHeaderValue)(token) } : {}
383
405
  },
384
406
  // Planner research on a real repository regularly exceeds two minutes.
385
407
  // The benchmark harness uses the same 15-minute default explicitly;
@@ -408,7 +430,7 @@ var ApiClient = class {
408
430
  req.end();
409
431
  });
410
432
  }
411
- async generatePlan(goal, runners2, workspace, sessionId = (0, import_core2.mintSessionId)()) {
433
+ async generatePlan(goal, runners2, workspace, sessionId = (0, import_core3.mintSessionId)()) {
412
434
  const res = await this.httpRequest("POST", `/api/plans/${sessionId}/generate`, {
413
435
  goal,
414
436
  runners: runners2 || void 0,
@@ -421,7 +443,7 @@ var ApiClient = class {
421
443
  }
422
444
  /**
423
445
  * Open the ADR-0002 planner dialogue. Unlike `generatePlan`, this path honours
424
- * the grill-me / PRD / review toggles, so the returned plan may be a question
446
+ * the verify toggle, so the returned plan may be a question
425
447
  * (empty `tasks`, last word in `conversationHistory`) rather than a committed plan.
426
448
  */
427
449
  async startConversation(sessionId, goal, runners2, workspace) {
@@ -609,7 +631,7 @@ var ApiClient = class {
609
631
  const token = this.token();
610
632
  const socket = new import_ws.default(
611
633
  `ws://127.0.0.1:${this.port}/ws/session/${sessionId}`,
612
- token ? (0, import_core2.tokenSubprotocols)(token) : void 0
634
+ token ? (0, import_core3.tokenSubprotocols)(token) : void 0
613
635
  );
614
636
  socket.on("unexpected-response", (_req, res) => {
615
637
  let body = "";
@@ -773,6 +795,7 @@ function positionals(args) {
773
795
  }
774
796
 
775
797
  // src/utils/tasks.ts
798
+ var import_core4 = require("@ordewell/core");
776
799
  function allTasksOf(plan) {
777
800
  const tasks = plan?.pendingTasks || plan?.tasks || [];
778
801
  const executionLog = plan?.executionLog || [];
@@ -780,6 +803,10 @@ function allTasksOf(plan) {
780
803
  }
781
804
  function resolveTaskId(plan, identifier) {
782
805
  const allTasks = allTasksOf(plan);
806
+ if (identifier.includes(".")) {
807
+ const subtask = (0, import_core4.resolveOrderLabel)(allTasks, identifier);
808
+ return subtask?.id;
809
+ }
783
810
  const order = parseInt(identifier, 10);
784
811
  if (!isNaN(order)) {
785
812
  const task = allTasks.find((t) => t.order === order);
@@ -795,20 +822,24 @@ function resolveTaskId(plan, identifier) {
795
822
  // src/utils/session.ts
796
823
  var import_fs3 = require("fs");
797
824
  var import_path3 = require("path");
798
- var import_os3 = require("os");
799
- var LAST_SESSION_FILE = (0, import_path3.join)((0, import_os3.homedir)(), ".config", "ordewell", "last-session.json");
825
+ var import_core5 = require("@ordewell/core");
826
+ var LAST_SESSION_FILENAME = "last-session.json";
827
+ function lastSessionFile(workspace) {
828
+ return (0, import_path3.join)((0, import_core5.getStateDir)(workspace), LAST_SESSION_FILENAME);
829
+ }
800
830
  function saveLastSession(sessionId, goal, runners2, workspace) {
801
- const dir = (0, import_path3.join)((0, import_os3.homedir)(), ".config", "ordewell");
831
+ const dir = (0, import_core5.getStateDir)(workspace);
802
832
  if (!(0, import_fs3.existsSync)(dir)) (0, import_fs3.mkdirSync)(dir, { recursive: true });
803
833
  (0, import_fs3.writeFileSync)(
804
- LAST_SESSION_FILE,
834
+ lastSessionFile(workspace),
805
835
  JSON.stringify({ sessionId, goal, runners: runners2, workspace, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
806
836
  );
807
837
  }
808
- function readLastSession() {
838
+ function readLastSession(workspace) {
809
839
  try {
810
- if ((0, import_fs3.existsSync)(LAST_SESSION_FILE)) {
811
- return JSON.parse((0, import_fs3.readFileSync)(LAST_SESSION_FILE, "utf8"));
840
+ const file = lastSessionFile(workspace);
841
+ if ((0, import_fs3.existsSync)(file)) {
842
+ return JSON.parse((0, import_fs3.readFileSync)(file, "utf8"));
812
843
  }
813
844
  } catch {
814
845
  }
@@ -914,7 +945,7 @@ function isTransient(event) {
914
945
  }
915
946
 
916
947
  // src/commands/plan.ts
917
- var import_core3 = require("@ordewell/core");
948
+ var import_core6 = require("@ordewell/core");
918
949
  function planTasks(plan) {
919
950
  return plan?.tasks || [];
920
951
  }
@@ -976,21 +1007,26 @@ function printPlan(plan, sessionId, runners2, models) {
976
1007
  const tasks = planTasks(plan);
977
1008
  const aiCount = tasks.filter((t) => t.type !== "user").length;
978
1009
  const manCount = tasks.filter((t) => t.type === "user").length;
1010
+ const modelSuffix = (task) => {
1011
+ if (!task.assignedModel) return "";
1012
+ const found = models.find((m) => m.modelId === task.assignedModel?.modelId);
1013
+ const rp = found?.runnerProvider;
1014
+ const label = rp ? rp.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ") : "";
1015
+ return rp ? ` (${task.assignedModel.modelLabel} \xB7 ${label})` : ` (${task.assignedModel.modelLabel})`;
1016
+ };
979
1017
  console.log(`
980
1018
  Plan: ${tasks.length} tasks (${aiCount} AI, ${manCount} Manual) \u2014 ${(plan.runners || runners2).join(", ")}`);
981
1019
  console.log(`Session: ${sessionId}
982
1020
  `);
983
1021
  for (const t of tasks) {
984
1022
  const typeIcon = t.type === "user" ? "[MAN]" : "[ AI]";
985
- let model = "";
986
- if (t.assignedModel) {
987
- const found = models.find((m) => m.modelId === t.assignedModel?.modelId);
988
- const rp = found?.runnerProvider;
989
- const label = rp ? rp.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ") : "";
990
- model = rp ? ` (${t.assignedModel.modelLabel} \xB7 ${label})` : ` (${t.assignedModel.modelLabel})`;
991
- }
992
1023
  const deps = t.dependencies?.length ? ` \u2190 ${t.dependencies.join(", ")}` : "";
993
- console.log(` ${String(t.order).padStart(2)}. ${typeIcon} ${t.title}${model}${deps}`);
1024
+ console.log(` ${(0, import_core6.taskOrderLabel)(t).padStart(2)}. ${typeIcon} ${t.title}${modelSuffix(t)}${deps}`);
1025
+ for (const sub of t.subtasks || []) {
1026
+ const subTypeIcon = sub.type === "user" ? "[MAN]" : "[ AI]";
1027
+ const subDeps = sub.dependencies?.length ? ` \u2190 ${sub.dependencies.join(", ")}` : "";
1028
+ console.log(` ${(0, import_core6.taskOrderLabel)(sub, t)}. ${subTypeIcon} ${sub.title}${modelSuffix(sub)}${subDeps}`);
1029
+ }
994
1030
  }
995
1031
  if (manCount > 0) {
996
1032
  console.log("\n [MAN] = manual step \u2014 run `ordewell tui` to work through it");
@@ -1036,7 +1072,7 @@ async function handlePlan(subArgs, deps = {}) {
1036
1072
  runners2 = runnerFlags;
1037
1073
  }
1038
1074
  console.error(`Generating plan for: "${goal}"...`);
1039
- const sessionId = (0, import_core3.mintSessionId)();
1075
+ const sessionId = (0, import_core6.mintSessionId)();
1040
1076
  const isTty = !!process.stderr.isTTY;
1041
1077
  let lastLineLength = 0;
1042
1078
  function renderStep(event) {
@@ -1148,9 +1184,10 @@ function iconFor(status) {
1148
1184
  function resolveSessionId(subArgs) {
1149
1185
  const explicit = flag(subArgs, "--session-id");
1150
1186
  if (explicit) return explicit;
1151
- const last = readLastSession();
1187
+ const workspace = flag(subArgs, "--workspace") || process.cwd();
1188
+ const last = readLastSession(workspace);
1152
1189
  if (!last) {
1153
- console.error("No session specified. Use --session-id <id> or run `ordewell plan` first.");
1190
+ console.error(`No session specified. Use --session-id <id> or run \`ordewell plan\` in ${workspace} first.`);
1154
1191
  process.exit(1);
1155
1192
  }
1156
1193
  console.error(`Using last session: ${last.sessionId} ("${last.goal}")`);
@@ -1224,51 +1261,29 @@ async function handleRun(subArgs) {
1224
1261
 
1225
1262
  // src/commands/status.ts
1226
1263
  var import_fs4 = require("fs");
1264
+ var import_core7 = require("@ordewell/core");
1227
1265
  async function handleStatus(subArgs, injectedApi) {
1228
1266
  const sessionId = flag(subArgs, "--session-id");
1229
- const explicitWorkspace = flag(subArgs, "--workspace");
1230
- let workspace = explicitWorkspace || process.cwd();
1267
+ const workspace = flag(subArgs, "--workspace") || process.cwd();
1231
1268
  const outputPath = flag(subArgs, "--output");
1232
1269
  const asJson = hasFlag(subArgs, "--json") || Boolean(outputPath);
1233
1270
  const port = injectedApi ? resolvePort(subArgs) : await ensureDaemon(resolvePort(subArgs));
1234
1271
  const api = injectedApi || new ApiClient(port);
1235
1272
  if (sessionId) {
1236
- let session;
1237
- try {
1238
- session = await api.getSession(sessionId, workspace);
1239
- } catch (err) {
1240
- const last = readLastSession();
1241
- if (!explicitWorkspace && last?.workspace && last.workspace !== workspace) {
1242
- session = await api.getSession(sessionId, last.workspace);
1243
- } else {
1244
- throw err;
1245
- }
1246
- }
1273
+ const session = await api.getSession(sessionId, workspace);
1247
1274
  if (asJson) {
1248
1275
  emitJson(session, outputPath);
1249
1276
  return;
1250
1277
  }
1251
1278
  printOneSession(sessionId, session.meta, session.plan);
1252
1279
  } else {
1253
- let sessions = await api.getSessions(workspace);
1254
- if (sessions.length === 0 && !explicitWorkspace) {
1255
- const last2 = readLastSession();
1256
- if (last2?.workspace && last2.workspace !== workspace) {
1257
- const fallback = await api.getSessions(last2.workspace);
1258
- if (fallback.length > 0) {
1259
- workspace = last2.workspace;
1260
- sessions = fallback;
1261
- console.log(`(no sessions in ${process.cwd()} \u2014 showing ${workspace})
1262
- `);
1263
- }
1264
- }
1265
- }
1280
+ const sessions = await api.getSessions(workspace);
1266
1281
  if (asJson) {
1267
1282
  emitJson(sessions, outputPath);
1268
1283
  return;
1269
1284
  }
1270
1285
  if (sessions.length === 0) {
1271
- console.log("No sessions found.");
1286
+ console.log(`No sessions found in ${workspace}.`);
1272
1287
  return;
1273
1288
  }
1274
1289
  console.log(`${sessions.length} session(s):
@@ -1276,7 +1291,7 @@ async function handleStatus(subArgs, injectedApi) {
1276
1291
  for (const s of sessions) {
1277
1292
  console.log(` ${s.id.slice(-8)} ${s.status.padEnd(10)} ${s.taskCount} tasks ${(s.runners || []).join(",").padEnd(12)} ${s.goal.slice(0, 60)}`);
1278
1293
  }
1279
- const last = readLastSession();
1294
+ const last = readLastSession(workspace);
1280
1295
  if (last) {
1281
1296
  console.log(`
1282
1297
  Last active: ${last.sessionId.slice(-8)} ("${last.goal.slice(0, 50)}")`);
@@ -1317,7 +1332,13 @@ function printOneSession(sessionId, meta, plan) {
1317
1332
  const icon = iconFor(t.status);
1318
1333
  const model = t.assignedModel ? ` (${t.assignedModel.modelLabel})` : "";
1319
1334
  const shortId = (t.id ?? "").slice(-8);
1320
- console.log(` ${String(t.order).padStart(2)}. ${icon} [${shortId}] ${t.title}${model}`);
1335
+ console.log(` ${(0, import_core7.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}] ${t.title}${model}`);
1336
+ for (const sub of t.subtasks || []) {
1337
+ const subIcon = iconFor(sub.status);
1338
+ const subModel = sub.assignedModel ? ` (${sub.assignedModel.modelLabel})` : "";
1339
+ const subShortId = (sub.id ?? "").slice(-8);
1340
+ console.log(` ${(0, import_core7.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
1341
+ }
1321
1342
  }
1322
1343
  }
1323
1344
 
@@ -1330,7 +1351,8 @@ async function handleStop(subArgs) {
1330
1351
  }
1331
1352
  let sessionId = flag(subArgs, "--session-id");
1332
1353
  if (!sessionId) {
1333
- const last = readLastSession();
1354
+ const workspace = flag(subArgs, "--workspace") || process.cwd();
1355
+ const last = readLastSession(workspace);
1334
1356
  if (!last) {
1335
1357
  console.error("No session specified. Use --session-id <id> or --server to stop the server.");
1336
1358
  process.exit(1);
@@ -1367,10 +1389,10 @@ async function handleWeb(subArgs) {
1367
1389
  }
1368
1390
 
1369
1391
  // src/commands/models.ts
1370
- var import_core5 = require("@ordewell/core");
1392
+ var import_core9 = require("@ordewell/core");
1371
1393
 
1372
1394
  // src/commands/model.ts
1373
- var import_core4 = require("@ordewell/core");
1395
+ var import_core8 = require("@ordewell/core");
1374
1396
 
1375
1397
  // src/catalog.ts
1376
1398
  function normalizeCatalog(result) {
@@ -1452,7 +1474,8 @@ function toTaskView(task) {
1452
1474
  dependencies: task.dependencies ?? [],
1453
1475
  assignedRunner: task.assignedRunner || void 0,
1454
1476
  taskMode: task.taskMode || void 0,
1455
- assignedModel: task.assignedModel ?? void 0
1477
+ assignedModel: task.assignedModel ?? void 0,
1478
+ subtasks: [...task.subtasks ?? []].sort((a, b) => a.order - b.order).map(toTaskView)
1456
1479
  };
1457
1480
  }
1458
1481
  function taskViews(plan) {
@@ -1475,7 +1498,7 @@ async function handleModel(subArgs, injectedApi) {
1475
1498
  if (!modelId) fail(USAGE);
1476
1499
  const settings = await api.getSettings();
1477
1500
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
1478
- const runner = (0, import_core4.runnerForProvider)(provider);
1501
+ const runner = (0, import_core8.runnerForProvider)(provider);
1479
1502
  if (runner) {
1480
1503
  const known = (await fetchCatalog(api)).models.filter((m) => m.runners?.includes(runner));
1481
1504
  if (known.length > 0 && !known.some((m) => m.id === modelId)) {
@@ -1494,7 +1517,7 @@ async function showModels(api) {
1494
1517
  const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
1495
1518
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
1496
1519
  const current = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
1497
- const runner = (0, import_core4.runnerForProvider)(provider);
1520
+ const runner = (0, import_core8.runnerForProvider)(provider);
1498
1521
  const options = runner ? catalog.models.filter((m) => m.runners?.includes(runner)) : catalog.orchestratorModels;
1499
1522
  console.log(`
1500
1523
  Planner model: ${current || "(backend default)"}`);
@@ -1528,8 +1551,8 @@ async function handleModels(subArgs) {
1528
1551
  const rest = subArgs.filter((_, i) => i !== setIdx && i !== setIdx + 1);
1529
1552
  return handleModel(["set", subArgs[setIdx + 1], ...rest]);
1530
1553
  }
1531
- const config = new import_core5.EnvConfig();
1532
- const providers = (0, import_core5.configuredProviders)(config);
1554
+ const config = new import_core9.EnvConfig();
1555
+ const providers = (0, import_core9.configuredProviders)(config);
1533
1556
  if (providers.length === 0) {
1534
1557
  console.error("No providers configured. Set an API key for a provider, e.g.:");
1535
1558
  console.error(" OPENROUTER_API_KEY=... (OpenRouter)");
@@ -1538,19 +1561,19 @@ async function handleModels(subArgs) {
1538
1561
  console.error(" OPENAI_COMPATIBLE_BASE_URL=... (self-hosted / OpenAI-compatible)");
1539
1562
  process.exit(1);
1540
1563
  }
1541
- const { apiKeys, baseUrls } = (0, import_core5.collectProviderCredentials)(config);
1564
+ const { apiKeys, baseUrls } = (0, import_core9.collectProviderCredentials)(config);
1542
1565
  let models;
1543
1566
  let errors;
1544
1567
  try {
1545
- const result = await (0, import_core5.fetchAllProviderModels)({ apiKeys, baseUrls });
1546
- models = (0, import_core5.toOrchestratorOptions)(result.models, import_core5.ORCHESTRATOR_SHORTCUTS);
1568
+ const result = await (0, import_core9.fetchAllProviderModels)({ apiKeys, baseUrls });
1569
+ models = (0, import_core9.toOrchestratorOptions)(result.models, import_core9.ORCHESTRATOR_SHORTCUTS);
1547
1570
  errors = result.errors;
1548
1571
  } catch (err) {
1549
1572
  console.error(`Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`);
1550
1573
  process.exit(1);
1551
1574
  return;
1552
1575
  }
1553
- const providerLabel = (p) => (0, import_core5.getProviderMeta)(p)?.label ?? p;
1576
+ const providerLabel = (p) => (0, import_core9.getProviderMeta)(p)?.label ?? p;
1554
1577
  console.log(
1555
1578
  `
1556
1579
  Available models (${models.length}) across ${providers.length} configured provider(s):`
@@ -1667,9 +1690,9 @@ function handleSetup(subArgs) {
1667
1690
  }
1668
1691
 
1669
1692
  // src/commands/plugins.ts
1670
- var import_core6 = require("@ordewell/core");
1693
+ var import_core10 = require("@ordewell/core");
1671
1694
  function handlePlugins(subArgs) {
1672
- const registry = new import_core6.RunnerRegistry();
1695
+ const registry = new import_core10.RunnerRegistry();
1673
1696
  registry.loadUserPlugins();
1674
1697
  const sub = subArgs[0];
1675
1698
  if (sub === "list" || !sub) {
@@ -1705,7 +1728,7 @@ ${plugins.length} plugin(s):
1705
1728
  process.exit(1);
1706
1729
  }
1707
1730
  try {
1708
- const resolved = (0, import_core6.classifyPluginSource)(source);
1731
+ const resolved = (0, import_core10.classifyPluginSource)(source);
1709
1732
  const manifest = resolved.kind === "git" ? registry.installFromGit(resolved.url) : registry.installFromPath(resolved.path);
1710
1733
  console.log(`Installed plugin: ${manifest.name} v${manifest.version}`);
1711
1734
  } catch (err) {
@@ -1746,23 +1769,6 @@ ${plugins.length} plugin(s):
1746
1769
  process.exit(1);
1747
1770
  }
1748
1771
 
1749
- // src/commands/grillMe.ts
1750
- async function handleGrillMe(subArgs) {
1751
- const action = subArgs[0];
1752
- const port = await ensureDaemon(resolvePort(subArgs));
1753
- const api = new ApiClient(port);
1754
- if (action === "on" || action === "off") {
1755
- const result = await api.sendCommand("grill-me", { action });
1756
- const state = result.settings?.grillMe;
1757
- console.log(`Grill-Me: ${state?.enabled ? "ON" : "OFF"}`);
1758
- } else {
1759
- const settings = await api.getSettings();
1760
- const grillMe = settings.grillMe;
1761
- console.log(`Grill-Me: ${grillMe?.enabled ? "ON" : "OFF"}`);
1762
- console.log("Usage: ordewell grill-me [on|off]");
1763
- }
1764
- }
1765
-
1766
1772
  // src/commands/tdd.ts
1767
1773
  async function handleTdd(subArgs) {
1768
1774
  const action = subArgs[0];
@@ -1780,24 +1786,6 @@ async function handleTdd(subArgs) {
1780
1786
  }
1781
1787
  }
1782
1788
 
1783
- // src/commands/prd.ts
1784
- async function handlePrd(subArgs) {
1785
- const action = subArgs[0];
1786
- const port = await ensureDaemon(resolvePort(subArgs));
1787
- const api = new ApiClient(port);
1788
- if (action === "on" || action === "off") {
1789
- const result = await api.sendCommand("prd", { action });
1790
- const state = result.settings?.prd;
1791
- console.log(`PRD mode: ${state?.enabled ? "ON" : "OFF"}`);
1792
- } else {
1793
- const settings = await api.getSettings();
1794
- const prd = settings.prd;
1795
- console.log(`PRD mode: ${prd?.enabled ? "ON" : "OFF"}`);
1796
- console.log("Usage: ordewell prd [on|off]");
1797
- console.log("When ON, the planner previews and writes a markdown PRD (saved to .scratch/<slug>/PRD.md) before the task plan.");
1798
- }
1799
- }
1800
-
1801
1789
  // src/commands/allowlist.ts
1802
1790
  var USAGE2 = `Usage:
1803
1791
  ordewell allowlist set <runner> <id1,id2,\u2026> Set allowlist for a runner
@@ -1884,23 +1872,6 @@ async function handleShow(args, api) {
1884
1872
  }
1885
1873
  }
1886
1874
 
1887
- // src/commands/review.ts
1888
- async function handleReview(subArgs, api) {
1889
- const action = subArgs[0];
1890
- const port = api ? void 0 : await ensureDaemon(resolvePort(subArgs));
1891
- const client = api || new ApiClient(port);
1892
- if (action === "on" || action === "off") {
1893
- const result = await client.sendCommand("review", { action });
1894
- const state = result.settings?.review;
1895
- console.log(`Review mode: ${state?.enabled ? "ON" : "OFF"}`);
1896
- } else {
1897
- const settings = await client.getSettings();
1898
- const review = settings.review;
1899
- console.log(`Review mode: ${review?.enabled ? "ON" : "OFF"}`);
1900
- console.log("Usage: ordewell review [on|off]");
1901
- }
1902
- }
1903
-
1904
1875
  // src/commands/verify.ts
1905
1876
  async function handleVerify(subArgs, api) {
1906
1877
  const action = subArgs[0];
@@ -1918,23 +1889,6 @@ async function handleVerify(subArgs, api) {
1918
1889
  }
1919
1890
  }
1920
1891
 
1921
- // src/commands/researchSubagents.ts
1922
- async function handleResearchSubagents(subArgs, api) {
1923
- const action = subArgs[0];
1924
- const port = api ? void 0 : await ensureDaemon();
1925
- const client = api || new ApiClient(port);
1926
- if (action === "on" || action === "off") {
1927
- const result = await client.sendCommand("research-subagents", { action });
1928
- const state = result.settings?.researchSubagents;
1929
- console.log(`Research subagents: ${state?.enabled ? "ON" : "OFF"}`);
1930
- } else {
1931
- const settings = await client.getSettings();
1932
- const researchSubagents = settings.researchSubagents;
1933
- console.log(`Research subagents: ${researchSubagents?.enabled ? "ON" : "OFF"}`);
1934
- console.log("Usage: ordewell research-subagents [on|off]");
1935
- }
1936
- }
1937
-
1938
1892
  // src/commands/task-control.ts
1939
1893
  var PAST_TENSE = {
1940
1894
  run: "Task started.",
@@ -1943,11 +1897,12 @@ var PAST_TENSE = {
1943
1897
  cancel: "Task cancelled."
1944
1898
  };
1945
1899
  async function withResolvedTask(subArgs, usage, injectedApi, run) {
1946
- const last = readLastSession();
1900
+ const workspace = flag(subArgs, "--workspace") || process.cwd();
1947
1901
  let sessionId = flag(subArgs, "--session-id");
1948
1902
  if (!sessionId) {
1903
+ const last = readLastSession(workspace);
1949
1904
  if (!last) {
1950
- console.error("No session specified. Use --session-id <id> or run `ordewell plan` first.");
1905
+ console.error(`No session specified. Use --session-id <id> or run \`ordewell plan\` in ${workspace} first.`);
1951
1906
  process.exit(1);
1952
1907
  }
1953
1908
  sessionId = last.sessionId;
@@ -1958,22 +1913,12 @@ async function withResolvedTask(subArgs, usage, injectedApi, run) {
1958
1913
  process.exit(1);
1959
1914
  }
1960
1915
  const api = injectedApi || new ApiClient(await ensureDaemon(resolvePort(subArgs)));
1961
- const explicitWorkspace = flag(subArgs, "--workspace");
1962
1916
  let plan;
1963
1917
  try {
1964
- plan = (await api.getSession(sessionId, explicitWorkspace || process.cwd())).plan;
1918
+ plan = (await api.getSession(sessionId, workspace)).plan;
1965
1919
  } catch (err) {
1966
- if (!explicitWorkspace && last?.workspace && last.workspace !== process.cwd()) {
1967
- try {
1968
- plan = (await api.getSession(sessionId, last.workspace)).plan;
1969
- } catch {
1970
- console.error(`Failed to load session: ${err.message}`);
1971
- process.exit(1);
1972
- }
1973
- } else {
1974
- console.error(`Failed to load session: ${err.message}`);
1975
- process.exit(1);
1976
- }
1920
+ console.error(`Failed to load session: ${err.message}`);
1921
+ process.exit(1);
1977
1922
  }
1978
1923
  const taskId = resolveTaskId(plan, identifier);
1979
1924
  if (!taskId) {
@@ -1986,7 +1931,7 @@ function makeHandler(action, command = action) {
1986
1931
  return async function handle(subArgs, injectedApi) {
1987
1932
  await withResolvedTask(
1988
1933
  subArgs,
1989
- `Usage: ordewell ${command} <task-id-or-order> [--session-id <id>]`,
1934
+ `Usage: ordewell ${command} <task-id-or-order> [--session-id <id>] [--workspace /path]`,
1990
1935
  injectedApi,
1991
1936
  async (api, sessionId, taskId) => {
1992
1937
  try {
@@ -2059,14 +2004,15 @@ async function handleSkip(subArgs, api) {
2059
2004
  async function handleAddTask(subArgs, injectedApi) {
2060
2005
  const title = flag(subArgs, "--title");
2061
2006
  if (!title) {
2062
- console.error('Usage: ordewell add-task --title "..." [--description "..."] [--prompt "..."] [--type ai|user] [--depends-on <task-id> ...] [--session-id <id>]');
2007
+ console.error('Usage: ordewell add-task --title "..." [--description "..."] [--prompt "..."] [--type ai|user] [--depends-on <task-id> ...] [--session-id <id>] [--workspace /path]');
2063
2008
  process.exit(1);
2064
2009
  }
2065
2010
  let sessionId = flag(subArgs, "--session-id");
2066
2011
  if (!sessionId) {
2067
- const last = readLastSession();
2012
+ const workspace = flag(subArgs, "--workspace") || process.cwd();
2013
+ const last = readLastSession(workspace);
2068
2014
  if (!last) {
2069
- console.error("No session specified. Use --session-id <id> or run `ordewell plan` first.");
2015
+ console.error(`No session specified. Use --session-id <id> or run \`ordewell plan\` in ${workspace} first.`);
2070
2016
  process.exit(1);
2071
2017
  }
2072
2018
  sessionId = last.sessionId;
@@ -2096,7 +2042,7 @@ async function handleAddTask(subArgs, injectedApi) {
2096
2042
  async function handleRemoveTask(subArgs, api) {
2097
2043
  await withResolvedTask(
2098
2044
  subArgs,
2099
- "Usage: ordewell remove-task <task-id-or-order> [--session-id <id>]",
2045
+ "Usage: ordewell remove-task <task-id-or-order> [--session-id <id>] [--workspace /path]",
2100
2046
  api,
2101
2047
  async (client, sessionId, taskId) => {
2102
2048
  try {
@@ -2127,7 +2073,7 @@ async function handleSessions(subArgs, injectedApi) {
2127
2073
  console.log(`No sessions found in ${workspace}.`);
2128
2074
  return;
2129
2075
  }
2130
- const last = readLastSession();
2076
+ const last = readLastSession(workspace);
2131
2077
  console.log(`${sessions.length} session(s) in ${workspace}:
2132
2078
  `);
2133
2079
  for (const s of sessions) {
@@ -2177,7 +2123,7 @@ async function handleSessions(subArgs, injectedApi) {
2177
2123
  }
2178
2124
 
2179
2125
  // src/commands/planner.ts
2180
- var import_core7 = require("@ordewell/core");
2126
+ var import_core11 = require("@ordewell/core");
2181
2127
 
2182
2128
  // src/plannerModelSwitch.ts
2183
2129
  function describePlannerSwitch(settings, provider) {
@@ -2196,7 +2142,7 @@ function rememberedModelFor(settings, provider) {
2196
2142
  }
2197
2143
 
2198
2144
  // src/commands/planner.ts
2199
- var KNOWN_PROVIDERS = Object.keys(import_core7.ALL_PROVIDERS);
2145
+ var KNOWN_PROVIDERS = Object.keys(import_core11.ALL_PROVIDERS);
2200
2146
  async function handlePlanner(subArgs, injectedApi) {
2201
2147
  const api = await connect(subArgs, injectedApi);
2202
2148
  const [wanted] = positionals(subArgs);
@@ -2211,13 +2157,13 @@ async function handlePlanner(subArgs, injectedApi) {
2211
2157
  const settings = await persistEnv(api, { AI_PROVIDER: provider });
2212
2158
  const recall2 = describePlannerSwitch(settings, provider);
2213
2159
  writeEnv({ ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
2214
- const meta = import_core7.ALL_PROVIDERS[provider];
2160
+ const meta = import_core11.ALL_PROVIDERS[provider];
2215
2161
  const catalog = await fetchCatalog(api);
2216
2162
  console.log(
2217
- (0, import_core7.isCliProvider)(provider) ? `Planning with ${meta.label} \u2014 no API key needed, it uses that agent's own subscription.` : `Planner set to ${meta.label}.`
2163
+ (0, import_core11.isCliProvider)(provider) ? `Planning with ${meta.label} \u2014 no API key needed, it uses that agent's own subscription.` : `Planner set to ${meta.label}.`
2218
2164
  );
2219
2165
  console.log(plannerModelLine(recall2));
2220
- if (!(0, import_core7.isCliProvider)(provider) && !catalog.providers.includes(provider)) {
2166
+ if (!(0, import_core11.isCliProvider)(provider) && !catalog.providers.includes(provider)) {
2221
2167
  console.log(` No ${meta.apiKeyEnvVar} configured yet \u2014 set one with \`ordewell key set ${provider} <key>\`.`);
2222
2168
  }
2223
2169
  }
@@ -2235,15 +2181,15 @@ async function showPlanners(api) {
2235
2181
  const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
2236
2182
  const current = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
2237
2183
  console.log("\nCoding agents (no API key \u2014 they use their own subscription)");
2238
- for (const id of import_core7.CLI_PROVIDERS) {
2239
- const runner = (0, import_core7.runnerForProvider)(id);
2184
+ for (const id of import_core11.CLI_PROVIDERS) {
2185
+ const runner = (0, import_core11.runnerForProvider)(id);
2240
2186
  const discovered = runner ? catalog.models.some((m) => m.runners?.includes(runner)) : false;
2241
- console.log(` ${marker(id === current)} ${id.padEnd(14)} ${import_core7.ALL_PROVIDERS[id].label}${discovered ? "" : " (not detected)"}`);
2187
+ console.log(` ${marker(id === current)} ${id.padEnd(14)} ${import_core11.ALL_PROVIDERS[id].label}${discovered ? "" : " (not detected)"}`);
2242
2188
  }
2243
2189
  console.log("\nAPI providers");
2244
- for (const id of import_core7.PROVIDER_PRIORITY) {
2190
+ for (const id of import_core11.PROVIDER_PRIORITY) {
2245
2191
  const configured = catalog.providers.includes(id);
2246
- const meta = import_core7.ALL_PROVIDERS[id];
2192
+ const meta = import_core11.ALL_PROVIDERS[id];
2247
2193
  console.log(` ${marker(id === current)} ${id.padEnd(14)} ${meta.label}${configured ? "" : ` (no ${meta.apiKeyEnvVar})`}`);
2248
2194
  }
2249
2195
  console.log(`
@@ -2260,7 +2206,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
2260
2206
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
2261
2207
  const modelId = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
2262
2208
  const currentEffort = typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : "";
2263
- const runner = (0, import_core7.runnerForProvider)(provider);
2209
+ const runner = (0, import_core11.runnerForProvider)(provider);
2264
2210
  if (!runner) {
2265
2211
  fail(
2266
2212
  "Thinking effort applies to a coding-agent planner.",
@@ -2300,7 +2246,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
2300
2246
  }
2301
2247
 
2302
2248
  // src/commands/key.ts
2303
- var import_core8 = require("@ordewell/core");
2249
+ var import_core12 = require("@ordewell/core");
2304
2250
  var USAGE4 = "Usage: ordewell key [list | set <provider> <key>]";
2305
2251
  async function handleKey(subArgs, injectedApi) {
2306
2252
  const [action, provider, ...keyParts] = positionals(subArgs);
@@ -2311,8 +2257,8 @@ async function handleKey(subArgs, injectedApi) {
2311
2257
  if (action !== "set") {
2312
2258
  const catalog = await fetchCatalog(api);
2313
2259
  console.log("\nAPI provider keys\n");
2314
- for (const id2 of import_core8.PROVIDER_PRIORITY) {
2315
- const meta2 = import_core8.ALL_PROVIDERS[id2];
2260
+ for (const id2 of import_core12.PROVIDER_PRIORITY) {
2261
+ const meta2 = import_core12.ALL_PROVIDERS[id2];
2316
2262
  const configured = catalog.providers.includes(id2);
2317
2263
  console.log(` ${configured ? "\u2713" : " "} ${id2.padEnd(14)} ${meta2.apiKeyEnvVar.padEnd(28)} ${meta2.label}`);
2318
2264
  }
@@ -2322,7 +2268,7 @@ async function handleKey(subArgs, injectedApi) {
2322
2268
  }
2323
2269
  if (!provider) fail(USAGE4);
2324
2270
  const id = provider.toLowerCase();
2325
- const meta = import_core8.ALL_PROVIDERS[id];
2271
+ const meta = import_core12.ALL_PROVIDERS[id];
2326
2272
  if (!meta) {
2327
2273
  fail(`Unknown provider: ${provider}`, "Run `ordewell key` to see the list.");
2328
2274
  }
@@ -2402,7 +2348,7 @@ async function handleApprove(subArgs, injectedApi) {
2402
2348
  // src/tui/terminalLauncher.ts
2403
2349
  var import_child_process3 = require("child_process");
2404
2350
  var import_util = require("util");
2405
- var import_core9 = require("@ordewell/core");
2351
+ var import_core13 = require("@ordewell/core");
2406
2352
  var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
2407
2353
  var defaultExecFile = async (command, args) => {
2408
2354
  const { stdout, stderr } = await execFileAsync(command, args);
@@ -2422,16 +2368,16 @@ var defaultWhich = (bin) => {
2422
2368
  async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
2423
2369
  const platform = deps.platform ?? process.platform;
2424
2370
  const env = deps.env ?? process.env;
2425
- const hasTmuxImpl = deps.hasTmuxImpl ?? import_core9.hasTmux;
2371
+ const hasTmuxImpl = deps.hasTmuxImpl ?? import_core13.hasTmux;
2426
2372
  const execFileImpl = deps.execFileImpl ?? defaultExecFile;
2427
2373
  const spawnImpl = deps.spawnImpl ?? defaultSpawn;
2428
2374
  const which = deps.which ?? defaultWhich;
2429
2375
  if (!hasTmuxImpl()) {
2430
2376
  return { ok: false, message: "tmux is not installed \u2014 install it to get a real terminal per task." };
2431
2377
  }
2432
- const session = (0, import_core9.tmuxSessionName)(port);
2433
- const socket = (0, import_core9.tmuxSocketName)(port);
2434
- const base = (0, import_core9.tmuxWindowName)(taskId, planSessionId);
2378
+ const session = (0, import_core13.tmuxSessionName)(port);
2379
+ const socket = (0, import_core13.tmuxSocketName)(port);
2380
+ const base = (0, import_core13.tmuxWindowName)(taskId, planSessionId);
2435
2381
  let windows;
2436
2382
  try {
2437
2383
  const { stdout } = await execFileImpl("tmux", ["-L", socket, "list-windows", "-t", session, "-F", "#{window_name}"]);
@@ -2725,13 +2671,25 @@ Dependencies \xB7 #${task.order} ${task.title}
2725
2671
  }
2726
2672
 
2727
2673
  // src/tui/index.ts
2728
- var import_core13 = require("@ordewell/core");
2674
+ var import_core17 = require("@ordewell/core");
2729
2675
 
2730
2676
  // src/tui/ansi.ts
2731
2677
  var import_string_width = __toESM(require("string-width"));
2732
- var ANSI = /\x1b\[[0-9;]*m/g;
2678
+ var ESCAPE = /\x1b(?:\][\s\S]*?(?:\x07|\x1b\\|$)|[P^_][\s\S]*?(?:\x1b\\|$)|\[[0-?]*[ -/]*[@-~]?|[ -/]*[0-~]?)/g;
2679
+ var ESCAPE_SPLIT = new RegExp(`(${ESCAPE.source})`);
2680
+ var CONTROL = /[\x00-\x08\x0b-\x1f\x7f-\x9f]/g;
2681
+ var CONTROL_IN_ROW = /[\x00-\x1f\x7f-\x9f]/g;
2682
+ var HAS_CONTROL = /[\x00-\x1f\x7f-\x9f]/;
2683
+ var PAINT_ESCAPE = /^\x1b\[[0-9;]*[mGK]$/;
2733
2684
  function stripAnsi(text) {
2734
- return text.replace(ANSI, "");
2685
+ return text.replace(ESCAPE, "");
2686
+ }
2687
+ function sanitize(text) {
2688
+ return text.replace(ESCAPE, "").replace(/\r\n?/g, "\n").replace(/\t/g, " ").replace(CONTROL, "");
2689
+ }
2690
+ function paintOnly(line) {
2691
+ if (!HAS_CONTROL.test(line)) return line;
2692
+ return line.split(ESCAPE_SPLIT).map((piece) => piece.startsWith("\x1B") ? PAINT_ESCAPE.test(piece) ? piece : "" : piece.replace(CONTROL_IN_ROW, "")).join("");
2735
2693
  }
2736
2694
  function width(text) {
2737
2695
  return (0, import_string_width.default)(stripAnsi(text));
@@ -2749,9 +2707,6 @@ function truncate(text, max) {
2749
2707
  }
2750
2708
  return out + "\u2026";
2751
2709
  }
2752
- function stripTabs(text) {
2753
- return text.replace(/\t/g, " ");
2754
- }
2755
2710
  function pad(text, target) {
2756
2711
  const current = width(text);
2757
2712
  if (current > target) return truncate(text, target);
@@ -3021,12 +2976,8 @@ var SLASH_COMMANDS = [
3021
2976
  { name: "auto", usage: "/auto [on|off]", description: "Autonomous permission mode for new plans", category: "models" },
3022
2977
  { name: "refresh", usage: "/refresh", description: "Re-discover runners and model catalogs", category: "models" },
3023
2978
  // Skills
3024
- { name: "grill-me", usage: "/grill-me [on|off]", description: "Grill-Me challenge mode \u2014 the planner interrogates the goal", category: "skills" },
3025
2979
  { name: "tdd", usage: "/tdd [on|off]", description: "Test-Driven Development mode", category: "skills" },
3026
- { name: "prd", usage: "/prd [on|off]", description: "PRD mode \u2014 write a PRD before the plan", category: "skills" },
3027
- { name: "review", usage: "/review [on|off]", description: "Review mode \u2014 final opinion-review task with human sign-off", category: "skills" },
3028
2980
  { name: "verify", usage: "/verify [on|off]", description: "Verification mode \u2014 final evidence-based task that runs the suite", category: "skills" },
3029
- { name: "research-subagents", usage: "/research-subagents [on|off]", description: "Parallel read-only research subagents during planning", category: "skills" },
3030
2981
  // Sessions
3031
2982
  { name: "sessions", usage: "/sessions", description: "List saved sessions", category: "session" },
3032
2983
  { name: "new", usage: "/new", description: "Start a new session", category: "session" },
@@ -3039,6 +2990,23 @@ var SLASH_COMMANDS = [
3039
2990
  { name: "quit", usage: "/quit", description: "Exit the TUI", category: "system" }
3040
2991
  ];
3041
2992
  var BY_NAME = new Map(SLASH_COMMANDS.map((c) => [c.name, c]));
2993
+ var skillCommands = [];
2994
+ var skillByName = /* @__PURE__ */ new Map();
2995
+ function registerSkillCommands(skills) {
2996
+ const map = /* @__PURE__ */ new Map();
2997
+ for (const skill of skills) {
2998
+ if (BY_NAME.has(skill.name)) continue;
2999
+ map.set(skill.name, {
3000
+ name: skill.name,
3001
+ usage: `/${skill.name}`,
3002
+ description: skill.description,
3003
+ category: "skills",
3004
+ source: "skill"
3005
+ });
3006
+ }
3007
+ skillByName = map;
3008
+ skillCommands = [...map.values()];
3009
+ }
3042
3010
  function parseSlash(text) {
3043
3011
  const trimmed = text.trim();
3044
3012
  if (!trimmed.startsWith("/")) return null;
@@ -3047,14 +3015,15 @@ function parseSlash(text) {
3047
3015
  return { name: parts[0].toLowerCase(), args: parts.slice(1) };
3048
3016
  }
3049
3017
  function findCommand(name) {
3050
- return BY_NAME.get(name.replace(/^\//, "").toLowerCase());
3018
+ const key = name.replace(/^\//, "").toLowerCase();
3019
+ return BY_NAME.get(key) ?? skillByName.get(key);
3051
3020
  }
3052
3021
  function completions(text) {
3053
3022
  if (!text.startsWith("/")) return [];
3054
3023
  const typed = text.slice(1);
3055
3024
  if (/\s/.test(typed)) return [];
3056
3025
  const prefix = typed.toLowerCase();
3057
- return SLASH_COMMANDS.filter((c) => c.name.startsWith(prefix));
3026
+ return [...SLASH_COMMANDS, ...skillCommands].filter((c) => c.name.startsWith(prefix));
3058
3027
  }
3059
3028
 
3060
3029
  // src/tui/markdown.ts
@@ -3199,9 +3168,9 @@ function chatEditorRoom(cols, active) {
3199
3168
  }
3200
3169
 
3201
3170
  // src/tui/state.ts
3202
- var SKILL_IDS = ["grill-me", "tdd", "prd", "review", "verify", "research-subagents"];
3171
+ var SKILL_IDS = ["tdd", "verify"];
3203
3172
  function noSkills() {
3204
- return { "grill-me": false, tdd: false, prd: false, review: false, verify: false, "research-subagents": false };
3173
+ return { tdd: false, verify: false };
3205
3174
  }
3206
3175
  function visibleItems(picker2) {
3207
3176
  const filter = picker2.filter.trim().toLowerCase();
@@ -3212,6 +3181,21 @@ function visibleItems(picker2) {
3212
3181
  }
3213
3182
  var isTaskRunning = (task) => task.status === "in_progress" || task.status === "running";
3214
3183
  var anyTaskRunning = (state) => state.tasks.some(isTaskRunning);
3184
+ function planRows(state) {
3185
+ const rows = [];
3186
+ for (const task of state.tasks) {
3187
+ rows.push({ task, parent: null });
3188
+ const subtasks = task.subtasks ?? [];
3189
+ const open = state.expandedTaskId === task.id || subtasks.some((s) => s.id === state.expandedTaskId);
3190
+ if (open) {
3191
+ for (const subtask of subtasks) rows.push({ task: subtask, parent: task });
3192
+ }
3193
+ }
3194
+ return rows;
3195
+ }
3196
+ function selectedPlanRow(state) {
3197
+ return planRows(state)[state.selectedTask] ?? null;
3198
+ }
3215
3199
  function initialState(overrides = {}) {
3216
3200
  return {
3217
3201
  editor: emptyEditor(),
@@ -3257,7 +3241,7 @@ function initialState(overrides = {}) {
3257
3241
  }
3258
3242
 
3259
3243
  // src/tui/layout.ts
3260
- var import_core10 = require("@ordewell/core");
3244
+ var import_core14 = require("@ordewell/core");
3261
3245
  var CHROME_ROWS = 4;
3262
3246
  function chatInputWrap(state) {
3263
3247
  const active = state.focus === "chat" && !state.overlay;
@@ -3265,7 +3249,7 @@ function chatInputWrap(state) {
3265
3249
  return wrapLines(state.editor.text, chatEditorRoom(state.cols, true));
3266
3250
  }
3267
3251
  function footerHints(state) {
3268
- const markHint = state.tasks[state.selectedTask]?.status === "completed" ? "m undone" : "m done";
3252
+ const markHint = selectedPlanRow(state)?.task.status === "completed" ? "m undone" : "m done";
3269
3253
  const planning = state.status === "planning" || state.status === "researching";
3270
3254
  const escHint = planning ? "esc stop planning" : null;
3271
3255
  if (state.focus === "plan") {
@@ -3393,9 +3377,9 @@ function chatScrollMax(state) {
3393
3377
  }
3394
3378
  function plannerSetup(state) {
3395
3379
  const provider = state.plannerProvider;
3396
- const registration = import_core10.ALL_PROVIDERS[provider];
3380
+ const registration = import_core14.ALL_PROVIDERS[provider];
3397
3381
  if (!registration) return { label: "not set", ready: false };
3398
- const runner = (0, import_core10.runnerForProvider)(provider);
3382
+ const runner = (0, import_core14.runnerForProvider)(provider);
3399
3383
  const ready = runner ? state.runners.some((r) => r.id === runner) : state.configuredProviders.includes(provider);
3400
3384
  return { label: registration.label, ready };
3401
3385
  }
@@ -3502,16 +3486,17 @@ var STATUS_ICON = {
3502
3486
  approved: "\xB7",
3503
3487
  pending: "\xB7"
3504
3488
  };
3489
+ var IDLE_ICON = "~";
3505
3490
  function planLayout(state, rows, cols) {
3506
3491
  const done = state.tasks.filter((t) => t.status === "completed").length;
3507
3492
  const lines = [style.bold(`Plan ${done}/${state.tasks.length}`), ""];
3508
3493
  let selectedLine = 2;
3509
3494
  let editorLine;
3510
- state.tasks.forEach((task, i) => {
3495
+ planRows(state).forEach((row, i) => {
3511
3496
  const taskStart = lines.length;
3512
3497
  if (i === state.selectedTask) selectedLine = taskStart;
3513
- const renderedTask = taskLines(state, task, i, cols);
3514
- if (task.id === state.expandedTaskId && renderedTask.editorLine !== void 0) {
3498
+ const renderedTask = taskLines(state, row, i, cols);
3499
+ if (row.task.id === state.expandedTaskId && renderedTask.editorLine !== void 0) {
3515
3500
  editorLine = taskStart + renderedTask.editorLine;
3516
3501
  }
3517
3502
  lines.push(...renderedTask.lines);
@@ -3530,16 +3515,20 @@ function planScrollExtent(state) {
3530
3515
  const { followOffset, maxScroll } = planLayout(state, bodyRows(state), cols);
3531
3516
  return { followOffset, maxScroll };
3532
3517
  }
3533
- function taskLines(state, task, index, cols) {
3518
+ function taskLines(state, row, index, cols) {
3519
+ const { task, parent } = row;
3534
3520
  const selected = state.focus === "plan" && index === state.selectedTask;
3535
3521
  const expanded = state.expandedTaskId === task.id;
3536
3522
  const running = isTaskRunning(task);
3537
- const paint = STATUS_MARK[task.status] ?? ((t) => t);
3538
- const rawIcon = running ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[task.status] ?? "\xB7";
3523
+ const idle = running && task.status === "in_progress" && task.idleSince != null;
3524
+ const paint = idle ? style.cyan : STATUS_MARK[task.status] ?? ((t) => t);
3525
+ const rawIcon = idle ? IDLE_ICON : running ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[task.status] ?? "\xB7";
3539
3526
  const icon = paint(rawIcon);
3540
3527
  const kind = running ? style.yellow("RUN") : task.type === "user" ? style.yellow("MAN") : style.grey(" AI");
3541
3528
  const caret = selected ? style.cyan("\u276F") : " ";
3542
- const head = `${caret} ${icon} ${String(task.order).padStart(2)} ${kind} `;
3529
+ const orderLabel = parent ? (0, import_core14.taskOrderLabel)(task, parent) : String(task.order).padStart(2);
3530
+ const indent = parent ? " " : "";
3531
+ const head = `${indent}${caret} ${icon} ${orderLabel} ${kind} `;
3543
3532
  const titleRoom = Math.max(1, cols - width(head));
3544
3533
  const wrappedTitle = wrap(task.title, titleRoom);
3545
3534
  const shownTitle = expanded ? wrappedTitle : wrappedTitle.slice(0, 2);
@@ -3557,50 +3546,51 @@ function taskLines(state, task, index, cols) {
3557
3546
  const modeInfo = task.type === "ai" ? modesForTask(state.modesByRunner, task).find((m) => m.id === task.taskMode) : void 0;
3558
3547
  const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${modeInfo?.autonomous ? style.yellow(" \u26A1") : ""}` : "";
3559
3548
  const runner = task.assignedRunner ?? "";
3549
+ const bodyPad = parent ? " " : " ";
3560
3550
  const meta = [running ? "working" : "", runner, model].filter(Boolean).join(" \xB7 ");
3561
- if (meta) lines.push(style.grey(truncate(` ${meta}`, cols)));
3562
- if (effort || mode) lines.push(style.grey(truncate(` ${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
3551
+ if (meta) lines.push(style.grey(truncate(`${bodyPad}${meta}`, cols)));
3552
+ if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
3563
3553
  let editorLine;
3564
3554
  if (expanded) {
3565
- lines.push(style.grey(` ${task.status.replace(/_/g, " ")}`));
3555
+ lines.push(style.grey(`${bodyPad}${task.status.replace(/_/g, " ")}`));
3566
3556
  if (modeInfo?.autonomous) {
3567
- lines.push(...taskText("Autonomy", "Runs without permission prompts. Toggle with /auto.", cols));
3557
+ lines.push(...taskText("Autonomy", "Runs without permission prompts. Toggle with /auto.", cols, bodyPad));
3568
3558
  }
3569
3559
  const editableSeed = task.prompt ?? task.description ?? task.title;
3570
3560
  if (task.description && task.description !== task.title && task.description !== editableSeed) {
3571
- lines.push(...taskText("Description", task.description, cols));
3561
+ lines.push(...taskText("Description", task.description, cols, bodyPad));
3572
3562
  }
3573
3563
  if (state.taskEditor) {
3574
- const room = paneTextRoom(cols);
3564
+ const room = Math.max(1, cols - width(bodyPad));
3575
3565
  const wrapped = wrapLines(state.taskEditor.text, room);
3576
3566
  const cp = cursorInLines(wrapped, state.taskEditor.cursor, state.taskEditor.text.length);
3577
3567
  editorLine = lines.length + 1 + cp.line;
3578
- lines.push(...taskPromptLines(wrapped, cp));
3568
+ lines.push(...taskPromptLines(wrapped, cp, bodyPad));
3579
3569
  }
3580
3570
  if (task.dependencies.length > 0) {
3581
3571
  const orderById = new Map(state.tasks.map((candidate) => [candidate.id, candidate.order]));
3582
3572
  const dependencies = task.dependencies.map((id) => orderById.has(id) ? `#${orderById.get(id)}` : id).join(", ");
3583
- lines.push(...taskText("Depends on", dependencies, cols));
3573
+ lines.push(...taskText("Depends on", dependencies, cols, bodyPad));
3584
3574
  }
3585
- lines.push(style.grey(" enter save \xB7 esc cancel"));
3586
- lines.push(style.grey(truncate(" then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps", cols)));
3575
+ lines.push(style.grey(`${bodyPad}enter save \xB7 esc cancel`));
3576
+ lines.push(style.grey(truncate(`${bodyPad}then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps`, cols)));
3587
3577
  }
3588
3578
  return { lines, editorLine };
3589
3579
  }
3590
- function taskText(label, text, cols) {
3591
- const room = paneTextRoom(cols);
3580
+ function taskText(label, text, cols, pad2 = " ") {
3581
+ const room = Math.max(1, cols - width(pad2));
3592
3582
  return [
3593
- style.grey(` ${label}`),
3594
- ...wrap(text, room).map((line) => ` ${line}`)
3583
+ style.grey(`${pad2}${label}`),
3584
+ ...wrap(text, room).map((line) => `${pad2}${line}`)
3595
3585
  ];
3596
3586
  }
3597
- function taskPromptLines(wrapped, cursor) {
3587
+ function taskPromptLines(wrapped, cursor, pad2 = " ") {
3598
3588
  return [
3599
- style.grey(" Prompt"),
3589
+ style.grey(`${pad2}Prompt`),
3600
3590
  ...wrapped.map(({ line }, i) => {
3601
- if (i !== cursor.line) return ` ${line}`;
3591
+ if (i !== cursor.line) return `${pad2}${line}`;
3602
3592
  const col = cursor.col;
3603
- return ` ${line.slice(0, col)}${style.inverse(line.slice(col, col + 1) || " ")}${line.slice(col + 1)}`;
3593
+ return `${pad2}${line.slice(0, col)}${style.inverse(line.slice(col, col + 1) || " ")}${line.slice(col + 1)}`;
3604
3594
  })
3605
3595
  ];
3606
3596
  }
@@ -3676,13 +3666,16 @@ function cells(line) {
3676
3666
  }
3677
3667
  var INVERSE_ON = "\x1B[7m";
3678
3668
  var INVERSE_OFF = "\x1B[27m";
3679
- var SGR_SPLIT = /(\x1b\[[0-9;]*m)/;
3669
+ var ESCAPE_SPLIT2 = /(\x1b\[[0-9;]*[A-Za-z])/;
3670
+ var CURSOR_COLUMN = /^\x1b\[([0-9]*)G$/;
3680
3671
  function invertCells(line, from, to) {
3681
3672
  let out = "";
3682
3673
  let col = 1;
3683
3674
  let inside = false;
3684
- for (const piece of line.split(SGR_SPLIT)) {
3685
- if (SGR_SPLIT.test(piece)) {
3675
+ for (const piece of line.split(ESCAPE_SPLIT2)) {
3676
+ if (ESCAPE_SPLIT2.test(piece)) {
3677
+ const jump = CURSOR_COLUMN.exec(piece);
3678
+ if (jump) col = Number(jump[1] || "1");
3686
3679
  out += piece + (inside ? INVERSE_ON : "");
3687
3680
  continue;
3688
3681
  }
@@ -3750,15 +3743,29 @@ function renderStatus(state, cols) {
3750
3743
  const tail = thinking.length > room ? `\u2026${thinking.slice(-(room - 1)).trimStart()}` : thinking;
3751
3744
  return truncate(`${head}${style.grey(` \xB7 ${tail}`)}`, cols);
3752
3745
  }
3746
+ var LEADING_TOKEN = /^\/\S*/;
3747
+ function skillTokenLength(text) {
3748
+ const match = LEADING_TOKEN.exec(text);
3749
+ if (!match) return 0;
3750
+ return findCommand(match[0])?.source === "skill" ? match[0].length : 0;
3751
+ }
3752
+ function paintRange(text, base, tokenEnd) {
3753
+ const localEnd = Math.max(0, Math.min(text.length, tokenEnd - base));
3754
+ if (localEnd <= 0) return text;
3755
+ if (localEnd >= text.length) return style.cyan(text);
3756
+ return style.cyan(text.slice(0, localEnd)) + text.slice(localEnd);
3757
+ }
3753
3758
  function renderInput(state, cols) {
3754
3759
  const prompt = state.focus === "plan" ? style.grey("\u276F ") : style.cyan("\u276F ");
3755
3760
  const lines = chatInputWrap(state);
3756
3761
  const room = chatEditorRoom(cols, lines !== null);
3757
3762
  const { text, cursor } = state.editor;
3758
3763
  const continuation = " ".repeat(width(prompt));
3764
+ const tokenEnd = skillTokenLength(text);
3759
3765
  if (!lines) {
3760
3766
  const visible = text.replace(/\n/g, "\xB6").replace(/\t/g, " ");
3761
- return [prompt + visible + suggest(state, room - width(visible))];
3767
+ const painted = tokenEnd > 0 ? paintRange(visible, 0, tokenEnd) : visible;
3768
+ return [prompt + painted + suggest(state, room - width(visible))];
3762
3769
  }
3763
3770
  const { line: lineIndex, col } = cursorInLines(lines, cursor, text.length);
3764
3771
  const rawLines = lines.map((l) => l.line);
@@ -3766,7 +3773,8 @@ function renderInput(state, cols) {
3766
3773
  const prefix = i === 0 ? prompt : continuation;
3767
3774
  const line = raw.replace(/\t/g, " ");
3768
3775
  const isCursorLine = i === lineIndex;
3769
- const rendered = isCursorLine ? line.slice(0, col) + style.inverse(line.slice(col, col + 1) || " ") + line.slice(col + 1) : line;
3776
+ const lineTokenEnd = i === 0 ? tokenEnd : 0;
3777
+ const rendered = isCursorLine ? paintRange(line.slice(0, col), 0, lineTokenEnd) + style.inverse(line.slice(col, col + 1) || " ") + paintRange(line.slice(col + 1), col + 1, lineTokenEnd) : paintRange(line, 0, lineTokenEnd);
3770
3778
  const used = width(line) + (isCursorLine && col >= line.length ? 1 : 0);
3771
3779
  const hint = i === rawLines.length - 1 ? suggest(state, room - used) : "";
3772
3780
  return prefix + rendered + hint;
@@ -3782,15 +3790,18 @@ function suggest(state, room) {
3782
3790
  function renderFooter(state, cols) {
3783
3791
  return packHints(footerHints(state), cols).map((line) => truncate(style.grey(line), cols));
3784
3792
  }
3793
+ var ERASE_TO_END = "\x1B[K";
3794
+ var atColumn = (col) => `\x1B[${col}G`;
3785
3795
  function renderBody(state, rows, cols) {
3786
3796
  const planCols = planPaneWidth(state);
3787
3797
  if (planCols === 0) return renderChat(state, rows, cols);
3788
3798
  const chatCols = chatPaneWidth(state);
3789
3799
  const chat = renderChat(state, rows, chatCols);
3790
3800
  const plan = renderPlan(state, rows, planCols);
3801
+ const divider = `${ERASE_TO_END}${atColumn(chatCols + 1)}${style.grey("\u2502")}`;
3791
3802
  return Array.from(
3792
3803
  { length: rows },
3793
- (_, i) => `${pad(chat[i] ?? "", chatCols)}${style.grey("\u2502")}${pad(plan[i] ?? "", planCols)}`
3804
+ (_, i) => `${pad(chat[i] ?? "", chatCols)}${divider}${pad(plan[i] ?? "", planCols)}`
3794
3805
  );
3795
3806
  }
3796
3807
  function renderChat(state, rows, cols) {
@@ -3893,21 +3904,27 @@ function frame(title, body, rows, cols) {
3893
3904
  }
3894
3905
 
3895
3906
  // src/tui/reducer.ts
3896
- var import_core11 = require("@ordewell/core");
3907
+ var import_core15 = require("@ordewell/core");
3897
3908
  var import_plan_utils3 = require("@ordewell/core/plan-utils");
3898
- var KNOWN_PROVIDERS2 = Object.keys(import_core11.ALL_PROVIDERS);
3909
+ var KNOWN_PROVIDERS2 = Object.keys(import_core15.ALL_PROVIDERS);
3899
3910
  var DEFAULT_EFFORT = "__runner_default__";
3900
3911
  function step(state, effects = []) {
3901
3912
  return { state, effects };
3902
3913
  }
3903
- function say(state, role, content, research) {
3904
- const message = { role, content: stripTabs(content), timestamp: (/* @__PURE__ */ new Date()).toISOString(), ...research ? { research } : {} };
3914
+ function say(state, role, content, research, extra) {
3915
+ const message = {
3916
+ role,
3917
+ content: sanitize(content),
3918
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3919
+ ...research ? { research } : {},
3920
+ ...extra
3921
+ };
3905
3922
  return { ...state, messages: [...state.messages, message], scroll: 0 };
3906
3923
  }
3907
3924
  function restoredMessages(history) {
3908
3925
  return history.map((entry) => ({
3909
3926
  role: entry.kind === "plan_generated" ? "system" : entry.kind === "system" ? "system" : entry.role,
3910
- content: stripTabs(entry.kind === "plan_generated" ? "Plan generated." : entry.content),
3927
+ content: sanitize(entry.kind === "plan_generated" ? "Plan generated." : entry.content),
3911
3928
  timestamp: entry.timestamp
3912
3929
  }));
3913
3930
  }
@@ -3921,13 +3938,13 @@ function researchLabel(messages, summary) {
3921
3938
  return others > 0 ? `${summary} (+${others} more)` : summary;
3922
3939
  }
3923
3940
  function settleResearchStep(messages, action) {
3924
- const summary = stripTabs(action.summary);
3941
+ const summary = sanitize(action.summary);
3925
3942
  const index = findLastIndex(messages, (m) => isPendingResearch(m) && (action.toolCallId ? m.research?.toolCallId === action.toolCallId : m.content === summary));
3926
3943
  if (index < 0) return messages;
3927
3944
  const next = [...messages];
3928
3945
  next[index] = {
3929
3946
  ...next[index],
3930
- research: { ...next[index].research, outcome: action.outcome, result: stripTabs(action.result) }
3947
+ research: { ...next[index].research, outcome: action.outcome, result: sanitize(action.result) }
3931
3948
  };
3932
3949
  return next;
3933
3950
  }
@@ -3971,27 +3988,45 @@ function reduce(state, action) {
3971
3988
  case "planUpdated": {
3972
3989
  if (stale(state, action.sessionId)) return step(state);
3973
3990
  const tasks = normalizeTasks(action.plan);
3974
- return step({
3991
+ const expandedTaskId = tasks.some((task) => task.id === state.expandedTaskId) ? state.expandedTaskId : null;
3992
+ const next = {
3975
3993
  ...state,
3976
3994
  tasks,
3977
3995
  status: state.status === "planning" || state.status === "researching" ? "idle" : state.status,
3978
3996
  busyLabel: "",
3979
3997
  thinkingLine: "",
3980
- selectedTask: Math.min(state.selectedTask, Math.max(0, tasks.length - 1)),
3981
- expandedTaskId: tasks.some((task) => task.id === state.expandedTaskId) ? state.expandedTaskId : null,
3982
- taskEditor: tasks.some((task) => task.id === state.expandedTaskId) ? state.taskEditor : null
3983
- });
3998
+ expandedTaskId,
3999
+ taskEditor: expandedTaskId !== null ? state.taskEditor : null
4000
+ };
4001
+ return step({ ...next, selectedTask: Math.min(state.selectedTask, Math.max(0, planRows(next).length - 1)) });
3984
4002
  }
3985
4003
  case "plannerMessage": {
3986
4004
  if (stale(state, action.sessionId)) return step(state);
3987
4005
  const settled = { ...state, status: "idle", busyLabel: "", thinkingLine: "" };
3988
- const content = stripTabs(action.content);
4006
+ const content = sanitize(action.content);
4007
+ const last = state.messages.at(-1);
4008
+ if (last?.role === "assistant" && last.streaming) {
4009
+ const messages = [...state.messages];
4010
+ messages[messages.length - 1] = { ...last, content, streaming: void 0 };
4011
+ return step({ ...settled, messages });
4012
+ }
3989
4013
  if (alreadySpoken(state.messages, content)) return step(settled);
3990
4014
  return step(say(settled, "assistant", content));
3991
4015
  }
4016
+ case "plannerToken": {
4017
+ if (stale(state, action.sessionId)) return step(state);
4018
+ const text = sanitize(action.text);
4019
+ const last = state.messages.at(-1);
4020
+ if (last?.role === "assistant" && last.streaming) {
4021
+ const messages = [...state.messages];
4022
+ messages[messages.length - 1] = { ...last, content: last.content + text };
4023
+ return step({ ...state, messages });
4024
+ }
4025
+ return step(say(state, "assistant", text, void 0, { streaming: true }));
4026
+ }
3992
4027
  case "researchStep": {
3993
4028
  if (stale(state, action.sessionId)) return step(state);
3994
- const summary = stripTabs(action.summary);
4029
+ const summary = sanitize(action.summary);
3995
4030
  const spoken = say(state, "research", summary, { toolCallId: action.toolCallId });
3996
4031
  const status = state.status === "planning" ? "researching" : state.status;
3997
4032
  return step({ ...spoken, status, busyLabel: researchLabel(spoken.messages, summary) });
@@ -4010,7 +4045,7 @@ function reduce(state, action) {
4010
4045
  }
4011
4046
  case "plannerThinking": {
4012
4047
  if (stale(state, action.sessionId)) return step(state);
4013
- return step({ ...state, thinkingLine: (state.thinkingLine + action.text).slice(-THINKING_TAIL) });
4048
+ return step({ ...state, thinkingLine: (state.thinkingLine + sanitize(action.text)).slice(-THINKING_TAIL) });
4014
4049
  }
4015
4050
  case "taskStatus": {
4016
4051
  if (stale(state, action.sessionId) || !state.tasks.some((t) => t.id === action.taskId)) return step(state);
@@ -4025,10 +4060,12 @@ function reduce(state, action) {
4025
4060
  const updates = action.updates;
4026
4061
  let changed = false;
4027
4062
  const tasks = state.tasks.map((t) => {
4028
- const status = updates[t.id];
4029
- if (status !== void 0 && status !== t.status) {
4063
+ const update = updates[t.id];
4064
+ if (update === void 0) return t;
4065
+ const idleSince = update.idleSince ?? null;
4066
+ if (update.status !== t.status || idleSince !== (t.idleSince ?? null)) {
4030
4067
  changed = true;
4031
- return { ...t, status };
4068
+ return { ...t, status: update.status, idleSince };
4032
4069
  }
4033
4070
  return t;
4034
4071
  });
@@ -4099,6 +4136,8 @@ function reduce(state, action) {
4099
4136
  return step({ ...state, spinnerFrame: (state.spinnerFrame + 1) % 10 });
4100
4137
  }
4101
4138
  }
4139
+ var planText = (value, fallback = "") => sanitize(String(value ?? fallback));
4140
+ var planLabel = (value, fallback = "") => planText(value, fallback).replace(/\n+/g, " ");
4102
4141
  function normalizeTasks(plan) {
4103
4142
  const source = plan;
4104
4143
  const byId = /* @__PURE__ */ new Map();
@@ -4108,34 +4147,34 @@ function normalizeTasks(plan) {
4108
4147
  }
4109
4148
  }
4110
4149
  const raw = byId.size > 0 ? [...byId.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) : source?.tasks ?? [];
4111
- return raw.map((t, i) => ({
4112
- id: String(t.id ?? `task-${i + 1}`),
4113
- order: typeof t.order === "number" ? t.order : i + 1,
4114
- title: String(t.title ?? "Untitled task"),
4115
- description: String(t.description ?? t.title ?? ""),
4116
- prompt: typeof t.prompt === "string" ? t.prompt : void 0,
4150
+ return raw.map((t, i) => toTaskView2(t, i));
4151
+ }
4152
+ function toTaskView2(t, index) {
4153
+ return {
4154
+ id: String(t.id ?? `task-${index + 1}`),
4155
+ order: typeof t.order === "number" ? t.order : index + 1,
4156
+ title: planLabel(t.title, "Untitled task"),
4157
+ description: planText(t.description ?? t.title),
4158
+ prompt: typeof t.prompt === "string" ? sanitize(t.prompt) : void 0,
4117
4159
  type: t.type === "user" ? "user" : "ai",
4118
- status: String(t.status ?? "pending"),
4119
- dependencies: Array.isArray(t.dependencies) ? t.dependencies.map(String) : [],
4120
- assignedRunner: typeof t.assignedRunner === "string" ? t.assignedRunner : void 0,
4121
- taskMode: typeof t.taskMode === "string" ? t.taskMode : void 0,
4160
+ status: planLabel(t.status, "pending"),
4161
+ dependencies: Array.isArray(t.dependencies) ? t.dependencies.map((id) => planLabel(id)) : [],
4162
+ assignedRunner: typeof t.assignedRunner === "string" ? planLabel(t.assignedRunner) : void 0,
4163
+ taskMode: typeof t.taskMode === "string" ? planLabel(t.taskMode) : void 0,
4122
4164
  assignedModel: t.assignedModel && typeof t.assignedModel === "object" ? {
4123
- modelId: String(t.assignedModel.modelId ?? ""),
4124
- modelLabel: String(t.assignedModel.modelLabel ?? t.assignedModel.modelId ?? ""),
4125
- thinkingEffort: typeof t.assignedModel.thinkingEffort === "string" ? t.assignedModel.thinkingEffort : typeof t.thinkingEffort === "string" ? t.thinkingEffort : void 0,
4126
- availableVariants: Array.isArray(t.assignedModel.availableVariants) ? t.assignedModel.availableVariants.map(String) : void 0
4127
- } : void 0
4128
- }));
4165
+ modelId: planLabel(t.assignedModel.modelId),
4166
+ modelLabel: planLabel(t.assignedModel.modelLabel ?? t.assignedModel.modelId),
4167
+ thinkingEffort: typeof t.assignedModel.thinkingEffort === "string" ? planLabel(t.assignedModel.thinkingEffort) : typeof t.thinkingEffort === "string" ? planLabel(t.thinkingEffort) : void 0,
4168
+ availableVariants: Array.isArray(t.assignedModel.availableVariants) ? t.assignedModel.availableVariants.map((variant) => planLabel(variant)) : void 0
4169
+ } : void 0,
4170
+ subtasks: Array.isArray(t.subtasks) ? [...t.subtasks].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)).map((s, i) => toTaskView2(s, i)) : void 0
4171
+ };
4129
4172
  }
4130
4173
  var enabledFlag = (value) => typeof value?.enabled === "boolean" ? value.enabled : void 0;
4131
4174
  function applySettings(state, settings) {
4132
4175
  const sources = {
4133
- "grill-me": settings.grillMe,
4134
4176
  tdd: settings.tdd,
4135
- prd: settings.prd,
4136
- review: settings.review,
4137
- verify: settings.verification,
4138
- "research-subagents": settings.researchSubagents
4177
+ verify: settings.verification
4139
4178
  };
4140
4179
  const skills = { ...state.skills };
4141
4180
  for (const id of SKILL_IDS) {
@@ -4164,11 +4203,11 @@ function modelDetail(m) {
4164
4203
  function providerErrorHint(state) {
4165
4204
  const failed = Object.keys(state.providerErrors ?? {});
4166
4205
  if (failed.length === 0) return void 0;
4167
- const names = failed.map((p) => import_core11.ALL_PROVIDERS[p]?.label ?? p);
4206
+ const names = failed.map((p) => import_core15.ALL_PROVIDERS[p]?.label ?? p);
4168
4207
  return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")} \u2014 showing working providers only.`;
4169
4208
  }
4170
4209
  function plannerModelItems(state) {
4171
- const runner = (0, import_core11.runnerForProvider)(state.plannerProvider);
4210
+ const runner = (0, import_core15.runnerForProvider)(state.plannerProvider);
4172
4211
  if (!runner) return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m) }));
4173
4212
  const items = state.models.filter((m) => m.runners?.includes(runner)).map((m) => ({
4174
4213
  id: m.id,
@@ -4179,7 +4218,7 @@ function plannerModelItems(state) {
4179
4218
  return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
4180
4219
  }
4181
4220
  function plannerEffortItems(state) {
4182
- const runner = (0, import_core11.runnerForProvider)(state.plannerProvider);
4221
+ const runner = (0, import_core15.runnerForProvider)(state.plannerProvider);
4183
4222
  if (!runner) {
4184
4223
  return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
4185
4224
  }
@@ -4392,20 +4431,20 @@ function handlePlanKey(state, key) {
4392
4431
  if (editingTask && state.taskEditor) return handleTaskEditKey(state, editingTask, state.taskEditor, key);
4393
4432
  if (key.name === "escape") return step({ ...state, focus: "chat" });
4394
4433
  if (key.name === "up") {
4395
- return step({ ...state, selectedTask: Math.max(0, state.selectedTask - 1), expandedTaskId: null, planScroll: null });
4434
+ return step({ ...state, selectedTask: Math.max(0, state.selectedTask - 1), planScroll: null });
4396
4435
  }
4397
4436
  if (key.name === "down") {
4398
4437
  return step({
4399
4438
  ...state,
4400
- selectedTask: Math.min(state.tasks.length - 1, state.selectedTask + 1),
4401
- expandedTaskId: null,
4439
+ selectedTask: Math.min(planRows(state).length - 1, state.selectedTask + 1),
4402
4440
  planScroll: null
4403
4441
  });
4404
4442
  }
4405
4443
  const scroll = scrollDelta(key, state);
4406
4444
  if (scroll !== null) return scrollPlan(state, scroll);
4407
- const task = state.tasks[state.selectedTask];
4408
- if (!task) return step(state);
4445
+ const row = selectedPlanRow(state);
4446
+ if (!row) return step(state);
4447
+ const task = row.task;
4409
4448
  if (key.name === "enter" || key.name === "right") {
4410
4449
  const text = task.prompt ?? task.description ?? task.title;
4411
4450
  return step({
@@ -4655,7 +4694,7 @@ function submit(state) {
4655
4694
  if (!text) return step(state);
4656
4695
  const cleared = { ...state, editor: commit(state.editor) };
4657
4696
  const command = parseSlash(text);
4658
- if (command) return runCommand(cleared, command);
4697
+ if (command && findCommand(command.name)?.source !== "skill") return runCommand(cleared, command);
4659
4698
  const spoken = say(cleared, "user", text);
4660
4699
  const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
4661
4700
  return step({ ...spoken, status: "planning" }, [effect]);
@@ -4817,7 +4856,7 @@ function newSession(state) {
4817
4856
  }
4818
4857
  function setModel(state, args) {
4819
4858
  if (args[0] === "set" && args[1]) {
4820
- const runner = (0, import_core11.runnerForProvider)(state.plannerProvider);
4859
+ const runner = (0, import_core15.runnerForProvider)(state.plannerProvider);
4821
4860
  const known = runner ? modelsForRunner(state.models, runner) : [];
4822
4861
  if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
4823
4862
  return fail2(state, `${args[1]} was not discovered for ${runner}.`);
@@ -4839,21 +4878,21 @@ function setModel(state, args) {
4839
4878
  }
4840
4879
  function plannerItems(state) {
4841
4880
  const installed = new Set(state.runners.map((r) => r.id));
4842
- const agents = import_core11.CLI_PROVIDERS.map((id) => {
4843
- const runner = (0, import_core11.runnerForProvider)(id);
4881
+ const agents = import_core15.CLI_PROVIDERS.map((id) => {
4882
+ const runner = (0, import_core15.runnerForProvider)(id);
4844
4883
  const ready = installed.has(runner);
4845
4884
  return {
4846
4885
  id,
4847
- label: import_core11.ALL_PROVIDERS[id].label,
4886
+ label: import_core15.ALL_PROVIDERS[id].label,
4848
4887
  detail: ready ? "coding agent \xB7 no API key needed" : "CLI not installed or not on PATH",
4849
4888
  selected: state.plannerProvider === id,
4850
4889
  disabled: !ready
4851
4890
  };
4852
4891
  });
4853
- const vendors = import_core11.PROVIDER_PRIORITY.filter((id) => state.configuredProviders.includes(id)).map((id) => ({
4892
+ const vendors = import_core15.PROVIDER_PRIORITY.filter((id) => state.configuredProviders.includes(id)).map((id) => ({
4854
4893
  id,
4855
- label: import_core11.ALL_PROVIDERS[id].label,
4856
- detail: import_core11.ALL_PROVIDERS[id].apiKeyEnvVar,
4894
+ label: import_core15.ALL_PROVIDERS[id].label,
4895
+ detail: import_core15.ALL_PROVIDERS[id].apiKeyEnvVar,
4857
4896
  selected: state.plannerProvider === id
4858
4897
  }));
4859
4898
  return vendors.length > 0 ? [...agents, ...vendors] : [...agents, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
@@ -4911,16 +4950,16 @@ function setKey(state, args) {
4911
4950
  }
4912
4951
  return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
4913
4952
  }
4914
- const items = import_core11.PROVIDER_PRIORITY.map((id) => ({
4953
+ const items = import_core15.PROVIDER_PRIORITY.map((id) => ({
4915
4954
  id,
4916
- label: import_core11.ALL_PROVIDERS[id].label,
4917
- detail: import_core11.ALL_PROVIDERS[id].apiKeyEnvVar,
4955
+ label: import_core15.ALL_PROVIDERS[id].label,
4956
+ detail: import_core15.ALL_PROVIDERS[id].apiKeyEnvVar,
4918
4957
  selected: state.configuredProviders.includes(id)
4919
4958
  }));
4920
4959
  return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
4921
4960
  }
4922
4961
  function keyPrompt(provider) {
4923
- const meta = import_core11.ALL_PROVIDERS[provider];
4962
+ const meta = import_core15.ALL_PROVIDERS[provider];
4924
4963
  return {
4925
4964
  kind: "prompt",
4926
4965
  title: `${meta.label} API key`,
@@ -5278,7 +5317,7 @@ function createApp(deps) {
5278
5317
 
5279
5318
  // src/tui/effects.ts
5280
5319
  var import_child_process4 = require("child_process");
5281
- var import_core12 = require("@ordewell/core");
5320
+ var import_core16 = require("@ordewell/core");
5282
5321
  var import_plan_utils4 = require("@ordewell/core/plan-utils");
5283
5322
  var ConversationQueue = class {
5284
5323
  tail = Promise.resolve();
@@ -5445,7 +5484,7 @@ async function perform(effect, deps) {
5445
5484
  dispatch({ type: "notice", message: `Orchestrator model set to ${effect.modelId}.` });
5446
5485
  return;
5447
5486
  case "setPlanner": {
5448
- const meta = import_core12.ALL_PROVIDERS[effect.provider];
5487
+ const meta = import_core16.ALL_PROVIDERS[effect.provider];
5449
5488
  if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
5450
5489
  const env = { AI_PROVIDER: effect.provider };
5451
5490
  const settings = await api.updateSettings({ env });
@@ -5457,7 +5496,7 @@ async function perform(effect, deps) {
5457
5496
  });
5458
5497
  dispatch({
5459
5498
  type: "notice",
5460
- message: `${(0, import_core12.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
5499
+ message: `${(0, import_core16.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
5461
5500
  });
5462
5501
  await loadModels(deps);
5463
5502
  return;
@@ -5469,7 +5508,7 @@ async function perform(effect, deps) {
5469
5508
  dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
5470
5509
  return;
5471
5510
  case "setApiKey": {
5472
- const meta = import_core12.ALL_PROVIDERS[effect.provider];
5511
+ const meta = import_core16.ALL_PROVIDERS[effect.provider];
5473
5512
  if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
5474
5513
  await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
5475
5514
  deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
@@ -5557,7 +5596,7 @@ function osc52(text) {
5557
5596
  function copySelection(deps, text) {
5558
5597
  if (!text) return;
5559
5598
  const lines = text.split("\n").length;
5560
- const command = (0, import_core12.clipboardCopyCommand)(deps.hasBin);
5599
+ const command = (0, import_core16.clipboardCopyCommand)(deps.hasBin);
5561
5600
  if (command) {
5562
5601
  try {
5563
5602
  (deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
@@ -5590,7 +5629,28 @@ async function withExecutionStream(deps, sessionId, request) {
5590
5629
  }
5591
5630
  async function converse(deps, sessionId, call) {
5592
5631
  let streamed = null;
5632
+ const TOKEN_DEBOUNCE_MS = 75;
5633
+ let tokenBuffer = "";
5634
+ let tokenTimer = null;
5635
+ const flushTokens = () => {
5636
+ if (tokenTimer !== null) {
5637
+ clearTimeout(tokenTimer);
5638
+ tokenTimer = null;
5639
+ }
5640
+ if (!tokenBuffer) return;
5641
+ const text = tokenBuffer;
5642
+ tokenBuffer = "";
5643
+ deps.dispatch({ type: "plannerToken", text, sessionId });
5644
+ };
5593
5645
  const stream = deps.api.streamPlanning(sessionId, (event) => {
5646
+ if (event?.type === "plan_token" && event.token) {
5647
+ tokenBuffer += event.token;
5648
+ if (tokenTimer === null) {
5649
+ tokenTimer = setTimeout(flushTokens, TOKEN_DEBOUNCE_MS);
5650
+ }
5651
+ return;
5652
+ }
5653
+ flushTokens();
5594
5654
  if (event?.type === "approval_request") {
5595
5655
  deps.dispatch({
5596
5656
  type: "approvalRequested",
@@ -5647,6 +5707,7 @@ async function converse(deps, sessionId, call) {
5647
5707
  deps.dispatch({ type: "plannerMessage", content: question, sessionId });
5648
5708
  }
5649
5709
  } finally {
5710
+ if (tokenTimer !== null) clearTimeout(tokenTimer);
5650
5711
  stream.close();
5651
5712
  }
5652
5713
  }
@@ -5666,7 +5727,7 @@ function onExecutionEvent(dispatch, event, sessionId) {
5666
5727
  case "status_update": {
5667
5728
  const updates = {};
5668
5729
  for (const task of event.tasks ?? []) {
5669
- updates[String(task.id)] = String(task.status);
5730
+ updates[String(task.id)] = { status: String(task.status), idleSince: task.idleSince ?? null };
5670
5731
  }
5671
5732
  dispatch({ type: "tasksStatus", updates, sessionId });
5672
5733
  return;
@@ -5745,7 +5806,7 @@ async function loadSettings(deps) {
5745
5806
  }
5746
5807
 
5747
5808
  // src/tui/keys.ts
5748
- var CONTROL = {
5809
+ var CONTROL2 = {
5749
5810
  "\r": "enter",
5750
5811
  "\n": "enter",
5751
5812
  " ": "tab",
@@ -5850,7 +5911,7 @@ function decodeMouse(seq) {
5850
5911
  function decodeKey(seq) {
5851
5912
  const escaped = ESCAPES[seq];
5852
5913
  if (escaped) return { name: escaped };
5853
- const control = CONTROL[seq];
5914
+ const control = CONTROL2[seq];
5854
5915
  if (control) return { name: control };
5855
5916
  if (seq.startsWith("\x1B[")) {
5856
5917
  const mouse = decodeMouse(seq);
@@ -5862,8 +5923,7 @@ function decodeKey(seq) {
5862
5923
  var PASTE_START = "\x1B[200~";
5863
5924
  var PASTE_END = "\x1B[201~";
5864
5925
  function pasteKey(raw) {
5865
- const text = stripTabs(raw.replace(/\r\n?/g, "\n").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ""));
5866
- return { name: "paste", text };
5926
+ return { name: "paste", text: sanitize(raw) };
5867
5927
  }
5868
5928
  function createKeyDecoder() {
5869
5929
  let paste = null;
@@ -5997,7 +6057,7 @@ function openTerminal(options) {
5997
6057
  draw(frame2) {
5998
6058
  if (closed) return;
5999
6059
  const { cols } = size();
6000
- const rows = frame2.map((line, i) => `\x1B[${i + 1};1H${clampToCols(line, cols)}\x1B[K`).join("");
6060
+ const rows = frame2.map((line, i) => `\x1B[${i + 1};1H${clampToCols(paintOnly(line), cols)}\x1B[K`).join("");
6001
6061
  output.write(AUTOWRAP_OFF + rows + AUTOWRAP_ON);
6002
6062
  },
6003
6063
  setMouse(enabled) {
@@ -6031,7 +6091,7 @@ async function handleTui(subArgs) {
6031
6091
  }
6032
6092
  const workspace = flag(subArgs, "--workspace") || process.cwd();
6033
6093
  try {
6034
- (0, import_core13.assertWorkspaceExists)(workspace);
6094
+ (0, import_core17.assertWorkspaceExists)(workspace);
6035
6095
  } catch {
6036
6096
  console.error(
6037
6097
  `Workspace "${workspace}" does not exist.
@@ -6040,7 +6100,10 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
6040
6100
  );
6041
6101
  process.exit(1);
6042
6102
  }
6043
- const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(resolvePort(subArgs), { detached: false });
6103
+ registerSkillCommands((0, import_core17.createSkillsService)(workspace).listSkills());
6104
+ const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
6105
+ const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
6106
+ const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
6044
6107
  let owned = ownedAtLaunch;
6045
6108
  const api = new ApiClient(port);
6046
6109
  const conversationQueue = new ConversationQueue();
@@ -6056,7 +6119,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
6056
6119
  conversationQueue,
6057
6120
  port,
6058
6121
  dispatch: (action) => app.dispatch(action),
6059
- newSessionId: () => (0, import_core13.mintSessionId)(),
6122
+ newSessionId: () => (0, import_core17.mintSessionId)(),
6060
6123
  setEnvVar: (key, value) => {
6061
6124
  writeEnvVar(findEnvFile(), key, value);
6062
6125
  process.env[key] = value;
@@ -6125,14 +6188,8 @@ var COMMANDS = {
6125
6188
  refresh: handleRefresh,
6126
6189
  setup: handleSetup,
6127
6190
  plugins: handlePlugins,
6128
- "grill-me": handleGrillMe,
6129
6191
  "tdd": handleTdd,
6130
- "prd": handlePrd,
6131
- "review": handleReview,
6132
- // 'verify' was once a legacy alias for review; it now toggles the
6133
- // evidence-based verification mode.
6134
6192
  "verify": handleVerify,
6135
- "research-subagents": handleResearchSubagents,
6136
6193
  "mark-complete": handleMarkComplete,
6137
6194
  complete: handleMarkComplete,
6138
6195
  uncomplete: handleUncomplete,
@@ -6168,6 +6225,7 @@ function cliVersion() {
6168
6225
 
6169
6226
  // src/main.ts
6170
6227
  async function main() {
6228
+ (0, import_core18.migrateOldConfigDir)();
6171
6229
  loadEnvFile();
6172
6230
  const argv = process.argv.slice(2);
6173
6231
  const command = argv[0] ?? (process.stdin.isTTY ? "tui" : "--help");