@ordewell/cli 0.4.10 → 0.4.12
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 +358 -296
- 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_core19 = 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) {
|
|
@@ -1145,12 +1181,14 @@ function iconFor(status) {
|
|
|
1145
1181
|
}
|
|
1146
1182
|
|
|
1147
1183
|
// src/commands/run.ts
|
|
1184
|
+
var import_core7 = require("@ordewell/core");
|
|
1148
1185
|
function resolveSessionId(subArgs) {
|
|
1149
1186
|
const explicit = flag(subArgs, "--session-id");
|
|
1150
1187
|
if (explicit) return explicit;
|
|
1151
|
-
const
|
|
1188
|
+
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
1189
|
+
const last = readLastSession(workspace);
|
|
1152
1190
|
if (!last) {
|
|
1153
|
-
console.error(
|
|
1191
|
+
console.error(`No session specified. Use --session-id <id> or run \`ordewell plan\` in ${workspace} first.`);
|
|
1154
1192
|
process.exit(1);
|
|
1155
1193
|
}
|
|
1156
1194
|
console.error(`Using last session: ${last.sessionId} ("${last.goal}")`);
|
|
@@ -1184,6 +1222,10 @@ async function followExecution(api, sessionId) {
|
|
|
1184
1222
|
await api.streamExecution(sessionId, (event) => {
|
|
1185
1223
|
if (event.type === "task_started") {
|
|
1186
1224
|
process.stderr.write(`[${event.order}/${event.title}] Started: ${event.runner} / ${event.modelId}
|
|
1225
|
+
`);
|
|
1226
|
+
}
|
|
1227
|
+
if (event.type === "checkpoint") {
|
|
1228
|
+
process.stderr.write(`\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core7.truncateCheckpointSummary)(event.summary)}
|
|
1187
1229
|
`);
|
|
1188
1230
|
}
|
|
1189
1231
|
if (event.type === "status_update" && event.tasks) {
|
|
@@ -1224,51 +1266,29 @@ async function handleRun(subArgs) {
|
|
|
1224
1266
|
|
|
1225
1267
|
// src/commands/status.ts
|
|
1226
1268
|
var import_fs4 = require("fs");
|
|
1269
|
+
var import_core8 = require("@ordewell/core");
|
|
1227
1270
|
async function handleStatus(subArgs, injectedApi) {
|
|
1228
1271
|
const sessionId = flag(subArgs, "--session-id");
|
|
1229
|
-
const
|
|
1230
|
-
let workspace = explicitWorkspace || process.cwd();
|
|
1272
|
+
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
1231
1273
|
const outputPath = flag(subArgs, "--output");
|
|
1232
1274
|
const asJson = hasFlag(subArgs, "--json") || Boolean(outputPath);
|
|
1233
1275
|
const port = injectedApi ? resolvePort(subArgs) : await ensureDaemon(resolvePort(subArgs));
|
|
1234
1276
|
const api = injectedApi || new ApiClient(port);
|
|
1235
1277
|
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
|
-
}
|
|
1278
|
+
const session = await api.getSession(sessionId, workspace);
|
|
1247
1279
|
if (asJson) {
|
|
1248
1280
|
emitJson(session, outputPath);
|
|
1249
1281
|
return;
|
|
1250
1282
|
}
|
|
1251
1283
|
printOneSession(sessionId, session.meta, session.plan);
|
|
1252
1284
|
} 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
|
-
}
|
|
1285
|
+
const sessions = await api.getSessions(workspace);
|
|
1266
1286
|
if (asJson) {
|
|
1267
1287
|
emitJson(sessions, outputPath);
|
|
1268
1288
|
return;
|
|
1269
1289
|
}
|
|
1270
1290
|
if (sessions.length === 0) {
|
|
1271
|
-
console.log(
|
|
1291
|
+
console.log(`No sessions found in ${workspace}.`);
|
|
1272
1292
|
return;
|
|
1273
1293
|
}
|
|
1274
1294
|
console.log(`${sessions.length} session(s):
|
|
@@ -1276,7 +1296,7 @@ async function handleStatus(subArgs, injectedApi) {
|
|
|
1276
1296
|
for (const s of sessions) {
|
|
1277
1297
|
console.log(` ${s.id.slice(-8)} ${s.status.padEnd(10)} ${s.taskCount} tasks ${(s.runners || []).join(",").padEnd(12)} ${s.goal.slice(0, 60)}`);
|
|
1278
1298
|
}
|
|
1279
|
-
const last = readLastSession();
|
|
1299
|
+
const last = readLastSession(workspace);
|
|
1280
1300
|
if (last) {
|
|
1281
1301
|
console.log(`
|
|
1282
1302
|
Last active: ${last.sessionId.slice(-8)} ("${last.goal.slice(0, 50)}")`);
|
|
@@ -1317,7 +1337,13 @@ function printOneSession(sessionId, meta, plan) {
|
|
|
1317
1337
|
const icon = iconFor(t.status);
|
|
1318
1338
|
const model = t.assignedModel ? ` (${t.assignedModel.modelLabel})` : "";
|
|
1319
1339
|
const shortId = (t.id ?? "").slice(-8);
|
|
1320
|
-
console.log(` ${
|
|
1340
|
+
console.log(` ${(0, import_core8.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}] ${t.title}${model}`);
|
|
1341
|
+
for (const sub of t.subtasks || []) {
|
|
1342
|
+
const subIcon = iconFor(sub.status);
|
|
1343
|
+
const subModel = sub.assignedModel ? ` (${sub.assignedModel.modelLabel})` : "";
|
|
1344
|
+
const subShortId = (sub.id ?? "").slice(-8);
|
|
1345
|
+
console.log(` ${(0, import_core8.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
|
|
1346
|
+
}
|
|
1321
1347
|
}
|
|
1322
1348
|
}
|
|
1323
1349
|
|
|
@@ -1330,7 +1356,8 @@ async function handleStop(subArgs) {
|
|
|
1330
1356
|
}
|
|
1331
1357
|
let sessionId = flag(subArgs, "--session-id");
|
|
1332
1358
|
if (!sessionId) {
|
|
1333
|
-
const
|
|
1359
|
+
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
1360
|
+
const last = readLastSession(workspace);
|
|
1334
1361
|
if (!last) {
|
|
1335
1362
|
console.error("No session specified. Use --session-id <id> or --server to stop the server.");
|
|
1336
1363
|
process.exit(1);
|
|
@@ -1367,10 +1394,10 @@ async function handleWeb(subArgs) {
|
|
|
1367
1394
|
}
|
|
1368
1395
|
|
|
1369
1396
|
// src/commands/models.ts
|
|
1370
|
-
var
|
|
1397
|
+
var import_core10 = require("@ordewell/core");
|
|
1371
1398
|
|
|
1372
1399
|
// src/commands/model.ts
|
|
1373
|
-
var
|
|
1400
|
+
var import_core9 = require("@ordewell/core");
|
|
1374
1401
|
|
|
1375
1402
|
// src/catalog.ts
|
|
1376
1403
|
function normalizeCatalog(result) {
|
|
@@ -1452,7 +1479,8 @@ function toTaskView(task) {
|
|
|
1452
1479
|
dependencies: task.dependencies ?? [],
|
|
1453
1480
|
assignedRunner: task.assignedRunner || void 0,
|
|
1454
1481
|
taskMode: task.taskMode || void 0,
|
|
1455
|
-
assignedModel: task.assignedModel ?? void 0
|
|
1482
|
+
assignedModel: task.assignedModel ?? void 0,
|
|
1483
|
+
subtasks: [...task.subtasks ?? []].sort((a, b) => a.order - b.order).map(toTaskView)
|
|
1456
1484
|
};
|
|
1457
1485
|
}
|
|
1458
1486
|
function taskViews(plan) {
|
|
@@ -1475,7 +1503,7 @@ async function handleModel(subArgs, injectedApi) {
|
|
|
1475
1503
|
if (!modelId) fail(USAGE);
|
|
1476
1504
|
const settings = await api.getSettings();
|
|
1477
1505
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
1478
|
-
const runner = (0,
|
|
1506
|
+
const runner = (0, import_core9.runnerForProvider)(provider);
|
|
1479
1507
|
if (runner) {
|
|
1480
1508
|
const known = (await fetchCatalog(api)).models.filter((m) => m.runners?.includes(runner));
|
|
1481
1509
|
if (known.length > 0 && !known.some((m) => m.id === modelId)) {
|
|
@@ -1494,7 +1522,7 @@ async function showModels(api) {
|
|
|
1494
1522
|
const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
|
|
1495
1523
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
1496
1524
|
const current = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
|
|
1497
|
-
const runner = (0,
|
|
1525
|
+
const runner = (0, import_core9.runnerForProvider)(provider);
|
|
1498
1526
|
const options = runner ? catalog.models.filter((m) => m.runners?.includes(runner)) : catalog.orchestratorModels;
|
|
1499
1527
|
console.log(`
|
|
1500
1528
|
Planner model: ${current || "(backend default)"}`);
|
|
@@ -1528,8 +1556,8 @@ async function handleModels(subArgs) {
|
|
|
1528
1556
|
const rest = subArgs.filter((_, i) => i !== setIdx && i !== setIdx + 1);
|
|
1529
1557
|
return handleModel(["set", subArgs[setIdx + 1], ...rest]);
|
|
1530
1558
|
}
|
|
1531
|
-
const config = new
|
|
1532
|
-
const providers = (0,
|
|
1559
|
+
const config = new import_core10.EnvConfig();
|
|
1560
|
+
const providers = (0, import_core10.configuredProviders)(config);
|
|
1533
1561
|
if (providers.length === 0) {
|
|
1534
1562
|
console.error("No providers configured. Set an API key for a provider, e.g.:");
|
|
1535
1563
|
console.error(" OPENROUTER_API_KEY=... (OpenRouter)");
|
|
@@ -1538,19 +1566,19 @@ async function handleModels(subArgs) {
|
|
|
1538
1566
|
console.error(" OPENAI_COMPATIBLE_BASE_URL=... (self-hosted / OpenAI-compatible)");
|
|
1539
1567
|
process.exit(1);
|
|
1540
1568
|
}
|
|
1541
|
-
const { apiKeys, baseUrls } = (0,
|
|
1569
|
+
const { apiKeys, baseUrls } = (0, import_core10.collectProviderCredentials)(config);
|
|
1542
1570
|
let models;
|
|
1543
1571
|
let errors;
|
|
1544
1572
|
try {
|
|
1545
|
-
const result = await (0,
|
|
1546
|
-
models = (0,
|
|
1573
|
+
const result = await (0, import_core10.fetchAllProviderModels)({ apiKeys, baseUrls });
|
|
1574
|
+
models = (0, import_core10.toOrchestratorOptions)(result.models, import_core10.ORCHESTRATOR_SHORTCUTS);
|
|
1547
1575
|
errors = result.errors;
|
|
1548
1576
|
} catch (err) {
|
|
1549
1577
|
console.error(`Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`);
|
|
1550
1578
|
process.exit(1);
|
|
1551
1579
|
return;
|
|
1552
1580
|
}
|
|
1553
|
-
const providerLabel = (p) => (0,
|
|
1581
|
+
const providerLabel = (p) => (0, import_core10.getProviderMeta)(p)?.label ?? p;
|
|
1554
1582
|
console.log(
|
|
1555
1583
|
`
|
|
1556
1584
|
Available models (${models.length}) across ${providers.length} configured provider(s):`
|
|
@@ -1667,9 +1695,9 @@ function handleSetup(subArgs) {
|
|
|
1667
1695
|
}
|
|
1668
1696
|
|
|
1669
1697
|
// src/commands/plugins.ts
|
|
1670
|
-
var
|
|
1698
|
+
var import_core11 = require("@ordewell/core");
|
|
1671
1699
|
function handlePlugins(subArgs) {
|
|
1672
|
-
const registry = new
|
|
1700
|
+
const registry = new import_core11.RunnerRegistry();
|
|
1673
1701
|
registry.loadUserPlugins();
|
|
1674
1702
|
const sub = subArgs[0];
|
|
1675
1703
|
if (sub === "list" || !sub) {
|
|
@@ -1705,7 +1733,7 @@ ${plugins.length} plugin(s):
|
|
|
1705
1733
|
process.exit(1);
|
|
1706
1734
|
}
|
|
1707
1735
|
try {
|
|
1708
|
-
const resolved = (0,
|
|
1736
|
+
const resolved = (0, import_core11.classifyPluginSource)(source);
|
|
1709
1737
|
const manifest = resolved.kind === "git" ? registry.installFromGit(resolved.url) : registry.installFromPath(resolved.path);
|
|
1710
1738
|
console.log(`Installed plugin: ${manifest.name} v${manifest.version}`);
|
|
1711
1739
|
} catch (err) {
|
|
@@ -1746,23 +1774,6 @@ ${plugins.length} plugin(s):
|
|
|
1746
1774
|
process.exit(1);
|
|
1747
1775
|
}
|
|
1748
1776
|
|
|
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
1777
|
// src/commands/tdd.ts
|
|
1767
1778
|
async function handleTdd(subArgs) {
|
|
1768
1779
|
const action = subArgs[0];
|
|
@@ -1780,24 +1791,6 @@ async function handleTdd(subArgs) {
|
|
|
1780
1791
|
}
|
|
1781
1792
|
}
|
|
1782
1793
|
|
|
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
1794
|
// src/commands/allowlist.ts
|
|
1802
1795
|
var USAGE2 = `Usage:
|
|
1803
1796
|
ordewell allowlist set <runner> <id1,id2,\u2026> Set allowlist for a runner
|
|
@@ -1884,23 +1877,6 @@ async function handleShow(args, api) {
|
|
|
1884
1877
|
}
|
|
1885
1878
|
}
|
|
1886
1879
|
|
|
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
1880
|
// src/commands/verify.ts
|
|
1905
1881
|
async function handleVerify(subArgs, api) {
|
|
1906
1882
|
const action = subArgs[0];
|
|
@@ -1918,23 +1894,6 @@ async function handleVerify(subArgs, api) {
|
|
|
1918
1894
|
}
|
|
1919
1895
|
}
|
|
1920
1896
|
|
|
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
1897
|
// src/commands/task-control.ts
|
|
1939
1898
|
var PAST_TENSE = {
|
|
1940
1899
|
run: "Task started.",
|
|
@@ -1943,11 +1902,12 @@ var PAST_TENSE = {
|
|
|
1943
1902
|
cancel: "Task cancelled."
|
|
1944
1903
|
};
|
|
1945
1904
|
async function withResolvedTask(subArgs, usage, injectedApi, run) {
|
|
1946
|
-
const
|
|
1905
|
+
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
1947
1906
|
let sessionId = flag(subArgs, "--session-id");
|
|
1948
1907
|
if (!sessionId) {
|
|
1908
|
+
const last = readLastSession(workspace);
|
|
1949
1909
|
if (!last) {
|
|
1950
|
-
console.error(
|
|
1910
|
+
console.error(`No session specified. Use --session-id <id> or run \`ordewell plan\` in ${workspace} first.`);
|
|
1951
1911
|
process.exit(1);
|
|
1952
1912
|
}
|
|
1953
1913
|
sessionId = last.sessionId;
|
|
@@ -1958,22 +1918,12 @@ async function withResolvedTask(subArgs, usage, injectedApi, run) {
|
|
|
1958
1918
|
process.exit(1);
|
|
1959
1919
|
}
|
|
1960
1920
|
const api = injectedApi || new ApiClient(await ensureDaemon(resolvePort(subArgs)));
|
|
1961
|
-
const explicitWorkspace = flag(subArgs, "--workspace");
|
|
1962
1921
|
let plan;
|
|
1963
1922
|
try {
|
|
1964
|
-
plan = (await api.getSession(sessionId,
|
|
1923
|
+
plan = (await api.getSession(sessionId, workspace)).plan;
|
|
1965
1924
|
} 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
|
-
}
|
|
1925
|
+
console.error(`Failed to load session: ${err.message}`);
|
|
1926
|
+
process.exit(1);
|
|
1977
1927
|
}
|
|
1978
1928
|
const taskId = resolveTaskId(plan, identifier);
|
|
1979
1929
|
if (!taskId) {
|
|
@@ -1986,7 +1936,7 @@ function makeHandler(action, command = action) {
|
|
|
1986
1936
|
return async function handle(subArgs, injectedApi) {
|
|
1987
1937
|
await withResolvedTask(
|
|
1988
1938
|
subArgs,
|
|
1989
|
-
`Usage: ordewell ${command} <task-id-or-order> [--session-id <id>]`,
|
|
1939
|
+
`Usage: ordewell ${command} <task-id-or-order> [--session-id <id>] [--workspace /path]`,
|
|
1990
1940
|
injectedApi,
|
|
1991
1941
|
async (api, sessionId, taskId) => {
|
|
1992
1942
|
try {
|
|
@@ -2059,14 +2009,15 @@ async function handleSkip(subArgs, api) {
|
|
|
2059
2009
|
async function handleAddTask(subArgs, injectedApi) {
|
|
2060
2010
|
const title = flag(subArgs, "--title");
|
|
2061
2011
|
if (!title) {
|
|
2062
|
-
console.error('Usage: ordewell add-task --title "..." [--description "..."] [--prompt "..."] [--type ai|user] [--depends-on <task-id> ...] [--session-id <id>]');
|
|
2012
|
+
console.error('Usage: ordewell add-task --title "..." [--description "..."] [--prompt "..."] [--type ai|user] [--depends-on <task-id> ...] [--session-id <id>] [--workspace /path]');
|
|
2063
2013
|
process.exit(1);
|
|
2064
2014
|
}
|
|
2065
2015
|
let sessionId = flag(subArgs, "--session-id");
|
|
2066
2016
|
if (!sessionId) {
|
|
2067
|
-
const
|
|
2017
|
+
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
2018
|
+
const last = readLastSession(workspace);
|
|
2068
2019
|
if (!last) {
|
|
2069
|
-
console.error(
|
|
2020
|
+
console.error(`No session specified. Use --session-id <id> or run \`ordewell plan\` in ${workspace} first.`);
|
|
2070
2021
|
process.exit(1);
|
|
2071
2022
|
}
|
|
2072
2023
|
sessionId = last.sessionId;
|
|
@@ -2096,7 +2047,7 @@ async function handleAddTask(subArgs, injectedApi) {
|
|
|
2096
2047
|
async function handleRemoveTask(subArgs, api) {
|
|
2097
2048
|
await withResolvedTask(
|
|
2098
2049
|
subArgs,
|
|
2099
|
-
"Usage: ordewell remove-task <task-id-or-order> [--session-id <id>]",
|
|
2050
|
+
"Usage: ordewell remove-task <task-id-or-order> [--session-id <id>] [--workspace /path]",
|
|
2100
2051
|
api,
|
|
2101
2052
|
async (client, sessionId, taskId) => {
|
|
2102
2053
|
try {
|
|
@@ -2127,7 +2078,7 @@ async function handleSessions(subArgs, injectedApi) {
|
|
|
2127
2078
|
console.log(`No sessions found in ${workspace}.`);
|
|
2128
2079
|
return;
|
|
2129
2080
|
}
|
|
2130
|
-
const last = readLastSession();
|
|
2081
|
+
const last = readLastSession(workspace);
|
|
2131
2082
|
console.log(`${sessions.length} session(s) in ${workspace}:
|
|
2132
2083
|
`);
|
|
2133
2084
|
for (const s of sessions) {
|
|
@@ -2177,7 +2128,7 @@ async function handleSessions(subArgs, injectedApi) {
|
|
|
2177
2128
|
}
|
|
2178
2129
|
|
|
2179
2130
|
// src/commands/planner.ts
|
|
2180
|
-
var
|
|
2131
|
+
var import_core12 = require("@ordewell/core");
|
|
2181
2132
|
|
|
2182
2133
|
// src/plannerModelSwitch.ts
|
|
2183
2134
|
function describePlannerSwitch(settings, provider) {
|
|
@@ -2196,7 +2147,7 @@ function rememberedModelFor(settings, provider) {
|
|
|
2196
2147
|
}
|
|
2197
2148
|
|
|
2198
2149
|
// src/commands/planner.ts
|
|
2199
|
-
var KNOWN_PROVIDERS = Object.keys(
|
|
2150
|
+
var KNOWN_PROVIDERS = Object.keys(import_core12.ALL_PROVIDERS);
|
|
2200
2151
|
async function handlePlanner(subArgs, injectedApi) {
|
|
2201
2152
|
const api = await connect(subArgs, injectedApi);
|
|
2202
2153
|
const [wanted] = positionals(subArgs);
|
|
@@ -2211,13 +2162,13 @@ async function handlePlanner(subArgs, injectedApi) {
|
|
|
2211
2162
|
const settings = await persistEnv(api, { AI_PROVIDER: provider });
|
|
2212
2163
|
const recall2 = describePlannerSwitch(settings, provider);
|
|
2213
2164
|
writeEnv({ ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
|
|
2214
|
-
const meta =
|
|
2165
|
+
const meta = import_core12.ALL_PROVIDERS[provider];
|
|
2215
2166
|
const catalog = await fetchCatalog(api);
|
|
2216
2167
|
console.log(
|
|
2217
|
-
(0,
|
|
2168
|
+
(0, import_core12.isCliProvider)(provider) ? `Planning with ${meta.label} \u2014 no API key needed, it uses that agent's own subscription.` : `Planner set to ${meta.label}.`
|
|
2218
2169
|
);
|
|
2219
2170
|
console.log(plannerModelLine(recall2));
|
|
2220
|
-
if (!(0,
|
|
2171
|
+
if (!(0, import_core12.isCliProvider)(provider) && !catalog.providers.includes(provider)) {
|
|
2221
2172
|
console.log(` No ${meta.apiKeyEnvVar} configured yet \u2014 set one with \`ordewell key set ${provider} <key>\`.`);
|
|
2222
2173
|
}
|
|
2223
2174
|
}
|
|
@@ -2235,15 +2186,15 @@ async function showPlanners(api) {
|
|
|
2235
2186
|
const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
|
|
2236
2187
|
const current = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
2237
2188
|
console.log("\nCoding agents (no API key \u2014 they use their own subscription)");
|
|
2238
|
-
for (const id of
|
|
2239
|
-
const runner = (0,
|
|
2189
|
+
for (const id of import_core12.CLI_PROVIDERS) {
|
|
2190
|
+
const runner = (0, import_core12.runnerForProvider)(id);
|
|
2240
2191
|
const discovered = runner ? catalog.models.some((m) => m.runners?.includes(runner)) : false;
|
|
2241
|
-
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${
|
|
2192
|
+
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${import_core12.ALL_PROVIDERS[id].label}${discovered ? "" : " (not detected)"}`);
|
|
2242
2193
|
}
|
|
2243
2194
|
console.log("\nAPI providers");
|
|
2244
|
-
for (const id of
|
|
2195
|
+
for (const id of import_core12.PROVIDER_PRIORITY) {
|
|
2245
2196
|
const configured = catalog.providers.includes(id);
|
|
2246
|
-
const meta =
|
|
2197
|
+
const meta = import_core12.ALL_PROVIDERS[id];
|
|
2247
2198
|
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${meta.label}${configured ? "" : ` (no ${meta.apiKeyEnvVar})`}`);
|
|
2248
2199
|
}
|
|
2249
2200
|
console.log(`
|
|
@@ -2260,7 +2211,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
2260
2211
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
2261
2212
|
const modelId = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
|
|
2262
2213
|
const currentEffort = typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : "";
|
|
2263
|
-
const runner = (0,
|
|
2214
|
+
const runner = (0, import_core12.runnerForProvider)(provider);
|
|
2264
2215
|
if (!runner) {
|
|
2265
2216
|
fail(
|
|
2266
2217
|
"Thinking effort applies to a coding-agent planner.",
|
|
@@ -2300,7 +2251,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
2300
2251
|
}
|
|
2301
2252
|
|
|
2302
2253
|
// src/commands/key.ts
|
|
2303
|
-
var
|
|
2254
|
+
var import_core13 = require("@ordewell/core");
|
|
2304
2255
|
var USAGE4 = "Usage: ordewell key [list | set <provider> <key>]";
|
|
2305
2256
|
async function handleKey(subArgs, injectedApi) {
|
|
2306
2257
|
const [action, provider, ...keyParts] = positionals(subArgs);
|
|
@@ -2311,8 +2262,8 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
2311
2262
|
if (action !== "set") {
|
|
2312
2263
|
const catalog = await fetchCatalog(api);
|
|
2313
2264
|
console.log("\nAPI provider keys\n");
|
|
2314
|
-
for (const id2 of
|
|
2315
|
-
const meta2 =
|
|
2265
|
+
for (const id2 of import_core13.PROVIDER_PRIORITY) {
|
|
2266
|
+
const meta2 = import_core13.ALL_PROVIDERS[id2];
|
|
2316
2267
|
const configured = catalog.providers.includes(id2);
|
|
2317
2268
|
console.log(` ${configured ? "\u2713" : " "} ${id2.padEnd(14)} ${meta2.apiKeyEnvVar.padEnd(28)} ${meta2.label}`);
|
|
2318
2269
|
}
|
|
@@ -2322,7 +2273,7 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
2322
2273
|
}
|
|
2323
2274
|
if (!provider) fail(USAGE4);
|
|
2324
2275
|
const id = provider.toLowerCase();
|
|
2325
|
-
const meta =
|
|
2276
|
+
const meta = import_core13.ALL_PROVIDERS[id];
|
|
2326
2277
|
if (!meta) {
|
|
2327
2278
|
fail(`Unknown provider: ${provider}`, "Run `ordewell key` to see the list.");
|
|
2328
2279
|
}
|
|
@@ -2402,7 +2353,7 @@ async function handleApprove(subArgs, injectedApi) {
|
|
|
2402
2353
|
// src/tui/terminalLauncher.ts
|
|
2403
2354
|
var import_child_process3 = require("child_process");
|
|
2404
2355
|
var import_util = require("util");
|
|
2405
|
-
var
|
|
2356
|
+
var import_core14 = require("@ordewell/core");
|
|
2406
2357
|
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
2407
2358
|
var defaultExecFile = async (command, args) => {
|
|
2408
2359
|
const { stdout, stderr } = await execFileAsync(command, args);
|
|
@@ -2422,16 +2373,16 @@ var defaultWhich = (bin) => {
|
|
|
2422
2373
|
async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
|
|
2423
2374
|
const platform = deps.platform ?? process.platform;
|
|
2424
2375
|
const env = deps.env ?? process.env;
|
|
2425
|
-
const hasTmuxImpl = deps.hasTmuxImpl ??
|
|
2376
|
+
const hasTmuxImpl = deps.hasTmuxImpl ?? import_core14.hasTmux;
|
|
2426
2377
|
const execFileImpl = deps.execFileImpl ?? defaultExecFile;
|
|
2427
2378
|
const spawnImpl = deps.spawnImpl ?? defaultSpawn;
|
|
2428
2379
|
const which = deps.which ?? defaultWhich;
|
|
2429
2380
|
if (!hasTmuxImpl()) {
|
|
2430
2381
|
return { ok: false, message: "tmux is not installed \u2014 install it to get a real terminal per task." };
|
|
2431
2382
|
}
|
|
2432
|
-
const session = (0,
|
|
2433
|
-
const socket = (0,
|
|
2434
|
-
const base = (0,
|
|
2383
|
+
const session = (0, import_core14.tmuxSessionName)(port);
|
|
2384
|
+
const socket = (0, import_core14.tmuxSocketName)(port);
|
|
2385
|
+
const base = (0, import_core14.tmuxWindowName)(taskId, planSessionId);
|
|
2435
2386
|
let windows;
|
|
2436
2387
|
try {
|
|
2437
2388
|
const { stdout } = await execFileImpl("tmux", ["-L", socket, "list-windows", "-t", session, "-F", "#{window_name}"]);
|
|
@@ -2725,7 +2676,7 @@ Dependencies \xB7 #${task.order} ${task.title}
|
|
|
2725
2676
|
}
|
|
2726
2677
|
|
|
2727
2678
|
// src/tui/index.ts
|
|
2728
|
-
var
|
|
2679
|
+
var import_core18 = require("@ordewell/core");
|
|
2729
2680
|
|
|
2730
2681
|
// src/tui/ansi.ts
|
|
2731
2682
|
var import_string_width = __toESM(require("string-width"));
|
|
@@ -3030,12 +2981,8 @@ var SLASH_COMMANDS = [
|
|
|
3030
2981
|
{ name: "auto", usage: "/auto [on|off]", description: "Autonomous permission mode for new plans", category: "models" },
|
|
3031
2982
|
{ name: "refresh", usage: "/refresh", description: "Re-discover runners and model catalogs", category: "models" },
|
|
3032
2983
|
// Skills
|
|
3033
|
-
{ name: "grill-me", usage: "/grill-me [on|off]", description: "Grill-Me challenge mode \u2014 the planner interrogates the goal", category: "skills" },
|
|
3034
2984
|
{ name: "tdd", usage: "/tdd [on|off]", description: "Test-Driven Development mode", category: "skills" },
|
|
3035
|
-
{ name: "prd", usage: "/prd [on|off]", description: "PRD mode \u2014 write a PRD before the plan", category: "skills" },
|
|
3036
|
-
{ name: "review", usage: "/review [on|off]", description: "Review mode \u2014 final opinion-review task with human sign-off", category: "skills" },
|
|
3037
2985
|
{ name: "verify", usage: "/verify [on|off]", description: "Verification mode \u2014 final evidence-based task that runs the suite", category: "skills" },
|
|
3038
|
-
{ name: "research-subagents", usage: "/research-subagents [on|off]", description: "Parallel read-only research subagents during planning", category: "skills" },
|
|
3039
2986
|
// Sessions
|
|
3040
2987
|
{ name: "sessions", usage: "/sessions", description: "List saved sessions", category: "session" },
|
|
3041
2988
|
{ name: "new", usage: "/new", description: "Start a new session", category: "session" },
|
|
@@ -3048,6 +2995,23 @@ var SLASH_COMMANDS = [
|
|
|
3048
2995
|
{ name: "quit", usage: "/quit", description: "Exit the TUI", category: "system" }
|
|
3049
2996
|
];
|
|
3050
2997
|
var BY_NAME = new Map(SLASH_COMMANDS.map((c) => [c.name, c]));
|
|
2998
|
+
var skillCommands = [];
|
|
2999
|
+
var skillByName = /* @__PURE__ */ new Map();
|
|
3000
|
+
function registerSkillCommands(skills) {
|
|
3001
|
+
const map = /* @__PURE__ */ new Map();
|
|
3002
|
+
for (const skill of skills) {
|
|
3003
|
+
if (BY_NAME.has(skill.name)) continue;
|
|
3004
|
+
map.set(skill.name, {
|
|
3005
|
+
name: skill.name,
|
|
3006
|
+
usage: `/${skill.name}`,
|
|
3007
|
+
description: skill.description,
|
|
3008
|
+
category: "skills",
|
|
3009
|
+
source: "skill"
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
skillByName = map;
|
|
3013
|
+
skillCommands = [...map.values()];
|
|
3014
|
+
}
|
|
3051
3015
|
function parseSlash(text) {
|
|
3052
3016
|
const trimmed = text.trim();
|
|
3053
3017
|
if (!trimmed.startsWith("/")) return null;
|
|
@@ -3056,14 +3020,15 @@ function parseSlash(text) {
|
|
|
3056
3020
|
return { name: parts[0].toLowerCase(), args: parts.slice(1) };
|
|
3057
3021
|
}
|
|
3058
3022
|
function findCommand(name) {
|
|
3059
|
-
|
|
3023
|
+
const key = name.replace(/^\//, "").toLowerCase();
|
|
3024
|
+
return BY_NAME.get(key) ?? skillByName.get(key);
|
|
3060
3025
|
}
|
|
3061
3026
|
function completions(text) {
|
|
3062
3027
|
if (!text.startsWith("/")) return [];
|
|
3063
3028
|
const typed = text.slice(1);
|
|
3064
3029
|
if (/\s/.test(typed)) return [];
|
|
3065
3030
|
const prefix = typed.toLowerCase();
|
|
3066
|
-
return SLASH_COMMANDS.filter((c) => c.name.startsWith(prefix));
|
|
3031
|
+
return [...SLASH_COMMANDS, ...skillCommands].filter((c) => c.name.startsWith(prefix));
|
|
3067
3032
|
}
|
|
3068
3033
|
|
|
3069
3034
|
// src/tui/markdown.ts
|
|
@@ -3208,9 +3173,9 @@ function chatEditorRoom(cols, active) {
|
|
|
3208
3173
|
}
|
|
3209
3174
|
|
|
3210
3175
|
// src/tui/state.ts
|
|
3211
|
-
var SKILL_IDS = ["
|
|
3176
|
+
var SKILL_IDS = ["tdd", "verify"];
|
|
3212
3177
|
function noSkills() {
|
|
3213
|
-
return {
|
|
3178
|
+
return { tdd: false, verify: false };
|
|
3214
3179
|
}
|
|
3215
3180
|
function visibleItems(picker2) {
|
|
3216
3181
|
const filter = picker2.filter.trim().toLowerCase();
|
|
@@ -3221,6 +3186,29 @@ function visibleItems(picker2) {
|
|
|
3221
3186
|
}
|
|
3222
3187
|
var isTaskRunning = (task) => task.status === "in_progress" || task.status === "running";
|
|
3223
3188
|
var anyTaskRunning = (state) => state.tasks.some(isTaskRunning);
|
|
3189
|
+
function planRows(state) {
|
|
3190
|
+
const rows = [];
|
|
3191
|
+
for (const task of state.tasks) {
|
|
3192
|
+
rows.push({ task, parent: null });
|
|
3193
|
+
const subtasks = task.subtasks ?? [];
|
|
3194
|
+
const open = state.expandedTaskId === task.id || subtasks.some((s) => s.id === state.expandedTaskId);
|
|
3195
|
+
if (open) {
|
|
3196
|
+
for (const subtask of subtasks) rows.push({ task: subtask, parent: task });
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
return rows;
|
|
3200
|
+
}
|
|
3201
|
+
function selectedPlanRow(state) {
|
|
3202
|
+
return planRows(state)[state.selectedTask] ?? null;
|
|
3203
|
+
}
|
|
3204
|
+
function findTask(tasks, id) {
|
|
3205
|
+
for (const task of tasks) {
|
|
3206
|
+
if (task.id === id) return task;
|
|
3207
|
+
const found = findTask(task.subtasks ?? [], id);
|
|
3208
|
+
if (found) return found;
|
|
3209
|
+
}
|
|
3210
|
+
return void 0;
|
|
3211
|
+
}
|
|
3224
3212
|
function initialState(overrides = {}) {
|
|
3225
3213
|
return {
|
|
3226
3214
|
editor: emptyEditor(),
|
|
@@ -3266,7 +3254,7 @@ function initialState(overrides = {}) {
|
|
|
3266
3254
|
}
|
|
3267
3255
|
|
|
3268
3256
|
// src/tui/layout.ts
|
|
3269
|
-
var
|
|
3257
|
+
var import_core15 = require("@ordewell/core");
|
|
3270
3258
|
var CHROME_ROWS = 4;
|
|
3271
3259
|
function chatInputWrap(state) {
|
|
3272
3260
|
const active = state.focus === "chat" && !state.overlay;
|
|
@@ -3274,11 +3262,11 @@ function chatInputWrap(state) {
|
|
|
3274
3262
|
return wrapLines(state.editor.text, chatEditorRoom(state.cols, true));
|
|
3275
3263
|
}
|
|
3276
3264
|
function footerHints(state) {
|
|
3277
|
-
const markHint = state.
|
|
3265
|
+
const markHint = selectedPlanRow(state)?.task.status === "completed" ? "m undone" : "m done";
|
|
3278
3266
|
const planning = state.status === "planning" || state.status === "researching";
|
|
3279
3267
|
const escHint = planning ? "esc stop planning" : null;
|
|
3280
3268
|
if (state.focus === "plan") {
|
|
3281
|
-
if (state.
|
|
3269
|
+
if (state.taskEditor) {
|
|
3282
3270
|
return ["type to edit prompt", "pgup/pgdn scroll", "alt-enter newline", "enter save", "esc cancel"];
|
|
3283
3271
|
}
|
|
3284
3272
|
return [
|
|
@@ -3402,9 +3390,9 @@ function chatScrollMax(state) {
|
|
|
3402
3390
|
}
|
|
3403
3391
|
function plannerSetup(state) {
|
|
3404
3392
|
const provider = state.plannerProvider;
|
|
3405
|
-
const registration =
|
|
3393
|
+
const registration = import_core15.ALL_PROVIDERS[provider];
|
|
3406
3394
|
if (!registration) return { label: "not set", ready: false };
|
|
3407
|
-
const runner = (0,
|
|
3395
|
+
const runner = (0, import_core15.runnerForProvider)(provider);
|
|
3408
3396
|
const ready = runner ? state.runners.some((r) => r.id === runner) : state.configuredProviders.includes(provider);
|
|
3409
3397
|
return { label: registration.label, ready };
|
|
3410
3398
|
}
|
|
@@ -3511,16 +3499,17 @@ var STATUS_ICON = {
|
|
|
3511
3499
|
approved: "\xB7",
|
|
3512
3500
|
pending: "\xB7"
|
|
3513
3501
|
};
|
|
3502
|
+
var IDLE_ICON = "~";
|
|
3514
3503
|
function planLayout(state, rows, cols) {
|
|
3515
3504
|
const done = state.tasks.filter((t) => t.status === "completed").length;
|
|
3516
3505
|
const lines = [style.bold(`Plan ${done}/${state.tasks.length}`), ""];
|
|
3517
3506
|
let selectedLine = 2;
|
|
3518
3507
|
let editorLine;
|
|
3519
|
-
state.
|
|
3508
|
+
planRows(state).forEach((row, i) => {
|
|
3520
3509
|
const taskStart = lines.length;
|
|
3521
3510
|
if (i === state.selectedTask) selectedLine = taskStart;
|
|
3522
|
-
const renderedTask = taskLines(state,
|
|
3523
|
-
if (task.id === state.expandedTaskId && renderedTask.editorLine !== void 0) {
|
|
3511
|
+
const renderedTask = taskLines(state, row, i, cols);
|
|
3512
|
+
if (row.task.id === state.expandedTaskId && renderedTask.editorLine !== void 0) {
|
|
3524
3513
|
editorLine = taskStart + renderedTask.editorLine;
|
|
3525
3514
|
}
|
|
3526
3515
|
lines.push(...renderedTask.lines);
|
|
@@ -3539,16 +3528,20 @@ function planScrollExtent(state) {
|
|
|
3539
3528
|
const { followOffset, maxScroll } = planLayout(state, bodyRows(state), cols);
|
|
3540
3529
|
return { followOffset, maxScroll };
|
|
3541
3530
|
}
|
|
3542
|
-
function taskLines(state,
|
|
3531
|
+
function taskLines(state, row, index, cols) {
|
|
3532
|
+
const { task, parent } = row;
|
|
3543
3533
|
const selected = state.focus === "plan" && index === state.selectedTask;
|
|
3544
3534
|
const expanded = state.expandedTaskId === task.id;
|
|
3545
3535
|
const running = isTaskRunning(task);
|
|
3546
|
-
const
|
|
3547
|
-
const
|
|
3536
|
+
const idle = running && task.status === "in_progress" && task.idleSince != null;
|
|
3537
|
+
const paint = idle ? style.cyan : STATUS_MARK[task.status] ?? ((t) => t);
|
|
3538
|
+
const rawIcon = idle ? IDLE_ICON : running ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[task.status] ?? "\xB7";
|
|
3548
3539
|
const icon = paint(rawIcon);
|
|
3549
3540
|
const kind = running ? style.yellow("RUN") : task.type === "user" ? style.yellow("MAN") : style.grey(" AI");
|
|
3550
3541
|
const caret = selected ? style.cyan("\u276F") : " ";
|
|
3551
|
-
const
|
|
3542
|
+
const orderLabel = parent ? (0, import_core15.taskOrderLabel)(task, parent) : String(task.order).padStart(2);
|
|
3543
|
+
const indent = parent ? " " : "";
|
|
3544
|
+
const head = `${indent}${caret} ${icon} ${orderLabel} ${kind} `;
|
|
3552
3545
|
const titleRoom = Math.max(1, cols - width(head));
|
|
3553
3546
|
const wrappedTitle = wrap(task.title, titleRoom);
|
|
3554
3547
|
const shownTitle = expanded ? wrappedTitle : wrappedTitle.slice(0, 2);
|
|
@@ -3566,50 +3559,51 @@ function taskLines(state, task, index, cols) {
|
|
|
3566
3559
|
const modeInfo = task.type === "ai" ? modesForTask(state.modesByRunner, task).find((m) => m.id === task.taskMode) : void 0;
|
|
3567
3560
|
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${modeInfo?.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
3568
3561
|
const runner = task.assignedRunner ?? "";
|
|
3562
|
+
const bodyPad = parent ? " " : " ";
|
|
3569
3563
|
const meta = [running ? "working" : "", runner, model].filter(Boolean).join(" \xB7 ");
|
|
3570
|
-
if (meta) lines.push(style.grey(truncate(
|
|
3571
|
-
if (effort || mode) lines.push(style.grey(truncate(
|
|
3564
|
+
if (meta) lines.push(style.grey(truncate(`${bodyPad}${meta}`, cols)));
|
|
3565
|
+
if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
|
|
3572
3566
|
let editorLine;
|
|
3573
3567
|
if (expanded) {
|
|
3574
|
-
lines.push(style.grey(
|
|
3568
|
+
lines.push(style.grey(`${bodyPad}${task.status.replace(/_/g, " ")}`));
|
|
3575
3569
|
if (modeInfo?.autonomous) {
|
|
3576
|
-
lines.push(...taskText("Autonomy", "Runs without permission prompts. Toggle with /auto.", cols));
|
|
3570
|
+
lines.push(...taskText("Autonomy", "Runs without permission prompts. Toggle with /auto.", cols, bodyPad));
|
|
3577
3571
|
}
|
|
3578
3572
|
const editableSeed = task.prompt ?? task.description ?? task.title;
|
|
3579
3573
|
if (task.description && task.description !== task.title && task.description !== editableSeed) {
|
|
3580
|
-
lines.push(...taskText("Description", task.description, cols));
|
|
3574
|
+
lines.push(...taskText("Description", task.description, cols, bodyPad));
|
|
3581
3575
|
}
|
|
3582
3576
|
if (state.taskEditor) {
|
|
3583
|
-
const room =
|
|
3577
|
+
const room = Math.max(1, cols - width(bodyPad));
|
|
3584
3578
|
const wrapped = wrapLines(state.taskEditor.text, room);
|
|
3585
3579
|
const cp = cursorInLines(wrapped, state.taskEditor.cursor, state.taskEditor.text.length);
|
|
3586
3580
|
editorLine = lines.length + 1 + cp.line;
|
|
3587
|
-
lines.push(...taskPromptLines(wrapped, cp));
|
|
3581
|
+
lines.push(...taskPromptLines(wrapped, cp, bodyPad));
|
|
3588
3582
|
}
|
|
3589
3583
|
if (task.dependencies.length > 0) {
|
|
3590
3584
|
const orderById = new Map(state.tasks.map((candidate) => [candidate.id, candidate.order]));
|
|
3591
3585
|
const dependencies = task.dependencies.map((id) => orderById.has(id) ? `#${orderById.get(id)}` : id).join(", ");
|
|
3592
|
-
lines.push(...taskText("Depends on", dependencies, cols));
|
|
3586
|
+
lines.push(...taskText("Depends on", dependencies, cols, bodyPad));
|
|
3593
3587
|
}
|
|
3594
|
-
lines.push(style.grey(
|
|
3595
|
-
lines.push(style.grey(truncate(
|
|
3588
|
+
lines.push(style.grey(`${bodyPad}enter save \xB7 esc cancel`));
|
|
3589
|
+
lines.push(style.grey(truncate(`${bodyPad}then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps`, cols)));
|
|
3596
3590
|
}
|
|
3597
3591
|
return { lines, editorLine };
|
|
3598
3592
|
}
|
|
3599
|
-
function taskText(label, text, cols) {
|
|
3600
|
-
const room =
|
|
3593
|
+
function taskText(label, text, cols, pad2 = " ") {
|
|
3594
|
+
const room = Math.max(1, cols - width(pad2));
|
|
3601
3595
|
return [
|
|
3602
|
-
style.grey(
|
|
3603
|
-
...wrap(text, room).map((line) =>
|
|
3596
|
+
style.grey(`${pad2}${label}`),
|
|
3597
|
+
...wrap(text, room).map((line) => `${pad2}${line}`)
|
|
3604
3598
|
];
|
|
3605
3599
|
}
|
|
3606
|
-
function taskPromptLines(wrapped, cursor) {
|
|
3600
|
+
function taskPromptLines(wrapped, cursor, pad2 = " ") {
|
|
3607
3601
|
return [
|
|
3608
|
-
style.grey(
|
|
3602
|
+
style.grey(`${pad2}Prompt`),
|
|
3609
3603
|
...wrapped.map(({ line }, i) => {
|
|
3610
|
-
if (i !== cursor.line) return
|
|
3604
|
+
if (i !== cursor.line) return `${pad2}${line}`;
|
|
3611
3605
|
const col = cursor.col;
|
|
3612
|
-
return
|
|
3606
|
+
return `${pad2}${line.slice(0, col)}${style.inverse(line.slice(col, col + 1) || " ")}${line.slice(col + 1)}`;
|
|
3613
3607
|
})
|
|
3614
3608
|
];
|
|
3615
3609
|
}
|
|
@@ -3762,15 +3756,29 @@ function renderStatus(state, cols) {
|
|
|
3762
3756
|
const tail = thinking.length > room ? `\u2026${thinking.slice(-(room - 1)).trimStart()}` : thinking;
|
|
3763
3757
|
return truncate(`${head}${style.grey(` \xB7 ${tail}`)}`, cols);
|
|
3764
3758
|
}
|
|
3759
|
+
var LEADING_TOKEN = /^\/\S*/;
|
|
3760
|
+
function skillTokenLength(text) {
|
|
3761
|
+
const match = LEADING_TOKEN.exec(text);
|
|
3762
|
+
if (!match) return 0;
|
|
3763
|
+
return findCommand(match[0])?.source === "skill" ? match[0].length : 0;
|
|
3764
|
+
}
|
|
3765
|
+
function paintRange(text, base, tokenEnd) {
|
|
3766
|
+
const localEnd = Math.max(0, Math.min(text.length, tokenEnd - base));
|
|
3767
|
+
if (localEnd <= 0) return text;
|
|
3768
|
+
if (localEnd >= text.length) return style.cyan(text);
|
|
3769
|
+
return style.cyan(text.slice(0, localEnd)) + text.slice(localEnd);
|
|
3770
|
+
}
|
|
3765
3771
|
function renderInput(state, cols) {
|
|
3766
3772
|
const prompt = state.focus === "plan" ? style.grey("\u276F ") : style.cyan("\u276F ");
|
|
3767
3773
|
const lines = chatInputWrap(state);
|
|
3768
3774
|
const room = chatEditorRoom(cols, lines !== null);
|
|
3769
3775
|
const { text, cursor } = state.editor;
|
|
3770
3776
|
const continuation = " ".repeat(width(prompt));
|
|
3777
|
+
const tokenEnd = skillTokenLength(text);
|
|
3771
3778
|
if (!lines) {
|
|
3772
3779
|
const visible = text.replace(/\n/g, "\xB6").replace(/\t/g, " ");
|
|
3773
|
-
|
|
3780
|
+
const painted = tokenEnd > 0 ? paintRange(visible, 0, tokenEnd) : visible;
|
|
3781
|
+
return [prompt + painted + suggest(state, room - width(visible))];
|
|
3774
3782
|
}
|
|
3775
3783
|
const { line: lineIndex, col } = cursorInLines(lines, cursor, text.length);
|
|
3776
3784
|
const rawLines = lines.map((l) => l.line);
|
|
@@ -3778,7 +3786,8 @@ function renderInput(state, cols) {
|
|
|
3778
3786
|
const prefix = i === 0 ? prompt : continuation;
|
|
3779
3787
|
const line = raw.replace(/\t/g, " ");
|
|
3780
3788
|
const isCursorLine = i === lineIndex;
|
|
3781
|
-
const
|
|
3789
|
+
const lineTokenEnd = i === 0 ? tokenEnd : 0;
|
|
3790
|
+
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);
|
|
3782
3791
|
const used = width(line) + (isCursorLine && col >= line.length ? 1 : 0);
|
|
3783
3792
|
const hint = i === rawLines.length - 1 ? suggest(state, room - used) : "";
|
|
3784
3793
|
return prefix + rendered + hint;
|
|
@@ -3908,15 +3917,21 @@ function frame(title, body, rows, cols) {
|
|
|
3908
3917
|
}
|
|
3909
3918
|
|
|
3910
3919
|
// src/tui/reducer.ts
|
|
3911
|
-
var
|
|
3920
|
+
var import_core16 = require("@ordewell/core");
|
|
3912
3921
|
var import_plan_utils3 = require("@ordewell/core/plan-utils");
|
|
3913
|
-
var KNOWN_PROVIDERS2 = Object.keys(
|
|
3922
|
+
var KNOWN_PROVIDERS2 = Object.keys(import_core16.ALL_PROVIDERS);
|
|
3914
3923
|
var DEFAULT_EFFORT = "__runner_default__";
|
|
3915
3924
|
function step(state, effects = []) {
|
|
3916
3925
|
return { state, effects };
|
|
3917
3926
|
}
|
|
3918
|
-
function say(state, role, content, research) {
|
|
3919
|
-
const message = {
|
|
3927
|
+
function say(state, role, content, research, extra) {
|
|
3928
|
+
const message = {
|
|
3929
|
+
role,
|
|
3930
|
+
content: sanitize(content),
|
|
3931
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3932
|
+
...research ? { research } : {},
|
|
3933
|
+
...extra
|
|
3934
|
+
};
|
|
3920
3935
|
return { ...state, messages: [...state.messages, message], scroll: 0 };
|
|
3921
3936
|
}
|
|
3922
3937
|
function restoredMessages(history) {
|
|
@@ -3986,24 +4001,42 @@ function reduce(state, action) {
|
|
|
3986
4001
|
case "planUpdated": {
|
|
3987
4002
|
if (stale(state, action.sessionId)) return step(state);
|
|
3988
4003
|
const tasks = normalizeTasks(action.plan);
|
|
3989
|
-
|
|
4004
|
+
const expandedTaskId = state.expandedTaskId && findTask(tasks, state.expandedTaskId) ? state.expandedTaskId : null;
|
|
4005
|
+
const next = {
|
|
3990
4006
|
...state,
|
|
3991
4007
|
tasks,
|
|
3992
4008
|
status: state.status === "planning" || state.status === "researching" ? "idle" : state.status,
|
|
3993
4009
|
busyLabel: "",
|
|
3994
4010
|
thinkingLine: "",
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
});
|
|
4011
|
+
expandedTaskId,
|
|
4012
|
+
taskEditor: expandedTaskId !== null ? state.taskEditor : null
|
|
4013
|
+
};
|
|
4014
|
+
return step({ ...next, selectedTask: Math.min(state.selectedTask, Math.max(0, planRows(next).length - 1)) });
|
|
3999
4015
|
}
|
|
4000
4016
|
case "plannerMessage": {
|
|
4001
4017
|
if (stale(state, action.sessionId)) return step(state);
|
|
4002
4018
|
const settled = { ...state, status: "idle", busyLabel: "", thinkingLine: "" };
|
|
4003
4019
|
const content = sanitize(action.content);
|
|
4020
|
+
const last = state.messages.at(-1);
|
|
4021
|
+
if (last?.role === "assistant" && last.streaming) {
|
|
4022
|
+
const messages = [...state.messages];
|
|
4023
|
+
messages[messages.length - 1] = { ...last, content, streaming: void 0 };
|
|
4024
|
+
return step({ ...settled, messages });
|
|
4025
|
+
}
|
|
4004
4026
|
if (alreadySpoken(state.messages, content)) return step(settled);
|
|
4005
4027
|
return step(say(settled, "assistant", content));
|
|
4006
4028
|
}
|
|
4029
|
+
case "plannerToken": {
|
|
4030
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
4031
|
+
const text = sanitize(action.text);
|
|
4032
|
+
const last = state.messages.at(-1);
|
|
4033
|
+
if (last?.role === "assistant" && last.streaming) {
|
|
4034
|
+
const messages = [...state.messages];
|
|
4035
|
+
messages[messages.length - 1] = { ...last, content: last.content + text };
|
|
4036
|
+
return step({ ...state, messages });
|
|
4037
|
+
}
|
|
4038
|
+
return step(say(state, "assistant", text, void 0, { streaming: true }));
|
|
4039
|
+
}
|
|
4007
4040
|
case "researchStep": {
|
|
4008
4041
|
if (stale(state, action.sessionId)) return step(state);
|
|
4009
4042
|
const summary = sanitize(action.summary);
|
|
@@ -4040,10 +4073,12 @@ function reduce(state, action) {
|
|
|
4040
4073
|
const updates = action.updates;
|
|
4041
4074
|
let changed = false;
|
|
4042
4075
|
const tasks = state.tasks.map((t) => {
|
|
4043
|
-
const
|
|
4044
|
-
if (
|
|
4076
|
+
const update = updates[t.id];
|
|
4077
|
+
if (update === void 0) return t;
|
|
4078
|
+
const idleSince = update.idleSince ?? null;
|
|
4079
|
+
if (update.status !== t.status || idleSince !== (t.idleSince ?? null)) {
|
|
4045
4080
|
changed = true;
|
|
4046
|
-
return { ...t, status };
|
|
4081
|
+
return { ...t, status: update.status, idleSince };
|
|
4047
4082
|
}
|
|
4048
4083
|
return t;
|
|
4049
4084
|
});
|
|
@@ -4125,9 +4160,12 @@ function normalizeTasks(plan) {
|
|
|
4125
4160
|
}
|
|
4126
4161
|
}
|
|
4127
4162
|
const raw = byId.size > 0 ? [...byId.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) : source?.tasks ?? [];
|
|
4128
|
-
return raw.map((t, i) => (
|
|
4129
|
-
|
|
4130
|
-
|
|
4163
|
+
return raw.map((t, i) => toTaskView2(t, i));
|
|
4164
|
+
}
|
|
4165
|
+
function toTaskView2(t, index) {
|
|
4166
|
+
return {
|
|
4167
|
+
id: String(t.id ?? `task-${index + 1}`),
|
|
4168
|
+
order: typeof t.order === "number" ? t.order : index + 1,
|
|
4131
4169
|
title: planLabel(t.title, "Untitled task"),
|
|
4132
4170
|
description: planText(t.description ?? t.title),
|
|
4133
4171
|
prompt: typeof t.prompt === "string" ? sanitize(t.prompt) : void 0,
|
|
@@ -4141,18 +4179,15 @@ function normalizeTasks(plan) {
|
|
|
4141
4179
|
modelLabel: planLabel(t.assignedModel.modelLabel ?? t.assignedModel.modelId),
|
|
4142
4180
|
thinkingEffort: typeof t.assignedModel.thinkingEffort === "string" ? planLabel(t.assignedModel.thinkingEffort) : typeof t.thinkingEffort === "string" ? planLabel(t.thinkingEffort) : void 0,
|
|
4143
4181
|
availableVariants: Array.isArray(t.assignedModel.availableVariants) ? t.assignedModel.availableVariants.map((variant) => planLabel(variant)) : void 0
|
|
4144
|
-
} : void 0
|
|
4145
|
-
|
|
4182
|
+
} : void 0,
|
|
4183
|
+
subtasks: Array.isArray(t.subtasks) ? [...t.subtasks].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)).map((s, i) => toTaskView2(s, i)) : void 0
|
|
4184
|
+
};
|
|
4146
4185
|
}
|
|
4147
4186
|
var enabledFlag = (value) => typeof value?.enabled === "boolean" ? value.enabled : void 0;
|
|
4148
4187
|
function applySettings(state, settings) {
|
|
4149
4188
|
const sources = {
|
|
4150
|
-
"grill-me": settings.grillMe,
|
|
4151
4189
|
tdd: settings.tdd,
|
|
4152
|
-
|
|
4153
|
-
review: settings.review,
|
|
4154
|
-
verify: settings.verification,
|
|
4155
|
-
"research-subagents": settings.researchSubagents
|
|
4190
|
+
verify: settings.verification
|
|
4156
4191
|
};
|
|
4157
4192
|
const skills = { ...state.skills };
|
|
4158
4193
|
for (const id of SKILL_IDS) {
|
|
@@ -4181,11 +4216,11 @@ function modelDetail(m) {
|
|
|
4181
4216
|
function providerErrorHint(state) {
|
|
4182
4217
|
const failed = Object.keys(state.providerErrors ?? {});
|
|
4183
4218
|
if (failed.length === 0) return void 0;
|
|
4184
|
-
const names = failed.map((p) =>
|
|
4219
|
+
const names = failed.map((p) => import_core16.ALL_PROVIDERS[p]?.label ?? p);
|
|
4185
4220
|
return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")} \u2014 showing working providers only.`;
|
|
4186
4221
|
}
|
|
4187
4222
|
function plannerModelItems(state) {
|
|
4188
|
-
const runner = (0,
|
|
4223
|
+
const runner = (0, import_core16.runnerForProvider)(state.plannerProvider);
|
|
4189
4224
|
if (!runner) return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m) }));
|
|
4190
4225
|
const items = state.models.filter((m) => m.runners?.includes(runner)).map((m) => ({
|
|
4191
4226
|
id: m.id,
|
|
@@ -4196,7 +4231,7 @@ function plannerModelItems(state) {
|
|
|
4196
4231
|
return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
|
|
4197
4232
|
}
|
|
4198
4233
|
function plannerEffortItems(state) {
|
|
4199
|
-
const runner = (0,
|
|
4234
|
+
const runner = (0, import_core16.runnerForProvider)(state.plannerProvider);
|
|
4200
4235
|
if (!runner) {
|
|
4201
4236
|
return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
|
|
4202
4237
|
}
|
|
@@ -4405,25 +4440,32 @@ function markAction(task) {
|
|
|
4405
4440
|
return task.status === "completed" ? "uncomplete" : "complete";
|
|
4406
4441
|
}
|
|
4407
4442
|
function handlePlanKey(state, key) {
|
|
4408
|
-
const editingTask = state.expandedTaskId ? state.tasks
|
|
4443
|
+
const editingTask = state.expandedTaskId ? findTask(state.tasks, state.expandedTaskId) : void 0;
|
|
4409
4444
|
if (editingTask && state.taskEditor) return handleTaskEditKey(state, editingTask, state.taskEditor, key);
|
|
4410
|
-
if (key.name === "escape")
|
|
4445
|
+
if (key.name === "escape") {
|
|
4446
|
+
if (state.expandedTaskId) return step({ ...state, expandedTaskId: null, planScroll: null });
|
|
4447
|
+
return step({ ...state, focus: "chat" });
|
|
4448
|
+
}
|
|
4411
4449
|
if (key.name === "up") {
|
|
4412
|
-
return step({ ...state, selectedTask: Math.max(0, state.selectedTask - 1),
|
|
4450
|
+
return step({ ...state, selectedTask: Math.max(0, state.selectedTask - 1), planScroll: null });
|
|
4413
4451
|
}
|
|
4414
4452
|
if (key.name === "down") {
|
|
4415
4453
|
return step({
|
|
4416
4454
|
...state,
|
|
4417
|
-
selectedTask: Math.min(state.
|
|
4418
|
-
expandedTaskId: null,
|
|
4455
|
+
selectedTask: Math.min(planRows(state).length - 1, state.selectedTask + 1),
|
|
4419
4456
|
planScroll: null
|
|
4420
4457
|
});
|
|
4421
4458
|
}
|
|
4422
4459
|
const scroll = scrollDelta(key, state);
|
|
4423
4460
|
if (scroll !== null) return scrollPlan(state, scroll);
|
|
4424
|
-
const
|
|
4425
|
-
if (!
|
|
4461
|
+
const row = selectedPlanRow(state);
|
|
4462
|
+
if (!row) return step(state);
|
|
4463
|
+
const task = row.task;
|
|
4426
4464
|
if (key.name === "enter" || key.name === "right") {
|
|
4465
|
+
const hasSubtasks = (task.subtasks?.length ?? 0) > 0;
|
|
4466
|
+
if (hasSubtasks && state.expandedTaskId !== task.id) {
|
|
4467
|
+
return step({ ...state, expandedTaskId: task.id, taskEditor: null, planScroll: null });
|
|
4468
|
+
}
|
|
4427
4469
|
const text = task.prompt ?? task.description ?? task.title;
|
|
4428
4470
|
return step({
|
|
4429
4471
|
...state,
|
|
@@ -4672,7 +4714,7 @@ function submit(state) {
|
|
|
4672
4714
|
if (!text) return step(state);
|
|
4673
4715
|
const cleared = { ...state, editor: commit(state.editor) };
|
|
4674
4716
|
const command = parseSlash(text);
|
|
4675
|
-
if (command) return runCommand(cleared, command);
|
|
4717
|
+
if (command && findCommand(command.name)?.source !== "skill") return runCommand(cleared, command);
|
|
4676
4718
|
const spoken = say(cleared, "user", text);
|
|
4677
4719
|
const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
|
|
4678
4720
|
return step({ ...spoken, status: "planning" }, [effect]);
|
|
@@ -4834,7 +4876,7 @@ function newSession(state) {
|
|
|
4834
4876
|
}
|
|
4835
4877
|
function setModel(state, args) {
|
|
4836
4878
|
if (args[0] === "set" && args[1]) {
|
|
4837
|
-
const runner = (0,
|
|
4879
|
+
const runner = (0, import_core16.runnerForProvider)(state.plannerProvider);
|
|
4838
4880
|
const known = runner ? modelsForRunner(state.models, runner) : [];
|
|
4839
4881
|
if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
|
|
4840
4882
|
return fail2(state, `${args[1]} was not discovered for ${runner}.`);
|
|
@@ -4856,21 +4898,21 @@ function setModel(state, args) {
|
|
|
4856
4898
|
}
|
|
4857
4899
|
function plannerItems(state) {
|
|
4858
4900
|
const installed = new Set(state.runners.map((r) => r.id));
|
|
4859
|
-
const agents =
|
|
4860
|
-
const runner = (0,
|
|
4901
|
+
const agents = import_core16.CLI_PROVIDERS.map((id) => {
|
|
4902
|
+
const runner = (0, import_core16.runnerForProvider)(id);
|
|
4861
4903
|
const ready = installed.has(runner);
|
|
4862
4904
|
return {
|
|
4863
4905
|
id,
|
|
4864
|
-
label:
|
|
4906
|
+
label: import_core16.ALL_PROVIDERS[id].label,
|
|
4865
4907
|
detail: ready ? "coding agent \xB7 no API key needed" : "CLI not installed or not on PATH",
|
|
4866
4908
|
selected: state.plannerProvider === id,
|
|
4867
4909
|
disabled: !ready
|
|
4868
4910
|
};
|
|
4869
4911
|
});
|
|
4870
|
-
const vendors =
|
|
4912
|
+
const vendors = import_core16.PROVIDER_PRIORITY.filter((id) => state.configuredProviders.includes(id)).map((id) => ({
|
|
4871
4913
|
id,
|
|
4872
|
-
label:
|
|
4873
|
-
detail:
|
|
4914
|
+
label: import_core16.ALL_PROVIDERS[id].label,
|
|
4915
|
+
detail: import_core16.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
4874
4916
|
selected: state.plannerProvider === id
|
|
4875
4917
|
}));
|
|
4876
4918
|
return vendors.length > 0 ? [...agents, ...vendors] : [...agents, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
|
|
@@ -4928,16 +4970,16 @@ function setKey(state, args) {
|
|
|
4928
4970
|
}
|
|
4929
4971
|
return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
|
|
4930
4972
|
}
|
|
4931
|
-
const items =
|
|
4973
|
+
const items = import_core16.PROVIDER_PRIORITY.map((id) => ({
|
|
4932
4974
|
id,
|
|
4933
|
-
label:
|
|
4934
|
-
detail:
|
|
4975
|
+
label: import_core16.ALL_PROVIDERS[id].label,
|
|
4976
|
+
detail: import_core16.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
4935
4977
|
selected: state.configuredProviders.includes(id)
|
|
4936
4978
|
}));
|
|
4937
4979
|
return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
|
|
4938
4980
|
}
|
|
4939
4981
|
function keyPrompt(provider) {
|
|
4940
|
-
const meta =
|
|
4982
|
+
const meta = import_core16.ALL_PROVIDERS[provider];
|
|
4941
4983
|
return {
|
|
4942
4984
|
kind: "prompt",
|
|
4943
4985
|
title: `${meta.label} API key`,
|
|
@@ -5295,7 +5337,7 @@ function createApp(deps) {
|
|
|
5295
5337
|
|
|
5296
5338
|
// src/tui/effects.ts
|
|
5297
5339
|
var import_child_process4 = require("child_process");
|
|
5298
|
-
var
|
|
5340
|
+
var import_core17 = require("@ordewell/core");
|
|
5299
5341
|
var import_plan_utils4 = require("@ordewell/core/plan-utils");
|
|
5300
5342
|
var ConversationQueue = class {
|
|
5301
5343
|
tail = Promise.resolve();
|
|
@@ -5462,7 +5504,7 @@ async function perform(effect, deps) {
|
|
|
5462
5504
|
dispatch({ type: "notice", message: `Orchestrator model set to ${effect.modelId}.` });
|
|
5463
5505
|
return;
|
|
5464
5506
|
case "setPlanner": {
|
|
5465
|
-
const meta =
|
|
5507
|
+
const meta = import_core17.ALL_PROVIDERS[effect.provider];
|
|
5466
5508
|
if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
|
|
5467
5509
|
const env = { AI_PROVIDER: effect.provider };
|
|
5468
5510
|
const settings = await api.updateSettings({ env });
|
|
@@ -5474,7 +5516,7 @@ async function perform(effect, deps) {
|
|
|
5474
5516
|
});
|
|
5475
5517
|
dispatch({
|
|
5476
5518
|
type: "notice",
|
|
5477
|
-
message: `${(0,
|
|
5519
|
+
message: `${(0, import_core17.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
|
|
5478
5520
|
});
|
|
5479
5521
|
await loadModels(deps);
|
|
5480
5522
|
return;
|
|
@@ -5486,7 +5528,7 @@ async function perform(effect, deps) {
|
|
|
5486
5528
|
dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
|
|
5487
5529
|
return;
|
|
5488
5530
|
case "setApiKey": {
|
|
5489
|
-
const meta =
|
|
5531
|
+
const meta = import_core17.ALL_PROVIDERS[effect.provider];
|
|
5490
5532
|
if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
|
|
5491
5533
|
await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
|
|
5492
5534
|
deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
|
|
@@ -5574,7 +5616,7 @@ function osc52(text) {
|
|
|
5574
5616
|
function copySelection(deps, text) {
|
|
5575
5617
|
if (!text) return;
|
|
5576
5618
|
const lines = text.split("\n").length;
|
|
5577
|
-
const command = (0,
|
|
5619
|
+
const command = (0, import_core17.clipboardCopyCommand)(deps.hasBin);
|
|
5578
5620
|
if (command) {
|
|
5579
5621
|
try {
|
|
5580
5622
|
(deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
|
|
@@ -5607,7 +5649,28 @@ async function withExecutionStream(deps, sessionId, request) {
|
|
|
5607
5649
|
}
|
|
5608
5650
|
async function converse(deps, sessionId, call) {
|
|
5609
5651
|
let streamed = null;
|
|
5652
|
+
const TOKEN_DEBOUNCE_MS = 75;
|
|
5653
|
+
let tokenBuffer = "";
|
|
5654
|
+
let tokenTimer = null;
|
|
5655
|
+
const flushTokens = () => {
|
|
5656
|
+
if (tokenTimer !== null) {
|
|
5657
|
+
clearTimeout(tokenTimer);
|
|
5658
|
+
tokenTimer = null;
|
|
5659
|
+
}
|
|
5660
|
+
if (!tokenBuffer) return;
|
|
5661
|
+
const text = tokenBuffer;
|
|
5662
|
+
tokenBuffer = "";
|
|
5663
|
+
deps.dispatch({ type: "plannerToken", text, sessionId });
|
|
5664
|
+
};
|
|
5610
5665
|
const stream = deps.api.streamPlanning(sessionId, (event) => {
|
|
5666
|
+
if (event?.type === "plan_token" && event.token) {
|
|
5667
|
+
tokenBuffer += event.token;
|
|
5668
|
+
if (tokenTimer === null) {
|
|
5669
|
+
tokenTimer = setTimeout(flushTokens, TOKEN_DEBOUNCE_MS);
|
|
5670
|
+
}
|
|
5671
|
+
return;
|
|
5672
|
+
}
|
|
5673
|
+
flushTokens();
|
|
5611
5674
|
if (event?.type === "approval_request") {
|
|
5612
5675
|
deps.dispatch({
|
|
5613
5676
|
type: "approvalRequested",
|
|
@@ -5664,6 +5727,7 @@ async function converse(deps, sessionId, call) {
|
|
|
5664
5727
|
deps.dispatch({ type: "plannerMessage", content: question, sessionId });
|
|
5665
5728
|
}
|
|
5666
5729
|
} finally {
|
|
5730
|
+
if (tokenTimer !== null) clearTimeout(tokenTimer);
|
|
5667
5731
|
stream.close();
|
|
5668
5732
|
}
|
|
5669
5733
|
}
|
|
@@ -5683,7 +5747,7 @@ function onExecutionEvent(dispatch, event, sessionId) {
|
|
|
5683
5747
|
case "status_update": {
|
|
5684
5748
|
const updates = {};
|
|
5685
5749
|
for (const task of event.tasks ?? []) {
|
|
5686
|
-
updates[String(task.id)] = String(task.status);
|
|
5750
|
+
updates[String(task.id)] = { status: String(task.status), idleSince: task.idleSince ?? null };
|
|
5687
5751
|
}
|
|
5688
5752
|
dispatch({ type: "tasksStatus", updates, sessionId });
|
|
5689
5753
|
return;
|
|
@@ -5694,7 +5758,7 @@ function onExecutionEvent(dispatch, event, sessionId) {
|
|
|
5694
5758
|
// The extension shows these in its checkpoint panel; here they are
|
|
5695
5759
|
// transcript lines, which is the TUI's equivalent surface.
|
|
5696
5760
|
case "checkpoint":
|
|
5697
|
-
dispatch({ type: "notice", message:
|
|
5761
|
+
dispatch({ type: "notice", message: `\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core17.truncateCheckpointSummary)(event.summary)}` });
|
|
5698
5762
|
return;
|
|
5699
5763
|
case "approval_decided":
|
|
5700
5764
|
dispatch({ type: "notice", message: describeApprovalDecision2(event) });
|
|
@@ -6047,7 +6111,7 @@ async function handleTui(subArgs) {
|
|
|
6047
6111
|
}
|
|
6048
6112
|
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
6049
6113
|
try {
|
|
6050
|
-
(0,
|
|
6114
|
+
(0, import_core18.assertWorkspaceExists)(workspace);
|
|
6051
6115
|
} catch {
|
|
6052
6116
|
console.error(
|
|
6053
6117
|
`Workspace "${workspace}" does not exist.
|
|
@@ -6056,7 +6120,10 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
6056
6120
|
);
|
|
6057
6121
|
process.exit(1);
|
|
6058
6122
|
}
|
|
6059
|
-
|
|
6123
|
+
registerSkillCommands((0, import_core18.createSkillsService)(workspace).listSkills());
|
|
6124
|
+
const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
|
|
6125
|
+
const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
|
|
6126
|
+
const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
|
|
6060
6127
|
let owned = ownedAtLaunch;
|
|
6061
6128
|
const api = new ApiClient(port);
|
|
6062
6129
|
const conversationQueue = new ConversationQueue();
|
|
@@ -6072,7 +6139,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
6072
6139
|
conversationQueue,
|
|
6073
6140
|
port,
|
|
6074
6141
|
dispatch: (action) => app.dispatch(action),
|
|
6075
|
-
newSessionId: () => (0,
|
|
6142
|
+
newSessionId: () => (0, import_core18.mintSessionId)(),
|
|
6076
6143
|
setEnvVar: (key, value) => {
|
|
6077
6144
|
writeEnvVar(findEnvFile(), key, value);
|
|
6078
6145
|
process.env[key] = value;
|
|
@@ -6141,14 +6208,8 @@ var COMMANDS = {
|
|
|
6141
6208
|
refresh: handleRefresh,
|
|
6142
6209
|
setup: handleSetup,
|
|
6143
6210
|
plugins: handlePlugins,
|
|
6144
|
-
"grill-me": handleGrillMe,
|
|
6145
6211
|
"tdd": handleTdd,
|
|
6146
|
-
"prd": handlePrd,
|
|
6147
|
-
"review": handleReview,
|
|
6148
|
-
// 'verify' was once a legacy alias for review; it now toggles the
|
|
6149
|
-
// evidence-based verification mode.
|
|
6150
6212
|
"verify": handleVerify,
|
|
6151
|
-
"research-subagents": handleResearchSubagents,
|
|
6152
6213
|
"mark-complete": handleMarkComplete,
|
|
6153
6214
|
complete: handleMarkComplete,
|
|
6154
6215
|
uncomplete: handleUncomplete,
|
|
@@ -6184,6 +6245,7 @@ function cliVersion() {
|
|
|
6184
6245
|
|
|
6185
6246
|
// src/main.ts
|
|
6186
6247
|
async function main() {
|
|
6248
|
+
(0, import_core19.migrateOldConfigDir)();
|
|
6187
6249
|
loadEnvFile();
|
|
6188
6250
|
const argv = process.argv.slice(2);
|
|
6189
6251
|
const command = argv[0] ?? (process.stdin.isTTY ? "tui" : "--help");
|