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