@ordewell/cli 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/main.js +571 -878
- package/package.json +3 -3
package/dist/main.js
CHANGED
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/main.ts
|
|
27
|
-
var
|
|
27
|
+
var import_core38 = require("@ordewell/core");
|
|
28
28
|
|
|
29
29
|
// src/utils/env.ts
|
|
30
30
|
var import_fs = require("fs");
|
|
@@ -100,7 +100,7 @@ an id.
|
|
|
100
100
|
Planning:
|
|
101
101
|
ordewell Full-screen terminal UI (everything the VS Code extension does)
|
|
102
102
|
ordewell tui The same thing, named \u2014 for scripts and aliases
|
|
103
|
-
ordewell plan --goal "text" Plan a goal conversationally
|
|
103
|
+
ordewell plan --goal "text" Plan a goal conversationally
|
|
104
104
|
ordewell run Execute the last generated plan (streams status)
|
|
105
105
|
ordewell approve Sign off a plan paused for review, and continue it
|
|
106
106
|
ordewell stop Stop execution of the last session (or --server)
|
|
@@ -123,7 +123,6 @@ Tasks (<id> is an order number or a task ID):
|
|
|
123
123
|
ordewell continue <id> <msg> Continue a finished task in its saved runner session
|
|
124
124
|
ordewell checkpoint <id> approve|reject [reason] Answer the checkpoint a task waits at
|
|
125
125
|
ordewell cancel <id> Kill a running task
|
|
126
|
-
ordewell terminal <id> Open a real terminal attached to a task's runner
|
|
127
126
|
|
|
128
127
|
Per-task assignment (omit the value to list the options):
|
|
129
128
|
ordewell task-runner <id> [runner] Set a task's executor (re-picks its model, effort and mode)
|
|
@@ -131,6 +130,7 @@ Per-task assignment (omit the value to list the options):
|
|
|
131
130
|
ordewell task-effort <id> [level] Set a task's thinking effort ("default" to clear)
|
|
132
131
|
ordewell task-mode <id> [mode] Set a task's runner mode
|
|
133
132
|
ordewell task-deps <id> [a,b|none] Set which earlier tasks a task waits for
|
|
133
|
+
ordewell task-skills <id> [a,b|none] Set the skills attached to a task (applies-to: task); none clears them
|
|
134
134
|
ordewell task-ops <id> [on|off] Make a task an ops task (runs in your checkout once its dependencies are merged) or a change task
|
|
135
135
|
|
|
136
136
|
Planner, models and runners:
|
|
@@ -141,19 +141,14 @@ Planner, models and runners:
|
|
|
141
141
|
ordewell runners [<id> on|off] Enable or disable runners (claude-code, opencode, codex)
|
|
142
142
|
ordewell allowlist set|clear|show Limit which models a runner may use
|
|
143
143
|
ordewell auto [full|guarded] Autonomy level for new sessions: Full or Guarded
|
|
144
|
-
ordewell transport [terminal|structured] Structured (default) or terminal task transport, from the next run
|
|
145
144
|
ordewell parallel [<n>] How many AI tasks run at once (default 3; applies to a live run)
|
|
146
145
|
ordewell refresh Re-discover runners and model catalogs
|
|
147
146
|
ordewell models List every provider's catalog (works without a server)
|
|
148
147
|
|
|
149
|
-
Modes:
|
|
150
|
-
ordewell tdd [on|off] Toggle Test-Driven Development mode (or show status)
|
|
151
|
-
ordewell verify [on|off] Toggle verification mode \u2014 adds a final evidence-based task that runs the full suite (or show status)
|
|
152
|
-
|
|
153
148
|
Other:
|
|
154
149
|
ordewell web Start the API server (foreground; --daemon for background)
|
|
155
150
|
ordewell setup Interactive first-run setup wizard
|
|
156
|
-
ordewell
|
|
151
|
+
ordewell skills List installed skills and shadowed workspace duplicates
|
|
157
152
|
ordewell --help Show this help
|
|
158
153
|
ordewell --version Print the installed version (alias: version, -v)
|
|
159
154
|
|
|
@@ -171,7 +166,7 @@ Plan options:
|
|
|
171
166
|
--goal "text" Task description (required)
|
|
172
167
|
--runner <id> Runner to use (repeatable; default: all enabled runners)
|
|
173
168
|
--workspace /path Workspace directory (default: cwd)
|
|
174
|
-
--no-chat One-shot plan; skips the planner dialogue
|
|
169
|
+
--no-chat One-shot plan; skips the planner dialogue
|
|
175
170
|
|
|
176
171
|
Run options:
|
|
177
172
|
--session-id <id> Execute a specific session (default: last session)
|
|
@@ -185,6 +180,10 @@ Sessions options:
|
|
|
185
180
|
--workspace /path Workspace to list sessions from (default: cwd)
|
|
186
181
|
--json Output the list as JSON
|
|
187
182
|
|
|
183
|
+
Skills options:
|
|
184
|
+
--workspace /path Workspace to read skills from (default: cwd)
|
|
185
|
+
--json Output resolved skills and shadowed duplicates as JSON
|
|
186
|
+
|
|
188
187
|
Task options (add-task):
|
|
189
188
|
--title "text" Task title (required)
|
|
190
189
|
--description "..." Task description (default: the title)
|
|
@@ -247,13 +246,13 @@ function oldPidFileFor(port) {
|
|
|
247
246
|
return port === DEFAULT_PORT ? (0, import_path2.join)(OLD_CONFIG_DIR, "server.pid") : (0, import_path2.join)(OLD_CONFIG_DIR, `server-${port}.pid`);
|
|
248
247
|
}
|
|
249
248
|
function httpGet(url, timeoutMs = 2e3, headers = {}) {
|
|
250
|
-
return new Promise((
|
|
249
|
+
return new Promise((resolve4, reject) => {
|
|
251
250
|
const req = import_http.default.get(url, { timeout: timeoutMs, headers }, (res) => {
|
|
252
251
|
let data = "";
|
|
253
252
|
res.on("data", (chunk) => {
|
|
254
253
|
data += chunk.toString();
|
|
255
254
|
});
|
|
256
|
-
res.on("end", () =>
|
|
255
|
+
res.on("end", () => resolve4({ status: res.statusCode || 0, data }));
|
|
257
256
|
});
|
|
258
257
|
req.on("error", reject);
|
|
259
258
|
req.on("timeout", () => {
|
|
@@ -406,15 +405,30 @@ var import_http2 = __toESM(require("http"));
|
|
|
406
405
|
var import_ws = __toESM(require("ws"));
|
|
407
406
|
var import_core3 = require("@ordewell/core");
|
|
408
407
|
var DEFAULT_HTTP_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
409
|
-
var
|
|
410
|
-
constructor(message,
|
|
408
|
+
var DaemonError = class extends Error {
|
|
409
|
+
constructor(message, status, code) {
|
|
411
410
|
super(message);
|
|
411
|
+
this.status = status;
|
|
412
|
+
this.code = code;
|
|
413
|
+
this.name = "DaemonError";
|
|
414
|
+
}
|
|
415
|
+
status;
|
|
416
|
+
code;
|
|
417
|
+
};
|
|
418
|
+
var WorkspaceInitNeededError = class extends DaemonError {
|
|
419
|
+
constructor(message, workspace) {
|
|
420
|
+
super(message, 400, "workspace_not_a_project");
|
|
412
421
|
this.workspace = workspace;
|
|
413
422
|
this.name = "WorkspaceInitNeededError";
|
|
414
423
|
}
|
|
415
424
|
workspace;
|
|
416
425
|
};
|
|
417
426
|
var SESSION_PATH = /^\/api\/(?:plans|sessions)\/([^/?]+)/;
|
|
427
|
+
function daemonError(status, data, fallback) {
|
|
428
|
+
const { error, code, workspace } = typeof data === "object" && data !== null ? data : {};
|
|
429
|
+
if (code === "workspace_not_a_project") return new WorkspaceInitNeededError(error || "Not a project directory", workspace ?? "");
|
|
430
|
+
return new DaemonError(error || fallback, status, code);
|
|
431
|
+
}
|
|
418
432
|
var ApiClient = class _ApiClient {
|
|
419
433
|
/** Where a CLI invocation's sessions live, when a client is built without one: `--workspace`, else the cwd. */
|
|
420
434
|
static defaultWorkspace;
|
|
@@ -422,6 +436,7 @@ var ApiClient = class _ApiClient {
|
|
|
422
436
|
workspace;
|
|
423
437
|
/** A session's open execution stream, so a new one can end it: one run is reported once. */
|
|
424
438
|
executionStreams = /* @__PURE__ */ new Map();
|
|
439
|
+
executionStreamEnds = /* @__PURE__ */ new WeakMap();
|
|
425
440
|
constructor(port, workspace) {
|
|
426
441
|
this.port = port || DEFAULT_PORT;
|
|
427
442
|
this.workspace = workspace ?? _ApiClient.defaultWorkspace ?? process.cwd();
|
|
@@ -436,7 +451,7 @@ var ApiClient = class _ApiClient {
|
|
|
436
451
|
}
|
|
437
452
|
/**
|
|
438
453
|
* A daemon holds only the sessions adopted since it started, so after a
|
|
439
|
-
* restart every session-scoped call answered "
|
|
454
|
+
* restart every session-scoped call answered "session not found" until the
|
|
440
455
|
* user ran `ordewell sessions load`. The saved session is adopted here
|
|
441
456
|
* instead — idempotent for one the daemon still holds — and the call made
|
|
442
457
|
* once more; a 404 means the first attempt never ran, so the retry cannot
|
|
@@ -445,13 +460,19 @@ var ApiClient = class _ApiClient {
|
|
|
445
460
|
async httpRequest(method, urlPath, body) {
|
|
446
461
|
const res = await this.rawRequest(method, urlPath, body);
|
|
447
462
|
const sessionId = urlPath.match(SESSION_PATH)?.[1];
|
|
448
|
-
const missing = res.status === 404 && res.data?.
|
|
463
|
+
const missing = res.status === 404 && res.data?.code === "session_not_found";
|
|
449
464
|
if (!missing || !sessionId || urlPath.split("?")[0].endsWith("/load")) return res;
|
|
450
465
|
const adopted2 = await this.rawRequest("POST", `/api/sessions/${sessionId}/load?workspace=${encodeURIComponent(this.workspace)}`);
|
|
451
466
|
return adopted2.status === 200 ? this.rawRequest(method, urlPath, body) : res;
|
|
452
467
|
}
|
|
468
|
+
/** One request whose only good answer is a 200: anything else becomes a {@link DaemonError} carrying the daemon's code. */
|
|
469
|
+
async call(method, urlPath, fallback, body) {
|
|
470
|
+
const res = await this.httpRequest(method, urlPath, body);
|
|
471
|
+
if (res.status !== 200) throw daemonError(res.status, res.data, fallback);
|
|
472
|
+
return res.data;
|
|
473
|
+
}
|
|
453
474
|
rawRequest(method, urlPath, body) {
|
|
454
|
-
return new Promise((
|
|
475
|
+
return new Promise((resolve4, reject) => {
|
|
455
476
|
const url = new URL(urlPath, `http://127.0.0.1:${this.port}`);
|
|
456
477
|
const configuredTimeout = Number.parseInt(process.env.ORDEWELL_HTTP_TIMEOUT_MS || "", 10);
|
|
457
478
|
const token = this.token();
|
|
@@ -476,9 +497,9 @@ var ApiClient = class _ApiClient {
|
|
|
476
497
|
});
|
|
477
498
|
res.on("end", () => {
|
|
478
499
|
try {
|
|
479
|
-
|
|
500
|
+
resolve4({ status: res.statusCode || 0, data: JSON.parse(data) });
|
|
480
501
|
} catch {
|
|
481
|
-
|
|
502
|
+
resolve4({ status: res.statusCode || 0, data });
|
|
482
503
|
}
|
|
483
504
|
});
|
|
484
505
|
});
|
|
@@ -492,74 +513,43 @@ var ApiClient = class _ApiClient {
|
|
|
492
513
|
});
|
|
493
514
|
}
|
|
494
515
|
async generatePlan(goal, runners2, workspace, sessionId = (0, import_core3.mintSessionId)()) {
|
|
495
|
-
const
|
|
516
|
+
const { plan, models, modelsByRunner, notes } = await this.call("POST", `/api/plans/${sessionId}/generate`, "Plan generation failed", {
|
|
496
517
|
goal,
|
|
497
518
|
runners: runners2 || void 0,
|
|
498
519
|
workspace
|
|
499
520
|
});
|
|
500
|
-
|
|
501
|
-
throw new Error(res.data?.error || "Plan generation failed");
|
|
502
|
-
}
|
|
503
|
-
return { sessionId, plan: res.data.plan, models: res.data.models, modelsByRunner: res.data.modelsByRunner };
|
|
521
|
+
return { sessionId, plan, models, modelsByRunner, ...notes ? { notes } : {} };
|
|
504
522
|
}
|
|
505
523
|
/**
|
|
506
|
-
* Open the ADR-0002 planner dialogue. Unlike `generatePlan`,
|
|
507
|
-
*
|
|
508
|
-
*
|
|
524
|
+
* Open the ADR-0002 planner dialogue. Unlike `generatePlan`, the returned plan
|
|
525
|
+
* may be a question (empty `tasks`, last word in `conversationHistory`) rather
|
|
526
|
+
* than a committed plan.
|
|
509
527
|
*/
|
|
510
528
|
async startConversation(sessionId, goal, runners2, workspace, allowInit) {
|
|
511
|
-
const res = await this.
|
|
529
|
+
const res = await this.call("POST", `/api/plans/${sessionId}/converse/start`, "Planning failed", {
|
|
512
530
|
goal,
|
|
513
531
|
runners: runners2 || void 0,
|
|
514
532
|
workspace,
|
|
515
533
|
allowInit
|
|
516
534
|
});
|
|
517
|
-
|
|
518
|
-
if (res.data?.code === "workspace_not_a_project") {
|
|
519
|
-
throw new WorkspaceInitNeededError(res.data.error || "Not a project directory", res.data.workspace || workspace || "");
|
|
520
|
-
}
|
|
521
|
-
throw new Error(res.data?.error || "Planning failed");
|
|
522
|
-
}
|
|
523
|
-
return res.data.plan;
|
|
535
|
+
return res.plan;
|
|
524
536
|
}
|
|
525
537
|
async sendConversationMessage(sessionId, message) {
|
|
526
|
-
const res = await this.
|
|
527
|
-
|
|
528
|
-
throw new Error(res.data?.error || "Planner message failed");
|
|
529
|
-
}
|
|
530
|
-
return res.data.plan;
|
|
538
|
+
const res = await this.call("POST", `/api/plans/${sessionId}/converse/message`, "Planner message failed", { message });
|
|
539
|
+
return res.plan;
|
|
531
540
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (res.status !== 200) {
|
|
535
|
-
throw new Error(res.data?.error || "Failed to fetch runners");
|
|
536
|
-
}
|
|
537
|
-
return res.data;
|
|
541
|
+
getRunners() {
|
|
542
|
+
return this.call("GET", "/api/runners", "Failed to fetch runners");
|
|
538
543
|
}
|
|
539
544
|
/** The full provider catalog the daemon has discovered (used by the TUI model picker). */
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if (res.status !== 200) {
|
|
543
|
-
throw new Error(res.data?.error || "Failed to fetch models");
|
|
544
|
-
}
|
|
545
|
-
return res.data;
|
|
545
|
+
getModels() {
|
|
546
|
+
return this.call("GET", "/api/models", "Failed to fetch models");
|
|
546
547
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
if (res.status !== 200) {
|
|
550
|
-
throw new Error(res.data?.error || `Failed to ${enabled ? "enable" : "disable"} ${runner}`);
|
|
551
|
-
}
|
|
552
|
-
return res.data;
|
|
548
|
+
setRunnerEnabled(runner, enabled) {
|
|
549
|
+
return this.call("PUT", `/api/runners/${runner}`, `Failed to ${enabled ? "enable" : "disable"} ${runner}`, { enabled });
|
|
553
550
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
"POST",
|
|
557
|
-
`/api/plans/${sessionId}/execute`
|
|
558
|
-
);
|
|
559
|
-
if (res.status !== 200) {
|
|
560
|
-
throw new Error(res.data?.error || "Execute failed");
|
|
561
|
-
}
|
|
562
|
-
return res.data;
|
|
551
|
+
executePlan(sessionId) {
|
|
552
|
+
return this.call("POST", `/api/plans/${sessionId}/execute`, "Execute failed");
|
|
563
553
|
}
|
|
564
554
|
/**
|
|
565
555
|
* Sign off a plan waiting on `review_needed` and let it continue.
|
|
@@ -568,228 +558,111 @@ var ApiClient = class _ApiClient {
|
|
|
568
558
|
* `resetForRun`) before approving, which is right for starting a plan and
|
|
569
559
|
* wrong for releasing one that is already part-way through a review pause.
|
|
570
560
|
*/
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
"POST",
|
|
574
|
-
`/api/plans/${sessionId}/review/approve`
|
|
575
|
-
);
|
|
576
|
-
if (res.status !== 200) {
|
|
577
|
-
throw new Error(res.data?.error || "Approve failed");
|
|
578
|
-
}
|
|
579
|
-
return res.data;
|
|
561
|
+
approveReview(sessionId) {
|
|
562
|
+
return this.call("POST", `/api/plans/${sessionId}/review/approve`, "Approve failed");
|
|
580
563
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
"POST",
|
|
584
|
-
`/api/plans/${sessionId}/tasks/${taskId}/complete`
|
|
585
|
-
);
|
|
586
|
-
if (res.status !== 200) {
|
|
587
|
-
throw new Error(res.data?.error || "Mark complete failed");
|
|
588
|
-
}
|
|
589
|
-
return res.data;
|
|
564
|
+
markTaskComplete(sessionId, taskId) {
|
|
565
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/complete`, "Mark complete failed");
|
|
590
566
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
"POST",
|
|
594
|
-
`/api/plans/${sessionId}/tasks/${taskId}/uncomplete`
|
|
595
|
-
);
|
|
596
|
-
if (res.status !== 200) {
|
|
597
|
-
throw new Error(res.data?.error || "Mark not done failed");
|
|
598
|
-
}
|
|
599
|
-
return res.data;
|
|
567
|
+
markTaskIncomplete(sessionId, taskId) {
|
|
568
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/uncomplete`, "Mark not done failed");
|
|
600
569
|
}
|
|
601
570
|
/** run | force-start | retry | cancel — real orchestrator work, not a status patch. */
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
"POST",
|
|
605
|
-
`/api/plans/${sessionId}/tasks/${taskId}/${action}`
|
|
606
|
-
);
|
|
607
|
-
if (res.status !== 200) {
|
|
608
|
-
throw new Error(res.data?.error || `${action} failed`);
|
|
609
|
-
}
|
|
610
|
-
return res.data;
|
|
571
|
+
taskControl(sessionId, taskId, action) {
|
|
572
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/${action}`, `${action} failed`);
|
|
611
573
|
}
|
|
612
574
|
/** A user message to a structured task: delivered now if it waits for input, else queued behind its turn. */
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if (res.status !== 200) {
|
|
616
|
-
throw new Error(res.data?.error || "Send message failed");
|
|
617
|
-
}
|
|
618
|
-
return res.data;
|
|
575
|
+
sendTaskMessage(sessionId, taskId, text) {
|
|
576
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages`, "Send message failed", { text });
|
|
619
577
|
}
|
|
620
578
|
/** Force send: interrupt the task's running turn and deliver `text` next, ahead of anything queued (ADR-0023, F1). */
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (res.status !== 200) {
|
|
624
|
-
throw new Error(res.data?.error || "Send now failed");
|
|
625
|
-
}
|
|
626
|
-
return res.data;
|
|
579
|
+
forceSendTaskMessage(sessionId, taskId, text) {
|
|
580
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages/now`, "Send now failed", { text });
|
|
627
581
|
}
|
|
628
582
|
/** Force send a message still queued; `sent` is false once the runner already has it. */
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
"POST",
|
|
632
|
-
`/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}/now`
|
|
633
|
-
);
|
|
634
|
-
if (res.status !== 200) {
|
|
635
|
-
throw new Error(res.data?.error || "Send now failed");
|
|
636
|
-
}
|
|
637
|
-
return res.data;
|
|
583
|
+
forceSendQueuedTaskMessage(sessionId, taskId, messageId) {
|
|
584
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}/now`, "Send now failed");
|
|
638
585
|
}
|
|
639
586
|
/** Continue a finished structured task in its saved session, with `text` as its next turn. */
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
if (res.status !== 200) {
|
|
643
|
-
throw new Error(res.data?.error || "Continue failed");
|
|
644
|
-
}
|
|
645
|
-
return res.data;
|
|
587
|
+
continueTask(sessionId, taskId, text) {
|
|
588
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/continue`, "Continue failed", { text });
|
|
646
589
|
}
|
|
647
590
|
/** `removed` is false once the message was already delivered. */
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
"DELETE",
|
|
651
|
-
`/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}`
|
|
652
|
-
);
|
|
653
|
-
if (res.status !== 200) {
|
|
654
|
-
throw new Error(res.data?.error || "Remove message failed");
|
|
655
|
-
}
|
|
656
|
-
return res.data;
|
|
591
|
+
removeQueuedTaskMessage(sessionId, taskId, messageId) {
|
|
592
|
+
return this.call("DELETE", `/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}`, "Remove message failed");
|
|
657
593
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (res.status !== 200) {
|
|
661
|
-
throw new Error(res.data?.error || "Interrupt failed");
|
|
662
|
-
}
|
|
663
|
-
return res.data;
|
|
594
|
+
interruptTask(sessionId, taskId) {
|
|
595
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/interrupt`, "Interrupt failed");
|
|
664
596
|
}
|
|
665
597
|
/** Let a task go on from the checkpoint it waits at; refused, with the reason, when none waits. */
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
if (res.status !== 200) {
|
|
669
|
-
throw new Error(res.data?.error || "Approve checkpoint failed");
|
|
670
|
-
}
|
|
671
|
-
return res.data;
|
|
598
|
+
approveTaskCheckpoint(sessionId, taskId) {
|
|
599
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/checkpoint/approve`, "Approve checkpoint failed");
|
|
672
600
|
}
|
|
673
601
|
/** Turn a task back at its checkpoint; `reason` is what the agent is told. */
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
"POST",
|
|
677
|
-
`/api/plans/${sessionId}/tasks/${taskId}/checkpoint/reject`,
|
|
678
|
-
reason ? { reason } : {}
|
|
679
|
-
);
|
|
680
|
-
if (res.status !== 200) {
|
|
681
|
-
throw new Error(res.data?.error || "Reject checkpoint failed");
|
|
682
|
-
}
|
|
683
|
-
return res.data;
|
|
602
|
+
rejectTaskCheckpoint(sessionId, taskId, reason) {
|
|
603
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/checkpoint/reject`, "Reject checkpoint failed", reason ? { reason } : {});
|
|
684
604
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
if (res.status !== 200) {
|
|
688
|
-
throw new Error(res.data?.error || "Add task failed");
|
|
689
|
-
}
|
|
690
|
-
return res.data;
|
|
605
|
+
addTask(sessionId, task) {
|
|
606
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks`, "Add task failed", task);
|
|
691
607
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if (res.status !== 200) {
|
|
695
|
-
throw new Error(res.data?.error || "Update task failed");
|
|
696
|
-
}
|
|
697
|
-
return res.data;
|
|
608
|
+
updateTask(sessionId, taskId, changes) {
|
|
609
|
+
return this.call("PUT", `/api/plans/${sessionId}/tasks/${taskId}`, "Update task failed", changes);
|
|
698
610
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (res.status !== 200) {
|
|
702
|
-
throw new Error(res.data?.error || "Remove task failed");
|
|
703
|
-
}
|
|
704
|
-
return res.data;
|
|
611
|
+
removeTask(sessionId, taskId) {
|
|
612
|
+
return this.call("DELETE", `/api/plans/${sessionId}/tasks/${taskId}`, "Remove task failed");
|
|
705
613
|
}
|
|
706
614
|
/**
|
|
707
615
|
* Make a saved session live on the server. `getSession` only reads the file;
|
|
708
616
|
* until the session is adopted there is no orchestrator behind it, so
|
|
709
|
-
* execute/retry/cancel answer
|
|
617
|
+
* execute/retry/cancel answer `session_not_found`.
|
|
710
618
|
*/
|
|
711
619
|
async adoptSession(sessionId, workspace) {
|
|
712
620
|
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
713
|
-
const res = await this.
|
|
714
|
-
|
|
715
|
-
throw new Error(res.data?.error || "Failed to load session");
|
|
716
|
-
}
|
|
717
|
-
return { plan: res.data.plan, goal: res.data.goal ?? "" };
|
|
621
|
+
const res = await this.call("POST", `/api/sessions/${sessionId}/load${qs}`, "Failed to load session");
|
|
622
|
+
return { plan: res.plan, goal: res.goal ?? "" };
|
|
718
623
|
}
|
|
719
|
-
|
|
624
|
+
deleteSession(sessionId, workspace) {
|
|
720
625
|
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
721
|
-
|
|
722
|
-
if (res.status !== 200) {
|
|
723
|
-
throw new Error(res.data?.error || "Delete session failed");
|
|
724
|
-
}
|
|
725
|
-
return res.data;
|
|
626
|
+
return this.call("DELETE", `/api/sessions/${sessionId}${qs}`, "Delete session failed");
|
|
726
627
|
}
|
|
727
628
|
/** Copy the conversation and its tasks into a new session the daemon has already adopted. */
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if (res.status !== 200) {
|
|
731
|
-
throw new Error(res.data?.error || "Fork failed");
|
|
732
|
-
}
|
|
733
|
-
return { sessionId: res.data.sessionId, goal: res.data.goal, plan: res.data.plan };
|
|
629
|
+
forkConversation(sessionId) {
|
|
630
|
+
return this.call("POST", `/api/plans/${sessionId}/conversation/fork`, "Fork failed");
|
|
734
631
|
}
|
|
735
632
|
async rewindTargets(sessionId) {
|
|
736
|
-
const res = await this.
|
|
737
|
-
|
|
738
|
-
throw new Error(res.data?.error || "Failed to list rewind targets");
|
|
739
|
-
}
|
|
740
|
-
return res.data.targets;
|
|
633
|
+
const res = await this.call("GET", `/api/plans/${sessionId}/conversation/rewind-targets`, "Failed to list rewind targets");
|
|
634
|
+
return res.targets;
|
|
741
635
|
}
|
|
742
636
|
/**
|
|
743
637
|
* Fork the conversation from just before the user message at `index` (a
|
|
744
638
|
* transcript position) into a session the daemon has already adopted. The
|
|
745
639
|
* original is left as it was; `rewoundMessage` is that message in full.
|
|
746
640
|
*/
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
if (res.status !== 200) {
|
|
750
|
-
throw new Error(res.data?.error || "Rewind failed");
|
|
751
|
-
}
|
|
752
|
-
return { sessionId: res.data.sessionId, goal: res.data.goal, plan: res.data.plan, rewoundMessage: res.data.rewoundMessage };
|
|
641
|
+
rewindConversation(sessionId, index) {
|
|
642
|
+
return this.call("POST", `/api/plans/${sessionId}/conversation/rewind`, "Rewind failed", { index });
|
|
753
643
|
}
|
|
754
644
|
/**
|
|
755
645
|
* Condense the conversation into a summary. One planner call, so it can take
|
|
756
646
|
* as long as a reply; a refusal or failure leaves the conversation as it was.
|
|
757
647
|
*/
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
if (res.status !== 200) {
|
|
761
|
-
throw new Error(res.data?.error || "Compaction failed");
|
|
762
|
-
}
|
|
763
|
-
return { plan: res.data.plan, summary: res.data.summary, keptMessages: res.data.keptMessages };
|
|
648
|
+
compactConversation(sessionId) {
|
|
649
|
+
return this.call("POST", `/api/plans/${sessionId}/conversation/compact`, "Compaction failed");
|
|
764
650
|
}
|
|
765
651
|
/** The dependencies a task waits on at its merge gate (ADR-0020); empty when nothing gates it. */
|
|
766
652
|
async getMergeGate(sessionId, taskId) {
|
|
767
|
-
const res = await this.
|
|
768
|
-
|
|
769
|
-
`/api/plans/${sessionId}/tasks/${taskId}/merge-gate`
|
|
770
|
-
);
|
|
771
|
-
if (res.status !== 200) {
|
|
772
|
-
throw new Error(res.data?.error || "Could not read the merge gate");
|
|
773
|
-
}
|
|
774
|
-
return res.data.mergeGate ?? [];
|
|
653
|
+
const res = await this.call("GET", `/api/plans/${sessionId}/tasks/${taskId}/merge-gate`, "Could not read the merge gate");
|
|
654
|
+
return res.mergeGate ?? [];
|
|
775
655
|
}
|
|
776
656
|
async reviewRunDiff(sessionId) {
|
|
777
|
-
const res = await this.
|
|
778
|
-
|
|
779
|
-
throw new Error(res.data?.error || "Could not read the diff");
|
|
780
|
-
}
|
|
781
|
-
return res.data.diff;
|
|
657
|
+
const res = await this.call("GET", `/api/plans/${sessionId}/isolation/diff`, "Could not read the diff");
|
|
658
|
+
return res.diff;
|
|
782
659
|
}
|
|
783
660
|
/**
|
|
784
661
|
* A conflict or a block is an outcome, not an error. Passed on whole: which
|
|
785
662
|
* repos blocked the merge, or landed before it stopped, is the answer.
|
|
786
663
|
*/
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
if (res.status !== 200) {
|
|
790
|
-
throw new Error(res.data?.error || "Merge failed");
|
|
791
|
-
}
|
|
792
|
-
return res.data;
|
|
664
|
+
mergeRun(sessionId) {
|
|
665
|
+
return this.call("POST", `/api/plans/${sessionId}/isolation/merge`, "Merge failed");
|
|
793
666
|
}
|
|
794
667
|
discardRun(sessionId) {
|
|
795
668
|
return this.isolationAction(sessionId, "discard", "Discard failed");
|
|
@@ -804,17 +677,11 @@ var ApiClient = class _ApiClient {
|
|
|
804
677
|
return this.isolationAction(sessionId, "run-without", "Could not continue without isolation");
|
|
805
678
|
}
|
|
806
679
|
async isolationAction(sessionId, segment, failure) {
|
|
807
|
-
|
|
808
|
-
if (res.status !== 200) {
|
|
809
|
-
throw new Error(res.data?.error || failure);
|
|
810
|
-
}
|
|
680
|
+
await this.call("POST", `/api/plans/${sessionId}/isolation/${segment}`, failure);
|
|
811
681
|
}
|
|
812
682
|
async resolveConflictAsTask(sessionId, taskId) {
|
|
813
|
-
const res = await this.
|
|
814
|
-
|
|
815
|
-
throw new Error(res.data?.error || "Could not add a resolver task");
|
|
816
|
-
}
|
|
817
|
-
return res.data.plan;
|
|
683
|
+
const res = await this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/resolve-conflict`, "Could not add a resolver task");
|
|
684
|
+
return res.plan;
|
|
818
685
|
}
|
|
819
686
|
async closeSession(sessionId) {
|
|
820
687
|
const res = await this.httpRequest("POST", `/api/sessions/${sessionId}/close`);
|
|
@@ -829,42 +696,26 @@ var ApiClient = class _ApiClient {
|
|
|
829
696
|
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/planning/stop`);
|
|
830
697
|
return res.data;
|
|
831
698
|
}
|
|
832
|
-
async processQueued(sessionId) {
|
|
833
|
-
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/process-queued`);
|
|
834
|
-
if (res.status !== 200) {
|
|
835
|
-
throw new Error(res.data?.error || "Process queued failed");
|
|
836
|
-
}
|
|
837
|
-
return res.data;
|
|
838
|
-
}
|
|
839
699
|
async getSessions(workspace) {
|
|
840
700
|
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
841
701
|
const res = await this.httpRequest("GET", `/api/sessions${qs}`);
|
|
842
702
|
return res.data || [];
|
|
843
703
|
}
|
|
844
|
-
|
|
704
|
+
getSession(sessionId, workspace) {
|
|
845
705
|
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
846
|
-
|
|
847
|
-
"GET",
|
|
848
|
-
`/api/sessions/${sessionId}${qs}`
|
|
849
|
-
);
|
|
850
|
-
if (res.status !== 200) {
|
|
851
|
-
throw new Error(res.data?.error || "Session not found");
|
|
852
|
-
}
|
|
853
|
-
return res.data;
|
|
706
|
+
return this.call("GET", `/api/sessions/${sessionId}${qs}`, "Session not found");
|
|
854
707
|
}
|
|
855
708
|
/** The attempts of a structured task that have a saved log, oldest first. */
|
|
856
709
|
async getTaskLogAttempts(sessionId, taskId, workspace) {
|
|
857
710
|
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
858
|
-
const res = await this.
|
|
859
|
-
|
|
860
|
-
return res.data.attempts;
|
|
711
|
+
const res = await this.call("GET", `/api/sessions/${sessionId}/tasks/${encodeURIComponent(taskId)}/log${qs}`, "Could not read the task log");
|
|
712
|
+
return res.attempts;
|
|
861
713
|
}
|
|
862
714
|
/** One attempt's saved log, for `replayTaskLog`. */
|
|
863
715
|
async getTaskLog(sessionId, taskId, attempt, workspace) {
|
|
864
716
|
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
865
|
-
const res = await this.
|
|
866
|
-
|
|
867
|
-
return res.data.events;
|
|
717
|
+
const res = await this.call("GET", `/api/sessions/${sessionId}/tasks/${encodeURIComponent(taskId)}/log/${attempt}${qs}`, "Could not read the task log");
|
|
718
|
+
return res.events;
|
|
868
719
|
}
|
|
869
720
|
/**
|
|
870
721
|
* The one place a session socket is built, so the token travels on both
|
|
@@ -895,8 +746,14 @@ var ApiClient = class _ApiClient {
|
|
|
895
746
|
});
|
|
896
747
|
return socket;
|
|
897
748
|
}
|
|
749
|
+
/** Ends one stream `streamExecution` returned, leaving any newer stream the session has opened since alone. */
|
|
750
|
+
closeExecutionStream(stream) {
|
|
751
|
+
this.executionStreamEnds.get(stream)?.();
|
|
752
|
+
}
|
|
898
753
|
streamExecution(sessionId, onEvent, onReady) {
|
|
899
|
-
|
|
754
|
+
let endStream = () => {
|
|
755
|
+
};
|
|
756
|
+
const stream = new Promise((resolve4, reject) => {
|
|
900
757
|
this.executionStreams.get(sessionId)?.();
|
|
901
758
|
const socket = this.openSessionSocket(sessionId);
|
|
902
759
|
let resolved = false;
|
|
@@ -909,9 +766,10 @@ var ApiClient = class _ApiClient {
|
|
|
909
766
|
if (resolved) return;
|
|
910
767
|
resolved = true;
|
|
911
768
|
socket.close();
|
|
912
|
-
|
|
769
|
+
resolve4();
|
|
913
770
|
};
|
|
914
771
|
this.executionStreams.set(sessionId, end);
|
|
772
|
+
endStream = end;
|
|
915
773
|
socket.on("open", () => {
|
|
916
774
|
opened = true;
|
|
917
775
|
onReady?.();
|
|
@@ -928,8 +786,12 @@ var ApiClient = class _ApiClient {
|
|
|
928
786
|
socket.on("error", (err) => {
|
|
929
787
|
forget();
|
|
930
788
|
if (!opened) onReady?.(err);
|
|
931
|
-
if (
|
|
932
|
-
|
|
789
|
+
if (resolved) return;
|
|
790
|
+
resolved = true;
|
|
791
|
+
if (opened) {
|
|
792
|
+
socket.close();
|
|
793
|
+
resolve4("lost");
|
|
794
|
+
} else {
|
|
933
795
|
reject(err);
|
|
934
796
|
}
|
|
935
797
|
});
|
|
@@ -938,10 +800,12 @@ var ApiClient = class _ApiClient {
|
|
|
938
800
|
if (!opened) onReady?.(new Error("Execution stream closed before connecting"));
|
|
939
801
|
if (!resolved) {
|
|
940
802
|
resolved = true;
|
|
941
|
-
|
|
803
|
+
resolve4("lost");
|
|
942
804
|
}
|
|
943
805
|
});
|
|
944
806
|
});
|
|
807
|
+
this.executionStreamEnds.set(stream, endStream);
|
|
808
|
+
return stream;
|
|
945
809
|
}
|
|
946
810
|
/**
|
|
947
811
|
* Answer an approval prompt: the planner's with a yes/no, a task runner's
|
|
@@ -954,7 +818,7 @@ var ApiClient = class _ApiClient {
|
|
|
954
818
|
`/api/approvals/${encodeURIComponent(sessionId)}/${encodeURIComponent(approvalId)}`,
|
|
955
819
|
typeof answer === "boolean" ? { granted: answer } : answer
|
|
956
820
|
);
|
|
957
|
-
if (status !== 200) throw new
|
|
821
|
+
if (status !== 200) throw new DaemonError(`Failed to answer approval ${approvalId} (HTTP ${status})`, status, data.code);
|
|
958
822
|
return data;
|
|
959
823
|
}
|
|
960
824
|
/**
|
|
@@ -964,10 +828,10 @@ var ApiClient = class _ApiClient {
|
|
|
964
828
|
*/
|
|
965
829
|
streamPlanning(sessionId, onEvent) {
|
|
966
830
|
const socket = this.openSessionSocket(sessionId);
|
|
967
|
-
const ready = new Promise((
|
|
968
|
-
socket.once("open", () =>
|
|
969
|
-
socket.once("error", () =>
|
|
970
|
-
socket.once("close", () =>
|
|
831
|
+
const ready = new Promise((resolve4) => {
|
|
832
|
+
socket.once("open", () => resolve4());
|
|
833
|
+
socket.once("error", () => resolve4());
|
|
834
|
+
socket.once("close", () => resolve4());
|
|
971
835
|
});
|
|
972
836
|
socket.on("message", (data) => {
|
|
973
837
|
try {
|
|
@@ -979,33 +843,11 @@ var ApiClient = class _ApiClient {
|
|
|
979
843
|
});
|
|
980
844
|
return { ready, close: () => socket.close() };
|
|
981
845
|
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
if (res.status !== 200) {
|
|
985
|
-
throw new Error(res.data?.error || "Failed to fetch commands");
|
|
986
|
-
}
|
|
987
|
-
return res.data;
|
|
846
|
+
getSettings() {
|
|
847
|
+
return this.call("GET", "/api/settings", "Failed to fetch settings");
|
|
988
848
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
if (res.status !== 200) {
|
|
992
|
-
throw new Error(res.data?.error || `Command ${name} failed`);
|
|
993
|
-
}
|
|
994
|
-
return res.data;
|
|
995
|
-
}
|
|
996
|
-
async getSettings() {
|
|
997
|
-
const res = await this.httpRequest("GET", "/api/settings");
|
|
998
|
-
if (res.status !== 200) {
|
|
999
|
-
throw new Error(res.data?.error || "Failed to fetch settings");
|
|
1000
|
-
}
|
|
1001
|
-
return res.data;
|
|
1002
|
-
}
|
|
1003
|
-
async updateSettings(changes) {
|
|
1004
|
-
const res = await this.httpRequest("PATCH", "/api/settings", changes);
|
|
1005
|
-
if (res.status !== 200) {
|
|
1006
|
-
throw new Error(res.data?.error || "Failed to update settings");
|
|
1007
|
-
}
|
|
1008
|
-
return res.data;
|
|
849
|
+
updateSettings(changes) {
|
|
850
|
+
return this.call("PATCH", "/api/settings", "Failed to update settings", changes);
|
|
1009
851
|
}
|
|
1010
852
|
};
|
|
1011
853
|
|
|
@@ -1253,16 +1095,24 @@ var import_core7 = require("@ordewell/core");
|
|
|
1253
1095
|
function planTasks(plan) {
|
|
1254
1096
|
return plan ? allTasksOf(plan) : [];
|
|
1255
1097
|
}
|
|
1098
|
+
function runnersOf(plan, requested) {
|
|
1099
|
+
return "runners" in plan && plan.runners || requested;
|
|
1100
|
+
}
|
|
1256
1101
|
function hasCommittedPlan(plan) {
|
|
1257
1102
|
return planTasks(plan).length > 0;
|
|
1258
1103
|
}
|
|
1259
1104
|
function lastPlannerMessage(plan) {
|
|
1260
|
-
const history = plan
|
|
1105
|
+
const history = "conversationHistory" in plan && plan.conversationHistory || [];
|
|
1261
1106
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
1262
|
-
if (history[i].role === "assistant") return history[i].content;
|
|
1107
|
+
if (history[i].role === "assistant" && history[i].kind !== "skill_load") return history[i].content;
|
|
1263
1108
|
}
|
|
1264
1109
|
return "(the planner returned no plan and no message)";
|
|
1265
1110
|
}
|
|
1111
|
+
function commitNotes(plan) {
|
|
1112
|
+
const history = "conversationHistory" in plan && plan.conversationHistory || [];
|
|
1113
|
+
const marker2 = history.map((e) => e.kind).lastIndexOf("plan_generated");
|
|
1114
|
+
return marker2 < 0 ? [] : history.slice(marker2 + 1).filter((e) => e.kind === "system").map((e) => e.content);
|
|
1115
|
+
}
|
|
1266
1116
|
function stdinReader(input = process.stdin) {
|
|
1267
1117
|
const rl = (0, import_readline.createInterface)({ input, output: process.stderr });
|
|
1268
1118
|
const buffered = [];
|
|
@@ -1270,9 +1120,9 @@ function stdinReader(input = process.stdin) {
|
|
|
1270
1120
|
let ended = false;
|
|
1271
1121
|
const settle = (answer) => {
|
|
1272
1122
|
if (!waiting) return false;
|
|
1273
|
-
const
|
|
1123
|
+
const resolve4 = waiting;
|
|
1274
1124
|
waiting = null;
|
|
1275
|
-
|
|
1125
|
+
resolve4(answer);
|
|
1276
1126
|
return true;
|
|
1277
1127
|
};
|
|
1278
1128
|
rl.on("line", (line) => {
|
|
@@ -1288,8 +1138,8 @@ function stdinReader(input = process.stdin) {
|
|
|
1288
1138
|
if (ended) return Promise.resolve(null);
|
|
1289
1139
|
rl.setPrompt(question);
|
|
1290
1140
|
rl.prompt();
|
|
1291
|
-
return new Promise((
|
|
1292
|
-
waiting =
|
|
1141
|
+
return new Promise((resolve4) => {
|
|
1142
|
+
waiting = resolve4;
|
|
1293
1143
|
});
|
|
1294
1144
|
},
|
|
1295
1145
|
close() {
|
|
@@ -1319,7 +1169,7 @@ function printPlan(plan, sessionId, runners2, models) {
|
|
|
1319
1169
|
return rp ? ` (${task.assignedModel.modelLabel} \xB7 ${label})` : ` (${task.assignedModel.modelLabel})`;
|
|
1320
1170
|
};
|
|
1321
1171
|
console.log(`
|
|
1322
|
-
Plan: ${tasks.length} task${tasks.length === 1 ? "" : "s"} (${aiCount} AI, ${manCount} Manual) \u2014 ${(plan
|
|
1172
|
+
Plan: ${tasks.length} task${tasks.length === 1 ? "" : "s"} (${aiCount} AI, ${manCount} Manual) \u2014 ${runnersOf(plan, runners2).join(", ")}`);
|
|
1323
1173
|
console.log(`Session: ${sessionId}
|
|
1324
1174
|
`);
|
|
1325
1175
|
for (const t of tasks) {
|
|
@@ -1423,6 +1273,7 @@ async function handlePlan(subArgs, deps = {}) {
|
|
|
1423
1273
|
await stream.ready;
|
|
1424
1274
|
let plan;
|
|
1425
1275
|
let models = [];
|
|
1276
|
+
let notes = [];
|
|
1426
1277
|
if (oneShot) {
|
|
1427
1278
|
const result = await withSpinner(
|
|
1428
1279
|
"Researching codebase and building task plan",
|
|
@@ -1430,6 +1281,7 @@ async function handlePlan(subArgs, deps = {}) {
|
|
|
1430
1281
|
);
|
|
1431
1282
|
plan = result.plan;
|
|
1432
1283
|
models = result.models ?? [];
|
|
1284
|
+
notes = result.notes ?? [];
|
|
1433
1285
|
} else {
|
|
1434
1286
|
plan = await withSpinner(
|
|
1435
1287
|
"Researching codebase and building task plan",
|
|
@@ -1461,12 +1313,15 @@ ${lastPlannerMessage(plan)}
|
|
|
1461
1313
|
plan = await withSpinner("Thinking", () => api.sendConversationMessage(sessionId, reply));
|
|
1462
1314
|
}
|
|
1463
1315
|
reader?.close();
|
|
1316
|
+
notes = commitNotes(plan);
|
|
1464
1317
|
}
|
|
1465
1318
|
closeStream();
|
|
1466
1319
|
if (isTty && lastLineLength > 0) process.stderr.write("\r\x1B[2K");
|
|
1467
1320
|
process.stderr.write("\n");
|
|
1468
1321
|
printPlan(plan, sessionId, runners2, models);
|
|
1469
|
-
|
|
1322
|
+
for (const note of notes) console.log(`
|
|
1323
|
+
${note}`);
|
|
1324
|
+
saveLastSession(sessionId, goal, runnersOf(plan, runners2), workspace);
|
|
1470
1325
|
console.log(`
|
|
1471
1326
|
Run 'ordewell run' to execute, 'ordewell status' to inspect, or 'ordewell tui' for the full UI.`);
|
|
1472
1327
|
} catch (err) {
|
|
@@ -1584,23 +1439,27 @@ function mergeOutcome(result, branch, group, repaired = []) {
|
|
|
1584
1439
|
}
|
|
1585
1440
|
|
|
1586
1441
|
// src/utils/output.ts
|
|
1442
|
+
var import_core9 = require("@ordewell/core");
|
|
1587
1443
|
function iconFor(status) {
|
|
1588
|
-
switch (status) {
|
|
1589
|
-
case "
|
|
1444
|
+
switch ((0, import_core9.taskStatusKind)({ status })) {
|
|
1445
|
+
case "done":
|
|
1590
1446
|
return "\x1B[32m\u2713\x1B[0m";
|
|
1591
|
-
case "
|
|
1447
|
+
case "running":
|
|
1448
|
+
case "quiet":
|
|
1592
1449
|
return "\x1B[34m\u27F3\x1B[0m";
|
|
1593
1450
|
case "failed":
|
|
1594
1451
|
return "\x1B[31m\u2717\x1B[0m";
|
|
1595
1452
|
case "blocked":
|
|
1596
1453
|
return "\x1B[33m\u2298\x1B[0m";
|
|
1597
|
-
|
|
1454
|
+
case "awaiting":
|
|
1455
|
+
return "\x1B[33m?\x1B[0m";
|
|
1456
|
+
case "todo":
|
|
1598
1457
|
return "\x1B[90m\u25CB\x1B[0m";
|
|
1599
1458
|
}
|
|
1600
1459
|
}
|
|
1601
1460
|
|
|
1602
1461
|
// src/commands/run.ts
|
|
1603
|
-
var
|
|
1462
|
+
var import_core10 = require("@ordewell/core");
|
|
1604
1463
|
|
|
1605
1464
|
// src/commands/shared.ts
|
|
1606
1465
|
var import_readline2 = require("readline");
|
|
@@ -1664,7 +1523,7 @@ async function askYesNo(question) {
|
|
|
1664
1523
|
if (!process.stdin.isTTY) return false;
|
|
1665
1524
|
const rl = (0, import_readline2.createInterface)({ input: process.stdin, output: process.stderr });
|
|
1666
1525
|
try {
|
|
1667
|
-
const answer = await new Promise((
|
|
1526
|
+
const answer = await new Promise((resolve4) => rl.question(`${question} [y/N] `, resolve4));
|
|
1668
1527
|
return /^y(es)?$/i.test(answer.trim());
|
|
1669
1528
|
} finally {
|
|
1670
1529
|
rl.close();
|
|
@@ -1696,7 +1555,8 @@ function toTaskView(task) {
|
|
|
1696
1555
|
taskMode: task.taskMode || void 0,
|
|
1697
1556
|
assignedModel: task.assignedModel ?? void 0,
|
|
1698
1557
|
subtasks: [...task.subtasks ?? []].sort((a, b) => a.order - b.order).map(toTaskView),
|
|
1699
|
-
...task.ops ? { ops: true } : {}
|
|
1558
|
+
...task.ops ? { ops: true } : {},
|
|
1559
|
+
...task.skills?.length ? { skills: [...task.skills] } : {}
|
|
1700
1560
|
};
|
|
1701
1561
|
}
|
|
1702
1562
|
function taskViews(plan) {
|
|
@@ -1746,21 +1606,21 @@ async function followExecution(api, sessionId, start, onBlocked) {
|
|
|
1746
1606
|
let pausedAtGate = false;
|
|
1747
1607
|
let noteBlocked = () => {
|
|
1748
1608
|
};
|
|
1749
|
-
const blockedSeen = new Promise((
|
|
1750
|
-
noteBlocked =
|
|
1609
|
+
const blockedSeen = new Promise((resolve4) => {
|
|
1610
|
+
noteBlocked = resolve4;
|
|
1751
1611
|
});
|
|
1752
1612
|
let settleReady = () => {
|
|
1753
1613
|
};
|
|
1754
|
-
const streamReady = new Promise((
|
|
1755
|
-
settleReady = (error) => error ? reject(error) :
|
|
1614
|
+
const streamReady = new Promise((resolve4, reject) => {
|
|
1615
|
+
settleReady = (error) => error ? reject(error) : resolve4();
|
|
1756
1616
|
});
|
|
1757
1617
|
const stream = api.streamExecution(sessionId, (event) => {
|
|
1758
1618
|
if (event.type === "task_started") {
|
|
1759
|
-
process.stderr.write(`\xB7 ${(0,
|
|
1619
|
+
process.stderr.write(`\xB7 ${(0, import_core10.taskStartedNotice)(event.title, event.runner)}
|
|
1760
1620
|
`);
|
|
1761
1621
|
}
|
|
1762
1622
|
if (event.type === "checkpoint") {
|
|
1763
|
-
process.stderr.write(`\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0,
|
|
1623
|
+
process.stderr.write(`\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core10.truncateCheckpointSummary)(event.summary)} \u2014 \`ordewell checkpoint <id> approve|reject [reason]\` answers it
|
|
1764
1624
|
`);
|
|
1765
1625
|
}
|
|
1766
1626
|
if (event.type === "status_update" && event.tasks) {
|
|
@@ -1805,7 +1665,11 @@ Run finished on ${handoffBranch(event)}${where} \u2014 ${n} task${n === 1 ? "" :
|
|
|
1805
1665
|
void stream.catch(settleReady);
|
|
1806
1666
|
await streamReady.catch(streamFailed);
|
|
1807
1667
|
await start();
|
|
1808
|
-
await Promise.race([stream, blockedSeen]).catch(streamFailed);
|
|
1668
|
+
const outcome = await Promise.race([stream, blockedSeen]).catch(streamFailed);
|
|
1669
|
+
if (outcome === "lost") {
|
|
1670
|
+
console.error("Lost the connection to the daemon \u2014 the run is no longer being followed, but the daemon may still be running it. `ordewell status` shows where it stands.");
|
|
1671
|
+
process.exit(1);
|
|
1672
|
+
}
|
|
1809
1673
|
if (blocked === null) return;
|
|
1810
1674
|
if (!onBlocked) {
|
|
1811
1675
|
await api.stopExecution(sessionId);
|
|
@@ -1833,7 +1697,7 @@ async function handleRun(subArgs, injectedApi) {
|
|
|
1833
1697
|
|
|
1834
1698
|
// src/commands/status.ts
|
|
1835
1699
|
var import_fs4 = require("fs");
|
|
1836
|
-
var
|
|
1700
|
+
var import_core11 = require("@ordewell/core");
|
|
1837
1701
|
async function handleStatus(subArgs, injectedApi) {
|
|
1838
1702
|
const sessionId = flag(subArgs, "--session-id");
|
|
1839
1703
|
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
@@ -1904,12 +1768,12 @@ function printOneSession(sessionId, meta, plan) {
|
|
|
1904
1768
|
const icon = iconFor(t.status);
|
|
1905
1769
|
const model = t.assignedModel ? ` (${t.assignedModel.modelLabel})` : "";
|
|
1906
1770
|
const shortId = (t.id ?? "").slice(-8);
|
|
1907
|
-
console.log(` ${(0,
|
|
1771
|
+
console.log(` ${(0, import_core11.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}] ${t.title}${model}`);
|
|
1908
1772
|
for (const sub of t.subtasks || []) {
|
|
1909
1773
|
const subIcon = iconFor(sub.status);
|
|
1910
1774
|
const subModel = sub.assignedModel ? ` (${sub.assignedModel.modelLabel})` : "";
|
|
1911
1775
|
const subShortId = (sub.id ?? "").slice(-8);
|
|
1912
|
-
console.log(` ${(0,
|
|
1776
|
+
console.log(` ${(0, import_core11.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
|
|
1913
1777
|
}
|
|
1914
1778
|
}
|
|
1915
1779
|
}
|
|
@@ -1961,10 +1825,10 @@ async function handleWeb(subArgs) {
|
|
|
1961
1825
|
}
|
|
1962
1826
|
|
|
1963
1827
|
// src/commands/models.ts
|
|
1964
|
-
var
|
|
1828
|
+
var import_core13 = require("@ordewell/core");
|
|
1965
1829
|
|
|
1966
1830
|
// src/commands/model.ts
|
|
1967
|
-
var
|
|
1831
|
+
var import_core12 = require("@ordewell/core");
|
|
1968
1832
|
var USAGE = "Usage: ordewell model [show | set <model-id>]";
|
|
1969
1833
|
async function handleModel(subArgs, injectedApi) {
|
|
1970
1834
|
const [action, modelId] = positionals(subArgs);
|
|
@@ -1979,7 +1843,7 @@ async function handleModel(subArgs, injectedApi) {
|
|
|
1979
1843
|
if (!modelId) fail(USAGE);
|
|
1980
1844
|
const settings = await api.getSettings();
|
|
1981
1845
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
1982
|
-
const runner = (0,
|
|
1846
|
+
const runner = (0, import_core12.runnerForProvider)(provider);
|
|
1983
1847
|
if (runner) {
|
|
1984
1848
|
const known = (await fetchCatalog(api)).models.filter((m) => m.runners?.includes(runner));
|
|
1985
1849
|
if (known.length > 0 && !known.some((m) => m.id === modelId)) {
|
|
@@ -1998,7 +1862,7 @@ async function showModels(api) {
|
|
|
1998
1862
|
const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
|
|
1999
1863
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
2000
1864
|
const current = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
|
|
2001
|
-
const runner = (0,
|
|
1865
|
+
const runner = (0, import_core12.runnerForProvider)(provider);
|
|
2002
1866
|
const options = runner ? catalog.models.filter((m) => m.runners?.includes(runner)) : catalog.orchestratorModels;
|
|
2003
1867
|
console.log(`
|
|
2004
1868
|
Planner model: ${current || "(backend default)"}`);
|
|
@@ -2032,8 +1896,8 @@ async function handleModels(subArgs) {
|
|
|
2032
1896
|
const rest = subArgs.filter((_, i) => i !== setIdx && i !== setIdx + 1);
|
|
2033
1897
|
return handleModel(["set", subArgs[setIdx + 1], ...rest]);
|
|
2034
1898
|
}
|
|
2035
|
-
const config = new
|
|
2036
|
-
const providers = (0,
|
|
1899
|
+
const config = new import_core13.EnvConfig();
|
|
1900
|
+
const providers = (0, import_core13.configuredProviders)(config);
|
|
2037
1901
|
if (providers.length === 0) {
|
|
2038
1902
|
console.error("No providers configured. Set an API key for a provider, e.g.:");
|
|
2039
1903
|
console.error(" OPENROUTER_API_KEY=... (OpenRouter)");
|
|
@@ -2042,19 +1906,19 @@ async function handleModels(subArgs) {
|
|
|
2042
1906
|
console.error(" OPENAI_COMPATIBLE_BASE_URL=... (self-hosted / OpenAI-compatible)");
|
|
2043
1907
|
process.exit(1);
|
|
2044
1908
|
}
|
|
2045
|
-
const { apiKeys, baseUrls } = (0,
|
|
1909
|
+
const { apiKeys, baseUrls } = (0, import_core13.collectProviderCredentials)(config);
|
|
2046
1910
|
let models;
|
|
2047
1911
|
let errors;
|
|
2048
1912
|
try {
|
|
2049
|
-
const result = await (0,
|
|
2050
|
-
models = (0,
|
|
1913
|
+
const result = await (0, import_core13.fetchAllProviderModels)({ apiKeys, baseUrls });
|
|
1914
|
+
models = (0, import_core13.toOrchestratorOptions)(result.models, import_core13.ORCHESTRATOR_SHORTCUTS);
|
|
2051
1915
|
errors = result.errors;
|
|
2052
1916
|
} catch (err) {
|
|
2053
1917
|
console.error(`Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`);
|
|
2054
1918
|
process.exit(1);
|
|
2055
1919
|
return;
|
|
2056
1920
|
}
|
|
2057
|
-
const providerLabel = (p) => (0,
|
|
1921
|
+
const providerLabel = (p) => (0, import_core13.getProviderMeta)(p)?.label ?? p;
|
|
2058
1922
|
console.log(
|
|
2059
1923
|
`
|
|
2060
1924
|
Available models (${models.length}) across ${providers.length} configured provider(s):`
|
|
@@ -2094,7 +1958,7 @@ ${providerLabel(provider)}`);
|
|
|
2094
1958
|
|
|
2095
1959
|
// src/commands/setup.ts
|
|
2096
1960
|
var import_fs6 = require("fs");
|
|
2097
|
-
var
|
|
1961
|
+
var import_core14 = require("@ordewell/core");
|
|
2098
1962
|
function handleSetup(subArgs) {
|
|
2099
1963
|
const modelIdx = subArgs.indexOf("--model");
|
|
2100
1964
|
const chosenModel = modelIdx !== -1 ? subArgs[modelIdx + 1] : null;
|
|
@@ -2166,105 +2030,65 @@ function handleSetup(subArgs) {
|
|
|
2166
2030
|
# STEPFUN_API_KEY=...
|
|
2167
2031
|
# SILICONFLOW_API_KEY=...
|
|
2168
2032
|
`;
|
|
2169
|
-
(0,
|
|
2033
|
+
(0, import_core14.writePrivateFile)(envFile, content);
|
|
2170
2034
|
console.log(`Wrote: ${envFile}`);
|
|
2171
2035
|
console.log("Review and edit the file, then run: ordewell tui");
|
|
2172
2036
|
}
|
|
2173
2037
|
|
|
2174
|
-
// src/commands/
|
|
2175
|
-
var
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
if (
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
)
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
}
|
|
2206
|
-
if (
|
|
2207
|
-
|
|
2208
|
-
if (!source) {
|
|
2209
|
-
console.error("Usage: ordewell plugins install <path|github:user/repo|name>");
|
|
2210
|
-
process.exit(1);
|
|
2211
|
-
}
|
|
2212
|
-
try {
|
|
2213
|
-
const resolved = (0, import_core14.classifyPluginSource)(source);
|
|
2214
|
-
const manifest = resolved.kind === "git" ? registry.installFromGit(resolved.url) : registry.installFromPath(resolved.path);
|
|
2215
|
-
console.log(`Installed plugin: ${manifest.name} v${manifest.version}`);
|
|
2216
|
-
} catch (err) {
|
|
2217
|
-
console.error(`Install failed: ${err.message}`);
|
|
2218
|
-
process.exit(1);
|
|
2219
|
-
}
|
|
2220
|
-
return;
|
|
2221
|
-
}
|
|
2222
|
-
if (sub === "remove") {
|
|
2223
|
-
const name = subArgs[1];
|
|
2224
|
-
if (!name) {
|
|
2225
|
-
console.error("Usage: ordewell plugins remove <name>");
|
|
2226
|
-
process.exit(1);
|
|
2227
|
-
}
|
|
2228
|
-
try {
|
|
2229
|
-
registry.remove(name);
|
|
2230
|
-
console.log(`Removed plugin: ${name}`);
|
|
2231
|
-
} catch (err) {
|
|
2232
|
-
console.error(`Remove failed: ${err.message}`);
|
|
2233
|
-
process.exit(1);
|
|
2234
|
-
}
|
|
2038
|
+
// src/commands/skills.ts
|
|
2039
|
+
var import_core15 = require("@ordewell/core");
|
|
2040
|
+
var import_path5 = require("path");
|
|
2041
|
+
function invocation(skill) {
|
|
2042
|
+
if (skill.userInvocable && skill.modelInvocable) return "both";
|
|
2043
|
+
if (skill.userInvocable) return "user";
|
|
2044
|
+
if (skill.modelInvocable) return "model";
|
|
2045
|
+
return "none";
|
|
2046
|
+
}
|
|
2047
|
+
function handleSkills(subArgs) {
|
|
2048
|
+
if (positionals(subArgs).length > 0) {
|
|
2049
|
+
fail("Usage: ordewell skills [--workspace /path] [--json]");
|
|
2050
|
+
}
|
|
2051
|
+
const workspace = (0, import_path5.resolve)(flag(subArgs, "--workspace") || process.cwd());
|
|
2052
|
+
const catalog = (0, import_core15.createSkillsService)(workspace, new import_core15.EnvConfig().workspaceRepos).readCatalog();
|
|
2053
|
+
const skills = catalog.skills.map((skill) => ({
|
|
2054
|
+
name: skill.name,
|
|
2055
|
+
scope: skill.source,
|
|
2056
|
+
appliesTo: skill.appliesTo,
|
|
2057
|
+
invocation: invocation(skill),
|
|
2058
|
+
path: (0, import_core15.abbreviateHome)(skill.path)
|
|
2059
|
+
}));
|
|
2060
|
+
const shadowed = catalog.shadowed.map(({ skill, shadowedBy }) => ({
|
|
2061
|
+
name: skill.name,
|
|
2062
|
+
path: (0, import_core15.abbreviateHome)(skill.path),
|
|
2063
|
+
shadowedBy: { scope: shadowedBy.source, path: (0, import_core15.abbreviateHome)(shadowedBy.path) }
|
|
2064
|
+
}));
|
|
2065
|
+
const invalid = catalog.invalid.map((skill) => ({
|
|
2066
|
+
name: skill.name,
|
|
2067
|
+
path: (0, import_core15.abbreviateHome)(skill.path),
|
|
2068
|
+
reason: skill.reason
|
|
2069
|
+
}));
|
|
2070
|
+
if (hasFlag(subArgs, "--json")) {
|
|
2071
|
+
console.log(JSON.stringify({ skills, shadowed, invalid }, null, 2));
|
|
2235
2072
|
return;
|
|
2236
2073
|
}
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
const
|
|
2244
|
-
console.log(`Created plugin skeleton: ${dir}`);
|
|
2245
|
-
console.log(` Edit ${dir}/manifest.json to configure your runner`);
|
|
2246
|
-
console.log(` Then install with: ordewell plugins install ${dir}`);
|
|
2074
|
+
const skipped = [
|
|
2075
|
+
...shadowed.map((skill) => `workspace skill "${skill.name}" shadowed by the ${skill.shadowedBy.scope} copy at ${skill.shadowedBy.path} \xB7 ${skill.path}`),
|
|
2076
|
+
...invalid.map((skill) => `skill folder "${skill.name}" skipped: ${skill.reason} \xB7 ${skill.path}`)
|
|
2077
|
+
];
|
|
2078
|
+
if (skills.length === 0) {
|
|
2079
|
+
console.log("No skills installed.");
|
|
2080
|
+
for (const line of skipped) console.log(line);
|
|
2247
2081
|
return;
|
|
2248
2082
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
const api = new ApiClient(port);
|
|
2259
|
-
if (action === "on" || action === "off") {
|
|
2260
|
-
const result = await api.sendCommand("tdd", { action });
|
|
2261
|
-
const state = result.settings?.tdd;
|
|
2262
|
-
console.log(`TDD mode: ${state?.enabled ? "ON" : "OFF"}`);
|
|
2263
|
-
} else {
|
|
2264
|
-
const settings = await api.getSettings();
|
|
2265
|
-
const tdd = settings.tdd;
|
|
2266
|
-
console.log(`TDD mode: ${tdd?.enabled ? "ON" : "OFF"}`);
|
|
2267
|
-
console.log("Usage: ordewell tdd [on|off]");
|
|
2083
|
+
const headers = ["Name", "Scope", "Applies-to", "Invocation", "SKILL.md"];
|
|
2084
|
+
const rows = skills.map((skill) => [skill.name, skill.scope, skill.appliesTo, skill.invocation, skill.path]);
|
|
2085
|
+
const widths = headers.map((header, i) => Math.max(header.length, ...rows.map((row) => row[i].length)));
|
|
2086
|
+
const format = (row) => row.map((cell, i) => i === row.length - 1 ? cell : cell.padEnd(widths[i])).join(" ");
|
|
2087
|
+
console.log(format(headers));
|
|
2088
|
+
for (const row of rows) console.log(format(row));
|
|
2089
|
+
if (skipped.length > 0) {
|
|
2090
|
+
console.log("");
|
|
2091
|
+
for (const line of skipped) console.log(line);
|
|
2268
2092
|
}
|
|
2269
2093
|
}
|
|
2270
2094
|
|
|
@@ -2354,41 +2178,8 @@ async function handleShow(args, api) {
|
|
|
2354
2178
|
}
|
|
2355
2179
|
}
|
|
2356
2180
|
|
|
2357
|
-
// src/commands/verify.ts
|
|
2358
|
-
async function handleVerify(subArgs, api) {
|
|
2359
|
-
const action = subArgs[0];
|
|
2360
|
-
const port = api ? void 0 : await ensureDaemon(resolvePort(subArgs));
|
|
2361
|
-
const client = api || new ApiClient(port);
|
|
2362
|
-
if (action === "on" || action === "off") {
|
|
2363
|
-
const result = await client.sendCommand("verify", { action });
|
|
2364
|
-
const state = result.settings?.verification;
|
|
2365
|
-
console.log(`Verification mode: ${state?.enabled ? "ON" : "OFF"}`);
|
|
2366
|
-
} else {
|
|
2367
|
-
const settings = await client.getSettings();
|
|
2368
|
-
const verification = settings.verification;
|
|
2369
|
-
console.log(`Verification mode: ${verification?.enabled ? "ON" : "OFF"}`);
|
|
2370
|
-
console.log("Usage: ordewell verify [on|off]");
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2374
|
-
// src/commands/transport.ts
|
|
2375
|
-
var describe = (transport) => `Runner transport: ${transport === "terminal" ? "terminal" : "structured"} \u2014 applies from the next run`;
|
|
2376
|
-
async function handleTransport(subArgs, api) {
|
|
2377
|
-
const action = subArgs[0];
|
|
2378
|
-
const port = api ? void 0 : await ensureDaemon(resolvePort(subArgs));
|
|
2379
|
-
const client = api || new ApiClient(port);
|
|
2380
|
-
if (action === "terminal" || action === "structured") {
|
|
2381
|
-
const result = await client.sendCommand("transport", { action });
|
|
2382
|
-
console.log(describe(result.settings?.runnerTransport));
|
|
2383
|
-
} else {
|
|
2384
|
-
const settings = await client.getSettings();
|
|
2385
|
-
console.log(describe(settings.runnerTransport));
|
|
2386
|
-
console.log("Usage: ordewell transport [terminal|structured]");
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2390
2181
|
// src/commands/task-control.ts
|
|
2391
|
-
var
|
|
2182
|
+
var import_core16 = require("@ordewell/core");
|
|
2392
2183
|
var PAST_TENSE = {
|
|
2393
2184
|
run: "Task started.",
|
|
2394
2185
|
"force-start": "Task force-started.",
|
|
@@ -2431,7 +2222,7 @@ async function confirmPastGate(subArgs, api, sessionId, taskId, confirm) {
|
|
|
2431
2222
|
if (hasFlag(subArgs, "--yes")) return;
|
|
2432
2223
|
const unmerged = await api.getMergeGate(sessionId, taskId).catch(() => []);
|
|
2433
2224
|
if (unmerged.length === 0) return;
|
|
2434
|
-
const question = `${(0,
|
|
2225
|
+
const question = `${(0, import_core16.pastGateConfirmation)(unmerged.map((d) => (0, import_core16.titledTaskRef)(d)))} Start it anyway?`;
|
|
2435
2226
|
if (!await confirm(question)) fail("Not started \u2014 nothing was changed. Pass --yes to start it without a prompt.");
|
|
2436
2227
|
}
|
|
2437
2228
|
function makeHandler(action, command = action) {
|
|
@@ -2744,24 +2535,24 @@ async function handleRewind(subArgs, injectedApi) {
|
|
|
2744
2535
|
}
|
|
2745
2536
|
|
|
2746
2537
|
// src/tui/transcript.ts
|
|
2747
|
-
var
|
|
2538
|
+
var import_core17 = require("@ordewell/core");
|
|
2748
2539
|
function show(state, { view: conversation, gate: turnGate }) {
|
|
2749
2540
|
const gated = turnGate === state.turnGate ? state : { ...state, turnGate };
|
|
2750
2541
|
if (conversation === state.conversation) return gated;
|
|
2751
2542
|
return { ...gated, conversation };
|
|
2752
2543
|
}
|
|
2753
2544
|
function fold(state, input) {
|
|
2754
|
-
return show(state, { view: (0,
|
|
2545
|
+
return show(state, { view: (0, import_core17.reduceConversation)(state.conversation, input), gate: state.turnGate });
|
|
2755
2546
|
}
|
|
2756
2547
|
function say(state, role, text) {
|
|
2757
2548
|
const said = fold(state, { type: "local_entry", role, text });
|
|
2758
2549
|
return said.conversation.nextId > state.conversation.nextId ? { ...said, scroll: 0 } : said;
|
|
2759
2550
|
}
|
|
2760
2551
|
function hear(state, message) {
|
|
2761
|
-
return show(state, (0,
|
|
2552
|
+
return show(state, (0, import_core17.followTurn)(state.conversation, state.turnGate, message));
|
|
2762
2553
|
}
|
|
2763
2554
|
function cutTurn(state) {
|
|
2764
|
-
return show(state, (0,
|
|
2555
|
+
return show(state, (0, import_core17.stopTurn)(state.conversation, state.turnGate));
|
|
2765
2556
|
}
|
|
2766
2557
|
function wiped(view) {
|
|
2767
2558
|
return { ...view, blocks: view.blocks.filter((b) => b.type === "usage") };
|
|
@@ -3175,26 +2966,17 @@ async function handleHandoff(subArgs, injectedApi, confirm = askYesNo) {
|
|
|
3175
2966
|
}
|
|
3176
2967
|
|
|
3177
2968
|
// src/commands/planner.ts
|
|
3178
|
-
var
|
|
2969
|
+
var import_core18 = require("@ordewell/core");
|
|
3179
2970
|
|
|
3180
2971
|
// src/plannerModelSwitch.ts
|
|
3181
|
-
function
|
|
3182
|
-
|
|
3183
|
-
const
|
|
3184
|
-
|
|
3185
|
-
return rememberedModelFor(settings, provider) === model ? { model, effort, source: "remembered" } : { model, effort, source: "catalog-default" };
|
|
3186
|
-
}
|
|
3187
|
-
function rememberedModelFor(settings, provider) {
|
|
3188
|
-
const map = settings.plannerModels;
|
|
3189
|
-
if (!map || typeof map !== "object") return void 0;
|
|
3190
|
-
const entry = map[provider];
|
|
3191
|
-
if (!entry || typeof entry !== "object") return void 0;
|
|
3192
|
-
const { model } = entry;
|
|
3193
|
-
return typeof model === "string" ? model : void 0;
|
|
2972
|
+
function plannerSwitchRecall(settings) {
|
|
2973
|
+
if (settings.switchRecall) return settings.switchRecall;
|
|
2974
|
+
const model = settings.orchestratorModel;
|
|
2975
|
+
return model ? { model, effort: settings.plannerThinkingEffort, source: "remembered" } : { model: "", effort: "", source: "none" };
|
|
3194
2976
|
}
|
|
3195
2977
|
|
|
3196
2978
|
// src/commands/planner.ts
|
|
3197
|
-
var
|
|
2979
|
+
var isAiProvider = (id) => Object.hasOwn(import_core18.ALL_PROVIDERS, id);
|
|
3198
2980
|
async function handlePlanner(subArgs, injectedApi) {
|
|
3199
2981
|
const api = await connect(subArgs, injectedApi);
|
|
3200
2982
|
const [wanted] = positionals(subArgs);
|
|
@@ -3203,19 +2985,19 @@ async function handlePlanner(subArgs, injectedApi) {
|
|
|
3203
2985
|
return;
|
|
3204
2986
|
}
|
|
3205
2987
|
const provider = wanted.toLowerCase();
|
|
3206
|
-
if (!
|
|
2988
|
+
if (!isAiProvider(provider)) {
|
|
3207
2989
|
fail(`Unknown planner: ${provider}`, "Run `ordewell planner` with no arguments to see the list.");
|
|
3208
2990
|
}
|
|
3209
2991
|
const settings = await persistEnv(api, { AI_PROVIDER: provider });
|
|
3210
|
-
const recall2 =
|
|
2992
|
+
const recall2 = plannerSwitchRecall(settings);
|
|
3211
2993
|
writeEnv({ ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
|
|
3212
|
-
const meta =
|
|
2994
|
+
const meta = import_core18.ALL_PROVIDERS[provider];
|
|
3213
2995
|
const catalog = await fetchCatalog(api);
|
|
3214
2996
|
console.log(
|
|
3215
|
-
(0,
|
|
2997
|
+
(0, import_core18.isCliProvider)(provider) ? `Planning with ${meta.label} \u2014 no API key needed, it uses that agent's own subscription.` : `Planner set to ${meta.label}.`
|
|
3216
2998
|
);
|
|
3217
2999
|
console.log(plannerModelLine(recall2));
|
|
3218
|
-
if (!(0,
|
|
3000
|
+
if (!(0, import_core18.isCliProvider)(provider) && !catalog.providers.includes(provider)) {
|
|
3219
3001
|
console.log(` No ${meta.apiKeyEnvVar} configured yet \u2014 set one with \`ordewell key set ${provider} <key>\`.`);
|
|
3220
3002
|
}
|
|
3221
3003
|
}
|
|
@@ -3233,15 +3015,15 @@ async function showPlanners(api) {
|
|
|
3233
3015
|
const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
|
|
3234
3016
|
const current = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
3235
3017
|
console.log("\nCoding agents (no API key \u2014 they use their own subscription)");
|
|
3236
|
-
for (const id of
|
|
3237
|
-
const runner = (0,
|
|
3018
|
+
for (const id of import_core18.CLI_PROVIDERS) {
|
|
3019
|
+
const runner = (0, import_core18.runnerForProvider)(id);
|
|
3238
3020
|
const discovered = runner ? catalog.models.some((m) => m.runners?.includes(runner)) : false;
|
|
3239
|
-
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${
|
|
3021
|
+
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${import_core18.ALL_PROVIDERS[id].label}${discovered ? "" : " (not detected)"}`);
|
|
3240
3022
|
}
|
|
3241
3023
|
console.log("\nAPI providers");
|
|
3242
|
-
for (const id of
|
|
3024
|
+
for (const id of import_core18.PROVIDER_PRIORITY) {
|
|
3243
3025
|
const configured = catalog.providers.includes(id);
|
|
3244
|
-
const meta =
|
|
3026
|
+
const meta = import_core18.ALL_PROVIDERS[id];
|
|
3245
3027
|
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${meta.label}${configured ? "" : ` (no ${meta.apiKeyEnvVar})`}`);
|
|
3246
3028
|
}
|
|
3247
3029
|
console.log(`
|
|
@@ -3258,7 +3040,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
3258
3040
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
3259
3041
|
const modelId = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
|
|
3260
3042
|
const currentEffort = typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : "";
|
|
3261
|
-
const runner = (0,
|
|
3043
|
+
const runner = isAiProvider(provider) ? (0, import_core18.runnerForProvider)(provider) : null;
|
|
3262
3044
|
if (!runner) {
|
|
3263
3045
|
fail(
|
|
3264
3046
|
"Thinking effort applies to a coding-agent planner.",
|
|
@@ -3298,7 +3080,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
3298
3080
|
}
|
|
3299
3081
|
|
|
3300
3082
|
// src/commands/parallel.ts
|
|
3301
|
-
var
|
|
3083
|
+
var import_core19 = require("@ordewell/core");
|
|
3302
3084
|
var USAGE5 = "Usage: ordewell parallel [<number of tasks, 1 or more>]";
|
|
3303
3085
|
async function handleParallel(subArgs, injectedApi) {
|
|
3304
3086
|
const [wanted] = positionals(subArgs);
|
|
@@ -3308,14 +3090,14 @@ async function handleParallel(subArgs, injectedApi) {
|
|
|
3308
3090
|
console.log(`Up to ${String(settings.maxParallel ?? "?")} AI tasks run at once. Change with: ordewell parallel <n>`);
|
|
3309
3091
|
return;
|
|
3310
3092
|
}
|
|
3311
|
-
const limit = (0,
|
|
3093
|
+
const limit = (0, import_core19.parseMaxParallel)(wanted);
|
|
3312
3094
|
if (limit === null) fail(`"${wanted}" is not a number of tasks (1 or more).`, USAGE5);
|
|
3313
3095
|
await persistEnv(api, { ORDEWELL_MAX_PARALLEL: String(limit) });
|
|
3314
3096
|
console.log(`Up to ${limit} AI task${limit === 1 ? "" : "s"} now run at once.`);
|
|
3315
3097
|
}
|
|
3316
3098
|
|
|
3317
3099
|
// src/commands/key.ts
|
|
3318
|
-
var
|
|
3100
|
+
var import_core20 = require("@ordewell/core");
|
|
3319
3101
|
var USAGE6 = "Usage: ordewell key [list | set <provider> <key>]";
|
|
3320
3102
|
async function handleKey(subArgs, injectedApi) {
|
|
3321
3103
|
const [action, provider, ...keyParts] = positionals(subArgs);
|
|
@@ -3326,8 +3108,8 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3326
3108
|
if (action !== "set") {
|
|
3327
3109
|
const catalog = await fetchCatalog(api);
|
|
3328
3110
|
console.log("\nAPI provider keys\n");
|
|
3329
|
-
for (const id2 of
|
|
3330
|
-
const meta2 =
|
|
3111
|
+
for (const id2 of import_core20.PROVIDER_PRIORITY) {
|
|
3112
|
+
const meta2 = import_core20.ALL_PROVIDERS[id2];
|
|
3331
3113
|
const configured = catalog.providers.includes(id2);
|
|
3332
3114
|
console.log(` ${configured ? "\u2713" : " "} ${id2.padEnd(14)} ${meta2.apiKeyEnvVar.padEnd(28)} ${meta2.label}`);
|
|
3333
3115
|
}
|
|
@@ -3337,7 +3119,7 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3337
3119
|
}
|
|
3338
3120
|
if (!provider) fail(USAGE6);
|
|
3339
3121
|
const id = provider.toLowerCase();
|
|
3340
|
-
const meta =
|
|
3122
|
+
const meta = import_core20.ALL_PROVIDERS[id];
|
|
3341
3123
|
if (!meta) {
|
|
3342
3124
|
fail(`Unknown provider: ${provider}`, "Run `ordewell key` to see the list.");
|
|
3343
3125
|
}
|
|
@@ -3348,7 +3130,7 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3348
3130
|
}
|
|
3349
3131
|
|
|
3350
3132
|
// src/commands/runners.ts
|
|
3351
|
-
var
|
|
3133
|
+
var import_core21 = require("@ordewell/core");
|
|
3352
3134
|
var USAGE7 = "Usage: ordewell runners [<runner-id> on|off]";
|
|
3353
3135
|
async function handleRunners(subArgs, injectedApi) {
|
|
3354
3136
|
const [runner, toggle] = positionals(subArgs);
|
|
@@ -3379,15 +3161,15 @@ function handleAuto(subArgs) {
|
|
|
3379
3161
|
const currentRaw = process.env.ORDEWELL_AUTONOMOUS_MODE;
|
|
3380
3162
|
const current = currentRaw !== "false" && currentRaw !== "0";
|
|
3381
3163
|
if (!arg) {
|
|
3382
|
-
console.log(`Autonomy level: ${(0,
|
|
3164
|
+
console.log(`Autonomy level: ${(0, import_core21.autonomyLevelLabel)(current)}`);
|
|
3383
3165
|
console.log(` ${AUTO_USAGE}`);
|
|
3384
3166
|
return;
|
|
3385
3167
|
}
|
|
3386
|
-
const enabled = (0,
|
|
3168
|
+
const enabled = (0, import_core21.parseAutonomyLevel)(arg);
|
|
3387
3169
|
if (enabled === null) fail(AUTO_USAGE);
|
|
3388
3170
|
writeEnvVar(findEnvFile(), "ORDEWELL_AUTONOMOUS_MODE", String(enabled));
|
|
3389
3171
|
process.env.ORDEWELL_AUTONOMOUS_MODE = String(enabled);
|
|
3390
|
-
console.log(`Autonomy level: ${(0,
|
|
3172
|
+
console.log(`Autonomy level: ${(0, import_core21.autonomyLevelLabel)(enabled)} for new sessions.`);
|
|
3391
3173
|
}
|
|
3392
3174
|
async function handleRefresh(subArgs, injectedApi) {
|
|
3393
3175
|
const api = await connect(subArgs, injectedApi);
|
|
@@ -3416,130 +3198,8 @@ async function handleApprove(subArgs, injectedApi) {
|
|
|
3416
3198
|
});
|
|
3417
3199
|
}
|
|
3418
3200
|
|
|
3419
|
-
// src/tui/terminalLauncher.ts
|
|
3420
|
-
var import_child_process3 = require("child_process");
|
|
3421
|
-
var import_util = require("util");
|
|
3422
|
-
var import_core21 = require("@ordewell/core");
|
|
3423
|
-
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
3424
|
-
var defaultExecFile = async (command, args) => {
|
|
3425
|
-
const { stdout, stderr } = await execFileAsync(command, args);
|
|
3426
|
-
return { stdout: String(stdout), stderr: String(stderr) };
|
|
3427
|
-
};
|
|
3428
|
-
var defaultSpawn = (command, args) => {
|
|
3429
|
-
(0, import_child_process3.spawn)(command, args, { detached: true, stdio: "ignore" }).unref();
|
|
3430
|
-
};
|
|
3431
|
-
var defaultWhich = (bin) => {
|
|
3432
|
-
try {
|
|
3433
|
-
(0, import_child_process3.execSync)(`${process.platform === "win32" ? "where" : "which"} ${bin}`, { stdio: "ignore" });
|
|
3434
|
-
return true;
|
|
3435
|
-
} catch {
|
|
3436
|
-
return false;
|
|
3437
|
-
}
|
|
3438
|
-
};
|
|
3439
|
-
async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
|
|
3440
|
-
const platform = deps.platform ?? process.platform;
|
|
3441
|
-
const env = deps.env ?? process.env;
|
|
3442
|
-
const hasTmuxImpl = deps.hasTmuxImpl ?? import_core21.hasTmux;
|
|
3443
|
-
const execFileImpl = deps.execFileImpl ?? defaultExecFile;
|
|
3444
|
-
const spawnImpl = deps.spawnImpl ?? defaultSpawn;
|
|
3445
|
-
const which = deps.which ?? defaultWhich;
|
|
3446
|
-
if (!hasTmuxImpl()) {
|
|
3447
|
-
return { ok: false, message: "No terminal window for this task: tmux is not installed. Install tmux to get one for terminal-transport tasks; a structured task has its log view instead." };
|
|
3448
|
-
}
|
|
3449
|
-
const session = (0, import_core21.tmuxSessionName)(port);
|
|
3450
|
-
const socket = (0, import_core21.tmuxSocketName)(port);
|
|
3451
|
-
const base = (0, import_core21.tmuxWindowName)(taskId, planSessionId);
|
|
3452
|
-
let windows;
|
|
3453
|
-
try {
|
|
3454
|
-
const { stdout } = await execFileImpl("tmux", ["-L", socket, "list-windows", "-t", session, "-F", "#{window_name}"]);
|
|
3455
|
-
windows = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3456
|
-
} catch {
|
|
3457
|
-
return { ok: false, message: "No terminal session found for this daemon \u2014 is it still running?" };
|
|
3458
|
-
}
|
|
3459
|
-
const window = latestAttempt(windows, base);
|
|
3460
|
-
if (!window) {
|
|
3461
|
-
return { ok: false, message: "This task has no live terminal and no saved log \u2014 it may still be pending." };
|
|
3462
|
-
}
|
|
3463
|
-
const manualHint = `tmux -L ${socket} attach -t ${session}:${window}`;
|
|
3464
|
-
const viewer = `${session}-view-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3465
|
-
const attachCmd = `tmux -L ${socket} new-session -d -s ${viewer} -t ${session} \\; set-option -t ${viewer} destroy-unattached on \\; select-window -t ${viewer}:${window} \\; attach-session -t ${viewer}`;
|
|
3466
|
-
const opened = launchTerminal(platform, env, which, spawnImpl, attachCmd);
|
|
3467
|
-
if (!opened) {
|
|
3468
|
-
return { ok: false, message: `Couldn't open a terminal window automatically \u2014 attach manually: ${manualHint}` };
|
|
3469
|
-
}
|
|
3470
|
-
return { ok: true, message: "Opened a terminal for this task." };
|
|
3471
|
-
}
|
|
3472
|
-
function latestAttempt(windows, base) {
|
|
3473
|
-
let best = null;
|
|
3474
|
-
let bestAttempt = 0;
|
|
3475
|
-
for (const name of windows) {
|
|
3476
|
-
if (name === base && bestAttempt < 1) {
|
|
3477
|
-
best = name;
|
|
3478
|
-
bestAttempt = 1;
|
|
3479
|
-
continue;
|
|
3480
|
-
}
|
|
3481
|
-
const suffix = name.startsWith(`${base}-`) ? Number(name.slice(base.length + 1)) : NaN;
|
|
3482
|
-
if (Number.isInteger(suffix) && suffix > bestAttempt) {
|
|
3483
|
-
best = name;
|
|
3484
|
-
bestAttempt = suffix;
|
|
3485
|
-
}
|
|
3486
|
-
}
|
|
3487
|
-
return best;
|
|
3488
|
-
}
|
|
3489
|
-
function launchTerminal(platform, env, which, spawnImpl, attachCmd) {
|
|
3490
|
-
if (platform === "darwin") {
|
|
3491
|
-
const script = `tell application "Terminal" to do script "${attachCmd.replace(/"/g, '\\"')}"`;
|
|
3492
|
-
spawnImpl("osascript", ["-e", script]);
|
|
3493
|
-
return true;
|
|
3494
|
-
}
|
|
3495
|
-
if (platform === "win32") {
|
|
3496
|
-
if (which("wt.exe")) {
|
|
3497
|
-
spawnImpl("wt.exe", ["-w", "0", "nt", "bash", "-lc", attachCmd]);
|
|
3498
|
-
return true;
|
|
3499
|
-
}
|
|
3500
|
-
if (which("cmd.exe")) {
|
|
3501
|
-
spawnImpl("cmd.exe", ["/c", "start", '""', "bash", "-lc", attachCmd]);
|
|
3502
|
-
return true;
|
|
3503
|
-
}
|
|
3504
|
-
return false;
|
|
3505
|
-
}
|
|
3506
|
-
if (!env.DISPLAY && !env.WAYLAND_DISPLAY) return false;
|
|
3507
|
-
const candidates = [];
|
|
3508
|
-
if (env.TERMINAL) candidates.push([env.TERMINAL, ["-e", "bash", "-lc", attachCmd]]);
|
|
3509
|
-
candidates.push(
|
|
3510
|
-
["x-terminal-emulator", ["-e", "bash", "-lc", attachCmd]],
|
|
3511
|
-
["gnome-terminal", ["--", "bash", "-lc", attachCmd]],
|
|
3512
|
-
["konsole", ["-e", "bash", "-lc", attachCmd]],
|
|
3513
|
-
["xfce4-terminal", ["-e", `bash -lc ${JSON.stringify(attachCmd)}`]],
|
|
3514
|
-
["xterm", ["-e", "bash", "-lc", attachCmd]]
|
|
3515
|
-
);
|
|
3516
|
-
for (const [bin, args] of candidates) {
|
|
3517
|
-
if (which(bin)) {
|
|
3518
|
-
spawnImpl(bin, args);
|
|
3519
|
-
return true;
|
|
3520
|
-
}
|
|
3521
|
-
}
|
|
3522
|
-
return false;
|
|
3523
|
-
}
|
|
3524
|
-
|
|
3525
|
-
// src/commands/terminal.ts
|
|
3526
|
-
async function handleTerminal(subArgs, injectedApi) {
|
|
3527
|
-
await withResolvedTask(
|
|
3528
|
-
subArgs,
|
|
3529
|
-
"Usage: ordewell terminal <task-id-or-order> [--session-id <id>]",
|
|
3530
|
-
injectedApi,
|
|
3531
|
-
async (_api, sessionId, taskId) => {
|
|
3532
|
-
const result = await openTaskTerminal(resolvePort(subArgs), sessionId, taskId);
|
|
3533
|
-
if (!result.ok) {
|
|
3534
|
-
console.error(result.message);
|
|
3535
|
-
process.exit(1);
|
|
3536
|
-
}
|
|
3537
|
-
console.log(result.message);
|
|
3538
|
-
}
|
|
3539
|
-
);
|
|
3540
|
-
}
|
|
3541
|
-
|
|
3542
3201
|
// src/commands/task-assign.ts
|
|
3202
|
+
var import_core22 = require("@ordewell/core");
|
|
3543
3203
|
var import_plan_utils = require("@ordewell/core/plan-utils");
|
|
3544
3204
|
|
|
3545
3205
|
// src/tui/taskAssignment.ts
|
|
@@ -3579,12 +3239,13 @@ function modesForTask(modesByRunner, task) {
|
|
|
3579
3239
|
}
|
|
3580
3240
|
|
|
3581
3241
|
// src/commands/task-assign.ts
|
|
3582
|
-
async function withTask(subArgs, usage, injectedApi, run) {
|
|
3242
|
+
async function withTask(subArgs, usage, injectedApi, run, { joinValue = false } = {}) {
|
|
3583
3243
|
await withResolvedTask(subArgs, usage, injectedApi, async (api, sessionId, taskId, plan) => {
|
|
3584
3244
|
const tasks = taskViews(plan);
|
|
3585
3245
|
const task = tasks.find((t) => t.id === taskId);
|
|
3586
3246
|
if (!task) fail(`Task not found in the plan: ${taskId}`);
|
|
3587
|
-
const
|
|
3247
|
+
const [, ...rest] = positionals(subArgs);
|
|
3248
|
+
const value = joinValue ? rest.join(" ") || void 0 : rest[0];
|
|
3588
3249
|
await run(api, sessionId, task, tasks, value);
|
|
3589
3250
|
});
|
|
3590
3251
|
}
|
|
@@ -3740,6 +3401,35 @@ Dependencies \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}
|
|
|
3740
3401
|
console.log(dependencies.length > 0 ? `Task ${(0, import_plan_utils.taskRef)(task)} now depends on ${dependencies.length} task${dependencies.length === 1 ? "" : "s"}.` : `Task ${(0, import_plan_utils.taskRef)(task)} no longer depends on anything.`);
|
|
3741
3402
|
});
|
|
3742
3403
|
}
|
|
3404
|
+
var SKILLS_USAGE = "Usage: ordewell task-skills <task-id-or-order> [<name> [<name> \u2026]|<name,name,\u2026>|none] [--session-id <id>] [--workspace <path>]";
|
|
3405
|
+
async function handleTaskSkills(subArgs, injectedApi) {
|
|
3406
|
+
await withTask(subArgs, SKILLS_USAGE, injectedApi, async (api, sessionId, task, _tasks, value) => {
|
|
3407
|
+
if (task.type !== "ai") fail("Manual tasks do not run an executor, so they take no skills.");
|
|
3408
|
+
const catalog = (0, import_core22.createSkillsService)(flag(subArgs, "--workspace") || process.cwd(), new import_core22.EnvConfig().workspaceRepos).listSkills().filter((s) => s.appliesTo === "task");
|
|
3409
|
+
if (!value) {
|
|
3410
|
+
console.log(`
|
|
3411
|
+
Skills \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}
|
|
3412
|
+
`);
|
|
3413
|
+
if (catalog.length === 0) console.log(" No task skills found (applies-to: task in .ordewell/skills/ or ~/.ordewell/skills/).");
|
|
3414
|
+
for (const s of catalog) {
|
|
3415
|
+
console.log(` ${task.skills?.includes(s.name) ? "*" : " "} ${s.name}${s.description ? ` ${s.description}` : ""}`);
|
|
3416
|
+
}
|
|
3417
|
+
for (const name of task.skills ?? []) {
|
|
3418
|
+
if (!catalog.some((s) => s.name === name)) console.log(` * ${name} (not found)`);
|
|
3419
|
+
}
|
|
3420
|
+
console.log(`
|
|
3421
|
+
* = attached. ${SKILLS_USAGE}`);
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3424
|
+
const skills = value.toLowerCase() === "none" ? [] : [...new Set(value.toLowerCase().split(/[,\s]+/).filter(Boolean))];
|
|
3425
|
+
const unknown = skills.filter((name) => !catalog.some((s) => s.name === name));
|
|
3426
|
+
if (unknown.length > 0) {
|
|
3427
|
+
fail(`No task skill named ${unknown.map((n) => `"${n}"`).join(", ")}.`, `Task skills: ${catalog.map((s) => s.name).join(", ") || "none"}`);
|
|
3428
|
+
}
|
|
3429
|
+
await api.updateTask(sessionId, task.id, { skills });
|
|
3430
|
+
console.log(skills.length > 0 ? `Task ${(0, import_plan_utils.taskRef)(task)} skills set to ${skills.join(", ")}.` : `Task ${(0, import_plan_utils.taskRef)(task)} has no skills attached.`);
|
|
3431
|
+
}, { joinValue: true });
|
|
3432
|
+
}
|
|
3743
3433
|
var OPS_USAGE = "Usage: ordewell task-ops <task-id-or-order> [on|off] [--session-id <id>]";
|
|
3744
3434
|
async function handleTaskOps(subArgs, injectedApi) {
|
|
3745
3435
|
await withTask(subArgs, OPS_USAGE, injectedApi, async (api, sessionId, task, tasks, value) => {
|
|
@@ -3760,7 +3450,7 @@ ${(0, import_plan_utils.titledTaskRef)(task)} is ${task.ops ? "an ops task: it r
|
|
|
3760
3450
|
}
|
|
3761
3451
|
|
|
3762
3452
|
// src/tui/index.ts
|
|
3763
|
-
var
|
|
3453
|
+
var import_core37 = require("@ordewell/core");
|
|
3764
3454
|
|
|
3765
3455
|
// src/tui/editor.ts
|
|
3766
3456
|
function emptyEditor() {
|
|
@@ -3910,12 +3600,12 @@ var SLASH_COMMANDS = [
|
|
|
3910
3600
|
{ name: "force-start", usage: "/force-start <id>", description: "Start a task now, ignoring dependencies", category: "tasks" },
|
|
3911
3601
|
{ name: "continue", usage: "/continue <id> <message>", description: "Continue a finished task in its saved runner session, with your message", category: "tasks" },
|
|
3912
3602
|
{ name: "checkpoint", usage: "/checkpoint <id> approve|reject [reason]", description: "Answer the checkpoint a task waits at: approve lets it go on, reject tells it why not", category: "tasks" },
|
|
3913
|
-
{ name: "terminal", usage: "/terminal <id>", description: "Open a task: its OS terminal, or a structured task's live log", category: "tasks" },
|
|
3914
3603
|
{ name: "task-runner", usage: "/task-runner <id> [runner]", description: "Choose or set a task's executor (re-picks its model and mode)", category: "tasks" },
|
|
3915
3604
|
{ name: "task-model", usage: "/task-model <id> [model]", description: "Choose or set a task's executor model", category: "tasks" },
|
|
3916
3605
|
{ name: "task-effort", usage: "/task-effort <id> [level]", description: "Choose or set a task's thinking effort", category: "tasks" },
|
|
3917
3606
|
{ name: "task-mode", usage: "/task-mode <id> [mode]", description: "Choose or set a task's runner mode", category: "tasks" },
|
|
3918
3607
|
{ name: "task-deps", usage: "/task-deps <id>", description: "Edit which earlier tasks a task waits for", category: "tasks" },
|
|
3608
|
+
{ name: "task-skills", usage: "/task-skills <id> [a,b|none]", description: "Choose or set the skills attached to a task; none clears them", category: "tasks" },
|
|
3919
3609
|
{ name: "task-ops", usage: "/task-ops <id> [on|off]", description: "Make a task an ops task \u2014 run in your checkout once its dependencies are merged \u2014 or a change task", category: "tasks" },
|
|
3920
3610
|
// Models & providers
|
|
3921
3611
|
{ name: "planner", usage: "/planner [<provider>]", description: "Choose who plans \u2014 an API provider or a coding agent (no API key)", category: "models" },
|
|
@@ -3925,12 +3615,8 @@ var SLASH_COMMANDS = [
|
|
|
3925
3615
|
{ name: "allowlist", usage: "/allowlist [set <runner> <ids> | clear <runner>]", description: "Limit which models a runner may use", category: "models" },
|
|
3926
3616
|
{ name: "runners", usage: "/runners [<id> on|off]", description: "Enable or disable runners (claude-code, opencode, codex)", category: "models" },
|
|
3927
3617
|
{ name: "auto", usage: "/auto [full|guarded]", description: "Autonomy level for new plans: Full or Guarded", category: "models" },
|
|
3928
|
-
{ name: "transport", usage: "/transport [terminal|structured]", description: "Drive tasks through each runner's protocol (structured) or a terminal, from the next run", category: "models" },
|
|
3929
3618
|
{ name: "parallel", usage: "/parallel [<n>]", description: "How many AI tasks run at once (applies to a live run)", category: "models" },
|
|
3930
3619
|
{ name: "refresh", usage: "/refresh", description: "Re-discover runners and model catalogs", category: "models" },
|
|
3931
|
-
// Skills
|
|
3932
|
-
{ name: "tdd", usage: "/tdd [on|off]", description: "Test-Driven Development mode", category: "skills" },
|
|
3933
|
-
{ name: "verify", usage: "/verify [on|off]", description: "Verification mode \u2014 final evidence-based task that runs the suite", category: "skills" },
|
|
3934
3620
|
// Sessions
|
|
3935
3621
|
{ name: "sessions", usage: "/sessions", description: "List saved sessions", category: "session" },
|
|
3936
3622
|
{ name: "fork", usage: "/fork", description: "Continue in a copy of this conversation and its tasks; the original stays as it is", category: "session" },
|
|
@@ -3951,11 +3637,11 @@ var skillByName = /* @__PURE__ */ new Map();
|
|
|
3951
3637
|
function registerSkillCommands(skills) {
|
|
3952
3638
|
const map = /* @__PURE__ */ new Map();
|
|
3953
3639
|
for (const skill of skills) {
|
|
3954
|
-
if (BY_NAME.has(skill.name)) continue;
|
|
3640
|
+
if (BY_NAME.has(skill.name) || skill.userInvocable === false) continue;
|
|
3955
3641
|
map.set(skill.name, {
|
|
3956
3642
|
name: skill.name,
|
|
3957
3643
|
usage: `/${skill.name}`,
|
|
3958
|
-
description: skill.description,
|
|
3644
|
+
description: skill.appliesTo === "task" ? `task skill \xB7 ${skill.description}` : skill.description,
|
|
3959
3645
|
category: "skills",
|
|
3960
3646
|
source: "skill"
|
|
3961
3647
|
});
|
|
@@ -4010,7 +3696,7 @@ function tokenCompletions(token) {
|
|
|
4010
3696
|
}
|
|
4011
3697
|
|
|
4012
3698
|
// src/tui/blocks.ts
|
|
4013
|
-
var
|
|
3699
|
+
var import_core23 = require("@ordewell/core");
|
|
4014
3700
|
|
|
4015
3701
|
// src/tui/markdown.ts
|
|
4016
3702
|
function renderMarkdown(source, cols) {
|
|
@@ -4150,9 +3836,29 @@ var ROLE_PREFIX = {
|
|
|
4150
3836
|
function messageLines(block, cols) {
|
|
4151
3837
|
const text = sanitize(block.text);
|
|
4152
3838
|
const room = Math.max(1, cols - 2);
|
|
4153
|
-
const wrapped = block.role === "planner" || block.role === "agent" ? renderMarkdown(text, room) : wrap(text, room);
|
|
3839
|
+
const wrapped = block.role === "planner" || block.role === "agent" ? renderMarkdown(text, room) : block.skills ? skillPaintedLines(text, block.skills, room) : wrap(text, room);
|
|
4154
3840
|
return wrapped.map((line, i) => i === 0 ? ROLE_PREFIX[block.role](line) : ` ${line}`);
|
|
4155
3841
|
}
|
|
3842
|
+
function skillPaintedLines(text, skills, room) {
|
|
3843
|
+
const tokens = (0, import_core23.loadedSkillTokens)(text, skills);
|
|
3844
|
+
return wrapLines(text, room).map(({ line, start }) => {
|
|
3845
|
+
let painted = "";
|
|
3846
|
+
let at = 0;
|
|
3847
|
+
for (const token of tokens) {
|
|
3848
|
+
const from = Math.max(token.start - start, at);
|
|
3849
|
+
const to = Math.min(token.end - start, line.length);
|
|
3850
|
+
if (to <= from) continue;
|
|
3851
|
+
painted += line.slice(at, from) + style.cyan(line.slice(from, to));
|
|
3852
|
+
at = to;
|
|
3853
|
+
}
|
|
3854
|
+
return painted + line.slice(at);
|
|
3855
|
+
});
|
|
3856
|
+
}
|
|
3857
|
+
function skillLoadLine(block, cols) {
|
|
3858
|
+
const head = block.attaches ? `\u25CF /${sanitize(block.name)} will be attached to fitting tasks \xB7 ` : block.invokedBy === "planner" ? `\u25CF ${sanitize(block.name)} skill loaded by planner \xB7 ` : `\u25CF /${sanitize(block.name)} skill loaded \xB7 `;
|
|
3859
|
+
const line = truncate(`${head}${truncatePath(sanitize(block.path), Math.max(1, cols - width(head)))}`, cols);
|
|
3860
|
+
return `${style.green("\u25CF")}${style.grey(line.slice(1))}`;
|
|
3861
|
+
}
|
|
4156
3862
|
function thinkingLines(block, cols, detailAll) {
|
|
4157
3863
|
const text = sanitize(block.text).trim();
|
|
4158
3864
|
const head = `\u2234 Thinking (${countOf(text ? text.split(/\s+/).length : 0, "word")})`;
|
|
@@ -4196,7 +3902,7 @@ function truncatePath(path, max) {
|
|
|
4196
3902
|
if (width(char + tail) > max - 1) break;
|
|
4197
3903
|
tail = char + tail;
|
|
4198
3904
|
}
|
|
4199
|
-
const slash = tail.
|
|
3905
|
+
const slash = tail.search(/[\\/]/);
|
|
4200
3906
|
return `\u2026${slash > 0 ? tail.slice(slash) : tail}`;
|
|
4201
3907
|
}
|
|
4202
3908
|
function commandHeader(block, cols, detailAll) {
|
|
@@ -4230,16 +3936,16 @@ function resultLines(block, room, detailAll) {
|
|
|
4230
3936
|
const fit2 = (line) => detailAll ? wrap(line, room) : [truncate(line, room)];
|
|
4231
3937
|
const outcome = unsettledOutcome(block);
|
|
4232
3938
|
if (outcome) {
|
|
4233
|
-
const [reason, ...rest] = (0,
|
|
3939
|
+
const [reason, ...rest] = (0, import_core23.outputLines)(block.output).map(sanitize);
|
|
4234
3940
|
const status = fit2(reason ? `${outcome.label}: ${reason}` : outcome.label).map(outcome.paint);
|
|
4235
3941
|
return detailAll ? [...status, ...rest.flatMap(fit2).map(style.grey)] : status;
|
|
4236
3942
|
}
|
|
4237
3943
|
if (detailAll) {
|
|
4238
|
-
const lines2 = (0,
|
|
3944
|
+
const lines2 = (0, import_core23.outputLines)(block.output).map(sanitize);
|
|
4239
3945
|
const note = lines2.length > PREVIEW_LINES ? ["\u2026 (ctrl+o to collapse)"] : [];
|
|
4240
3946
|
return lines2.length > 0 ? [...lines2.flatMap(fit2), ...note].map(style.grey) : [style.grey("(no output)")];
|
|
4241
3947
|
}
|
|
4242
|
-
const { lines, hiddenLineCount } = (0,
|
|
3948
|
+
const { lines, hiddenLineCount } = (0, import_core23.outputPreview)(block.output, PREVIEW_LINES);
|
|
4243
3949
|
if (lines.length === 0) return [style.grey("(no output)")];
|
|
4244
3950
|
const more = hiddenLineCount > 0 ? [`\u2026 +${countOf(hiddenLineCount, "line")} (ctrl+o to expand)`] : [];
|
|
4245
3951
|
return [...lines.map(sanitize), ...more].map((line) => style.grey(truncate(line, room)));
|
|
@@ -4253,7 +3959,7 @@ var DIFF_PAINT = {
|
|
|
4253
3959
|
};
|
|
4254
3960
|
var DIFF_SIGN = { added: "+", removed: "-", context: " ", gap: "" };
|
|
4255
3961
|
function diffLines(block, diff, room, detailAll) {
|
|
4256
|
-
const rows = (0,
|
|
3962
|
+
const rows = (0, import_core23.diffRows)(block.output);
|
|
4257
3963
|
const gutter = Math.max(0, ...rows.map((row) => String(row.line ?? "").length));
|
|
4258
3964
|
const draw = (row) => {
|
|
4259
3965
|
const text = row.kind === "gap" ? `${" ".repeat(gutter)} \u22EE` : `${String(row.line ?? "").padStart(gutter)} ${DIFF_SIGN[row.kind]} ${sanitize(row.text)}`;
|
|
@@ -4262,7 +3968,7 @@ function diffLines(block, diff, room, detailAll) {
|
|
|
4262
3968
|
const shown = detailAll ? rows : rows.slice(0, DIFF_PREVIEW_ROWS);
|
|
4263
3969
|
const hidden = rows.length - shown.length;
|
|
4264
3970
|
const more = hidden > 0 ? [style.grey(`\u2026 +${countOf(hidden, "line")} (ctrl+o to expand)`)] : [];
|
|
4265
|
-
return [(0,
|
|
3971
|
+
return [(0, import_core23.diffSummary)(diff), ...shown.flatMap(draw), ...more];
|
|
4266
3972
|
}
|
|
4267
3973
|
function toolLines(block, cols, detailAll) {
|
|
4268
3974
|
const room = Math.max(1, cols - OUTPUT_INDENT.length);
|
|
@@ -4350,6 +4056,8 @@ function blockLines(block, cols, detailAll) {
|
|
|
4350
4056
|
return [approvalLine(block, cols)];
|
|
4351
4057
|
case "plan":
|
|
4352
4058
|
return [planLine(block, cols)];
|
|
4059
|
+
case "skill_load":
|
|
4060
|
+
return [skillLoadLine(block, cols)];
|
|
4353
4061
|
// Pinned under the pane rather than drawn in it — see `tokenLine`.
|
|
4354
4062
|
case "usage":
|
|
4355
4063
|
return [];
|
|
@@ -4365,11 +4073,11 @@ function drawn(block, cols, detailAll) {
|
|
|
4365
4073
|
}
|
|
4366
4074
|
function conversationLines(blocks, cols, detailAll, answering) {
|
|
4367
4075
|
const lines = [];
|
|
4368
|
-
for (const block of blocks) {
|
|
4076
|
+
for (const [i, block] of blocks.entries()) {
|
|
4369
4077
|
if (block.type === "usage") continue;
|
|
4370
4078
|
lines.push(...drawn(block, cols, detailAll));
|
|
4371
4079
|
if (block === answering) lines.push(approvalKeysLine(block, cols));
|
|
4372
|
-
lines.push("");
|
|
4080
|
+
if (blocks[i + 1]?.type !== "skill_load") lines.push("");
|
|
4373
4081
|
}
|
|
4374
4082
|
return lines;
|
|
4375
4083
|
}
|
|
@@ -4397,11 +4105,7 @@ function tokenLine(blocks, cols) {
|
|
|
4397
4105
|
}
|
|
4398
4106
|
|
|
4399
4107
|
// src/tui/state.ts
|
|
4400
|
-
var
|
|
4401
|
-
var SKILL_IDS = ["tdd", "verify"];
|
|
4402
|
-
function noSkills() {
|
|
4403
|
-
return { tdd: false, verify: false };
|
|
4404
|
-
}
|
|
4108
|
+
var import_core24 = require("@ordewell/core");
|
|
4405
4109
|
function visibleItems(picker2) {
|
|
4406
4110
|
const filter = picker2.filter.trim().toLowerCase();
|
|
4407
4111
|
if (!filter) return picker2.items;
|
|
@@ -4409,7 +4113,7 @@ function visibleItems(picker2) {
|
|
|
4409
4113
|
(i) => i.id.toLowerCase().includes(filter) || i.label.toLowerCase().includes(filter) || (i.detail ?? "").toLowerCase().includes(filter)
|
|
4410
4114
|
);
|
|
4411
4115
|
}
|
|
4412
|
-
var anyTaskRunning = (state) => state.tasks.some(
|
|
4116
|
+
var anyTaskRunning = (state) => state.tasks.some(import_core24.isTaskRunning);
|
|
4413
4117
|
var plannerInFlight = (state) => state.status === "planning" || state.status === "researching";
|
|
4414
4118
|
function planRows(state) {
|
|
4415
4119
|
const rows = [];
|
|
@@ -4446,9 +4150,9 @@ function findTask(tasks, id) {
|
|
|
4446
4150
|
function initialState(overrides = {}) {
|
|
4447
4151
|
return {
|
|
4448
4152
|
editor: emptyEditor(),
|
|
4449
|
-
conversation:
|
|
4153
|
+
conversation: import_core24.EMPTY_CONVERSATION,
|
|
4450
4154
|
taskView: null,
|
|
4451
|
-
turnGate:
|
|
4155
|
+
turnGate: import_core24.NO_TURN,
|
|
4452
4156
|
detailAll: false,
|
|
4453
4157
|
status: "idle",
|
|
4454
4158
|
busyLabel: "",
|
|
@@ -4462,22 +4166,21 @@ function initialState(overrides = {}) {
|
|
|
4462
4166
|
taskEditor: null,
|
|
4463
4167
|
scroll: 0,
|
|
4464
4168
|
planScroll: null,
|
|
4465
|
-
skills: noSkills(),
|
|
4466
4169
|
runners: [],
|
|
4467
4170
|
sessions: [],
|
|
4468
4171
|
rewindTargets: null,
|
|
4469
4172
|
models: [],
|
|
4470
4173
|
modesByRunner: {},
|
|
4174
|
+
taskSkills: [],
|
|
4471
4175
|
orchestratorModels: [],
|
|
4472
4176
|
providerErrors: {},
|
|
4473
4177
|
orchestratorModel: "",
|
|
4474
4178
|
plannerProvider: "",
|
|
4475
4179
|
plannerEffort: "",
|
|
4476
|
-
maxParallel:
|
|
4180
|
+
maxParallel: import_core24.DEFAULT_MAX_PARALLEL,
|
|
4477
4181
|
configuredProviders: [],
|
|
4478
4182
|
allowlist: {},
|
|
4479
4183
|
autonomous: true,
|
|
4480
|
-
runnerTransport: "structured",
|
|
4481
4184
|
mouseCapture: true,
|
|
4482
4185
|
selection: null,
|
|
4483
4186
|
workspace: process.cwd(),
|
|
@@ -4485,7 +4188,7 @@ function initialState(overrides = {}) {
|
|
|
4485
4188
|
gate: null,
|
|
4486
4189
|
overlay: null,
|
|
4487
4190
|
pendingApprovals: [],
|
|
4488
|
-
queuedPrompts:
|
|
4191
|
+
queuedPrompts: import_core24.EMPTY_HOLD,
|
|
4489
4192
|
stopArmed: false,
|
|
4490
4193
|
stopArmToken: 0,
|
|
4491
4194
|
stopRequested: false,
|
|
@@ -4533,7 +4236,7 @@ function chatEditorRoomFor(state, active) {
|
|
|
4533
4236
|
}
|
|
4534
4237
|
|
|
4535
4238
|
// src/tui/layout.ts
|
|
4536
|
-
var
|
|
4239
|
+
var import_core25 = require("@ordewell/core");
|
|
4537
4240
|
var CHROME_ROWS = 4;
|
|
4538
4241
|
function chatInputWrap(state) {
|
|
4539
4242
|
const active = state.focus === "chat" && !state.overlay;
|
|
@@ -4542,7 +4245,7 @@ function chatInputWrap(state) {
|
|
|
4542
4245
|
}
|
|
4543
4246
|
function footerHints(state) {
|
|
4544
4247
|
const selected = selectedPlanRow(state)?.task;
|
|
4545
|
-
const markHint = selected && (0,
|
|
4248
|
+
const markHint = selected && (0, import_core25.markAction)(selected) === "uncomplete" ? "m undone" : "m done";
|
|
4546
4249
|
const resolveHint = selected?.isolation?.state === "conflict" ? ["x resolve conflict"] : [];
|
|
4547
4250
|
const planning = plannerInFlight(state) && !state.taskView;
|
|
4548
4251
|
const escHint = !planning ? null : state.queuedPrompts.length > 0 ? "esc unsend" : "esc \xD72 stop planning";
|
|
@@ -4552,12 +4255,13 @@ function footerHints(state) {
|
|
|
4552
4255
|
}
|
|
4553
4256
|
return [
|
|
4554
4257
|
...escHint ? [escHint] : [],
|
|
4555
|
-
"
|
|
4258
|
+
"\u2192 expand/edit",
|
|
4556
4259
|
"R runner",
|
|
4557
4260
|
"o model",
|
|
4558
4261
|
"e effort",
|
|
4559
4262
|
"M mode",
|
|
4560
4263
|
"D deps",
|
|
4264
|
+
"K skills",
|
|
4561
4265
|
"O ops",
|
|
4562
4266
|
"f start",
|
|
4563
4267
|
"E run plan",
|
|
@@ -4567,13 +4271,13 @@ function footerHints(state) {
|
|
|
4567
4271
|
"s skip",
|
|
4568
4272
|
"a add",
|
|
4569
4273
|
"d remove",
|
|
4570
|
-
"
|
|
4274
|
+
"enter log",
|
|
4571
4275
|
...resolveHint,
|
|
4572
4276
|
"pgup/pgdn scroll",
|
|
4573
4277
|
"tab chat"
|
|
4574
4278
|
];
|
|
4575
4279
|
}
|
|
4576
|
-
const detailHint = (0,
|
|
4280
|
+
const detailHint = (0, import_core25.hasHiddenDetail)(state.taskView ? state.taskView.view.blocks : state.conversation.blocks) ? [state.detailAll ? "ctrl-o collapse all" : "ctrl-o expand all"] : [];
|
|
4577
4281
|
return [
|
|
4578
4282
|
"/help",
|
|
4579
4283
|
"tab plan",
|
|
@@ -4609,10 +4313,10 @@ function queuedTaskBubble(message, selected, cols) {
|
|
|
4609
4313
|
return wrap(text, room).map((line, i) => i === 0 ? truncate(`${marker2} ${selected ? style.bold(line) : line}${style.grey(tag)}`, cols) : truncate(` ${line}`, cols));
|
|
4610
4314
|
}
|
|
4611
4315
|
function taskActivity(task, working) {
|
|
4612
|
-
const approvals = (0,
|
|
4316
|
+
const approvals = (0, import_core25.approvalLabel)(task?.awaitingApproval);
|
|
4613
4317
|
if (approvals) return inRow(approvals);
|
|
4614
4318
|
if (working) return "working";
|
|
4615
|
-
if (task?.status === "awaiting_user") return inRow((0,
|
|
4319
|
+
if (task?.status === "awaiting_user") return inRow((0, import_core25.awaitingLabel)(task) ?? "Waiting for your input");
|
|
4616
4320
|
return task?.status ?? "";
|
|
4617
4321
|
}
|
|
4618
4322
|
function taskHeaderLines(state, tv, cols) {
|
|
@@ -4687,9 +4391,9 @@ function chatScrollMax(state) {
|
|
|
4687
4391
|
}
|
|
4688
4392
|
function plannerSetup(state) {
|
|
4689
4393
|
const provider = state.plannerProvider;
|
|
4690
|
-
const registration =
|
|
4394
|
+
const registration = import_core25.ALL_PROVIDERS[provider];
|
|
4691
4395
|
if (!registration) return { label: "not set", ready: false };
|
|
4692
|
-
const runner = (0,
|
|
4396
|
+
const runner = (0, import_core25.runnerForProvider)(provider);
|
|
4693
4397
|
const ready = runner ? state.runners.some((r) => r.id === runner) : state.configuredProviders.includes(provider);
|
|
4694
4398
|
return { label: registration.label, ready };
|
|
4695
4399
|
}
|
|
@@ -4861,7 +4565,7 @@ function planScrollExtent(state) {
|
|
|
4861
4565
|
}
|
|
4862
4566
|
function taskLines(state, planRow, index, cols, orderOf) {
|
|
4863
4567
|
const { task, parent } = planRow;
|
|
4864
|
-
const row = (0,
|
|
4568
|
+
const row = (0, import_core25.taskRowView)(task, { parent, orderOf, modes: modesForTask(state.modesByRunner, task) });
|
|
4865
4569
|
const selected = state.focus === "plan" && index === state.selectedTask;
|
|
4866
4570
|
const expanded = state.expandedTaskId === task.id;
|
|
4867
4571
|
const rawIcon = row.status === "running" ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[row.status];
|
|
@@ -4888,11 +4592,11 @@ function taskLines(state, planRow, index, cols, orderOf) {
|
|
|
4888
4592
|
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${row.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
4889
4593
|
const runner = task.assignedRunner ?? "";
|
|
4890
4594
|
const bodyPad = parent ? " " : " ";
|
|
4891
|
-
const
|
|
4892
|
-
const
|
|
4893
|
-
const meta = [activity, runner,
|
|
4595
|
+
const activity = row.approvals ? `${inRow(row.approvals)} \u2014 enter opens it` : row.status === "quiet" ? "quiet" : row.running ? "working" : row.awaiting ? inRow(row.awaiting) : "";
|
|
4596
|
+
const skills = (task.skills ?? []).join(" \xB7 ");
|
|
4597
|
+
const meta = [activity, runner, model].filter(Boolean).join(" \xB7 ");
|
|
4894
4598
|
if (meta) lines.push(style.grey(truncate(`${bodyPad}${meta}`, cols)));
|
|
4895
|
-
if (
|
|
4599
|
+
if (skills) lines.push(style.grey(truncate(`${bodyPad}skills: ${skills}`, cols)));
|
|
4896
4600
|
if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
|
|
4897
4601
|
if (row.conflict) {
|
|
4898
4602
|
const where = row.conflict.repo ? ` in ${row.conflict.repo}` : "";
|
|
@@ -4947,7 +4651,7 @@ function taskLines(state, planRow, index, cols, orderOf) {
|
|
|
4947
4651
|
lines.push(...taskText("Depends on", row.dependencies.join(", "), cols, bodyPad));
|
|
4948
4652
|
}
|
|
4949
4653
|
lines.push(style.grey(`${bodyPad}enter save \xB7 esc cancel`));
|
|
4950
|
-
lines.push(style.grey(truncate(`${bodyPad}then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps`, cols)));
|
|
4654
|
+
lines.push(style.grey(truncate(`${bodyPad}then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps \xB7 K skills`, cols)));
|
|
4951
4655
|
}
|
|
4952
4656
|
return { lines, editorLine };
|
|
4953
4657
|
}
|
|
@@ -4992,7 +4696,7 @@ function helpLayout(rows, cols) {
|
|
|
4992
4696
|
);
|
|
4993
4697
|
body.push(
|
|
4994
4698
|
"",
|
|
4995
|
-
style.bold("In a task view (
|
|
4699
|
+
style.bold("In a task view (enter on a task)"),
|
|
4996
4700
|
style.grey(" ctrl-s sends now: interrupts the running step, then delivers the composer text \u2014 or, with it empty, the selected queued message"),
|
|
4997
4701
|
style.grey(" ctrl-n/ctrl-p select a queued message \xB7 ctrl-r removes it \xB7 ctrl-x interrupts"),
|
|
4998
4702
|
style.grey(" ctrl-y allows a tool request (its keys sit under it) \xB7 ctrl-t allows it for the task \xB7 ctrl-g denies it, with the composer text as the note"),
|
|
@@ -5008,13 +4712,13 @@ function helpScrollMax(state) {
|
|
|
5008
4712
|
}
|
|
5009
4713
|
|
|
5010
4714
|
// src/tui/render.ts
|
|
5011
|
-
var
|
|
4715
|
+
var import_core26 = require("@ordewell/core");
|
|
5012
4716
|
function render(state) {
|
|
5013
4717
|
const { rows, cols } = state;
|
|
5014
4718
|
const rowsForBody = bodyRows(state);
|
|
5015
4719
|
const body = state.overlay ? renderOverlay(state, rowsForBody, cols) : renderBody(state, rowsForBody, cols);
|
|
5016
4720
|
const lines = [
|
|
5017
|
-
|
|
4721
|
+
renderAutonomy(state, cols),
|
|
5018
4722
|
...body,
|
|
5019
4723
|
renderStatus(state, cols),
|
|
5020
4724
|
...renderInput(state),
|
|
@@ -5099,13 +4803,9 @@ function fit(lines, rows, anchor) {
|
|
|
5099
4803
|
const blanks = Array(rows - lines.length).fill("");
|
|
5100
4804
|
return anchor === "bottom" ? [...blanks, ...lines] : [...lines, ...blanks];
|
|
5101
4805
|
}
|
|
5102
|
-
function
|
|
5103
|
-
const badges = SKILL_IDS.map(
|
|
5104
|
-
(id) => state.skills[id] ? style.green(`\u25CF ${id}`) : style.grey(`\u25CB ${id}`)
|
|
5105
|
-
);
|
|
4806
|
+
function renderAutonomy(state, cols) {
|
|
5106
4807
|
const auto = state.autonomous ? style.yellow("\u25CF Full") : style.green("\u25CF Guarded");
|
|
5107
|
-
|
|
5108
|
-
return truncate([...badges, auto, ...terminal].join(" "), cols);
|
|
4808
|
+
return truncate(auto, cols);
|
|
5109
4809
|
}
|
|
5110
4810
|
function scrolledBackMark(state) {
|
|
5111
4811
|
return state.focus !== "plan" && state.scroll > 0 ? "\u2191 scrolled back \xB7 pgdn for live" : "";
|
|
@@ -5273,8 +4973,8 @@ function renderHandoff(state, overlay, rows, cols) {
|
|
|
5273
4973
|
return frame(`Diff \u2014 ${handoffBranch(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
|
|
5274
4974
|
}
|
|
5275
4975
|
const landed = handoff.landed.length === 0 ? [style.grey("Nothing landed on it.")] : handoff.landed.map((task) => {
|
|
5276
|
-
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0,
|
|
5277
|
-
return ` ${style.green("\u2713")} ${(0,
|
|
4976
|
+
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0, import_core26.capConflictFiles)(task.repairedFiles)})`) : "";
|
|
4977
|
+
return ` ${style.green("\u2713")} ${(0, import_core26.titledTaskRef)(task)}${repaired}`;
|
|
5278
4978
|
});
|
|
5279
4979
|
const actions = handoffActions(handoff).map((action, index) => {
|
|
5280
4980
|
const active = index === overlay.index;
|
|
@@ -5363,10 +5063,10 @@ function frame(title, body, rows, cols) {
|
|
|
5363
5063
|
}
|
|
5364
5064
|
|
|
5365
5065
|
// src/tui/reducer.ts
|
|
5366
|
-
var
|
|
5066
|
+
var import_core34 = require("@ordewell/core");
|
|
5367
5067
|
|
|
5368
5068
|
// src/tui/reducers/overlays.ts
|
|
5369
|
-
var
|
|
5069
|
+
var import_core32 = require("@ordewell/core");
|
|
5370
5070
|
|
|
5371
5071
|
// src/tui/reducers/shared.ts
|
|
5372
5072
|
function step(state, effects = []) {
|
|
@@ -5415,10 +5115,10 @@ function handleApprovalKey(state, overlay, key) {
|
|
|
5415
5115
|
}
|
|
5416
5116
|
|
|
5417
5117
|
// src/tui/reducers/commands.ts
|
|
5418
|
-
var
|
|
5118
|
+
var import_core31 = require("@ordewell/core");
|
|
5419
5119
|
|
|
5420
5120
|
// src/tui/reducers/pickers.ts
|
|
5421
|
-
var
|
|
5121
|
+
var import_core27 = require("@ordewell/core");
|
|
5422
5122
|
var import_plan_utils2 = require("@ordewell/core/plan-utils");
|
|
5423
5123
|
var DEFAULT_EFFORT = "__runner_default__";
|
|
5424
5124
|
function picker(title, items, action, extra = {}) {
|
|
@@ -5437,11 +5137,11 @@ function modelDetail(m) {
|
|
|
5437
5137
|
function providerErrorHint(state) {
|
|
5438
5138
|
const failed = Object.keys(state.providerErrors ?? {});
|
|
5439
5139
|
if (failed.length === 0) return void 0;
|
|
5440
|
-
const names = failed.map((p) =>
|
|
5140
|
+
const names = failed.map((p) => import_core27.ALL_PROVIDERS[p]?.label ?? p);
|
|
5441
5141
|
return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")} \u2014 showing working providers only.`;
|
|
5442
5142
|
}
|
|
5443
5143
|
function plannerModelItems(state) {
|
|
5444
|
-
const runner = (0,
|
|
5144
|
+
const runner = (0, import_core27.runnerForProvider)(state.plannerProvider);
|
|
5445
5145
|
if (!runner) return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m) }));
|
|
5446
5146
|
const items = state.models.filter((m) => m.runners?.includes(runner)).map((m) => ({
|
|
5447
5147
|
id: m.id,
|
|
@@ -5455,7 +5155,7 @@ function plannerModelItems(state) {
|
|
|
5455
5155
|
return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
|
|
5456
5156
|
}
|
|
5457
5157
|
function plannerEffortItems(state) {
|
|
5458
|
-
const runner = (0,
|
|
5158
|
+
const runner = (0, import_core27.runnerForProvider)(state.plannerProvider);
|
|
5459
5159
|
if (!runner) {
|
|
5460
5160
|
return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
|
|
5461
5161
|
}
|
|
@@ -5519,10 +5219,21 @@ function pickerItemsFor(state, action) {
|
|
|
5519
5219
|
if (action.kind === "set-task-deps") {
|
|
5520
5220
|
return (0, import_plan_utils2.dependencyCandidates)(state.tasks, action.taskId).map((candidate) => ({
|
|
5521
5221
|
id: candidate.id,
|
|
5522
|
-
label: (0,
|
|
5222
|
+
label: (0, import_core27.titledTaskRef)(candidate),
|
|
5523
5223
|
detail: candidate.status === "completed" ? "already completed" : void 0
|
|
5524
5224
|
}));
|
|
5525
5225
|
}
|
|
5226
|
+
if (action.kind === "set-task-skills") {
|
|
5227
|
+
const task = findTask(state.tasks, action.taskId);
|
|
5228
|
+
if (!task) return [];
|
|
5229
|
+
const known = new Set(state.taskSkills.map((s) => s.name));
|
|
5230
|
+
return [
|
|
5231
|
+
...state.taskSkills.map((s) => ({ id: s.name, label: s.name, detail: s.description || void 0 })),
|
|
5232
|
+
// Attached but not in the catalog (created by an earlier task's worktree): keep it
|
|
5233
|
+
// selectable so confirming the picker does not silently detach it.
|
|
5234
|
+
...(task.skills ?? []).filter((name) => !known.has(name)).map((name) => ({ id: name, label: `${name} (not found)`, detail: "not found in the catalog" }))
|
|
5235
|
+
];
|
|
5236
|
+
}
|
|
5526
5237
|
if (action.kind === "set-task-mode") {
|
|
5527
5238
|
const task = findTask(state.tasks, action.taskId);
|
|
5528
5239
|
if (!task) return [];
|
|
@@ -5564,28 +5275,24 @@ function pickerItemsFor(state, action) {
|
|
|
5564
5275
|
}
|
|
5565
5276
|
function plannerItems(state) {
|
|
5566
5277
|
const installed = new Set(state.runners.map((r) => r.id));
|
|
5567
|
-
const
|
|
5568
|
-
|
|
5569
|
-
const
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
id,
|
|
5580
|
-
label: import_core26.ALL_PROVIDERS[id].label,
|
|
5581
|
-
detail: import_core26.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
5582
|
-
selected: state.plannerProvider === id
|
|
5278
|
+
const usability = {};
|
|
5279
|
+
for (const id of import_core27.CLI_PROVIDERS) {
|
|
5280
|
+
const runner = (0, import_core27.runnerForProvider)(id);
|
|
5281
|
+
usability[runner] = installed.has(runner) ? { usable: true } : { usable: false, reason: "CLI not installed or not on PATH" };
|
|
5282
|
+
}
|
|
5283
|
+
const entries = (0, import_core27.plannerBackendEntries)(usability, state.configuredProviders);
|
|
5284
|
+
const items = entries.map((entry) => ({
|
|
5285
|
+
id: entry.id,
|
|
5286
|
+
label: entry.label,
|
|
5287
|
+
detail: entry.reason,
|
|
5288
|
+
selected: state.plannerProvider === entry.id,
|
|
5289
|
+
disabled: !entry.usable
|
|
5583
5290
|
}));
|
|
5584
|
-
return
|
|
5291
|
+
return entries.some((entry) => entry.kind === "vendor") ? items : [...items, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
|
|
5585
5292
|
}
|
|
5586
5293
|
|
|
5587
5294
|
// src/tui/reducers/planPane.ts
|
|
5588
|
-
var
|
|
5295
|
+
var import_core29 = require("@ordewell/core");
|
|
5589
5296
|
|
|
5590
5297
|
// src/tui/reducers/pointer.ts
|
|
5591
5298
|
var WHEEL_NOTCH = 3;
|
|
@@ -5663,13 +5370,13 @@ function handleMouseSelect(state, key) {
|
|
|
5663
5370
|
}
|
|
5664
5371
|
|
|
5665
5372
|
// src/tui/reducers/taskView.ts
|
|
5666
|
-
var
|
|
5373
|
+
var import_core28 = require("@ordewell/core");
|
|
5667
5374
|
function openTaskView(state, sessionId, taskId) {
|
|
5668
5375
|
const task = findTask(state.tasks, taskId);
|
|
5669
5376
|
if (!task) return step(say(state, "system", `No task ${taskId} in this plan.`));
|
|
5670
5377
|
const taskView = {
|
|
5671
5378
|
taskId,
|
|
5672
|
-
view:
|
|
5379
|
+
view: import_core28.EMPTY_TASK_LOG,
|
|
5673
5380
|
attempts: [],
|
|
5674
5381
|
attempt: 0,
|
|
5675
5382
|
pending: [],
|
|
@@ -5679,11 +5386,6 @@ function openTaskView(state, sessionId, taskId) {
|
|
|
5679
5386
|
};
|
|
5680
5387
|
return step({ ...state, taskView, focus: "chat", scroll: 0 }, [{ type: "openTaskLog", sessionId, taskId }]);
|
|
5681
5388
|
}
|
|
5682
|
-
function openTaskTerminalOrView(state, sessionId, taskId) {
|
|
5683
|
-
const task = findTask(state.tasks, taskId);
|
|
5684
|
-
if (task?.transport?.kind === "structured") return openTaskView(state, sessionId, taskId);
|
|
5685
|
-
return step(state, [{ type: "openTaskTerminal", sessionId, taskId }]);
|
|
5686
|
-
}
|
|
5687
5389
|
function continueTaskStep(state, sessionId, taskId, text) {
|
|
5688
5390
|
const opened = state.taskView?.taskId === taskId ? step({ ...state, taskView: { ...state.taskView, followLatest: true } }) : openTaskView(state, sessionId, taskId);
|
|
5689
5391
|
const watch = state.status !== "executing";
|
|
@@ -5702,13 +5404,13 @@ function applyLive(tv, attempt, events) {
|
|
|
5702
5404
|
}
|
|
5703
5405
|
next = {
|
|
5704
5406
|
...next,
|
|
5705
|
-
view:
|
|
5407
|
+
view: import_core28.EMPTY_TASK_LOG,
|
|
5706
5408
|
attempt,
|
|
5707
5409
|
attempts: next.attempts.includes(attempt) ? next.attempts : [...next.attempts, attempt]
|
|
5708
5410
|
};
|
|
5709
5411
|
}
|
|
5710
5412
|
if (attempt < next.attempt) return next;
|
|
5711
|
-
return clampQueueIndex({ ...next, view: events.reduce(
|
|
5413
|
+
return clampQueueIndex({ ...next, view: events.reduce(import_core28.reduceTaskLog, next.view) });
|
|
5712
5414
|
}
|
|
5713
5415
|
function taskLogAbandoned(state) {
|
|
5714
5416
|
const tv = state.taskView;
|
|
@@ -5728,14 +5430,14 @@ function taskLogArrived(state, action) {
|
|
|
5728
5430
|
function announceApprovals(state, action) {
|
|
5729
5431
|
if (state.taskView?.taskId === action.taskId) return state;
|
|
5730
5432
|
const task = findTask(state.tasks, action.taskId);
|
|
5731
|
-
const spoken = action.events.reduce((s, event) => event.type === "approval_requested" ? say(s, "system", `\xB7 Task ${task?.order ?? "?"} waits for approval: ${(0,
|
|
5433
|
+
const spoken = action.events.reduce((s, event) => event.type === "approval_requested" ? say(s, "system", `\xB7 Task ${task?.order ?? "?"} waits for approval: ${(0, import_core28.runnerToolSubject)(event.tool, event.args)} \u2014 enter on it, then ctrl-y to allow or ctrl-g to deny`) : s, state);
|
|
5732
5434
|
return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
|
|
5733
5435
|
}
|
|
5734
5436
|
function announceCheckpoint(state, action) {
|
|
5735
5437
|
const task = findTask(state.tasks, action.taskId) ?? state.tasks.find((t) => t.title === action.title);
|
|
5736
5438
|
const label = task ? `Task ${task.order}` : action.title;
|
|
5737
|
-
const where =
|
|
5738
|
-
const spoken = say(state, "system", `\xB7 ${label} asks: ${(0,
|
|
5439
|
+
const where = "enter on it to answer";
|
|
5440
|
+
const spoken = say(state, "system", `\xB7 ${label} asks: ${(0, import_core28.truncateCheckpointSummary)(action.summary)} \u2014 ${where}`);
|
|
5739
5441
|
return { ...spoken, scroll: state.scroll };
|
|
5740
5442
|
}
|
|
5741
5443
|
function taskLogLoaded(state, action) {
|
|
@@ -5743,7 +5445,7 @@ function taskLogLoaded(state, action) {
|
|
|
5743
5445
|
if (!tv || tv.taskId !== action.taskId) return state;
|
|
5744
5446
|
const loaded = action.attempt >= 1 ? {
|
|
5745
5447
|
...tv,
|
|
5746
|
-
view: (0,
|
|
5448
|
+
view: (0, import_core28.replayTaskLog)(action.events),
|
|
5747
5449
|
attempts: action.attempts ?? tv.attempts,
|
|
5748
5450
|
attempt: action.attempt
|
|
5749
5451
|
} : { ...tv, attempts: action.attempts ?? tv.attempts };
|
|
@@ -5895,6 +5597,34 @@ function openTaskDepsPicker(state, task) {
|
|
|
5895
5597
|
}
|
|
5896
5598
|
});
|
|
5897
5599
|
}
|
|
5600
|
+
function openTaskSkillsPicker(state, task) {
|
|
5601
|
+
if (task.type !== "ai") return fail3(state, "Manual tasks do not run an executor, so they take no skills.");
|
|
5602
|
+
const action = { kind: "set-task-skills", taskId: task.id };
|
|
5603
|
+
const items = pickerItemsFor(state, action);
|
|
5604
|
+
if (items.length === 0) {
|
|
5605
|
+
return fail3(state, "No task skills found. A skill with applies-to: task in .ordewell/skills/ or ~/.ordewell/skills/ shows up here.");
|
|
5606
|
+
}
|
|
5607
|
+
return step({
|
|
5608
|
+
...state,
|
|
5609
|
+
overlay: {
|
|
5610
|
+
kind: "picker",
|
|
5611
|
+
picker: picker(`Skills \xB7 ${(0, import_plan_utils3.titledTaskRef)(task)}`, items, action, {
|
|
5612
|
+
hint: "Attached skills go into this task's prompt when it starts.",
|
|
5613
|
+
multi: true,
|
|
5614
|
+
chosen: task.skills ?? []
|
|
5615
|
+
})
|
|
5616
|
+
}
|
|
5617
|
+
});
|
|
5618
|
+
}
|
|
5619
|
+
function assignTaskSkills(state, sessionId, task, skills) {
|
|
5620
|
+
return step(state, [{
|
|
5621
|
+
type: "updateTask",
|
|
5622
|
+
sessionId,
|
|
5623
|
+
taskId: task.id,
|
|
5624
|
+
changes: { skills },
|
|
5625
|
+
message: skills.length > 0 ? `Task ${(0, import_plan_utils3.taskRef)(task)} skills set to ${skills.join(", ")}.` : `Task ${(0, import_plan_utils3.taskRef)(task)} has no skills attached.`
|
|
5626
|
+
}]);
|
|
5627
|
+
}
|
|
5898
5628
|
function openTaskModePicker(state, task) {
|
|
5899
5629
|
if (task.type !== "ai") return fail3(state, "Manual tasks do not have an executor mode.");
|
|
5900
5630
|
const modes = modesForTask(state.modesByRunner, task);
|
|
@@ -6035,6 +5765,20 @@ function taskEffortCommand(state, args) {
|
|
|
6035
5765
|
return assignTaskEffort(state, sessionId, task, value);
|
|
6036
5766
|
});
|
|
6037
5767
|
}
|
|
5768
|
+
function taskSkillsCommand(state, args) {
|
|
5769
|
+
return taskCommand(state, args[0], (sessionId, taskId) => {
|
|
5770
|
+
const task = findTask(state.tasks, taskId);
|
|
5771
|
+
if (args.length < 2) return openTaskSkillsPicker(state, task);
|
|
5772
|
+
if (task.type !== "ai") return fail3(state, "Manual tasks do not run an executor, so they take no skills.");
|
|
5773
|
+
const value = args.slice(1).join(" ");
|
|
5774
|
+
const names = value.toLowerCase() === "none" ? [] : [...new Set(value.toLowerCase().split(/[,\s]+/).filter(Boolean))];
|
|
5775
|
+
const unknown = names.filter((name) => !state.taskSkills.some((s) => s.name === name));
|
|
5776
|
+
if (unknown.length > 0) {
|
|
5777
|
+
return fail3(state, `No task skill named ${unknown.map((n) => `"${n}"`).join(", ")}. Task skills: ${state.taskSkills.map((s) => s.name).join(", ") || "none"}.`);
|
|
5778
|
+
}
|
|
5779
|
+
return assignTaskSkills(state, sessionId, task, names);
|
|
5780
|
+
});
|
|
5781
|
+
}
|
|
6038
5782
|
function taskOpsCommand(state, args) {
|
|
6039
5783
|
return taskCommand(state, args[0], (_sessionId, taskId) => {
|
|
6040
5784
|
const task = findTask(state.tasks, taskId);
|
|
@@ -6105,7 +5849,8 @@ function handlePlanKey(state, key) {
|
|
|
6105
5849
|
const row = selectedPlanRow(state);
|
|
6106
5850
|
if (!row) return step(state);
|
|
6107
5851
|
const task = row.task;
|
|
6108
|
-
if (key.name === "enter"
|
|
5852
|
+
if (key.name === "enter" && state.sessionId) return openTaskView(state, state.sessionId, task.id);
|
|
5853
|
+
if (key.name === "right") {
|
|
6109
5854
|
const hasSubtasks = (task.subtasks?.length ?? 0) > 0;
|
|
6110
5855
|
if (hasSubtasks && state.expandedTaskId !== task.id) {
|
|
6111
5856
|
return step(settlePlan({ ...state, expandedTaskId: task.id, taskEditor: null }));
|
|
@@ -6120,14 +5865,13 @@ function handlePlanKey(state, key) {
|
|
|
6120
5865
|
if (!state.sessionId || key.name !== "char") return step(state);
|
|
6121
5866
|
if (key.char === "E") return step(state, [{ type: "execute", sessionId: state.sessionId }]);
|
|
6122
5867
|
if (key.char === "S") return step(state, [{ type: "stopExecution", sessionId: state.sessionId }]);
|
|
6123
|
-
const action = key.char === "m" ? (0,
|
|
5868
|
+
const action = key.char === "m" ? (0, import_core29.markAction)(task) : PLAN_SHORTCUTS[key.char ?? ""];
|
|
6124
5869
|
if (action === "force-start" && task.mergeGate?.length) return confirmForceStartPastGate(state, task);
|
|
6125
5870
|
if (action) {
|
|
6126
5871
|
return step(state, [taskActionEffect(state, state.sessionId, task.id, action)]);
|
|
6127
5872
|
}
|
|
6128
5873
|
if (key.char === "O") return toggleTaskOps(state, task, row.parent !== null);
|
|
6129
5874
|
if (key.char === "d") return confirmRemoveTask(state, task);
|
|
6130
|
-
if (key.char === "t") return openTaskTerminalOrView(state, state.sessionId, task.id);
|
|
6131
5875
|
if (key.char === "x" && task.isolation?.state === "conflict") {
|
|
6132
5876
|
return step(state, [{ type: "resolveConflict", sessionId: state.sessionId, taskId: task.id }]);
|
|
6133
5877
|
}
|
|
@@ -6136,6 +5880,7 @@ function handlePlanKey(state, key) {
|
|
|
6136
5880
|
if (key.char === "e") return openTaskEffortPicker(state, task);
|
|
6137
5881
|
if (key.char === "M") return openTaskModePicker(state, task);
|
|
6138
5882
|
if (key.char === "D") return openTaskDepsPicker(state, task);
|
|
5883
|
+
if (key.char === "K") return openTaskSkillsPicker(state, task);
|
|
6139
5884
|
return step(state);
|
|
6140
5885
|
}
|
|
6141
5886
|
function handleTaskEditKey(state, task, editor, key) {
|
|
@@ -6155,14 +5900,14 @@ function commitTaskEdit(state, task, editor) {
|
|
|
6155
5900
|
sessionId: state.sessionId,
|
|
6156
5901
|
taskId: task.id,
|
|
6157
5902
|
changes: { prompt },
|
|
6158
|
-
message: `Task ${(0,
|
|
5903
|
+
message: `Task ${(0, import_core29.taskRef)(task)} prompt updated.`
|
|
6159
5904
|
}]);
|
|
6160
5905
|
}
|
|
6161
5906
|
|
|
6162
5907
|
// src/tui/reducers/turnQueue.ts
|
|
6163
|
-
var
|
|
5908
|
+
var import_core30 = require("@ordewell/core");
|
|
6164
5909
|
function drainQueue(state, settled) {
|
|
6165
|
-
const next = (0,
|
|
5910
|
+
const next = (0, import_core30.drainNext)(state.queuedPrompts);
|
|
6166
5911
|
const ended = disarmStop({ ...settled, stopRequested: false });
|
|
6167
5912
|
if (!next) return step(ended);
|
|
6168
5913
|
const spoken = say(ended, "user", next.text);
|
|
@@ -6181,14 +5926,14 @@ function stopPlanning(state, sessionId) {
|
|
|
6181
5926
|
return step(queueToEditor(cutTurn({ ...dismissed, stopArmed: false, stopRequested: true })), [{ type: "cancelPlanning", sessionId }]);
|
|
6182
5927
|
}
|
|
6183
5928
|
function queueToEditor(state) {
|
|
6184
|
-
const all = (0,
|
|
5929
|
+
const all = (0, import_core30.unsendAll)(state.queuedPrompts);
|
|
6185
5930
|
if (!all) return state;
|
|
6186
5931
|
return { ...state, queuedPrompts: all.rest, editor: { ...state.editor, text: all.text, cursor: all.text.length } };
|
|
6187
5932
|
}
|
|
6188
5933
|
function unsendLatest(state) {
|
|
6189
|
-
const latest = (0,
|
|
5934
|
+
const latest = (0, import_core30.unsendLatest)(state.queuedPrompts);
|
|
6190
5935
|
if (!latest) return step(state);
|
|
6191
|
-
const text = (0,
|
|
5936
|
+
const text = (0, import_core30.aheadOfDraft)(latest.text, state.editor.text);
|
|
6192
5937
|
return step({
|
|
6193
5938
|
...state,
|
|
6194
5939
|
queuedPrompts: latest.rest,
|
|
@@ -6205,14 +5950,11 @@ function plannerEscape(state) {
|
|
|
6205
5950
|
}
|
|
6206
5951
|
|
|
6207
5952
|
// src/tui/reducers/commands.ts
|
|
6208
|
-
var
|
|
5953
|
+
var KNOWN_PROVIDERS = Object.keys(import_core31.ALL_PROVIDERS);
|
|
5954
|
+
var unknownCommand = (state, name) => fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
|
|
5955
|
+
var skillInTaskView = (state, name) => fail3(state, `/${name} is a skill, and skills aren't loaded in a task's chat. To give a task a skill, use /task-skills <id> <name>.`);
|
|
6209
5956
|
function runCommand(state, { name, args }) {
|
|
6210
|
-
if (!findCommand(name))
|
|
6211
|
-
return fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
|
|
6212
|
-
}
|
|
6213
|
-
if (SKILL_IDS.includes(name)) {
|
|
6214
|
-
return toggleSkill(state, name, args[0]);
|
|
6215
|
-
}
|
|
5957
|
+
if (!findCommand(name)) return unknownCommand(state, name);
|
|
6216
5958
|
switch (name) {
|
|
6217
5959
|
case "help":
|
|
6218
5960
|
return step({ ...state, overlay: { kind: "help", scroll: 0 } });
|
|
@@ -6246,8 +5988,6 @@ function runCommand(state, { name, args }) {
|
|
|
6246
5988
|
return runners(state, args);
|
|
6247
5989
|
case "auto":
|
|
6248
5990
|
return setAutonomous(state, args[0]);
|
|
6249
|
-
case "transport":
|
|
6250
|
-
return setTransport(state, args[0]);
|
|
6251
5991
|
case "parallel":
|
|
6252
5992
|
return setMaxParallel(state, args[0]);
|
|
6253
5993
|
case "mouse":
|
|
@@ -6284,8 +6024,6 @@ function runCommand(state, { name, args }) {
|
|
|
6284
6024
|
args[0],
|
|
6285
6025
|
(sessionId, taskId) => step(state, [{ type: "removeTask", sessionId, taskId }])
|
|
6286
6026
|
);
|
|
6287
|
-
case "terminal":
|
|
6288
|
-
return taskCommand(state, args[0], (sessionId, taskId) => openTaskTerminalOrView(state, sessionId, taskId));
|
|
6289
6027
|
case "continue":
|
|
6290
6028
|
return taskCommand(state, args[0], (sessionId, taskId) => {
|
|
6291
6029
|
const text = args.slice(1).join(" ").trim();
|
|
@@ -6310,6 +6048,8 @@ function runCommand(state, { name, args }) {
|
|
|
6310
6048
|
return taskModeCommand(state, args);
|
|
6311
6049
|
case "task-ops":
|
|
6312
6050
|
return taskOpsCommand(state, args);
|
|
6051
|
+
case "task-skills":
|
|
6052
|
+
return taskSkillsCommand(state, args);
|
|
6313
6053
|
case "task-deps":
|
|
6314
6054
|
return taskCommand(
|
|
6315
6055
|
state,
|
|
@@ -6343,11 +6083,6 @@ function resolveToggle(arg, current) {
|
|
|
6343
6083
|
if (value === void 0) return !current;
|
|
6344
6084
|
return null;
|
|
6345
6085
|
}
|
|
6346
|
-
function toggleSkill(state, skill, arg) {
|
|
6347
|
-
const enabled = resolveToggle(arg, state.skills[skill]);
|
|
6348
|
-
if (enabled === null) return fail3(state, `Usage: /${skill} [on|off]`);
|
|
6349
|
-
return step(state, [{ type: "command", name: skill, action: enabled ? "on" : "off" }]);
|
|
6350
|
-
}
|
|
6351
6086
|
function requestNewSession(state) {
|
|
6352
6087
|
const hasContent = state.tasks.length > 0 || state.conversation.blocks.length > 0 || state.goal !== "" || state.status !== "idle";
|
|
6353
6088
|
if (!state.sessionId || !hasContent) return newSession(state);
|
|
@@ -6371,8 +6106,8 @@ function newSession(state) {
|
|
|
6371
6106
|
taskView: null,
|
|
6372
6107
|
tasks: [],
|
|
6373
6108
|
planApproved: false,
|
|
6374
|
-
conversation:
|
|
6375
|
-
turnGate:
|
|
6109
|
+
conversation: import_core31.EMPTY_CONVERSATION,
|
|
6110
|
+
turnGate: import_core31.NO_TURN,
|
|
6376
6111
|
selectedTask: 0,
|
|
6377
6112
|
expandedTaskId: null,
|
|
6378
6113
|
taskEditor: null,
|
|
@@ -6383,14 +6118,14 @@ function newSession(state) {
|
|
|
6383
6118
|
// Prompts belong to the session that raised them; the old planner is gone
|
|
6384
6119
|
// and its pending requests deny on their own timeout.
|
|
6385
6120
|
pendingApprovals: [],
|
|
6386
|
-
queuedPrompts:
|
|
6121
|
+
queuedPrompts: import_core31.EMPTY_HOLD,
|
|
6387
6122
|
stopArmed: false,
|
|
6388
6123
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6389
6124
|
}, closeEffects);
|
|
6390
6125
|
}
|
|
6391
6126
|
function setModel(state, args) {
|
|
6392
6127
|
if (args[0] === "set" && args[1]) {
|
|
6393
|
-
const runner = (0,
|
|
6128
|
+
const runner = (0, import_core31.runnerForProvider)(state.plannerProvider);
|
|
6394
6129
|
const known = runner ? modelsForRunner(state.models, runner) : [];
|
|
6395
6130
|
if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
|
|
6396
6131
|
return fail3(state, `${args[1]} was not discovered for ${runner}.`);
|
|
@@ -6413,7 +6148,7 @@ function setModel(state, args) {
|
|
|
6413
6148
|
function setPlanner(state, args) {
|
|
6414
6149
|
if (args[0]) {
|
|
6415
6150
|
const id = args[0].toLowerCase();
|
|
6416
|
-
if (!
|
|
6151
|
+
if (!KNOWN_PROVIDERS.includes(id)) {
|
|
6417
6152
|
return fail3(state, `Unknown planner: ${id}. Run /planner with no arguments to see the list.`);
|
|
6418
6153
|
}
|
|
6419
6154
|
return step(state, [{ type: "setPlanner", provider: id }]);
|
|
@@ -6452,7 +6187,7 @@ function setPlannerEffort(state, args) {
|
|
|
6452
6187
|
function setKey(state, args) {
|
|
6453
6188
|
if (args[0] === "set" && args[1]) {
|
|
6454
6189
|
const provider = args[1].toLowerCase();
|
|
6455
|
-
if (!
|
|
6190
|
+
if (!KNOWN_PROVIDERS.includes(provider)) {
|
|
6456
6191
|
return fail3(state, `Unknown provider: ${provider}. Run /key with no arguments to see the list.`);
|
|
6457
6192
|
}
|
|
6458
6193
|
if (!args[2]) {
|
|
@@ -6463,16 +6198,16 @@ function setKey(state, args) {
|
|
|
6463
6198
|
}
|
|
6464
6199
|
return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
|
|
6465
6200
|
}
|
|
6466
|
-
const items =
|
|
6201
|
+
const items = import_core31.PROVIDER_PRIORITY.map((id) => ({
|
|
6467
6202
|
id,
|
|
6468
|
-
label:
|
|
6469
|
-
detail:
|
|
6203
|
+
label: import_core31.ALL_PROVIDERS[id].label,
|
|
6204
|
+
detail: import_core31.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
6470
6205
|
selected: state.configuredProviders.includes(id)
|
|
6471
6206
|
}));
|
|
6472
6207
|
return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
|
|
6473
6208
|
}
|
|
6474
6209
|
function keyPrompt(provider) {
|
|
6475
|
-
const meta =
|
|
6210
|
+
const meta = import_core31.ALL_PROVIDERS[provider];
|
|
6476
6211
|
return {
|
|
6477
6212
|
kind: "prompt",
|
|
6478
6213
|
title: `${meta.label} API key`,
|
|
@@ -6549,21 +6284,16 @@ function runners(state, args) {
|
|
|
6549
6284
|
}
|
|
6550
6285
|
function setMaxParallel(state, arg) {
|
|
6551
6286
|
if (!arg) return step(say(state, "system", `Up to ${state.maxParallel} AI task${state.maxParallel === 1 ? "" : "s"} run at once \u2014 /parallel <n> changes it.`));
|
|
6552
|
-
const limit = (0,
|
|
6287
|
+
const limit = (0, import_core31.parseMaxParallel)(arg);
|
|
6553
6288
|
if (limit === null) return fail3(state, "Usage: /parallel [<number of tasks, 1 or more>]");
|
|
6554
6289
|
return step({ ...state, maxParallel: limit }, [{ type: "setMaxParallel", limit }]);
|
|
6555
6290
|
}
|
|
6556
6291
|
function setAutonomous(state, arg) {
|
|
6557
|
-
if (arg === void 0) return step(say(state, "system", `Autonomy level: ${(0,
|
|
6558
|
-
const enabled = (0,
|
|
6292
|
+
if (arg === void 0) return step(say(state, "system", `Autonomy level: ${(0, import_core31.autonomyLevelLabel)(state.autonomous)} \u2014 /auto full or /auto guarded changes it for new plans.`));
|
|
6293
|
+
const enabled = (0, import_core31.parseAutonomyLevel)(arg);
|
|
6559
6294
|
if (enabled === null) return fail3(state, "Usage: /auto [full|guarded]");
|
|
6560
6295
|
return step({ ...state, autonomous: enabled }, [{ type: "setAutonomous", enabled }]);
|
|
6561
6296
|
}
|
|
6562
|
-
function setTransport(state, arg) {
|
|
6563
|
-
const value = arg?.toLowerCase() ?? (state.runnerTransport === "structured" ? "terminal" : "structured");
|
|
6564
|
-
if (!(0, import_core30.isRunnerTransport)(value)) return fail3(state, "Usage: /transport [terminal|structured]");
|
|
6565
|
-
return step(state, [{ type: "setTransport", transport: value }]);
|
|
6566
|
-
}
|
|
6567
6297
|
function setMouseCapture(state, arg) {
|
|
6568
6298
|
const enabled = resolveToggle(arg, state.mouseCapture);
|
|
6569
6299
|
if (enabled === null) return fail3(state, "Usage: /mouse [on|off]");
|
|
@@ -6738,9 +6468,14 @@ function choose(state, picker2, item) {
|
|
|
6738
6468
|
sessionId: state.sessionId,
|
|
6739
6469
|
taskId: task.id,
|
|
6740
6470
|
changes: { dependencies: picker2.chosen },
|
|
6741
|
-
message: picker2.chosen.length > 0 ? `Task ${(0,
|
|
6471
|
+
message: picker2.chosen.length > 0 ? `Task ${(0, import_core32.taskRef)(task)} now depends on ${picker2.chosen.length} task${picker2.chosen.length === 1 ? "" : "s"}.` : `Task ${(0, import_core32.taskRef)(task)} no longer depends on anything.`
|
|
6742
6472
|
}]);
|
|
6743
6473
|
}
|
|
6474
|
+
if (picker2.action.kind === "set-task-skills") {
|
|
6475
|
+
const task = findTask(state.tasks, picker2.action.taskId);
|
|
6476
|
+
if (!task || !state.sessionId) return step(closed);
|
|
6477
|
+
return assignTaskSkills(closed, state.sessionId, task, picker2.chosen);
|
|
6478
|
+
}
|
|
6744
6479
|
if (!item) return step(state);
|
|
6745
6480
|
if (item.disabled) {
|
|
6746
6481
|
return step(say(state, "error", item.detail ? `${item.label} \u2014 ${item.detail}` : `${item.label} is not available.`));
|
|
@@ -6815,7 +6550,7 @@ function choose(state, picker2, item) {
|
|
|
6815
6550
|
}
|
|
6816
6551
|
|
|
6817
6552
|
// src/tui/reducers/incoming.ts
|
|
6818
|
-
var
|
|
6553
|
+
var import_core33 = require("@ordewell/core");
|
|
6819
6554
|
var planText = (value, fallback = "") => sanitize(String(value ?? fallback));
|
|
6820
6555
|
var planLabel = (value, fallback = "") => planText(value, fallback).replace(/\n+/g, " ");
|
|
6821
6556
|
var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
|
|
@@ -6846,7 +6581,7 @@ function toTaskView2(t, index) {
|
|
|
6846
6581
|
prompt: typeof t.prompt === "string" ? sanitize(t.prompt) : void 0,
|
|
6847
6582
|
type: t.type === "user" ? "user" : "ai",
|
|
6848
6583
|
status: planLabel(t.status, "pending"),
|
|
6849
|
-
awaitingReason: t.status === "awaiting_user" && (0,
|
|
6584
|
+
awaitingReason: t.status === "awaiting_user" && (0, import_core33.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
|
|
6850
6585
|
dependencies: Array.isArray(t.dependencies) ? t.dependencies.map((id) => planLabel(id)) : [],
|
|
6851
6586
|
assignedRunner: typeof t.assignedRunner === "string" ? planLabel(t.assignedRunner) : void 0,
|
|
6852
6587
|
taskMode: typeof t.taskMode === "string" ? planLabel(t.taskMode) : void 0,
|
|
@@ -6858,33 +6593,22 @@ function toTaskView2(t, index) {
|
|
|
6858
6593
|
} : void 0,
|
|
6859
6594
|
subtasks: Array.isArray(t.subtasks) ? [...t.subtasks].sort((a, b) => wireOrder(a) - wireOrder(b)).map((s, i) => toTaskView2(asRecord(s), i)) : void 0,
|
|
6860
6595
|
...t.ops === true ? { ops: true } : {},
|
|
6596
|
+
...Array.isArray(t.skills) && t.skills.length > 0 ? { skills: t.skills.map((name) => planLabel(name)) } : {},
|
|
6861
6597
|
...Array.isArray(t.forcedPastGate) && t.forcedPastGate.length > 0 ? { forcedPastGate: t.forcedPastGate.map((title) => planLabel(title)) } : {}
|
|
6862
6598
|
};
|
|
6863
6599
|
}
|
|
6864
|
-
var enabledFlag = (value) => typeof value?.enabled === "boolean" ? value.enabled : void 0;
|
|
6865
6600
|
function applySettings(state, settings) {
|
|
6866
|
-
const sources = {
|
|
6867
|
-
tdd: settings.tdd,
|
|
6868
|
-
verify: settings.verification
|
|
6869
|
-
};
|
|
6870
|
-
const skills = { ...state.skills };
|
|
6871
|
-
for (const id of SKILL_IDS) {
|
|
6872
|
-
const flag2 = enabledFlag(sources[id]);
|
|
6873
|
-
if (flag2 !== void 0) skills[id] = flag2;
|
|
6874
|
-
}
|
|
6875
6601
|
return {
|
|
6876
6602
|
...state,
|
|
6877
|
-
skills,
|
|
6878
6603
|
orchestratorModel: typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : state.orchestratorModel,
|
|
6879
6604
|
plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
|
|
6880
6605
|
plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
|
|
6881
6606
|
maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
|
|
6882
|
-
runnerTransport: (0, import_core32.isRunnerTransport)(settings.runnerTransport) ? settings.runnerTransport : state.runnerTransport,
|
|
6883
6607
|
allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist
|
|
6884
6608
|
};
|
|
6885
6609
|
}
|
|
6886
6610
|
function runLabel(tasks, gate = null) {
|
|
6887
|
-
const running = tasks.filter(
|
|
6611
|
+
const running = tasks.filter(import_core24.isTaskRunning);
|
|
6888
6612
|
if (running.length > 0) {
|
|
6889
6613
|
const [first] = running;
|
|
6890
6614
|
const name = first.assignedRunner ? `${first.title} \xB7 ${first.assignedRunner}` : first.title;
|
|
@@ -6950,13 +6674,13 @@ function reduceAction(state, action) {
|
|
|
6950
6674
|
goal: "",
|
|
6951
6675
|
taskView: null,
|
|
6952
6676
|
pendingApprovals: [],
|
|
6953
|
-
queuedPrompts:
|
|
6677
|
+
queuedPrompts: import_core34.EMPTY_HOLD,
|
|
6954
6678
|
stopArmed: false,
|
|
6955
6679
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6956
6680
|
});
|
|
6957
6681
|
case "chatRestored":
|
|
6958
6682
|
if (stale(state, action.sessionId)) return step(state);
|
|
6959
|
-
return step({ ...state, conversation: (0,
|
|
6683
|
+
return step({ ...state, conversation: (0, import_core34.fromTranscript)(action.history, action.researchLog, action.plannerUsage), scroll: 0 });
|
|
6960
6684
|
case "planUpdated": {
|
|
6961
6685
|
if (stale(state, action.sessionId)) return step(state);
|
|
6962
6686
|
const isolation = isolationOfPlan(action.plan);
|
|
@@ -7003,9 +6727,9 @@ function reduceAction(state, action) {
|
|
|
7003
6727
|
case "taskStarted": {
|
|
7004
6728
|
if (stale(state, action.sessionId)) return step(state);
|
|
7005
6729
|
const existing = findTask(state.tasks, action.taskId);
|
|
7006
|
-
if (existing && (0,
|
|
7007
|
-
const tasks = state.tasks.map((t) => t.id === action.taskId && !(0,
|
|
7008
|
-
const spoken = say(state, "system", (0,
|
|
6730
|
+
if (existing && (0, import_core24.isTaskRunning)(existing)) return step(state);
|
|
6731
|
+
const tasks = state.tasks.map((t) => t.id === action.taskId && !(0, import_core24.isTaskRunning)(t) ? { ...t, status: "in_progress" } : t);
|
|
6732
|
+
const spoken = say(state, "system", (0, import_core34.taskStartedNotice)(action.title, action.runner));
|
|
7009
6733
|
return step({ ...spoken, tasks, status: "executing", busyLabel: runLabel(tasks) });
|
|
7010
6734
|
}
|
|
7011
6735
|
case "taskCheckpoint":
|
|
@@ -7026,16 +6750,15 @@ function reduceAction(state, action) {
|
|
|
7026
6750
|
if (update === void 0) return t;
|
|
7027
6751
|
const idleSince = update.idleSince ?? null;
|
|
7028
6752
|
const isolation = update.isolation ?? t.isolation;
|
|
7029
|
-
const transport = update.transport;
|
|
7030
6753
|
const awaitingReason = update.awaitingReason;
|
|
7031
6754
|
const checkpoint = update.checkpoint;
|
|
7032
6755
|
const continuable = update.continuable === true;
|
|
7033
6756
|
const awaitingApproval = update.awaitingApproval;
|
|
7034
6757
|
const mergeGate = update.mergeGate;
|
|
7035
6758
|
const forcedPastGate = update.forcedPastGate;
|
|
7036
|
-
if (update.status !== t.status || idleSince !== (t.idleSince ?? null) || !sameIsolation(isolation, t.isolation) ||
|
|
6759
|
+
if (update.status !== t.status || idleSince !== (t.idleSince ?? null) || !sameIsolation(isolation, t.isolation) || awaitingReason !== t.awaitingReason || checkpoint !== t.checkpoint || continuable !== (t.continuable ?? false) || awaitingApproval !== t.awaitingApproval || !sameList(mergeGate, t.mergeGate) || !sameList(forcedPastGate, t.forcedPastGate)) {
|
|
7037
6760
|
changed = true;
|
|
7038
|
-
return { ...t, status: update.status, idleSince, isolation,
|
|
6761
|
+
return { ...t, status: update.status, idleSince, isolation, awaitingReason, checkpoint, continuable, awaitingApproval, mergeGate, forcedPastGate };
|
|
7039
6762
|
}
|
|
7040
6763
|
return t;
|
|
7041
6764
|
});
|
|
@@ -7046,12 +6769,6 @@ function reduceAction(state, action) {
|
|
|
7046
6769
|
const status = runStatus(next, tasks);
|
|
7047
6770
|
return step({ ...next, status, busyLabel: status === "executing" ? runLabel(tasks, gate) : state.busyLabel });
|
|
7048
6771
|
}
|
|
7049
|
-
case "queueReady": {
|
|
7050
|
-
if (stale(state, action.sessionId)) return step(state);
|
|
7051
|
-
const sessionId = action.sessionId ?? state.sessionId;
|
|
7052
|
-
if (sessionId === null) return step(state);
|
|
7053
|
-
return step(state, [{ type: "processQueued", sessionId }]);
|
|
7054
|
-
}
|
|
7055
6772
|
case "isolationBlocked": {
|
|
7056
6773
|
if (stale(state, action.sessionId)) return step(state);
|
|
7057
6774
|
return step(blockedPicker(state, action.message, action.repos));
|
|
@@ -7084,6 +6801,14 @@ function reduceAction(state, action) {
|
|
|
7084
6801
|
busyLabel: ""
|
|
7085
6802
|
});
|
|
7086
6803
|
}
|
|
6804
|
+
case "executionLost": {
|
|
6805
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
6806
|
+
return step({
|
|
6807
|
+
...say(state, "error", "Lost the connection to the daemon \u2014 this view is no longer following the run, but the daemon may still be running it. /load this session again to see where it stands."),
|
|
6808
|
+
status: "idle",
|
|
6809
|
+
busyLabel: ""
|
|
6810
|
+
});
|
|
6811
|
+
}
|
|
7087
6812
|
case "settingsLoaded":
|
|
7088
6813
|
return step(applySettings(state, action.settings));
|
|
7089
6814
|
case "modelsLoaded":
|
|
@@ -7118,7 +6843,7 @@ function reduceAction(state, action) {
|
|
|
7118
6843
|
busyLabel: "",
|
|
7119
6844
|
planApproved: false,
|
|
7120
6845
|
pendingApprovals: [],
|
|
7121
|
-
queuedPrompts:
|
|
6846
|
+
queuedPrompts: import_core34.EMPTY_HOLD,
|
|
7122
6847
|
stopArmed: false,
|
|
7123
6848
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
7124
6849
|
});
|
|
@@ -7174,9 +6899,6 @@ function reduceAction(state, action) {
|
|
|
7174
6899
|
return step(disarmStop(state));
|
|
7175
6900
|
}
|
|
7176
6901
|
}
|
|
7177
|
-
function sameTransport(a, b) {
|
|
7178
|
-
return a?.kind === b?.kind && a?.fallback === b?.fallback;
|
|
7179
|
-
}
|
|
7180
6902
|
function sameList(a, b) {
|
|
7181
6903
|
return (a ?? []).join("\0") === (b ?? []).join("\0");
|
|
7182
6904
|
}
|
|
@@ -7185,7 +6907,7 @@ function sameGate(a, b) {
|
|
|
7185
6907
|
return a.paused === b.paused && JSON.stringify(a.handoff) === JSON.stringify(b.handoff);
|
|
7186
6908
|
}
|
|
7187
6909
|
function runStatus(state, tasks) {
|
|
7188
|
-
if (tasks.some(
|
|
6910
|
+
if (tasks.some(import_core24.isTaskRunning)) return "executing";
|
|
7189
6911
|
if (tasks.some((t) => t.status === "awaiting_user") || state.gate?.paused) return "executing";
|
|
7190
6912
|
return plannerInFlight(state) ? state.status : "idle";
|
|
7191
6913
|
}
|
|
@@ -7246,7 +6968,11 @@ function submit(state) {
|
|
|
7246
6968
|
if (!text) return step(state);
|
|
7247
6969
|
const cleared = { ...state, editor: commit(state.editor) };
|
|
7248
6970
|
const command = parseSlash(text);
|
|
7249
|
-
if (command
|
|
6971
|
+
if (command) {
|
|
6972
|
+
const known = findCommand(command.name);
|
|
6973
|
+
if (known && known.source !== "skill") return runCommand(cleared, command);
|
|
6974
|
+
if (state.taskView) return known ? skillInTaskView(cleared, command.name) : unknownCommand(cleared, command.name);
|
|
6975
|
+
}
|
|
7250
6976
|
if (state.taskView) {
|
|
7251
6977
|
if (!state.sessionId) return step(state);
|
|
7252
6978
|
const taskId = state.taskView.taskId;
|
|
@@ -7254,7 +6980,7 @@ function submit(state) {
|
|
|
7254
6980
|
return step(cleared, [{ type: "sendTaskMessage", sessionId: state.sessionId, taskId, text }]);
|
|
7255
6981
|
}
|
|
7256
6982
|
if (plannerInFlight(state)) {
|
|
7257
|
-
return step({ ...cleared, queuedPrompts: (0,
|
|
6983
|
+
return step({ ...cleared, queuedPrompts: (0, import_core34.holdPrompt)(state.queuedPrompts, text) });
|
|
7258
6984
|
}
|
|
7259
6985
|
const spoken = say(cleared, "user", text);
|
|
7260
6986
|
const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
|
|
@@ -7313,17 +7039,18 @@ function createApp(deps) {
|
|
|
7313
7039
|
}
|
|
7314
7040
|
|
|
7315
7041
|
// src/tui/effects.ts
|
|
7316
|
-
var
|
|
7317
|
-
var
|
|
7042
|
+
var import_child_process3 = require("child_process");
|
|
7043
|
+
var import_core36 = require("@ordewell/core");
|
|
7318
7044
|
|
|
7319
7045
|
// src/tui/inbound.ts
|
|
7320
|
-
var
|
|
7046
|
+
var import_core35 = require("@ordewell/core");
|
|
7321
7047
|
var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
|
|
7322
7048
|
"planner_message",
|
|
7323
7049
|
"planner_turn_started",
|
|
7324
7050
|
"planner_turn_ended",
|
|
7325
7051
|
"planner_text_delta",
|
|
7326
7052
|
"planner_thinking_delta",
|
|
7053
|
+
"planner_skill_loaded",
|
|
7327
7054
|
"planner_text_retracted",
|
|
7328
7055
|
"plan_token",
|
|
7329
7056
|
"plan_generated",
|
|
@@ -7531,8 +7258,7 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
7531
7258
|
status: String(task.status),
|
|
7532
7259
|
idleSince: task.idleSince ?? null,
|
|
7533
7260
|
isolation: task.isolation,
|
|
7534
|
-
|
|
7535
|
-
awaitingReason: (0, import_core34.isAwaitingReason)(task.awaitingReason) ? task.awaitingReason : void 0,
|
|
7261
|
+
awaitingReason: (0, import_core35.isAwaitingReason)(task.awaitingReason) ? task.awaitingReason : void 0,
|
|
7536
7262
|
...typeof task.checkpoint === "string" && task.checkpoint ? { checkpoint: sanitize(task.checkpoint) } : {},
|
|
7537
7263
|
...task.continuable ? { continuable: true } : {},
|
|
7538
7264
|
awaitingApproval: typeof task.awaitingApproval === "number" && task.awaitingApproval > 0 ? task.awaitingApproval : void 0,
|
|
@@ -7558,13 +7284,6 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
7558
7284
|
case "review_approved":
|
|
7559
7285
|
dispatch({ type: "notice", message: "Plan approved." });
|
|
7560
7286
|
return;
|
|
7561
|
-
// The orchestrator paused fan-out because a structural edit is queued; drain
|
|
7562
|
-
// it so the planner reconciles the plan and dependents resume spawning.
|
|
7563
|
-
// Dropped on the floor, the queue suppresses every later tick() — which is
|
|
7564
|
-
// exactly "tasks did not fan out when the dependent ones finished."
|
|
7565
|
-
case "queue_ready":
|
|
7566
|
-
dispatch({ type: "queueReady", sessionId });
|
|
7567
|
-
return;
|
|
7568
7287
|
case "execution_complete":
|
|
7569
7288
|
dispatch({ type: "executionComplete", summary: event.summary, sessionId });
|
|
7570
7289
|
return;
|
|
@@ -7614,8 +7333,7 @@ async function runEffect(effect, deps) {
|
|
|
7614
7333
|
await perform(effect, deps);
|
|
7615
7334
|
} catch (err) {
|
|
7616
7335
|
if (!isConnectionRefused(err)) {
|
|
7617
|
-
|
|
7618
|
-
deps.dispatch({ type: "failed", message: explain(message) });
|
|
7336
|
+
deps.dispatch({ type: "failed", message: explain(err) });
|
|
7619
7337
|
return;
|
|
7620
7338
|
}
|
|
7621
7339
|
let revived = false;
|
|
@@ -7632,21 +7350,20 @@ async function runEffect(effect, deps) {
|
|
|
7632
7350
|
try {
|
|
7633
7351
|
await perform(effect, deps);
|
|
7634
7352
|
} catch (retryErr) {
|
|
7635
|
-
const message = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
7636
7353
|
deps.dispatch({
|
|
7637
7354
|
type: "failed",
|
|
7638
7355
|
// A fresh daemon holds no sessions, so the retry of anything
|
|
7639
7356
|
// session-scoped legitimately 404s. Say which of the two happened.
|
|
7640
|
-
message: isConnectionRefused(retryErr) ? describeConnectionRefused(deps.port) : explain(
|
|
7357
|
+
message: isConnectionRefused(retryErr) ? describeConnectionRefused(deps.port) : explain(retryErr)
|
|
7641
7358
|
});
|
|
7642
7359
|
}
|
|
7643
7360
|
}
|
|
7644
7361
|
}
|
|
7645
|
-
function explain(
|
|
7646
|
-
if (
|
|
7362
|
+
function explain(err) {
|
|
7363
|
+
if (err instanceof DaemonError && err.code === "session_not_found") {
|
|
7647
7364
|
return "This server is no longer holding that session \u2014 it was probably restarted. Reload it with /sessions.";
|
|
7648
7365
|
}
|
|
7649
|
-
return message;
|
|
7366
|
+
return err instanceof Error ? err.message : String(err);
|
|
7650
7367
|
}
|
|
7651
7368
|
function persistAfterDaemon(deps, env) {
|
|
7652
7369
|
for (const [key, value] of Object.entries(env)) deps.setEnvVar(key, value);
|
|
@@ -7702,19 +7419,15 @@ async function perform(effect, deps) {
|
|
|
7702
7419
|
timer.unref?.();
|
|
7703
7420
|
return;
|
|
7704
7421
|
}
|
|
7705
|
-
case "processQueued":
|
|
7706
|
-
await api.processQueued(effect.sessionId);
|
|
7707
|
-
await refreshPlan(deps, effect.sessionId);
|
|
7708
|
-
return;
|
|
7709
7422
|
case "taskAction": {
|
|
7710
7423
|
const { sessionId, taskId, action } = effect;
|
|
7711
|
-
const request = action === "cancel" || action === "retry" || action === "force-start" ? () => api.taskControl(sessionId, taskId, action) : (0,
|
|
7424
|
+
const request = action === "cancel" || action === "retry" || action === "force-start" ? () => api.taskControl(sessionId, taskId, action) : (0, import_core36.markRequestFor)(action) === "uncomplete" ? () => api.markTaskIncomplete(sessionId, taskId) : () => api.markTaskComplete(sessionId, taskId);
|
|
7712
7425
|
await (effect.watch ? withExecutionStream(deps, sessionId, request) : request());
|
|
7713
7426
|
await refreshPlan(deps, sessionId);
|
|
7714
7427
|
return;
|
|
7715
7428
|
}
|
|
7716
7429
|
case "addTask": {
|
|
7717
|
-
const fields = (0,
|
|
7430
|
+
const fields = (0, import_core36.newTaskFields)(effect.title);
|
|
7718
7431
|
if (!fields) return;
|
|
7719
7432
|
await api.addTask(effect.sessionId, { ...fields });
|
|
7720
7433
|
await refreshPlan(deps, effect.sessionId);
|
|
@@ -7753,18 +7466,6 @@ async function perform(effect, deps) {
|
|
|
7753
7466
|
if (effect.answer === "approve") await api.approveTaskCheckpoint(effect.sessionId, effect.taskId);
|
|
7754
7467
|
else await api.rejectTaskCheckpoint(effect.sessionId, effect.taskId, effect.reason);
|
|
7755
7468
|
return;
|
|
7756
|
-
// A task with a saved log ran structured, whatever its `transport` says
|
|
7757
|
-
// after a reload; it has no tmux window, so open the log rather than fail.
|
|
7758
|
-
case "openTaskTerminal": {
|
|
7759
|
-
const saved = await api.getTaskLogAttempts(effect.sessionId, effect.taskId, workspace).catch(() => []);
|
|
7760
|
-
if (saved.length > 0) {
|
|
7761
|
-
dispatch({ type: "taskViewRequested", sessionId: effect.sessionId, taskId: effect.taskId });
|
|
7762
|
-
return;
|
|
7763
|
-
}
|
|
7764
|
-
const result = await deps.openTerminal(effect.sessionId, effect.taskId);
|
|
7765
|
-
dispatch(result.ok ? { type: "notice", message: result.message } : { type: "failed", message: result.message });
|
|
7766
|
-
return;
|
|
7767
|
-
}
|
|
7768
7469
|
// The saved log is read off disk (ADR-0018, P1) so the view opens with the
|
|
7769
7470
|
// task's history; the live `task_log` stream then catches it up in place.
|
|
7770
7471
|
case "openTaskLog": {
|
|
@@ -7816,18 +7517,6 @@ async function perform(effect, deps) {
|
|
|
7816
7517
|
await api.interruptTask(effect.sessionId, effect.taskId);
|
|
7817
7518
|
dispatch({ type: "notice", message: "Interrupting the task\u2026" });
|
|
7818
7519
|
return;
|
|
7819
|
-
case "command": {
|
|
7820
|
-
const result = await api.sendCommand(effect.name, { action: effect.action });
|
|
7821
|
-
if (result.settings) dispatch({ type: "settingsLoaded", settings: result.settings });
|
|
7822
|
-
dispatch({ type: "notice", message: `${effect.name} is ${effect.action}.` });
|
|
7823
|
-
return;
|
|
7824
|
-
}
|
|
7825
|
-
case "setTransport": {
|
|
7826
|
-
const result = await api.sendCommand("transport", { action: effect.transport });
|
|
7827
|
-
if (result.settings) dispatch({ type: "settingsLoaded", settings: result.settings });
|
|
7828
|
-
dispatch({ type: "notice", message: `Runner transport is ${effect.transport} \u2014 it applies from the next run.` });
|
|
7829
|
-
return;
|
|
7830
|
-
}
|
|
7831
7520
|
case "setModel":
|
|
7832
7521
|
await api.updateSettings({ orchestratorModel: effect.modelId });
|
|
7833
7522
|
deps.setEnvVar("ORCHESTRATOR_MODEL", effect.modelId);
|
|
@@ -7835,11 +7524,11 @@ async function perform(effect, deps) {
|
|
|
7835
7524
|
dispatch({ type: "notice", message: `Orchestrator model set to ${effect.modelId}.` });
|
|
7836
7525
|
return;
|
|
7837
7526
|
case "setPlanner": {
|
|
7838
|
-
const meta =
|
|
7527
|
+
const meta = import_core36.ALL_PROVIDERS[effect.provider];
|
|
7839
7528
|
if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
|
|
7840
7529
|
const env = { AI_PROVIDER: effect.provider };
|
|
7841
7530
|
const settings = await api.updateSettings({ env });
|
|
7842
|
-
const recall2 =
|
|
7531
|
+
const recall2 = plannerSwitchRecall(settings);
|
|
7843
7532
|
persistAfterDaemon(deps, { ...env, ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
|
|
7844
7533
|
dispatch({
|
|
7845
7534
|
type: "settingsLoaded",
|
|
@@ -7847,7 +7536,7 @@ async function perform(effect, deps) {
|
|
|
7847
7536
|
});
|
|
7848
7537
|
dispatch({
|
|
7849
7538
|
type: "notice",
|
|
7850
|
-
message: `${(0,
|
|
7539
|
+
message: `${(0, import_core36.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
|
|
7851
7540
|
});
|
|
7852
7541
|
await loadModels(deps);
|
|
7853
7542
|
return;
|
|
@@ -7859,7 +7548,7 @@ async function perform(effect, deps) {
|
|
|
7859
7548
|
dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
|
|
7860
7549
|
return;
|
|
7861
7550
|
case "setApiKey": {
|
|
7862
|
-
const meta =
|
|
7551
|
+
const meta = import_core36.ALL_PROVIDERS[effect.provider];
|
|
7863
7552
|
if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
|
|
7864
7553
|
await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
|
|
7865
7554
|
deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
|
|
@@ -7897,7 +7586,7 @@ async function perform(effect, deps) {
|
|
|
7897
7586
|
return;
|
|
7898
7587
|
case "setAutonomous":
|
|
7899
7588
|
deps.setEnvVar("ORDEWELL_AUTONOMOUS_MODE", String(effect.enabled));
|
|
7900
|
-
dispatch({ type: "notice", message: `Autonomy level: ${(0,
|
|
7589
|
+
dispatch({ type: "notice", message: `Autonomy level: ${(0, import_core36.autonomyLevelLabel)(effect.enabled)} for new plans.` });
|
|
7901
7590
|
return;
|
|
7902
7591
|
case "setMouseCapture":
|
|
7903
7592
|
deps.setMouseCapture(effect.enabled);
|
|
@@ -8023,7 +7712,7 @@ async function perform(effect, deps) {
|
|
|
8023
7712
|
}
|
|
8024
7713
|
}
|
|
8025
7714
|
var pipeToClipboardDefault = (command, text) => {
|
|
8026
|
-
(0,
|
|
7715
|
+
(0, import_child_process3.execSync)(command, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
8027
7716
|
};
|
|
8028
7717
|
function osc52(text) {
|
|
8029
7718
|
return `\x1B]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`;
|
|
@@ -8031,7 +7720,7 @@ function osc52(text) {
|
|
|
8031
7720
|
function copySelection(deps, text) {
|
|
8032
7721
|
if (!text) return;
|
|
8033
7722
|
const lines = text.split("\n").length;
|
|
8034
|
-
const command = (0,
|
|
7723
|
+
const command = (0, import_core36.clipboardCopyCommand)(deps.hasBin);
|
|
8035
7724
|
if (command) {
|
|
8036
7725
|
try {
|
|
8037
7726
|
(deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
|
|
@@ -8049,15 +7738,20 @@ function copySelection(deps, text) {
|
|
|
8049
7738
|
async function withExecutionStream(deps, sessionId, request) {
|
|
8050
7739
|
let settleReady = () => {
|
|
8051
7740
|
};
|
|
8052
|
-
const streamReady = new Promise((
|
|
8053
|
-
settleReady = (error) => error ? reject(error) :
|
|
7741
|
+
const streamReady = new Promise((resolve4, reject) => {
|
|
7742
|
+
settleReady = (error) => error ? reject(error) : resolve4();
|
|
8054
7743
|
});
|
|
8055
7744
|
const inbound = inboundFor(deps.api, deps.dispatch, sessionId);
|
|
8056
7745
|
const stream = deps.api.streamExecution(sessionId, inbound.execution(), settleReady);
|
|
8057
7746
|
void stream.catch(settleReady);
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
7747
|
+
try {
|
|
7748
|
+
await streamReady;
|
|
7749
|
+
await request();
|
|
7750
|
+
} catch (err) {
|
|
7751
|
+
deps.api.closeExecutionStream(stream);
|
|
7752
|
+
throw err;
|
|
7753
|
+
}
|
|
7754
|
+
if (await stream === "lost") deps.dispatch({ type: "executionLost", sessionId });
|
|
8061
7755
|
}
|
|
8062
7756
|
async function converse(deps, sessionId, call) {
|
|
8063
7757
|
const inbound = inboundFor(deps.api, deps.dispatch, sessionId);
|
|
@@ -8087,7 +7781,7 @@ function restoredChat(plan, sessionId) {
|
|
|
8087
7781
|
function lastAssistantMessage(plan) {
|
|
8088
7782
|
const history = plan?.conversationHistory ?? [];
|
|
8089
7783
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
8090
|
-
if (history[i].role === "assistant") {
|
|
7784
|
+
if (history[i].role === "assistant" && history[i].kind !== "skill_load") {
|
|
8091
7785
|
const { content, timestamp } = history[i];
|
|
8092
7786
|
return { content, ...timestamp ? { timestamp } : {} };
|
|
8093
7787
|
}
|
|
@@ -8422,7 +8116,7 @@ async function handleTui(subArgs) {
|
|
|
8422
8116
|
}
|
|
8423
8117
|
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
8424
8118
|
try {
|
|
8425
|
-
(0,
|
|
8119
|
+
(0, import_core37.assertWorkspaceExists)(workspace);
|
|
8426
8120
|
} catch {
|
|
8427
8121
|
console.error(
|
|
8428
8122
|
`Workspace "${workspace}" does not exist.
|
|
@@ -8431,7 +8125,9 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8431
8125
|
);
|
|
8432
8126
|
process.exit(1);
|
|
8433
8127
|
}
|
|
8434
|
-
|
|
8128
|
+
const discoveredSkills = (0, import_core37.createSkillsService)(workspace, new import_core37.EnvConfig().workspaceRepos).listSkills();
|
|
8129
|
+
registerSkillCommands(discoveredSkills);
|
|
8130
|
+
const taskSkills = discoveredSkills.filter((s) => s.appliesTo === "task").map((s) => ({ name: s.name, description: s.description }));
|
|
8435
8131
|
const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
|
|
8436
8132
|
const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
|
|
8437
8133
|
const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
|
|
@@ -8441,19 +8137,18 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8441
8137
|
const autoMode = process.env.ORDEWELL_AUTONOMOUS_MODE;
|
|
8442
8138
|
const mouseCapture = process.env.ORDEWELL_TUI_MOUSE !== "false" && process.env.ORDEWELL_TUI_MOUSE !== "0";
|
|
8443
8139
|
const app = createApp({
|
|
8444
|
-
initial: { workspace, autonomous: autoMode !== "false" && autoMode !== "0", mouseCapture },
|
|
8140
|
+
initial: { workspace, autonomous: autoMode !== "false" && autoMode !== "0", mouseCapture, taskSkills },
|
|
8445
8141
|
draw: (frame2) => terminal?.draw(frame2),
|
|
8446
8142
|
perform: (effect) => runEffect(effect, {
|
|
8447
8143
|
api,
|
|
8448
8144
|
workspace,
|
|
8449
8145
|
port,
|
|
8450
8146
|
dispatch: (action) => app.dispatch(action),
|
|
8451
|
-
newSessionId: () => (0,
|
|
8147
|
+
newSessionId: () => (0, import_core37.mintSessionId)(),
|
|
8452
8148
|
setEnvVar: (key, value) => {
|
|
8453
8149
|
writeEnvVar(findEnvFile(), key, value);
|
|
8454
8150
|
process.env[key] = value;
|
|
8455
8151
|
},
|
|
8456
|
-
openTerminal: (sessionId, taskId) => openTaskTerminal(port, sessionId, taskId),
|
|
8457
8152
|
setMouseCapture: (enabled) => terminal?.setMouse(enabled),
|
|
8458
8153
|
// The alt screen is up with the cursor hidden, so an OSC 52 write shows
|
|
8459
8154
|
// nothing — the terminal reads it and answers by taking the clipboard.
|
|
@@ -8467,7 +8162,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8467
8162
|
}),
|
|
8468
8163
|
onExit: () => shutdown(0),
|
|
8469
8164
|
// The same pointer `ordewell plan` leaves, so `ordewell handoff`,
|
|
8470
|
-
//
|
|
8165
|
+
// The one-shot commands act on the session this
|
|
8471
8166
|
// TUI is showing instead of saying there is none.
|
|
8472
8167
|
onSessionChange: (sessionId, state) => {
|
|
8473
8168
|
try {
|
|
@@ -8499,6 +8194,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8499
8194
|
app.dispatch({ type: "resize", rows: r, cols: c });
|
|
8500
8195
|
});
|
|
8501
8196
|
process.on("uncaughtException", (err) => {
|
|
8197
|
+
terminal?.close();
|
|
8502
8198
|
console.error(`Fatal: ${err.message}`);
|
|
8503
8199
|
shutdown(1);
|
|
8504
8200
|
});
|
|
@@ -8524,12 +8220,9 @@ var COMMANDS = {
|
|
|
8524
8220
|
key: handleKey,
|
|
8525
8221
|
runners: handleRunners,
|
|
8526
8222
|
auto: handleAuto,
|
|
8527
|
-
transport: handleTransport,
|
|
8528
8223
|
refresh: handleRefresh,
|
|
8529
8224
|
setup: handleSetup,
|
|
8530
|
-
|
|
8531
|
-
"tdd": handleTdd,
|
|
8532
|
-
"verify": handleVerify,
|
|
8225
|
+
skills: handleSkills,
|
|
8533
8226
|
"mark-complete": handleMarkComplete,
|
|
8534
8227
|
complete: handleMarkComplete,
|
|
8535
8228
|
uncomplete: handleUncomplete,
|
|
@@ -8547,8 +8240,8 @@ var COMMANDS = {
|
|
|
8547
8240
|
"task-effort": handleTaskEffort,
|
|
8548
8241
|
"task-mode": handleTaskMode,
|
|
8549
8242
|
"task-deps": handleTaskDeps,
|
|
8243
|
+
"task-skills": handleTaskSkills,
|
|
8550
8244
|
"task-ops": handleTaskOps,
|
|
8551
|
-
terminal: handleTerminal,
|
|
8552
8245
|
"sessions": handleSessions,
|
|
8553
8246
|
fork: handleFork,
|
|
8554
8247
|
compact: handleCompact,
|
|
@@ -8559,10 +8252,10 @@ var COMMANDS = {
|
|
|
8559
8252
|
|
|
8560
8253
|
// src/version.ts
|
|
8561
8254
|
var import_fs7 = require("fs");
|
|
8562
|
-
var
|
|
8255
|
+
var import_path6 = require("path");
|
|
8563
8256
|
function cliVersion() {
|
|
8564
8257
|
try {
|
|
8565
|
-
const pkg = JSON.parse((0, import_fs7.readFileSync)((0,
|
|
8258
|
+
const pkg = JSON.parse((0, import_fs7.readFileSync)((0, import_path6.join)(__dirname, "..", "package.json"), "utf8"));
|
|
8566
8259
|
const version = pkg.version;
|
|
8567
8260
|
return typeof version === "string" && version ? version : "unknown";
|
|
8568
8261
|
} catch {
|
|
@@ -8573,7 +8266,7 @@ function cliVersion() {
|
|
|
8573
8266
|
// src/main.ts
|
|
8574
8267
|
var TUI_FLAGS = /* @__PURE__ */ new Set(["--workspace", "--port"]);
|
|
8575
8268
|
async function main() {
|
|
8576
|
-
(0,
|
|
8269
|
+
(0, import_core38.migrateOldConfigDir)();
|
|
8577
8270
|
loadEnvFile();
|
|
8578
8271
|
const argv = process.argv.slice(2);
|
|
8579
8272
|
if (TUI_FLAGS.has(argv[0] ?? "")) argv.unshift("tui");
|