@herdingchats/friday 0.1.653 → 0.1.663

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 (3) hide show
  1. package/herd.mjs +1247 -1142
  2. package/package.json +2 -2
  3. package/terminal-host.mjs +37 -3
package/herd.mjs CHANGED
@@ -18,7 +18,7 @@ var __export = (target, all) => {
18
18
  var define_HERD_PACKAGE_default;
19
19
  var init_define_HERD_PACKAGE = __esm({
20
20
  "<define:HERD_PACKAGE>"() {
21
- define_HERD_PACKAGE_default = { version: "0.1.653", gitHead: "66b195d50009a6d5e3e3f7c256873c5b63fb1271" };
21
+ define_HERD_PACKAGE_default = { version: "0.1.663", gitHead: "8ba46f6a5262b074559ade9422eb6ff6a3326b3f" };
22
22
  }
23
23
  });
24
24
 
@@ -472,8 +472,69 @@ var init_github = __esm({
472
472
  }
473
473
  });
474
474
 
475
- // ../common-chat/src/usage.ts
475
+ // ../common-chat/src/terminals.ts
476
476
  import { z as z6 } from "zod";
477
+ var terminalAccessSchema, machineTerminalsSchema, terminalModeSchema, terminalTargetSchema, terminalClosedReasonSchema, size, terminalFrameSchema, TERMINAL_DIAL_MS, TERMINAL_IDLE_MS, TERMINAL_MAX_MS, TERMINAL_PROOF_MS;
478
+ var init_terminals = __esm({
479
+ "../common-chat/src/terminals.ts"() {
480
+ "use strict";
481
+ init_define_HERD_PACKAGE();
482
+ terminalAccessSchema = z6.enum(["drive", "watch", "off"]);
483
+ machineTerminalsSchema = z6.object({ remote: terminalAccessSchema });
484
+ terminalModeSchema = z6.enum(["watch", "drive"]);
485
+ terminalTargetSchema = z6.discriminatedUnion("kind", [
486
+ z6.object({ kind: z6.literal("conversation"), conversationId: z6.string().min(1) }),
487
+ z6.object({ kind: z6.literal("activation"), agent: z6.string().min(1) })
488
+ ]);
489
+ terminalClosedReasonSchema = z6.enum([
490
+ // The page closed it, or the person let it go.
491
+ "closed",
492
+ // The machine never dialed, or its socket dropped.
493
+ "expired",
494
+ "machine_gone",
495
+ // Its limits: no input for a while, too long in all, or the browser's sign-in is due a check.
496
+ "idle",
497
+ "too_long",
498
+ "reauthorize",
499
+ // A newer session took its place on the machine.
500
+ "superseded",
501
+ // The machine wouldn't open it, saying why in `detail`.
502
+ "refused",
503
+ // The terminal itself exited.
504
+ "exited"
505
+ ]);
506
+ size = {
507
+ cols: z6.number().int().min(10).max(1e3),
508
+ rows: z6.number().int().min(5).max(1e3)
509
+ };
510
+ terminalFrameSchema = z6.discriminatedUnion("type", [
511
+ z6.object({ type: z6.literal("snapshot"), data: z6.string() }),
512
+ z6.object({ type: z6.literal("data"), data: z6.string() }),
513
+ z6.object({ type: z6.literal("exit"), exitCode: z6.number().int().nullable() }),
514
+ z6.object({ type: z6.literal("mode"), mode: terminalModeSchema }),
515
+ z6.object({ type: z6.literal("input"), data: z6.string().max(64 * 1024) }),
516
+ z6.object({ type: z6.literal("resize"), ...size }),
517
+ z6.object({ type: z6.literal("drive") }),
518
+ z6.object({ type: z6.literal("watch") }),
519
+ z6.object({
520
+ type: z6.literal("closed"),
521
+ reason: terminalClosedReasonSchema,
522
+ detail: z6.string().max(500).optional()
523
+ }),
524
+ z6.object({
525
+ type: z6.literal("refused"),
526
+ code: z6.enum(["confirmation_needed", "watch_only", "not_driving"])
527
+ })
528
+ ]);
529
+ TERMINAL_DIAL_MS = 60 * 1e3;
530
+ TERMINAL_IDLE_MS = 15 * 60 * 1e3;
531
+ TERMINAL_MAX_MS = 2 * 60 * 60 * 1e3;
532
+ TERMINAL_PROOF_MS = 60 * 60 * 1e3;
533
+ }
534
+ });
535
+
536
+ // ../common-chat/src/usage.ts
537
+ import { z as z7 } from "zod";
477
538
  function currentUsage(usage, now) {
478
539
  if (!usage) return void 0;
479
540
  const fresh = (window) => window && window.resetsAt > now ? window : void 0;
@@ -491,38 +552,38 @@ var init_usage = __esm({
491
552
  "../common-chat/src/usage.ts"() {
492
553
  "use strict";
493
554
  init_define_HERD_PACKAGE();
494
- usageWindowSchema = z6.object({
495
- usedPercentage: z6.number().min(0).max(100),
496
- resetsAt: z6.number().int().nonnegative()
555
+ usageWindowSchema = z7.object({
556
+ usedPercentage: z7.number().min(0).max(100),
557
+ resetsAt: z7.number().int().nonnegative()
497
558
  });
498
559
  USAGE_AGENTS = {
499
560
  "claude-code": "Claude",
500
561
  codex: "Codex",
501
562
  agy: "Antigravity"
502
563
  };
503
- usageSchema = z6.object({
564
+ usageSchema = z7.object({
504
565
  fiveHour: usageWindowSchema.optional(),
505
566
  sevenDay: usageWindowSchema.optional(),
506
- agent: z6.enum(["claude-code", "codex", "agy"]).optional()
567
+ agent: z7.enum(["claude-code", "codex", "agy"]).optional()
507
568
  }).refine((usage) => usage.fiveHour || usage.sevenDay, "Usage needs at least one window");
508
569
  MAX_ACTIVITY_LENGTH = 150;
509
- activityTextSchema = z6.string().trim().min(1).max(MAX_ACTIVITY_LENGTH).regex(/^[^\r\n]+$/);
570
+ activityTextSchema = z7.string().trim().min(1).max(MAX_ACTIVITY_LENGTH).regex(/^[^\r\n]+$/);
510
571
  MAX_TITLE_LENGTH = 40;
511
- titleSchema = z6.string().trim().min(1).max(MAX_TITLE_LENGTH).regex(/^[^\r\n]+$/);
512
- listedWindowSchema = z6.union([
572
+ titleSchema = z7.string().trim().min(1).max(MAX_TITLE_LENGTH).regex(/^[^\r\n]+$/);
573
+ listedWindowSchema = z7.union([
513
574
  usageWindowSchema,
514
- z6.object({ reset: z6.literal(true), resetsAt: z6.number().int().nonnegative() })
575
+ z7.object({ reset: z7.literal(true), resetsAt: z7.number().int().nonnegative() })
515
576
  ]);
516
- listedUsageSchema = z6.object({
577
+ listedUsageSchema = z7.object({
517
578
  fiveHour: listedWindowSchema.optional(),
518
579
  sevenDay: listedWindowSchema.optional(),
519
- agent: z6.enum(["claude-code", "codex", "agy"]).optional()
580
+ agent: z7.enum(["claude-code", "codex", "agy"]).optional()
520
581
  });
521
582
  }
522
583
  });
523
584
 
524
585
  // ../common-chat/src/workers.ts
525
- import { z as z7 } from "zod";
586
+ import { z as z8 } from "zod";
526
587
  function agentShortName(cli) {
527
588
  return AGENTS[cli]?.short ?? cli;
528
589
  }
@@ -558,11 +619,11 @@ var init_workers = __esm({
558
619
  "xcode",
559
620
  "image_generation"
560
621
  ];
561
- capabilitySchema = z7.enum(CAPABILITIES);
622
+ capabilitySchema = z8.enum(CAPABILITIES);
562
623
  PLATFORMS = ["macos", "linux", "windows"];
563
- platformSchema = z7.enum(PLATFORMS);
624
+ platformSchema = z8.enum(PLATFORMS);
564
625
  TOOLS = ["xcode", "gpu"];
565
- toolSchema = z7.enum(TOOLS);
626
+ toolSchema = z8.enum(TOOLS);
566
627
  CAPABILITY_WORDING = {
567
628
  macos: "a Mac running macOS",
568
629
  linux: "a Linux computer",
@@ -580,51 +641,51 @@ var init_workers = __esm({
580
641
  PHRASE_MAX = 100;
581
642
  PHRASES_MAX = 10;
582
643
  ABOUT_MAX = 500;
583
- phrase = z7.string().trim().min(1).max(PHRASE_MAX, `at most ${PHRASE_MAX} characters`);
584
- phrases = z7.array(phrase).max(PHRASES_MAX, `at most ${PHRASES_MAX} phrases`);
644
+ phrase = z8.string().trim().min(1).max(PHRASE_MAX, `at most ${PHRASE_MAX} characters`);
645
+ phrases = z8.array(phrase).max(PHRASES_MAX, `at most ${PHRASES_MAX} phrases`);
585
646
  SHARES = ["off", "little", "some", "lot"];
586
- shareSchema = z7.enum(SHARES);
647
+ shareSchema = z8.enum(SHARES);
587
648
  SHARE_LABELS = {
588
649
  off: "off",
589
650
  little: "a little",
590
651
  some: "some",
591
652
  lot: "a lot"
592
653
  };
593
- harnessConfigSchema = z7.object({
654
+ harnessConfigSchema = z8.object({
594
655
  share: shareSchema.optional(),
595
656
  /** Read as `share: "off"` when false, from files written before shares. */
596
- enabled: z7.boolean().optional(),
657
+ enabled: z8.boolean().optional(),
597
658
  only: phrases.optional(),
598
659
  great: phrases.optional(),
599
660
  good: phrases.optional(),
600
661
  okay: phrases.optional(),
601
662
  cant: phrases.optional(),
602
663
  /** false starts the harness's profile empty, rather than from herd's defaults. */
603
- defaults: z7.boolean().optional(),
664
+ defaults: z8.boolean().optional(),
604
665
  /** Read as `great`, from files written before profiles. */
605
666
  prefer: phrases.optional(),
606
667
  /** Read as `cant`, from files written before profiles. */
607
668
  avoid: phrases.optional(),
608
- priority: z7.number().int().min(-100).max(100).optional()
669
+ priority: z8.number().int().min(-100).max(100).optional()
609
670
  }).strict();
610
671
  BUILDS_MAX = 4;
611
- buildsSettingSchema = z7.union([
612
- z7.literal(true),
613
- z7.object({ max: z7.number().int().min(1).max(BUILDS_MAX) }).strict()
672
+ buildsSettingSchema = z8.union([
673
+ z8.literal(true),
674
+ z8.object({ max: z8.number().int().min(1).max(BUILDS_MAX) }).strict()
614
675
  ]);
615
676
  CONFIG_REPOS_MAX = 50;
616
- configRepoSchema = z7.object({
617
- remote: z7.string().trim().min(1).max(300),
618
- about: z7.string().trim().max(PHRASE_MAX, `at most ${PHRASE_MAX} characters`).optional()
677
+ configRepoSchema = z8.object({
678
+ remote: z8.string().trim().min(1).max(300),
679
+ about: z8.string().trim().max(PHRASE_MAX, `at most ${PHRASE_MAX} characters`).optional()
619
680
  }).strict();
620
- herdConfigSchema = z7.object({
621
- about: z7.string().trim().max(ABOUT_MAX, `at most ${ABOUT_MAX} characters`).optional(),
681
+ herdConfigSchema = z8.object({
682
+ about: z8.string().trim().max(ABOUT_MAX, `at most ${ABOUT_MAX} characters`).optional(),
622
683
  cant: phrases.optional(),
623
684
  /** Read as `cant`, from files written before profiles. */
624
685
  avoid: phrases.optional(),
625
- harnesses: z7.record(z7.string().regex(/^[a-z][a-z0-9-]*$/), harnessConfigSchema).optional(),
686
+ harnesses: z8.record(z8.string().regex(/^[a-z][a-z0-9-]*$/), harnessConfigSchema).optional(),
626
687
  builds: buildsSettingSchema.optional(),
627
- repos: z7.array(configRepoSchema).max(CONFIG_REPOS_MAX, `at most ${CONFIG_REPOS_MAX} repos`).optional()
688
+ repos: z8.array(configRepoSchema).max(CONFIG_REPOS_MAX, `at most ${CONFIG_REPOS_MAX} repos`).optional()
628
689
  }).strict();
629
690
  HERD_CONFIG_TYPE = `type HerdConfig = {
630
691
  /** What this machine is for, in plain words. At most ${ABOUT_MAX} characters. */
@@ -675,54 +736,55 @@ export default {
675
736
  harnesses: { claude: { great: ["code review"] }, codex: { cant: ["the billing service"] } },
676
737
  } satisfies HerdConfig;`;
677
738
  PROFILE_LEVELS = ["great", "good", "okay", "cant"];
678
- profileEntrySchema = z7.object({
679
- statement: z7.string().min(1),
680
- level: z7.enum(PROFILE_LEVELS),
681
- source: z7.enum(["default", "owner"])
682
- });
683
- workerSchema = z7.object({
684
- key: z7.string().min(1),
685
- name: z7.string().min(1),
686
- machineId: z7.string().min(1),
687
- machineName: z7.string().min(1),
688
- agent: z7.string().min(1),
689
- capabilities: z7.array(capabilitySchema),
690
- about: z7.string().optional(),
691
- only: z7.array(z7.string()),
739
+ profileEntrySchema = z8.object({
740
+ statement: z8.string().min(1),
741
+ level: z8.enum(PROFILE_LEVELS),
742
+ source: z8.enum(["default", "owner"])
743
+ });
744
+ workerSchema = z8.object({
745
+ key: z8.string().min(1),
746
+ name: z8.string().min(1),
747
+ machineId: z8.string().min(1),
748
+ machineName: z8.string().min(1),
749
+ agent: z8.string().min(1),
750
+ capabilities: z8.array(capabilitySchema),
751
+ about: z8.string().optional(),
752
+ only: z8.array(z8.string()),
692
753
  /** What it's great, good and okay at, and can't do: its agent's defaults with its owner's word. */
693
- profile: z7.array(profileEntrySchema),
694
- priority: z7.number().int(),
754
+ profile: z8.array(profileEntrySchema),
755
+ priority: z8.number().int(),
695
756
  /** How much of its agent its owner has. Servers from before shares send none, which is "some". */
696
757
  share: shareSchema.default("some"),
697
- enabled: z7.boolean(),
758
+ enabled: z8.boolean(),
698
759
  /** Whether its agent checked in from herd's terminal on this version, so it's offered work. */
699
- activated: z7.boolean(),
760
+ activated: z8.boolean(),
700
761
  activation: agentActivationSchema.optional(),
701
- online: z7.boolean(),
762
+ online: z8.boolean(),
702
763
  /** A folder its machine works in that isn't there, so it's offered no work until it is. */
703
- folderUnavailable: z7.object({ path: z7.string().min(1), target: z7.string().min(1).optional() }).optional(),
764
+ folderUnavailable: z8.object({ path: z8.string().min(1), target: z8.string().min(1).optional() }).optional(),
704
765
  // Its machine's repos, each marked whether herd has cloned it there. Older daemons send no `cloned`.
705
- repos: z7.array(
706
- z7.object({ name: z7.string(), remote: z7.string(), cloned: z7.boolean().optional() })
766
+ repos: z8.array(
767
+ z8.object({ name: z8.string(), remote: z8.string(), cloned: z8.boolean().optional() })
707
768
  ),
708
- machineCreatedAt: z7.number().int().nonnegative()
769
+ machineCreatedAt: z8.number().int().nonnegative()
709
770
  });
710
771
  }
711
772
  });
712
773
 
713
774
  // ../common-chat/src/machines.ts
714
- import { z as z8 } from "zod";
775
+ import { z as z9 } from "zod";
715
776
  var nonEmpty4, time2, terminalStateSchema, agentStatusSchema, machineRepoSchema, isCloned, machineCliSchema, folderUnavailableSchema, machineRegistrationSchema, machineSchema, machinePresenceSchema, worktreeSchema, MAX_REPO_HINTS, repoHintSchema, terminalSchema, offerSchema, offerAnswerSchema, dispatchFrameSchema, machineMessageSchema;
716
777
  var init_machines = __esm({
717
778
  "../common-chat/src/machines.ts"() {
718
779
  "use strict";
719
780
  init_define_HERD_PACKAGE();
720
781
  init_activation();
782
+ init_terminals();
721
783
  init_usage();
722
784
  init_workers();
723
- nonEmpty4 = z8.string().min(1);
724
- time2 = z8.number().int().nonnegative();
725
- terminalStateSchema = z8.enum([
785
+ nonEmpty4 = z9.string().min(1);
786
+ time2 = z9.number().int().nonnegative();
787
+ terminalStateSchema = z9.enum([
726
788
  "working",
727
789
  "idle",
728
790
  "waiting_on_someone",
@@ -730,64 +792,66 @@ var init_machines = __esm({
730
792
  "signed_out",
731
793
  "unknown"
732
794
  ]);
733
- agentStatusSchema = z8.enum(["working", "idle", "waiting_on_someone", "exited"]);
734
- machineRepoSchema = z8.object({
795
+ agentStatusSchema = z9.enum(["working", "idle", "waiting_on_someone", "exited"]);
796
+ machineRepoSchema = z9.object({
735
797
  name: nonEmpty4,
736
798
  remote: nonEmpty4,
737
- cloned: z8.boolean().optional()
799
+ cloned: z9.boolean().optional()
738
800
  });
739
801
  isCloned = (repo) => repo.cloned !== false;
740
- machineCliSchema = z8.object({
802
+ machineCliSchema = z9.object({
741
803
  name: nonEmpty4,
742
804
  version: nonEmpty4,
743
- runnable: z8.boolean().optional(),
744
- capabilities: z8.array(capabilitySchema).optional(),
805
+ runnable: z9.boolean().optional(),
806
+ capabilities: z9.array(capabilitySchema).optional(),
745
807
  activation: agentActivationSchema.optional()
746
808
  });
747
- folderUnavailableSchema = z8.object({
748
- path: z8.string().min(1).max(1e3),
749
- target: z8.string().min(1).max(1e3).optional()
809
+ folderUnavailableSchema = z9.object({
810
+ path: z9.string().min(1).max(1e3),
811
+ target: z9.string().min(1).max(1e3).optional()
750
812
  });
751
- machineRegistrationSchema = z8.object({
813
+ machineRegistrationSchema = z9.object({
752
814
  id: nonEmpty4,
753
815
  name: nonEmpty4,
754
- repos: z8.array(machineRepoSchema),
755
- clis: z8.array(machineCliSchema),
816
+ repos: z9.array(machineRepoSchema),
817
+ clis: z9.array(machineCliSchema),
756
818
  // The platform the agent actually runs on, and tools from herd's fixed list.
757
819
  platform: platformSchema.optional(),
758
- tools: z8.array(toolSchema).optional(),
820
+ tools: z9.array(toolSchema).optional(),
759
821
  // The machine's validated `herd.config.ts`, and why the file on disk didn't load if it didn't.
760
822
  config: herdConfigSchema.optional(),
761
- configError: z8.string().max(1e3).optional(),
823
+ configError: z9.string().max(1e3).optional(),
762
824
  // Whether its GitHub CLI is signed in, and as whom.
763
825
  gh: gitHubCliSchema.optional(),
764
826
  // A folder it works in that isn't there, while it takes no work.
765
827
  folderUnavailable: folderUnavailableSchema.optional(),
766
828
  // The daemon's own package version, or `dev` from a checkout. Daemons from before send none,
767
829
  // which reads as older than any that does.
768
- herdVersion: z8.string().min(1).max(50).optional()
830
+ herdVersion: z9.string().min(1).max(50).optional(),
831
+ // What the web may do with its terminals. A daemon from before terminal sessions doesn't say.
832
+ terminals: machineTerminalsSchema.optional()
769
833
  });
770
834
  machineSchema = machineRegistrationSchema.extend({
771
835
  ownerId: nonEmpty4,
772
- online: z8.boolean(),
836
+ online: z9.boolean(),
773
837
  lastSeenAt: time2,
774
838
  createdAt: time2
775
839
  });
776
- machinePresenceSchema = z8.object({ name: nonEmpty4, online: z8.boolean() });
777
- worktreeSchema = z8.object({ repo: nonEmpty4, branch: nonEmpty4 });
840
+ machinePresenceSchema = z9.object({ name: nonEmpty4, online: z9.boolean() });
841
+ worktreeSchema = z9.object({ repo: nonEmpty4, branch: nonEmpty4 });
778
842
  MAX_REPO_HINTS = 10;
779
- repoHintSchema = z8.object({
843
+ repoHintSchema = z9.object({
780
844
  remote: nonEmpty4,
781
845
  branch: nonEmpty4.optional(),
782
846
  folder: nonEmpty4.optional()
783
847
  });
784
- terminalSchema = z8.object({
848
+ terminalSchema = z9.object({
785
849
  id: nonEmpty4,
786
850
  name: nonEmpty4,
787
851
  machineId: nonEmpty4,
788
852
  conversationId: nonEmpty4,
789
853
  // The lead terminal receives the conversation's notices. Others are started with `herd run`.
790
- lead: z8.boolean(),
854
+ lead: z9.boolean(),
791
855
  // Only for a conversation from before folders, which works in one repo's worktree.
792
856
  worktree: worktreeSchema.optional(),
793
857
  // The program the screen reader recognized, such as `claude-code` or `shell`, when it knows.
@@ -795,7 +859,7 @@ var init_machines = __esm({
795
859
  state: terminalStateSchema,
796
860
  updatedAt: time2
797
861
  });
798
- offerSchema = z8.object({
862
+ offerSchema = z9.object({
799
863
  id: nonEmpty4,
800
864
  conversationId: nonEmpty4,
801
865
  machineId: nonEmpty4,
@@ -803,33 +867,41 @@ var init_machines = __esm({
803
867
  expiresAt: time2,
804
868
  // The repos the work hints, which the machine adds to the conversation's folder before the agent
805
869
  // starts. Hints never decide which machines are offered the work.
806
- repos: z8.array(repoHintSchema).max(MAX_REPO_HINTS).optional(),
870
+ repos: z9.array(repoHintSchema).max(MAX_REPO_HINTS).optional(),
807
871
  // The first hint's remote, for daemons from before `repos`, which work in that one repo.
808
872
  repo: nonEmpty4.optional(),
809
873
  // The agent CLI of the worker it's offered to. Without one, it's Claude Code.
810
874
  agent: nonEmpty4.optional(),
811
875
  // A handed-off conversation restores each of its repos from its branch, if the remote has it.
812
- handoff: z8.boolean().optional()
876
+ handoff: z9.boolean().optional()
813
877
  });
814
- offerAnswerSchema = z8.discriminatedUnion("answer", [
815
- z8.object({ answer: z8.literal("accept") }),
816
- z8.object({ answer: z8.literal("refuse"), reason: nonEmpty4 })
878
+ offerAnswerSchema = z9.discriminatedUnion("answer", [
879
+ z9.object({ answer: z9.literal("accept") }),
880
+ z9.object({ answer: z9.literal("refuse"), reason: nonEmpty4 })
817
881
  ]);
818
- dispatchFrameSchema = z8.discriminatedUnion("type", [
819
- z8.object({ type: z8.literal("offer"), offer: offerSchema }),
820
- z8.object({
821
- type: z8.literal("message"),
882
+ dispatchFrameSchema = z9.discriminatedUnion("type", [
883
+ z9.object({ type: z9.literal("offer"), offer: offerSchema }),
884
+ z9.object({
885
+ type: z9.literal("message"),
822
886
  conversationId: nonEmpty4,
823
- delivery: z8.enum(["interrupt", "defer"])
887
+ delivery: z9.enum(["interrupt", "defer"])
824
888
  }),
825
- z8.object({ type: z8.literal("reconnect") }),
889
+ z9.object({ type: z9.literal("reconnect") }),
826
890
  // Its owner asked for a sign-in on this machine, which the daemon runs and reports on.
827
- z8.object({ type: z8.literal("sign_in"), signInId: nonEmpty4, tool: signInToolSchema })
891
+ z9.object({ type: z9.literal("sign_in"), signInId: nonEmpty4, tool: signInToolSchema }),
892
+ // Its owner opened a terminal session from the web, which the daemon dials. An older daemon
893
+ // ignores it, and the session expires.
894
+ z9.object({
895
+ type: z9.literal("terminal_open"),
896
+ sessionId: nonEmpty4,
897
+ target: terminalTargetSchema,
898
+ mode: terminalModeSchema
899
+ })
828
900
  ]);
829
- machineMessageSchema = z8.discriminatedUnion("type", [
830
- z8.object({ type: z8.literal("ack"), conversationId: nonEmpty4 }),
831
- z8.object({
832
- type: z8.literal("usage"),
901
+ machineMessageSchema = z9.discriminatedUnion("type", [
902
+ z9.object({ type: z9.literal("ack"), conversationId: nonEmpty4 }),
903
+ z9.object({
904
+ type: z9.literal("usage"),
833
905
  /** The worker's agent on this machine, such as `claude-code`. */
834
906
  agent: nonEmpty4,
835
907
  usage: usageSchema,
@@ -840,39 +912,39 @@ var init_machines = __esm({
840
912
  });
841
913
 
842
914
  // ../common-chat/src/prompts.ts
843
- import { z as z9 } from "zod";
915
+ import { z as z10 } from "zod";
844
916
  var nonEmpty5, id, promptKindSchema, promptOptionSchema, promptQuestionSchema, promptRequestSchema, promptOutcomeSchema;
845
917
  var init_prompts = __esm({
846
918
  "../common-chat/src/prompts.ts"() {
847
919
  "use strict";
848
920
  init_define_HERD_PACKAGE();
849
- nonEmpty5 = z9.string().min(1);
850
- id = z9.string().regex(/^[A-Za-z0-9_-]{1,40}$/).describe("Short, so it fits in a button's value and action ID");
851
- promptKindSchema = z9.enum(["permission", "question", "plan", "automation"]);
852
- promptOptionSchema = z9.object({
921
+ nonEmpty5 = z10.string().min(1);
922
+ id = z10.string().regex(/^[A-Za-z0-9_-]{1,40}$/).describe("Short, so it fits in a button's value and action ID");
923
+ promptKindSchema = z10.enum(["permission", "question", "plan", "automation"]);
924
+ promptOptionSchema = z10.object({
853
925
  id,
854
- label: z9.string().trim().min(1).max(75),
855
- description: z9.string().max(200).optional(),
926
+ label: z10.string().trim().min(1).max(75),
927
+ description: z10.string().max(200).optional(),
856
928
  // How a button for it is drawn: `primary` to go ahead, `danger` to refuse.
857
- style: z9.enum(["primary", "danger"]).optional()
929
+ style: z10.enum(["primary", "danger"]).optional()
858
930
  });
859
- promptQuestionSchema = z9.object({
931
+ promptQuestionSchema = z10.object({
860
932
  id,
861
- text: z9.string().trim().min(1).max(300),
862
- options: z9.array(promptOptionSchema).min(1).max(10)
933
+ text: z10.string().trim().min(1).max(300),
934
+ options: z10.array(promptOptionSchema).min(1).max(10)
863
935
  });
864
- promptRequestSchema = z9.object({
936
+ promptRequestSchema = z10.object({
865
937
  kind: promptKindSchema,
866
- title: z9.string().trim().min(1).max(150),
867
- detail: z9.string().max(2800).optional(),
868
- questions: z9.array(promptQuestionSchema).min(1).max(4)
938
+ title: z10.string().trim().min(1).max(150),
939
+ detail: z10.string().max(2800).optional(),
940
+ questions: z10.array(promptQuestionSchema).min(1).max(4)
869
941
  });
870
- promptOutcomeSchema = z9.enum(["answered", "cancelled"]);
942
+ promptOutcomeSchema = z10.enum(["answered", "cancelled"]);
871
943
  }
872
944
  });
873
945
 
874
946
  // ../common-chat/src/pull-requests.ts
875
- import { z as z10 } from "zod";
947
+ import { z as z11 } from "zod";
876
948
  function statusOfPhase(phase) {
877
949
  return phase === "merged" ? "done" : phase === "closed" ? "closed" : "open";
878
950
  }
@@ -911,8 +983,8 @@ var init_pull_requests = __esm({
911
983
  "../common-chat/src/pull-requests.ts"() {
912
984
  "use strict";
913
985
  init_define_HERD_PACKAGE();
914
- time3 = z10.number().int().nonnegative();
915
- pullRequestPhaseSchema = z10.enum([
986
+ time3 = z11.number().int().nonnegative();
987
+ pullRequestPhaseSchema = z11.enum([
916
988
  "draft",
917
989
  "checks_running",
918
990
  "checks_failed",
@@ -923,7 +995,7 @@ var init_pull_requests = __esm({
923
995
  "merged",
924
996
  "closed"
925
997
  ]);
926
- pullRequestMoveSchema = z10.enum(["you", "agent", "others", "nobody"]);
998
+ pullRequestMoveSchema = z11.enum(["you", "agent", "others", "nobody"]);
927
999
  PHASE_LABELS = {
928
1000
  draft: "draft",
929
1001
  checks_running: "checks running",
@@ -935,22 +1007,22 @@ var init_pull_requests = __esm({
935
1007
  merged: "merged",
936
1008
  closed: "closed"
937
1009
  };
938
- leadChangeSchema = z10.object({
1010
+ leadChangeSchema = z11.object({
939
1011
  /** Its repo and number, such as `api#104`, or "a pull request" when hidden. */
940
- label: z10.string().min(1),
1012
+ label: z11.string().min(1),
941
1013
  phase: pullRequestPhaseSchema,
942
1014
  move: pullRequestMoveSchema,
943
1015
  changedAt: time3,
944
1016
  /** How many of the conversation's other open pull requests are also the person's move. */
945
- moreForYou: z10.number().int().nonnegative(),
1017
+ moreForYou: z11.number().int().nonnegative(),
946
1018
  /** Absent when hidden. */
947
- url: z10.string().optional()
1019
+ url: z11.string().optional()
948
1020
  });
949
1021
  }
950
1022
  });
951
1023
 
952
1024
  // ../common-chat/src/work-objects.ts
953
- import { z as z11 } from "zod";
1025
+ import { z as z12 } from "zod";
954
1026
  function workLine(work) {
955
1027
  const parts = [
956
1028
  work.phase ? `${work.kind} ${PHASE_LABELS[work.phase]}` : `${work.kind} ${work.status}`
@@ -965,21 +1037,21 @@ var init_work_objects = __esm({
965
1037
  "use strict";
966
1038
  init_define_HERD_PACKAGE();
967
1039
  init_pull_requests();
968
- time4 = z11.number().int().nonnegative();
969
- workKindSchema = z11.enum(["change", "ticket", "deploy", "document", "other"]);
970
- workStatusSchema = z11.enum(["open", "done", "closed"]);
971
- workSourceSchema = z11.enum(["agent", "provider"]);
1040
+ time4 = z12.number().int().nonnegative();
1041
+ workKindSchema = z12.enum(["change", "ticket", "deploy", "document", "other"]);
1042
+ workStatusSchema = z12.enum(["open", "done", "closed"]);
1043
+ workSourceSchema = z12.enum(["agent", "provider"]);
972
1044
  MAX_WORK_TITLE = 200;
973
1045
  MAX_WORK_DETAIL = 100;
974
1046
  MAX_WORK_URL = 2048;
975
- workObjectSchema = z11.object({
976
- url: z11.string().min(1).max(MAX_WORK_URL),
1047
+ workObjectSchema = z12.object({
1048
+ url: z12.string().min(1).max(MAX_WORK_URL),
977
1049
  kind: workKindSchema,
978
- title: z11.string().min(1).max(MAX_WORK_TITLE),
1050
+ title: z12.string().min(1).max(MAX_WORK_TITLE),
979
1051
  status: workStatusSchema,
980
- detail: z11.string().max(MAX_WORK_DETAIL).nullable(),
1052
+ detail: z12.string().max(MAX_WORK_DETAIL).nullable(),
981
1053
  /** Whether the conversation made it, rather than only being about it. */
982
- made: z11.boolean(),
1054
+ made: z12.boolean(),
983
1055
  changedAt: time4,
984
1056
  changedBy: workSourceSchema,
985
1057
  createdAt: time4,
@@ -987,21 +1059,21 @@ var init_work_objects = __esm({
987
1059
  phase: pullRequestPhaseSchema.nullable().optional(),
988
1060
  move: pullRequestMoveSchema.nullable().optional(),
989
1061
  /** The delegate holding it, when it's read from an ancestor. */
990
- conversationId: z11.string().min(1).optional()
1062
+ conversationId: z12.string().min(1).optional()
991
1063
  });
992
- workChangeSchema = z11.object({
993
- url: z11.string().trim().min(1).max(MAX_WORK_URL),
1064
+ workChangeSchema = z12.object({
1065
+ url: z12.string().trim().min(1).max(MAX_WORK_URL),
994
1066
  kind: workKindSchema.optional(),
995
- title: z11.string().trim().min(1).max(2e3).optional(),
1067
+ title: z12.string().trim().min(1).max(2e3).optional(),
996
1068
  status: workStatusSchema.optional(),
997
- detail: z11.string().trim().max(2e3).nullable().optional(),
998
- made: z11.boolean().optional(),
1069
+ detail: z12.string().trim().max(2e3).nullable().optional(),
1070
+ made: z12.boolean().optional(),
999
1071
  /** Only for a `change`. Its status then follows it. */
1000
1072
  phase: pullRequestPhaseSchema.optional(),
1001
1073
  /** Set by a provider, which knows who was asked to review. Otherwise it follows the phase. */
1002
1074
  move: pullRequestMoveSchema.optional()
1003
1075
  });
1004
- workFieldSchema = z11.enum([
1076
+ workFieldSchema = z12.enum([
1005
1077
  "kind",
1006
1078
  "title",
1007
1079
  "status",
@@ -1010,27 +1082,27 @@ var init_work_objects = __esm({
1010
1082
  "phase",
1011
1083
  "move"
1012
1084
  ]);
1013
- workObjectSummarySchema = z11.object({
1085
+ workObjectSummarySchema = z12.object({
1014
1086
  kind: workKindSchema,
1015
1087
  status: workStatusSchema,
1016
- made: z11.boolean(),
1088
+ made: z12.boolean(),
1017
1089
  changedAt: time4,
1018
1090
  /** A pull request's phase and move, which say nothing of its content, so they're never hidden. */
1019
1091
  phase: pullRequestPhaseSchema.nullable().optional(),
1020
1092
  move: pullRequestMoveSchema.nullable().optional(),
1021
- url: z11.string().optional(),
1022
- title: z11.string().optional(),
1023
- detail: z11.string().nullable().optional(),
1093
+ url: z12.string().optional(),
1094
+ title: z12.string().optional(),
1095
+ detail: z12.string().nullable().optional(),
1024
1096
  /** The delegate holding it, when it isn't the item's own conversation. */
1025
- conversationId: z11.string().min(1).optional()
1097
+ conversationId: z12.string().min(1).optional()
1026
1098
  });
1027
1099
  }
1028
1100
  });
1029
1101
 
1030
1102
  // ../common-chat/src/events.ts
1031
- import { z as z12 } from "zod";
1103
+ import { z as z13 } from "zod";
1032
1104
  function event(type, payload) {
1033
- return z12.object({ ...eventBase, type: z12.literal(type), payload: z12.object(payload) });
1105
+ return z13.object({ ...eventBase, type: z13.literal(type), payload: z13.object(payload) });
1034
1106
  }
1035
1107
  var routeSchema, deliveryModeSchema, eventSourceSchema, nonEmpty6, actorSchema, eventBase, entryIds, routingRecordSchema, threadChangeSchema, conversationEventSchema, conversationVisibilitySchema, conversationStatusSchema, conversationSummarySchema, placementSchema, treeSummarySchema, conversationSnapshotSchema, liveFrameSchema, adapterCapabilitiesSchema;
1036
1108
  var init_events = __esm({
@@ -1045,141 +1117,141 @@ var init_events = __esm({
1045
1117
  init_usage();
1046
1118
  init_work_objects();
1047
1119
  init_workers();
1048
- routeSchema = z12.enum(["direct", "mention", "joined"]);
1049
- deliveryModeSchema = z12.enum(["interrupt", "defer"]);
1050
- eventSourceSchema = z12.enum(["slack", "discord", "web", "api", "cli", "mcp", "system"]);
1051
- nonEmpty6 = z12.string().min(1);
1052
- actorSchema = z12.discriminatedUnion("kind", [
1120
+ routeSchema = z13.enum(["direct", "mention", "joined"]);
1121
+ deliveryModeSchema = z13.enum(["interrupt", "defer"]);
1122
+ eventSourceSchema = z13.enum(["slack", "discord", "web", "api", "cli", "mcp", "system"]);
1123
+ nonEmpty6 = z13.string().min(1);
1124
+ actorSchema = z13.discriminatedUnion("kind", [
1053
1125
  // Someone signed in to this system. `id` is the stable person identifier.
1054
- z12.object({ kind: z12.literal("person"), id: nonEmpty6, name: nonEmpty6 }),
1126
+ z13.object({ kind: z13.literal("person"), id: nonEmpty6, name: nonEmpty6 }),
1055
1127
  // An account on a platform, which may or may not be linked to a person.
1056
- z12.object({
1057
- kind: z12.literal("account"),
1128
+ z13.object({
1129
+ kind: z13.literal("account"),
1058
1130
  provider: nonEmpty6,
1059
1131
  tenant: nonEmpty6,
1060
1132
  subject: nonEmpty6,
1061
- name: z12.string().optional(),
1062
- isBot: z12.boolean()
1133
+ name: z13.string().optional(),
1134
+ isBot: z13.boolean()
1063
1135
  }),
1064
- z12.object({ kind: z12.literal("system") })
1136
+ z13.object({ kind: z13.literal("system") })
1065
1137
  ]);
1066
1138
  eventBase = {
1067
- seq: z12.number().int().positive(),
1139
+ seq: z13.number().int().positive(),
1068
1140
  source: eventSourceSchema,
1069
1141
  actor: actorSchema,
1070
- createdAt: z12.number().int().nonnegative()
1142
+ createdAt: z13.number().int().nonnegative()
1071
1143
  };
1072
- entryIds = z12.array(nonEmpty6);
1073
- routingRecordSchema = z12.object({
1074
- eligible: z12.array(z12.object({ key: nonEmpty6, name: nonEmpty6 })),
1075
- questions: z12.array(
1076
- z12.object({
1144
+ entryIds = z13.array(nonEmpty6);
1145
+ routingRecordSchema = z13.object({
1146
+ eligible: z13.array(z13.object({ key: nonEmpty6, name: nonEmpty6 })),
1147
+ questions: z13.array(
1148
+ z13.object({
1077
1149
  id: nonEmpty6,
1078
1150
  question: nonEmpty6,
1079
- probability: z12.number().min(0).max(1).nullable()
1151
+ probability: z13.number().min(0).max(1).nullable()
1080
1152
  })
1081
1153
  ),
1082
1154
  model: nonEmpty6.optional(),
1083
1155
  // Why the model's answers weren't used, when they weren't.
1084
- fallback: z12.string().optional(),
1085
- removed: z12.array(z12.object({ key: nonEmpty6, name: nonEmpty6, rule: nonEmpty6 })),
1086
- order: z12.array(nonEmpty6),
1156
+ fallback: z13.string().optional(),
1157
+ removed: z13.array(z13.object({ key: nonEmpty6, name: nonEmpty6, rule: nonEmpty6 })),
1158
+ order: z13.array(nonEmpty6),
1087
1159
  // Each ordered worker's fit, and the profile statement and level that gave it, when the model
1088
1160
  // answered.
1089
- fits: z12.array(
1090
- z12.object({
1161
+ fits: z13.array(
1162
+ z13.object({
1091
1163
  key: nonEmpty6,
1092
- fit: z12.number().min(0).max(1),
1093
- statement: z12.string().optional(),
1094
- level: z12.enum(["great", "good", "okay"]).optional()
1164
+ fit: z13.number().min(0).max(1),
1165
+ statement: z13.string().optional(),
1166
+ level: z13.enum(["great", "good", "okay"]).optional()
1095
1167
  })
1096
1168
  ).optional(),
1097
1169
  // Profile statements left out to keep within the most questions one call asks.
1098
- dropped: z12.array(z12.string()).optional(),
1170
+ dropped: z13.array(z13.string()).optional(),
1099
1171
  // The repos the work hinted, and the workers whose machine had cloned every one, which go first
1100
1172
  // among workers that tie on fit and priority.
1101
- hints: z12.array(nonEmpty6).optional(),
1102
- warm: z12.array(nonEmpty6).optional(),
1173
+ hints: z13.array(nonEmpty6).optional(),
1174
+ warm: z13.array(nonEmpty6).optional(),
1103
1175
  // Each ordered worker's share, how its weekly usage is pacing, and the weight that gives it. The
1104
1176
  // first worker was drawn by weight from those in `drawn`, which suited the work equally.
1105
- shares: z12.array(
1106
- z12.object({
1177
+ shares: z13.array(
1178
+ z13.object({
1107
1179
  key: nonEmpty6,
1108
1180
  share: shareSchema,
1109
- pacing: z12.enum(["ahead", "even", "under", "unknown"]),
1110
- weight: z12.number().nonnegative()
1181
+ pacing: z13.enum(["ahead", "even", "under", "unknown"]),
1182
+ weight: z13.number().nonnegative()
1111
1183
  })
1112
1184
  ).optional(),
1113
- drawn: z12.array(nonEmpty6).optional()
1185
+ drawn: z13.array(nonEmpty6).optional()
1114
1186
  });
1115
- threadChangeSchema = z12.discriminatedUnion("kind", [
1116
- z12.object({ kind: z12.literal("edited"), messageId: nonEmpty6, previous: z12.string() }),
1117
- z12.object({
1118
- kind: z12.literal("reacted"),
1187
+ threadChangeSchema = z13.discriminatedUnion("kind", [
1188
+ z13.object({ kind: z13.literal("edited"), messageId: nonEmpty6, previous: z13.string() }),
1189
+ z13.object({
1190
+ kind: z13.literal("reacted"),
1119
1191
  messageId: nonEmpty6,
1120
1192
  reaction: nonEmpty6,
1121
1193
  /** The words of the message reacted to, cut short. */
1122
- target: z12.string(),
1194
+ target: z13.string(),
1123
1195
  /** Whether Friday wrote that message. */
1124
- onBot: z12.boolean()
1196
+ onBot: z13.boolean()
1125
1197
  })
1126
1198
  ]);
1127
- conversationEventSchema = z12.discriminatedUnion("type", [
1199
+ conversationEventSchema = z13.discriminatedUnion("type", [
1128
1200
  event("message.created", {
1129
1201
  messageId: nonEmpty6,
1130
- text: z12.string(),
1131
- direction: z12.enum(["inbound", "outbound"]),
1202
+ text: z13.string(),
1203
+ direction: z13.enum(["inbound", "outbound"]),
1132
1204
  route: routeSchema.optional(),
1133
1205
  // Why the message did or didn't wake the conversation.
1134
- reason: z12.string().optional(),
1206
+ reason: z13.string().optional(),
1135
1207
  // Loaded from before the conversation joined the thread, so it never waits for a response.
1136
- isContext: z12.boolean().optional(),
1208
+ isContext: z13.boolean().optional(),
1137
1209
  // Set when the message was deleted on its platform. `text` is then empty.
1138
- deleted: z12.boolean().optional(),
1210
+ deleted: z13.boolean().optional(),
1139
1211
  // Sent through the API but kept out of the platform, so only clients see it.
1140
- quiet: z12.boolean().optional(),
1212
+ quiet: z13.boolean().optional(),
1141
1213
  // The client an agent sent it through, such as "Claude Code". Such a message is the agent's.
1142
- client: z12.string().optional(),
1214
+ client: z13.string().optional(),
1143
1215
  // How to read `text`. Absent means plain text, as every message before this field was.
1144
- format: z12.enum(["markdown"]).optional(),
1216
+ format: z13.enum(["markdown"]).optional(),
1145
1217
  // Files attached to it, kept with the conversation.
1146
- files: z12.array(messageFileSchema).optional(),
1218
+ files: z13.array(messageFileSchema).optional(),
1147
1219
  // A platform's own layout, shown there in place of `text`. Every other surface shows `text`.
1148
- layout: z12.object({ platform: layoutPlatformSchema, blocks: z12.array(z12.unknown()) }).optional(),
1220
+ layout: z13.object({ platform: layoutPlatformSchema, blocks: z13.array(z13.unknown()) }).optional(),
1149
1221
  // Set when this was posted in another channel, starting the conversation named here.
1150
1222
  post: postTargetSchema.optional(),
1151
1223
  // Set on a channel post's own conversation: the conversation that posted it. Its ID is left
1152
1224
  // out for anyone who can't read that conversation.
1153
- origin: z12.object({ conversationId: nonEmpty6.optional() }).optional(),
1225
+ origin: z13.object({ conversationId: nonEmpty6.optional() }).optional(),
1154
1226
  // Set on Friday's one notice that a firing with no thread can't run yet, saying why.
1155
1227
  threadNotice: nonEmpty6.optional(),
1156
1228
  // Set when this reply answered an open prompt, so it never waits as a message of its own.
1157
1229
  answersPrompt: nonEmpty6.optional(),
1158
1230
  // Set on a child conversation's first message: the brief its parent's agent wrote, from the
1159
1231
  // principal's own account. `by` is the parent's worker.
1160
- delegation: z12.object({ parent: nonEmpty6, by: nonEmpty6 }).optional(),
1232
+ delegation: z13.object({ parent: nonEmpty6, by: nonEmpty6 }).optional(),
1161
1233
  // Set on a message from another of the principal's conversations' agents, which is that agent's
1162
1234
  // words, not a person's. `posted` means a delegate already posted it in the thread, and
1163
1235
  // `outsideTree` that the sender isn't in this conversation's tree.
1164
- agentMessage: z12.object({
1236
+ agentMessage: z13.object({
1165
1237
  from: nonEmpty6,
1166
1238
  worker: nonEmpty6,
1167
- posted: z12.boolean().optional(),
1168
- overdue: z12.boolean().optional(),
1239
+ posted: z13.boolean().optional(),
1240
+ overdue: z13.boolean().optional(),
1169
1241
  // herd's own note about the delegate, such as that it's waiting for its worker.
1170
- notice: z12.boolean().optional(),
1171
- outsideTree: z12.boolean().optional()
1242
+ notice: z13.boolean().optional(),
1243
+ outsideTree: z13.boolean().optional()
1172
1244
  }).optional(),
1173
1245
  // Set on a message a root posted for one of its children, which is labelled as the child's.
1174
- forChild: z12.object({ id: nonEmpty6, worker: nonEmpty6, for: nonEmpty6 }).optional(),
1246
+ forChild: z13.object({ id: nonEmpty6, worker: nonEmpty6, for: nonEmpty6 }).optional(),
1175
1247
  // Set on an automation's firing, whose `text` is the owner's instructions, and on the opening
1176
1248
  // post that starts a conversation for one. `payload` is what the trigger brought: untrusted.
1177
- automation: z12.object({
1249
+ automation: z13.object({
1178
1250
  id: nonEmpty6,
1179
1251
  name: nonEmpty6,
1180
1252
  reason: nonEmpty6,
1181
1253
  key: nonEmpty6,
1182
- payload: z12.string().optional(),
1254
+ payload: z13.string().optional(),
1183
1255
  // The opening post a firing's reactions are shown on, in a conversation it started.
1184
1256
  opener: nonEmpty6.optional()
1185
1257
  }).optional(),
@@ -1190,27 +1262,27 @@ var init_events = __esm({
1190
1262
  // quiet waking work. For an edit `text` is the new words, and for a reaction its emoji.
1191
1263
  threadChange: threadChangeSchema.optional()
1192
1264
  }),
1193
- event("message.updated", { messageId: nonEmpty6, text: z12.string() }),
1265
+ event("message.updated", { messageId: nonEmpty6, text: z13.string() }),
1194
1266
  // A decision model judged whether a reply in a joined thread was meant for the bot. `for_bot`
1195
1267
  // woke the conversation. `failed` means it couldn't be decided in time, so nothing woke.
1196
1268
  event("message.decided", {
1197
1269
  messageId: nonEmpty6,
1198
- messageSeq: z12.number().int().positive(),
1199
- outcome: z12.enum(["for_bot", "not_for_bot", "failed"]),
1200
- probability: z12.number().min(0).max(1).optional(),
1270
+ messageSeq: z13.number().int().positive(),
1271
+ outcome: z13.enum(["for_bot", "not_for_bot", "failed"]),
1272
+ probability: z13.number().min(0).max(1).optional(),
1201
1273
  model: nonEmpty6,
1202
- error: z12.string().optional()
1274
+ error: z13.string().optional()
1203
1275
  }),
1204
1276
  // A decision model judged whether the bot's last message of a turn asks a person to answer,
1205
1277
  // decide, provide or confirm something before it can carry on. `asks` keeps the conversation
1206
1278
  // waiting on them. `failed` means it couldn't be judged in time, so nothing changed.
1207
1279
  event("message.asked", {
1208
1280
  messageId: nonEmpty6,
1209
- messageSeq: z12.number().int().positive(),
1210
- outcome: z12.enum(["asks", "doesnt_ask", "failed"]),
1211
- probability: z12.number().min(0).max(1).optional(),
1281
+ messageSeq: z13.number().int().positive(),
1282
+ outcome: z13.enum(["asks", "doesnt_ask", "failed"]),
1283
+ probability: z13.number().min(0).max(1).optional(),
1212
1284
  model: nonEmpty6,
1213
- error: z12.string().optional()
1285
+ error: z13.string().optional()
1214
1286
  }),
1215
1287
  // One of an incoming message's files arrived, or couldn't. `file` replaces the one with its ID.
1216
1288
  event("message.file_changed", { messageId: nonEmpty6, file: messageFileSchema }),
@@ -1222,21 +1294,21 @@ var init_events = __esm({
1222
1294
  event("context.unavailable", { reason: nonEmpty6 }),
1223
1295
  event("mailbox.queued", {
1224
1296
  entryId: nonEmpty6,
1225
- messageSeq: z12.number().int().positive(),
1297
+ messageSeq: z13.number().int().positive(),
1226
1298
  delivery: deliveryModeSchema
1227
1299
  }),
1228
1300
  event("mailbox.claimed", {
1229
1301
  claimId: nonEmpty6,
1230
1302
  entryIds,
1231
1303
  holder: nonEmpty6,
1232
- expiresAt: z12.number().int()
1304
+ expiresAt: z13.number().int()
1233
1305
  }),
1234
- event("mailbox.renewed", { claimId: nonEmpty6, expiresAt: z12.number().int() }),
1306
+ event("mailbox.renewed", { claimId: nonEmpty6, expiresAt: z13.number().int() }),
1235
1307
  event("mailbox.released", {
1236
1308
  claimId: nonEmpty6,
1237
1309
  entryIds,
1238
1310
  // `delegates` means messages held for delegates are waiting again now that they've settled.
1239
- reason: z12.enum(["released", "expired", "delegates"])
1311
+ reason: z13.enum(["released", "expired", "delegates"])
1240
1312
  }),
1241
1313
  // A turn ended with delegates still open, so the messages it worked on wait for them, still ⏳.
1242
1314
  event("mailbox.held", { claimId: nonEmpty6, entryIds }),
@@ -1244,7 +1316,7 @@ var init_events = __esm({
1244
1316
  event("mailbox.completed", {
1245
1317
  entryIds,
1246
1318
  claimId: nonEmpty6.optional(),
1247
- outcome: z12.enum(["passed"]).optional()
1319
+ outcome: z13.enum(["passed"]).optional()
1248
1320
  }),
1249
1321
  event("mailbox.cancelled", { entryIds, reason: nonEmpty6 }),
1250
1322
  // A person asked for what's waiting to be delivered again. The entries stay waiting.
@@ -1261,15 +1333,15 @@ var init_events = __esm({
1261
1333
  // The message it was delivering, when it was one of the conversation's own messages.
1262
1334
  messageId: nonEmpty6.optional(),
1263
1335
  // For `kind: "files"`, which of that message's files didn't arrive.
1264
- fileIds: z12.array(nonEmpty6).optional()
1336
+ fileIds: z13.array(nonEmpty6).optional()
1265
1337
  }),
1266
1338
  event("status.changed", { status: agentStatusSchema }),
1267
1339
  // A work object was added or changed: the whole of it as it now stands, which fields changed, and
1268
1340
  // whether the agent or a provider changed them. Recording one never wakes the conversation.
1269
1341
  event("work.recorded", {
1270
1342
  work: workObjectSchema.omit({ conversationId: true }),
1271
- changed: z12.array(workFieldSchema),
1272
- added: z12.boolean(),
1343
+ changed: z13.array(workFieldSchema),
1344
+ added: z13.boolean(),
1273
1345
  by: workSourceSchema
1274
1346
  }),
1275
1347
  event("work.removed", { url: nonEmpty6, by: workSourceSchema }),
@@ -1282,7 +1354,7 @@ var init_events = __esm({
1282
1354
  event("prompt.opened", {
1283
1355
  promptId: nonEmpty6,
1284
1356
  ...promptRequestSchema.shape,
1285
- forChild: z12.object({ id: nonEmpty6, worker: nonEmpty6, promptId: nonEmpty6 }).optional()
1357
+ forChild: z13.object({ id: nonEmpty6, worker: nonEmpty6, promptId: nonEmpty6 }).optional()
1286
1358
  }),
1287
1359
  // A reply in the thread answers with its `messageId`, and with `text` when it was taken as the
1288
1360
  // answer in its own words rather than as naming an option.
@@ -1291,12 +1363,12 @@ var init_events = __esm({
1291
1363
  questionId: nonEmpty6,
1292
1364
  optionId: nonEmpty6.optional(),
1293
1365
  messageId: nonEmpty6.optional(),
1294
- text: z12.string().optional()
1366
+ text: z13.string().optional()
1295
1367
  }),
1296
1368
  event("prompt.closed", {
1297
1369
  promptId: nonEmpty6,
1298
1370
  outcome: promptOutcomeSchema,
1299
- reason: z12.string().optional()
1371
+ reason: z13.string().optional()
1300
1372
  }),
1301
1373
  // Dispatch: which machine the conversation was offered to, and which one holds it.
1302
1374
  event("dispatch.offered", {
@@ -1320,19 +1392,19 @@ var init_events = __esm({
1320
1392
  // An agent started a child conversation on another worker, and how the child has gone since.
1321
1393
  event("delegation.started", {
1322
1394
  childId: nonEmpty6,
1323
- workers: z12.array(nonEmpty6),
1324
- routed: z12.boolean()
1395
+ workers: z13.array(nonEmpty6),
1396
+ routed: z13.boolean()
1325
1397
  }),
1326
1398
  event("delegation.settled", { childId: nonEmpty6 }),
1327
1399
  event("delegation.overdue", { childId: nonEmpty6 }),
1328
- event("delegation.waiting", { childId: nonEmpty6, waiting: z12.boolean() }),
1400
+ event("delegation.waiting", { childId: nonEmpty6, waiting: z13.boolean() }),
1329
1401
  // An agent asked for its conversation to move to another worker once its turn ends.
1330
1402
  event("dispatch.handoff_requested", { workerKey: nonEmpty6, workerName: nonEmpty6 }),
1331
1403
  // Terminals on the bound machine. Their output never enters the history, only these facts.
1332
1404
  event("terminal.opened", {
1333
1405
  terminalId: nonEmpty6,
1334
1406
  machineId: nonEmpty6,
1335
- lead: z12.boolean(),
1407
+ lead: z13.boolean(),
1336
1408
  // Only for a conversation from before folders, which works in one repo's worktree.
1337
1409
  worktree: worktreeSchema.optional()
1338
1410
  }),
@@ -1344,9 +1416,9 @@ var init_events = __esm({
1344
1416
  folder: nonEmpty6,
1345
1417
  branch: nonEmpty6,
1346
1418
  base: nonEmpty6.optional(),
1347
- fetchFailed: z12.string().max(1e3).optional()
1419
+ fetchFailed: z13.string().max(1e3).optional()
1348
1420
  }),
1349
- event("terminal.exited", { terminalId: nonEmpty6, exitCode: z12.number().int().nullable() }),
1421
+ event("terminal.exited", { terminalId: nonEmpty6, exitCode: z13.number().int().nullable() }),
1350
1422
  event("terminal.notice_failed", { terminalId: nonEmpty6, reason: nonEmpty6 }),
1351
1423
  /**
1352
1424
  * The agent's start didn't check in with `herd inbox` within its window, so it looks like it
@@ -1357,29 +1429,43 @@ var init_events = __esm({
1357
1429
  terminalId: nonEmpty6,
1358
1430
  agent: nonEmpty6,
1359
1431
  machineName: nonEmpty6,
1360
- detail: z12.string().max(200).optional()
1432
+ detail: z13.string().max(200).optional()
1361
1433
  }),
1362
1434
  /** A start that needed attention checked in after all. */
1363
1435
  event("terminal.checked_in", { terminalId: nonEmpty6 }),
1364
1436
  /** The daemon closed one of Claude Code's own dialogs that it has recorded, named by its title. */
1365
1437
  event("terminal.dialog_closed", { terminalId: nonEmpty6, dialog: nonEmpty6 }),
1438
+ /**
1439
+ * Someone watched the conversation's terminal from the web, or typed into it, from `startedAt`
1440
+ * until `endedAt`, and why it ended. Nothing of what was shown or typed is ever recorded.
1441
+ */
1442
+ event("terminal.watched", {
1443
+ startedAt: z13.number().int(),
1444
+ endedAt: z13.number().int(),
1445
+ reason: nonEmpty6
1446
+ }),
1447
+ event("terminal.driven", {
1448
+ startedAt: z13.number().int(),
1449
+ endedAt: z13.number().int(),
1450
+ reason: nonEmpty6
1451
+ }),
1366
1452
  // A subscribed event that didn't wake the conversation, with the reason. One that woke it is a
1367
1453
  // quiet inbound message carrying `github`, so it waits like any waking work.
1368
1454
  event("github.event", githubEventSummarySchema.shape),
1369
1455
  // A pull request's status card, queued for the conversation's thread.
1370
1456
  event("github.card", {
1371
1457
  resource: nonEmpty6,
1372
- url: z12.url(),
1458
+ url: z13.url(),
1373
1459
  label: nonEmpty6,
1374
- title: z12.string(),
1460
+ title: z13.string(),
1375
1461
  phase: pullRequestPhaseSchema,
1376
1462
  move: pullRequestMoveSchema.nullable(),
1377
- detail: z12.string().nullable()
1463
+ detail: z13.string().nullable()
1378
1464
  }),
1379
1465
  event("subscription.created", {
1380
1466
  resource: nonEmpty6,
1381
1467
  label: nonEmpty6,
1382
- url: z12.url(),
1468
+ url: z13.url(),
1383
1469
  subscriber: nonEmpty6,
1384
1470
  createdBy: subscriptionOriginSchema
1385
1471
  }),
@@ -1391,72 +1477,72 @@ var init_events = __esm({
1391
1477
  // A subscription reached its hourly limit of wakes, noted once each hour.
1392
1478
  event("subscription.limited", { resource: nonEmpty6 })
1393
1479
  ]);
1394
- conversationVisibilitySchema = z12.enum(["public", "private"]);
1395
- conversationStatusSchema = z12.enum(["idle", "waiting", "working"]);
1396
- conversationSummarySchema = z12.object({
1480
+ conversationVisibilitySchema = z13.enum(["public", "private"]);
1481
+ conversationStatusSchema = z13.enum(["idle", "waiting", "working"]);
1482
+ conversationSummarySchema = z13.object({
1397
1483
  id: nonEmpty6,
1398
1484
  provider: nonEmpty6,
1399
1485
  visibility: conversationVisibilitySchema,
1400
1486
  title: nonEmpty6,
1401
1487
  // Where the conversation lives on its platform, when it has a link.
1402
- placeUrl: z12.string().nullable(),
1488
+ placeUrl: z13.string().nullable(),
1403
1489
  status: conversationStatusSchema,
1404
- waitingCount: z12.number().int().nonnegative(),
1405
- createdAt: z12.number().int(),
1406
- lastActivityAt: z12.number().int(),
1407
- expiresAt: z12.number().int()
1408
- });
1409
- placementSchema = z12.discriminatedUnion("kind", [
1410
- z12.object({
1411
- kind: z12.literal("bound"),
1412
- machine: z12.object({ name: nonEmpty6, online: z12.boolean() }),
1490
+ waitingCount: z13.number().int().nonnegative(),
1491
+ createdAt: z13.number().int(),
1492
+ lastActivityAt: z13.number().int(),
1493
+ expiresAt: z13.number().int()
1494
+ });
1495
+ placementSchema = z13.discriminatedUnion("kind", [
1496
+ z13.object({
1497
+ kind: z13.literal("bound"),
1498
+ machine: z13.object({ name: nonEmpty6, online: z13.boolean() }),
1413
1499
  // The worker's name, such as `mac-mini/claude`, and never its configuration or capabilities.
1414
1500
  worker: nonEmpty6.optional()
1415
1501
  }),
1416
- z12.object({ kind: z12.literal("waiting_for_machine") }),
1417
- z12.object({ kind: z12.literal("author_not_linked") })
1502
+ z13.object({ kind: z13.literal("waiting_for_machine") }),
1503
+ z13.object({ kind: z13.literal("author_not_linked") })
1418
1504
  ]);
1419
- treeSummarySchema = z12.object({
1505
+ treeSummarySchema = z13.object({
1420
1506
  parent: nonEmpty6.nullable(),
1421
1507
  root: nonEmpty6,
1422
- children: z12.array(
1423
- z12.object({
1508
+ children: z13.array(
1509
+ z13.object({
1424
1510
  id: nonEmpty6,
1425
1511
  title: nonEmpty6,
1426
- state: z12.string().nullable(),
1427
- createdAt: z12.number()
1512
+ state: z13.string().nullable(),
1513
+ createdAt: z13.number()
1428
1514
  })
1429
1515
  )
1430
1516
  });
1431
- conversationSnapshotSchema = z12.object({
1517
+ conversationSnapshotSchema = z13.object({
1432
1518
  conversation: conversationSummarySchema,
1433
- events: z12.array(conversationEventSchema),
1519
+ events: z13.array(conversationEventSchema),
1434
1520
  /**
1435
1521
  * Each person actor's display name, by ID, as it is now. A person is shown by this, never by
1436
1522
  * the name stored with an event, which for older events was their address.
1437
1523
  */
1438
- people: z12.record(z12.string(), z12.string()).optional(),
1524
+ people: z13.record(z13.string(), z13.string()).optional(),
1439
1525
  placement: placementSchema.nullable().optional(),
1440
1526
  tree: treeSummarySchema.optional()
1441
1527
  });
1442
- liveFrameSchema = z12.discriminatedUnion("type", [
1443
- z12.object({ type: z12.literal("event"), event: conversationEventSchema }),
1444
- z12.object({ type: z12.literal("reconnect") }),
1445
- z12.object({ type: z12.literal("expired") })
1528
+ liveFrameSchema = z13.discriminatedUnion("type", [
1529
+ z13.object({ type: z13.literal("event"), event: conversationEventSchema }),
1530
+ z13.object({ type: z13.literal("reconnect") }),
1531
+ z13.object({ type: z13.literal("expired") })
1446
1532
  ]);
1447
- adapterCapabilitiesSchema = z12.object({
1448
- messages: z12.enum(["push", "window"]),
1449
- edits: z12.enum(["push", "rescan"]),
1450
- deletes: z12.enum(["push", "rescan"]),
1451
- reactions: z12.enum(["push", "counts"]),
1452
- status: z12.boolean(),
1453
- search: z12.boolean()
1533
+ adapterCapabilitiesSchema = z13.object({
1534
+ messages: z13.enum(["push", "window"]),
1535
+ edits: z13.enum(["push", "rescan"]),
1536
+ deletes: z13.enum(["push", "rescan"]),
1537
+ reactions: z13.enum(["push", "counts"]),
1538
+ status: z13.boolean(),
1539
+ search: z13.boolean()
1454
1540
  });
1455
1541
  }
1456
1542
  });
1457
1543
 
1458
1544
  // ../common-chat/src/conversation-state.ts
1459
- import { z as z13 } from "zod";
1545
+ import { z as z14 } from "zod";
1460
1546
  function openRank(item) {
1461
1547
  if (item.state === "stuck") return 0;
1462
1548
  if (leadOpen(item.lead) && item.lead?.move === "you") return 1;
@@ -1493,8 +1579,8 @@ var init_conversation_state = __esm({
1493
1579
  init_machines();
1494
1580
  init_pull_requests();
1495
1581
  init_work_objects();
1496
- nonEmpty7 = z13.string().min(1);
1497
- time5 = z13.number().int().nonnegative();
1582
+ nonEmpty7 = z14.string().min(1);
1583
+ time5 = z14.number().int().nonnegative();
1498
1584
  REACTIONS = {
1499
1585
  waiting: "eyes",
1500
1586
  working: "hourglass_flowing_sand",
@@ -1503,14 +1589,14 @@ var init_conversation_state = __esm({
1503
1589
  // Stuck because it's waiting on a person's answer, approval or decision, rather than on a fault.
1504
1590
  asking: "question"
1505
1591
  };
1506
- conversationStateSchema = z13.enum([
1592
+ conversationStateSchema = z14.enum([
1507
1593
  "waiting",
1508
1594
  "working",
1509
1595
  "stuck",
1510
1596
  "unanswered",
1511
1597
  "done"
1512
1598
  ]);
1513
- stuckReasonSchema = z13.enum([
1599
+ stuckReasonSchema = z14.enum([
1514
1600
  "approval",
1515
1601
  "waiting_on_someone",
1516
1602
  // A delegate has an open prompt or is waiting on a person.
@@ -1533,43 +1619,43 @@ var init_conversation_state = __esm({
1533
1619
  "awaiting_answer"
1534
1620
  ]);
1535
1621
  STUCK_REASONS = stuckReasonSchema.options;
1536
- placeKindSchema = z13.enum(["channel", "direct", "group", "web"]);
1537
- conversationSourceSchema = z13.object({
1622
+ placeKindSchema = z14.enum(["channel", "direct", "group", "web"]);
1623
+ conversationSourceSchema = z14.object({
1538
1624
  provider: nonEmpty7,
1539
1625
  kind: placeKindSchema,
1540
1626
  /** Such as a Slack channel's name, without its `#`. Left out where it's unknown or hidden. */
1541
- name: z13.string().optional()
1627
+ name: z14.string().optional()
1542
1628
  });
1543
1629
  LATEST_MESSAGE_LENGTH = 200;
1544
- latestMessageSchema = z13.object({
1545
- text: z13.string().max(LATEST_MESSAGE_LENGTH),
1630
+ latestMessageSchema = z14.object({
1631
+ text: z14.string().max(LATEST_MESSAGE_LENGTH),
1546
1632
  /** A person wrote it, or the agent did. */
1547
- from: z13.enum(["person", "agent"]),
1633
+ from: z14.enum(["person", "agent"]),
1548
1634
  at: time5
1549
1635
  });
1550
1636
  listMachineSchema = machinePresenceSchema.extend({
1551
- mine: z13.boolean().optional(),
1637
+ mine: z14.boolean().optional(),
1552
1638
  /** The agent holding it on that machine, such as `Claude Code`, when it's known. */
1553
- agent: z13.string().optional()
1639
+ agent: z14.string().optional()
1554
1640
  });
1555
- conversationListItemSchema = z13.object({
1641
+ conversationListItemSchema = z14.object({
1556
1642
  id: nonEmpty7,
1557
1643
  title: nonEmpty7,
1558
1644
  visibility: conversationVisibilitySchema,
1559
1645
  /** The conversation's page. */
1560
1646
  url: nonEmpty7,
1561
1647
  /** Its thread on its platform, when it has a link. */
1562
- placeUrl: z13.string().nullable(),
1648
+ placeUrl: z14.string().nullable(),
1563
1649
  state: conversationStateSchema,
1564
1650
  // A reason this version no longer gives, such as one stored before it, reads as none.
1565
1651
  reason: stuckReasonSchema.nullable().catch(null),
1566
1652
  /** The start of the latest waking message. */
1567
- latestRequest: z13.string().nullable(),
1653
+ latestRequest: z14.string().nullable(),
1568
1654
  lastRequestAt: time5.nullable(),
1569
1655
  /** When the bot last posted in the thread. */
1570
1656
  lastReplyAt: time5.nullable(),
1571
1657
  /** What the agent says it's doing, while it works. */
1572
- activity: z13.string().nullable(),
1658
+ activity: z14.string().nullable(),
1573
1659
  /** The machine holding it, by name and presence only, and whether it's the reader's own. */
1574
1660
  machine: listMachineSchema.nullable(),
1575
1661
  lastActivityAt: time5,
@@ -1580,28 +1666,28 @@ var init_conversation_state = __esm({
1580
1666
  /** When its activity last changed, which says how long an agent has been quiet. */
1581
1667
  activityAt: time5.nullable().optional(),
1582
1668
  /** Set when the title, latest request, link and activity were left out because it's private. */
1583
- hidden: z13.boolean().optional(),
1669
+ hidden: z14.boolean().optional(),
1584
1670
  /**
1585
1671
  * What it's about, its delegates' included: at most 5, open ones first. Missing, from servers
1586
1672
  * older than work objects, means none.
1587
1673
  */
1588
- work: z13.array(workObjectSummarySchema).optional(),
1674
+ work: z14.array(workObjectSummarySchema).optional(),
1589
1675
  /** How many more work objects it holds than `work` carries. */
1590
- moreWork: z13.number().int().nonnegative().optional(),
1676
+ moreWork: z14.number().int().nonnegative().optional(),
1591
1677
  /** The pull request it leads with, from all of its tree's, not only those in `work`. */
1592
1678
  lead: leadChangeSchema.nullable().optional()
1593
1679
  });
1594
- conversationListSchema = z13.object({
1595
- items: z13.array(conversationListItemSchema),
1680
+ conversationListSchema = z14.object({
1681
+ items: z14.array(conversationListItemSchema),
1596
1682
  /** Present when there's more, to pass back as `next` for the following page. */
1597
- next: z13.string().optional()
1683
+ next: z14.string().optional()
1598
1684
  });
1599
- listFrameSchema = z13.discriminatedUnion("type", [
1600
- z13.object({ type: z13.literal("changed"), conversations: z13.array(nonEmpty7).min(1) }),
1685
+ listFrameSchema = z14.discriminatedUnion("type", [
1686
+ z14.object({ type: z14.literal("changed"), conversations: z14.array(nonEmpty7).min(1) }),
1601
1687
  /** A machine went online or offline, or a worker's usage changed. */
1602
- z13.object({ type: z13.literal("machines") }),
1603
- z13.object({ type: z13.literal("reconnect") }),
1604
- z13.object({ type: z13.literal("expired") })
1688
+ z14.object({ type: z14.literal("machines") }),
1689
+ z14.object({ type: z14.literal("reconnect") }),
1690
+ z14.object({ type: z14.literal("expired") })
1605
1691
  ]);
1606
1692
  STATE_REACTIONS = {
1607
1693
  stuck: REACTIONS.stuck,
@@ -1642,7 +1728,7 @@ var init_conversation_state = __esm({
1642
1728
  });
1643
1729
 
1644
1730
  // ../common-chat/src/board.ts
1645
- import { z as z14 } from "zod";
1731
+ import { z as z15 } from "zod";
1646
1732
  var nonEmpty8, time6, BOARD_ITEMS, boardItemSchema, boardWorkerSchema, boardMachineSchema, boardSchema;
1647
1733
  var init_board = __esm({
1648
1734
  "../common-chat/src/board.ts"() {
@@ -1651,27 +1737,27 @@ var init_board = __esm({
1651
1737
  init_conversation_state();
1652
1738
  init_pull_requests();
1653
1739
  init_usage();
1654
- nonEmpty8 = z14.string().min(1);
1655
- time6 = z14.number().int().nonnegative();
1740
+ nonEmpty8 = z15.string().min(1);
1741
+ time6 = z15.number().int().nonnegative();
1656
1742
  BOARD_ITEMS = 8;
1657
- boardItemSchema = z14.object({
1743
+ boardItemSchema = z15.object({
1658
1744
  id: nonEmpty8,
1659
1745
  /** `HIDDEN_TITLE` when the item is private and the board doesn't show private titles. */
1660
1746
  title: nonEmpty8,
1661
1747
  state: conversationStateSchema,
1662
1748
  reason: stuckReasonSchema.nullable().catch(null),
1663
1749
  /** Where the work came from, such as `#herd-dev` or a direct message, when it's known. */
1664
- source: z14.string().nullable(),
1750
+ source: z15.string().nullable(),
1665
1751
  /** What the agent says it's doing, while it works. */
1666
- activity: z14.string().nullable(),
1752
+ activity: z15.string().nullable(),
1667
1753
  lastActivityAt: time6,
1668
1754
  /** The machine and agent holding it, such as `mac-mini` and `Claude Code`, once it's bound. */
1669
- heldBy: z14.object({ machine: nonEmpty8, agent: nonEmpty8 }).nullable().optional(),
1755
+ heldBy: z15.object({ machine: nonEmpty8, agent: nonEmpty8 }).nullable().optional(),
1670
1756
  /** The pull request it leads with, and how many more are the person's move. */
1671
1757
  change: leadChangeSchema.nullable().optional(),
1672
- hidden: z14.boolean().optional()
1758
+ hidden: z15.boolean().optional()
1673
1759
  });
1674
- boardWorkerSchema = z14.object({
1760
+ boardWorkerSchema = z15.object({
1675
1761
  /** The worker's short name, such as `claude`, under its machine. */
1676
1762
  name: nonEmpty8,
1677
1763
  /** Its agent CLI, such as `claude-code`, which names it on the board. */
@@ -1680,33 +1766,33 @@ var init_board = __esm({
1680
1766
  usage: usageSchema.nullable(),
1681
1767
  reportedAt: time6.nullable(),
1682
1768
  /** What it's great at, from its profile: its owner's statements, or herd's defaults. */
1683
- strengths: z14.array(z14.string().min(1).max(100)).max(3).optional()
1769
+ strengths: z15.array(z15.string().min(1).max(100)).max(3).optional()
1684
1770
  });
1685
- boardMachineSchema = z14.object({
1771
+ boardMachineSchema = z15.object({
1686
1772
  name: nonEmpty8,
1687
- online: z14.boolean(),
1688
- workers: z14.array(boardWorkerSchema).max(8)
1773
+ online: z15.boolean(),
1774
+ workers: z15.array(boardWorkerSchema).max(8)
1689
1775
  });
1690
- boardSchema = z14.object({
1776
+ boardSchema = z15.object({
1691
1777
  /** How many are in each open state: every state but `done`, which is counted for the last day. */
1692
- counts: z14.object({
1693
- stuck: z14.number().int().nonnegative(),
1694
- unanswered: z14.number().int().nonnegative(),
1695
- waiting: z14.number().int().nonnegative(),
1696
- working: z14.number().int().nonnegative(),
1778
+ counts: z15.object({
1779
+ stuck: z15.number().int().nonnegative(),
1780
+ unanswered: z15.number().int().nonnegative(),
1781
+ waiting: z15.number().int().nonnegative(),
1782
+ working: z15.number().int().nonnegative(),
1697
1783
  /** Open pull requests the person's conversations made that are their move, each counted. */
1698
- pullRequests: z14.number().int().nonnegative().optional()
1784
+ pullRequests: z15.number().int().nonnegative().optional()
1699
1785
  }),
1700
1786
  /** How many became `done` in the last 24 hours. */
1701
- doneToday: z14.number().int().nonnegative(),
1787
+ doneToday: z15.number().int().nonnegative(),
1702
1788
  /**
1703
1789
  * What needs the person (stuck, then pull requests that are their move), then what's working or
1704
1790
  * has a pull request under way, then what's queued, then the latest finished to fill the rest.
1705
1791
  */
1706
- items: z14.array(boardItemSchema).max(BOARD_ITEMS),
1707
- machines: z14.array(boardMachineSchema).max(16),
1792
+ items: z15.array(boardItemSchema).max(BOARD_ITEMS),
1793
+ machines: z15.array(boardMachineSchema).max(16),
1708
1794
  /** Changes whenever anything the board shows changes, and only then. */
1709
- stamp: z14.string().regex(/^[0-9a-f]{12}$/),
1795
+ stamp: z15.string().regex(/^[0-9a-f]{12}$/),
1710
1796
  /** When its content last changed. */
1711
1797
  changedAt: time6
1712
1798
  });
@@ -1728,31 +1814,31 @@ var init_bot = __esm({
1728
1814
  });
1729
1815
 
1730
1816
  // ../common-chat/src/builds.ts
1731
- import { z as z15 } from "zod";
1817
+ import { z as z16 } from "zod";
1732
1818
  var nonEmpty9, time7, BUILD_LEASE_MS, BUILD_CLAIM_WINDOW_MS, MAX_LOG_CHUNK_BYTES, MAX_LOG_BYTES, MAX_NODE_MODULES_PART_BYTES, shaSchema, buildOfferSchema, buildCancelReasonSchema, runnerMessageSchema, serviceMessageSchema, runnerRegisterRequestSchema, runnerRegisterResponseSchema, runnerPollRequestSchema, runnerPollResponseSchema, buildStepSchema, buildFinishSchema, sharedPassSchema, passesResponseSchema, deployCredentialsSchema, deployedSchema, healthSchema;
1733
1819
  var init_builds = __esm({
1734
1820
  "../common-chat/src/builds.ts"() {
1735
1821
  "use strict";
1736
1822
  init_define_HERD_PACKAGE();
1737
1823
  init_workers();
1738
- nonEmpty9 = z15.string().min(1);
1739
- time7 = z15.number().int().nonnegative();
1824
+ nonEmpty9 = z16.string().min(1);
1825
+ time7 = z16.number().int().nonnegative();
1740
1826
  BUILD_LEASE_MS = 5 * 60 * 1e3;
1741
1827
  BUILD_CLAIM_WINDOW_MS = 15 * 1e3;
1742
1828
  MAX_LOG_CHUNK_BYTES = 256 * 1024;
1743
1829
  MAX_LOG_BYTES = 5 * 1024 * 1024;
1744
1830
  MAX_NODE_MODULES_PART_BYTES = 95 * 1024 * 1024;
1745
- shaSchema = z15.string().regex(/^[0-9a-f]{40}$/);
1746
- buildOfferSchema = z15.object({
1831
+ shaSchema = z16.string().regex(/^[0-9a-f]{40}$/);
1832
+ buildOfferSchema = z16.object({
1747
1833
  id: nonEmpty9,
1748
1834
  offerId: nonEmpty9,
1749
1835
  repo: nonEmpty9,
1750
1836
  sha: shaSchema,
1751
1837
  ref: nonEmpty9,
1752
- main: z15.boolean(),
1838
+ main: z16.boolean(),
1753
1839
  expiresAt: time7
1754
1840
  });
1755
- buildCancelReasonSchema = z15.enum([
1841
+ buildCancelReasonSchema = z16.enum([
1756
1842
  // A newer push to the same branch replaced it.
1757
1843
  "superseded",
1758
1844
  // The branch was deleted.
@@ -1762,7 +1848,7 @@ var init_builds = __esm({
1762
1848
  // Stopped for another reason, such as the repo being removed.
1763
1849
  "cancelled"
1764
1850
  ]);
1765
- runnerMessageSchema = z15.discriminatedUnion("type", [
1851
+ runnerMessageSchema = z16.discriminatedUnion("type", [
1766
1852
  /**
1767
1853
  * Sent first, and again whenever any of it changes. `capacity` comes from `herd.config.ts`'s
1768
1854
  * `builds` through `runnerCapacity`, `repos` are the normalized remotes of the repos herd has
@@ -1770,82 +1856,82 @@ var init_builds = __esm({
1770
1856
  * any of its owner's repos, cloning one it hasn't got, and `repos` only puts it first for those.
1771
1857
  * A daemon from before herd's root sent its configured checkouts here, which it builds from.
1772
1858
  */
1773
- z15.object({
1774
- type: z15.literal("hello"),
1859
+ z16.object({
1860
+ type: z16.literal("hello"),
1775
1861
  /** How many builds at once. 0 when the machine can't sandbox a build, so it takes none. */
1776
- capacity: z15.number().int().min(0).max(BUILDS_MAX),
1777
- repos: z15.array(nonEmpty9.max(300)).max(100),
1778
- running: z15.array(nonEmpty9.max(100)).max(BUILDS_MAX * 2)
1862
+ capacity: z16.number().int().min(0).max(BUILDS_MAX),
1863
+ repos: z16.array(nonEmpty9.max(300)).max(100),
1864
+ running: z16.array(nonEmpty9.max(100)).max(BUILDS_MAX * 2)
1779
1865
  }),
1780
1866
  /** An answer to an offer, before its `expiresAt`. A refusal may say why. */
1781
- z15.object({
1782
- type: z15.literal("answer"),
1867
+ z16.object({
1868
+ type: z16.literal("answer"),
1783
1869
  buildId: nonEmpty9,
1784
1870
  offerId: nonEmpty9,
1785
- answer: z15.enum(["accept", "refuse"]),
1786
- reason: z15.string().max(500).optional()
1871
+ answer: z16.enum(["accept", "refuse"]),
1872
+ reason: z16.string().max(500).optional()
1787
1873
  })
1788
1874
  ]);
1789
- serviceMessageSchema = z15.discriminatedUnion("type", [
1790
- z15.object({ type: z15.literal("build"), build: buildOfferSchema }),
1875
+ serviceMessageSchema = z16.discriminatedUnion("type", [
1876
+ z16.object({ type: z16.literal("build"), build: buildOfferSchema }),
1791
1877
  /** An accepted offer: the build's own secret, and how long its claim lasts between renewals. */
1792
- z15.object({
1793
- type: z15.literal("accepted"),
1878
+ z16.object({
1879
+ type: z16.literal("accepted"),
1794
1880
  buildId: nonEmpty9,
1795
1881
  secret: nonEmpty9,
1796
- leaseMs: z15.number().int().positive()
1882
+ leaseMs: z16.number().int().positive()
1797
1883
  }),
1798
1884
  /**
1799
1885
  * Stop a build this runner holds, within 5 seconds. An accept that came too late, for an offer
1800
1886
  * that had expired or was withdrawn, gets this too, with the reason `cancelled`.
1801
1887
  */
1802
- z15.object({ type: z15.literal("build-cancel"), buildId: nonEmpty9, reason: buildCancelReasonSchema }),
1888
+ z16.object({ type: z16.literal("build-cancel"), buildId: nonEmpty9, reason: buildCancelReasonSchema }),
1803
1889
  /** Connect again. Sent before the service closes a socket that another replaced. */
1804
- z15.object({ type: z15.literal("reconnect") })
1890
+ z16.object({ type: z16.literal("reconnect") })
1805
1891
  ]);
1806
- runnerRegisterRequestSchema = z15.object({ name: z15.string().trim().min(1).max(100) });
1807
- runnerRegisterResponseSchema = z15.object({
1892
+ runnerRegisterRequestSchema = z16.object({ name: z16.string().trim().min(1).max(100) });
1893
+ runnerRegisterResponseSchema = z16.object({
1808
1894
  /** Shown to the owner, so they can match the approval page to the machine. */
1809
1895
  code: nonEmpty9,
1810
1896
  /** The page the owner opens, signed in, to approve it. */
1811
- approveUrl: z15.url(),
1897
+ approveUrl: z16.url(),
1812
1898
  /** Sent to `POST /runners/register/poll`. Only this machine has it. */
1813
1899
  pollToken: nonEmpty9,
1814
1900
  expiresAt: time7.optional(),
1815
1901
  /** How often to poll. */
1816
- pollMs: z15.number().int().positive().optional()
1902
+ pollMs: z16.number().int().positive().optional()
1817
1903
  });
1818
- runnerPollRequestSchema = z15.object({ pollToken: nonEmpty9.max(200) });
1819
- runnerPollResponseSchema = z15.discriminatedUnion("status", [
1820
- z15.object({ status: z15.literal("waiting") }),
1904
+ runnerPollRequestSchema = z16.object({ pollToken: nonEmpty9.max(200) });
1905
+ runnerPollResponseSchema = z16.discriminatedUnion("status", [
1906
+ z16.object({ status: z16.literal("waiting") }),
1821
1907
  /** Approved. The key is returned this once, and the service keeps only its hash. */
1822
- z15.object({ status: z15.literal("approved"), runnerId: nonEmpty9, key: nonEmpty9 }),
1908
+ z16.object({ status: z16.literal("approved"), runnerId: nonEmpty9, key: nonEmpty9 }),
1823
1909
  /** Expired, or the key was already collected. Start again. */
1824
- z15.object({ status: z15.literal("expired") })
1910
+ z16.object({ status: z16.literal("expired") })
1825
1911
  ]);
1826
- buildStepSchema = z15.object({
1827
- label: z15.string().min(1).max(200),
1828
- key: z15.string().regex(/^[0-9a-zA-Z_-]{8,128}$/),
1829
- status: z15.enum(["passed", "failed", "remembered"]),
1830
- ms: z15.number().int().nonnegative(),
1912
+ buildStepSchema = z16.object({
1913
+ label: z16.string().min(1).max(200),
1914
+ key: z16.string().regex(/^[0-9a-zA-Z_-]{8,128}$/),
1915
+ status: z16.enum(["passed", "failed", "remembered"]),
1916
+ ms: z16.number().int().nonnegative(),
1831
1917
  /** Only some of the step's tests ran, so its pass isn't shared. */
1832
- partial: z15.boolean().default(false),
1833
- budgetMs: z15.number().int().positive().nullable().default(null)
1918
+ partial: z16.boolean().default(false),
1919
+ budgetMs: z16.number().int().positive().nullable().default(null)
1834
1920
  });
1835
- buildFinishSchema = z15.object({
1836
- status: z15.enum(["success", "failure", "error"]),
1837
- summary: z15.string().max(2e3).default(""),
1838
- deployed: z15.boolean().default(false)
1921
+ buildFinishSchema = z16.object({
1922
+ status: z16.enum(["success", "failure", "error"]),
1923
+ summary: z16.string().max(2e3).default(""),
1924
+ deployed: z16.boolean().default(false)
1839
1925
  });
1840
- sharedPassSchema = z15.object({
1926
+ sharedPassSchema = z16.object({
1841
1927
  step: nonEmpty9,
1842
1928
  passedAt: time7,
1843
1929
  sha: nonEmpty9,
1844
1930
  runner: nonEmpty9,
1845
- partial: z15.boolean()
1931
+ partial: z16.boolean()
1846
1932
  });
1847
- passesResponseSchema = z15.object({ passes: z15.record(z15.string(), sharedPassSchema) });
1848
- deployCredentialsSchema = z15.object({
1933
+ passesResponseSchema = z16.object({ passes: z16.record(z16.string(), sharedPassSchema) });
1934
+ deployCredentialsSchema = z16.object({
1849
1935
  cloudflareToken: nonEmpty9,
1850
1936
  /** Publishes `@herdingchats/friday`. Missing when the build service has none set. */
1851
1937
  npmToken: nonEmpty9.optional(),
@@ -1854,20 +1940,20 @@ var init_builds = __esm({
1854
1940
  /** `owner/name`, for moving `deployed`. */
1855
1941
  repo: nonEmpty9,
1856
1942
  /** The commit being built, which `deployed` moves to. Older services don't send it. */
1857
- sha: z15.string().regex(/^[0-9a-f]{40}$/).optional(),
1943
+ sha: z16.string().regex(/^[0-9a-f]{40}$/).optional(),
1858
1944
  /** Each Worker's key at its last successful deploy. */
1859
- lastDeployed: z15.record(z15.string(), z15.string())
1945
+ lastDeployed: z16.record(z16.string(), z16.string())
1860
1946
  });
1861
- deployedSchema = z15.object({
1862
- worker: z15.string().regex(/^[a-z][a-z0-9-]{0,62}$/),
1863
- key: z15.string().regex(/^[0-9a-zA-Z_-]{8,128}$/)
1947
+ deployedSchema = z16.object({
1948
+ worker: z16.string().regex(/^[a-z][a-z0-9-]{0,62}$/),
1949
+ key: z16.string().regex(/^[0-9a-zA-Z_-]{8,128}$/)
1864
1950
  });
1865
- healthSchema = z15.object({ ok: z15.boolean(), version: z15.string() });
1951
+ healthSchema = z16.object({ ok: z16.boolean(), version: z16.string() });
1866
1952
  }
1867
1953
  });
1868
1954
 
1869
1955
  // ../common-chat/src/setup.ts
1870
- import { z as z16 } from "zod";
1956
+ import { z as z17 } from "zod";
1871
1957
  function actionText(action) {
1872
1958
  if (action.kind === "link") return `${action.label}: ${action.url}`;
1873
1959
  if (action.kind === "sign_in") {
@@ -1902,42 +1988,42 @@ var init_setup = __esm({
1902
1988
  init_activation();
1903
1989
  init_bot();
1904
1990
  SETUP_STEP_IDS = ["slack", "machine", "github", "repos"];
1905
- setupActionSchema = z16.discriminatedUnion("kind", [
1991
+ setupActionSchema = z17.discriminatedUnion("kind", [
1906
1992
  /** A page to open, which signs in to HerdingChats first. */
1907
- z16.object({ kind: z16.literal("link"), url: z16.string(), label: z16.string() }),
1993
+ z17.object({ kind: z17.literal("link"), url: z17.string(), label: z17.string() }),
1908
1994
  /** A command to run on the computer. */
1909
- z16.object({ kind: z16.literal("command"), command: z16.string() }),
1995
+ z17.object({ kind: z17.literal("command"), command: z17.string() }),
1910
1996
  /**
1911
1997
  * A sign-in herd starts on one of the person's machines, from Slack or the page, whose code
1912
1998
  * Friday sends to their direct message.
1913
1999
  */
1914
- z16.object({
1915
- kind: z16.literal("sign_in"),
1916
- machineId: z16.string(),
2000
+ z17.object({
2001
+ kind: z17.literal("sign_in"),
2002
+ machineId: z17.string(),
1917
2003
  tool: signInToolSchema,
1918
- label: z16.string()
2004
+ label: z17.string()
1919
2005
  })
1920
2006
  ]);
1921
- setupStepSchema = z16.object({
1922
- id: z16.enum(SETUP_STEP_IDS),
2007
+ setupStepSchema = z17.object({
2008
+ id: z17.enum(SETUP_STEP_IDS),
1923
2009
  /** `unavailable` is a step nobody can take yet, such as GitHub linking before an admin turns it on. */
1924
- state: z16.enum(["done", "todo", "unavailable"]),
1925
- optional: z16.boolean(),
1926
- title: z16.string(),
2010
+ state: z17.enum(["done", "todo", "unavailable"]),
2011
+ optional: z17.boolean(),
2012
+ title: z17.string(),
1927
2013
  /** What was found, such as the machine and whether it's online, or why it can't be done yet. */
1928
- detail: z16.string().nullable(),
2014
+ detail: z17.string().nullable(),
1929
2015
  /** What to do next. None once it's done, or while it's unavailable. */
1930
2016
  action: setupActionSchema.nullable(),
1931
2017
  /**
1932
2018
  * More that can be done, which doesn't decide whether the step is done, such as signing in a
1933
2019
  * computer's GitHub CLI. Shown whatever the step's state.
1934
2020
  */
1935
- extra: z16.array(setupActionSchema).optional()
2021
+ extra: z17.array(setupActionSchema).optional()
1936
2022
  });
1937
- setupStatusSchema = z16.object({
1938
- steps: z16.array(setupStepSchema),
2023
+ setupStatusSchema = z17.object({
2024
+ steps: z17.array(setupStepSchema),
1939
2025
  /** Whether every step that isn't optional is done, so Friday can take work. */
1940
- ready: z16.boolean()
2026
+ ready: z17.boolean()
1941
2027
  });
1942
2028
  READY_TEXT = `You're set up: mention @${BOT_NAME} in any channel it's in, or message it directly.`;
1943
2029
  READY_SUGGESTIONS_TITLE = "Some things to try:";
@@ -1979,6 +2065,7 @@ var init_src = __esm({
1979
2065
  init_pull_requests();
1980
2066
  init_setup();
1981
2067
  init_state();
2068
+ init_terminals();
1982
2069
  init_usage();
1983
2070
  init_work_objects();
1984
2071
  init_workers();
@@ -2158,7 +2245,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
2158
2245
  function Text({
2159
2246
  theme,
2160
2247
  children,
2161
- size = "md",
2248
+ size: size2 = "md",
2162
2249
  bold,
2163
2250
  muted,
2164
2251
  faint,
@@ -2171,7 +2258,7 @@ function Text({
2171
2258
  {
2172
2259
  style: {
2173
2260
  display: "flex",
2174
- fontSize: theme.size[size],
2261
+ fontSize: theme.size[size2],
2175
2262
  fontWeight: bold ? 700 : 400,
2176
2263
  color: faint ? theme.faint : muted ? theme.muted : theme.ink,
2177
2264
  lineHeight: 1.2,
@@ -2183,9 +2270,9 @@ function Text({
2183
2270
  }
2184
2271
  );
2185
2272
  }
2186
- function Icon({ icon: icon2, color, size }) {
2273
+ function Icon({ icon: icon2, color, size: size2 }) {
2187
2274
  const { d, label: label3 } = ICON_PATHS[icon2];
2188
- return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", role: "img", "aria-label": label3, children: /* @__PURE__ */ jsx(
2275
+ return /* @__PURE__ */ jsx("svg", { width: size2, height: size2, viewBox: "0 0 24 24", role: "img", "aria-label": label3, children: /* @__PURE__ */ jsx(
2189
2276
  "path",
2190
2277
  {
2191
2278
  d,
@@ -2388,7 +2475,7 @@ var init_widget = __esm({
2388
2475
  });
2389
2476
 
2390
2477
  // ../common-board/src/widgets/conversations.tsx
2391
- import { z as z29 } from "zod";
2478
+ import { z as z30 } from "zod";
2392
2479
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2393
2480
  function badge(item) {
2394
2481
  if (pullRequestBadge(item)) {
@@ -2432,10 +2519,10 @@ var init_conversations = __esm({
2432
2519
  conversations2 = defineWidget({
2433
2520
  kind: "conversations",
2434
2521
  describe: "Blocked, working and queued conversations, then the latest done, with what each is doing, where it came from and who holds it",
2435
- options: z29.object({
2436
- states: z29.array(conversationStateSchema).min(1).default(["stuck", "working", "waiting", "done"]),
2437
- limit: z29.number().int().min(1).max(BOARD_ITEMS).default(4),
2438
- show: z29.array(z29.enum(DETAILS)).default([...DETAILS])
2522
+ options: z30.object({
2523
+ states: z30.array(conversationStateSchema).min(1).default(["stuck", "working", "waiting", "done"]),
2524
+ limit: z30.number().int().min(1).max(BOARD_ITEMS).default(4),
2525
+ show: z30.array(z30.enum(DETAILS)).default([...DETAILS])
2439
2526
  }),
2440
2527
  reads: ["items", "machines"],
2441
2528
  Component: ({ board, options, theme, now }) => {
@@ -2479,7 +2566,7 @@ var init_conversations = __esm({
2479
2566
  });
2480
2567
 
2481
2568
  // ../common-board/src/widgets/counts.tsx
2482
- import { z as z30 } from "zod";
2569
+ import { z as z31 } from "zod";
2483
2570
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2484
2571
  var COUNTS, LABELS2, counts;
2485
2572
  var init_counts = __esm({
@@ -2499,8 +2586,8 @@ var init_counts = __esm({
2499
2586
  counts = defineWidget({
2500
2587
  kind: "counts",
2501
2588
  describe: "How many conversations are stuck, unanswered, waiting and working, and done today",
2502
- options: z30.object({
2503
- show: z30.array(z30.enum(COUNTS)).min(1).max(5).default([...COUNTS])
2589
+ options: z31.object({
2590
+ show: z31.array(z31.enum(COUNTS)).min(1).max(5).default([...COUNTS])
2504
2591
  }),
2505
2592
  reads: ["counts", "doneToday"],
2506
2593
  Component: ({ board, options, theme }) => {
@@ -2539,7 +2626,7 @@ var init_counts = __esm({
2539
2626
  });
2540
2627
 
2541
2628
  // ../common-board/src/widgets/header.tsx
2542
- import { z as z31 } from "zod";
2629
+ import { z as z32 } from "zod";
2543
2630
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2544
2631
  function summary(board) {
2545
2632
  const parts = [
@@ -2564,9 +2651,9 @@ var init_header = __esm({
2564
2651
  header = defineWidget({
2565
2652
  kind: "header",
2566
2653
  describe: "The board's title, a line of how much is open, and when it was drawn",
2567
- options: z31.object({
2568
- title: z31.string().min(1).max(40).default(`@${BOT_NAME}`),
2569
- summary: z31.boolean().default(true)
2654
+ options: z32.object({
2655
+ title: z32.string().min(1).max(40).default(`@${BOT_NAME}`),
2656
+ summary: z32.boolean().default(true)
2570
2657
  }),
2571
2658
  reads: ["counts"],
2572
2659
  Component: ({ board, options, theme, now }) => {
@@ -2584,7 +2671,7 @@ var init_header = __esm({
2584
2671
  });
2585
2672
 
2586
2673
  // ../common-board/src/widgets/machines.tsx
2587
- import { z as z32 } from "zod";
2674
+ import { z as z33 } from "zod";
2588
2675
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2589
2676
  function usageText(worker) {
2590
2677
  const usage = worker.usage;
@@ -2615,7 +2702,7 @@ var init_machines2 = __esm({
2615
2702
  machines = defineWidget({
2616
2703
  kind: "machines",
2617
2704
  describe: "One bubble per agent on an online machine, with its usage, most recently active first",
2618
- options: z32.object({ limit: z32.number().int().min(1).max(8).default(3) }),
2705
+ options: z33.object({ limit: z33.number().int().min(1).max(8).default(3) }),
2619
2706
  reads: ["machines"],
2620
2707
  Component: ({ board, options, theme }) => {
2621
2708
  const workers2 = board.machines.filter((machine) => machine.online).flatMap((machine) => machine.workers.map((worker) => ({ ...worker, machine: machine.name }))).sort((a, b) => (b.reportedAt ?? 0) - (a.reportedAt ?? 0));
@@ -2732,9 +2819,9 @@ var init_board2 = __esm({
2732
2819
  });
2733
2820
 
2734
2821
  // ../common-board/src/composition.ts
2735
- import { z as z33 } from "zod";
2822
+ import { z as z34 } from "zod";
2736
2823
  function readComposition(stored) {
2737
- const shape = z33.object({ widgets: z33.array(placedWidgetSchema) }).safeParse(stored);
2824
+ const shape = z34.object({ widgets: z34.array(placedWidgetSchema) }).safeParse(stored);
2738
2825
  if (!shape.success) return DEFAULT_COMPOSITION;
2739
2826
  const known = shape.data.widgets.filter(
2740
2827
  (widget) => WIDGETS[widget.kind]?.options.safeParse(widget.options).success
@@ -2749,11 +2836,11 @@ var init_composition = __esm({
2749
2836
  init_registry();
2750
2837
  MAX_WIDGETS = 12;
2751
2838
  MAX_COMPOSITION_BYTES = 4096;
2752
- placedWidgetSchema = z33.object({
2753
- kind: z33.string().min(1),
2754
- options: z33.record(z33.string(), z33.unknown()).default({})
2839
+ placedWidgetSchema = z34.object({
2840
+ kind: z34.string().min(1),
2841
+ options: z34.record(z34.string(), z34.unknown()).default({})
2755
2842
  });
2756
- compositionSchema = z33.object({ widgets: z33.array(placedWidgetSchema).max(MAX_WIDGETS) }).superRefine((composition, context2) => {
2843
+ compositionSchema = z34.object({ widgets: z34.array(placedWidgetSchema).max(MAX_WIDGETS) }).superRefine((composition, context2) => {
2757
2844
  if (JSON.stringify(composition).length > MAX_COMPOSITION_BYTES) {
2758
2845
  context2.addIssue({
2759
2846
  code: "custom",
@@ -3208,12 +3295,12 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
3208
3295
  // src/friday/main.ts
3209
3296
  init_define_HERD_PACKAGE();
3210
3297
  init_src();
3211
- import { spawn as spawn7 } from "node:child_process";
3298
+ import { spawn as spawn8 } from "node:child_process";
3212
3299
  import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
3213
3300
  import { homedir as homedir5 } from "node:os";
3214
- import { dirname as dirname10, join as join15 } from "node:path";
3301
+ import { dirname as dirname10, join as join16 } from "node:path";
3215
3302
  import { createInterface } from "node:readline/promises";
3216
- import { z as z22 } from "zod";
3303
+ import { z as z23 } from "zod";
3217
3304
 
3218
3305
  // src/auth.ts
3219
3306
  init_define_HERD_PACKAGE();
@@ -3222,21 +3309,21 @@ import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:
3222
3309
  import { createServer } from "node:http";
3223
3310
  import { homedir } from "node:os";
3224
3311
  import { join } from "node:path";
3225
- import { z as z17 } from "zod";
3312
+ import { z as z18 } from "zod";
3226
3313
  var herdHome = () => process.env.HERD_HOME ?? join(homedir(), ".herd");
3227
3314
  var apiUrl = () => (process.env.HERD_API_URL ?? "https://api.herdingchats.com").replace(/\/+$/, "");
3228
3315
  var isLocal = (url) => /^http:\/\/localhost:\d+\/?$/.test(url);
3229
3316
  var localSignIn = () => process.env.HERD_LOCAL_SLACK ? { "X-Herd-Local-Slack": process.env.HERD_LOCAL_SLACK } : {};
3230
3317
  var HerdError = class extends Error {
3231
3318
  };
3232
- var credentialsSchema = z17.object({
3233
- api: z17.string(),
3234
- tokenEndpoint: z17.string(),
3235
- revocationEndpoint: z17.string().optional(),
3236
- clientId: z17.string(),
3237
- accessToken: z17.string(),
3238
- accessTokenExpiresAt: z17.number(),
3239
- refreshToken: z17.string()
3319
+ var credentialsSchema = z18.object({
3320
+ api: z18.string(),
3321
+ tokenEndpoint: z18.string(),
3322
+ revocationEndpoint: z18.string().optional(),
3323
+ clientId: z18.string(),
3324
+ accessToken: z18.string(),
3325
+ accessTokenExpiresAt: z18.number(),
3326
+ refreshToken: z18.string()
3240
3327
  });
3241
3328
  var credentialsPath = () => join(herdHome(), "credentials.json");
3242
3329
  function readCredentials() {
@@ -3252,16 +3339,16 @@ function writeCredentials(credentials) {
3252
3339
  writeFileSync(credentialsPath(), JSON.stringify(credentials, null, 2), { mode: 384 });
3253
3340
  chmodSync(credentialsPath(), 384);
3254
3341
  }
3255
- var metadataSchema = z17.object({
3256
- authorization_endpoint: z17.string(),
3257
- token_endpoint: z17.string(),
3258
- registration_endpoint: z17.string(),
3259
- revocation_endpoint: z17.string().optional()
3342
+ var metadataSchema = z18.object({
3343
+ authorization_endpoint: z18.string(),
3344
+ token_endpoint: z18.string(),
3345
+ registration_endpoint: z18.string(),
3346
+ revocation_endpoint: z18.string().optional()
3260
3347
  });
3261
- var tokenSchema = z17.object({
3262
- access_token: z17.string().min(1),
3263
- refresh_token: z17.string().min(1),
3264
- expires_in: z17.number()
3348
+ var tokenSchema = z18.object({
3349
+ access_token: z18.string().min(1),
3350
+ refresh_token: z18.string().min(1),
3351
+ expires_in: z18.number()
3265
3352
  });
3266
3353
  var base64url = (bytes) => bytes.toString("base64url");
3267
3354
  async function postForm(url, fields) {
@@ -3293,7 +3380,7 @@ async function login(openBrowser2, { api = apiUrl(), timeoutMs = 10 * 60 * 1e3 }
3293
3380
  token_endpoint_auth_method: "none"
3294
3381
  })
3295
3382
  });
3296
- const client = z17.object({ client_id: z17.string() }).safeParse(await registration2.json());
3383
+ const client = z18.object({ client_id: z18.string() }).safeParse(await registration2.json());
3297
3384
  if (!registration2.ok || !client.success) {
3298
3385
  throw new HerdError("The sign-in server refused to register this tool.");
3299
3386
  }
@@ -3414,7 +3501,7 @@ var HERD_FLAGS = {
3414
3501
  login: [],
3415
3502
  logout: [],
3416
3503
  daemon: ["--repo"],
3417
- attach: [],
3504
+ attach: ["--watch"],
3418
3505
  builds: [],
3419
3506
  inbox: [],
3420
3507
  send: ["--json"],
@@ -3527,7 +3614,7 @@ import { randomUUID } from "node:crypto";
3527
3614
  import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync2, writeFileSync as writeFileSync3 } from "node:fs";
3528
3615
  import { homedir as homedir3, hostname } from "node:os";
3529
3616
  import { basename, dirname as dirname4, join as join6, resolve } from "node:path";
3530
- import { z as z18 } from "zod";
3617
+ import { z as z19 } from "zod";
3531
3618
 
3532
3619
  // src/login-shell.ts
3533
3620
  init_define_HERD_PACKAGE();
@@ -3653,16 +3740,16 @@ function recordTime(line2) {
3653
3740
  return Number.isNaN(time8) ? 0 : time8;
3654
3741
  }
3655
3742
  function readNew(path, offset) {
3656
- let size;
3743
+ let size2;
3657
3744
  try {
3658
- size = statSync(path).size;
3745
+ size2 = statSync(path).size;
3659
3746
  } catch {
3660
3747
  return { events: [], offset };
3661
3748
  }
3662
- if (size <= offset) return { events: [], offset };
3749
+ if (size2 <= offset) return { events: [], offset };
3663
3750
  const fd = openSync(path, "r");
3664
3751
  try {
3665
- const buffer = Buffer.alloc(size - offset);
3752
+ const buffer = Buffer.alloc(size2 - offset);
3666
3753
  const read = readSync(fd, buffer, 0, buffer.length, offset);
3667
3754
  const chunk = buffer.subarray(0, read);
3668
3755
  const end = chunk.lastIndexOf(10);
@@ -3763,9 +3850,9 @@ function latestCodexUsage(home = codexHome()) {
3763
3850
  if (!newest) return null;
3764
3851
  let tail3;
3765
3852
  try {
3766
- const size = statSync(newest).size;
3767
- const start = Math.max(0, size - USAGE_TAIL_BYTES);
3768
- const buffer = Buffer.alloc(size - start);
3853
+ const size2 = statSync(newest).size;
3854
+ const start = Math.max(0, size2 - USAGE_TAIL_BYTES);
3855
+ const buffer = Buffer.alloc(size2 - start);
3769
3856
  const fd = openSync(newest, "r");
3770
3857
  try {
3771
3858
  readSync(fd, buffer, 0, buffer.length, start);
@@ -4306,13 +4393,13 @@ function hasUnpushed(path) {
4306
4393
 
4307
4394
  // src/config.ts
4308
4395
  init_self();
4309
- var repoSchema = z18.object({ name: z18.string(), path: z18.string(), remote: z18.string() });
4310
- var configSchema = z18.object({
4311
- machineId: z18.string(),
4312
- machineName: z18.string(),
4313
- repos: z18.array(repoSchema),
4396
+ var repoSchema = z19.object({ name: z19.string(), path: z19.string(), remote: z19.string() });
4397
+ var configSchema = z19.object({
4398
+ machineId: z19.string(),
4399
+ machineName: z19.string(),
4400
+ repos: z19.array(repoSchema),
4314
4401
  /** The machine's root folder, holding herd's clones and its conversations' folders. */
4315
- root: z18.string().optional()
4402
+ root: z19.string().optional()
4316
4403
  });
4317
4404
  var configPath = () => join6(herdHome(), "config.json");
4318
4405
  function save(config) {
@@ -4519,32 +4606,32 @@ async function readSetup(fetch2, init) {
4519
4606
  // src/friday/agents.ts
4520
4607
  init_define_HERD_PACKAGE();
4521
4608
  init_src();
4522
- import { existsSync as existsSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
4523
- import { dirname as dirname6, join as join8 } from "node:path";
4609
+ import { existsSync as existsSync5, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "node:fs";
4610
+ import { dirname as dirname6, join as join9 } from "node:path";
4524
4611
 
4525
4612
  // src/activation.ts
4526
4613
  init_define_HERD_PACKAGE();
4527
4614
  init_src();
4528
4615
  import { randomBytes as randomBytes4, randomUUID as randomUUID3 } from "node:crypto";
4529
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
4530
- import { dirname as dirname5, join as join7 } from "node:path";
4531
- import { z as z21 } from "zod";
4616
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync6, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
4617
+ import { dirname as dirname5, join as join8 } from "node:path";
4618
+ import { z as z22 } from "zod";
4532
4619
 
4533
4620
  // src/permissions.ts
4534
4621
  init_define_HERD_PACKAGE();
4535
4622
  init_src();
4536
4623
  import { randomUUID as randomUUID2 } from "node:crypto";
4537
- import { z as z20 } from "zod";
4624
+ import { z as z21 } from "zod";
4538
4625
 
4539
4626
  // src/local-socket.ts
4540
4627
  init_define_HERD_PACKAGE();
4541
4628
  import { randomBytes as randomBytes3 } from "node:crypto";
4542
4629
  import { chmodSync as chmodSync2, existsSync as existsSync4, rmSync as rmSync4 } from "node:fs";
4543
4630
  import { connect, createServer as createServer2 } from "node:net";
4544
- import { z as z19 } from "zod";
4545
- var localRequestSchema = z19.object({
4546
- secret: z19.string().min(1),
4547
- op: z19.enum([
4631
+ import { z as z20 } from "zod";
4632
+ var localRequestSchema = z20.object({
4633
+ secret: z20.string().min(1),
4634
+ op: z20.enum([
4548
4635
  "inbox",
4549
4636
  "send",
4550
4637
  "run",
@@ -4576,32 +4663,32 @@ var localRequestSchema = z19.object({
4576
4663
  "activate"
4577
4664
  ]),
4578
4665
  // The key of the agent's start, from `HERD_START`, which `herd inbox` proves the start with.
4579
- start: z19.string().min(1).max(100).optional(),
4666
+ start: z20.string().min(1).max(100).optional(),
4580
4667
  // `herd repo`: the repo it added to the conversation's folder, for the daemon to record.
4581
- repo: z19.object({
4582
- remote: z19.string().min(1).max(500),
4583
- folder: z19.string().min(1).max(100),
4584
- base: z19.string().min(1).max(300).optional(),
4585
- fetchFailed: z19.string().max(1e3).optional()
4668
+ repo: z20.object({
4669
+ remote: z20.string().min(1).max(500),
4670
+ folder: z20.string().min(1).max(100),
4671
+ base: z20.string().min(1).max(300).optional(),
4672
+ fetchFailed: z20.string().max(1e3).optional()
4586
4673
  }).optional(),
4587
4674
  // `herd repo`: what the agent named and the folder it asked for, for the daemon to add.
4588
- repoAdd: z19.object({
4589
- asked: z19.string().min(1).max(500),
4590
- folder: z19.string().min(1).max(100).optional()
4675
+ repoAdd: z20.object({
4676
+ asked: z20.string().min(1).max(500),
4677
+ folder: z20.string().min(1).max(100).optional()
4591
4678
  }).optional(),
4592
4679
  // `herd delegate --repo`, given once for each repo the delegate works in.
4593
- repos: z19.array(z19.string().min(1).max(500)).max(10).optional(),
4680
+ repos: z20.array(z20.string().min(1).max(500)).max(10).optional(),
4594
4681
  // `herd watch`, `herd unwatch` and `herd watching`: what to do, and the pull request.
4595
- watch: z19.object({
4596
- action: z19.enum(["watch", "unwatch", "list"]),
4597
- url: z19.string().min(1).max(500).optional()
4682
+ watch: z20.object({
4683
+ action: z20.enum(["watch", "unwatch", "list"]),
4684
+ url: z20.string().min(1).max(500).optional()
4598
4685
  }).optional(),
4599
4686
  // `herd delegate`, `herd tell` and `herd handoff`: the worker or conversation they name.
4600
- worker: z19.string().min(1).max(200).optional(),
4601
- to: z19.string().min(1).max(200).optional(),
4687
+ worker: z20.string().min(1).max(200).optional(),
4688
+ to: z20.string().min(1).max(200).optional(),
4602
4689
  // `herd automations`: what to do, to which automation, and the definition or change it sends.
4603
- automation: z19.object({
4604
- action: z19.enum([
4690
+ automation: z20.object({
4691
+ action: z20.enum([
4605
4692
  "list",
4606
4693
  "show",
4607
4694
  "create",
@@ -4612,47 +4699,47 @@ var localRequestSchema = z19.object({
4612
4699
  "test",
4613
4700
  "rotate"
4614
4701
  ]),
4615
- id: z19.string().min(1).max(100).optional(),
4616
- body: z19.record(z19.string(), z19.unknown()).optional(),
4617
- waitMs: z19.number().int().min(0).max(6e5).optional()
4702
+ id: z20.string().min(1).max(100).optional(),
4703
+ body: z20.record(z20.string(), z20.unknown()).optional(),
4704
+ waitMs: z20.number().int().min(0).max(6e5).optional()
4618
4705
  }).optional(),
4619
4706
  // `herd work`: list, record or remove one, with the change the daemon checks again.
4620
- work: z19.object({
4621
- action: z19.enum(["list", "record", "remove"]),
4622
- change: z19.unknown().optional(),
4623
- url: z19.string().min(1).max(2048).optional()
4707
+ work: z20.object({
4708
+ action: z20.enum(["list", "record", "remove"]),
4709
+ change: z20.unknown().optional(),
4710
+ url: z20.string().min(1).max(2048).optional()
4624
4711
  }).optional(),
4625
4712
  // `herd conversations --about <url>`: only the owner's conversations about that URL.
4626
- about: z19.string().min(1).max(2048).optional(),
4713
+ about: z20.string().min(1).max(2048).optional(),
4627
4714
  // `herd investigate screen` or `herd investigate propose`, with the proposal the daemon checks.
4628
- action: z19.enum(["screen", "propose"]).optional(),
4629
- proposal: z19.unknown().optional(),
4715
+ action: z20.enum(["screen", "propose"]).optional(),
4716
+ proposal: z20.unknown().optional(),
4630
4717
  // Commands may name a conversation, but only their own is ever allowed.
4631
- conversationId: z19.string().optional(),
4718
+ conversationId: z20.string().optional(),
4632
4719
  // Where `conversations` is shown, which decides whether private titles are. Only the terminal's
4633
4720
  // own conversation is ever allowed, so a terminal can't pick one that shows more.
4634
- for: z19.string().optional(),
4635
- text: z19.string().optional(),
4721
+ for: z20.string().optional(),
4722
+ text: z20.string().optional(),
4636
4723
  // A whole message, from `herd send --json`, which the daemon checks again.
4637
- message: z19.unknown().optional(),
4638
- idempotencyKey: z19.string().optional(),
4639
- command: z19.array(z19.string()).optional(),
4724
+ message: z20.unknown().optional(),
4725
+ idempotencyKey: z20.string().optional(),
4726
+ command: z20.array(z20.string()).optional(),
4640
4727
  // `status --clear`.
4641
- clear: z19.boolean().optional(),
4728
+ clear: z20.boolean().optional(),
4642
4729
  // What an agent's status line reported, checked by the daemon.
4643
- usage: z19.unknown().optional(),
4730
+ usage: z20.unknown().optional(),
4644
4731
  // Which agent and session a status line ran for. Its reports count only from the session herd
4645
4732
  // started in the conversation's lead terminal.
4646
- agent: z19.enum(["claude-code", "codex", "agy"]).optional(),
4647
- sessionId: z19.string().min(1).max(200).optional(),
4733
+ agent: z20.enum(["claude-code", "codex", "agy"]).optional(),
4734
+ sessionId: z20.string().min(1).max(200).optional(),
4648
4735
  // What agy's status line says its agent is doing: `working` or `idle`.
4649
- state: z19.enum(["working", "idle"]).optional(),
4736
+ state: z20.enum(["working", "idle"]).optional(),
4650
4737
  // A permission request from Claude Code's hook, with the key it keeps across reconnects.
4651
- key: z19.string().min(1).max(200).optional(),
4652
- request: z19.unknown().optional(),
4738
+ key: z20.string().min(1).max(200).optional(),
4739
+ request: z20.unknown().optional(),
4653
4740
  // For one of Claude Code's other hooks: which event, and the JSON Claude Code gave it.
4654
- event: z19.string().optional(),
4655
- input: z19.record(z19.string(), z19.unknown()).optional()
4741
+ event: z20.string().optional(),
4742
+ input: z20.record(z20.string(), z20.unknown()).optional()
4656
4743
  });
4657
4744
  var LocalSocket = class {
4658
4745
  constructor(path, handle) {
@@ -4774,10 +4861,10 @@ function isAnswering(path) {
4774
4861
  }
4775
4862
 
4776
4863
  // src/permissions.ts
4777
- var hookRequestSchema = z20.looseObject({
4778
- tool_name: z20.string().min(1),
4779
- tool_input: z20.record(z20.string(), z20.unknown()).default({}),
4780
- permission_suggestions: z20.array(z20.unknown()).optional()
4864
+ var hookRequestSchema = z21.looseObject({
4865
+ tool_name: z21.string().min(1),
4866
+ tool_input: z21.record(z21.string(), z21.unknown()).default({}),
4867
+ permission_suggestions: z21.array(z21.unknown()).optional()
4781
4868
  });
4782
4869
  var DETAIL_LIMIT = 2800;
4783
4870
  var REPLY_MESSAGE_LIMIT = 2e3;
@@ -4930,6 +5017,243 @@ async function permissionHook(raw, options = {}) {
4930
5017
  }
4931
5018
  }
4932
5019
 
5020
+ // src/terminals.ts
5021
+ init_define_HERD_PACKAGE();
5022
+ import { spawn as spawn4 } from "node:child_process";
5023
+ import { mkdirSync as mkdirSync5, readdirSync as readdirSync3, rmSync as rmSync5 } from "node:fs";
5024
+ import { connect as connect2 } from "node:net";
5025
+ import { join as join7 } from "node:path";
5026
+ init_self();
5027
+ import {
5028
+ HOST_PROTOCOL,
5029
+ hostVersion
5030
+ } from "./terminal-host.mjs";
5031
+ var hostsDir = () => join7(herdHome(), "terminals");
5032
+ var legacyHostSocketPath = () => join7(herdHome(), "terminals.sock");
5033
+ var currentHostSocketPath = (version = hostVersion()) => join7(hostsDir(), `${version}.sock`);
5034
+ var speaksProtocol = (protocol) => protocol === HOST_PROTOCOL || protocol === HOST_PROTOCOL - 1;
5035
+ var sleep = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
5036
+ function askHost(path, request) {
5037
+ return new Promise((resolve8, reject) => {
5038
+ const socket = connect2(path);
5039
+ let data = "";
5040
+ socket.on("connect", () => socket.write(`${JSON.stringify(request)}
5041
+ `));
5042
+ socket.on("data", (chunk) => data += chunk);
5043
+ socket.on("end", () => {
5044
+ try {
5045
+ const reply = JSON.parse(data);
5046
+ if (reply.ok) resolve8(reply);
5047
+ else reject(new HerdError(reply.error ?? "The terminal host refused that."));
5048
+ } catch {
5049
+ reject(new HerdError("The terminal host's answer wasn't readable."));
5050
+ }
5051
+ });
5052
+ socket.on("error", reject);
5053
+ });
5054
+ }
5055
+ var isDead = (error) => {
5056
+ const code = error?.code;
5057
+ return code === "ECONNREFUSED" || code === "ENOENT";
5058
+ };
5059
+ var HostTerminals = class {
5060
+ hosts = [];
5061
+ /** Which host each terminal name was last found in. */
5062
+ where = /* @__PURE__ */ new Map();
5063
+ version;
5064
+ log;
5065
+ constructor(options = {}) {
5066
+ this.version = options.version ?? hostVersion();
5067
+ this.log = options.log ?? (() => {
5068
+ });
5069
+ }
5070
+ currentVersion() {
5071
+ return this.version;
5072
+ }
5073
+ unreachable(version) {
5074
+ return this.hosts.some((host) => host.hello.version === version && !host.speaks);
5075
+ }
5076
+ /** Every host that answers, with sockets nothing answers on removed. */
5077
+ async discover() {
5078
+ const dir = hostsDir();
5079
+ let names = [];
5080
+ try {
5081
+ names = readdirSync3(dir).filter((name) => name.endsWith(".sock"));
5082
+ } catch {
5083
+ }
5084
+ const paths = [legacyHostSocketPath(), ...names.map((name) => join7(dir, name))];
5085
+ const found = [];
5086
+ for (const path of paths) {
5087
+ try {
5088
+ const hello = await askHost(path, { op: "hello" });
5089
+ found.push({
5090
+ path,
5091
+ hello,
5092
+ current: hello.version === this.version && speaksProtocol(hello.protocol),
5093
+ speaks: speaksProtocol(hello.protocol)
5094
+ });
5095
+ } catch (error) {
5096
+ if (isDead(error) && path !== legacyHostSocketPath()) rmSync5(path, { force: true });
5097
+ }
5098
+ }
5099
+ this.hosts = found;
5100
+ return found;
5101
+ }
5102
+ current() {
5103
+ return this.hosts.find((host) => host.current);
5104
+ }
5105
+ /** Starts a host of the daemon's own version if none is running, detached so it outlives it. */
5106
+ async ensureHost() {
5107
+ const hosts = await this.discover();
5108
+ for (const host of hosts) {
5109
+ const version = host.hello.version ?? "unversioned";
5110
+ const role = host.current ? "current" : host.speaks ? "older" : "not driveable";
5111
+ this.log(
5112
+ `Terminal host ${version} (protocol ${host.hello.protocol}, pid ${host.hello.pid}): ${role}.`
5113
+ );
5114
+ }
5115
+ if (this.current()) return;
5116
+ const path = currentHostSocketPath(this.version);
5117
+ mkdirSync5(hostsDir(), { recursive: true, mode: 448 });
5118
+ const child = spawn4(process.execPath, [herdBin, "terminal-host", "--socket", path], {
5119
+ detached: true,
5120
+ stdio: "ignore",
5121
+ env: { ...process.env, HERD_HOME: herdHome() }
5122
+ });
5123
+ child.unref();
5124
+ for (let attempt = 0; attempt < 50 && !this.current(); attempt++) {
5125
+ await sleep(100);
5126
+ const hello = await askHost(path, { op: "hello" }).catch(() => null);
5127
+ if (!hello) continue;
5128
+ if (hello.version !== this.version) {
5129
+ throw new HerdError(
5130
+ `The terminal host started as version ${hello.version}, not ${this.version}, so its files changed as it started. Starting the daemon again picks this up.`
5131
+ );
5132
+ }
5133
+ this.hosts.push({ path, hello, current: true, speaks: speaksProtocol(hello.protocol) });
5134
+ }
5135
+ if (!this.current()) throw new HerdError("The terminal host didn't start.");
5136
+ this.log(`Started terminal host ${this.version}.`);
5137
+ }
5138
+ /**
5139
+ * Every driveable host's terminals. A name in more than one host, as when a session moved, is
5140
+ * the running one, or else the newest.
5141
+ */
5142
+ async list() {
5143
+ if (this.hosts.length === 0) await this.discover();
5144
+ const byName = /* @__PURE__ */ new Map();
5145
+ for (const host of this.hosts.filter((candidate) => candidate.speaks)) {
5146
+ const terminals = await askHost(host.path, { op: "list" }).then((reply) => reply.terminals).catch(() => []);
5147
+ for (const info of terminals) {
5148
+ const seen = byName.get(info.name);
5149
+ const better = !seen || seen.info.exited && !info.exited || seen.info.exited === info.exited && info.createdAt > seen.info.createdAt;
5150
+ if (better) byName.set(info.name, { info, path: host.path });
5151
+ }
5152
+ }
5153
+ this.where = new Map([...byName].map(([name, { path }]) => [name, path]));
5154
+ return [...byName.values()].map(({ info }) => info);
5155
+ }
5156
+ /** The socket of the host holding a terminal. */
5157
+ async pathOf(name) {
5158
+ let path = this.where.get(name);
5159
+ if (!path) {
5160
+ await this.list();
5161
+ path = this.where.get(name);
5162
+ }
5163
+ if (!path) throw new HerdError(`There's no terminal named ${name}.`);
5164
+ return path;
5165
+ }
5166
+ async askFor(name, request) {
5167
+ return askHost(await this.pathOf(name), request);
5168
+ }
5169
+ async create(opts) {
5170
+ const running = (await this.list()).find((t) => t.name === opts.name && !t.exited);
5171
+ if (running) throw new HerdError(`A terminal named ${opts.name} is already running.`);
5172
+ let host = this.current();
5173
+ if (!host) {
5174
+ await this.ensureHost();
5175
+ host = this.current();
5176
+ }
5177
+ if (!host) throw new HerdError("The terminal host didn't start.");
5178
+ const { terminal } = await askHost(host.path, {
5179
+ op: "create",
5180
+ ...opts
5181
+ });
5182
+ this.where.set(opts.name, host.path);
5183
+ return terminal;
5184
+ }
5185
+ async screen(name) {
5186
+ return (await this.askFor(name, { op: "screen", name })).screen;
5187
+ }
5188
+ async snapshot(name) {
5189
+ return (await this.askFor(name, { op: "snapshot", name })).snapshot;
5190
+ }
5191
+ async type(name, text4) {
5192
+ await this.askFor(name, { op: "type", name, text: text4 });
5193
+ }
5194
+ async press(name, key) {
5195
+ await this.askFor(name, { op: "press", name, key });
5196
+ }
5197
+ async exitStatus(name) {
5198
+ const terminal = (await this.list()).find((candidate) => candidate.name === name);
5199
+ if (!terminal) throw new HerdError(`There's no terminal named ${name}.`);
5200
+ return terminal.exited ? terminal.exitCode : null;
5201
+ }
5202
+ async kill(name) {
5203
+ await this.askFor(name, { op: "kill", name });
5204
+ }
5205
+ async retire(canStop) {
5206
+ const stopped2 = [];
5207
+ for (const host of await this.discover()) {
5208
+ if (host.current) continue;
5209
+ const version = host.hello.version ?? "unversioned";
5210
+ if (host.speaks) {
5211
+ const terminals = await askHost(host.path, { op: "list" }).then((reply) => reply.terminals).catch(() => null);
5212
+ if (!terminals) continue;
5213
+ if (terminals.some((t) => !t.exited || (t.viewers ?? 0) > 0)) continue;
5214
+ } else if (!host.hello.version || !canStop(host.hello.version)) {
5215
+ continue;
5216
+ }
5217
+ if (await this.stop(host)) {
5218
+ stopped2.push(version);
5219
+ this.log(`Retired terminal host ${version}, which had nothing left running.`);
5220
+ }
5221
+ }
5222
+ if (stopped2.length > 0) await this.discover();
5223
+ return stopped2;
5224
+ }
5225
+ /** Stops a host: politely where it knows how, and otherwise by its process. */
5226
+ async stop(host) {
5227
+ if (host.speaks && host.hello.ops?.includes("shutdown")) {
5228
+ return askHost(host.path, { op: "shutdown" }).then(
5229
+ () => true,
5230
+ () => false
5231
+ );
5232
+ }
5233
+ const hello = await askHost(host.path, { op: "hello" }).catch(() => null);
5234
+ if (!hello || hello.pid !== host.hello.pid) return false;
5235
+ try {
5236
+ process.kill(hello.pid, "SIGTERM");
5237
+ } catch {
5238
+ return false;
5239
+ }
5240
+ if (host.path === legacyHostSocketPath()) {
5241
+ for (let attempt = 0; attempt < 20; attempt++) {
5242
+ await sleep(100);
5243
+ if (!await askHost(host.path, { op: "hello" }).catch(() => null)) break;
5244
+ }
5245
+ rmSync5(host.path, { force: true });
5246
+ }
5247
+ return true;
5248
+ }
5249
+ };
5250
+ var DRIVER_QUIET_MS = 10 * 1e3;
5251
+ function someoneDriving(terminal, quietMs = DRIVER_QUIET_MS, now = Date.now()) {
5252
+ if (!terminal || (terminal.viewers ?? 0) === 0 || !terminal.drivenAt) return false;
5253
+ return now - terminal.drivenAt < quietMs;
5254
+ }
5255
+ var lookingAt = (terminal) => (terminal?.viewers ?? 0) + (terminal?.watchers ?? 0);
5256
+
4933
5257
  // src/timing.ts
4934
5258
  init_define_HERD_PACKAGE();
4935
5259
  var CHECK_IN_MS = 15e3;
@@ -4943,6 +5267,7 @@ var VARIABLES = {
4943
5267
  agyIdleSettleMs: "HERD_AGY_IDLE_SETTLE_MS",
4944
5268
  promptPollMs: "HERD_PROMPT_POLL_MS",
4945
5269
  noticeRetryMs: "HERD_NOTICE_RETRY_MS",
5270
+ driverQuietMs: "HERD_DRIVER_QUIET_MS",
4946
5271
  investigateAfterMs: "HERD_INVESTIGATE_AFTER_MS",
4947
5272
  idleMs: "HERD_IDLE_MS",
4948
5273
  watchMs: "HERD_WATCH_MS",
@@ -4986,14 +5311,14 @@ function newActivationCode() {
4986
5311
  return [...randomBytes4(8)].map((byte) => CODE_LETTERS[byte % CODE_LETTERS.length]).join("");
4987
5312
  }
4988
5313
  var activationTerminal = (agent) => `herd-activate-${agentShortName(agent)}`;
4989
- var activationFolder = (root, agent) => join7(convosHome(root), ".activate", agentShortName(agent));
5314
+ var activationFolder = (root, agent) => join8(convosHome(root), ".activate", agentShortName(agent));
4990
5315
  var recordSchema = agentActivationSchema.extend({
4991
5316
  /** While it's waiting: the terminal it runs in, which `friday start` can attach to. */
4992
- terminal: z21.string().optional()
5317
+ terminal: z22.string().optional()
4993
5318
  });
4994
- var storeSchema = z21.object({ agents: z21.record(z21.string(), recordSchema) });
4995
- var activationsPath = (home = herdHome()) => join7(home, "activations.json");
4996
- var activationRequestPath = (home = herdHome()) => join7(home, "activate-request");
5319
+ var storeSchema = z22.object({ agents: z22.record(z22.string(), recordSchema) });
5320
+ var activationsPath = (home = herdHome()) => join8(home, "activations.json");
5321
+ var activationRequestPath = (home = herdHome()) => join8(home, "activate-request");
4997
5322
  function readActivations(path = activationsPath()) {
4998
5323
  try {
4999
5324
  return storeSchema.parse(JSON.parse(readFileSync5(path, "utf8"))).agents;
@@ -5002,7 +5327,7 @@ function readActivations(path = activationsPath()) {
5002
5327
  }
5003
5328
  }
5004
5329
  function requestActivation(home = herdHome(), now = Date.now()) {
5005
- mkdirSync5(home, { recursive: true, mode: 448 });
5330
+ mkdirSync6(home, { recursive: true, mode: 448 });
5006
5331
  writeFileSync4(activationRequestPath(home), String(now), { mode: 384 });
5007
5332
  }
5008
5333
  function needsActivation(record, version) {
@@ -5019,7 +5344,7 @@ function activateDecision(request, code) {
5019
5344
  }
5020
5345
  };
5021
5346
  }
5022
- var sleep = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
5347
+ var sleep2 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
5023
5348
  var label = (agent) => AGENTS[agent]?.label ?? agent;
5024
5349
  var Activator = class {
5025
5350
  constructor(options) {
@@ -5047,7 +5372,7 @@ var Activator = class {
5047
5372
  }
5048
5373
  record(agent, record) {
5049
5374
  const agents = { ...readActivations(this.path), [agent]: record };
5050
- mkdirSync5(dirname5(this.path), { recursive: true, mode: 448 });
5375
+ mkdirSync6(dirname5(this.path), { recursive: true, mode: 448 });
5051
5376
  const temporary = `${this.path}.${process.pid}`;
5052
5377
  writeFileSync4(temporary, JSON.stringify({ agents }, null, 2), { mode: 384 });
5053
5378
  renameSync2(temporary, this.path);
@@ -5094,7 +5419,7 @@ var Activator = class {
5094
5419
  this.log(`Activating ${label(next.agent)} once its conversations here settle.`);
5095
5420
  }
5096
5421
  while (this.options.busy(next.agent) && !this.stopped) {
5097
- await sleep(this.options.busyPollMs ?? BUSY_POLL_MS);
5422
+ await sleep2(this.options.busyPollMs ?? BUSY_POLL_MS);
5098
5423
  }
5099
5424
  if (this.stopped) break;
5100
5425
  await this.activate(next.agent, next.version).catch(
@@ -5140,8 +5465,8 @@ var Activator = class {
5140
5465
  const code = newActivationCode();
5141
5466
  const terminal = activationTerminal(agent);
5142
5467
  const folder = activationFolder(this.options.root(), agent);
5143
- rmSync5(folder, { recursive: true, force: true });
5144
- mkdirSync5(folder, { recursive: true });
5468
+ rmSync6(folder, { recursive: true, force: true });
5469
+ mkdirSync6(folder, { recursive: true });
5145
5470
  const current = { agent, code, terminal, checkedIn: false, wake: () => {
5146
5471
  } };
5147
5472
  this.current = current;
@@ -5167,7 +5492,7 @@ var Activator = class {
5167
5492
  );
5168
5493
  if (open) await terminals.kill(terminal).catch(() => {
5169
5494
  });
5170
- rmSync5(folder, { recursive: true, force: true });
5495
+ rmSync6(folder, { recursive: true, force: true });
5171
5496
  }
5172
5497
  this.record(agent, outcome);
5173
5498
  this.log(
@@ -5228,7 +5553,9 @@ var Activator = class {
5228
5553
  terminal: current.terminal
5229
5554
  });
5230
5555
  }
5231
- const viewers = (await terminals.list()).find((t) => t.name === current.terminal)?.viewers ?? 0;
5556
+ const viewers = lookingAt(
5557
+ (await terminals.list()).find((t) => t.name === current.terminal)
5558
+ );
5232
5559
  if (viewers === 0 && Date.now() - startedAt >= restartMs) await start();
5233
5560
  } else if (reading?.state === "idle" && reading.program !== "shell" && !prompted) {
5234
5561
  prompted = true;
@@ -5330,7 +5657,7 @@ async function chooseAgents(options, print) {
5330
5657
  Object.fromEntries(choices.map((choice) => [choice.cli, choice.share]));
5331
5658
  const choosing = options.ask || options.given !== void 0;
5332
5659
  if (choosing && (!sameChoice(saved, shares) || !existsSync5(options.configPath))) {
5333
- mkdirSync6(dirname6(options.configPath), { recursive: true, mode: 448 });
5660
+ mkdirSync7(dirname6(options.configPath), { recursive: true, mode: 448 });
5334
5661
  writeFileSync5(options.configPath, configSource(withChoice(saved, shares)), { mode: 384 });
5335
5662
  }
5336
5663
  const chosen = ADAPTERS.map(({ cli }) => cli).filter((cli) => (shares[cli] ?? "off") !== "off");
@@ -5344,7 +5671,7 @@ function askToActivate(herdHome2, now = Date.now()) {
5344
5671
  requestActivation(herdHome2, now);
5345
5672
  return now;
5346
5673
  }
5347
- var sleep2 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
5674
+ var sleep3 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
5348
5675
  function settledLine(cli, activation) {
5349
5676
  if (activation.state === "activated") return `${label2(cli)} is activated.`;
5350
5677
  return `${label2(cli)} ${activationText(cli, activation).replace(" there", " on this computer")}.`;
@@ -5395,11 +5722,11 @@ async function awaitActivations(options, chosen, since, print) {
5395
5722
  );
5396
5723
  break;
5397
5724
  }
5398
- await sleep2(pollMs);
5725
+ await sleep3(pollMs);
5399
5726
  }
5400
5727
  return settled;
5401
5728
  }
5402
- var herdConfigIn = (herdHome2) => join8(herdHome2, "herd.config.ts");
5729
+ var herdConfigIn = (herdHome2) => join9(herdHome2, "herd.config.ts");
5403
5730
 
5404
5731
  // src/friday/home.ts
5405
5732
  init_define_HERD_PACKAGE();
@@ -5408,32 +5735,32 @@ import { spawnSync } from "node:child_process";
5408
5735
  import {
5409
5736
  closeSync as closeSync2,
5410
5737
  existsSync as existsSync6,
5411
- mkdirSync as mkdirSync7,
5738
+ mkdirSync as mkdirSync8,
5412
5739
  openSync as openSync2,
5413
5740
  readFileSync as readFileSync6,
5414
- rmSync as rmSync6,
5741
+ rmSync as rmSync7,
5415
5742
  statSync as statSync5,
5416
5743
  writeFileSync as writeFileSync6
5417
5744
  } from "node:fs";
5418
- import { dirname as dirname7, join as join9, resolve as resolve2 } from "node:path";
5745
+ import { dirname as dirname7, join as join10, resolve as resolve2 } from "node:path";
5419
5746
  var PACKAGE_NAME = FRIDAY_PACKAGE;
5420
5747
  function fridayPaths(herdHome2) {
5421
- const logs = join9(herdHome2, "logs");
5748
+ const logs = join10(herdHome2, "logs");
5422
5749
  return {
5423
5750
  herdHome: herdHome2,
5424
- config: join9(herdHome2, "config.json"),
5425
- versions: join9(herdHome2, "versions"),
5426
- current: join9(herdHome2, "current"),
5427
- pin: join9(herdHome2, "pin"),
5751
+ config: join10(herdHome2, "config.json"),
5752
+ versions: join10(herdHome2, "versions"),
5753
+ current: join10(herdHome2, "current"),
5754
+ pin: join10(herdHome2, "pin"),
5428
5755
  logs,
5429
- daemonLog: join9(logs, "daemon.log"),
5430
- supervisorLog: join9(logs, "supervisor.log"),
5431
- updateLock: join9(herdHome2, "update.lock"),
5432
- status: join9(herdHome2, "supervisor.json"),
5433
- terminals: join9(herdHome2, "terminals")
5756
+ daemonLog: join10(logs, "daemon.log"),
5757
+ supervisorLog: join10(logs, "supervisor.log"),
5758
+ updateLock: join10(herdHome2, "update.lock"),
5759
+ status: join10(herdHome2, "supervisor.json"),
5760
+ terminals: join10(herdHome2, "terminals")
5434
5761
  };
5435
5762
  }
5436
- var packageDir = (versionDir) => join9(versionDir, "node_modules", ...PACKAGE_NAME.split("/"));
5763
+ var packageDir = (versionDir) => join10(versionDir, "node_modules", ...PACKAGE_NAME.split("/"));
5437
5764
  function alive2(pid) {
5438
5765
  if (!Number.isInteger(pid) || pid <= 0) return false;
5439
5766
  try {
@@ -5444,11 +5771,11 @@ function alive2(pid) {
5444
5771
  }
5445
5772
  }
5446
5773
  function takeLock(path, staleMs = 10 * 6e4) {
5447
- mkdirSync7(dirname7(path), { recursive: true });
5774
+ mkdirSync8(dirname7(path), { recursive: true });
5448
5775
  if (existsSync6(path)) {
5449
5776
  const holder2 = Number(readFileSync6(path, "utf8").trim());
5450
5777
  const old = Date.now() - statSync5(path).mtimeMs > staleMs;
5451
- if (old || !alive2(holder2)) rmSync6(path, { force: true });
5778
+ if (old || !alive2(holder2)) rmSync7(path, { force: true });
5452
5779
  }
5453
5780
  try {
5454
5781
  const fd = openSync2(path, "wx");
@@ -5457,7 +5784,7 @@ function takeLock(path, staleMs = 10 * 6e4) {
5457
5784
  } catch {
5458
5785
  return null;
5459
5786
  }
5460
- return () => rmSync6(path, { force: true });
5787
+ return () => rmSync7(path, { force: true });
5461
5788
  }
5462
5789
  function readStatus(paths) {
5463
5790
  try {
@@ -5467,7 +5794,7 @@ function readStatus(paths) {
5467
5794
  }
5468
5795
  }
5469
5796
  function writeStatus(paths, status2) {
5470
- mkdirSync7(paths.herdHome, { recursive: true, mode: 448 });
5797
+ mkdirSync8(paths.herdHome, { recursive: true, mode: 448 });
5471
5798
  writeFileSync6(paths.status, `${JSON.stringify(status2, null, 2)}
5472
5799
  `);
5473
5800
  }
@@ -5490,7 +5817,7 @@ function writeRoot(paths, root) {
5490
5817
  } catch {
5491
5818
  config = {};
5492
5819
  }
5493
- mkdirSync7(paths.herdHome, { recursive: true, mode: 448 });
5820
+ mkdirSync8(paths.herdHome, { recursive: true, mode: 448 });
5494
5821
  writeFileSync6(paths.config, JSON.stringify({ ...config, root }, null, 2), { mode: 384 });
5495
5822
  }
5496
5823
  function insideWorkTree(folder) {
@@ -5583,10 +5910,10 @@ function pickInTerminal(choices, input = process.stdin, output2 = process.stdout
5583
5910
 
5584
5911
  // src/friday/service.ts
5585
5912
  init_define_HERD_PACKAGE();
5586
- import { spawn as spawn4 } from "node:child_process";
5587
- import { existsSync as existsSync7, mkdirSync as mkdirSync8, openSync as openSync3, readFileSync as readFileSync7, rmSync as rmSync7, writeFileSync as writeFileSync7 } from "node:fs";
5913
+ import { spawn as spawn5 } from "node:child_process";
5914
+ import { existsSync as existsSync7, mkdirSync as mkdirSync9, openSync as openSync3, readFileSync as readFileSync7, rmSync as rmSync8, writeFileSync as writeFileSync7 } from "node:fs";
5588
5915
  import { userInfo } from "node:os";
5589
- import { dirname as dirname8, join as join10, win32 } from "node:path";
5916
+ import { dirname as dirname8, join as join11, win32 } from "node:path";
5590
5917
  var systemdBooted = () => existsSync7("/run/systemd/system");
5591
5918
  var LAUNCH_AGENT = "com.herdingchats.friday";
5592
5919
  var SYSTEMD_UNIT = "herdingchats-friday.service";
@@ -5674,16 +6001,16 @@ WantedBy=default.target
5674
6001
  `;
5675
6002
  }
5676
6003
  function makeLogFolder(context2) {
5677
- mkdirSync8(dirname8(context2.paths.supervisorLog), { recursive: true });
6004
+ mkdirSync9(dirname8(context2.paths.supervisorLog), { recursive: true });
5678
6005
  }
5679
6006
  function writeIfChanged(path, text4) {
5680
6007
  if (existsSync7(path) && readFileSync7(path, "utf8") === text4) return false;
5681
- mkdirSync8(dirname8(path), { recursive: true });
6008
+ mkdirSync9(dirname8(path), { recursive: true });
5682
6009
  writeFileSync7(path, text4);
5683
6010
  return true;
5684
6011
  }
5685
6012
  function launchd(context2) {
5686
- const plist = join10(context2.home, "Library", "LaunchAgents", `${LAUNCH_AGENT}.plist`);
6013
+ const plist = join11(context2.home, "Library", "LaunchAgents", `${LAUNCH_AGENT}.plist`);
5687
6014
  const domain = `gui/${userInfo().uid}`;
5688
6015
  const target = `${domain}/${LAUNCH_AGENT}`;
5689
6016
  const loaded = async () => (await context2.run("launchctl", ["print", target])).status === 0;
@@ -5707,12 +6034,12 @@ function launchd(context2) {
5707
6034
  },
5708
6035
  async remove() {
5709
6036
  await context2.run("launchctl", ["bootout", target]);
5710
- rmSync7(plist, { force: true });
6037
+ rmSync8(plist, { force: true });
5711
6038
  }
5712
6039
  };
5713
6040
  }
5714
6041
  function systemd(context2) {
5715
- const unit = join10(context2.home, ".config", "systemd", "user", SYSTEMD_UNIT);
6042
+ const unit = join11(context2.home, ".config", "systemd", "user", SYSTEMD_UNIT);
5716
6043
  const systemctl = async (...args) => {
5717
6044
  const ran = await context2.run("systemctl", ["--user", ...args]);
5718
6045
  if (ran.status !== 0)
@@ -5734,15 +6061,15 @@ function systemd(context2) {
5734
6061
  },
5735
6062
  async remove() {
5736
6063
  await context2.run("systemctl", ["--user", "disable", "--now", SYSTEMD_UNIT]);
5737
- rmSync7(unit, { force: true });
6064
+ rmSync8(unit, { force: true });
5738
6065
  }
5739
6066
  };
5740
6067
  }
5741
6068
  function spawnInBackground(command, env, log) {
5742
- mkdirSync8(dirname8(log), { recursive: true });
6069
+ mkdirSync9(dirname8(log), { recursive: true });
5743
6070
  const out = openSync3(log, "a");
5744
6071
  const [program = process.execPath, ...args] = command;
5745
- spawn4(program, args, {
6072
+ spawn5(program, args, {
5746
6073
  detached: true,
5747
6074
  // On Windows, a detached process otherwise opens a console window of its own.
5748
6075
  windowsHide: true,
@@ -5794,35 +6121,35 @@ function serviceFor(kind, context2) {
5794
6121
  // src/friday/start.ts
5795
6122
  init_define_HERD_PACKAGE();
5796
6123
  init_src();
5797
- import { mkdirSync as mkdirSync10, realpathSync as realpathSync4 } from "node:fs";
5798
- import { join as join14, resolve as resolve3 } from "node:path";
6124
+ import { mkdirSync as mkdirSync11, realpathSync as realpathSync4 } from "node:fs";
6125
+ import { join as join15, resolve as resolve3 } from "node:path";
5799
6126
 
5800
6127
  // src/package-manager.ts
5801
6128
  init_define_HERD_PACKAGE();
5802
6129
  import { existsSync as existsSync9 } from "node:fs";
5803
6130
  import { homedir as homedir4 } from "node:os";
5804
- import { basename as basename3, delimiter, dirname as dirname9, isAbsolute as isAbsolute2, join as join12 } from "node:path";
6131
+ import { basename as basename3, delimiter, dirname as dirname9, isAbsolute as isAbsolute2, join as join13 } from "node:path";
5805
6132
 
5806
6133
  // src/friday/versions.ts
5807
6134
  init_define_HERD_PACKAGE();
5808
- import { spawn as spawn5 } from "node:child_process";
6135
+ import { spawn as spawn6 } from "node:child_process";
5809
6136
  import {
5810
6137
  existsSync as existsSync8,
5811
- mkdirSync as mkdirSync9,
5812
- readdirSync as readdirSync3,
6138
+ mkdirSync as mkdirSync10,
6139
+ readdirSync as readdirSync4,
5813
6140
  readFileSync as readFileSync8,
5814
6141
  readlinkSync,
5815
6142
  realpathSync as realpathSync3,
5816
6143
  renameSync as renameSync3,
5817
- rmSync as rmSync8,
6144
+ rmSync as rmSync9,
5818
6145
  symlinkSync,
5819
6146
  writeFileSync as writeFileSync8
5820
6147
  } from "node:fs";
5821
- import { basename as basename2, join as join11 } from "node:path";
6148
+ import { basename as basename2, join as join12 } from "node:path";
5822
6149
  var exec = (command, args, options = {}) => new Promise((resolve8) => {
5823
6150
  let stdout = "";
5824
6151
  let stderr = "";
5825
- const child = spawn5(command, args, {
6152
+ const child = spawn6(command, args, {
5826
6153
  cwd: options.cwd,
5827
6154
  env: options.env ?? process.env,
5828
6155
  stdio: ["ignore", "pipe", "pipe"]
@@ -5848,9 +6175,9 @@ function currentVersion(paths) {
5848
6175
  }
5849
6176
  function installedVersions(paths) {
5850
6177
  if (!existsSync8(paths.versions)) return [];
5851
- return readdirSync3(paths.versions).filter((name) => VERSION.test(name)).sort(compareVersions);
6178
+ return readdirSync4(paths.versions).filter((name) => VERSION.test(name)).sort(compareVersions);
5852
6179
  }
5853
- var binOf = (versionDir, bin) => join11(packageDir(versionDir), `${bin}.mjs`);
6180
+ var binOf = (versionDir, bin) => join12(packageDir(versionDir), `${bin}.mjs`);
5854
6181
  async function versionRuns(versionDir, version, run4 = exec) {
5855
6182
  const ran = await run4(process.execPath, [binOf(versionDir, "herd"), "--version"], {
5856
6183
  timeoutMs: 3e4
@@ -5866,7 +6193,7 @@ function recordedIntegrity(dir) {
5866
6193
  for (const file of LOCKFILES[manager]) {
5867
6194
  let lock;
5868
6195
  try {
5869
- lock = readFileSync8(join11(dir, file), "utf8");
6196
+ lock = readFileSync8(join12(dir, file), "utf8");
5870
6197
  } catch {
5871
6198
  continue;
5872
6199
  }
@@ -5884,7 +6211,7 @@ function npmFreeEnv() {
5884
6211
  async function installVersion(paths, version, { spec, integrity, run: run4 = exec, env = {}, log = () => {
5885
6212
  }, manager } = {}) {
5886
6213
  if (!VERSION.test(version)) return { ok: false, reason: `${version} isn't a version.` };
5887
- const dir = join11(paths.versions, version);
6214
+ const dir = join12(paths.versions, version);
5888
6215
  if (existsSync8(dir)) {
5889
6216
  const installed2 = recordedIntegrity(dir);
5890
6217
  if (integrity && installed2 !== integrity)
@@ -5900,19 +6227,19 @@ async function installVersion(paths, version, { spec, integrity, run: run4 = exe
5900
6227
  reason: "there's no package manager to install it with: herd looked for npm, pnpm and Yarn 1 beside Node, on the PATH and in their usual folders."
5901
6228
  };
5902
6229
  sweepStaging(paths);
5903
- const named = join11(paths.versions, `.staging-${version}-${process.pid}`);
5904
- rmSync8(named, { recursive: true, force: true });
5905
- mkdirSync9(named, { recursive: true });
6230
+ const named = join12(paths.versions, `.staging-${version}-${process.pid}`);
6231
+ rmSync9(named, { recursive: true, force: true });
6232
+ mkdirSync10(named, { recursive: true });
5906
6233
  const staging = realpathSync3(named);
5907
6234
  writeFileSync8(
5908
- join11(staging, "package.json"),
6235
+ join12(staging, "package.json"),
5909
6236
  `${JSON.stringify(allowScriptsManifest(["node-pty"]), null, 2)}
5910
6237
  `
5911
6238
  );
5912
6239
  if (using.name === "pnpm")
5913
- writeFileSync8(join11(staging, "pnpm-workspace.yaml"), allowScriptsWorkspace(["node-pty"]));
6240
+ writeFileSync8(join12(staging, "pnpm-workspace.yaml"), allowScriptsWorkspace(["node-pty"]));
5914
6241
  const fail = (reason) => {
5915
- rmSync8(staging, { recursive: true, force: true });
6242
+ rmSync9(staging, { recursive: true, force: true });
5916
6243
  return { ok: false, reason };
5917
6244
  };
5918
6245
  log(`Installing ${PACKAGE_NAME}@${version} with ${using.name} ${using.version}.`);
@@ -5935,21 +6262,21 @@ async function installVersion(paths, version, { spec, integrity, run: run4 = exe
5935
6262
  }
5936
6263
  const broken = await versionRuns(staging, version, run4);
5937
6264
  if (broken) return fail(broken);
5938
- renameSync3(staging, join11(realpathSync3(paths.versions), version));
6265
+ renameSync3(staging, join12(realpathSync3(paths.versions), version));
5939
6266
  return { ok: true, dir };
5940
6267
  }
5941
6268
  function sweepStaging(paths) {
5942
6269
  if (!existsSync8(paths.versions)) return;
5943
- for (const name of readdirSync3(paths.versions)) {
6270
+ for (const name of readdirSync4(paths.versions)) {
5944
6271
  const match = /^\.staging-.+-(\d+)$/.exec(name);
5945
6272
  if (match && !alive2(Number(match[1])))
5946
- rmSync8(join11(paths.versions, name), { recursive: true, force: true });
6273
+ rmSync9(join12(paths.versions, name), { recursive: true, force: true });
5947
6274
  }
5948
6275
  }
5949
6276
  function activate(paths, version) {
5950
6277
  const next = `${paths.current}.next-${process.pid}`;
5951
- rmSync8(next, { force: true });
5952
- symlinkSync(join11("versions", version), next);
6278
+ rmSync9(next, { force: true });
6279
+ symlinkSync(join12("versions", version), next);
5953
6280
  renameSync3(next, paths.current);
5954
6281
  }
5955
6282
  function pruneVersions(paths, { inUse = [], keep = 3 } = {}) {
@@ -5964,7 +6291,7 @@ function pruneVersions(paths, { inUse = [], keep = 3 } = {}) {
5964
6291
  const removed = [];
5965
6292
  for (const version of versions) {
5966
6293
  if (kept.has(version)) continue;
5967
- rmSync8(join11(paths.versions, version), { recursive: true, force: true });
6294
+ rmSync9(join12(paths.versions, version), { recursive: true, force: true });
5968
6295
  removed.push(version);
5969
6296
  }
5970
6297
  return removed;
@@ -5997,19 +6324,19 @@ var nameOf = (command) => {
5997
6324
  return MANAGERS.includes(name) ? name : null;
5998
6325
  };
5999
6326
  function homesOf(name, env, home) {
6000
- const common = [join12(home, ".volta", "bin"), join12(home, ".local", "bin")];
6327
+ const common = [join13(home, ".volta", "bin"), join13(home, ".local", "bin")];
6001
6328
  switch (name) {
6002
6329
  case "pnpm":
6003
6330
  return [
6004
6331
  ...env.PNPM_HOME ? [env.PNPM_HOME] : [],
6005
- join12(home, "Library", "pnpm"),
6006
- join12(home, ".local", "share", "pnpm"),
6332
+ join13(home, "Library", "pnpm"),
6333
+ join13(home, ".local", "share", "pnpm"),
6007
6334
  ...common
6008
6335
  ];
6009
6336
  case "bun":
6010
- return [join12(env.BUN_INSTALL || join12(home, ".bun"), "bin"), ...common];
6337
+ return [join13(env.BUN_INSTALL || join13(home, ".bun"), "bin"), ...common];
6011
6338
  case "yarn":
6012
- return [join12(home, ".yarn", "bin"), ...common];
6339
+ return [join13(home, ".yarn", "bin"), ...common];
6013
6340
  case "npm":
6014
6341
  return common;
6015
6342
  }
@@ -6032,7 +6359,7 @@ function candidatePaths(name, {
6032
6359
  const files = platform2 === "win32" ? [`${name}.cmd`, `${name}.exe`] : [name];
6033
6360
  const seen = /* @__PURE__ */ new Set();
6034
6361
  for (const dir of dirs.filter(Boolean)) {
6035
- for (const file of files) seen.add(join12(dir, file));
6362
+ for (const file of files) seen.add(join13(dir, file));
6036
6363
  }
6037
6364
  return [...seen];
6038
6365
  }
@@ -6192,22 +6519,22 @@ function frozenInstallArgs(name, { offline, yarnMajor }) {
6192
6519
 
6193
6520
  // src/friday/legacy.ts
6194
6521
  init_define_HERD_PACKAGE();
6195
- import { existsSync as existsSync10, readdirSync as readdirSync4, rmSync as rmSync9 } from "node:fs";
6522
+ import { existsSync as existsSync10, readdirSync as readdirSync5, rmSync as rmSync10 } from "node:fs";
6196
6523
  import { userInfo as userInfo2 } from "node:os";
6197
- import { join as join13 } from "node:path";
6524
+ import { join as join14 } from "node:path";
6198
6525
  var OLD_LAUNCH_AGENT = "com.herdingchats.herd";
6199
6526
  var OLD_APPS = ["herd-daemon", "herd-updater"];
6200
6527
  function oldPm2s(paths) {
6201
- const dir = join13(paths.herdHome, "daemon");
6528
+ const dir = join14(paths.herdHome, "daemon");
6202
6529
  if (!existsSync10(dir)) return [];
6203
- return readdirSync4(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join13(dir, entry.name, "node_modules", "pm2", "bin", "pm2")).filter((pm2) => existsSync10(pm2));
6530
+ return readdirSync5(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join14(dir, entry.name, "node_modules", "pm2", "bin", "pm2")).filter((pm2) => existsSync10(pm2));
6204
6531
  }
6205
6532
  async function retireOldDaemon(paths, { home, run: run4 }) {
6206
6533
  const removed = [];
6207
- const plist = join13(home, "Library", "LaunchAgents", `${OLD_LAUNCH_AGENT}.plist`);
6534
+ const plist = join14(home, "Library", "LaunchAgents", `${OLD_LAUNCH_AGENT}.plist`);
6208
6535
  if (existsSync10(plist)) {
6209
6536
  await run4("launchctl", ["bootout", `gui/${userInfo2().uid}/${OLD_LAUNCH_AGENT}`]);
6210
- rmSync9(plist, { force: true });
6537
+ rmSync10(plist, { force: true });
6211
6538
  removed.push(`the login item ${OLD_LAUNCH_AGENT}`);
6212
6539
  }
6213
6540
  for (const pm2 of oldPm2s(paths)) {
@@ -6283,7 +6610,7 @@ var real = (path) => {
6283
6610
  }
6284
6611
  };
6285
6612
  function chooseRoot(cwd, home) {
6286
- return real(cwd) === real(home) ? join14(home, "friday") : resolve3(cwd);
6613
+ return real(cwd) === real(home) ? join15(home, "friday") : resolve3(cwd);
6287
6614
  }
6288
6615
  async function runStart(options) {
6289
6616
  const { paths, run: run4 } = options;
@@ -6363,7 +6690,7 @@ async function runStart(options) {
6363
6690
  await service.stop();
6364
6691
  await stopped(options);
6365
6692
  }
6366
- mkdirSync10(root, { recursive: true });
6693
+ mkdirSync11(root, { recursive: true });
6367
6694
  writeRoot(paths, root);
6368
6695
  verbose2(`installing the ${service.kind} service`);
6369
6696
  const changed = await service.install();
@@ -6459,11 +6786,11 @@ async function ensureInstalled(options, print) {
6459
6786
  // src/friday/supervisor.ts
6460
6787
  init_define_HERD_PACKAGE();
6461
6788
  init_src();
6462
- import { spawn as spawn6 } from "node:child_process";
6789
+ import { spawn as spawn7 } from "node:child_process";
6463
6790
  import {
6464
6791
  closeSync as closeSync3,
6465
6792
  existsSync as existsSync11,
6466
- mkdirSync as mkdirSync11,
6793
+ mkdirSync as mkdirSync12,
6467
6794
  openSync as openSync4,
6468
6795
  readFileSync as readFileSync9,
6469
6796
  realpathSync as realpathSync5,
@@ -6558,7 +6885,7 @@ var Supervisor = class {
6558
6885
  spawnDaemon() {
6559
6886
  if (this.stopping) return;
6560
6887
  const { daemonLog, logs } = this.options.paths;
6561
- mkdirSync11(logs, { recursive: true });
6888
+ mkdirSync12(logs, { recursive: true });
6562
6889
  try {
6563
6890
  if (existsSync11(daemonLog) && statSync6(daemonLog).size > MAX_LOG_BYTES2)
6564
6891
  renameSync4(daemonLog, `${daemonLog}.1`);
@@ -6567,7 +6894,7 @@ var Supervisor = class {
6567
6894
  const out = openSync4(daemonLog, "a");
6568
6895
  const [program = process.execPath, ...args] = this.daemonCommand();
6569
6896
  const startedAt = Date.now();
6570
- const child = spawn6(program, args, { stdio: ["ignore", out, out], env: process.env });
6897
+ const child = spawn7(program, args, { stdio: ["ignore", out, out], env: process.env });
6571
6898
  closeSync3(out);
6572
6899
  this.daemon = child;
6573
6900
  this.status.daemonPid = child.pid ?? null;
@@ -6719,14 +7046,14 @@ Agents use the herd command, which comes with this one. herd setup shows what's
6719
7046
  function defaults() {
6720
7047
  const home = homedir5();
6721
7048
  return {
6722
- paths: fridayPaths(process.env.HERD_HOME ?? join15(home, ".herd")),
7049
+ paths: fridayPaths(process.env.HERD_HOME ?? join16(home, ".herd")),
6723
7050
  home,
6724
7051
  cwd: process.cwd(),
6725
7052
  run: exec,
6726
7053
  print: (line2) => console.log(line2)
6727
7054
  };
6728
7055
  }
6729
- var releaseSchema = z22.object({ version: z22.string().min(1), integrity: z22.string().min(1) });
7056
+ var releaseSchema = z23.object({ version: z23.string().min(1), integrity: z23.string().min(1) });
6730
7057
  async function fetchRelease() {
6731
7058
  const response = await apiFetch("/api/daemon/release");
6732
7059
  if (response.status === 404) return null;
@@ -6850,8 +7177,8 @@ function agentsFlag(args) {
6850
7177
  return parsed.shares;
6851
7178
  }
6852
7179
  function attachTo(paths, terminal) {
6853
- const herd = existsSync12(binOf(paths.current, "herd")) ? binOf(paths.current, "herd") : join15(dirname10(process.argv[1] ?? ""), "herd.mjs");
6854
- const child = spawn7(process.execPath, [herd, "attach", terminal], {
7180
+ const herd = existsSync12(binOf(paths.current, "herd")) ? binOf(paths.current, "herd") : join16(dirname10(process.argv[1] ?? ""), "herd.mjs");
7181
+ const child = spawn8(process.execPath, [herd, "attach", terminal], {
6855
7182
  stdio: "inherit",
6856
7183
  env: { ...process.env, HERD_HOME: paths.herdHome }
6857
7184
  });
@@ -6914,7 +7241,7 @@ function runStatus(deps) {
6914
7241
  function readMissingFolder(herdHome2) {
6915
7242
  try {
6916
7243
  return folderUnavailableSchema.parse(
6917
- JSON.parse(readFileSync10(join15(herdHome2, "folder-unavailable.json"), "utf8"))
7244
+ JSON.parse(readFileSync10(join16(herdHome2, "folder-unavailable.json"), "utf8"))
6918
7245
  );
6919
7246
  } catch {
6920
7247
  return null;
@@ -6935,7 +7262,7 @@ async function runLogs(args, deps) {
6935
7262
  return 0;
6936
7263
  }
6937
7264
  if (follow) {
6938
- const child = spawn7("tail", ["-n", "50", "-F", ...files], { stdio: "inherit" });
7265
+ const child = spawn8("tail", ["-n", "50", "-F", ...files], { stdio: "inherit" });
6939
7266
  return await new Promise((resolve8) => child.on("close", (code) => resolve8(code ?? 0)));
6940
7267
  }
6941
7268
  for (const file of files) {
@@ -6977,16 +7304,16 @@ init_define_HERD_PACKAGE();
6977
7304
 
6978
7305
  // src/agent-sandbox.ts
6979
7306
  init_define_HERD_PACKAGE();
6980
- import { spawn as spawn9 } from "node:child_process";
6981
- import { existsSync as existsSync13, mkdirSync as mkdirSync12, readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync7, writeFileSync as writeFileSync9 } from "node:fs";
7307
+ import { spawn as spawn10 } from "node:child_process";
7308
+ import { existsSync as existsSync13, mkdirSync as mkdirSync13, readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync7, writeFileSync as writeFileSync9 } from "node:fs";
6982
7309
  import { homedir as homedir6, tmpdir } from "node:os";
6983
- import { dirname as dirname12, isAbsolute as isAbsolute3, join as join17, resolve as resolve4 } from "node:path";
7310
+ import { dirname as dirname12, isAbsolute as isAbsolute3, join as join18, resolve as resolve4 } from "node:path";
6984
7311
 
6985
7312
  // src/build-sandbox.ts
6986
7313
  init_define_HERD_PACKAGE();
6987
- import { spawn as spawn8 } from "node:child_process";
7314
+ import { spawn as spawn9 } from "node:child_process";
6988
7315
  import { appendFileSync, readFileSync as readFileSync11 } from "node:fs";
6989
- import { dirname as dirname11, join as join16 } from "node:path";
7316
+ import { dirname as dirname11, join as join17 } from "node:path";
6990
7317
  var DEPLOY_HOSTS = ["api.cloudflare.com", "api.github.com", "registry.npmjs.org"];
6991
7318
  function buildPolicy(input) {
6992
7319
  const platform2 = input.platform ?? process.platform;
@@ -7021,7 +7348,7 @@ var shellQuote = (args) => args.map((arg) => `'${arg.replace(/'/g, "'\\''")}'`).
7021
7348
  var BLOCKED_LOG = "blocked.log";
7022
7349
  async function runSandboxed(args, {
7023
7350
  usage = "Usage: herd build-sandbox <policy> -- <command\u2026>",
7024
- blockedLog = (_policyPath) => join16(process.env.HOME ?? ".", BLOCKED_LOG)
7351
+ blockedLog = (_policyPath) => join17(process.env.HOME ?? ".", BLOCKED_LOG)
7025
7352
  } = {}) {
7026
7353
  const { SandboxManager } = await import("@anthropic-ai/sandbox-runtime");
7027
7354
  const [policyPath, separator, ...command] = args;
@@ -7049,7 +7376,7 @@ async function runSandboxed(args, {
7049
7376
  }
7050
7377
  });
7051
7378
  const wrapped = await SandboxManager.wrapWithSandbox(shellQuote(command));
7052
- const child = spawn8(wrapped, { shell: true, stdio: "inherit" });
7379
+ const child = spawn9(wrapped, { shell: true, stdio: "inherit" });
7053
7380
  const forward = (signal2) => () => child.kill(signal2);
7054
7381
  process.once("SIGTERM", forward("SIGTERM"));
7055
7382
  process.once("SIGINT", forward("SIGINT"));
@@ -7190,13 +7517,13 @@ var AGENT_STATE = [
7190
7517
  ".config/gh"
7191
7518
  ];
7192
7519
  function agentAccess(input) {
7193
- const home = (path) => join17(input.ownerHome, path);
7520
+ const home = (path) => join18(input.ownerHome, path);
7194
7521
  return {
7195
7522
  read: [
7196
7523
  ...SYSTEM_READ,
7197
7524
  input.nodePrefix,
7198
7525
  ...HOME_READ.map(home),
7199
- ...HERD_READ.map((path) => join17(input.herdHome, path))
7526
+ ...HERD_READ.map((path) => join18(input.herdHome, path))
7200
7527
  ],
7201
7528
  write: [
7202
7529
  .../* @__PURE__ */ new Set([
@@ -7214,7 +7541,7 @@ function seatbeltPolicy(input) {
7214
7541
  const access = agentAccess(input);
7215
7542
  return {
7216
7543
  network: {
7217
- allowUnixSockets: [join17(input.herdHome, "daemon.sock"), input.tmp, input.folder],
7544
+ allowUnixSockets: [join18(input.herdHome, "daemon.sock"), input.tmp, input.folder],
7218
7545
  allowLocalBinding: true
7219
7546
  },
7220
7547
  filesystem: {
@@ -7229,7 +7556,7 @@ function seatbeltPolicy(input) {
7229
7556
  function worktreeGitDirs(folder) {
7230
7557
  const candidates2 = [folder];
7231
7558
  try {
7232
- for (const name of readdirSync5(folder)) candidates2.push(join17(folder, name));
7559
+ for (const name of readdirSync6(folder)) candidates2.push(join18(folder, name));
7233
7560
  } catch {
7234
7561
  return [];
7235
7562
  }
@@ -7241,13 +7568,13 @@ function worktreeGitDirs(folder) {
7241
7568
  return [...dirs];
7242
7569
  }
7243
7570
  function commonGitDir(worktree) {
7244
- const dotGit = join17(worktree, ".git");
7571
+ const dotGit = join18(worktree, ".git");
7245
7572
  try {
7246
7573
  if (!statSync7(dotGit).isFile()) return null;
7247
7574
  const line2 = /^gitdir:\s*(.+)$/m.exec(readFileSync12(dotGit, "utf8"))?.[1]?.trim();
7248
7575
  if (!line2) return null;
7249
7576
  const gitDir = isAbsolute3(line2) ? line2 : resolve4(worktree, line2);
7250
- const common = join17(gitDir, "commondir");
7577
+ const common = join18(gitDir, "commondir");
7251
7578
  if (!existsSync13(common)) return gitDir;
7252
7579
  const relative3 = readFileSync12(common, "utf8").trim();
7253
7580
  return isAbsolute3(relative3) ? relative3 : resolve4(gitDir, relative3);
@@ -7255,10 +7582,10 @@ function commonGitDir(worktree) {
7255
7582
  return null;
7256
7583
  }
7257
7584
  }
7258
- var policyPathFor = (conversationId) => join17(herdHome(), "sandbox", `${conversationId}.json`);
7585
+ var policyPathFor = (conversationId) => join18(herdHome(), "sandbox", `${conversationId}.json`);
7259
7586
  function writeAgentPolicy(conversationId, paths, platform2 = process.platform) {
7260
7587
  const path = policyPathFor(conversationId);
7261
- mkdirSync12(dirname12(path), { recursive: true, mode: 448 });
7588
+ mkdirSync13(dirname12(path), { recursive: true, mode: 448 });
7262
7589
  const input = {
7263
7590
  ownerHome: homedir6(),
7264
7591
  herdHome: herdHome(),
@@ -7273,7 +7600,7 @@ function writeAgentPolicy(conversationId, paths, platform2 = process.platform) {
7273
7600
  }
7274
7601
  var confinementOn = (env = process.env) => env.HERD_CONFINE === "on";
7275
7602
  function runLandlocked(access, command) {
7276
- const child = spawn9(
7603
+ const child = spawn10(
7277
7604
  "python3",
7278
7605
  ["-I", "-c", LANDLOCK_LAUNCHER, JSON.stringify(access), ...command],
7279
7606
  { stdio: "inherit" }
@@ -7367,11 +7694,11 @@ init_define_HERD_PACKAGE();
7367
7694
  // src/investigator.ts
7368
7695
  init_define_HERD_PACKAGE();
7369
7696
  init_src();
7370
- import { z as z23 } from "zod";
7697
+ import { z as z24 } from "zod";
7371
7698
  var INVESTIGATE_KEYS = ["escape", "up", "down", "enter", "tab"];
7372
- var proposalSchema = z23.object({
7373
- key: z23.enum([...INVESTIGATE_KEYS, "none"]),
7374
- reason: z23.string().trim().min(1).max(300)
7699
+ var proposalSchema = z24.object({
7700
+ key: z24.enum([...INVESTIGATE_KEYS, "none"]),
7701
+ reason: z24.string().trim().min(1).max(300)
7375
7702
  });
7376
7703
  var KEY_LABELS = {
7377
7704
  escape: "Esc",
@@ -7779,16 +8106,16 @@ init_define_HERD_PACKAGE();
7779
8106
  // src/build-runner-key.ts
7780
8107
  init_define_HERD_PACKAGE();
7781
8108
  init_src();
7782
- import { chmodSync as chmodSync3, existsSync as existsSync14, mkdirSync as mkdirSync13, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
7783
- import { join as join18 } from "node:path";
7784
- import { z as z24 } from "zod";
8109
+ import { chmodSync as chmodSync3, existsSync as existsSync14, mkdirSync as mkdirSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
8110
+ import { join as join19 } from "node:path";
8111
+ import { z as z25 } from "zod";
7785
8112
  var BUILDS_URL = "https://builds.herdingchats.com";
7786
- var runnerKeySchema = z24.object({
7787
- runnerId: z24.string().min(1),
7788
- key: z24.string().min(20),
7789
- url: z24.string().url()
8113
+ var runnerKeySchema = z25.object({
8114
+ runnerId: z25.string().min(1),
8115
+ key: z25.string().min(20),
8116
+ url: z25.string().url()
7790
8117
  });
7791
- var runnerKeyPath = () => join18(herdHome(), "build-runner.json");
8118
+ var runnerKeyPath = () => join19(herdHome(), "build-runner.json");
7792
8119
  function readRunnerKey() {
7793
8120
  const path = runnerKeyPath();
7794
8121
  if (!existsSync14(path)) return null;
@@ -7801,7 +8128,7 @@ function readRunnerKey() {
7801
8128
  }
7802
8129
  }
7803
8130
  function saveRunnerKey(key) {
7804
- mkdirSync13(herdHome(), { recursive: true, mode: 448 });
8131
+ mkdirSync14(herdHome(), { recursive: true, mode: 448 });
7805
8132
  const path = runnerKeyPath();
7806
8133
  writeFileSync10(path, JSON.stringify(runnerKeySchema.parse(key), null, 2), { mode: 384 });
7807
8134
  chmodSync3(path, 384);
@@ -7855,33 +8182,33 @@ async function registerRunner({
7855
8182
 
7856
8183
  // src/build-speed.ts
7857
8184
  init_define_HERD_PACKAGE();
7858
- import { z as z25 } from "zod";
7859
- var ms = z25.number().int().nullable();
7860
- var figuresSchema = z25.object({ count: z25.number().int(), median: ms, p90: ms });
7861
- var buildTimesSchema = z25.object({ run: figuresSchema, push: figuresSchema });
7862
- var stepFiguresSchema = z25.object({
7863
- ran: z25.number().int(),
7864
- remembered: z25.number().int(),
8185
+ import { z as z26 } from "zod";
8186
+ var ms = z26.number().int().nullable();
8187
+ var figuresSchema = z26.object({ count: z26.number().int(), median: ms, p90: ms });
8188
+ var buildTimesSchema = z26.object({ run: figuresSchema, push: figuresSchema });
8189
+ var stepFiguresSchema = z26.object({
8190
+ ran: z26.number().int(),
8191
+ remembered: z26.number().int(),
7865
8192
  median: ms,
7866
8193
  p90: ms,
7867
- overBudget: z25.number().int()
8194
+ overBudget: z26.number().int()
7868
8195
  });
7869
- var kindSchema = z25.object({
7870
- builds: z25.object({ current: buildTimesSchema, previous: buildTimesSchema }),
7871
- steps: z25.array(
7872
- z25.object({ label: z25.string(), current: stepFiguresSchema, previous: stepFiguresSchema })
8196
+ var kindSchema = z26.object({
8197
+ builds: z26.object({ current: buildTimesSchema, previous: buildTimesSchema }),
8198
+ steps: z26.array(
8199
+ z26.object({ label: z26.string(), current: stepFiguresSchema, previous: stepFiguresSchema })
7873
8200
  ),
7874
- overBudget: z25.array(
7875
- z25.object({ label: z25.string(), current: z25.number().int(), previous: z25.number().int() })
8201
+ overBudget: z26.array(
8202
+ z26.object({ label: z26.string(), current: z26.number().int(), previous: z26.number().int() })
7876
8203
  )
7877
8204
  });
7878
- var range = z25.object({ from: z25.number(), to: z25.number() });
7879
- var speedReportSchema = z25.object({
7880
- periods: z25.object({ current: range, previous: range }),
7881
- repos: z25.array(
7882
- z25.object({
7883
- repo: z25.string(),
7884
- capped: z25.boolean(),
8205
+ var range = z26.object({ from: z26.number(), to: z26.number() });
8206
+ var speedReportSchema = z26.object({
8207
+ periods: z26.object({ current: range, previous: range }),
8208
+ repos: z26.array(
8209
+ z26.object({
8210
+ repo: z26.string(),
8211
+ capped: z26.boolean(),
7885
8212
  pullRequests: kindSchema,
7886
8213
  main: kindSchema
7887
8214
  })
@@ -7969,8 +8296,8 @@ function formatSpeed(report) {
7969
8296
 
7970
8297
  // src/builds-admin.ts
7971
8298
  init_define_HERD_PACKAGE();
7972
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync15, rmSync as rmSync10, writeFileSync as writeFileSync11 } from "node:fs";
7973
- import { join as join19 } from "node:path";
8299
+ import { mkdirSync as mkdirSync15, readFileSync as readFileSync15, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
8300
+ import { join as join20 } from "node:path";
7974
8301
  var NOT_ADMIN = "builds are only for the admin's machines";
7975
8302
  async function isAdmin(fetch2) {
7976
8303
  const response = await fetch2("/api/people/me").catch(() => null);
@@ -7978,13 +8305,13 @@ async function isAdmin(fetch2) {
7978
8305
  const body = await response.json().catch(() => ({}));
7979
8306
  return body.admin === true;
7980
8307
  }
7981
- var refusalPath = () => join19(herdHome(), "builds", "refused.json");
8308
+ var refusalPath = () => join20(herdHome(), "builds", "refused.json");
7982
8309
  function writeBuildsRefusal(reason) {
7983
8310
  if (!reason) {
7984
- rmSync10(refusalPath(), { force: true });
8311
+ rmSync11(refusalPath(), { force: true });
7985
8312
  return;
7986
8313
  }
7987
- mkdirSync14(join19(herdHome(), "builds"), { recursive: true });
8314
+ mkdirSync15(join20(herdHome(), "builds"), { recursive: true });
7988
8315
  writeFileSync11(refusalPath(), JSON.stringify({ reason }));
7989
8316
  }
7990
8317
  function readBuildsRefusal() {
@@ -8258,12 +8585,12 @@ function readClaudeScreen(raw) {
8258
8585
  // src/worktrees.ts
8259
8586
  init_define_HERD_PACKAGE();
8260
8587
  import { existsSync as existsSync15, realpathSync as realpathSync6 } from "node:fs";
8261
- import { isAbsolute as isAbsolute4, join as join20, relative } from "node:path";
8262
- var worktreesRoot = () => join20(herdHome(), "worktrees");
8588
+ import { isAbsolute as isAbsolute4, join as join21, relative } from "node:path";
8589
+ var worktreesRoot = () => join21(herdHome(), "worktrees");
8263
8590
  var WORD_ID = /^[a-z]+-[a-z]+-[a-z]+$/;
8264
8591
  var conversationPrefix = (conversationId) => WORD_ID.test(conversationId) ? conversationId : conversationId.replace(/[^A-Za-z0-9]/g, "").slice(0, 12).toLowerCase();
8265
8592
  var conversationBranch = (conversationId) => `herd/${conversationPrefix(conversationId)}`;
8266
- var conversationFolder = (convos, conversationId) => join20(convos, conversationPrefix(conversationId));
8593
+ var conversationFolder = (convos, conversationId) => join21(convos, conversationPrefix(conversationId));
8267
8594
  function folderForConversation(root, conversationId) {
8268
8595
  const earlier = conversationFolder(legacyConvosDir(root), conversationId);
8269
8596
  return existsSync15(earlier) ? earlier : conversationFolder(convosHome(root), conversationId);
@@ -8310,7 +8637,7 @@ function withStartKey(command, startKey, confine) {
8310
8637
  return startKey ? `HERD_START=${startKey} ${line2}` : line2;
8311
8638
  }
8312
8639
  var DEFAULT_TIMING = { settleMs: 800, pollMs: 150, startMs: 6e4, confirmMs: 3e3 };
8313
- var sleep3 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
8640
+ var sleep4 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
8314
8641
  var EXIT_WAIT_MS = 5e3;
8315
8642
  var AgentDriver = class {
8316
8643
  constructor(terminals, timing = {}) {
@@ -8338,7 +8665,7 @@ var AgentDriver = class {
8338
8665
  const reading = await this.read(name);
8339
8666
  if (check2(reading)) return reading;
8340
8667
  if (Date.now() > end) return null;
8341
- await sleep3(this.timing.pollMs);
8668
+ await sleep4(this.timing.pollMs);
8342
8669
  }
8343
8670
  }
8344
8671
  /** Waits for a terminal's output to stop changing, as a shell does once it's ready. */
@@ -8354,7 +8681,7 @@ var AgentDriver = class {
8354
8681
  } else if (Date.now() - stableSince >= this.timing.settleMs) {
8355
8682
  return;
8356
8683
  }
8357
- await sleep3(this.timing.pollMs);
8684
+ await sleep4(this.timing.pollMs);
8358
8685
  }
8359
8686
  }
8360
8687
  /**
@@ -8394,7 +8721,7 @@ var AgentDriver = class {
8394
8721
  for (let attempt = 0; attempt < 5; attempt++) {
8395
8722
  if ((await this.read(name)).trustPrompt?.yesHighlighted) break;
8396
8723
  await this.terminals.press(name, "down");
8397
- await sleep3(this.timing.pollMs * 2);
8724
+ await sleep4(this.timing.pollMs * 2);
8398
8725
  }
8399
8726
  if (!(await this.read(name)).trustPrompt?.yesHighlighted) {
8400
8727
  throw new HerdError(
@@ -8472,7 +8799,7 @@ var AgentDriver = class {
8472
8799
  }
8473
8800
  });
8474
8801
  });
8475
- const confirmed = () => Promise.race([hooked, sleep3(this.timing.confirmMs)]);
8802
+ const confirmed = () => Promise.race([hooked, sleep4(this.timing.confirmMs)]);
8476
8803
  try {
8477
8804
  if (!leftover) await this.terminals.type(name, text4);
8478
8805
  const head = sent.slice(0, 40);
@@ -8576,9 +8903,9 @@ function readAgyScreen(raw) {
8576
8903
 
8577
8904
  // src/agy-settings.ts
8578
8905
  init_define_HERD_PACKAGE();
8579
- import { lstatSync as lstatSync2, mkdirSync as mkdirSync15, realpathSync as realpathSync7, writeFileSync as writeFileSync12 } from "node:fs";
8580
- import { join as join21, sep } from "node:path";
8581
- var AGY_SETTINGS_FILE = join21(".herd", "agy-settings.json");
8906
+ import { lstatSync as lstatSync2, mkdirSync as mkdirSync16, realpathSync as realpathSync7, writeFileSync as writeFileSync12 } from "node:fs";
8907
+ import { join as join22, sep } from "node:path";
8908
+ var AGY_SETTINGS_FILE = join22(".herd", "agy-settings.json");
8582
8909
  var AGY_HOOK_EVENTS = ["SessionStart", "PreInvocation", "Stop"];
8583
8910
  var agyHookCommand = (event2, herd) => `${herd} hook agy ${event2} || true`;
8584
8911
  function agyHooks(herd) {
@@ -8595,11 +8922,11 @@ function agySettings(herd) {
8595
8922
  return { statusLine: { type: "command", command: `${herd} statusline agy` } };
8596
8923
  }
8597
8924
  function ownFolder(worktree, name) {
8598
- const folder = join21(worktree, name);
8925
+ const folder = join22(worktree, name);
8599
8926
  if (lstatSync2(folder, { throwIfNoEntry: false })?.isSymbolicLink()) {
8600
8927
  throw new Error(`${name} in the worktree is a link`);
8601
8928
  }
8602
- mkdirSync15(folder, { recursive: true });
8929
+ mkdirSync16(folder, { recursive: true });
8603
8930
  if (!realpathSync7(folder).startsWith(realpathSync7(worktree) + sep)) {
8604
8931
  throw new Error(`${name} leads outside the worktree`);
8605
8932
  }
@@ -8607,13 +8934,13 @@ function ownFolder(worktree, name) {
8607
8934
  }
8608
8935
  function writeAgyFiles(worktree, herd) {
8609
8936
  const agents = ownFolder(worktree, ".agents");
8610
- writeFileSync12(join21(agents, "hooks.json"), `${JSON.stringify(agyHooks(herd), null, 2)}
8937
+ writeFileSync12(join22(agents, "hooks.json"), `${JSON.stringify(agyHooks(herd), null, 2)}
8611
8938
  `);
8612
- writeFileSync12(join21(agents, ".gitignore"), "*\n");
8939
+ writeFileSync12(join22(agents, ".gitignore"), "*\n");
8613
8940
  const herdFolder = ownFolder(worktree, ".herd");
8614
- writeFileSync12(join21(herdFolder, ".gitignore"), "*\n");
8941
+ writeFileSync12(join22(herdFolder, ".gitignore"), "*\n");
8615
8942
  writeFileSync12(
8616
- join21(worktree, AGY_SETTINGS_FILE),
8943
+ join22(worktree, AGY_SETTINGS_FILE),
8617
8944
  `${JSON.stringify(agySettings(herd), null, 2)}
8618
8945
  `
8619
8946
  );
@@ -8622,7 +8949,7 @@ function writeAgyFiles(worktree, herd) {
8622
8949
  // src/agy-driver.ts
8623
8950
  var AGY_FLAGS = `--dangerously-skip-permissions --settings ${AGY_SETTINGS_FILE}`;
8624
8951
  var DEFAULT_TIMING2 = { settleMs: 800, pollMs: 150, startMs: 6e4, confirmMs: 3e3 };
8625
- var sleep4 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
8952
+ var sleep5 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
8626
8953
  var EXIT_WAIT_MS2 = 5e3;
8627
8954
  var AgyDriver = class {
8628
8955
  constructor(terminals, timing = {}) {
@@ -8645,7 +8972,7 @@ var AgyDriver = class {
8645
8972
  const reading = await this.read(name);
8646
8973
  if (check2(reading)) return reading;
8647
8974
  if (Date.now() > end) return null;
8648
- await sleep4(this.timing.pollMs);
8975
+ await sleep5(this.timing.pollMs);
8649
8976
  }
8650
8977
  }
8651
8978
  /** Waits for a terminal's output to stop changing, as a shell does once it's ready. */
@@ -8661,7 +8988,7 @@ var AgyDriver = class {
8661
8988
  } else if (Date.now() - stableSince >= this.timing.settleMs) {
8662
8989
  return;
8663
8990
  }
8664
- await sleep4(this.timing.pollMs);
8991
+ await sleep5(this.timing.pollMs);
8665
8992
  }
8666
8993
  }
8667
8994
  /**
@@ -8727,7 +9054,7 @@ var AgyDriver = class {
8727
9054
  }
8728
9055
  });
8729
9056
  });
8730
- const confirmed = () => Promise.race([reported, sleep4(this.timing.confirmMs)]);
9057
+ const confirmed = () => Promise.race([reported, sleep5(this.timing.confirmMs)]);
8731
9058
  const head = sent.slice(0, 40);
8732
9059
  const queuedOnScreen = async () => {
8733
9060
  const screen = await this.terminals.screen(name);
@@ -8774,12 +9101,12 @@ var AgyDriver = class {
8774
9101
  // src/bare-clones.ts
8775
9102
  init_define_HERD_PACKAGE();
8776
9103
  import { randomBytes as randomBytes5 } from "node:crypto";
8777
- import { existsSync as existsSync16, mkdirSync as mkdirSync16, readdirSync as readdirSync6, renameSync as renameSync5, rmSync as rmSync11, statSync as statSync8 } from "node:fs";
8778
- import { dirname as dirname13, join as join22 } from "node:path";
9104
+ import { existsSync as existsSync16, mkdirSync as mkdirSync17, readdirSync as readdirSync7, renameSync as renameSync5, rmSync as rmSync12, statSync as statSync8 } from "node:fs";
9105
+ import { dirname as dirname13, join as join23 } from "node:path";
8779
9106
  function bareClonePath(dir, remote) {
8780
9107
  const key = repoKey(remote);
8781
9108
  if (!key) throw new StoreError(`${remote} isn't a repo's URL.`);
8782
- return join22(dir, `${key}.git`);
9109
+ return join23(dir, `${key}.git`);
8783
9110
  }
8784
9111
  async function ensureBareClone(store, remote, env) {
8785
9112
  if (existsSync16(store)) {
@@ -8793,7 +9120,7 @@ async function ensureBareClone(store, remote, env) {
8793
9120
  return { store, created: false, fetchFailed };
8794
9121
  }
8795
9122
  const building = `${store}.tmp-${process.pid}-${randomBytes5(4).toString("hex")}`;
8796
- mkdirSync16(dirname13(store), { recursive: true });
9123
+ mkdirSync17(dirname13(store), { recursive: true });
8797
9124
  try {
8798
9125
  await git(env, dirname13(store), "init", "--quiet", "--bare", building);
8799
9126
  await git(env, building, "remote", "add", "origin", remote);
@@ -8808,7 +9135,7 @@ async function ensureBareClone(store, remote, env) {
8808
9135
  await git(env, building, "remote", "set-head", "origin", "--auto").catch(() => "");
8809
9136
  renameSync5(building, store);
8810
9137
  } catch (error) {
8811
- rmSync11(building, { recursive: true, force: true });
9138
+ rmSync12(building, { recursive: true, force: true });
8812
9139
  throw error instanceof StoreError ? error : new StoreError(gitError(error));
8813
9140
  }
8814
9141
  return { store, created: true, fetchFailed: null };
@@ -8818,12 +9145,12 @@ function bareClones(dir) {
8818
9145
  const walk = (at, depth) => {
8819
9146
  let entries;
8820
9147
  try {
8821
- entries = readdirSync6(at);
9148
+ entries = readdirSync7(at);
8822
9149
  } catch {
8823
9150
  return;
8824
9151
  }
8825
9152
  for (const name of entries.sort()) {
8826
- const path = join22(at, name);
9153
+ const path = join23(at, name);
8827
9154
  if (name.endsWith(".git")) {
8828
9155
  try {
8829
9156
  const remote = gitSync(path, "config", "--get", "remote.origin.url");
@@ -9001,28 +9328,28 @@ var BuildConnection = class {
9001
9328
 
9002
9329
  // src/builds.ts
9003
9330
  init_define_HERD_PACKAGE();
9004
- import { execFile as execFile2, execFileSync as execFileSync5, spawn as spawn10 } from "node:child_process";
9331
+ import { execFile as execFile2, execFileSync as execFileSync5, spawn as spawn11 } from "node:child_process";
9005
9332
  import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
9006
9333
  import {
9007
9334
  existsSync as existsSync17,
9008
- mkdirSync as mkdirSync18,
9335
+ mkdirSync as mkdirSync19,
9009
9336
  mkdtempSync,
9010
- readdirSync as readdirSync8,
9337
+ readdirSync as readdirSync9,
9011
9338
  readFileSync as readFileSync17,
9012
9339
  realpathSync as realpathSync8,
9013
- rmSync as rmSync13,
9340
+ rmSync as rmSync14,
9014
9341
  writeFileSync as writeFileSync14
9015
9342
  } from "node:fs";
9016
9343
  import { homedir as homedir8, tmpdir as tmpdir2 } from "node:os";
9017
- import { join as join24 } from "node:path";
9344
+ import { join as join25 } from "node:path";
9018
9345
  import { promisify as promisify2 } from "node:util";
9019
9346
 
9020
9347
  // src/check-registry.ts
9021
9348
  init_define_HERD_PACKAGE();
9022
- import { mkdirSync as mkdirSync17, readdirSync as readdirSync7, readFileSync as readFileSync16, rmSync as rmSync12, writeFileSync as writeFileSync13 } from "node:fs";
9349
+ import { mkdirSync as mkdirSync18, readdirSync as readdirSync8, readFileSync as readFileSync16, rmSync as rmSync13, writeFileSync as writeFileSync13 } from "node:fs";
9023
9350
  import { homedir as homedir7 } from "node:os";
9024
- import { join as join23 } from "node:path";
9025
- var checkRegistryDir = () => process.env.HERDINGCHATS_CHECK_RUNNING ?? join23(homedir7(), ".cache", "herdingchats", "check", "running");
9351
+ import { join as join24 } from "node:path";
9352
+ var checkRegistryDir = () => process.env.HERDINGCHATS_CHECK_RUNNING ?? join24(homedir7(), ".cache", "herdingchats", "check", "running");
9026
9353
  var RESERVED_MB = 6 * 1024;
9027
9354
  var MIN_MEMORY_MB = 1024;
9028
9355
  var alive3 = (pid) => {
@@ -9034,15 +9361,15 @@ var alive3 = (pid) => {
9034
9361
  }
9035
9362
  };
9036
9363
  function registerCheck(dir, pid, startedAt = Date.now()) {
9037
- mkdirSync17(dir, { recursive: true });
9038
- const file = join23(dir, `${pid}.json`);
9364
+ mkdirSync18(dir, { recursive: true });
9365
+ const file = join24(dir, `${pid}.json`);
9039
9366
  writeFileSync13(file, JSON.stringify({ pid, startedAt }));
9040
- return () => rmSync12(file, { force: true });
9367
+ return () => rmSync13(file, { force: true });
9041
9368
  }
9042
9369
  function runningChecks(dir, isAlive = alive3) {
9043
9370
  let names = [];
9044
9371
  try {
9045
- names = readdirSync7(dir).filter((name) => /^\d+\.json$/.test(name));
9372
+ names = readdirSync8(dir).filter((name) => /^\d+\.json$/.test(name));
9046
9373
  } catch {
9047
9374
  return 0;
9048
9375
  }
@@ -9051,12 +9378,12 @@ function runningChecks(dir, isAlive = alive3) {
9051
9378
  const pid = Number(name.slice(0, -".json".length));
9052
9379
  let entry = null;
9053
9380
  try {
9054
- entry = JSON.parse(readFileSync16(join23(dir, name), "utf8"));
9381
+ entry = JSON.parse(readFileSync16(join24(dir, name), "utf8"));
9055
9382
  } catch {
9056
9383
  continue;
9057
9384
  }
9058
9385
  if (entry?.pid === pid && isAlive(pid)) count += 1;
9059
- else rmSync12(join23(dir, name), { force: true });
9386
+ else rmSync13(join24(dir, name), { force: true });
9060
9387
  }
9061
9388
  return count;
9062
9389
  }
@@ -9064,9 +9391,9 @@ var memoryShareOf = (totalMb, running) => Math.max(MIN_MEMORY_MB, Math.floor((to
9064
9391
 
9065
9392
  // src/builds.ts
9066
9393
  init_self();
9067
- var sandboxStatusPath = () => join24(herdHome(), "builds", "sandbox.json");
9394
+ var sandboxStatusPath = () => join25(herdHome(), "builds", "sandbox.json");
9068
9395
  function writeSandboxStatus(status2, now = Date.now()) {
9069
- mkdirSync18(join24(herdHome(), "builds"), { recursive: true });
9396
+ mkdirSync19(join25(herdHome(), "builds"), { recursive: true });
9070
9397
  writeFileSync14(sandboxStatusPath(), JSON.stringify({ ...status2, at: now }));
9071
9398
  }
9072
9399
  function readSandboxStatus() {
@@ -9098,7 +9425,7 @@ var real3 = (path) => {
9098
9425
  }
9099
9426
  };
9100
9427
  function worktreeOf(dir, store) {
9101
- if (!existsSync17(join24(dir, ".git"))) return false;
9428
+ if (!existsSync17(join25(dir, ".git"))) return false;
9102
9429
  try {
9103
9430
  return real3(git2(dir, "rev-parse", "--path-format=absolute", "--git-common-dir")) === real3(store);
9104
9431
  } catch {
@@ -9110,11 +9437,11 @@ function cloneFolder(from, to) {
9110
9437
  execFileSync5("cp", [...args, from, to]);
9111
9438
  }
9112
9439
  function moduleFolders(dir) {
9113
- const found = existsSync17(join24(dir, "node_modules")) ? ["node_modules"] : [];
9114
- for (const entry of readdirSync8(dir, { withFileTypes: true })) {
9440
+ const found = existsSync17(join25(dir, "node_modules")) ? ["node_modules"] : [];
9441
+ for (const entry of readdirSync9(dir, { withFileTypes: true })) {
9115
9442
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
9116
- if (existsSync17(join24(dir, entry.name, "node_modules")))
9117
- found.push(join24(entry.name, "node_modules"));
9443
+ if (existsSync17(join25(dir, entry.name, "node_modules")))
9444
+ found.push(join25(entry.name, "node_modules"));
9118
9445
  }
9119
9446
  }
9120
9447
  return found;
@@ -9122,11 +9449,11 @@ function moduleFolders(dir) {
9122
9449
  function checkoutManager(dir) {
9123
9450
  let field;
9124
9451
  try {
9125
- const pkg = JSON.parse(readFileSync17(join24(dir, "package.json"), "utf8"));
9452
+ const pkg = JSON.parse(readFileSync17(join25(dir, "package.json"), "utf8"));
9126
9453
  if (typeof pkg.packageManager === "string") field = pkg.packageManager;
9127
9454
  } catch {
9128
9455
  }
9129
- const found = managerOfCheckout(field, (name) => existsSync17(join24(dir, name)));
9456
+ const found = managerOfCheckout(field, (name) => existsSync17(join25(dir, name)));
9130
9457
  if (!found?.lockfile)
9131
9458
  throw new Error(
9132
9459
  `${dir} has no lockfile to install node_modules from: herd looked for ${Object.values(LOCKFILES).flat().join(", ")}.`
@@ -9134,7 +9461,7 @@ function checkoutManager(dir) {
9134
9461
  return { ...found, lockfile: found.lockfile };
9135
9462
  }
9136
9463
  function lockHash(dir) {
9137
- const lock = readFileSync17(join24(dir, checkoutManager(dir).lockfile));
9464
+ const lock = readFileSync17(join25(dir, checkoutManager(dir).lockfile));
9138
9465
  const major = process.versions.node.split(".")[0];
9139
9466
  return createHash3("sha256").update(lock).update(`${process.platform}-${process.arch}-${major}`).digest("hex").slice(0, 32);
9140
9467
  }
@@ -9146,7 +9473,7 @@ var frozenInstall = (gitEnv) => async (dir) => {
9146
9473
  throw new Error(`${dir} installs with ${name}, which isn't on this machine, or doesn't run.`);
9147
9474
  const args = frozenInstallArgs(name, { offline: insideBuildSandbox(), yarnMajor: major });
9148
9475
  await new Promise((resolve8, reject) => {
9149
- const child = spawn10(manager.command, args, {
9476
+ const child = spawn11(manager.command, args, {
9150
9477
  cwd: dir,
9151
9478
  env: { ...env, COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
9152
9479
  stdio: "ignore"
@@ -9165,11 +9492,11 @@ var BuildRunner = class _BuildRunner {
9165
9492
  options;
9166
9493
  running = /* @__PURE__ */ new Map();
9167
9494
  home() {
9168
- return this.options.home ?? join24(herdHome(), "builds");
9495
+ return this.options.home ?? join25(herdHome(), "builds");
9169
9496
  }
9170
9497
  /** Where builds' own bare clones are, apart from the root's checkouts. */
9171
9498
  clonesDir() {
9172
- return join24(this.home(), "clones");
9499
+ return join25(this.home(), "clones");
9173
9500
  }
9174
9501
  /** Herd's bare clone of a build's repo, for builds, whether or not it's there yet. */
9175
9502
  storeFor(build) {
@@ -9190,8 +9517,8 @@ var BuildRunner = class _BuildRunner {
9190
9517
  const store = bareClonePath(this.clonesDir(), remote);
9191
9518
  const env = this.options.gitEnv?.();
9192
9519
  const name = (repoKey(build.repo) ?? build.repo).replace(/[^A-Za-z0-9._-]+/g, "_");
9193
- const dir = join24(this.home(), name, String(slot));
9194
- mkdirSync18(join24(this.home(), name), { recursive: true });
9520
+ const dir = join25(this.home(), name, String(slot));
9521
+ mkdirSync19(join25(this.home(), name), { recursive: true });
9195
9522
  await withStoreLock(
9196
9523
  store,
9197
9524
  async () => {
@@ -9205,7 +9532,7 @@ var BuildRunner = class _BuildRunner {
9205
9532
  await storeGit(env, store, "fetch", "--quiet", "origin", build.sha);
9206
9533
  }
9207
9534
  if (!worktreeOf(dir, store)) {
9208
- rmSync13(dir, { recursive: true, force: true });
9535
+ rmSync14(dir, { recursive: true, force: true });
9209
9536
  await storeGit(env, store, "worktree", "prune");
9210
9537
  await storeGit(
9211
9538
  env,
@@ -9230,30 +9557,30 @@ var BuildRunner = class _BuildRunner {
9230
9557
  /** Makes `node_modules` match the checkout's lockfile: kept, copied from the cache, or installed. */
9231
9558
  async ensureModules(dir) {
9232
9559
  const wanted = lockHash(dir);
9233
- const marker = join24(dir, "node_modules", ".herd-lock");
9560
+ const marker = join25(dir, "node_modules", ".herd-lock");
9234
9561
  if (existsSync17(marker) && readFileSync17(marker, "utf8") === wanted) return;
9235
- const cache = join24(this.home(), "node_modules", wanted);
9562
+ const cache = join25(this.home(), "node_modules", wanted);
9236
9563
  for (const folder of moduleFolders(dir))
9237
- rmSync13(join24(dir, folder), { recursive: true, force: true });
9238
- if (existsSync17(join24(cache, ".complete"))) {
9239
- for (const folder of readFileSync17(join24(cache, ".complete"), "utf8").split("\n").filter(Boolean)) {
9240
- cloneFolder(join24(cache, folder), join24(dir, folder));
9564
+ rmSync14(join25(dir, folder), { recursive: true, force: true });
9565
+ if (existsSync17(join25(cache, ".complete"))) {
9566
+ for (const folder of readFileSync17(join25(cache, ".complete"), "utf8").split("\n").filter(Boolean)) {
9567
+ cloneFolder(join25(cache, folder), join25(dir, folder));
9241
9568
  }
9242
9569
  } else {
9243
9570
  this.options.log?.(`Installing node_modules for lockfile ${wanted.slice(0, 8)}.`);
9244
9571
  await (this.options.install ?? frozenInstall(this.options.gitEnv))(dir);
9245
- const staging = mkdtempSync(join24(this.home(), "node_modules-"));
9572
+ const staging = mkdtempSync(join25(this.home(), "node_modules-"));
9246
9573
  const folders = moduleFolders(dir);
9247
9574
  for (const folder of folders) {
9248
- mkdirSync18(join24(staging, folder, ".."), { recursive: true });
9249
- cloneFolder(join24(dir, folder), join24(staging, folder));
9575
+ mkdirSync19(join25(staging, folder, ".."), { recursive: true });
9576
+ cloneFolder(join25(dir, folder), join25(staging, folder));
9250
9577
  }
9251
- writeFileSync14(join24(staging, ".complete"), folders.join("\n"));
9252
- mkdirSync18(join24(this.home(), "node_modules"), { recursive: true });
9253
- rmSync13(cache, { recursive: true, force: true });
9578
+ writeFileSync14(join25(staging, ".complete"), folders.join("\n"));
9579
+ mkdirSync19(join25(this.home(), "node_modules"), { recursive: true });
9580
+ rmSync14(cache, { recursive: true, force: true });
9254
9581
  execFileSync5("mv", [staging, cache]);
9255
9582
  }
9256
- mkdirSync18(join24(dir, "node_modules"), { recursive: true });
9583
+ mkdirSync19(join25(dir, "node_modules"), { recursive: true });
9257
9584
  writeFileSync14(marker, wanted);
9258
9585
  }
9259
9586
  /**
@@ -9264,7 +9591,7 @@ var BuildRunner = class _BuildRunner {
9264
9591
  const env = {
9265
9592
  PATH: process.env.PATH ?? "/usr/bin:/bin",
9266
9593
  HOME: home,
9267
- TMPDIR: join24(home, "tmp"),
9594
+ TMPDIR: join25(home, "tmp"),
9268
9595
  LANG: process.env.LANG ?? "en_US.UTF-8",
9269
9596
  HERD_BUILD_ID: build.id,
9270
9597
  HERD_BUILD_SECRET: build.secret,
@@ -9276,7 +9603,7 @@ var BuildRunner = class _BuildRunner {
9276
9603
  // The sandbox's proxy is the only way out, and Node's own requests use it only when asked.
9277
9604
  NODE_USE_ENV_PROXY: "1",
9278
9605
  // Where `srt` points TMPDIR inside the sandbox: the build's own temporary folder.
9279
- CLAUDE_CODE_TMPDIR: join24(home, "tmp")
9606
+ CLAUDE_CODE_TMPDIR: join25(home, "tmp")
9280
9607
  };
9281
9608
  return env;
9282
9609
  }
@@ -9287,8 +9614,8 @@ var BuildRunner = class _BuildRunner {
9287
9614
  /** Runs `scripts/build.mjs` from the build's own commit, in a process group of its own. */
9288
9615
  async run(build, slot) {
9289
9616
  const dir = await this.prepare(build, slot);
9290
- const home = mkdtempSync(join24(this.options.scratch ?? scratchRoot(), "hb-"));
9291
- mkdirSync18(join24(home, "tmp"), { recursive: true });
9617
+ const home = mkdtempSync(join25(this.options.scratch ?? scratchRoot(), "hb-"));
9618
+ mkdirSync19(join25(home, "tmp"), { recursive: true });
9292
9619
  const run4 = randomUUID4();
9293
9620
  const env = {
9294
9621
  ..._BuildRunner.environment(build, home),
@@ -9298,7 +9625,7 @@ var BuildRunner = class _BuildRunner {
9298
9625
  env.HERD_CHECK_CPUS = String(Math.max(1, Math.floor(this.options.cpus())));
9299
9626
  if (this.options.memoryMb)
9300
9627
  env.HERD_CHECK_MEMORY_MB = String(Math.max(1, Math.floor(this.options.memoryMb())));
9301
- let command = [process.execPath, join24(dir, "scripts", "build.mjs")];
9628
+ let command = [process.execPath, join25(dir, "scripts", "build.mjs")];
9302
9629
  let policyPath = null;
9303
9630
  if (this.sandboxed()) {
9304
9631
  const policy = buildPolicy({
@@ -9313,8 +9640,8 @@ var BuildRunner = class _BuildRunner {
9313
9640
  main: build.main,
9314
9641
  buildsUrl: build.url
9315
9642
  });
9316
- mkdirSync18(join24(this.home(), "policies"), { recursive: true });
9317
- policyPath = join24(this.home(), "policies", `${build.id}.json`);
9643
+ mkdirSync19(join25(this.home(), "policies"), { recursive: true });
9644
+ policyPath = join25(this.home(), "policies", `${build.id}.json`);
9318
9645
  writeFileSync14(policyPath, JSON.stringify(policy), { mode: 384 });
9319
9646
  env.HERD_BUILD_SANDBOX = describePolicy(policy);
9320
9647
  command = [process.execPath, HERD_BIN, "build-sandbox", policyPath, "--", ...command];
@@ -9322,7 +9649,7 @@ var BuildRunner = class _BuildRunner {
9322
9649
  env.HERD_BUILD_SANDBOX = insideBuildSandbox() ? "Inside the sandbox of the build that started this one." : "";
9323
9650
  }
9324
9651
  const [executable, ...args] = command;
9325
- const child = spawn10(executable, args, { cwd: dir, env, stdio: "ignore", detached: true });
9652
+ const child = spawn11(executable, args, { cwd: dir, env, stdio: "ignore", detached: true });
9326
9653
  const done = new Promise((resolve8) => {
9327
9654
  child.on("error", () => resolve8(1));
9328
9655
  child.on("close", (code) => resolve8(code ?? 1));
@@ -9342,8 +9669,8 @@ var BuildRunner = class _BuildRunner {
9342
9669
  unregister();
9343
9670
  await this.stopBuildProcesses(run4, dir, entry.pid);
9344
9671
  if (this.running.get(build.id) === entry) this.running.delete(build.id);
9345
- rmSync13(home, { recursive: true, force: true });
9346
- if (policyPath) rmSync13(policyPath, { force: true });
9672
+ rmSync14(home, { recursive: true, force: true });
9673
+ if (policyPath) rmSync14(policyPath, { force: true });
9347
9674
  markEnded();
9348
9675
  }
9349
9676
  }
@@ -9363,7 +9690,7 @@ var BuildRunner = class _BuildRunner {
9363
9690
  * It goes only by where they run from, never by a build ID, since the daemon a build's own tests
9364
9691
  * start must never stop the build it runs in.
9365
9692
  */
9366
- static async sweep(home = join24(herdHome(), "builds"), list = listProcesses) {
9693
+ static async sweep(home = join25(herdHome(), "builds"), list = listProcesses) {
9367
9694
  const left = buildProcesses(list(), null, home);
9368
9695
  await stopPids(left);
9369
9696
  return left.length;
@@ -9375,37 +9702,37 @@ var BuildRunner = class _BuildRunner {
9375
9702
  */
9376
9703
  static async probe(options = { buildsUrl: "" }) {
9377
9704
  if (insideBuildSandbox()) return { ok: true };
9378
- const herd = options.home ?? join24(herdHome(), "builds");
9379
- const dir = mkdtempSync(join24(scratchRoot(), "hp-"));
9380
- const secret = join24(dir, "owner", "secret");
9705
+ const herd = options.home ?? join25(herdHome(), "builds");
9706
+ const dir = mkdtempSync(join25(scratchRoot(), "hp-"));
9707
+ const secret = join25(dir, "owner", "secret");
9381
9708
  try {
9382
- mkdirSync18(herd, { recursive: true });
9383
- for (const sub of ["work", "git", "home", "owner"]) mkdirSync18(join24(dir, sub));
9709
+ mkdirSync19(herd, { recursive: true });
9710
+ for (const sub of ["work", "git", "home", "owner"]) mkdirSync19(join25(dir, sub));
9384
9711
  writeFileSync14(secret, "probe");
9385
9712
  const policy = buildPolicy({
9386
- ownerHome: join24(dir, "owner"),
9387
- root: join24(dir, "owner"),
9388
- convos: join24(dir, "owner"),
9389
- worktree: join24(dir, "work"),
9390
- store: join24(dir, "git"),
9391
- worktreeGitDir: join24(dir, "git"),
9392
- home: join24(dir, "home"),
9713
+ ownerHome: join25(dir, "owner"),
9714
+ root: join25(dir, "owner"),
9715
+ convos: join25(dir, "owner"),
9716
+ worktree: join25(dir, "work"),
9717
+ store: join25(dir, "git"),
9718
+ worktreeGitDir: join25(dir, "git"),
9719
+ home: join25(dir, "home"),
9393
9720
  nodePrefix: nodePrefix(),
9394
9721
  main: false,
9395
9722
  buildsUrl: options.buildsUrl || "https://builds.herdingchats.com"
9396
9723
  });
9397
- const policyPath = join24(herd, "probe-policy.json");
9724
+ const policyPath = join25(herd, "probe-policy.json");
9398
9725
  writeFileSync14(policyPath, JSON.stringify(policy), { mode: 384 });
9399
9726
  const script = [
9400
9727
  'const fs = require("node:fs");',
9401
9728
  `try { fs.readFileSync(${JSON.stringify(secret)}); process.exit(10); } catch {}`,
9402
- `fs.writeFileSync(${JSON.stringify(join24(dir, "home", "ok"))}, "ok");`
9729
+ `fs.writeFileSync(${JSON.stringify(join25(dir, "home", "ok"))}, "ok");`
9403
9730
  ].join(" ");
9404
- const child = spawn10(
9731
+ const child = spawn11(
9405
9732
  process.execPath,
9406
9733
  [HERD_BIN, "build-sandbox", policyPath, "--", process.execPath, "-e", script],
9407
9734
  {
9408
- env: { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: join24(dir, "home") },
9735
+ env: { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: join25(dir, "home") },
9409
9736
  stdio: ["ignore", "ignore", "pipe"]
9410
9737
  }
9411
9738
  );
@@ -9419,7 +9746,7 @@ var BuildRunner = class _BuildRunner {
9419
9746
  clearTimeout(timer);
9420
9747
  if (status2 === 10)
9421
9748
  return { ok: false, reason: "the sandbox let a build read the owner's files" };
9422
- if (status2 !== 0 || !existsSync17(join24(dir, "home", "ok"))) {
9749
+ if (status2 !== 0 || !existsSync17(join25(dir, "home", "ok"))) {
9423
9750
  const why = stderr.trim().split("\n").at(-1) || `the probe exited with ${status2}`;
9424
9751
  return { ok: false, reason: why.slice(0, 300) };
9425
9752
  }
@@ -9427,7 +9754,7 @@ var BuildRunner = class _BuildRunner {
9427
9754
  } catch (error) {
9428
9755
  return { ok: false, reason: String(error).slice(0, 300) };
9429
9756
  } finally {
9430
- rmSync13(dir, { recursive: true, force: true });
9757
+ rmSync14(dir, { recursive: true, force: true });
9431
9758
  }
9432
9759
  }
9433
9760
  /** Stops a build's process group and every process it started, however it got away. */
@@ -9456,7 +9783,7 @@ function listProcesses() {
9456
9783
  function listFromProc() {
9457
9784
  let pids = [];
9458
9785
  try {
9459
- pids = readdirSync8("/proc").filter((name) => /^\d+$/.test(name));
9786
+ pids = readdirSync9("/proc").filter((name) => /^\d+$/.test(name));
9460
9787
  } catch {
9461
9788
  return [];
9462
9789
  }
@@ -9634,7 +9961,7 @@ var DEFAULT_TIMING3 = {
9634
9961
  confirmMs: 8e3,
9635
9962
  pasteMs: 600
9636
9963
  };
9637
- var sleep5 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
9964
+ var sleep6 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
9638
9965
  var EXIT_WAIT_MS3 = 5e3;
9639
9966
  var CodexDriver = class {
9640
9967
  constructor(terminals, timing = {}) {
@@ -9657,7 +9984,7 @@ var CodexDriver = class {
9657
9984
  const reading = await this.read(name);
9658
9985
  if (check2(reading)) return reading;
9659
9986
  if (Date.now() > end) return null;
9660
- await sleep5(this.timing.pollMs);
9987
+ await sleep6(this.timing.pollMs);
9661
9988
  }
9662
9989
  }
9663
9990
  /** Waits for a terminal's output to stop changing, as a shell does once it's ready. */
@@ -9673,7 +10000,7 @@ var CodexDriver = class {
9673
10000
  } else if (Date.now() - stableSince >= this.timing.settleMs) {
9674
10001
  return;
9675
10002
  }
9676
- await sleep5(this.timing.pollMs);
10003
+ await sleep6(this.timing.pollMs);
9677
10004
  }
9678
10005
  }
9679
10006
  /**
@@ -9739,10 +10066,10 @@ var CodexDriver = class {
9739
10066
  }
9740
10067
  });
9741
10068
  });
9742
- const confirmed = () => Promise.race([reported, sleep5(this.timing.confirmMs)]);
10069
+ const confirmed = () => Promise.race([reported, sleep6(this.timing.confirmMs)]);
9743
10070
  try {
9744
10071
  if (!leftover) await this.terminals.type(name, text4);
9745
- await sleep5(this.timing.pasteMs);
10072
+ await sleep6(this.timing.pasteMs);
9746
10073
  const head = sent.slice(0, 40);
9747
10074
  let inBox = true;
9748
10075
  for (let attempt = 0; attempt <= SUBMIT_RETRIES && !taken; attempt++) {
@@ -9772,7 +10099,7 @@ var CodexDriver = class {
9772
10099
  async exit(name) {
9773
10100
  if ((await this.read(name)).program !== "codex") return;
9774
10101
  await this.terminals.type(name, "/exit");
9775
- await sleep5(this.timing.pasteMs);
10102
+ await sleep6(this.timing.pasteMs);
9776
10103
  await this.terminals.press(name, "enter");
9777
10104
  await this.until(name, (reading) => reading.program !== "codex", EXIT_WAIT_MS3);
9778
10105
  }
@@ -9780,7 +10107,7 @@ var CodexDriver = class {
9780
10107
 
9781
10108
  // src/daemon.ts
9782
10109
  init_define_HERD_PACKAGE();
9783
- import { join as join25 } from "node:path";
10110
+ import { join as join26 } from "node:path";
9784
10111
 
9785
10112
  // src/dispatch-connection.ts
9786
10113
  init_define_HERD_PACKAGE();
@@ -9895,7 +10222,7 @@ var DispatchConnection = class {
9895
10222
 
9896
10223
  // src/daemon.ts
9897
10224
  var CONFIG_POLL_MS = 2e3;
9898
- var daemonSocketPath = () => join25(herdHome(), "daemon.sock");
10225
+ var daemonSocketPath = () => join26(herdHome(), "daemon.sock");
9899
10226
  var Daemon = class {
9900
10227
  constructor(options) {
9901
10228
  this.options = options;
@@ -10108,7 +10435,7 @@ var Daemon = class {
10108
10435
 
10109
10436
  // src/folders.ts
10110
10437
  init_define_HERD_PACKAGE();
10111
- import { existsSync as existsSync18, lstatSync as lstatSync3, readlinkSync as readlinkSync2, rmSync as rmSync14, writeFileSync as writeFileSync15 } from "node:fs";
10438
+ import { existsSync as existsSync18, lstatSync as lstatSync3, readlinkSync as readlinkSync2, rmSync as rmSync15, writeFileSync as writeFileSync15 } from "node:fs";
10112
10439
  import { dirname as dirname14, resolve as resolve5 } from "node:path";
10113
10440
  var FOLDER_WAIT_MS = 2 * 60 * 1e3;
10114
10441
  var FOLDER_CHECK_MS = 30 * 1e3;
@@ -10191,7 +10518,7 @@ var FolderWatch = class {
10191
10518
  write() {
10192
10519
  try {
10193
10520
  if (this.current) writeFileSync15(this.options.statusPath, JSON.stringify(this.current));
10194
- else rmSync14(this.options.statusPath, { force: true });
10521
+ else rmSync15(this.options.statusPath, { force: true });
10195
10522
  } catch {
10196
10523
  }
10197
10524
  }
@@ -10200,7 +10527,7 @@ var FolderWatch = class {
10200
10527
  // src/gh.ts
10201
10528
  init_define_HERD_PACKAGE();
10202
10529
  init_src();
10203
- import { execFile as execFile4, spawn as spawn11 } from "node:child_process";
10530
+ import { execFile as execFile4, spawn as spawn12 } from "node:child_process";
10204
10531
  import { promisify as promisify3 } from "node:util";
10205
10532
  var GH_CHECK_MS = 10 * 60 * 1e3;
10206
10533
  var GH_CODE_WAIT_MS = 30 * 1e3;
@@ -10271,7 +10598,7 @@ function readDeviceCode(output2) {
10271
10598
  return code && url === GITHUB_DEVICE_URL ? { code, url: GITHUB_DEVICE_URL } : null;
10272
10599
  }
10273
10600
  async function signInGh(options) {
10274
- const spawnGh = options.spawnGh ?? ((args) => spawn11("gh", args, {
10601
+ const spawnGh = options.spawnGh ?? ((args) => spawn12("gh", args, {
10275
10602
  env: options.env?.() ?? process.env,
10276
10603
  stdio: ["ignore", "pipe", "pipe"]
10277
10604
  }));
@@ -10324,20 +10651,20 @@ import { execFileSync as execFileSync6 } from "node:child_process";
10324
10651
  import {
10325
10652
  appendFileSync as appendFileSync2,
10326
10653
  existsSync as existsSync19,
10327
- mkdirSync as mkdirSync19,
10654
+ mkdirSync as mkdirSync20,
10328
10655
  readFileSync as readFileSync18,
10329
10656
  renameSync as renameSync6,
10330
10657
  writeFileSync as writeFileSync16
10331
10658
  } from "node:fs";
10332
- import { isAbsolute as isAbsolute5, join as join26 } from "node:path";
10333
- var LOCAL_SETTINGS = join26(".claude", "settings.local.json");
10659
+ import { isAbsolute as isAbsolute5, join as join27 } from "node:path";
10660
+ var LOCAL_SETTINGS = join27(".claude", "settings.local.json");
10334
10661
  var git3 = (cwd, ...args) => execFileSync6("git", ["-C", cwd, ...args], {
10335
10662
  encoding: "utf8",
10336
10663
  stdio: ["ignore", "pipe", "pipe"]
10337
10664
  }).trim();
10338
10665
  var shellQuote2 = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
10339
10666
  function writeWorktreeSettings(worktree, settings, ownCommand) {
10340
- const path = join26(worktree, LOCAL_SETTINGS);
10667
+ const path = join27(worktree, LOCAL_SETTINGS);
10341
10668
  let current = {};
10342
10669
  if (existsSync19(path)) {
10343
10670
  try {
@@ -10361,7 +10688,7 @@ function writeWorktreeSettings(worktree, settings, ownCommand) {
10361
10688
  }
10362
10689
  next.hooks = hooks;
10363
10690
  }
10364
- mkdirSync19(join26(worktree, ".claude"), { recursive: true });
10691
+ mkdirSync20(join27(worktree, ".claude"), { recursive: true });
10365
10692
  const temporary = `${path}.herd-${process.pid}`;
10366
10693
  writeFileSync16(temporary, `${JSON.stringify(next, null, 2)}
10367
10694
  `);
@@ -10381,12 +10708,12 @@ function ignoreLocalSettings(worktree) {
10381
10708
  } catch {
10382
10709
  return;
10383
10710
  }
10384
- const info = join26(isAbsolute5(common) ? common : join26(worktree, common), "info");
10385
- const exclude = join26(info, "exclude");
10711
+ const info = join27(isAbsolute5(common) ? common : join27(worktree, common), "info");
10712
+ const exclude = join27(info, "exclude");
10386
10713
  const line2 = "/.claude/settings.local.json";
10387
10714
  const existing = existsSync19(exclude) ? readFileSync18(exclude, "utf8") : "";
10388
10715
  if (existing.split("\n").includes(line2)) return;
10389
- mkdirSync19(info, { recursive: true });
10716
+ mkdirSync20(info, { recursive: true });
10390
10717
  appendFileSync2(exclude, `${existing && !existing.endsWith("\n") ? "\n" : ""}${line2}
10391
10718
  `);
10392
10719
  }
@@ -10429,7 +10756,7 @@ function loginShellEnv() {
10429
10756
 
10430
10757
  // src/machine-checks.ts
10431
10758
  init_define_HERD_PACKAGE();
10432
- import { existsSync as existsSync20, rmSync as rmSync15 } from "node:fs";
10759
+ import { existsSync as existsSync20, rmSync as rmSync16 } from "node:fs";
10433
10760
  var REQUEST_POLL_MS = 2e3;
10434
10761
  var MachineChecks = class {
10435
10762
  constructor(options) {
@@ -10476,7 +10803,7 @@ var MachineChecks = class {
10476
10803
  takeRequest() {
10477
10804
  const path = activationRequestPath(this.options.herdHome);
10478
10805
  if (!existsSync20(path)) return;
10479
- rmSync15(path, { force: true });
10806
+ rmSync16(path, { force: true });
10480
10807
  this.activateChosen(true);
10481
10808
  }
10482
10809
  start() {
@@ -10522,237 +10849,6 @@ var MachineChecks = class {
10522
10849
  }
10523
10850
  };
10524
10851
 
10525
- // src/terminals.ts
10526
- init_define_HERD_PACKAGE();
10527
- import { spawn as spawn12 } from "node:child_process";
10528
- import { mkdirSync as mkdirSync20, readdirSync as readdirSync9, rmSync as rmSync16 } from "node:fs";
10529
- import { connect as connect2 } from "node:net";
10530
- import { join as join27 } from "node:path";
10531
- init_self();
10532
- import {
10533
- HOST_PROTOCOL,
10534
- hostVersion
10535
- } from "./terminal-host.mjs";
10536
- var hostsDir = () => join27(herdHome(), "terminals");
10537
- var legacyHostSocketPath = () => join27(herdHome(), "terminals.sock");
10538
- var currentHostSocketPath = (version = hostVersion()) => join27(hostsDir(), `${version}.sock`);
10539
- var speaksProtocol = (protocol) => protocol === HOST_PROTOCOL || protocol === HOST_PROTOCOL - 1;
10540
- var sleep6 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
10541
- function askHost(path, request) {
10542
- return new Promise((resolve8, reject) => {
10543
- const socket = connect2(path);
10544
- let data = "";
10545
- socket.on("connect", () => socket.write(`${JSON.stringify(request)}
10546
- `));
10547
- socket.on("data", (chunk) => data += chunk);
10548
- socket.on("end", () => {
10549
- try {
10550
- const reply = JSON.parse(data);
10551
- if (reply.ok) resolve8(reply);
10552
- else reject(new HerdError(reply.error ?? "The terminal host refused that."));
10553
- } catch {
10554
- reject(new HerdError("The terminal host's answer wasn't readable."));
10555
- }
10556
- });
10557
- socket.on("error", reject);
10558
- });
10559
- }
10560
- var isDead = (error) => {
10561
- const code = error?.code;
10562
- return code === "ECONNREFUSED" || code === "ENOENT";
10563
- };
10564
- var HostTerminals = class {
10565
- hosts = [];
10566
- /** Which host each terminal name was last found in. */
10567
- where = /* @__PURE__ */ new Map();
10568
- version;
10569
- log;
10570
- constructor(options = {}) {
10571
- this.version = options.version ?? hostVersion();
10572
- this.log = options.log ?? (() => {
10573
- });
10574
- }
10575
- currentVersion() {
10576
- return this.version;
10577
- }
10578
- unreachable(version) {
10579
- return this.hosts.some((host) => host.hello.version === version && !host.speaks);
10580
- }
10581
- /** Every host that answers, with sockets nothing answers on removed. */
10582
- async discover() {
10583
- const dir = hostsDir();
10584
- let names = [];
10585
- try {
10586
- names = readdirSync9(dir).filter((name) => name.endsWith(".sock"));
10587
- } catch {
10588
- }
10589
- const paths = [legacyHostSocketPath(), ...names.map((name) => join27(dir, name))];
10590
- const found = [];
10591
- for (const path of paths) {
10592
- try {
10593
- const hello = await askHost(path, { op: "hello" });
10594
- found.push({
10595
- path,
10596
- hello,
10597
- current: hello.version === this.version && speaksProtocol(hello.protocol),
10598
- speaks: speaksProtocol(hello.protocol)
10599
- });
10600
- } catch (error) {
10601
- if (isDead(error) && path !== legacyHostSocketPath()) rmSync16(path, { force: true });
10602
- }
10603
- }
10604
- this.hosts = found;
10605
- return found;
10606
- }
10607
- current() {
10608
- return this.hosts.find((host) => host.current);
10609
- }
10610
- /** Starts a host of the daemon's own version if none is running, detached so it outlives it. */
10611
- async ensureHost() {
10612
- const hosts = await this.discover();
10613
- for (const host of hosts) {
10614
- const version = host.hello.version ?? "unversioned";
10615
- const role = host.current ? "current" : host.speaks ? "older" : "not driveable";
10616
- this.log(
10617
- `Terminal host ${version} (protocol ${host.hello.protocol}, pid ${host.hello.pid}): ${role}.`
10618
- );
10619
- }
10620
- if (this.current()) return;
10621
- const path = currentHostSocketPath(this.version);
10622
- mkdirSync20(hostsDir(), { recursive: true, mode: 448 });
10623
- const child = spawn12(process.execPath, [herdBin, "terminal-host", "--socket", path], {
10624
- detached: true,
10625
- stdio: "ignore",
10626
- env: { ...process.env, HERD_HOME: herdHome() }
10627
- });
10628
- child.unref();
10629
- for (let attempt = 0; attempt < 50 && !this.current(); attempt++) {
10630
- await sleep6(100);
10631
- const hello = await askHost(path, { op: "hello" }).catch(() => null);
10632
- if (!hello) continue;
10633
- if (hello.version !== this.version) {
10634
- throw new HerdError(
10635
- `The terminal host started as version ${hello.version}, not ${this.version}, so its files changed as it started. Starting the daemon again picks this up.`
10636
- );
10637
- }
10638
- this.hosts.push({ path, hello, current: true, speaks: speaksProtocol(hello.protocol) });
10639
- }
10640
- if (!this.current()) throw new HerdError("The terminal host didn't start.");
10641
- this.log(`Started terminal host ${this.version}.`);
10642
- }
10643
- /**
10644
- * Every driveable host's terminals. A name in more than one host, as when a session moved, is
10645
- * the running one, or else the newest.
10646
- */
10647
- async list() {
10648
- if (this.hosts.length === 0) await this.discover();
10649
- const byName = /* @__PURE__ */ new Map();
10650
- for (const host of this.hosts.filter((candidate) => candidate.speaks)) {
10651
- const terminals = await askHost(host.path, { op: "list" }).then((reply) => reply.terminals).catch(() => []);
10652
- for (const info of terminals) {
10653
- const seen = byName.get(info.name);
10654
- const better = !seen || seen.info.exited && !info.exited || seen.info.exited === info.exited && info.createdAt > seen.info.createdAt;
10655
- if (better) byName.set(info.name, { info, path: host.path });
10656
- }
10657
- }
10658
- this.where = new Map([...byName].map(([name, { path }]) => [name, path]));
10659
- return [...byName.values()].map(({ info }) => info);
10660
- }
10661
- /** The socket of the host holding a terminal. */
10662
- async pathOf(name) {
10663
- let path = this.where.get(name);
10664
- if (!path) {
10665
- await this.list();
10666
- path = this.where.get(name);
10667
- }
10668
- if (!path) throw new HerdError(`There's no terminal named ${name}.`);
10669
- return path;
10670
- }
10671
- async askFor(name, request) {
10672
- return askHost(await this.pathOf(name), request);
10673
- }
10674
- async create(opts) {
10675
- const running = (await this.list()).find((t) => t.name === opts.name && !t.exited);
10676
- if (running) throw new HerdError(`A terminal named ${opts.name} is already running.`);
10677
- let host = this.current();
10678
- if (!host) {
10679
- await this.ensureHost();
10680
- host = this.current();
10681
- }
10682
- if (!host) throw new HerdError("The terminal host didn't start.");
10683
- const { terminal } = await askHost(host.path, {
10684
- op: "create",
10685
- ...opts
10686
- });
10687
- this.where.set(opts.name, host.path);
10688
- return terminal;
10689
- }
10690
- async screen(name) {
10691
- return (await this.askFor(name, { op: "screen", name })).screen;
10692
- }
10693
- async snapshot(name) {
10694
- return (await this.askFor(name, { op: "snapshot", name })).snapshot;
10695
- }
10696
- async type(name, text4) {
10697
- await this.askFor(name, { op: "type", name, text: text4 });
10698
- }
10699
- async press(name, key) {
10700
- await this.askFor(name, { op: "press", name, key });
10701
- }
10702
- async exitStatus(name) {
10703
- const terminal = (await this.list()).find((candidate) => candidate.name === name);
10704
- if (!terminal) throw new HerdError(`There's no terminal named ${name}.`);
10705
- return terminal.exited ? terminal.exitCode : null;
10706
- }
10707
- async kill(name) {
10708
- await this.askFor(name, { op: "kill", name });
10709
- }
10710
- async retire(canStop) {
10711
- const stopped2 = [];
10712
- for (const host of await this.discover()) {
10713
- if (host.current) continue;
10714
- const version = host.hello.version ?? "unversioned";
10715
- if (host.speaks) {
10716
- const terminals = await askHost(host.path, { op: "list" }).then((reply) => reply.terminals).catch(() => null);
10717
- if (!terminals) continue;
10718
- if (terminals.some((t) => !t.exited || (t.viewers ?? 0) > 0)) continue;
10719
- } else if (!host.hello.version || !canStop(host.hello.version)) {
10720
- continue;
10721
- }
10722
- if (await this.stop(host)) {
10723
- stopped2.push(version);
10724
- this.log(`Retired terminal host ${version}, which had nothing left running.`);
10725
- }
10726
- }
10727
- if (stopped2.length > 0) await this.discover();
10728
- return stopped2;
10729
- }
10730
- /** Stops a host: politely where it knows how, and otherwise by its process. */
10731
- async stop(host) {
10732
- if (host.speaks && host.hello.ops?.includes("shutdown")) {
10733
- return askHost(host.path, { op: "shutdown" }).then(
10734
- () => true,
10735
- () => false
10736
- );
10737
- }
10738
- const hello = await askHost(host.path, { op: "hello" }).catch(() => null);
10739
- if (!hello || hello.pid !== host.hello.pid) return false;
10740
- try {
10741
- process.kill(hello.pid, "SIGTERM");
10742
- } catch {
10743
- return false;
10744
- }
10745
- if (host.path === legacyHostSocketPath()) {
10746
- for (let attempt = 0; attempt < 20; attempt++) {
10747
- await sleep6(100);
10748
- if (!await askHost(host.path, { op: "hello" }).catch(() => null)) break;
10749
- }
10750
- rmSync16(host.path, { force: true });
10751
- }
10752
- return true;
10753
- }
10754
- };
10755
-
10756
10852
  // src/usage-reporter.ts
10757
10853
  init_define_HERD_PACKAGE();
10758
10854
  var USAGE_REPORT_MS = 5 * 60 * 1e3;
@@ -10838,7 +10934,7 @@ init_define_HERD_PACKAGE();
10838
10934
  init_src();
10839
10935
  init_self();
10840
10936
  import { join as join28 } from "node:path";
10841
- import { z as z26 } from "zod";
10937
+ import { z as z27 } from "zod";
10842
10938
  function hasThread(events) {
10843
10939
  return events.some(
10844
10940
  (event2) => event2.type === "prompt.opened" || event2.type === "message.created" && !(event2.payload.direction === "inbound" && event2.payload.automation) && !event2.payload.quiet && !event2.payload.post
@@ -10904,16 +11000,16 @@ var WAITING_NOTIFICATIONS = /* @__PURE__ */ new Set([
10904
11000
  "agent_needs_input",
10905
11001
  "elicitation_dialog"
10906
11002
  ]);
10907
- var agentMessageSchema = z26.object({
10908
- text: z26.string().trim().min(1).max(12e3),
10909
- files: z26.array(z26.object({ path: z26.string().min(1), title: z26.string().optional() })).max(10).optional()
11003
+ var agentMessageSchema = z27.object({
11004
+ text: z27.string().trim().min(1).max(12e3),
11005
+ files: z27.array(z27.object({ path: z27.string().min(1), title: z27.string().optional() })).max(10).optional()
10910
11006
  });
10911
- var workspaceRepoSchema = z26.object({
10912
- folder: z26.string(),
10913
- remote: z26.string(),
10914
- branch: z26.string(),
11007
+ var workspaceRepoSchema = z27.object({
11008
+ folder: z27.string(),
11009
+ remote: z27.string(),
11010
+ branch: z27.string(),
10915
11011
  /** For a conversation from before folders: the name of the repo it was allowed as. */
10916
- name: z26.string().optional()
11012
+ name: z27.string().optional()
10917
11013
  });
10918
11014
  function fromBeforeFolders(raw) {
10919
11015
  if (!raw || typeof raw !== "object" || "folder" in raw) return raw;
@@ -10934,106 +11030,106 @@ function fromBeforeFolders(raw) {
10934
11030
  legacy: true
10935
11031
  };
10936
11032
  }
10937
- var workspaceFields = z26.object({
10938
- conversationId: z26.string(),
11033
+ var workspaceFields = z27.object({
11034
+ conversationId: z27.string(),
10939
11035
  /** The conversation's own folder, where its terminals start. */
10940
- folder: z26.string(),
11036
+ folder: z27.string(),
10941
11037
  /** The repos in it, in the order they were added. */
10942
- worktrees: z26.array(workspaceRepoSchema).default([]),
11038
+ worktrees: z27.array(workspaceRepoSchema).default([]),
10943
11039
  /** A conversation from before folders, which works in its one repo's worktree. */
10944
- legacy: z26.boolean().default(false),
10945
- lead: z26.string(),
10946
- secret: z26.string(),
10947
- readSeq: z26.number(),
10948
- claimId: z26.string().nullable(),
10949
- claimRenewedAt: z26.number(),
10950
- started: z26.boolean(),
11040
+ legacy: z27.boolean().default(false),
11041
+ lead: z27.string(),
11042
+ secret: z27.string(),
11043
+ readSeq: z27.number(),
11044
+ claimId: z27.string().nullable(),
11045
+ claimRenewedAt: z27.number(),
11046
+ started: z27.boolean(),
10951
11047
  /** Messages to deliver once the current turn ends. */
10952
- deferred: z26.boolean(),
11048
+ deferred: z27.boolean(),
10953
11049
  /** A `herd run` command finished, which the agent hears about even with nothing to claim. */
10954
- terminalNotice: z26.boolean().default(false),
10955
- status: z26.string().nullable(),
11050
+ terminalNotice: z27.boolean().default(false),
11051
+ status: z27.string().nullable(),
10956
11052
  /** Set once the thread has been told the agent is waiting, so it's told once per wait. */
10957
- toldWaiting: z26.boolean(),
10958
- runs: z26.array(z26.object({ name: z26.string(), secret: z26.string(), reported: z26.boolean() })),
11053
+ toldWaiting: z27.boolean(),
11054
+ runs: z27.array(z27.object({ name: z27.string(), secret: z27.string(), reported: z27.boolean() })),
10959
11055
  /** The subscription usage Claude Code last reported in this worktree. */
10960
11056
  usage: usageSchema.nullable().default(null),
10961
11057
  /** When that usage was reported, or 0 if it was before this was kept. */
10962
- usageAt: z26.number().default(0),
11058
+ usageAt: z27.number().default(0),
10963
11059
  /** The lead agent's Claude Code session, as its `SessionStart` hook reported it. */
10964
- sessionId: z26.string().nullable().default(null),
11060
+ sessionId: z27.string().nullable().default(null),
10965
11061
  /** When the agent's last turn ended with nothing left to do, for shutting it down later. */
10966
- idleSince: z26.number().nullable().default(null),
11062
+ idleSince: z27.number().nullable().default(null),
10967
11063
  /** The agent ran `herd exit`, so its session ends after this turn. */
10968
- exitRequested: z26.boolean().default(false),
11064
+ exitRequested: z27.boolean().default(false),
10969
11065
  /** Whether the agent sent anything with `herd send` this turn. */
10970
- sentThisTurn: z26.boolean().default(false),
11066
+ sentThisTurn: z27.boolean().default(false),
10971
11067
  /** Whether it's been asked this turn to reply. */
10972
- askedToReply: z26.boolean().default(false),
11068
+ askedToReply: z27.boolean().default(false),
10973
11069
  /** Whether it ran `herd handled` this turn, which handles the turn's messages with no reply. */
10974
- handledThisTurn: z26.boolean().default(false),
11070
+ handledThisTurn: z27.boolean().default(false),
10975
11071
  /** The newest waiting message it's been pointed at, so one arrival is pointed out once. */
10976
- nudgedSeq: z26.number().default(0),
11072
+ nudgedSeq: z27.number().default(0),
10977
11073
  /** When a `Stop` hook last ended a turn. */
10978
- hookIdleAt: z26.number().default(0),
11074
+ hookIdleAt: z27.number().default(0),
10979
11075
  /** When a turn last ended with background work still running, which keeps its messages held. */
10980
- pausedAt: z26.number().nullable().default(null),
11076
+ pausedAt: z27.number().nullable().default(null),
10981
11077
  /** The version of the terminal host the lead terminal was opened in, if it was recorded. */
10982
- hostVersion: z26.string().nullable().default(null),
11078
+ hostVersion: z27.string().nullable().default(null),
10983
11079
  /** When a notice last failed for a reason other than someone's draft, to wait before retrying. */
10984
- noticeFailedAt: z26.number().nullable().default(null),
11080
+ noticeFailedAt: z27.number().nullable().default(null),
10985
11081
  /** Why the last notice failed, so the history records each reason once, not every attempt. */
10986
- noticeFailure: z26.string().nullable().default(null),
11082
+ noticeFailure: z27.string().nullable().default(null),
10987
11083
  /** A recorded dialog herd pressed its way out of, so a dialog that stays isn't pressed again. */
10988
- closedDialog: z26.string().nullable().default(null),
11084
+ closedDialog: z27.string().nullable().default(null),
10989
11085
  /**
10990
11086
  * Prompts Claude Code's hooks reported that haven't had their `Stop`. A turn is open while this
10991
11087
  * is above zero. It's a count because a prompt queued mid-turn is reported as it's queued, and
10992
11088
  * runs as a turn of its own, ending with a second `Stop`.
10993
11089
  */
10994
- promptsOpen: z26.number().default(0),
11090
+ promptsOpen: z27.number().default(0),
10995
11091
  /** The messages the latest claim or addition took, for choosing which notice to type. */
10996
- claimedSeqs: z26.array(z26.number()).default([]),
11092
+ claimedSeqs: z27.array(z27.number()).default([]),
10997
11093
  /** The investigator looking at a screen herd can't read, from opening to the owner's answer. */
10998
- investigation: z26.object({
10999
- terminal: z26.string(),
11000
- secret: z26.string(),
11001
- startedAt: z26.number(),
11094
+ investigation: z27.object({
11095
+ terminal: z27.string(),
11096
+ secret: z27.string(),
11097
+ startedAt: z27.number(),
11002
11098
  /** The stuck screen when it opened, saved with the key if the owner approves one. */
11003
- screen: z26.array(z26.string()),
11004
- key: z26.string().nullable(),
11005
- promptId: z26.string().nullable()
11099
+ screen: z27.array(z27.string()),
11100
+ key: z27.string().nullable(),
11101
+ promptId: z27.string().nullable()
11006
11102
  }).nullable().default(null),
11007
11103
  /** The failed notice an investigator was opened for, so each failure gets one. */
11008
- investigatedFailure: z26.number().nullable().default(null),
11104
+ investigatedFailure: z27.number().nullable().default(null),
11009
11105
  /** The agent CLI this conversation's worker runs, as its offer named it. */
11010
- agent: z26.string().default("claude-code"),
11106
+ agent: z27.string().default("claude-code"),
11011
11107
  /** Whether what the current claim took needs a reply, which other agents' messages don't. */
11012
- replyWanted: z26.boolean().default(true),
11108
+ replyWanted: z27.boolean().default(true),
11013
11109
  /** For a delegate: the conversation that started it, which its starting prompt names. */
11014
- parent: z26.string().nullable().default(null),
11110
+ parent: z27.string().nullable().default(null),
11015
11111
  /** Codex's session file for this worktree, once found, and how far the daemon has read it. */
11016
- codexFile: z26.string().nullable().default(null),
11017
- codexOffset: z26.number().default(0),
11112
+ codexFile: z27.string().nullable().default(null),
11113
+ codexOffset: z27.number().default(0),
11018
11114
  /** When the agent was last started, so Codex's new session file is told from older ones. */
11019
- agentStartedAt: z26.number().default(0),
11115
+ agentStartedAt: z27.number().default(0),
11020
11116
  /**
11021
11117
  * The key the agent was last started with, as `HERD_START`. Its `herd inbox` carrying this key
11022
11118
  * is what proves the start works: never a hook, a prompt being taken, or the screen.
11023
11119
  */
11024
- startKey: z26.string().nullable().default(null),
11120
+ startKey: z27.string().nullable().default(null),
11025
11121
  /** Whether the current start has checked in. One from before keys counts as proven. */
11026
- startProven: z26.boolean().default(true),
11122
+ startProven: z27.boolean().default(true),
11027
11123
  /** When the check-in window of an unproven start began, or null once it's proven or for none. */
11028
- checkInFrom: z26.number().nullable().default(null),
11124
+ checkInFrom: z27.number().nullable().default(null),
11029
11125
  /** Whether this start has been flagged as needing someone at its terminal, so it's said once. */
11030
- needsAttention: z26.boolean().default(false),
11126
+ needsAttention: z27.boolean().default(false),
11031
11127
  /** What agy's status line last said its agent was doing, and when. */
11032
- agentState: z26.enum(["working", "idle"]).nullable().default(null),
11033
- agentStateAt: z26.number().default(0)
11128
+ agentState: z27.enum(["working", "idle"]).nullable().default(null),
11129
+ agentStateAt: z27.number().default(0)
11034
11130
  });
11035
- var workspaceSchema = z26.preprocess(fromBeforeFolders, workspaceFields);
11036
- var stateSchema = z26.object({ workspaces: z26.record(z26.string(), workspaceSchema) });
11131
+ var workspaceSchema = z27.preprocess(fromBeforeFolders, workspaceFields);
11132
+ var stateSchema = z27.object({ workspaces: z27.record(z27.string(), workspaceSchema) });
11037
11133
 
11038
11134
  // src/workspaces/claims.ts
11039
11135
  var holder = (ws, workspace) => `${ws.options.machineName()}/${workspace.lead}`;
@@ -11114,7 +11210,7 @@ import { join as join29 } from "node:path";
11114
11210
 
11115
11211
  // src/commands/repo.ts
11116
11212
  init_define_HERD_PACKAGE();
11117
- import { z as z27 } from "zod";
11213
+ import { z as z28 } from "zod";
11118
11214
  var REPO_HELP = `Usage: herd repo <url or owner/name> [folder]
11119
11215
 
11120
11216
  Checks a repo out in this conversation's folder, on a branch named for the conversation, started
@@ -11128,16 +11224,16 @@ var REPOS_HELP = `Usage: herd repos [--json]
11128
11224
 
11129
11225
  Lists the repos in this conversation's folder, the repos checked out in this machine's root, and
11130
11226
  the repos your owner's herd.config.ts describes, with what each is for. Add one with herd repo.`;
11131
- var repoContextSchema = z27.object({
11132
- folder: z27.string(),
11133
- root: z27.string(),
11134
- branch: z27.string(),
11135
- legacy: z27.boolean(),
11136
- legacyReason: z27.string().optional(),
11137
- recorded: z27.array(z27.object({ folder: z27.string(), remote: z27.string(), branch: z27.string() })),
11138
- cloned: z27.array(z27.object({ key: z27.string(), remote: z27.string() })),
11139
- described: z27.array(
11140
- z27.object({ remote: z27.string(), key: z27.string().nullable(), about: z27.string().optional() })
11227
+ var repoContextSchema = z28.object({
11228
+ folder: z28.string(),
11229
+ root: z28.string(),
11230
+ branch: z28.string(),
11231
+ legacy: z28.boolean(),
11232
+ legacyReason: z28.string().optional(),
11233
+ recorded: z28.array(z28.object({ folder: z28.string(), remote: z28.string(), branch: z28.string() })),
11234
+ cloned: z28.array(z28.object({ key: z28.string(), remote: z28.string() })),
11235
+ described: z28.array(
11236
+ z28.object({ remote: z28.string(), key: z28.string().nullable(), about: z28.string().optional() })
11141
11237
  )
11142
11238
  });
11143
11239
  var isUrl = (text4) => /^[a-z][a-z0-9+.-]*:\/\//i.test(text4) || text4.startsWith("/") || /^[^@/\s]+@[^:/\s]+:/.test(text4);
@@ -11194,7 +11290,7 @@ async function runRepo(rest) {
11194
11290
  op: "repo-add",
11195
11291
  repoAdd: { asked, ...folderName !== void 0 ? { folder: folderName } : {} }
11196
11292
  });
11197
- const lines = z27.array(z27.string()).safeParse(reply.lines);
11293
+ const lines = z28.array(z28.string()).safeParse(reply.lines);
11198
11294
  if (!lines.success) throw new HerdError("The daemon's answer about this repo wasn't readable.");
11199
11295
  console.log(lines.data.join("\n"));
11200
11296
  return 0;
@@ -11962,7 +12058,7 @@ async function watchOne(ws, conversationId, terminals, rows = []) {
11962
12058
  const dialog = listed?.dialog ?? reading?.dialog ?? null;
11963
12059
  await followInvestigation(ws, workspace);
11964
12060
  workspace = ws.get(conversationId) ?? workspace;
11965
- if (needsInvestigator(ws, workspace, reading)) {
12061
+ if ((lead?.viewers ?? 0) === 0 && needsInvestigator(ws, workspace, reading)) {
11966
12062
  ws.inBackground(
11967
12063
  openInvestigator(ws, workspace).catch(
11968
12064
  (error) => ws.log(`${conversationId}: the investigator didn't start: ${error}`)
@@ -12015,6 +12111,7 @@ async function watchOne(ws, conversationId, terminals, rows = []) {
12015
12111
  const owed = workspace.terminalNotice;
12016
12112
  const retryAt = (workspace.noticeFailedAt ?? 0) + (ws.options.noticeRetryMs ?? NOTICE_RETRY_MS);
12017
12113
  if (!owed && Date.now() < retryAt) return;
12114
+ if (someoneDriving(lead, ws.options.driverQuietMs)) return;
12018
12115
  ws.update(conversationId, { deferred: false, terminalNotice: false });
12019
12116
  const claimed = workspace.deferred && await claim(ws, workspace);
12020
12117
  if (claimed || owed) {
@@ -12033,7 +12130,7 @@ async function watchOne(ws, conversationId, terminals, rows = []) {
12033
12130
  if (idle.idleSince === null && !idle.claimId) {
12034
12131
  idle = ws.update(conversationId, { idleSince: Date.now() });
12035
12132
  }
12036
- await shutDownIfDone(ws, idle, reading.draft, lead.viewers);
12133
+ await shutDownIfDone(ws, idle, reading.draft, lookingAt(lead));
12037
12134
  }
12038
12135
  }
12039
12136
  function unreachable(ws, workspace) {
@@ -12095,7 +12192,7 @@ async function endTurn(ws, workspace) {
12095
12192
  async function shutDownIfDone(ws, workspace, draft, viewers) {
12096
12193
  if (workspace.idleSince === null || workspace.claimId) return;
12097
12194
  if (!workspace.exitRequested && Date.now() - workspace.idleSince < ws.idleMs) return;
12098
- if (draft || (viewers ?? 0) > 0) return;
12195
+ if (draft || viewers > 0) return;
12099
12196
  await ws.driverFor(workspace).exit(workspace.lead);
12100
12197
  await ws.options.terminals.kill(workspace.lead);
12101
12198
  await ws.report(workspace.conversationId, "exited");
@@ -12161,9 +12258,13 @@ async function deliver(ws, conversationId, delivery) {
12161
12258
  if (!workspace.startProven) workspace = ws.update(conversationId, { checkInFrom: Date.now() });
12162
12259
  return afterSubmit(ws, workspace, result2);
12163
12260
  }
12261
+ const lead = (await ws.options.terminals.list()).find((t) => t.name === workspace.lead);
12262
+ if (someoneDriving(lead, ws.options.driverQuietMs)) {
12263
+ ws.update(conversationId, { deferred: true });
12264
+ return;
12265
+ }
12164
12266
  let reading = await ws.driverFor(workspace).read(workspace.lead);
12165
12267
  if (reading.knownDialog) {
12166
- const lead = (await ws.options.terminals.list()).find((t) => t.name === workspace.lead);
12167
12268
  if (await closeKnownDialog(ws, workspace, reading.knownDialog, lead?.viewers ?? 0)) {
12168
12269
  reading = await ws.driverFor(workspace).read(workspace.lead);
12169
12270
  }
@@ -12368,21 +12469,21 @@ function resolveAttachments(worktree, files) {
12368
12469
  // src/workers.ts
12369
12470
  init_define_HERD_PACKAGE();
12370
12471
  init_src();
12371
- import { z as z28 } from "zod";
12372
- var workerListingSchema = z28.object({
12373
- workers: z28.array(workerSchema),
12374
- machines: z28.array(
12375
- z28.object({
12376
- id: z28.string(),
12377
- name: z28.string(),
12378
- online: z28.boolean(),
12379
- missing: z28.array(z28.string()),
12380
- configError: z28.string().optional(),
12472
+ import { z as z29 } from "zod";
12473
+ var workerListingSchema = z29.object({
12474
+ workers: z29.array(workerSchema),
12475
+ machines: z29.array(
12476
+ z29.object({
12477
+ id: z29.string(),
12478
+ name: z29.string(),
12479
+ online: z29.boolean(),
12480
+ missing: z29.array(z29.string()),
12481
+ configError: z29.string().optional(),
12381
12482
  // Servers from before versions were reported send none.
12382
- herdVersion: z28.string().nullable().optional()
12483
+ herdVersion: z29.string().nullable().optional()
12383
12484
  })
12384
12485
  ),
12385
- configType: z28.string()
12486
+ configType: z29.string()
12386
12487
  });
12387
12488
  async function readWorkers(fetch2) {
12388
12489
  const response = await fetch2("/api/workers");
@@ -12450,20 +12551,20 @@ function formatWorkers(listing, current = null) {
12450
12551
  );
12451
12552
  return lines.join("\n");
12452
12553
  }
12453
- var routingCheckSchema = z28.object({
12454
- suits: z28.array(z28.object({ key: z28.string(), name: z28.string() })),
12455
- eligible: z28.array(z28.object({ key: z28.string(), name: z28.string() })),
12456
- removed: z28.array(z28.object({ key: z28.string(), name: z28.string(), rule: z28.string() })),
12457
- questions: z28.array(
12458
- z28.object({ id: z28.string(), question: z28.string(), probability: z28.number().nullable() })
12554
+ var routingCheckSchema = z29.object({
12555
+ suits: z29.array(z29.object({ key: z29.string(), name: z29.string() })),
12556
+ eligible: z29.array(z29.object({ key: z29.string(), name: z29.string() })),
12557
+ removed: z29.array(z29.object({ key: z29.string(), name: z29.string(), rule: z29.string() })),
12558
+ questions: z29.array(
12559
+ z29.object({ id: z29.string(), question: z29.string(), probability: z29.number().nullable() })
12459
12560
  ),
12460
- fallback: z28.string().optional(),
12461
- fits: z28.array(
12462
- z28.object({
12463
- key: z28.string(),
12464
- fit: z28.number(),
12465
- statement: z28.string().optional(),
12466
- level: z28.string().optional()
12561
+ fallback: z29.string().optional(),
12562
+ fits: z29.array(
12563
+ z29.object({
12564
+ key: z29.string(),
12565
+ fit: z29.number(),
12566
+ statement: z29.string().optional(),
12567
+ level: z29.string().optional()
12467
12568
  })
12468
12569
  ).optional()
12469
12570
  });
@@ -13318,8 +13419,8 @@ async function download(ws, workspace, file) {
13318
13419
  Readable.fromWeb(response.body),
13319
13420
  createWriteStream(partial, { flags: "wx" })
13320
13421
  );
13321
- const { size } = statSync10(partial);
13322
- if (size !== file.size) throw new Error(`it came to ${size} bytes, not ${file.size}`);
13422
+ const { size: size2 } = statSync10(partial);
13423
+ if (size2 !== file.size) throw new Error(`it came to ${size2} bytes, not ${file.size}`);
13323
13424
  renameSync7(partial, target);
13324
13425
  } finally {
13325
13426
  rmSync17(partial, { force: true });
@@ -13883,6 +13984,7 @@ async function runDaemon(args) {
13883
13984
  ...timing.agyIdleSettleMs === void 0 ? {} : { agyIdleSettleMs: timing.agyIdleSettleMs },
13884
13985
  ...timing.promptPollMs === void 0 ? {} : { promptPollMs: timing.promptPollMs },
13885
13986
  ...timing.noticeRetryMs === void 0 ? {} : { noticeRetryMs: timing.noticeRetryMs },
13987
+ ...timing.driverQuietMs === void 0 ? {} : { driverQuietMs: timing.driverQuietMs },
13886
13988
  ...timing.investigateAfterMs === void 0 ? {} : { investigateAfterMs: timing.investigateAfterMs },
13887
13989
  config: () => loadConfig(),
13888
13990
  root: () => machineRoot(),
@@ -14580,10 +14682,10 @@ init_define_HERD_PACKAGE();
14580
14682
  init_define_HERD_PACKAGE();
14581
14683
  import { connect as connect4 } from "node:net";
14582
14684
  var DETACH_KEY = "";
14583
- function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: process.stdout }, path = currentHostSocketPath()) {
14685
+ function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: process.stdout }, path = currentHostSocketPath(), { watch: watch2 = false } = {}) {
14584
14686
  return new Promise((resolve8, reject) => {
14585
14687
  const socket = connect4(path);
14586
- const size = () => ({ cols: stdout.columns || 120, rows: stdout.rows || 40 });
14688
+ const size2 = () => ({ cols: stdout.columns || 120, rows: stdout.rows || 40 });
14587
14689
  let buffer = "";
14588
14690
  let started = false;
14589
14691
  let ended = false;
@@ -14601,19 +14703,21 @@ function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: proces
14601
14703
  const data = chunk.toString("utf8");
14602
14704
  const detach = data.indexOf(DETACH_KEY);
14603
14705
  const before = detach >= 0 ? data.slice(0, detach) : data;
14604
- if (before) socket.write(`${JSON.stringify({ type: "input", data: before })}
14706
+ if (before && !watch2) socket.write(`${JSON.stringify({ type: "input", data: before })}
14605
14707
  `);
14606
14708
  if (detach >= 0) {
14607
14709
  stdout.write("\r\n[detached; it's still running]\r\n");
14608
14710
  finish("detached");
14609
14711
  }
14610
14712
  };
14611
- const onResize = () => socket.write(`${JSON.stringify({ type: "resize", ...size() })}
14713
+ const onResize = () => socket.write(`${JSON.stringify({ type: "resize", ...size2() })}
14612
14714
  `);
14613
14715
  socket.on(
14614
14716
  "connect",
14615
- () => socket.write(`${JSON.stringify({ op: "attach", name, ...size() })}
14616
- `)
14717
+ () => socket.write(
14718
+ `${JSON.stringify(watch2 ? { op: "watch", name } : { op: "attach", name, ...size2() })}
14719
+ `
14720
+ )
14617
14721
  );
14618
14722
  socket.on("data", (chunk) => {
14619
14723
  buffer += chunk;
@@ -14634,7 +14738,7 @@ function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: proces
14634
14738
  if (stdin.isTTY) stdin.setRawMode(true);
14635
14739
  stdin.on("data", onInput);
14636
14740
  stdin.resume();
14637
- stdout.on("resize", onResize);
14741
+ if (!watch2) stdout.on("resize", onResize);
14638
14742
  }
14639
14743
  } else if (message.type === "data") {
14640
14744
  stdout.write(message.data ?? "");
@@ -14657,7 +14761,8 @@ function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: proces
14657
14761
  import { serveHost } from "./terminal-host.mjs";
14658
14762
  async function runAttach(rest) {
14659
14763
  const terminals = new HostTerminals();
14660
- const name = rest[0];
14764
+ const watch2 = rest.includes("--watch");
14765
+ const name = rest.find((arg) => arg !== "--watch");
14661
14766
  if (!name) {
14662
14767
  const running = (await terminals.list().catch(() => [])).filter((t) => !t.exited);
14663
14768
  console.log(
@@ -14666,7 +14771,7 @@ ${running.map((t) => ` ${t.name} (${t.cwd})`).join("\n")}` : "No terminals are
14666
14771
  );
14667
14772
  return 0;
14668
14773
  }
14669
- const result = await attach(name, void 0, await terminals.pathOf(name));
14774
+ const result = await attach(name, void 0, await terminals.pathOf(name), { watch: watch2 });
14670
14775
  return result === "detached" ? 0 : result.exitCode ?? 1;
14671
14776
  }
14672
14777
  async function runTerminalHost(rest) {
@@ -14883,7 +14988,7 @@ async function runWatch(command, rest) {
14883
14988
  // src/commands/work.ts
14884
14989
  init_define_HERD_PACKAGE();
14885
14990
  init_src();
14886
- import { z as z34 } from "zod";
14991
+ import { z as z35 } from "zod";
14887
14992
  var WORK_HELP = `Usage: herd work <url> [--kind <kind>] [--title "<title>"] [--status <status>]
14888
14993
  [--detail "<detail>"] [--made] [--phase <phase>]
14889
14994
  herd work --remove <url>
@@ -14975,9 +15080,9 @@ ${WORK_HELP}`
14975
15080
  };
14976
15081
  return { kind: "record", change, json };
14977
15082
  }
14978
- var followSchema = z34.union([
14979
- z34.object({ following: z34.literal(true) }),
14980
- z34.object({ following: z34.literal(false), reason: z34.string() })
15083
+ var followSchema = z35.union([
15084
+ z35.object({ following: z35.literal(true) }),
15085
+ z35.object({ following: z35.literal(false), reason: z35.string() })
14981
15086
  ]);
14982
15087
  function formatFollow(follow) {
14983
15088
  return follow.following ? "Following it: its reviews, CI and merge will reach herd inbox." : `Not followed: ${follow.reason}`;
@@ -14993,7 +15098,7 @@ function formatWork(work, own) {
14993
15098
  return ` ${parts.join(" \xB7 ")}${work.made ? " made here" : ""}
14994
15099
  ${work.url}${whose}`;
14995
15100
  }
14996
- var listSchema = z34.array(workObjectSchema);
15101
+ var listSchema = z35.array(workObjectSchema);
14997
15102
  async function runWork(rest) {
14998
15103
  const parsed = parseWork(rest);
14999
15104
  if (parsed.kind === "help") {
@@ -15241,7 +15346,7 @@ On this computer:
15241
15346
  daemon [--repo <path>]...
15242
15347
  Run this machine's daemon, working from its root folder. Repos given with
15243
15348
  --repo are only listed as known, since herd works in its own clones
15244
- attach [<terminal>] Watch or take over a terminal. Ctrl+\\ detaches. With no name, lists them
15349
+ attach [<terminal>] Take over a terminal, or only watch it with --watch. Ctrl+\\ detaches. With no name, lists them
15245
15350
  builds [register|speed]
15246
15351
  Whether this machine is a build runner, register it, with its owner's
15247
15352
  approval in the browser, or show how long builds take. See builds --help