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