@ordewell/cli 0.7.1 → 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.
Files changed (2) hide show
  1. package/dist/main.js +328 -486
  2. 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 import_core37 = require("@ordewell/core");
27
+ var import_core38 = require("@ordewell/core");
28
28
 
29
29
  // src/utils/env.ts
30
30
  var import_fs = require("fs");
@@ -406,15 +406,30 @@ var import_http2 = __toESM(require("http"));
406
406
  var import_ws = __toESM(require("ws"));
407
407
  var import_core3 = require("@ordewell/core");
408
408
  var DEFAULT_HTTP_TIMEOUT_MS = 15 * 60 * 1e3;
409
- var WorkspaceInitNeededError = class extends Error {
410
- constructor(message, workspace) {
409
+ var DaemonError = class extends Error {
410
+ constructor(message, status, code) {
411
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");
412
422
  this.workspace = workspace;
413
423
  this.name = "WorkspaceInitNeededError";
414
424
  }
415
425
  workspace;
416
426
  };
417
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
+ }
418
433
  var ApiClient = class _ApiClient {
419
434
  /** Where a CLI invocation's sessions live, when a client is built without one: `--workspace`, else the cwd. */
420
435
  static defaultWorkspace;
@@ -422,6 +437,7 @@ var ApiClient = class _ApiClient {
422
437
  workspace;
423
438
  /** A session's open execution stream, so a new one can end it: one run is reported once. */
424
439
  executionStreams = /* @__PURE__ */ new Map();
440
+ executionStreamEnds = /* @__PURE__ */ new WeakMap();
425
441
  constructor(port, workspace) {
426
442
  this.port = port || DEFAULT_PORT;
427
443
  this.workspace = workspace ?? _ApiClient.defaultWorkspace ?? process.cwd();
@@ -436,7 +452,7 @@ var ApiClient = class _ApiClient {
436
452
  }
437
453
  /**
438
454
  * A daemon holds only the sessions adopted since it started, so after a
439
- * restart every session-scoped call answered "Session not found" until the
455
+ * restart every session-scoped call answered "session not found" until the
440
456
  * user ran `ordewell sessions load`. The saved session is adopted here
441
457
  * instead — idempotent for one the daemon still holds — and the call made
442
458
  * once more; a 404 means the first attempt never ran, so the retry cannot
@@ -445,11 +461,17 @@ var ApiClient = class _ApiClient {
445
461
  async httpRequest(method, urlPath, body) {
446
462
  const res = await this.rawRequest(method, urlPath, body);
447
463
  const sessionId = urlPath.match(SESSION_PATH)?.[1];
448
- const missing = res.status === 404 && res.data?.error === "Session not found";
464
+ const missing = res.status === 404 && res.data?.code === "session_not_found";
449
465
  if (!missing || !sessionId || urlPath.split("?")[0].endsWith("/load")) return res;
450
466
  const adopted2 = await this.rawRequest("POST", `/api/sessions/${sessionId}/load?workspace=${encodeURIComponent(this.workspace)}`);
451
467
  return adopted2.status === 200 ? this.rawRequest(method, urlPath, body) : res;
452
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
+ }
453
475
  rawRequest(method, urlPath, body) {
454
476
  return new Promise((resolve3, reject) => {
455
477
  const url = new URL(urlPath, `http://127.0.0.1:${this.port}`);
@@ -492,15 +514,12 @@ var ApiClient = class _ApiClient {
492
514
  });
493
515
  }
494
516
  async generatePlan(goal, runners2, workspace, sessionId = (0, import_core3.mintSessionId)()) {
495
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/generate`, {
517
+ const { plan, models, modelsByRunner } = await this.call("POST", `/api/plans/${sessionId}/generate`, "Plan generation failed", {
496
518
  goal,
497
519
  runners: runners2 || void 0,
498
520
  workspace
499
521
  });
500
- if (res.status !== 200) {
501
- throw new Error(res.data?.error || "Plan generation failed");
502
- }
503
- return { sessionId, plan: res.data.plan, models: res.data.models, modelsByRunner: res.data.modelsByRunner };
522
+ return { sessionId, plan, models, modelsByRunner };
504
523
  }
505
524
  /**
506
525
  * Open the ADR-0002 planner dialogue. Unlike `generatePlan`, this path honours
@@ -508,58 +527,30 @@ var ApiClient = class _ApiClient {
508
527
  * (empty `tasks`, last word in `conversationHistory`) rather than a committed plan.
509
528
  */
510
529
  async startConversation(sessionId, goal, runners2, workspace, allowInit) {
511
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/converse/start`, {
530
+ const res = await this.call("POST", `/api/plans/${sessionId}/converse/start`, "Planning failed", {
512
531
  goal,
513
532
  runners: runners2 || void 0,
514
533
  workspace,
515
534
  allowInit
516
535
  });
517
- if (res.status !== 200) {
518
- if (res.data?.code === "workspace_not_a_project") {
519
- throw new WorkspaceInitNeededError(res.data.error || "Not a project directory", res.data.workspace || workspace || "");
520
- }
521
- throw new Error(res.data?.error || "Planning failed");
522
- }
523
- return res.data.plan;
536
+ return res.plan;
524
537
  }
525
538
  async sendConversationMessage(sessionId, message) {
526
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/converse/message`, { message });
527
- if (res.status !== 200) {
528
- throw new Error(res.data?.error || "Planner message failed");
529
- }
530
- return res.data.plan;
539
+ const res = await this.call("POST", `/api/plans/${sessionId}/converse/message`, "Planner message failed", { message });
540
+ return res.plan;
531
541
  }
532
- async getRunners() {
533
- const res = await this.httpRequest("GET", "/api/runners");
534
- if (res.status !== 200) {
535
- throw new Error(res.data?.error || "Failed to fetch runners");
536
- }
537
- return res.data;
542
+ getRunners() {
543
+ return this.call("GET", "/api/runners", "Failed to fetch runners");
538
544
  }
539
545
  /** The full provider catalog the daemon has discovered (used by the TUI model picker). */
540
- async getModels() {
541
- const res = await this.httpRequest("GET", "/api/models");
542
- if (res.status !== 200) {
543
- throw new Error(res.data?.error || "Failed to fetch models");
544
- }
545
- return res.data;
546
+ getModels() {
547
+ return this.call("GET", "/api/models", "Failed to fetch models");
546
548
  }
547
- async setRunnerEnabled(runner, enabled) {
548
- const res = await this.httpRequest("PUT", `/api/runners/${runner}`, { enabled });
549
- if (res.status !== 200) {
550
- throw new Error(res.data?.error || `Failed to ${enabled ? "enable" : "disable"} ${runner}`);
551
- }
552
- return res.data;
549
+ setRunnerEnabled(runner, enabled) {
550
+ return this.call("PUT", `/api/runners/${runner}`, `Failed to ${enabled ? "enable" : "disable"} ${runner}`, { enabled });
553
551
  }
554
- async executePlan(sessionId) {
555
- const res = await this.httpRequest(
556
- "POST",
557
- `/api/plans/${sessionId}/execute`
558
- );
559
- if (res.status !== 200) {
560
- throw new Error(res.data?.error || "Execute failed");
561
- }
562
- return res.data;
552
+ executePlan(sessionId) {
553
+ return this.call("POST", `/api/plans/${sessionId}/execute`, "Execute failed");
563
554
  }
564
555
  /**
565
556
  * Sign off a plan waiting on `review_needed` and let it continue.
@@ -568,228 +559,111 @@ var ApiClient = class _ApiClient {
568
559
  * `resetForRun`) before approving, which is right for starting a plan and
569
560
  * wrong for releasing one that is already part-way through a review pause.
570
561
  */
571
- async approveReview(sessionId) {
572
- const res = await this.httpRequest(
573
- "POST",
574
- `/api/plans/${sessionId}/review/approve`
575
- );
576
- if (res.status !== 200) {
577
- throw new Error(res.data?.error || "Approve failed");
578
- }
579
- return res.data;
562
+ approveReview(sessionId) {
563
+ return this.call("POST", `/api/plans/${sessionId}/review/approve`, "Approve failed");
580
564
  }
581
- async markTaskComplete(sessionId, taskId) {
582
- const res = await this.httpRequest(
583
- "POST",
584
- `/api/plans/${sessionId}/tasks/${taskId}/complete`
585
- );
586
- if (res.status !== 200) {
587
- throw new Error(res.data?.error || "Mark complete failed");
588
- }
589
- return res.data;
565
+ markTaskComplete(sessionId, taskId) {
566
+ return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/complete`, "Mark complete failed");
590
567
  }
591
- async markTaskIncomplete(sessionId, taskId) {
592
- const res = await this.httpRequest(
593
- "POST",
594
- `/api/plans/${sessionId}/tasks/${taskId}/uncomplete`
595
- );
596
- if (res.status !== 200) {
597
- throw new Error(res.data?.error || "Mark not done failed");
598
- }
599
- return res.data;
568
+ markTaskIncomplete(sessionId, taskId) {
569
+ return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/uncomplete`, "Mark not done failed");
600
570
  }
601
571
  /** run | force-start | retry | cancel — real orchestrator work, not a status patch. */
602
- async taskControl(sessionId, taskId, action) {
603
- const res = await this.httpRequest(
604
- "POST",
605
- `/api/plans/${sessionId}/tasks/${taskId}/${action}`
606
- );
607
- if (res.status !== 200) {
608
- throw new Error(res.data?.error || `${action} failed`);
609
- }
610
- return res.data;
572
+ taskControl(sessionId, taskId, action) {
573
+ return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/${action}`, `${action} failed`);
611
574
  }
612
575
  /** A user message to a structured task: delivered now if it waits for input, else queued behind its turn. */
613
- async sendTaskMessage(sessionId, taskId, text) {
614
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages`, { text });
615
- if (res.status !== 200) {
616
- throw new Error(res.data?.error || "Send message failed");
617
- }
618
- return res.data;
576
+ sendTaskMessage(sessionId, taskId, text) {
577
+ return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages`, "Send message failed", { text });
619
578
  }
620
579
  /** Force send: interrupt the task's running turn and deliver `text` next, ahead of anything queued (ADR-0023, F1). */
621
- async forceSendTaskMessage(sessionId, taskId, text) {
622
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages/now`, { text });
623
- if (res.status !== 200) {
624
- throw new Error(res.data?.error || "Send now failed");
625
- }
626
- return res.data;
580
+ forceSendTaskMessage(sessionId, taskId, text) {
581
+ return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages/now`, "Send now failed", { text });
627
582
  }
628
583
  /** Force send a message still queued; `sent` is false once the runner already has it. */
629
- async forceSendQueuedTaskMessage(sessionId, taskId, messageId) {
630
- const res = await this.httpRequest(
631
- "POST",
632
- `/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}/now`
633
- );
634
- if (res.status !== 200) {
635
- throw new Error(res.data?.error || "Send now failed");
636
- }
637
- return res.data;
584
+ forceSendQueuedTaskMessage(sessionId, taskId, messageId) {
585
+ return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}/now`, "Send now failed");
638
586
  }
639
587
  /** Continue a finished structured task in its saved session, with `text` as its next turn. */
640
- async continueTask(sessionId, taskId, text) {
641
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks/${taskId}/continue`, { text });
642
- if (res.status !== 200) {
643
- throw new Error(res.data?.error || "Continue failed");
644
- }
645
- return res.data;
588
+ continueTask(sessionId, taskId, text) {
589
+ return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/continue`, "Continue failed", { text });
646
590
  }
647
591
  /** `removed` is false once the message was already delivered. */
648
- async removeQueuedTaskMessage(sessionId, taskId, messageId) {
649
- const res = await this.httpRequest(
650
- "DELETE",
651
- `/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}`
652
- );
653
- if (res.status !== 200) {
654
- throw new Error(res.data?.error || "Remove message failed");
655
- }
656
- return res.data;
592
+ removeQueuedTaskMessage(sessionId, taskId, messageId) {
593
+ return this.call("DELETE", `/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}`, "Remove message failed");
657
594
  }
658
- async interruptTask(sessionId, taskId) {
659
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks/${taskId}/interrupt`);
660
- if (res.status !== 200) {
661
- throw new Error(res.data?.error || "Interrupt failed");
662
- }
663
- return res.data;
595
+ interruptTask(sessionId, taskId) {
596
+ return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/interrupt`, "Interrupt failed");
664
597
  }
665
598
  /** Let a task go on from the checkpoint it waits at; refused, with the reason, when none waits. */
666
- async approveTaskCheckpoint(sessionId, taskId) {
667
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks/${taskId}/checkpoint/approve`);
668
- if (res.status !== 200) {
669
- throw new Error(res.data?.error || "Approve checkpoint failed");
670
- }
671
- return res.data;
599
+ approveTaskCheckpoint(sessionId, taskId) {
600
+ return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/checkpoint/approve`, "Approve checkpoint failed");
672
601
  }
673
602
  /** Turn a task back at its checkpoint; `reason` is what the agent is told. */
674
- async rejectTaskCheckpoint(sessionId, taskId, reason) {
675
- const res = await this.httpRequest(
676
- "POST",
677
- `/api/plans/${sessionId}/tasks/${taskId}/checkpoint/reject`,
678
- reason ? { reason } : {}
679
- );
680
- if (res.status !== 200) {
681
- throw new Error(res.data?.error || "Reject checkpoint failed");
682
- }
683
- return res.data;
603
+ rejectTaskCheckpoint(sessionId, taskId, reason) {
604
+ return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/checkpoint/reject`, "Reject checkpoint failed", reason ? { reason } : {});
684
605
  }
685
- async addTask(sessionId, task) {
686
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks`, task);
687
- if (res.status !== 200) {
688
- throw new Error(res.data?.error || "Add task failed");
689
- }
690
- return res.data;
606
+ addTask(sessionId, task) {
607
+ return this.call("POST", `/api/plans/${sessionId}/tasks`, "Add task failed", task);
691
608
  }
692
- async updateTask(sessionId, taskId, changes) {
693
- const res = await this.httpRequest("PUT", `/api/plans/${sessionId}/tasks/${taskId}`, changes);
694
- if (res.status !== 200) {
695
- throw new Error(res.data?.error || "Update task failed");
696
- }
697
- return res.data;
609
+ updateTask(sessionId, taskId, changes) {
610
+ return this.call("PUT", `/api/plans/${sessionId}/tasks/${taskId}`, "Update task failed", changes);
698
611
  }
699
- async removeTask(sessionId, taskId) {
700
- const res = await this.httpRequest("DELETE", `/api/plans/${sessionId}/tasks/${taskId}`);
701
- if (res.status !== 200) {
702
- throw new Error(res.data?.error || "Remove task failed");
703
- }
704
- return res.data;
612
+ removeTask(sessionId, taskId) {
613
+ return this.call("DELETE", `/api/plans/${sessionId}/tasks/${taskId}`, "Remove task failed");
705
614
  }
706
615
  /**
707
616
  * Make a saved session live on the server. `getSession` only reads the file;
708
617
  * until the session is adopted there is no orchestrator behind it, so
709
- * execute/retry/cancel answer "Session not found".
618
+ * execute/retry/cancel answer `session_not_found`.
710
619
  */
711
620
  async adoptSession(sessionId, workspace) {
712
621
  const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
713
- const res = await this.httpRequest("POST", `/api/sessions/${sessionId}/load${qs}`);
714
- if (res.status !== 200) {
715
- throw new Error(res.data?.error || "Failed to load session");
716
- }
717
- 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 ?? "" };
718
624
  }
719
- async deleteSession(sessionId, workspace) {
625
+ deleteSession(sessionId, workspace) {
720
626
  const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
721
- const res = await this.httpRequest("DELETE", `/api/sessions/${sessionId}${qs}`);
722
- if (res.status !== 200) {
723
- throw new Error(res.data?.error || "Delete session failed");
724
- }
725
- return res.data;
627
+ return this.call("DELETE", `/api/sessions/${sessionId}${qs}`, "Delete session failed");
726
628
  }
727
629
  /** Copy the conversation and its tasks into a new session the daemon has already adopted. */
728
- async forkConversation(sessionId) {
729
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/conversation/fork`);
730
- if (res.status !== 200) {
731
- throw new Error(res.data?.error || "Fork failed");
732
- }
733
- return { sessionId: res.data.sessionId, goal: res.data.goal, plan: res.data.plan };
630
+ forkConversation(sessionId) {
631
+ return this.call("POST", `/api/plans/${sessionId}/conversation/fork`, "Fork failed");
734
632
  }
735
633
  async rewindTargets(sessionId) {
736
- const res = await this.httpRequest("GET", `/api/plans/${sessionId}/conversation/rewind-targets`);
737
- if (res.status !== 200) {
738
- throw new Error(res.data?.error || "Failed to list rewind targets");
739
- }
740
- 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;
741
636
  }
742
637
  /**
743
638
  * Fork the conversation from just before the user message at `index` (a
744
639
  * transcript position) into a session the daemon has already adopted. The
745
640
  * original is left as it was; `rewoundMessage` is that message in full.
746
641
  */
747
- async rewindConversation(sessionId, index) {
748
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/conversation/rewind`, { index });
749
- if (res.status !== 200) {
750
- throw new Error(res.data?.error || "Rewind failed");
751
- }
752
- return { sessionId: res.data.sessionId, goal: res.data.goal, plan: res.data.plan, rewoundMessage: res.data.rewoundMessage };
642
+ rewindConversation(sessionId, index) {
643
+ return this.call("POST", `/api/plans/${sessionId}/conversation/rewind`, "Rewind failed", { index });
753
644
  }
754
645
  /**
755
646
  * Condense the conversation into a summary. One planner call, so it can take
756
647
  * as long as a reply; a refusal or failure leaves the conversation as it was.
757
648
  */
758
- async compactConversation(sessionId) {
759
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/conversation/compact`);
760
- if (res.status !== 200) {
761
- throw new Error(res.data?.error || "Compaction failed");
762
- }
763
- return { plan: res.data.plan, summary: res.data.summary, keptMessages: res.data.keptMessages };
649
+ compactConversation(sessionId) {
650
+ return this.call("POST", `/api/plans/${sessionId}/conversation/compact`, "Compaction failed");
764
651
  }
765
652
  /** The dependencies a task waits on at its merge gate (ADR-0020); empty when nothing gates it. */
766
653
  async getMergeGate(sessionId, taskId) {
767
- const res = await this.httpRequest(
768
- "GET",
769
- `/api/plans/${sessionId}/tasks/${taskId}/merge-gate`
770
- );
771
- if (res.status !== 200) {
772
- throw new Error(res.data?.error || "Could not read the merge gate");
773
- }
774
- return res.data.mergeGate ?? [];
654
+ const res = await this.call("GET", `/api/plans/${sessionId}/tasks/${taskId}/merge-gate`, "Could not read the merge gate");
655
+ return res.mergeGate ?? [];
775
656
  }
776
657
  async reviewRunDiff(sessionId) {
777
- const res = await this.httpRequest("GET", `/api/plans/${sessionId}/isolation/diff`);
778
- if (res.status !== 200) {
779
- throw new Error(res.data?.error || "Could not read the diff");
780
- }
781
- 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;
782
660
  }
783
661
  /**
784
662
  * A conflict or a block is an outcome, not an error. Passed on whole: which
785
663
  * repos blocked the merge, or landed before it stopped, is the answer.
786
664
  */
787
- async mergeRun(sessionId) {
788
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/isolation/merge`);
789
- if (res.status !== 200) {
790
- throw new Error(res.data?.error || "Merge failed");
791
- }
792
- return res.data;
665
+ mergeRun(sessionId) {
666
+ return this.call("POST", `/api/plans/${sessionId}/isolation/merge`, "Merge failed");
793
667
  }
794
668
  discardRun(sessionId) {
795
669
  return this.isolationAction(sessionId, "discard", "Discard failed");
@@ -804,17 +678,11 @@ var ApiClient = class _ApiClient {
804
678
  return this.isolationAction(sessionId, "run-without", "Could not continue without isolation");
805
679
  }
806
680
  async isolationAction(sessionId, segment, failure) {
807
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/isolation/${segment}`);
808
- if (res.status !== 200) {
809
- throw new Error(res.data?.error || failure);
810
- }
681
+ await this.call("POST", `/api/plans/${sessionId}/isolation/${segment}`, failure);
811
682
  }
812
683
  async resolveConflictAsTask(sessionId, taskId) {
813
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks/${taskId}/resolve-conflict`);
814
- if (res.status !== 200) {
815
- throw new Error(res.data?.error || "Could not add a resolver task");
816
- }
817
- 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;
818
686
  }
819
687
  async closeSession(sessionId) {
820
688
  const res = await this.httpRequest("POST", `/api/sessions/${sessionId}/close`);
@@ -829,42 +697,26 @@ var ApiClient = class _ApiClient {
829
697
  const res = await this.httpRequest("POST", `/api/plans/${sessionId}/planning/stop`);
830
698
  return res.data;
831
699
  }
832
- async processQueued(sessionId) {
833
- const res = await this.httpRequest("POST", `/api/plans/${sessionId}/process-queued`);
834
- if (res.status !== 200) {
835
- throw new Error(res.data?.error || "Process queued failed");
836
- }
837
- return res.data;
838
- }
839
700
  async getSessions(workspace) {
840
701
  const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
841
702
  const res = await this.httpRequest("GET", `/api/sessions${qs}`);
842
703
  return res.data || [];
843
704
  }
844
- async getSession(sessionId, workspace) {
705
+ getSession(sessionId, workspace) {
845
706
  const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
846
- const res = await this.httpRequest(
847
- "GET",
848
- `/api/sessions/${sessionId}${qs}`
849
- );
850
- if (res.status !== 200) {
851
- throw new Error(res.data?.error || "Session not found");
852
- }
853
- return res.data;
707
+ return this.call("GET", `/api/sessions/${sessionId}${qs}`, "Session not found");
854
708
  }
855
709
  /** The attempts of a structured task that have a saved log, oldest first. */
856
710
  async getTaskLogAttempts(sessionId, taskId, workspace) {
857
711
  const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
858
- const res = await this.httpRequest("GET", `/api/sessions/${sessionId}/tasks/${encodeURIComponent(taskId)}/log${qs}`);
859
- if (res.status !== 200) throw new Error(res.data?.error || "Could not read the task log");
860
- 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;
861
714
  }
862
715
  /** One attempt's saved log, for `replayTaskLog`. */
863
716
  async getTaskLog(sessionId, taskId, attempt, workspace) {
864
717
  const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
865
- const res = await this.httpRequest("GET", `/api/sessions/${sessionId}/tasks/${encodeURIComponent(taskId)}/log/${attempt}${qs}`);
866
- if (res.status !== 200) throw new Error(res.data?.error || "Could not read the task log");
867
- 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;
868
720
  }
869
721
  /**
870
722
  * The one place a session socket is built, so the token travels on both
@@ -895,8 +747,14 @@ var ApiClient = class _ApiClient {
895
747
  });
896
748
  return socket;
897
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
+ }
898
754
  streamExecution(sessionId, onEvent, onReady) {
899
- return new Promise((resolve3, reject) => {
755
+ let endStream = () => {
756
+ };
757
+ const stream = new Promise((resolve3, reject) => {
900
758
  this.executionStreams.get(sessionId)?.();
901
759
  const socket = this.openSessionSocket(sessionId);
902
760
  let resolved = false;
@@ -912,6 +770,7 @@ var ApiClient = class _ApiClient {
912
770
  resolve3();
913
771
  };
914
772
  this.executionStreams.set(sessionId, end);
773
+ endStream = end;
915
774
  socket.on("open", () => {
916
775
  opened = true;
917
776
  onReady?.();
@@ -928,8 +787,12 @@ var ApiClient = class _ApiClient {
928
787
  socket.on("error", (err) => {
929
788
  forget();
930
789
  if (!opened) onReady?.(err);
931
- if (!resolved) {
932
- resolved = true;
790
+ if (resolved) return;
791
+ resolved = true;
792
+ if (opened) {
793
+ socket.close();
794
+ resolve3("lost");
795
+ } else {
933
796
  reject(err);
934
797
  }
935
798
  });
@@ -938,10 +801,12 @@ var ApiClient = class _ApiClient {
938
801
  if (!opened) onReady?.(new Error("Execution stream closed before connecting"));
939
802
  if (!resolved) {
940
803
  resolved = true;
941
- resolve3();
804
+ resolve3("lost");
942
805
  }
943
806
  });
944
807
  });
808
+ this.executionStreamEnds.set(stream, endStream);
809
+ return stream;
945
810
  }
946
811
  /**
947
812
  * Answer an approval prompt: the planner's with a yes/no, a task runner's
@@ -954,7 +819,7 @@ var ApiClient = class _ApiClient {
954
819
  `/api/approvals/${encodeURIComponent(sessionId)}/${encodeURIComponent(approvalId)}`,
955
820
  typeof answer === "boolean" ? { granted: answer } : answer
956
821
  );
957
- if (status !== 200) throw new Error(`Failed to answer approval ${approvalId} (HTTP ${status})`);
822
+ if (status !== 200) throw new DaemonError(`Failed to answer approval ${approvalId} (HTTP ${status})`, status, data.code);
958
823
  return data;
959
824
  }
960
825
  /**
@@ -979,33 +844,17 @@ var ApiClient = class _ApiClient {
979
844
  });
980
845
  return { ready, close: () => socket.close() };
981
846
  }
982
- async getCommands() {
983
- const res = await this.httpRequest("GET", "/api/commands");
984
- if (res.status !== 200) {
985
- throw new Error(res.data?.error || "Failed to fetch commands");
986
- }
987
- return res.data;
847
+ getCommands() {
848
+ return this.call("GET", "/api/commands", "Failed to fetch commands");
988
849
  }
989
- async sendCommand(name, args = {}) {
990
- const res = await this.httpRequest("POST", `/api/commands/${name}`, { args });
991
- if (res.status !== 200) {
992
- throw new Error(res.data?.error || `Command ${name} failed`);
993
- }
994
- return res.data;
850
+ sendCommand(name, args = {}) {
851
+ return this.call("POST", `/api/commands/${name}`, `Command ${name} failed`, { args });
995
852
  }
996
- async getSettings() {
997
- const res = await this.httpRequest("GET", "/api/settings");
998
- if (res.status !== 200) {
999
- throw new Error(res.data?.error || "Failed to fetch settings");
1000
- }
1001
- return res.data;
853
+ getSettings() {
854
+ return this.call("GET", "/api/settings", "Failed to fetch settings");
1002
855
  }
1003
- async updateSettings(changes) {
1004
- const res = await this.httpRequest("PATCH", "/api/settings", changes);
1005
- if (res.status !== 200) {
1006
- throw new Error(res.data?.error || "Failed to update settings");
1007
- }
1008
- return res.data;
856
+ updateSettings(changes) {
857
+ return this.call("PATCH", "/api/settings", "Failed to update settings", changes);
1009
858
  }
1010
859
  };
1011
860
 
@@ -1253,11 +1102,14 @@ var import_core7 = require("@ordewell/core");
1253
1102
  function planTasks(plan) {
1254
1103
  return plan ? allTasksOf(plan) : [];
1255
1104
  }
1105
+ function runnersOf(plan, requested) {
1106
+ return "runners" in plan && plan.runners || requested;
1107
+ }
1256
1108
  function hasCommittedPlan(plan) {
1257
1109
  return planTasks(plan).length > 0;
1258
1110
  }
1259
1111
  function lastPlannerMessage(plan) {
1260
- const history = plan?.conversationHistory || [];
1112
+ const history = "conversationHistory" in plan && plan.conversationHistory || [];
1261
1113
  for (let i = history.length - 1; i >= 0; i--) {
1262
1114
  if (history[i].role === "assistant") return history[i].content;
1263
1115
  }
@@ -1319,7 +1171,7 @@ function printPlan(plan, sessionId, runners2, models) {
1319
1171
  return rp ? ` (${task.assignedModel.modelLabel} \xB7 ${label})` : ` (${task.assignedModel.modelLabel})`;
1320
1172
  };
1321
1173
  console.log(`
1322
- Plan: ${tasks.length} task${tasks.length === 1 ? "" : "s"} (${aiCount} AI, ${manCount} Manual) \u2014 ${(plan.runners || runners2).join(", ")}`);
1174
+ Plan: ${tasks.length} task${tasks.length === 1 ? "" : "s"} (${aiCount} AI, ${manCount} Manual) \u2014 ${runnersOf(plan, runners2).join(", ")}`);
1323
1175
  console.log(`Session: ${sessionId}
1324
1176
  `);
1325
1177
  for (const t of tasks) {
@@ -1466,7 +1318,7 @@ ${lastPlannerMessage(plan)}
1466
1318
  if (isTty && lastLineLength > 0) process.stderr.write("\r\x1B[2K");
1467
1319
  process.stderr.write("\n");
1468
1320
  printPlan(plan, sessionId, runners2, models);
1469
- saveLastSession(sessionId, goal, plan.runners || runners2, workspace);
1321
+ saveLastSession(sessionId, goal, runnersOf(plan, runners2), workspace);
1470
1322
  console.log(`
1471
1323
  Run 'ordewell run' to execute, 'ordewell status' to inspect, or 'ordewell tui' for the full UI.`);
1472
1324
  } catch (err) {
@@ -1584,23 +1436,27 @@ function mergeOutcome(result, branch, group, repaired = []) {
1584
1436
  }
1585
1437
 
1586
1438
  // src/utils/output.ts
1439
+ var import_core9 = require("@ordewell/core");
1587
1440
  function iconFor(status) {
1588
- switch (status) {
1589
- case "completed":
1441
+ switch ((0, import_core9.taskStatusKind)({ status })) {
1442
+ case "done":
1590
1443
  return "\x1B[32m\u2713\x1B[0m";
1591
- case "in_progress":
1444
+ case "running":
1445
+ case "quiet":
1592
1446
  return "\x1B[34m\u27F3\x1B[0m";
1593
1447
  case "failed":
1594
1448
  return "\x1B[31m\u2717\x1B[0m";
1595
1449
  case "blocked":
1596
1450
  return "\x1B[33m\u2298\x1B[0m";
1597
- default:
1451
+ case "awaiting":
1452
+ return "\x1B[33m?\x1B[0m";
1453
+ case "todo":
1598
1454
  return "\x1B[90m\u25CB\x1B[0m";
1599
1455
  }
1600
1456
  }
1601
1457
 
1602
1458
  // src/commands/run.ts
1603
- var import_core9 = require("@ordewell/core");
1459
+ var import_core10 = require("@ordewell/core");
1604
1460
 
1605
1461
  // src/commands/shared.ts
1606
1462
  var import_readline2 = require("readline");
@@ -1756,11 +1612,11 @@ async function followExecution(api, sessionId, start, onBlocked) {
1756
1612
  });
1757
1613
  const stream = api.streamExecution(sessionId, (event) => {
1758
1614
  if (event.type === "task_started") {
1759
- process.stderr.write(`\xB7 ${(0, import_core9.taskStartedNotice)(event.title, event.runner)}
1615
+ process.stderr.write(`\xB7 ${(0, import_core10.taskStartedNotice)(event.title, event.runner)}
1760
1616
  `);
1761
1617
  }
1762
1618
  if (event.type === "checkpoint") {
1763
- process.stderr.write(`\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core9.truncateCheckpointSummary)(event.summary)} \u2014 \`ordewell checkpoint <id> approve|reject [reason]\` answers it
1619
+ process.stderr.write(`\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core10.truncateCheckpointSummary)(event.summary)} \u2014 \`ordewell checkpoint <id> approve|reject [reason]\` answers it
1764
1620
  `);
1765
1621
  }
1766
1622
  if (event.type === "status_update" && event.tasks) {
@@ -1805,7 +1661,11 @@ Run finished on ${handoffBranch(event)}${where} \u2014 ${n} task${n === 1 ? "" :
1805
1661
  void stream.catch(settleReady);
1806
1662
  await streamReady.catch(streamFailed);
1807
1663
  await start();
1808
- 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
+ }
1809
1669
  if (blocked === null) return;
1810
1670
  if (!onBlocked) {
1811
1671
  await api.stopExecution(sessionId);
@@ -1833,7 +1693,7 @@ async function handleRun(subArgs, injectedApi) {
1833
1693
 
1834
1694
  // src/commands/status.ts
1835
1695
  var import_fs4 = require("fs");
1836
- var import_core10 = require("@ordewell/core");
1696
+ var import_core11 = require("@ordewell/core");
1837
1697
  async function handleStatus(subArgs, injectedApi) {
1838
1698
  const sessionId = flag(subArgs, "--session-id");
1839
1699
  const workspace = flag(subArgs, "--workspace") || process.cwd();
@@ -1904,12 +1764,12 @@ function printOneSession(sessionId, meta, plan) {
1904
1764
  const icon = iconFor(t.status);
1905
1765
  const model = t.assignedModel ? ` (${t.assignedModel.modelLabel})` : "";
1906
1766
  const shortId = (t.id ?? "").slice(-8);
1907
- console.log(` ${(0, import_core10.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}] ${t.title}${model}`);
1767
+ console.log(` ${(0, import_core11.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}] ${t.title}${model}`);
1908
1768
  for (const sub of t.subtasks || []) {
1909
1769
  const subIcon = iconFor(sub.status);
1910
1770
  const subModel = sub.assignedModel ? ` (${sub.assignedModel.modelLabel})` : "";
1911
1771
  const subShortId = (sub.id ?? "").slice(-8);
1912
- console.log(` ${(0, import_core10.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
1772
+ console.log(` ${(0, import_core11.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
1913
1773
  }
1914
1774
  }
1915
1775
  }
@@ -1961,10 +1821,10 @@ async function handleWeb(subArgs) {
1961
1821
  }
1962
1822
 
1963
1823
  // src/commands/models.ts
1964
- var import_core12 = require("@ordewell/core");
1824
+ var import_core13 = require("@ordewell/core");
1965
1825
 
1966
1826
  // src/commands/model.ts
1967
- var import_core11 = require("@ordewell/core");
1827
+ var import_core12 = require("@ordewell/core");
1968
1828
  var USAGE = "Usage: ordewell model [show | set <model-id>]";
1969
1829
  async function handleModel(subArgs, injectedApi) {
1970
1830
  const [action, modelId] = positionals(subArgs);
@@ -1979,7 +1839,7 @@ async function handleModel(subArgs, injectedApi) {
1979
1839
  if (!modelId) fail(USAGE);
1980
1840
  const settings = await api.getSettings();
1981
1841
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
1982
- const runner = (0, import_core11.runnerForProvider)(provider);
1842
+ const runner = (0, import_core12.runnerForProvider)(provider);
1983
1843
  if (runner) {
1984
1844
  const known = (await fetchCatalog(api)).models.filter((m) => m.runners?.includes(runner));
1985
1845
  if (known.length > 0 && !known.some((m) => m.id === modelId)) {
@@ -1998,7 +1858,7 @@ async function showModels(api) {
1998
1858
  const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
1999
1859
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
2000
1860
  const current = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
2001
- const runner = (0, import_core11.runnerForProvider)(provider);
1861
+ const runner = (0, import_core12.runnerForProvider)(provider);
2002
1862
  const options = runner ? catalog.models.filter((m) => m.runners?.includes(runner)) : catalog.orchestratorModels;
2003
1863
  console.log(`
2004
1864
  Planner model: ${current || "(backend default)"}`);
@@ -2032,8 +1892,8 @@ async function handleModels(subArgs) {
2032
1892
  const rest = subArgs.filter((_, i) => i !== setIdx && i !== setIdx + 1);
2033
1893
  return handleModel(["set", subArgs[setIdx + 1], ...rest]);
2034
1894
  }
2035
- const config = new import_core12.EnvConfig();
2036
- const providers = (0, import_core12.configuredProviders)(config);
1895
+ const config = new import_core13.EnvConfig();
1896
+ const providers = (0, import_core13.configuredProviders)(config);
2037
1897
  if (providers.length === 0) {
2038
1898
  console.error("No providers configured. Set an API key for a provider, e.g.:");
2039
1899
  console.error(" OPENROUTER_API_KEY=... (OpenRouter)");
@@ -2042,19 +1902,19 @@ async function handleModels(subArgs) {
2042
1902
  console.error(" OPENAI_COMPATIBLE_BASE_URL=... (self-hosted / OpenAI-compatible)");
2043
1903
  process.exit(1);
2044
1904
  }
2045
- const { apiKeys, baseUrls } = (0, import_core12.collectProviderCredentials)(config);
1905
+ const { apiKeys, baseUrls } = (0, import_core13.collectProviderCredentials)(config);
2046
1906
  let models;
2047
1907
  let errors;
2048
1908
  try {
2049
- const result = await (0, import_core12.fetchAllProviderModels)({ apiKeys, baseUrls });
2050
- models = (0, import_core12.toOrchestratorOptions)(result.models, import_core12.ORCHESTRATOR_SHORTCUTS);
1909
+ const result = await (0, import_core13.fetchAllProviderModels)({ apiKeys, baseUrls });
1910
+ models = (0, import_core13.toOrchestratorOptions)(result.models, import_core13.ORCHESTRATOR_SHORTCUTS);
2051
1911
  errors = result.errors;
2052
1912
  } catch (err) {
2053
1913
  console.error(`Failed to fetch models: ${err instanceof Error ? err.message : String(err)}`);
2054
1914
  process.exit(1);
2055
1915
  return;
2056
1916
  }
2057
- const providerLabel = (p) => (0, import_core12.getProviderMeta)(p)?.label ?? p;
1917
+ const providerLabel = (p) => (0, import_core13.getProviderMeta)(p)?.label ?? p;
2058
1918
  console.log(
2059
1919
  `
2060
1920
  Available models (${models.length}) across ${providers.length} configured provider(s):`
@@ -2094,7 +1954,7 @@ ${providerLabel(provider)}`);
2094
1954
 
2095
1955
  // src/commands/setup.ts
2096
1956
  var import_fs6 = require("fs");
2097
- var import_core13 = require("@ordewell/core");
1957
+ var import_core14 = require("@ordewell/core");
2098
1958
  function handleSetup(subArgs) {
2099
1959
  const modelIdx = subArgs.indexOf("--model");
2100
1960
  const chosenModel = modelIdx !== -1 ? subArgs[modelIdx + 1] : null;
@@ -2166,15 +2026,15 @@ function handleSetup(subArgs) {
2166
2026
  # STEPFUN_API_KEY=...
2167
2027
  # SILICONFLOW_API_KEY=...
2168
2028
  `;
2169
- (0, import_core13.writePrivateFile)(envFile, content);
2029
+ (0, import_core14.writePrivateFile)(envFile, content);
2170
2030
  console.log(`Wrote: ${envFile}`);
2171
2031
  console.log("Review and edit the file, then run: ordewell tui");
2172
2032
  }
2173
2033
 
2174
2034
  // src/commands/plugins.ts
2175
- var import_core14 = require("@ordewell/core");
2035
+ var import_core15 = require("@ordewell/core");
2176
2036
  function handlePlugins(subArgs) {
2177
- const registry = new import_core14.RunnerRegistry();
2037
+ const registry = new import_core15.RunnerRegistry();
2178
2038
  registry.loadUserPlugins();
2179
2039
  const sub = subArgs[0];
2180
2040
  if (sub === "list" || !sub) {
@@ -2210,7 +2070,7 @@ ${plugins.length} plugin(s):
2210
2070
  process.exit(1);
2211
2071
  }
2212
2072
  try {
2213
- const resolved = (0, import_core14.classifyPluginSource)(source);
2073
+ const resolved = (0, import_core15.classifyPluginSource)(source);
2214
2074
  const manifest = resolved.kind === "git" ? registry.installFromGit(resolved.url) : registry.installFromPath(resolved.path);
2215
2075
  console.log(`Installed plugin: ${manifest.name} v${manifest.version}`);
2216
2076
  } catch (err) {
@@ -2388,7 +2248,7 @@ async function handleTransport(subArgs, api) {
2388
2248
  }
2389
2249
 
2390
2250
  // src/commands/task-control.ts
2391
- var import_core15 = require("@ordewell/core");
2251
+ var import_core16 = require("@ordewell/core");
2392
2252
  var PAST_TENSE = {
2393
2253
  run: "Task started.",
2394
2254
  "force-start": "Task force-started.",
@@ -2431,7 +2291,7 @@ async function confirmPastGate(subArgs, api, sessionId, taskId, confirm) {
2431
2291
  if (hasFlag(subArgs, "--yes")) return;
2432
2292
  const unmerged = await api.getMergeGate(sessionId, taskId).catch(() => []);
2433
2293
  if (unmerged.length === 0) return;
2434
- const question = `${(0, import_core15.pastGateConfirmation)(unmerged.map((d) => (0, import_core15.titledTaskRef)(d)))} Start it anyway?`;
2294
+ const question = `${(0, import_core16.pastGateConfirmation)(unmerged.map((d) => (0, import_core16.titledTaskRef)(d)))} Start it anyway?`;
2435
2295
  if (!await confirm(question)) fail("Not started \u2014 nothing was changed. Pass --yes to start it without a prompt.");
2436
2296
  }
2437
2297
  function makeHandler(action, command = action) {
@@ -2744,24 +2604,24 @@ async function handleRewind(subArgs, injectedApi) {
2744
2604
  }
2745
2605
 
2746
2606
  // src/tui/transcript.ts
2747
- var import_core16 = require("@ordewell/core");
2607
+ var import_core17 = require("@ordewell/core");
2748
2608
  function show(state, { view: conversation, gate: turnGate }) {
2749
2609
  const gated = turnGate === state.turnGate ? state : { ...state, turnGate };
2750
2610
  if (conversation === state.conversation) return gated;
2751
2611
  return { ...gated, conversation };
2752
2612
  }
2753
2613
  function fold(state, input) {
2754
- return show(state, { view: (0, import_core16.reduceConversation)(state.conversation, input), gate: state.turnGate });
2614
+ return show(state, { view: (0, import_core17.reduceConversation)(state.conversation, input), gate: state.turnGate });
2755
2615
  }
2756
2616
  function say(state, role, text) {
2757
2617
  const said = fold(state, { type: "local_entry", role, text });
2758
2618
  return said.conversation.nextId > state.conversation.nextId ? { ...said, scroll: 0 } : said;
2759
2619
  }
2760
2620
  function hear(state, message) {
2761
- return show(state, (0, import_core16.followTurn)(state.conversation, state.turnGate, message));
2621
+ return show(state, (0, import_core17.followTurn)(state.conversation, state.turnGate, message));
2762
2622
  }
2763
2623
  function cutTurn(state) {
2764
- return show(state, (0, import_core16.stopTurn)(state.conversation, state.turnGate));
2624
+ return show(state, (0, import_core17.stopTurn)(state.conversation, state.turnGate));
2765
2625
  }
2766
2626
  function wiped(view) {
2767
2627
  return { ...view, blocks: view.blocks.filter((b) => b.type === "usage") };
@@ -3175,26 +3035,17 @@ async function handleHandoff(subArgs, injectedApi, confirm = askYesNo) {
3175
3035
  }
3176
3036
 
3177
3037
  // src/commands/planner.ts
3178
- var import_core17 = require("@ordewell/core");
3038
+ var import_core18 = require("@ordewell/core");
3179
3039
 
3180
3040
  // src/plannerModelSwitch.ts
3181
- function describePlannerSwitch(settings, provider) {
3182
- const model = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
3183
- const effort = typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : "";
3184
- if (!model) return { model: "", effort: "", source: "none" };
3185
- return rememberedModelFor(settings, provider) === model ? { model, effort, source: "remembered" } : { model, effort, source: "catalog-default" };
3186
- }
3187
- function rememberedModelFor(settings, provider) {
3188
- const map = settings.plannerModels;
3189
- if (!map || typeof map !== "object") return void 0;
3190
- const entry = map[provider];
3191
- if (!entry || typeof entry !== "object") return void 0;
3192
- const { model } = entry;
3193
- return typeof model === "string" ? model : void 0;
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" };
3194
3045
  }
3195
3046
 
3196
3047
  // src/commands/planner.ts
3197
- var KNOWN_PROVIDERS = Object.keys(import_core17.ALL_PROVIDERS);
3048
+ var isAiProvider = (id) => Object.hasOwn(import_core18.ALL_PROVIDERS, id);
3198
3049
  async function handlePlanner(subArgs, injectedApi) {
3199
3050
  const api = await connect(subArgs, injectedApi);
3200
3051
  const [wanted] = positionals(subArgs);
@@ -3203,19 +3054,19 @@ async function handlePlanner(subArgs, injectedApi) {
3203
3054
  return;
3204
3055
  }
3205
3056
  const provider = wanted.toLowerCase();
3206
- if (!KNOWN_PROVIDERS.includes(provider)) {
3057
+ if (!isAiProvider(provider)) {
3207
3058
  fail(`Unknown planner: ${provider}`, "Run `ordewell planner` with no arguments to see the list.");
3208
3059
  }
3209
3060
  const settings = await persistEnv(api, { AI_PROVIDER: provider });
3210
- const recall2 = describePlannerSwitch(settings, provider);
3061
+ const recall2 = plannerSwitchRecall(settings);
3211
3062
  writeEnv({ ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
3212
- const meta = import_core17.ALL_PROVIDERS[provider];
3063
+ const meta = import_core18.ALL_PROVIDERS[provider];
3213
3064
  const catalog = await fetchCatalog(api);
3214
3065
  console.log(
3215
- (0, import_core17.isCliProvider)(provider) ? `Planning with ${meta.label} \u2014 no API key needed, it uses that agent's own subscription.` : `Planner set to ${meta.label}.`
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}.`
3216
3067
  );
3217
3068
  console.log(plannerModelLine(recall2));
3218
- if (!(0, import_core17.isCliProvider)(provider) && !catalog.providers.includes(provider)) {
3069
+ if (!(0, import_core18.isCliProvider)(provider) && !catalog.providers.includes(provider)) {
3219
3070
  console.log(` No ${meta.apiKeyEnvVar} configured yet \u2014 set one with \`ordewell key set ${provider} <key>\`.`);
3220
3071
  }
3221
3072
  }
@@ -3233,15 +3084,15 @@ async function showPlanners(api) {
3233
3084
  const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
3234
3085
  const current = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
3235
3086
  console.log("\nCoding agents (no API key \u2014 they use their own subscription)");
3236
- for (const id of import_core17.CLI_PROVIDERS) {
3237
- const runner = (0, import_core17.runnerForProvider)(id);
3087
+ for (const id of import_core18.CLI_PROVIDERS) {
3088
+ const runner = (0, import_core18.runnerForProvider)(id);
3238
3089
  const discovered = runner ? catalog.models.some((m) => m.runners?.includes(runner)) : false;
3239
- console.log(` ${marker(id === current)} ${id.padEnd(14)} ${import_core17.ALL_PROVIDERS[id].label}${discovered ? "" : " (not detected)"}`);
3090
+ console.log(` ${marker(id === current)} ${id.padEnd(14)} ${import_core18.ALL_PROVIDERS[id].label}${discovered ? "" : " (not detected)"}`);
3240
3091
  }
3241
3092
  console.log("\nAPI providers");
3242
- for (const id of import_core17.PROVIDER_PRIORITY) {
3093
+ for (const id of import_core18.PROVIDER_PRIORITY) {
3243
3094
  const configured = catalog.providers.includes(id);
3244
- const meta = import_core17.ALL_PROVIDERS[id];
3095
+ const meta = import_core18.ALL_PROVIDERS[id];
3245
3096
  console.log(` ${marker(id === current)} ${id.padEnd(14)} ${meta.label}${configured ? "" : ` (no ${meta.apiKeyEnvVar})`}`);
3246
3097
  }
3247
3098
  console.log(`
@@ -3258,7 +3109,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
3258
3109
  const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
3259
3110
  const modelId = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
3260
3111
  const currentEffort = typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : "";
3261
- const runner = (0, import_core17.runnerForProvider)(provider);
3112
+ const runner = isAiProvider(provider) ? (0, import_core18.runnerForProvider)(provider) : null;
3262
3113
  if (!runner) {
3263
3114
  fail(
3264
3115
  "Thinking effort applies to a coding-agent planner.",
@@ -3298,7 +3149,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
3298
3149
  }
3299
3150
 
3300
3151
  // src/commands/parallel.ts
3301
- var import_core18 = require("@ordewell/core");
3152
+ var import_core19 = require("@ordewell/core");
3302
3153
  var USAGE5 = "Usage: ordewell parallel [<number of tasks, 1 or more>]";
3303
3154
  async function handleParallel(subArgs, injectedApi) {
3304
3155
  const [wanted] = positionals(subArgs);
@@ -3308,14 +3159,14 @@ async function handleParallel(subArgs, injectedApi) {
3308
3159
  console.log(`Up to ${String(settings.maxParallel ?? "?")} AI tasks run at once. Change with: ordewell parallel <n>`);
3309
3160
  return;
3310
3161
  }
3311
- const limit = (0, import_core18.parseMaxParallel)(wanted);
3162
+ const limit = (0, import_core19.parseMaxParallel)(wanted);
3312
3163
  if (limit === null) fail(`"${wanted}" is not a number of tasks (1 or more).`, USAGE5);
3313
3164
  await persistEnv(api, { ORDEWELL_MAX_PARALLEL: String(limit) });
3314
3165
  console.log(`Up to ${limit} AI task${limit === 1 ? "" : "s"} now run at once.`);
3315
3166
  }
3316
3167
 
3317
3168
  // src/commands/key.ts
3318
- var import_core19 = require("@ordewell/core");
3169
+ var import_core20 = require("@ordewell/core");
3319
3170
  var USAGE6 = "Usage: ordewell key [list | set <provider> <key>]";
3320
3171
  async function handleKey(subArgs, injectedApi) {
3321
3172
  const [action, provider, ...keyParts] = positionals(subArgs);
@@ -3326,8 +3177,8 @@ async function handleKey(subArgs, injectedApi) {
3326
3177
  if (action !== "set") {
3327
3178
  const catalog = await fetchCatalog(api);
3328
3179
  console.log("\nAPI provider keys\n");
3329
- for (const id2 of import_core19.PROVIDER_PRIORITY) {
3330
- const meta2 = import_core19.ALL_PROVIDERS[id2];
3180
+ for (const id2 of import_core20.PROVIDER_PRIORITY) {
3181
+ const meta2 = import_core20.ALL_PROVIDERS[id2];
3331
3182
  const configured = catalog.providers.includes(id2);
3332
3183
  console.log(` ${configured ? "\u2713" : " "} ${id2.padEnd(14)} ${meta2.apiKeyEnvVar.padEnd(28)} ${meta2.label}`);
3333
3184
  }
@@ -3337,7 +3188,7 @@ async function handleKey(subArgs, injectedApi) {
3337
3188
  }
3338
3189
  if (!provider) fail(USAGE6);
3339
3190
  const id = provider.toLowerCase();
3340
- const meta = import_core19.ALL_PROVIDERS[id];
3191
+ const meta = import_core20.ALL_PROVIDERS[id];
3341
3192
  if (!meta) {
3342
3193
  fail(`Unknown provider: ${provider}`, "Run `ordewell key` to see the list.");
3343
3194
  }
@@ -3348,7 +3199,7 @@ async function handleKey(subArgs, injectedApi) {
3348
3199
  }
3349
3200
 
3350
3201
  // src/commands/runners.ts
3351
- var import_core20 = require("@ordewell/core");
3202
+ var import_core21 = require("@ordewell/core");
3352
3203
  var USAGE7 = "Usage: ordewell runners [<runner-id> on|off]";
3353
3204
  async function handleRunners(subArgs, injectedApi) {
3354
3205
  const [runner, toggle] = positionals(subArgs);
@@ -3379,15 +3230,15 @@ function handleAuto(subArgs) {
3379
3230
  const currentRaw = process.env.ORDEWELL_AUTONOMOUS_MODE;
3380
3231
  const current = currentRaw !== "false" && currentRaw !== "0";
3381
3232
  if (!arg) {
3382
- console.log(`Autonomy level: ${(0, import_core20.autonomyLevelLabel)(current)}`);
3233
+ console.log(`Autonomy level: ${(0, import_core21.autonomyLevelLabel)(current)}`);
3383
3234
  console.log(` ${AUTO_USAGE}`);
3384
3235
  return;
3385
3236
  }
3386
- const enabled = (0, import_core20.parseAutonomyLevel)(arg);
3237
+ const enabled = (0, import_core21.parseAutonomyLevel)(arg);
3387
3238
  if (enabled === null) fail(AUTO_USAGE);
3388
3239
  writeEnvVar(findEnvFile(), "ORDEWELL_AUTONOMOUS_MODE", String(enabled));
3389
3240
  process.env.ORDEWELL_AUTONOMOUS_MODE = String(enabled);
3390
- console.log(`Autonomy level: ${(0, import_core20.autonomyLevelLabel)(enabled)} for new sessions.`);
3241
+ console.log(`Autonomy level: ${(0, import_core21.autonomyLevelLabel)(enabled)} for new sessions.`);
3391
3242
  }
3392
3243
  async function handleRefresh(subArgs, injectedApi) {
3393
3244
  const api = await connect(subArgs, injectedApi);
@@ -3419,7 +3270,7 @@ async function handleApprove(subArgs, injectedApi) {
3419
3270
  // src/tui/terminalLauncher.ts
3420
3271
  var import_child_process3 = require("child_process");
3421
3272
  var import_util = require("util");
3422
- var import_core21 = require("@ordewell/core");
3273
+ var import_core22 = require("@ordewell/core");
3423
3274
  var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
3424
3275
  var defaultExecFile = async (command, args) => {
3425
3276
  const { stdout, stderr } = await execFileAsync(command, args);
@@ -3439,16 +3290,16 @@ var defaultWhich = (bin) => {
3439
3290
  async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
3440
3291
  const platform = deps.platform ?? process.platform;
3441
3292
  const env = deps.env ?? process.env;
3442
- const hasTmuxImpl = deps.hasTmuxImpl ?? import_core21.hasTmux;
3293
+ const hasTmuxImpl = deps.hasTmuxImpl ?? import_core22.hasTmux;
3443
3294
  const execFileImpl = deps.execFileImpl ?? defaultExecFile;
3444
3295
  const spawnImpl = deps.spawnImpl ?? defaultSpawn;
3445
3296
  const which = deps.which ?? defaultWhich;
3446
3297
  if (!hasTmuxImpl()) {
3447
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." };
3448
3299
  }
3449
- const session = (0, import_core21.tmuxSessionName)(port);
3450
- const socket = (0, import_core21.tmuxSocketName)(port);
3451
- const base = (0, import_core21.tmuxWindowName)(taskId, planSessionId);
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);
3452
3303
  let windows;
3453
3304
  try {
3454
3305
  const { stdout } = await execFileImpl("tmux", ["-L", socket, "list-windows", "-t", session, "-F", "#{window_name}"]);
@@ -3760,7 +3611,7 @@ ${(0, import_plan_utils.titledTaskRef)(task)} is ${task.ops ? "an ops task: it r
3760
3611
  }
3761
3612
 
3762
3613
  // src/tui/index.ts
3763
- var import_core36 = require("@ordewell/core");
3614
+ var import_core37 = require("@ordewell/core");
3764
3615
 
3765
3616
  // src/tui/editor.ts
3766
3617
  function emptyEditor() {
@@ -4010,7 +3861,7 @@ function tokenCompletions(token) {
4010
3861
  }
4011
3862
 
4012
3863
  // src/tui/blocks.ts
4013
- var import_core22 = require("@ordewell/core");
3864
+ var import_core23 = require("@ordewell/core");
4014
3865
 
4015
3866
  // src/tui/markdown.ts
4016
3867
  function renderMarkdown(source, cols) {
@@ -4230,16 +4081,16 @@ function resultLines(block, room, detailAll) {
4230
4081
  const fit2 = (line) => detailAll ? wrap(line, room) : [truncate(line, room)];
4231
4082
  const outcome = unsettledOutcome(block);
4232
4083
  if (outcome) {
4233
- const [reason, ...rest] = (0, import_core22.outputLines)(block.output).map(sanitize);
4084
+ const [reason, ...rest] = (0, import_core23.outputLines)(block.output).map(sanitize);
4234
4085
  const status = fit2(reason ? `${outcome.label}: ${reason}` : outcome.label).map(outcome.paint);
4235
4086
  return detailAll ? [...status, ...rest.flatMap(fit2).map(style.grey)] : status;
4236
4087
  }
4237
4088
  if (detailAll) {
4238
- const lines2 = (0, import_core22.outputLines)(block.output).map(sanitize);
4089
+ const lines2 = (0, import_core23.outputLines)(block.output).map(sanitize);
4239
4090
  const note = lines2.length > PREVIEW_LINES ? ["\u2026 (ctrl+o to collapse)"] : [];
4240
4091
  return lines2.length > 0 ? [...lines2.flatMap(fit2), ...note].map(style.grey) : [style.grey("(no output)")];
4241
4092
  }
4242
- const { lines, hiddenLineCount } = (0, import_core22.outputPreview)(block.output, PREVIEW_LINES);
4093
+ const { lines, hiddenLineCount } = (0, import_core23.outputPreview)(block.output, PREVIEW_LINES);
4243
4094
  if (lines.length === 0) return [style.grey("(no output)")];
4244
4095
  const more = hiddenLineCount > 0 ? [`\u2026 +${countOf(hiddenLineCount, "line")} (ctrl+o to expand)`] : [];
4245
4096
  return [...lines.map(sanitize), ...more].map((line) => style.grey(truncate(line, room)));
@@ -4253,7 +4104,7 @@ var DIFF_PAINT = {
4253
4104
  };
4254
4105
  var DIFF_SIGN = { added: "+", removed: "-", context: " ", gap: "" };
4255
4106
  function diffLines(block, diff, room, detailAll) {
4256
- const rows = (0, import_core22.diffRows)(block.output);
4107
+ const rows = (0, import_core23.diffRows)(block.output);
4257
4108
  const gutter = Math.max(0, ...rows.map((row) => String(row.line ?? "").length));
4258
4109
  const draw = (row) => {
4259
4110
  const text = row.kind === "gap" ? `${" ".repeat(gutter)} \u22EE` : `${String(row.line ?? "").padStart(gutter)} ${DIFF_SIGN[row.kind]} ${sanitize(row.text)}`;
@@ -4262,7 +4113,7 @@ function diffLines(block, diff, room, detailAll) {
4262
4113
  const shown = detailAll ? rows : rows.slice(0, DIFF_PREVIEW_ROWS);
4263
4114
  const hidden = rows.length - shown.length;
4264
4115
  const more = hidden > 0 ? [style.grey(`\u2026 +${countOf(hidden, "line")} (ctrl+o to expand)`)] : [];
4265
- return [(0, import_core22.diffSummary)(diff), ...shown.flatMap(draw), ...more];
4116
+ return [(0, import_core23.diffSummary)(diff), ...shown.flatMap(draw), ...more];
4266
4117
  }
4267
4118
  function toolLines(block, cols, detailAll) {
4268
4119
  const room = Math.max(1, cols - OUTPUT_INDENT.length);
@@ -4397,7 +4248,7 @@ function tokenLine(blocks, cols) {
4397
4248
  }
4398
4249
 
4399
4250
  // src/tui/state.ts
4400
- var import_core23 = require("@ordewell/core");
4251
+ var import_core24 = require("@ordewell/core");
4401
4252
  var SKILL_IDS = ["tdd", "verify"];
4402
4253
  function noSkills() {
4403
4254
  return { tdd: false, verify: false };
@@ -4409,7 +4260,7 @@ function visibleItems(picker2) {
4409
4260
  (i) => i.id.toLowerCase().includes(filter) || i.label.toLowerCase().includes(filter) || (i.detail ?? "").toLowerCase().includes(filter)
4410
4261
  );
4411
4262
  }
4412
- var anyTaskRunning = (state) => state.tasks.some(import_core23.isTaskRunning);
4263
+ var anyTaskRunning = (state) => state.tasks.some(import_core24.isTaskRunning);
4413
4264
  var plannerInFlight = (state) => state.status === "planning" || state.status === "researching";
4414
4265
  function planRows(state) {
4415
4266
  const rows = [];
@@ -4446,9 +4297,9 @@ function findTask(tasks, id) {
4446
4297
  function initialState(overrides = {}) {
4447
4298
  return {
4448
4299
  editor: emptyEditor(),
4449
- conversation: import_core23.EMPTY_CONVERSATION,
4300
+ conversation: import_core24.EMPTY_CONVERSATION,
4450
4301
  taskView: null,
4451
- turnGate: import_core23.NO_TURN,
4302
+ turnGate: import_core24.NO_TURN,
4452
4303
  detailAll: false,
4453
4304
  status: "idle",
4454
4305
  busyLabel: "",
@@ -4473,7 +4324,7 @@ function initialState(overrides = {}) {
4473
4324
  orchestratorModel: "",
4474
4325
  plannerProvider: "",
4475
4326
  plannerEffort: "",
4476
- maxParallel: import_core23.DEFAULT_MAX_PARALLEL,
4327
+ maxParallel: import_core24.DEFAULT_MAX_PARALLEL,
4477
4328
  configuredProviders: [],
4478
4329
  allowlist: {},
4479
4330
  autonomous: true,
@@ -4485,7 +4336,7 @@ function initialState(overrides = {}) {
4485
4336
  gate: null,
4486
4337
  overlay: null,
4487
4338
  pendingApprovals: [],
4488
- queuedPrompts: import_core23.EMPTY_HOLD,
4339
+ queuedPrompts: import_core24.EMPTY_HOLD,
4489
4340
  stopArmed: false,
4490
4341
  stopArmToken: 0,
4491
4342
  stopRequested: false,
@@ -4533,7 +4384,7 @@ function chatEditorRoomFor(state, active) {
4533
4384
  }
4534
4385
 
4535
4386
  // src/tui/layout.ts
4536
- var import_core24 = require("@ordewell/core");
4387
+ var import_core25 = require("@ordewell/core");
4537
4388
  var CHROME_ROWS = 4;
4538
4389
  function chatInputWrap(state) {
4539
4390
  const active = state.focus === "chat" && !state.overlay;
@@ -4542,7 +4393,7 @@ function chatInputWrap(state) {
4542
4393
  }
4543
4394
  function footerHints(state) {
4544
4395
  const selected = selectedPlanRow(state)?.task;
4545
- const markHint = selected && (0, import_core24.markAction)(selected) === "uncomplete" ? "m undone" : "m done";
4396
+ const markHint = selected && (0, import_core25.markAction)(selected) === "uncomplete" ? "m undone" : "m done";
4546
4397
  const resolveHint = selected?.isolation?.state === "conflict" ? ["x resolve conflict"] : [];
4547
4398
  const planning = plannerInFlight(state) && !state.taskView;
4548
4399
  const escHint = !planning ? null : state.queuedPrompts.length > 0 ? "esc unsend" : "esc \xD72 stop planning";
@@ -4573,7 +4424,7 @@ function footerHints(state) {
4573
4424
  "tab chat"
4574
4425
  ];
4575
4426
  }
4576
- const detailHint = (0, import_core24.hasHiddenDetail)(state.taskView ? state.taskView.view.blocks : state.conversation.blocks) ? [state.detailAll ? "ctrl-o collapse all" : "ctrl-o expand all"] : [];
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"] : [];
4577
4428
  return [
4578
4429
  "/help",
4579
4430
  "tab plan",
@@ -4609,10 +4460,10 @@ function queuedTaskBubble(message, selected, cols) {
4609
4460
  return wrap(text, room).map((line, i) => i === 0 ? truncate(`${marker2} ${selected ? style.bold(line) : line}${style.grey(tag)}`, cols) : truncate(` ${line}`, cols));
4610
4461
  }
4611
4462
  function taskActivity(task, working) {
4612
- const approvals = (0, import_core24.approvalLabel)(task?.awaitingApproval);
4463
+ const approvals = (0, import_core25.approvalLabel)(task?.awaitingApproval);
4613
4464
  if (approvals) return inRow(approvals);
4614
4465
  if (working) return "working";
4615
- if (task?.status === "awaiting_user") return inRow((0, import_core24.awaitingLabel)(task) ?? "Waiting for your input");
4466
+ if (task?.status === "awaiting_user") return inRow((0, import_core25.awaitingLabel)(task) ?? "Waiting for your input");
4616
4467
  return task?.status ?? "";
4617
4468
  }
4618
4469
  function taskHeaderLines(state, tv, cols) {
@@ -4687,9 +4538,9 @@ function chatScrollMax(state) {
4687
4538
  }
4688
4539
  function plannerSetup(state) {
4689
4540
  const provider = state.plannerProvider;
4690
- const registration = import_core24.ALL_PROVIDERS[provider];
4541
+ const registration = import_core25.ALL_PROVIDERS[provider];
4691
4542
  if (!registration) return { label: "not set", ready: false };
4692
- const runner = (0, import_core24.runnerForProvider)(provider);
4543
+ const runner = (0, import_core25.runnerForProvider)(provider);
4693
4544
  const ready = runner ? state.runners.some((r) => r.id === runner) : state.configuredProviders.includes(provider);
4694
4545
  return { label: registration.label, ready };
4695
4546
  }
@@ -4861,7 +4712,7 @@ function planScrollExtent(state) {
4861
4712
  }
4862
4713
  function taskLines(state, planRow, index, cols, orderOf) {
4863
4714
  const { task, parent } = planRow;
4864
- const row = (0, import_core24.taskRowView)(task, { parent, orderOf, modes: modesForTask(state.modesByRunner, task) });
4715
+ const row = (0, import_core25.taskRowView)(task, { parent, orderOf, modes: modesForTask(state.modesByRunner, task) });
4865
4716
  const selected = state.focus === "plan" && index === state.selectedTask;
4866
4717
  const expanded = state.expandedTaskId === task.id;
4867
4718
  const rawIcon = row.status === "running" ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[row.status];
@@ -5008,7 +4859,7 @@ function helpScrollMax(state) {
5008
4859
  }
5009
4860
 
5010
4861
  // src/tui/render.ts
5011
- var import_core25 = require("@ordewell/core");
4862
+ var import_core26 = require("@ordewell/core");
5012
4863
  function render(state) {
5013
4864
  const { rows, cols } = state;
5014
4865
  const rowsForBody = bodyRows(state);
@@ -5273,8 +5124,8 @@ function renderHandoff(state, overlay, rows, cols) {
5273
5124
  return frame(`Diff \u2014 ${handoffBranch(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
5274
5125
  }
5275
5126
  const landed = handoff.landed.length === 0 ? [style.grey("Nothing landed on it.")] : handoff.landed.map((task) => {
5276
- const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0, import_core25.capConflictFiles)(task.repairedFiles)})`) : "";
5277
- return ` ${style.green("\u2713")} ${(0, import_core25.titledTaskRef)(task)}${repaired}`;
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}`;
5278
5129
  });
5279
5130
  const actions = handoffActions(handoff).map((action, index) => {
5280
5131
  const active = index === overlay.index;
@@ -5363,10 +5214,10 @@ function frame(title, body, rows, cols) {
5363
5214
  }
5364
5215
 
5365
5216
  // src/tui/reducer.ts
5366
- var import_core33 = require("@ordewell/core");
5217
+ var import_core34 = require("@ordewell/core");
5367
5218
 
5368
5219
  // src/tui/reducers/overlays.ts
5369
- var import_core31 = require("@ordewell/core");
5220
+ var import_core32 = require("@ordewell/core");
5370
5221
 
5371
5222
  // src/tui/reducers/shared.ts
5372
5223
  function step(state, effects = []) {
@@ -5415,10 +5266,10 @@ function handleApprovalKey(state, overlay, key) {
5415
5266
  }
5416
5267
 
5417
5268
  // src/tui/reducers/commands.ts
5418
- var import_core30 = require("@ordewell/core");
5269
+ var import_core31 = require("@ordewell/core");
5419
5270
 
5420
5271
  // src/tui/reducers/pickers.ts
5421
- var import_core26 = require("@ordewell/core");
5272
+ var import_core27 = require("@ordewell/core");
5422
5273
  var import_plan_utils2 = require("@ordewell/core/plan-utils");
5423
5274
  var DEFAULT_EFFORT = "__runner_default__";
5424
5275
  function picker(title, items, action, extra = {}) {
@@ -5437,11 +5288,11 @@ function modelDetail(m) {
5437
5288
  function providerErrorHint(state) {
5438
5289
  const failed = Object.keys(state.providerErrors ?? {});
5439
5290
  if (failed.length === 0) return void 0;
5440
- const names = failed.map((p) => import_core26.ALL_PROVIDERS[p]?.label ?? p);
5291
+ const names = failed.map((p) => import_core27.ALL_PROVIDERS[p]?.label ?? p);
5441
5292
  return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")} \u2014 showing working providers only.`;
5442
5293
  }
5443
5294
  function plannerModelItems(state) {
5444
- const runner = (0, import_core26.runnerForProvider)(state.plannerProvider);
5295
+ const runner = (0, import_core27.runnerForProvider)(state.plannerProvider);
5445
5296
  if (!runner) return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m) }));
5446
5297
  const items = state.models.filter((m) => m.runners?.includes(runner)).map((m) => ({
5447
5298
  id: m.id,
@@ -5455,7 +5306,7 @@ function plannerModelItems(state) {
5455
5306
  return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
5456
5307
  }
5457
5308
  function plannerEffortItems(state) {
5458
- const runner = (0, import_core26.runnerForProvider)(state.plannerProvider);
5309
+ const runner = (0, import_core27.runnerForProvider)(state.plannerProvider);
5459
5310
  if (!runner) {
5460
5311
  return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
5461
5312
  }
@@ -5519,7 +5370,7 @@ function pickerItemsFor(state, action) {
5519
5370
  if (action.kind === "set-task-deps") {
5520
5371
  return (0, import_plan_utils2.dependencyCandidates)(state.tasks, action.taskId).map((candidate) => ({
5521
5372
  id: candidate.id,
5522
- label: (0, import_core26.titledTaskRef)(candidate),
5373
+ label: (0, import_core27.titledTaskRef)(candidate),
5523
5374
  detail: candidate.status === "completed" ? "already completed" : void 0
5524
5375
  }));
5525
5376
  }
@@ -5564,28 +5415,24 @@ function pickerItemsFor(state, action) {
5564
5415
  }
5565
5416
  function plannerItems(state) {
5566
5417
  const installed = new Set(state.runners.map((r) => r.id));
5567
- const agents = import_core26.CLI_PROVIDERS.map((id) => {
5568
- const runner = (0, import_core26.runnerForProvider)(id);
5569
- const ready = installed.has(runner);
5570
- return {
5571
- id,
5572
- label: import_core26.ALL_PROVIDERS[id].label,
5573
- detail: ready ? "coding agent \xB7 no API key needed" : "CLI not installed or not on PATH",
5574
- selected: state.plannerProvider === id,
5575
- disabled: !ready
5576
- };
5577
- });
5578
- const vendors = import_core26.PROVIDER_PRIORITY.filter((id) => state.configuredProviders.includes(id)).map((id) => ({
5579
- id,
5580
- label: import_core26.ALL_PROVIDERS[id].label,
5581
- detail: import_core26.ALL_PROVIDERS[id].apiKeyEnvVar,
5582
- 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
5583
5430
  }));
5584
- return vendors.length > 0 ? [...agents, ...vendors] : [...agents, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
5431
+ return entries.some((entry) => entry.kind === "vendor") ? items : [...items, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
5585
5432
  }
5586
5433
 
5587
5434
  // src/tui/reducers/planPane.ts
5588
- var import_core28 = require("@ordewell/core");
5435
+ var import_core29 = require("@ordewell/core");
5589
5436
 
5590
5437
  // src/tui/reducers/pointer.ts
5591
5438
  var WHEEL_NOTCH = 3;
@@ -5663,13 +5510,13 @@ function handleMouseSelect(state, key) {
5663
5510
  }
5664
5511
 
5665
5512
  // src/tui/reducers/taskView.ts
5666
- var import_core27 = require("@ordewell/core");
5513
+ var import_core28 = require("@ordewell/core");
5667
5514
  function openTaskView(state, sessionId, taskId) {
5668
5515
  const task = findTask(state.tasks, taskId);
5669
5516
  if (!task) return step(say(state, "system", `No task ${taskId} in this plan.`));
5670
5517
  const taskView = {
5671
5518
  taskId,
5672
- view: import_core27.EMPTY_TASK_LOG,
5519
+ view: import_core28.EMPTY_TASK_LOG,
5673
5520
  attempts: [],
5674
5521
  attempt: 0,
5675
5522
  pending: [],
@@ -5702,13 +5549,13 @@ function applyLive(tv, attempt, events) {
5702
5549
  }
5703
5550
  next = {
5704
5551
  ...next,
5705
- view: import_core27.EMPTY_TASK_LOG,
5552
+ view: import_core28.EMPTY_TASK_LOG,
5706
5553
  attempt,
5707
5554
  attempts: next.attempts.includes(attempt) ? next.attempts : [...next.attempts, attempt]
5708
5555
  };
5709
5556
  }
5710
5557
  if (attempt < next.attempt) return next;
5711
- return clampQueueIndex({ ...next, view: events.reduce(import_core27.reduceTaskLog, next.view) });
5558
+ return clampQueueIndex({ ...next, view: events.reduce(import_core28.reduceTaskLog, next.view) });
5712
5559
  }
5713
5560
  function taskLogAbandoned(state) {
5714
5561
  const tv = state.taskView;
@@ -5728,14 +5575,14 @@ function taskLogArrived(state, action) {
5728
5575
  function announceApprovals(state, action) {
5729
5576
  if (state.taskView?.taskId === action.taskId) return state;
5730
5577
  const task = findTask(state.tasks, action.taskId);
5731
- const spoken = action.events.reduce((s, event) => event.type === "approval_requested" ? say(s, "system", `\xB7 Task ${task?.order ?? "?"} waits for approval: ${(0, import_core27.runnerToolSubject)(event.tool, event.args)} \u2014 t on it, then ctrl-y to allow or ctrl-g to deny`) : s, state);
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);
5732
5579
  return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
5733
5580
  }
5734
5581
  function announceCheckpoint(state, action) {
5735
5582
  const task = findTask(state.tasks, action.taskId) ?? state.tasks.find((t) => t.title === action.title);
5736
5583
  const label = task ? `Task ${task.order}` : action.title;
5737
5584
  const where = task?.transport?.kind === "structured" ? "t on it to answer" : `/checkpoint ${task?.order ?? "<id>"} approve|reject to answer`;
5738
- const spoken = say(state, "system", `\xB7 ${label} asks: ${(0, import_core27.truncateCheckpointSummary)(action.summary)} \u2014 ${where}`);
5585
+ const spoken = say(state, "system", `\xB7 ${label} asks: ${(0, import_core28.truncateCheckpointSummary)(action.summary)} \u2014 ${where}`);
5739
5586
  return { ...spoken, scroll: state.scroll };
5740
5587
  }
5741
5588
  function taskLogLoaded(state, action) {
@@ -5743,7 +5590,7 @@ function taskLogLoaded(state, action) {
5743
5590
  if (!tv || tv.taskId !== action.taskId) return state;
5744
5591
  const loaded = action.attempt >= 1 ? {
5745
5592
  ...tv,
5746
- view: (0, import_core27.replayTaskLog)(action.events),
5593
+ view: (0, import_core28.replayTaskLog)(action.events),
5747
5594
  attempts: action.attempts ?? tv.attempts,
5748
5595
  attempt: action.attempt
5749
5596
  } : { ...tv, attempts: action.attempts ?? tv.attempts };
@@ -6120,7 +5967,7 @@ function handlePlanKey(state, key) {
6120
5967
  if (!state.sessionId || key.name !== "char") return step(state);
6121
5968
  if (key.char === "E") return step(state, [{ type: "execute", sessionId: state.sessionId }]);
6122
5969
  if (key.char === "S") return step(state, [{ type: "stopExecution", sessionId: state.sessionId }]);
6123
- const action = key.char === "m" ? (0, import_core28.markAction)(task) : PLAN_SHORTCUTS[key.char ?? ""];
5970
+ const action = key.char === "m" ? (0, import_core29.markAction)(task) : PLAN_SHORTCUTS[key.char ?? ""];
6124
5971
  if (action === "force-start" && task.mergeGate?.length) return confirmForceStartPastGate(state, task);
6125
5972
  if (action) {
6126
5973
  return step(state, [taskActionEffect(state, state.sessionId, task.id, action)]);
@@ -6155,14 +6002,14 @@ function commitTaskEdit(state, task, editor) {
6155
6002
  sessionId: state.sessionId,
6156
6003
  taskId: task.id,
6157
6004
  changes: { prompt },
6158
- message: `Task ${(0, import_core28.taskRef)(task)} prompt updated.`
6005
+ message: `Task ${(0, import_core29.taskRef)(task)} prompt updated.`
6159
6006
  }]);
6160
6007
  }
6161
6008
 
6162
6009
  // src/tui/reducers/turnQueue.ts
6163
- var import_core29 = require("@ordewell/core");
6010
+ var import_core30 = require("@ordewell/core");
6164
6011
  function drainQueue(state, settled) {
6165
- const next = (0, import_core29.drainNext)(state.queuedPrompts);
6012
+ const next = (0, import_core30.drainNext)(state.queuedPrompts);
6166
6013
  const ended = disarmStop({ ...settled, stopRequested: false });
6167
6014
  if (!next) return step(ended);
6168
6015
  const spoken = say(ended, "user", next.text);
@@ -6181,14 +6028,14 @@ function stopPlanning(state, sessionId) {
6181
6028
  return step(queueToEditor(cutTurn({ ...dismissed, stopArmed: false, stopRequested: true })), [{ type: "cancelPlanning", sessionId }]);
6182
6029
  }
6183
6030
  function queueToEditor(state) {
6184
- const all = (0, import_core29.unsendAll)(state.queuedPrompts);
6031
+ const all = (0, import_core30.unsendAll)(state.queuedPrompts);
6185
6032
  if (!all) return state;
6186
6033
  return { ...state, queuedPrompts: all.rest, editor: { ...state.editor, text: all.text, cursor: all.text.length } };
6187
6034
  }
6188
6035
  function unsendLatest(state) {
6189
- const latest = (0, import_core29.unsendLatest)(state.queuedPrompts);
6036
+ const latest = (0, import_core30.unsendLatest)(state.queuedPrompts);
6190
6037
  if (!latest) return step(state);
6191
- const text = (0, import_core29.aheadOfDraft)(latest.text, state.editor.text);
6038
+ const text = (0, import_core30.aheadOfDraft)(latest.text, state.editor.text);
6192
6039
  return step({
6193
6040
  ...state,
6194
6041
  queuedPrompts: latest.rest,
@@ -6205,7 +6052,7 @@ function plannerEscape(state) {
6205
6052
  }
6206
6053
 
6207
6054
  // src/tui/reducers/commands.ts
6208
- var KNOWN_PROVIDERS2 = Object.keys(import_core30.ALL_PROVIDERS);
6055
+ var KNOWN_PROVIDERS = Object.keys(import_core31.ALL_PROVIDERS);
6209
6056
  function runCommand(state, { name, args }) {
6210
6057
  if (!findCommand(name)) {
6211
6058
  return fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
@@ -6371,8 +6218,8 @@ function newSession(state) {
6371
6218
  taskView: null,
6372
6219
  tasks: [],
6373
6220
  planApproved: false,
6374
- conversation: import_core30.EMPTY_CONVERSATION,
6375
- turnGate: import_core30.NO_TURN,
6221
+ conversation: import_core31.EMPTY_CONVERSATION,
6222
+ turnGate: import_core31.NO_TURN,
6376
6223
  selectedTask: 0,
6377
6224
  expandedTaskId: null,
6378
6225
  taskEditor: null,
@@ -6383,14 +6230,14 @@ function newSession(state) {
6383
6230
  // Prompts belong to the session that raised them; the old planner is gone
6384
6231
  // and its pending requests deny on their own timeout.
6385
6232
  pendingApprovals: [],
6386
- queuedPrompts: import_core30.EMPTY_HOLD,
6233
+ queuedPrompts: import_core31.EMPTY_HOLD,
6387
6234
  stopArmed: false,
6388
6235
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
6389
6236
  }, closeEffects);
6390
6237
  }
6391
6238
  function setModel(state, args) {
6392
6239
  if (args[0] === "set" && args[1]) {
6393
- const runner = (0, import_core30.runnerForProvider)(state.plannerProvider);
6240
+ const runner = (0, import_core31.runnerForProvider)(state.plannerProvider);
6394
6241
  const known = runner ? modelsForRunner(state.models, runner) : [];
6395
6242
  if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
6396
6243
  return fail3(state, `${args[1]} was not discovered for ${runner}.`);
@@ -6413,7 +6260,7 @@ function setModel(state, args) {
6413
6260
  function setPlanner(state, args) {
6414
6261
  if (args[0]) {
6415
6262
  const id = args[0].toLowerCase();
6416
- if (!KNOWN_PROVIDERS2.includes(id)) {
6263
+ if (!KNOWN_PROVIDERS.includes(id)) {
6417
6264
  return fail3(state, `Unknown planner: ${id}. Run /planner with no arguments to see the list.`);
6418
6265
  }
6419
6266
  return step(state, [{ type: "setPlanner", provider: id }]);
@@ -6452,7 +6299,7 @@ function setPlannerEffort(state, args) {
6452
6299
  function setKey(state, args) {
6453
6300
  if (args[0] === "set" && args[1]) {
6454
6301
  const provider = args[1].toLowerCase();
6455
- if (!KNOWN_PROVIDERS2.includes(provider)) {
6302
+ if (!KNOWN_PROVIDERS.includes(provider)) {
6456
6303
  return fail3(state, `Unknown provider: ${provider}. Run /key with no arguments to see the list.`);
6457
6304
  }
6458
6305
  if (!args[2]) {
@@ -6463,16 +6310,16 @@ function setKey(state, args) {
6463
6310
  }
6464
6311
  return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
6465
6312
  }
6466
- const items = import_core30.PROVIDER_PRIORITY.map((id) => ({
6313
+ const items = import_core31.PROVIDER_PRIORITY.map((id) => ({
6467
6314
  id,
6468
- label: import_core30.ALL_PROVIDERS[id].label,
6469
- detail: import_core30.ALL_PROVIDERS[id].apiKeyEnvVar,
6315
+ label: import_core31.ALL_PROVIDERS[id].label,
6316
+ detail: import_core31.ALL_PROVIDERS[id].apiKeyEnvVar,
6470
6317
  selected: state.configuredProviders.includes(id)
6471
6318
  }));
6472
6319
  return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
6473
6320
  }
6474
6321
  function keyPrompt(provider) {
6475
- const meta = import_core30.ALL_PROVIDERS[provider];
6322
+ const meta = import_core31.ALL_PROVIDERS[provider];
6476
6323
  return {
6477
6324
  kind: "prompt",
6478
6325
  title: `${meta.label} API key`,
@@ -6549,19 +6396,19 @@ function runners(state, args) {
6549
6396
  }
6550
6397
  function setMaxParallel(state, arg) {
6551
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.`));
6552
- const limit = (0, import_core30.parseMaxParallel)(arg);
6399
+ const limit = (0, import_core31.parseMaxParallel)(arg);
6553
6400
  if (limit === null) return fail3(state, "Usage: /parallel [<number of tasks, 1 or more>]");
6554
6401
  return step({ ...state, maxParallel: limit }, [{ type: "setMaxParallel", limit }]);
6555
6402
  }
6556
6403
  function setAutonomous(state, arg) {
6557
- if (arg === void 0) return step(say(state, "system", `Autonomy level: ${(0, import_core30.autonomyLevelLabel)(state.autonomous)} \u2014 /auto full or /auto guarded changes it for new plans.`));
6558
- const enabled = (0, import_core30.parseAutonomyLevel)(arg);
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);
6559
6406
  if (enabled === null) return fail3(state, "Usage: /auto [full|guarded]");
6560
6407
  return step({ ...state, autonomous: enabled }, [{ type: "setAutonomous", enabled }]);
6561
6408
  }
6562
6409
  function setTransport(state, arg) {
6563
6410
  const value = arg?.toLowerCase() ?? (state.runnerTransport === "structured" ? "terminal" : "structured");
6564
- if (!(0, import_core30.isRunnerTransport)(value)) return fail3(state, "Usage: /transport [terminal|structured]");
6411
+ if (!(0, import_core31.isRunnerTransport)(value)) return fail3(state, "Usage: /transport [terminal|structured]");
6565
6412
  return step(state, [{ type: "setTransport", transport: value }]);
6566
6413
  }
6567
6414
  function setMouseCapture(state, arg) {
@@ -6738,7 +6585,7 @@ function choose(state, picker2, item) {
6738
6585
  sessionId: state.sessionId,
6739
6586
  taskId: task.id,
6740
6587
  changes: { dependencies: picker2.chosen },
6741
- message: picker2.chosen.length > 0 ? `Task ${(0, import_core31.taskRef)(task)} now depends on ${picker2.chosen.length} task${picker2.chosen.length === 1 ? "" : "s"}.` : `Task ${(0, import_core31.taskRef)(task)} no longer depends on anything.`
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.`
6742
6589
  }]);
6743
6590
  }
6744
6591
  if (!item) return step(state);
@@ -6815,7 +6662,7 @@ function choose(state, picker2, item) {
6815
6662
  }
6816
6663
 
6817
6664
  // src/tui/reducers/incoming.ts
6818
- var import_core32 = require("@ordewell/core");
6665
+ var import_core33 = require("@ordewell/core");
6819
6666
  var planText = (value, fallback = "") => sanitize(String(value ?? fallback));
6820
6667
  var planLabel = (value, fallback = "") => planText(value, fallback).replace(/\n+/g, " ");
6821
6668
  var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
@@ -6846,7 +6693,7 @@ function toTaskView2(t, index) {
6846
6693
  prompt: typeof t.prompt === "string" ? sanitize(t.prompt) : void 0,
6847
6694
  type: t.type === "user" ? "user" : "ai",
6848
6695
  status: planLabel(t.status, "pending"),
6849
- awaitingReason: t.status === "awaiting_user" && (0, import_core32.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
6696
+ awaitingReason: t.status === "awaiting_user" && (0, import_core33.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
6850
6697
  dependencies: Array.isArray(t.dependencies) ? t.dependencies.map((id) => planLabel(id)) : [],
6851
6698
  assignedRunner: typeof t.assignedRunner === "string" ? planLabel(t.assignedRunner) : void 0,
6852
6699
  taskMode: typeof t.taskMode === "string" ? planLabel(t.taskMode) : void 0,
@@ -6879,12 +6726,12 @@ function applySettings(state, settings) {
6879
6726
  plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
6880
6727
  plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
6881
6728
  maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
6882
- runnerTransport: (0, import_core32.isRunnerTransport)(settings.runnerTransport) ? settings.runnerTransport : state.runnerTransport,
6729
+ runnerTransport: (0, import_core33.isRunnerTransport)(settings.runnerTransport) ? settings.runnerTransport : state.runnerTransport,
6883
6730
  allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist
6884
6731
  };
6885
6732
  }
6886
6733
  function runLabel(tasks, gate = null) {
6887
- const running = tasks.filter(import_core23.isTaskRunning);
6734
+ const running = tasks.filter(import_core24.isTaskRunning);
6888
6735
  if (running.length > 0) {
6889
6736
  const [first] = running;
6890
6737
  const name = first.assignedRunner ? `${first.title} \xB7 ${first.assignedRunner}` : first.title;
@@ -6950,13 +6797,13 @@ function reduceAction(state, action) {
6950
6797
  goal: "",
6951
6798
  taskView: null,
6952
6799
  pendingApprovals: [],
6953
- queuedPrompts: import_core33.EMPTY_HOLD,
6800
+ queuedPrompts: import_core34.EMPTY_HOLD,
6954
6801
  stopArmed: false,
6955
6802
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
6956
6803
  });
6957
6804
  case "chatRestored":
6958
6805
  if (stale(state, action.sessionId)) return step(state);
6959
- return step({ ...state, conversation: (0, import_core33.fromTranscript)(action.history, action.researchLog, action.plannerUsage), scroll: 0 });
6806
+ return step({ ...state, conversation: (0, import_core34.fromTranscript)(action.history, action.researchLog, action.plannerUsage), scroll: 0 });
6960
6807
  case "planUpdated": {
6961
6808
  if (stale(state, action.sessionId)) return step(state);
6962
6809
  const isolation = isolationOfPlan(action.plan);
@@ -7003,9 +6850,9 @@ function reduceAction(state, action) {
7003
6850
  case "taskStarted": {
7004
6851
  if (stale(state, action.sessionId)) return step(state);
7005
6852
  const existing = findTask(state.tasks, action.taskId);
7006
- if (existing && (0, import_core23.isTaskRunning)(existing)) return step(state);
7007
- const tasks = state.tasks.map((t) => t.id === action.taskId && !(0, import_core23.isTaskRunning)(t) ? { ...t, status: "in_progress" } : t);
7008
- const spoken = say(state, "system", (0, import_core33.taskStartedNotice)(action.title, action.runner));
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));
7009
6856
  return step({ ...spoken, tasks, status: "executing", busyLabel: runLabel(tasks) });
7010
6857
  }
7011
6858
  case "taskCheckpoint":
@@ -7046,12 +6893,6 @@ function reduceAction(state, action) {
7046
6893
  const status = runStatus(next, tasks);
7047
6894
  return step({ ...next, status, busyLabel: status === "executing" ? runLabel(tasks, gate) : state.busyLabel });
7048
6895
  }
7049
- case "queueReady": {
7050
- if (stale(state, action.sessionId)) return step(state);
7051
- const sessionId = action.sessionId ?? state.sessionId;
7052
- if (sessionId === null) return step(state);
7053
- return step(state, [{ type: "processQueued", sessionId }]);
7054
- }
7055
6896
  case "isolationBlocked": {
7056
6897
  if (stale(state, action.sessionId)) return step(state);
7057
6898
  return step(blockedPicker(state, action.message, action.repos));
@@ -7084,6 +6925,14 @@ function reduceAction(state, action) {
7084
6925
  busyLabel: ""
7085
6926
  });
7086
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
+ }
7087
6936
  case "settingsLoaded":
7088
6937
  return step(applySettings(state, action.settings));
7089
6938
  case "modelsLoaded":
@@ -7118,7 +6967,7 @@ function reduceAction(state, action) {
7118
6967
  busyLabel: "",
7119
6968
  planApproved: false,
7120
6969
  pendingApprovals: [],
7121
- queuedPrompts: import_core33.EMPTY_HOLD,
6970
+ queuedPrompts: import_core34.EMPTY_HOLD,
7122
6971
  stopArmed: false,
7123
6972
  overlay: state.overlay?.kind === "approval" ? null : state.overlay
7124
6973
  });
@@ -7185,7 +7034,7 @@ function sameGate(a, b) {
7185
7034
  return a.paused === b.paused && JSON.stringify(a.handoff) === JSON.stringify(b.handoff);
7186
7035
  }
7187
7036
  function runStatus(state, tasks) {
7188
- if (tasks.some(import_core23.isTaskRunning)) return "executing";
7037
+ if (tasks.some(import_core24.isTaskRunning)) return "executing";
7189
7038
  if (tasks.some((t) => t.status === "awaiting_user") || state.gate?.paused) return "executing";
7190
7039
  return plannerInFlight(state) ? state.status : "idle";
7191
7040
  }
@@ -7254,7 +7103,7 @@ function submit(state) {
7254
7103
  return step(cleared, [{ type: "sendTaskMessage", sessionId: state.sessionId, taskId, text }]);
7255
7104
  }
7256
7105
  if (plannerInFlight(state)) {
7257
- return step({ ...cleared, queuedPrompts: (0, import_core33.holdPrompt)(state.queuedPrompts, text) });
7106
+ return step({ ...cleared, queuedPrompts: (0, import_core34.holdPrompt)(state.queuedPrompts, text) });
7258
7107
  }
7259
7108
  const spoken = say(cleared, "user", text);
7260
7109
  const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
@@ -7314,10 +7163,10 @@ function createApp(deps) {
7314
7163
 
7315
7164
  // src/tui/effects.ts
7316
7165
  var import_child_process4 = require("child_process");
7317
- var import_core35 = require("@ordewell/core");
7166
+ var import_core36 = require("@ordewell/core");
7318
7167
 
7319
7168
  // src/tui/inbound.ts
7320
- var import_core34 = require("@ordewell/core");
7169
+ var import_core35 = require("@ordewell/core");
7321
7170
  var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
7322
7171
  "planner_message",
7323
7172
  "planner_turn_started",
@@ -7532,7 +7381,7 @@ function dispatchLifecycle(dispatch, event, sessionId) {
7532
7381
  idleSince: task.idleSince ?? null,
7533
7382
  isolation: task.isolation,
7534
7383
  transport: task.transport,
7535
- awaitingReason: (0, import_core34.isAwaitingReason)(task.awaitingReason) ? task.awaitingReason : void 0,
7384
+ awaitingReason: (0, import_core35.isAwaitingReason)(task.awaitingReason) ? task.awaitingReason : void 0,
7536
7385
  ...typeof task.checkpoint === "string" && task.checkpoint ? { checkpoint: sanitize(task.checkpoint) } : {},
7537
7386
  ...task.continuable ? { continuable: true } : {},
7538
7387
  awaitingApproval: typeof task.awaitingApproval === "number" && task.awaitingApproval > 0 ? task.awaitingApproval : void 0,
@@ -7558,13 +7407,6 @@ function dispatchLifecycle(dispatch, event, sessionId) {
7558
7407
  case "review_approved":
7559
7408
  dispatch({ type: "notice", message: "Plan approved." });
7560
7409
  return;
7561
- // The orchestrator paused fan-out because a structural edit is queued; drain
7562
- // it so the planner reconciles the plan and dependents resume spawning.
7563
- // Dropped on the floor, the queue suppresses every later tick() — which is
7564
- // exactly "tasks did not fan out when the dependent ones finished."
7565
- case "queue_ready":
7566
- dispatch({ type: "queueReady", sessionId });
7567
- return;
7568
7410
  case "execution_complete":
7569
7411
  dispatch({ type: "executionComplete", summary: event.summary, sessionId });
7570
7412
  return;
@@ -7614,8 +7456,7 @@ async function runEffect(effect, deps) {
7614
7456
  await perform(effect, deps);
7615
7457
  } catch (err) {
7616
7458
  if (!isConnectionRefused(err)) {
7617
- const message = err instanceof Error ? err.message : String(err);
7618
- deps.dispatch({ type: "failed", message: explain(message) });
7459
+ deps.dispatch({ type: "failed", message: explain(err) });
7619
7460
  return;
7620
7461
  }
7621
7462
  let revived = false;
@@ -7632,21 +7473,20 @@ async function runEffect(effect, deps) {
7632
7473
  try {
7633
7474
  await perform(effect, deps);
7634
7475
  } catch (retryErr) {
7635
- const message = retryErr instanceof Error ? retryErr.message : String(retryErr);
7636
7476
  deps.dispatch({
7637
7477
  type: "failed",
7638
7478
  // A fresh daemon holds no sessions, so the retry of anything
7639
7479
  // session-scoped legitimately 404s. Say which of the two happened.
7640
- message: isConnectionRefused(retryErr) ? describeConnectionRefused(deps.port) : explain(message)
7480
+ message: isConnectionRefused(retryErr) ? describeConnectionRefused(deps.port) : explain(retryErr)
7641
7481
  });
7642
7482
  }
7643
7483
  }
7644
7484
  }
7645
- function explain(message) {
7646
- if (message === "Session not found") {
7485
+ function explain(err) {
7486
+ if (err instanceof DaemonError && err.code === "session_not_found") {
7647
7487
  return "This server is no longer holding that session \u2014 it was probably restarted. Reload it with /sessions.";
7648
7488
  }
7649
- return message;
7489
+ return err instanceof Error ? err.message : String(err);
7650
7490
  }
7651
7491
  function persistAfterDaemon(deps, env) {
7652
7492
  for (const [key, value] of Object.entries(env)) deps.setEnvVar(key, value);
@@ -7702,19 +7542,15 @@ async function perform(effect, deps) {
7702
7542
  timer.unref?.();
7703
7543
  return;
7704
7544
  }
7705
- case "processQueued":
7706
- await api.processQueued(effect.sessionId);
7707
- await refreshPlan(deps, effect.sessionId);
7708
- return;
7709
7545
  case "taskAction": {
7710
7546
  const { sessionId, taskId, action } = effect;
7711
- const request = action === "cancel" || action === "retry" || action === "force-start" ? () => api.taskControl(sessionId, taskId, action) : (0, import_core35.markRequestFor)(action) === "uncomplete" ? () => api.markTaskIncomplete(sessionId, taskId) : () => api.markTaskComplete(sessionId, taskId);
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);
7712
7548
  await (effect.watch ? withExecutionStream(deps, sessionId, request) : request());
7713
7549
  await refreshPlan(deps, sessionId);
7714
7550
  return;
7715
7551
  }
7716
7552
  case "addTask": {
7717
- const fields = (0, import_core35.newTaskFields)(effect.title);
7553
+ const fields = (0, import_core36.newTaskFields)(effect.title);
7718
7554
  if (!fields) return;
7719
7555
  await api.addTask(effect.sessionId, { ...fields });
7720
7556
  await refreshPlan(deps, effect.sessionId);
@@ -7835,11 +7671,11 @@ async function perform(effect, deps) {
7835
7671
  dispatch({ type: "notice", message: `Orchestrator model set to ${effect.modelId}.` });
7836
7672
  return;
7837
7673
  case "setPlanner": {
7838
- const meta = import_core35.ALL_PROVIDERS[effect.provider];
7674
+ const meta = import_core36.ALL_PROVIDERS[effect.provider];
7839
7675
  if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
7840
7676
  const env = { AI_PROVIDER: effect.provider };
7841
7677
  const settings = await api.updateSettings({ env });
7842
- const recall2 = describePlannerSwitch(settings, effect.provider);
7678
+ const recall2 = plannerSwitchRecall(settings);
7843
7679
  persistAfterDaemon(deps, { ...env, ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
7844
7680
  dispatch({
7845
7681
  type: "settingsLoaded",
@@ -7847,7 +7683,7 @@ async function perform(effect, deps) {
7847
7683
  });
7848
7684
  dispatch({
7849
7685
  type: "notice",
7850
- message: `${(0, import_core35.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
7686
+ message: `${(0, import_core36.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
7851
7687
  });
7852
7688
  await loadModels(deps);
7853
7689
  return;
@@ -7859,7 +7695,7 @@ async function perform(effect, deps) {
7859
7695
  dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
7860
7696
  return;
7861
7697
  case "setApiKey": {
7862
- const meta = import_core35.ALL_PROVIDERS[effect.provider];
7698
+ const meta = import_core36.ALL_PROVIDERS[effect.provider];
7863
7699
  if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
7864
7700
  await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
7865
7701
  deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
@@ -7897,7 +7733,7 @@ async function perform(effect, deps) {
7897
7733
  return;
7898
7734
  case "setAutonomous":
7899
7735
  deps.setEnvVar("ORDEWELL_AUTONOMOUS_MODE", String(effect.enabled));
7900
- dispatch({ type: "notice", message: `Autonomy level: ${(0, import_core35.autonomyLevelLabel)(effect.enabled)} for new plans.` });
7736
+ dispatch({ type: "notice", message: `Autonomy level: ${(0, import_core36.autonomyLevelLabel)(effect.enabled)} for new plans.` });
7901
7737
  return;
7902
7738
  case "setMouseCapture":
7903
7739
  deps.setMouseCapture(effect.enabled);
@@ -8031,7 +7867,7 @@ function osc52(text) {
8031
7867
  function copySelection(deps, text) {
8032
7868
  if (!text) return;
8033
7869
  const lines = text.split("\n").length;
8034
- const command = (0, import_core35.clipboardCopyCommand)(deps.hasBin);
7870
+ const command = (0, import_core36.clipboardCopyCommand)(deps.hasBin);
8035
7871
  if (command) {
8036
7872
  try {
8037
7873
  (deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
@@ -8055,9 +7891,14 @@ async function withExecutionStream(deps, sessionId, request) {
8055
7891
  const inbound = inboundFor(deps.api, deps.dispatch, sessionId);
8056
7892
  const stream = deps.api.streamExecution(sessionId, inbound.execution(), settleReady);
8057
7893
  void stream.catch(settleReady);
8058
- await streamReady;
8059
- await request();
8060
- await stream;
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 });
8061
7902
  }
8062
7903
  async function converse(deps, sessionId, call) {
8063
7904
  const inbound = inboundFor(deps.api, deps.dispatch, sessionId);
@@ -8422,7 +8263,7 @@ async function handleTui(subArgs) {
8422
8263
  }
8423
8264
  const workspace = flag(subArgs, "--workspace") || process.cwd();
8424
8265
  try {
8425
- (0, import_core36.assertWorkspaceExists)(workspace);
8266
+ (0, import_core37.assertWorkspaceExists)(workspace);
8426
8267
  } catch {
8427
8268
  console.error(
8428
8269
  `Workspace "${workspace}" does not exist.
@@ -8431,7 +8272,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
8431
8272
  );
8432
8273
  process.exit(1);
8433
8274
  }
8434
- registerSkillCommands((0, import_core36.createSkillsService)(workspace).listSkills());
8275
+ registerSkillCommands((0, import_core37.createSkillsService)(workspace).listSkills());
8435
8276
  const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
8436
8277
  const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
8437
8278
  const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
@@ -8448,7 +8289,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
8448
8289
  workspace,
8449
8290
  port,
8450
8291
  dispatch: (action) => app.dispatch(action),
8451
- newSessionId: () => (0, import_core36.mintSessionId)(),
8292
+ newSessionId: () => (0, import_core37.mintSessionId)(),
8452
8293
  setEnvVar: (key, value) => {
8453
8294
  writeEnvVar(findEnvFile(), key, value);
8454
8295
  process.env[key] = value;
@@ -8499,6 +8340,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
8499
8340
  app.dispatch({ type: "resize", rows: r, cols: c });
8500
8341
  });
8501
8342
  process.on("uncaughtException", (err) => {
8343
+ terminal?.close();
8502
8344
  console.error(`Fatal: ${err.message}`);
8503
8345
  shutdown(1);
8504
8346
  });
@@ -8573,7 +8415,7 @@ function cliVersion() {
8573
8415
  // src/main.ts
8574
8416
  var TUI_FLAGS = /* @__PURE__ */ new Set(["--workspace", "--port"]);
8575
8417
  async function main() {
8576
- (0, import_core37.migrateOldConfigDir)();
8418
+ (0, import_core38.migrateOldConfigDir)();
8577
8419
  loadEnvFile();
8578
8420
  const argv = process.argv.slice(2);
8579
8421
  if (TUI_FLAGS.has(argv[0] ?? "")) argv.unshift("tui");