@herdingchats/friday 0.1.550 → 0.1.561
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 +906 -559
- package/package.json +2 -2
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.561", gitHead: "dc277703d4460a12bee165c4c4e6653088631c16" };
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -689,6 +689,8 @@ export default {
|
|
|
689
689
|
activated: z7.boolean(),
|
|
690
690
|
activation: agentActivationSchema.optional(),
|
|
691
691
|
online: z7.boolean(),
|
|
692
|
+
/** A folder its machine works in that isn't there, so it's offered no work until it is. */
|
|
693
|
+
folderUnavailable: z7.object({ path: z7.string().min(1), target: z7.string().min(1).optional() }).optional(),
|
|
692
694
|
// Its machine's repos, each marked whether herd has cloned it there. Older daemons send no `cloned`.
|
|
693
695
|
repos: z7.array(
|
|
694
696
|
z7.object({ name: z7.string(), remote: z7.string(), cloned: z7.boolean().optional() })
|
|
@@ -700,7 +702,7 @@ export default {
|
|
|
700
702
|
|
|
701
703
|
// ../common-chat/src/machines.ts
|
|
702
704
|
import { z as z8 } from "zod";
|
|
703
|
-
var nonEmpty4, time2, terminalStateSchema, agentStatusSchema, machineRepoSchema, isCloned, machineCliSchema, machineRegistrationSchema, machineSchema, machinePresenceSchema, worktreeSchema, MAX_REPO_HINTS, repoHintSchema, terminalSchema, offerSchema, offerAnswerSchema, dispatchFrameSchema, machineMessageSchema;
|
|
705
|
+
var nonEmpty4, time2, terminalStateSchema, agentStatusSchema, machineRepoSchema, isCloned, machineCliSchema, folderUnavailableSchema, machineRegistrationSchema, machineSchema, machinePresenceSchema, worktreeSchema, MAX_REPO_HINTS, repoHintSchema, terminalSchema, offerSchema, offerAnswerSchema, dispatchFrameSchema, machineMessageSchema;
|
|
704
706
|
var init_machines = __esm({
|
|
705
707
|
"../common-chat/src/machines.ts"() {
|
|
706
708
|
"use strict";
|
|
@@ -732,6 +734,10 @@ var init_machines = __esm({
|
|
|
732
734
|
capabilities: z8.array(capabilitySchema).optional(),
|
|
733
735
|
activation: agentActivationSchema.optional()
|
|
734
736
|
});
|
|
737
|
+
folderUnavailableSchema = z8.object({
|
|
738
|
+
path: z8.string().min(1).max(1e3),
|
|
739
|
+
target: z8.string().min(1).max(1e3).optional()
|
|
740
|
+
});
|
|
735
741
|
machineRegistrationSchema = z8.object({
|
|
736
742
|
id: nonEmpty4,
|
|
737
743
|
name: nonEmpty4,
|
|
@@ -744,7 +750,9 @@ var init_machines = __esm({
|
|
|
744
750
|
config: herdConfigSchema.optional(),
|
|
745
751
|
configError: z8.string().max(1e3).optional(),
|
|
746
752
|
// Whether its GitHub CLI is signed in, and as whom.
|
|
747
|
-
gh: gitHubCliSchema.optional()
|
|
753
|
+
gh: gitHubCliSchema.optional(),
|
|
754
|
+
// A folder it works in that isn't there, while it takes no work.
|
|
755
|
+
folderUnavailable: folderUnavailableSchema.optional()
|
|
748
756
|
});
|
|
749
757
|
machineSchema = machineRegistrationSchema.extend({
|
|
750
758
|
ownerId: nonEmpty4,
|
|
@@ -1458,6 +1466,9 @@ var init_conversation_state = __esm({
|
|
|
1458
1466
|
"author_not_linked",
|
|
1459
1467
|
// The only agents that could take it showed their own sign-in, so a person has to sign in.
|
|
1460
1468
|
"agent_signed_out",
|
|
1469
|
+
// The only machines that could take it have a folder that isn't there, such as a drive not
|
|
1470
|
+
// yet mounted.
|
|
1471
|
+
"folder_unavailable",
|
|
1461
1472
|
// The owner's "only" and "avoid" rules left no worker for the work.
|
|
1462
1473
|
"no_suitable_worker",
|
|
1463
1474
|
"notice_failed",
|
|
@@ -1557,6 +1568,7 @@ var init_conversation_state = __esm({
|
|
|
1557
1568
|
author_not_linked: "the author's account isn't linked",
|
|
1558
1569
|
delegate_waiting: "a delegate is waiting on someone",
|
|
1559
1570
|
agent_signed_out: "its agent isn't signed in on its machine",
|
|
1571
|
+
folder_unavailable: "its machine's folder isn't there",
|
|
1560
1572
|
no_suitable_worker: "no worker may take it under its configuration",
|
|
1561
1573
|
notice_failed: "a notice didn't reach the agent's terminal",
|
|
1562
1574
|
machine_offline: "its machine is offline",
|
|
@@ -1937,12 +1949,12 @@ var init_self = __esm({
|
|
|
1937
1949
|
|
|
1938
1950
|
// src/desk-display/usb.ts
|
|
1939
1951
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
1940
|
-
import { closeSync as closeSync4, constants, openSync as openSync5, readdirSync as
|
|
1952
|
+
import { closeSync as closeSync4, constants as constants2, openSync as openSync5, readdirSync as readdirSync9, readSync as readSync2, writeSync } from "node:fs";
|
|
1941
1953
|
import { platform } from "node:os";
|
|
1942
1954
|
import { setTimeout as delay } from "node:timers/promises";
|
|
1943
1955
|
function candidates() {
|
|
1944
1956
|
try {
|
|
1945
|
-
const names =
|
|
1957
|
+
const names = readdirSync9("/dev");
|
|
1946
1958
|
return platform() === "darwin" ? names.filter((name) => name.startsWith("cu.usbmodem")).map((name) => `/dev/${name}`) : names.filter((name) => name.startsWith("ttyACM")).map((name) => `/dev/${name}`);
|
|
1947
1959
|
} catch {
|
|
1948
1960
|
return [];
|
|
@@ -1975,7 +1987,7 @@ var init_usb = __esm({
|
|
|
1975
1987
|
fd;
|
|
1976
1988
|
buffered = "";
|
|
1977
1989
|
static open(path) {
|
|
1978
|
-
const fd = openSync5(path,
|
|
1990
|
+
const fd = openSync5(path, constants2.O_RDWR | constants2.O_NOCTTY | constants2.O_NONBLOCK);
|
|
1979
1991
|
try {
|
|
1980
1992
|
execFileSync7(
|
|
1981
1993
|
"stty",
|
|
@@ -3134,16 +3146,16 @@ var init_desk_display = __esm({
|
|
|
3134
3146
|
|
|
3135
3147
|
// src/package-entry.ts
|
|
3136
3148
|
init_define_HERD_PACKAGE();
|
|
3137
|
-
import { realpathSync as
|
|
3149
|
+
import { realpathSync as realpathSync11 } from "node:fs";
|
|
3138
3150
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3139
3151
|
|
|
3140
3152
|
// src/friday/main.ts
|
|
3141
3153
|
init_define_HERD_PACKAGE();
|
|
3142
3154
|
init_src();
|
|
3143
|
-
import { spawn as
|
|
3144
|
-
import { existsSync as
|
|
3155
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
3156
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
|
|
3145
3157
|
import { homedir as homedir4 } from "node:os";
|
|
3146
|
-
import { dirname as
|
|
3158
|
+
import { dirname as dirname10, join as join14 } from "node:path";
|
|
3147
3159
|
import { z as z22 } from "zod";
|
|
3148
3160
|
|
|
3149
3161
|
// src/auth.ts
|
|
@@ -3208,7 +3220,7 @@ async function login(openBrowser2, { api = apiUrl(), timeoutMs = 10 * 60 * 1e3 }
|
|
|
3208
3220
|
if (!metadata.success) throw new HerdError(`Couldn't find where to sign in for ${api}.`);
|
|
3209
3221
|
const meta = metadata.data;
|
|
3210
3222
|
const server = createServer();
|
|
3211
|
-
await new Promise((
|
|
3223
|
+
await new Promise((resolve7) => server.listen(0, "127.0.0.1", resolve7));
|
|
3212
3224
|
const address = server.address();
|
|
3213
3225
|
const port = typeof address === "object" && address ? address.port : 0;
|
|
3214
3226
|
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
@@ -3240,7 +3252,7 @@ async function login(openBrowser2, { api = apiUrl(), timeoutMs = 10 * 60 * 1e3 }
|
|
|
3240
3252
|
state,
|
|
3241
3253
|
resource: api
|
|
3242
3254
|
}).toString();
|
|
3243
|
-
const code = new Promise((
|
|
3255
|
+
const code = new Promise((resolve7, reject) => {
|
|
3244
3256
|
const timer = setTimeout(
|
|
3245
3257
|
() => reject(new HerdError("Nobody signed in within 10 minutes. Run herd login again.")),
|
|
3246
3258
|
timeoutMs
|
|
@@ -3254,7 +3266,7 @@ async function login(openBrowser2, { api = apiUrl(), timeoutMs = 10 * 60 * 1e3 }
|
|
|
3254
3266
|
res.writeHead(ok ? 200 : 400, { "Content-Type": "text/plain" }).end(ok ? "Signed in to herd. You can close this tab." : "Sign-in didn't complete.");
|
|
3255
3267
|
if (error) return reject(new HerdError(`Sign-in was refused: ${error}.`));
|
|
3256
3268
|
if (!ok) return reject(new HerdError("Sign-in came back for a different request."));
|
|
3257
|
-
|
|
3269
|
+
resolve7(url.searchParams.get("code") ?? "");
|
|
3258
3270
|
});
|
|
3259
3271
|
});
|
|
3260
3272
|
code.catch(() => {
|
|
@@ -3453,9 +3465,9 @@ async function runLogout() {
|
|
|
3453
3465
|
init_define_HERD_PACKAGE();
|
|
3454
3466
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
3455
3467
|
import { randomUUID } from "node:crypto";
|
|
3456
|
-
import { existsSync as
|
|
3468
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3457
3469
|
import { homedir as homedir3, hostname } from "node:os";
|
|
3458
|
-
import { basename, dirname as
|
|
3470
|
+
import { basename, dirname as dirname4, join as join6, resolve } from "node:path";
|
|
3459
3471
|
import { z as z18 } from "zod";
|
|
3460
3472
|
|
|
3461
3473
|
// src/machine-facts.ts
|
|
@@ -3762,8 +3774,8 @@ init_src();
|
|
|
3762
3774
|
import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
3763
3775
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
3764
3776
|
import {
|
|
3765
|
-
existsSync,
|
|
3766
|
-
mkdirSync as
|
|
3777
|
+
existsSync as existsSync2,
|
|
3778
|
+
mkdirSync as mkdirSync3,
|
|
3767
3779
|
readdirSync as readdirSync2,
|
|
3768
3780
|
readFileSync as readFileSync3,
|
|
3769
3781
|
renameSync,
|
|
@@ -3771,9 +3783,83 @@ import {
|
|
|
3771
3783
|
statSync as statSync3
|
|
3772
3784
|
} from "node:fs";
|
|
3773
3785
|
import { writeFile } from "node:fs/promises";
|
|
3774
|
-
import { dirname as
|
|
3786
|
+
import { dirname as dirname3, isAbsolute, join as join5 } from "node:path";
|
|
3775
3787
|
import { fileURLToPath } from "node:url";
|
|
3776
3788
|
import { promisify } from "node:util";
|
|
3789
|
+
|
|
3790
|
+
// src/local-files.ts
|
|
3791
|
+
init_define_HERD_PACKAGE();
|
|
3792
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
3793
|
+
import { constants, copyFileSync, existsSync, lstatSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
3794
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
3795
|
+
var INCLUDE_FILE = ".worktreeinclude";
|
|
3796
|
+
var DEFAULT_INCLUDE = ".env*";
|
|
3797
|
+
var HEAVY_FOLDERS = /* @__PURE__ */ new Set([
|
|
3798
|
+
".git",
|
|
3799
|
+
"node_modules",
|
|
3800
|
+
".venv",
|
|
3801
|
+
"venv",
|
|
3802
|
+
"__pycache__",
|
|
3803
|
+
".next",
|
|
3804
|
+
".turbo",
|
|
3805
|
+
".cache"
|
|
3806
|
+
]);
|
|
3807
|
+
var MAX_COPIED = 500;
|
|
3808
|
+
function gitOut(env, cwd, args, input = "") {
|
|
3809
|
+
return new Promise((resolve7) => {
|
|
3810
|
+
const child = spawn2("git", ["-C", cwd, ...args], {
|
|
3811
|
+
env: { ...env ?? process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
3812
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
3813
|
+
});
|
|
3814
|
+
let out = "";
|
|
3815
|
+
child.stdout.setEncoding("utf8");
|
|
3816
|
+
child.stdout.on("data", (chunk) => {
|
|
3817
|
+
out += chunk;
|
|
3818
|
+
});
|
|
3819
|
+
child.on("error", () => resolve7(""));
|
|
3820
|
+
child.on("close", () => resolve7(out));
|
|
3821
|
+
child.stdin.end(input);
|
|
3822
|
+
});
|
|
3823
|
+
}
|
|
3824
|
+
var split = (output2) => output2.split("\0").filter(Boolean);
|
|
3825
|
+
async function localFiles(checkout, env) {
|
|
3826
|
+
const include = join4(checkout, INCLUDE_FILE);
|
|
3827
|
+
const patterns = existsSync(include) ? [`--exclude-from=${include}`] : [`--exclude=${DEFAULT_INCLUDE}`];
|
|
3828
|
+
const matched = split(
|
|
3829
|
+
await gitOut(env, checkout, ["ls-files", "-z", "--others", "--ignored", ...patterns])
|
|
3830
|
+
).filter((path) => !path.split("/").some((part) => HEAVY_FOLDERS.has(part)));
|
|
3831
|
+
if (matched.length === 0) return [];
|
|
3832
|
+
const ignored = new Set(
|
|
3833
|
+
split(
|
|
3834
|
+
await gitOut(env, checkout, ["check-ignore", "-z", "--stdin"], `${matched.join("\0")}\0`)
|
|
3835
|
+
)
|
|
3836
|
+
);
|
|
3837
|
+
return matched.filter((path) => ignored.has(path)).sort();
|
|
3838
|
+
}
|
|
3839
|
+
function describeCopied(folder, copied) {
|
|
3840
|
+
const shown = copied.slice(0, 10).join(", ");
|
|
3841
|
+
const more = copied.length > 10 ? `, and ${copied.length - 10} more` : "";
|
|
3842
|
+
return `Copied ${copied.length === 1 ? "a local file" : "local files"} git ignores from the root's checkout into ${folder}: ${shown}${more}.`;
|
|
3843
|
+
}
|
|
3844
|
+
async function copyLocalFiles(checkout, worktree, env) {
|
|
3845
|
+
const copied = [];
|
|
3846
|
+
for (const path of await localFiles(checkout, env)) {
|
|
3847
|
+
if (copied.length >= MAX_COPIED) break;
|
|
3848
|
+
if (path.split("/").includes("..")) continue;
|
|
3849
|
+
const from = join4(checkout, path);
|
|
3850
|
+
const to = join4(worktree, path);
|
|
3851
|
+
try {
|
|
3852
|
+
if (!lstatSync(from).isFile() || existsSync(to)) continue;
|
|
3853
|
+
mkdirSync2(dirname2(to), { recursive: true });
|
|
3854
|
+
copyFileSync(from, to, constants.COPYFILE_EXCL);
|
|
3855
|
+
copied.push(path);
|
|
3856
|
+
} catch {
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
return copied;
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
// src/repo-store.ts
|
|
3777
3863
|
var run = promisify(execFile);
|
|
3778
3864
|
var StoreError = class extends Error {
|
|
3779
3865
|
};
|
|
@@ -3806,8 +3892,8 @@ function gitError(error) {
|
|
|
3806
3892
|
if (stderr) return stderr.slice(0, 1e3);
|
|
3807
3893
|
return error instanceof Error ? error.message.slice(0, 1e3) : String(error);
|
|
3808
3894
|
}
|
|
3809
|
-
var
|
|
3810
|
-
var
|
|
3895
|
+
var legacyConvosDir = (root) => join5(root, "convos");
|
|
3896
|
+
var checkoutLock = (root, key) => join5(root, ".herd", "locks", key);
|
|
3811
3897
|
function localPath(remote) {
|
|
3812
3898
|
if (remote.startsWith("file://")) {
|
|
3813
3899
|
try {
|
|
@@ -3829,11 +3915,6 @@ function repoKey(remote) {
|
|
|
3829
3915
|
}
|
|
3830
3916
|
var repoName = (key) => key.split("/").at(-1) ?? key;
|
|
3831
3917
|
var repoOwner = (key) => key.split("/").at(-2) ?? "";
|
|
3832
|
-
function storePath(root, remote) {
|
|
3833
|
-
const key = repoKey(remote);
|
|
3834
|
-
if (!key) throw new StoreError(`${remote} isn't a repo's URL.`);
|
|
3835
|
-
return join4(reposDir(root), `${key}.git`);
|
|
3836
|
-
}
|
|
3837
3918
|
function cloneUrl(key, known = []) {
|
|
3838
3919
|
return known.find((remote) => repoKey(remote) === key) ?? `https://${key}`;
|
|
3839
3920
|
}
|
|
@@ -3862,13 +3943,13 @@ function isStale(lock, content, staleMs) {
|
|
|
3862
3943
|
return true;
|
|
3863
3944
|
}
|
|
3864
3945
|
}
|
|
3865
|
-
async function withStoreLock(
|
|
3866
|
-
const lock = `${
|
|
3946
|
+
async function withStoreLock(target, work, options = {}) {
|
|
3947
|
+
const lock = `${target}.lock`;
|
|
3867
3948
|
const pollMs = options.pollMs ?? LOCK_POLL_MS;
|
|
3868
3949
|
const deadline = Date.now() + (options.timeoutMs ?? LOCK_TIMEOUT_MS);
|
|
3869
3950
|
const token = `${process.pid} ${randomBytes2(8).toString("hex")}
|
|
3870
3951
|
`;
|
|
3871
|
-
|
|
3952
|
+
mkdirSync3(dirname3(lock), { recursive: true });
|
|
3872
3953
|
for (; ; ) {
|
|
3873
3954
|
try {
|
|
3874
3955
|
await writeFile(lock, token, { flag: "wx" });
|
|
@@ -3892,7 +3973,7 @@ async function withStoreLock(store, work, options = {}) {
|
|
|
3892
3973
|
if (Date.now() > deadline) {
|
|
3893
3974
|
throw new StoreError(`Another process has held ${lock} for too long.`);
|
|
3894
3975
|
}
|
|
3895
|
-
await new Promise((
|
|
3976
|
+
await new Promise((resolve7) => setTimeout(resolve7, pollMs));
|
|
3896
3977
|
}
|
|
3897
3978
|
try {
|
|
3898
3979
|
return await work();
|
|
@@ -3903,38 +3984,63 @@ async function withStoreLock(store, work, options = {}) {
|
|
|
3903
3984
|
}
|
|
3904
3985
|
}
|
|
3905
3986
|
}
|
|
3906
|
-
|
|
3907
|
-
const
|
|
3908
|
-
|
|
3987
|
+
function checkouts(root) {
|
|
3988
|
+
const all = reposInFolder(root).map((repo) => ({
|
|
3989
|
+
key: repo.key,
|
|
3990
|
+
remote: repo.remote,
|
|
3991
|
+
folder: repo.folder,
|
|
3992
|
+
path: join5(root, repo.folder)
|
|
3993
|
+
}));
|
|
3994
|
+
const named = (repo) => repo.folder === repoName(repo.key) ? 0 : 1;
|
|
3995
|
+
const best = /* @__PURE__ */ new Map();
|
|
3996
|
+
for (const repo of [...all].sort((a, b) => named(a) - named(b))) {
|
|
3997
|
+
if (!best.has(repo.key)) best.set(repo.key, repo);
|
|
3998
|
+
}
|
|
3999
|
+
return all.filter((repo) => best.get(repo.key) === repo);
|
|
4000
|
+
}
|
|
4001
|
+
var checkoutFor = (root, key) => checkouts(root).find((repo) => repo.key === key) ?? null;
|
|
4002
|
+
function checkoutsStamp(root) {
|
|
4003
|
+
let entries;
|
|
4004
|
+
try {
|
|
4005
|
+
entries = readdirSync2(root);
|
|
4006
|
+
} catch {
|
|
4007
|
+
return "";
|
|
4008
|
+
}
|
|
4009
|
+
return entries.filter((name) => !name.startsWith(".") && existsSync2(join5(root, name, ".git"))).sort().join("\n");
|
|
4010
|
+
}
|
|
4011
|
+
async function ensureCheckout(root, remote, env) {
|
|
4012
|
+
const key = repoKey(remote);
|
|
4013
|
+
if (!key) throw new StoreError(`${remote} isn't a repo's URL.`);
|
|
4014
|
+
const found = checkoutFor(root, key);
|
|
4015
|
+
if (found) {
|
|
3909
4016
|
let fetchFailed = null;
|
|
3910
4017
|
try {
|
|
3911
|
-
await git(env,
|
|
3912
|
-
await
|
|
4018
|
+
await git(env, found.path, "fetch", "--quiet", "origin");
|
|
4019
|
+
if (!await hasRef(env, found.path, "refs/remotes/origin/HEAD")) {
|
|
4020
|
+
await git(env, found.path, "remote", "set-head", "origin", "--auto").catch(() => "");
|
|
4021
|
+
}
|
|
3913
4022
|
} catch (error) {
|
|
3914
4023
|
fetchFailed = error instanceof Error ? error.message : String(error);
|
|
3915
4024
|
}
|
|
3916
|
-
return {
|
|
4025
|
+
return { path: found.path, created: false, fetchFailed };
|
|
3917
4026
|
}
|
|
3918
|
-
const
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
await git(env, building, "remote", "add", "origin", remote);
|
|
3923
|
-
await git(
|
|
3924
|
-
env,
|
|
3925
|
-
building,
|
|
3926
|
-
"config",
|
|
3927
|
-
"remote.origin.fetch",
|
|
3928
|
-
"+refs/heads/*:refs/remotes/origin/*"
|
|
4027
|
+
const name = [repoName(key), `${repoOwner(key)}-${repoName(key)}`].filter(validFolderName).find((candidate) => !existsSync2(join5(root, candidate)));
|
|
4028
|
+
if (!name) {
|
|
4029
|
+
throw new StoreError(
|
|
4030
|
+
`Both ${repoName(key)} and ${repoOwner(key)}-${repoName(key)} are taken in ${root}, so herd can't clone ${key} there.`
|
|
3929
4031
|
);
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
4032
|
+
}
|
|
4033
|
+
const target = join5(root, name);
|
|
4034
|
+
const building = join5(root, `.${name}.tmp-${process.pid}-${randomBytes2(4).toString("hex")}`);
|
|
4035
|
+
mkdirSync3(root, { recursive: true });
|
|
4036
|
+
try {
|
|
4037
|
+
await git(env, root, "clone", "--quiet", remote, building);
|
|
4038
|
+
renameSync(building, target);
|
|
3933
4039
|
} catch (error) {
|
|
3934
4040
|
rmSync3(building, { recursive: true, force: true });
|
|
3935
4041
|
throw error instanceof StoreError ? error : new StoreError(gitError(error));
|
|
3936
4042
|
}
|
|
3937
|
-
return {
|
|
4043
|
+
return { path: target, created: true, fetchFailed: null };
|
|
3938
4044
|
}
|
|
3939
4045
|
async function hasRef(env, repo, ref) {
|
|
3940
4046
|
try {
|
|
@@ -3944,16 +4050,16 @@ async function hasRef(env, repo, ref) {
|
|
|
3944
4050
|
return false;
|
|
3945
4051
|
}
|
|
3946
4052
|
}
|
|
3947
|
-
async function remoteDefault(
|
|
4053
|
+
async function remoteDefault(repo, env) {
|
|
3948
4054
|
try {
|
|
3949
|
-
return await git(env,
|
|
4055
|
+
return await git(env, repo, "symbolic-ref", "--short", "refs/remotes/origin/HEAD");
|
|
3950
4056
|
} catch {
|
|
3951
4057
|
for (const name of ["main", "master"]) {
|
|
3952
|
-
if (await hasRef(env,
|
|
4058
|
+
if (await hasRef(env, repo, `refs/remotes/origin/${name}`)) return `origin/${name}`;
|
|
3953
4059
|
}
|
|
3954
4060
|
const first = await git(
|
|
3955
4061
|
env,
|
|
3956
|
-
|
|
4062
|
+
repo,
|
|
3957
4063
|
"for-each-ref",
|
|
3958
4064
|
"--count=1",
|
|
3959
4065
|
"--format=%(refname:short)",
|
|
@@ -3967,111 +4073,72 @@ async function addWorktree(options) {
|
|
|
3967
4073
|
const { root, remote, path, branch, env } = options;
|
|
3968
4074
|
const key = repoKey(remote);
|
|
3969
4075
|
if (!key) throw new StoreError(`${remote} isn't a repo's URL.`);
|
|
3970
|
-
const store = storePath(root, remote);
|
|
3971
4076
|
return withStoreLock(
|
|
3972
|
-
|
|
4077
|
+
checkoutLock(root, key),
|
|
3973
4078
|
async () => {
|
|
3974
|
-
const
|
|
4079
|
+
const checkout = await ensureCheckout(root, remote, env);
|
|
4080
|
+
const repo = checkout.path;
|
|
3975
4081
|
const common = {
|
|
3976
4082
|
path,
|
|
3977
4083
|
remote: key,
|
|
3978
4084
|
branch,
|
|
3979
|
-
fetchFailed:
|
|
3980
|
-
cloned:
|
|
4085
|
+
fetchFailed: checkout.fetchFailed,
|
|
4086
|
+
cloned: checkout.created,
|
|
4087
|
+
checkout: repo
|
|
3981
4088
|
};
|
|
3982
|
-
if (
|
|
4089
|
+
if (existsSync2(path)) {
|
|
3983
4090
|
return {
|
|
3984
4091
|
...common,
|
|
3985
|
-
base: await remoteDefault(
|
|
4092
|
+
base: await remoteDefault(repo, env).catch(() => "origin/HEAD"),
|
|
3986
4093
|
created: false,
|
|
4094
|
+
copied: [],
|
|
3987
4095
|
...options.continueFrom ? { continued: true } : {}
|
|
3988
4096
|
};
|
|
3989
4097
|
}
|
|
3990
|
-
|
|
3991
|
-
const own = await hasRef(env,
|
|
4098
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
4099
|
+
const own = await hasRef(env, repo, `refs/heads/${branch}`);
|
|
3992
4100
|
const pushed = options.continueFrom ? `origin/${options.continueFrom}` : null;
|
|
3993
|
-
|
|
4101
|
+
let base;
|
|
4102
|
+
let continued;
|
|
4103
|
+
if (pushed && await hasRef(env, repo, `refs/remotes/${pushed}`)) {
|
|
3994
4104
|
if (own) {
|
|
3995
|
-
await git(env,
|
|
4105
|
+
await git(env, repo, "worktree", "add", "--quiet", path, branch);
|
|
3996
4106
|
await git(env, path, "merge", "--quiet", "--ff-only", pushed).catch(() => "");
|
|
3997
4107
|
} else {
|
|
4108
|
+
await git(env, repo, "worktree", "add", "--quiet", "--track", "-b", branch, path, pushed);
|
|
4109
|
+
}
|
|
4110
|
+
base = pushed;
|
|
4111
|
+
continued = true;
|
|
4112
|
+
} else {
|
|
4113
|
+
base = await remoteDefault(repo, env);
|
|
4114
|
+
if (own) await git(env, repo, "worktree", "add", "--quiet", path, branch);
|
|
4115
|
+
else
|
|
3998
4116
|
await git(
|
|
3999
4117
|
env,
|
|
4000
|
-
|
|
4118
|
+
repo,
|
|
4001
4119
|
"worktree",
|
|
4002
4120
|
"add",
|
|
4003
4121
|
"--quiet",
|
|
4004
|
-
"--track",
|
|
4122
|
+
"--no-track",
|
|
4005
4123
|
"-b",
|
|
4006
4124
|
branch,
|
|
4007
4125
|
path,
|
|
4008
|
-
|
|
4126
|
+
base
|
|
4009
4127
|
);
|
|
4010
|
-
|
|
4011
|
-
return { ...common, base: pushed, created: true, continued: true };
|
|
4128
|
+
if (options.continueFrom) continued = false;
|
|
4012
4129
|
}
|
|
4013
|
-
const
|
|
4014
|
-
if (own) await git(env, store, "worktree", "add", "--quiet", path, branch);
|
|
4015
|
-
else
|
|
4016
|
-
await git(env, store, "worktree", "add", "--quiet", "--no-track", "-b", branch, path, base);
|
|
4130
|
+
const copied = await copyLocalFiles(repo, path, env);
|
|
4017
4131
|
return {
|
|
4018
4132
|
...common,
|
|
4019
4133
|
base,
|
|
4020
4134
|
created: true,
|
|
4021
|
-
|
|
4135
|
+
copied,
|
|
4136
|
+
...continued === void 0 ? {} : { continued }
|
|
4022
4137
|
};
|
|
4023
4138
|
},
|
|
4024
4139
|
options.lock
|
|
4025
4140
|
);
|
|
4026
4141
|
}
|
|
4027
|
-
function clonedRepos(root) {
|
|
4028
|
-
const found = [];
|
|
4029
|
-
const walk = (dir, depth) => {
|
|
4030
|
-
let entries;
|
|
4031
|
-
try {
|
|
4032
|
-
entries = readdirSync2(dir);
|
|
4033
|
-
} catch {
|
|
4034
|
-
return;
|
|
4035
|
-
}
|
|
4036
|
-
for (const name of entries.sort()) {
|
|
4037
|
-
const path = join4(dir, name);
|
|
4038
|
-
if (name.endsWith(".git")) {
|
|
4039
|
-
try {
|
|
4040
|
-
const remote = gitSync(path, "config", "--get", "remote.origin.url");
|
|
4041
|
-
const key = repoKey(remote);
|
|
4042
|
-
if (key) found.push({ key, remote, store: path });
|
|
4043
|
-
} catch {
|
|
4044
|
-
}
|
|
4045
|
-
} else if (depth < 8 && !name.includes(".tmp-") && !name.endsWith(".lock")) {
|
|
4046
|
-
try {
|
|
4047
|
-
if (statSync3(path).isDirectory()) walk(path, depth + 1);
|
|
4048
|
-
} catch {
|
|
4049
|
-
}
|
|
4050
|
-
}
|
|
4051
|
-
}
|
|
4052
|
-
};
|
|
4053
|
-
walk(reposDir(root), 0);
|
|
4054
|
-
return found;
|
|
4055
|
-
}
|
|
4056
|
-
function clonedStamp(root) {
|
|
4057
|
-
const names = [];
|
|
4058
|
-
const walk = (dir, depth) => {
|
|
4059
|
-
let entries;
|
|
4060
|
-
try {
|
|
4061
|
-
entries = readdirSync2(dir);
|
|
4062
|
-
} catch {
|
|
4063
|
-
return;
|
|
4064
|
-
}
|
|
4065
|
-
for (const name of entries) {
|
|
4066
|
-
if (name.endsWith(".git")) names.push(join4(dir, name));
|
|
4067
|
-
else if (depth < 8 && !name.includes(".tmp-") && !name.endsWith(".lock")) {
|
|
4068
|
-
walk(join4(dir, name), depth + 1);
|
|
4069
|
-
}
|
|
4070
|
-
}
|
|
4071
|
-
};
|
|
4072
|
-
walk(reposDir(root), 0);
|
|
4073
|
-
return names.sort().join("\n");
|
|
4074
|
-
}
|
|
4075
4142
|
function reposInFolder(folder) {
|
|
4076
4143
|
let entries;
|
|
4077
4144
|
try {
|
|
@@ -4082,8 +4149,8 @@ function reposInFolder(folder) {
|
|
|
4082
4149
|
const found = [];
|
|
4083
4150
|
for (const name of entries.sort()) {
|
|
4084
4151
|
if (name.startsWith(".")) continue;
|
|
4085
|
-
const path =
|
|
4086
|
-
if (!
|
|
4152
|
+
const path = join5(folder, name);
|
|
4153
|
+
if (!existsSync2(join5(path, ".git"))) continue;
|
|
4087
4154
|
try {
|
|
4088
4155
|
const remote = gitSync(path, "config", "--get", "remote.origin.url");
|
|
4089
4156
|
const key = repoKey(remote);
|
|
@@ -4097,8 +4164,8 @@ function reposInFolder(folder) {
|
|
|
4097
4164
|
}
|
|
4098
4165
|
var validFolderName = (name) => /^[A-Za-z0-9_][A-Za-z0-9._-]{0,99}$/.test(name) && !name.endsWith(".git");
|
|
4099
4166
|
function checkoutAt(path) {
|
|
4100
|
-
if (!
|
|
4101
|
-
if (!
|
|
4167
|
+
if (!existsSync2(path)) return { taken: false, key: null };
|
|
4168
|
+
if (!existsSync2(join5(path, ".git"))) return { taken: true, key: null };
|
|
4102
4169
|
try {
|
|
4103
4170
|
return {
|
|
4104
4171
|
taken: true,
|
|
@@ -4115,7 +4182,7 @@ function folderFor(conversation, key, asked) {
|
|
|
4115
4182
|
}
|
|
4116
4183
|
const candidates2 = asked ? [asked] : [repoName(key), `${repoOwner(key)}-${repoName(key)}`].filter(validFolderName);
|
|
4117
4184
|
for (const name of candidates2) {
|
|
4118
|
-
const at = checkoutAt(
|
|
4185
|
+
const at = checkoutAt(join5(conversation, name));
|
|
4119
4186
|
if (!at.taken) return { name, existing: false };
|
|
4120
4187
|
if (at.key === key) return { name, existing: true };
|
|
4121
4188
|
}
|
|
@@ -4138,9 +4205,9 @@ var configSchema = z18.object({
|
|
|
4138
4205
|
/** The machine's root folder, holding herd's clones and its conversations' folders. */
|
|
4139
4206
|
root: z18.string().optional()
|
|
4140
4207
|
});
|
|
4141
|
-
var configPath = () =>
|
|
4208
|
+
var configPath = () => join6(herdHome(), "config.json");
|
|
4142
4209
|
function save(config) {
|
|
4143
|
-
|
|
4210
|
+
mkdirSync4(herdHome(), { recursive: true, mode: 448 });
|
|
4144
4211
|
writeFileSync3(configPath(), JSON.stringify(config, null, 2), { mode: 384 });
|
|
4145
4212
|
return config;
|
|
4146
4213
|
}
|
|
@@ -4151,7 +4218,7 @@ function loadConfig() {
|
|
|
4151
4218
|
return save({ machineId: randomUUID(), machineName: hostname(), repos: [] });
|
|
4152
4219
|
}
|
|
4153
4220
|
}
|
|
4154
|
-
var defaultRoot = () =>
|
|
4221
|
+
var defaultRoot = () => join6(homedir3(), "friday");
|
|
4155
4222
|
function readRoot() {
|
|
4156
4223
|
try {
|
|
4157
4224
|
return configSchema.parse(JSON.parse(readFileSync4(configPath(), "utf8"))).root ?? null;
|
|
@@ -4162,21 +4229,39 @@ function readRoot() {
|
|
|
4162
4229
|
function setRoot(path) {
|
|
4163
4230
|
return save({ ...loadConfig(), root: resolve(path) });
|
|
4164
4231
|
}
|
|
4165
|
-
async function waitForRoot(root, {
|
|
4166
|
-
pollMs = 2e3,
|
|
4167
|
-
exists = existsSync2,
|
|
4168
|
-
sleep: sleep8 = (ms) => new Promise((done) => setTimeout(done, ms)),
|
|
4169
|
-
log = (line2) => console.log(line2)
|
|
4170
|
-
} = {}) {
|
|
4171
|
-
const parent = dirname3(root);
|
|
4172
|
-
if (exists(parent)) return;
|
|
4173
|
-
log(
|
|
4174
|
-
`Waiting for ${parent}, which holds this machine's root, to be there, as when its drive mounts.`
|
|
4175
|
-
);
|
|
4176
|
-
while (!exists(parent)) await sleep8(pollMs);
|
|
4177
|
-
log(`${parent} is there.`);
|
|
4178
|
-
}
|
|
4179
4232
|
var machineRoot = () => readRoot() ?? defaultRoot();
|
|
4233
|
+
var preferredConvos = () => join6(homedir3(), ".friday_convos");
|
|
4234
|
+
function workTreeOf(folder) {
|
|
4235
|
+
let dir = resolve(folder);
|
|
4236
|
+
while (!existsSync3(dir) && dirname4(dir) !== dir) dir = dirname4(dir);
|
|
4237
|
+
try {
|
|
4238
|
+
const top = execFileSync3("git", ["-C", dir, "rev-parse", "--show-toplevel"], {
|
|
4239
|
+
encoding: "utf8",
|
|
4240
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
4241
|
+
}).trim();
|
|
4242
|
+
return top || null;
|
|
4243
|
+
} catch {
|
|
4244
|
+
return null;
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4247
|
+
var convosChosen = /* @__PURE__ */ new Map();
|
|
4248
|
+
function convosPath(root = machineRoot()) {
|
|
4249
|
+
if (process.env.HERD_CONVOS) return resolve(process.env.HERD_CONVOS);
|
|
4250
|
+
const known = convosChosen.get(root);
|
|
4251
|
+
if (known) return known;
|
|
4252
|
+
const chosen = chooseConvos(root, preferredConvos(), workTreeOf);
|
|
4253
|
+
convosChosen.set(root, chosen);
|
|
4254
|
+
return chosen;
|
|
4255
|
+
}
|
|
4256
|
+
function convosHome(root = machineRoot()) {
|
|
4257
|
+
const path = convosPath(root);
|
|
4258
|
+
try {
|
|
4259
|
+
return realpathSync2(path);
|
|
4260
|
+
} catch {
|
|
4261
|
+
return path;
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
var chooseConvos = (root, preferred, inWorkTree) => inWorkTree(preferred) ? join6(root, ".friday_convos") : preferred;
|
|
4180
4265
|
function describeRepo(path) {
|
|
4181
4266
|
const full = resolve(path);
|
|
4182
4267
|
const git4 = (...args) => execFileSync3("git", ["-C", full, ...args], {
|
|
@@ -4241,7 +4326,8 @@ function registration(config, clis, facts = {}, cloned = []) {
|
|
|
4241
4326
|
...facts.tools ? { tools: facts.tools } : {},
|
|
4242
4327
|
...facts.config ? { config: facts.config } : {},
|
|
4243
4328
|
...facts.configError ? { configError: facts.configError.slice(0, 1e3) } : {},
|
|
4244
|
-
...facts.gh ? { gh: facts.gh } : {}
|
|
4329
|
+
...facts.gh ? { gh: facts.gh } : {},
|
|
4330
|
+
...facts.folderUnavailable ? { folderUnavailable: facts.folderUnavailable } : {}
|
|
4245
4331
|
};
|
|
4246
4332
|
}
|
|
4247
4333
|
function describeCli(name, version) {
|
|
@@ -4332,15 +4418,15 @@ async function readSetup(fetch2, init) {
|
|
|
4332
4418
|
// src/friday/agents.ts
|
|
4333
4419
|
init_define_HERD_PACKAGE();
|
|
4334
4420
|
init_src();
|
|
4335
|
-
import { existsSync as
|
|
4336
|
-
import { dirname as
|
|
4421
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4422
|
+
import { dirname as dirname6, join as join8 } from "node:path";
|
|
4337
4423
|
|
|
4338
4424
|
// src/activation.ts
|
|
4339
4425
|
init_define_HERD_PACKAGE();
|
|
4340
4426
|
init_src();
|
|
4341
4427
|
import { randomBytes as randomBytes4, randomUUID as randomUUID3 } from "node:crypto";
|
|
4342
|
-
import { mkdirSync as
|
|
4343
|
-
import { dirname as
|
|
4428
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4429
|
+
import { dirname as dirname5, join as join7 } from "node:path";
|
|
4344
4430
|
import { z as z21 } from "zod";
|
|
4345
4431
|
|
|
4346
4432
|
// src/permissions.ts
|
|
@@ -4352,7 +4438,7 @@ import { z as z20 } from "zod";
|
|
|
4352
4438
|
// src/local-socket.ts
|
|
4353
4439
|
init_define_HERD_PACKAGE();
|
|
4354
4440
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
4355
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
4441
|
+
import { chmodSync as chmodSync2, existsSync as existsSync4, rmSync as rmSync4 } from "node:fs";
|
|
4356
4442
|
import { connect, createServer as createServer2 } from "node:net";
|
|
4357
4443
|
import { z as z19 } from "zod";
|
|
4358
4444
|
var localRequestSchema = z19.object({
|
|
@@ -4485,11 +4571,11 @@ var LocalSocket = class {
|
|
|
4485
4571
|
}
|
|
4486
4572
|
}
|
|
4487
4573
|
async listen() {
|
|
4488
|
-
if (
|
|
4574
|
+
if (existsSync4(this.path)) rmSync4(this.path);
|
|
4489
4575
|
this.server = createServer2((socket) => this.serve(socket));
|
|
4490
|
-
await new Promise((
|
|
4576
|
+
await new Promise((resolve7, reject) => {
|
|
4491
4577
|
this.server?.once("error", reject);
|
|
4492
|
-
this.server?.listen(this.path, () =>
|
|
4578
|
+
this.server?.listen(this.path, () => resolve7());
|
|
4493
4579
|
});
|
|
4494
4580
|
chmodSync2(this.path, 384);
|
|
4495
4581
|
}
|
|
@@ -4501,7 +4587,7 @@ var LocalSocket = class {
|
|
|
4501
4587
|
async close() {
|
|
4502
4588
|
this.closing = true;
|
|
4503
4589
|
for (const socket of this.open) socket.destroy();
|
|
4504
|
-
await new Promise((
|
|
4590
|
+
await new Promise((resolve7) => this.server ? this.server.close(resolve7) : resolve7(null));
|
|
4505
4591
|
rmSync4(this.path, { force: true });
|
|
4506
4592
|
}
|
|
4507
4593
|
serve(socket) {
|
|
@@ -4548,7 +4634,7 @@ var LocalSocket = class {
|
|
|
4548
4634
|
}
|
|
4549
4635
|
};
|
|
4550
4636
|
function askDaemon(path, request) {
|
|
4551
|
-
return new Promise((
|
|
4637
|
+
return new Promise((resolve7, reject) => {
|
|
4552
4638
|
const socket = connect(path);
|
|
4553
4639
|
let data = "";
|
|
4554
4640
|
socket.on("connect", () => socket.write(`${JSON.stringify(request)}
|
|
@@ -4556,7 +4642,7 @@ function askDaemon(path, request) {
|
|
|
4556
4642
|
socket.on("data", (chunk) => data += chunk);
|
|
4557
4643
|
socket.on("end", () => {
|
|
4558
4644
|
try {
|
|
4559
|
-
|
|
4645
|
+
resolve7(JSON.parse(data));
|
|
4560
4646
|
} catch {
|
|
4561
4647
|
reject(new Error("The daemon's answer wasn't readable."));
|
|
4562
4648
|
}
|
|
@@ -4566,14 +4652,14 @@ function askDaemon(path, request) {
|
|
|
4566
4652
|
});
|
|
4567
4653
|
}
|
|
4568
4654
|
function isAnswering(path) {
|
|
4569
|
-
return new Promise((
|
|
4570
|
-
if (!
|
|
4655
|
+
return new Promise((resolve7) => {
|
|
4656
|
+
if (!existsSync4(path)) return resolve7(false);
|
|
4571
4657
|
const socket = connect(path);
|
|
4572
4658
|
socket.on("connect", () => {
|
|
4573
4659
|
socket.destroy();
|
|
4574
|
-
|
|
4660
|
+
resolve7(true);
|
|
4575
4661
|
});
|
|
4576
|
-
socket.on("error", () =>
|
|
4662
|
+
socket.on("error", () => resolve7(false));
|
|
4577
4663
|
});
|
|
4578
4664
|
}
|
|
4579
4665
|
|
|
@@ -4729,7 +4815,7 @@ async function permissionHook(raw, options = {}) {
|
|
|
4729
4815
|
} catch {
|
|
4730
4816
|
if (failingSince === null || Date.now() - started > 5e3) failingSince = Date.now();
|
|
4731
4817
|
if (Date.now() - failingSince >= reconnectMs) return "";
|
|
4732
|
-
await new Promise((
|
|
4818
|
+
await new Promise((resolve7) => setTimeout(resolve7, retryMs));
|
|
4733
4819
|
}
|
|
4734
4820
|
}
|
|
4735
4821
|
}
|
|
@@ -4746,14 +4832,14 @@ function newActivationCode() {
|
|
|
4746
4832
|
return [...randomBytes4(8)].map((byte) => CODE_LETTERS[byte % CODE_LETTERS.length]).join("");
|
|
4747
4833
|
}
|
|
4748
4834
|
var activationTerminal = (agent) => `herd-activate-${agentShortName(agent)}`;
|
|
4749
|
-
var activationFolder = (root, agent) =>
|
|
4835
|
+
var activationFolder = (root, agent) => join7(convosHome(root), ".activate", agentShortName(agent));
|
|
4750
4836
|
var recordSchema = agentActivationSchema.extend({
|
|
4751
4837
|
/** While it's waiting: the terminal it runs in, which `friday start` can attach to. */
|
|
4752
4838
|
terminal: z21.string().optional()
|
|
4753
4839
|
});
|
|
4754
4840
|
var storeSchema = z21.object({ agents: z21.record(z21.string(), recordSchema) });
|
|
4755
|
-
var activationsPath = (home = herdHome()) =>
|
|
4756
|
-
var activationRequestPath = (home = herdHome()) =>
|
|
4841
|
+
var activationsPath = (home = herdHome()) => join7(home, "activations.json");
|
|
4842
|
+
var activationRequestPath = (home = herdHome()) => join7(home, "activate-request");
|
|
4757
4843
|
function readActivations(path = activationsPath()) {
|
|
4758
4844
|
try {
|
|
4759
4845
|
return storeSchema.parse(JSON.parse(readFileSync5(path, "utf8"))).agents;
|
|
@@ -4762,7 +4848,7 @@ function readActivations(path = activationsPath()) {
|
|
|
4762
4848
|
}
|
|
4763
4849
|
}
|
|
4764
4850
|
function requestActivation(home = herdHome(), now = Date.now()) {
|
|
4765
|
-
|
|
4851
|
+
mkdirSync5(home, { recursive: true, mode: 448 });
|
|
4766
4852
|
writeFileSync4(activationRequestPath(home), String(now), { mode: 384 });
|
|
4767
4853
|
}
|
|
4768
4854
|
function needsActivation(record, version) {
|
|
@@ -4779,7 +4865,7 @@ function activateDecision(request, code) {
|
|
|
4779
4865
|
}
|
|
4780
4866
|
};
|
|
4781
4867
|
}
|
|
4782
|
-
var sleep = (ms) => new Promise((
|
|
4868
|
+
var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
4783
4869
|
var label = (agent) => AGENTS[agent]?.label ?? agent;
|
|
4784
4870
|
var Activator = class {
|
|
4785
4871
|
constructor(options) {
|
|
@@ -4807,7 +4893,7 @@ var Activator = class {
|
|
|
4807
4893
|
}
|
|
4808
4894
|
record(agent, record) {
|
|
4809
4895
|
const agents = { ...readActivations(this.path), [agent]: record };
|
|
4810
|
-
|
|
4896
|
+
mkdirSync5(dirname5(this.path), { recursive: true, mode: 448 });
|
|
4811
4897
|
const temporary = `${this.path}.${process.pid}`;
|
|
4812
4898
|
writeFileSync4(temporary, JSON.stringify({ agents }, null, 2), { mode: 384 });
|
|
4813
4899
|
renameSync2(temporary, this.path);
|
|
@@ -4901,7 +4987,7 @@ var Activator = class {
|
|
|
4901
4987
|
const terminal = activationTerminal(agent);
|
|
4902
4988
|
const folder = activationFolder(this.options.root(), agent);
|
|
4903
4989
|
rmSync5(folder, { recursive: true, force: true });
|
|
4904
|
-
|
|
4990
|
+
mkdirSync5(folder, { recursive: true });
|
|
4905
4991
|
const current = { agent, code, terminal, checkedIn: false, wake: () => {
|
|
4906
4992
|
} };
|
|
4907
4993
|
this.current = current;
|
|
@@ -4955,7 +5041,7 @@ var Activator = class {
|
|
|
4955
5041
|
const until = Date.now() + (this.options.killWaitMs ?? KILL_WAIT_MS);
|
|
4956
5042
|
while (await stillRunning()) {
|
|
4957
5043
|
if (Date.now() >= until) return;
|
|
4958
|
-
await new Promise((
|
|
5044
|
+
await new Promise((resolve7) => setTimeout(resolve7, Math.min(pollMs, 100)));
|
|
4959
5045
|
}
|
|
4960
5046
|
}
|
|
4961
5047
|
const shell = process.env.SHELL ?? "/bin/sh";
|
|
@@ -5009,11 +5095,11 @@ var Activator = class {
|
|
|
5009
5095
|
await start();
|
|
5010
5096
|
}
|
|
5011
5097
|
if (current.checkedIn) break;
|
|
5012
|
-
await new Promise((
|
|
5013
|
-
const timer = setTimeout(
|
|
5098
|
+
await new Promise((resolve7) => {
|
|
5099
|
+
const timer = setTimeout(resolve7, pollMs);
|
|
5014
5100
|
current.wake = () => {
|
|
5015
5101
|
clearTimeout(timer);
|
|
5016
|
-
|
|
5102
|
+
resolve7();
|
|
5017
5103
|
};
|
|
5018
5104
|
});
|
|
5019
5105
|
}
|
|
@@ -5087,8 +5173,8 @@ async function chooseAgents(options, print) {
|
|
|
5087
5173
|
const asked = options.given ? null : await (options.pick?.(choices) ?? Promise.resolve(null));
|
|
5088
5174
|
const shares = options.given ?? asked ?? // No terminal: what was chosen before, or what's installed.
|
|
5089
5175
|
Object.fromEntries(choices.map((choice) => [choice.cli, choice.share]));
|
|
5090
|
-
if (!sameChoice(saved, shares) || !
|
|
5091
|
-
|
|
5176
|
+
if (!sameChoice(saved, shares) || !existsSync5(options.configPath)) {
|
|
5177
|
+
mkdirSync6(dirname6(options.configPath), { recursive: true, mode: 448 });
|
|
5092
5178
|
writeFileSync5(options.configPath, configSource(withChoice(saved, shares)), { mode: 384 });
|
|
5093
5179
|
}
|
|
5094
5180
|
const chosen = ADAPTERS.map(({ cli }) => cli).filter((cli) => (shares[cli] ?? "off") !== "off");
|
|
@@ -5101,7 +5187,7 @@ function askToActivate(herdHome2, now = Date.now()) {
|
|
|
5101
5187
|
requestActivation(herdHome2, now);
|
|
5102
5188
|
return now;
|
|
5103
5189
|
}
|
|
5104
|
-
var sleep2 = (ms) => new Promise((
|
|
5190
|
+
var sleep2 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
5105
5191
|
function settledLine(cli, activation) {
|
|
5106
5192
|
if (activation.state === "activated") return `${label2(cli)} is activated.`;
|
|
5107
5193
|
return `${label2(cli)} ${activationText(cli, activation).replace(" there", " on this computer")}.`;
|
|
@@ -5156,7 +5242,7 @@ async function awaitActivations(options, chosen, since, print) {
|
|
|
5156
5242
|
}
|
|
5157
5243
|
return settled;
|
|
5158
5244
|
}
|
|
5159
|
-
var herdConfigIn = (herdHome2) =>
|
|
5245
|
+
var herdConfigIn = (herdHome2) => join8(herdHome2, "herd.config.ts");
|
|
5160
5246
|
|
|
5161
5247
|
// src/friday/home.ts
|
|
5162
5248
|
init_define_HERD_PACKAGE();
|
|
@@ -5164,33 +5250,33 @@ init_src();
|
|
|
5164
5250
|
import { spawnSync } from "node:child_process";
|
|
5165
5251
|
import {
|
|
5166
5252
|
closeSync as closeSync2,
|
|
5167
|
-
existsSync as
|
|
5168
|
-
mkdirSync as
|
|
5253
|
+
existsSync as existsSync6,
|
|
5254
|
+
mkdirSync as mkdirSync7,
|
|
5169
5255
|
openSync as openSync2,
|
|
5170
5256
|
readFileSync as readFileSync6,
|
|
5171
5257
|
rmSync as rmSync6,
|
|
5172
5258
|
statSync as statSync5,
|
|
5173
5259
|
writeFileSync as writeFileSync6
|
|
5174
5260
|
} from "node:fs";
|
|
5175
|
-
import { dirname as
|
|
5261
|
+
import { dirname as dirname7, join as join9, resolve as resolve2 } from "node:path";
|
|
5176
5262
|
var PACKAGE_NAME = FRIDAY_PACKAGE;
|
|
5177
5263
|
function fridayPaths(herdHome2) {
|
|
5178
|
-
const logs =
|
|
5264
|
+
const logs = join9(herdHome2, "logs");
|
|
5179
5265
|
return {
|
|
5180
5266
|
herdHome: herdHome2,
|
|
5181
|
-
config:
|
|
5182
|
-
versions:
|
|
5183
|
-
current:
|
|
5184
|
-
pin:
|
|
5267
|
+
config: join9(herdHome2, "config.json"),
|
|
5268
|
+
versions: join9(herdHome2, "versions"),
|
|
5269
|
+
current: join9(herdHome2, "current"),
|
|
5270
|
+
pin: join9(herdHome2, "pin"),
|
|
5185
5271
|
logs,
|
|
5186
|
-
daemonLog:
|
|
5187
|
-
supervisorLog:
|
|
5188
|
-
updateLock:
|
|
5189
|
-
status:
|
|
5190
|
-
terminals:
|
|
5272
|
+
daemonLog: join9(logs, "daemon.log"),
|
|
5273
|
+
supervisorLog: join9(logs, "supervisor.log"),
|
|
5274
|
+
updateLock: join9(herdHome2, "update.lock"),
|
|
5275
|
+
status: join9(herdHome2, "supervisor.json"),
|
|
5276
|
+
terminals: join9(herdHome2, "terminals")
|
|
5191
5277
|
};
|
|
5192
5278
|
}
|
|
5193
|
-
var packageDir = (versionDir) =>
|
|
5279
|
+
var packageDir = (versionDir) => join9(versionDir, "node_modules", ...PACKAGE_NAME.split("/"));
|
|
5194
5280
|
function alive2(pid) {
|
|
5195
5281
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
5196
5282
|
try {
|
|
@@ -5201,8 +5287,8 @@ function alive2(pid) {
|
|
|
5201
5287
|
}
|
|
5202
5288
|
}
|
|
5203
5289
|
function takeLock(path, staleMs = 10 * 6e4) {
|
|
5204
|
-
|
|
5205
|
-
if (
|
|
5290
|
+
mkdirSync7(dirname7(path), { recursive: true });
|
|
5291
|
+
if (existsSync6(path)) {
|
|
5206
5292
|
const holder2 = Number(readFileSync6(path, "utf8").trim());
|
|
5207
5293
|
const old = Date.now() - statSync5(path).mtimeMs > staleMs;
|
|
5208
5294
|
if (old || !alive2(holder2)) rmSync6(path, { force: true });
|
|
@@ -5224,7 +5310,7 @@ function readStatus(paths) {
|
|
|
5224
5310
|
}
|
|
5225
5311
|
}
|
|
5226
5312
|
function writeStatus(paths, status2) {
|
|
5227
|
-
|
|
5313
|
+
mkdirSync7(paths.herdHome, { recursive: true, mode: 448 });
|
|
5228
5314
|
writeFileSync6(paths.status, `${JSON.stringify(status2, null, 2)}
|
|
5229
5315
|
`);
|
|
5230
5316
|
}
|
|
@@ -5247,12 +5333,12 @@ function writeRoot(paths, root) {
|
|
|
5247
5333
|
} catch {
|
|
5248
5334
|
config = {};
|
|
5249
5335
|
}
|
|
5250
|
-
|
|
5336
|
+
mkdirSync7(paths.herdHome, { recursive: true, mode: 448 });
|
|
5251
5337
|
writeFileSync6(paths.config, JSON.stringify({ ...config, root }, null, 2), { mode: 384 });
|
|
5252
5338
|
}
|
|
5253
5339
|
function insideWorkTree(folder) {
|
|
5254
5340
|
let dir = resolve2(folder);
|
|
5255
|
-
while (!
|
|
5341
|
+
while (!existsSync6(dir) && dirname7(dir) !== dir) dir = dirname7(dir);
|
|
5256
5342
|
const result = spawnSync("git", ["-C", dir, "rev-parse", "--show-toplevel"], {
|
|
5257
5343
|
encoding: "utf8",
|
|
5258
5344
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -5316,7 +5402,7 @@ function pickInTerminal(choices, input = process.stdin, output2 = process.stdout
|
|
|
5316
5402
|
for (const line2 of lines) output2.write(`\x1B[2K${line2}
|
|
5317
5403
|
`);
|
|
5318
5404
|
};
|
|
5319
|
-
return new Promise((
|
|
5405
|
+
return new Promise((resolve7) => {
|
|
5320
5406
|
const wasRaw = input.isRaw;
|
|
5321
5407
|
input.setRawMode(true);
|
|
5322
5408
|
input.resume();
|
|
@@ -5325,7 +5411,7 @@ function pickInTerminal(choices, input = process.stdin, output2 = process.stdout
|
|
|
5325
5411
|
input.off("data", onData);
|
|
5326
5412
|
input.setRawMode(wasRaw);
|
|
5327
5413
|
input.pause();
|
|
5328
|
-
|
|
5414
|
+
resolve7(shares);
|
|
5329
5415
|
};
|
|
5330
5416
|
const onData = (data) => {
|
|
5331
5417
|
const next = pickerKey(state, data.toString("utf8"));
|
|
@@ -5340,25 +5426,27 @@ function pickInTerminal(choices, input = process.stdin, output2 = process.stdout
|
|
|
5340
5426
|
|
|
5341
5427
|
// src/friday/service.ts
|
|
5342
5428
|
init_define_HERD_PACKAGE();
|
|
5343
|
-
import { spawn as
|
|
5344
|
-
import { existsSync as
|
|
5429
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
5430
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync8, openSync as openSync3, readFileSync as readFileSync7, rmSync as rmSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
5345
5431
|
import { userInfo } from "node:os";
|
|
5346
|
-
import { dirname as
|
|
5432
|
+
import { dirname as dirname8, join as join10, win32 } from "node:path";
|
|
5433
|
+
var systemdBooted = () => existsSync7("/run/systemd/system");
|
|
5347
5434
|
var LAUNCH_AGENT = "com.herdingchats.friday";
|
|
5348
5435
|
var SYSTEMD_UNIT = "herdingchats-friday.service";
|
|
5349
|
-
async function detectService(run4, platform2 = process.platform, override = process.env.HERD_SERVICE) {
|
|
5436
|
+
async function detectService(run4, platform2 = process.platform, override = process.env.HERD_SERVICE, booted = systemdBooted) {
|
|
5350
5437
|
if (override === "launchd" || override === "systemd" || override === "background")
|
|
5351
5438
|
return override;
|
|
5352
5439
|
if (platform2 === "darwin") return "launchd";
|
|
5353
|
-
if (platform2 === "linux") {
|
|
5440
|
+
if (platform2 === "linux" && booted()) {
|
|
5354
5441
|
const probe = await run4("systemctl", ["--user", "show-environment"], { timeoutMs: 1e4 });
|
|
5355
5442
|
if (probe.status === 0) return "systemd";
|
|
5356
5443
|
}
|
|
5357
5444
|
return "background";
|
|
5358
5445
|
}
|
|
5359
|
-
function servicePath(node) {
|
|
5446
|
+
function servicePath(node, platform2 = process.platform, inherited = process.env.PATH ?? process.env.Path) {
|
|
5447
|
+
if (platform2 === "win32") return [win32.dirname(node), inherited].filter(Boolean).join(";");
|
|
5360
5448
|
return [
|
|
5361
|
-
|
|
5449
|
+
dirname8(node),
|
|
5362
5450
|
"/opt/homebrew/bin",
|
|
5363
5451
|
"/usr/local/bin",
|
|
5364
5452
|
"/usr/bin",
|
|
@@ -5429,13 +5517,13 @@ WantedBy=default.target
|
|
|
5429
5517
|
`;
|
|
5430
5518
|
}
|
|
5431
5519
|
function writeIfChanged(path, text4) {
|
|
5432
|
-
if (
|
|
5433
|
-
|
|
5520
|
+
if (existsSync7(path) && readFileSync7(path, "utf8") === text4) return false;
|
|
5521
|
+
mkdirSync8(dirname8(path), { recursive: true });
|
|
5434
5522
|
writeFileSync7(path, text4);
|
|
5435
5523
|
return true;
|
|
5436
5524
|
}
|
|
5437
5525
|
function launchd(context2) {
|
|
5438
|
-
const plist =
|
|
5526
|
+
const plist = join10(context2.home, "Library", "LaunchAgents", `${LAUNCH_AGENT}.plist`);
|
|
5439
5527
|
const domain = `gui/${userInfo().uid}`;
|
|
5440
5528
|
const target = `${domain}/${LAUNCH_AGENT}`;
|
|
5441
5529
|
const loaded = async () => (await context2.run("launchctl", ["print", target])).status === 0;
|
|
@@ -5461,7 +5549,7 @@ function launchd(context2) {
|
|
|
5461
5549
|
};
|
|
5462
5550
|
}
|
|
5463
5551
|
function systemd(context2) {
|
|
5464
|
-
const unit =
|
|
5552
|
+
const unit = join10(context2.home, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
5465
5553
|
const systemctl = async (...args) => {
|
|
5466
5554
|
const ran = await context2.run("systemctl", ["--user", ...args]);
|
|
5467
5555
|
if (ran.status !== 0)
|
|
@@ -5485,15 +5573,26 @@ function systemd(context2) {
|
|
|
5485
5573
|
};
|
|
5486
5574
|
}
|
|
5487
5575
|
function spawnInBackground(command, env, log) {
|
|
5488
|
-
|
|
5576
|
+
mkdirSync8(dirname8(log), { recursive: true });
|
|
5489
5577
|
const out = openSync3(log, "a");
|
|
5490
5578
|
const [program = process.execPath, ...args] = command;
|
|
5491
|
-
|
|
5579
|
+
spawn3(program, args, {
|
|
5492
5580
|
detached: true,
|
|
5581
|
+
// On Windows, a detached process otherwise opens a console window of its own.
|
|
5582
|
+
windowsHide: true,
|
|
5493
5583
|
stdio: ["ignore", out, out],
|
|
5494
|
-
env:
|
|
5584
|
+
env: mergeEnv(process.env, env)
|
|
5495
5585
|
}).unref();
|
|
5496
5586
|
}
|
|
5587
|
+
function mergeEnv(base, extra) {
|
|
5588
|
+
const replaced = new Set(Object.keys(extra).map((key) => key.toUpperCase()));
|
|
5589
|
+
const merged = {};
|
|
5590
|
+
for (const [key, value] of Object.entries(base)) {
|
|
5591
|
+
if (value !== void 0 && !(key.toUpperCase() === "PATH" && replaced.has("PATH")))
|
|
5592
|
+
merged[key] = value;
|
|
5593
|
+
}
|
|
5594
|
+
return { ...merged, ...extra };
|
|
5595
|
+
}
|
|
5497
5596
|
function background(context2) {
|
|
5498
5597
|
const launch = context2.spawnBackground ?? spawnInBackground;
|
|
5499
5598
|
const supervisor = () => {
|
|
@@ -5529,25 +5628,25 @@ function serviceFor(kind, context2) {
|
|
|
5529
5628
|
// src/friday/start.ts
|
|
5530
5629
|
init_define_HERD_PACKAGE();
|
|
5531
5630
|
init_src();
|
|
5532
|
-
import { mkdirSync as
|
|
5533
|
-
import { join as
|
|
5631
|
+
import { mkdirSync as mkdirSync10, realpathSync as realpathSync4 } from "node:fs";
|
|
5632
|
+
import { join as join13, resolve as resolve3 } from "node:path";
|
|
5534
5633
|
|
|
5535
5634
|
// src/friday/legacy.ts
|
|
5536
5635
|
init_define_HERD_PACKAGE();
|
|
5537
|
-
import { existsSync as
|
|
5636
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, rmSync as rmSync8 } from "node:fs";
|
|
5538
5637
|
import { userInfo as userInfo2 } from "node:os";
|
|
5539
|
-
import { join as
|
|
5638
|
+
import { join as join11 } from "node:path";
|
|
5540
5639
|
var OLD_LAUNCH_AGENT = "com.herdingchats.herd";
|
|
5541
5640
|
var OLD_APPS = ["herd-daemon", "herd-updater"];
|
|
5542
5641
|
function oldPm2s(paths) {
|
|
5543
|
-
const dir =
|
|
5544
|
-
if (!
|
|
5545
|
-
return readdirSync3(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) =>
|
|
5642
|
+
const dir = join11(paths.herdHome, "daemon");
|
|
5643
|
+
if (!existsSync8(dir)) return [];
|
|
5644
|
+
return readdirSync3(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join11(dir, entry.name, "node_modules", "pm2", "bin", "pm2")).filter((pm2) => existsSync8(pm2));
|
|
5546
5645
|
}
|
|
5547
5646
|
async function retireOldDaemon(paths, { home, run: run4 }) {
|
|
5548
5647
|
const removed = [];
|
|
5549
|
-
const plist =
|
|
5550
|
-
if (
|
|
5648
|
+
const plist = join11(home, "Library", "LaunchAgents", `${OLD_LAUNCH_AGENT}.plist`);
|
|
5649
|
+
if (existsSync8(plist)) {
|
|
5551
5650
|
await run4("launchctl", ["bootout", `gui/${userInfo2().uid}/${OLD_LAUNCH_AGENT}`]);
|
|
5552
5651
|
rmSync8(plist, { force: true });
|
|
5553
5652
|
removed.push(`the login item ${OLD_LAUNCH_AGENT}`);
|
|
@@ -5571,24 +5670,24 @@ async function retireOldDaemon(paths, { home, run: run4 }) {
|
|
|
5571
5670
|
|
|
5572
5671
|
// src/friday/versions.ts
|
|
5573
5672
|
init_define_HERD_PACKAGE();
|
|
5574
|
-
import { spawn as
|
|
5673
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
5575
5674
|
import {
|
|
5576
|
-
existsSync as
|
|
5577
|
-
mkdirSync as
|
|
5675
|
+
existsSync as existsSync9,
|
|
5676
|
+
mkdirSync as mkdirSync9,
|
|
5578
5677
|
readdirSync as readdirSync4,
|
|
5579
5678
|
readFileSync as readFileSync8,
|
|
5580
5679
|
readlinkSync,
|
|
5581
|
-
realpathSync as
|
|
5680
|
+
realpathSync as realpathSync3,
|
|
5582
5681
|
renameSync as renameSync3,
|
|
5583
5682
|
rmSync as rmSync9,
|
|
5584
5683
|
symlinkSync,
|
|
5585
5684
|
writeFileSync as writeFileSync8
|
|
5586
5685
|
} from "node:fs";
|
|
5587
|
-
import { basename as basename2, dirname as
|
|
5588
|
-
var exec = (command, args, options = {}) => new Promise((
|
|
5686
|
+
import { basename as basename2, dirname as dirname9, join as join12 } from "node:path";
|
|
5687
|
+
var exec = (command, args, options = {}) => new Promise((resolve7) => {
|
|
5589
5688
|
let stdout = "";
|
|
5590
5689
|
let stderr = "";
|
|
5591
|
-
const child =
|
|
5690
|
+
const child = spawn4(command, args, {
|
|
5592
5691
|
cwd: options.cwd,
|
|
5593
5692
|
env: options.env ?? process.env,
|
|
5594
5693
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5598,16 +5697,16 @@ var exec = (command, args, options = {}) => new Promise((resolve6) => {
|
|
|
5598
5697
|
child.stderr.on("data", (data) => stderr += data);
|
|
5599
5698
|
child.on("error", (error) => {
|
|
5600
5699
|
if (timer) clearTimeout(timer);
|
|
5601
|
-
|
|
5700
|
+
resolve7({ status: 127, stdout, stderr: `${stderr}${error.message}` });
|
|
5602
5701
|
});
|
|
5603
5702
|
child.on("close", (status2) => {
|
|
5604
5703
|
if (timer) clearTimeout(timer);
|
|
5605
|
-
|
|
5704
|
+
resolve7({ status: status2 ?? 1, stdout, stderr });
|
|
5606
5705
|
});
|
|
5607
5706
|
});
|
|
5608
5707
|
function npmCommand() {
|
|
5609
|
-
const beside =
|
|
5610
|
-
return
|
|
5708
|
+
const beside = join12(dirname9(process.execPath), "npm");
|
|
5709
|
+
return existsSync9(beside) ? beside : "npm";
|
|
5611
5710
|
}
|
|
5612
5711
|
function currentVersion(paths) {
|
|
5613
5712
|
try {
|
|
@@ -5617,10 +5716,10 @@ function currentVersion(paths) {
|
|
|
5617
5716
|
}
|
|
5618
5717
|
}
|
|
5619
5718
|
function installedVersions(paths) {
|
|
5620
|
-
if (!
|
|
5719
|
+
if (!existsSync9(paths.versions)) return [];
|
|
5621
5720
|
return readdirSync4(paths.versions).filter((name) => VERSION.test(name)).sort(compareVersions);
|
|
5622
5721
|
}
|
|
5623
|
-
var binOf = (versionDir, bin) =>
|
|
5722
|
+
var binOf = (versionDir, bin) => join12(packageDir(versionDir), `${bin}.mjs`);
|
|
5624
5723
|
async function versionRuns(versionDir, version, run4 = exec) {
|
|
5625
5724
|
const ran = await run4(process.execPath, [binOf(versionDir, "herd"), "--version"], {
|
|
5626
5725
|
timeoutMs: 3e4
|
|
@@ -5633,7 +5732,7 @@ async function versionRuns(versionDir, version, run4 = exec) {
|
|
|
5633
5732
|
}
|
|
5634
5733
|
function recordedIntegrity(dir) {
|
|
5635
5734
|
try {
|
|
5636
|
-
const lock = JSON.parse(readFileSync8(
|
|
5735
|
+
const lock = JSON.parse(readFileSync8(join12(dir, "package-lock.json"), "utf8"));
|
|
5637
5736
|
return lock.packages?.[`node_modules/${PACKAGE_NAME}`]?.integrity ?? null;
|
|
5638
5737
|
} catch {
|
|
5639
5738
|
return null;
|
|
@@ -5647,8 +5746,8 @@ function npmFreeEnv() {
|
|
|
5647
5746
|
async function installVersion(paths, version, { spec, integrity, run: run4 = exec, env = {}, log = () => {
|
|
5648
5747
|
} } = {}) {
|
|
5649
5748
|
if (!VERSION.test(version)) return { ok: false, reason: `${version} isn't a version.` };
|
|
5650
|
-
const dir =
|
|
5651
|
-
if (
|
|
5749
|
+
const dir = join12(paths.versions, version);
|
|
5750
|
+
if (existsSync9(dir)) {
|
|
5652
5751
|
const installed2 = recordedIntegrity(dir);
|
|
5653
5752
|
if (integrity && installed2 !== integrity)
|
|
5654
5753
|
return { ok: false, reason: `the installed ${version} doesn't have the integrity named.` };
|
|
@@ -5657,12 +5756,12 @@ async function installVersion(paths, version, { spec, integrity, run: run4 = exe
|
|
|
5657
5756
|
return { ok: false, reason: `the installed ${version} doesn't run: ${broken2}.` };
|
|
5658
5757
|
}
|
|
5659
5758
|
sweepStaging(paths);
|
|
5660
|
-
const named =
|
|
5759
|
+
const named = join12(paths.versions, `.staging-${version}-${process.pid}`);
|
|
5661
5760
|
rmSync9(named, { recursive: true, force: true });
|
|
5662
|
-
|
|
5663
|
-
const staging =
|
|
5761
|
+
mkdirSync9(named, { recursive: true });
|
|
5762
|
+
const staging = realpathSync3(named);
|
|
5664
5763
|
writeFileSync8(
|
|
5665
|
-
|
|
5764
|
+
join12(staging, "package.json"),
|
|
5666
5765
|
`${JSON.stringify({ private: true, allowScripts: { "node-pty": true } }, null, 2)}
|
|
5667
5766
|
`
|
|
5668
5767
|
);
|
|
@@ -5697,21 +5796,21 @@ async function installVersion(paths, version, { spec, integrity, run: run4 = exe
|
|
|
5697
5796
|
}
|
|
5698
5797
|
const broken = await versionRuns(staging, version, run4);
|
|
5699
5798
|
if (broken) return fail(broken);
|
|
5700
|
-
renameSync3(staging,
|
|
5799
|
+
renameSync3(staging, join12(realpathSync3(paths.versions), version));
|
|
5701
5800
|
return { ok: true, dir };
|
|
5702
5801
|
}
|
|
5703
5802
|
function sweepStaging(paths) {
|
|
5704
|
-
if (!
|
|
5803
|
+
if (!existsSync9(paths.versions)) return;
|
|
5705
5804
|
for (const name of readdirSync4(paths.versions)) {
|
|
5706
5805
|
const match = /^\.staging-.+-(\d+)$/.exec(name);
|
|
5707
5806
|
if (match && !alive2(Number(match[1])))
|
|
5708
|
-
rmSync9(
|
|
5807
|
+
rmSync9(join12(paths.versions, name), { recursive: true, force: true });
|
|
5709
5808
|
}
|
|
5710
5809
|
}
|
|
5711
5810
|
function activate(paths, version) {
|
|
5712
5811
|
const next = `${paths.current}.next-${process.pid}`;
|
|
5713
5812
|
rmSync9(next, { force: true });
|
|
5714
|
-
symlinkSync(
|
|
5813
|
+
symlinkSync(join12("versions", version), next);
|
|
5715
5814
|
renameSync3(next, paths.current);
|
|
5716
5815
|
}
|
|
5717
5816
|
function pruneVersions(paths, { inUse = [], keep = 3 } = {}) {
|
|
@@ -5726,7 +5825,7 @@ function pruneVersions(paths, { inUse = [], keep = 3 } = {}) {
|
|
|
5726
5825
|
const removed = [];
|
|
5727
5826
|
for (const version of versions) {
|
|
5728
5827
|
if (kept.has(version)) continue;
|
|
5729
|
-
rmSync9(
|
|
5828
|
+
rmSync9(join12(paths.versions, version), { recursive: true, force: true });
|
|
5730
5829
|
removed.push(version);
|
|
5731
5830
|
}
|
|
5732
5831
|
return removed;
|
|
@@ -5736,7 +5835,7 @@ async function hostVersions(paths, run4 = exec) {
|
|
|
5736
5835
|
if (ran.status !== 0) return installedVersions(paths);
|
|
5737
5836
|
const roots = /* @__PURE__ */ new Set([paths.versions]);
|
|
5738
5837
|
try {
|
|
5739
|
-
roots.add(
|
|
5838
|
+
roots.add(realpathSync3(paths.versions));
|
|
5740
5839
|
} catch {
|
|
5741
5840
|
}
|
|
5742
5841
|
const found = /* @__PURE__ */ new Set();
|
|
@@ -5774,13 +5873,13 @@ async function missingBuildTools(run4) {
|
|
|
5774
5873
|
}
|
|
5775
5874
|
var real = (path) => {
|
|
5776
5875
|
try {
|
|
5777
|
-
return
|
|
5876
|
+
return realpathSync4(path);
|
|
5778
5877
|
} catch {
|
|
5779
5878
|
return resolve3(path);
|
|
5780
5879
|
}
|
|
5781
5880
|
};
|
|
5782
5881
|
function chooseRoot(cwd, home) {
|
|
5783
|
-
return real(cwd) === real(home) ?
|
|
5882
|
+
return real(cwd) === real(home) ? join13(home, "friday") : resolve3(cwd);
|
|
5784
5883
|
}
|
|
5785
5884
|
async function runStart(options) {
|
|
5786
5885
|
const { paths, run: run4 } = options;
|
|
@@ -5790,7 +5889,7 @@ async function runStart(options) {
|
|
|
5790
5889
|
const wanted = chooseRoot(options.cwd, options.home);
|
|
5791
5890
|
const running = runningSupervisor(paths);
|
|
5792
5891
|
if (existing && real(existing) !== real(wanted)) {
|
|
5793
|
-
print(`This machine's root is ${existing},
|
|
5892
|
+
print(`This machine's root is ${existing}, which holds the repos Friday works on.`);
|
|
5794
5893
|
if (running) {
|
|
5795
5894
|
print(`Friday is running there, version ${running.version}, so nothing changed.`);
|
|
5796
5895
|
await waitForAgents(options, await chooseAndAsk(options, print), print);
|
|
@@ -5808,7 +5907,7 @@ async function runStart(options) {
|
|
|
5808
5907
|
const repo = insideWorkTree(root);
|
|
5809
5908
|
if (repo) {
|
|
5810
5909
|
throw new StartError(
|
|
5811
|
-
`${root} is inside the git repo ${repo}, so it can't be the machine's root: every conversation would load that repo's settings. Run ${START_COMMAND} in
|
|
5910
|
+
`${root} is inside the git repo ${repo}, so it can't be the machine's root: every conversation would load that repo's settings. Run ${START_COMMAND} in a folder outside any repo, such as the one that holds your checkouts, like ~/code.`
|
|
5812
5911
|
);
|
|
5813
5912
|
}
|
|
5814
5913
|
if ((options.platform ?? process.platform) === "linux") {
|
|
@@ -5830,7 +5929,7 @@ async function runStart(options) {
|
|
|
5830
5929
|
`Stopped the old daemon: removed ${retired.join(" and ")}. Its checkout is left as it was.`
|
|
5831
5930
|
);
|
|
5832
5931
|
await ensureInstalled(options, print);
|
|
5833
|
-
|
|
5932
|
+
mkdirSync10(root, { recursive: true });
|
|
5834
5933
|
writeRoot(paths, root);
|
|
5835
5934
|
const command = [process.execPath, binOf(paths.current, "friday"), "supervise"];
|
|
5836
5935
|
const env = { HERD_HOME: paths.herdHome, ...options.env };
|
|
@@ -5908,14 +6007,14 @@ async function ensureInstalled(options, print) {
|
|
|
5908
6007
|
// src/friday/supervisor.ts
|
|
5909
6008
|
init_define_HERD_PACKAGE();
|
|
5910
6009
|
init_src();
|
|
5911
|
-
import { spawn as
|
|
6010
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
5912
6011
|
import {
|
|
5913
6012
|
closeSync as closeSync3,
|
|
5914
|
-
existsSync as
|
|
5915
|
-
mkdirSync as
|
|
6013
|
+
existsSync as existsSync10,
|
|
6014
|
+
mkdirSync as mkdirSync11,
|
|
5916
6015
|
openSync as openSync4,
|
|
5917
6016
|
readFileSync as readFileSync9,
|
|
5918
|
-
realpathSync as
|
|
6017
|
+
realpathSync as realpathSync5,
|
|
5919
6018
|
renameSync as renameSync4,
|
|
5920
6019
|
statSync as statSync6
|
|
5921
6020
|
} from "node:fs";
|
|
@@ -5985,7 +6084,7 @@ var Supervisor = class {
|
|
|
5985
6084
|
if (this.options.daemonCommand) return this.options.daemonCommand();
|
|
5986
6085
|
let dir;
|
|
5987
6086
|
try {
|
|
5988
|
-
dir =
|
|
6087
|
+
dir = realpathSync5(this.options.paths.current);
|
|
5989
6088
|
} catch {
|
|
5990
6089
|
throw new Error(`Nothing is installed in ~/.herd/current. Run ${START_COMMAND}.`);
|
|
5991
6090
|
}
|
|
@@ -5994,16 +6093,16 @@ var Supervisor = class {
|
|
|
5994
6093
|
spawnDaemon() {
|
|
5995
6094
|
if (this.stopping) return;
|
|
5996
6095
|
const { daemonLog, logs } = this.options.paths;
|
|
5997
|
-
|
|
6096
|
+
mkdirSync11(logs, { recursive: true });
|
|
5998
6097
|
try {
|
|
5999
|
-
if (
|
|
6098
|
+
if (existsSync10(daemonLog) && statSync6(daemonLog).size > MAX_LOG_BYTES2)
|
|
6000
6099
|
renameSync4(daemonLog, `${daemonLog}.1`);
|
|
6001
6100
|
} catch {
|
|
6002
6101
|
}
|
|
6003
6102
|
const out = openSync4(daemonLog, "a");
|
|
6004
6103
|
const [program = process.execPath, ...args] = this.daemonCommand();
|
|
6005
6104
|
const startedAt = Date.now();
|
|
6006
|
-
const child =
|
|
6105
|
+
const child = spawn5(program, args, { stdio: ["ignore", out, out], env: process.env });
|
|
6007
6106
|
closeSync3(out);
|
|
6008
6107
|
this.daemon = child;
|
|
6009
6108
|
this.status.daemonPid = child.pid ?? null;
|
|
@@ -6027,7 +6126,7 @@ var Supervisor = class {
|
|
|
6027
6126
|
async stopDaemon() {
|
|
6028
6127
|
const child = this.daemon;
|
|
6029
6128
|
if (!child || child.exitCode !== null || child.signalCode !== null) return;
|
|
6030
|
-
const exited = new Promise((
|
|
6129
|
+
const exited = new Promise((resolve7) => child.once("exit", () => resolve7()));
|
|
6031
6130
|
child.kill("SIGTERM");
|
|
6032
6131
|
const timer = setTimeout(() => child.kill("SIGKILL"), this.options.stopMs ?? 15e3);
|
|
6033
6132
|
await exited;
|
|
@@ -6134,8 +6233,8 @@ var HELP = `friday: run HerdingChats' agent daemon on this computer.
|
|
|
6134
6233
|
|
|
6135
6234
|
Usage: ${FRIDAY_COMMAND} <command>
|
|
6136
6235
|
|
|
6137
|
-
start Make this computer a machine, with the folder this runs in as its root,
|
|
6138
|
-
|
|
6236
|
+
start Make this computer a machine, with the folder this runs in as its root, which
|
|
6237
|
+
holds your repos, such as ~/code. Signs in through your browser when
|
|
6139
6238
|
needed, asks how much of each agent you have, starts the daemon now and whenever
|
|
6140
6239
|
you log in, waits for each agent to check in, then shows what's left to set up.
|
|
6141
6240
|
Safe to run again. --agents claude=lot,codex=little sets the shares without
|
|
@@ -6149,7 +6248,7 @@ Agents use the herd command, which comes with this one. herd setup shows what's
|
|
|
6149
6248
|
function defaults() {
|
|
6150
6249
|
const home = homedir4();
|
|
6151
6250
|
return {
|
|
6152
|
-
paths: fridayPaths(process.env.HERD_HOME ??
|
|
6251
|
+
paths: fridayPaths(process.env.HERD_HOME ?? join14(home, ".herd")),
|
|
6153
6252
|
home,
|
|
6154
6253
|
cwd: process.cwd(),
|
|
6155
6254
|
run: exec,
|
|
@@ -6259,12 +6358,12 @@ function agentsFlag(args) {
|
|
|
6259
6358
|
return parsed.shares;
|
|
6260
6359
|
}
|
|
6261
6360
|
function attachTo(paths, terminal) {
|
|
6262
|
-
const herd =
|
|
6263
|
-
const child =
|
|
6361
|
+
const herd = existsSync11(binOf(paths.current, "herd")) ? binOf(paths.current, "herd") : join14(dirname10(process.argv[1] ?? ""), "herd.mjs");
|
|
6362
|
+
const child = spawn6(process.execPath, [herd, "attach", terminal], {
|
|
6264
6363
|
stdio: "inherit",
|
|
6265
6364
|
env: { ...process.env, HERD_HOME: paths.herdHome }
|
|
6266
6365
|
});
|
|
6267
|
-
return new Promise((
|
|
6366
|
+
return new Promise((resolve7) => child.on("close", () => resolve7()));
|
|
6268
6367
|
}
|
|
6269
6368
|
async function runStop(deps) {
|
|
6270
6369
|
const status2 = readStatus(deps.paths);
|
|
@@ -6309,13 +6408,28 @@ function runStatus(deps) {
|
|
|
6309
6408
|
);
|
|
6310
6409
|
if (version) print(`Installed: ${version}${pin ? `, pinned to ${pin} in ${paths.pin}` : ""}.`);
|
|
6311
6410
|
if (root) print(`Root: ${root}`);
|
|
6411
|
+
const missing = readMissingFolder(paths.herdHome);
|
|
6412
|
+
if (running && missing) {
|
|
6413
|
+
print(
|
|
6414
|
+
`Taking no work or builds: ${missing.target ? `${missing.path} links to ${missing.target}, which isn't there` : `${missing.path} isn't there`}. It takes work again once it is.`
|
|
6415
|
+
);
|
|
6416
|
+
}
|
|
6312
6417
|
if (running && status2.checkedAt) print(`Last asked for a release ${ago(status2.checkedAt)}.`);
|
|
6313
6418
|
if (status2?.problem) print(`Last problem: ${status2.problem}`);
|
|
6314
6419
|
print(`Logs: ${paths.logs}`);
|
|
6315
6420
|
return running ? 0 : 1;
|
|
6316
6421
|
}
|
|
6422
|
+
function readMissingFolder(herdHome2) {
|
|
6423
|
+
try {
|
|
6424
|
+
return folderUnavailableSchema.parse(
|
|
6425
|
+
JSON.parse(readFileSync10(join14(herdHome2, "folder-unavailable.json"), "utf8"))
|
|
6426
|
+
);
|
|
6427
|
+
} catch {
|
|
6428
|
+
return null;
|
|
6429
|
+
}
|
|
6430
|
+
}
|
|
6317
6431
|
function tail(path, count) {
|
|
6318
|
-
if (!
|
|
6432
|
+
if (!existsSync11(path)) return [];
|
|
6319
6433
|
const lines = readFileSync10(path, "utf8").split("\n");
|
|
6320
6434
|
if (lines.at(-1) === "") lines.pop();
|
|
6321
6435
|
return lines.slice(-count);
|
|
@@ -6323,14 +6437,14 @@ function tail(path, count) {
|
|
|
6323
6437
|
async function runLogs(args, deps) {
|
|
6324
6438
|
const { paths, print } = deps;
|
|
6325
6439
|
const follow = args.includes("-f") || args.includes("--follow");
|
|
6326
|
-
const files = [paths.supervisorLog, paths.daemonLog].filter((file) =>
|
|
6440
|
+
const files = [paths.supervisorLog, paths.daemonLog].filter((file) => existsSync11(file));
|
|
6327
6441
|
if (files.length === 0) {
|
|
6328
6442
|
print(`There are no logs in ${paths.logs} yet.`);
|
|
6329
6443
|
return 0;
|
|
6330
6444
|
}
|
|
6331
6445
|
if (follow) {
|
|
6332
|
-
const child =
|
|
6333
|
-
return await new Promise((
|
|
6446
|
+
const child = spawn6("tail", ["-n", "50", "-F", ...files], { stdio: "inherit" });
|
|
6447
|
+
return await new Promise((resolve7) => child.on("close", (code) => resolve7(code ?? 0)));
|
|
6334
6448
|
}
|
|
6335
6449
|
for (const file of files) {
|
|
6336
6450
|
print(`==> ${file} <==`);
|
|
@@ -6371,9 +6485,9 @@ init_define_HERD_PACKAGE();
|
|
|
6371
6485
|
|
|
6372
6486
|
// src/build-sandbox.ts
|
|
6373
6487
|
init_define_HERD_PACKAGE();
|
|
6374
|
-
import { spawn as
|
|
6488
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
6375
6489
|
import { appendFileSync, readFileSync as readFileSync11 } from "node:fs";
|
|
6376
|
-
import { dirname as
|
|
6490
|
+
import { dirname as dirname11, join as join15 } from "node:path";
|
|
6377
6491
|
var DEPLOY_HOSTS = ["api.cloudflare.com", "api.github.com", "registry.npmjs.org"];
|
|
6378
6492
|
function buildPolicy(input) {
|
|
6379
6493
|
const platform2 = input.platform ?? process.platform;
|
|
@@ -6389,7 +6503,7 @@ function buildPolicy(input) {
|
|
|
6389
6503
|
...platform2 === "darwin" ? { allowUnixSockets: [input.home] } : { allowAllUnixSockets: true }
|
|
6390
6504
|
},
|
|
6391
6505
|
filesystem: {
|
|
6392
|
-
denyRead: [.../* @__PURE__ */ new Set([input.ownerHome, input.root])],
|
|
6506
|
+
denyRead: [.../* @__PURE__ */ new Set([input.ownerHome, input.root, input.convos])],
|
|
6393
6507
|
allowRead: [input.worktree, input.store, input.nodePrefix, input.home],
|
|
6394
6508
|
allowWrite: [input.worktree, input.worktreeGitDir, input.home],
|
|
6395
6509
|
denyWrite: []
|
|
@@ -6403,7 +6517,7 @@ function describePolicy(policy, platform2 = process.platform) {
|
|
|
6403
6517
|
const hosts = policy.network.allowedDomains.join(", ");
|
|
6404
6518
|
return `Sandboxed with ${kind}: the owner's home is unreadable, writes go only to the worktree and the build's home, and the only hosts it reaches are ${hosts}.`;
|
|
6405
6519
|
}
|
|
6406
|
-
var nodePrefix = (execPath = process.execPath) =>
|
|
6520
|
+
var nodePrefix = (execPath = process.execPath) => dirname11(dirname11(execPath));
|
|
6407
6521
|
var shellQuote = (args) => args.map((arg) => `'${arg.replace(/'/g, "'\\''")}'`).join(" ");
|
|
6408
6522
|
var BLOCKED_LOG = "blocked.log";
|
|
6409
6523
|
async function runSandboxed(args) {
|
|
@@ -6420,7 +6534,7 @@ async function runSandboxed(args) {
|
|
|
6420
6534
|
return 3;
|
|
6421
6535
|
}
|
|
6422
6536
|
await SandboxManager.initialize(policy, void 0, true);
|
|
6423
|
-
const blocked =
|
|
6537
|
+
const blocked = join15(process.env.HOME ?? ".", BLOCKED_LOG);
|
|
6424
6538
|
let written = 0;
|
|
6425
6539
|
const unsubscribe = SandboxManager.getSandboxViolationStore().subscribe((violations) => {
|
|
6426
6540
|
const fresh = violations.slice(written).map((violation) => describeRefusal(violation.line)).filter((line2) => line2 !== null);
|
|
@@ -6433,13 +6547,13 @@ async function runSandboxed(args) {
|
|
|
6433
6547
|
}
|
|
6434
6548
|
});
|
|
6435
6549
|
const wrapped = await SandboxManager.wrapWithSandbox(shellQuote(command));
|
|
6436
|
-
const child =
|
|
6550
|
+
const child = spawn7(wrapped, { shell: true, stdio: "inherit" });
|
|
6437
6551
|
const forward = (signal2) => () => child.kill(signal2);
|
|
6438
6552
|
process.once("SIGTERM", forward("SIGTERM"));
|
|
6439
6553
|
process.once("SIGINT", forward("SIGINT"));
|
|
6440
|
-
const status2 = await new Promise((
|
|
6441
|
-
child.on("error", () =>
|
|
6442
|
-
child.on("exit", (code, signal2) =>
|
|
6554
|
+
const status2 = await new Promise((resolve7) => {
|
|
6555
|
+
child.on("error", () => resolve7(1));
|
|
6556
|
+
child.on("exit", (code, signal2) => resolve7(code ?? (signal2 ? 1 : 0)));
|
|
6443
6557
|
});
|
|
6444
6558
|
unsubscribe();
|
|
6445
6559
|
SandboxManager.cleanupAfterCommand();
|
|
@@ -6470,7 +6584,7 @@ async function fromTerminal(request) {
|
|
|
6470
6584
|
reply = await askDaemon(socket, { secret, ...request }).catch(() => null);
|
|
6471
6585
|
if (reply) break;
|
|
6472
6586
|
if (Date.now() > deadline) throw new HerdError("Couldn't reach herd's daemon. Is it running?");
|
|
6473
|
-
await new Promise((
|
|
6587
|
+
await new Promise((resolve7) => setTimeout(resolve7, 500));
|
|
6474
6588
|
}
|
|
6475
6589
|
if (!reply.ok) throw new HerdError(reply.error);
|
|
6476
6590
|
return reply;
|
|
@@ -6897,8 +7011,8 @@ init_define_HERD_PACKAGE();
|
|
|
6897
7011
|
// src/build-runner-key.ts
|
|
6898
7012
|
init_define_HERD_PACKAGE();
|
|
6899
7013
|
init_src();
|
|
6900
|
-
import { chmodSync as chmodSync3, existsSync as
|
|
6901
|
-
import { join as
|
|
7014
|
+
import { chmodSync as chmodSync3, existsSync as existsSync12, mkdirSync as mkdirSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
|
|
7015
|
+
import { join as join16 } from "node:path";
|
|
6902
7016
|
import { z as z24 } from "zod";
|
|
6903
7017
|
var BUILDS_URL = "https://builds.herdingchats.com";
|
|
6904
7018
|
var runnerKeySchema = z24.object({
|
|
@@ -6906,10 +7020,10 @@ var runnerKeySchema = z24.object({
|
|
|
6906
7020
|
key: z24.string().min(20),
|
|
6907
7021
|
url: z24.string().url()
|
|
6908
7022
|
});
|
|
6909
|
-
var runnerKeyPath = () =>
|
|
7023
|
+
var runnerKeyPath = () => join16(herdHome(), "build-runner.json");
|
|
6910
7024
|
function readRunnerKey() {
|
|
6911
7025
|
const path = runnerKeyPath();
|
|
6912
|
-
if (!
|
|
7026
|
+
if (!existsSync12(path)) return null;
|
|
6913
7027
|
try {
|
|
6914
7028
|
return runnerKeySchema.parse(JSON.parse(readFileSync13(path, "utf8")));
|
|
6915
7029
|
} catch {
|
|
@@ -6919,7 +7033,7 @@ function readRunnerKey() {
|
|
|
6919
7033
|
}
|
|
6920
7034
|
}
|
|
6921
7035
|
function saveRunnerKey(key) {
|
|
6922
|
-
|
|
7036
|
+
mkdirSync12(herdHome(), { recursive: true, mode: 448 });
|
|
6923
7037
|
const path = runnerKeyPath();
|
|
6924
7038
|
writeFileSync9(path, JSON.stringify(runnerKeySchema.parse(key), null, 2), { mode: 384 });
|
|
6925
7039
|
chmodSync3(path, 384);
|
|
@@ -6934,7 +7048,7 @@ async function registerRunner({
|
|
|
6934
7048
|
url = buildsUrl(),
|
|
6935
7049
|
fetchImpl = fetch,
|
|
6936
7050
|
say = (line2) => console.log(line2),
|
|
6937
|
-
sleep: sleep8 = (ms) => new Promise((
|
|
7051
|
+
sleep: sleep8 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
|
|
6938
7052
|
pollMs = 2e3,
|
|
6939
7053
|
waitMs = 15 * 60 * 1e3
|
|
6940
7054
|
}) {
|
|
@@ -6973,8 +7087,8 @@ async function registerRunner({
|
|
|
6973
7087
|
|
|
6974
7088
|
// src/builds-admin.ts
|
|
6975
7089
|
init_define_HERD_PACKAGE();
|
|
6976
|
-
import { mkdirSync as
|
|
6977
|
-
import { join as
|
|
7090
|
+
import { mkdirSync as mkdirSync13, readFileSync as readFileSync14, rmSync as rmSync10, writeFileSync as writeFileSync10 } from "node:fs";
|
|
7091
|
+
import { join as join17 } from "node:path";
|
|
6978
7092
|
var NOT_ADMIN = "builds are only for the admin's machines";
|
|
6979
7093
|
async function isAdmin(fetch2) {
|
|
6980
7094
|
const response = await fetch2("/api/people/me").catch(() => null);
|
|
@@ -6982,13 +7096,13 @@ async function isAdmin(fetch2) {
|
|
|
6982
7096
|
const body = await response.json().catch(() => ({}));
|
|
6983
7097
|
return body.admin === true;
|
|
6984
7098
|
}
|
|
6985
|
-
var refusalPath = () =>
|
|
7099
|
+
var refusalPath = () => join17(herdHome(), "builds", "refused.json");
|
|
6986
7100
|
function writeBuildsRefusal(reason) {
|
|
6987
7101
|
if (!reason) {
|
|
6988
7102
|
rmSync10(refusalPath(), { force: true });
|
|
6989
7103
|
return;
|
|
6990
7104
|
}
|
|
6991
|
-
|
|
7105
|
+
mkdirSync13(join17(herdHome(), "builds"), { recursive: true });
|
|
6992
7106
|
writeFileSync10(refusalPath(), JSON.stringify({ reason }));
|
|
6993
7107
|
}
|
|
6994
7108
|
function readBuildsRefusal() {
|
|
@@ -7134,7 +7248,7 @@ async function runConversations(rest) {
|
|
|
7134
7248
|
init_define_HERD_PACKAGE();
|
|
7135
7249
|
init_src();
|
|
7136
7250
|
import { availableParallelism, totalmem } from "node:os";
|
|
7137
|
-
import { join as
|
|
7251
|
+
import { join as join33 } from "node:path";
|
|
7138
7252
|
|
|
7139
7253
|
// src/agent.ts
|
|
7140
7254
|
init_define_HERD_PACKAGE();
|
|
@@ -7243,17 +7357,34 @@ function readClaudeScreen(raw) {
|
|
|
7243
7357
|
|
|
7244
7358
|
// src/worktrees.ts
|
|
7245
7359
|
init_define_HERD_PACKAGE();
|
|
7246
|
-
import {
|
|
7247
|
-
|
|
7360
|
+
import { existsSync as existsSync13, realpathSync as realpathSync6 } from "node:fs";
|
|
7361
|
+
import { isAbsolute as isAbsolute2, join as join18, relative } from "node:path";
|
|
7362
|
+
var worktreesRoot = () => join18(herdHome(), "worktrees");
|
|
7248
7363
|
var WORD_ID = /^[a-z]+-[a-z]+-[a-z]+$/;
|
|
7249
7364
|
var conversationPrefix = (conversationId) => WORD_ID.test(conversationId) ? conversationId : conversationId.replace(/[^A-Za-z0-9]/g, "").slice(0, 12).toLowerCase();
|
|
7250
7365
|
var conversationBranch = (conversationId) => `herd/${conversationPrefix(conversationId)}`;
|
|
7251
|
-
var conversationFolder = (
|
|
7366
|
+
var conversationFolder = (convos, conversationId) => join18(convos, conversationPrefix(conversationId));
|
|
7367
|
+
function folderForConversation(root, conversationId) {
|
|
7368
|
+
const earlier = conversationFolder(legacyConvosDir(root), conversationId);
|
|
7369
|
+
return existsSync13(earlier) ? earlier : conversationFolder(convosHome(root), conversationId);
|
|
7370
|
+
}
|
|
7252
7371
|
var within = (dir, path) => {
|
|
7253
7372
|
const between = relative(dir, path);
|
|
7254
7373
|
return between !== "" && !between.startsWith("..") && !isAbsolute2(between);
|
|
7255
7374
|
};
|
|
7256
|
-
var
|
|
7375
|
+
var real2 = (path) => {
|
|
7376
|
+
try {
|
|
7377
|
+
return realpathSync6(path);
|
|
7378
|
+
} catch {
|
|
7379
|
+
return path;
|
|
7380
|
+
}
|
|
7381
|
+
};
|
|
7382
|
+
function isHerdFolder(path, root = machineRoot()) {
|
|
7383
|
+
const at = real2(path);
|
|
7384
|
+
return [convosPath(root), convosHome(root), legacyConvosDir(root), worktreesRoot()].some(
|
|
7385
|
+
(dir) => within(dir, path) || within(real2(dir), at)
|
|
7386
|
+
);
|
|
7387
|
+
}
|
|
7257
7388
|
|
|
7258
7389
|
// src/agent.ts
|
|
7259
7390
|
var FOLDER_PARAGRAPH = "This folder is this conversation's own, and holds only the repos added to it: `herd repo <url or owner/name>` checks a repo out here, on a branch named for this conversation, and `herd repos` lists the repos here and the ones to choose from, with what your owner says each is. Work in each repo's folder, and follow each repo's own AGENTS.md or README to set it up. ";
|
|
@@ -7274,7 +7405,7 @@ var ownPrompt = (draft) => OWN_PROMPTS.find(
|
|
|
7274
7405
|
var SESSION_ID = /^[A-Za-z0-9-]{1,100}$/;
|
|
7275
7406
|
var SUBMIT_RETRIES = 3;
|
|
7276
7407
|
var DEFAULT_TIMING = { settleMs: 800, pollMs: 150, startMs: 6e4, confirmMs: 3e3 };
|
|
7277
|
-
var sleep3 = (ms) => new Promise((
|
|
7408
|
+
var sleep3 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
7278
7409
|
var EXIT_WAIT_MS = 5e3;
|
|
7279
7410
|
var AgentDriver = class {
|
|
7280
7411
|
constructor(terminals, timing = {}) {
|
|
@@ -7421,12 +7552,12 @@ var AgentDriver = class {
|
|
|
7421
7552
|
if (before.draft && !leftover) return { status: "draft", draft: before.draft };
|
|
7422
7553
|
const sent = leftover ?? text4;
|
|
7423
7554
|
let taken = false;
|
|
7424
|
-
const hooked = new Promise((
|
|
7555
|
+
const hooked = new Promise((resolve7) => {
|
|
7425
7556
|
this.waiters.set(name, {
|
|
7426
7557
|
text: sent,
|
|
7427
7558
|
taken: () => {
|
|
7428
7559
|
taken = true;
|
|
7429
|
-
|
|
7560
|
+
resolve7();
|
|
7430
7561
|
}
|
|
7431
7562
|
});
|
|
7432
7563
|
});
|
|
@@ -7533,9 +7664,9 @@ function readAgyScreen(raw) {
|
|
|
7533
7664
|
|
|
7534
7665
|
// src/agy-settings.ts
|
|
7535
7666
|
init_define_HERD_PACKAGE();
|
|
7536
|
-
import { lstatSync, mkdirSync as
|
|
7537
|
-
import { join as
|
|
7538
|
-
var AGY_SETTINGS_FILE =
|
|
7667
|
+
import { lstatSync as lstatSync2, mkdirSync as mkdirSync14, realpathSync as realpathSync7, writeFileSync as writeFileSync11 } from "node:fs";
|
|
7668
|
+
import { join as join19, sep } from "node:path";
|
|
7669
|
+
var AGY_SETTINGS_FILE = join19(".herd", "agy-settings.json");
|
|
7539
7670
|
var AGY_HOOK_EVENTS = ["SessionStart", "PreInvocation", "Stop"];
|
|
7540
7671
|
var agyHookCommand = (event2, herd) => `${herd} hook agy ${event2} || true`;
|
|
7541
7672
|
function agyHooks(herd) {
|
|
@@ -7552,25 +7683,25 @@ function agySettings(herd) {
|
|
|
7552
7683
|
return { statusLine: { type: "command", command: `${herd} statusline agy` } };
|
|
7553
7684
|
}
|
|
7554
7685
|
function ownFolder(worktree, name) {
|
|
7555
|
-
const folder =
|
|
7556
|
-
if (
|
|
7686
|
+
const folder = join19(worktree, name);
|
|
7687
|
+
if (lstatSync2(folder, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
7557
7688
|
throw new Error(`${name} in the worktree is a link`);
|
|
7558
7689
|
}
|
|
7559
|
-
|
|
7560
|
-
if (!
|
|
7690
|
+
mkdirSync14(folder, { recursive: true });
|
|
7691
|
+
if (!realpathSync7(folder).startsWith(realpathSync7(worktree) + sep)) {
|
|
7561
7692
|
throw new Error(`${name} leads outside the worktree`);
|
|
7562
7693
|
}
|
|
7563
7694
|
return folder;
|
|
7564
7695
|
}
|
|
7565
7696
|
function writeAgyFiles(worktree, herd) {
|
|
7566
7697
|
const agents = ownFolder(worktree, ".agents");
|
|
7567
|
-
writeFileSync11(
|
|
7698
|
+
writeFileSync11(join19(agents, "hooks.json"), `${JSON.stringify(agyHooks(herd), null, 2)}
|
|
7568
7699
|
`);
|
|
7569
|
-
writeFileSync11(
|
|
7700
|
+
writeFileSync11(join19(agents, ".gitignore"), "*\n");
|
|
7570
7701
|
const herdFolder = ownFolder(worktree, ".herd");
|
|
7571
|
-
writeFileSync11(
|
|
7702
|
+
writeFileSync11(join19(herdFolder, ".gitignore"), "*\n");
|
|
7572
7703
|
writeFileSync11(
|
|
7573
|
-
|
|
7704
|
+
join19(worktree, AGY_SETTINGS_FILE),
|
|
7574
7705
|
`${JSON.stringify(agySettings(herd), null, 2)}
|
|
7575
7706
|
`
|
|
7576
7707
|
);
|
|
@@ -7579,7 +7710,7 @@ function writeAgyFiles(worktree, herd) {
|
|
|
7579
7710
|
// src/agy-driver.ts
|
|
7580
7711
|
var AGY_FLAGS = `--dangerously-skip-permissions --settings ${AGY_SETTINGS_FILE}`;
|
|
7581
7712
|
var DEFAULT_TIMING2 = { settleMs: 800, pollMs: 150, startMs: 6e4, confirmMs: 3e3 };
|
|
7582
|
-
var sleep4 = (ms) => new Promise((
|
|
7713
|
+
var sleep4 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
7583
7714
|
var EXIT_WAIT_MS2 = 5e3;
|
|
7584
7715
|
var AgyDriver = class {
|
|
7585
7716
|
constructor(terminals, timing = {}) {
|
|
@@ -7669,12 +7800,12 @@ var AgyDriver = class {
|
|
|
7669
7800
|
if (before.draft && !leftover) return { status: "draft", draft: before.draft };
|
|
7670
7801
|
const sent = leftover ?? text4;
|
|
7671
7802
|
let taken = false;
|
|
7672
|
-
const reported = new Promise((
|
|
7803
|
+
const reported = new Promise((resolve7) => {
|
|
7673
7804
|
this.waiters.set(name, {
|
|
7674
7805
|
text: sent,
|
|
7675
7806
|
taken: () => {
|
|
7676
7807
|
taken = true;
|
|
7677
|
-
|
|
7808
|
+
resolve7();
|
|
7678
7809
|
}
|
|
7679
7810
|
});
|
|
7680
7811
|
});
|
|
@@ -7722,6 +7853,78 @@ var AgyDriver = class {
|
|
|
7722
7853
|
}
|
|
7723
7854
|
};
|
|
7724
7855
|
|
|
7856
|
+
// src/bare-clones.ts
|
|
7857
|
+
init_define_HERD_PACKAGE();
|
|
7858
|
+
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
7859
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync15, readdirSync as readdirSync5, renameSync as renameSync5, rmSync as rmSync11, statSync as statSync7 } from "node:fs";
|
|
7860
|
+
import { dirname as dirname12, join as join20 } from "node:path";
|
|
7861
|
+
function bareClonePath(dir, remote) {
|
|
7862
|
+
const key = repoKey(remote);
|
|
7863
|
+
if (!key) throw new StoreError(`${remote} isn't a repo's URL.`);
|
|
7864
|
+
return join20(dir, `${key}.git`);
|
|
7865
|
+
}
|
|
7866
|
+
async function ensureBareClone(store, remote, env) {
|
|
7867
|
+
if (existsSync14(store)) {
|
|
7868
|
+
let fetchFailed = null;
|
|
7869
|
+
try {
|
|
7870
|
+
await git(env, store, "fetch", "--quiet", "--prune", "origin");
|
|
7871
|
+
await git(env, store, "remote", "set-head", "origin", "--auto").catch(() => "");
|
|
7872
|
+
} catch (error) {
|
|
7873
|
+
fetchFailed = error instanceof Error ? error.message : String(error);
|
|
7874
|
+
}
|
|
7875
|
+
return { store, created: false, fetchFailed };
|
|
7876
|
+
}
|
|
7877
|
+
const building = `${store}.tmp-${process.pid}-${randomBytes5(4).toString("hex")}`;
|
|
7878
|
+
mkdirSync15(dirname12(store), { recursive: true });
|
|
7879
|
+
try {
|
|
7880
|
+
await git(env, dirname12(store), "init", "--quiet", "--bare", building);
|
|
7881
|
+
await git(env, building, "remote", "add", "origin", remote);
|
|
7882
|
+
await git(
|
|
7883
|
+
env,
|
|
7884
|
+
building,
|
|
7885
|
+
"config",
|
|
7886
|
+
"remote.origin.fetch",
|
|
7887
|
+
"+refs/heads/*:refs/remotes/origin/*"
|
|
7888
|
+
);
|
|
7889
|
+
await git(env, building, "fetch", "--quiet", "origin");
|
|
7890
|
+
await git(env, building, "remote", "set-head", "origin", "--auto").catch(() => "");
|
|
7891
|
+
renameSync5(building, store);
|
|
7892
|
+
} catch (error) {
|
|
7893
|
+
rmSync11(building, { recursive: true, force: true });
|
|
7894
|
+
throw error instanceof StoreError ? error : new StoreError(gitError(error));
|
|
7895
|
+
}
|
|
7896
|
+
return { store, created: true, fetchFailed: null };
|
|
7897
|
+
}
|
|
7898
|
+
function bareClones(dir) {
|
|
7899
|
+
const found = [];
|
|
7900
|
+
const walk = (at, depth) => {
|
|
7901
|
+
let entries;
|
|
7902
|
+
try {
|
|
7903
|
+
entries = readdirSync5(at);
|
|
7904
|
+
} catch {
|
|
7905
|
+
return;
|
|
7906
|
+
}
|
|
7907
|
+
for (const name of entries.sort()) {
|
|
7908
|
+
const path = join20(at, name);
|
|
7909
|
+
if (name.endsWith(".git")) {
|
|
7910
|
+
try {
|
|
7911
|
+
const remote = gitSync(path, "config", "--get", "remote.origin.url");
|
|
7912
|
+
const key = repoKey(remote);
|
|
7913
|
+
if (key) found.push({ key, remote, store: path });
|
|
7914
|
+
} catch {
|
|
7915
|
+
}
|
|
7916
|
+
} else if (depth < 8 && !name.includes(".tmp-") && !name.endsWith(".lock")) {
|
|
7917
|
+
try {
|
|
7918
|
+
if (statSync7(path).isDirectory()) walk(path, depth + 1);
|
|
7919
|
+
} catch {
|
|
7920
|
+
}
|
|
7921
|
+
}
|
|
7922
|
+
}
|
|
7923
|
+
};
|
|
7924
|
+
walk(dir, 0);
|
|
7925
|
+
return found;
|
|
7926
|
+
}
|
|
7927
|
+
|
|
7725
7928
|
// src/build-connection.ts
|
|
7726
7929
|
init_define_HERD_PACKAGE();
|
|
7727
7930
|
init_src();
|
|
@@ -7880,28 +8083,28 @@ var BuildConnection = class {
|
|
|
7880
8083
|
|
|
7881
8084
|
// src/builds.ts
|
|
7882
8085
|
init_define_HERD_PACKAGE();
|
|
7883
|
-
import { execFile as execFile2, execFileSync as execFileSync4, spawn as
|
|
8086
|
+
import { execFile as execFile2, execFileSync as execFileSync4, spawn as spawn8 } from "node:child_process";
|
|
7884
8087
|
import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
|
|
7885
8088
|
import {
|
|
7886
|
-
existsSync as
|
|
7887
|
-
mkdirSync as
|
|
8089
|
+
existsSync as existsSync15,
|
|
8090
|
+
mkdirSync as mkdirSync17,
|
|
7888
8091
|
mkdtempSync,
|
|
7889
|
-
readdirSync as
|
|
8092
|
+
readdirSync as readdirSync7,
|
|
7890
8093
|
readFileSync as readFileSync16,
|
|
7891
|
-
realpathSync as
|
|
7892
|
-
rmSync as
|
|
8094
|
+
realpathSync as realpathSync8,
|
|
8095
|
+
rmSync as rmSync13,
|
|
7893
8096
|
writeFileSync as writeFileSync13
|
|
7894
8097
|
} from "node:fs";
|
|
7895
8098
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
7896
|
-
import { join as
|
|
8099
|
+
import { join as join22 } from "node:path";
|
|
7897
8100
|
import { promisify as promisify2 } from "node:util";
|
|
7898
8101
|
|
|
7899
8102
|
// src/check-registry.ts
|
|
7900
8103
|
init_define_HERD_PACKAGE();
|
|
7901
|
-
import { mkdirSync as
|
|
8104
|
+
import { mkdirSync as mkdirSync16, readdirSync as readdirSync6, readFileSync as readFileSync15, rmSync as rmSync12, writeFileSync as writeFileSync12 } from "node:fs";
|
|
7902
8105
|
import { homedir as homedir5 } from "node:os";
|
|
7903
|
-
import { join as
|
|
7904
|
-
var checkRegistryDir = () => process.env.HERDINGCHATS_CHECK_RUNNING ??
|
|
8106
|
+
import { join as join21 } from "node:path";
|
|
8107
|
+
var checkRegistryDir = () => process.env.HERDINGCHATS_CHECK_RUNNING ?? join21(homedir5(), ".cache", "herdingchats", "check", "running");
|
|
7905
8108
|
var RESERVED_MB = 6 * 1024;
|
|
7906
8109
|
var MIN_MEMORY_MB = 1024;
|
|
7907
8110
|
var alive3 = (pid) => {
|
|
@@ -7913,15 +8116,15 @@ var alive3 = (pid) => {
|
|
|
7913
8116
|
}
|
|
7914
8117
|
};
|
|
7915
8118
|
function registerCheck(dir, pid, startedAt = Date.now()) {
|
|
7916
|
-
|
|
7917
|
-
const file =
|
|
8119
|
+
mkdirSync16(dir, { recursive: true });
|
|
8120
|
+
const file = join21(dir, `${pid}.json`);
|
|
7918
8121
|
writeFileSync12(file, JSON.stringify({ pid, startedAt }));
|
|
7919
|
-
return () =>
|
|
8122
|
+
return () => rmSync12(file, { force: true });
|
|
7920
8123
|
}
|
|
7921
8124
|
function runningChecks(dir, isAlive = alive3) {
|
|
7922
8125
|
let names = [];
|
|
7923
8126
|
try {
|
|
7924
|
-
names =
|
|
8127
|
+
names = readdirSync6(dir).filter((name) => /^\d+\.json$/.test(name));
|
|
7925
8128
|
} catch {
|
|
7926
8129
|
return 0;
|
|
7927
8130
|
}
|
|
@@ -7930,12 +8133,12 @@ function runningChecks(dir, isAlive = alive3) {
|
|
|
7930
8133
|
const pid = Number(name.slice(0, -".json".length));
|
|
7931
8134
|
let entry = null;
|
|
7932
8135
|
try {
|
|
7933
|
-
entry = JSON.parse(readFileSync15(
|
|
8136
|
+
entry = JSON.parse(readFileSync15(join21(dir, name), "utf8"));
|
|
7934
8137
|
} catch {
|
|
7935
8138
|
continue;
|
|
7936
8139
|
}
|
|
7937
8140
|
if (entry?.pid === pid && isAlive(pid)) count += 1;
|
|
7938
|
-
else
|
|
8141
|
+
else rmSync12(join21(dir, name), { force: true });
|
|
7939
8142
|
}
|
|
7940
8143
|
return count;
|
|
7941
8144
|
}
|
|
@@ -7943,9 +8146,9 @@ var memoryShareOf = (totalMb, running) => Math.max(MIN_MEMORY_MB, Math.floor((to
|
|
|
7943
8146
|
|
|
7944
8147
|
// src/builds.ts
|
|
7945
8148
|
init_self();
|
|
7946
|
-
var sandboxStatusPath = () =>
|
|
8149
|
+
var sandboxStatusPath = () => join22(herdHome(), "builds", "sandbox.json");
|
|
7947
8150
|
function writeSandboxStatus(status2, now = Date.now()) {
|
|
7948
|
-
|
|
8151
|
+
mkdirSync17(join22(herdHome(), "builds"), { recursive: true });
|
|
7949
8152
|
writeFileSync13(sandboxStatusPath(), JSON.stringify({ ...status2, at: now }));
|
|
7950
8153
|
}
|
|
7951
8154
|
function readSandboxStatus() {
|
|
@@ -7969,17 +8172,17 @@ async function storeGit(env, store, ...args) {
|
|
|
7969
8172
|
throw new StoreError(gitError(error));
|
|
7970
8173
|
}
|
|
7971
8174
|
}
|
|
7972
|
-
var
|
|
8175
|
+
var real3 = (path) => {
|
|
7973
8176
|
try {
|
|
7974
|
-
return
|
|
8177
|
+
return realpathSync8(path);
|
|
7975
8178
|
} catch {
|
|
7976
8179
|
return path;
|
|
7977
8180
|
}
|
|
7978
8181
|
};
|
|
7979
8182
|
function worktreeOf(dir, store) {
|
|
7980
|
-
if (!
|
|
8183
|
+
if (!existsSync15(join22(dir, ".git"))) return false;
|
|
7981
8184
|
try {
|
|
7982
|
-
return
|
|
8185
|
+
return real3(git2(dir, "rev-parse", "--path-format=absolute", "--git-common-dir")) === real3(store);
|
|
7983
8186
|
} catch {
|
|
7984
8187
|
return false;
|
|
7985
8188
|
}
|
|
@@ -7989,23 +8192,23 @@ function cloneFolder(from, to) {
|
|
|
7989
8192
|
execFileSync4("cp", [...args, from, to]);
|
|
7990
8193
|
}
|
|
7991
8194
|
function moduleFolders(dir) {
|
|
7992
|
-
const found =
|
|
7993
|
-
for (const entry of
|
|
8195
|
+
const found = existsSync15(join22(dir, "node_modules")) ? ["node_modules"] : [];
|
|
8196
|
+
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
7994
8197
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
7995
|
-
if (
|
|
7996
|
-
found.push(
|
|
8198
|
+
if (existsSync15(join22(dir, entry.name, "node_modules")))
|
|
8199
|
+
found.push(join22(entry.name, "node_modules"));
|
|
7997
8200
|
}
|
|
7998
8201
|
}
|
|
7999
8202
|
return found;
|
|
8000
8203
|
}
|
|
8001
8204
|
function lockHash(dir) {
|
|
8002
|
-
const lock = readFileSync16(
|
|
8205
|
+
const lock = readFileSync16(join22(dir, "package-lock.json"));
|
|
8003
8206
|
const major = process.versions.node.split(".")[0];
|
|
8004
8207
|
return createHash3("sha256").update(lock).update(`${process.platform}-${process.arch}-${major}`).digest("hex").slice(0, 32);
|
|
8005
8208
|
}
|
|
8006
|
-
var npmCi = (dir) => new Promise((
|
|
8209
|
+
var npmCi = (dir) => new Promise((resolve7, reject) => {
|
|
8007
8210
|
const network = insideBuildSandbox() ? ["--offline"] : ["--prefer-offline"];
|
|
8008
|
-
const child =
|
|
8211
|
+
const child = spawn8(
|
|
8009
8212
|
"npm",
|
|
8010
8213
|
["ci", ...network, "--no-audit", "--no-fund", "--no-update-notifier"],
|
|
8011
8214
|
{
|
|
@@ -8016,7 +8219,7 @@ var npmCi = (dir) => new Promise((resolve6, reject) => {
|
|
|
8016
8219
|
child.on("error", reject);
|
|
8017
8220
|
child.on(
|
|
8018
8221
|
"close",
|
|
8019
|
-
(code) => code === 0 ?
|
|
8222
|
+
(code) => code === 0 ? resolve7() : reject(new Error(`npm ci exited with ${code}`))
|
|
8020
8223
|
);
|
|
8021
8224
|
});
|
|
8022
8225
|
var BuildRunner = class _BuildRunner {
|
|
@@ -8026,11 +8229,15 @@ var BuildRunner = class _BuildRunner {
|
|
|
8026
8229
|
options;
|
|
8027
8230
|
running = /* @__PURE__ */ new Map();
|
|
8028
8231
|
home() {
|
|
8029
|
-
return this.options.home ??
|
|
8232
|
+
return this.options.home ?? join22(herdHome(), "builds");
|
|
8233
|
+
}
|
|
8234
|
+
/** Where builds' own bare clones are, apart from the root's checkouts. */
|
|
8235
|
+
clonesDir() {
|
|
8236
|
+
return join22(this.home(), "clones");
|
|
8030
8237
|
}
|
|
8031
|
-
/** Herd's clone of a build's repo
|
|
8238
|
+
/** Herd's bare clone of a build's repo, for builds, whether or not it's there yet. */
|
|
8032
8239
|
storeFor(build) {
|
|
8033
|
-
return
|
|
8240
|
+
return bareClonePath(this.clonesDir(), this.source(build));
|
|
8034
8241
|
}
|
|
8035
8242
|
/** What to clone a build's repo from: a remote the machine knows for it, or its HTTPS URL. */
|
|
8036
8243
|
source(build) {
|
|
@@ -8038,21 +8245,21 @@ var BuildRunner = class _BuildRunner {
|
|
|
8038
8245
|
}
|
|
8039
8246
|
/**
|
|
8040
8247
|
* The worktree for one of the repo's build slots, made or updated to the build's commit. The repo
|
|
8041
|
-
* is cloned into the
|
|
8042
|
-
* holding the clone's lock
|
|
8248
|
+
* is cloned bare into the builds' own folder first if it isn't there, and fetched otherwise,
|
|
8249
|
+
* outside the sandbox and holding the clone's lock. Builds never work from the root's checkouts,
|
|
8250
|
+
* which hold the person's own local files.
|
|
8043
8251
|
*/
|
|
8044
8252
|
async prepare(build, slot) {
|
|
8045
|
-
const root = this.options.root();
|
|
8046
8253
|
const remote = this.source(build);
|
|
8047
|
-
const store =
|
|
8254
|
+
const store = bareClonePath(this.clonesDir(), remote);
|
|
8048
8255
|
const env = this.options.gitEnv?.();
|
|
8049
8256
|
const name = (repoKey(build.repo) ?? build.repo).replace(/[^A-Za-z0-9._-]+/g, "_");
|
|
8050
|
-
const dir =
|
|
8051
|
-
|
|
8257
|
+
const dir = join22(this.home(), name, String(slot));
|
|
8258
|
+
mkdirSync17(join22(this.home(), name), { recursive: true });
|
|
8052
8259
|
await withStoreLock(
|
|
8053
8260
|
store,
|
|
8054
8261
|
async () => {
|
|
8055
|
-
const clone = await
|
|
8262
|
+
const clone = await ensureBareClone(store, remote, env);
|
|
8056
8263
|
if (clone.created) this.options.log?.(`Cloned ${build.repo} into ${store}.`);
|
|
8057
8264
|
if (clone.fetchFailed)
|
|
8058
8265
|
this.options.log?.(`Couldn't fetch ${build.repo}: ${clone.fetchFailed}`);
|
|
@@ -8062,7 +8269,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
8062
8269
|
await storeGit(env, store, "fetch", "--quiet", "origin", build.sha);
|
|
8063
8270
|
}
|
|
8064
8271
|
if (!worktreeOf(dir, store)) {
|
|
8065
|
-
|
|
8272
|
+
rmSync13(dir, { recursive: true, force: true });
|
|
8066
8273
|
await storeGit(env, store, "worktree", "prune");
|
|
8067
8274
|
await storeGit(
|
|
8068
8275
|
env,
|
|
@@ -8087,30 +8294,30 @@ var BuildRunner = class _BuildRunner {
|
|
|
8087
8294
|
/** Makes `node_modules` match the checkout's lockfile: kept, copied from the cache, or installed. */
|
|
8088
8295
|
async ensureModules(dir) {
|
|
8089
8296
|
const wanted = lockHash(dir);
|
|
8090
|
-
const marker =
|
|
8091
|
-
if (
|
|
8092
|
-
const cache =
|
|
8297
|
+
const marker = join22(dir, "node_modules", ".herd-lock");
|
|
8298
|
+
if (existsSync15(marker) && readFileSync16(marker, "utf8") === wanted) return;
|
|
8299
|
+
const cache = join22(this.home(), "node_modules", wanted);
|
|
8093
8300
|
for (const folder of moduleFolders(dir))
|
|
8094
|
-
|
|
8095
|
-
if (
|
|
8096
|
-
for (const folder of readFileSync16(
|
|
8097
|
-
cloneFolder(
|
|
8301
|
+
rmSync13(join22(dir, folder), { recursive: true, force: true });
|
|
8302
|
+
if (existsSync15(join22(cache, ".complete"))) {
|
|
8303
|
+
for (const folder of readFileSync16(join22(cache, ".complete"), "utf8").split("\n").filter(Boolean)) {
|
|
8304
|
+
cloneFolder(join22(cache, folder), join22(dir, folder));
|
|
8098
8305
|
}
|
|
8099
8306
|
} else {
|
|
8100
8307
|
this.options.log?.(`Installing node_modules for lockfile ${wanted.slice(0, 8)}.`);
|
|
8101
8308
|
await (this.options.install ?? npmCi)(dir);
|
|
8102
|
-
const staging = mkdtempSync(
|
|
8309
|
+
const staging = mkdtempSync(join22(this.home(), "node_modules-"));
|
|
8103
8310
|
const folders = moduleFolders(dir);
|
|
8104
8311
|
for (const folder of folders) {
|
|
8105
|
-
|
|
8106
|
-
cloneFolder(
|
|
8312
|
+
mkdirSync17(join22(staging, folder, ".."), { recursive: true });
|
|
8313
|
+
cloneFolder(join22(dir, folder), join22(staging, folder));
|
|
8107
8314
|
}
|
|
8108
|
-
writeFileSync13(
|
|
8109
|
-
|
|
8110
|
-
|
|
8315
|
+
writeFileSync13(join22(staging, ".complete"), folders.join("\n"));
|
|
8316
|
+
mkdirSync17(join22(this.home(), "node_modules"), { recursive: true });
|
|
8317
|
+
rmSync13(cache, { recursive: true, force: true });
|
|
8111
8318
|
execFileSync4("mv", [staging, cache]);
|
|
8112
8319
|
}
|
|
8113
|
-
|
|
8320
|
+
mkdirSync17(join22(dir, "node_modules"), { recursive: true });
|
|
8114
8321
|
writeFileSync13(marker, wanted);
|
|
8115
8322
|
}
|
|
8116
8323
|
/**
|
|
@@ -8121,7 +8328,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
8121
8328
|
const env = {
|
|
8122
8329
|
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
|
8123
8330
|
HOME: home,
|
|
8124
|
-
TMPDIR:
|
|
8331
|
+
TMPDIR: join22(home, "tmp"),
|
|
8125
8332
|
LANG: process.env.LANG ?? "en_US.UTF-8",
|
|
8126
8333
|
HERD_BUILD_ID: build.id,
|
|
8127
8334
|
HERD_BUILD_SECRET: build.secret,
|
|
@@ -8133,7 +8340,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
8133
8340
|
// The sandbox's proxy is the only way out, and Node's own requests use it only when asked.
|
|
8134
8341
|
NODE_USE_ENV_PROXY: "1",
|
|
8135
8342
|
// Where `srt` points TMPDIR inside the sandbox: the build's own temporary folder.
|
|
8136
|
-
CLAUDE_CODE_TMPDIR:
|
|
8343
|
+
CLAUDE_CODE_TMPDIR: join22(home, "tmp")
|
|
8137
8344
|
};
|
|
8138
8345
|
return env;
|
|
8139
8346
|
}
|
|
@@ -8144,8 +8351,8 @@ var BuildRunner = class _BuildRunner {
|
|
|
8144
8351
|
/** Runs `scripts/build.mjs` from the build's own commit, in a process group of its own. */
|
|
8145
8352
|
async run(build, slot) {
|
|
8146
8353
|
const dir = await this.prepare(build, slot);
|
|
8147
|
-
const home = mkdtempSync(
|
|
8148
|
-
|
|
8354
|
+
const home = mkdtempSync(join22(this.options.scratch ?? scratchRoot(), "hb-"));
|
|
8355
|
+
mkdirSync17(join22(home, "tmp"), { recursive: true });
|
|
8149
8356
|
const run4 = randomUUID4();
|
|
8150
8357
|
const env = {
|
|
8151
8358
|
..._BuildRunner.environment(build, home),
|
|
@@ -8155,12 +8362,13 @@ var BuildRunner = class _BuildRunner {
|
|
|
8155
8362
|
env.HERD_CHECK_CPUS = String(Math.max(1, Math.floor(this.options.cpus())));
|
|
8156
8363
|
if (this.options.memoryMb)
|
|
8157
8364
|
env.HERD_CHECK_MEMORY_MB = String(Math.max(1, Math.floor(this.options.memoryMb())));
|
|
8158
|
-
let command = [process.execPath,
|
|
8365
|
+
let command = [process.execPath, join22(dir, "scripts", "build.mjs")];
|
|
8159
8366
|
let policyPath = null;
|
|
8160
8367
|
if (this.sandboxed()) {
|
|
8161
8368
|
const policy = buildPolicy({
|
|
8162
8369
|
ownerHome: this.options.ownerHome ?? homedir6(),
|
|
8163
8370
|
root: this.options.root(),
|
|
8371
|
+
convos: convosHome(this.options.root()),
|
|
8164
8372
|
worktree: dir,
|
|
8165
8373
|
store: this.storeFor(build),
|
|
8166
8374
|
worktreeGitDir: git2(dir, "rev-parse", "--path-format=absolute", "--git-dir"),
|
|
@@ -8169,8 +8377,8 @@ var BuildRunner = class _BuildRunner {
|
|
|
8169
8377
|
main: build.main,
|
|
8170
8378
|
buildsUrl: build.url
|
|
8171
8379
|
});
|
|
8172
|
-
|
|
8173
|
-
policyPath =
|
|
8380
|
+
mkdirSync17(join22(this.home(), "policies"), { recursive: true });
|
|
8381
|
+
policyPath = join22(this.home(), "policies", `${build.id}.json`);
|
|
8174
8382
|
writeFileSync13(policyPath, JSON.stringify(policy), { mode: 384 });
|
|
8175
8383
|
env.HERD_BUILD_SANDBOX = describePolicy(policy);
|
|
8176
8384
|
command = [process.execPath, HERD_BIN, "build-sandbox", policyPath, "--", ...command];
|
|
@@ -8178,15 +8386,15 @@ var BuildRunner = class _BuildRunner {
|
|
|
8178
8386
|
env.HERD_BUILD_SANDBOX = insideBuildSandbox() ? "Inside the sandbox of the build that started this one." : "";
|
|
8179
8387
|
}
|
|
8180
8388
|
const [executable, ...args] = command;
|
|
8181
|
-
const child =
|
|
8182
|
-
const done = new Promise((
|
|
8183
|
-
child.on("error", () =>
|
|
8184
|
-
child.on("close", (code) =>
|
|
8389
|
+
const child = spawn8(executable, args, { cwd: dir, env, stdio: "ignore", detached: true });
|
|
8390
|
+
const done = new Promise((resolve7) => {
|
|
8391
|
+
child.on("error", () => resolve7(1));
|
|
8392
|
+
child.on("close", (code) => resolve7(code ?? 1));
|
|
8185
8393
|
});
|
|
8186
8394
|
let markEnded = () => {
|
|
8187
8395
|
};
|
|
8188
|
-
const ended = new Promise((
|
|
8189
|
-
markEnded =
|
|
8396
|
+
const ended = new Promise((resolve7) => {
|
|
8397
|
+
markEnded = resolve7;
|
|
8190
8398
|
});
|
|
8191
8399
|
const entry = { pid: child.pid ?? 0, done, dir, ended, run: run4 };
|
|
8192
8400
|
this.running.set(build.id, entry);
|
|
@@ -8198,8 +8406,8 @@ var BuildRunner = class _BuildRunner {
|
|
|
8198
8406
|
unregister();
|
|
8199
8407
|
await this.stopBuildProcesses(run4, dir, entry.pid);
|
|
8200
8408
|
if (this.running.get(build.id) === entry) this.running.delete(build.id);
|
|
8201
|
-
|
|
8202
|
-
if (policyPath)
|
|
8409
|
+
rmSync13(home, { recursive: true, force: true });
|
|
8410
|
+
if (policyPath) rmSync13(policyPath, { force: true });
|
|
8203
8411
|
markEnded();
|
|
8204
8412
|
}
|
|
8205
8413
|
}
|
|
@@ -8219,7 +8427,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
8219
8427
|
* It goes only by where they run from, never by a build ID, since the daemon a build's own tests
|
|
8220
8428
|
* start must never stop the build it runs in.
|
|
8221
8429
|
*/
|
|
8222
|
-
static async sweep(home =
|
|
8430
|
+
static async sweep(home = join22(herdHome(), "builds"), list = listProcesses) {
|
|
8223
8431
|
const left = buildProcesses(list(), null, home);
|
|
8224
8432
|
await stopPids(left);
|
|
8225
8433
|
return left.length;
|
|
@@ -8231,50 +8439,51 @@ var BuildRunner = class _BuildRunner {
|
|
|
8231
8439
|
*/
|
|
8232
8440
|
static async probe(options = { buildsUrl: "" }) {
|
|
8233
8441
|
if (insideBuildSandbox()) return { ok: true };
|
|
8234
|
-
const herd = options.home ??
|
|
8235
|
-
const dir = mkdtempSync(
|
|
8236
|
-
const secret =
|
|
8442
|
+
const herd = options.home ?? join22(herdHome(), "builds");
|
|
8443
|
+
const dir = mkdtempSync(join22(scratchRoot(), "hp-"));
|
|
8444
|
+
const secret = join22(dir, "owner", "secret");
|
|
8237
8445
|
try {
|
|
8238
|
-
|
|
8239
|
-
for (const sub of ["work", "git", "home", "owner"])
|
|
8446
|
+
mkdirSync17(herd, { recursive: true });
|
|
8447
|
+
for (const sub of ["work", "git", "home", "owner"]) mkdirSync17(join22(dir, sub));
|
|
8240
8448
|
writeFileSync13(secret, "probe");
|
|
8241
8449
|
const policy = buildPolicy({
|
|
8242
|
-
ownerHome:
|
|
8243
|
-
root:
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8450
|
+
ownerHome: join22(dir, "owner"),
|
|
8451
|
+
root: join22(dir, "owner"),
|
|
8452
|
+
convos: join22(dir, "owner"),
|
|
8453
|
+
worktree: join22(dir, "work"),
|
|
8454
|
+
store: join22(dir, "git"),
|
|
8455
|
+
worktreeGitDir: join22(dir, "git"),
|
|
8456
|
+
home: join22(dir, "home"),
|
|
8248
8457
|
nodePrefix: nodePrefix(),
|
|
8249
8458
|
main: false,
|
|
8250
8459
|
buildsUrl: options.buildsUrl || "https://builds.herdingchats.com"
|
|
8251
8460
|
});
|
|
8252
|
-
const policyPath =
|
|
8461
|
+
const policyPath = join22(herd, "probe-policy.json");
|
|
8253
8462
|
writeFileSync13(policyPath, JSON.stringify(policy), { mode: 384 });
|
|
8254
8463
|
const script = [
|
|
8255
8464
|
'const fs = require("node:fs");',
|
|
8256
8465
|
`try { fs.readFileSync(${JSON.stringify(secret)}); process.exit(10); } catch {}`,
|
|
8257
|
-
`fs.writeFileSync(${JSON.stringify(
|
|
8466
|
+
`fs.writeFileSync(${JSON.stringify(join22(dir, "home", "ok"))}, "ok");`
|
|
8258
8467
|
].join(" ");
|
|
8259
|
-
const child =
|
|
8468
|
+
const child = spawn8(
|
|
8260
8469
|
process.execPath,
|
|
8261
8470
|
[HERD_BIN, "build-sandbox", policyPath, "--", process.execPath, "-e", script],
|
|
8262
8471
|
{
|
|
8263
|
-
env: { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME:
|
|
8472
|
+
env: { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: join22(dir, "home") },
|
|
8264
8473
|
stdio: ["ignore", "ignore", "pipe"]
|
|
8265
8474
|
}
|
|
8266
8475
|
);
|
|
8267
8476
|
let stderr = "";
|
|
8268
8477
|
child.stderr.on("data", (data) => stderr += data);
|
|
8269
8478
|
const timer = setTimeout(() => child.kill("SIGKILL"), options.timeoutMs ?? 3e4);
|
|
8270
|
-
const status2 = await new Promise((
|
|
8271
|
-
child.on("error", () =>
|
|
8272
|
-
child.on("close", (code) =>
|
|
8479
|
+
const status2 = await new Promise((resolve7) => {
|
|
8480
|
+
child.on("error", () => resolve7(1));
|
|
8481
|
+
child.on("close", (code) => resolve7(code ?? 1));
|
|
8273
8482
|
});
|
|
8274
8483
|
clearTimeout(timer);
|
|
8275
8484
|
if (status2 === 10)
|
|
8276
8485
|
return { ok: false, reason: "the sandbox let a build read the owner's files" };
|
|
8277
|
-
if (status2 !== 0 || !
|
|
8486
|
+
if (status2 !== 0 || !existsSync15(join22(dir, "home", "ok"))) {
|
|
8278
8487
|
const why = stderr.trim().split("\n").at(-1) || `the probe exited with ${status2}`;
|
|
8279
8488
|
return { ok: false, reason: why.slice(0, 300) };
|
|
8280
8489
|
}
|
|
@@ -8282,7 +8491,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
8282
8491
|
} catch (error) {
|
|
8283
8492
|
return { ok: false, reason: String(error).slice(0, 300) };
|
|
8284
8493
|
} finally {
|
|
8285
|
-
|
|
8494
|
+
rmSync13(dir, { recursive: true, force: true });
|
|
8286
8495
|
}
|
|
8287
8496
|
}
|
|
8288
8497
|
/** Stops a build's process group and every process it started, however it got away. */
|
|
@@ -8291,7 +8500,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
8291
8500
|
if (done) {
|
|
8292
8501
|
const ended = await Promise.race([
|
|
8293
8502
|
done.then(() => true),
|
|
8294
|
-
new Promise((
|
|
8503
|
+
new Promise((resolve7) => setTimeout(() => resolve7(false), 3e3).unref())
|
|
8295
8504
|
]);
|
|
8296
8505
|
if (!ended && pid) signal(-pid, "SIGKILL");
|
|
8297
8506
|
} else if (pid) {
|
|
@@ -8311,7 +8520,7 @@ function listProcesses() {
|
|
|
8311
8520
|
function listFromProc() {
|
|
8312
8521
|
let pids = [];
|
|
8313
8522
|
try {
|
|
8314
|
-
pids =
|
|
8523
|
+
pids = readdirSync7("/proc").filter((name) => /^\d+$/.test(name));
|
|
8315
8524
|
} catch {
|
|
8316
8525
|
return [];
|
|
8317
8526
|
}
|
|
@@ -8351,7 +8560,7 @@ async function stopPids(pids) {
|
|
|
8351
8560
|
if (live.length === 0) return;
|
|
8352
8561
|
const deadline = Date.now() + 3e3;
|
|
8353
8562
|
while (Date.now() < deadline && live.some((pid) => signal(pid, 0))) {
|
|
8354
|
-
await new Promise((
|
|
8563
|
+
await new Promise((resolve7) => setTimeout(resolve7, 100));
|
|
8355
8564
|
}
|
|
8356
8565
|
for (const pid of live) signal(pid, "SIGKILL");
|
|
8357
8566
|
}
|
|
@@ -8360,32 +8569,32 @@ async function stopPids(pids) {
|
|
|
8360
8569
|
init_define_HERD_PACKAGE();
|
|
8361
8570
|
import { execFile as execFile3 } from "node:child_process";
|
|
8362
8571
|
function listClaudeAgents(timeoutMs = 1e4, command = "claude") {
|
|
8363
|
-
return new Promise((
|
|
8572
|
+
return new Promise((resolve7) => {
|
|
8364
8573
|
execFile3(
|
|
8365
8574
|
command,
|
|
8366
8575
|
["agents", "--json", "--all"],
|
|
8367
8576
|
{ env: terminalEnv({}), timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024 },
|
|
8368
8577
|
(error, stdout) => {
|
|
8369
|
-
if (error) return
|
|
8578
|
+
if (error) return resolve7([]);
|
|
8370
8579
|
try {
|
|
8371
8580
|
const rows = JSON.parse(stdout);
|
|
8372
|
-
|
|
8581
|
+
resolve7(Array.isArray(rows) ? rows : []);
|
|
8373
8582
|
} catch {
|
|
8374
|
-
|
|
8583
|
+
resolve7([]);
|
|
8375
8584
|
}
|
|
8376
8585
|
}
|
|
8377
8586
|
);
|
|
8378
8587
|
});
|
|
8379
8588
|
}
|
|
8380
8589
|
function findClaude(shell = process.env.SHELL || "/bin/sh", timeoutMs = 1e4) {
|
|
8381
|
-
return new Promise((
|
|
8590
|
+
return new Promise((resolve7) => {
|
|
8382
8591
|
execFile3(
|
|
8383
8592
|
shell,
|
|
8384
8593
|
["-ilc", "command -v claude"],
|
|
8385
8594
|
{ env: terminalEnv({}), timeout: timeoutMs },
|
|
8386
8595
|
(_error, stdout) => {
|
|
8387
8596
|
const found = String(stdout ?? "").split("\n").map((line2) => line2.trim()).reverse().find((line2) => line2.startsWith("/"));
|
|
8388
|
-
|
|
8597
|
+
resolve7(found ?? null);
|
|
8389
8598
|
}
|
|
8390
8599
|
);
|
|
8391
8600
|
});
|
|
@@ -8493,7 +8702,7 @@ var DEFAULT_TIMING3 = {
|
|
|
8493
8702
|
confirmMs: 8e3,
|
|
8494
8703
|
pasteMs: 600
|
|
8495
8704
|
};
|
|
8496
|
-
var sleep5 = (ms) => new Promise((
|
|
8705
|
+
var sleep5 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
8497
8706
|
var EXIT_WAIT_MS3 = 5e3;
|
|
8498
8707
|
var CodexDriver = class {
|
|
8499
8708
|
constructor(terminals, timing = {}) {
|
|
@@ -8583,12 +8792,12 @@ var CodexDriver = class {
|
|
|
8583
8792
|
if (before.draft && !leftover) return { status: "draft", draft: before.draft };
|
|
8584
8793
|
const sent = leftover ?? text4;
|
|
8585
8794
|
let taken = false;
|
|
8586
|
-
const reported = new Promise((
|
|
8795
|
+
const reported = new Promise((resolve7) => {
|
|
8587
8796
|
this.waiters.set(name, {
|
|
8588
8797
|
text: sent,
|
|
8589
8798
|
taken: () => {
|
|
8590
8799
|
taken = true;
|
|
8591
|
-
|
|
8800
|
+
resolve7();
|
|
8592
8801
|
}
|
|
8593
8802
|
});
|
|
8594
8803
|
});
|
|
@@ -8633,7 +8842,7 @@ var CodexDriver = class {
|
|
|
8633
8842
|
|
|
8634
8843
|
// src/daemon.ts
|
|
8635
8844
|
init_define_HERD_PACKAGE();
|
|
8636
|
-
import { join as
|
|
8845
|
+
import { join as join23 } from "node:path";
|
|
8637
8846
|
|
|
8638
8847
|
// src/dispatch-connection.ts
|
|
8639
8848
|
init_define_HERD_PACKAGE();
|
|
@@ -8737,7 +8946,7 @@ var DispatchConnection = class {
|
|
|
8737
8946
|
|
|
8738
8947
|
// src/daemon.ts
|
|
8739
8948
|
var CONFIG_POLL_MS = 2e3;
|
|
8740
|
-
var daemonSocketPath = () =>
|
|
8949
|
+
var daemonSocketPath = () => join23(herdHome(), "daemon.sock");
|
|
8741
8950
|
var Daemon = class {
|
|
8742
8951
|
constructor(options) {
|
|
8743
8952
|
this.options = options;
|
|
@@ -8819,9 +9028,11 @@ var Daemon = class {
|
|
|
8819
9028
|
return this.registering;
|
|
8820
9029
|
}
|
|
8821
9030
|
async register() {
|
|
8822
|
-
const reposStamp =
|
|
9031
|
+
const reposStamp = checkoutsStamp(this.root());
|
|
8823
9032
|
const gh = this.options.gh?.();
|
|
9033
|
+
const folderUnavailable = this.options.folderUnavailable?.() ?? void 0;
|
|
8824
9034
|
const facts = {
|
|
9035
|
+
...folderUnavailable ? { folderUnavailable } : {},
|
|
8825
9036
|
...this.options.facts,
|
|
8826
9037
|
...this.herdConfig ? { config: this.herdConfig } : {},
|
|
8827
9038
|
...this.configError ? { configError: this.configError } : {},
|
|
@@ -8831,7 +9042,7 @@ var Daemon = class {
|
|
|
8831
9042
|
const response = await this.options.api.fetch("/api/machines", {
|
|
8832
9043
|
method: "POST",
|
|
8833
9044
|
headers: { "Content-Type": "application/json" },
|
|
8834
|
-
body: JSON.stringify(registration(this.config, this.clis, facts,
|
|
9045
|
+
body: JSON.stringify(registration(this.config, this.clis, facts, checkouts(this.root())))
|
|
8835
9046
|
});
|
|
8836
9047
|
if (!response.ok) {
|
|
8837
9048
|
const body = await response.json().catch(() => ({}));
|
|
@@ -8859,7 +9070,7 @@ var Daemon = class {
|
|
|
8859
9070
|
}
|
|
8860
9071
|
}
|
|
8861
9072
|
/**
|
|
8862
|
-
* Registers again when `herd.config.ts` changed, or a
|
|
9073
|
+
* Registers again when `herd.config.ts` changed, or a checkout came or went in the root, so routing
|
|
8863
9074
|
* uses it within seconds.
|
|
8864
9075
|
*/
|
|
8865
9076
|
async refreshHerdConfig() {
|
|
@@ -8869,7 +9080,7 @@ var Daemon = class {
|
|
|
8869
9080
|
};
|
|
8870
9081
|
if (this.refreshing) return false;
|
|
8871
9082
|
const configChanged = source.stamp() !== this.configStamp;
|
|
8872
|
-
if (!configChanged &&
|
|
9083
|
+
if (!configChanged && checkoutsStamp(this.root()) === this.reposStamp) return false;
|
|
8873
9084
|
this.refreshing = true;
|
|
8874
9085
|
try {
|
|
8875
9086
|
if (configChanged) await this.readHerdConfig();
|
|
@@ -8891,16 +9102,41 @@ var Daemon = class {
|
|
|
8891
9102
|
this.connection?.stop();
|
|
8892
9103
|
await this.local.close();
|
|
8893
9104
|
}
|
|
9105
|
+
/** Messages held while a folder isn't there, by conversation, delivered once it is. */
|
|
9106
|
+
held = /* @__PURE__ */ new Map();
|
|
9107
|
+
/**
|
|
9108
|
+
* A folder came or went: registers again, so dispatch knows, and once nothing is missing,
|
|
9109
|
+
* delivers what was held meanwhile.
|
|
9110
|
+
*/
|
|
9111
|
+
async foldersChanged() {
|
|
9112
|
+
await this.reregister();
|
|
9113
|
+
if (this.options.folderUnavailable?.()) return;
|
|
9114
|
+
for (const [conversationId, delivery] of [...this.held]) {
|
|
9115
|
+
this.held.delete(conversationId);
|
|
9116
|
+
await this.options.work.onMessage(conversationId, delivery);
|
|
9117
|
+
this.connection?.acknowledge(conversationId);
|
|
9118
|
+
}
|
|
9119
|
+
}
|
|
8894
9120
|
async onFrame(frame) {
|
|
8895
9121
|
if (frame.type === "offer") return this.onOffer(frame.offer);
|
|
8896
9122
|
if (frame.type === "sign_in") return this.options.onSignIn?.(frame);
|
|
8897
9123
|
if (frame.type === "message") {
|
|
9124
|
+
if (this.options.folderUnavailable?.()) {
|
|
9125
|
+
if (this.held.get(frame.conversationId) !== "interrupt") {
|
|
9126
|
+
this.held.set(frame.conversationId, frame.delivery);
|
|
9127
|
+
}
|
|
9128
|
+
return;
|
|
9129
|
+
}
|
|
8898
9130
|
await this.options.work.onMessage(frame.conversationId, frame.delivery);
|
|
8899
9131
|
this.connection?.acknowledge(frame.conversationId);
|
|
8900
9132
|
}
|
|
8901
9133
|
}
|
|
8902
9134
|
async onOffer(offer) {
|
|
8903
|
-
const
|
|
9135
|
+
const missing = this.options.folderUnavailable?.();
|
|
9136
|
+
const decision = missing ? {
|
|
9137
|
+
accept: false,
|
|
9138
|
+
reason: `A folder this machine works in isn't there: ${missing.target ? `${missing.path} links to ${missing.target}` : missing.path}.`
|
|
9139
|
+
} : await this.options.work.onOffer(offer, this.config);
|
|
8904
9140
|
const answer2 = decision.accept ? { answer: "accept" } : { answer: "refuse", reason: decision.reason };
|
|
8905
9141
|
const response = await this.options.api.fetch(
|
|
8906
9142
|
`/api/machines/${encodeURIComponent(this.config.machineId)}/offers/${encodeURIComponent(offer.id)}`,
|
|
@@ -8920,10 +9156,101 @@ var Daemon = class {
|
|
|
8920
9156
|
}
|
|
8921
9157
|
};
|
|
8922
9158
|
|
|
9159
|
+
// src/folders.ts
|
|
9160
|
+
init_define_HERD_PACKAGE();
|
|
9161
|
+
import { existsSync as existsSync16, lstatSync as lstatSync3, readlinkSync as readlinkSync2, rmSync as rmSync14, writeFileSync as writeFileSync14 } from "node:fs";
|
|
9162
|
+
import { dirname as dirname13, resolve as resolve4 } from "node:path";
|
|
9163
|
+
var FOLDER_WAIT_MS = 2 * 60 * 1e3;
|
|
9164
|
+
var FOLDER_CHECK_MS = 30 * 1e3;
|
|
9165
|
+
function linkOf(path) {
|
|
9166
|
+
try {
|
|
9167
|
+
if (!lstatSync3(path).isSymbolicLink()) return null;
|
|
9168
|
+
return resolve4(dirname13(path), readlinkSync2(path));
|
|
9169
|
+
} catch {
|
|
9170
|
+
return null;
|
|
9171
|
+
}
|
|
9172
|
+
}
|
|
9173
|
+
function missingFolder(path, required, { exists = existsSync16, linkTarget = linkOf } = {}) {
|
|
9174
|
+
if (exists(path)) return null;
|
|
9175
|
+
const target = linkTarget(path);
|
|
9176
|
+
if (target) return { path, target };
|
|
9177
|
+
return required ? { path } : null;
|
|
9178
|
+
}
|
|
9179
|
+
function machineFolderProblem(root, convos, checks = {}) {
|
|
9180
|
+
return missingFolder(dirname13(root), true, checks) ?? missingFolder(convos, false, checks);
|
|
9181
|
+
}
|
|
9182
|
+
async function waitForFolders(problem, {
|
|
9183
|
+
timeoutMs = FOLDER_WAIT_MS,
|
|
9184
|
+
pollMs = 2e3,
|
|
9185
|
+
sleep: sleep8 = (ms) => new Promise((done) => setTimeout(done, ms)),
|
|
9186
|
+
now = Date.now,
|
|
9187
|
+
log = (line2) => console.log(line2)
|
|
9188
|
+
} = {}) {
|
|
9189
|
+
let missing = problem();
|
|
9190
|
+
if (!missing) return null;
|
|
9191
|
+
log(`Waiting for ${describeMissing(missing)} to be there, as when its drive mounts.`);
|
|
9192
|
+
const deadline = now() + timeoutMs;
|
|
9193
|
+
while (missing && now() < deadline) {
|
|
9194
|
+
await sleep8(pollMs);
|
|
9195
|
+
missing = problem();
|
|
9196
|
+
}
|
|
9197
|
+
if (!missing) {
|
|
9198
|
+
log("It's there.");
|
|
9199
|
+
return null;
|
|
9200
|
+
}
|
|
9201
|
+
log(
|
|
9202
|
+
`Taking no work or builds: ${describeMissing(missing)} still isn't there after ${Math.round(timeoutMs / 1e3)} seconds. Looking again every ${FOLDER_CHECK_MS / 1e3} seconds.`
|
|
9203
|
+
);
|
|
9204
|
+
return missing;
|
|
9205
|
+
}
|
|
9206
|
+
var waitForRoot = (root, options = {}) => waitForFolders(() => missingFolder(dirname13(root), true, options), options);
|
|
9207
|
+
var waitForConvos = (convos, options = {}) => waitForFolders(() => missingFolder(convos, false, options), options);
|
|
9208
|
+
var describeMissing = (missing) => missing.target ? `${missing.path}, which links to ${missing.target},` : missing.path;
|
|
9209
|
+
var FolderWatch = class {
|
|
9210
|
+
constructor(options) {
|
|
9211
|
+
this.options = options;
|
|
9212
|
+
this.current = options.initial;
|
|
9213
|
+
this.write();
|
|
9214
|
+
}
|
|
9215
|
+
options;
|
|
9216
|
+
current;
|
|
9217
|
+
timer = null;
|
|
9218
|
+
/** What's missing now, or null. */
|
|
9219
|
+
get problem() {
|
|
9220
|
+
return this.current;
|
|
9221
|
+
}
|
|
9222
|
+
start() {
|
|
9223
|
+
this.timer = setInterval(() => void this.tick(), this.options.everyMs ?? FOLDER_CHECK_MS);
|
|
9224
|
+
this.timer.unref?.();
|
|
9225
|
+
}
|
|
9226
|
+
stop() {
|
|
9227
|
+
if (this.timer) clearInterval(this.timer);
|
|
9228
|
+
}
|
|
9229
|
+
/** Looks once, and tells `onChange` when what's missing changed. */
|
|
9230
|
+
async tick() {
|
|
9231
|
+
const next = this.options.check();
|
|
9232
|
+
if (JSON.stringify(next) === JSON.stringify(this.current)) return;
|
|
9233
|
+
this.current = next;
|
|
9234
|
+
this.write();
|
|
9235
|
+
const log = this.options.log ?? ((line2) => console.log(line2));
|
|
9236
|
+
log(
|
|
9237
|
+
next ? `Taking no work or builds: ${describeMissing(next)} isn't there.` : "The machine's folders are there again, so it takes work again."
|
|
9238
|
+
);
|
|
9239
|
+
await this.options.onChange(next);
|
|
9240
|
+
}
|
|
9241
|
+
write() {
|
|
9242
|
+
try {
|
|
9243
|
+
if (this.current) writeFileSync14(this.options.statusPath, JSON.stringify(this.current));
|
|
9244
|
+
else rmSync14(this.options.statusPath, { force: true });
|
|
9245
|
+
} catch {
|
|
9246
|
+
}
|
|
9247
|
+
}
|
|
9248
|
+
};
|
|
9249
|
+
|
|
8923
9250
|
// src/gh.ts
|
|
8924
9251
|
init_define_HERD_PACKAGE();
|
|
8925
9252
|
init_src();
|
|
8926
|
-
import { execFile as execFile4, spawn as
|
|
9253
|
+
import { execFile as execFile4, spawn as spawn9 } from "node:child_process";
|
|
8927
9254
|
import { promisify as promisify3 } from "node:util";
|
|
8928
9255
|
var GH_CHECK_MS = 10 * 60 * 1e3;
|
|
8929
9256
|
var GH_CODE_WAIT_MS = 30 * 1e3;
|
|
@@ -8994,7 +9321,7 @@ function readDeviceCode(output2) {
|
|
|
8994
9321
|
return code && url === GITHUB_DEVICE_URL ? { code, url: GITHUB_DEVICE_URL } : null;
|
|
8995
9322
|
}
|
|
8996
9323
|
async function signInGh(options) {
|
|
8997
|
-
const spawnGh = options.spawnGh ?? ((args) =>
|
|
9324
|
+
const spawnGh = options.spawnGh ?? ((args) => spawn9("gh", args, {
|
|
8998
9325
|
env: options.env?.() ?? process.env,
|
|
8999
9326
|
stdio: ["ignore", "pipe", "pipe"]
|
|
9000
9327
|
}));
|
|
@@ -9003,8 +9330,8 @@ async function signInGh(options) {
|
|
|
9003
9330
|
let sent = false;
|
|
9004
9331
|
let resolveCode = () => {
|
|
9005
9332
|
};
|
|
9006
|
-
const codeShown = new Promise((
|
|
9007
|
-
resolveCode =
|
|
9333
|
+
const codeShown = new Promise((resolve7) => {
|
|
9334
|
+
resolveCode = resolve7;
|
|
9008
9335
|
});
|
|
9009
9336
|
const read = (chunk) => {
|
|
9010
9337
|
output2 += chunk.toString();
|
|
@@ -9012,9 +9339,9 @@ async function signInGh(options) {
|
|
|
9012
9339
|
};
|
|
9013
9340
|
child.stdout?.on("data", read);
|
|
9014
9341
|
child.stderr?.on("data", read);
|
|
9015
|
-
const exited = new Promise((
|
|
9016
|
-
child.once("close", () =>
|
|
9017
|
-
child.once("error", () =>
|
|
9342
|
+
const exited = new Promise((resolve7) => {
|
|
9343
|
+
child.once("close", () => resolve7());
|
|
9344
|
+
child.once("error", () => resolve7());
|
|
9018
9345
|
});
|
|
9019
9346
|
void exited.then(() => resolveCode(false));
|
|
9020
9347
|
const codeTimer = setTimeout(() => resolveCode(false), options.codeWaitMs ?? GH_CODE_WAIT_MS);
|
|
@@ -9047,23 +9374,23 @@ init_define_HERD_PACKAGE();
|
|
|
9047
9374
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
9048
9375
|
import {
|
|
9049
9376
|
appendFileSync as appendFileSync2,
|
|
9050
|
-
existsSync as
|
|
9051
|
-
mkdirSync as
|
|
9377
|
+
existsSync as existsSync17,
|
|
9378
|
+
mkdirSync as mkdirSync18,
|
|
9052
9379
|
readFileSync as readFileSync17,
|
|
9053
|
-
renameSync as
|
|
9054
|
-
writeFileSync as
|
|
9380
|
+
renameSync as renameSync6,
|
|
9381
|
+
writeFileSync as writeFileSync15
|
|
9055
9382
|
} from "node:fs";
|
|
9056
|
-
import { isAbsolute as isAbsolute3, join as
|
|
9057
|
-
var LOCAL_SETTINGS =
|
|
9383
|
+
import { isAbsolute as isAbsolute3, join as join24 } from "node:path";
|
|
9384
|
+
var LOCAL_SETTINGS = join24(".claude", "settings.local.json");
|
|
9058
9385
|
var git3 = (cwd, ...args) => execFileSync5("git", ["-C", cwd, ...args], {
|
|
9059
9386
|
encoding: "utf8",
|
|
9060
9387
|
stdio: ["ignore", "pipe", "pipe"]
|
|
9061
9388
|
}).trim();
|
|
9062
9389
|
var shellQuote2 = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
9063
9390
|
function writeWorktreeSettings(worktree, settings, ownCommand) {
|
|
9064
|
-
const path =
|
|
9391
|
+
const path = join24(worktree, LOCAL_SETTINGS);
|
|
9065
9392
|
let current = {};
|
|
9066
|
-
if (
|
|
9393
|
+
if (existsSync17(path)) {
|
|
9067
9394
|
try {
|
|
9068
9395
|
const parsed = JSON.parse(readFileSync17(path, "utf8"));
|
|
9069
9396
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
@@ -9085,11 +9412,11 @@ function writeWorktreeSettings(worktree, settings, ownCommand) {
|
|
|
9085
9412
|
}
|
|
9086
9413
|
next.hooks = hooks;
|
|
9087
9414
|
}
|
|
9088
|
-
|
|
9415
|
+
mkdirSync18(join24(worktree, ".claude"), { recursive: true });
|
|
9089
9416
|
const temporary = `${path}.herd-${process.pid}`;
|
|
9090
|
-
|
|
9417
|
+
writeFileSync15(temporary, `${JSON.stringify(next, null, 2)}
|
|
9091
9418
|
`);
|
|
9092
|
-
|
|
9419
|
+
renameSync6(temporary, path);
|
|
9093
9420
|
ignoreLocalSettings(worktree);
|
|
9094
9421
|
return true;
|
|
9095
9422
|
}
|
|
@@ -9105,12 +9432,12 @@ function ignoreLocalSettings(worktree) {
|
|
|
9105
9432
|
} catch {
|
|
9106
9433
|
return;
|
|
9107
9434
|
}
|
|
9108
|
-
const info =
|
|
9109
|
-
const exclude =
|
|
9435
|
+
const info = join24(isAbsolute3(common) ? common : join24(worktree, common), "info");
|
|
9436
|
+
const exclude = join24(info, "exclude");
|
|
9110
9437
|
const line2 = "/.claude/settings.local.json";
|
|
9111
|
-
const existing =
|
|
9438
|
+
const existing = existsSync17(exclude) ? readFileSync17(exclude, "utf8") : "";
|
|
9112
9439
|
if (existing.split("\n").includes(line2)) return;
|
|
9113
|
-
|
|
9440
|
+
mkdirSync18(info, { recursive: true });
|
|
9114
9441
|
appendFileSync2(exclude, `${existing && !existing.endsWith("\n") ? "\n" : ""}${line2}
|
|
9115
9442
|
`);
|
|
9116
9443
|
}
|
|
@@ -9157,7 +9484,7 @@ function loginShellEnv() {
|
|
|
9157
9484
|
|
|
9158
9485
|
// src/machine-checks.ts
|
|
9159
9486
|
init_define_HERD_PACKAGE();
|
|
9160
|
-
import { existsSync as
|
|
9487
|
+
import { existsSync as existsSync18, rmSync as rmSync15 } from "node:fs";
|
|
9161
9488
|
var REQUEST_POLL_MS = 2e3;
|
|
9162
9489
|
var MachineChecks = class {
|
|
9163
9490
|
constructor(options) {
|
|
@@ -9203,8 +9530,8 @@ var MachineChecks = class {
|
|
|
9203
9530
|
/** `friday start` asked for every agent to be activated again. */
|
|
9204
9531
|
takeRequest() {
|
|
9205
9532
|
const path = activationRequestPath(this.options.herdHome);
|
|
9206
|
-
if (!
|
|
9207
|
-
|
|
9533
|
+
if (!existsSync18(path)) return;
|
|
9534
|
+
rmSync15(path, { force: true });
|
|
9208
9535
|
this.activateChosen(true);
|
|
9209
9536
|
}
|
|
9210
9537
|
start() {
|
|
@@ -9252,22 +9579,22 @@ var MachineChecks = class {
|
|
|
9252
9579
|
|
|
9253
9580
|
// src/terminals.ts
|
|
9254
9581
|
init_define_HERD_PACKAGE();
|
|
9255
|
-
import { spawn as
|
|
9256
|
-
import { mkdirSync as
|
|
9582
|
+
import { spawn as spawn10 } from "node:child_process";
|
|
9583
|
+
import { mkdirSync as mkdirSync19, readdirSync as readdirSync8, rmSync as rmSync16 } from "node:fs";
|
|
9257
9584
|
import { connect as connect2 } from "node:net";
|
|
9258
|
-
import { join as
|
|
9585
|
+
import { join as join25 } from "node:path";
|
|
9259
9586
|
init_self();
|
|
9260
9587
|
import {
|
|
9261
9588
|
HOST_PROTOCOL,
|
|
9262
9589
|
hostVersion
|
|
9263
9590
|
} from "./terminal-host.mjs";
|
|
9264
|
-
var hostsDir = () =>
|
|
9265
|
-
var legacyHostSocketPath = () =>
|
|
9266
|
-
var currentHostSocketPath = (version = hostVersion()) =>
|
|
9591
|
+
var hostsDir = () => join25(herdHome(), "terminals");
|
|
9592
|
+
var legacyHostSocketPath = () => join25(herdHome(), "terminals.sock");
|
|
9593
|
+
var currentHostSocketPath = (version = hostVersion()) => join25(hostsDir(), `${version}.sock`);
|
|
9267
9594
|
var speaksProtocol = (protocol) => protocol === HOST_PROTOCOL || protocol === HOST_PROTOCOL - 1;
|
|
9268
|
-
var sleep6 = (ms) => new Promise((
|
|
9595
|
+
var sleep6 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
9269
9596
|
function askHost(path, request) {
|
|
9270
|
-
return new Promise((
|
|
9597
|
+
return new Promise((resolve7, reject) => {
|
|
9271
9598
|
const socket = connect2(path);
|
|
9272
9599
|
let data = "";
|
|
9273
9600
|
socket.on("connect", () => socket.write(`${JSON.stringify(request)}
|
|
@@ -9276,7 +9603,7 @@ function askHost(path, request) {
|
|
|
9276
9603
|
socket.on("end", () => {
|
|
9277
9604
|
try {
|
|
9278
9605
|
const reply = JSON.parse(data);
|
|
9279
|
-
if (reply.ok)
|
|
9606
|
+
if (reply.ok) resolve7(reply);
|
|
9280
9607
|
else reject(new HerdError(reply.error ?? "The terminal host refused that."));
|
|
9281
9608
|
} catch {
|
|
9282
9609
|
reject(new HerdError("The terminal host's answer wasn't readable."));
|
|
@@ -9311,10 +9638,10 @@ var HostTerminals = class {
|
|
|
9311
9638
|
const dir = hostsDir();
|
|
9312
9639
|
let names = [];
|
|
9313
9640
|
try {
|
|
9314
|
-
names =
|
|
9641
|
+
names = readdirSync8(dir).filter((name) => name.endsWith(".sock"));
|
|
9315
9642
|
} catch {
|
|
9316
9643
|
}
|
|
9317
|
-
const paths = [legacyHostSocketPath(), ...names.map((name) =>
|
|
9644
|
+
const paths = [legacyHostSocketPath(), ...names.map((name) => join25(dir, name))];
|
|
9318
9645
|
const found = [];
|
|
9319
9646
|
for (const path of paths) {
|
|
9320
9647
|
try {
|
|
@@ -9326,7 +9653,7 @@ var HostTerminals = class {
|
|
|
9326
9653
|
speaks: speaksProtocol(hello.protocol)
|
|
9327
9654
|
});
|
|
9328
9655
|
} catch (error) {
|
|
9329
|
-
if (isDead(error) && path !== legacyHostSocketPath())
|
|
9656
|
+
if (isDead(error) && path !== legacyHostSocketPath()) rmSync16(path, { force: true });
|
|
9330
9657
|
}
|
|
9331
9658
|
}
|
|
9332
9659
|
this.hosts = found;
|
|
@@ -9347,8 +9674,8 @@ var HostTerminals = class {
|
|
|
9347
9674
|
}
|
|
9348
9675
|
if (this.current()) return;
|
|
9349
9676
|
const path = currentHostSocketPath(this.version);
|
|
9350
|
-
|
|
9351
|
-
const child =
|
|
9677
|
+
mkdirSync19(hostsDir(), { recursive: true, mode: 448 });
|
|
9678
|
+
const child = spawn10(process.execPath, [herdBin, "terminal-host", "--socket", path], {
|
|
9352
9679
|
detached: true,
|
|
9353
9680
|
stdio: "ignore",
|
|
9354
9681
|
env: { ...process.env, HERD_HOME: herdHome() }
|
|
@@ -9475,7 +9802,7 @@ var HostTerminals = class {
|
|
|
9475
9802
|
await sleep6(100);
|
|
9476
9803
|
if (!await askHost(host.path, { op: "hello" }).catch(() => null)) break;
|
|
9477
9804
|
}
|
|
9478
|
-
|
|
9805
|
+
rmSync16(host.path, { force: true });
|
|
9479
9806
|
}
|
|
9480
9807
|
return true;
|
|
9481
9808
|
}
|
|
@@ -9594,8 +9921,8 @@ var UsageReporter = class {
|
|
|
9594
9921
|
// src/workspaces/index.ts
|
|
9595
9922
|
init_define_HERD_PACKAGE();
|
|
9596
9923
|
init_src();
|
|
9597
|
-
import { mkdirSync as
|
|
9598
|
-
import { join as
|
|
9924
|
+
import { mkdirSync as mkdirSync23, readFileSync as readFileSync18, writeFileSync as writeFileSync18 } from "node:fs";
|
|
9925
|
+
import { join as join32 } from "node:path";
|
|
9599
9926
|
|
|
9600
9927
|
// src/workspaces/codex.ts
|
|
9601
9928
|
init_define_HERD_PACKAGE();
|
|
@@ -9607,7 +9934,7 @@ init_define_HERD_PACKAGE();
|
|
|
9607
9934
|
init_define_HERD_PACKAGE();
|
|
9608
9935
|
init_src();
|
|
9609
9936
|
init_self();
|
|
9610
|
-
import { join as
|
|
9937
|
+
import { join as join26 } from "node:path";
|
|
9611
9938
|
import { z as z25 } from "zod";
|
|
9612
9939
|
function hasThread(events) {
|
|
9613
9940
|
return events.some(
|
|
@@ -9642,7 +9969,7 @@ function filesByMessage(events) {
|
|
|
9642
9969
|
}
|
|
9643
9970
|
return files;
|
|
9644
9971
|
}
|
|
9645
|
-
var FILES_FOLDER =
|
|
9972
|
+
var FILES_FOLDER = join26(".herd", "files");
|
|
9646
9973
|
function safeFileName(name) {
|
|
9647
9974
|
const chars = Array.from(name.replace(/[/\\\p{Cc}]/gu, "_").trim());
|
|
9648
9975
|
while (Buffer.byteLength(chars.join("")) > 200) chars.shift();
|
|
@@ -9864,11 +10191,11 @@ async function claimChange(ws, workspace, change) {
|
|
|
9864
10191
|
// src/workspaces/delivery.ts
|
|
9865
10192
|
init_define_HERD_PACKAGE();
|
|
9866
10193
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
9867
|
-
import { join as
|
|
10194
|
+
import { join as join30 } from "node:path";
|
|
9868
10195
|
|
|
9869
10196
|
// src/workspaces/repos.ts
|
|
9870
10197
|
init_define_HERD_PACKAGE();
|
|
9871
|
-
import { join as
|
|
10198
|
+
import { join as join27 } from "node:path";
|
|
9872
10199
|
var rootOf = (ws) => ws.options.root?.() ?? ws.options.config().root ?? defaultRoot();
|
|
9873
10200
|
var gitEnvOf = (ws) => ws.options.gitEnv?.() ?? loginShellEnv();
|
|
9874
10201
|
function knownRemotes(ws) {
|
|
@@ -9920,7 +10247,7 @@ function repoContext(ws, workspace) {
|
|
|
9920
10247
|
remote: repo.remote || remotesOf(ws, { ...workspace, worktrees: [repo] })[0] || "",
|
|
9921
10248
|
branch: repo.branch
|
|
9922
10249
|
})),
|
|
9923
|
-
cloned:
|
|
10250
|
+
cloned: checkouts(root).map(({ key, remote }) => ({ key, remote })),
|
|
9924
10251
|
described: (ws.options.herdConfig?.()?.repos ?? []).map((repo) => ({
|
|
9925
10252
|
remote: repo.remote,
|
|
9926
10253
|
key: repoKey(repo.remote),
|
|
@@ -9980,7 +10307,7 @@ async function addHints(ws, workspace, hints, handedOff) {
|
|
|
9980
10307
|
const added = await addWorktree({
|
|
9981
10308
|
root,
|
|
9982
10309
|
remote: cloneSource(hint.remote, knownRemotes(ws)),
|
|
9983
|
-
path:
|
|
10310
|
+
path: join27(workspace.folder, place.name),
|
|
9984
10311
|
branch,
|
|
9985
10312
|
...handedOff ? { continueFrom: hint.branch ?? branch } : {},
|
|
9986
10313
|
env: gitEnvOf(ws),
|
|
@@ -10000,6 +10327,8 @@ async function addHints(ws, workspace, hints, handedOff) {
|
|
|
10000
10327
|
true
|
|
10001
10328
|
);
|
|
10002
10329
|
}
|
|
10330
|
+
if (added.copied.length > 0)
|
|
10331
|
+
await ws.note(id2, describeCopied(place.name, added.copied), true);
|
|
10003
10332
|
const found = reposInFolder(workspace.folder).find((repo) => repo.folder === place.name);
|
|
10004
10333
|
await recordRepo(ws, id2, {
|
|
10005
10334
|
folder: place.name,
|
|
@@ -10030,14 +10359,14 @@ function handoffRepos(workspace) {
|
|
|
10030
10359
|
return workspace.worktrees.map((repo) => ({
|
|
10031
10360
|
folder: repo.folder,
|
|
10032
10361
|
branch: repo.branch,
|
|
10033
|
-
unpushed: hasUnpushed(
|
|
10362
|
+
unpushed: hasUnpushed(join27(workspace.folder, repo.folder))
|
|
10034
10363
|
}));
|
|
10035
10364
|
}
|
|
10036
10365
|
|
|
10037
10366
|
// src/workspaces/setup.ts
|
|
10038
10367
|
init_define_HERD_PACKAGE();
|
|
10039
|
-
import { chmodSync as chmodSync4, mkdirSync as
|
|
10040
|
-
import { join as
|
|
10368
|
+
import { chmodSync as chmodSync4, mkdirSync as mkdirSync20, writeFileSync as writeFileSync16 } from "node:fs";
|
|
10369
|
+
import { join as join28 } from "node:path";
|
|
10041
10370
|
|
|
10042
10371
|
// src/hooks.ts
|
|
10043
10372
|
init_define_HERD_PACKAGE();
|
|
@@ -10070,7 +10399,7 @@ function configureClaude(ws, workspace) {
|
|
|
10070
10399
|
configureClaudeIn(workspace.folder, (line2) => ws.log(`${workspace.conversationId}: ${line2}`));
|
|
10071
10400
|
}
|
|
10072
10401
|
function configureClaudeIn(folder, log) {
|
|
10073
|
-
const herd = shellQuote2(
|
|
10402
|
+
const herd = shellQuote2(join28(herdPathDir(), "herd"));
|
|
10074
10403
|
try {
|
|
10075
10404
|
const written = writeWorktreeSettings(
|
|
10076
10405
|
folder,
|
|
@@ -10095,10 +10424,10 @@ function configureClaudeIn(folder, log) {
|
|
|
10095
10424
|
}
|
|
10096
10425
|
}
|
|
10097
10426
|
function herdPathDir() {
|
|
10098
|
-
const dir =
|
|
10099
|
-
|
|
10100
|
-
const shim =
|
|
10101
|
-
|
|
10427
|
+
const dir = join28(herdHome(), "bin");
|
|
10428
|
+
mkdirSync20(dir, { recursive: true, mode: 448 });
|
|
10429
|
+
const shim = join28(dir, "herd");
|
|
10430
|
+
writeFileSync16(shim, `#!/bin/sh
|
|
10102
10431
|
exec "${process.execPath}" "${herdBin}" "$@"
|
|
10103
10432
|
`);
|
|
10104
10433
|
chmodSync4(shim, 493);
|
|
@@ -10116,8 +10445,8 @@ async function setUp(ws, conversationId) {
|
|
|
10116
10445
|
const hints = ws.offeredRepos.get(conversationId) ?? [];
|
|
10117
10446
|
ws.offeredRepos.delete(conversationId);
|
|
10118
10447
|
const handedOff = ws.handedOff.delete(conversationId);
|
|
10119
|
-
const folder =
|
|
10120
|
-
|
|
10448
|
+
const folder = folderForConversation(rootOf(ws), conversationId);
|
|
10449
|
+
mkdirSync20(folder, { recursive: true });
|
|
10121
10450
|
const lead = `herd-${conversationPrefix(conversationId)}-1`;
|
|
10122
10451
|
const secret = ws.options.local().grant({ terminalId: lead, conversationId });
|
|
10123
10452
|
const workspace = {
|
|
@@ -10236,8 +10565,8 @@ init_define_HERD_PACKAGE();
|
|
|
10236
10565
|
// src/workspaces/investigation.ts
|
|
10237
10566
|
init_define_HERD_PACKAGE();
|
|
10238
10567
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
10239
|
-
import { appendFileSync as appendFileSync3, mkdirSync as
|
|
10240
|
-
import { join as
|
|
10568
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync21 } from "node:fs";
|
|
10569
|
+
import { join as join29 } from "node:path";
|
|
10241
10570
|
function needsInvestigator(ws, workspace, reading) {
|
|
10242
10571
|
const failedAt = workspace.noticeFailedAt;
|
|
10243
10572
|
if (workspace.agent === "codex" || workspace.agent === "agy") return false;
|
|
@@ -10393,9 +10722,9 @@ async function followInvestigation(ws, workspace) {
|
|
|
10393
10722
|
}
|
|
10394
10723
|
function saveScreen(ws, conversationId, screen, key) {
|
|
10395
10724
|
try {
|
|
10396
|
-
const dir =
|
|
10397
|
-
|
|
10398
|
-
const file =
|
|
10725
|
+
const dir = join29(herdHome(), "dialogs");
|
|
10726
|
+
mkdirSync21(dir, { recursive: true });
|
|
10727
|
+
const file = join29(dir, `${conversationId}-${Date.now()}.txt`);
|
|
10399
10728
|
appendFileSync3(file, `${screen.join("\n")}
|
|
10400
10729
|
|
|
10401
10730
|
Way out: ${key}
|
|
@@ -10599,7 +10928,7 @@ async function deliver(ws, conversationId, delivery) {
|
|
|
10599
10928
|
await ensureLeadTerminal(ws, workspace);
|
|
10600
10929
|
const codex = workspace.agent === "codex";
|
|
10601
10930
|
if (workspace.agent === "agy") {
|
|
10602
|
-
writeAgyFiles(workspace.folder, shellQuote2(
|
|
10931
|
+
writeAgyFiles(workspace.folder, shellQuote2(join30(herdPathDir(), "herd")));
|
|
10603
10932
|
} else if (!codex) configureClaude(ws, workspace);
|
|
10604
10933
|
const ownId = codex || workspace.agent === "agy";
|
|
10605
10934
|
if (!workspace.started) {
|
|
@@ -10733,15 +11062,15 @@ init_define_HERD_PACKAGE();
|
|
|
10733
11062
|
init_src();
|
|
10734
11063
|
import {
|
|
10735
11064
|
createWriteStream,
|
|
10736
|
-
lstatSync as
|
|
10737
|
-
mkdirSync as
|
|
10738
|
-
realpathSync as
|
|
10739
|
-
renameSync as
|
|
10740
|
-
rmSync as
|
|
10741
|
-
statSync as
|
|
10742
|
-
writeFileSync as
|
|
11065
|
+
lstatSync as lstatSync4,
|
|
11066
|
+
mkdirSync as mkdirSync22,
|
|
11067
|
+
realpathSync as realpathSync10,
|
|
11068
|
+
renameSync as renameSync7,
|
|
11069
|
+
rmSync as rmSync17,
|
|
11070
|
+
statSync as statSync9,
|
|
11071
|
+
writeFileSync as writeFileSync17
|
|
10743
11072
|
} from "node:fs";
|
|
10744
|
-
import { dirname as
|
|
11073
|
+
import { dirname as dirname14, join as join31, sep as sep2 } from "node:path";
|
|
10745
11074
|
import { Readable } from "node:stream";
|
|
10746
11075
|
import { pipeline } from "node:stream/promises";
|
|
10747
11076
|
|
|
@@ -10754,8 +11083,8 @@ import { readFile } from "node:fs/promises";
|
|
|
10754
11083
|
// src/attachments.ts
|
|
10755
11084
|
init_define_HERD_PACKAGE();
|
|
10756
11085
|
init_src();
|
|
10757
|
-
import { realpathSync as
|
|
10758
|
-
import { basename as basename3, extname, isAbsolute as isAbsolute4, relative as relative2, resolve as
|
|
11086
|
+
import { realpathSync as realpathSync9, statSync as statSync8 } from "node:fs";
|
|
11087
|
+
import { basename as basename3, extname, isAbsolute as isAbsolute4, relative as relative2, resolve as resolve5 } from "node:path";
|
|
10759
11088
|
var TYPES = {
|
|
10760
11089
|
".png": "image/png",
|
|
10761
11090
|
".jpg": "image/jpeg",
|
|
@@ -10785,33 +11114,33 @@ function isInside(root, path) {
|
|
|
10785
11114
|
function resolveAttachments(worktree, files) {
|
|
10786
11115
|
let root;
|
|
10787
11116
|
try {
|
|
10788
|
-
root =
|
|
11117
|
+
root = realpathSync9(worktree);
|
|
10789
11118
|
} catch {
|
|
10790
11119
|
return { ok: false, error: "This terminal's conversation folder is missing." };
|
|
10791
11120
|
}
|
|
10792
11121
|
const resolved = [];
|
|
10793
11122
|
for (const file of files) {
|
|
10794
|
-
let
|
|
11123
|
+
let real4;
|
|
10795
11124
|
try {
|
|
10796
|
-
|
|
11125
|
+
real4 = realpathSync9(resolve5(worktree, file.path));
|
|
10797
11126
|
} catch {
|
|
10798
11127
|
return { ok: false, error: `There's no file at ${file.path}.` };
|
|
10799
11128
|
}
|
|
10800
|
-
if (!isInside(root,
|
|
11129
|
+
if (!isInside(root, real4)) {
|
|
10801
11130
|
return {
|
|
10802
11131
|
ok: false,
|
|
10803
11132
|
error: `${file.path} is outside this conversation's folder, so it can't be attached.`
|
|
10804
11133
|
};
|
|
10805
11134
|
}
|
|
10806
|
-
const stats =
|
|
11135
|
+
const stats = statSync8(real4);
|
|
10807
11136
|
if (!stats.isFile()) return { ok: false, error: `${file.path} isn't a file.` };
|
|
10808
11137
|
if (stats.size > MAX_FILE_BYTES) {
|
|
10809
11138
|
return { ok: false, error: `${file.path} is larger than 100 MB, so it can't be attached.` };
|
|
10810
11139
|
}
|
|
10811
11140
|
resolved.push({
|
|
10812
|
-
path:
|
|
11141
|
+
path: real4,
|
|
10813
11142
|
name: basename3(file.path),
|
|
10814
|
-
type: mediaTypeFor(
|
|
11143
|
+
type: mediaTypeFor(real4),
|
|
10815
11144
|
size: stats.size,
|
|
10816
11145
|
...file.title ? { title: file.title } : {},
|
|
10817
11146
|
...file.alt ? { alt: file.alt } : {}
|
|
@@ -10969,11 +11298,11 @@ async function permission(ws, workspace, request, gone) {
|
|
|
10969
11298
|
}
|
|
10970
11299
|
if (state.state === "cancelled") return { ok: true, decision: null };
|
|
10971
11300
|
}
|
|
10972
|
-
await new Promise((
|
|
10973
|
-
const timer = setTimeout(
|
|
11301
|
+
await new Promise((resolve7) => {
|
|
11302
|
+
const timer = setTimeout(resolve7, ws.options.promptPollMs ?? 1500);
|
|
10974
11303
|
gone.addEventListener("abort", () => {
|
|
10975
11304
|
clearTimeout(timer);
|
|
10976
|
-
|
|
11305
|
+
resolve7();
|
|
10977
11306
|
});
|
|
10978
11307
|
});
|
|
10979
11308
|
}
|
|
@@ -11327,7 +11656,7 @@ async function automations(ws, workspace, request, gone = new AbortController().
|
|
|
11327
11656
|
const deadline = Date.now() + (asked.waitMs ?? 9e4);
|
|
11328
11657
|
const promptPath = `${ws.conversationPath(workspace.conversationId)}/prompts/${encodeURIComponent(promptId ?? "")}`;
|
|
11329
11658
|
while (promptId && Date.now() < deadline && !gone.aborted) {
|
|
11330
|
-
await new Promise((
|
|
11659
|
+
await new Promise((resolve7) => setTimeout(resolve7, ws.options.promptPollMs ?? 1500));
|
|
11331
11660
|
const prompt = await ws.api(promptPath).catch(() => null);
|
|
11332
11661
|
if (!prompt?.ok) continue;
|
|
11333
11662
|
const state = await prompt.json();
|
|
@@ -11716,34 +12045,34 @@ async function bringIn(ws, workspace, files) {
|
|
|
11716
12045
|
return listed;
|
|
11717
12046
|
}
|
|
11718
12047
|
async function download(ws, workspace, file) {
|
|
11719
|
-
const path =
|
|
11720
|
-
const target =
|
|
11721
|
-
if (
|
|
11722
|
-
const herd =
|
|
11723
|
-
if (
|
|
12048
|
+
const path = join31(FILES_FOLDER, safeFileName(file.id), safeFileName(file.name));
|
|
12049
|
+
const target = join31(workspace.folder, path);
|
|
12050
|
+
if (statSync9(target, { throwIfNoEntry: false })?.size === file.size) return path;
|
|
12051
|
+
const herd = join31(workspace.folder, ".herd");
|
|
12052
|
+
if (lstatSync4(herd, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
11724
12053
|
throw new Error("its folder leads outside the worktree");
|
|
11725
12054
|
}
|
|
11726
|
-
|
|
11727
|
-
if (!
|
|
12055
|
+
mkdirSync22(dirname14(target), { recursive: true });
|
|
12056
|
+
if (!realpathSync10(dirname14(target)).startsWith(realpathSync10(workspace.folder) + sep2)) {
|
|
11728
12057
|
throw new Error("its folder leads outside the worktree");
|
|
11729
12058
|
}
|
|
11730
|
-
|
|
12059
|
+
writeFileSync17(join31(herd, ".gitignore"), "*\n");
|
|
11731
12060
|
const response = await ws.api(
|
|
11732
12061
|
`${ws.conversationPath(workspace.conversationId)}/files/${encodeURIComponent(file.id)}`
|
|
11733
12062
|
);
|
|
11734
12063
|
if (!response.ok || !response.body) throw new Error(`the server answered ${response.status}`);
|
|
11735
12064
|
const partial = `${target}.partial`;
|
|
11736
|
-
|
|
12065
|
+
rmSync17(partial, { force: true });
|
|
11737
12066
|
try {
|
|
11738
12067
|
await pipeline(
|
|
11739
12068
|
Readable.fromWeb(response.body),
|
|
11740
12069
|
createWriteStream(partial, { flags: "wx" })
|
|
11741
12070
|
);
|
|
11742
|
-
const { size } =
|
|
12071
|
+
const { size } = statSync9(partial);
|
|
11743
12072
|
if (size !== file.size) throw new Error(`it came to ${size} bytes, not ${file.size}`);
|
|
11744
|
-
|
|
12073
|
+
renameSync7(partial, target);
|
|
11745
12074
|
} finally {
|
|
11746
|
-
|
|
12075
|
+
rmSync17(partial, { force: true });
|
|
11747
12076
|
}
|
|
11748
12077
|
return path;
|
|
11749
12078
|
}
|
|
@@ -12017,7 +12346,7 @@ var Workspaces = class {
|
|
|
12017
12346
|
return this.driver;
|
|
12018
12347
|
}
|
|
12019
12348
|
// ---- State, kept on disk so a restarted daemon picks up where it was.
|
|
12020
|
-
statePath = () =>
|
|
12349
|
+
statePath = () => join32(herdHome(), "state.json");
|
|
12021
12350
|
load() {
|
|
12022
12351
|
try {
|
|
12023
12352
|
return stateSchema.parse(JSON.parse(readFileSync18(this.statePath(), "utf8"))).workspaces;
|
|
@@ -12026,8 +12355,8 @@ var Workspaces = class {
|
|
|
12026
12355
|
}
|
|
12027
12356
|
}
|
|
12028
12357
|
save(workspaces) {
|
|
12029
|
-
|
|
12030
|
-
|
|
12358
|
+
mkdirSync23(herdHome(), { recursive: true, mode: 448 });
|
|
12359
|
+
writeFileSync18(this.statePath(), JSON.stringify({ workspaces }, null, 2), { mode: 384 });
|
|
12031
12360
|
}
|
|
12032
12361
|
get(conversationId) {
|
|
12033
12362
|
return this.load()[conversationId] ?? null;
|
|
@@ -12237,7 +12566,19 @@ async function runDaemon(args) {
|
|
|
12237
12566
|
const root = setRoot(defaultRoot()).root;
|
|
12238
12567
|
console.log(`No root folder is set for this machine, so it works from ${root}.`);
|
|
12239
12568
|
}
|
|
12240
|
-
|
|
12569
|
+
const folderTiming = {
|
|
12570
|
+
pollMs: Number(process.env.HERD_ROOT_POLL_MS) || 2e3,
|
|
12571
|
+
...process.env.HERD_FOLDER_WAIT_MS ? { timeoutMs: Number(process.env.HERD_FOLDER_WAIT_MS) } : {}
|
|
12572
|
+
};
|
|
12573
|
+
const missing = await waitForRoot(machineRoot(), folderTiming) ?? await waitForConvos(convosPath(machineRoot()), folderTiming);
|
|
12574
|
+
let daemon = null;
|
|
12575
|
+
const folders = new FolderWatch({
|
|
12576
|
+
check: () => machineFolderProblem(machineRoot(), convosPath(machineRoot())),
|
|
12577
|
+
initial: missing,
|
|
12578
|
+
statusPath: join33(herdHome(), "folder-unavailable.json"),
|
|
12579
|
+
...process.env.HERD_FOLDER_CHECK_MS ? { everyMs: Number(process.env.HERD_FOLDER_CHECK_MS) } : {},
|
|
12580
|
+
onChange: () => daemon?.foldersChanged()
|
|
12581
|
+
});
|
|
12241
12582
|
const api = apiUrl();
|
|
12242
12583
|
const terminals = new HostTerminals({ log: (line2) => console.log(line2) });
|
|
12243
12584
|
await terminals.ensureHost();
|
|
@@ -12248,7 +12589,6 @@ async function runDaemon(args) {
|
|
|
12248
12589
|
headers: isLocal(api) ? localSignIn() : { Authorization: `Bearer ${await accessToken()}` }
|
|
12249
12590
|
})
|
|
12250
12591
|
};
|
|
12251
|
-
let daemon = null;
|
|
12252
12592
|
let activator = null;
|
|
12253
12593
|
let checks = null;
|
|
12254
12594
|
const log = (line2) => console.log(line2);
|
|
@@ -12330,7 +12670,7 @@ async function runDaemon(args) {
|
|
|
12330
12670
|
env: (secret) => envFor(workspaces, secret),
|
|
12331
12671
|
// As for a conversation: Claude Code's hooks and status line, or agy's. Codex has none.
|
|
12332
12672
|
configure: (agent, folder) => {
|
|
12333
|
-
if (agent === "agy") writeAgyFiles(folder, shellQuote2(
|
|
12673
|
+
if (agent === "agy") writeAgyFiles(folder, shellQuote2(join33(herdPathDir(), "herd")));
|
|
12334
12674
|
else if (agent === "claude-code") configureClaudeIn(folder, log);
|
|
12335
12675
|
},
|
|
12336
12676
|
...shellCommand ? { shellCommand } : {},
|
|
@@ -12368,9 +12708,11 @@ async function runDaemon(args) {
|
|
|
12368
12708
|
activations: () => activator?.states() ?? {},
|
|
12369
12709
|
gh: () => checks?.gh ?? null,
|
|
12370
12710
|
onSignIn: (frame) => void checks?.signIn(frame),
|
|
12371
|
-
onConfigChanged: () => checks?.activateChosen()
|
|
12711
|
+
onConfigChanged: () => checks?.activateChosen(),
|
|
12712
|
+
folderUnavailable: () => folders.problem
|
|
12372
12713
|
});
|
|
12373
12714
|
await daemon.start();
|
|
12715
|
+
folders.start();
|
|
12374
12716
|
checks.start();
|
|
12375
12717
|
usage.start();
|
|
12376
12718
|
const display = process.env.HERD_DISPLAY === "off" ? null : new (await Promise.resolve().then(() => (init_desk_display(), desk_display_exports))).DeskDisplay({
|
|
@@ -12411,9 +12753,11 @@ async function runDaemon(args) {
|
|
|
12411
12753
|
headers: { Authorization: `Bearer ${runnerKey.key}` }
|
|
12412
12754
|
}),
|
|
12413
12755
|
serviceUrl: runnerKey.url,
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
repos
|
|
12756
|
+
// None while a folder it works in isn't there, as for its conversations.
|
|
12757
|
+
capacity: () => sandbox.ok && !folders.problem ? runnerCapacity(daemon?.configuration().config?.builds) : 0,
|
|
12758
|
+
// The repos builds have cloned here, as many as the service's hello takes, to put this runner
|
|
12759
|
+
// first for their builds.
|
|
12760
|
+
repos: () => bareClones(join33(herdHome(), "builds", "clones")).map((repo) => repo.key).filter((key) => key.length <= 300).slice(0, 100),
|
|
12417
12761
|
runner: new BuildRunner({
|
|
12418
12762
|
root: () => machineRoot(),
|
|
12419
12763
|
gitEnv,
|
|
@@ -12446,10 +12790,11 @@ async function runDaemon(args) {
|
|
|
12446
12790
|
const codexFollower = setInterval(() => {
|
|
12447
12791
|
void workspaces.followCodex().catch((error) => console.error(`Reading Codex's sessions failed: ${error}`));
|
|
12448
12792
|
}, 250);
|
|
12449
|
-
await new Promise((
|
|
12450
|
-
for (const signal2 of ["SIGINT", "SIGTERM"]) process.once(signal2, () =>
|
|
12793
|
+
await new Promise((resolve7) => {
|
|
12794
|
+
for (const signal2 of ["SIGINT", "SIGTERM"]) process.once(signal2, () => resolve7());
|
|
12451
12795
|
});
|
|
12452
12796
|
builds?.stop();
|
|
12797
|
+
folders.stop();
|
|
12453
12798
|
checks.stop();
|
|
12454
12799
|
activator.stop();
|
|
12455
12800
|
usage.stop();
|
|
@@ -12543,11 +12888,11 @@ function agyAnswer(event2, output2) {
|
|
|
12543
12888
|
}
|
|
12544
12889
|
|
|
12545
12890
|
// src/hook-client.ts
|
|
12546
|
-
var sleep7 = (ms) => new Promise((
|
|
12891
|
+
var sleep7 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
12547
12892
|
function within2(promise, ms) {
|
|
12548
12893
|
let timer;
|
|
12549
|
-
const timeout = new Promise((
|
|
12550
|
-
timer = setTimeout(() =>
|
|
12894
|
+
const timeout = new Promise((resolve7) => {
|
|
12895
|
+
timer = setTimeout(() => resolve7(null), ms);
|
|
12551
12896
|
});
|
|
12552
12897
|
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
12553
12898
|
}
|
|
@@ -12596,11 +12941,11 @@ async function runAgyHook(event2, stdin, env, options = {}) {
|
|
|
12596
12941
|
|
|
12597
12942
|
// src/statusline.ts
|
|
12598
12943
|
init_define_HERD_PACKAGE();
|
|
12599
|
-
import { spawn as
|
|
12944
|
+
import { spawn as spawn11 } from "node:child_process";
|
|
12600
12945
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
12601
12946
|
import { connect as connect3 } from "node:net";
|
|
12602
12947
|
import { homedir as homedir7 } from "node:os";
|
|
12603
|
-
import { join as
|
|
12948
|
+
import { join as join34 } from "node:path";
|
|
12604
12949
|
var MAX_INPUT = 64 * 1024;
|
|
12605
12950
|
var REPORT_TIMEOUT_MS = 300;
|
|
12606
12951
|
var OWN_COMMAND_TIMEOUT_MS = 5e3;
|
|
@@ -12637,7 +12982,7 @@ function agyStateFrom(input) {
|
|
|
12637
12982
|
function ownAgyStatusLine(home = homedir7()) {
|
|
12638
12983
|
try {
|
|
12639
12984
|
const settings = JSON.parse(
|
|
12640
|
-
readFileSync21(
|
|
12985
|
+
readFileSync21(join34(home, ".gemini", "antigravity-cli", "settings.json"), "utf8")
|
|
12641
12986
|
);
|
|
12642
12987
|
const command = settings.statusLine?.command;
|
|
12643
12988
|
if (typeof command !== "string" || !command.trim()) return null;
|
|
@@ -12657,25 +13002,25 @@ function commandIn(path) {
|
|
|
12657
13002
|
}
|
|
12658
13003
|
}
|
|
12659
13004
|
function ownStatusLine(projectDir, home = homedir7()) {
|
|
12660
|
-
return commandIn(
|
|
13005
|
+
return commandIn(join34(projectDir, ".claude", "settings.json")) ?? commandIn(join34(home, ".claude", "settings.json"));
|
|
12661
13006
|
}
|
|
12662
13007
|
function runOwnCommand(command, input, cwd, timeoutMs = OWN_COMMAND_TIMEOUT_MS) {
|
|
12663
|
-
return new Promise((
|
|
13008
|
+
return new Promise((resolve7) => {
|
|
12664
13009
|
let output2 = "";
|
|
12665
13010
|
let child;
|
|
12666
13011
|
try {
|
|
12667
|
-
child =
|
|
13012
|
+
child = spawn11("sh", ["-c", command], {
|
|
12668
13013
|
cwd,
|
|
12669
13014
|
detached: true,
|
|
12670
13015
|
stdio: ["pipe", "pipe", "ignore"]
|
|
12671
13016
|
});
|
|
12672
13017
|
} catch {
|
|
12673
|
-
return
|
|
13018
|
+
return resolve7("");
|
|
12674
13019
|
}
|
|
12675
13020
|
const finish = (text4) => {
|
|
12676
13021
|
clearTimeout(timer);
|
|
12677
13022
|
child.stdout?.destroy();
|
|
12678
|
-
|
|
13023
|
+
resolve7(text4);
|
|
12679
13024
|
};
|
|
12680
13025
|
const timer = setTimeout(() => {
|
|
12681
13026
|
try {
|
|
@@ -12693,12 +13038,12 @@ function runOwnCommand(command, input, cwd, timeoutMs = OWN_COMMAND_TIMEOUT_MS)
|
|
|
12693
13038
|
});
|
|
12694
13039
|
}
|
|
12695
13040
|
function tellDaemon(path, request, timeoutMs = REPORT_TIMEOUT_MS) {
|
|
12696
|
-
return new Promise((
|
|
13041
|
+
return new Promise((resolve7) => {
|
|
12697
13042
|
const socket = connect3(path);
|
|
12698
13043
|
const done = () => {
|
|
12699
13044
|
clearTimeout(timer);
|
|
12700
13045
|
socket.destroy();
|
|
12701
|
-
|
|
13046
|
+
resolve7();
|
|
12702
13047
|
};
|
|
12703
13048
|
const timer = setTimeout(done, timeoutMs);
|
|
12704
13049
|
socket.on("connect", () => socket.write(`${JSON.stringify(request)}
|
|
@@ -12950,7 +13295,7 @@ init_define_HERD_PACKAGE();
|
|
|
12950
13295
|
import { connect as connect4 } from "node:net";
|
|
12951
13296
|
var DETACH_KEY = "";
|
|
12952
13297
|
function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: process.stdout }, path = currentHostSocketPath()) {
|
|
12953
|
-
return new Promise((
|
|
13298
|
+
return new Promise((resolve7, reject) => {
|
|
12954
13299
|
const socket = connect4(path);
|
|
12955
13300
|
const size = () => ({ cols: stdout.columns || 120, rows: stdout.rows || 40 });
|
|
12956
13301
|
let buffer = "";
|
|
@@ -12964,7 +13309,7 @@ function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: proces
|
|
|
12964
13309
|
stdin.pause();
|
|
12965
13310
|
stdout.off("resize", onResize);
|
|
12966
13311
|
socket.end();
|
|
12967
|
-
|
|
13312
|
+
resolve7(result);
|
|
12968
13313
|
};
|
|
12969
13314
|
const onInput = (chunk) => {
|
|
12970
13315
|
const data = chunk.toString("utf8");
|
|
@@ -13049,20 +13394,21 @@ async function runTerminalHost(rest) {
|
|
|
13049
13394
|
|
|
13050
13395
|
// src/commands/repo.ts
|
|
13051
13396
|
init_define_HERD_PACKAGE();
|
|
13052
|
-
import { join as
|
|
13397
|
+
import { join as join35 } from "node:path";
|
|
13053
13398
|
import { z as z32 } from "zod";
|
|
13054
13399
|
var REPO_HELP = `Usage: herd repo <url or owner/name> [folder]
|
|
13055
13400
|
|
|
13056
13401
|
Checks a repo out in this conversation's folder, on a branch named for the conversation, started
|
|
13057
|
-
from the freshly fetched default branch, and records it in the conversation.
|
|
13058
|
-
|
|
13059
|
-
|
|
13402
|
+
from the freshly fetched default branch, and records it in the conversation. It comes from your
|
|
13403
|
+
owner's own checkout of the repo in the machine's root, and gets that checkout's local files git
|
|
13404
|
+
ignores, such as .env, copied in. A repo the root doesn't have is cloned into it first, with your
|
|
13405
|
+
owner's own git credentials. owner/name means one of the repos herd repos lists, or else
|
|
13060
13406
|
github.com/owner/name. The folder is the repo's name, or <owner>-<name> when that's taken, unless
|
|
13061
13407
|
you give one. Running it again for a repo that's already here changes nothing.`;
|
|
13062
13408
|
var REPOS_HELP = `Usage: herd repos [--json]
|
|
13063
13409
|
|
|
13064
|
-
Lists the repos in this conversation's folder, the repos
|
|
13065
|
-
repos your owner's herd.config.ts describes, with what each is for. Add one with herd repo.`;
|
|
13410
|
+
Lists the repos in this conversation's folder, the repos checked out in this machine's root, and
|
|
13411
|
+
the repos your owner's herd.config.ts describes, with what each is for. Add one with herd repo.`;
|
|
13066
13412
|
var repoContextSchema = z32.object({
|
|
13067
13413
|
folder: z32.string(),
|
|
13068
13414
|
root: z32.string(),
|
|
@@ -13136,7 +13482,7 @@ async function runRepo(rest) {
|
|
|
13136
13482
|
folderName ? `${folderName} is already taken in this conversation's folder. Give another name.` : `Both ${repoName(key)} and ${repoOwner(key)}-${repoName(key)} are taken in this conversation's folder. Give a folder name: herd repo ${asked} <folder>.`
|
|
13137
13483
|
);
|
|
13138
13484
|
}
|
|
13139
|
-
const path =
|
|
13485
|
+
const path = join35(here.folder, place.name);
|
|
13140
13486
|
const recorded = here.recorded.some((repo) => repo.folder === place.name);
|
|
13141
13487
|
if (place.existing && recorded) {
|
|
13142
13488
|
const branch = here.recorded.find((repo) => repo.folder === place.name)?.branch ?? here.branch;
|
|
@@ -13178,13 +13524,14 @@ ${error.message}`);
|
|
|
13178
13524
|
note = ` The conversation couldn't record it: ${error instanceof Error ? error.message : error}`;
|
|
13179
13525
|
}
|
|
13180
13526
|
const lines = [
|
|
13181
|
-
added.created ? `Added ${key} in ${path}, on ${added.branch} from ${added.base}.${added.cloned ? " herd cloned it
|
|
13527
|
+
added.created ? `Added ${key} in ${path}, on ${added.branch} from ${added.base}.${added.cloned ? " herd cloned it into the root first." : ""}` : `${key} is already here, in ${path}.`
|
|
13182
13528
|
];
|
|
13183
13529
|
if (added.fetchFailed) {
|
|
13184
13530
|
lines.push(
|
|
13185
13531
|
`Couldn't fetch it, so it started from the last fetched ${added.base}: ${added.fetchFailed}`
|
|
13186
13532
|
);
|
|
13187
13533
|
}
|
|
13534
|
+
if (added.copied.length > 0) lines.push(describeCopied(place.name, added.copied));
|
|
13188
13535
|
if (added.created) lines.push("Its own AGENTS.md or README says how to set it up.");
|
|
13189
13536
|
console.log(lines.join("\n") + note);
|
|
13190
13537
|
return 0;
|
|
@@ -13196,7 +13543,7 @@ function formatRepos(here) {
|
|
|
13196
13543
|
for (const repo of here.recorded)
|
|
13197
13544
|
lines.push(` ${repo.folder} ${repo.remote} on ${repo.branch}`);
|
|
13198
13545
|
const cloned = here.cloned.filter((repo) => !inFolder.has(repo.key));
|
|
13199
|
-
lines.push("", "
|
|
13546
|
+
lines.push("", "Checked out in the root:");
|
|
13200
13547
|
if (cloned.length === 0) lines.push(" none others");
|
|
13201
13548
|
const about = new Map(here.described.map((repo) => [repo.key, repo.about]));
|
|
13202
13549
|
for (const repo of cloned) {
|
|
@@ -13240,7 +13587,7 @@ async function runRepos(rest) {
|
|
|
13240
13587
|
init_define_HERD_PACKAGE();
|
|
13241
13588
|
init_src();
|
|
13242
13589
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
13243
|
-
import { resolve as
|
|
13590
|
+
import { resolve as resolve6 } from "node:path";
|
|
13244
13591
|
var SEND_HELP = `Usage: herd send <text>
|
|
13245
13592
|
herd send -- <text that starts with a dash>
|
|
13246
13593
|
herd send < reply.txt
|
|
@@ -13323,7 +13670,7 @@ function withAbsolutePaths(message, cwd = process.cwd()) {
|
|
|
13323
13670
|
return {
|
|
13324
13671
|
...message,
|
|
13325
13672
|
files: files.map(
|
|
13326
|
-
(file) => file && typeof file === "object" && typeof file.path === "string" ? { ...file, path:
|
|
13673
|
+
(file) => file && typeof file === "object" && typeof file.path === "string" ? { ...file, path: resolve6(cwd, file.path) } : file
|
|
13327
13674
|
)
|
|
13328
13675
|
};
|
|
13329
13676
|
}
|
|
@@ -13960,7 +14307,7 @@ ${HELP4}`);
|
|
|
13960
14307
|
// src/package-entry.ts
|
|
13961
14308
|
var invoked = (() => {
|
|
13962
14309
|
try {
|
|
13963
|
-
return
|
|
14310
|
+
return realpathSync11(process.argv[1] ?? "");
|
|
13964
14311
|
} catch {
|
|
13965
14312
|
return "";
|
|
13966
14313
|
}
|