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