@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.
- package/herd.mjs +1247 -1142
- package/package.json +2 -2
- 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.
|
|
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/
|
|
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 =
|
|
495
|
-
usedPercentage:
|
|
496
|
-
resetsAt:
|
|
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 =
|
|
564
|
+
usageSchema = z7.object({
|
|
504
565
|
fiveHour: usageWindowSchema.optional(),
|
|
505
566
|
sevenDay: usageWindowSchema.optional(),
|
|
506
|
-
agent:
|
|
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 =
|
|
570
|
+
activityTextSchema = z7.string().trim().min(1).max(MAX_ACTIVITY_LENGTH).regex(/^[^\r\n]+$/);
|
|
510
571
|
MAX_TITLE_LENGTH = 40;
|
|
511
|
-
titleSchema =
|
|
512
|
-
listedWindowSchema =
|
|
572
|
+
titleSchema = z7.string().trim().min(1).max(MAX_TITLE_LENGTH).regex(/^[^\r\n]+$/);
|
|
573
|
+
listedWindowSchema = z7.union([
|
|
513
574
|
usageWindowSchema,
|
|
514
|
-
|
|
575
|
+
z7.object({ reset: z7.literal(true), resetsAt: z7.number().int().nonnegative() })
|
|
515
576
|
]);
|
|
516
|
-
listedUsageSchema =
|
|
577
|
+
listedUsageSchema = z7.object({
|
|
517
578
|
fiveHour: listedWindowSchema.optional(),
|
|
518
579
|
sevenDay: listedWindowSchema.optional(),
|
|
519
|
-
agent:
|
|
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
|
|
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 =
|
|
622
|
+
capabilitySchema = z8.enum(CAPABILITIES);
|
|
562
623
|
PLATFORMS = ["macos", "linux", "windows"];
|
|
563
|
-
platformSchema =
|
|
624
|
+
platformSchema = z8.enum(PLATFORMS);
|
|
564
625
|
TOOLS = ["xcode", "gpu"];
|
|
565
|
-
toolSchema =
|
|
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 =
|
|
584
|
-
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 =
|
|
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 =
|
|
654
|
+
harnessConfigSchema = z8.object({
|
|
594
655
|
share: shareSchema.optional(),
|
|
595
656
|
/** Read as `share: "off"` when false, from files written before shares. */
|
|
596
|
-
enabled:
|
|
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:
|
|
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:
|
|
669
|
+
priority: z8.number().int().min(-100).max(100).optional()
|
|
609
670
|
}).strict();
|
|
610
671
|
BUILDS_MAX = 4;
|
|
611
|
-
buildsSettingSchema =
|
|
612
|
-
|
|
613
|
-
|
|
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 =
|
|
617
|
-
remote:
|
|
618
|
-
about:
|
|
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 =
|
|
621
|
-
about:
|
|
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:
|
|
686
|
+
harnesses: z8.record(z8.string().regex(/^[a-z][a-z0-9-]*$/), harnessConfigSchema).optional(),
|
|
626
687
|
builds: buildsSettingSchema.optional(),
|
|
627
|
-
repos:
|
|
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 =
|
|
679
|
-
statement:
|
|
680
|
-
level:
|
|
681
|
-
source:
|
|
682
|
-
});
|
|
683
|
-
workerSchema =
|
|
684
|
-
key:
|
|
685
|
-
name:
|
|
686
|
-
machineId:
|
|
687
|
-
machineName:
|
|
688
|
-
agent:
|
|
689
|
-
capabilities:
|
|
690
|
-
about:
|
|
691
|
-
only:
|
|
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:
|
|
694
|
-
priority:
|
|
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:
|
|
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:
|
|
760
|
+
activated: z8.boolean(),
|
|
700
761
|
activation: agentActivationSchema.optional(),
|
|
701
|
-
online:
|
|
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:
|
|
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:
|
|
706
|
-
|
|
766
|
+
repos: z8.array(
|
|
767
|
+
z8.object({ name: z8.string(), remote: z8.string(), cloned: z8.boolean().optional() })
|
|
707
768
|
),
|
|
708
|
-
machineCreatedAt:
|
|
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
|
|
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 =
|
|
724
|
-
time2 =
|
|
725
|
-
terminalStateSchema =
|
|
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 =
|
|
734
|
-
machineRepoSchema =
|
|
795
|
+
agentStatusSchema = z9.enum(["working", "idle", "waiting_on_someone", "exited"]);
|
|
796
|
+
machineRepoSchema = z9.object({
|
|
735
797
|
name: nonEmpty4,
|
|
736
798
|
remote: nonEmpty4,
|
|
737
|
-
cloned:
|
|
799
|
+
cloned: z9.boolean().optional()
|
|
738
800
|
});
|
|
739
801
|
isCloned = (repo) => repo.cloned !== false;
|
|
740
|
-
machineCliSchema =
|
|
802
|
+
machineCliSchema = z9.object({
|
|
741
803
|
name: nonEmpty4,
|
|
742
804
|
version: nonEmpty4,
|
|
743
|
-
runnable:
|
|
744
|
-
capabilities:
|
|
805
|
+
runnable: z9.boolean().optional(),
|
|
806
|
+
capabilities: z9.array(capabilitySchema).optional(),
|
|
745
807
|
activation: agentActivationSchema.optional()
|
|
746
808
|
});
|
|
747
|
-
folderUnavailableSchema =
|
|
748
|
-
path:
|
|
749
|
-
target:
|
|
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 =
|
|
813
|
+
machineRegistrationSchema = z9.object({
|
|
752
814
|
id: nonEmpty4,
|
|
753
815
|
name: nonEmpty4,
|
|
754
|
-
repos:
|
|
755
|
-
clis:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
836
|
+
online: z9.boolean(),
|
|
773
837
|
lastSeenAt: time2,
|
|
774
838
|
createdAt: time2
|
|
775
839
|
});
|
|
776
|
-
machinePresenceSchema =
|
|
777
|
-
worktreeSchema =
|
|
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 =
|
|
843
|
+
repoHintSchema = z9.object({
|
|
780
844
|
remote: nonEmpty4,
|
|
781
845
|
branch: nonEmpty4.optional(),
|
|
782
846
|
folder: nonEmpty4.optional()
|
|
783
847
|
});
|
|
784
|
-
terminalSchema =
|
|
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:
|
|
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 =
|
|
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:
|
|
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:
|
|
876
|
+
handoff: z9.boolean().optional()
|
|
813
877
|
});
|
|
814
|
-
offerAnswerSchema =
|
|
815
|
-
|
|
816
|
-
|
|
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 =
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
type:
|
|
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:
|
|
887
|
+
delivery: z9.enum(["interrupt", "defer"])
|
|
824
888
|
}),
|
|
825
|
-
|
|
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
|
-
|
|
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 =
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
type:
|
|
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
|
|
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 =
|
|
850
|
-
id =
|
|
851
|
-
promptKindSchema =
|
|
852
|
-
promptOptionSchema =
|
|
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:
|
|
855
|
-
description:
|
|
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:
|
|
929
|
+
style: z10.enum(["primary", "danger"]).optional()
|
|
858
930
|
});
|
|
859
|
-
promptQuestionSchema =
|
|
931
|
+
promptQuestionSchema = z10.object({
|
|
860
932
|
id,
|
|
861
|
-
text:
|
|
862
|
-
options:
|
|
933
|
+
text: z10.string().trim().min(1).max(300),
|
|
934
|
+
options: z10.array(promptOptionSchema).min(1).max(10)
|
|
863
935
|
});
|
|
864
|
-
promptRequestSchema =
|
|
936
|
+
promptRequestSchema = z10.object({
|
|
865
937
|
kind: promptKindSchema,
|
|
866
|
-
title:
|
|
867
|
-
detail:
|
|
868
|
-
questions:
|
|
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 =
|
|
942
|
+
promptOutcomeSchema = z10.enum(["answered", "cancelled"]);
|
|
871
943
|
}
|
|
872
944
|
});
|
|
873
945
|
|
|
874
946
|
// ../common-chat/src/pull-requests.ts
|
|
875
|
-
import { z as
|
|
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 =
|
|
915
|
-
pullRequestPhaseSchema =
|
|
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 =
|
|
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 =
|
|
1010
|
+
leadChangeSchema = z11.object({
|
|
939
1011
|
/** Its repo and number, such as `api#104`, or "a pull request" when hidden. */
|
|
940
|
-
label:
|
|
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:
|
|
1017
|
+
moreForYou: z11.number().int().nonnegative(),
|
|
946
1018
|
/** Absent when hidden. */
|
|
947
|
-
url:
|
|
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
|
|
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 =
|
|
969
|
-
workKindSchema =
|
|
970
|
-
workStatusSchema =
|
|
971
|
-
workSourceSchema =
|
|
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 =
|
|
976
|
-
url:
|
|
1047
|
+
workObjectSchema = z12.object({
|
|
1048
|
+
url: z12.string().min(1).max(MAX_WORK_URL),
|
|
977
1049
|
kind: workKindSchema,
|
|
978
|
-
title:
|
|
1050
|
+
title: z12.string().min(1).max(MAX_WORK_TITLE),
|
|
979
1051
|
status: workStatusSchema,
|
|
980
|
-
detail:
|
|
1052
|
+
detail: z12.string().max(MAX_WORK_DETAIL).nullable(),
|
|
981
1053
|
/** Whether the conversation made it, rather than only being about it. */
|
|
982
|
-
made:
|
|
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:
|
|
1062
|
+
conversationId: z12.string().min(1).optional()
|
|
991
1063
|
});
|
|
992
|
-
workChangeSchema =
|
|
993
|
-
url:
|
|
1064
|
+
workChangeSchema = z12.object({
|
|
1065
|
+
url: z12.string().trim().min(1).max(MAX_WORK_URL),
|
|
994
1066
|
kind: workKindSchema.optional(),
|
|
995
|
-
title:
|
|
1067
|
+
title: z12.string().trim().min(1).max(2e3).optional(),
|
|
996
1068
|
status: workStatusSchema.optional(),
|
|
997
|
-
detail:
|
|
998
|
-
made:
|
|
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 =
|
|
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 =
|
|
1085
|
+
workObjectSummarySchema = z12.object({
|
|
1014
1086
|
kind: workKindSchema,
|
|
1015
1087
|
status: workStatusSchema,
|
|
1016
|
-
made:
|
|
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:
|
|
1022
|
-
title:
|
|
1023
|
-
detail:
|
|
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:
|
|
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
|
|
1103
|
+
import { z as z13 } from "zod";
|
|
1032
1104
|
function event(type, payload) {
|
|
1033
|
-
return
|
|
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 =
|
|
1049
|
-
deliveryModeSchema =
|
|
1050
|
-
eventSourceSchema =
|
|
1051
|
-
nonEmpty6 =
|
|
1052
|
-
actorSchema =
|
|
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
|
-
|
|
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
|
-
|
|
1057
|
-
kind:
|
|
1128
|
+
z13.object({
|
|
1129
|
+
kind: z13.literal("account"),
|
|
1058
1130
|
provider: nonEmpty6,
|
|
1059
1131
|
tenant: nonEmpty6,
|
|
1060
1132
|
subject: nonEmpty6,
|
|
1061
|
-
name:
|
|
1062
|
-
isBot:
|
|
1133
|
+
name: z13.string().optional(),
|
|
1134
|
+
isBot: z13.boolean()
|
|
1063
1135
|
}),
|
|
1064
|
-
|
|
1136
|
+
z13.object({ kind: z13.literal("system") })
|
|
1065
1137
|
]);
|
|
1066
1138
|
eventBase = {
|
|
1067
|
-
seq:
|
|
1139
|
+
seq: z13.number().int().positive(),
|
|
1068
1140
|
source: eventSourceSchema,
|
|
1069
1141
|
actor: actorSchema,
|
|
1070
|
-
createdAt:
|
|
1142
|
+
createdAt: z13.number().int().nonnegative()
|
|
1071
1143
|
};
|
|
1072
|
-
entryIds =
|
|
1073
|
-
routingRecordSchema =
|
|
1074
|
-
eligible:
|
|
1075
|
-
questions:
|
|
1076
|
-
|
|
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:
|
|
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:
|
|
1085
|
-
removed:
|
|
1086
|
-
order:
|
|
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:
|
|
1090
|
-
|
|
1161
|
+
fits: z13.array(
|
|
1162
|
+
z13.object({
|
|
1091
1163
|
key: nonEmpty6,
|
|
1092
|
-
fit:
|
|
1093
|
-
statement:
|
|
1094
|
-
level:
|
|
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:
|
|
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:
|
|
1102
|
-
warm:
|
|
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:
|
|
1106
|
-
|
|
1177
|
+
shares: z13.array(
|
|
1178
|
+
z13.object({
|
|
1107
1179
|
key: nonEmpty6,
|
|
1108
1180
|
share: shareSchema,
|
|
1109
|
-
pacing:
|
|
1110
|
-
weight:
|
|
1181
|
+
pacing: z13.enum(["ahead", "even", "under", "unknown"]),
|
|
1182
|
+
weight: z13.number().nonnegative()
|
|
1111
1183
|
})
|
|
1112
1184
|
).optional(),
|
|
1113
|
-
drawn:
|
|
1185
|
+
drawn: z13.array(nonEmpty6).optional()
|
|
1114
1186
|
});
|
|
1115
|
-
threadChangeSchema =
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
kind:
|
|
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:
|
|
1194
|
+
target: z13.string(),
|
|
1123
1195
|
/** Whether Friday wrote that message. */
|
|
1124
|
-
onBot:
|
|
1196
|
+
onBot: z13.boolean()
|
|
1125
1197
|
})
|
|
1126
1198
|
]);
|
|
1127
|
-
conversationEventSchema =
|
|
1199
|
+
conversationEventSchema = z13.discriminatedUnion("type", [
|
|
1128
1200
|
event("message.created", {
|
|
1129
1201
|
messageId: nonEmpty6,
|
|
1130
|
-
text:
|
|
1131
|
-
direction:
|
|
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:
|
|
1206
|
+
reason: z13.string().optional(),
|
|
1135
1207
|
// Loaded from before the conversation joined the thread, so it never waits for a response.
|
|
1136
|
-
isContext:
|
|
1208
|
+
isContext: z13.boolean().optional(),
|
|
1137
1209
|
// Set when the message was deleted on its platform. `text` is then empty.
|
|
1138
|
-
deleted:
|
|
1210
|
+
deleted: z13.boolean().optional(),
|
|
1139
1211
|
// Sent through the API but kept out of the platform, so only clients see it.
|
|
1140
|
-
quiet:
|
|
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:
|
|
1214
|
+
client: z13.string().optional(),
|
|
1143
1215
|
// How to read `text`. Absent means plain text, as every message before this field was.
|
|
1144
|
-
format:
|
|
1216
|
+
format: z13.enum(["markdown"]).optional(),
|
|
1145
1217
|
// Files attached to it, kept with the conversation.
|
|
1146
|
-
files:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
1236
|
+
agentMessage: z13.object({
|
|
1165
1237
|
from: nonEmpty6,
|
|
1166
1238
|
worker: nonEmpty6,
|
|
1167
|
-
posted:
|
|
1168
|
-
overdue:
|
|
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:
|
|
1171
|
-
outsideTree:
|
|
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:
|
|
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:
|
|
1249
|
+
automation: z13.object({
|
|
1178
1250
|
id: nonEmpty6,
|
|
1179
1251
|
name: nonEmpty6,
|
|
1180
1252
|
reason: nonEmpty6,
|
|
1181
1253
|
key: nonEmpty6,
|
|
1182
|
-
payload:
|
|
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:
|
|
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:
|
|
1199
|
-
outcome:
|
|
1200
|
-
probability:
|
|
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:
|
|
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:
|
|
1210
|
-
outcome:
|
|
1211
|
-
probability:
|
|
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:
|
|
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:
|
|
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:
|
|
1304
|
+
expiresAt: z13.number().int()
|
|
1233
1305
|
}),
|
|
1234
|
-
event("mailbox.renewed", { claimId: nonEmpty6, expiresAt:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
1272
|
-
added:
|
|
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:
|
|
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:
|
|
1366
|
+
text: z13.string().optional()
|
|
1295
1367
|
}),
|
|
1296
1368
|
event("prompt.closed", {
|
|
1297
1369
|
promptId: nonEmpty6,
|
|
1298
1370
|
outcome: promptOutcomeSchema,
|
|
1299
|
-
reason:
|
|
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:
|
|
1324
|
-
routed:
|
|
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:
|
|
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:
|
|
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:
|
|
1419
|
+
fetchFailed: z13.string().max(1e3).optional()
|
|
1348
1420
|
}),
|
|
1349
|
-
event("terminal.exited", { terminalId: nonEmpty6, exitCode:
|
|
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:
|
|
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:
|
|
1458
|
+
url: z13.url(),
|
|
1373
1459
|
label: nonEmpty6,
|
|
1374
|
-
title:
|
|
1460
|
+
title: z13.string(),
|
|
1375
1461
|
phase: pullRequestPhaseSchema,
|
|
1376
1462
|
move: pullRequestMoveSchema.nullable(),
|
|
1377
|
-
detail:
|
|
1463
|
+
detail: z13.string().nullable()
|
|
1378
1464
|
}),
|
|
1379
1465
|
event("subscription.created", {
|
|
1380
1466
|
resource: nonEmpty6,
|
|
1381
1467
|
label: nonEmpty6,
|
|
1382
|
-
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 =
|
|
1395
|
-
conversationStatusSchema =
|
|
1396
|
-
conversationSummarySchema =
|
|
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:
|
|
1488
|
+
placeUrl: z13.string().nullable(),
|
|
1403
1489
|
status: conversationStatusSchema,
|
|
1404
|
-
waitingCount:
|
|
1405
|
-
createdAt:
|
|
1406
|
-
lastActivityAt:
|
|
1407
|
-
expiresAt:
|
|
1408
|
-
});
|
|
1409
|
-
placementSchema =
|
|
1410
|
-
|
|
1411
|
-
kind:
|
|
1412
|
-
machine:
|
|
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
|
-
|
|
1417
|
-
|
|
1502
|
+
z13.object({ kind: z13.literal("waiting_for_machine") }),
|
|
1503
|
+
z13.object({ kind: z13.literal("author_not_linked") })
|
|
1418
1504
|
]);
|
|
1419
|
-
treeSummarySchema =
|
|
1505
|
+
treeSummarySchema = z13.object({
|
|
1420
1506
|
parent: nonEmpty6.nullable(),
|
|
1421
1507
|
root: nonEmpty6,
|
|
1422
|
-
children:
|
|
1423
|
-
|
|
1508
|
+
children: z13.array(
|
|
1509
|
+
z13.object({
|
|
1424
1510
|
id: nonEmpty6,
|
|
1425
1511
|
title: nonEmpty6,
|
|
1426
|
-
state:
|
|
1427
|
-
createdAt:
|
|
1512
|
+
state: z13.string().nullable(),
|
|
1513
|
+
createdAt: z13.number()
|
|
1428
1514
|
})
|
|
1429
1515
|
)
|
|
1430
1516
|
});
|
|
1431
|
-
conversationSnapshotSchema =
|
|
1517
|
+
conversationSnapshotSchema = z13.object({
|
|
1432
1518
|
conversation: conversationSummarySchema,
|
|
1433
|
-
events:
|
|
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:
|
|
1524
|
+
people: z13.record(z13.string(), z13.string()).optional(),
|
|
1439
1525
|
placement: placementSchema.nullable().optional(),
|
|
1440
1526
|
tree: treeSummarySchema.optional()
|
|
1441
1527
|
});
|
|
1442
|
-
liveFrameSchema =
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
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 =
|
|
1448
|
-
messages:
|
|
1449
|
-
edits:
|
|
1450
|
-
deletes:
|
|
1451
|
-
reactions:
|
|
1452
|
-
status:
|
|
1453
|
-
search:
|
|
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
|
|
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 =
|
|
1497
|
-
time5 =
|
|
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 =
|
|
1592
|
+
conversationStateSchema = z14.enum([
|
|
1507
1593
|
"waiting",
|
|
1508
1594
|
"working",
|
|
1509
1595
|
"stuck",
|
|
1510
1596
|
"unanswered",
|
|
1511
1597
|
"done"
|
|
1512
1598
|
]);
|
|
1513
|
-
stuckReasonSchema =
|
|
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 =
|
|
1537
|
-
conversationSourceSchema =
|
|
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:
|
|
1627
|
+
name: z14.string().optional()
|
|
1542
1628
|
});
|
|
1543
1629
|
LATEST_MESSAGE_LENGTH = 200;
|
|
1544
|
-
latestMessageSchema =
|
|
1545
|
-
text:
|
|
1630
|
+
latestMessageSchema = z14.object({
|
|
1631
|
+
text: z14.string().max(LATEST_MESSAGE_LENGTH),
|
|
1546
1632
|
/** A person wrote it, or the agent did. */
|
|
1547
|
-
from:
|
|
1633
|
+
from: z14.enum(["person", "agent"]),
|
|
1548
1634
|
at: time5
|
|
1549
1635
|
});
|
|
1550
1636
|
listMachineSchema = machinePresenceSchema.extend({
|
|
1551
|
-
mine:
|
|
1637
|
+
mine: z14.boolean().optional(),
|
|
1552
1638
|
/** The agent holding it on that machine, such as `Claude Code`, when it's known. */
|
|
1553
|
-
agent:
|
|
1639
|
+
agent: z14.string().optional()
|
|
1554
1640
|
});
|
|
1555
|
-
conversationListItemSchema =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
1674
|
+
work: z14.array(workObjectSummarySchema).optional(),
|
|
1589
1675
|
/** How many more work objects it holds than `work` carries. */
|
|
1590
|
-
moreWork:
|
|
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 =
|
|
1595
|
-
items:
|
|
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:
|
|
1683
|
+
next: z14.string().optional()
|
|
1598
1684
|
});
|
|
1599
|
-
listFrameSchema =
|
|
1600
|
-
|
|
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
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
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
|
|
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 =
|
|
1655
|
-
time6 =
|
|
1740
|
+
nonEmpty8 = z15.string().min(1);
|
|
1741
|
+
time6 = z15.number().int().nonnegative();
|
|
1656
1742
|
BOARD_ITEMS = 8;
|
|
1657
|
-
boardItemSchema =
|
|
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:
|
|
1750
|
+
source: z15.string().nullable(),
|
|
1665
1751
|
/** What the agent says it's doing, while it works. */
|
|
1666
|
-
activity:
|
|
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:
|
|
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:
|
|
1758
|
+
hidden: z15.boolean().optional()
|
|
1673
1759
|
});
|
|
1674
|
-
boardWorkerSchema =
|
|
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:
|
|
1769
|
+
strengths: z15.array(z15.string().min(1).max(100)).max(3).optional()
|
|
1684
1770
|
});
|
|
1685
|
-
boardMachineSchema =
|
|
1771
|
+
boardMachineSchema = z15.object({
|
|
1686
1772
|
name: nonEmpty8,
|
|
1687
|
-
online:
|
|
1688
|
-
workers:
|
|
1773
|
+
online: z15.boolean(),
|
|
1774
|
+
workers: z15.array(boardWorkerSchema).max(8)
|
|
1689
1775
|
});
|
|
1690
|
-
boardSchema =
|
|
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:
|
|
1693
|
-
stuck:
|
|
1694
|
-
unanswered:
|
|
1695
|
-
waiting:
|
|
1696
|
-
working:
|
|
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:
|
|
1784
|
+
pullRequests: z15.number().int().nonnegative().optional()
|
|
1699
1785
|
}),
|
|
1700
1786
|
/** How many became `done` in the last 24 hours. */
|
|
1701
|
-
doneToday:
|
|
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:
|
|
1707
|
-
machines:
|
|
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:
|
|
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
|
|
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 =
|
|
1739
|
-
time7 =
|
|
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 =
|
|
1746
|
-
buildOfferSchema =
|
|
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:
|
|
1838
|
+
main: z16.boolean(),
|
|
1753
1839
|
expiresAt: time7
|
|
1754
1840
|
});
|
|
1755
|
-
buildCancelReasonSchema =
|
|
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 =
|
|
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
|
-
|
|
1774
|
-
type:
|
|
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:
|
|
1777
|
-
repos:
|
|
1778
|
-
running:
|
|
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
|
-
|
|
1782
|
-
type:
|
|
1867
|
+
z16.object({
|
|
1868
|
+
type: z16.literal("answer"),
|
|
1783
1869
|
buildId: nonEmpty9,
|
|
1784
1870
|
offerId: nonEmpty9,
|
|
1785
|
-
answer:
|
|
1786
|
-
reason:
|
|
1871
|
+
answer: z16.enum(["accept", "refuse"]),
|
|
1872
|
+
reason: z16.string().max(500).optional()
|
|
1787
1873
|
})
|
|
1788
1874
|
]);
|
|
1789
|
-
serviceMessageSchema =
|
|
1790
|
-
|
|
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
|
-
|
|
1793
|
-
type:
|
|
1878
|
+
z16.object({
|
|
1879
|
+
type: z16.literal("accepted"),
|
|
1794
1880
|
buildId: nonEmpty9,
|
|
1795
1881
|
secret: nonEmpty9,
|
|
1796
|
-
leaseMs:
|
|
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
|
-
|
|
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
|
-
|
|
1890
|
+
z16.object({ type: z16.literal("reconnect") })
|
|
1805
1891
|
]);
|
|
1806
|
-
runnerRegisterRequestSchema =
|
|
1807
|
-
runnerRegisterResponseSchema =
|
|
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:
|
|
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:
|
|
1902
|
+
pollMs: z16.number().int().positive().optional()
|
|
1817
1903
|
});
|
|
1818
|
-
runnerPollRequestSchema =
|
|
1819
|
-
runnerPollResponseSchema =
|
|
1820
|
-
|
|
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
|
-
|
|
1908
|
+
z16.object({ status: z16.literal("approved"), runnerId: nonEmpty9, key: nonEmpty9 }),
|
|
1823
1909
|
/** Expired, or the key was already collected. Start again. */
|
|
1824
|
-
|
|
1910
|
+
z16.object({ status: z16.literal("expired") })
|
|
1825
1911
|
]);
|
|
1826
|
-
buildStepSchema =
|
|
1827
|
-
label:
|
|
1828
|
-
key:
|
|
1829
|
-
status:
|
|
1830
|
-
ms:
|
|
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:
|
|
1833
|
-
budgetMs:
|
|
1918
|
+
partial: z16.boolean().default(false),
|
|
1919
|
+
budgetMs: z16.number().int().positive().nullable().default(null)
|
|
1834
1920
|
});
|
|
1835
|
-
buildFinishSchema =
|
|
1836
|
-
status:
|
|
1837
|
-
summary:
|
|
1838
|
-
deployed:
|
|
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 =
|
|
1926
|
+
sharedPassSchema = z16.object({
|
|
1841
1927
|
step: nonEmpty9,
|
|
1842
1928
|
passedAt: time7,
|
|
1843
1929
|
sha: nonEmpty9,
|
|
1844
1930
|
runner: nonEmpty9,
|
|
1845
|
-
partial:
|
|
1931
|
+
partial: z16.boolean()
|
|
1846
1932
|
});
|
|
1847
|
-
passesResponseSchema =
|
|
1848
|
-
deployCredentialsSchema =
|
|
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:
|
|
1943
|
+
sha: z16.string().regex(/^[0-9a-f]{40}$/).optional(),
|
|
1858
1944
|
/** Each Worker's key at its last successful deploy. */
|
|
1859
|
-
lastDeployed:
|
|
1945
|
+
lastDeployed: z16.record(z16.string(), z16.string())
|
|
1860
1946
|
});
|
|
1861
|
-
deployedSchema =
|
|
1862
|
-
worker:
|
|
1863
|
-
key:
|
|
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 =
|
|
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
|
|
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 =
|
|
1991
|
+
setupActionSchema = z17.discriminatedUnion("kind", [
|
|
1906
1992
|
/** A page to open, which signs in to HerdingChats first. */
|
|
1907
|
-
|
|
1993
|
+
z17.object({ kind: z17.literal("link"), url: z17.string(), label: z17.string() }),
|
|
1908
1994
|
/** A command to run on the computer. */
|
|
1909
|
-
|
|
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
|
-
|
|
1915
|
-
kind:
|
|
1916
|
-
machineId:
|
|
2000
|
+
z17.object({
|
|
2001
|
+
kind: z17.literal("sign_in"),
|
|
2002
|
+
machineId: z17.string(),
|
|
1917
2003
|
tool: signInToolSchema,
|
|
1918
|
-
label:
|
|
2004
|
+
label: z17.string()
|
|
1919
2005
|
})
|
|
1920
2006
|
]);
|
|
1921
|
-
setupStepSchema =
|
|
1922
|
-
id:
|
|
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:
|
|
1925
|
-
optional:
|
|
1926
|
-
title:
|
|
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:
|
|
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:
|
|
2021
|
+
extra: z17.array(setupActionSchema).optional()
|
|
1936
2022
|
});
|
|
1937
|
-
setupStatusSchema =
|
|
1938
|
-
steps:
|
|
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:
|
|
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[
|
|
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:
|
|
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
|
|
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:
|
|
2436
|
-
states:
|
|
2437
|
-
limit:
|
|
2438
|
-
show:
|
|
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
|
|
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:
|
|
2503
|
-
show:
|
|
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
|
|
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:
|
|
2568
|
-
title:
|
|
2569
|
-
summary:
|
|
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
|
|
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:
|
|
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
|
|
2822
|
+
import { z as z34 } from "zod";
|
|
2736
2823
|
function readComposition(stored) {
|
|
2737
|
-
const shape =
|
|
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 =
|
|
2753
|
-
kind:
|
|
2754
|
-
options:
|
|
2839
|
+
placedWidgetSchema = z34.object({
|
|
2840
|
+
kind: z34.string().min(1),
|
|
2841
|
+
options: z34.record(z34.string(), z34.unknown()).default({})
|
|
2755
2842
|
});
|
|
2756
|
-
compositionSchema =
|
|
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
|
|
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
|
|
3301
|
+
import { dirname as dirname10, join as join16 } from "node:path";
|
|
3215
3302
|
import { createInterface } from "node:readline/promises";
|
|
3216
|
-
import { z as
|
|
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
|
|
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 =
|
|
3233
|
-
api:
|
|
3234
|
-
tokenEndpoint:
|
|
3235
|
-
revocationEndpoint:
|
|
3236
|
-
clientId:
|
|
3237
|
-
accessToken:
|
|
3238
|
-
accessTokenExpiresAt:
|
|
3239
|
-
refreshToken:
|
|
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 =
|
|
3256
|
-
authorization_endpoint:
|
|
3257
|
-
token_endpoint:
|
|
3258
|
-
registration_endpoint:
|
|
3259
|
-
revocation_endpoint:
|
|
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 =
|
|
3262
|
-
access_token:
|
|
3263
|
-
refresh_token:
|
|
3264
|
-
expires_in:
|
|
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 =
|
|
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
|
|
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
|
|
3743
|
+
let size2;
|
|
3657
3744
|
try {
|
|
3658
|
-
|
|
3745
|
+
size2 = statSync(path).size;
|
|
3659
3746
|
} catch {
|
|
3660
3747
|
return { events: [], offset };
|
|
3661
3748
|
}
|
|
3662
|
-
if (
|
|
3749
|
+
if (size2 <= offset) return { events: [], offset };
|
|
3663
3750
|
const fd = openSync(path, "r");
|
|
3664
3751
|
try {
|
|
3665
|
-
const buffer = Buffer.alloc(
|
|
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
|
|
3767
|
-
const start = Math.max(0,
|
|
3768
|
-
const buffer = Buffer.alloc(
|
|
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 =
|
|
4310
|
-
var configSchema =
|
|
4311
|
-
machineId:
|
|
4312
|
-
machineName:
|
|
4313
|
-
repos:
|
|
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:
|
|
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
|
|
4523
|
-
import { dirname as dirname6, join as
|
|
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
|
|
4530
|
-
import { dirname as dirname5, join as
|
|
4531
|
-
import { z as
|
|
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
|
|
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
|
|
4545
|
-
var localRequestSchema =
|
|
4546
|
-
secret:
|
|
4547
|
-
op:
|
|
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:
|
|
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:
|
|
4582
|
-
remote:
|
|
4583
|
-
folder:
|
|
4584
|
-
base:
|
|
4585
|
-
fetchFailed:
|
|
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:
|
|
4589
|
-
asked:
|
|
4590
|
-
folder:
|
|
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:
|
|
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:
|
|
4596
|
-
action:
|
|
4597
|
-
url:
|
|
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:
|
|
4601
|
-
to:
|
|
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:
|
|
4604
|
-
action:
|
|
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:
|
|
4616
|
-
body:
|
|
4617
|
-
waitMs:
|
|
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:
|
|
4621
|
-
action:
|
|
4622
|
-
change:
|
|
4623
|
-
url:
|
|
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:
|
|
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:
|
|
4629
|
-
proposal:
|
|
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:
|
|
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:
|
|
4635
|
-
text:
|
|
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:
|
|
4638
|
-
idempotencyKey:
|
|
4639
|
-
command:
|
|
4724
|
+
message: z20.unknown().optional(),
|
|
4725
|
+
idempotencyKey: z20.string().optional(),
|
|
4726
|
+
command: z20.array(z20.string()).optional(),
|
|
4640
4727
|
// `status --clear`.
|
|
4641
|
-
clear:
|
|
4728
|
+
clear: z20.boolean().optional(),
|
|
4642
4729
|
// What an agent's status line reported, checked by the daemon.
|
|
4643
|
-
usage:
|
|
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:
|
|
4647
|
-
sessionId:
|
|
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:
|
|
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:
|
|
4652
|
-
request:
|
|
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:
|
|
4655
|
-
input:
|
|
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 =
|
|
4778
|
-
tool_name:
|
|
4779
|
-
tool_input:
|
|
4780
|
-
permission_suggestions:
|
|
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) =>
|
|
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:
|
|
5317
|
+
terminal: z22.string().optional()
|
|
4993
5318
|
});
|
|
4994
|
-
var storeSchema =
|
|
4995
|
-
var activationsPath = (home = herdHome()) =>
|
|
4996
|
-
var activationRequestPath = (home = herdHome()) =>
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
5144
|
-
|
|
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
|
-
|
|
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 = (
|
|
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
|
-
|
|
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
|
|
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
|
|
5725
|
+
await sleep3(pollMs);
|
|
5399
5726
|
}
|
|
5400
5727
|
return settled;
|
|
5401
5728
|
}
|
|
5402
|
-
var herdConfigIn = (herdHome2) =>
|
|
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
|
|
5738
|
+
mkdirSync as mkdirSync8,
|
|
5412
5739
|
openSync as openSync2,
|
|
5413
5740
|
readFileSync as readFileSync6,
|
|
5414
|
-
rmSync as
|
|
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
|
|
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 =
|
|
5748
|
+
const logs = join10(herdHome2, "logs");
|
|
5422
5749
|
return {
|
|
5423
5750
|
herdHome: herdHome2,
|
|
5424
|
-
config:
|
|
5425
|
-
versions:
|
|
5426
|
-
current:
|
|
5427
|
-
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:
|
|
5430
|
-
supervisorLog:
|
|
5431
|
-
updateLock:
|
|
5432
|
-
status:
|
|
5433
|
-
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) =>
|
|
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
|
-
|
|
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))
|
|
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 () =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5587
|
-
import { existsSync as existsSync7, mkdirSync as
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
6037
|
+
rmSync8(plist, { force: true });
|
|
5711
6038
|
}
|
|
5712
6039
|
};
|
|
5713
6040
|
}
|
|
5714
6041
|
function systemd(context2) {
|
|
5715
|
-
const 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
|
-
|
|
6064
|
+
rmSync8(unit, { force: true });
|
|
5738
6065
|
}
|
|
5739
6066
|
};
|
|
5740
6067
|
}
|
|
5741
6068
|
function spawnInBackground(command, env, log) {
|
|
5742
|
-
|
|
6069
|
+
mkdirSync9(dirname8(log), { recursive: true });
|
|
5743
6070
|
const out = openSync3(log, "a");
|
|
5744
6071
|
const [program = process.execPath, ...args] = command;
|
|
5745
|
-
|
|
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
|
|
5798
|
-
import { join as
|
|
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
|
|
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
|
|
6135
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
5809
6136
|
import {
|
|
5810
6137
|
existsSync as existsSync8,
|
|
5811
|
-
mkdirSync as
|
|
5812
|
-
readdirSync as
|
|
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
|
|
6144
|
+
rmSync as rmSync9,
|
|
5818
6145
|
symlinkSync,
|
|
5819
6146
|
writeFileSync as writeFileSync8
|
|
5820
6147
|
} from "node:fs";
|
|
5821
|
-
import { basename as basename2, join as
|
|
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 =
|
|
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
|
|
6178
|
+
return readdirSync4(paths.versions).filter((name) => VERSION.test(name)).sort(compareVersions);
|
|
5852
6179
|
}
|
|
5853
|
-
var binOf = (versionDir, bin) =>
|
|
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(
|
|
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 =
|
|
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 =
|
|
5904
|
-
|
|
5905
|
-
|
|
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
|
-
|
|
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(
|
|
6240
|
+
writeFileSync8(join12(staging, "pnpm-workspace.yaml"), allowScriptsWorkspace(["node-pty"]));
|
|
5914
6241
|
const fail = (reason) => {
|
|
5915
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
5952
|
-
symlinkSync(
|
|
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
|
-
|
|
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 = [
|
|
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
|
-
|
|
6006
|
-
|
|
6332
|
+
join13(home, "Library", "pnpm"),
|
|
6333
|
+
join13(home, ".local", "share", "pnpm"),
|
|
6007
6334
|
...common
|
|
6008
6335
|
];
|
|
6009
6336
|
case "bun":
|
|
6010
|
-
return [
|
|
6337
|
+
return [join13(env.BUN_INSTALL || join13(home, ".bun"), "bin"), ...common];
|
|
6011
6338
|
case "yarn":
|
|
6012
|
-
return [
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
6528
|
+
const dir = join14(paths.herdHome, "daemon");
|
|
6202
6529
|
if (!existsSync10(dir)) return [];
|
|
6203
|
-
return
|
|
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 =
|
|
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
|
-
|
|
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) ?
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 ??
|
|
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 =
|
|
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") :
|
|
6854
|
-
const child =
|
|
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(
|
|
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 =
|
|
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
|
|
6981
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
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
|
|
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
|
|
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
|
|
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) =>
|
|
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 =
|
|
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) =>
|
|
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) =>
|
|
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: [
|
|
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
|
|
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 =
|
|
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 =
|
|
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) =>
|
|
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
|
-
|
|
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 =
|
|
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
|
|
7697
|
+
import { z as z24 } from "zod";
|
|
7371
7698
|
var INVESTIGATE_KEYS = ["escape", "up", "down", "enter", "tab"];
|
|
7372
|
-
var proposalSchema =
|
|
7373
|
-
key:
|
|
7374
|
-
reason:
|
|
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
|
|
7783
|
-
import { join as
|
|
7784
|
-
import { z as
|
|
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 =
|
|
7787
|
-
runnerId:
|
|
7788
|
-
key:
|
|
7789
|
-
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 = () =>
|
|
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
|
-
|
|
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
|
|
7859
|
-
var ms =
|
|
7860
|
-
var figuresSchema =
|
|
7861
|
-
var buildTimesSchema =
|
|
7862
|
-
var stepFiguresSchema =
|
|
7863
|
-
ran:
|
|
7864
|
-
remembered:
|
|
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:
|
|
8194
|
+
overBudget: z26.number().int()
|
|
7868
8195
|
});
|
|
7869
|
-
var kindSchema =
|
|
7870
|
-
builds:
|
|
7871
|
-
steps:
|
|
7872
|
-
|
|
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:
|
|
7875
|
-
|
|
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 =
|
|
7879
|
-
var speedReportSchema =
|
|
7880
|
-
periods:
|
|
7881
|
-
repos:
|
|
7882
|
-
|
|
7883
|
-
repo:
|
|
7884
|
-
capped:
|
|
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
|
|
7973
|
-
import { join as
|
|
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 = () =>
|
|
8308
|
+
var refusalPath = () => join20(herdHome(), "builds", "refused.json");
|
|
7982
8309
|
function writeBuildsRefusal(reason) {
|
|
7983
8310
|
if (!reason) {
|
|
7984
|
-
|
|
8311
|
+
rmSync11(refusalPath(), { force: true });
|
|
7985
8312
|
return;
|
|
7986
8313
|
}
|
|
7987
|
-
|
|
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
|
|
8262
|
-
var worktreesRoot = () =>
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
8580
|
-
import { join as
|
|
8581
|
-
var AGY_SETTINGS_FILE =
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
8937
|
+
writeFileSync12(join22(agents, "hooks.json"), `${JSON.stringify(agyHooks(herd), null, 2)}
|
|
8611
8938
|
`);
|
|
8612
|
-
writeFileSync12(
|
|
8939
|
+
writeFileSync12(join22(agents, ".gitignore"), "*\n");
|
|
8613
8940
|
const herdFolder = ownFolder(worktree, ".herd");
|
|
8614
|
-
writeFileSync12(
|
|
8941
|
+
writeFileSync12(join22(herdFolder, ".gitignore"), "*\n");
|
|
8615
8942
|
writeFileSync12(
|
|
8616
|
-
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
8778
|
-
import { dirname as dirname13, join as
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
9148
|
+
entries = readdirSync7(at);
|
|
8822
9149
|
} catch {
|
|
8823
9150
|
return;
|
|
8824
9151
|
}
|
|
8825
9152
|
for (const name of entries.sort()) {
|
|
8826
|
-
const path =
|
|
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
|
|
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
|
|
9335
|
+
mkdirSync as mkdirSync19,
|
|
9009
9336
|
mkdtempSync,
|
|
9010
|
-
readdirSync as
|
|
9337
|
+
readdirSync as readdirSync9,
|
|
9011
9338
|
readFileSync as readFileSync17,
|
|
9012
9339
|
realpathSync as realpathSync8,
|
|
9013
|
-
rmSync as
|
|
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
|
|
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
|
|
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
|
|
9025
|
-
var checkRegistryDir = () => process.env.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
|
-
|
|
9038
|
-
const file =
|
|
9364
|
+
mkdirSync18(dir, { recursive: true });
|
|
9365
|
+
const file = join24(dir, `${pid}.json`);
|
|
9039
9366
|
writeFileSync13(file, JSON.stringify({ pid, startedAt }));
|
|
9040
|
-
return () =>
|
|
9367
|
+
return () => rmSync13(file, { force: true });
|
|
9041
9368
|
}
|
|
9042
9369
|
function runningChecks(dir, isAlive = alive3) {
|
|
9043
9370
|
let names = [];
|
|
9044
9371
|
try {
|
|
9045
|
-
names =
|
|
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(
|
|
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
|
|
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 = () =>
|
|
9394
|
+
var sandboxStatusPath = () => join25(herdHome(), "builds", "sandbox.json");
|
|
9068
9395
|
function writeSandboxStatus(status2, now = Date.now()) {
|
|
9069
|
-
|
|
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(
|
|
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(
|
|
9114
|
-
for (const entry of
|
|
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(
|
|
9117
|
-
found.push(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 ??
|
|
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
|
|
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 =
|
|
9194
|
-
|
|
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
|
-
|
|
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 =
|
|
9560
|
+
const marker = join25(dir, "node_modules", ".herd-lock");
|
|
9234
9561
|
if (existsSync17(marker) && readFileSync17(marker, "utf8") === wanted) return;
|
|
9235
|
-
const cache =
|
|
9562
|
+
const cache = join25(this.home(), "node_modules", wanted);
|
|
9236
9563
|
for (const folder of moduleFolders(dir))
|
|
9237
|
-
|
|
9238
|
-
if (existsSync17(
|
|
9239
|
-
for (const folder of readFileSync17(
|
|
9240
|
-
cloneFolder(
|
|
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(
|
|
9572
|
+
const staging = mkdtempSync(join25(this.home(), "node_modules-"));
|
|
9246
9573
|
const folders = moduleFolders(dir);
|
|
9247
9574
|
for (const folder of folders) {
|
|
9248
|
-
|
|
9249
|
-
cloneFolder(
|
|
9575
|
+
mkdirSync19(join25(staging, folder, ".."), { recursive: true });
|
|
9576
|
+
cloneFolder(join25(dir, folder), join25(staging, folder));
|
|
9250
9577
|
}
|
|
9251
|
-
writeFileSync14(
|
|
9252
|
-
|
|
9253
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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(
|
|
9291
|
-
|
|
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,
|
|
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
|
-
|
|
9317
|
-
policyPath =
|
|
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 =
|
|
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
|
-
|
|
9346
|
-
if (policyPath)
|
|
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 =
|
|
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 ??
|
|
9379
|
-
const dir = mkdtempSync(
|
|
9380
|
-
const 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
|
-
|
|
9383
|
-
for (const sub of ["work", "git", "home", "owner"])
|
|
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:
|
|
9387
|
-
root:
|
|
9388
|
-
convos:
|
|
9389
|
-
worktree:
|
|
9390
|
-
store:
|
|
9391
|
-
worktreeGitDir:
|
|
9392
|
-
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 =
|
|
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(
|
|
9729
|
+
`fs.writeFileSync(${JSON.stringify(join25(dir, "home", "ok"))}, "ok");`
|
|
9403
9730
|
].join(" ");
|
|
9404
|
-
const child =
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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 = () =>
|
|
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
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
|
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
|
|
10333
|
-
var LOCAL_SETTINGS =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
10385
|
-
const 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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
10908
|
-
text:
|
|
10909
|
-
files:
|
|
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 =
|
|
10912
|
-
folder:
|
|
10913
|
-
remote:
|
|
10914
|
-
branch:
|
|
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:
|
|
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 =
|
|
10938
|
-
conversationId:
|
|
11033
|
+
var workspaceFields = z27.object({
|
|
11034
|
+
conversationId: z27.string(),
|
|
10939
11035
|
/** The conversation's own folder, where its terminals start. */
|
|
10940
|
-
folder:
|
|
11036
|
+
folder: z27.string(),
|
|
10941
11037
|
/** The repos in it, in the order they were added. */
|
|
10942
|
-
worktrees:
|
|
11038
|
+
worktrees: z27.array(workspaceRepoSchema).default([]),
|
|
10943
11039
|
/** A conversation from before folders, which works in its one repo's worktree. */
|
|
10944
|
-
legacy:
|
|
10945
|
-
lead:
|
|
10946
|
-
secret:
|
|
10947
|
-
readSeq:
|
|
10948
|
-
claimId:
|
|
10949
|
-
claimRenewedAt:
|
|
10950
|
-
started:
|
|
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:
|
|
11048
|
+
deferred: z27.boolean(),
|
|
10953
11049
|
/** A `herd run` command finished, which the agent hears about even with nothing to claim. */
|
|
10954
|
-
terminalNotice:
|
|
10955
|
-
status:
|
|
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:
|
|
10958
|
-
runs:
|
|
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:
|
|
11058
|
+
usageAt: z27.number().default(0),
|
|
10963
11059
|
/** The lead agent's Claude Code session, as its `SessionStart` hook reported it. */
|
|
10964
|
-
sessionId:
|
|
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:
|
|
11062
|
+
idleSince: z27.number().nullable().default(null),
|
|
10967
11063
|
/** The agent ran `herd exit`, so its session ends after this turn. */
|
|
10968
|
-
exitRequested:
|
|
11064
|
+
exitRequested: z27.boolean().default(false),
|
|
10969
11065
|
/** Whether the agent sent anything with `herd send` this turn. */
|
|
10970
|
-
sentThisTurn:
|
|
11066
|
+
sentThisTurn: z27.boolean().default(false),
|
|
10971
11067
|
/** Whether it's been asked this turn to reply. */
|
|
10972
|
-
askedToReply:
|
|
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:
|
|
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:
|
|
11072
|
+
nudgedSeq: z27.number().default(0),
|
|
10977
11073
|
/** When a `Stop` hook last ended a turn. */
|
|
10978
|
-
hookIdleAt:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
11090
|
+
promptsOpen: z27.number().default(0),
|
|
10995
11091
|
/** The messages the latest claim or addition took, for choosing which notice to type. */
|
|
10996
|
-
claimedSeqs:
|
|
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:
|
|
10999
|
-
terminal:
|
|
11000
|
-
secret:
|
|
11001
|
-
startedAt:
|
|
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:
|
|
11004
|
-
key:
|
|
11005
|
-
promptId:
|
|
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:
|
|
11104
|
+
investigatedFailure: z27.number().nullable().default(null),
|
|
11009
11105
|
/** The agent CLI this conversation's worker runs, as its offer named it. */
|
|
11010
|
-
agent:
|
|
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:
|
|
11108
|
+
replyWanted: z27.boolean().default(true),
|
|
11013
11109
|
/** For a delegate: the conversation that started it, which its starting prompt names. */
|
|
11014
|
-
parent:
|
|
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:
|
|
11017
|
-
codexOffset:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
11126
|
+
needsAttention: z27.boolean().default(false),
|
|
11031
11127
|
/** What agy's status line last said its agent was doing, and when. */
|
|
11032
|
-
agentState:
|
|
11033
|
-
agentStateAt:
|
|
11128
|
+
agentState: z27.enum(["working", "idle"]).nullable().default(null),
|
|
11129
|
+
agentStateAt: z27.number().default(0)
|
|
11034
11130
|
});
|
|
11035
|
-
var workspaceSchema =
|
|
11036
|
-
var stateSchema =
|
|
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
|
|
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 =
|
|
11132
|
-
folder:
|
|
11133
|
-
root:
|
|
11134
|
-
branch:
|
|
11135
|
-
legacy:
|
|
11136
|
-
legacyReason:
|
|
11137
|
-
recorded:
|
|
11138
|
-
cloned:
|
|
11139
|
-
described:
|
|
11140
|
-
|
|
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 =
|
|
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
|
|
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 ||
|
|
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
|
|
12372
|
-
var workerListingSchema =
|
|
12373
|
-
workers:
|
|
12374
|
-
machines:
|
|
12375
|
-
|
|
12376
|
-
id:
|
|
12377
|
-
name:
|
|
12378
|
-
online:
|
|
12379
|
-
missing:
|
|
12380
|
-
configError:
|
|
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:
|
|
12483
|
+
herdVersion: z29.string().nullable().optional()
|
|
12383
12484
|
})
|
|
12384
12485
|
),
|
|
12385
|
-
configType:
|
|
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 =
|
|
12454
|
-
suits:
|
|
12455
|
-
eligible:
|
|
12456
|
-
removed:
|
|
12457
|
-
questions:
|
|
12458
|
-
|
|
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:
|
|
12461
|
-
fits:
|
|
12462
|
-
|
|
12463
|
-
key:
|
|
12464
|
-
fit:
|
|
12465
|
-
statement:
|
|
12466
|
-
level:
|
|
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 (
|
|
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
|
|
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", ...
|
|
14713
|
+
const onResize = () => socket.write(`${JSON.stringify({ type: "resize", ...size2() })}
|
|
14612
14714
|
`);
|
|
14613
14715
|
socket.on(
|
|
14614
14716
|
"connect",
|
|
14615
|
-
() => socket.write(
|
|
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
|
|
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
|
|
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 =
|
|
14979
|
-
|
|
14980
|
-
|
|
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 =
|
|
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>]
|
|
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
|