@ordewell/cli 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +562 -582
- 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");
|
|
@@ -121,6 +121,7 @@ Tasks (<id> is an order number or a task ID):
|
|
|
121
121
|
ordewell run-task <id> Run only one task
|
|
122
122
|
ordewell retry <id> Re-run a failed task
|
|
123
123
|
ordewell continue <id> <msg> Continue a finished task in its saved runner session
|
|
124
|
+
ordewell checkpoint <id> approve|reject [reason] Answer the checkpoint a task waits at
|
|
124
125
|
ordewell cancel <id> Kill a running task
|
|
125
126
|
ordewell terminal <id> Open a real terminal attached to a task's runner
|
|
126
127
|
|
|
@@ -160,6 +161,12 @@ TUI options:
|
|
|
160
161
|
--workspace /path Workspace directory (default: cwd)
|
|
161
162
|
--port N Target a daemon on port N
|
|
162
163
|
|
|
164
|
+
TUI task view keys (t on a structured task; /help in the TUI lists them all):
|
|
165
|
+
ctrl-s Send now: interrupt the running step and deliver the composer text next
|
|
166
|
+
(with the composer empty, the selected queued message)
|
|
167
|
+
ctrl-n / ctrl-p Select a queued message; ctrl-r removes it
|
|
168
|
+
ctrl-x Interrupt the running turn
|
|
169
|
+
|
|
163
170
|
Plan options:
|
|
164
171
|
--goal "text" Task description (required)
|
|
165
172
|
--runner <id> Runner to use (repeatable; default: all enabled runners)
|
|
@@ -399,15 +406,30 @@ var import_http2 = __toESM(require("http"));
|
|
|
399
406
|
var import_ws = __toESM(require("ws"));
|
|
400
407
|
var import_core3 = require("@ordewell/core");
|
|
401
408
|
var DEFAULT_HTTP_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
402
|
-
var
|
|
403
|
-
constructor(message,
|
|
409
|
+
var DaemonError = class extends Error {
|
|
410
|
+
constructor(message, status, code) {
|
|
404
411
|
super(message);
|
|
412
|
+
this.status = status;
|
|
413
|
+
this.code = code;
|
|
414
|
+
this.name = "DaemonError";
|
|
415
|
+
}
|
|
416
|
+
status;
|
|
417
|
+
code;
|
|
418
|
+
};
|
|
419
|
+
var WorkspaceInitNeededError = class extends DaemonError {
|
|
420
|
+
constructor(message, workspace) {
|
|
421
|
+
super(message, 400, "workspace_not_a_project");
|
|
405
422
|
this.workspace = workspace;
|
|
406
423
|
this.name = "WorkspaceInitNeededError";
|
|
407
424
|
}
|
|
408
425
|
workspace;
|
|
409
426
|
};
|
|
410
427
|
var SESSION_PATH = /^\/api\/(?:plans|sessions)\/([^/?]+)/;
|
|
428
|
+
function daemonError(status, data, fallback) {
|
|
429
|
+
const { error, code, workspace } = typeof data === "object" && data !== null ? data : {};
|
|
430
|
+
if (code === "workspace_not_a_project") return new WorkspaceInitNeededError(error || "Not a project directory", workspace ?? "");
|
|
431
|
+
return new DaemonError(error || fallback, status, code);
|
|
432
|
+
}
|
|
411
433
|
var ApiClient = class _ApiClient {
|
|
412
434
|
/** Where a CLI invocation's sessions live, when a client is built without one: `--workspace`, else the cwd. */
|
|
413
435
|
static defaultWorkspace;
|
|
@@ -415,6 +437,7 @@ var ApiClient = class _ApiClient {
|
|
|
415
437
|
workspace;
|
|
416
438
|
/** A session's open execution stream, so a new one can end it: one run is reported once. */
|
|
417
439
|
executionStreams = /* @__PURE__ */ new Map();
|
|
440
|
+
executionStreamEnds = /* @__PURE__ */ new WeakMap();
|
|
418
441
|
constructor(port, workspace) {
|
|
419
442
|
this.port = port || DEFAULT_PORT;
|
|
420
443
|
this.workspace = workspace ?? _ApiClient.defaultWorkspace ?? process.cwd();
|
|
@@ -429,7 +452,7 @@ var ApiClient = class _ApiClient {
|
|
|
429
452
|
}
|
|
430
453
|
/**
|
|
431
454
|
* A daemon holds only the sessions adopted since it started, so after a
|
|
432
|
-
* restart every session-scoped call answered "
|
|
455
|
+
* restart every session-scoped call answered "session not found" until the
|
|
433
456
|
* user ran `ordewell sessions load`. The saved session is adopted here
|
|
434
457
|
* instead — idempotent for one the daemon still holds — and the call made
|
|
435
458
|
* once more; a 404 means the first attempt never ran, so the retry cannot
|
|
@@ -438,11 +461,17 @@ var ApiClient = class _ApiClient {
|
|
|
438
461
|
async httpRequest(method, urlPath, body) {
|
|
439
462
|
const res = await this.rawRequest(method, urlPath, body);
|
|
440
463
|
const sessionId = urlPath.match(SESSION_PATH)?.[1];
|
|
441
|
-
const missing = res.status === 404 && res.data?.
|
|
464
|
+
const missing = res.status === 404 && res.data?.code === "session_not_found";
|
|
442
465
|
if (!missing || !sessionId || urlPath.split("?")[0].endsWith("/load")) return res;
|
|
443
466
|
const adopted2 = await this.rawRequest("POST", `/api/sessions/${sessionId}/load?workspace=${encodeURIComponent(this.workspace)}`);
|
|
444
467
|
return adopted2.status === 200 ? this.rawRequest(method, urlPath, body) : res;
|
|
445
468
|
}
|
|
469
|
+
/** One request whose only good answer is a 200: anything else becomes a {@link DaemonError} carrying the daemon's code. */
|
|
470
|
+
async call(method, urlPath, fallback, body) {
|
|
471
|
+
const res = await this.httpRequest(method, urlPath, body);
|
|
472
|
+
if (res.status !== 200) throw daemonError(res.status, res.data, fallback);
|
|
473
|
+
return res.data;
|
|
474
|
+
}
|
|
446
475
|
rawRequest(method, urlPath, body) {
|
|
447
476
|
return new Promise((resolve3, reject) => {
|
|
448
477
|
const url = new URL(urlPath, `http://127.0.0.1:${this.port}`);
|
|
@@ -485,15 +514,12 @@ var ApiClient = class _ApiClient {
|
|
|
485
514
|
});
|
|
486
515
|
}
|
|
487
516
|
async generatePlan(goal, runners2, workspace, sessionId = (0, import_core3.mintSessionId)()) {
|
|
488
|
-
const
|
|
517
|
+
const { plan, models, modelsByRunner } = await this.call("POST", `/api/plans/${sessionId}/generate`, "Plan generation failed", {
|
|
489
518
|
goal,
|
|
490
519
|
runners: runners2 || void 0,
|
|
491
520
|
workspace
|
|
492
521
|
});
|
|
493
|
-
|
|
494
|
-
throw new Error(res.data?.error || "Plan generation failed");
|
|
495
|
-
}
|
|
496
|
-
return { sessionId, plan: res.data.plan, models: res.data.models, modelsByRunner: res.data.modelsByRunner };
|
|
522
|
+
return { sessionId, plan, models, modelsByRunner };
|
|
497
523
|
}
|
|
498
524
|
/**
|
|
499
525
|
* Open the ADR-0002 planner dialogue. Unlike `generatePlan`, this path honours
|
|
@@ -501,58 +527,30 @@ var ApiClient = class _ApiClient {
|
|
|
501
527
|
* (empty `tasks`, last word in `conversationHistory`) rather than a committed plan.
|
|
502
528
|
*/
|
|
503
529
|
async startConversation(sessionId, goal, runners2, workspace, allowInit) {
|
|
504
|
-
const res = await this.
|
|
530
|
+
const res = await this.call("POST", `/api/plans/${sessionId}/converse/start`, "Planning failed", {
|
|
505
531
|
goal,
|
|
506
532
|
runners: runners2 || void 0,
|
|
507
533
|
workspace,
|
|
508
534
|
allowInit
|
|
509
535
|
});
|
|
510
|
-
|
|
511
|
-
if (res.data?.code === "workspace_not_a_project") {
|
|
512
|
-
throw new WorkspaceInitNeededError(res.data.error || "Not a project directory", res.data.workspace || workspace || "");
|
|
513
|
-
}
|
|
514
|
-
throw new Error(res.data?.error || "Planning failed");
|
|
515
|
-
}
|
|
516
|
-
return res.data.plan;
|
|
536
|
+
return res.plan;
|
|
517
537
|
}
|
|
518
538
|
async sendConversationMessage(sessionId, message) {
|
|
519
|
-
const res = await this.
|
|
520
|
-
|
|
521
|
-
throw new Error(res.data?.error || "Planner message failed");
|
|
522
|
-
}
|
|
523
|
-
return res.data.plan;
|
|
539
|
+
const res = await this.call("POST", `/api/plans/${sessionId}/converse/message`, "Planner message failed", { message });
|
|
540
|
+
return res.plan;
|
|
524
541
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
if (res.status !== 200) {
|
|
528
|
-
throw new Error(res.data?.error || "Failed to fetch runners");
|
|
529
|
-
}
|
|
530
|
-
return res.data;
|
|
542
|
+
getRunners() {
|
|
543
|
+
return this.call("GET", "/api/runners", "Failed to fetch runners");
|
|
531
544
|
}
|
|
532
545
|
/** The full provider catalog the daemon has discovered (used by the TUI model picker). */
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (res.status !== 200) {
|
|
536
|
-
throw new Error(res.data?.error || "Failed to fetch models");
|
|
537
|
-
}
|
|
538
|
-
return res.data;
|
|
546
|
+
getModels() {
|
|
547
|
+
return this.call("GET", "/api/models", "Failed to fetch models");
|
|
539
548
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if (res.status !== 200) {
|
|
543
|
-
throw new Error(res.data?.error || `Failed to ${enabled ? "enable" : "disable"} ${runner}`);
|
|
544
|
-
}
|
|
545
|
-
return res.data;
|
|
549
|
+
setRunnerEnabled(runner, enabled) {
|
|
550
|
+
return this.call("PUT", `/api/runners/${runner}`, `Failed to ${enabled ? "enable" : "disable"} ${runner}`, { enabled });
|
|
546
551
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
"POST",
|
|
550
|
-
`/api/plans/${sessionId}/execute`
|
|
551
|
-
);
|
|
552
|
-
if (res.status !== 200) {
|
|
553
|
-
throw new Error(res.data?.error || "Execute failed");
|
|
554
|
-
}
|
|
555
|
-
return res.data;
|
|
552
|
+
executePlan(sessionId) {
|
|
553
|
+
return this.call("POST", `/api/plans/${sessionId}/execute`, "Execute failed");
|
|
556
554
|
}
|
|
557
555
|
/**
|
|
558
556
|
* Sign off a plan waiting on `review_needed` and let it continue.
|
|
@@ -561,189 +559,111 @@ var ApiClient = class _ApiClient {
|
|
|
561
559
|
* `resetForRun`) before approving, which is right for starting a plan and
|
|
562
560
|
* wrong for releasing one that is already part-way through a review pause.
|
|
563
561
|
*/
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
"POST",
|
|
567
|
-
`/api/plans/${sessionId}/review/approve`
|
|
568
|
-
);
|
|
569
|
-
if (res.status !== 200) {
|
|
570
|
-
throw new Error(res.data?.error || "Approve failed");
|
|
571
|
-
}
|
|
572
|
-
return res.data;
|
|
562
|
+
approveReview(sessionId) {
|
|
563
|
+
return this.call("POST", `/api/plans/${sessionId}/review/approve`, "Approve failed");
|
|
573
564
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
"POST",
|
|
577
|
-
`/api/plans/${sessionId}/tasks/${taskId}/complete`
|
|
578
|
-
);
|
|
579
|
-
if (res.status !== 200) {
|
|
580
|
-
throw new Error(res.data?.error || "Mark complete failed");
|
|
581
|
-
}
|
|
582
|
-
return res.data;
|
|
565
|
+
markTaskComplete(sessionId, taskId) {
|
|
566
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/complete`, "Mark complete failed");
|
|
583
567
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
"POST",
|
|
587
|
-
`/api/plans/${sessionId}/tasks/${taskId}/uncomplete`
|
|
588
|
-
);
|
|
589
|
-
if (res.status !== 200) {
|
|
590
|
-
throw new Error(res.data?.error || "Mark not done failed");
|
|
591
|
-
}
|
|
592
|
-
return res.data;
|
|
568
|
+
markTaskIncomplete(sessionId, taskId) {
|
|
569
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/uncomplete`, "Mark not done failed");
|
|
593
570
|
}
|
|
594
571
|
/** run | force-start | retry | cancel — real orchestrator work, not a status patch. */
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
"POST",
|
|
598
|
-
`/api/plans/${sessionId}/tasks/${taskId}/${action}`
|
|
599
|
-
);
|
|
600
|
-
if (res.status !== 200) {
|
|
601
|
-
throw new Error(res.data?.error || `${action} failed`);
|
|
602
|
-
}
|
|
603
|
-
return res.data;
|
|
572
|
+
taskControl(sessionId, taskId, action) {
|
|
573
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/${action}`, `${action} failed`);
|
|
604
574
|
}
|
|
605
575
|
/** A user message to a structured task: delivered now if it waits for input, else queued behind its turn. */
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
return
|
|
576
|
+
sendTaskMessage(sessionId, taskId, text) {
|
|
577
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages`, "Send message failed", { text });
|
|
578
|
+
}
|
|
579
|
+
/** Force send: interrupt the task's running turn and deliver `text` next, ahead of anything queued (ADR-0023, F1). */
|
|
580
|
+
forceSendTaskMessage(sessionId, taskId, text) {
|
|
581
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages/now`, "Send now failed", { text });
|
|
582
|
+
}
|
|
583
|
+
/** Force send a message still queued; `sent` is false once the runner already has it. */
|
|
584
|
+
forceSendQueuedTaskMessage(sessionId, taskId, messageId) {
|
|
585
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}/now`, "Send now failed");
|
|
612
586
|
}
|
|
613
587
|
/** Continue a finished structured task in its saved session, with `text` as its next turn. */
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
if (res.status !== 200) {
|
|
617
|
-
throw new Error(res.data?.error || "Continue failed");
|
|
618
|
-
}
|
|
619
|
-
return res.data;
|
|
588
|
+
continueTask(sessionId, taskId, text) {
|
|
589
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/continue`, "Continue failed", { text });
|
|
620
590
|
}
|
|
621
591
|
/** `removed` is false once the message was already delivered. */
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
"DELETE",
|
|
625
|
-
`/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}`
|
|
626
|
-
);
|
|
627
|
-
if (res.status !== 200) {
|
|
628
|
-
throw new Error(res.data?.error || "Remove message failed");
|
|
629
|
-
}
|
|
630
|
-
return res.data;
|
|
592
|
+
removeQueuedTaskMessage(sessionId, taskId, messageId) {
|
|
593
|
+
return this.call("DELETE", `/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}`, "Remove message failed");
|
|
631
594
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
if (res.status !== 200) {
|
|
635
|
-
throw new Error(res.data?.error || "Interrupt failed");
|
|
636
|
-
}
|
|
637
|
-
return res.data;
|
|
595
|
+
interruptTask(sessionId, taskId) {
|
|
596
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/interrupt`, "Interrupt failed");
|
|
638
597
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
throw new Error(res.data?.error || "Add task failed");
|
|
643
|
-
}
|
|
644
|
-
return res.data;
|
|
598
|
+
/** Let a task go on from the checkpoint it waits at; refused, with the reason, when none waits. */
|
|
599
|
+
approveTaskCheckpoint(sessionId, taskId) {
|
|
600
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/checkpoint/approve`, "Approve checkpoint failed");
|
|
645
601
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
throw new Error(res.data?.error || "Update task failed");
|
|
650
|
-
}
|
|
651
|
-
return res.data;
|
|
602
|
+
/** Turn a task back at its checkpoint; `reason` is what the agent is told. */
|
|
603
|
+
rejectTaskCheckpoint(sessionId, taskId, reason) {
|
|
604
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/checkpoint/reject`, "Reject checkpoint failed", reason ? { reason } : {});
|
|
652
605
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
|
|
606
|
+
addTask(sessionId, task) {
|
|
607
|
+
return this.call("POST", `/api/plans/${sessionId}/tasks`, "Add task failed", task);
|
|
608
|
+
}
|
|
609
|
+
updateTask(sessionId, taskId, changes) {
|
|
610
|
+
return this.call("PUT", `/api/plans/${sessionId}/tasks/${taskId}`, "Update task failed", changes);
|
|
611
|
+
}
|
|
612
|
+
removeTask(sessionId, taskId) {
|
|
613
|
+
return this.call("DELETE", `/api/plans/${sessionId}/tasks/${taskId}`, "Remove task failed");
|
|
659
614
|
}
|
|
660
615
|
/**
|
|
661
616
|
* Make a saved session live on the server. `getSession` only reads the file;
|
|
662
617
|
* until the session is adopted there is no orchestrator behind it, so
|
|
663
|
-
* execute/retry/cancel answer
|
|
618
|
+
* execute/retry/cancel answer `session_not_found`.
|
|
664
619
|
*/
|
|
665
620
|
async adoptSession(sessionId, workspace) {
|
|
666
621
|
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
667
|
-
const res = await this.
|
|
668
|
-
|
|
669
|
-
throw new Error(res.data?.error || "Failed to load session");
|
|
670
|
-
}
|
|
671
|
-
return { plan: res.data.plan, goal: res.data.goal ?? "" };
|
|
622
|
+
const res = await this.call("POST", `/api/sessions/${sessionId}/load${qs}`, "Failed to load session");
|
|
623
|
+
return { plan: res.plan, goal: res.goal ?? "" };
|
|
672
624
|
}
|
|
673
|
-
|
|
625
|
+
deleteSession(sessionId, workspace) {
|
|
674
626
|
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
675
|
-
|
|
676
|
-
if (res.status !== 200) {
|
|
677
|
-
throw new Error(res.data?.error || "Delete session failed");
|
|
678
|
-
}
|
|
679
|
-
return res.data;
|
|
627
|
+
return this.call("DELETE", `/api/sessions/${sessionId}${qs}`, "Delete session failed");
|
|
680
628
|
}
|
|
681
629
|
/** Copy the conversation and its tasks into a new session the daemon has already adopted. */
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if (res.status !== 200) {
|
|
685
|
-
throw new Error(res.data?.error || "Fork failed");
|
|
686
|
-
}
|
|
687
|
-
return { sessionId: res.data.sessionId, goal: res.data.goal, plan: res.data.plan };
|
|
630
|
+
forkConversation(sessionId) {
|
|
631
|
+
return this.call("POST", `/api/plans/${sessionId}/conversation/fork`, "Fork failed");
|
|
688
632
|
}
|
|
689
633
|
async rewindTargets(sessionId) {
|
|
690
|
-
const res = await this.
|
|
691
|
-
|
|
692
|
-
throw new Error(res.data?.error || "Failed to list rewind targets");
|
|
693
|
-
}
|
|
694
|
-
return res.data.targets;
|
|
634
|
+
const res = await this.call("GET", `/api/plans/${sessionId}/conversation/rewind-targets`, "Failed to list rewind targets");
|
|
635
|
+
return res.targets;
|
|
695
636
|
}
|
|
696
637
|
/**
|
|
697
638
|
* Fork the conversation from just before the user message at `index` (a
|
|
698
639
|
* transcript position) into a session the daemon has already adopted. The
|
|
699
640
|
* original is left as it was; `rewoundMessage` is that message in full.
|
|
700
641
|
*/
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
if (res.status !== 200) {
|
|
704
|
-
throw new Error(res.data?.error || "Rewind failed");
|
|
705
|
-
}
|
|
706
|
-
return { sessionId: res.data.sessionId, goal: res.data.goal, plan: res.data.plan, rewoundMessage: res.data.rewoundMessage };
|
|
642
|
+
rewindConversation(sessionId, index) {
|
|
643
|
+
return this.call("POST", `/api/plans/${sessionId}/conversation/rewind`, "Rewind failed", { index });
|
|
707
644
|
}
|
|
708
645
|
/**
|
|
709
646
|
* Condense the conversation into a summary. One planner call, so it can take
|
|
710
647
|
* as long as a reply; a refusal or failure leaves the conversation as it was.
|
|
711
648
|
*/
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
if (res.status !== 200) {
|
|
715
|
-
throw new Error(res.data?.error || "Compaction failed");
|
|
716
|
-
}
|
|
717
|
-
return { plan: res.data.plan, summary: res.data.summary, keptMessages: res.data.keptMessages };
|
|
649
|
+
compactConversation(sessionId) {
|
|
650
|
+
return this.call("POST", `/api/plans/${sessionId}/conversation/compact`, "Compaction failed");
|
|
718
651
|
}
|
|
719
652
|
/** The dependencies a task waits on at its merge gate (ADR-0020); empty when nothing gates it. */
|
|
720
653
|
async getMergeGate(sessionId, taskId) {
|
|
721
|
-
const res = await this.
|
|
722
|
-
|
|
723
|
-
`/api/plans/${sessionId}/tasks/${taskId}/merge-gate`
|
|
724
|
-
);
|
|
725
|
-
if (res.status !== 200) {
|
|
726
|
-
throw new Error(res.data?.error || "Could not read the merge gate");
|
|
727
|
-
}
|
|
728
|
-
return res.data.mergeGate ?? [];
|
|
654
|
+
const res = await this.call("GET", `/api/plans/${sessionId}/tasks/${taskId}/merge-gate`, "Could not read the merge gate");
|
|
655
|
+
return res.mergeGate ?? [];
|
|
729
656
|
}
|
|
730
657
|
async reviewRunDiff(sessionId) {
|
|
731
|
-
const res = await this.
|
|
732
|
-
|
|
733
|
-
throw new Error(res.data?.error || "Could not read the diff");
|
|
734
|
-
}
|
|
735
|
-
return res.data.diff;
|
|
658
|
+
const res = await this.call("GET", `/api/plans/${sessionId}/isolation/diff`, "Could not read the diff");
|
|
659
|
+
return res.diff;
|
|
736
660
|
}
|
|
737
661
|
/**
|
|
738
662
|
* A conflict or a block is an outcome, not an error. Passed on whole: which
|
|
739
663
|
* repos blocked the merge, or landed before it stopped, is the answer.
|
|
740
664
|
*/
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
if (res.status !== 200) {
|
|
744
|
-
throw new Error(res.data?.error || "Merge failed");
|
|
745
|
-
}
|
|
746
|
-
return res.data;
|
|
665
|
+
mergeRun(sessionId) {
|
|
666
|
+
return this.call("POST", `/api/plans/${sessionId}/isolation/merge`, "Merge failed");
|
|
747
667
|
}
|
|
748
668
|
discardRun(sessionId) {
|
|
749
669
|
return this.isolationAction(sessionId, "discard", "Discard failed");
|
|
@@ -758,17 +678,11 @@ var ApiClient = class _ApiClient {
|
|
|
758
678
|
return this.isolationAction(sessionId, "run-without", "Could not continue without isolation");
|
|
759
679
|
}
|
|
760
680
|
async isolationAction(sessionId, segment, failure) {
|
|
761
|
-
|
|
762
|
-
if (res.status !== 200) {
|
|
763
|
-
throw new Error(res.data?.error || failure);
|
|
764
|
-
}
|
|
681
|
+
await this.call("POST", `/api/plans/${sessionId}/isolation/${segment}`, failure);
|
|
765
682
|
}
|
|
766
683
|
async resolveConflictAsTask(sessionId, taskId) {
|
|
767
|
-
const res = await this.
|
|
768
|
-
|
|
769
|
-
throw new Error(res.data?.error || "Could not add a resolver task");
|
|
770
|
-
}
|
|
771
|
-
return res.data.plan;
|
|
684
|
+
const res = await this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/resolve-conflict`, "Could not add a resolver task");
|
|
685
|
+
return res.plan;
|
|
772
686
|
}
|
|
773
687
|
async closeSession(sessionId) {
|
|
774
688
|
const res = await this.httpRequest("POST", `/api/sessions/${sessionId}/close`);
|
|
@@ -783,42 +697,26 @@ var ApiClient = class _ApiClient {
|
|
|
783
697
|
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/planning/stop`);
|
|
784
698
|
return res.data;
|
|
785
699
|
}
|
|
786
|
-
async processQueued(sessionId) {
|
|
787
|
-
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/process-queued`);
|
|
788
|
-
if (res.status !== 200) {
|
|
789
|
-
throw new Error(res.data?.error || "Process queued failed");
|
|
790
|
-
}
|
|
791
|
-
return res.data;
|
|
792
|
-
}
|
|
793
700
|
async getSessions(workspace) {
|
|
794
701
|
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
795
702
|
const res = await this.httpRequest("GET", `/api/sessions${qs}`);
|
|
796
703
|
return res.data || [];
|
|
797
704
|
}
|
|
798
|
-
|
|
705
|
+
getSession(sessionId, workspace) {
|
|
799
706
|
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
800
|
-
|
|
801
|
-
"GET",
|
|
802
|
-
`/api/sessions/${sessionId}${qs}`
|
|
803
|
-
);
|
|
804
|
-
if (res.status !== 200) {
|
|
805
|
-
throw new Error(res.data?.error || "Session not found");
|
|
806
|
-
}
|
|
807
|
-
return res.data;
|
|
707
|
+
return this.call("GET", `/api/sessions/${sessionId}${qs}`, "Session not found");
|
|
808
708
|
}
|
|
809
709
|
/** The attempts of a structured task that have a saved log, oldest first. */
|
|
810
710
|
async getTaskLogAttempts(sessionId, taskId, workspace) {
|
|
811
711
|
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
812
|
-
const res = await this.
|
|
813
|
-
|
|
814
|
-
return res.data.attempts;
|
|
712
|
+
const res = await this.call("GET", `/api/sessions/${sessionId}/tasks/${encodeURIComponent(taskId)}/log${qs}`, "Could not read the task log");
|
|
713
|
+
return res.attempts;
|
|
815
714
|
}
|
|
816
715
|
/** One attempt's saved log, for `replayTaskLog`. */
|
|
817
716
|
async getTaskLog(sessionId, taskId, attempt, workspace) {
|
|
818
717
|
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
819
|
-
const res = await this.
|
|
820
|
-
|
|
821
|
-
return res.data.events;
|
|
718
|
+
const res = await this.call("GET", `/api/sessions/${sessionId}/tasks/${encodeURIComponent(taskId)}/log/${attempt}${qs}`, "Could not read the task log");
|
|
719
|
+
return res.events;
|
|
822
720
|
}
|
|
823
721
|
/**
|
|
824
722
|
* The one place a session socket is built, so the token travels on both
|
|
@@ -849,8 +747,14 @@ var ApiClient = class _ApiClient {
|
|
|
849
747
|
});
|
|
850
748
|
return socket;
|
|
851
749
|
}
|
|
750
|
+
/** Ends one stream `streamExecution` returned, leaving any newer stream the session has opened since alone. */
|
|
751
|
+
closeExecutionStream(stream) {
|
|
752
|
+
this.executionStreamEnds.get(stream)?.();
|
|
753
|
+
}
|
|
852
754
|
streamExecution(sessionId, onEvent, onReady) {
|
|
853
|
-
|
|
755
|
+
let endStream = () => {
|
|
756
|
+
};
|
|
757
|
+
const stream = new Promise((resolve3, reject) => {
|
|
854
758
|
this.executionStreams.get(sessionId)?.();
|
|
855
759
|
const socket = this.openSessionSocket(sessionId);
|
|
856
760
|
let resolved = false;
|
|
@@ -866,6 +770,7 @@ var ApiClient = class _ApiClient {
|
|
|
866
770
|
resolve3();
|
|
867
771
|
};
|
|
868
772
|
this.executionStreams.set(sessionId, end);
|
|
773
|
+
endStream = end;
|
|
869
774
|
socket.on("open", () => {
|
|
870
775
|
opened = true;
|
|
871
776
|
onReady?.();
|
|
@@ -882,8 +787,12 @@ var ApiClient = class _ApiClient {
|
|
|
882
787
|
socket.on("error", (err) => {
|
|
883
788
|
forget();
|
|
884
789
|
if (!opened) onReady?.(err);
|
|
885
|
-
if (
|
|
886
|
-
|
|
790
|
+
if (resolved) return;
|
|
791
|
+
resolved = true;
|
|
792
|
+
if (opened) {
|
|
793
|
+
socket.close();
|
|
794
|
+
resolve3("lost");
|
|
795
|
+
} else {
|
|
887
796
|
reject(err);
|
|
888
797
|
}
|
|
889
798
|
});
|
|
@@ -892,10 +801,12 @@ var ApiClient = class _ApiClient {
|
|
|
892
801
|
if (!opened) onReady?.(new Error("Execution stream closed before connecting"));
|
|
893
802
|
if (!resolved) {
|
|
894
803
|
resolved = true;
|
|
895
|
-
resolve3();
|
|
804
|
+
resolve3("lost");
|
|
896
805
|
}
|
|
897
806
|
});
|
|
898
807
|
});
|
|
808
|
+
this.executionStreamEnds.set(stream, endStream);
|
|
809
|
+
return stream;
|
|
899
810
|
}
|
|
900
811
|
/**
|
|
901
812
|
* Answer an approval prompt: the planner's with a yes/no, a task runner's
|
|
@@ -908,7 +819,7 @@ var ApiClient = class _ApiClient {
|
|
|
908
819
|
`/api/approvals/${encodeURIComponent(sessionId)}/${encodeURIComponent(approvalId)}`,
|
|
909
820
|
typeof answer === "boolean" ? { granted: answer } : answer
|
|
910
821
|
);
|
|
911
|
-
if (status !== 200) throw new
|
|
822
|
+
if (status !== 200) throw new DaemonError(`Failed to answer approval ${approvalId} (HTTP ${status})`, status, data.code);
|
|
912
823
|
return data;
|
|
913
824
|
}
|
|
914
825
|
/**
|
|
@@ -933,33 +844,17 @@ var ApiClient = class _ApiClient {
|
|
|
933
844
|
});
|
|
934
845
|
return { ready, close: () => socket.close() };
|
|
935
846
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if (res.status !== 200) {
|
|
939
|
-
throw new Error(res.data?.error || "Failed to fetch commands");
|
|
940
|
-
}
|
|
941
|
-
return res.data;
|
|
847
|
+
getCommands() {
|
|
848
|
+
return this.call("GET", "/api/commands", "Failed to fetch commands");
|
|
942
849
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
if (res.status !== 200) {
|
|
946
|
-
throw new Error(res.data?.error || `Command ${name} failed`);
|
|
947
|
-
}
|
|
948
|
-
return res.data;
|
|
850
|
+
sendCommand(name, args = {}) {
|
|
851
|
+
return this.call("POST", `/api/commands/${name}`, `Command ${name} failed`, { args });
|
|
949
852
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
if (res.status !== 200) {
|
|
953
|
-
throw new Error(res.data?.error || "Failed to fetch settings");
|
|
954
|
-
}
|
|
955
|
-
return res.data;
|
|
853
|
+
getSettings() {
|
|
854
|
+
return this.call("GET", "/api/settings", "Failed to fetch settings");
|
|
956
855
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
if (res.status !== 200) {
|
|
960
|
-
throw new Error(res.data?.error || "Failed to update settings");
|
|
961
|
-
}
|
|
962
|
-
return res.data;
|
|
856
|
+
updateSettings(changes) {
|
|
857
|
+
return this.call("PATCH", "/api/settings", "Failed to update settings", changes);
|
|
963
858
|
}
|
|
964
859
|
};
|
|
965
860
|
|
|
@@ -1207,11 +1102,14 @@ var import_core7 = require("@ordewell/core");
|
|
|
1207
1102
|
function planTasks(plan) {
|
|
1208
1103
|
return plan ? allTasksOf(plan) : [];
|
|
1209
1104
|
}
|
|
1105
|
+
function runnersOf(plan, requested) {
|
|
1106
|
+
return "runners" in plan && plan.runners || requested;
|
|
1107
|
+
}
|
|
1210
1108
|
function hasCommittedPlan(plan) {
|
|
1211
1109
|
return planTasks(plan).length > 0;
|
|
1212
1110
|
}
|
|
1213
1111
|
function lastPlannerMessage(plan) {
|
|
1214
|
-
const history = plan
|
|
1112
|
+
const history = "conversationHistory" in plan && plan.conversationHistory || [];
|
|
1215
1113
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
1216
1114
|
if (history[i].role === "assistant") return history[i].content;
|
|
1217
1115
|
}
|
|
@@ -1273,7 +1171,7 @@ function printPlan(plan, sessionId, runners2, models) {
|
|
|
1273
1171
|
return rp ? ` (${task.assignedModel.modelLabel} \xB7 ${label})` : ` (${task.assignedModel.modelLabel})`;
|
|
1274
1172
|
};
|
|
1275
1173
|
console.log(`
|
|
1276
|
-
Plan: ${tasks.length} task${tasks.length === 1 ? "" : "s"} (${aiCount} AI, ${manCount} Manual) \u2014 ${(plan
|
|
1174
|
+
Plan: ${tasks.length} task${tasks.length === 1 ? "" : "s"} (${aiCount} AI, ${manCount} Manual) \u2014 ${runnersOf(plan, runners2).join(", ")}`);
|
|
1277
1175
|
console.log(`Session: ${sessionId}
|
|
1278
1176
|
`);
|
|
1279
1177
|
for (const t of tasks) {
|
|
@@ -1420,7 +1318,7 @@ ${lastPlannerMessage(plan)}
|
|
|
1420
1318
|
if (isTty && lastLineLength > 0) process.stderr.write("\r\x1B[2K");
|
|
1421
1319
|
process.stderr.write("\n");
|
|
1422
1320
|
printPlan(plan, sessionId, runners2, models);
|
|
1423
|
-
saveLastSession(sessionId, goal, plan
|
|
1321
|
+
saveLastSession(sessionId, goal, runnersOf(plan, runners2), workspace);
|
|
1424
1322
|
console.log(`
|
|
1425
1323
|
Run 'ordewell run' to execute, 'ordewell status' to inspect, or 'ordewell tui' for the full UI.`);
|
|
1426
1324
|
} catch (err) {
|
|
@@ -1528,9 +1426,6 @@ function repoResultLines(handoff) {
|
|
|
1528
1426
|
return `${path}: ${n === 0 ? "nothing to merge" : `${n} task${n === 1 ? "" : "s"} landed`}`;
|
|
1529
1427
|
});
|
|
1530
1428
|
}
|
|
1531
|
-
function taskRepoNames(isolation) {
|
|
1532
|
-
return (isolation?.repos ?? []).filter((r) => r !== ".");
|
|
1533
|
-
}
|
|
1534
1429
|
function mergeOutcome(result, branch, group, repaired = []) {
|
|
1535
1430
|
const ok = result.outcome === "merged";
|
|
1536
1431
|
if (!group && result.outcome === "conflict") {
|
|
@@ -1541,23 +1436,27 @@ function mergeOutcome(result, branch, group, repaired = []) {
|
|
|
1541
1436
|
}
|
|
1542
1437
|
|
|
1543
1438
|
// src/utils/output.ts
|
|
1439
|
+
var import_core9 = require("@ordewell/core");
|
|
1544
1440
|
function iconFor(status) {
|
|
1545
|
-
switch (status) {
|
|
1546
|
-
case "
|
|
1441
|
+
switch ((0, import_core9.taskStatusKind)({ status })) {
|
|
1442
|
+
case "done":
|
|
1547
1443
|
return "\x1B[32m\u2713\x1B[0m";
|
|
1548
|
-
case "
|
|
1444
|
+
case "running":
|
|
1445
|
+
case "quiet":
|
|
1549
1446
|
return "\x1B[34m\u27F3\x1B[0m";
|
|
1550
1447
|
case "failed":
|
|
1551
1448
|
return "\x1B[31m\u2717\x1B[0m";
|
|
1552
1449
|
case "blocked":
|
|
1553
1450
|
return "\x1B[33m\u2298\x1B[0m";
|
|
1554
|
-
|
|
1451
|
+
case "awaiting":
|
|
1452
|
+
return "\x1B[33m?\x1B[0m";
|
|
1453
|
+
case "todo":
|
|
1555
1454
|
return "\x1B[90m\u25CB\x1B[0m";
|
|
1556
1455
|
}
|
|
1557
1456
|
}
|
|
1558
1457
|
|
|
1559
1458
|
// src/commands/run.ts
|
|
1560
|
-
var
|
|
1459
|
+
var import_core10 = require("@ordewell/core");
|
|
1561
1460
|
|
|
1562
1461
|
// src/commands/shared.ts
|
|
1563
1462
|
var import_readline2 = require("readline");
|
|
@@ -1713,11 +1612,11 @@ async function followExecution(api, sessionId, start, onBlocked) {
|
|
|
1713
1612
|
});
|
|
1714
1613
|
const stream = api.streamExecution(sessionId, (event) => {
|
|
1715
1614
|
if (event.type === "task_started") {
|
|
1716
|
-
process.stderr.write(`\xB7 ${(0,
|
|
1615
|
+
process.stderr.write(`\xB7 ${(0, import_core10.taskStartedNotice)(event.title, event.runner)}
|
|
1717
1616
|
`);
|
|
1718
1617
|
}
|
|
1719
1618
|
if (event.type === "checkpoint") {
|
|
1720
|
-
process.stderr.write(`\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0,
|
|
1619
|
+
process.stderr.write(`\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core10.truncateCheckpointSummary)(event.summary)} \u2014 \`ordewell checkpoint <id> approve|reject [reason]\` answers it
|
|
1721
1620
|
`);
|
|
1722
1621
|
}
|
|
1723
1622
|
if (event.type === "status_update" && event.tasks) {
|
|
@@ -1762,7 +1661,11 @@ Run finished on ${handoffBranch(event)}${where} \u2014 ${n} task${n === 1 ? "" :
|
|
|
1762
1661
|
void stream.catch(settleReady);
|
|
1763
1662
|
await streamReady.catch(streamFailed);
|
|
1764
1663
|
await start();
|
|
1765
|
-
await Promise.race([stream, blockedSeen]).catch(streamFailed);
|
|
1664
|
+
const outcome = await Promise.race([stream, blockedSeen]).catch(streamFailed);
|
|
1665
|
+
if (outcome === "lost") {
|
|
1666
|
+
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.");
|
|
1667
|
+
process.exit(1);
|
|
1668
|
+
}
|
|
1766
1669
|
if (blocked === null) return;
|
|
1767
1670
|
if (!onBlocked) {
|
|
1768
1671
|
await api.stopExecution(sessionId);
|
|
@@ -1790,7 +1693,7 @@ async function handleRun(subArgs, injectedApi) {
|
|
|
1790
1693
|
|
|
1791
1694
|
// src/commands/status.ts
|
|
1792
1695
|
var import_fs4 = require("fs");
|
|
1793
|
-
var
|
|
1696
|
+
var import_core11 = require("@ordewell/core");
|
|
1794
1697
|
async function handleStatus(subArgs, injectedApi) {
|
|
1795
1698
|
const sessionId = flag(subArgs, "--session-id");
|
|
1796
1699
|
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
@@ -1861,12 +1764,12 @@ function printOneSession(sessionId, meta, plan) {
|
|
|
1861
1764
|
const icon = iconFor(t.status);
|
|
1862
1765
|
const model = t.assignedModel ? ` (${t.assignedModel.modelLabel})` : "";
|
|
1863
1766
|
const shortId = (t.id ?? "").slice(-8);
|
|
1864
|
-
console.log(` ${(0,
|
|
1767
|
+
console.log(` ${(0, import_core11.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}] ${t.title}${model}`);
|
|
1865
1768
|
for (const sub of t.subtasks || []) {
|
|
1866
1769
|
const subIcon = iconFor(sub.status);
|
|
1867
1770
|
const subModel = sub.assignedModel ? ` (${sub.assignedModel.modelLabel})` : "";
|
|
1868
1771
|
const subShortId = (sub.id ?? "").slice(-8);
|
|
1869
|
-
console.log(` ${(0,
|
|
1772
|
+
console.log(` ${(0, import_core11.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
|
|
1870
1773
|
}
|
|
1871
1774
|
}
|
|
1872
1775
|
}
|
|
@@ -1918,10 +1821,10 @@ async function handleWeb(subArgs) {
|
|
|
1918
1821
|
}
|
|
1919
1822
|
|
|
1920
1823
|
// src/commands/models.ts
|
|
1921
|
-
var
|
|
1824
|
+
var import_core13 = require("@ordewell/core");
|
|
1922
1825
|
|
|
1923
1826
|
// src/commands/model.ts
|
|
1924
|
-
var
|
|
1827
|
+
var import_core12 = require("@ordewell/core");
|
|
1925
1828
|
var USAGE = "Usage: ordewell model [show | set <model-id>]";
|
|
1926
1829
|
async function handleModel(subArgs, injectedApi) {
|
|
1927
1830
|
const [action, modelId] = positionals(subArgs);
|
|
@@ -1936,7 +1839,7 @@ async function handleModel(subArgs, injectedApi) {
|
|
|
1936
1839
|
if (!modelId) fail(USAGE);
|
|
1937
1840
|
const settings = await api.getSettings();
|
|
1938
1841
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
1939
|
-
const runner = (0,
|
|
1842
|
+
const runner = (0, import_core12.runnerForProvider)(provider);
|
|
1940
1843
|
if (runner) {
|
|
1941
1844
|
const known = (await fetchCatalog(api)).models.filter((m) => m.runners?.includes(runner));
|
|
1942
1845
|
if (known.length > 0 && !known.some((m) => m.id === modelId)) {
|
|
@@ -1955,7 +1858,7 @@ async function showModels(api) {
|
|
|
1955
1858
|
const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
|
|
1956
1859
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
1957
1860
|
const current = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
|
|
1958
|
-
const runner = (0,
|
|
1861
|
+
const runner = (0, import_core12.runnerForProvider)(provider);
|
|
1959
1862
|
const options = runner ? catalog.models.filter((m) => m.runners?.includes(runner)) : catalog.orchestratorModels;
|
|
1960
1863
|
console.log(`
|
|
1961
1864
|
Planner model: ${current || "(backend default)"}`);
|
|
@@ -1989,8 +1892,8 @@ async function handleModels(subArgs) {
|
|
|
1989
1892
|
const rest = subArgs.filter((_, i) => i !== setIdx && i !== setIdx + 1);
|
|
1990
1893
|
return handleModel(["set", subArgs[setIdx + 1], ...rest]);
|
|
1991
1894
|
}
|
|
1992
|
-
const config = new
|
|
1993
|
-
const providers = (0,
|
|
1895
|
+
const config = new import_core13.EnvConfig();
|
|
1896
|
+
const providers = (0, import_core13.configuredProviders)(config);
|
|
1994
1897
|
if (providers.length === 0) {
|
|
1995
1898
|
console.error("No providers configured. Set an API key for a provider, e.g.:");
|
|
1996
1899
|
console.error(" OPENROUTER_API_KEY=... (OpenRouter)");
|
|
@@ -1999,19 +1902,19 @@ async function handleModels(subArgs) {
|
|
|
1999
1902
|
console.error(" OPENAI_COMPATIBLE_BASE_URL=... (self-hosted / OpenAI-compatible)");
|
|
2000
1903
|
process.exit(1);
|
|
2001
1904
|
}
|
|
2002
|
-
const { apiKeys, baseUrls } = (0,
|
|
1905
|
+
const { apiKeys, baseUrls } = (0, import_core13.collectProviderCredentials)(config);
|
|
2003
1906
|
let models;
|
|
2004
1907
|
let errors;
|
|
2005
1908
|
try {
|
|
2006
|
-
const result = await (0,
|
|
2007
|
-
models = (0,
|
|
1909
|
+
const result = await (0, import_core13.fetchAllProviderModels)({ apiKeys, baseUrls });
|
|
1910
|
+
models = (0, import_core13.toOrchestratorOptions)(result.models, import_core13.ORCHESTRATOR_SHORTCUTS);
|
|
2008
1911
|
errors = result.errors;
|
|
2009
1912
|
} catch (err) {
|
|
2010
1913
|
console.error(`Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`);
|
|
2011
1914
|
process.exit(1);
|
|
2012
1915
|
return;
|
|
2013
1916
|
}
|
|
2014
|
-
const providerLabel = (p) => (0,
|
|
1917
|
+
const providerLabel = (p) => (0, import_core13.getProviderMeta)(p)?.label ?? p;
|
|
2015
1918
|
console.log(
|
|
2016
1919
|
`
|
|
2017
1920
|
Available models (${models.length}) across ${providers.length} configured provider(s):`
|
|
@@ -2051,7 +1954,7 @@ ${providerLabel(provider)}`);
|
|
|
2051
1954
|
|
|
2052
1955
|
// src/commands/setup.ts
|
|
2053
1956
|
var import_fs6 = require("fs");
|
|
2054
|
-
var
|
|
1957
|
+
var import_core14 = require("@ordewell/core");
|
|
2055
1958
|
function handleSetup(subArgs) {
|
|
2056
1959
|
const modelIdx = subArgs.indexOf("--model");
|
|
2057
1960
|
const chosenModel = modelIdx !== -1 ? subArgs[modelIdx + 1] : null;
|
|
@@ -2123,15 +2026,15 @@ function handleSetup(subArgs) {
|
|
|
2123
2026
|
# STEPFUN_API_KEY=...
|
|
2124
2027
|
# SILICONFLOW_API_KEY=...
|
|
2125
2028
|
`;
|
|
2126
|
-
(0,
|
|
2029
|
+
(0, import_core14.writePrivateFile)(envFile, content);
|
|
2127
2030
|
console.log(`Wrote: ${envFile}`);
|
|
2128
2031
|
console.log("Review and edit the file, then run: ordewell tui");
|
|
2129
2032
|
}
|
|
2130
2033
|
|
|
2131
2034
|
// src/commands/plugins.ts
|
|
2132
|
-
var
|
|
2035
|
+
var import_core15 = require("@ordewell/core");
|
|
2133
2036
|
function handlePlugins(subArgs) {
|
|
2134
|
-
const registry = new
|
|
2037
|
+
const registry = new import_core15.RunnerRegistry();
|
|
2135
2038
|
registry.loadUserPlugins();
|
|
2136
2039
|
const sub = subArgs[0];
|
|
2137
2040
|
if (sub === "list" || !sub) {
|
|
@@ -2167,7 +2070,7 @@ ${plugins.length} plugin(s):
|
|
|
2167
2070
|
process.exit(1);
|
|
2168
2071
|
}
|
|
2169
2072
|
try {
|
|
2170
|
-
const resolved = (0,
|
|
2073
|
+
const resolved = (0, import_core15.classifyPluginSource)(source);
|
|
2171
2074
|
const manifest = resolved.kind === "git" ? registry.installFromGit(resolved.url) : registry.installFromPath(resolved.path);
|
|
2172
2075
|
console.log(`Installed plugin: ${manifest.name} v${manifest.version}`);
|
|
2173
2076
|
} catch (err) {
|
|
@@ -2345,6 +2248,7 @@ async function handleTransport(subArgs, api) {
|
|
|
2345
2248
|
}
|
|
2346
2249
|
|
|
2347
2250
|
// src/commands/task-control.ts
|
|
2251
|
+
var import_core16 = require("@ordewell/core");
|
|
2348
2252
|
var PAST_TENSE = {
|
|
2349
2253
|
run: "Task started.",
|
|
2350
2254
|
"force-start": "Task force-started.",
|
|
@@ -2387,7 +2291,7 @@ async function confirmPastGate(subArgs, api, sessionId, taskId, confirm) {
|
|
|
2387
2291
|
if (hasFlag(subArgs, "--yes")) return;
|
|
2388
2292
|
const unmerged = await api.getMergeGate(sessionId, taskId).catch(() => []);
|
|
2389
2293
|
if (unmerged.length === 0) return;
|
|
2390
|
-
const question =
|
|
2294
|
+
const question = `${(0, import_core16.pastGateConfirmation)(unmerged.map((d) => (0, import_core16.titledTaskRef)(d)))} Start it anyway?`;
|
|
2391
2295
|
if (!await confirm(question)) fail("Not started \u2014 nothing was changed. Pass --yes to start it without a prompt.");
|
|
2392
2296
|
}
|
|
2393
2297
|
function makeHandler(action, command = action) {
|
|
@@ -2427,6 +2331,30 @@ async function handleContinue(subArgs, injectedApi) {
|
|
|
2427
2331
|
}
|
|
2428
2332
|
});
|
|
2429
2333
|
}
|
|
2334
|
+
async function handleCheckpoint(subArgs, injectedApi) {
|
|
2335
|
+
const usage = "Usage: ordewell checkpoint <task-id-or-order> approve|reject [reason] [--session-id <id>] [--workspace /path]";
|
|
2336
|
+
const [, answer, ...words] = positionals(subArgs);
|
|
2337
|
+
const reason = words.join(" ").trim();
|
|
2338
|
+
if (answer !== "approve" && answer !== "reject") {
|
|
2339
|
+
console.error(usage);
|
|
2340
|
+
process.exit(1);
|
|
2341
|
+
}
|
|
2342
|
+
if (answer === "approve" && reason) {
|
|
2343
|
+
console.error(`Approving takes no note \u2014 reject with a reason to tell the task something.
|
|
2344
|
+
${usage}`);
|
|
2345
|
+
process.exit(1);
|
|
2346
|
+
}
|
|
2347
|
+
await withResolvedTask(subArgs, usage, injectedApi, async (api, sessionId, taskId) => {
|
|
2348
|
+
try {
|
|
2349
|
+
if (answer === "approve") await api.approveTaskCheckpoint(sessionId, taskId);
|
|
2350
|
+
else await api.rejectTaskCheckpoint(sessionId, taskId, reason || void 0);
|
|
2351
|
+
console.log(answer === "approve" ? "Checkpoint approved." : "Checkpoint rejected.");
|
|
2352
|
+
} catch (err) {
|
|
2353
|
+
console.error(`Failed to ${answer} checkpoint: ${err.message}`);
|
|
2354
|
+
process.exit(1);
|
|
2355
|
+
}
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2430
2358
|
var handleRunTask = makeHandler("run", "run-task");
|
|
2431
2359
|
var handleForceStart = makeHandler("force-start");
|
|
2432
2360
|
var handleRetry = makeHandler("retry");
|
|
@@ -2676,24 +2604,24 @@ async function handleRewind(subArgs, injectedApi) {
|
|
|
2676
2604
|
}
|
|
2677
2605
|
|
|
2678
2606
|
// src/tui/transcript.ts
|
|
2679
|
-
var
|
|
2607
|
+
var import_core17 = require("@ordewell/core");
|
|
2680
2608
|
function show(state, { view: conversation, gate: turnGate }) {
|
|
2681
2609
|
const gated = turnGate === state.turnGate ? state : { ...state, turnGate };
|
|
2682
2610
|
if (conversation === state.conversation) return gated;
|
|
2683
2611
|
return { ...gated, conversation };
|
|
2684
2612
|
}
|
|
2685
2613
|
function fold(state, input) {
|
|
2686
|
-
return show(state, { view: (0,
|
|
2614
|
+
return show(state, { view: (0, import_core17.reduceConversation)(state.conversation, input), gate: state.turnGate });
|
|
2687
2615
|
}
|
|
2688
2616
|
function say(state, role, text) {
|
|
2689
2617
|
const said = fold(state, { type: "local_entry", role, text });
|
|
2690
2618
|
return said.conversation.nextId > state.conversation.nextId ? { ...said, scroll: 0 } : said;
|
|
2691
2619
|
}
|
|
2692
2620
|
function hear(state, message) {
|
|
2693
|
-
return show(state, (0,
|
|
2621
|
+
return show(state, (0, import_core17.followTurn)(state.conversation, state.turnGate, message));
|
|
2694
2622
|
}
|
|
2695
2623
|
function cutTurn(state) {
|
|
2696
|
-
return show(state, (0,
|
|
2624
|
+
return show(state, (0, import_core17.stopTurn)(state.conversation, state.turnGate));
|
|
2697
2625
|
}
|
|
2698
2626
|
function wiped(view) {
|
|
2699
2627
|
return { ...view, blocks: view.blocks.filter((b) => b.type === "usage") };
|
|
@@ -2919,12 +2847,12 @@ function runHandoffAction(state, id) {
|
|
|
2919
2847
|
overlay: isRepoGroup(handoff) ? {
|
|
2920
2848
|
kind: "confirm",
|
|
2921
2849
|
title: "Merge all into your branches?",
|
|
2922
|
-
message: `Merge ${handoffBranch(handoff)} into whatever each of ${reposWithWork(handoff).join(", ")} has checked out. Ordewell never does this on its own. It checks every repository first and merges none unless every repository can take it; on git older than 2.38 it goes repository by repository and stops at the first that fails. A merge that conflicts is aborted, and your trees stay as they were. Once every repository has merged, the run's worktrees and branches are removed.${repairedNotice(handoff)}`,
|
|
2850
|
+
message: `Merge ${handoffBranch(handoff)} into whatever each of ${reposWithWork(handoff).join(", ")} has checked out. Ordewell never does this on its own. It checks every repository first and merges none unless every repository can take it; on git older than 2.38 it goes repository by repository and stops at the first that fails. A merge that conflicts is aborted, and your trees stay as they were. Once every repository has merged, the run's worktrees and branches are removed \u2014 unless a task still has work in its worktree that has not landed: then the run stays, and so does that work.${repairedNotice(handoff)}`,
|
|
2923
2851
|
action: { kind: "merge-run" }
|
|
2924
2852
|
} : {
|
|
2925
2853
|
kind: "confirm",
|
|
2926
2854
|
title: "Merge into your branch?",
|
|
2927
|
-
message: `Merge ${handoffBranch(handoff)} into whatever you have checked out. Ordewell never does this on its own. If it conflicts the merge is aborted and your tree stays as it was. Once it has merged, the run's worktrees and branches are removed.${repairedNotice(handoff)}`,
|
|
2855
|
+
message: `Merge ${handoffBranch(handoff)} into whatever you have checked out. Ordewell never does this on its own. If it conflicts the merge is aborted and your tree stays as it was. Once it has merged, the run's worktrees and branches are removed \u2014 unless a task still has work in its worktree that has not landed: then the run stays, and so does that work.${repairedNotice(handoff)}`,
|
|
2928
2856
|
action: { kind: "merge-run" }
|
|
2929
2857
|
}
|
|
2930
2858
|
},
|
|
@@ -3107,26 +3035,17 @@ async function handleHandoff(subArgs, injectedApi, confirm = askYesNo) {
|
|
|
3107
3035
|
}
|
|
3108
3036
|
|
|
3109
3037
|
// src/commands/planner.ts
|
|
3110
|
-
var
|
|
3038
|
+
var import_core18 = require("@ordewell/core");
|
|
3111
3039
|
|
|
3112
3040
|
// src/plannerModelSwitch.ts
|
|
3113
|
-
function
|
|
3114
|
-
|
|
3115
|
-
const
|
|
3116
|
-
|
|
3117
|
-
return rememberedModelFor(settings, provider) === model ? { model, effort, source: "remembered" } : { model, effort, source: "catalog-default" };
|
|
3118
|
-
}
|
|
3119
|
-
function rememberedModelFor(settings, provider) {
|
|
3120
|
-
const map = settings.plannerModels;
|
|
3121
|
-
if (!map || typeof map !== "object") return void 0;
|
|
3122
|
-
const entry = map[provider];
|
|
3123
|
-
if (!entry || typeof entry !== "object") return void 0;
|
|
3124
|
-
const { model } = entry;
|
|
3125
|
-
return typeof model === "string" ? model : void 0;
|
|
3041
|
+
function plannerSwitchRecall(settings) {
|
|
3042
|
+
if (settings.switchRecall) return settings.switchRecall;
|
|
3043
|
+
const model = settings.orchestratorModel;
|
|
3044
|
+
return model ? { model, effort: settings.plannerThinkingEffort, source: "remembered" } : { model: "", effort: "", source: "none" };
|
|
3126
3045
|
}
|
|
3127
3046
|
|
|
3128
3047
|
// src/commands/planner.ts
|
|
3129
|
-
var
|
|
3048
|
+
var isAiProvider = (id) => Object.hasOwn(import_core18.ALL_PROVIDERS, id);
|
|
3130
3049
|
async function handlePlanner(subArgs, injectedApi) {
|
|
3131
3050
|
const api = await connect(subArgs, injectedApi);
|
|
3132
3051
|
const [wanted] = positionals(subArgs);
|
|
@@ -3135,19 +3054,19 @@ async function handlePlanner(subArgs, injectedApi) {
|
|
|
3135
3054
|
return;
|
|
3136
3055
|
}
|
|
3137
3056
|
const provider = wanted.toLowerCase();
|
|
3138
|
-
if (!
|
|
3057
|
+
if (!isAiProvider(provider)) {
|
|
3139
3058
|
fail(`Unknown planner: ${provider}`, "Run `ordewell planner` with no arguments to see the list.");
|
|
3140
3059
|
}
|
|
3141
3060
|
const settings = await persistEnv(api, { AI_PROVIDER: provider });
|
|
3142
|
-
const recall2 =
|
|
3061
|
+
const recall2 = plannerSwitchRecall(settings);
|
|
3143
3062
|
writeEnv({ ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
|
|
3144
|
-
const meta =
|
|
3063
|
+
const meta = import_core18.ALL_PROVIDERS[provider];
|
|
3145
3064
|
const catalog = await fetchCatalog(api);
|
|
3146
3065
|
console.log(
|
|
3147
|
-
(0,
|
|
3066
|
+
(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}.`
|
|
3148
3067
|
);
|
|
3149
3068
|
console.log(plannerModelLine(recall2));
|
|
3150
|
-
if (!(0,
|
|
3069
|
+
if (!(0, import_core18.isCliProvider)(provider) && !catalog.providers.includes(provider)) {
|
|
3151
3070
|
console.log(` No ${meta.apiKeyEnvVar} configured yet \u2014 set one with \`ordewell key set ${provider} <key>\`.`);
|
|
3152
3071
|
}
|
|
3153
3072
|
}
|
|
@@ -3165,15 +3084,15 @@ async function showPlanners(api) {
|
|
|
3165
3084
|
const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
|
|
3166
3085
|
const current = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
3167
3086
|
console.log("\nCoding agents (no API key \u2014 they use their own subscription)");
|
|
3168
|
-
for (const id of
|
|
3169
|
-
const runner = (0,
|
|
3087
|
+
for (const id of import_core18.CLI_PROVIDERS) {
|
|
3088
|
+
const runner = (0, import_core18.runnerForProvider)(id);
|
|
3170
3089
|
const discovered = runner ? catalog.models.some((m) => m.runners?.includes(runner)) : false;
|
|
3171
|
-
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${
|
|
3090
|
+
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${import_core18.ALL_PROVIDERS[id].label}${discovered ? "" : " (not detected)"}`);
|
|
3172
3091
|
}
|
|
3173
3092
|
console.log("\nAPI providers");
|
|
3174
|
-
for (const id of
|
|
3093
|
+
for (const id of import_core18.PROVIDER_PRIORITY) {
|
|
3175
3094
|
const configured = catalog.providers.includes(id);
|
|
3176
|
-
const meta =
|
|
3095
|
+
const meta = import_core18.ALL_PROVIDERS[id];
|
|
3177
3096
|
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${meta.label}${configured ? "" : ` (no ${meta.apiKeyEnvVar})`}`);
|
|
3178
3097
|
}
|
|
3179
3098
|
console.log(`
|
|
@@ -3190,7 +3109,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
3190
3109
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
3191
3110
|
const modelId = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
|
|
3192
3111
|
const currentEffort = typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : "";
|
|
3193
|
-
const runner = (0,
|
|
3112
|
+
const runner = isAiProvider(provider) ? (0, import_core18.runnerForProvider)(provider) : null;
|
|
3194
3113
|
if (!runner) {
|
|
3195
3114
|
fail(
|
|
3196
3115
|
"Thinking effort applies to a coding-agent planner.",
|
|
@@ -3230,7 +3149,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
3230
3149
|
}
|
|
3231
3150
|
|
|
3232
3151
|
// src/commands/parallel.ts
|
|
3233
|
-
var
|
|
3152
|
+
var import_core19 = require("@ordewell/core");
|
|
3234
3153
|
var USAGE5 = "Usage: ordewell parallel [<number of tasks, 1 or more>]";
|
|
3235
3154
|
async function handleParallel(subArgs, injectedApi) {
|
|
3236
3155
|
const [wanted] = positionals(subArgs);
|
|
@@ -3240,14 +3159,14 @@ async function handleParallel(subArgs, injectedApi) {
|
|
|
3240
3159
|
console.log(`Up to ${String(settings.maxParallel ?? "?")} AI tasks run at once. Change with: ordewell parallel <n>`);
|
|
3241
3160
|
return;
|
|
3242
3161
|
}
|
|
3243
|
-
const limit = (0,
|
|
3162
|
+
const limit = (0, import_core19.parseMaxParallel)(wanted);
|
|
3244
3163
|
if (limit === null) fail(`"${wanted}" is not a number of tasks (1 or more).`, USAGE5);
|
|
3245
3164
|
await persistEnv(api, { ORDEWELL_MAX_PARALLEL: String(limit) });
|
|
3246
3165
|
console.log(`Up to ${limit} AI task${limit === 1 ? "" : "s"} now run at once.`);
|
|
3247
3166
|
}
|
|
3248
3167
|
|
|
3249
3168
|
// src/commands/key.ts
|
|
3250
|
-
var
|
|
3169
|
+
var import_core20 = require("@ordewell/core");
|
|
3251
3170
|
var USAGE6 = "Usage: ordewell key [list | set <provider> <key>]";
|
|
3252
3171
|
async function handleKey(subArgs, injectedApi) {
|
|
3253
3172
|
const [action, provider, ...keyParts] = positionals(subArgs);
|
|
@@ -3258,8 +3177,8 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3258
3177
|
if (action !== "set") {
|
|
3259
3178
|
const catalog = await fetchCatalog(api);
|
|
3260
3179
|
console.log("\nAPI provider keys\n");
|
|
3261
|
-
for (const id2 of
|
|
3262
|
-
const meta2 =
|
|
3180
|
+
for (const id2 of import_core20.PROVIDER_PRIORITY) {
|
|
3181
|
+
const meta2 = import_core20.ALL_PROVIDERS[id2];
|
|
3263
3182
|
const configured = catalog.providers.includes(id2);
|
|
3264
3183
|
console.log(` ${configured ? "\u2713" : " "} ${id2.padEnd(14)} ${meta2.apiKeyEnvVar.padEnd(28)} ${meta2.label}`);
|
|
3265
3184
|
}
|
|
@@ -3269,7 +3188,7 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3269
3188
|
}
|
|
3270
3189
|
if (!provider) fail(USAGE6);
|
|
3271
3190
|
const id = provider.toLowerCase();
|
|
3272
|
-
const meta =
|
|
3191
|
+
const meta = import_core20.ALL_PROVIDERS[id];
|
|
3273
3192
|
if (!meta) {
|
|
3274
3193
|
fail(`Unknown provider: ${provider}`, "Run `ordewell key` to see the list.");
|
|
3275
3194
|
}
|
|
@@ -3280,7 +3199,7 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3280
3199
|
}
|
|
3281
3200
|
|
|
3282
3201
|
// src/commands/runners.ts
|
|
3283
|
-
var
|
|
3202
|
+
var import_core21 = require("@ordewell/core");
|
|
3284
3203
|
var USAGE7 = "Usage: ordewell runners [<runner-id> on|off]";
|
|
3285
3204
|
async function handleRunners(subArgs, injectedApi) {
|
|
3286
3205
|
const [runner, toggle] = positionals(subArgs);
|
|
@@ -3311,15 +3230,15 @@ function handleAuto(subArgs) {
|
|
|
3311
3230
|
const currentRaw = process.env.ORDEWELL_AUTONOMOUS_MODE;
|
|
3312
3231
|
const current = currentRaw !== "false" && currentRaw !== "0";
|
|
3313
3232
|
if (!arg) {
|
|
3314
|
-
console.log(`Autonomy level: ${(0,
|
|
3233
|
+
console.log(`Autonomy level: ${(0, import_core21.autonomyLevelLabel)(current)}`);
|
|
3315
3234
|
console.log(` ${AUTO_USAGE}`);
|
|
3316
3235
|
return;
|
|
3317
3236
|
}
|
|
3318
|
-
const enabled = (0,
|
|
3237
|
+
const enabled = (0, import_core21.parseAutonomyLevel)(arg);
|
|
3319
3238
|
if (enabled === null) fail(AUTO_USAGE);
|
|
3320
3239
|
writeEnvVar(findEnvFile(), "ORDEWELL_AUTONOMOUS_MODE", String(enabled));
|
|
3321
3240
|
process.env.ORDEWELL_AUTONOMOUS_MODE = String(enabled);
|
|
3322
|
-
console.log(`Autonomy level: ${(0,
|
|
3241
|
+
console.log(`Autonomy level: ${(0, import_core21.autonomyLevelLabel)(enabled)} for new sessions.`);
|
|
3323
3242
|
}
|
|
3324
3243
|
async function handleRefresh(subArgs, injectedApi) {
|
|
3325
3244
|
const api = await connect(subArgs, injectedApi);
|
|
@@ -3351,7 +3270,7 @@ async function handleApprove(subArgs, injectedApi) {
|
|
|
3351
3270
|
// src/tui/terminalLauncher.ts
|
|
3352
3271
|
var import_child_process3 = require("child_process");
|
|
3353
3272
|
var import_util = require("util");
|
|
3354
|
-
var
|
|
3273
|
+
var import_core22 = require("@ordewell/core");
|
|
3355
3274
|
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
3356
3275
|
var defaultExecFile = async (command, args) => {
|
|
3357
3276
|
const { stdout, stderr } = await execFileAsync(command, args);
|
|
@@ -3371,16 +3290,16 @@ var defaultWhich = (bin) => {
|
|
|
3371
3290
|
async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
|
|
3372
3291
|
const platform = deps.platform ?? process.platform;
|
|
3373
3292
|
const env = deps.env ?? process.env;
|
|
3374
|
-
const hasTmuxImpl = deps.hasTmuxImpl ??
|
|
3293
|
+
const hasTmuxImpl = deps.hasTmuxImpl ?? import_core22.hasTmux;
|
|
3375
3294
|
const execFileImpl = deps.execFileImpl ?? defaultExecFile;
|
|
3376
3295
|
const spawnImpl = deps.spawnImpl ?? defaultSpawn;
|
|
3377
3296
|
const which = deps.which ?? defaultWhich;
|
|
3378
3297
|
if (!hasTmuxImpl()) {
|
|
3379
3298
|
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." };
|
|
3380
3299
|
}
|
|
3381
|
-
const session = (0,
|
|
3382
|
-
const socket = (0,
|
|
3383
|
-
const base = (0,
|
|
3300
|
+
const session = (0, import_core22.tmuxSessionName)(port);
|
|
3301
|
+
const socket = (0, import_core22.tmuxSocketName)(port);
|
|
3302
|
+
const base = (0, import_core22.tmuxWindowName)(taskId, planSessionId);
|
|
3384
3303
|
let windows;
|
|
3385
3304
|
try {
|
|
3386
3305
|
const { stdout } = await execFileImpl("tmux", ["-L", socket, "list-windows", "-t", session, "-F", "#{window_name}"]);
|
|
@@ -3529,7 +3448,7 @@ async function handleTaskRunner(subArgs, injectedApi) {
|
|
|
3529
3448
|
const state = await api.getRunners();
|
|
3530
3449
|
if (!runner) {
|
|
3531
3450
|
console.log(`
|
|
3532
|
-
Runner \xB7
|
|
3451
|
+
Runner \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}
|
|
3533
3452
|
`);
|
|
3534
3453
|
for (const r of state.runners) {
|
|
3535
3454
|
console.log(` ${r.id === task.assignedRunner ? "*" : " "} ${r.id.padEnd(14)} ${r.name}${r.enabled ? "" : " (not enabled for planning)"}`);
|
|
@@ -3542,7 +3461,7 @@ Runner \xB7 #${task.order} ${task.title}
|
|
|
3542
3461
|
fail(`Unknown runner: ${runner}`, `This daemon knows: ${state.runners.map((r) => r.id).join(", ")}`);
|
|
3543
3462
|
}
|
|
3544
3463
|
await api.updateTask(sessionId, task.id, { assignedRunner: runner });
|
|
3545
|
-
console.log(`Task
|
|
3464
|
+
console.log(`Task ${(0, import_plan_utils.taskRef)(task)} runner set to ${runner} \u2014 its model, effort and mode were re-picked for it.`);
|
|
3546
3465
|
});
|
|
3547
3466
|
}
|
|
3548
3467
|
var MODEL_USAGE = "Usage: ordewell task-model <task-id-or-order> [<model-id>] [--session-id <id>]";
|
|
@@ -3552,7 +3471,7 @@ async function handleTaskModel(subArgs, injectedApi) {
|
|
|
3552
3471
|
const options = modelsForTask(catalog.models, task);
|
|
3553
3472
|
if (!modelId) {
|
|
3554
3473
|
console.log(`
|
|
3555
|
-
Model \xB7
|
|
3474
|
+
Model \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}${task.assignedRunner ? ` (${task.assignedRunner})` : ""}
|
|
3556
3475
|
`);
|
|
3557
3476
|
if (options.length === 0) console.log(" No models discovered for this runner \u2014 try `ordewell refresh`.");
|
|
3558
3477
|
for (const m of options) {
|
|
@@ -3575,7 +3494,7 @@ Model \xB7 #${task.order} ${task.title}${task.assignedRunner ? ` (${task.assigne
|
|
|
3575
3494
|
assignedModel,
|
|
3576
3495
|
thinkingEffort: assignedModel.thinkingEffort ?? null
|
|
3577
3496
|
});
|
|
3578
|
-
console.log(`Task
|
|
3497
|
+
console.log(`Task ${(0, import_plan_utils.taskRef)(task)} model set to ${model.label}.`);
|
|
3579
3498
|
if (task.assignedModel?.thinkingEffort && !assignedModel.thinkingEffort) {
|
|
3580
3499
|
console.log(` ${model.label} does not expose "${task.assignedModel.thinkingEffort}", so the effort went back to the runner default.`);
|
|
3581
3500
|
}
|
|
@@ -3589,7 +3508,7 @@ async function handleTaskEffort(subArgs, injectedApi) {
|
|
|
3589
3508
|
const current = task.assignedModel?.thinkingEffort;
|
|
3590
3509
|
if (!level) {
|
|
3591
3510
|
console.log(`
|
|
3592
|
-
Thinking effort \xB7
|
|
3511
|
+
Thinking effort \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}
|
|
3593
3512
|
`);
|
|
3594
3513
|
console.log(` ${current ? " " : "*"} default Let the executor choose`);
|
|
3595
3514
|
for (const v of variants) {
|
|
@@ -3614,7 +3533,7 @@ Thinking effort \xB7 #${task.order} ${task.title}
|
|
|
3614
3533
|
assignedModel: task.assignedModel ? { ...task.assignedModel, thinkingEffort } : void 0,
|
|
3615
3534
|
thinkingEffort: thinkingEffort ?? null
|
|
3616
3535
|
});
|
|
3617
|
-
console.log(`Task
|
|
3536
|
+
console.log(`Task ${(0, import_plan_utils.taskRef)(task)} thinking effort set to ${thinkingEffort ?? "runner default"}.`);
|
|
3618
3537
|
});
|
|
3619
3538
|
}
|
|
3620
3539
|
var MODE_USAGE = "Usage: ordewell task-mode <task-id-or-order> [<mode>] [--session-id <id>]";
|
|
@@ -3624,7 +3543,7 @@ async function handleTaskMode(subArgs, injectedApi) {
|
|
|
3624
3543
|
const modes = modesForTask(catalog.modesByRunner, task);
|
|
3625
3544
|
if (!mode) {
|
|
3626
3545
|
console.log(`
|
|
3627
|
-
Mode \xB7
|
|
3546
|
+
Mode \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}${task.assignedRunner ? ` (${task.assignedRunner})` : ""}
|
|
3628
3547
|
`);
|
|
3629
3548
|
if (modes.length === 0) {
|
|
3630
3549
|
console.log(" This task has no runner, or its runner declares no modes.");
|
|
@@ -3643,7 +3562,7 @@ Mode \xB7 #${task.order} ${task.title}${task.assignedRunner ? ` (${task.assigned
|
|
|
3643
3562
|
);
|
|
3644
3563
|
}
|
|
3645
3564
|
await api.updateTask(sessionId, task.id, { taskMode: chosen.id });
|
|
3646
|
-
console.log(`Task
|
|
3565
|
+
console.log(`Task ${(0, import_plan_utils.taskRef)(task)} mode set to ${chosen.label}.`);
|
|
3647
3566
|
});
|
|
3648
3567
|
}
|
|
3649
3568
|
var DEPS_USAGE = "Usage: ordewell task-deps <task-id-or-order> [<id,id,\u2026>|none] [--session-id <id>]";
|
|
@@ -3652,14 +3571,14 @@ async function handleTaskDeps(subArgs, injectedApi) {
|
|
|
3652
3571
|
const candidates = (0, import_plan_utils.dependencyCandidates)(tasks, task.id);
|
|
3653
3572
|
if (!value) {
|
|
3654
3573
|
console.log(`
|
|
3655
|
-
Dependencies \xB7
|
|
3574
|
+
Dependencies \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}
|
|
3656
3575
|
`);
|
|
3657
3576
|
if (candidates.length === 0) {
|
|
3658
|
-
console.log(` Nothing runs before
|
|
3577
|
+
console.log(` Nothing runs before ${(0, import_plan_utils.taskRef)(task)}, so it has no possible dependencies.`);
|
|
3659
3578
|
}
|
|
3660
3579
|
for (const c of candidates) {
|
|
3661
3580
|
const chosen = task.dependencies.includes(c.id);
|
|
3662
|
-
console.log(` ${chosen ? "*" : " "} ${c.id}
|
|
3581
|
+
console.log(` ${chosen ? "*" : " "} ${c.id} ${(0, import_plan_utils.titledTaskRef)(c)}${c.status === "completed" ? " (already completed)" : ""}`);
|
|
3663
3582
|
}
|
|
3664
3583
|
console.log(`
|
|
3665
3584
|
* = current. ${DEPS_USAGE}`);
|
|
@@ -3669,7 +3588,7 @@ Dependencies \xB7 #${task.order} ${task.title}
|
|
|
3669
3588
|
const check = (0, import_plan_utils.canSetDependencies)(tasks, task.id, dependencies);
|
|
3670
3589
|
if (!check.ok) fail(check.error ?? "Invalid dependencies.");
|
|
3671
3590
|
await api.updateTask(sessionId, task.id, { dependencies });
|
|
3672
|
-
console.log(dependencies.length > 0 ? `Task
|
|
3591
|
+
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.`);
|
|
3673
3592
|
});
|
|
3674
3593
|
}
|
|
3675
3594
|
var OPS_USAGE = "Usage: ordewell task-ops <task-id-or-order> [on|off] [--session-id <id>]";
|
|
@@ -3677,7 +3596,7 @@ async function handleTaskOps(subArgs, injectedApi) {
|
|
|
3677
3596
|
await withTask(subArgs, OPS_USAGE, injectedApi, async (api, sessionId, task, tasks, value) => {
|
|
3678
3597
|
if (!value) {
|
|
3679
3598
|
console.log(`
|
|
3680
|
-
|
|
3599
|
+
${(0, import_plan_utils.titledTaskRef)(task)} is ${task.ops ? "an ops task: it runs in your checkout once the work it depends on is merged" : "a change task: it runs in its own worktree"}.`);
|
|
3681
3600
|
console.log(`
|
|
3682
3601
|
${OPS_USAGE}`);
|
|
3683
3602
|
return;
|
|
@@ -3687,12 +3606,12 @@ async function handleTaskOps(subArgs, injectedApi) {
|
|
|
3687
3606
|
if (task.type !== "ai") fail("Only an AI task can be an ops task \u2014 a manual task already runs outside any worktree.");
|
|
3688
3607
|
if (!tasks.some((t) => t.id === task.id)) fail("A subtask runs with its parent; make the parent an ops task instead.");
|
|
3689
3608
|
await api.updateTask(sessionId, task.id, { ops: to === "on" });
|
|
3690
|
-
console.log(to === "on" ? `Task
|
|
3609
|
+
console.log(to === "on" ? `Task ${(0, import_plan_utils.taskRef)(task)} is an ops task: it runs in your checkout once the work it depends on is merged.` : `Task ${(0, import_plan_utils.taskRef)(task)} is a change task: it runs in its own worktree.`);
|
|
3691
3610
|
});
|
|
3692
3611
|
}
|
|
3693
3612
|
|
|
3694
3613
|
// src/tui/index.ts
|
|
3695
|
-
var
|
|
3614
|
+
var import_core37 = require("@ordewell/core");
|
|
3696
3615
|
|
|
3697
3616
|
// src/tui/editor.ts
|
|
3698
3617
|
function emptyEditor() {
|
|
@@ -3841,6 +3760,7 @@ var SLASH_COMMANDS = [
|
|
|
3841
3760
|
{ name: "cancel", usage: "/cancel <id>", description: "Kill a running task", category: "tasks" },
|
|
3842
3761
|
{ name: "force-start", usage: "/force-start <id>", description: "Start a task now, ignoring dependencies", category: "tasks" },
|
|
3843
3762
|
{ name: "continue", usage: "/continue <id> <message>", description: "Continue a finished task in its saved runner session, with your message", category: "tasks" },
|
|
3763
|
+
{ 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" },
|
|
3844
3764
|
{ name: "terminal", usage: "/terminal <id>", description: "Open a task: its OS terminal, or a structured task's live log", category: "tasks" },
|
|
3845
3765
|
{ name: "task-runner", usage: "/task-runner <id> [runner]", description: "Choose or set a task's executor (re-picks its model and mode)", category: "tasks" },
|
|
3846
3766
|
{ name: "task-model", usage: "/task-model <id> [model]", description: "Choose or set a task's executor model", category: "tasks" },
|
|
@@ -3941,7 +3861,7 @@ function tokenCompletions(token) {
|
|
|
3941
3861
|
}
|
|
3942
3862
|
|
|
3943
3863
|
// src/tui/blocks.ts
|
|
3944
|
-
var
|
|
3864
|
+
var import_core23 = require("@ordewell/core");
|
|
3945
3865
|
|
|
3946
3866
|
// src/tui/markdown.ts
|
|
3947
3867
|
function renderMarkdown(source, cols) {
|
|
@@ -4161,16 +4081,16 @@ function resultLines(block, room, detailAll) {
|
|
|
4161
4081
|
const fit2 = (line) => detailAll ? wrap(line, room) : [truncate(line, room)];
|
|
4162
4082
|
const outcome = unsettledOutcome(block);
|
|
4163
4083
|
if (outcome) {
|
|
4164
|
-
const [reason, ...rest] = (0,
|
|
4084
|
+
const [reason, ...rest] = (0, import_core23.outputLines)(block.output).map(sanitize);
|
|
4165
4085
|
const status = fit2(reason ? `${outcome.label}: ${reason}` : outcome.label).map(outcome.paint);
|
|
4166
4086
|
return detailAll ? [...status, ...rest.flatMap(fit2).map(style.grey)] : status;
|
|
4167
4087
|
}
|
|
4168
4088
|
if (detailAll) {
|
|
4169
|
-
const lines2 = (0,
|
|
4089
|
+
const lines2 = (0, import_core23.outputLines)(block.output).map(sanitize);
|
|
4170
4090
|
const note = lines2.length > PREVIEW_LINES ? ["\u2026 (ctrl+o to collapse)"] : [];
|
|
4171
4091
|
return lines2.length > 0 ? [...lines2.flatMap(fit2), ...note].map(style.grey) : [style.grey("(no output)")];
|
|
4172
4092
|
}
|
|
4173
|
-
const { lines, hiddenLineCount } = (0,
|
|
4093
|
+
const { lines, hiddenLineCount } = (0, import_core23.outputPreview)(block.output, PREVIEW_LINES);
|
|
4174
4094
|
if (lines.length === 0) return [style.grey("(no output)")];
|
|
4175
4095
|
const more = hiddenLineCount > 0 ? [`\u2026 +${countOf(hiddenLineCount, "line")} (ctrl+o to expand)`] : [];
|
|
4176
4096
|
return [...lines.map(sanitize), ...more].map((line) => style.grey(truncate(line, room)));
|
|
@@ -4184,7 +4104,7 @@ var DIFF_PAINT = {
|
|
|
4184
4104
|
};
|
|
4185
4105
|
var DIFF_SIGN = { added: "+", removed: "-", context: " ", gap: "" };
|
|
4186
4106
|
function diffLines(block, diff, room, detailAll) {
|
|
4187
|
-
const rows = (0,
|
|
4107
|
+
const rows = (0, import_core23.diffRows)(block.output);
|
|
4188
4108
|
const gutter = Math.max(0, ...rows.map((row) => String(row.line ?? "").length));
|
|
4189
4109
|
const draw = (row) => {
|
|
4190
4110
|
const text = row.kind === "gap" ? `${" ".repeat(gutter)} \u22EE` : `${String(row.line ?? "").padStart(gutter)} ${DIFF_SIGN[row.kind]} ${sanitize(row.text)}`;
|
|
@@ -4193,7 +4113,7 @@ function diffLines(block, diff, room, detailAll) {
|
|
|
4193
4113
|
const shown = detailAll ? rows : rows.slice(0, DIFF_PREVIEW_ROWS);
|
|
4194
4114
|
const hidden = rows.length - shown.length;
|
|
4195
4115
|
const more = hidden > 0 ? [style.grey(`\u2026 +${countOf(hidden, "line")} (ctrl+o to expand)`)] : [];
|
|
4196
|
-
return [(0,
|
|
4116
|
+
return [(0, import_core23.diffSummary)(diff), ...shown.flatMap(draw), ...more];
|
|
4197
4117
|
}
|
|
4198
4118
|
function toolLines(block, cols, detailAll) {
|
|
4199
4119
|
const room = Math.max(1, cols - OUTPUT_INDENT.length);
|
|
@@ -4249,6 +4169,19 @@ function approvalLine(block, cols) {
|
|
|
4249
4169
|
const line = `${mark} ${status} \xB7 ${APPROVAL_KIND[block.kind]}: ${firstLine(block.subject)}${note}`;
|
|
4250
4170
|
return `${paint(mark)}${truncate(line, cols).slice(mark.length)}`;
|
|
4251
4171
|
}
|
|
4172
|
+
function approvalKeysLine(block, cols) {
|
|
4173
|
+
const keys = ["ctrl-y allow", ...block.allowForTask ? ["ctrl-t allow for task"] : [], "ctrl-g deny (composer text is the note)"];
|
|
4174
|
+
return style.grey(truncate(` ${keys.join(" \xB7 ")}`, cols));
|
|
4175
|
+
}
|
|
4176
|
+
function checkpointCardLines(order, question, cols, approvalFirst) {
|
|
4177
|
+
const room = Math.max(1, cols - 2);
|
|
4178
|
+
const keys = approvalFirst ? "answer the tool request above first (ctrl-y / ctrl-g), then ctrl-y approves \xB7 ctrl-g rejects (composer text is the reason)" : "ctrl-y approve \xB7 ctrl-g reject (composer text is the reason)";
|
|
4179
|
+
return [
|
|
4180
|
+
truncate(`${style.accent("\u25C6")} ${style.bold(`Task ${order} asks`)}`, cols),
|
|
4181
|
+
...wrap(sanitize(question).trim(), room).map((line) => ` ${line}`),
|
|
4182
|
+
...wrap(keys, room).map((line) => style.grey(` ${line}`))
|
|
4183
|
+
];
|
|
4184
|
+
}
|
|
4252
4185
|
function planLine(block, cols) {
|
|
4253
4186
|
const count = block.taskCount === void 0 ? "" : ` (${countOf(block.taskCount, "task")})`;
|
|
4254
4187
|
const text = block.status === "building" ? "Building plan\u2026" : `Plan ${block.status}${count}`;
|
|
@@ -4281,11 +4214,13 @@ function drawn(block, cols, detailAll) {
|
|
|
4281
4214
|
drawnBlocks.set(block, { cols, detailAll, lines });
|
|
4282
4215
|
return lines;
|
|
4283
4216
|
}
|
|
4284
|
-
function conversationLines(blocks, cols, detailAll) {
|
|
4217
|
+
function conversationLines(blocks, cols, detailAll, answering) {
|
|
4285
4218
|
const lines = [];
|
|
4286
4219
|
for (const block of blocks) {
|
|
4287
4220
|
if (block.type === "usage") continue;
|
|
4288
|
-
lines.push(...drawn(block, cols, detailAll)
|
|
4221
|
+
lines.push(...drawn(block, cols, detailAll));
|
|
4222
|
+
if (block === answering) lines.push(approvalKeysLine(block, cols));
|
|
4223
|
+
lines.push("");
|
|
4289
4224
|
}
|
|
4290
4225
|
return lines;
|
|
4291
4226
|
}
|
|
@@ -4313,7 +4248,7 @@ function tokenLine(blocks, cols) {
|
|
|
4313
4248
|
}
|
|
4314
4249
|
|
|
4315
4250
|
// src/tui/state.ts
|
|
4316
|
-
var
|
|
4251
|
+
var import_core24 = require("@ordewell/core");
|
|
4317
4252
|
var SKILL_IDS = ["tdd", "verify"];
|
|
4318
4253
|
function noSkills() {
|
|
4319
4254
|
return { tdd: false, verify: false };
|
|
@@ -4325,8 +4260,7 @@ function visibleItems(picker2) {
|
|
|
4325
4260
|
(i) => i.id.toLowerCase().includes(filter) || i.label.toLowerCase().includes(filter) || (i.detail ?? "").toLowerCase().includes(filter)
|
|
4326
4261
|
);
|
|
4327
4262
|
}
|
|
4328
|
-
var
|
|
4329
|
-
var anyTaskRunning = (state) => state.tasks.some(isTaskRunning);
|
|
4263
|
+
var anyTaskRunning = (state) => state.tasks.some(import_core24.isTaskRunning);
|
|
4330
4264
|
var plannerInFlight = (state) => state.status === "planning" || state.status === "researching";
|
|
4331
4265
|
function planRows(state) {
|
|
4332
4266
|
const rows = [];
|
|
@@ -4346,6 +4280,9 @@ function selectedPlanRow(state) {
|
|
|
4346
4280
|
function waitingApproval(tv) {
|
|
4347
4281
|
return tv.view.blocks.find((b) => b.type === "approval" && b.status === "pending" && b.approvalId !== void 0);
|
|
4348
4282
|
}
|
|
4283
|
+
function waitingCheckpoint(task) {
|
|
4284
|
+
return task?.status === "awaiting_user" && task.awaitingReason === "checkpoint" ? task.checkpoint : void 0;
|
|
4285
|
+
}
|
|
4349
4286
|
function continuesTask(task) {
|
|
4350
4287
|
return task?.continuable === true && (task.status === "completed" || task.status === "failed");
|
|
4351
4288
|
}
|
|
@@ -4360,9 +4297,9 @@ function findTask(tasks, id) {
|
|
|
4360
4297
|
function initialState(overrides = {}) {
|
|
4361
4298
|
return {
|
|
4362
4299
|
editor: emptyEditor(),
|
|
4363
|
-
conversation:
|
|
4300
|
+
conversation: import_core24.EMPTY_CONVERSATION,
|
|
4364
4301
|
taskView: null,
|
|
4365
|
-
turnGate:
|
|
4302
|
+
turnGate: import_core24.NO_TURN,
|
|
4366
4303
|
detailAll: false,
|
|
4367
4304
|
status: "idle",
|
|
4368
4305
|
busyLabel: "",
|
|
@@ -4387,7 +4324,7 @@ function initialState(overrides = {}) {
|
|
|
4387
4324
|
orchestratorModel: "",
|
|
4388
4325
|
plannerProvider: "",
|
|
4389
4326
|
plannerEffort: "",
|
|
4390
|
-
maxParallel:
|
|
4327
|
+
maxParallel: import_core24.DEFAULT_MAX_PARALLEL,
|
|
4391
4328
|
configuredProviders: [],
|
|
4392
4329
|
allowlist: {},
|
|
4393
4330
|
autonomous: true,
|
|
@@ -4399,7 +4336,7 @@ function initialState(overrides = {}) {
|
|
|
4399
4336
|
gate: null,
|
|
4400
4337
|
overlay: null,
|
|
4401
4338
|
pendingApprovals: [],
|
|
4402
|
-
queuedPrompts:
|
|
4339
|
+
queuedPrompts: import_core24.EMPTY_HOLD,
|
|
4403
4340
|
stopArmed: false,
|
|
4404
4341
|
stopArmToken: 0,
|
|
4405
4342
|
stopRequested: false,
|
|
@@ -4447,7 +4384,7 @@ function chatEditorRoomFor(state, active) {
|
|
|
4447
4384
|
}
|
|
4448
4385
|
|
|
4449
4386
|
// src/tui/layout.ts
|
|
4450
|
-
var
|
|
4387
|
+
var import_core25 = require("@ordewell/core");
|
|
4451
4388
|
var CHROME_ROWS = 4;
|
|
4452
4389
|
function chatInputWrap(state) {
|
|
4453
4390
|
const active = state.focus === "chat" && !state.overlay;
|
|
@@ -4456,7 +4393,7 @@ function chatInputWrap(state) {
|
|
|
4456
4393
|
}
|
|
4457
4394
|
function footerHints(state) {
|
|
4458
4395
|
const selected = selectedPlanRow(state)?.task;
|
|
4459
|
-
const markHint = selected
|
|
4396
|
+
const markHint = selected && (0, import_core25.markAction)(selected) === "uncomplete" ? "m undone" : "m done";
|
|
4460
4397
|
const resolveHint = selected?.isolation?.state === "conflict" ? ["x resolve conflict"] : [];
|
|
4461
4398
|
const planning = plannerInFlight(state) && !state.taskView;
|
|
4462
4399
|
const escHint = !planning ? null : state.queuedPrompts.length > 0 ? "esc unsend" : "esc \xD72 stop planning";
|
|
@@ -4487,7 +4424,7 @@ function footerHints(state) {
|
|
|
4487
4424
|
"tab chat"
|
|
4488
4425
|
];
|
|
4489
4426
|
}
|
|
4490
|
-
const detailHint = (0,
|
|
4427
|
+
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"] : [];
|
|
4491
4428
|
return [
|
|
4492
4429
|
"/help",
|
|
4493
4430
|
"tab plan",
|
|
@@ -4508,20 +4445,25 @@ function queuedBubble(text, cols) {
|
|
|
4508
4445
|
function taskQueuedRows(state, cols) {
|
|
4509
4446
|
const tv = state.taskView;
|
|
4510
4447
|
if (!tv) return [];
|
|
4511
|
-
return tv.view.queued.flatMap((message, i) => queuedTaskBubble(message
|
|
4448
|
+
return tv.view.queued.flatMap((message, i) => queuedTaskBubble(message, i === tv.queuedIndex, cols));
|
|
4449
|
+
}
|
|
4450
|
+
function queuedTaskTag({ handedOver, forced }, selected) {
|
|
4451
|
+
if (forced) return " \xB7 sending now";
|
|
4452
|
+
if (handedOver) return " \xB7 handed over";
|
|
4453
|
+
return selected ? " \xB7 queued \xB7 ctrl-r removes" : " \xB7 queued";
|
|
4512
4454
|
}
|
|
4513
|
-
function queuedTaskBubble(
|
|
4455
|
+
function queuedTaskBubble(message, selected, cols) {
|
|
4456
|
+
const { text } = message;
|
|
4514
4457
|
const marker2 = selected ? style.accent("\u276F") : style.grey("\u25C7");
|
|
4515
|
-
const tag = selected
|
|
4458
|
+
const tag = queuedTaskTag(message, selected);
|
|
4516
4459
|
const room = Math.max(1, cols - width(`\u276F \u25C7 ${tag}`));
|
|
4517
4460
|
return wrap(text, room).map((line, i) => i === 0 ? truncate(`${marker2} ${selected ? style.bold(line) : line}${style.grey(tag)}`, cols) : truncate(` ${line}`, cols));
|
|
4518
4461
|
}
|
|
4519
4462
|
function taskActivity(task, working) {
|
|
4520
|
-
|
|
4463
|
+
const approvals = (0, import_core25.approvalLabel)(task?.awaitingApproval);
|
|
4464
|
+
if (approvals) return inRow(approvals);
|
|
4521
4465
|
if (working) return "working";
|
|
4522
|
-
if (task?.status === "awaiting_user")
|
|
4523
|
-
return task.awaitingReason ? AWAITING_LABEL[task.awaitingReason] : "waiting for your input";
|
|
4524
|
-
}
|
|
4466
|
+
if (task?.status === "awaiting_user") return inRow((0, import_core25.awaitingLabel)(task) ?? "Waiting for your input");
|
|
4525
4467
|
return task?.status ?? "";
|
|
4526
4468
|
}
|
|
4527
4469
|
function taskHeaderLines(state, tv, cols) {
|
|
@@ -4533,7 +4475,8 @@ function taskHeaderLines(state, tv, cols) {
|
|
|
4533
4475
|
taskActivity(task, tv.view.working)
|
|
4534
4476
|
].filter(Boolean);
|
|
4535
4477
|
const approval = waitingApproval(tv);
|
|
4536
|
-
const hint = approval ? ["ctrl-y allow", ...approval.allowForTask ? ["ctrl-t allow for task"] : [], "ctrl-g deny (composer text is the note)"] : ["ctrl-r remove queued", "ctrl-x interrupt"];
|
|
4478
|
+
const hint = approval ? ["ctrl-y allow", ...approval.allowForTask ? ["ctrl-t allow for task"] : [], "ctrl-g deny (composer text is the note)"] : waitingCheckpoint(task) ? ["ctrl-y approve", "ctrl-g reject (composer text is the reason)", "ctrl-x interrupt"] : ["ctrl-r remove queued", "ctrl-x interrupt", "ctrl-s send now"];
|
|
4479
|
+
if (approval && waitingCheckpoint(task)) hint.push("then the checkpoint");
|
|
4537
4480
|
const index = tv.attempts.indexOf(tv.attempt);
|
|
4538
4481
|
if (tv.attempts.length > 1) hint.push(`alt\u2190/\u2192 attempt ${index >= 0 ? index + 1 : 1}/${tv.attempts.length}`);
|
|
4539
4482
|
hint.push("esc back");
|
|
@@ -4566,10 +4509,10 @@ function bodyRows(state) {
|
|
|
4566
4509
|
return Math.max(0, state.rows - CHROME_ROWS - input - footer + 2);
|
|
4567
4510
|
}
|
|
4568
4511
|
var chatBodyMemo = null;
|
|
4569
|
-
function chatBodyLines(blocks, cols, detailAll) {
|
|
4512
|
+
function chatBodyLines(blocks, cols, detailAll, answering) {
|
|
4570
4513
|
const memo = chatBodyMemo;
|
|
4571
4514
|
if (memo && memo.blocks === blocks && memo.cols === cols && memo.detailAll === detailAll) return memo.lines;
|
|
4572
|
-
const lines = conversationLines(blocks, cols, detailAll);
|
|
4515
|
+
const lines = conversationLines(blocks, cols, detailAll, answering);
|
|
4573
4516
|
chatBodyMemo = { blocks, cols, detailAll, lines };
|
|
4574
4517
|
return lines;
|
|
4575
4518
|
}
|
|
@@ -4577,7 +4520,10 @@ function chatLayout(state, rows, cols) {
|
|
|
4577
4520
|
const tv = state.taskView;
|
|
4578
4521
|
const blocks = tv ? tv.view.blocks : state.conversation.blocks;
|
|
4579
4522
|
const welcome = !tv && state.tasks.length === 0;
|
|
4580
|
-
const
|
|
4523
|
+
const checkpoint = tv ? waitingCheckpoint(findTask(state.tasks, tv.taskId)) : void 0;
|
|
4524
|
+
const card = tv && checkpoint ? checkpointCardLines(findTask(state.tasks, tv.taskId)?.order ?? 0, checkpoint, cols, waitingApproval(tv) !== void 0) : [];
|
|
4525
|
+
const answering = chatBodyLines(blocks, cols, state.detailAll, tv ? waitingApproval(tv) : void 0);
|
|
4526
|
+
const body = card.length > 0 ? [...answering, ...card] : answering;
|
|
4581
4527
|
const transcript = !welcome ? body : body.length === 0 ? welcomeLines(state, cols) : [...welcomeLines(state, cols), "", ...body];
|
|
4582
4528
|
const bubbles = tv ? taskQueuedRows(state, cols) : queuedPromptRows(state, cols);
|
|
4583
4529
|
const lines = [...transcript, ...bubbles.length > 0 ? ["", ...bubbles] : []];
|
|
@@ -4592,9 +4538,9 @@ function chatScrollMax(state) {
|
|
|
4592
4538
|
}
|
|
4593
4539
|
function plannerSetup(state) {
|
|
4594
4540
|
const provider = state.plannerProvider;
|
|
4595
|
-
const registration =
|
|
4541
|
+
const registration = import_core25.ALL_PROVIDERS[provider];
|
|
4596
4542
|
if (!registration) return { label: "not set", ready: false };
|
|
4597
|
-
const runner = (0,
|
|
4543
|
+
const runner = (0, import_core25.runnerForProvider)(provider);
|
|
4598
4544
|
const ready = runner ? state.runners.some((r) => r.id === runner) : state.configuredProviders.includes(provider);
|
|
4599
4545
|
return { label: registration.label, ready };
|
|
4600
4546
|
}
|
|
@@ -4681,45 +4627,34 @@ function welcomeLines(state, cols) {
|
|
|
4681
4627
|
return lines;
|
|
4682
4628
|
}
|
|
4683
4629
|
var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4684
|
-
var
|
|
4685
|
-
|
|
4630
|
+
var STATUS_PAINT = {
|
|
4631
|
+
done: style.green,
|
|
4686
4632
|
running: style.yellow,
|
|
4687
|
-
|
|
4633
|
+
quiet: style.cyan,
|
|
4688
4634
|
failed: style.red,
|
|
4689
4635
|
blocked: style.red,
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
skipped: style.grey
|
|
4636
|
+
awaiting: style.yellow,
|
|
4637
|
+
todo: (text) => text
|
|
4693
4638
|
};
|
|
4694
4639
|
var STATUS_ICON = {
|
|
4695
|
-
|
|
4640
|
+
done: "\u2713",
|
|
4696
4641
|
failed: "\u2717",
|
|
4697
4642
|
blocked: "!",
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
approved: "\xB7",
|
|
4702
|
-
pending: "\xB7"
|
|
4703
|
-
};
|
|
4704
|
-
var AWAITING_LABEL = {
|
|
4705
|
-
input: "waiting for your input",
|
|
4706
|
-
checkpoint: "checkpoint",
|
|
4707
|
-
conflict: "merge conflict",
|
|
4708
|
-
"files-changed": "changed tracked files"
|
|
4643
|
+
awaiting: "?",
|
|
4644
|
+
quiet: "~",
|
|
4645
|
+
todo: "\xB7"
|
|
4709
4646
|
};
|
|
4710
|
-
|
|
4711
|
-
return count > 1 ? `waiting for approval (${count})` : "waiting for approval";
|
|
4712
|
-
}
|
|
4713
|
-
var IDLE_ICON = "~";
|
|
4647
|
+
var inRow = (label) => label.charAt(0).toLowerCase() + label.slice(1);
|
|
4714
4648
|
var FIRST_ROW_LINE = 2;
|
|
4715
4649
|
function planLayout(state, rows, cols) {
|
|
4716
4650
|
const done = state.tasks.filter((t) => t.status === "completed").length;
|
|
4717
4651
|
const lines = [style.bold(`Plan ${done}/${state.tasks.length}`), ""];
|
|
4718
4652
|
const rowSpans = [];
|
|
4653
|
+
const orderOf = new Map(state.tasks.map((task) => [task.id, task.order]));
|
|
4719
4654
|
let editorLine;
|
|
4720
4655
|
planRows(state).forEach((row, i) => {
|
|
4721
4656
|
const taskStart = lines.length;
|
|
4722
|
-
const renderedTask = taskLines(state, row, i, cols);
|
|
4657
|
+
const renderedTask = taskLines(state, row, i, cols, orderOf);
|
|
4723
4658
|
if (row.task.id === state.expandedTaskId && renderedTask.editorLine !== void 0) {
|
|
4724
4659
|
editorLine = taskStart + renderedTask.editorLine;
|
|
4725
4660
|
}
|
|
@@ -4775,18 +4710,16 @@ function planScrollExtent(state) {
|
|
|
4775
4710
|
}
|
|
4776
4711
|
return planLayout(state, bodyRows(state), cols);
|
|
4777
4712
|
}
|
|
4778
|
-
function taskLines(state,
|
|
4779
|
-
const { task, parent } =
|
|
4713
|
+
function taskLines(state, planRow, index, cols, orderOf) {
|
|
4714
|
+
const { task, parent } = planRow;
|
|
4715
|
+
const row = (0, import_core25.taskRowView)(task, { parent, orderOf, modes: modesForTask(state.modesByRunner, task) });
|
|
4780
4716
|
const selected = state.focus === "plan" && index === state.selectedTask;
|
|
4781
4717
|
const expanded = state.expandedTaskId === task.id;
|
|
4782
|
-
const
|
|
4783
|
-
const
|
|
4784
|
-
const
|
|
4785
|
-
const rawIcon = idle ? IDLE_ICON : running ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[task.status] ?? "\xB7";
|
|
4786
|
-
const icon = paint(rawIcon);
|
|
4787
|
-
const kind = running ? style.yellow("RUN") : task.type === "user" ? style.yellow("MAN") : task.ops ? style.magenta("OPS") : style.grey(" AI");
|
|
4718
|
+
const rawIcon = row.status === "running" ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[row.status];
|
|
4719
|
+
const icon = STATUS_PAINT[row.status](rawIcon);
|
|
4720
|
+
const kind = row.running ? style.yellow("RUN") : row.kind === "user" ? style.yellow("MAN") : row.kind === "ops" ? style.magenta("OPS") : style.grey(" AI");
|
|
4788
4721
|
const caret = selected ? style.cyan("\u276F") : " ";
|
|
4789
|
-
const orderLabel = parent ?
|
|
4722
|
+
const orderLabel = parent ? row.orderLabel : row.orderLabel.padStart(2);
|
|
4790
4723
|
const indent = parent ? " " : "";
|
|
4791
4724
|
const head = `${indent}${caret} ${icon} ${orderLabel} ${kind} `;
|
|
4792
4725
|
const titleRoom = Math.max(1, cols - width(head));
|
|
@@ -4803,58 +4736,51 @@ function taskLines(state, row, index, cols) {
|
|
|
4803
4736
|
});
|
|
4804
4737
|
const model = task.assignedModel?.modelLabel ?? (task.type === "ai" ? "default model" : "");
|
|
4805
4738
|
const effort = task.type === "ai" ? `effort: ${task.assignedModel?.thinkingEffort ?? "default"}` : "";
|
|
4806
|
-
const
|
|
4807
|
-
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${modeInfo?.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
4739
|
+
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${row.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
4808
4740
|
const runner = task.assignedRunner ?? "";
|
|
4809
4741
|
const bodyPad = parent ? " " : " ";
|
|
4810
4742
|
const structured = task.transport?.kind === "structured";
|
|
4811
|
-
const
|
|
4812
|
-
const activity = task.awaitingApproval ? `${approvalLabel(task.awaitingApproval)} \u2014 t opens it` : idle ? structured ? "quiet" : "quiet \u2014 t opens its terminal" : running ? "working" : waiting;
|
|
4743
|
+
const activity = row.approvals ? `${inRow(row.approvals)} \u2014 t opens it` : row.status === "quiet" ? structured ? "quiet" : "quiet \u2014 t opens its terminal" : row.running ? "working" : row.awaiting ? inRow(row.awaiting) : "";
|
|
4813
4744
|
const meta = [activity, runner, structured ? "structured" : "", model].filter(Boolean).join(" \xB7 ");
|
|
4814
4745
|
if (meta) lines.push(style.grey(truncate(`${bodyPad}${meta}`, cols)));
|
|
4815
4746
|
if (task.transport?.fallback) lines.push(style.yellow(truncate(`${bodyPad}terminal: ${task.transport.fallback}`, cols)));
|
|
4816
4747
|
if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
|
|
4817
|
-
if (
|
|
4818
|
-
const where =
|
|
4819
|
-
const files =
|
|
4748
|
+
if (row.conflict) {
|
|
4749
|
+
const where = row.conflict.repo ? ` in ${row.conflict.repo}` : "";
|
|
4750
|
+
const files = row.conflict.files ? ` (${row.conflict.files})` : "";
|
|
4820
4751
|
lines.push(style.red(truncate(`${bodyPad}\u26A0 merge conflict${where}${files} \u2014 its work is kept on its own branch`, cols)));
|
|
4821
4752
|
}
|
|
4822
|
-
if (
|
|
4823
|
-
const files =
|
|
4824
|
-
const attempt =
|
|
4753
|
+
if (row.repairing) {
|
|
4754
|
+
const files = row.repairing.files ? ` in ${row.repairing.files}` : "";
|
|
4755
|
+
const attempt = row.repairing.attempt ? ` (attempt ${row.repairing.attempt.attempt}/${row.repairing.attempt.limit})` : "";
|
|
4825
4756
|
lines.push(style.yellow(truncate(`${bodyPad}\u21BB repairing conflict${files}${attempt}`, cols)));
|
|
4826
4757
|
}
|
|
4827
|
-
if (
|
|
4828
|
-
|
|
4829
|
-
const dep = state.tasks.find((t) => t.id === id);
|
|
4830
|
-
return dep ? `#${dep.order}` : id;
|
|
4831
|
-
});
|
|
4832
|
-
lines.push(style.yellow(truncate(`${bodyPad}\u23F8 waits for Merge all \u2014 ${deps.join(", ")} not merged into your branch yet`, cols)));
|
|
4758
|
+
if (row.mergeGate) {
|
|
4759
|
+
lines.push(style.yellow(truncate(`${bodyPad}\u23F8 waits for Merge all \u2014 ${row.mergeGate.join(", ")} not merged into your branch yet`, cols)));
|
|
4833
4760
|
}
|
|
4834
|
-
if (
|
|
4761
|
+
if (row.opsChangedFiles) {
|
|
4835
4762
|
lines.push(style.yellow(truncate(`${bodyPad}\u26A0 an ops task changed tracked files \u2014 check them, then m done or /retry`, cols)));
|
|
4836
4763
|
}
|
|
4837
|
-
if (
|
|
4838
|
-
lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${
|
|
4764
|
+
if (row.repaired?.landed) {
|
|
4765
|
+
lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${row.repaired.files}`, cols)));
|
|
4839
4766
|
}
|
|
4840
4767
|
let editorLine;
|
|
4841
4768
|
if (expanded) {
|
|
4842
4769
|
lines.push(style.grey(`${bodyPad}${task.status.replace(/_/g, " ")}`));
|
|
4843
4770
|
if (task.isolation && task.isolation.state !== "none" && task.isolation.branch) {
|
|
4844
4771
|
lines.push(...taskText("Branch", task.isolation.branch, cols, bodyPad));
|
|
4845
|
-
|
|
4846
|
-
if (repos.length > 0) lines.push(...taskText("Repos", repos.join(", "), cols, bodyPad));
|
|
4772
|
+
if (row.repos.length > 0) lines.push(...taskText("Repos", row.repos.join(", "), cols, bodyPad));
|
|
4847
4773
|
if (task.isolation.state !== "integrated" && task.isolation.worktree) {
|
|
4848
4774
|
lines.push(...taskText("Worktree", task.isolation.worktree, cols, bodyPad));
|
|
4849
4775
|
}
|
|
4850
4776
|
}
|
|
4851
|
-
if (
|
|
4777
|
+
if (row.kind === "ops") {
|
|
4852
4778
|
lines.push(...taskText("Ops", "Runs in your checkout, not a worktree, once the work it depends on is merged. O makes it a change task.", cols, bodyPad));
|
|
4853
4779
|
}
|
|
4854
|
-
if (
|
|
4855
|
-
lines.push(...taskText("Forced", `Started before the work of ${
|
|
4780
|
+
if (row.forcedPastGate) {
|
|
4781
|
+
lines.push(...taskText("Forced", `Started before the work of ${row.forcedPastGate.join(", ")} was merged into your branch.`, cols, bodyPad));
|
|
4856
4782
|
}
|
|
4857
|
-
if (
|
|
4783
|
+
if (row.autonomous) {
|
|
4858
4784
|
lines.push(...taskText("Autonomy", "Runs without permission prompts (Full). Change the level with /auto.", cols, bodyPad));
|
|
4859
4785
|
}
|
|
4860
4786
|
const editableSeed = task.prompt ?? task.description ?? task.title;
|
|
@@ -4868,10 +4794,8 @@ function taskLines(state, row, index, cols) {
|
|
|
4868
4794
|
editorLine = lines.length + 1 + cp.line;
|
|
4869
4795
|
lines.push(...taskPromptLines(wrapped, cp, bodyPad));
|
|
4870
4796
|
}
|
|
4871
|
-
if (
|
|
4872
|
-
|
|
4873
|
-
const dependencies = task.dependencies.map((id) => orderById.has(id) ? `#${orderById.get(id)}` : id).join(", ");
|
|
4874
|
-
lines.push(...taskText("Depends on", dependencies, cols, bodyPad));
|
|
4797
|
+
if (row.dependencies.length > 0) {
|
|
4798
|
+
lines.push(...taskText("Depends on", row.dependencies.join(", "), cols, bodyPad));
|
|
4875
4799
|
}
|
|
4876
4800
|
lines.push(style.grey(`${bodyPad}enter save \xB7 esc cancel`));
|
|
4877
4801
|
lines.push(style.grey(truncate(`${bodyPad}then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps`, cols)));
|
|
@@ -4918,7 +4842,13 @@ function helpLayout(rows, cols) {
|
|
|
4918
4842
|
style.grey("tab switches panes \xB7 pgup/pgdn scroll \xB7 ctrl-o toggles full detail \xB7 esc takes back a queued prompt, otherwise esc twice stops \xB7 ctrl-l clears \xB7 ctrl-c quits")
|
|
4919
4843
|
);
|
|
4920
4844
|
body.push(
|
|
4921
|
-
|
|
4845
|
+
"",
|
|
4846
|
+
style.bold("In a task view (t or /terminal on a structured task)"),
|
|
4847
|
+
style.grey(" ctrl-s sends now: interrupts the running step, then delivers the composer text \u2014 or, with it empty, the selected queued message"),
|
|
4848
|
+
style.grey(" ctrl-n/ctrl-p select a queued message \xB7 ctrl-r removes it \xB7 ctrl-x interrupts"),
|
|
4849
|
+
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"),
|
|
4850
|
+
style.grey(" at a checkpoint, ctrl-y approves and ctrl-g rejects, with the composer text as the reason \xB7 /checkpoint <id> approve|reject [reason] from anywhere"),
|
|
4851
|
+
style.grey(" alt\u2190/\u2192 changes attempt \xB7 esc returns")
|
|
4922
4852
|
);
|
|
4923
4853
|
const lines = body.map((line) => truncate(line, Math.max(1, cols - 2)));
|
|
4924
4854
|
const room = Math.max(1, rows - 2);
|
|
@@ -4929,7 +4859,7 @@ function helpScrollMax(state) {
|
|
|
4929
4859
|
}
|
|
4930
4860
|
|
|
4931
4861
|
// src/tui/render.ts
|
|
4932
|
-
var
|
|
4862
|
+
var import_core26 = require("@ordewell/core");
|
|
4933
4863
|
function render(state) {
|
|
4934
4864
|
const { rows, cols } = state;
|
|
4935
4865
|
const rowsForBody = bodyRows(state);
|
|
@@ -5194,8 +5124,8 @@ function renderHandoff(state, overlay, rows, cols) {
|
|
|
5194
5124
|
return frame(`Diff \u2014 ${handoffBranch(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
|
|
5195
5125
|
}
|
|
5196
5126
|
const landed = handoff.landed.length === 0 ? [style.grey("Nothing landed on it.")] : handoff.landed.map((task) => {
|
|
5197
|
-
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0,
|
|
5198
|
-
return ` ${style.green("\u2713")}
|
|
5127
|
+
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0, import_core26.capConflictFiles)(task.repairedFiles)})`) : "";
|
|
5128
|
+
return ` ${style.green("\u2713")} ${(0, import_core26.titledTaskRef)(task)}${repaired}`;
|
|
5199
5129
|
});
|
|
5200
5130
|
const actions = handoffActions(handoff).map((action, index) => {
|
|
5201
5131
|
const active = index === overlay.index;
|
|
@@ -5284,7 +5214,10 @@ function frame(title, body, rows, cols) {
|
|
|
5284
5214
|
}
|
|
5285
5215
|
|
|
5286
5216
|
// src/tui/reducer.ts
|
|
5287
|
-
var
|
|
5217
|
+
var import_core34 = require("@ordewell/core");
|
|
5218
|
+
|
|
5219
|
+
// src/tui/reducers/overlays.ts
|
|
5220
|
+
var import_core32 = require("@ordewell/core");
|
|
5288
5221
|
|
|
5289
5222
|
// src/tui/reducers/shared.ts
|
|
5290
5223
|
function step(state, effects = []) {
|
|
@@ -5333,10 +5266,10 @@ function handleApprovalKey(state, overlay, key) {
|
|
|
5333
5266
|
}
|
|
5334
5267
|
|
|
5335
5268
|
// src/tui/reducers/commands.ts
|
|
5336
|
-
var
|
|
5269
|
+
var import_core31 = require("@ordewell/core");
|
|
5337
5270
|
|
|
5338
5271
|
// src/tui/reducers/pickers.ts
|
|
5339
|
-
var
|
|
5272
|
+
var import_core27 = require("@ordewell/core");
|
|
5340
5273
|
var import_plan_utils2 = require("@ordewell/core/plan-utils");
|
|
5341
5274
|
var DEFAULT_EFFORT = "__runner_default__";
|
|
5342
5275
|
function picker(title, items, action, extra = {}) {
|
|
@@ -5355,11 +5288,11 @@ function modelDetail(m) {
|
|
|
5355
5288
|
function providerErrorHint(state) {
|
|
5356
5289
|
const failed = Object.keys(state.providerErrors ?? {});
|
|
5357
5290
|
if (failed.length === 0) return void 0;
|
|
5358
|
-
const names = failed.map((p) =>
|
|
5291
|
+
const names = failed.map((p) => import_core27.ALL_PROVIDERS[p]?.label ?? p);
|
|
5359
5292
|
return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")} \u2014 showing working providers only.`;
|
|
5360
5293
|
}
|
|
5361
5294
|
function plannerModelItems(state) {
|
|
5362
|
-
const runner = (0,
|
|
5295
|
+
const runner = (0, import_core27.runnerForProvider)(state.plannerProvider);
|
|
5363
5296
|
if (!runner) return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m) }));
|
|
5364
5297
|
const items = state.models.filter((m) => m.runners?.includes(runner)).map((m) => ({
|
|
5365
5298
|
id: m.id,
|
|
@@ -5373,7 +5306,7 @@ function plannerModelItems(state) {
|
|
|
5373
5306
|
return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
|
|
5374
5307
|
}
|
|
5375
5308
|
function plannerEffortItems(state) {
|
|
5376
|
-
const runner = (0,
|
|
5309
|
+
const runner = (0, import_core27.runnerForProvider)(state.plannerProvider);
|
|
5377
5310
|
if (!runner) {
|
|
5378
5311
|
return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
|
|
5379
5312
|
}
|
|
@@ -5437,7 +5370,7 @@ function pickerItemsFor(state, action) {
|
|
|
5437
5370
|
if (action.kind === "set-task-deps") {
|
|
5438
5371
|
return (0, import_plan_utils2.dependencyCandidates)(state.tasks, action.taskId).map((candidate) => ({
|
|
5439
5372
|
id: candidate.id,
|
|
5440
|
-
label:
|
|
5373
|
+
label: (0, import_core27.titledTaskRef)(candidate),
|
|
5441
5374
|
detail: candidate.status === "completed" ? "already completed" : void 0
|
|
5442
5375
|
}));
|
|
5443
5376
|
}
|
|
@@ -5482,26 +5415,25 @@ function pickerItemsFor(state, action) {
|
|
|
5482
5415
|
}
|
|
5483
5416
|
function plannerItems(state) {
|
|
5484
5417
|
const installed = new Set(state.runners.map((r) => r.id));
|
|
5485
|
-
const
|
|
5486
|
-
|
|
5487
|
-
const
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
id,
|
|
5498
|
-
label: import_core25.ALL_PROVIDERS[id].label,
|
|
5499
|
-
detail: import_core25.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
5500
|
-
selected: state.plannerProvider === id
|
|
5418
|
+
const usability = {};
|
|
5419
|
+
for (const id of import_core27.CLI_PROVIDERS) {
|
|
5420
|
+
const runner = (0, import_core27.runnerForProvider)(id);
|
|
5421
|
+
usability[runner] = installed.has(runner) ? { usable: true } : { usable: false, reason: "CLI not installed or not on PATH" };
|
|
5422
|
+
}
|
|
5423
|
+
const entries = (0, import_core27.plannerBackendEntries)(usability, state.configuredProviders);
|
|
5424
|
+
const items = entries.map((entry) => ({
|
|
5425
|
+
id: entry.id,
|
|
5426
|
+
label: entry.label,
|
|
5427
|
+
detail: entry.reason,
|
|
5428
|
+
selected: state.plannerProvider === entry.id,
|
|
5429
|
+
disabled: !entry.usable
|
|
5501
5430
|
}));
|
|
5502
|
-
return
|
|
5431
|
+
return entries.some((entry) => entry.kind === "vendor") ? items : [...items, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
|
|
5503
5432
|
}
|
|
5504
5433
|
|
|
5434
|
+
// src/tui/reducers/planPane.ts
|
|
5435
|
+
var import_core29 = require("@ordewell/core");
|
|
5436
|
+
|
|
5505
5437
|
// src/tui/reducers/pointer.ts
|
|
5506
5438
|
var WHEEL_NOTCH = 3;
|
|
5507
5439
|
function pageNotch(viewport) {
|
|
@@ -5578,13 +5510,13 @@ function handleMouseSelect(state, key) {
|
|
|
5578
5510
|
}
|
|
5579
5511
|
|
|
5580
5512
|
// src/tui/reducers/taskView.ts
|
|
5581
|
-
var
|
|
5513
|
+
var import_core28 = require("@ordewell/core");
|
|
5582
5514
|
function openTaskView(state, sessionId, taskId) {
|
|
5583
5515
|
const task = findTask(state.tasks, taskId);
|
|
5584
5516
|
if (!task) return step(say(state, "system", `No task ${taskId} in this plan.`));
|
|
5585
5517
|
const taskView = {
|
|
5586
5518
|
taskId,
|
|
5587
|
-
view:
|
|
5519
|
+
view: import_core28.EMPTY_TASK_LOG,
|
|
5588
5520
|
attempts: [],
|
|
5589
5521
|
attempt: 0,
|
|
5590
5522
|
pending: [],
|
|
@@ -5617,13 +5549,13 @@ function applyLive(tv, attempt, events) {
|
|
|
5617
5549
|
}
|
|
5618
5550
|
next = {
|
|
5619
5551
|
...next,
|
|
5620
|
-
view:
|
|
5552
|
+
view: import_core28.EMPTY_TASK_LOG,
|
|
5621
5553
|
attempt,
|
|
5622
5554
|
attempts: next.attempts.includes(attempt) ? next.attempts : [...next.attempts, attempt]
|
|
5623
5555
|
};
|
|
5624
5556
|
}
|
|
5625
5557
|
if (attempt < next.attempt) return next;
|
|
5626
|
-
return clampQueueIndex({ ...next, view: events.reduce(
|
|
5558
|
+
return clampQueueIndex({ ...next, view: events.reduce(import_core28.reduceTaskLog, next.view) });
|
|
5627
5559
|
}
|
|
5628
5560
|
function taskLogAbandoned(state) {
|
|
5629
5561
|
const tv = state.taskView;
|
|
@@ -5643,15 +5575,22 @@ function taskLogArrived(state, action) {
|
|
|
5643
5575
|
function announceApprovals(state, action) {
|
|
5644
5576
|
if (state.taskView?.taskId === action.taskId) return state;
|
|
5645
5577
|
const task = findTask(state.tasks, action.taskId);
|
|
5646
|
-
const spoken = action.events.reduce((s, event) => event.type === "approval_requested" ? say(s, "system", `\xB7 Task ${task?.order ?? "?"} waits for approval: ${(0,
|
|
5578
|
+
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 t on it, then ctrl-y to allow or ctrl-g to deny`) : s, state);
|
|
5647
5579
|
return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
|
|
5648
5580
|
}
|
|
5581
|
+
function announceCheckpoint(state, action) {
|
|
5582
|
+
const task = findTask(state.tasks, action.taskId) ?? state.tasks.find((t) => t.title === action.title);
|
|
5583
|
+
const label = task ? `Task ${task.order}` : action.title;
|
|
5584
|
+
const where = task?.transport?.kind === "structured" ? "t on it to answer" : `/checkpoint ${task?.order ?? "<id>"} approve|reject to answer`;
|
|
5585
|
+
const spoken = say(state, "system", `\xB7 ${label} asks: ${(0, import_core28.truncateCheckpointSummary)(action.summary)} \u2014 ${where}`);
|
|
5586
|
+
return { ...spoken, scroll: state.scroll };
|
|
5587
|
+
}
|
|
5649
5588
|
function taskLogLoaded(state, action) {
|
|
5650
5589
|
const tv = state.taskView;
|
|
5651
5590
|
if (!tv || tv.taskId !== action.taskId) return state;
|
|
5652
5591
|
const loaded = action.attempt >= 1 ? {
|
|
5653
5592
|
...tv,
|
|
5654
|
-
view: (0,
|
|
5593
|
+
view: (0, import_core28.replayTaskLog)(action.events),
|
|
5655
5594
|
attempts: action.attempts ?? tv.attempts,
|
|
5656
5595
|
attempt: action.attempt
|
|
5657
5596
|
} : { ...tv, attempts: action.attempts ?? tv.attempts };
|
|
@@ -5673,6 +5612,29 @@ function answerApproval(state, tv, sessionId, decision) {
|
|
|
5673
5612
|
const next = note ? { ...state, editor: { ...state.editor, text: "", cursor: 0 } } : state;
|
|
5674
5613
|
return step(next, [{ type: "answerTaskApproval", sessionId, approvalId: approval.approvalId, answer }]);
|
|
5675
5614
|
}
|
|
5615
|
+
function answerCheckpoint(state, sessionId, taskId, answer, reason = "") {
|
|
5616
|
+
const task = findTask(state.tasks, taskId);
|
|
5617
|
+
if (!waitingCheckpoint(task)) return step(say(state, "system", `Task ${task?.order ?? taskId} is not waiting at a checkpoint.`));
|
|
5618
|
+
const note = answer === "reject" ? reason.trim() : "";
|
|
5619
|
+
return step(state, [{ type: "answerTaskCheckpoint", sessionId, taskId, answer, ...note ? { reason: note } : {} }]);
|
|
5620
|
+
}
|
|
5621
|
+
function answerWaiting(state, tv, sessionId, decision) {
|
|
5622
|
+
if (waitingApproval(tv) || !waitingCheckpoint(findTask(state.tasks, tv.taskId))) return answerApproval(state, tv, sessionId, decision);
|
|
5623
|
+
if (decision === "allow") return answerCheckpoint(state, sessionId, tv.taskId, "approve");
|
|
5624
|
+
const answered = answerCheckpoint(state, sessionId, tv.taskId, "reject", state.editor.text);
|
|
5625
|
+
return state.editor.text.trim() ? { ...answered, state: { ...answered.state, editor: { ...answered.state.editor, text: "", cursor: 0 } } } : answered;
|
|
5626
|
+
}
|
|
5627
|
+
function sendNow(state, tv, sessionId) {
|
|
5628
|
+
const text = state.editor.text.trim();
|
|
5629
|
+
if (text) {
|
|
5630
|
+
if (continuesTask(findTask(state.tasks, tv.taskId))) return step(say(state, "system", "The task has finished, so there is no turn to interrupt \u2014 enter continues it."));
|
|
5631
|
+
return step({ ...state, editor: commit(state.editor) }, [{ type: "forceSendTaskMessage", sessionId, taskId: tv.taskId, text }]);
|
|
5632
|
+
}
|
|
5633
|
+
const queued = tv.view.queued[tv.queuedIndex];
|
|
5634
|
+
if (!queued) return step(say(state, "system", "Nothing to send now: type a message, or pick a queued one with ctrl-n/ctrl-p."));
|
|
5635
|
+
if (queued.handedOver) return step(say(state, "system", "The runner already has that message; it reads it after its current step."));
|
|
5636
|
+
return step(state, [{ type: "forceSendQueuedTaskMessage", sessionId, taskId: tv.taskId, messageId: queued.id }]);
|
|
5637
|
+
}
|
|
5676
5638
|
function handleTaskViewKey(state, key) {
|
|
5677
5639
|
const tv = state.taskView;
|
|
5678
5640
|
if (!tv || !state.sessionId) return null;
|
|
@@ -5692,14 +5654,16 @@ function handleTaskViewKey(state, key) {
|
|
|
5692
5654
|
if (key.name === "ctrl-r") {
|
|
5693
5655
|
const queued = tv.view.queued[tv.queuedIndex];
|
|
5694
5656
|
if (!queued) return step(say(state, "system", "No queued message to remove."));
|
|
5657
|
+
if (queued.handedOver) return step(say(state, "system", "The runner already has that message; it can no longer be taken back."));
|
|
5695
5658
|
return step(state, [{ type: "removeTaskMessage", sessionId: state.sessionId, taskId: tv.taskId, messageId: queued.id }]);
|
|
5696
5659
|
}
|
|
5660
|
+
if (key.name === "ctrl-s") return sendNow(state, tv, state.sessionId);
|
|
5697
5661
|
if (key.name === "ctrl-x") {
|
|
5698
5662
|
return step(state, [{ type: "interruptTask", sessionId: state.sessionId, taskId: tv.taskId }]);
|
|
5699
5663
|
}
|
|
5700
|
-
if (key.name === "ctrl-y") return
|
|
5664
|
+
if (key.name === "ctrl-y") return answerWaiting(state, tv, state.sessionId, "allow");
|
|
5701
5665
|
if (key.name === "ctrl-t") return answerApproval(state, tv, state.sessionId, "allowForTask");
|
|
5702
|
-
if (key.name === "ctrl-g") return
|
|
5666
|
+
if (key.name === "ctrl-g") return answerWaiting(state, tv, state.sessionId, "deny");
|
|
5703
5667
|
if (key.name === "ctrl-n" || key.name === "ctrl-p") {
|
|
5704
5668
|
const count = tv.view.queued.length;
|
|
5705
5669
|
if (count === 0) return null;
|
|
@@ -5720,7 +5684,7 @@ function openTaskRunnerPicker(state, task) {
|
|
|
5720
5684
|
...state,
|
|
5721
5685
|
overlay: {
|
|
5722
5686
|
kind: "picker",
|
|
5723
|
-
picker: picker(`Runner \xB7
|
|
5687
|
+
picker: picker(`Runner \xB7 ${(0, import_plan_utils3.titledTaskRef)(task)}`, pickerItemsFor(state, action), action, {
|
|
5724
5688
|
hint: "Changing the runner re-picks this task model, effort and mode for it."
|
|
5725
5689
|
})
|
|
5726
5690
|
}
|
|
@@ -5728,14 +5692,12 @@ function openTaskRunnerPicker(state, task) {
|
|
|
5728
5692
|
);
|
|
5729
5693
|
}
|
|
5730
5694
|
function confirmRemoveTask(state, task) {
|
|
5731
|
-
const dependents = (0, import_plan_utils3.dependentsOf)(state.tasks, task.id);
|
|
5732
|
-
const named = dependents.map((t) => `#${t.order} ${t.title}`).join(", ");
|
|
5733
5695
|
return step({
|
|
5734
5696
|
...state,
|
|
5735
5697
|
overlay: {
|
|
5736
5698
|
kind: "confirm",
|
|
5737
|
-
title: `Remove
|
|
5738
|
-
message:
|
|
5699
|
+
title: `Remove ${(0, import_plan_utils3.titledTaskRef)(task)}?`,
|
|
5700
|
+
message: (0, import_plan_utils3.dependentsNotice)((0, import_plan_utils3.dependentsOf)(state.tasks, task.id)) ?? "This cannot be undone.",
|
|
5739
5701
|
action: { kind: "remove-task", taskId: task.id }
|
|
5740
5702
|
}
|
|
5741
5703
|
});
|
|
@@ -5744,26 +5706,22 @@ function toggleTaskOps(state, task, subtask, to = !task.ops) {
|
|
|
5744
5706
|
if (!state.sessionId) return fail3(state, "No active plan.");
|
|
5745
5707
|
if (task.type !== "ai") return fail3(state, "Only an AI task can be an ops task \u2014 a manual task already runs outside any worktree.");
|
|
5746
5708
|
if (subtask) return fail3(state, "A subtask runs with its parent; make the parent an ops task instead.");
|
|
5747
|
-
if (!!task.ops === to) return fail3(state,
|
|
5709
|
+
if (!!task.ops === to) return fail3(state, `${(0, import_plan_utils3.taskRef)(task)} is already ${to ? "an ops" : "a change"} task.`);
|
|
5748
5710
|
return step(state, [{
|
|
5749
5711
|
type: "updateTask",
|
|
5750
5712
|
sessionId: state.sessionId,
|
|
5751
5713
|
taskId: task.id,
|
|
5752
5714
|
changes: { ops: to },
|
|
5753
|
-
message: to ? `Task
|
|
5715
|
+
message: to ? `Task ${(0, import_plan_utils3.taskRef)(task)} is an ops task: it runs in your checkout once the work it depends on is merged.` : `Task ${(0, import_plan_utils3.taskRef)(task)} is a change task: it runs in its own worktree.`
|
|
5754
5716
|
}]);
|
|
5755
5717
|
}
|
|
5756
5718
|
function confirmForceStartPastGate(state, task) {
|
|
5757
|
-
const named = (task.mergeGate ?? []).map((id) => {
|
|
5758
|
-
const dep = findTask(state.tasks, id);
|
|
5759
|
-
return dep ? `#${dep.order} ${dep.title}` : id;
|
|
5760
|
-
});
|
|
5761
5719
|
return step({
|
|
5762
5720
|
...state,
|
|
5763
5721
|
overlay: {
|
|
5764
5722
|
kind: "confirm",
|
|
5765
|
-
title: `Force start
|
|
5766
|
-
message:
|
|
5723
|
+
title: `Force start ${(0, import_plan_utils3.titledTaskRef)(task)}?`,
|
|
5724
|
+
message: (0, import_plan_utils3.pastGateConfirmation)((0, import_plan_utils3.titledRefs)(task.mergeGate ?? [], state.tasks), "It"),
|
|
5767
5725
|
action: { kind: "force-start-gated", taskId: task.id }
|
|
5768
5726
|
}
|
|
5769
5727
|
});
|
|
@@ -5771,12 +5729,12 @@ function confirmForceStartPastGate(state, task) {
|
|
|
5771
5729
|
function openTaskDepsPicker(state, task) {
|
|
5772
5730
|
const action = { kind: "set-task-deps", taskId: task.id };
|
|
5773
5731
|
const items = pickerItemsFor(state, action);
|
|
5774
|
-
if (items.length === 0) return fail3(state, `Nothing runs before
|
|
5732
|
+
if (items.length === 0) return fail3(state, `Nothing runs before ${(0, import_plan_utils3.taskRef)(task)}, so it has no possible dependencies.`);
|
|
5775
5733
|
return step({
|
|
5776
5734
|
...state,
|
|
5777
5735
|
overlay: {
|
|
5778
5736
|
kind: "picker",
|
|
5779
|
-
picker: picker(`Depends on \xB7
|
|
5737
|
+
picker: picker(`Depends on \xB7 ${(0, import_plan_utils3.titledTaskRef)(task)}`, items, action, {
|
|
5780
5738
|
hint: "Only tasks earlier in the plan can be dependencies.",
|
|
5781
5739
|
multi: true,
|
|
5782
5740
|
chosen: task.dependencies.filter((id) => items.some((i) => i.id === id))
|
|
@@ -5796,7 +5754,7 @@ function openTaskModePicker(state, task) {
|
|
|
5796
5754
|
...state,
|
|
5797
5755
|
overlay: {
|
|
5798
5756
|
kind: "picker",
|
|
5799
|
-
picker: picker(`Mode \xB7
|
|
5757
|
+
picker: picker(`Mode \xB7 ${(0, import_plan_utils3.taskRef)(task)}`, pickerItemsFor(state, action), action, {
|
|
5800
5758
|
hint: `Modes declared by ${task.assignedRunner}.`
|
|
5801
5759
|
})
|
|
5802
5760
|
}
|
|
@@ -5811,7 +5769,7 @@ function openTaskModelPicker(state, task) {
|
|
|
5811
5769
|
...state,
|
|
5812
5770
|
overlay: {
|
|
5813
5771
|
kind: "picker",
|
|
5814
|
-
picker: picker(`Model \xB7
|
|
5772
|
+
picker: picker(`Model \xB7 ${(0, import_plan_utils3.titledTaskRef)(task)}`, pickerItemsFor(state, action), action, {
|
|
5815
5773
|
hint: task.assignedRunner ? `Showing models discovered for ${task.assignedRunner}.` : "Choose the model this task will run with."
|
|
5816
5774
|
})
|
|
5817
5775
|
}
|
|
@@ -5828,7 +5786,7 @@ function openTaskEffortPicker(state, task) {
|
|
|
5828
5786
|
...state,
|
|
5829
5787
|
overlay: {
|
|
5830
5788
|
kind: "picker",
|
|
5831
|
-
picker: picker(`Thinking effort \xB7
|
|
5789
|
+
picker: picker(`Thinking effort \xB7 ${(0, import_plan_utils3.taskRef)(task)}`, pickerItemsFor(state, action), action, {
|
|
5832
5790
|
hint: `${task.assignedModel.modelLabel} \xB7 choose runner default or a supported effort`
|
|
5833
5791
|
})
|
|
5834
5792
|
}
|
|
@@ -5845,7 +5803,7 @@ function assignTaskModel(state, sessionId, task, model) {
|
|
|
5845
5803
|
// JSON drops `undefined`; null is intentional here so changing models can
|
|
5846
5804
|
// also clear a stale legacy top-level effort on the persisted task.
|
|
5847
5805
|
changes: { assignedModel, thinkingEffort: assignedModel.thinkingEffort ?? null },
|
|
5848
|
-
message: `Task
|
|
5806
|
+
message: `Task ${(0, import_plan_utils3.taskRef)(task)} model set to ${model.label}.`
|
|
5849
5807
|
}]);
|
|
5850
5808
|
}
|
|
5851
5809
|
function assignTaskRunner(state, sessionId, task, runner, runnerLabel) {
|
|
@@ -5854,7 +5812,7 @@ function assignTaskRunner(state, sessionId, task, runner, runnerLabel) {
|
|
|
5854
5812
|
sessionId,
|
|
5855
5813
|
taskId: task.id,
|
|
5856
5814
|
changes: { assignedRunner: runner },
|
|
5857
|
-
message: `Task
|
|
5815
|
+
message: `Task ${(0, import_plan_utils3.taskRef)(task)} runner set to ${runnerLabel}.`
|
|
5858
5816
|
}]);
|
|
5859
5817
|
}
|
|
5860
5818
|
function assignTaskMode(state, sessionId, task, mode, modeLabel) {
|
|
@@ -5863,7 +5821,7 @@ function assignTaskMode(state, sessionId, task, mode, modeLabel) {
|
|
|
5863
5821
|
sessionId,
|
|
5864
5822
|
taskId: task.id,
|
|
5865
5823
|
changes: { taskMode: mode },
|
|
5866
|
-
message: `Task
|
|
5824
|
+
message: `Task ${(0, import_plan_utils3.taskRef)(task)} mode set to ${modeLabel}.`
|
|
5867
5825
|
}]);
|
|
5868
5826
|
}
|
|
5869
5827
|
function assignTaskEffort(state, sessionId, task, thinkingEffort) {
|
|
@@ -5873,7 +5831,7 @@ function assignTaskEffort(state, sessionId, task, thinkingEffort) {
|
|
|
5873
5831
|
sessionId,
|
|
5874
5832
|
taskId: task.id,
|
|
5875
5833
|
changes: { assignedModel, thinkingEffort: thinkingEffort ?? null },
|
|
5876
|
-
message: `Task
|
|
5834
|
+
message: `Task ${(0, import_plan_utils3.taskRef)(task)} thinking effort set to ${thinkingEffort ?? "runner default"}.`
|
|
5877
5835
|
}]);
|
|
5878
5836
|
}
|
|
5879
5837
|
function taskModelCommand(state, args) {
|
|
@@ -5975,9 +5933,6 @@ function taskActionEffect(state, sessionId, taskId, action) {
|
|
|
5975
5933
|
const watch = SPAWNS_RUNNER.includes(action) && state.status !== "executing";
|
|
5976
5934
|
return watch ? { type: "taskAction", sessionId, taskId, action, watch: true } : { type: "taskAction", sessionId, taskId, action };
|
|
5977
5935
|
}
|
|
5978
|
-
function markAction(task) {
|
|
5979
|
-
return task.status === "completed" ? "uncomplete" : "complete";
|
|
5980
|
-
}
|
|
5981
5936
|
function handlePlanKey(state, key) {
|
|
5982
5937
|
const editingTask = state.expandedTaskId ? findTask(state.tasks, state.expandedTaskId) : void 0;
|
|
5983
5938
|
if (editingTask && state.taskEditor) return handleTaskEditKey(state, editingTask, state.taskEditor, key);
|
|
@@ -6012,7 +5967,7 @@ function handlePlanKey(state, key) {
|
|
|
6012
5967
|
if (!state.sessionId || key.name !== "char") return step(state);
|
|
6013
5968
|
if (key.char === "E") return step(state, [{ type: "execute", sessionId: state.sessionId }]);
|
|
6014
5969
|
if (key.char === "S") return step(state, [{ type: "stopExecution", sessionId: state.sessionId }]);
|
|
6015
|
-
const action = key.char === "m" ? markAction(task) : PLAN_SHORTCUTS[key.char ?? ""];
|
|
5970
|
+
const action = key.char === "m" ? (0, import_core29.markAction)(task) : PLAN_SHORTCUTS[key.char ?? ""];
|
|
6016
5971
|
if (action === "force-start" && task.mergeGate?.length) return confirmForceStartPastGate(state, task);
|
|
6017
5972
|
if (action) {
|
|
6018
5973
|
return step(state, [taskActionEffect(state, state.sessionId, task.id, action)]);
|
|
@@ -6047,14 +6002,14 @@ function commitTaskEdit(state, task, editor) {
|
|
|
6047
6002
|
sessionId: state.sessionId,
|
|
6048
6003
|
taskId: task.id,
|
|
6049
6004
|
changes: { prompt },
|
|
6050
|
-
message: `Task
|
|
6005
|
+
message: `Task ${(0, import_core29.taskRef)(task)} prompt updated.`
|
|
6051
6006
|
}]);
|
|
6052
6007
|
}
|
|
6053
6008
|
|
|
6054
6009
|
// src/tui/reducers/turnQueue.ts
|
|
6055
|
-
var
|
|
6010
|
+
var import_core30 = require("@ordewell/core");
|
|
6056
6011
|
function drainQueue(state, settled) {
|
|
6057
|
-
const next = (0,
|
|
6012
|
+
const next = (0, import_core30.drainNext)(state.queuedPrompts);
|
|
6058
6013
|
const ended = disarmStop({ ...settled, stopRequested: false });
|
|
6059
6014
|
if (!next) return step(ended);
|
|
6060
6015
|
const spoken = say(ended, "user", next.text);
|
|
@@ -6073,14 +6028,14 @@ function stopPlanning(state, sessionId) {
|
|
|
6073
6028
|
return step(queueToEditor(cutTurn({ ...dismissed, stopArmed: false, stopRequested: true })), [{ type: "cancelPlanning", sessionId }]);
|
|
6074
6029
|
}
|
|
6075
6030
|
function queueToEditor(state) {
|
|
6076
|
-
const all = (0,
|
|
6031
|
+
const all = (0, import_core30.unsendAll)(state.queuedPrompts);
|
|
6077
6032
|
if (!all) return state;
|
|
6078
6033
|
return { ...state, queuedPrompts: all.rest, editor: { ...state.editor, text: all.text, cursor: all.text.length } };
|
|
6079
6034
|
}
|
|
6080
6035
|
function unsendLatest(state) {
|
|
6081
|
-
const latest = (0,
|
|
6036
|
+
const latest = (0, import_core30.unsendLatest)(state.queuedPrompts);
|
|
6082
6037
|
if (!latest) return step(state);
|
|
6083
|
-
const text = (0,
|
|
6038
|
+
const text = (0, import_core30.aheadOfDraft)(latest.text, state.editor.text);
|
|
6084
6039
|
return step({
|
|
6085
6040
|
...state,
|
|
6086
6041
|
queuedPrompts: latest.rest,
|
|
@@ -6097,7 +6052,7 @@ function plannerEscape(state) {
|
|
|
6097
6052
|
}
|
|
6098
6053
|
|
|
6099
6054
|
// src/tui/reducers/commands.ts
|
|
6100
|
-
var
|
|
6055
|
+
var KNOWN_PROVIDERS = Object.keys(import_core31.ALL_PROVIDERS);
|
|
6101
6056
|
function runCommand(state, { name, args }) {
|
|
6102
6057
|
if (!findCommand(name)) {
|
|
6103
6058
|
return fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
|
|
@@ -6184,6 +6139,14 @@ function runCommand(state, { name, args }) {
|
|
|
6184
6139
|
if (!text) return fail3(state, "Usage: /continue <id> <message>");
|
|
6185
6140
|
return continueTaskStep(state, sessionId, taskId, text);
|
|
6186
6141
|
});
|
|
6142
|
+
case "checkpoint":
|
|
6143
|
+
return taskCommand(state, args[0], (sessionId, taskId) => {
|
|
6144
|
+
const answer = args[1]?.toLowerCase();
|
|
6145
|
+
const reason = args.slice(2).join(" ").trim();
|
|
6146
|
+
if (answer !== "approve" && answer !== "reject") return fail3(state, "Usage: /checkpoint <id> approve|reject [reason]");
|
|
6147
|
+
if (answer === "approve" && reason) return fail3(state, "Approving takes no note \u2014 reject with a reason to tell the task something.");
|
|
6148
|
+
return answerCheckpoint(state, sessionId, taskId, answer, reason);
|
|
6149
|
+
});
|
|
6187
6150
|
case "task-runner":
|
|
6188
6151
|
return taskRunnerCommand(state, args);
|
|
6189
6152
|
case "task-model":
|
|
@@ -6255,8 +6218,8 @@ function newSession(state) {
|
|
|
6255
6218
|
taskView: null,
|
|
6256
6219
|
tasks: [],
|
|
6257
6220
|
planApproved: false,
|
|
6258
|
-
conversation:
|
|
6259
|
-
turnGate:
|
|
6221
|
+
conversation: import_core31.EMPTY_CONVERSATION,
|
|
6222
|
+
turnGate: import_core31.NO_TURN,
|
|
6260
6223
|
selectedTask: 0,
|
|
6261
6224
|
expandedTaskId: null,
|
|
6262
6225
|
taskEditor: null,
|
|
@@ -6267,14 +6230,14 @@ function newSession(state) {
|
|
|
6267
6230
|
// Prompts belong to the session that raised them; the old planner is gone
|
|
6268
6231
|
// and its pending requests deny on their own timeout.
|
|
6269
6232
|
pendingApprovals: [],
|
|
6270
|
-
queuedPrompts:
|
|
6233
|
+
queuedPrompts: import_core31.EMPTY_HOLD,
|
|
6271
6234
|
stopArmed: false,
|
|
6272
6235
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6273
6236
|
}, closeEffects);
|
|
6274
6237
|
}
|
|
6275
6238
|
function setModel(state, args) {
|
|
6276
6239
|
if (args[0] === "set" && args[1]) {
|
|
6277
|
-
const runner = (0,
|
|
6240
|
+
const runner = (0, import_core31.runnerForProvider)(state.plannerProvider);
|
|
6278
6241
|
const known = runner ? modelsForRunner(state.models, runner) : [];
|
|
6279
6242
|
if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
|
|
6280
6243
|
return fail3(state, `${args[1]} was not discovered for ${runner}.`);
|
|
@@ -6297,7 +6260,7 @@ function setModel(state, args) {
|
|
|
6297
6260
|
function setPlanner(state, args) {
|
|
6298
6261
|
if (args[0]) {
|
|
6299
6262
|
const id = args[0].toLowerCase();
|
|
6300
|
-
if (!
|
|
6263
|
+
if (!KNOWN_PROVIDERS.includes(id)) {
|
|
6301
6264
|
return fail3(state, `Unknown planner: ${id}. Run /planner with no arguments to see the list.`);
|
|
6302
6265
|
}
|
|
6303
6266
|
return step(state, [{ type: "setPlanner", provider: id }]);
|
|
@@ -6336,7 +6299,7 @@ function setPlannerEffort(state, args) {
|
|
|
6336
6299
|
function setKey(state, args) {
|
|
6337
6300
|
if (args[0] === "set" && args[1]) {
|
|
6338
6301
|
const provider = args[1].toLowerCase();
|
|
6339
|
-
if (!
|
|
6302
|
+
if (!KNOWN_PROVIDERS.includes(provider)) {
|
|
6340
6303
|
return fail3(state, `Unknown provider: ${provider}. Run /key with no arguments to see the list.`);
|
|
6341
6304
|
}
|
|
6342
6305
|
if (!args[2]) {
|
|
@@ -6347,16 +6310,16 @@ function setKey(state, args) {
|
|
|
6347
6310
|
}
|
|
6348
6311
|
return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
|
|
6349
6312
|
}
|
|
6350
|
-
const items =
|
|
6313
|
+
const items = import_core31.PROVIDER_PRIORITY.map((id) => ({
|
|
6351
6314
|
id,
|
|
6352
|
-
label:
|
|
6353
|
-
detail:
|
|
6315
|
+
label: import_core31.ALL_PROVIDERS[id].label,
|
|
6316
|
+
detail: import_core31.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
6354
6317
|
selected: state.configuredProviders.includes(id)
|
|
6355
6318
|
}));
|
|
6356
6319
|
return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
|
|
6357
6320
|
}
|
|
6358
6321
|
function keyPrompt(provider) {
|
|
6359
|
-
const meta =
|
|
6322
|
+
const meta = import_core31.ALL_PROVIDERS[provider];
|
|
6360
6323
|
return {
|
|
6361
6324
|
kind: "prompt",
|
|
6362
6325
|
title: `${meta.label} API key`,
|
|
@@ -6433,19 +6396,19 @@ function runners(state, args) {
|
|
|
6433
6396
|
}
|
|
6434
6397
|
function setMaxParallel(state, arg) {
|
|
6435
6398
|
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.`));
|
|
6436
|
-
const limit = (0,
|
|
6399
|
+
const limit = (0, import_core31.parseMaxParallel)(arg);
|
|
6437
6400
|
if (limit === null) return fail3(state, "Usage: /parallel [<number of tasks, 1 or more>]");
|
|
6438
6401
|
return step({ ...state, maxParallel: limit }, [{ type: "setMaxParallel", limit }]);
|
|
6439
6402
|
}
|
|
6440
6403
|
function setAutonomous(state, arg) {
|
|
6441
|
-
if (arg === void 0) return step(say(state, "system", `Autonomy level: ${(0,
|
|
6442
|
-
const enabled = (0,
|
|
6404
|
+
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.`));
|
|
6405
|
+
const enabled = (0, import_core31.parseAutonomyLevel)(arg);
|
|
6443
6406
|
if (enabled === null) return fail3(state, "Usage: /auto [full|guarded]");
|
|
6444
6407
|
return step({ ...state, autonomous: enabled }, [{ type: "setAutonomous", enabled }]);
|
|
6445
6408
|
}
|
|
6446
6409
|
function setTransport(state, arg) {
|
|
6447
6410
|
const value = arg?.toLowerCase() ?? (state.runnerTransport === "structured" ? "terminal" : "structured");
|
|
6448
|
-
if (!(0,
|
|
6411
|
+
if (!(0, import_core31.isRunnerTransport)(value)) return fail3(state, "Usage: /transport [terminal|structured]");
|
|
6449
6412
|
return step(state, [{ type: "setTransport", transport: value }]);
|
|
6450
6413
|
}
|
|
6451
6414
|
function setMouseCapture(state, arg) {
|
|
@@ -6622,7 +6585,7 @@ function choose(state, picker2, item) {
|
|
|
6622
6585
|
sessionId: state.sessionId,
|
|
6623
6586
|
taskId: task.id,
|
|
6624
6587
|
changes: { dependencies: picker2.chosen },
|
|
6625
|
-
message: picker2.chosen.length > 0 ? `Task
|
|
6588
|
+
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.`
|
|
6626
6589
|
}]);
|
|
6627
6590
|
}
|
|
6628
6591
|
if (!item) return step(state);
|
|
@@ -6699,7 +6662,7 @@ function choose(state, picker2, item) {
|
|
|
6699
6662
|
}
|
|
6700
6663
|
|
|
6701
6664
|
// src/tui/reducers/incoming.ts
|
|
6702
|
-
var
|
|
6665
|
+
var import_core33 = require("@ordewell/core");
|
|
6703
6666
|
var planText = (value, fallback = "") => sanitize(String(value ?? fallback));
|
|
6704
6667
|
var planLabel = (value, fallback = "") => planText(value, fallback).replace(/\n+/g, " ");
|
|
6705
6668
|
var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
|
|
@@ -6730,7 +6693,7 @@ function toTaskView2(t, index) {
|
|
|
6730
6693
|
prompt: typeof t.prompt === "string" ? sanitize(t.prompt) : void 0,
|
|
6731
6694
|
type: t.type === "user" ? "user" : "ai",
|
|
6732
6695
|
status: planLabel(t.status, "pending"),
|
|
6733
|
-
awaitingReason: t.status === "awaiting_user" && (0,
|
|
6696
|
+
awaitingReason: t.status === "awaiting_user" && (0, import_core33.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
|
|
6734
6697
|
dependencies: Array.isArray(t.dependencies) ? t.dependencies.map((id) => planLabel(id)) : [],
|
|
6735
6698
|
assignedRunner: typeof t.assignedRunner === "string" ? planLabel(t.assignedRunner) : void 0,
|
|
6736
6699
|
taskMode: typeof t.taskMode === "string" ? planLabel(t.taskMode) : void 0,
|
|
@@ -6763,12 +6726,12 @@ function applySettings(state, settings) {
|
|
|
6763
6726
|
plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
|
|
6764
6727
|
plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
|
|
6765
6728
|
maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
|
|
6766
|
-
runnerTransport: (0,
|
|
6729
|
+
runnerTransport: (0, import_core33.isRunnerTransport)(settings.runnerTransport) ? settings.runnerTransport : state.runnerTransport,
|
|
6767
6730
|
allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist
|
|
6768
6731
|
};
|
|
6769
6732
|
}
|
|
6770
6733
|
function runLabel(tasks, gate = null) {
|
|
6771
|
-
const running = tasks.filter(isTaskRunning);
|
|
6734
|
+
const running = tasks.filter(import_core24.isTaskRunning);
|
|
6772
6735
|
if (running.length > 0) {
|
|
6773
6736
|
const [first] = running;
|
|
6774
6737
|
const name = first.assignedRunner ? `${first.title} \xB7 ${first.assignedRunner}` : first.title;
|
|
@@ -6834,13 +6797,13 @@ function reduceAction(state, action) {
|
|
|
6834
6797
|
goal: "",
|
|
6835
6798
|
taskView: null,
|
|
6836
6799
|
pendingApprovals: [],
|
|
6837
|
-
queuedPrompts:
|
|
6800
|
+
queuedPrompts: import_core34.EMPTY_HOLD,
|
|
6838
6801
|
stopArmed: false,
|
|
6839
6802
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6840
6803
|
});
|
|
6841
6804
|
case "chatRestored":
|
|
6842
6805
|
if (stale(state, action.sessionId)) return step(state);
|
|
6843
|
-
return step({ ...state, conversation: (0,
|
|
6806
|
+
return step({ ...state, conversation: (0, import_core34.fromTranscript)(action.history, action.researchLog, action.plannerUsage), scroll: 0 });
|
|
6844
6807
|
case "planUpdated": {
|
|
6845
6808
|
if (stale(state, action.sessionId)) return step(state);
|
|
6846
6809
|
const isolation = isolationOfPlan(action.plan);
|
|
@@ -6887,14 +6850,17 @@ function reduceAction(state, action) {
|
|
|
6887
6850
|
case "taskStarted": {
|
|
6888
6851
|
if (stale(state, action.sessionId)) return step(state);
|
|
6889
6852
|
const existing = findTask(state.tasks, action.taskId);
|
|
6890
|
-
if (existing && isTaskRunning(existing)) return step(state);
|
|
6891
|
-
const tasks = state.tasks.map((t) => t.id === action.taskId && !isTaskRunning(t) ? { ...t, status: "in_progress" } : t);
|
|
6892
|
-
const spoken = say(state, "system", (0,
|
|
6853
|
+
if (existing && (0, import_core24.isTaskRunning)(existing)) return step(state);
|
|
6854
|
+
const tasks = state.tasks.map((t) => t.id === action.taskId && !(0, import_core24.isTaskRunning)(t) ? { ...t, status: "in_progress" } : t);
|
|
6855
|
+
const spoken = say(state, "system", (0, import_core34.taskStartedNotice)(action.title, action.runner));
|
|
6893
6856
|
return step({ ...spoken, tasks, status: "executing", busyLabel: runLabel(tasks) });
|
|
6894
6857
|
}
|
|
6858
|
+
case "taskCheckpoint":
|
|
6859
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
6860
|
+
return step(announceCheckpoint(state, action));
|
|
6895
6861
|
case "taskStatus": {
|
|
6896
6862
|
if (stale(state, action.sessionId) || !state.tasks.some((t) => t.id === action.taskId)) return step(state);
|
|
6897
|
-
const tasks = state.tasks.map((t) => t.id === action.taskId ? { ...t, status: action.status, awaitingReason: void 0 } : t);
|
|
6863
|
+
const tasks = state.tasks.map((t) => t.id === action.taskId ? { ...t, status: action.status, awaitingReason: void 0, checkpoint: void 0 } : t);
|
|
6898
6864
|
const status = runStatus(state, tasks);
|
|
6899
6865
|
return step({ ...state, status, tasks, busyLabel: status === "executing" ? runLabel(tasks, state.gate) : state.busyLabel });
|
|
6900
6866
|
}
|
|
@@ -6909,13 +6875,14 @@ function reduceAction(state, action) {
|
|
|
6909
6875
|
const isolation = update.isolation ?? t.isolation;
|
|
6910
6876
|
const transport = update.transport;
|
|
6911
6877
|
const awaitingReason = update.awaitingReason;
|
|
6878
|
+
const checkpoint = update.checkpoint;
|
|
6912
6879
|
const continuable = update.continuable === true;
|
|
6913
6880
|
const awaitingApproval = update.awaitingApproval;
|
|
6914
6881
|
const mergeGate = update.mergeGate;
|
|
6915
6882
|
const forcedPastGate = update.forcedPastGate;
|
|
6916
|
-
if (update.status !== t.status || idleSince !== (t.idleSince ?? null) || !sameIsolation(isolation, t.isolation) || !sameTransport(transport, t.transport) || awaitingReason !== t.awaitingReason || continuable !== (t.continuable ?? false) || awaitingApproval !== t.awaitingApproval || !sameList(mergeGate, t.mergeGate) || !sameList(forcedPastGate, t.forcedPastGate)) {
|
|
6883
|
+
if (update.status !== t.status || idleSince !== (t.idleSince ?? null) || !sameIsolation(isolation, t.isolation) || !sameTransport(transport, t.transport) || awaitingReason !== t.awaitingReason || checkpoint !== t.checkpoint || continuable !== (t.continuable ?? false) || awaitingApproval !== t.awaitingApproval || !sameList(mergeGate, t.mergeGate) || !sameList(forcedPastGate, t.forcedPastGate)) {
|
|
6917
6884
|
changed = true;
|
|
6918
|
-
return { ...t, status: update.status, idleSince, isolation, transport, awaitingReason, continuable, awaitingApproval, mergeGate, forcedPastGate };
|
|
6885
|
+
return { ...t, status: update.status, idleSince, isolation, transport, awaitingReason, checkpoint, continuable, awaitingApproval, mergeGate, forcedPastGate };
|
|
6919
6886
|
}
|
|
6920
6887
|
return t;
|
|
6921
6888
|
});
|
|
@@ -6926,12 +6893,6 @@ function reduceAction(state, action) {
|
|
|
6926
6893
|
const status = runStatus(next, tasks);
|
|
6927
6894
|
return step({ ...next, status, busyLabel: status === "executing" ? runLabel(tasks, gate) : state.busyLabel });
|
|
6928
6895
|
}
|
|
6929
|
-
case "queueReady": {
|
|
6930
|
-
if (stale(state, action.sessionId)) return step(state);
|
|
6931
|
-
const sessionId = action.sessionId ?? state.sessionId;
|
|
6932
|
-
if (sessionId === null) return step(state);
|
|
6933
|
-
return step(state, [{ type: "processQueued", sessionId }]);
|
|
6934
|
-
}
|
|
6935
6896
|
case "isolationBlocked": {
|
|
6936
6897
|
if (stale(state, action.sessionId)) return step(state);
|
|
6937
6898
|
return step(blockedPicker(state, action.message, action.repos));
|
|
@@ -6964,6 +6925,14 @@ function reduceAction(state, action) {
|
|
|
6964
6925
|
busyLabel: ""
|
|
6965
6926
|
});
|
|
6966
6927
|
}
|
|
6928
|
+
case "executionLost": {
|
|
6929
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
6930
|
+
return step({
|
|
6931
|
+
...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."),
|
|
6932
|
+
status: "idle",
|
|
6933
|
+
busyLabel: ""
|
|
6934
|
+
});
|
|
6935
|
+
}
|
|
6967
6936
|
case "settingsLoaded":
|
|
6968
6937
|
return step(applySettings(state, action.settings));
|
|
6969
6938
|
case "modelsLoaded":
|
|
@@ -6998,7 +6967,7 @@ function reduceAction(state, action) {
|
|
|
6998
6967
|
busyLabel: "",
|
|
6999
6968
|
planApproved: false,
|
|
7000
6969
|
pendingApprovals: [],
|
|
7001
|
-
queuedPrompts:
|
|
6970
|
+
queuedPrompts: import_core34.EMPTY_HOLD,
|
|
7002
6971
|
stopArmed: false,
|
|
7003
6972
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
7004
6973
|
});
|
|
@@ -7065,7 +7034,7 @@ function sameGate(a, b) {
|
|
|
7065
7034
|
return a.paused === b.paused && JSON.stringify(a.handoff) === JSON.stringify(b.handoff);
|
|
7066
7035
|
}
|
|
7067
7036
|
function runStatus(state, tasks) {
|
|
7068
|
-
if (tasks.some(isTaskRunning)) return "executing";
|
|
7037
|
+
if (tasks.some(import_core24.isTaskRunning)) return "executing";
|
|
7069
7038
|
if (tasks.some((t) => t.status === "awaiting_user") || state.gate?.paused) return "executing";
|
|
7070
7039
|
return plannerInFlight(state) ? state.status : "idle";
|
|
7071
7040
|
}
|
|
@@ -7134,7 +7103,7 @@ function submit(state) {
|
|
|
7134
7103
|
return step(cleared, [{ type: "sendTaskMessage", sessionId: state.sessionId, taskId, text }]);
|
|
7135
7104
|
}
|
|
7136
7105
|
if (plannerInFlight(state)) {
|
|
7137
|
-
return step({ ...cleared, queuedPrompts: (0,
|
|
7106
|
+
return step({ ...cleared, queuedPrompts: (0, import_core34.holdPrompt)(state.queuedPrompts, text) });
|
|
7138
7107
|
}
|
|
7139
7108
|
const spoken = say(cleared, "user", text);
|
|
7140
7109
|
const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
|
|
@@ -7194,10 +7163,10 @@ function createApp(deps) {
|
|
|
7194
7163
|
|
|
7195
7164
|
// src/tui/effects.ts
|
|
7196
7165
|
var import_child_process4 = require("child_process");
|
|
7197
|
-
var
|
|
7166
|
+
var import_core36 = require("@ordewell/core");
|
|
7198
7167
|
|
|
7199
7168
|
// src/tui/inbound.ts
|
|
7200
|
-
var
|
|
7169
|
+
var import_core35 = require("@ordewell/core");
|
|
7201
7170
|
var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
|
|
7202
7171
|
"planner_message",
|
|
7203
7172
|
"planner_turn_started",
|
|
@@ -7412,7 +7381,8 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
7412
7381
|
idleSince: task.idleSince ?? null,
|
|
7413
7382
|
isolation: task.isolation,
|
|
7414
7383
|
transport: task.transport,
|
|
7415
|
-
awaitingReason: (0,
|
|
7384
|
+
awaitingReason: (0, import_core35.isAwaitingReason)(task.awaitingReason) ? task.awaitingReason : void 0,
|
|
7385
|
+
...typeof task.checkpoint === "string" && task.checkpoint ? { checkpoint: sanitize(task.checkpoint) } : {},
|
|
7416
7386
|
...task.continuable ? { continuable: true } : {},
|
|
7417
7387
|
awaitingApproval: typeof task.awaitingApproval === "number" && task.awaitingApproval > 0 ? task.awaitingApproval : void 0,
|
|
7418
7388
|
...task.mergeGate?.length ? { mergeGate: task.mergeGate.map(String) } : {},
|
|
@@ -7426,10 +7396,10 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
7426
7396
|
case "task_started":
|
|
7427
7397
|
dispatch({ type: "taskStarted", taskId: String(event.taskId), title: String(event.title ?? event.taskId), runner: event.runner, sessionId });
|
|
7428
7398
|
return;
|
|
7429
|
-
// The extension shows these in its checkpoint panel; here they are
|
|
7430
|
-
// transcript
|
|
7399
|
+
// The extension shows these in its checkpoint panel; here they are a
|
|
7400
|
+
// transcript line, and the task view carries the card that answers them.
|
|
7431
7401
|
case "checkpoint":
|
|
7432
|
-
dispatch({ type: "
|
|
7402
|
+
dispatch({ type: "taskCheckpoint", taskId: String(event.taskId), title: String(event.taskTitle), summary: event.summary, sessionId });
|
|
7433
7403
|
return;
|
|
7434
7404
|
case "review_needed":
|
|
7435
7405
|
dispatch({ type: "notice", message: "Plan needs your sign-off \u2014 /approve to continue." });
|
|
@@ -7437,13 +7407,6 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
7437
7407
|
case "review_approved":
|
|
7438
7408
|
dispatch({ type: "notice", message: "Plan approved." });
|
|
7439
7409
|
return;
|
|
7440
|
-
// The orchestrator paused fan-out because a structural edit is queued; drain
|
|
7441
|
-
// it so the planner reconciles the plan and dependents resume spawning.
|
|
7442
|
-
// Dropped on the floor, the queue suppresses every later tick() — which is
|
|
7443
|
-
// exactly "tasks did not fan out when the dependent ones finished."
|
|
7444
|
-
case "queue_ready":
|
|
7445
|
-
dispatch({ type: "queueReady", sessionId });
|
|
7446
|
-
return;
|
|
7447
7410
|
case "execution_complete":
|
|
7448
7411
|
dispatch({ type: "executionComplete", summary: event.summary, sessionId });
|
|
7449
7412
|
return;
|
|
@@ -7493,8 +7456,7 @@ async function runEffect(effect, deps) {
|
|
|
7493
7456
|
await perform(effect, deps);
|
|
7494
7457
|
} catch (err) {
|
|
7495
7458
|
if (!isConnectionRefused(err)) {
|
|
7496
|
-
|
|
7497
|
-
deps.dispatch({ type: "failed", message: explain(message) });
|
|
7459
|
+
deps.dispatch({ type: "failed", message: explain(err) });
|
|
7498
7460
|
return;
|
|
7499
7461
|
}
|
|
7500
7462
|
let revived = false;
|
|
@@ -7511,21 +7473,20 @@ async function runEffect(effect, deps) {
|
|
|
7511
7473
|
try {
|
|
7512
7474
|
await perform(effect, deps);
|
|
7513
7475
|
} catch (retryErr) {
|
|
7514
|
-
const message = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
7515
7476
|
deps.dispatch({
|
|
7516
7477
|
type: "failed",
|
|
7517
7478
|
// A fresh daemon holds no sessions, so the retry of anything
|
|
7518
7479
|
// session-scoped legitimately 404s. Say which of the two happened.
|
|
7519
|
-
message: isConnectionRefused(retryErr) ? describeConnectionRefused(deps.port) : explain(
|
|
7480
|
+
message: isConnectionRefused(retryErr) ? describeConnectionRefused(deps.port) : explain(retryErr)
|
|
7520
7481
|
});
|
|
7521
7482
|
}
|
|
7522
7483
|
}
|
|
7523
7484
|
}
|
|
7524
|
-
function explain(
|
|
7525
|
-
if (
|
|
7485
|
+
function explain(err) {
|
|
7486
|
+
if (err instanceof DaemonError && err.code === "session_not_found") {
|
|
7526
7487
|
return "This server is no longer holding that session \u2014 it was probably restarted. Reload it with /sessions.";
|
|
7527
7488
|
}
|
|
7528
|
-
return message;
|
|
7489
|
+
return err instanceof Error ? err.message : String(err);
|
|
7529
7490
|
}
|
|
7530
7491
|
function persistAfterDaemon(deps, env) {
|
|
7531
7492
|
for (const [key, value] of Object.entries(env)) deps.setEnvVar(key, value);
|
|
@@ -7581,26 +7542,20 @@ async function perform(effect, deps) {
|
|
|
7581
7542
|
timer.unref?.();
|
|
7582
7543
|
return;
|
|
7583
7544
|
}
|
|
7584
|
-
case "processQueued":
|
|
7585
|
-
await api.processQueued(effect.sessionId);
|
|
7586
|
-
await refreshPlan(deps, effect.sessionId);
|
|
7587
|
-
return;
|
|
7588
7545
|
case "taskAction": {
|
|
7589
7546
|
const { sessionId, taskId, action } = effect;
|
|
7590
|
-
const request = action === "
|
|
7547
|
+
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);
|
|
7591
7548
|
await (effect.watch ? withExecutionStream(deps, sessionId, request) : request());
|
|
7592
7549
|
await refreshPlan(deps, sessionId);
|
|
7593
7550
|
return;
|
|
7594
7551
|
}
|
|
7595
|
-
case "addTask":
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
prompt: effect.title,
|
|
7600
|
-
type: "ai"
|
|
7601
|
-
});
|
|
7552
|
+
case "addTask": {
|
|
7553
|
+
const fields = (0, import_core36.newTaskFields)(effect.title);
|
|
7554
|
+
if (!fields) return;
|
|
7555
|
+
await api.addTask(effect.sessionId, { ...fields });
|
|
7602
7556
|
await refreshPlan(deps, effect.sessionId);
|
|
7603
7557
|
return;
|
|
7558
|
+
}
|
|
7604
7559
|
case "updateTask":
|
|
7605
7560
|
await api.updateTask(effect.sessionId, effect.taskId, effect.changes);
|
|
7606
7561
|
await refreshPlan(deps, effect.sessionId);
|
|
@@ -7627,6 +7582,13 @@ async function perform(effect, deps) {
|
|
|
7627
7582
|
}
|
|
7628
7583
|
return;
|
|
7629
7584
|
}
|
|
7585
|
+
// The card goes when the daemon's next status says the task left the
|
|
7586
|
+
// checkpoint; a refusal (settled elsewhere, withdrawn) surfaces as the
|
|
7587
|
+
// failure the daemon words.
|
|
7588
|
+
case "answerTaskCheckpoint":
|
|
7589
|
+
if (effect.answer === "approve") await api.approveTaskCheckpoint(effect.sessionId, effect.taskId);
|
|
7590
|
+
else await api.rejectTaskCheckpoint(effect.sessionId, effect.taskId, effect.reason);
|
|
7591
|
+
return;
|
|
7630
7592
|
// A task with a saved log ran structured, whatever its `transport` says
|
|
7631
7593
|
// after a reload; it has no tmux window, so open the log rather than fail.
|
|
7632
7594
|
case "openTaskTerminal": {
|
|
@@ -7676,6 +7638,16 @@ async function perform(effect, deps) {
|
|
|
7676
7638
|
if (!removed) dispatch({ type: "notice", message: "That message was already delivered to the task." });
|
|
7677
7639
|
return;
|
|
7678
7640
|
}
|
|
7641
|
+
// The task log shows the forced message at the head of the queue, then
|
|
7642
|
+
// the interrupt, then the turn it opens; nothing to dispatch here.
|
|
7643
|
+
case "forceSendTaskMessage":
|
|
7644
|
+
await api.forceSendTaskMessage(effect.sessionId, effect.taskId, effect.text);
|
|
7645
|
+
return;
|
|
7646
|
+
case "forceSendQueuedTaskMessage": {
|
|
7647
|
+
const { sent } = await api.forceSendQueuedTaskMessage(effect.sessionId, effect.taskId, effect.messageId);
|
|
7648
|
+
if (!sent) dispatch({ type: "notice", message: "The runner already has that message; it reads it after its current step." });
|
|
7649
|
+
return;
|
|
7650
|
+
}
|
|
7679
7651
|
case "interruptTask":
|
|
7680
7652
|
await api.interruptTask(effect.sessionId, effect.taskId);
|
|
7681
7653
|
dispatch({ type: "notice", message: "Interrupting the task\u2026" });
|
|
@@ -7699,11 +7671,11 @@ async function perform(effect, deps) {
|
|
|
7699
7671
|
dispatch({ type: "notice", message: `Orchestrator model set to ${effect.modelId}.` });
|
|
7700
7672
|
return;
|
|
7701
7673
|
case "setPlanner": {
|
|
7702
|
-
const meta =
|
|
7674
|
+
const meta = import_core36.ALL_PROVIDERS[effect.provider];
|
|
7703
7675
|
if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
|
|
7704
7676
|
const env = { AI_PROVIDER: effect.provider };
|
|
7705
7677
|
const settings = await api.updateSettings({ env });
|
|
7706
|
-
const recall2 =
|
|
7678
|
+
const recall2 = plannerSwitchRecall(settings);
|
|
7707
7679
|
persistAfterDaemon(deps, { ...env, ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
|
|
7708
7680
|
dispatch({
|
|
7709
7681
|
type: "settingsLoaded",
|
|
@@ -7711,7 +7683,7 @@ async function perform(effect, deps) {
|
|
|
7711
7683
|
});
|
|
7712
7684
|
dispatch({
|
|
7713
7685
|
type: "notice",
|
|
7714
|
-
message: `${(0,
|
|
7686
|
+
message: `${(0, import_core36.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
|
|
7715
7687
|
});
|
|
7716
7688
|
await loadModels(deps);
|
|
7717
7689
|
return;
|
|
@@ -7723,7 +7695,7 @@ async function perform(effect, deps) {
|
|
|
7723
7695
|
dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
|
|
7724
7696
|
return;
|
|
7725
7697
|
case "setApiKey": {
|
|
7726
|
-
const meta =
|
|
7698
|
+
const meta = import_core36.ALL_PROVIDERS[effect.provider];
|
|
7727
7699
|
if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
|
|
7728
7700
|
await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
|
|
7729
7701
|
deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
|
|
@@ -7761,7 +7733,7 @@ async function perform(effect, deps) {
|
|
|
7761
7733
|
return;
|
|
7762
7734
|
case "setAutonomous":
|
|
7763
7735
|
deps.setEnvVar("ORDEWELL_AUTONOMOUS_MODE", String(effect.enabled));
|
|
7764
|
-
dispatch({ type: "notice", message: `Autonomy level: ${(0,
|
|
7736
|
+
dispatch({ type: "notice", message: `Autonomy level: ${(0, import_core36.autonomyLevelLabel)(effect.enabled)} for new plans.` });
|
|
7765
7737
|
return;
|
|
7766
7738
|
case "setMouseCapture":
|
|
7767
7739
|
deps.setMouseCapture(effect.enabled);
|
|
@@ -7895,7 +7867,7 @@ function osc52(text) {
|
|
|
7895
7867
|
function copySelection(deps, text) {
|
|
7896
7868
|
if (!text) return;
|
|
7897
7869
|
const lines = text.split("\n").length;
|
|
7898
|
-
const command = (0,
|
|
7870
|
+
const command = (0, import_core36.clipboardCopyCommand)(deps.hasBin);
|
|
7899
7871
|
if (command) {
|
|
7900
7872
|
try {
|
|
7901
7873
|
(deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
|
|
@@ -7919,9 +7891,14 @@ async function withExecutionStream(deps, sessionId, request) {
|
|
|
7919
7891
|
const inbound = inboundFor(deps.api, deps.dispatch, sessionId);
|
|
7920
7892
|
const stream = deps.api.streamExecution(sessionId, inbound.execution(), settleReady);
|
|
7921
7893
|
void stream.catch(settleReady);
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7894
|
+
try {
|
|
7895
|
+
await streamReady;
|
|
7896
|
+
await request();
|
|
7897
|
+
} catch (err) {
|
|
7898
|
+
deps.api.closeExecutionStream(stream);
|
|
7899
|
+
throw err;
|
|
7900
|
+
}
|
|
7901
|
+
if (await stream === "lost") deps.dispatch({ type: "executionLost", sessionId });
|
|
7925
7902
|
}
|
|
7926
7903
|
async function converse(deps, sessionId, call) {
|
|
7927
7904
|
const inbound = inboundFor(deps.api, deps.dispatch, sessionId);
|
|
@@ -8012,6 +7989,7 @@ var CONTROL2 = {
|
|
|
8012
7989
|
"": "ctrl-o",
|
|
8013
7990
|
"": "ctrl-p",
|
|
8014
7991
|
"": "ctrl-r",
|
|
7992
|
+
"": "ctrl-s",
|
|
8015
7993
|
"": "ctrl-t",
|
|
8016
7994
|
"": "ctrl-u",
|
|
8017
7995
|
"": "ctrl-w",
|
|
@@ -8285,7 +8263,7 @@ async function handleTui(subArgs) {
|
|
|
8285
8263
|
}
|
|
8286
8264
|
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
8287
8265
|
try {
|
|
8288
|
-
(0,
|
|
8266
|
+
(0, import_core37.assertWorkspaceExists)(workspace);
|
|
8289
8267
|
} catch {
|
|
8290
8268
|
console.error(
|
|
8291
8269
|
`Workspace "${workspace}" does not exist.
|
|
@@ -8294,7 +8272,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8294
8272
|
);
|
|
8295
8273
|
process.exit(1);
|
|
8296
8274
|
}
|
|
8297
|
-
registerSkillCommands((0,
|
|
8275
|
+
registerSkillCommands((0, import_core37.createSkillsService)(workspace).listSkills());
|
|
8298
8276
|
const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
|
|
8299
8277
|
const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
|
|
8300
8278
|
const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
|
|
@@ -8311,7 +8289,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8311
8289
|
workspace,
|
|
8312
8290
|
port,
|
|
8313
8291
|
dispatch: (action) => app.dispatch(action),
|
|
8314
|
-
newSessionId: () => (0,
|
|
8292
|
+
newSessionId: () => (0, import_core37.mintSessionId)(),
|
|
8315
8293
|
setEnvVar: (key, value) => {
|
|
8316
8294
|
writeEnvVar(findEnvFile(), key, value);
|
|
8317
8295
|
process.env[key] = value;
|
|
@@ -8362,6 +8340,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8362
8340
|
app.dispatch({ type: "resize", rows: r, cols: c });
|
|
8363
8341
|
});
|
|
8364
8342
|
process.on("uncaughtException", (err) => {
|
|
8343
|
+
terminal?.close();
|
|
8365
8344
|
console.error(`Fatal: ${err.message}`);
|
|
8366
8345
|
shutdown(1);
|
|
8367
8346
|
});
|
|
@@ -8402,6 +8381,7 @@ var COMMANDS = {
|
|
|
8402
8381
|
"retry": handleRetry,
|
|
8403
8382
|
"cancel": handleCancel,
|
|
8404
8383
|
continue: handleContinue,
|
|
8384
|
+
checkpoint: handleCheckpoint,
|
|
8405
8385
|
"add-task": handleAddTask,
|
|
8406
8386
|
"remove-task": handleRemoveTask,
|
|
8407
8387
|
"task-runner": handleTaskRunner,
|
|
@@ -8435,7 +8415,7 @@ function cliVersion() {
|
|
|
8435
8415
|
// src/main.ts
|
|
8436
8416
|
var TUI_FLAGS = /* @__PURE__ */ new Set(["--workspace", "--port"]);
|
|
8437
8417
|
async function main() {
|
|
8438
|
-
(0,
|
|
8418
|
+
(0, import_core38.migrateOldConfigDir)();
|
|
8439
8419
|
loadEnvFile();
|
|
8440
8420
|
const argv = process.argv.slice(2);
|
|
8441
8421
|
if (TUI_FLAGS.has(argv[0] ?? "")) argv.unshift("tui");
|