@herdingchats/friday 0.1.556 → 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 +890 -556
- 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,11 +5426,11 @@ 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
|
|
5347
|
-
var systemdBooted = () =>
|
|
5432
|
+
import { dirname as dirname8, join as join10, win32 } from "node:path";
|
|
5433
|
+
var systemdBooted = () => existsSync7("/run/systemd/system");
|
|
5348
5434
|
var LAUNCH_AGENT = "com.herdingchats.friday";
|
|
5349
5435
|
var SYSTEMD_UNIT = "herdingchats-friday.service";
|
|
5350
5436
|
async function detectService(run4, platform2 = process.platform, override = process.env.HERD_SERVICE, booted = systemdBooted) {
|
|
@@ -5360,7 +5446,7 @@ async function detectService(run4, platform2 = process.platform, override = proc
|
|
|
5360
5446
|
function servicePath(node, platform2 = process.platform, inherited = process.env.PATH ?? process.env.Path) {
|
|
5361
5447
|
if (platform2 === "win32") return [win32.dirname(node), inherited].filter(Boolean).join(";");
|
|
5362
5448
|
return [
|
|
5363
|
-
|
|
5449
|
+
dirname8(node),
|
|
5364
5450
|
"/opt/homebrew/bin",
|
|
5365
5451
|
"/usr/local/bin",
|
|
5366
5452
|
"/usr/bin",
|
|
@@ -5431,13 +5517,13 @@ WantedBy=default.target
|
|
|
5431
5517
|
`;
|
|
5432
5518
|
}
|
|
5433
5519
|
function writeIfChanged(path, text4) {
|
|
5434
|
-
if (
|
|
5435
|
-
|
|
5520
|
+
if (existsSync7(path) && readFileSync7(path, "utf8") === text4) return false;
|
|
5521
|
+
mkdirSync8(dirname8(path), { recursive: true });
|
|
5436
5522
|
writeFileSync7(path, text4);
|
|
5437
5523
|
return true;
|
|
5438
5524
|
}
|
|
5439
5525
|
function launchd(context2) {
|
|
5440
|
-
const plist =
|
|
5526
|
+
const plist = join10(context2.home, "Library", "LaunchAgents", `${LAUNCH_AGENT}.plist`);
|
|
5441
5527
|
const domain = `gui/${userInfo().uid}`;
|
|
5442
5528
|
const target = `${domain}/${LAUNCH_AGENT}`;
|
|
5443
5529
|
const loaded = async () => (await context2.run("launchctl", ["print", target])).status === 0;
|
|
@@ -5463,7 +5549,7 @@ function launchd(context2) {
|
|
|
5463
5549
|
};
|
|
5464
5550
|
}
|
|
5465
5551
|
function systemd(context2) {
|
|
5466
|
-
const unit =
|
|
5552
|
+
const unit = join10(context2.home, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
5467
5553
|
const systemctl = async (...args) => {
|
|
5468
5554
|
const ran = await context2.run("systemctl", ["--user", ...args]);
|
|
5469
5555
|
if (ran.status !== 0)
|
|
@@ -5487,10 +5573,10 @@ function systemd(context2) {
|
|
|
5487
5573
|
};
|
|
5488
5574
|
}
|
|
5489
5575
|
function spawnInBackground(command, env, log) {
|
|
5490
|
-
|
|
5576
|
+
mkdirSync8(dirname8(log), { recursive: true });
|
|
5491
5577
|
const out = openSync3(log, "a");
|
|
5492
5578
|
const [program = process.execPath, ...args] = command;
|
|
5493
|
-
|
|
5579
|
+
spawn3(program, args, {
|
|
5494
5580
|
detached: true,
|
|
5495
5581
|
// On Windows, a detached process otherwise opens a console window of its own.
|
|
5496
5582
|
windowsHide: true,
|
|
@@ -5542,25 +5628,25 @@ function serviceFor(kind, context2) {
|
|
|
5542
5628
|
// src/friday/start.ts
|
|
5543
5629
|
init_define_HERD_PACKAGE();
|
|
5544
5630
|
init_src();
|
|
5545
|
-
import { mkdirSync as
|
|
5546
|
-
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";
|
|
5547
5633
|
|
|
5548
5634
|
// src/friday/legacy.ts
|
|
5549
5635
|
init_define_HERD_PACKAGE();
|
|
5550
|
-
import { existsSync as
|
|
5636
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, rmSync as rmSync8 } from "node:fs";
|
|
5551
5637
|
import { userInfo as userInfo2 } from "node:os";
|
|
5552
|
-
import { join as
|
|
5638
|
+
import { join as join11 } from "node:path";
|
|
5553
5639
|
var OLD_LAUNCH_AGENT = "com.herdingchats.herd";
|
|
5554
5640
|
var OLD_APPS = ["herd-daemon", "herd-updater"];
|
|
5555
5641
|
function oldPm2s(paths) {
|
|
5556
|
-
const dir =
|
|
5557
|
-
if (!
|
|
5558
|
-
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));
|
|
5559
5645
|
}
|
|
5560
5646
|
async function retireOldDaemon(paths, { home, run: run4 }) {
|
|
5561
5647
|
const removed = [];
|
|
5562
|
-
const plist =
|
|
5563
|
-
if (
|
|
5648
|
+
const plist = join11(home, "Library", "LaunchAgents", `${OLD_LAUNCH_AGENT}.plist`);
|
|
5649
|
+
if (existsSync8(plist)) {
|
|
5564
5650
|
await run4("launchctl", ["bootout", `gui/${userInfo2().uid}/${OLD_LAUNCH_AGENT}`]);
|
|
5565
5651
|
rmSync8(plist, { force: true });
|
|
5566
5652
|
removed.push(`the login item ${OLD_LAUNCH_AGENT}`);
|
|
@@ -5584,24 +5670,24 @@ async function retireOldDaemon(paths, { home, run: run4 }) {
|
|
|
5584
5670
|
|
|
5585
5671
|
// src/friday/versions.ts
|
|
5586
5672
|
init_define_HERD_PACKAGE();
|
|
5587
|
-
import { spawn as
|
|
5673
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
5588
5674
|
import {
|
|
5589
|
-
existsSync as
|
|
5590
|
-
mkdirSync as
|
|
5675
|
+
existsSync as existsSync9,
|
|
5676
|
+
mkdirSync as mkdirSync9,
|
|
5591
5677
|
readdirSync as readdirSync4,
|
|
5592
5678
|
readFileSync as readFileSync8,
|
|
5593
5679
|
readlinkSync,
|
|
5594
|
-
realpathSync as
|
|
5680
|
+
realpathSync as realpathSync3,
|
|
5595
5681
|
renameSync as renameSync3,
|
|
5596
5682
|
rmSync as rmSync9,
|
|
5597
5683
|
symlinkSync,
|
|
5598
5684
|
writeFileSync as writeFileSync8
|
|
5599
5685
|
} from "node:fs";
|
|
5600
|
-
import { basename as basename2, dirname as
|
|
5601
|
-
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) => {
|
|
5602
5688
|
let stdout = "";
|
|
5603
5689
|
let stderr = "";
|
|
5604
|
-
const child =
|
|
5690
|
+
const child = spawn4(command, args, {
|
|
5605
5691
|
cwd: options.cwd,
|
|
5606
5692
|
env: options.env ?? process.env,
|
|
5607
5693
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5611,16 +5697,16 @@ var exec = (command, args, options = {}) => new Promise((resolve6) => {
|
|
|
5611
5697
|
child.stderr.on("data", (data) => stderr += data);
|
|
5612
5698
|
child.on("error", (error) => {
|
|
5613
5699
|
if (timer) clearTimeout(timer);
|
|
5614
|
-
|
|
5700
|
+
resolve7({ status: 127, stdout, stderr: `${stderr}${error.message}` });
|
|
5615
5701
|
});
|
|
5616
5702
|
child.on("close", (status2) => {
|
|
5617
5703
|
if (timer) clearTimeout(timer);
|
|
5618
|
-
|
|
5704
|
+
resolve7({ status: status2 ?? 1, stdout, stderr });
|
|
5619
5705
|
});
|
|
5620
5706
|
});
|
|
5621
5707
|
function npmCommand() {
|
|
5622
|
-
const beside =
|
|
5623
|
-
return
|
|
5708
|
+
const beside = join12(dirname9(process.execPath), "npm");
|
|
5709
|
+
return existsSync9(beside) ? beside : "npm";
|
|
5624
5710
|
}
|
|
5625
5711
|
function currentVersion(paths) {
|
|
5626
5712
|
try {
|
|
@@ -5630,10 +5716,10 @@ function currentVersion(paths) {
|
|
|
5630
5716
|
}
|
|
5631
5717
|
}
|
|
5632
5718
|
function installedVersions(paths) {
|
|
5633
|
-
if (!
|
|
5719
|
+
if (!existsSync9(paths.versions)) return [];
|
|
5634
5720
|
return readdirSync4(paths.versions).filter((name) => VERSION.test(name)).sort(compareVersions);
|
|
5635
5721
|
}
|
|
5636
|
-
var binOf = (versionDir, bin) =>
|
|
5722
|
+
var binOf = (versionDir, bin) => join12(packageDir(versionDir), `${bin}.mjs`);
|
|
5637
5723
|
async function versionRuns(versionDir, version, run4 = exec) {
|
|
5638
5724
|
const ran = await run4(process.execPath, [binOf(versionDir, "herd"), "--version"], {
|
|
5639
5725
|
timeoutMs: 3e4
|
|
@@ -5646,7 +5732,7 @@ async function versionRuns(versionDir, version, run4 = exec) {
|
|
|
5646
5732
|
}
|
|
5647
5733
|
function recordedIntegrity(dir) {
|
|
5648
5734
|
try {
|
|
5649
|
-
const lock = JSON.parse(readFileSync8(
|
|
5735
|
+
const lock = JSON.parse(readFileSync8(join12(dir, "package-lock.json"), "utf8"));
|
|
5650
5736
|
return lock.packages?.[`node_modules/${PACKAGE_NAME}`]?.integrity ?? null;
|
|
5651
5737
|
} catch {
|
|
5652
5738
|
return null;
|
|
@@ -5660,8 +5746,8 @@ function npmFreeEnv() {
|
|
|
5660
5746
|
async function installVersion(paths, version, { spec, integrity, run: run4 = exec, env = {}, log = () => {
|
|
5661
5747
|
} } = {}) {
|
|
5662
5748
|
if (!VERSION.test(version)) return { ok: false, reason: `${version} isn't a version.` };
|
|
5663
|
-
const dir =
|
|
5664
|
-
if (
|
|
5749
|
+
const dir = join12(paths.versions, version);
|
|
5750
|
+
if (existsSync9(dir)) {
|
|
5665
5751
|
const installed2 = recordedIntegrity(dir);
|
|
5666
5752
|
if (integrity && installed2 !== integrity)
|
|
5667
5753
|
return { ok: false, reason: `the installed ${version} doesn't have the integrity named.` };
|
|
@@ -5670,12 +5756,12 @@ async function installVersion(paths, version, { spec, integrity, run: run4 = exe
|
|
|
5670
5756
|
return { ok: false, reason: `the installed ${version} doesn't run: ${broken2}.` };
|
|
5671
5757
|
}
|
|
5672
5758
|
sweepStaging(paths);
|
|
5673
|
-
const named =
|
|
5759
|
+
const named = join12(paths.versions, `.staging-${version}-${process.pid}`);
|
|
5674
5760
|
rmSync9(named, { recursive: true, force: true });
|
|
5675
|
-
|
|
5676
|
-
const staging =
|
|
5761
|
+
mkdirSync9(named, { recursive: true });
|
|
5762
|
+
const staging = realpathSync3(named);
|
|
5677
5763
|
writeFileSync8(
|
|
5678
|
-
|
|
5764
|
+
join12(staging, "package.json"),
|
|
5679
5765
|
`${JSON.stringify({ private: true, allowScripts: { "node-pty": true } }, null, 2)}
|
|
5680
5766
|
`
|
|
5681
5767
|
);
|
|
@@ -5710,21 +5796,21 @@ async function installVersion(paths, version, { spec, integrity, run: run4 = exe
|
|
|
5710
5796
|
}
|
|
5711
5797
|
const broken = await versionRuns(staging, version, run4);
|
|
5712
5798
|
if (broken) return fail(broken);
|
|
5713
|
-
renameSync3(staging,
|
|
5799
|
+
renameSync3(staging, join12(realpathSync3(paths.versions), version));
|
|
5714
5800
|
return { ok: true, dir };
|
|
5715
5801
|
}
|
|
5716
5802
|
function sweepStaging(paths) {
|
|
5717
|
-
if (!
|
|
5803
|
+
if (!existsSync9(paths.versions)) return;
|
|
5718
5804
|
for (const name of readdirSync4(paths.versions)) {
|
|
5719
5805
|
const match = /^\.staging-.+-(\d+)$/.exec(name);
|
|
5720
5806
|
if (match && !alive2(Number(match[1])))
|
|
5721
|
-
rmSync9(
|
|
5807
|
+
rmSync9(join12(paths.versions, name), { recursive: true, force: true });
|
|
5722
5808
|
}
|
|
5723
5809
|
}
|
|
5724
5810
|
function activate(paths, version) {
|
|
5725
5811
|
const next = `${paths.current}.next-${process.pid}`;
|
|
5726
5812
|
rmSync9(next, { force: true });
|
|
5727
|
-
symlinkSync(
|
|
5813
|
+
symlinkSync(join12("versions", version), next);
|
|
5728
5814
|
renameSync3(next, paths.current);
|
|
5729
5815
|
}
|
|
5730
5816
|
function pruneVersions(paths, { inUse = [], keep = 3 } = {}) {
|
|
@@ -5739,7 +5825,7 @@ function pruneVersions(paths, { inUse = [], keep = 3 } = {}) {
|
|
|
5739
5825
|
const removed = [];
|
|
5740
5826
|
for (const version of versions) {
|
|
5741
5827
|
if (kept.has(version)) continue;
|
|
5742
|
-
rmSync9(
|
|
5828
|
+
rmSync9(join12(paths.versions, version), { recursive: true, force: true });
|
|
5743
5829
|
removed.push(version);
|
|
5744
5830
|
}
|
|
5745
5831
|
return removed;
|
|
@@ -5749,7 +5835,7 @@ async function hostVersions(paths, run4 = exec) {
|
|
|
5749
5835
|
if (ran.status !== 0) return installedVersions(paths);
|
|
5750
5836
|
const roots = /* @__PURE__ */ new Set([paths.versions]);
|
|
5751
5837
|
try {
|
|
5752
|
-
roots.add(
|
|
5838
|
+
roots.add(realpathSync3(paths.versions));
|
|
5753
5839
|
} catch {
|
|
5754
5840
|
}
|
|
5755
5841
|
const found = /* @__PURE__ */ new Set();
|
|
@@ -5787,13 +5873,13 @@ async function missingBuildTools(run4) {
|
|
|
5787
5873
|
}
|
|
5788
5874
|
var real = (path) => {
|
|
5789
5875
|
try {
|
|
5790
|
-
return
|
|
5876
|
+
return realpathSync4(path);
|
|
5791
5877
|
} catch {
|
|
5792
5878
|
return resolve3(path);
|
|
5793
5879
|
}
|
|
5794
5880
|
};
|
|
5795
5881
|
function chooseRoot(cwd, home) {
|
|
5796
|
-
return real(cwd) === real(home) ?
|
|
5882
|
+
return real(cwd) === real(home) ? join13(home, "friday") : resolve3(cwd);
|
|
5797
5883
|
}
|
|
5798
5884
|
async function runStart(options) {
|
|
5799
5885
|
const { paths, run: run4 } = options;
|
|
@@ -5803,7 +5889,7 @@ async function runStart(options) {
|
|
|
5803
5889
|
const wanted = chooseRoot(options.cwd, options.home);
|
|
5804
5890
|
const running = runningSupervisor(paths);
|
|
5805
5891
|
if (existing && real(existing) !== real(wanted)) {
|
|
5806
|
-
print(`This machine's root is ${existing},
|
|
5892
|
+
print(`This machine's root is ${existing}, which holds the repos Friday works on.`);
|
|
5807
5893
|
if (running) {
|
|
5808
5894
|
print(`Friday is running there, version ${running.version}, so nothing changed.`);
|
|
5809
5895
|
await waitForAgents(options, await chooseAndAsk(options, print), print);
|
|
@@ -5821,7 +5907,7 @@ async function runStart(options) {
|
|
|
5821
5907
|
const repo = insideWorkTree(root);
|
|
5822
5908
|
if (repo) {
|
|
5823
5909
|
throw new StartError(
|
|
5824
|
-
`${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.`
|
|
5825
5911
|
);
|
|
5826
5912
|
}
|
|
5827
5913
|
if ((options.platform ?? process.platform) === "linux") {
|
|
@@ -5843,7 +5929,7 @@ async function runStart(options) {
|
|
|
5843
5929
|
`Stopped the old daemon: removed ${retired.join(" and ")}. Its checkout is left as it was.`
|
|
5844
5930
|
);
|
|
5845
5931
|
await ensureInstalled(options, print);
|
|
5846
|
-
|
|
5932
|
+
mkdirSync10(root, { recursive: true });
|
|
5847
5933
|
writeRoot(paths, root);
|
|
5848
5934
|
const command = [process.execPath, binOf(paths.current, "friday"), "supervise"];
|
|
5849
5935
|
const env = { HERD_HOME: paths.herdHome, ...options.env };
|
|
@@ -5921,14 +6007,14 @@ async function ensureInstalled(options, print) {
|
|
|
5921
6007
|
// src/friday/supervisor.ts
|
|
5922
6008
|
init_define_HERD_PACKAGE();
|
|
5923
6009
|
init_src();
|
|
5924
|
-
import { spawn as
|
|
6010
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
5925
6011
|
import {
|
|
5926
6012
|
closeSync as closeSync3,
|
|
5927
|
-
existsSync as
|
|
5928
|
-
mkdirSync as
|
|
6013
|
+
existsSync as existsSync10,
|
|
6014
|
+
mkdirSync as mkdirSync11,
|
|
5929
6015
|
openSync as openSync4,
|
|
5930
6016
|
readFileSync as readFileSync9,
|
|
5931
|
-
realpathSync as
|
|
6017
|
+
realpathSync as realpathSync5,
|
|
5932
6018
|
renameSync as renameSync4,
|
|
5933
6019
|
statSync as statSync6
|
|
5934
6020
|
} from "node:fs";
|
|
@@ -5998,7 +6084,7 @@ var Supervisor = class {
|
|
|
5998
6084
|
if (this.options.daemonCommand) return this.options.daemonCommand();
|
|
5999
6085
|
let dir;
|
|
6000
6086
|
try {
|
|
6001
|
-
dir =
|
|
6087
|
+
dir = realpathSync5(this.options.paths.current);
|
|
6002
6088
|
} catch {
|
|
6003
6089
|
throw new Error(`Nothing is installed in ~/.herd/current. Run ${START_COMMAND}.`);
|
|
6004
6090
|
}
|
|
@@ -6007,16 +6093,16 @@ var Supervisor = class {
|
|
|
6007
6093
|
spawnDaemon() {
|
|
6008
6094
|
if (this.stopping) return;
|
|
6009
6095
|
const { daemonLog, logs } = this.options.paths;
|
|
6010
|
-
|
|
6096
|
+
mkdirSync11(logs, { recursive: true });
|
|
6011
6097
|
try {
|
|
6012
|
-
if (
|
|
6098
|
+
if (existsSync10(daemonLog) && statSync6(daemonLog).size > MAX_LOG_BYTES2)
|
|
6013
6099
|
renameSync4(daemonLog, `${daemonLog}.1`);
|
|
6014
6100
|
} catch {
|
|
6015
6101
|
}
|
|
6016
6102
|
const out = openSync4(daemonLog, "a");
|
|
6017
6103
|
const [program = process.execPath, ...args] = this.daemonCommand();
|
|
6018
6104
|
const startedAt = Date.now();
|
|
6019
|
-
const child =
|
|
6105
|
+
const child = spawn5(program, args, { stdio: ["ignore", out, out], env: process.env });
|
|
6020
6106
|
closeSync3(out);
|
|
6021
6107
|
this.daemon = child;
|
|
6022
6108
|
this.status.daemonPid = child.pid ?? null;
|
|
@@ -6040,7 +6126,7 @@ var Supervisor = class {
|
|
|
6040
6126
|
async stopDaemon() {
|
|
6041
6127
|
const child = this.daemon;
|
|
6042
6128
|
if (!child || child.exitCode !== null || child.signalCode !== null) return;
|
|
6043
|
-
const exited = new Promise((
|
|
6129
|
+
const exited = new Promise((resolve7) => child.once("exit", () => resolve7()));
|
|
6044
6130
|
child.kill("SIGTERM");
|
|
6045
6131
|
const timer = setTimeout(() => child.kill("SIGKILL"), this.options.stopMs ?? 15e3);
|
|
6046
6132
|
await exited;
|
|
@@ -6147,8 +6233,8 @@ var HELP = `friday: run HerdingChats' agent daemon on this computer.
|
|
|
6147
6233
|
|
|
6148
6234
|
Usage: ${FRIDAY_COMMAND} <command>
|
|
6149
6235
|
|
|
6150
|
-
start Make this computer a machine, with the folder this runs in as its root,
|
|
6151
|
-
|
|
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
|
|
6152
6238
|
needed, asks how much of each agent you have, starts the daemon now and whenever
|
|
6153
6239
|
you log in, waits for each agent to check in, then shows what's left to set up.
|
|
6154
6240
|
Safe to run again. --agents claude=lot,codex=little sets the shares without
|
|
@@ -6162,7 +6248,7 @@ Agents use the herd command, which comes with this one. herd setup shows what's
|
|
|
6162
6248
|
function defaults() {
|
|
6163
6249
|
const home = homedir4();
|
|
6164
6250
|
return {
|
|
6165
|
-
paths: fridayPaths(process.env.HERD_HOME ??
|
|
6251
|
+
paths: fridayPaths(process.env.HERD_HOME ?? join14(home, ".herd")),
|
|
6166
6252
|
home,
|
|
6167
6253
|
cwd: process.cwd(),
|
|
6168
6254
|
run: exec,
|
|
@@ -6272,12 +6358,12 @@ function agentsFlag(args) {
|
|
|
6272
6358
|
return parsed.shares;
|
|
6273
6359
|
}
|
|
6274
6360
|
function attachTo(paths, terminal) {
|
|
6275
|
-
const herd =
|
|
6276
|
-
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], {
|
|
6277
6363
|
stdio: "inherit",
|
|
6278
6364
|
env: { ...process.env, HERD_HOME: paths.herdHome }
|
|
6279
6365
|
});
|
|
6280
|
-
return new Promise((
|
|
6366
|
+
return new Promise((resolve7) => child.on("close", () => resolve7()));
|
|
6281
6367
|
}
|
|
6282
6368
|
async function runStop(deps) {
|
|
6283
6369
|
const status2 = readStatus(deps.paths);
|
|
@@ -6322,13 +6408,28 @@ function runStatus(deps) {
|
|
|
6322
6408
|
);
|
|
6323
6409
|
if (version) print(`Installed: ${version}${pin ? `, pinned to ${pin} in ${paths.pin}` : ""}.`);
|
|
6324
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
|
+
}
|
|
6325
6417
|
if (running && status2.checkedAt) print(`Last asked for a release ${ago(status2.checkedAt)}.`);
|
|
6326
6418
|
if (status2?.problem) print(`Last problem: ${status2.problem}`);
|
|
6327
6419
|
print(`Logs: ${paths.logs}`);
|
|
6328
6420
|
return running ? 0 : 1;
|
|
6329
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
|
+
}
|
|
6330
6431
|
function tail(path, count) {
|
|
6331
|
-
if (!
|
|
6432
|
+
if (!existsSync11(path)) return [];
|
|
6332
6433
|
const lines = readFileSync10(path, "utf8").split("\n");
|
|
6333
6434
|
if (lines.at(-1) === "") lines.pop();
|
|
6334
6435
|
return lines.slice(-count);
|
|
@@ -6336,14 +6437,14 @@ function tail(path, count) {
|
|
|
6336
6437
|
async function runLogs(args, deps) {
|
|
6337
6438
|
const { paths, print } = deps;
|
|
6338
6439
|
const follow = args.includes("-f") || args.includes("--follow");
|
|
6339
|
-
const files = [paths.supervisorLog, paths.daemonLog].filter((file) =>
|
|
6440
|
+
const files = [paths.supervisorLog, paths.daemonLog].filter((file) => existsSync11(file));
|
|
6340
6441
|
if (files.length === 0) {
|
|
6341
6442
|
print(`There are no logs in ${paths.logs} yet.`);
|
|
6342
6443
|
return 0;
|
|
6343
6444
|
}
|
|
6344
6445
|
if (follow) {
|
|
6345
|
-
const child =
|
|
6346
|
-
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)));
|
|
6347
6448
|
}
|
|
6348
6449
|
for (const file of files) {
|
|
6349
6450
|
print(`==> ${file} <==`);
|
|
@@ -6384,9 +6485,9 @@ init_define_HERD_PACKAGE();
|
|
|
6384
6485
|
|
|
6385
6486
|
// src/build-sandbox.ts
|
|
6386
6487
|
init_define_HERD_PACKAGE();
|
|
6387
|
-
import { spawn as
|
|
6488
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
6388
6489
|
import { appendFileSync, readFileSync as readFileSync11 } from "node:fs";
|
|
6389
|
-
import { dirname as
|
|
6490
|
+
import { dirname as dirname11, join as join15 } from "node:path";
|
|
6390
6491
|
var DEPLOY_HOSTS = ["api.cloudflare.com", "api.github.com", "registry.npmjs.org"];
|
|
6391
6492
|
function buildPolicy(input) {
|
|
6392
6493
|
const platform2 = input.platform ?? process.platform;
|
|
@@ -6402,7 +6503,7 @@ function buildPolicy(input) {
|
|
|
6402
6503
|
...platform2 === "darwin" ? { allowUnixSockets: [input.home] } : { allowAllUnixSockets: true }
|
|
6403
6504
|
},
|
|
6404
6505
|
filesystem: {
|
|
6405
|
-
denyRead: [.../* @__PURE__ */ new Set([input.ownerHome, input.root])],
|
|
6506
|
+
denyRead: [.../* @__PURE__ */ new Set([input.ownerHome, input.root, input.convos])],
|
|
6406
6507
|
allowRead: [input.worktree, input.store, input.nodePrefix, input.home],
|
|
6407
6508
|
allowWrite: [input.worktree, input.worktreeGitDir, input.home],
|
|
6408
6509
|
denyWrite: []
|
|
@@ -6416,7 +6517,7 @@ function describePolicy(policy, platform2 = process.platform) {
|
|
|
6416
6517
|
const hosts = policy.network.allowedDomains.join(", ");
|
|
6417
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}.`;
|
|
6418
6519
|
}
|
|
6419
|
-
var nodePrefix = (execPath = process.execPath) =>
|
|
6520
|
+
var nodePrefix = (execPath = process.execPath) => dirname11(dirname11(execPath));
|
|
6420
6521
|
var shellQuote = (args) => args.map((arg) => `'${arg.replace(/'/g, "'\\''")}'`).join(" ");
|
|
6421
6522
|
var BLOCKED_LOG = "blocked.log";
|
|
6422
6523
|
async function runSandboxed(args) {
|
|
@@ -6433,7 +6534,7 @@ async function runSandboxed(args) {
|
|
|
6433
6534
|
return 3;
|
|
6434
6535
|
}
|
|
6435
6536
|
await SandboxManager.initialize(policy, void 0, true);
|
|
6436
|
-
const blocked =
|
|
6537
|
+
const blocked = join15(process.env.HOME ?? ".", BLOCKED_LOG);
|
|
6437
6538
|
let written = 0;
|
|
6438
6539
|
const unsubscribe = SandboxManager.getSandboxViolationStore().subscribe((violations) => {
|
|
6439
6540
|
const fresh = violations.slice(written).map((violation) => describeRefusal(violation.line)).filter((line2) => line2 !== null);
|
|
@@ -6446,13 +6547,13 @@ async function runSandboxed(args) {
|
|
|
6446
6547
|
}
|
|
6447
6548
|
});
|
|
6448
6549
|
const wrapped = await SandboxManager.wrapWithSandbox(shellQuote(command));
|
|
6449
|
-
const child =
|
|
6550
|
+
const child = spawn7(wrapped, { shell: true, stdio: "inherit" });
|
|
6450
6551
|
const forward = (signal2) => () => child.kill(signal2);
|
|
6451
6552
|
process.once("SIGTERM", forward("SIGTERM"));
|
|
6452
6553
|
process.once("SIGINT", forward("SIGINT"));
|
|
6453
|
-
const status2 = await new Promise((
|
|
6454
|
-
child.on("error", () =>
|
|
6455
|
-
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)));
|
|
6456
6557
|
});
|
|
6457
6558
|
unsubscribe();
|
|
6458
6559
|
SandboxManager.cleanupAfterCommand();
|
|
@@ -6483,7 +6584,7 @@ async function fromTerminal(request) {
|
|
|
6483
6584
|
reply = await askDaemon(socket, { secret, ...request }).catch(() => null);
|
|
6484
6585
|
if (reply) break;
|
|
6485
6586
|
if (Date.now() > deadline) throw new HerdError("Couldn't reach herd's daemon. Is it running?");
|
|
6486
|
-
await new Promise((
|
|
6587
|
+
await new Promise((resolve7) => setTimeout(resolve7, 500));
|
|
6487
6588
|
}
|
|
6488
6589
|
if (!reply.ok) throw new HerdError(reply.error);
|
|
6489
6590
|
return reply;
|
|
@@ -6910,8 +7011,8 @@ init_define_HERD_PACKAGE();
|
|
|
6910
7011
|
// src/build-runner-key.ts
|
|
6911
7012
|
init_define_HERD_PACKAGE();
|
|
6912
7013
|
init_src();
|
|
6913
|
-
import { chmodSync as chmodSync3, existsSync as
|
|
6914
|
-
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";
|
|
6915
7016
|
import { z as z24 } from "zod";
|
|
6916
7017
|
var BUILDS_URL = "https://builds.herdingchats.com";
|
|
6917
7018
|
var runnerKeySchema = z24.object({
|
|
@@ -6919,10 +7020,10 @@ var runnerKeySchema = z24.object({
|
|
|
6919
7020
|
key: z24.string().min(20),
|
|
6920
7021
|
url: z24.string().url()
|
|
6921
7022
|
});
|
|
6922
|
-
var runnerKeyPath = () =>
|
|
7023
|
+
var runnerKeyPath = () => join16(herdHome(), "build-runner.json");
|
|
6923
7024
|
function readRunnerKey() {
|
|
6924
7025
|
const path = runnerKeyPath();
|
|
6925
|
-
if (!
|
|
7026
|
+
if (!existsSync12(path)) return null;
|
|
6926
7027
|
try {
|
|
6927
7028
|
return runnerKeySchema.parse(JSON.parse(readFileSync13(path, "utf8")));
|
|
6928
7029
|
} catch {
|
|
@@ -6932,7 +7033,7 @@ function readRunnerKey() {
|
|
|
6932
7033
|
}
|
|
6933
7034
|
}
|
|
6934
7035
|
function saveRunnerKey(key) {
|
|
6935
|
-
|
|
7036
|
+
mkdirSync12(herdHome(), { recursive: true, mode: 448 });
|
|
6936
7037
|
const path = runnerKeyPath();
|
|
6937
7038
|
writeFileSync9(path, JSON.stringify(runnerKeySchema.parse(key), null, 2), { mode: 384 });
|
|
6938
7039
|
chmodSync3(path, 384);
|
|
@@ -6947,7 +7048,7 @@ async function registerRunner({
|
|
|
6947
7048
|
url = buildsUrl(),
|
|
6948
7049
|
fetchImpl = fetch,
|
|
6949
7050
|
say = (line2) => console.log(line2),
|
|
6950
|
-
sleep: sleep8 = (ms) => new Promise((
|
|
7051
|
+
sleep: sleep8 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
|
|
6951
7052
|
pollMs = 2e3,
|
|
6952
7053
|
waitMs = 15 * 60 * 1e3
|
|
6953
7054
|
}) {
|
|
@@ -6986,8 +7087,8 @@ async function registerRunner({
|
|
|
6986
7087
|
|
|
6987
7088
|
// src/builds-admin.ts
|
|
6988
7089
|
init_define_HERD_PACKAGE();
|
|
6989
|
-
import { mkdirSync as
|
|
6990
|
-
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";
|
|
6991
7092
|
var NOT_ADMIN = "builds are only for the admin's machines";
|
|
6992
7093
|
async function isAdmin(fetch2) {
|
|
6993
7094
|
const response = await fetch2("/api/people/me").catch(() => null);
|
|
@@ -6995,13 +7096,13 @@ async function isAdmin(fetch2) {
|
|
|
6995
7096
|
const body = await response.json().catch(() => ({}));
|
|
6996
7097
|
return body.admin === true;
|
|
6997
7098
|
}
|
|
6998
|
-
var refusalPath = () =>
|
|
7099
|
+
var refusalPath = () => join17(herdHome(), "builds", "refused.json");
|
|
6999
7100
|
function writeBuildsRefusal(reason) {
|
|
7000
7101
|
if (!reason) {
|
|
7001
7102
|
rmSync10(refusalPath(), { force: true });
|
|
7002
7103
|
return;
|
|
7003
7104
|
}
|
|
7004
|
-
|
|
7105
|
+
mkdirSync13(join17(herdHome(), "builds"), { recursive: true });
|
|
7005
7106
|
writeFileSync10(refusalPath(), JSON.stringify({ reason }));
|
|
7006
7107
|
}
|
|
7007
7108
|
function readBuildsRefusal() {
|
|
@@ -7147,7 +7248,7 @@ async function runConversations(rest) {
|
|
|
7147
7248
|
init_define_HERD_PACKAGE();
|
|
7148
7249
|
init_src();
|
|
7149
7250
|
import { availableParallelism, totalmem } from "node:os";
|
|
7150
|
-
import { join as
|
|
7251
|
+
import { join as join33 } from "node:path";
|
|
7151
7252
|
|
|
7152
7253
|
// src/agent.ts
|
|
7153
7254
|
init_define_HERD_PACKAGE();
|
|
@@ -7256,17 +7357,34 @@ function readClaudeScreen(raw) {
|
|
|
7256
7357
|
|
|
7257
7358
|
// src/worktrees.ts
|
|
7258
7359
|
init_define_HERD_PACKAGE();
|
|
7259
|
-
import {
|
|
7260
|
-
|
|
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");
|
|
7261
7363
|
var WORD_ID = /^[a-z]+-[a-z]+-[a-z]+$/;
|
|
7262
7364
|
var conversationPrefix = (conversationId) => WORD_ID.test(conversationId) ? conversationId : conversationId.replace(/[^A-Za-z0-9]/g, "").slice(0, 12).toLowerCase();
|
|
7263
7365
|
var conversationBranch = (conversationId) => `herd/${conversationPrefix(conversationId)}`;
|
|
7264
|
-
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
|
+
}
|
|
7265
7371
|
var within = (dir, path) => {
|
|
7266
7372
|
const between = relative(dir, path);
|
|
7267
7373
|
return between !== "" && !between.startsWith("..") && !isAbsolute2(between);
|
|
7268
7374
|
};
|
|
7269
|
-
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
|
+
}
|
|
7270
7388
|
|
|
7271
7389
|
// src/agent.ts
|
|
7272
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. ";
|
|
@@ -7287,7 +7405,7 @@ var ownPrompt = (draft) => OWN_PROMPTS.find(
|
|
|
7287
7405
|
var SESSION_ID = /^[A-Za-z0-9-]{1,100}$/;
|
|
7288
7406
|
var SUBMIT_RETRIES = 3;
|
|
7289
7407
|
var DEFAULT_TIMING = { settleMs: 800, pollMs: 150, startMs: 6e4, confirmMs: 3e3 };
|
|
7290
|
-
var sleep3 = (ms) => new Promise((
|
|
7408
|
+
var sleep3 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
7291
7409
|
var EXIT_WAIT_MS = 5e3;
|
|
7292
7410
|
var AgentDriver = class {
|
|
7293
7411
|
constructor(terminals, timing = {}) {
|
|
@@ -7434,12 +7552,12 @@ var AgentDriver = class {
|
|
|
7434
7552
|
if (before.draft && !leftover) return { status: "draft", draft: before.draft };
|
|
7435
7553
|
const sent = leftover ?? text4;
|
|
7436
7554
|
let taken = false;
|
|
7437
|
-
const hooked = new Promise((
|
|
7555
|
+
const hooked = new Promise((resolve7) => {
|
|
7438
7556
|
this.waiters.set(name, {
|
|
7439
7557
|
text: sent,
|
|
7440
7558
|
taken: () => {
|
|
7441
7559
|
taken = true;
|
|
7442
|
-
|
|
7560
|
+
resolve7();
|
|
7443
7561
|
}
|
|
7444
7562
|
});
|
|
7445
7563
|
});
|
|
@@ -7546,9 +7664,9 @@ function readAgyScreen(raw) {
|
|
|
7546
7664
|
|
|
7547
7665
|
// src/agy-settings.ts
|
|
7548
7666
|
init_define_HERD_PACKAGE();
|
|
7549
|
-
import { lstatSync, mkdirSync as
|
|
7550
|
-
import { join as
|
|
7551
|
-
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");
|
|
7552
7670
|
var AGY_HOOK_EVENTS = ["SessionStart", "PreInvocation", "Stop"];
|
|
7553
7671
|
var agyHookCommand = (event2, herd) => `${herd} hook agy ${event2} || true`;
|
|
7554
7672
|
function agyHooks(herd) {
|
|
@@ -7565,25 +7683,25 @@ function agySettings(herd) {
|
|
|
7565
7683
|
return { statusLine: { type: "command", command: `${herd} statusline agy` } };
|
|
7566
7684
|
}
|
|
7567
7685
|
function ownFolder(worktree, name) {
|
|
7568
|
-
const folder =
|
|
7569
|
-
if (
|
|
7686
|
+
const folder = join19(worktree, name);
|
|
7687
|
+
if (lstatSync2(folder, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
7570
7688
|
throw new Error(`${name} in the worktree is a link`);
|
|
7571
7689
|
}
|
|
7572
|
-
|
|
7573
|
-
if (!
|
|
7690
|
+
mkdirSync14(folder, { recursive: true });
|
|
7691
|
+
if (!realpathSync7(folder).startsWith(realpathSync7(worktree) + sep)) {
|
|
7574
7692
|
throw new Error(`${name} leads outside the worktree`);
|
|
7575
7693
|
}
|
|
7576
7694
|
return folder;
|
|
7577
7695
|
}
|
|
7578
7696
|
function writeAgyFiles(worktree, herd) {
|
|
7579
7697
|
const agents = ownFolder(worktree, ".agents");
|
|
7580
|
-
writeFileSync11(
|
|
7698
|
+
writeFileSync11(join19(agents, "hooks.json"), `${JSON.stringify(agyHooks(herd), null, 2)}
|
|
7581
7699
|
`);
|
|
7582
|
-
writeFileSync11(
|
|
7700
|
+
writeFileSync11(join19(agents, ".gitignore"), "*\n");
|
|
7583
7701
|
const herdFolder = ownFolder(worktree, ".herd");
|
|
7584
|
-
writeFileSync11(
|
|
7702
|
+
writeFileSync11(join19(herdFolder, ".gitignore"), "*\n");
|
|
7585
7703
|
writeFileSync11(
|
|
7586
|
-
|
|
7704
|
+
join19(worktree, AGY_SETTINGS_FILE),
|
|
7587
7705
|
`${JSON.stringify(agySettings(herd), null, 2)}
|
|
7588
7706
|
`
|
|
7589
7707
|
);
|
|
@@ -7592,7 +7710,7 @@ function writeAgyFiles(worktree, herd) {
|
|
|
7592
7710
|
// src/agy-driver.ts
|
|
7593
7711
|
var AGY_FLAGS = `--dangerously-skip-permissions --settings ${AGY_SETTINGS_FILE}`;
|
|
7594
7712
|
var DEFAULT_TIMING2 = { settleMs: 800, pollMs: 150, startMs: 6e4, confirmMs: 3e3 };
|
|
7595
|
-
var sleep4 = (ms) => new Promise((
|
|
7713
|
+
var sleep4 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
7596
7714
|
var EXIT_WAIT_MS2 = 5e3;
|
|
7597
7715
|
var AgyDriver = class {
|
|
7598
7716
|
constructor(terminals, timing = {}) {
|
|
@@ -7682,12 +7800,12 @@ var AgyDriver = class {
|
|
|
7682
7800
|
if (before.draft && !leftover) return { status: "draft", draft: before.draft };
|
|
7683
7801
|
const sent = leftover ?? text4;
|
|
7684
7802
|
let taken = false;
|
|
7685
|
-
const reported = new Promise((
|
|
7803
|
+
const reported = new Promise((resolve7) => {
|
|
7686
7804
|
this.waiters.set(name, {
|
|
7687
7805
|
text: sent,
|
|
7688
7806
|
taken: () => {
|
|
7689
7807
|
taken = true;
|
|
7690
|
-
|
|
7808
|
+
resolve7();
|
|
7691
7809
|
}
|
|
7692
7810
|
});
|
|
7693
7811
|
});
|
|
@@ -7735,6 +7853,78 @@ var AgyDriver = class {
|
|
|
7735
7853
|
}
|
|
7736
7854
|
};
|
|
7737
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
|
+
|
|
7738
7928
|
// src/build-connection.ts
|
|
7739
7929
|
init_define_HERD_PACKAGE();
|
|
7740
7930
|
init_src();
|
|
@@ -7893,28 +8083,28 @@ var BuildConnection = class {
|
|
|
7893
8083
|
|
|
7894
8084
|
// src/builds.ts
|
|
7895
8085
|
init_define_HERD_PACKAGE();
|
|
7896
|
-
import { execFile as execFile2, execFileSync as execFileSync4, spawn as
|
|
8086
|
+
import { execFile as execFile2, execFileSync as execFileSync4, spawn as spawn8 } from "node:child_process";
|
|
7897
8087
|
import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
|
|
7898
8088
|
import {
|
|
7899
|
-
existsSync as
|
|
7900
|
-
mkdirSync as
|
|
8089
|
+
existsSync as existsSync15,
|
|
8090
|
+
mkdirSync as mkdirSync17,
|
|
7901
8091
|
mkdtempSync,
|
|
7902
|
-
readdirSync as
|
|
8092
|
+
readdirSync as readdirSync7,
|
|
7903
8093
|
readFileSync as readFileSync16,
|
|
7904
|
-
realpathSync as
|
|
7905
|
-
rmSync as
|
|
8094
|
+
realpathSync as realpathSync8,
|
|
8095
|
+
rmSync as rmSync13,
|
|
7906
8096
|
writeFileSync as writeFileSync13
|
|
7907
8097
|
} from "node:fs";
|
|
7908
8098
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
7909
|
-
import { join as
|
|
8099
|
+
import { join as join22 } from "node:path";
|
|
7910
8100
|
import { promisify as promisify2 } from "node:util";
|
|
7911
8101
|
|
|
7912
8102
|
// src/check-registry.ts
|
|
7913
8103
|
init_define_HERD_PACKAGE();
|
|
7914
|
-
import { mkdirSync as
|
|
8104
|
+
import { mkdirSync as mkdirSync16, readdirSync as readdirSync6, readFileSync as readFileSync15, rmSync as rmSync12, writeFileSync as writeFileSync12 } from "node:fs";
|
|
7915
8105
|
import { homedir as homedir5 } from "node:os";
|
|
7916
|
-
import { join as
|
|
7917
|
-
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");
|
|
7918
8108
|
var RESERVED_MB = 6 * 1024;
|
|
7919
8109
|
var MIN_MEMORY_MB = 1024;
|
|
7920
8110
|
var alive3 = (pid) => {
|
|
@@ -7926,15 +8116,15 @@ var alive3 = (pid) => {
|
|
|
7926
8116
|
}
|
|
7927
8117
|
};
|
|
7928
8118
|
function registerCheck(dir, pid, startedAt = Date.now()) {
|
|
7929
|
-
|
|
7930
|
-
const file =
|
|
8119
|
+
mkdirSync16(dir, { recursive: true });
|
|
8120
|
+
const file = join21(dir, `${pid}.json`);
|
|
7931
8121
|
writeFileSync12(file, JSON.stringify({ pid, startedAt }));
|
|
7932
|
-
return () =>
|
|
8122
|
+
return () => rmSync12(file, { force: true });
|
|
7933
8123
|
}
|
|
7934
8124
|
function runningChecks(dir, isAlive = alive3) {
|
|
7935
8125
|
let names = [];
|
|
7936
8126
|
try {
|
|
7937
|
-
names =
|
|
8127
|
+
names = readdirSync6(dir).filter((name) => /^\d+\.json$/.test(name));
|
|
7938
8128
|
} catch {
|
|
7939
8129
|
return 0;
|
|
7940
8130
|
}
|
|
@@ -7943,12 +8133,12 @@ function runningChecks(dir, isAlive = alive3) {
|
|
|
7943
8133
|
const pid = Number(name.slice(0, -".json".length));
|
|
7944
8134
|
let entry = null;
|
|
7945
8135
|
try {
|
|
7946
|
-
entry = JSON.parse(readFileSync15(
|
|
8136
|
+
entry = JSON.parse(readFileSync15(join21(dir, name), "utf8"));
|
|
7947
8137
|
} catch {
|
|
7948
8138
|
continue;
|
|
7949
8139
|
}
|
|
7950
8140
|
if (entry?.pid === pid && isAlive(pid)) count += 1;
|
|
7951
|
-
else
|
|
8141
|
+
else rmSync12(join21(dir, name), { force: true });
|
|
7952
8142
|
}
|
|
7953
8143
|
return count;
|
|
7954
8144
|
}
|
|
@@ -7956,9 +8146,9 @@ var memoryShareOf = (totalMb, running) => Math.max(MIN_MEMORY_MB, Math.floor((to
|
|
|
7956
8146
|
|
|
7957
8147
|
// src/builds.ts
|
|
7958
8148
|
init_self();
|
|
7959
|
-
var sandboxStatusPath = () =>
|
|
8149
|
+
var sandboxStatusPath = () => join22(herdHome(), "builds", "sandbox.json");
|
|
7960
8150
|
function writeSandboxStatus(status2, now = Date.now()) {
|
|
7961
|
-
|
|
8151
|
+
mkdirSync17(join22(herdHome(), "builds"), { recursive: true });
|
|
7962
8152
|
writeFileSync13(sandboxStatusPath(), JSON.stringify({ ...status2, at: now }));
|
|
7963
8153
|
}
|
|
7964
8154
|
function readSandboxStatus() {
|
|
@@ -7982,17 +8172,17 @@ async function storeGit(env, store, ...args) {
|
|
|
7982
8172
|
throw new StoreError(gitError(error));
|
|
7983
8173
|
}
|
|
7984
8174
|
}
|
|
7985
|
-
var
|
|
8175
|
+
var real3 = (path) => {
|
|
7986
8176
|
try {
|
|
7987
|
-
return
|
|
8177
|
+
return realpathSync8(path);
|
|
7988
8178
|
} catch {
|
|
7989
8179
|
return path;
|
|
7990
8180
|
}
|
|
7991
8181
|
};
|
|
7992
8182
|
function worktreeOf(dir, store) {
|
|
7993
|
-
if (!
|
|
8183
|
+
if (!existsSync15(join22(dir, ".git"))) return false;
|
|
7994
8184
|
try {
|
|
7995
|
-
return
|
|
8185
|
+
return real3(git2(dir, "rev-parse", "--path-format=absolute", "--git-common-dir")) === real3(store);
|
|
7996
8186
|
} catch {
|
|
7997
8187
|
return false;
|
|
7998
8188
|
}
|
|
@@ -8002,23 +8192,23 @@ function cloneFolder(from, to) {
|
|
|
8002
8192
|
execFileSync4("cp", [...args, from, to]);
|
|
8003
8193
|
}
|
|
8004
8194
|
function moduleFolders(dir) {
|
|
8005
|
-
const found =
|
|
8006
|
-
for (const entry of
|
|
8195
|
+
const found = existsSync15(join22(dir, "node_modules")) ? ["node_modules"] : [];
|
|
8196
|
+
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
8007
8197
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
8008
|
-
if (
|
|
8009
|
-
found.push(
|
|
8198
|
+
if (existsSync15(join22(dir, entry.name, "node_modules")))
|
|
8199
|
+
found.push(join22(entry.name, "node_modules"));
|
|
8010
8200
|
}
|
|
8011
8201
|
}
|
|
8012
8202
|
return found;
|
|
8013
8203
|
}
|
|
8014
8204
|
function lockHash(dir) {
|
|
8015
|
-
const lock = readFileSync16(
|
|
8205
|
+
const lock = readFileSync16(join22(dir, "package-lock.json"));
|
|
8016
8206
|
const major = process.versions.node.split(".")[0];
|
|
8017
8207
|
return createHash3("sha256").update(lock).update(`${process.platform}-${process.arch}-${major}`).digest("hex").slice(0, 32);
|
|
8018
8208
|
}
|
|
8019
|
-
var npmCi = (dir) => new Promise((
|
|
8209
|
+
var npmCi = (dir) => new Promise((resolve7, reject) => {
|
|
8020
8210
|
const network = insideBuildSandbox() ? ["--offline"] : ["--prefer-offline"];
|
|
8021
|
-
const child =
|
|
8211
|
+
const child = spawn8(
|
|
8022
8212
|
"npm",
|
|
8023
8213
|
["ci", ...network, "--no-audit", "--no-fund", "--no-update-notifier"],
|
|
8024
8214
|
{
|
|
@@ -8029,7 +8219,7 @@ var npmCi = (dir) => new Promise((resolve6, reject) => {
|
|
|
8029
8219
|
child.on("error", reject);
|
|
8030
8220
|
child.on(
|
|
8031
8221
|
"close",
|
|
8032
|
-
(code) => code === 0 ?
|
|
8222
|
+
(code) => code === 0 ? resolve7() : reject(new Error(`npm ci exited with ${code}`))
|
|
8033
8223
|
);
|
|
8034
8224
|
});
|
|
8035
8225
|
var BuildRunner = class _BuildRunner {
|
|
@@ -8039,11 +8229,15 @@ var BuildRunner = class _BuildRunner {
|
|
|
8039
8229
|
options;
|
|
8040
8230
|
running = /* @__PURE__ */ new Map();
|
|
8041
8231
|
home() {
|
|
8042
|
-
return this.options.home ??
|
|
8232
|
+
return this.options.home ?? join22(herdHome(), "builds");
|
|
8043
8233
|
}
|
|
8044
|
-
/**
|
|
8234
|
+
/** Where builds' own bare clones are, apart from the root's checkouts. */
|
|
8235
|
+
clonesDir() {
|
|
8236
|
+
return join22(this.home(), "clones");
|
|
8237
|
+
}
|
|
8238
|
+
/** Herd's bare clone of a build's repo, for builds, whether or not it's there yet. */
|
|
8045
8239
|
storeFor(build) {
|
|
8046
|
-
return
|
|
8240
|
+
return bareClonePath(this.clonesDir(), this.source(build));
|
|
8047
8241
|
}
|
|
8048
8242
|
/** What to clone a build's repo from: a remote the machine knows for it, or its HTTPS URL. */
|
|
8049
8243
|
source(build) {
|
|
@@ -8051,21 +8245,21 @@ var BuildRunner = class _BuildRunner {
|
|
|
8051
8245
|
}
|
|
8052
8246
|
/**
|
|
8053
8247
|
* The worktree for one of the repo's build slots, made or updated to the build's commit. The repo
|
|
8054
|
-
* is cloned into the
|
|
8055
|
-
* 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.
|
|
8056
8251
|
*/
|
|
8057
8252
|
async prepare(build, slot) {
|
|
8058
|
-
const root = this.options.root();
|
|
8059
8253
|
const remote = this.source(build);
|
|
8060
|
-
const store =
|
|
8254
|
+
const store = bareClonePath(this.clonesDir(), remote);
|
|
8061
8255
|
const env = this.options.gitEnv?.();
|
|
8062
8256
|
const name = (repoKey(build.repo) ?? build.repo).replace(/[^A-Za-z0-9._-]+/g, "_");
|
|
8063
|
-
const dir =
|
|
8064
|
-
|
|
8257
|
+
const dir = join22(this.home(), name, String(slot));
|
|
8258
|
+
mkdirSync17(join22(this.home(), name), { recursive: true });
|
|
8065
8259
|
await withStoreLock(
|
|
8066
8260
|
store,
|
|
8067
8261
|
async () => {
|
|
8068
|
-
const clone = await
|
|
8262
|
+
const clone = await ensureBareClone(store, remote, env);
|
|
8069
8263
|
if (clone.created) this.options.log?.(`Cloned ${build.repo} into ${store}.`);
|
|
8070
8264
|
if (clone.fetchFailed)
|
|
8071
8265
|
this.options.log?.(`Couldn't fetch ${build.repo}: ${clone.fetchFailed}`);
|
|
@@ -8075,7 +8269,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
8075
8269
|
await storeGit(env, store, "fetch", "--quiet", "origin", build.sha);
|
|
8076
8270
|
}
|
|
8077
8271
|
if (!worktreeOf(dir, store)) {
|
|
8078
|
-
|
|
8272
|
+
rmSync13(dir, { recursive: true, force: true });
|
|
8079
8273
|
await storeGit(env, store, "worktree", "prune");
|
|
8080
8274
|
await storeGit(
|
|
8081
8275
|
env,
|
|
@@ -8100,30 +8294,30 @@ var BuildRunner = class _BuildRunner {
|
|
|
8100
8294
|
/** Makes `node_modules` match the checkout's lockfile: kept, copied from the cache, or installed. */
|
|
8101
8295
|
async ensureModules(dir) {
|
|
8102
8296
|
const wanted = lockHash(dir);
|
|
8103
|
-
const marker =
|
|
8104
|
-
if (
|
|
8105
|
-
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);
|
|
8106
8300
|
for (const folder of moduleFolders(dir))
|
|
8107
|
-
|
|
8108
|
-
if (
|
|
8109
|
-
for (const folder of readFileSync16(
|
|
8110
|
-
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));
|
|
8111
8305
|
}
|
|
8112
8306
|
} else {
|
|
8113
8307
|
this.options.log?.(`Installing node_modules for lockfile ${wanted.slice(0, 8)}.`);
|
|
8114
8308
|
await (this.options.install ?? npmCi)(dir);
|
|
8115
|
-
const staging = mkdtempSync(
|
|
8309
|
+
const staging = mkdtempSync(join22(this.home(), "node_modules-"));
|
|
8116
8310
|
const folders = moduleFolders(dir);
|
|
8117
8311
|
for (const folder of folders) {
|
|
8118
|
-
|
|
8119
|
-
cloneFolder(
|
|
8312
|
+
mkdirSync17(join22(staging, folder, ".."), { recursive: true });
|
|
8313
|
+
cloneFolder(join22(dir, folder), join22(staging, folder));
|
|
8120
8314
|
}
|
|
8121
|
-
writeFileSync13(
|
|
8122
|
-
|
|
8123
|
-
|
|
8315
|
+
writeFileSync13(join22(staging, ".complete"), folders.join("\n"));
|
|
8316
|
+
mkdirSync17(join22(this.home(), "node_modules"), { recursive: true });
|
|
8317
|
+
rmSync13(cache, { recursive: true, force: true });
|
|
8124
8318
|
execFileSync4("mv", [staging, cache]);
|
|
8125
8319
|
}
|
|
8126
|
-
|
|
8320
|
+
mkdirSync17(join22(dir, "node_modules"), { recursive: true });
|
|
8127
8321
|
writeFileSync13(marker, wanted);
|
|
8128
8322
|
}
|
|
8129
8323
|
/**
|
|
@@ -8134,7 +8328,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
8134
8328
|
const env = {
|
|
8135
8329
|
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
|
8136
8330
|
HOME: home,
|
|
8137
|
-
TMPDIR:
|
|
8331
|
+
TMPDIR: join22(home, "tmp"),
|
|
8138
8332
|
LANG: process.env.LANG ?? "en_US.UTF-8",
|
|
8139
8333
|
HERD_BUILD_ID: build.id,
|
|
8140
8334
|
HERD_BUILD_SECRET: build.secret,
|
|
@@ -8146,7 +8340,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
8146
8340
|
// The sandbox's proxy is the only way out, and Node's own requests use it only when asked.
|
|
8147
8341
|
NODE_USE_ENV_PROXY: "1",
|
|
8148
8342
|
// Where `srt` points TMPDIR inside the sandbox: the build's own temporary folder.
|
|
8149
|
-
CLAUDE_CODE_TMPDIR:
|
|
8343
|
+
CLAUDE_CODE_TMPDIR: join22(home, "tmp")
|
|
8150
8344
|
};
|
|
8151
8345
|
return env;
|
|
8152
8346
|
}
|
|
@@ -8157,8 +8351,8 @@ var BuildRunner = class _BuildRunner {
|
|
|
8157
8351
|
/** Runs `scripts/build.mjs` from the build's own commit, in a process group of its own. */
|
|
8158
8352
|
async run(build, slot) {
|
|
8159
8353
|
const dir = await this.prepare(build, slot);
|
|
8160
|
-
const home = mkdtempSync(
|
|
8161
|
-
|
|
8354
|
+
const home = mkdtempSync(join22(this.options.scratch ?? scratchRoot(), "hb-"));
|
|
8355
|
+
mkdirSync17(join22(home, "tmp"), { recursive: true });
|
|
8162
8356
|
const run4 = randomUUID4();
|
|
8163
8357
|
const env = {
|
|
8164
8358
|
..._BuildRunner.environment(build, home),
|
|
@@ -8168,12 +8362,13 @@ var BuildRunner = class _BuildRunner {
|
|
|
8168
8362
|
env.HERD_CHECK_CPUS = String(Math.max(1, Math.floor(this.options.cpus())));
|
|
8169
8363
|
if (this.options.memoryMb)
|
|
8170
8364
|
env.HERD_CHECK_MEMORY_MB = String(Math.max(1, Math.floor(this.options.memoryMb())));
|
|
8171
|
-
let command = [process.execPath,
|
|
8365
|
+
let command = [process.execPath, join22(dir, "scripts", "build.mjs")];
|
|
8172
8366
|
let policyPath = null;
|
|
8173
8367
|
if (this.sandboxed()) {
|
|
8174
8368
|
const policy = buildPolicy({
|
|
8175
8369
|
ownerHome: this.options.ownerHome ?? homedir6(),
|
|
8176
8370
|
root: this.options.root(),
|
|
8371
|
+
convos: convosHome(this.options.root()),
|
|
8177
8372
|
worktree: dir,
|
|
8178
8373
|
store: this.storeFor(build),
|
|
8179
8374
|
worktreeGitDir: git2(dir, "rev-parse", "--path-format=absolute", "--git-dir"),
|
|
@@ -8182,8 +8377,8 @@ var BuildRunner = class _BuildRunner {
|
|
|
8182
8377
|
main: build.main,
|
|
8183
8378
|
buildsUrl: build.url
|
|
8184
8379
|
});
|
|
8185
|
-
|
|
8186
|
-
policyPath =
|
|
8380
|
+
mkdirSync17(join22(this.home(), "policies"), { recursive: true });
|
|
8381
|
+
policyPath = join22(this.home(), "policies", `${build.id}.json`);
|
|
8187
8382
|
writeFileSync13(policyPath, JSON.stringify(policy), { mode: 384 });
|
|
8188
8383
|
env.HERD_BUILD_SANDBOX = describePolicy(policy);
|
|
8189
8384
|
command = [process.execPath, HERD_BIN, "build-sandbox", policyPath, "--", ...command];
|
|
@@ -8191,15 +8386,15 @@ var BuildRunner = class _BuildRunner {
|
|
|
8191
8386
|
env.HERD_BUILD_SANDBOX = insideBuildSandbox() ? "Inside the sandbox of the build that started this one." : "";
|
|
8192
8387
|
}
|
|
8193
8388
|
const [executable, ...args] = command;
|
|
8194
|
-
const child =
|
|
8195
|
-
const done = new Promise((
|
|
8196
|
-
child.on("error", () =>
|
|
8197
|
-
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));
|
|
8198
8393
|
});
|
|
8199
8394
|
let markEnded = () => {
|
|
8200
8395
|
};
|
|
8201
|
-
const ended = new Promise((
|
|
8202
|
-
markEnded =
|
|
8396
|
+
const ended = new Promise((resolve7) => {
|
|
8397
|
+
markEnded = resolve7;
|
|
8203
8398
|
});
|
|
8204
8399
|
const entry = { pid: child.pid ?? 0, done, dir, ended, run: run4 };
|
|
8205
8400
|
this.running.set(build.id, entry);
|
|
@@ -8211,8 +8406,8 @@ var BuildRunner = class _BuildRunner {
|
|
|
8211
8406
|
unregister();
|
|
8212
8407
|
await this.stopBuildProcesses(run4, dir, entry.pid);
|
|
8213
8408
|
if (this.running.get(build.id) === entry) this.running.delete(build.id);
|
|
8214
|
-
|
|
8215
|
-
if (policyPath)
|
|
8409
|
+
rmSync13(home, { recursive: true, force: true });
|
|
8410
|
+
if (policyPath) rmSync13(policyPath, { force: true });
|
|
8216
8411
|
markEnded();
|
|
8217
8412
|
}
|
|
8218
8413
|
}
|
|
@@ -8232,7 +8427,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
8232
8427
|
* It goes only by where they run from, never by a build ID, since the daemon a build's own tests
|
|
8233
8428
|
* start must never stop the build it runs in.
|
|
8234
8429
|
*/
|
|
8235
|
-
static async sweep(home =
|
|
8430
|
+
static async sweep(home = join22(herdHome(), "builds"), list = listProcesses) {
|
|
8236
8431
|
const left = buildProcesses(list(), null, home);
|
|
8237
8432
|
await stopPids(left);
|
|
8238
8433
|
return left.length;
|
|
@@ -8244,50 +8439,51 @@ var BuildRunner = class _BuildRunner {
|
|
|
8244
8439
|
*/
|
|
8245
8440
|
static async probe(options = { buildsUrl: "" }) {
|
|
8246
8441
|
if (insideBuildSandbox()) return { ok: true };
|
|
8247
|
-
const herd = options.home ??
|
|
8248
|
-
const dir = mkdtempSync(
|
|
8249
|
-
const secret =
|
|
8442
|
+
const herd = options.home ?? join22(herdHome(), "builds");
|
|
8443
|
+
const dir = mkdtempSync(join22(scratchRoot(), "hp-"));
|
|
8444
|
+
const secret = join22(dir, "owner", "secret");
|
|
8250
8445
|
try {
|
|
8251
|
-
|
|
8252
|
-
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));
|
|
8253
8448
|
writeFileSync13(secret, "probe");
|
|
8254
8449
|
const policy = buildPolicy({
|
|
8255
|
-
ownerHome:
|
|
8256
|
-
root:
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
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"),
|
|
8261
8457
|
nodePrefix: nodePrefix(),
|
|
8262
8458
|
main: false,
|
|
8263
8459
|
buildsUrl: options.buildsUrl || "https://builds.herdingchats.com"
|
|
8264
8460
|
});
|
|
8265
|
-
const policyPath =
|
|
8461
|
+
const policyPath = join22(herd, "probe-policy.json");
|
|
8266
8462
|
writeFileSync13(policyPath, JSON.stringify(policy), { mode: 384 });
|
|
8267
8463
|
const script = [
|
|
8268
8464
|
'const fs = require("node:fs");',
|
|
8269
8465
|
`try { fs.readFileSync(${JSON.stringify(secret)}); process.exit(10); } catch {}`,
|
|
8270
|
-
`fs.writeFileSync(${JSON.stringify(
|
|
8466
|
+
`fs.writeFileSync(${JSON.stringify(join22(dir, "home", "ok"))}, "ok");`
|
|
8271
8467
|
].join(" ");
|
|
8272
|
-
const child =
|
|
8468
|
+
const child = spawn8(
|
|
8273
8469
|
process.execPath,
|
|
8274
8470
|
[HERD_BIN, "build-sandbox", policyPath, "--", process.execPath, "-e", script],
|
|
8275
8471
|
{
|
|
8276
|
-
env: { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME:
|
|
8472
|
+
env: { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: join22(dir, "home") },
|
|
8277
8473
|
stdio: ["ignore", "ignore", "pipe"]
|
|
8278
8474
|
}
|
|
8279
8475
|
);
|
|
8280
8476
|
let stderr = "";
|
|
8281
8477
|
child.stderr.on("data", (data) => stderr += data);
|
|
8282
8478
|
const timer = setTimeout(() => child.kill("SIGKILL"), options.timeoutMs ?? 3e4);
|
|
8283
|
-
const status2 = await new Promise((
|
|
8284
|
-
child.on("error", () =>
|
|
8285
|
-
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));
|
|
8286
8482
|
});
|
|
8287
8483
|
clearTimeout(timer);
|
|
8288
8484
|
if (status2 === 10)
|
|
8289
8485
|
return { ok: false, reason: "the sandbox let a build read the owner's files" };
|
|
8290
|
-
if (status2 !== 0 || !
|
|
8486
|
+
if (status2 !== 0 || !existsSync15(join22(dir, "home", "ok"))) {
|
|
8291
8487
|
const why = stderr.trim().split("\n").at(-1) || `the probe exited with ${status2}`;
|
|
8292
8488
|
return { ok: false, reason: why.slice(0, 300) };
|
|
8293
8489
|
}
|
|
@@ -8295,7 +8491,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
8295
8491
|
} catch (error) {
|
|
8296
8492
|
return { ok: false, reason: String(error).slice(0, 300) };
|
|
8297
8493
|
} finally {
|
|
8298
|
-
|
|
8494
|
+
rmSync13(dir, { recursive: true, force: true });
|
|
8299
8495
|
}
|
|
8300
8496
|
}
|
|
8301
8497
|
/** Stops a build's process group and every process it started, however it got away. */
|
|
@@ -8304,7 +8500,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
8304
8500
|
if (done) {
|
|
8305
8501
|
const ended = await Promise.race([
|
|
8306
8502
|
done.then(() => true),
|
|
8307
|
-
new Promise((
|
|
8503
|
+
new Promise((resolve7) => setTimeout(() => resolve7(false), 3e3).unref())
|
|
8308
8504
|
]);
|
|
8309
8505
|
if (!ended && pid) signal(-pid, "SIGKILL");
|
|
8310
8506
|
} else if (pid) {
|
|
@@ -8324,7 +8520,7 @@ function listProcesses() {
|
|
|
8324
8520
|
function listFromProc() {
|
|
8325
8521
|
let pids = [];
|
|
8326
8522
|
try {
|
|
8327
|
-
pids =
|
|
8523
|
+
pids = readdirSync7("/proc").filter((name) => /^\d+$/.test(name));
|
|
8328
8524
|
} catch {
|
|
8329
8525
|
return [];
|
|
8330
8526
|
}
|
|
@@ -8364,7 +8560,7 @@ async function stopPids(pids) {
|
|
|
8364
8560
|
if (live.length === 0) return;
|
|
8365
8561
|
const deadline = Date.now() + 3e3;
|
|
8366
8562
|
while (Date.now() < deadline && live.some((pid) => signal(pid, 0))) {
|
|
8367
|
-
await new Promise((
|
|
8563
|
+
await new Promise((resolve7) => setTimeout(resolve7, 100));
|
|
8368
8564
|
}
|
|
8369
8565
|
for (const pid of live) signal(pid, "SIGKILL");
|
|
8370
8566
|
}
|
|
@@ -8373,32 +8569,32 @@ async function stopPids(pids) {
|
|
|
8373
8569
|
init_define_HERD_PACKAGE();
|
|
8374
8570
|
import { execFile as execFile3 } from "node:child_process";
|
|
8375
8571
|
function listClaudeAgents(timeoutMs = 1e4, command = "claude") {
|
|
8376
|
-
return new Promise((
|
|
8572
|
+
return new Promise((resolve7) => {
|
|
8377
8573
|
execFile3(
|
|
8378
8574
|
command,
|
|
8379
8575
|
["agents", "--json", "--all"],
|
|
8380
8576
|
{ env: terminalEnv({}), timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024 },
|
|
8381
8577
|
(error, stdout) => {
|
|
8382
|
-
if (error) return
|
|
8578
|
+
if (error) return resolve7([]);
|
|
8383
8579
|
try {
|
|
8384
8580
|
const rows = JSON.parse(stdout);
|
|
8385
|
-
|
|
8581
|
+
resolve7(Array.isArray(rows) ? rows : []);
|
|
8386
8582
|
} catch {
|
|
8387
|
-
|
|
8583
|
+
resolve7([]);
|
|
8388
8584
|
}
|
|
8389
8585
|
}
|
|
8390
8586
|
);
|
|
8391
8587
|
});
|
|
8392
8588
|
}
|
|
8393
8589
|
function findClaude(shell = process.env.SHELL || "/bin/sh", timeoutMs = 1e4) {
|
|
8394
|
-
return new Promise((
|
|
8590
|
+
return new Promise((resolve7) => {
|
|
8395
8591
|
execFile3(
|
|
8396
8592
|
shell,
|
|
8397
8593
|
["-ilc", "command -v claude"],
|
|
8398
8594
|
{ env: terminalEnv({}), timeout: timeoutMs },
|
|
8399
8595
|
(_error, stdout) => {
|
|
8400
8596
|
const found = String(stdout ?? "").split("\n").map((line2) => line2.trim()).reverse().find((line2) => line2.startsWith("/"));
|
|
8401
|
-
|
|
8597
|
+
resolve7(found ?? null);
|
|
8402
8598
|
}
|
|
8403
8599
|
);
|
|
8404
8600
|
});
|
|
@@ -8506,7 +8702,7 @@ var DEFAULT_TIMING3 = {
|
|
|
8506
8702
|
confirmMs: 8e3,
|
|
8507
8703
|
pasteMs: 600
|
|
8508
8704
|
};
|
|
8509
|
-
var sleep5 = (ms) => new Promise((
|
|
8705
|
+
var sleep5 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
8510
8706
|
var EXIT_WAIT_MS3 = 5e3;
|
|
8511
8707
|
var CodexDriver = class {
|
|
8512
8708
|
constructor(terminals, timing = {}) {
|
|
@@ -8596,12 +8792,12 @@ var CodexDriver = class {
|
|
|
8596
8792
|
if (before.draft && !leftover) return { status: "draft", draft: before.draft };
|
|
8597
8793
|
const sent = leftover ?? text4;
|
|
8598
8794
|
let taken = false;
|
|
8599
|
-
const reported = new Promise((
|
|
8795
|
+
const reported = new Promise((resolve7) => {
|
|
8600
8796
|
this.waiters.set(name, {
|
|
8601
8797
|
text: sent,
|
|
8602
8798
|
taken: () => {
|
|
8603
8799
|
taken = true;
|
|
8604
|
-
|
|
8800
|
+
resolve7();
|
|
8605
8801
|
}
|
|
8606
8802
|
});
|
|
8607
8803
|
});
|
|
@@ -8646,7 +8842,7 @@ var CodexDriver = class {
|
|
|
8646
8842
|
|
|
8647
8843
|
// src/daemon.ts
|
|
8648
8844
|
init_define_HERD_PACKAGE();
|
|
8649
|
-
import { join as
|
|
8845
|
+
import { join as join23 } from "node:path";
|
|
8650
8846
|
|
|
8651
8847
|
// src/dispatch-connection.ts
|
|
8652
8848
|
init_define_HERD_PACKAGE();
|
|
@@ -8750,7 +8946,7 @@ var DispatchConnection = class {
|
|
|
8750
8946
|
|
|
8751
8947
|
// src/daemon.ts
|
|
8752
8948
|
var CONFIG_POLL_MS = 2e3;
|
|
8753
|
-
var daemonSocketPath = () =>
|
|
8949
|
+
var daemonSocketPath = () => join23(herdHome(), "daemon.sock");
|
|
8754
8950
|
var Daemon = class {
|
|
8755
8951
|
constructor(options) {
|
|
8756
8952
|
this.options = options;
|
|
@@ -8832,9 +9028,11 @@ var Daemon = class {
|
|
|
8832
9028
|
return this.registering;
|
|
8833
9029
|
}
|
|
8834
9030
|
async register() {
|
|
8835
|
-
const reposStamp =
|
|
9031
|
+
const reposStamp = checkoutsStamp(this.root());
|
|
8836
9032
|
const gh = this.options.gh?.();
|
|
9033
|
+
const folderUnavailable = this.options.folderUnavailable?.() ?? void 0;
|
|
8837
9034
|
const facts = {
|
|
9035
|
+
...folderUnavailable ? { folderUnavailable } : {},
|
|
8838
9036
|
...this.options.facts,
|
|
8839
9037
|
...this.herdConfig ? { config: this.herdConfig } : {},
|
|
8840
9038
|
...this.configError ? { configError: this.configError } : {},
|
|
@@ -8844,7 +9042,7 @@ var Daemon = class {
|
|
|
8844
9042
|
const response = await this.options.api.fetch("/api/machines", {
|
|
8845
9043
|
method: "POST",
|
|
8846
9044
|
headers: { "Content-Type": "application/json" },
|
|
8847
|
-
body: JSON.stringify(registration(this.config, this.clis, facts,
|
|
9045
|
+
body: JSON.stringify(registration(this.config, this.clis, facts, checkouts(this.root())))
|
|
8848
9046
|
});
|
|
8849
9047
|
if (!response.ok) {
|
|
8850
9048
|
const body = await response.json().catch(() => ({}));
|
|
@@ -8872,7 +9070,7 @@ var Daemon = class {
|
|
|
8872
9070
|
}
|
|
8873
9071
|
}
|
|
8874
9072
|
/**
|
|
8875
|
-
* 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
|
|
8876
9074
|
* uses it within seconds.
|
|
8877
9075
|
*/
|
|
8878
9076
|
async refreshHerdConfig() {
|
|
@@ -8882,7 +9080,7 @@ var Daemon = class {
|
|
|
8882
9080
|
};
|
|
8883
9081
|
if (this.refreshing) return false;
|
|
8884
9082
|
const configChanged = source.stamp() !== this.configStamp;
|
|
8885
|
-
if (!configChanged &&
|
|
9083
|
+
if (!configChanged && checkoutsStamp(this.root()) === this.reposStamp) return false;
|
|
8886
9084
|
this.refreshing = true;
|
|
8887
9085
|
try {
|
|
8888
9086
|
if (configChanged) await this.readHerdConfig();
|
|
@@ -8904,16 +9102,41 @@ var Daemon = class {
|
|
|
8904
9102
|
this.connection?.stop();
|
|
8905
9103
|
await this.local.close();
|
|
8906
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
|
+
}
|
|
8907
9120
|
async onFrame(frame) {
|
|
8908
9121
|
if (frame.type === "offer") return this.onOffer(frame.offer);
|
|
8909
9122
|
if (frame.type === "sign_in") return this.options.onSignIn?.(frame);
|
|
8910
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
|
+
}
|
|
8911
9130
|
await this.options.work.onMessage(frame.conversationId, frame.delivery);
|
|
8912
9131
|
this.connection?.acknowledge(frame.conversationId);
|
|
8913
9132
|
}
|
|
8914
9133
|
}
|
|
8915
9134
|
async onOffer(offer) {
|
|
8916
|
-
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);
|
|
8917
9140
|
const answer2 = decision.accept ? { answer: "accept" } : { answer: "refuse", reason: decision.reason };
|
|
8918
9141
|
const response = await this.options.api.fetch(
|
|
8919
9142
|
`/api/machines/${encodeURIComponent(this.config.machineId)}/offers/${encodeURIComponent(offer.id)}`,
|
|
@@ -8933,10 +9156,101 @@ var Daemon = class {
|
|
|
8933
9156
|
}
|
|
8934
9157
|
};
|
|
8935
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
|
+
|
|
8936
9250
|
// src/gh.ts
|
|
8937
9251
|
init_define_HERD_PACKAGE();
|
|
8938
9252
|
init_src();
|
|
8939
|
-
import { execFile as execFile4, spawn as
|
|
9253
|
+
import { execFile as execFile4, spawn as spawn9 } from "node:child_process";
|
|
8940
9254
|
import { promisify as promisify3 } from "node:util";
|
|
8941
9255
|
var GH_CHECK_MS = 10 * 60 * 1e3;
|
|
8942
9256
|
var GH_CODE_WAIT_MS = 30 * 1e3;
|
|
@@ -9007,7 +9321,7 @@ function readDeviceCode(output2) {
|
|
|
9007
9321
|
return code && url === GITHUB_DEVICE_URL ? { code, url: GITHUB_DEVICE_URL } : null;
|
|
9008
9322
|
}
|
|
9009
9323
|
async function signInGh(options) {
|
|
9010
|
-
const spawnGh = options.spawnGh ?? ((args) =>
|
|
9324
|
+
const spawnGh = options.spawnGh ?? ((args) => spawn9("gh", args, {
|
|
9011
9325
|
env: options.env?.() ?? process.env,
|
|
9012
9326
|
stdio: ["ignore", "pipe", "pipe"]
|
|
9013
9327
|
}));
|
|
@@ -9016,8 +9330,8 @@ async function signInGh(options) {
|
|
|
9016
9330
|
let sent = false;
|
|
9017
9331
|
let resolveCode = () => {
|
|
9018
9332
|
};
|
|
9019
|
-
const codeShown = new Promise((
|
|
9020
|
-
resolveCode =
|
|
9333
|
+
const codeShown = new Promise((resolve7) => {
|
|
9334
|
+
resolveCode = resolve7;
|
|
9021
9335
|
});
|
|
9022
9336
|
const read = (chunk) => {
|
|
9023
9337
|
output2 += chunk.toString();
|
|
@@ -9025,9 +9339,9 @@ async function signInGh(options) {
|
|
|
9025
9339
|
};
|
|
9026
9340
|
child.stdout?.on("data", read);
|
|
9027
9341
|
child.stderr?.on("data", read);
|
|
9028
|
-
const exited = new Promise((
|
|
9029
|
-
child.once("close", () =>
|
|
9030
|
-
child.once("error", () =>
|
|
9342
|
+
const exited = new Promise((resolve7) => {
|
|
9343
|
+
child.once("close", () => resolve7());
|
|
9344
|
+
child.once("error", () => resolve7());
|
|
9031
9345
|
});
|
|
9032
9346
|
void exited.then(() => resolveCode(false));
|
|
9033
9347
|
const codeTimer = setTimeout(() => resolveCode(false), options.codeWaitMs ?? GH_CODE_WAIT_MS);
|
|
@@ -9060,23 +9374,23 @@ init_define_HERD_PACKAGE();
|
|
|
9060
9374
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
9061
9375
|
import {
|
|
9062
9376
|
appendFileSync as appendFileSync2,
|
|
9063
|
-
existsSync as
|
|
9064
|
-
mkdirSync as
|
|
9377
|
+
existsSync as existsSync17,
|
|
9378
|
+
mkdirSync as mkdirSync18,
|
|
9065
9379
|
readFileSync as readFileSync17,
|
|
9066
|
-
renameSync as
|
|
9067
|
-
writeFileSync as
|
|
9380
|
+
renameSync as renameSync6,
|
|
9381
|
+
writeFileSync as writeFileSync15
|
|
9068
9382
|
} from "node:fs";
|
|
9069
|
-
import { isAbsolute as isAbsolute3, join as
|
|
9070
|
-
var LOCAL_SETTINGS =
|
|
9383
|
+
import { isAbsolute as isAbsolute3, join as join24 } from "node:path";
|
|
9384
|
+
var LOCAL_SETTINGS = join24(".claude", "settings.local.json");
|
|
9071
9385
|
var git3 = (cwd, ...args) => execFileSync5("git", ["-C", cwd, ...args], {
|
|
9072
9386
|
encoding: "utf8",
|
|
9073
9387
|
stdio: ["ignore", "pipe", "pipe"]
|
|
9074
9388
|
}).trim();
|
|
9075
9389
|
var shellQuote2 = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
9076
9390
|
function writeWorktreeSettings(worktree, settings, ownCommand) {
|
|
9077
|
-
const path =
|
|
9391
|
+
const path = join24(worktree, LOCAL_SETTINGS);
|
|
9078
9392
|
let current = {};
|
|
9079
|
-
if (
|
|
9393
|
+
if (existsSync17(path)) {
|
|
9080
9394
|
try {
|
|
9081
9395
|
const parsed = JSON.parse(readFileSync17(path, "utf8"));
|
|
9082
9396
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
@@ -9098,11 +9412,11 @@ function writeWorktreeSettings(worktree, settings, ownCommand) {
|
|
|
9098
9412
|
}
|
|
9099
9413
|
next.hooks = hooks;
|
|
9100
9414
|
}
|
|
9101
|
-
|
|
9415
|
+
mkdirSync18(join24(worktree, ".claude"), { recursive: true });
|
|
9102
9416
|
const temporary = `${path}.herd-${process.pid}`;
|
|
9103
|
-
|
|
9417
|
+
writeFileSync15(temporary, `${JSON.stringify(next, null, 2)}
|
|
9104
9418
|
`);
|
|
9105
|
-
|
|
9419
|
+
renameSync6(temporary, path);
|
|
9106
9420
|
ignoreLocalSettings(worktree);
|
|
9107
9421
|
return true;
|
|
9108
9422
|
}
|
|
@@ -9118,12 +9432,12 @@ function ignoreLocalSettings(worktree) {
|
|
|
9118
9432
|
} catch {
|
|
9119
9433
|
return;
|
|
9120
9434
|
}
|
|
9121
|
-
const info =
|
|
9122
|
-
const exclude =
|
|
9435
|
+
const info = join24(isAbsolute3(common) ? common : join24(worktree, common), "info");
|
|
9436
|
+
const exclude = join24(info, "exclude");
|
|
9123
9437
|
const line2 = "/.claude/settings.local.json";
|
|
9124
|
-
const existing =
|
|
9438
|
+
const existing = existsSync17(exclude) ? readFileSync17(exclude, "utf8") : "";
|
|
9125
9439
|
if (existing.split("\n").includes(line2)) return;
|
|
9126
|
-
|
|
9440
|
+
mkdirSync18(info, { recursive: true });
|
|
9127
9441
|
appendFileSync2(exclude, `${existing && !existing.endsWith("\n") ? "\n" : ""}${line2}
|
|
9128
9442
|
`);
|
|
9129
9443
|
}
|
|
@@ -9170,7 +9484,7 @@ function loginShellEnv() {
|
|
|
9170
9484
|
|
|
9171
9485
|
// src/machine-checks.ts
|
|
9172
9486
|
init_define_HERD_PACKAGE();
|
|
9173
|
-
import { existsSync as
|
|
9487
|
+
import { existsSync as existsSync18, rmSync as rmSync15 } from "node:fs";
|
|
9174
9488
|
var REQUEST_POLL_MS = 2e3;
|
|
9175
9489
|
var MachineChecks = class {
|
|
9176
9490
|
constructor(options) {
|
|
@@ -9216,8 +9530,8 @@ var MachineChecks = class {
|
|
|
9216
9530
|
/** `friday start` asked for every agent to be activated again. */
|
|
9217
9531
|
takeRequest() {
|
|
9218
9532
|
const path = activationRequestPath(this.options.herdHome);
|
|
9219
|
-
if (!
|
|
9220
|
-
|
|
9533
|
+
if (!existsSync18(path)) return;
|
|
9534
|
+
rmSync15(path, { force: true });
|
|
9221
9535
|
this.activateChosen(true);
|
|
9222
9536
|
}
|
|
9223
9537
|
start() {
|
|
@@ -9265,22 +9579,22 @@ var MachineChecks = class {
|
|
|
9265
9579
|
|
|
9266
9580
|
// src/terminals.ts
|
|
9267
9581
|
init_define_HERD_PACKAGE();
|
|
9268
|
-
import { spawn as
|
|
9269
|
-
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";
|
|
9270
9584
|
import { connect as connect2 } from "node:net";
|
|
9271
|
-
import { join as
|
|
9585
|
+
import { join as join25 } from "node:path";
|
|
9272
9586
|
init_self();
|
|
9273
9587
|
import {
|
|
9274
9588
|
HOST_PROTOCOL,
|
|
9275
9589
|
hostVersion
|
|
9276
9590
|
} from "./terminal-host.mjs";
|
|
9277
|
-
var hostsDir = () =>
|
|
9278
|
-
var legacyHostSocketPath = () =>
|
|
9279
|
-
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`);
|
|
9280
9594
|
var speaksProtocol = (protocol) => protocol === HOST_PROTOCOL || protocol === HOST_PROTOCOL - 1;
|
|
9281
|
-
var sleep6 = (ms) => new Promise((
|
|
9595
|
+
var sleep6 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
9282
9596
|
function askHost(path, request) {
|
|
9283
|
-
return new Promise((
|
|
9597
|
+
return new Promise((resolve7, reject) => {
|
|
9284
9598
|
const socket = connect2(path);
|
|
9285
9599
|
let data = "";
|
|
9286
9600
|
socket.on("connect", () => socket.write(`${JSON.stringify(request)}
|
|
@@ -9289,7 +9603,7 @@ function askHost(path, request) {
|
|
|
9289
9603
|
socket.on("end", () => {
|
|
9290
9604
|
try {
|
|
9291
9605
|
const reply = JSON.parse(data);
|
|
9292
|
-
if (reply.ok)
|
|
9606
|
+
if (reply.ok) resolve7(reply);
|
|
9293
9607
|
else reject(new HerdError(reply.error ?? "The terminal host refused that."));
|
|
9294
9608
|
} catch {
|
|
9295
9609
|
reject(new HerdError("The terminal host's answer wasn't readable."));
|
|
@@ -9324,10 +9638,10 @@ var HostTerminals = class {
|
|
|
9324
9638
|
const dir = hostsDir();
|
|
9325
9639
|
let names = [];
|
|
9326
9640
|
try {
|
|
9327
|
-
names =
|
|
9641
|
+
names = readdirSync8(dir).filter((name) => name.endsWith(".sock"));
|
|
9328
9642
|
} catch {
|
|
9329
9643
|
}
|
|
9330
|
-
const paths = [legacyHostSocketPath(), ...names.map((name) =>
|
|
9644
|
+
const paths = [legacyHostSocketPath(), ...names.map((name) => join25(dir, name))];
|
|
9331
9645
|
const found = [];
|
|
9332
9646
|
for (const path of paths) {
|
|
9333
9647
|
try {
|
|
@@ -9339,7 +9653,7 @@ var HostTerminals = class {
|
|
|
9339
9653
|
speaks: speaksProtocol(hello.protocol)
|
|
9340
9654
|
});
|
|
9341
9655
|
} catch (error) {
|
|
9342
|
-
if (isDead(error) && path !== legacyHostSocketPath())
|
|
9656
|
+
if (isDead(error) && path !== legacyHostSocketPath()) rmSync16(path, { force: true });
|
|
9343
9657
|
}
|
|
9344
9658
|
}
|
|
9345
9659
|
this.hosts = found;
|
|
@@ -9360,8 +9674,8 @@ var HostTerminals = class {
|
|
|
9360
9674
|
}
|
|
9361
9675
|
if (this.current()) return;
|
|
9362
9676
|
const path = currentHostSocketPath(this.version);
|
|
9363
|
-
|
|
9364
|
-
const child =
|
|
9677
|
+
mkdirSync19(hostsDir(), { recursive: true, mode: 448 });
|
|
9678
|
+
const child = spawn10(process.execPath, [herdBin, "terminal-host", "--socket", path], {
|
|
9365
9679
|
detached: true,
|
|
9366
9680
|
stdio: "ignore",
|
|
9367
9681
|
env: { ...process.env, HERD_HOME: herdHome() }
|
|
@@ -9488,7 +9802,7 @@ var HostTerminals = class {
|
|
|
9488
9802
|
await sleep6(100);
|
|
9489
9803
|
if (!await askHost(host.path, { op: "hello" }).catch(() => null)) break;
|
|
9490
9804
|
}
|
|
9491
|
-
|
|
9805
|
+
rmSync16(host.path, { force: true });
|
|
9492
9806
|
}
|
|
9493
9807
|
return true;
|
|
9494
9808
|
}
|
|
@@ -9607,8 +9921,8 @@ var UsageReporter = class {
|
|
|
9607
9921
|
// src/workspaces/index.ts
|
|
9608
9922
|
init_define_HERD_PACKAGE();
|
|
9609
9923
|
init_src();
|
|
9610
|
-
import { mkdirSync as
|
|
9611
|
-
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";
|
|
9612
9926
|
|
|
9613
9927
|
// src/workspaces/codex.ts
|
|
9614
9928
|
init_define_HERD_PACKAGE();
|
|
@@ -9620,7 +9934,7 @@ init_define_HERD_PACKAGE();
|
|
|
9620
9934
|
init_define_HERD_PACKAGE();
|
|
9621
9935
|
init_src();
|
|
9622
9936
|
init_self();
|
|
9623
|
-
import { join as
|
|
9937
|
+
import { join as join26 } from "node:path";
|
|
9624
9938
|
import { z as z25 } from "zod";
|
|
9625
9939
|
function hasThread(events) {
|
|
9626
9940
|
return events.some(
|
|
@@ -9655,7 +9969,7 @@ function filesByMessage(events) {
|
|
|
9655
9969
|
}
|
|
9656
9970
|
return files;
|
|
9657
9971
|
}
|
|
9658
|
-
var FILES_FOLDER =
|
|
9972
|
+
var FILES_FOLDER = join26(".herd", "files");
|
|
9659
9973
|
function safeFileName(name) {
|
|
9660
9974
|
const chars = Array.from(name.replace(/[/\\\p{Cc}]/gu, "_").trim());
|
|
9661
9975
|
while (Buffer.byteLength(chars.join("")) > 200) chars.shift();
|
|
@@ -9877,11 +10191,11 @@ async function claimChange(ws, workspace, change) {
|
|
|
9877
10191
|
// src/workspaces/delivery.ts
|
|
9878
10192
|
init_define_HERD_PACKAGE();
|
|
9879
10193
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
9880
|
-
import { join as
|
|
10194
|
+
import { join as join30 } from "node:path";
|
|
9881
10195
|
|
|
9882
10196
|
// src/workspaces/repos.ts
|
|
9883
10197
|
init_define_HERD_PACKAGE();
|
|
9884
|
-
import { join as
|
|
10198
|
+
import { join as join27 } from "node:path";
|
|
9885
10199
|
var rootOf = (ws) => ws.options.root?.() ?? ws.options.config().root ?? defaultRoot();
|
|
9886
10200
|
var gitEnvOf = (ws) => ws.options.gitEnv?.() ?? loginShellEnv();
|
|
9887
10201
|
function knownRemotes(ws) {
|
|
@@ -9933,7 +10247,7 @@ function repoContext(ws, workspace) {
|
|
|
9933
10247
|
remote: repo.remote || remotesOf(ws, { ...workspace, worktrees: [repo] })[0] || "",
|
|
9934
10248
|
branch: repo.branch
|
|
9935
10249
|
})),
|
|
9936
|
-
cloned:
|
|
10250
|
+
cloned: checkouts(root).map(({ key, remote }) => ({ key, remote })),
|
|
9937
10251
|
described: (ws.options.herdConfig?.()?.repos ?? []).map((repo) => ({
|
|
9938
10252
|
remote: repo.remote,
|
|
9939
10253
|
key: repoKey(repo.remote),
|
|
@@ -9993,7 +10307,7 @@ async function addHints(ws, workspace, hints, handedOff) {
|
|
|
9993
10307
|
const added = await addWorktree({
|
|
9994
10308
|
root,
|
|
9995
10309
|
remote: cloneSource(hint.remote, knownRemotes(ws)),
|
|
9996
|
-
path:
|
|
10310
|
+
path: join27(workspace.folder, place.name),
|
|
9997
10311
|
branch,
|
|
9998
10312
|
...handedOff ? { continueFrom: hint.branch ?? branch } : {},
|
|
9999
10313
|
env: gitEnvOf(ws),
|
|
@@ -10013,6 +10327,8 @@ async function addHints(ws, workspace, hints, handedOff) {
|
|
|
10013
10327
|
true
|
|
10014
10328
|
);
|
|
10015
10329
|
}
|
|
10330
|
+
if (added.copied.length > 0)
|
|
10331
|
+
await ws.note(id2, describeCopied(place.name, added.copied), true);
|
|
10016
10332
|
const found = reposInFolder(workspace.folder).find((repo) => repo.folder === place.name);
|
|
10017
10333
|
await recordRepo(ws, id2, {
|
|
10018
10334
|
folder: place.name,
|
|
@@ -10043,14 +10359,14 @@ function handoffRepos(workspace) {
|
|
|
10043
10359
|
return workspace.worktrees.map((repo) => ({
|
|
10044
10360
|
folder: repo.folder,
|
|
10045
10361
|
branch: repo.branch,
|
|
10046
|
-
unpushed: hasUnpushed(
|
|
10362
|
+
unpushed: hasUnpushed(join27(workspace.folder, repo.folder))
|
|
10047
10363
|
}));
|
|
10048
10364
|
}
|
|
10049
10365
|
|
|
10050
10366
|
// src/workspaces/setup.ts
|
|
10051
10367
|
init_define_HERD_PACKAGE();
|
|
10052
|
-
import { chmodSync as chmodSync4, mkdirSync as
|
|
10053
|
-
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";
|
|
10054
10370
|
|
|
10055
10371
|
// src/hooks.ts
|
|
10056
10372
|
init_define_HERD_PACKAGE();
|
|
@@ -10083,7 +10399,7 @@ function configureClaude(ws, workspace) {
|
|
|
10083
10399
|
configureClaudeIn(workspace.folder, (line2) => ws.log(`${workspace.conversationId}: ${line2}`));
|
|
10084
10400
|
}
|
|
10085
10401
|
function configureClaudeIn(folder, log) {
|
|
10086
|
-
const herd = shellQuote2(
|
|
10402
|
+
const herd = shellQuote2(join28(herdPathDir(), "herd"));
|
|
10087
10403
|
try {
|
|
10088
10404
|
const written = writeWorktreeSettings(
|
|
10089
10405
|
folder,
|
|
@@ -10108,10 +10424,10 @@ function configureClaudeIn(folder, log) {
|
|
|
10108
10424
|
}
|
|
10109
10425
|
}
|
|
10110
10426
|
function herdPathDir() {
|
|
10111
|
-
const dir =
|
|
10112
|
-
|
|
10113
|
-
const shim =
|
|
10114
|
-
|
|
10427
|
+
const dir = join28(herdHome(), "bin");
|
|
10428
|
+
mkdirSync20(dir, { recursive: true, mode: 448 });
|
|
10429
|
+
const shim = join28(dir, "herd");
|
|
10430
|
+
writeFileSync16(shim, `#!/bin/sh
|
|
10115
10431
|
exec "${process.execPath}" "${herdBin}" "$@"
|
|
10116
10432
|
`);
|
|
10117
10433
|
chmodSync4(shim, 493);
|
|
@@ -10129,8 +10445,8 @@ async function setUp(ws, conversationId) {
|
|
|
10129
10445
|
const hints = ws.offeredRepos.get(conversationId) ?? [];
|
|
10130
10446
|
ws.offeredRepos.delete(conversationId);
|
|
10131
10447
|
const handedOff = ws.handedOff.delete(conversationId);
|
|
10132
|
-
const folder =
|
|
10133
|
-
|
|
10448
|
+
const folder = folderForConversation(rootOf(ws), conversationId);
|
|
10449
|
+
mkdirSync20(folder, { recursive: true });
|
|
10134
10450
|
const lead = `herd-${conversationPrefix(conversationId)}-1`;
|
|
10135
10451
|
const secret = ws.options.local().grant({ terminalId: lead, conversationId });
|
|
10136
10452
|
const workspace = {
|
|
@@ -10249,8 +10565,8 @@ init_define_HERD_PACKAGE();
|
|
|
10249
10565
|
// src/workspaces/investigation.ts
|
|
10250
10566
|
init_define_HERD_PACKAGE();
|
|
10251
10567
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
10252
|
-
import { appendFileSync as appendFileSync3, mkdirSync as
|
|
10253
|
-
import { join as
|
|
10568
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync21 } from "node:fs";
|
|
10569
|
+
import { join as join29 } from "node:path";
|
|
10254
10570
|
function needsInvestigator(ws, workspace, reading) {
|
|
10255
10571
|
const failedAt = workspace.noticeFailedAt;
|
|
10256
10572
|
if (workspace.agent === "codex" || workspace.agent === "agy") return false;
|
|
@@ -10406,9 +10722,9 @@ async function followInvestigation(ws, workspace) {
|
|
|
10406
10722
|
}
|
|
10407
10723
|
function saveScreen(ws, conversationId, screen, key) {
|
|
10408
10724
|
try {
|
|
10409
|
-
const dir =
|
|
10410
|
-
|
|
10411
|
-
const file =
|
|
10725
|
+
const dir = join29(herdHome(), "dialogs");
|
|
10726
|
+
mkdirSync21(dir, { recursive: true });
|
|
10727
|
+
const file = join29(dir, `${conversationId}-${Date.now()}.txt`);
|
|
10412
10728
|
appendFileSync3(file, `${screen.join("\n")}
|
|
10413
10729
|
|
|
10414
10730
|
Way out: ${key}
|
|
@@ -10612,7 +10928,7 @@ async function deliver(ws, conversationId, delivery) {
|
|
|
10612
10928
|
await ensureLeadTerminal(ws, workspace);
|
|
10613
10929
|
const codex = workspace.agent === "codex";
|
|
10614
10930
|
if (workspace.agent === "agy") {
|
|
10615
|
-
writeAgyFiles(workspace.folder, shellQuote2(
|
|
10931
|
+
writeAgyFiles(workspace.folder, shellQuote2(join30(herdPathDir(), "herd")));
|
|
10616
10932
|
} else if (!codex) configureClaude(ws, workspace);
|
|
10617
10933
|
const ownId = codex || workspace.agent === "agy";
|
|
10618
10934
|
if (!workspace.started) {
|
|
@@ -10746,15 +11062,15 @@ init_define_HERD_PACKAGE();
|
|
|
10746
11062
|
init_src();
|
|
10747
11063
|
import {
|
|
10748
11064
|
createWriteStream,
|
|
10749
|
-
lstatSync as
|
|
10750
|
-
mkdirSync as
|
|
10751
|
-
realpathSync as
|
|
10752
|
-
renameSync as
|
|
10753
|
-
rmSync as
|
|
10754
|
-
statSync as
|
|
10755
|
-
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
|
|
10756
11072
|
} from "node:fs";
|
|
10757
|
-
import { dirname as
|
|
11073
|
+
import { dirname as dirname14, join as join31, sep as sep2 } from "node:path";
|
|
10758
11074
|
import { Readable } from "node:stream";
|
|
10759
11075
|
import { pipeline } from "node:stream/promises";
|
|
10760
11076
|
|
|
@@ -10767,8 +11083,8 @@ import { readFile } from "node:fs/promises";
|
|
|
10767
11083
|
// src/attachments.ts
|
|
10768
11084
|
init_define_HERD_PACKAGE();
|
|
10769
11085
|
init_src();
|
|
10770
|
-
import { realpathSync as
|
|
10771
|
-
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";
|
|
10772
11088
|
var TYPES = {
|
|
10773
11089
|
".png": "image/png",
|
|
10774
11090
|
".jpg": "image/jpeg",
|
|
@@ -10798,33 +11114,33 @@ function isInside(root, path) {
|
|
|
10798
11114
|
function resolveAttachments(worktree, files) {
|
|
10799
11115
|
let root;
|
|
10800
11116
|
try {
|
|
10801
|
-
root =
|
|
11117
|
+
root = realpathSync9(worktree);
|
|
10802
11118
|
} catch {
|
|
10803
11119
|
return { ok: false, error: "This terminal's conversation folder is missing." };
|
|
10804
11120
|
}
|
|
10805
11121
|
const resolved = [];
|
|
10806
11122
|
for (const file of files) {
|
|
10807
|
-
let
|
|
11123
|
+
let real4;
|
|
10808
11124
|
try {
|
|
10809
|
-
|
|
11125
|
+
real4 = realpathSync9(resolve5(worktree, file.path));
|
|
10810
11126
|
} catch {
|
|
10811
11127
|
return { ok: false, error: `There's no file at ${file.path}.` };
|
|
10812
11128
|
}
|
|
10813
|
-
if (!isInside(root,
|
|
11129
|
+
if (!isInside(root, real4)) {
|
|
10814
11130
|
return {
|
|
10815
11131
|
ok: false,
|
|
10816
11132
|
error: `${file.path} is outside this conversation's folder, so it can't be attached.`
|
|
10817
11133
|
};
|
|
10818
11134
|
}
|
|
10819
|
-
const stats =
|
|
11135
|
+
const stats = statSync8(real4);
|
|
10820
11136
|
if (!stats.isFile()) return { ok: false, error: `${file.path} isn't a file.` };
|
|
10821
11137
|
if (stats.size > MAX_FILE_BYTES) {
|
|
10822
11138
|
return { ok: false, error: `${file.path} is larger than 100 MB, so it can't be attached.` };
|
|
10823
11139
|
}
|
|
10824
11140
|
resolved.push({
|
|
10825
|
-
path:
|
|
11141
|
+
path: real4,
|
|
10826
11142
|
name: basename3(file.path),
|
|
10827
|
-
type: mediaTypeFor(
|
|
11143
|
+
type: mediaTypeFor(real4),
|
|
10828
11144
|
size: stats.size,
|
|
10829
11145
|
...file.title ? { title: file.title } : {},
|
|
10830
11146
|
...file.alt ? { alt: file.alt } : {}
|
|
@@ -10982,11 +11298,11 @@ async function permission(ws, workspace, request, gone) {
|
|
|
10982
11298
|
}
|
|
10983
11299
|
if (state.state === "cancelled") return { ok: true, decision: null };
|
|
10984
11300
|
}
|
|
10985
|
-
await new Promise((
|
|
10986
|
-
const timer = setTimeout(
|
|
11301
|
+
await new Promise((resolve7) => {
|
|
11302
|
+
const timer = setTimeout(resolve7, ws.options.promptPollMs ?? 1500);
|
|
10987
11303
|
gone.addEventListener("abort", () => {
|
|
10988
11304
|
clearTimeout(timer);
|
|
10989
|
-
|
|
11305
|
+
resolve7();
|
|
10990
11306
|
});
|
|
10991
11307
|
});
|
|
10992
11308
|
}
|
|
@@ -11340,7 +11656,7 @@ async function automations(ws, workspace, request, gone = new AbortController().
|
|
|
11340
11656
|
const deadline = Date.now() + (asked.waitMs ?? 9e4);
|
|
11341
11657
|
const promptPath = `${ws.conversationPath(workspace.conversationId)}/prompts/${encodeURIComponent(promptId ?? "")}`;
|
|
11342
11658
|
while (promptId && Date.now() < deadline && !gone.aborted) {
|
|
11343
|
-
await new Promise((
|
|
11659
|
+
await new Promise((resolve7) => setTimeout(resolve7, ws.options.promptPollMs ?? 1500));
|
|
11344
11660
|
const prompt = await ws.api(promptPath).catch(() => null);
|
|
11345
11661
|
if (!prompt?.ok) continue;
|
|
11346
11662
|
const state = await prompt.json();
|
|
@@ -11729,34 +12045,34 @@ async function bringIn(ws, workspace, files) {
|
|
|
11729
12045
|
return listed;
|
|
11730
12046
|
}
|
|
11731
12047
|
async function download(ws, workspace, file) {
|
|
11732
|
-
const path =
|
|
11733
|
-
const target =
|
|
11734
|
-
if (
|
|
11735
|
-
const herd =
|
|
11736
|
-
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()) {
|
|
11737
12053
|
throw new Error("its folder leads outside the worktree");
|
|
11738
12054
|
}
|
|
11739
|
-
|
|
11740
|
-
if (!
|
|
12055
|
+
mkdirSync22(dirname14(target), { recursive: true });
|
|
12056
|
+
if (!realpathSync10(dirname14(target)).startsWith(realpathSync10(workspace.folder) + sep2)) {
|
|
11741
12057
|
throw new Error("its folder leads outside the worktree");
|
|
11742
12058
|
}
|
|
11743
|
-
|
|
12059
|
+
writeFileSync17(join31(herd, ".gitignore"), "*\n");
|
|
11744
12060
|
const response = await ws.api(
|
|
11745
12061
|
`${ws.conversationPath(workspace.conversationId)}/files/${encodeURIComponent(file.id)}`
|
|
11746
12062
|
);
|
|
11747
12063
|
if (!response.ok || !response.body) throw new Error(`the server answered ${response.status}`);
|
|
11748
12064
|
const partial = `${target}.partial`;
|
|
11749
|
-
|
|
12065
|
+
rmSync17(partial, { force: true });
|
|
11750
12066
|
try {
|
|
11751
12067
|
await pipeline(
|
|
11752
12068
|
Readable.fromWeb(response.body),
|
|
11753
12069
|
createWriteStream(partial, { flags: "wx" })
|
|
11754
12070
|
);
|
|
11755
|
-
const { size } =
|
|
12071
|
+
const { size } = statSync9(partial);
|
|
11756
12072
|
if (size !== file.size) throw new Error(`it came to ${size} bytes, not ${file.size}`);
|
|
11757
|
-
|
|
12073
|
+
renameSync7(partial, target);
|
|
11758
12074
|
} finally {
|
|
11759
|
-
|
|
12075
|
+
rmSync17(partial, { force: true });
|
|
11760
12076
|
}
|
|
11761
12077
|
return path;
|
|
11762
12078
|
}
|
|
@@ -12030,7 +12346,7 @@ var Workspaces = class {
|
|
|
12030
12346
|
return this.driver;
|
|
12031
12347
|
}
|
|
12032
12348
|
// ---- State, kept on disk so a restarted daemon picks up where it was.
|
|
12033
|
-
statePath = () =>
|
|
12349
|
+
statePath = () => join32(herdHome(), "state.json");
|
|
12034
12350
|
load() {
|
|
12035
12351
|
try {
|
|
12036
12352
|
return stateSchema.parse(JSON.parse(readFileSync18(this.statePath(), "utf8"))).workspaces;
|
|
@@ -12039,8 +12355,8 @@ var Workspaces = class {
|
|
|
12039
12355
|
}
|
|
12040
12356
|
}
|
|
12041
12357
|
save(workspaces) {
|
|
12042
|
-
|
|
12043
|
-
|
|
12358
|
+
mkdirSync23(herdHome(), { recursive: true, mode: 448 });
|
|
12359
|
+
writeFileSync18(this.statePath(), JSON.stringify({ workspaces }, null, 2), { mode: 384 });
|
|
12044
12360
|
}
|
|
12045
12361
|
get(conversationId) {
|
|
12046
12362
|
return this.load()[conversationId] ?? null;
|
|
@@ -12250,7 +12566,19 @@ async function runDaemon(args) {
|
|
|
12250
12566
|
const root = setRoot(defaultRoot()).root;
|
|
12251
12567
|
console.log(`No root folder is set for this machine, so it works from ${root}.`);
|
|
12252
12568
|
}
|
|
12253
|
-
|
|
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
|
+
});
|
|
12254
12582
|
const api = apiUrl();
|
|
12255
12583
|
const terminals = new HostTerminals({ log: (line2) => console.log(line2) });
|
|
12256
12584
|
await terminals.ensureHost();
|
|
@@ -12261,7 +12589,6 @@ async function runDaemon(args) {
|
|
|
12261
12589
|
headers: isLocal(api) ? localSignIn() : { Authorization: `Bearer ${await accessToken()}` }
|
|
12262
12590
|
})
|
|
12263
12591
|
};
|
|
12264
|
-
let daemon = null;
|
|
12265
12592
|
let activator = null;
|
|
12266
12593
|
let checks = null;
|
|
12267
12594
|
const log = (line2) => console.log(line2);
|
|
@@ -12343,7 +12670,7 @@ async function runDaemon(args) {
|
|
|
12343
12670
|
env: (secret) => envFor(workspaces, secret),
|
|
12344
12671
|
// As for a conversation: Claude Code's hooks and status line, or agy's. Codex has none.
|
|
12345
12672
|
configure: (agent, folder) => {
|
|
12346
|
-
if (agent === "agy") writeAgyFiles(folder, shellQuote2(
|
|
12673
|
+
if (agent === "agy") writeAgyFiles(folder, shellQuote2(join33(herdPathDir(), "herd")));
|
|
12347
12674
|
else if (agent === "claude-code") configureClaudeIn(folder, log);
|
|
12348
12675
|
},
|
|
12349
12676
|
...shellCommand ? { shellCommand } : {},
|
|
@@ -12381,9 +12708,11 @@ async function runDaemon(args) {
|
|
|
12381
12708
|
activations: () => activator?.states() ?? {},
|
|
12382
12709
|
gh: () => checks?.gh ?? null,
|
|
12383
12710
|
onSignIn: (frame) => void checks?.signIn(frame),
|
|
12384
|
-
onConfigChanged: () => checks?.activateChosen()
|
|
12711
|
+
onConfigChanged: () => checks?.activateChosen(),
|
|
12712
|
+
folderUnavailable: () => folders.problem
|
|
12385
12713
|
});
|
|
12386
12714
|
await daemon.start();
|
|
12715
|
+
folders.start();
|
|
12387
12716
|
checks.start();
|
|
12388
12717
|
usage.start();
|
|
12389
12718
|
const display = process.env.HERD_DISPLAY === "off" ? null : new (await Promise.resolve().then(() => (init_desk_display(), desk_display_exports))).DeskDisplay({
|
|
@@ -12424,9 +12753,11 @@ async function runDaemon(args) {
|
|
|
12424
12753
|
headers: { Authorization: `Bearer ${runnerKey.key}` }
|
|
12425
12754
|
}),
|
|
12426
12755
|
serviceUrl: runnerKey.url,
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
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),
|
|
12430
12761
|
runner: new BuildRunner({
|
|
12431
12762
|
root: () => machineRoot(),
|
|
12432
12763
|
gitEnv,
|
|
@@ -12459,10 +12790,11 @@ async function runDaemon(args) {
|
|
|
12459
12790
|
const codexFollower = setInterval(() => {
|
|
12460
12791
|
void workspaces.followCodex().catch((error) => console.error(`Reading Codex's sessions failed: ${error}`));
|
|
12461
12792
|
}, 250);
|
|
12462
|
-
await new Promise((
|
|
12463
|
-
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());
|
|
12464
12795
|
});
|
|
12465
12796
|
builds?.stop();
|
|
12797
|
+
folders.stop();
|
|
12466
12798
|
checks.stop();
|
|
12467
12799
|
activator.stop();
|
|
12468
12800
|
usage.stop();
|
|
@@ -12556,11 +12888,11 @@ function agyAnswer(event2, output2) {
|
|
|
12556
12888
|
}
|
|
12557
12889
|
|
|
12558
12890
|
// src/hook-client.ts
|
|
12559
|
-
var sleep7 = (ms) => new Promise((
|
|
12891
|
+
var sleep7 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
12560
12892
|
function within2(promise, ms) {
|
|
12561
12893
|
let timer;
|
|
12562
|
-
const timeout = new Promise((
|
|
12563
|
-
timer = setTimeout(() =>
|
|
12894
|
+
const timeout = new Promise((resolve7) => {
|
|
12895
|
+
timer = setTimeout(() => resolve7(null), ms);
|
|
12564
12896
|
});
|
|
12565
12897
|
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
12566
12898
|
}
|
|
@@ -12609,11 +12941,11 @@ async function runAgyHook(event2, stdin, env, options = {}) {
|
|
|
12609
12941
|
|
|
12610
12942
|
// src/statusline.ts
|
|
12611
12943
|
init_define_HERD_PACKAGE();
|
|
12612
|
-
import { spawn as
|
|
12944
|
+
import { spawn as spawn11 } from "node:child_process";
|
|
12613
12945
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
12614
12946
|
import { connect as connect3 } from "node:net";
|
|
12615
12947
|
import { homedir as homedir7 } from "node:os";
|
|
12616
|
-
import { join as
|
|
12948
|
+
import { join as join34 } from "node:path";
|
|
12617
12949
|
var MAX_INPUT = 64 * 1024;
|
|
12618
12950
|
var REPORT_TIMEOUT_MS = 300;
|
|
12619
12951
|
var OWN_COMMAND_TIMEOUT_MS = 5e3;
|
|
@@ -12650,7 +12982,7 @@ function agyStateFrom(input) {
|
|
|
12650
12982
|
function ownAgyStatusLine(home = homedir7()) {
|
|
12651
12983
|
try {
|
|
12652
12984
|
const settings = JSON.parse(
|
|
12653
|
-
readFileSync21(
|
|
12985
|
+
readFileSync21(join34(home, ".gemini", "antigravity-cli", "settings.json"), "utf8")
|
|
12654
12986
|
);
|
|
12655
12987
|
const command = settings.statusLine?.command;
|
|
12656
12988
|
if (typeof command !== "string" || !command.trim()) return null;
|
|
@@ -12670,25 +13002,25 @@ function commandIn(path) {
|
|
|
12670
13002
|
}
|
|
12671
13003
|
}
|
|
12672
13004
|
function ownStatusLine(projectDir, home = homedir7()) {
|
|
12673
|
-
return commandIn(
|
|
13005
|
+
return commandIn(join34(projectDir, ".claude", "settings.json")) ?? commandIn(join34(home, ".claude", "settings.json"));
|
|
12674
13006
|
}
|
|
12675
13007
|
function runOwnCommand(command, input, cwd, timeoutMs = OWN_COMMAND_TIMEOUT_MS) {
|
|
12676
|
-
return new Promise((
|
|
13008
|
+
return new Promise((resolve7) => {
|
|
12677
13009
|
let output2 = "";
|
|
12678
13010
|
let child;
|
|
12679
13011
|
try {
|
|
12680
|
-
child =
|
|
13012
|
+
child = spawn11("sh", ["-c", command], {
|
|
12681
13013
|
cwd,
|
|
12682
13014
|
detached: true,
|
|
12683
13015
|
stdio: ["pipe", "pipe", "ignore"]
|
|
12684
13016
|
});
|
|
12685
13017
|
} catch {
|
|
12686
|
-
return
|
|
13018
|
+
return resolve7("");
|
|
12687
13019
|
}
|
|
12688
13020
|
const finish = (text4) => {
|
|
12689
13021
|
clearTimeout(timer);
|
|
12690
13022
|
child.stdout?.destroy();
|
|
12691
|
-
|
|
13023
|
+
resolve7(text4);
|
|
12692
13024
|
};
|
|
12693
13025
|
const timer = setTimeout(() => {
|
|
12694
13026
|
try {
|
|
@@ -12706,12 +13038,12 @@ function runOwnCommand(command, input, cwd, timeoutMs = OWN_COMMAND_TIMEOUT_MS)
|
|
|
12706
13038
|
});
|
|
12707
13039
|
}
|
|
12708
13040
|
function tellDaemon(path, request, timeoutMs = REPORT_TIMEOUT_MS) {
|
|
12709
|
-
return new Promise((
|
|
13041
|
+
return new Promise((resolve7) => {
|
|
12710
13042
|
const socket = connect3(path);
|
|
12711
13043
|
const done = () => {
|
|
12712
13044
|
clearTimeout(timer);
|
|
12713
13045
|
socket.destroy();
|
|
12714
|
-
|
|
13046
|
+
resolve7();
|
|
12715
13047
|
};
|
|
12716
13048
|
const timer = setTimeout(done, timeoutMs);
|
|
12717
13049
|
socket.on("connect", () => socket.write(`${JSON.stringify(request)}
|
|
@@ -12963,7 +13295,7 @@ init_define_HERD_PACKAGE();
|
|
|
12963
13295
|
import { connect as connect4 } from "node:net";
|
|
12964
13296
|
var DETACH_KEY = "";
|
|
12965
13297
|
function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: process.stdout }, path = currentHostSocketPath()) {
|
|
12966
|
-
return new Promise((
|
|
13298
|
+
return new Promise((resolve7, reject) => {
|
|
12967
13299
|
const socket = connect4(path);
|
|
12968
13300
|
const size = () => ({ cols: stdout.columns || 120, rows: stdout.rows || 40 });
|
|
12969
13301
|
let buffer = "";
|
|
@@ -12977,7 +13309,7 @@ function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: proces
|
|
|
12977
13309
|
stdin.pause();
|
|
12978
13310
|
stdout.off("resize", onResize);
|
|
12979
13311
|
socket.end();
|
|
12980
|
-
|
|
13312
|
+
resolve7(result);
|
|
12981
13313
|
};
|
|
12982
13314
|
const onInput = (chunk) => {
|
|
12983
13315
|
const data = chunk.toString("utf8");
|
|
@@ -13062,20 +13394,21 @@ async function runTerminalHost(rest) {
|
|
|
13062
13394
|
|
|
13063
13395
|
// src/commands/repo.ts
|
|
13064
13396
|
init_define_HERD_PACKAGE();
|
|
13065
|
-
import { join as
|
|
13397
|
+
import { join as join35 } from "node:path";
|
|
13066
13398
|
import { z as z32 } from "zod";
|
|
13067
13399
|
var REPO_HELP = `Usage: herd repo <url or owner/name> [folder]
|
|
13068
13400
|
|
|
13069
13401
|
Checks a repo out in this conversation's folder, on a branch named for the conversation, started
|
|
13070
|
-
from the freshly fetched default branch, and records it in the conversation.
|
|
13071
|
-
|
|
13072
|
-
|
|
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
|
|
13073
13406
|
github.com/owner/name. The folder is the repo's name, or <owner>-<name> when that's taken, unless
|
|
13074
13407
|
you give one. Running it again for a repo that's already here changes nothing.`;
|
|
13075
13408
|
var REPOS_HELP = `Usage: herd repos [--json]
|
|
13076
13409
|
|
|
13077
|
-
Lists the repos in this conversation's folder, the repos
|
|
13078
|
-
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.`;
|
|
13079
13412
|
var repoContextSchema = z32.object({
|
|
13080
13413
|
folder: z32.string(),
|
|
13081
13414
|
root: z32.string(),
|
|
@@ -13149,7 +13482,7 @@ async function runRepo(rest) {
|
|
|
13149
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>.`
|
|
13150
13483
|
);
|
|
13151
13484
|
}
|
|
13152
|
-
const path =
|
|
13485
|
+
const path = join35(here.folder, place.name);
|
|
13153
13486
|
const recorded = here.recorded.some((repo) => repo.folder === place.name);
|
|
13154
13487
|
if (place.existing && recorded) {
|
|
13155
13488
|
const branch = here.recorded.find((repo) => repo.folder === place.name)?.branch ?? here.branch;
|
|
@@ -13191,13 +13524,14 @@ ${error.message}`);
|
|
|
13191
13524
|
note = ` The conversation couldn't record it: ${error instanceof Error ? error.message : error}`;
|
|
13192
13525
|
}
|
|
13193
13526
|
const lines = [
|
|
13194
|
-
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}.`
|
|
13195
13528
|
];
|
|
13196
13529
|
if (added.fetchFailed) {
|
|
13197
13530
|
lines.push(
|
|
13198
13531
|
`Couldn't fetch it, so it started from the last fetched ${added.base}: ${added.fetchFailed}`
|
|
13199
13532
|
);
|
|
13200
13533
|
}
|
|
13534
|
+
if (added.copied.length > 0) lines.push(describeCopied(place.name, added.copied));
|
|
13201
13535
|
if (added.created) lines.push("Its own AGENTS.md or README says how to set it up.");
|
|
13202
13536
|
console.log(lines.join("\n") + note);
|
|
13203
13537
|
return 0;
|
|
@@ -13209,7 +13543,7 @@ function formatRepos(here) {
|
|
|
13209
13543
|
for (const repo of here.recorded)
|
|
13210
13544
|
lines.push(` ${repo.folder} ${repo.remote} on ${repo.branch}`);
|
|
13211
13545
|
const cloned = here.cloned.filter((repo) => !inFolder.has(repo.key));
|
|
13212
|
-
lines.push("", "
|
|
13546
|
+
lines.push("", "Checked out in the root:");
|
|
13213
13547
|
if (cloned.length === 0) lines.push(" none others");
|
|
13214
13548
|
const about = new Map(here.described.map((repo) => [repo.key, repo.about]));
|
|
13215
13549
|
for (const repo of cloned) {
|
|
@@ -13253,7 +13587,7 @@ async function runRepos(rest) {
|
|
|
13253
13587
|
init_define_HERD_PACKAGE();
|
|
13254
13588
|
init_src();
|
|
13255
13589
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
13256
|
-
import { resolve as
|
|
13590
|
+
import { resolve as resolve6 } from "node:path";
|
|
13257
13591
|
var SEND_HELP = `Usage: herd send <text>
|
|
13258
13592
|
herd send -- <text that starts with a dash>
|
|
13259
13593
|
herd send < reply.txt
|
|
@@ -13336,7 +13670,7 @@ function withAbsolutePaths(message, cwd = process.cwd()) {
|
|
|
13336
13670
|
return {
|
|
13337
13671
|
...message,
|
|
13338
13672
|
files: files.map(
|
|
13339
|
-
(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
|
|
13340
13674
|
)
|
|
13341
13675
|
};
|
|
13342
13676
|
}
|
|
@@ -13973,7 +14307,7 @@ ${HELP4}`);
|
|
|
13973
14307
|
// src/package-entry.ts
|
|
13974
14308
|
var invoked = (() => {
|
|
13975
14309
|
try {
|
|
13976
|
-
return
|
|
14310
|
+
return realpathSync11(process.argv[1] ?? "");
|
|
13977
14311
|
} catch {
|
|
13978
14312
|
return "";
|
|
13979
14313
|
}
|