@herdingchats/friday 0.1.653 → 0.1.660
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 +510 -492
- package/package.json +2 -2
- package/terminal-host.mjs +37 -3
package/herd.mjs
CHANGED
|
@@ -18,7 +18,7 @@ var __export = (target, all) => {
|
|
|
18
18
|
var define_HERD_PACKAGE_default;
|
|
19
19
|
var init_define_HERD_PACKAGE = __esm({
|
|
20
20
|
"<define:HERD_PACKAGE>"() {
|
|
21
|
-
define_HERD_PACKAGE_default = { version: "0.1.
|
|
21
|
+
define_HERD_PACKAGE_default = { version: "0.1.660", gitHead: "21101252ff98a4dc317139a36c286b347f5f5933" };
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -3208,10 +3208,10 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
|
3208
3208
|
// src/friday/main.ts
|
|
3209
3209
|
init_define_HERD_PACKAGE();
|
|
3210
3210
|
init_src();
|
|
3211
|
-
import { spawn as
|
|
3211
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
3212
3212
|
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
|
|
3213
3213
|
import { homedir as homedir5 } from "node:os";
|
|
3214
|
-
import { dirname as dirname10, join as
|
|
3214
|
+
import { dirname as dirname10, join as join16 } from "node:path";
|
|
3215
3215
|
import { createInterface } from "node:readline/promises";
|
|
3216
3216
|
import { z as z22 } from "zod";
|
|
3217
3217
|
|
|
@@ -3414,7 +3414,7 @@ var HERD_FLAGS = {
|
|
|
3414
3414
|
login: [],
|
|
3415
3415
|
logout: [],
|
|
3416
3416
|
daemon: ["--repo"],
|
|
3417
|
-
attach: [],
|
|
3417
|
+
attach: ["--watch"],
|
|
3418
3418
|
builds: [],
|
|
3419
3419
|
inbox: [],
|
|
3420
3420
|
send: ["--json"],
|
|
@@ -4519,15 +4519,15 @@ async function readSetup(fetch2, init) {
|
|
|
4519
4519
|
// src/friday/agents.ts
|
|
4520
4520
|
init_define_HERD_PACKAGE();
|
|
4521
4521
|
init_src();
|
|
4522
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
4523
|
-
import { dirname as dirname6, join as
|
|
4522
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4523
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
4524
4524
|
|
|
4525
4525
|
// src/activation.ts
|
|
4526
4526
|
init_define_HERD_PACKAGE();
|
|
4527
4527
|
init_src();
|
|
4528
4528
|
import { randomBytes as randomBytes4, randomUUID as randomUUID3 } from "node:crypto";
|
|
4529
|
-
import { mkdirSync as
|
|
4530
|
-
import { dirname as dirname5, join as
|
|
4529
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync6, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4530
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
4531
4531
|
import { z as z21 } from "zod";
|
|
4532
4532
|
|
|
4533
4533
|
// src/permissions.ts
|
|
@@ -4930,6 +4930,243 @@ async function permissionHook(raw, options = {}) {
|
|
|
4930
4930
|
}
|
|
4931
4931
|
}
|
|
4932
4932
|
|
|
4933
|
+
// src/terminals.ts
|
|
4934
|
+
init_define_HERD_PACKAGE();
|
|
4935
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
4936
|
+
import { mkdirSync as mkdirSync5, readdirSync as readdirSync3, rmSync as rmSync5 } from "node:fs";
|
|
4937
|
+
import { connect as connect2 } from "node:net";
|
|
4938
|
+
import { join as join7 } from "node:path";
|
|
4939
|
+
init_self();
|
|
4940
|
+
import {
|
|
4941
|
+
HOST_PROTOCOL,
|
|
4942
|
+
hostVersion
|
|
4943
|
+
} from "./terminal-host.mjs";
|
|
4944
|
+
var hostsDir = () => join7(herdHome(), "terminals");
|
|
4945
|
+
var legacyHostSocketPath = () => join7(herdHome(), "terminals.sock");
|
|
4946
|
+
var currentHostSocketPath = (version = hostVersion()) => join7(hostsDir(), `${version}.sock`);
|
|
4947
|
+
var speaksProtocol = (protocol) => protocol === HOST_PROTOCOL || protocol === HOST_PROTOCOL - 1;
|
|
4948
|
+
var sleep = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
|
|
4949
|
+
function askHost(path, request) {
|
|
4950
|
+
return new Promise((resolve8, reject) => {
|
|
4951
|
+
const socket = connect2(path);
|
|
4952
|
+
let data = "";
|
|
4953
|
+
socket.on("connect", () => socket.write(`${JSON.stringify(request)}
|
|
4954
|
+
`));
|
|
4955
|
+
socket.on("data", (chunk) => data += chunk);
|
|
4956
|
+
socket.on("end", () => {
|
|
4957
|
+
try {
|
|
4958
|
+
const reply = JSON.parse(data);
|
|
4959
|
+
if (reply.ok) resolve8(reply);
|
|
4960
|
+
else reject(new HerdError(reply.error ?? "The terminal host refused that."));
|
|
4961
|
+
} catch {
|
|
4962
|
+
reject(new HerdError("The terminal host's answer wasn't readable."));
|
|
4963
|
+
}
|
|
4964
|
+
});
|
|
4965
|
+
socket.on("error", reject);
|
|
4966
|
+
});
|
|
4967
|
+
}
|
|
4968
|
+
var isDead = (error) => {
|
|
4969
|
+
const code = error?.code;
|
|
4970
|
+
return code === "ECONNREFUSED" || code === "ENOENT";
|
|
4971
|
+
};
|
|
4972
|
+
var HostTerminals = class {
|
|
4973
|
+
hosts = [];
|
|
4974
|
+
/** Which host each terminal name was last found in. */
|
|
4975
|
+
where = /* @__PURE__ */ new Map();
|
|
4976
|
+
version;
|
|
4977
|
+
log;
|
|
4978
|
+
constructor(options = {}) {
|
|
4979
|
+
this.version = options.version ?? hostVersion();
|
|
4980
|
+
this.log = options.log ?? (() => {
|
|
4981
|
+
});
|
|
4982
|
+
}
|
|
4983
|
+
currentVersion() {
|
|
4984
|
+
return this.version;
|
|
4985
|
+
}
|
|
4986
|
+
unreachable(version) {
|
|
4987
|
+
return this.hosts.some((host) => host.hello.version === version && !host.speaks);
|
|
4988
|
+
}
|
|
4989
|
+
/** Every host that answers, with sockets nothing answers on removed. */
|
|
4990
|
+
async discover() {
|
|
4991
|
+
const dir = hostsDir();
|
|
4992
|
+
let names = [];
|
|
4993
|
+
try {
|
|
4994
|
+
names = readdirSync3(dir).filter((name) => name.endsWith(".sock"));
|
|
4995
|
+
} catch {
|
|
4996
|
+
}
|
|
4997
|
+
const paths = [legacyHostSocketPath(), ...names.map((name) => join7(dir, name))];
|
|
4998
|
+
const found = [];
|
|
4999
|
+
for (const path of paths) {
|
|
5000
|
+
try {
|
|
5001
|
+
const hello = await askHost(path, { op: "hello" });
|
|
5002
|
+
found.push({
|
|
5003
|
+
path,
|
|
5004
|
+
hello,
|
|
5005
|
+
current: hello.version === this.version && speaksProtocol(hello.protocol),
|
|
5006
|
+
speaks: speaksProtocol(hello.protocol)
|
|
5007
|
+
});
|
|
5008
|
+
} catch (error) {
|
|
5009
|
+
if (isDead(error) && path !== legacyHostSocketPath()) rmSync5(path, { force: true });
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
this.hosts = found;
|
|
5013
|
+
return found;
|
|
5014
|
+
}
|
|
5015
|
+
current() {
|
|
5016
|
+
return this.hosts.find((host) => host.current);
|
|
5017
|
+
}
|
|
5018
|
+
/** Starts a host of the daemon's own version if none is running, detached so it outlives it. */
|
|
5019
|
+
async ensureHost() {
|
|
5020
|
+
const hosts = await this.discover();
|
|
5021
|
+
for (const host of hosts) {
|
|
5022
|
+
const version = host.hello.version ?? "unversioned";
|
|
5023
|
+
const role = host.current ? "current" : host.speaks ? "older" : "not driveable";
|
|
5024
|
+
this.log(
|
|
5025
|
+
`Terminal host ${version} (protocol ${host.hello.protocol}, pid ${host.hello.pid}): ${role}.`
|
|
5026
|
+
);
|
|
5027
|
+
}
|
|
5028
|
+
if (this.current()) return;
|
|
5029
|
+
const path = currentHostSocketPath(this.version);
|
|
5030
|
+
mkdirSync5(hostsDir(), { recursive: true, mode: 448 });
|
|
5031
|
+
const child = spawn4(process.execPath, [herdBin, "terminal-host", "--socket", path], {
|
|
5032
|
+
detached: true,
|
|
5033
|
+
stdio: "ignore",
|
|
5034
|
+
env: { ...process.env, HERD_HOME: herdHome() }
|
|
5035
|
+
});
|
|
5036
|
+
child.unref();
|
|
5037
|
+
for (let attempt = 0; attempt < 50 && !this.current(); attempt++) {
|
|
5038
|
+
await sleep(100);
|
|
5039
|
+
const hello = await askHost(path, { op: "hello" }).catch(() => null);
|
|
5040
|
+
if (!hello) continue;
|
|
5041
|
+
if (hello.version !== this.version) {
|
|
5042
|
+
throw new HerdError(
|
|
5043
|
+
`The terminal host started as version ${hello.version}, not ${this.version}, so its files changed as it started. Starting the daemon again picks this up.`
|
|
5044
|
+
);
|
|
5045
|
+
}
|
|
5046
|
+
this.hosts.push({ path, hello, current: true, speaks: speaksProtocol(hello.protocol) });
|
|
5047
|
+
}
|
|
5048
|
+
if (!this.current()) throw new HerdError("The terminal host didn't start.");
|
|
5049
|
+
this.log(`Started terminal host ${this.version}.`);
|
|
5050
|
+
}
|
|
5051
|
+
/**
|
|
5052
|
+
* Every driveable host's terminals. A name in more than one host, as when a session moved, is
|
|
5053
|
+
* the running one, or else the newest.
|
|
5054
|
+
*/
|
|
5055
|
+
async list() {
|
|
5056
|
+
if (this.hosts.length === 0) await this.discover();
|
|
5057
|
+
const byName = /* @__PURE__ */ new Map();
|
|
5058
|
+
for (const host of this.hosts.filter((candidate) => candidate.speaks)) {
|
|
5059
|
+
const terminals = await askHost(host.path, { op: "list" }).then((reply) => reply.terminals).catch(() => []);
|
|
5060
|
+
for (const info of terminals) {
|
|
5061
|
+
const seen = byName.get(info.name);
|
|
5062
|
+
const better = !seen || seen.info.exited && !info.exited || seen.info.exited === info.exited && info.createdAt > seen.info.createdAt;
|
|
5063
|
+
if (better) byName.set(info.name, { info, path: host.path });
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
this.where = new Map([...byName].map(([name, { path }]) => [name, path]));
|
|
5067
|
+
return [...byName.values()].map(({ info }) => info);
|
|
5068
|
+
}
|
|
5069
|
+
/** The socket of the host holding a terminal. */
|
|
5070
|
+
async pathOf(name) {
|
|
5071
|
+
let path = this.where.get(name);
|
|
5072
|
+
if (!path) {
|
|
5073
|
+
await this.list();
|
|
5074
|
+
path = this.where.get(name);
|
|
5075
|
+
}
|
|
5076
|
+
if (!path) throw new HerdError(`There's no terminal named ${name}.`);
|
|
5077
|
+
return path;
|
|
5078
|
+
}
|
|
5079
|
+
async askFor(name, request) {
|
|
5080
|
+
return askHost(await this.pathOf(name), request);
|
|
5081
|
+
}
|
|
5082
|
+
async create(opts) {
|
|
5083
|
+
const running = (await this.list()).find((t) => t.name === opts.name && !t.exited);
|
|
5084
|
+
if (running) throw new HerdError(`A terminal named ${opts.name} is already running.`);
|
|
5085
|
+
let host = this.current();
|
|
5086
|
+
if (!host) {
|
|
5087
|
+
await this.ensureHost();
|
|
5088
|
+
host = this.current();
|
|
5089
|
+
}
|
|
5090
|
+
if (!host) throw new HerdError("The terminal host didn't start.");
|
|
5091
|
+
const { terminal } = await askHost(host.path, {
|
|
5092
|
+
op: "create",
|
|
5093
|
+
...opts
|
|
5094
|
+
});
|
|
5095
|
+
this.where.set(opts.name, host.path);
|
|
5096
|
+
return terminal;
|
|
5097
|
+
}
|
|
5098
|
+
async screen(name) {
|
|
5099
|
+
return (await this.askFor(name, { op: "screen", name })).screen;
|
|
5100
|
+
}
|
|
5101
|
+
async snapshot(name) {
|
|
5102
|
+
return (await this.askFor(name, { op: "snapshot", name })).snapshot;
|
|
5103
|
+
}
|
|
5104
|
+
async type(name, text4) {
|
|
5105
|
+
await this.askFor(name, { op: "type", name, text: text4 });
|
|
5106
|
+
}
|
|
5107
|
+
async press(name, key) {
|
|
5108
|
+
await this.askFor(name, { op: "press", name, key });
|
|
5109
|
+
}
|
|
5110
|
+
async exitStatus(name) {
|
|
5111
|
+
const terminal = (await this.list()).find((candidate) => candidate.name === name);
|
|
5112
|
+
if (!terminal) throw new HerdError(`There's no terminal named ${name}.`);
|
|
5113
|
+
return terminal.exited ? terminal.exitCode : null;
|
|
5114
|
+
}
|
|
5115
|
+
async kill(name) {
|
|
5116
|
+
await this.askFor(name, { op: "kill", name });
|
|
5117
|
+
}
|
|
5118
|
+
async retire(canStop) {
|
|
5119
|
+
const stopped2 = [];
|
|
5120
|
+
for (const host of await this.discover()) {
|
|
5121
|
+
if (host.current) continue;
|
|
5122
|
+
const version = host.hello.version ?? "unversioned";
|
|
5123
|
+
if (host.speaks) {
|
|
5124
|
+
const terminals = await askHost(host.path, { op: "list" }).then((reply) => reply.terminals).catch(() => null);
|
|
5125
|
+
if (!terminals) continue;
|
|
5126
|
+
if (terminals.some((t) => !t.exited || (t.viewers ?? 0) > 0)) continue;
|
|
5127
|
+
} else if (!host.hello.version || !canStop(host.hello.version)) {
|
|
5128
|
+
continue;
|
|
5129
|
+
}
|
|
5130
|
+
if (await this.stop(host)) {
|
|
5131
|
+
stopped2.push(version);
|
|
5132
|
+
this.log(`Retired terminal host ${version}, which had nothing left running.`);
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
if (stopped2.length > 0) await this.discover();
|
|
5136
|
+
return stopped2;
|
|
5137
|
+
}
|
|
5138
|
+
/** Stops a host: politely where it knows how, and otherwise by its process. */
|
|
5139
|
+
async stop(host) {
|
|
5140
|
+
if (host.speaks && host.hello.ops?.includes("shutdown")) {
|
|
5141
|
+
return askHost(host.path, { op: "shutdown" }).then(
|
|
5142
|
+
() => true,
|
|
5143
|
+
() => false
|
|
5144
|
+
);
|
|
5145
|
+
}
|
|
5146
|
+
const hello = await askHost(host.path, { op: "hello" }).catch(() => null);
|
|
5147
|
+
if (!hello || hello.pid !== host.hello.pid) return false;
|
|
5148
|
+
try {
|
|
5149
|
+
process.kill(hello.pid, "SIGTERM");
|
|
5150
|
+
} catch {
|
|
5151
|
+
return false;
|
|
5152
|
+
}
|
|
5153
|
+
if (host.path === legacyHostSocketPath()) {
|
|
5154
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
5155
|
+
await sleep(100);
|
|
5156
|
+
if (!await askHost(host.path, { op: "hello" }).catch(() => null)) break;
|
|
5157
|
+
}
|
|
5158
|
+
rmSync5(host.path, { force: true });
|
|
5159
|
+
}
|
|
5160
|
+
return true;
|
|
5161
|
+
}
|
|
5162
|
+
};
|
|
5163
|
+
var DRIVER_QUIET_MS = 10 * 1e3;
|
|
5164
|
+
function someoneDriving(terminal, quietMs = DRIVER_QUIET_MS, now = Date.now()) {
|
|
5165
|
+
if (!terminal || (terminal.viewers ?? 0) === 0 || !terminal.drivenAt) return false;
|
|
5166
|
+
return now - terminal.drivenAt < quietMs;
|
|
5167
|
+
}
|
|
5168
|
+
var lookingAt = (terminal) => (terminal?.viewers ?? 0) + (terminal?.watchers ?? 0);
|
|
5169
|
+
|
|
4933
5170
|
// src/timing.ts
|
|
4934
5171
|
init_define_HERD_PACKAGE();
|
|
4935
5172
|
var CHECK_IN_MS = 15e3;
|
|
@@ -4943,6 +5180,7 @@ var VARIABLES = {
|
|
|
4943
5180
|
agyIdleSettleMs: "HERD_AGY_IDLE_SETTLE_MS",
|
|
4944
5181
|
promptPollMs: "HERD_PROMPT_POLL_MS",
|
|
4945
5182
|
noticeRetryMs: "HERD_NOTICE_RETRY_MS",
|
|
5183
|
+
driverQuietMs: "HERD_DRIVER_QUIET_MS",
|
|
4946
5184
|
investigateAfterMs: "HERD_INVESTIGATE_AFTER_MS",
|
|
4947
5185
|
idleMs: "HERD_IDLE_MS",
|
|
4948
5186
|
watchMs: "HERD_WATCH_MS",
|
|
@@ -4986,14 +5224,14 @@ function newActivationCode() {
|
|
|
4986
5224
|
return [...randomBytes4(8)].map((byte) => CODE_LETTERS[byte % CODE_LETTERS.length]).join("");
|
|
4987
5225
|
}
|
|
4988
5226
|
var activationTerminal = (agent) => `herd-activate-${agentShortName(agent)}`;
|
|
4989
|
-
var activationFolder = (root, agent) =>
|
|
5227
|
+
var activationFolder = (root, agent) => join8(convosHome(root), ".activate", agentShortName(agent));
|
|
4990
5228
|
var recordSchema = agentActivationSchema.extend({
|
|
4991
5229
|
/** While it's waiting: the terminal it runs in, which `friday start` can attach to. */
|
|
4992
5230
|
terminal: z21.string().optional()
|
|
4993
5231
|
});
|
|
4994
5232
|
var storeSchema = z21.object({ agents: z21.record(z21.string(), recordSchema) });
|
|
4995
|
-
var activationsPath = (home = herdHome()) =>
|
|
4996
|
-
var activationRequestPath = (home = herdHome()) =>
|
|
5233
|
+
var activationsPath = (home = herdHome()) => join8(home, "activations.json");
|
|
5234
|
+
var activationRequestPath = (home = herdHome()) => join8(home, "activate-request");
|
|
4997
5235
|
function readActivations(path = activationsPath()) {
|
|
4998
5236
|
try {
|
|
4999
5237
|
return storeSchema.parse(JSON.parse(readFileSync5(path, "utf8"))).agents;
|
|
@@ -5002,7 +5240,7 @@ function readActivations(path = activationsPath()) {
|
|
|
5002
5240
|
}
|
|
5003
5241
|
}
|
|
5004
5242
|
function requestActivation(home = herdHome(), now = Date.now()) {
|
|
5005
|
-
|
|
5243
|
+
mkdirSync6(home, { recursive: true, mode: 448 });
|
|
5006
5244
|
writeFileSync4(activationRequestPath(home), String(now), { mode: 384 });
|
|
5007
5245
|
}
|
|
5008
5246
|
function needsActivation(record, version) {
|
|
@@ -5019,7 +5257,7 @@ function activateDecision(request, code) {
|
|
|
5019
5257
|
}
|
|
5020
5258
|
};
|
|
5021
5259
|
}
|
|
5022
|
-
var
|
|
5260
|
+
var sleep2 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
|
|
5023
5261
|
var label = (agent) => AGENTS[agent]?.label ?? agent;
|
|
5024
5262
|
var Activator = class {
|
|
5025
5263
|
constructor(options) {
|
|
@@ -5047,7 +5285,7 @@ var Activator = class {
|
|
|
5047
5285
|
}
|
|
5048
5286
|
record(agent, record) {
|
|
5049
5287
|
const agents = { ...readActivations(this.path), [agent]: record };
|
|
5050
|
-
|
|
5288
|
+
mkdirSync6(dirname5(this.path), { recursive: true, mode: 448 });
|
|
5051
5289
|
const temporary = `${this.path}.${process.pid}`;
|
|
5052
5290
|
writeFileSync4(temporary, JSON.stringify({ agents }, null, 2), { mode: 384 });
|
|
5053
5291
|
renameSync2(temporary, this.path);
|
|
@@ -5094,7 +5332,7 @@ var Activator = class {
|
|
|
5094
5332
|
this.log(`Activating ${label(next.agent)} once its conversations here settle.`);
|
|
5095
5333
|
}
|
|
5096
5334
|
while (this.options.busy(next.agent) && !this.stopped) {
|
|
5097
|
-
await
|
|
5335
|
+
await sleep2(this.options.busyPollMs ?? BUSY_POLL_MS);
|
|
5098
5336
|
}
|
|
5099
5337
|
if (this.stopped) break;
|
|
5100
5338
|
await this.activate(next.agent, next.version).catch(
|
|
@@ -5140,8 +5378,8 @@ var Activator = class {
|
|
|
5140
5378
|
const code = newActivationCode();
|
|
5141
5379
|
const terminal = activationTerminal(agent);
|
|
5142
5380
|
const folder = activationFolder(this.options.root(), agent);
|
|
5143
|
-
|
|
5144
|
-
|
|
5381
|
+
rmSync6(folder, { recursive: true, force: true });
|
|
5382
|
+
mkdirSync6(folder, { recursive: true });
|
|
5145
5383
|
const current = { agent, code, terminal, checkedIn: false, wake: () => {
|
|
5146
5384
|
} };
|
|
5147
5385
|
this.current = current;
|
|
@@ -5167,7 +5405,7 @@ var Activator = class {
|
|
|
5167
5405
|
);
|
|
5168
5406
|
if (open) await terminals.kill(terminal).catch(() => {
|
|
5169
5407
|
});
|
|
5170
|
-
|
|
5408
|
+
rmSync6(folder, { recursive: true, force: true });
|
|
5171
5409
|
}
|
|
5172
5410
|
this.record(agent, outcome);
|
|
5173
5411
|
this.log(
|
|
@@ -5228,7 +5466,9 @@ var Activator = class {
|
|
|
5228
5466
|
terminal: current.terminal
|
|
5229
5467
|
});
|
|
5230
5468
|
}
|
|
5231
|
-
const viewers = (
|
|
5469
|
+
const viewers = lookingAt(
|
|
5470
|
+
(await terminals.list()).find((t) => t.name === current.terminal)
|
|
5471
|
+
);
|
|
5232
5472
|
if (viewers === 0 && Date.now() - startedAt >= restartMs) await start();
|
|
5233
5473
|
} else if (reading?.state === "idle" && reading.program !== "shell" && !prompted) {
|
|
5234
5474
|
prompted = true;
|
|
@@ -5330,7 +5570,7 @@ async function chooseAgents(options, print) {
|
|
|
5330
5570
|
Object.fromEntries(choices.map((choice) => [choice.cli, choice.share]));
|
|
5331
5571
|
const choosing = options.ask || options.given !== void 0;
|
|
5332
5572
|
if (choosing && (!sameChoice(saved, shares) || !existsSync5(options.configPath))) {
|
|
5333
|
-
|
|
5573
|
+
mkdirSync7(dirname6(options.configPath), { recursive: true, mode: 448 });
|
|
5334
5574
|
writeFileSync5(options.configPath, configSource(withChoice(saved, shares)), { mode: 384 });
|
|
5335
5575
|
}
|
|
5336
5576
|
const chosen = ADAPTERS.map(({ cli }) => cli).filter((cli) => (shares[cli] ?? "off") !== "off");
|
|
@@ -5344,7 +5584,7 @@ function askToActivate(herdHome2, now = Date.now()) {
|
|
|
5344
5584
|
requestActivation(herdHome2, now);
|
|
5345
5585
|
return now;
|
|
5346
5586
|
}
|
|
5347
|
-
var
|
|
5587
|
+
var sleep3 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
|
|
5348
5588
|
function settledLine(cli, activation) {
|
|
5349
5589
|
if (activation.state === "activated") return `${label2(cli)} is activated.`;
|
|
5350
5590
|
return `${label2(cli)} ${activationText(cli, activation).replace(" there", " on this computer")}.`;
|
|
@@ -5395,11 +5635,11 @@ async function awaitActivations(options, chosen, since, print) {
|
|
|
5395
5635
|
);
|
|
5396
5636
|
break;
|
|
5397
5637
|
}
|
|
5398
|
-
await
|
|
5638
|
+
await sleep3(pollMs);
|
|
5399
5639
|
}
|
|
5400
5640
|
return settled;
|
|
5401
5641
|
}
|
|
5402
|
-
var herdConfigIn = (herdHome2) =>
|
|
5642
|
+
var herdConfigIn = (herdHome2) => join9(herdHome2, "herd.config.ts");
|
|
5403
5643
|
|
|
5404
5644
|
// src/friday/home.ts
|
|
5405
5645
|
init_define_HERD_PACKAGE();
|
|
@@ -5408,32 +5648,32 @@ import { spawnSync } from "node:child_process";
|
|
|
5408
5648
|
import {
|
|
5409
5649
|
closeSync as closeSync2,
|
|
5410
5650
|
existsSync as existsSync6,
|
|
5411
|
-
mkdirSync as
|
|
5651
|
+
mkdirSync as mkdirSync8,
|
|
5412
5652
|
openSync as openSync2,
|
|
5413
5653
|
readFileSync as readFileSync6,
|
|
5414
|
-
rmSync as
|
|
5654
|
+
rmSync as rmSync7,
|
|
5415
5655
|
statSync as statSync5,
|
|
5416
5656
|
writeFileSync as writeFileSync6
|
|
5417
5657
|
} from "node:fs";
|
|
5418
|
-
import { dirname as dirname7, join as
|
|
5658
|
+
import { dirname as dirname7, join as join10, resolve as resolve2 } from "node:path";
|
|
5419
5659
|
var PACKAGE_NAME = FRIDAY_PACKAGE;
|
|
5420
5660
|
function fridayPaths(herdHome2) {
|
|
5421
|
-
const logs =
|
|
5661
|
+
const logs = join10(herdHome2, "logs");
|
|
5422
5662
|
return {
|
|
5423
5663
|
herdHome: herdHome2,
|
|
5424
|
-
config:
|
|
5425
|
-
versions:
|
|
5426
|
-
current:
|
|
5427
|
-
pin:
|
|
5664
|
+
config: join10(herdHome2, "config.json"),
|
|
5665
|
+
versions: join10(herdHome2, "versions"),
|
|
5666
|
+
current: join10(herdHome2, "current"),
|
|
5667
|
+
pin: join10(herdHome2, "pin"),
|
|
5428
5668
|
logs,
|
|
5429
|
-
daemonLog:
|
|
5430
|
-
supervisorLog:
|
|
5431
|
-
updateLock:
|
|
5432
|
-
status:
|
|
5433
|
-
terminals:
|
|
5669
|
+
daemonLog: join10(logs, "daemon.log"),
|
|
5670
|
+
supervisorLog: join10(logs, "supervisor.log"),
|
|
5671
|
+
updateLock: join10(herdHome2, "update.lock"),
|
|
5672
|
+
status: join10(herdHome2, "supervisor.json"),
|
|
5673
|
+
terminals: join10(herdHome2, "terminals")
|
|
5434
5674
|
};
|
|
5435
5675
|
}
|
|
5436
|
-
var packageDir = (versionDir) =>
|
|
5676
|
+
var packageDir = (versionDir) => join10(versionDir, "node_modules", ...PACKAGE_NAME.split("/"));
|
|
5437
5677
|
function alive2(pid) {
|
|
5438
5678
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
5439
5679
|
try {
|
|
@@ -5444,11 +5684,11 @@ function alive2(pid) {
|
|
|
5444
5684
|
}
|
|
5445
5685
|
}
|
|
5446
5686
|
function takeLock(path, staleMs = 10 * 6e4) {
|
|
5447
|
-
|
|
5687
|
+
mkdirSync8(dirname7(path), { recursive: true });
|
|
5448
5688
|
if (existsSync6(path)) {
|
|
5449
5689
|
const holder2 = Number(readFileSync6(path, "utf8").trim());
|
|
5450
5690
|
const old = Date.now() - statSync5(path).mtimeMs > staleMs;
|
|
5451
|
-
if (old || !alive2(holder2))
|
|
5691
|
+
if (old || !alive2(holder2)) rmSync7(path, { force: true });
|
|
5452
5692
|
}
|
|
5453
5693
|
try {
|
|
5454
5694
|
const fd = openSync2(path, "wx");
|
|
@@ -5457,7 +5697,7 @@ function takeLock(path, staleMs = 10 * 6e4) {
|
|
|
5457
5697
|
} catch {
|
|
5458
5698
|
return null;
|
|
5459
5699
|
}
|
|
5460
|
-
return () =>
|
|
5700
|
+
return () => rmSync7(path, { force: true });
|
|
5461
5701
|
}
|
|
5462
5702
|
function readStatus(paths) {
|
|
5463
5703
|
try {
|
|
@@ -5467,7 +5707,7 @@ function readStatus(paths) {
|
|
|
5467
5707
|
}
|
|
5468
5708
|
}
|
|
5469
5709
|
function writeStatus(paths, status2) {
|
|
5470
|
-
|
|
5710
|
+
mkdirSync8(paths.herdHome, { recursive: true, mode: 448 });
|
|
5471
5711
|
writeFileSync6(paths.status, `${JSON.stringify(status2, null, 2)}
|
|
5472
5712
|
`);
|
|
5473
5713
|
}
|
|
@@ -5490,7 +5730,7 @@ function writeRoot(paths, root) {
|
|
|
5490
5730
|
} catch {
|
|
5491
5731
|
config = {};
|
|
5492
5732
|
}
|
|
5493
|
-
|
|
5733
|
+
mkdirSync8(paths.herdHome, { recursive: true, mode: 448 });
|
|
5494
5734
|
writeFileSync6(paths.config, JSON.stringify({ ...config, root }, null, 2), { mode: 384 });
|
|
5495
5735
|
}
|
|
5496
5736
|
function insideWorkTree(folder) {
|
|
@@ -5583,10 +5823,10 @@ function pickInTerminal(choices, input = process.stdin, output2 = process.stdout
|
|
|
5583
5823
|
|
|
5584
5824
|
// src/friday/service.ts
|
|
5585
5825
|
init_define_HERD_PACKAGE();
|
|
5586
|
-
import { spawn as
|
|
5587
|
-
import { existsSync as existsSync7, mkdirSync as
|
|
5826
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
5827
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync9, openSync as openSync3, readFileSync as readFileSync7, rmSync as rmSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
5588
5828
|
import { userInfo } from "node:os";
|
|
5589
|
-
import { dirname as dirname8, join as
|
|
5829
|
+
import { dirname as dirname8, join as join11, win32 } from "node:path";
|
|
5590
5830
|
var systemdBooted = () => existsSync7("/run/systemd/system");
|
|
5591
5831
|
var LAUNCH_AGENT = "com.herdingchats.friday";
|
|
5592
5832
|
var SYSTEMD_UNIT = "herdingchats-friday.service";
|
|
@@ -5674,16 +5914,16 @@ WantedBy=default.target
|
|
|
5674
5914
|
`;
|
|
5675
5915
|
}
|
|
5676
5916
|
function makeLogFolder(context2) {
|
|
5677
|
-
|
|
5917
|
+
mkdirSync9(dirname8(context2.paths.supervisorLog), { recursive: true });
|
|
5678
5918
|
}
|
|
5679
5919
|
function writeIfChanged(path, text4) {
|
|
5680
5920
|
if (existsSync7(path) && readFileSync7(path, "utf8") === text4) return false;
|
|
5681
|
-
|
|
5921
|
+
mkdirSync9(dirname8(path), { recursive: true });
|
|
5682
5922
|
writeFileSync7(path, text4);
|
|
5683
5923
|
return true;
|
|
5684
5924
|
}
|
|
5685
5925
|
function launchd(context2) {
|
|
5686
|
-
const plist =
|
|
5926
|
+
const plist = join11(context2.home, "Library", "LaunchAgents", `${LAUNCH_AGENT}.plist`);
|
|
5687
5927
|
const domain = `gui/${userInfo().uid}`;
|
|
5688
5928
|
const target = `${domain}/${LAUNCH_AGENT}`;
|
|
5689
5929
|
const loaded = async () => (await context2.run("launchctl", ["print", target])).status === 0;
|
|
@@ -5707,12 +5947,12 @@ function launchd(context2) {
|
|
|
5707
5947
|
},
|
|
5708
5948
|
async remove() {
|
|
5709
5949
|
await context2.run("launchctl", ["bootout", target]);
|
|
5710
|
-
|
|
5950
|
+
rmSync8(plist, { force: true });
|
|
5711
5951
|
}
|
|
5712
5952
|
};
|
|
5713
5953
|
}
|
|
5714
5954
|
function systemd(context2) {
|
|
5715
|
-
const unit =
|
|
5955
|
+
const unit = join11(context2.home, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
5716
5956
|
const systemctl = async (...args) => {
|
|
5717
5957
|
const ran = await context2.run("systemctl", ["--user", ...args]);
|
|
5718
5958
|
if (ran.status !== 0)
|
|
@@ -5734,15 +5974,15 @@ function systemd(context2) {
|
|
|
5734
5974
|
},
|
|
5735
5975
|
async remove() {
|
|
5736
5976
|
await context2.run("systemctl", ["--user", "disable", "--now", SYSTEMD_UNIT]);
|
|
5737
|
-
|
|
5977
|
+
rmSync8(unit, { force: true });
|
|
5738
5978
|
}
|
|
5739
5979
|
};
|
|
5740
5980
|
}
|
|
5741
5981
|
function spawnInBackground(command, env, log) {
|
|
5742
|
-
|
|
5982
|
+
mkdirSync9(dirname8(log), { recursive: true });
|
|
5743
5983
|
const out = openSync3(log, "a");
|
|
5744
5984
|
const [program = process.execPath, ...args] = command;
|
|
5745
|
-
|
|
5985
|
+
spawn5(program, args, {
|
|
5746
5986
|
detached: true,
|
|
5747
5987
|
// On Windows, a detached process otherwise opens a console window of its own.
|
|
5748
5988
|
windowsHide: true,
|
|
@@ -5794,35 +6034,35 @@ function serviceFor(kind, context2) {
|
|
|
5794
6034
|
// src/friday/start.ts
|
|
5795
6035
|
init_define_HERD_PACKAGE();
|
|
5796
6036
|
init_src();
|
|
5797
|
-
import { mkdirSync as
|
|
5798
|
-
import { join as
|
|
6037
|
+
import { mkdirSync as mkdirSync11, realpathSync as realpathSync4 } from "node:fs";
|
|
6038
|
+
import { join as join15, resolve as resolve3 } from "node:path";
|
|
5799
6039
|
|
|
5800
6040
|
// src/package-manager.ts
|
|
5801
6041
|
init_define_HERD_PACKAGE();
|
|
5802
6042
|
import { existsSync as existsSync9 } from "node:fs";
|
|
5803
6043
|
import { homedir as homedir4 } from "node:os";
|
|
5804
|
-
import { basename as basename3, delimiter, dirname as dirname9, isAbsolute as isAbsolute2, join as
|
|
6044
|
+
import { basename as basename3, delimiter, dirname as dirname9, isAbsolute as isAbsolute2, join as join13 } from "node:path";
|
|
5805
6045
|
|
|
5806
6046
|
// src/friday/versions.ts
|
|
5807
6047
|
init_define_HERD_PACKAGE();
|
|
5808
|
-
import { spawn as
|
|
6048
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
5809
6049
|
import {
|
|
5810
6050
|
existsSync as existsSync8,
|
|
5811
|
-
mkdirSync as
|
|
5812
|
-
readdirSync as
|
|
6051
|
+
mkdirSync as mkdirSync10,
|
|
6052
|
+
readdirSync as readdirSync4,
|
|
5813
6053
|
readFileSync as readFileSync8,
|
|
5814
6054
|
readlinkSync,
|
|
5815
6055
|
realpathSync as realpathSync3,
|
|
5816
6056
|
renameSync as renameSync3,
|
|
5817
|
-
rmSync as
|
|
6057
|
+
rmSync as rmSync9,
|
|
5818
6058
|
symlinkSync,
|
|
5819
6059
|
writeFileSync as writeFileSync8
|
|
5820
6060
|
} from "node:fs";
|
|
5821
|
-
import { basename as basename2, join as
|
|
6061
|
+
import { basename as basename2, join as join12 } from "node:path";
|
|
5822
6062
|
var exec = (command, args, options = {}) => new Promise((resolve8) => {
|
|
5823
6063
|
let stdout = "";
|
|
5824
6064
|
let stderr = "";
|
|
5825
|
-
const child =
|
|
6065
|
+
const child = spawn6(command, args, {
|
|
5826
6066
|
cwd: options.cwd,
|
|
5827
6067
|
env: options.env ?? process.env,
|
|
5828
6068
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -5848,9 +6088,9 @@ function currentVersion(paths) {
|
|
|
5848
6088
|
}
|
|
5849
6089
|
function installedVersions(paths) {
|
|
5850
6090
|
if (!existsSync8(paths.versions)) return [];
|
|
5851
|
-
return
|
|
6091
|
+
return readdirSync4(paths.versions).filter((name) => VERSION.test(name)).sort(compareVersions);
|
|
5852
6092
|
}
|
|
5853
|
-
var binOf = (versionDir, bin) =>
|
|
6093
|
+
var binOf = (versionDir, bin) => join12(packageDir(versionDir), `${bin}.mjs`);
|
|
5854
6094
|
async function versionRuns(versionDir, version, run4 = exec) {
|
|
5855
6095
|
const ran = await run4(process.execPath, [binOf(versionDir, "herd"), "--version"], {
|
|
5856
6096
|
timeoutMs: 3e4
|
|
@@ -5866,7 +6106,7 @@ function recordedIntegrity(dir) {
|
|
|
5866
6106
|
for (const file of LOCKFILES[manager]) {
|
|
5867
6107
|
let lock;
|
|
5868
6108
|
try {
|
|
5869
|
-
lock = readFileSync8(
|
|
6109
|
+
lock = readFileSync8(join12(dir, file), "utf8");
|
|
5870
6110
|
} catch {
|
|
5871
6111
|
continue;
|
|
5872
6112
|
}
|
|
@@ -5884,7 +6124,7 @@ function npmFreeEnv() {
|
|
|
5884
6124
|
async function installVersion(paths, version, { spec, integrity, run: run4 = exec, env = {}, log = () => {
|
|
5885
6125
|
}, manager } = {}) {
|
|
5886
6126
|
if (!VERSION.test(version)) return { ok: false, reason: `${version} isn't a version.` };
|
|
5887
|
-
const dir =
|
|
6127
|
+
const dir = join12(paths.versions, version);
|
|
5888
6128
|
if (existsSync8(dir)) {
|
|
5889
6129
|
const installed2 = recordedIntegrity(dir);
|
|
5890
6130
|
if (integrity && installed2 !== integrity)
|
|
@@ -5900,19 +6140,19 @@ async function installVersion(paths, version, { spec, integrity, run: run4 = exe
|
|
|
5900
6140
|
reason: "there's no package manager to install it with: herd looked for npm, pnpm and Yarn 1 beside Node, on the PATH and in their usual folders."
|
|
5901
6141
|
};
|
|
5902
6142
|
sweepStaging(paths);
|
|
5903
|
-
const named =
|
|
5904
|
-
|
|
5905
|
-
|
|
6143
|
+
const named = join12(paths.versions, `.staging-${version}-${process.pid}`);
|
|
6144
|
+
rmSync9(named, { recursive: true, force: true });
|
|
6145
|
+
mkdirSync10(named, { recursive: true });
|
|
5906
6146
|
const staging = realpathSync3(named);
|
|
5907
6147
|
writeFileSync8(
|
|
5908
|
-
|
|
6148
|
+
join12(staging, "package.json"),
|
|
5909
6149
|
`${JSON.stringify(allowScriptsManifest(["node-pty"]), null, 2)}
|
|
5910
6150
|
`
|
|
5911
6151
|
);
|
|
5912
6152
|
if (using.name === "pnpm")
|
|
5913
|
-
writeFileSync8(
|
|
6153
|
+
writeFileSync8(join12(staging, "pnpm-workspace.yaml"), allowScriptsWorkspace(["node-pty"]));
|
|
5914
6154
|
const fail = (reason) => {
|
|
5915
|
-
|
|
6155
|
+
rmSync9(staging, { recursive: true, force: true });
|
|
5916
6156
|
return { ok: false, reason };
|
|
5917
6157
|
};
|
|
5918
6158
|
log(`Installing ${PACKAGE_NAME}@${version} with ${using.name} ${using.version}.`);
|
|
@@ -5935,21 +6175,21 @@ async function installVersion(paths, version, { spec, integrity, run: run4 = exe
|
|
|
5935
6175
|
}
|
|
5936
6176
|
const broken = await versionRuns(staging, version, run4);
|
|
5937
6177
|
if (broken) return fail(broken);
|
|
5938
|
-
renameSync3(staging,
|
|
6178
|
+
renameSync3(staging, join12(realpathSync3(paths.versions), version));
|
|
5939
6179
|
return { ok: true, dir };
|
|
5940
6180
|
}
|
|
5941
6181
|
function sweepStaging(paths) {
|
|
5942
6182
|
if (!existsSync8(paths.versions)) return;
|
|
5943
|
-
for (const name of
|
|
6183
|
+
for (const name of readdirSync4(paths.versions)) {
|
|
5944
6184
|
const match = /^\.staging-.+-(\d+)$/.exec(name);
|
|
5945
6185
|
if (match && !alive2(Number(match[1])))
|
|
5946
|
-
|
|
6186
|
+
rmSync9(join12(paths.versions, name), { recursive: true, force: true });
|
|
5947
6187
|
}
|
|
5948
6188
|
}
|
|
5949
6189
|
function activate(paths, version) {
|
|
5950
6190
|
const next = `${paths.current}.next-${process.pid}`;
|
|
5951
|
-
|
|
5952
|
-
symlinkSync(
|
|
6191
|
+
rmSync9(next, { force: true });
|
|
6192
|
+
symlinkSync(join12("versions", version), next);
|
|
5953
6193
|
renameSync3(next, paths.current);
|
|
5954
6194
|
}
|
|
5955
6195
|
function pruneVersions(paths, { inUse = [], keep = 3 } = {}) {
|
|
@@ -5964,7 +6204,7 @@ function pruneVersions(paths, { inUse = [], keep = 3 } = {}) {
|
|
|
5964
6204
|
const removed = [];
|
|
5965
6205
|
for (const version of versions) {
|
|
5966
6206
|
if (kept.has(version)) continue;
|
|
5967
|
-
|
|
6207
|
+
rmSync9(join12(paths.versions, version), { recursive: true, force: true });
|
|
5968
6208
|
removed.push(version);
|
|
5969
6209
|
}
|
|
5970
6210
|
return removed;
|
|
@@ -5997,19 +6237,19 @@ var nameOf = (command) => {
|
|
|
5997
6237
|
return MANAGERS.includes(name) ? name : null;
|
|
5998
6238
|
};
|
|
5999
6239
|
function homesOf(name, env, home) {
|
|
6000
|
-
const common = [
|
|
6240
|
+
const common = [join13(home, ".volta", "bin"), join13(home, ".local", "bin")];
|
|
6001
6241
|
switch (name) {
|
|
6002
6242
|
case "pnpm":
|
|
6003
6243
|
return [
|
|
6004
6244
|
...env.PNPM_HOME ? [env.PNPM_HOME] : [],
|
|
6005
|
-
|
|
6006
|
-
|
|
6245
|
+
join13(home, "Library", "pnpm"),
|
|
6246
|
+
join13(home, ".local", "share", "pnpm"),
|
|
6007
6247
|
...common
|
|
6008
6248
|
];
|
|
6009
6249
|
case "bun":
|
|
6010
|
-
return [
|
|
6250
|
+
return [join13(env.BUN_INSTALL || join13(home, ".bun"), "bin"), ...common];
|
|
6011
6251
|
case "yarn":
|
|
6012
|
-
return [
|
|
6252
|
+
return [join13(home, ".yarn", "bin"), ...common];
|
|
6013
6253
|
case "npm":
|
|
6014
6254
|
return common;
|
|
6015
6255
|
}
|
|
@@ -6032,7 +6272,7 @@ function candidatePaths(name, {
|
|
|
6032
6272
|
const files = platform2 === "win32" ? [`${name}.cmd`, `${name}.exe`] : [name];
|
|
6033
6273
|
const seen = /* @__PURE__ */ new Set();
|
|
6034
6274
|
for (const dir of dirs.filter(Boolean)) {
|
|
6035
|
-
for (const file of files) seen.add(
|
|
6275
|
+
for (const file of files) seen.add(join13(dir, file));
|
|
6036
6276
|
}
|
|
6037
6277
|
return [...seen];
|
|
6038
6278
|
}
|
|
@@ -6192,22 +6432,22 @@ function frozenInstallArgs(name, { offline, yarnMajor }) {
|
|
|
6192
6432
|
|
|
6193
6433
|
// src/friday/legacy.ts
|
|
6194
6434
|
init_define_HERD_PACKAGE();
|
|
6195
|
-
import { existsSync as existsSync10, readdirSync as
|
|
6435
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, rmSync as rmSync10 } from "node:fs";
|
|
6196
6436
|
import { userInfo as userInfo2 } from "node:os";
|
|
6197
|
-
import { join as
|
|
6437
|
+
import { join as join14 } from "node:path";
|
|
6198
6438
|
var OLD_LAUNCH_AGENT = "com.herdingchats.herd";
|
|
6199
6439
|
var OLD_APPS = ["herd-daemon", "herd-updater"];
|
|
6200
6440
|
function oldPm2s(paths) {
|
|
6201
|
-
const dir =
|
|
6441
|
+
const dir = join14(paths.herdHome, "daemon");
|
|
6202
6442
|
if (!existsSync10(dir)) return [];
|
|
6203
|
-
return
|
|
6443
|
+
return readdirSync5(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join14(dir, entry.name, "node_modules", "pm2", "bin", "pm2")).filter((pm2) => existsSync10(pm2));
|
|
6204
6444
|
}
|
|
6205
6445
|
async function retireOldDaemon(paths, { home, run: run4 }) {
|
|
6206
6446
|
const removed = [];
|
|
6207
|
-
const plist =
|
|
6447
|
+
const plist = join14(home, "Library", "LaunchAgents", `${OLD_LAUNCH_AGENT}.plist`);
|
|
6208
6448
|
if (existsSync10(plist)) {
|
|
6209
6449
|
await run4("launchctl", ["bootout", `gui/${userInfo2().uid}/${OLD_LAUNCH_AGENT}`]);
|
|
6210
|
-
|
|
6450
|
+
rmSync10(plist, { force: true });
|
|
6211
6451
|
removed.push(`the login item ${OLD_LAUNCH_AGENT}`);
|
|
6212
6452
|
}
|
|
6213
6453
|
for (const pm2 of oldPm2s(paths)) {
|
|
@@ -6283,7 +6523,7 @@ var real = (path) => {
|
|
|
6283
6523
|
}
|
|
6284
6524
|
};
|
|
6285
6525
|
function chooseRoot(cwd, home) {
|
|
6286
|
-
return real(cwd) === real(home) ?
|
|
6526
|
+
return real(cwd) === real(home) ? join15(home, "friday") : resolve3(cwd);
|
|
6287
6527
|
}
|
|
6288
6528
|
async function runStart(options) {
|
|
6289
6529
|
const { paths, run: run4 } = options;
|
|
@@ -6363,7 +6603,7 @@ async function runStart(options) {
|
|
|
6363
6603
|
await service.stop();
|
|
6364
6604
|
await stopped(options);
|
|
6365
6605
|
}
|
|
6366
|
-
|
|
6606
|
+
mkdirSync11(root, { recursive: true });
|
|
6367
6607
|
writeRoot(paths, root);
|
|
6368
6608
|
verbose2(`installing the ${service.kind} service`);
|
|
6369
6609
|
const changed = await service.install();
|
|
@@ -6459,11 +6699,11 @@ async function ensureInstalled(options, print) {
|
|
|
6459
6699
|
// src/friday/supervisor.ts
|
|
6460
6700
|
init_define_HERD_PACKAGE();
|
|
6461
6701
|
init_src();
|
|
6462
|
-
import { spawn as
|
|
6702
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
6463
6703
|
import {
|
|
6464
6704
|
closeSync as closeSync3,
|
|
6465
6705
|
existsSync as existsSync11,
|
|
6466
|
-
mkdirSync as
|
|
6706
|
+
mkdirSync as mkdirSync12,
|
|
6467
6707
|
openSync as openSync4,
|
|
6468
6708
|
readFileSync as readFileSync9,
|
|
6469
6709
|
realpathSync as realpathSync5,
|
|
@@ -6558,7 +6798,7 @@ var Supervisor = class {
|
|
|
6558
6798
|
spawnDaemon() {
|
|
6559
6799
|
if (this.stopping) return;
|
|
6560
6800
|
const { daemonLog, logs } = this.options.paths;
|
|
6561
|
-
|
|
6801
|
+
mkdirSync12(logs, { recursive: true });
|
|
6562
6802
|
try {
|
|
6563
6803
|
if (existsSync11(daemonLog) && statSync6(daemonLog).size > MAX_LOG_BYTES2)
|
|
6564
6804
|
renameSync4(daemonLog, `${daemonLog}.1`);
|
|
@@ -6567,7 +6807,7 @@ var Supervisor = class {
|
|
|
6567
6807
|
const out = openSync4(daemonLog, "a");
|
|
6568
6808
|
const [program = process.execPath, ...args] = this.daemonCommand();
|
|
6569
6809
|
const startedAt = Date.now();
|
|
6570
|
-
const child =
|
|
6810
|
+
const child = spawn7(program, args, { stdio: ["ignore", out, out], env: process.env });
|
|
6571
6811
|
closeSync3(out);
|
|
6572
6812
|
this.daemon = child;
|
|
6573
6813
|
this.status.daemonPid = child.pid ?? null;
|
|
@@ -6719,7 +6959,7 @@ Agents use the herd command, which comes with this one. herd setup shows what's
|
|
|
6719
6959
|
function defaults() {
|
|
6720
6960
|
const home = homedir5();
|
|
6721
6961
|
return {
|
|
6722
|
-
paths: fridayPaths(process.env.HERD_HOME ??
|
|
6962
|
+
paths: fridayPaths(process.env.HERD_HOME ?? join16(home, ".herd")),
|
|
6723
6963
|
home,
|
|
6724
6964
|
cwd: process.cwd(),
|
|
6725
6965
|
run: exec,
|
|
@@ -6850,8 +7090,8 @@ function agentsFlag(args) {
|
|
|
6850
7090
|
return parsed.shares;
|
|
6851
7091
|
}
|
|
6852
7092
|
function attachTo(paths, terminal) {
|
|
6853
|
-
const herd = existsSync12(binOf(paths.current, "herd")) ? binOf(paths.current, "herd") :
|
|
6854
|
-
const child =
|
|
7093
|
+
const herd = existsSync12(binOf(paths.current, "herd")) ? binOf(paths.current, "herd") : join16(dirname10(process.argv[1] ?? ""), "herd.mjs");
|
|
7094
|
+
const child = spawn8(process.execPath, [herd, "attach", terminal], {
|
|
6855
7095
|
stdio: "inherit",
|
|
6856
7096
|
env: { ...process.env, HERD_HOME: paths.herdHome }
|
|
6857
7097
|
});
|
|
@@ -6914,7 +7154,7 @@ function runStatus(deps) {
|
|
|
6914
7154
|
function readMissingFolder(herdHome2) {
|
|
6915
7155
|
try {
|
|
6916
7156
|
return folderUnavailableSchema.parse(
|
|
6917
|
-
JSON.parse(readFileSync10(
|
|
7157
|
+
JSON.parse(readFileSync10(join16(herdHome2, "folder-unavailable.json"), "utf8"))
|
|
6918
7158
|
);
|
|
6919
7159
|
} catch {
|
|
6920
7160
|
return null;
|
|
@@ -6935,7 +7175,7 @@ async function runLogs(args, deps) {
|
|
|
6935
7175
|
return 0;
|
|
6936
7176
|
}
|
|
6937
7177
|
if (follow) {
|
|
6938
|
-
const child =
|
|
7178
|
+
const child = spawn8("tail", ["-n", "50", "-F", ...files], { stdio: "inherit" });
|
|
6939
7179
|
return await new Promise((resolve8) => child.on("close", (code) => resolve8(code ?? 0)));
|
|
6940
7180
|
}
|
|
6941
7181
|
for (const file of files) {
|
|
@@ -6977,16 +7217,16 @@ init_define_HERD_PACKAGE();
|
|
|
6977
7217
|
|
|
6978
7218
|
// src/agent-sandbox.ts
|
|
6979
7219
|
init_define_HERD_PACKAGE();
|
|
6980
|
-
import { spawn as
|
|
6981
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
7220
|
+
import { spawn as spawn10 } from "node:child_process";
|
|
7221
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync13, readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
6982
7222
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
6983
|
-
import { dirname as dirname12, isAbsolute as isAbsolute3, join as
|
|
7223
|
+
import { dirname as dirname12, isAbsolute as isAbsolute3, join as join18, resolve as resolve4 } from "node:path";
|
|
6984
7224
|
|
|
6985
7225
|
// src/build-sandbox.ts
|
|
6986
7226
|
init_define_HERD_PACKAGE();
|
|
6987
|
-
import { spawn as
|
|
7227
|
+
import { spawn as spawn9 } from "node:child_process";
|
|
6988
7228
|
import { appendFileSync, readFileSync as readFileSync11 } from "node:fs";
|
|
6989
|
-
import { dirname as dirname11, join as
|
|
7229
|
+
import { dirname as dirname11, join as join17 } from "node:path";
|
|
6990
7230
|
var DEPLOY_HOSTS = ["api.cloudflare.com", "api.github.com", "registry.npmjs.org"];
|
|
6991
7231
|
function buildPolicy(input) {
|
|
6992
7232
|
const platform2 = input.platform ?? process.platform;
|
|
@@ -7021,7 +7261,7 @@ var shellQuote = (args) => args.map((arg) => `'${arg.replace(/'/g, "'\\''")}'`).
|
|
|
7021
7261
|
var BLOCKED_LOG = "blocked.log";
|
|
7022
7262
|
async function runSandboxed(args, {
|
|
7023
7263
|
usage = "Usage: herd build-sandbox <policy> -- <command\u2026>",
|
|
7024
|
-
blockedLog = (_policyPath) =>
|
|
7264
|
+
blockedLog = (_policyPath) => join17(process.env.HOME ?? ".", BLOCKED_LOG)
|
|
7025
7265
|
} = {}) {
|
|
7026
7266
|
const { SandboxManager } = await import("@anthropic-ai/sandbox-runtime");
|
|
7027
7267
|
const [policyPath, separator, ...command] = args;
|
|
@@ -7049,7 +7289,7 @@ async function runSandboxed(args, {
|
|
|
7049
7289
|
}
|
|
7050
7290
|
});
|
|
7051
7291
|
const wrapped = await SandboxManager.wrapWithSandbox(shellQuote(command));
|
|
7052
|
-
const child =
|
|
7292
|
+
const child = spawn9(wrapped, { shell: true, stdio: "inherit" });
|
|
7053
7293
|
const forward = (signal2) => () => child.kill(signal2);
|
|
7054
7294
|
process.once("SIGTERM", forward("SIGTERM"));
|
|
7055
7295
|
process.once("SIGINT", forward("SIGINT"));
|
|
@@ -7190,13 +7430,13 @@ var AGENT_STATE = [
|
|
|
7190
7430
|
".config/gh"
|
|
7191
7431
|
];
|
|
7192
7432
|
function agentAccess(input) {
|
|
7193
|
-
const home = (path) =>
|
|
7433
|
+
const home = (path) => join18(input.ownerHome, path);
|
|
7194
7434
|
return {
|
|
7195
7435
|
read: [
|
|
7196
7436
|
...SYSTEM_READ,
|
|
7197
7437
|
input.nodePrefix,
|
|
7198
7438
|
...HOME_READ.map(home),
|
|
7199
|
-
...HERD_READ.map((path) =>
|
|
7439
|
+
...HERD_READ.map((path) => join18(input.herdHome, path))
|
|
7200
7440
|
],
|
|
7201
7441
|
write: [
|
|
7202
7442
|
.../* @__PURE__ */ new Set([
|
|
@@ -7214,7 +7454,7 @@ function seatbeltPolicy(input) {
|
|
|
7214
7454
|
const access = agentAccess(input);
|
|
7215
7455
|
return {
|
|
7216
7456
|
network: {
|
|
7217
|
-
allowUnixSockets: [
|
|
7457
|
+
allowUnixSockets: [join18(input.herdHome, "daemon.sock"), input.tmp, input.folder],
|
|
7218
7458
|
allowLocalBinding: true
|
|
7219
7459
|
},
|
|
7220
7460
|
filesystem: {
|
|
@@ -7229,7 +7469,7 @@ function seatbeltPolicy(input) {
|
|
|
7229
7469
|
function worktreeGitDirs(folder) {
|
|
7230
7470
|
const candidates2 = [folder];
|
|
7231
7471
|
try {
|
|
7232
|
-
for (const name of
|
|
7472
|
+
for (const name of readdirSync6(folder)) candidates2.push(join18(folder, name));
|
|
7233
7473
|
} catch {
|
|
7234
7474
|
return [];
|
|
7235
7475
|
}
|
|
@@ -7241,13 +7481,13 @@ function worktreeGitDirs(folder) {
|
|
|
7241
7481
|
return [...dirs];
|
|
7242
7482
|
}
|
|
7243
7483
|
function commonGitDir(worktree) {
|
|
7244
|
-
const dotGit =
|
|
7484
|
+
const dotGit = join18(worktree, ".git");
|
|
7245
7485
|
try {
|
|
7246
7486
|
if (!statSync7(dotGit).isFile()) return null;
|
|
7247
7487
|
const line2 = /^gitdir:\s*(.+)$/m.exec(readFileSync12(dotGit, "utf8"))?.[1]?.trim();
|
|
7248
7488
|
if (!line2) return null;
|
|
7249
7489
|
const gitDir = isAbsolute3(line2) ? line2 : resolve4(worktree, line2);
|
|
7250
|
-
const common =
|
|
7490
|
+
const common = join18(gitDir, "commondir");
|
|
7251
7491
|
if (!existsSync13(common)) return gitDir;
|
|
7252
7492
|
const relative3 = readFileSync12(common, "utf8").trim();
|
|
7253
7493
|
return isAbsolute3(relative3) ? relative3 : resolve4(gitDir, relative3);
|
|
@@ -7255,10 +7495,10 @@ function commonGitDir(worktree) {
|
|
|
7255
7495
|
return null;
|
|
7256
7496
|
}
|
|
7257
7497
|
}
|
|
7258
|
-
var policyPathFor = (conversationId) =>
|
|
7498
|
+
var policyPathFor = (conversationId) => join18(herdHome(), "sandbox", `${conversationId}.json`);
|
|
7259
7499
|
function writeAgentPolicy(conversationId, paths, platform2 = process.platform) {
|
|
7260
7500
|
const path = policyPathFor(conversationId);
|
|
7261
|
-
|
|
7501
|
+
mkdirSync13(dirname12(path), { recursive: true, mode: 448 });
|
|
7262
7502
|
const input = {
|
|
7263
7503
|
ownerHome: homedir6(),
|
|
7264
7504
|
herdHome: herdHome(),
|
|
@@ -7273,7 +7513,7 @@ function writeAgentPolicy(conversationId, paths, platform2 = process.platform) {
|
|
|
7273
7513
|
}
|
|
7274
7514
|
var confinementOn = (env = process.env) => env.HERD_CONFINE === "on";
|
|
7275
7515
|
function runLandlocked(access, command) {
|
|
7276
|
-
const child =
|
|
7516
|
+
const child = spawn10(
|
|
7277
7517
|
"python3",
|
|
7278
7518
|
["-I", "-c", LANDLOCK_LAUNCHER, JSON.stringify(access), ...command],
|
|
7279
7519
|
{ stdio: "inherit" }
|
|
@@ -7779,8 +8019,8 @@ init_define_HERD_PACKAGE();
|
|
|
7779
8019
|
// src/build-runner-key.ts
|
|
7780
8020
|
init_define_HERD_PACKAGE();
|
|
7781
8021
|
init_src();
|
|
7782
|
-
import { chmodSync as chmodSync3, existsSync as existsSync14, mkdirSync as
|
|
7783
|
-
import { join as
|
|
8022
|
+
import { chmodSync as chmodSync3, existsSync as existsSync14, mkdirSync as mkdirSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
|
|
8023
|
+
import { join as join19 } from "node:path";
|
|
7784
8024
|
import { z as z24 } from "zod";
|
|
7785
8025
|
var BUILDS_URL = "https://builds.herdingchats.com";
|
|
7786
8026
|
var runnerKeySchema = z24.object({
|
|
@@ -7788,7 +8028,7 @@ var runnerKeySchema = z24.object({
|
|
|
7788
8028
|
key: z24.string().min(20),
|
|
7789
8029
|
url: z24.string().url()
|
|
7790
8030
|
});
|
|
7791
|
-
var runnerKeyPath = () =>
|
|
8031
|
+
var runnerKeyPath = () => join19(herdHome(), "build-runner.json");
|
|
7792
8032
|
function readRunnerKey() {
|
|
7793
8033
|
const path = runnerKeyPath();
|
|
7794
8034
|
if (!existsSync14(path)) return null;
|
|
@@ -7801,7 +8041,7 @@ function readRunnerKey() {
|
|
|
7801
8041
|
}
|
|
7802
8042
|
}
|
|
7803
8043
|
function saveRunnerKey(key) {
|
|
7804
|
-
|
|
8044
|
+
mkdirSync14(herdHome(), { recursive: true, mode: 448 });
|
|
7805
8045
|
const path = runnerKeyPath();
|
|
7806
8046
|
writeFileSync10(path, JSON.stringify(runnerKeySchema.parse(key), null, 2), { mode: 384 });
|
|
7807
8047
|
chmodSync3(path, 384);
|
|
@@ -7969,8 +8209,8 @@ function formatSpeed(report) {
|
|
|
7969
8209
|
|
|
7970
8210
|
// src/builds-admin.ts
|
|
7971
8211
|
init_define_HERD_PACKAGE();
|
|
7972
|
-
import { mkdirSync as
|
|
7973
|
-
import { join as
|
|
8212
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync15, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
|
|
8213
|
+
import { join as join20 } from "node:path";
|
|
7974
8214
|
var NOT_ADMIN = "builds are only for the admin's machines";
|
|
7975
8215
|
async function isAdmin(fetch2) {
|
|
7976
8216
|
const response = await fetch2("/api/people/me").catch(() => null);
|
|
@@ -7978,13 +8218,13 @@ async function isAdmin(fetch2) {
|
|
|
7978
8218
|
const body = await response.json().catch(() => ({}));
|
|
7979
8219
|
return body.admin === true;
|
|
7980
8220
|
}
|
|
7981
|
-
var refusalPath = () =>
|
|
8221
|
+
var refusalPath = () => join20(herdHome(), "builds", "refused.json");
|
|
7982
8222
|
function writeBuildsRefusal(reason) {
|
|
7983
8223
|
if (!reason) {
|
|
7984
|
-
|
|
8224
|
+
rmSync11(refusalPath(), { force: true });
|
|
7985
8225
|
return;
|
|
7986
8226
|
}
|
|
7987
|
-
|
|
8227
|
+
mkdirSync15(join20(herdHome(), "builds"), { recursive: true });
|
|
7988
8228
|
writeFileSync11(refusalPath(), JSON.stringify({ reason }));
|
|
7989
8229
|
}
|
|
7990
8230
|
function readBuildsRefusal() {
|
|
@@ -8258,12 +8498,12 @@ function readClaudeScreen(raw) {
|
|
|
8258
8498
|
// src/worktrees.ts
|
|
8259
8499
|
init_define_HERD_PACKAGE();
|
|
8260
8500
|
import { existsSync as existsSync15, realpathSync as realpathSync6 } from "node:fs";
|
|
8261
|
-
import { isAbsolute as isAbsolute4, join as
|
|
8262
|
-
var worktreesRoot = () =>
|
|
8501
|
+
import { isAbsolute as isAbsolute4, join as join21, relative } from "node:path";
|
|
8502
|
+
var worktreesRoot = () => join21(herdHome(), "worktrees");
|
|
8263
8503
|
var WORD_ID = /^[a-z]+-[a-z]+-[a-z]+$/;
|
|
8264
8504
|
var conversationPrefix = (conversationId) => WORD_ID.test(conversationId) ? conversationId : conversationId.replace(/[^A-Za-z0-9]/g, "").slice(0, 12).toLowerCase();
|
|
8265
8505
|
var conversationBranch = (conversationId) => `herd/${conversationPrefix(conversationId)}`;
|
|
8266
|
-
var conversationFolder = (convos, conversationId) =>
|
|
8506
|
+
var conversationFolder = (convos, conversationId) => join21(convos, conversationPrefix(conversationId));
|
|
8267
8507
|
function folderForConversation(root, conversationId) {
|
|
8268
8508
|
const earlier = conversationFolder(legacyConvosDir(root), conversationId);
|
|
8269
8509
|
return existsSync15(earlier) ? earlier : conversationFolder(convosHome(root), conversationId);
|
|
@@ -8310,7 +8550,7 @@ function withStartKey(command, startKey, confine) {
|
|
|
8310
8550
|
return startKey ? `HERD_START=${startKey} ${line2}` : line2;
|
|
8311
8551
|
}
|
|
8312
8552
|
var DEFAULT_TIMING = { settleMs: 800, pollMs: 150, startMs: 6e4, confirmMs: 3e3 };
|
|
8313
|
-
var
|
|
8553
|
+
var sleep4 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
|
|
8314
8554
|
var EXIT_WAIT_MS = 5e3;
|
|
8315
8555
|
var AgentDriver = class {
|
|
8316
8556
|
constructor(terminals, timing = {}) {
|
|
@@ -8338,7 +8578,7 @@ var AgentDriver = class {
|
|
|
8338
8578
|
const reading = await this.read(name);
|
|
8339
8579
|
if (check2(reading)) return reading;
|
|
8340
8580
|
if (Date.now() > end) return null;
|
|
8341
|
-
await
|
|
8581
|
+
await sleep4(this.timing.pollMs);
|
|
8342
8582
|
}
|
|
8343
8583
|
}
|
|
8344
8584
|
/** Waits for a terminal's output to stop changing, as a shell does once it's ready. */
|
|
@@ -8354,7 +8594,7 @@ var AgentDriver = class {
|
|
|
8354
8594
|
} else if (Date.now() - stableSince >= this.timing.settleMs) {
|
|
8355
8595
|
return;
|
|
8356
8596
|
}
|
|
8357
|
-
await
|
|
8597
|
+
await sleep4(this.timing.pollMs);
|
|
8358
8598
|
}
|
|
8359
8599
|
}
|
|
8360
8600
|
/**
|
|
@@ -8394,7 +8634,7 @@ var AgentDriver = class {
|
|
|
8394
8634
|
for (let attempt = 0; attempt < 5; attempt++) {
|
|
8395
8635
|
if ((await this.read(name)).trustPrompt?.yesHighlighted) break;
|
|
8396
8636
|
await this.terminals.press(name, "down");
|
|
8397
|
-
await
|
|
8637
|
+
await sleep4(this.timing.pollMs * 2);
|
|
8398
8638
|
}
|
|
8399
8639
|
if (!(await this.read(name)).trustPrompt?.yesHighlighted) {
|
|
8400
8640
|
throw new HerdError(
|
|
@@ -8472,7 +8712,7 @@ var AgentDriver = class {
|
|
|
8472
8712
|
}
|
|
8473
8713
|
});
|
|
8474
8714
|
});
|
|
8475
|
-
const confirmed = () => Promise.race([hooked,
|
|
8715
|
+
const confirmed = () => Promise.race([hooked, sleep4(this.timing.confirmMs)]);
|
|
8476
8716
|
try {
|
|
8477
8717
|
if (!leftover) await this.terminals.type(name, text4);
|
|
8478
8718
|
const head = sent.slice(0, 40);
|
|
@@ -8576,9 +8816,9 @@ function readAgyScreen(raw) {
|
|
|
8576
8816
|
|
|
8577
8817
|
// src/agy-settings.ts
|
|
8578
8818
|
init_define_HERD_PACKAGE();
|
|
8579
|
-
import { lstatSync as lstatSync2, mkdirSync as
|
|
8580
|
-
import { join as
|
|
8581
|
-
var AGY_SETTINGS_FILE =
|
|
8819
|
+
import { lstatSync as lstatSync2, mkdirSync as mkdirSync16, realpathSync as realpathSync7, writeFileSync as writeFileSync12 } from "node:fs";
|
|
8820
|
+
import { join as join22, sep } from "node:path";
|
|
8821
|
+
var AGY_SETTINGS_FILE = join22(".herd", "agy-settings.json");
|
|
8582
8822
|
var AGY_HOOK_EVENTS = ["SessionStart", "PreInvocation", "Stop"];
|
|
8583
8823
|
var agyHookCommand = (event2, herd) => `${herd} hook agy ${event2} || true`;
|
|
8584
8824
|
function agyHooks(herd) {
|
|
@@ -8595,11 +8835,11 @@ function agySettings(herd) {
|
|
|
8595
8835
|
return { statusLine: { type: "command", command: `${herd} statusline agy` } };
|
|
8596
8836
|
}
|
|
8597
8837
|
function ownFolder(worktree, name) {
|
|
8598
|
-
const folder =
|
|
8838
|
+
const folder = join22(worktree, name);
|
|
8599
8839
|
if (lstatSync2(folder, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
8600
8840
|
throw new Error(`${name} in the worktree is a link`);
|
|
8601
8841
|
}
|
|
8602
|
-
|
|
8842
|
+
mkdirSync16(folder, { recursive: true });
|
|
8603
8843
|
if (!realpathSync7(folder).startsWith(realpathSync7(worktree) + sep)) {
|
|
8604
8844
|
throw new Error(`${name} leads outside the worktree`);
|
|
8605
8845
|
}
|
|
@@ -8607,13 +8847,13 @@ function ownFolder(worktree, name) {
|
|
|
8607
8847
|
}
|
|
8608
8848
|
function writeAgyFiles(worktree, herd) {
|
|
8609
8849
|
const agents = ownFolder(worktree, ".agents");
|
|
8610
|
-
writeFileSync12(
|
|
8850
|
+
writeFileSync12(join22(agents, "hooks.json"), `${JSON.stringify(agyHooks(herd), null, 2)}
|
|
8611
8851
|
`);
|
|
8612
|
-
writeFileSync12(
|
|
8852
|
+
writeFileSync12(join22(agents, ".gitignore"), "*\n");
|
|
8613
8853
|
const herdFolder = ownFolder(worktree, ".herd");
|
|
8614
|
-
writeFileSync12(
|
|
8854
|
+
writeFileSync12(join22(herdFolder, ".gitignore"), "*\n");
|
|
8615
8855
|
writeFileSync12(
|
|
8616
|
-
|
|
8856
|
+
join22(worktree, AGY_SETTINGS_FILE),
|
|
8617
8857
|
`${JSON.stringify(agySettings(herd), null, 2)}
|
|
8618
8858
|
`
|
|
8619
8859
|
);
|
|
@@ -8622,7 +8862,7 @@ function writeAgyFiles(worktree, herd) {
|
|
|
8622
8862
|
// src/agy-driver.ts
|
|
8623
8863
|
var AGY_FLAGS = `--dangerously-skip-permissions --settings ${AGY_SETTINGS_FILE}`;
|
|
8624
8864
|
var DEFAULT_TIMING2 = { settleMs: 800, pollMs: 150, startMs: 6e4, confirmMs: 3e3 };
|
|
8625
|
-
var
|
|
8865
|
+
var sleep5 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
|
|
8626
8866
|
var EXIT_WAIT_MS2 = 5e3;
|
|
8627
8867
|
var AgyDriver = class {
|
|
8628
8868
|
constructor(terminals, timing = {}) {
|
|
@@ -8645,7 +8885,7 @@ var AgyDriver = class {
|
|
|
8645
8885
|
const reading = await this.read(name);
|
|
8646
8886
|
if (check2(reading)) return reading;
|
|
8647
8887
|
if (Date.now() > end) return null;
|
|
8648
|
-
await
|
|
8888
|
+
await sleep5(this.timing.pollMs);
|
|
8649
8889
|
}
|
|
8650
8890
|
}
|
|
8651
8891
|
/** Waits for a terminal's output to stop changing, as a shell does once it's ready. */
|
|
@@ -8661,7 +8901,7 @@ var AgyDriver = class {
|
|
|
8661
8901
|
} else if (Date.now() - stableSince >= this.timing.settleMs) {
|
|
8662
8902
|
return;
|
|
8663
8903
|
}
|
|
8664
|
-
await
|
|
8904
|
+
await sleep5(this.timing.pollMs);
|
|
8665
8905
|
}
|
|
8666
8906
|
}
|
|
8667
8907
|
/**
|
|
@@ -8727,7 +8967,7 @@ var AgyDriver = class {
|
|
|
8727
8967
|
}
|
|
8728
8968
|
});
|
|
8729
8969
|
});
|
|
8730
|
-
const confirmed = () => Promise.race([reported,
|
|
8970
|
+
const confirmed = () => Promise.race([reported, sleep5(this.timing.confirmMs)]);
|
|
8731
8971
|
const head = sent.slice(0, 40);
|
|
8732
8972
|
const queuedOnScreen = async () => {
|
|
8733
8973
|
const screen = await this.terminals.screen(name);
|
|
@@ -8774,12 +9014,12 @@ var AgyDriver = class {
|
|
|
8774
9014
|
// src/bare-clones.ts
|
|
8775
9015
|
init_define_HERD_PACKAGE();
|
|
8776
9016
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
8777
|
-
import { existsSync as existsSync16, mkdirSync as
|
|
8778
|
-
import { dirname as dirname13, join as
|
|
9017
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync17, readdirSync as readdirSync7, renameSync as renameSync5, rmSync as rmSync12, statSync as statSync8 } from "node:fs";
|
|
9018
|
+
import { dirname as dirname13, join as join23 } from "node:path";
|
|
8779
9019
|
function bareClonePath(dir, remote) {
|
|
8780
9020
|
const key = repoKey(remote);
|
|
8781
9021
|
if (!key) throw new StoreError(`${remote} isn't a repo's URL.`);
|
|
8782
|
-
return
|
|
9022
|
+
return join23(dir, `${key}.git`);
|
|
8783
9023
|
}
|
|
8784
9024
|
async function ensureBareClone(store, remote, env) {
|
|
8785
9025
|
if (existsSync16(store)) {
|
|
@@ -8793,7 +9033,7 @@ async function ensureBareClone(store, remote, env) {
|
|
|
8793
9033
|
return { store, created: false, fetchFailed };
|
|
8794
9034
|
}
|
|
8795
9035
|
const building = `${store}.tmp-${process.pid}-${randomBytes5(4).toString("hex")}`;
|
|
8796
|
-
|
|
9036
|
+
mkdirSync17(dirname13(store), { recursive: true });
|
|
8797
9037
|
try {
|
|
8798
9038
|
await git(env, dirname13(store), "init", "--quiet", "--bare", building);
|
|
8799
9039
|
await git(env, building, "remote", "add", "origin", remote);
|
|
@@ -8808,7 +9048,7 @@ async function ensureBareClone(store, remote, env) {
|
|
|
8808
9048
|
await git(env, building, "remote", "set-head", "origin", "--auto").catch(() => "");
|
|
8809
9049
|
renameSync5(building, store);
|
|
8810
9050
|
} catch (error) {
|
|
8811
|
-
|
|
9051
|
+
rmSync12(building, { recursive: true, force: true });
|
|
8812
9052
|
throw error instanceof StoreError ? error : new StoreError(gitError(error));
|
|
8813
9053
|
}
|
|
8814
9054
|
return { store, created: true, fetchFailed: null };
|
|
@@ -8818,12 +9058,12 @@ function bareClones(dir) {
|
|
|
8818
9058
|
const walk = (at, depth) => {
|
|
8819
9059
|
let entries;
|
|
8820
9060
|
try {
|
|
8821
|
-
entries =
|
|
9061
|
+
entries = readdirSync7(at);
|
|
8822
9062
|
} catch {
|
|
8823
9063
|
return;
|
|
8824
9064
|
}
|
|
8825
9065
|
for (const name of entries.sort()) {
|
|
8826
|
-
const path =
|
|
9066
|
+
const path = join23(at, name);
|
|
8827
9067
|
if (name.endsWith(".git")) {
|
|
8828
9068
|
try {
|
|
8829
9069
|
const remote = gitSync(path, "config", "--get", "remote.origin.url");
|
|
@@ -9001,28 +9241,28 @@ var BuildConnection = class {
|
|
|
9001
9241
|
|
|
9002
9242
|
// src/builds.ts
|
|
9003
9243
|
init_define_HERD_PACKAGE();
|
|
9004
|
-
import { execFile as execFile2, execFileSync as execFileSync5, spawn as
|
|
9244
|
+
import { execFile as execFile2, execFileSync as execFileSync5, spawn as spawn11 } from "node:child_process";
|
|
9005
9245
|
import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
|
|
9006
9246
|
import {
|
|
9007
9247
|
existsSync as existsSync17,
|
|
9008
|
-
mkdirSync as
|
|
9248
|
+
mkdirSync as mkdirSync19,
|
|
9009
9249
|
mkdtempSync,
|
|
9010
|
-
readdirSync as
|
|
9250
|
+
readdirSync as readdirSync9,
|
|
9011
9251
|
readFileSync as readFileSync17,
|
|
9012
9252
|
realpathSync as realpathSync8,
|
|
9013
|
-
rmSync as
|
|
9253
|
+
rmSync as rmSync14,
|
|
9014
9254
|
writeFileSync as writeFileSync14
|
|
9015
9255
|
} from "node:fs";
|
|
9016
9256
|
import { homedir as homedir8, tmpdir as tmpdir2 } from "node:os";
|
|
9017
|
-
import { join as
|
|
9257
|
+
import { join as join25 } from "node:path";
|
|
9018
9258
|
import { promisify as promisify2 } from "node:util";
|
|
9019
9259
|
|
|
9020
9260
|
// src/check-registry.ts
|
|
9021
9261
|
init_define_HERD_PACKAGE();
|
|
9022
|
-
import { mkdirSync as
|
|
9262
|
+
import { mkdirSync as mkdirSync18, readdirSync as readdirSync8, readFileSync as readFileSync16, rmSync as rmSync13, writeFileSync as writeFileSync13 } from "node:fs";
|
|
9023
9263
|
import { homedir as homedir7 } from "node:os";
|
|
9024
|
-
import { join as
|
|
9025
|
-
var checkRegistryDir = () => process.env.HERDINGCHATS_CHECK_RUNNING ??
|
|
9264
|
+
import { join as join24 } from "node:path";
|
|
9265
|
+
var checkRegistryDir = () => process.env.HERDINGCHATS_CHECK_RUNNING ?? join24(homedir7(), ".cache", "herdingchats", "check", "running");
|
|
9026
9266
|
var RESERVED_MB = 6 * 1024;
|
|
9027
9267
|
var MIN_MEMORY_MB = 1024;
|
|
9028
9268
|
var alive3 = (pid) => {
|
|
@@ -9034,15 +9274,15 @@ var alive3 = (pid) => {
|
|
|
9034
9274
|
}
|
|
9035
9275
|
};
|
|
9036
9276
|
function registerCheck(dir, pid, startedAt = Date.now()) {
|
|
9037
|
-
|
|
9038
|
-
const file =
|
|
9277
|
+
mkdirSync18(dir, { recursive: true });
|
|
9278
|
+
const file = join24(dir, `${pid}.json`);
|
|
9039
9279
|
writeFileSync13(file, JSON.stringify({ pid, startedAt }));
|
|
9040
|
-
return () =>
|
|
9280
|
+
return () => rmSync13(file, { force: true });
|
|
9041
9281
|
}
|
|
9042
9282
|
function runningChecks(dir, isAlive = alive3) {
|
|
9043
9283
|
let names = [];
|
|
9044
9284
|
try {
|
|
9045
|
-
names =
|
|
9285
|
+
names = readdirSync8(dir).filter((name) => /^\d+\.json$/.test(name));
|
|
9046
9286
|
} catch {
|
|
9047
9287
|
return 0;
|
|
9048
9288
|
}
|
|
@@ -9051,12 +9291,12 @@ function runningChecks(dir, isAlive = alive3) {
|
|
|
9051
9291
|
const pid = Number(name.slice(0, -".json".length));
|
|
9052
9292
|
let entry = null;
|
|
9053
9293
|
try {
|
|
9054
|
-
entry = JSON.parse(readFileSync16(
|
|
9294
|
+
entry = JSON.parse(readFileSync16(join24(dir, name), "utf8"));
|
|
9055
9295
|
} catch {
|
|
9056
9296
|
continue;
|
|
9057
9297
|
}
|
|
9058
9298
|
if (entry?.pid === pid && isAlive(pid)) count += 1;
|
|
9059
|
-
else
|
|
9299
|
+
else rmSync13(join24(dir, name), { force: true });
|
|
9060
9300
|
}
|
|
9061
9301
|
return count;
|
|
9062
9302
|
}
|
|
@@ -9064,9 +9304,9 @@ var memoryShareOf = (totalMb, running) => Math.max(MIN_MEMORY_MB, Math.floor((to
|
|
|
9064
9304
|
|
|
9065
9305
|
// src/builds.ts
|
|
9066
9306
|
init_self();
|
|
9067
|
-
var sandboxStatusPath = () =>
|
|
9307
|
+
var sandboxStatusPath = () => join25(herdHome(), "builds", "sandbox.json");
|
|
9068
9308
|
function writeSandboxStatus(status2, now = Date.now()) {
|
|
9069
|
-
|
|
9309
|
+
mkdirSync19(join25(herdHome(), "builds"), { recursive: true });
|
|
9070
9310
|
writeFileSync14(sandboxStatusPath(), JSON.stringify({ ...status2, at: now }));
|
|
9071
9311
|
}
|
|
9072
9312
|
function readSandboxStatus() {
|
|
@@ -9098,7 +9338,7 @@ var real3 = (path) => {
|
|
|
9098
9338
|
}
|
|
9099
9339
|
};
|
|
9100
9340
|
function worktreeOf(dir, store) {
|
|
9101
|
-
if (!existsSync17(
|
|
9341
|
+
if (!existsSync17(join25(dir, ".git"))) return false;
|
|
9102
9342
|
try {
|
|
9103
9343
|
return real3(git2(dir, "rev-parse", "--path-format=absolute", "--git-common-dir")) === real3(store);
|
|
9104
9344
|
} catch {
|
|
@@ -9110,11 +9350,11 @@ function cloneFolder(from, to) {
|
|
|
9110
9350
|
execFileSync5("cp", [...args, from, to]);
|
|
9111
9351
|
}
|
|
9112
9352
|
function moduleFolders(dir) {
|
|
9113
|
-
const found = existsSync17(
|
|
9114
|
-
for (const entry of
|
|
9353
|
+
const found = existsSync17(join25(dir, "node_modules")) ? ["node_modules"] : [];
|
|
9354
|
+
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
9115
9355
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
9116
|
-
if (existsSync17(
|
|
9117
|
-
found.push(
|
|
9356
|
+
if (existsSync17(join25(dir, entry.name, "node_modules")))
|
|
9357
|
+
found.push(join25(entry.name, "node_modules"));
|
|
9118
9358
|
}
|
|
9119
9359
|
}
|
|
9120
9360
|
return found;
|
|
@@ -9122,11 +9362,11 @@ function moduleFolders(dir) {
|
|
|
9122
9362
|
function checkoutManager(dir) {
|
|
9123
9363
|
let field;
|
|
9124
9364
|
try {
|
|
9125
|
-
const pkg = JSON.parse(readFileSync17(
|
|
9365
|
+
const pkg = JSON.parse(readFileSync17(join25(dir, "package.json"), "utf8"));
|
|
9126
9366
|
if (typeof pkg.packageManager === "string") field = pkg.packageManager;
|
|
9127
9367
|
} catch {
|
|
9128
9368
|
}
|
|
9129
|
-
const found = managerOfCheckout(field, (name) => existsSync17(
|
|
9369
|
+
const found = managerOfCheckout(field, (name) => existsSync17(join25(dir, name)));
|
|
9130
9370
|
if (!found?.lockfile)
|
|
9131
9371
|
throw new Error(
|
|
9132
9372
|
`${dir} has no lockfile to install node_modules from: herd looked for ${Object.values(LOCKFILES).flat().join(", ")}.`
|
|
@@ -9134,7 +9374,7 @@ function checkoutManager(dir) {
|
|
|
9134
9374
|
return { ...found, lockfile: found.lockfile };
|
|
9135
9375
|
}
|
|
9136
9376
|
function lockHash(dir) {
|
|
9137
|
-
const lock = readFileSync17(
|
|
9377
|
+
const lock = readFileSync17(join25(dir, checkoutManager(dir).lockfile));
|
|
9138
9378
|
const major = process.versions.node.split(".")[0];
|
|
9139
9379
|
return createHash3("sha256").update(lock).update(`${process.platform}-${process.arch}-${major}`).digest("hex").slice(0, 32);
|
|
9140
9380
|
}
|
|
@@ -9146,7 +9386,7 @@ var frozenInstall = (gitEnv) => async (dir) => {
|
|
|
9146
9386
|
throw new Error(`${dir} installs with ${name}, which isn't on this machine, or doesn't run.`);
|
|
9147
9387
|
const args = frozenInstallArgs(name, { offline: insideBuildSandbox(), yarnMajor: major });
|
|
9148
9388
|
await new Promise((resolve8, reject) => {
|
|
9149
|
-
const child =
|
|
9389
|
+
const child = spawn11(manager.command, args, {
|
|
9150
9390
|
cwd: dir,
|
|
9151
9391
|
env: { ...env, COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
|
|
9152
9392
|
stdio: "ignore"
|
|
@@ -9165,11 +9405,11 @@ var BuildRunner = class _BuildRunner {
|
|
|
9165
9405
|
options;
|
|
9166
9406
|
running = /* @__PURE__ */ new Map();
|
|
9167
9407
|
home() {
|
|
9168
|
-
return this.options.home ??
|
|
9408
|
+
return this.options.home ?? join25(herdHome(), "builds");
|
|
9169
9409
|
}
|
|
9170
9410
|
/** Where builds' own bare clones are, apart from the root's checkouts. */
|
|
9171
9411
|
clonesDir() {
|
|
9172
|
-
return
|
|
9412
|
+
return join25(this.home(), "clones");
|
|
9173
9413
|
}
|
|
9174
9414
|
/** Herd's bare clone of a build's repo, for builds, whether or not it's there yet. */
|
|
9175
9415
|
storeFor(build) {
|
|
@@ -9190,8 +9430,8 @@ var BuildRunner = class _BuildRunner {
|
|
|
9190
9430
|
const store = bareClonePath(this.clonesDir(), remote);
|
|
9191
9431
|
const env = this.options.gitEnv?.();
|
|
9192
9432
|
const name = (repoKey(build.repo) ?? build.repo).replace(/[^A-Za-z0-9._-]+/g, "_");
|
|
9193
|
-
const dir =
|
|
9194
|
-
|
|
9433
|
+
const dir = join25(this.home(), name, String(slot));
|
|
9434
|
+
mkdirSync19(join25(this.home(), name), { recursive: true });
|
|
9195
9435
|
await withStoreLock(
|
|
9196
9436
|
store,
|
|
9197
9437
|
async () => {
|
|
@@ -9205,7 +9445,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
9205
9445
|
await storeGit(env, store, "fetch", "--quiet", "origin", build.sha);
|
|
9206
9446
|
}
|
|
9207
9447
|
if (!worktreeOf(dir, store)) {
|
|
9208
|
-
|
|
9448
|
+
rmSync14(dir, { recursive: true, force: true });
|
|
9209
9449
|
await storeGit(env, store, "worktree", "prune");
|
|
9210
9450
|
await storeGit(
|
|
9211
9451
|
env,
|
|
@@ -9230,30 +9470,30 @@ var BuildRunner = class _BuildRunner {
|
|
|
9230
9470
|
/** Makes `node_modules` match the checkout's lockfile: kept, copied from the cache, or installed. */
|
|
9231
9471
|
async ensureModules(dir) {
|
|
9232
9472
|
const wanted = lockHash(dir);
|
|
9233
|
-
const marker =
|
|
9473
|
+
const marker = join25(dir, "node_modules", ".herd-lock");
|
|
9234
9474
|
if (existsSync17(marker) && readFileSync17(marker, "utf8") === wanted) return;
|
|
9235
|
-
const cache =
|
|
9475
|
+
const cache = join25(this.home(), "node_modules", wanted);
|
|
9236
9476
|
for (const folder of moduleFolders(dir))
|
|
9237
|
-
|
|
9238
|
-
if (existsSync17(
|
|
9239
|
-
for (const folder of readFileSync17(
|
|
9240
|
-
cloneFolder(
|
|
9477
|
+
rmSync14(join25(dir, folder), { recursive: true, force: true });
|
|
9478
|
+
if (existsSync17(join25(cache, ".complete"))) {
|
|
9479
|
+
for (const folder of readFileSync17(join25(cache, ".complete"), "utf8").split("\n").filter(Boolean)) {
|
|
9480
|
+
cloneFolder(join25(cache, folder), join25(dir, folder));
|
|
9241
9481
|
}
|
|
9242
9482
|
} else {
|
|
9243
9483
|
this.options.log?.(`Installing node_modules for lockfile ${wanted.slice(0, 8)}.`);
|
|
9244
9484
|
await (this.options.install ?? frozenInstall(this.options.gitEnv))(dir);
|
|
9245
|
-
const staging = mkdtempSync(
|
|
9485
|
+
const staging = mkdtempSync(join25(this.home(), "node_modules-"));
|
|
9246
9486
|
const folders = moduleFolders(dir);
|
|
9247
9487
|
for (const folder of folders) {
|
|
9248
|
-
|
|
9249
|
-
cloneFolder(
|
|
9488
|
+
mkdirSync19(join25(staging, folder, ".."), { recursive: true });
|
|
9489
|
+
cloneFolder(join25(dir, folder), join25(staging, folder));
|
|
9250
9490
|
}
|
|
9251
|
-
writeFileSync14(
|
|
9252
|
-
|
|
9253
|
-
|
|
9491
|
+
writeFileSync14(join25(staging, ".complete"), folders.join("\n"));
|
|
9492
|
+
mkdirSync19(join25(this.home(), "node_modules"), { recursive: true });
|
|
9493
|
+
rmSync14(cache, { recursive: true, force: true });
|
|
9254
9494
|
execFileSync5("mv", [staging, cache]);
|
|
9255
9495
|
}
|
|
9256
|
-
|
|
9496
|
+
mkdirSync19(join25(dir, "node_modules"), { recursive: true });
|
|
9257
9497
|
writeFileSync14(marker, wanted);
|
|
9258
9498
|
}
|
|
9259
9499
|
/**
|
|
@@ -9264,7 +9504,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
9264
9504
|
const env = {
|
|
9265
9505
|
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
|
9266
9506
|
HOME: home,
|
|
9267
|
-
TMPDIR:
|
|
9507
|
+
TMPDIR: join25(home, "tmp"),
|
|
9268
9508
|
LANG: process.env.LANG ?? "en_US.UTF-8",
|
|
9269
9509
|
HERD_BUILD_ID: build.id,
|
|
9270
9510
|
HERD_BUILD_SECRET: build.secret,
|
|
@@ -9276,7 +9516,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
9276
9516
|
// The sandbox's proxy is the only way out, and Node's own requests use it only when asked.
|
|
9277
9517
|
NODE_USE_ENV_PROXY: "1",
|
|
9278
9518
|
// Where `srt` points TMPDIR inside the sandbox: the build's own temporary folder.
|
|
9279
|
-
CLAUDE_CODE_TMPDIR:
|
|
9519
|
+
CLAUDE_CODE_TMPDIR: join25(home, "tmp")
|
|
9280
9520
|
};
|
|
9281
9521
|
return env;
|
|
9282
9522
|
}
|
|
@@ -9287,8 +9527,8 @@ var BuildRunner = class _BuildRunner {
|
|
|
9287
9527
|
/** Runs `scripts/build.mjs` from the build's own commit, in a process group of its own. */
|
|
9288
9528
|
async run(build, slot) {
|
|
9289
9529
|
const dir = await this.prepare(build, slot);
|
|
9290
|
-
const home = mkdtempSync(
|
|
9291
|
-
|
|
9530
|
+
const home = mkdtempSync(join25(this.options.scratch ?? scratchRoot(), "hb-"));
|
|
9531
|
+
mkdirSync19(join25(home, "tmp"), { recursive: true });
|
|
9292
9532
|
const run4 = randomUUID4();
|
|
9293
9533
|
const env = {
|
|
9294
9534
|
..._BuildRunner.environment(build, home),
|
|
@@ -9298,7 +9538,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
9298
9538
|
env.HERD_CHECK_CPUS = String(Math.max(1, Math.floor(this.options.cpus())));
|
|
9299
9539
|
if (this.options.memoryMb)
|
|
9300
9540
|
env.HERD_CHECK_MEMORY_MB = String(Math.max(1, Math.floor(this.options.memoryMb())));
|
|
9301
|
-
let command = [process.execPath,
|
|
9541
|
+
let command = [process.execPath, join25(dir, "scripts", "build.mjs")];
|
|
9302
9542
|
let policyPath = null;
|
|
9303
9543
|
if (this.sandboxed()) {
|
|
9304
9544
|
const policy = buildPolicy({
|
|
@@ -9313,8 +9553,8 @@ var BuildRunner = class _BuildRunner {
|
|
|
9313
9553
|
main: build.main,
|
|
9314
9554
|
buildsUrl: build.url
|
|
9315
9555
|
});
|
|
9316
|
-
|
|
9317
|
-
policyPath =
|
|
9556
|
+
mkdirSync19(join25(this.home(), "policies"), { recursive: true });
|
|
9557
|
+
policyPath = join25(this.home(), "policies", `${build.id}.json`);
|
|
9318
9558
|
writeFileSync14(policyPath, JSON.stringify(policy), { mode: 384 });
|
|
9319
9559
|
env.HERD_BUILD_SANDBOX = describePolicy(policy);
|
|
9320
9560
|
command = [process.execPath, HERD_BIN, "build-sandbox", policyPath, "--", ...command];
|
|
@@ -9322,7 +9562,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
9322
9562
|
env.HERD_BUILD_SANDBOX = insideBuildSandbox() ? "Inside the sandbox of the build that started this one." : "";
|
|
9323
9563
|
}
|
|
9324
9564
|
const [executable, ...args] = command;
|
|
9325
|
-
const child =
|
|
9565
|
+
const child = spawn11(executable, args, { cwd: dir, env, stdio: "ignore", detached: true });
|
|
9326
9566
|
const done = new Promise((resolve8) => {
|
|
9327
9567
|
child.on("error", () => resolve8(1));
|
|
9328
9568
|
child.on("close", (code) => resolve8(code ?? 1));
|
|
@@ -9342,8 +9582,8 @@ var BuildRunner = class _BuildRunner {
|
|
|
9342
9582
|
unregister();
|
|
9343
9583
|
await this.stopBuildProcesses(run4, dir, entry.pid);
|
|
9344
9584
|
if (this.running.get(build.id) === entry) this.running.delete(build.id);
|
|
9345
|
-
|
|
9346
|
-
if (policyPath)
|
|
9585
|
+
rmSync14(home, { recursive: true, force: true });
|
|
9586
|
+
if (policyPath) rmSync14(policyPath, { force: true });
|
|
9347
9587
|
markEnded();
|
|
9348
9588
|
}
|
|
9349
9589
|
}
|
|
@@ -9363,7 +9603,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
9363
9603
|
* It goes only by where they run from, never by a build ID, since the daemon a build's own tests
|
|
9364
9604
|
* start must never stop the build it runs in.
|
|
9365
9605
|
*/
|
|
9366
|
-
static async sweep(home =
|
|
9606
|
+
static async sweep(home = join25(herdHome(), "builds"), list = listProcesses) {
|
|
9367
9607
|
const left = buildProcesses(list(), null, home);
|
|
9368
9608
|
await stopPids(left);
|
|
9369
9609
|
return left.length;
|
|
@@ -9375,37 +9615,37 @@ var BuildRunner = class _BuildRunner {
|
|
|
9375
9615
|
*/
|
|
9376
9616
|
static async probe(options = { buildsUrl: "" }) {
|
|
9377
9617
|
if (insideBuildSandbox()) return { ok: true };
|
|
9378
|
-
const herd = options.home ??
|
|
9379
|
-
const dir = mkdtempSync(
|
|
9380
|
-
const secret =
|
|
9618
|
+
const herd = options.home ?? join25(herdHome(), "builds");
|
|
9619
|
+
const dir = mkdtempSync(join25(scratchRoot(), "hp-"));
|
|
9620
|
+
const secret = join25(dir, "owner", "secret");
|
|
9381
9621
|
try {
|
|
9382
|
-
|
|
9383
|
-
for (const sub of ["work", "git", "home", "owner"])
|
|
9622
|
+
mkdirSync19(herd, { recursive: true });
|
|
9623
|
+
for (const sub of ["work", "git", "home", "owner"]) mkdirSync19(join25(dir, sub));
|
|
9384
9624
|
writeFileSync14(secret, "probe");
|
|
9385
9625
|
const policy = buildPolicy({
|
|
9386
|
-
ownerHome:
|
|
9387
|
-
root:
|
|
9388
|
-
convos:
|
|
9389
|
-
worktree:
|
|
9390
|
-
store:
|
|
9391
|
-
worktreeGitDir:
|
|
9392
|
-
home:
|
|
9626
|
+
ownerHome: join25(dir, "owner"),
|
|
9627
|
+
root: join25(dir, "owner"),
|
|
9628
|
+
convos: join25(dir, "owner"),
|
|
9629
|
+
worktree: join25(dir, "work"),
|
|
9630
|
+
store: join25(dir, "git"),
|
|
9631
|
+
worktreeGitDir: join25(dir, "git"),
|
|
9632
|
+
home: join25(dir, "home"),
|
|
9393
9633
|
nodePrefix: nodePrefix(),
|
|
9394
9634
|
main: false,
|
|
9395
9635
|
buildsUrl: options.buildsUrl || "https://builds.herdingchats.com"
|
|
9396
9636
|
});
|
|
9397
|
-
const policyPath =
|
|
9637
|
+
const policyPath = join25(herd, "probe-policy.json");
|
|
9398
9638
|
writeFileSync14(policyPath, JSON.stringify(policy), { mode: 384 });
|
|
9399
9639
|
const script = [
|
|
9400
9640
|
'const fs = require("node:fs");',
|
|
9401
9641
|
`try { fs.readFileSync(${JSON.stringify(secret)}); process.exit(10); } catch {}`,
|
|
9402
|
-
`fs.writeFileSync(${JSON.stringify(
|
|
9642
|
+
`fs.writeFileSync(${JSON.stringify(join25(dir, "home", "ok"))}, "ok");`
|
|
9403
9643
|
].join(" ");
|
|
9404
|
-
const child =
|
|
9644
|
+
const child = spawn11(
|
|
9405
9645
|
process.execPath,
|
|
9406
9646
|
[HERD_BIN, "build-sandbox", policyPath, "--", process.execPath, "-e", script],
|
|
9407
9647
|
{
|
|
9408
|
-
env: { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME:
|
|
9648
|
+
env: { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: join25(dir, "home") },
|
|
9409
9649
|
stdio: ["ignore", "ignore", "pipe"]
|
|
9410
9650
|
}
|
|
9411
9651
|
);
|
|
@@ -9419,7 +9659,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
9419
9659
|
clearTimeout(timer);
|
|
9420
9660
|
if (status2 === 10)
|
|
9421
9661
|
return { ok: false, reason: "the sandbox let a build read the owner's files" };
|
|
9422
|
-
if (status2 !== 0 || !existsSync17(
|
|
9662
|
+
if (status2 !== 0 || !existsSync17(join25(dir, "home", "ok"))) {
|
|
9423
9663
|
const why = stderr.trim().split("\n").at(-1) || `the probe exited with ${status2}`;
|
|
9424
9664
|
return { ok: false, reason: why.slice(0, 300) };
|
|
9425
9665
|
}
|
|
@@ -9427,7 +9667,7 @@ var BuildRunner = class _BuildRunner {
|
|
|
9427
9667
|
} catch (error) {
|
|
9428
9668
|
return { ok: false, reason: String(error).slice(0, 300) };
|
|
9429
9669
|
} finally {
|
|
9430
|
-
|
|
9670
|
+
rmSync14(dir, { recursive: true, force: true });
|
|
9431
9671
|
}
|
|
9432
9672
|
}
|
|
9433
9673
|
/** Stops a build's process group and every process it started, however it got away. */
|
|
@@ -9456,7 +9696,7 @@ function listProcesses() {
|
|
|
9456
9696
|
function listFromProc() {
|
|
9457
9697
|
let pids = [];
|
|
9458
9698
|
try {
|
|
9459
|
-
pids =
|
|
9699
|
+
pids = readdirSync9("/proc").filter((name) => /^\d+$/.test(name));
|
|
9460
9700
|
} catch {
|
|
9461
9701
|
return [];
|
|
9462
9702
|
}
|
|
@@ -9634,7 +9874,7 @@ var DEFAULT_TIMING3 = {
|
|
|
9634
9874
|
confirmMs: 8e3,
|
|
9635
9875
|
pasteMs: 600
|
|
9636
9876
|
};
|
|
9637
|
-
var
|
|
9877
|
+
var sleep6 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
|
|
9638
9878
|
var EXIT_WAIT_MS3 = 5e3;
|
|
9639
9879
|
var CodexDriver = class {
|
|
9640
9880
|
constructor(terminals, timing = {}) {
|
|
@@ -9657,7 +9897,7 @@ var CodexDriver = class {
|
|
|
9657
9897
|
const reading = await this.read(name);
|
|
9658
9898
|
if (check2(reading)) return reading;
|
|
9659
9899
|
if (Date.now() > end) return null;
|
|
9660
|
-
await
|
|
9900
|
+
await sleep6(this.timing.pollMs);
|
|
9661
9901
|
}
|
|
9662
9902
|
}
|
|
9663
9903
|
/** Waits for a terminal's output to stop changing, as a shell does once it's ready. */
|
|
@@ -9673,7 +9913,7 @@ var CodexDriver = class {
|
|
|
9673
9913
|
} else if (Date.now() - stableSince >= this.timing.settleMs) {
|
|
9674
9914
|
return;
|
|
9675
9915
|
}
|
|
9676
|
-
await
|
|
9916
|
+
await sleep6(this.timing.pollMs);
|
|
9677
9917
|
}
|
|
9678
9918
|
}
|
|
9679
9919
|
/**
|
|
@@ -9739,10 +9979,10 @@ var CodexDriver = class {
|
|
|
9739
9979
|
}
|
|
9740
9980
|
});
|
|
9741
9981
|
});
|
|
9742
|
-
const confirmed = () => Promise.race([reported,
|
|
9982
|
+
const confirmed = () => Promise.race([reported, sleep6(this.timing.confirmMs)]);
|
|
9743
9983
|
try {
|
|
9744
9984
|
if (!leftover) await this.terminals.type(name, text4);
|
|
9745
|
-
await
|
|
9985
|
+
await sleep6(this.timing.pasteMs);
|
|
9746
9986
|
const head = sent.slice(0, 40);
|
|
9747
9987
|
let inBox = true;
|
|
9748
9988
|
for (let attempt = 0; attempt <= SUBMIT_RETRIES && !taken; attempt++) {
|
|
@@ -9772,7 +10012,7 @@ var CodexDriver = class {
|
|
|
9772
10012
|
async exit(name) {
|
|
9773
10013
|
if ((await this.read(name)).program !== "codex") return;
|
|
9774
10014
|
await this.terminals.type(name, "/exit");
|
|
9775
|
-
await
|
|
10015
|
+
await sleep6(this.timing.pasteMs);
|
|
9776
10016
|
await this.terminals.press(name, "enter");
|
|
9777
10017
|
await this.until(name, (reading) => reading.program !== "codex", EXIT_WAIT_MS3);
|
|
9778
10018
|
}
|
|
@@ -9780,7 +10020,7 @@ var CodexDriver = class {
|
|
|
9780
10020
|
|
|
9781
10021
|
// src/daemon.ts
|
|
9782
10022
|
init_define_HERD_PACKAGE();
|
|
9783
|
-
import { join as
|
|
10023
|
+
import { join as join26 } from "node:path";
|
|
9784
10024
|
|
|
9785
10025
|
// src/dispatch-connection.ts
|
|
9786
10026
|
init_define_HERD_PACKAGE();
|
|
@@ -9895,7 +10135,7 @@ var DispatchConnection = class {
|
|
|
9895
10135
|
|
|
9896
10136
|
// src/daemon.ts
|
|
9897
10137
|
var CONFIG_POLL_MS = 2e3;
|
|
9898
|
-
var daemonSocketPath = () =>
|
|
10138
|
+
var daemonSocketPath = () => join26(herdHome(), "daemon.sock");
|
|
9899
10139
|
var Daemon = class {
|
|
9900
10140
|
constructor(options) {
|
|
9901
10141
|
this.options = options;
|
|
@@ -10108,7 +10348,7 @@ var Daemon = class {
|
|
|
10108
10348
|
|
|
10109
10349
|
// src/folders.ts
|
|
10110
10350
|
init_define_HERD_PACKAGE();
|
|
10111
|
-
import { existsSync as existsSync18, lstatSync as lstatSync3, readlinkSync as readlinkSync2, rmSync as
|
|
10351
|
+
import { existsSync as existsSync18, lstatSync as lstatSync3, readlinkSync as readlinkSync2, rmSync as rmSync15, writeFileSync as writeFileSync15 } from "node:fs";
|
|
10112
10352
|
import { dirname as dirname14, resolve as resolve5 } from "node:path";
|
|
10113
10353
|
var FOLDER_WAIT_MS = 2 * 60 * 1e3;
|
|
10114
10354
|
var FOLDER_CHECK_MS = 30 * 1e3;
|
|
@@ -10191,7 +10431,7 @@ var FolderWatch = class {
|
|
|
10191
10431
|
write() {
|
|
10192
10432
|
try {
|
|
10193
10433
|
if (this.current) writeFileSync15(this.options.statusPath, JSON.stringify(this.current));
|
|
10194
|
-
else
|
|
10434
|
+
else rmSync15(this.options.statusPath, { force: true });
|
|
10195
10435
|
} catch {
|
|
10196
10436
|
}
|
|
10197
10437
|
}
|
|
@@ -10200,7 +10440,7 @@ var FolderWatch = class {
|
|
|
10200
10440
|
// src/gh.ts
|
|
10201
10441
|
init_define_HERD_PACKAGE();
|
|
10202
10442
|
init_src();
|
|
10203
|
-
import { execFile as execFile4, spawn as
|
|
10443
|
+
import { execFile as execFile4, spawn as spawn12 } from "node:child_process";
|
|
10204
10444
|
import { promisify as promisify3 } from "node:util";
|
|
10205
10445
|
var GH_CHECK_MS = 10 * 60 * 1e3;
|
|
10206
10446
|
var GH_CODE_WAIT_MS = 30 * 1e3;
|
|
@@ -10271,7 +10511,7 @@ function readDeviceCode(output2) {
|
|
|
10271
10511
|
return code && url === GITHUB_DEVICE_URL ? { code, url: GITHUB_DEVICE_URL } : null;
|
|
10272
10512
|
}
|
|
10273
10513
|
async function signInGh(options) {
|
|
10274
|
-
const spawnGh = options.spawnGh ?? ((args) =>
|
|
10514
|
+
const spawnGh = options.spawnGh ?? ((args) => spawn12("gh", args, {
|
|
10275
10515
|
env: options.env?.() ?? process.env,
|
|
10276
10516
|
stdio: ["ignore", "pipe", "pipe"]
|
|
10277
10517
|
}));
|
|
@@ -10324,20 +10564,20 @@ import { execFileSync as execFileSync6 } from "node:child_process";
|
|
|
10324
10564
|
import {
|
|
10325
10565
|
appendFileSync as appendFileSync2,
|
|
10326
10566
|
existsSync as existsSync19,
|
|
10327
|
-
mkdirSync as
|
|
10567
|
+
mkdirSync as mkdirSync20,
|
|
10328
10568
|
readFileSync as readFileSync18,
|
|
10329
10569
|
renameSync as renameSync6,
|
|
10330
10570
|
writeFileSync as writeFileSync16
|
|
10331
10571
|
} from "node:fs";
|
|
10332
|
-
import { isAbsolute as isAbsolute5, join as
|
|
10333
|
-
var LOCAL_SETTINGS =
|
|
10572
|
+
import { isAbsolute as isAbsolute5, join as join27 } from "node:path";
|
|
10573
|
+
var LOCAL_SETTINGS = join27(".claude", "settings.local.json");
|
|
10334
10574
|
var git3 = (cwd, ...args) => execFileSync6("git", ["-C", cwd, ...args], {
|
|
10335
10575
|
encoding: "utf8",
|
|
10336
10576
|
stdio: ["ignore", "pipe", "pipe"]
|
|
10337
10577
|
}).trim();
|
|
10338
10578
|
var shellQuote2 = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
10339
10579
|
function writeWorktreeSettings(worktree, settings, ownCommand) {
|
|
10340
|
-
const path =
|
|
10580
|
+
const path = join27(worktree, LOCAL_SETTINGS);
|
|
10341
10581
|
let current = {};
|
|
10342
10582
|
if (existsSync19(path)) {
|
|
10343
10583
|
try {
|
|
@@ -10361,7 +10601,7 @@ function writeWorktreeSettings(worktree, settings, ownCommand) {
|
|
|
10361
10601
|
}
|
|
10362
10602
|
next.hooks = hooks;
|
|
10363
10603
|
}
|
|
10364
|
-
|
|
10604
|
+
mkdirSync20(join27(worktree, ".claude"), { recursive: true });
|
|
10365
10605
|
const temporary = `${path}.herd-${process.pid}`;
|
|
10366
10606
|
writeFileSync16(temporary, `${JSON.stringify(next, null, 2)}
|
|
10367
10607
|
`);
|
|
@@ -10381,12 +10621,12 @@ function ignoreLocalSettings(worktree) {
|
|
|
10381
10621
|
} catch {
|
|
10382
10622
|
return;
|
|
10383
10623
|
}
|
|
10384
|
-
const info =
|
|
10385
|
-
const exclude =
|
|
10624
|
+
const info = join27(isAbsolute5(common) ? common : join27(worktree, common), "info");
|
|
10625
|
+
const exclude = join27(info, "exclude");
|
|
10386
10626
|
const line2 = "/.claude/settings.local.json";
|
|
10387
10627
|
const existing = existsSync19(exclude) ? readFileSync18(exclude, "utf8") : "";
|
|
10388
10628
|
if (existing.split("\n").includes(line2)) return;
|
|
10389
|
-
|
|
10629
|
+
mkdirSync20(info, { recursive: true });
|
|
10390
10630
|
appendFileSync2(exclude, `${existing && !existing.endsWith("\n") ? "\n" : ""}${line2}
|
|
10391
10631
|
`);
|
|
10392
10632
|
}
|
|
@@ -10429,7 +10669,7 @@ function loginShellEnv() {
|
|
|
10429
10669
|
|
|
10430
10670
|
// src/machine-checks.ts
|
|
10431
10671
|
init_define_HERD_PACKAGE();
|
|
10432
|
-
import { existsSync as existsSync20, rmSync as
|
|
10672
|
+
import { existsSync as existsSync20, rmSync as rmSync16 } from "node:fs";
|
|
10433
10673
|
var REQUEST_POLL_MS = 2e3;
|
|
10434
10674
|
var MachineChecks = class {
|
|
10435
10675
|
constructor(options) {
|
|
@@ -10476,7 +10716,7 @@ var MachineChecks = class {
|
|
|
10476
10716
|
takeRequest() {
|
|
10477
10717
|
const path = activationRequestPath(this.options.herdHome);
|
|
10478
10718
|
if (!existsSync20(path)) return;
|
|
10479
|
-
|
|
10719
|
+
rmSync16(path, { force: true });
|
|
10480
10720
|
this.activateChosen(true);
|
|
10481
10721
|
}
|
|
10482
10722
|
start() {
|
|
@@ -10522,237 +10762,6 @@ var MachineChecks = class {
|
|
|
10522
10762
|
}
|
|
10523
10763
|
};
|
|
10524
10764
|
|
|
10525
|
-
// src/terminals.ts
|
|
10526
|
-
init_define_HERD_PACKAGE();
|
|
10527
|
-
import { spawn as spawn12 } from "node:child_process";
|
|
10528
|
-
import { mkdirSync as mkdirSync20, readdirSync as readdirSync9, rmSync as rmSync16 } from "node:fs";
|
|
10529
|
-
import { connect as connect2 } from "node:net";
|
|
10530
|
-
import { join as join27 } from "node:path";
|
|
10531
|
-
init_self();
|
|
10532
|
-
import {
|
|
10533
|
-
HOST_PROTOCOL,
|
|
10534
|
-
hostVersion
|
|
10535
|
-
} from "./terminal-host.mjs";
|
|
10536
|
-
var hostsDir = () => join27(herdHome(), "terminals");
|
|
10537
|
-
var legacyHostSocketPath = () => join27(herdHome(), "terminals.sock");
|
|
10538
|
-
var currentHostSocketPath = (version = hostVersion()) => join27(hostsDir(), `${version}.sock`);
|
|
10539
|
-
var speaksProtocol = (protocol) => protocol === HOST_PROTOCOL || protocol === HOST_PROTOCOL - 1;
|
|
10540
|
-
var sleep6 = (ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2));
|
|
10541
|
-
function askHost(path, request) {
|
|
10542
|
-
return new Promise((resolve8, reject) => {
|
|
10543
|
-
const socket = connect2(path);
|
|
10544
|
-
let data = "";
|
|
10545
|
-
socket.on("connect", () => socket.write(`${JSON.stringify(request)}
|
|
10546
|
-
`));
|
|
10547
|
-
socket.on("data", (chunk) => data += chunk);
|
|
10548
|
-
socket.on("end", () => {
|
|
10549
|
-
try {
|
|
10550
|
-
const reply = JSON.parse(data);
|
|
10551
|
-
if (reply.ok) resolve8(reply);
|
|
10552
|
-
else reject(new HerdError(reply.error ?? "The terminal host refused that."));
|
|
10553
|
-
} catch {
|
|
10554
|
-
reject(new HerdError("The terminal host's answer wasn't readable."));
|
|
10555
|
-
}
|
|
10556
|
-
});
|
|
10557
|
-
socket.on("error", reject);
|
|
10558
|
-
});
|
|
10559
|
-
}
|
|
10560
|
-
var isDead = (error) => {
|
|
10561
|
-
const code = error?.code;
|
|
10562
|
-
return code === "ECONNREFUSED" || code === "ENOENT";
|
|
10563
|
-
};
|
|
10564
|
-
var HostTerminals = class {
|
|
10565
|
-
hosts = [];
|
|
10566
|
-
/** Which host each terminal name was last found in. */
|
|
10567
|
-
where = /* @__PURE__ */ new Map();
|
|
10568
|
-
version;
|
|
10569
|
-
log;
|
|
10570
|
-
constructor(options = {}) {
|
|
10571
|
-
this.version = options.version ?? hostVersion();
|
|
10572
|
-
this.log = options.log ?? (() => {
|
|
10573
|
-
});
|
|
10574
|
-
}
|
|
10575
|
-
currentVersion() {
|
|
10576
|
-
return this.version;
|
|
10577
|
-
}
|
|
10578
|
-
unreachable(version) {
|
|
10579
|
-
return this.hosts.some((host) => host.hello.version === version && !host.speaks);
|
|
10580
|
-
}
|
|
10581
|
-
/** Every host that answers, with sockets nothing answers on removed. */
|
|
10582
|
-
async discover() {
|
|
10583
|
-
const dir = hostsDir();
|
|
10584
|
-
let names = [];
|
|
10585
|
-
try {
|
|
10586
|
-
names = readdirSync9(dir).filter((name) => name.endsWith(".sock"));
|
|
10587
|
-
} catch {
|
|
10588
|
-
}
|
|
10589
|
-
const paths = [legacyHostSocketPath(), ...names.map((name) => join27(dir, name))];
|
|
10590
|
-
const found = [];
|
|
10591
|
-
for (const path of paths) {
|
|
10592
|
-
try {
|
|
10593
|
-
const hello = await askHost(path, { op: "hello" });
|
|
10594
|
-
found.push({
|
|
10595
|
-
path,
|
|
10596
|
-
hello,
|
|
10597
|
-
current: hello.version === this.version && speaksProtocol(hello.protocol),
|
|
10598
|
-
speaks: speaksProtocol(hello.protocol)
|
|
10599
|
-
});
|
|
10600
|
-
} catch (error) {
|
|
10601
|
-
if (isDead(error) && path !== legacyHostSocketPath()) rmSync16(path, { force: true });
|
|
10602
|
-
}
|
|
10603
|
-
}
|
|
10604
|
-
this.hosts = found;
|
|
10605
|
-
return found;
|
|
10606
|
-
}
|
|
10607
|
-
current() {
|
|
10608
|
-
return this.hosts.find((host) => host.current);
|
|
10609
|
-
}
|
|
10610
|
-
/** Starts a host of the daemon's own version if none is running, detached so it outlives it. */
|
|
10611
|
-
async ensureHost() {
|
|
10612
|
-
const hosts = await this.discover();
|
|
10613
|
-
for (const host of hosts) {
|
|
10614
|
-
const version = host.hello.version ?? "unversioned";
|
|
10615
|
-
const role = host.current ? "current" : host.speaks ? "older" : "not driveable";
|
|
10616
|
-
this.log(
|
|
10617
|
-
`Terminal host ${version} (protocol ${host.hello.protocol}, pid ${host.hello.pid}): ${role}.`
|
|
10618
|
-
);
|
|
10619
|
-
}
|
|
10620
|
-
if (this.current()) return;
|
|
10621
|
-
const path = currentHostSocketPath(this.version);
|
|
10622
|
-
mkdirSync20(hostsDir(), { recursive: true, mode: 448 });
|
|
10623
|
-
const child = spawn12(process.execPath, [herdBin, "terminal-host", "--socket", path], {
|
|
10624
|
-
detached: true,
|
|
10625
|
-
stdio: "ignore",
|
|
10626
|
-
env: { ...process.env, HERD_HOME: herdHome() }
|
|
10627
|
-
});
|
|
10628
|
-
child.unref();
|
|
10629
|
-
for (let attempt = 0; attempt < 50 && !this.current(); attempt++) {
|
|
10630
|
-
await sleep6(100);
|
|
10631
|
-
const hello = await askHost(path, { op: "hello" }).catch(() => null);
|
|
10632
|
-
if (!hello) continue;
|
|
10633
|
-
if (hello.version !== this.version) {
|
|
10634
|
-
throw new HerdError(
|
|
10635
|
-
`The terminal host started as version ${hello.version}, not ${this.version}, so its files changed as it started. Starting the daemon again picks this up.`
|
|
10636
|
-
);
|
|
10637
|
-
}
|
|
10638
|
-
this.hosts.push({ path, hello, current: true, speaks: speaksProtocol(hello.protocol) });
|
|
10639
|
-
}
|
|
10640
|
-
if (!this.current()) throw new HerdError("The terminal host didn't start.");
|
|
10641
|
-
this.log(`Started terminal host ${this.version}.`);
|
|
10642
|
-
}
|
|
10643
|
-
/**
|
|
10644
|
-
* Every driveable host's terminals. A name in more than one host, as when a session moved, is
|
|
10645
|
-
* the running one, or else the newest.
|
|
10646
|
-
*/
|
|
10647
|
-
async list() {
|
|
10648
|
-
if (this.hosts.length === 0) await this.discover();
|
|
10649
|
-
const byName = /* @__PURE__ */ new Map();
|
|
10650
|
-
for (const host of this.hosts.filter((candidate) => candidate.speaks)) {
|
|
10651
|
-
const terminals = await askHost(host.path, { op: "list" }).then((reply) => reply.terminals).catch(() => []);
|
|
10652
|
-
for (const info of terminals) {
|
|
10653
|
-
const seen = byName.get(info.name);
|
|
10654
|
-
const better = !seen || seen.info.exited && !info.exited || seen.info.exited === info.exited && info.createdAt > seen.info.createdAt;
|
|
10655
|
-
if (better) byName.set(info.name, { info, path: host.path });
|
|
10656
|
-
}
|
|
10657
|
-
}
|
|
10658
|
-
this.where = new Map([...byName].map(([name, { path }]) => [name, path]));
|
|
10659
|
-
return [...byName.values()].map(({ info }) => info);
|
|
10660
|
-
}
|
|
10661
|
-
/** The socket of the host holding a terminal. */
|
|
10662
|
-
async pathOf(name) {
|
|
10663
|
-
let path = this.where.get(name);
|
|
10664
|
-
if (!path) {
|
|
10665
|
-
await this.list();
|
|
10666
|
-
path = this.where.get(name);
|
|
10667
|
-
}
|
|
10668
|
-
if (!path) throw new HerdError(`There's no terminal named ${name}.`);
|
|
10669
|
-
return path;
|
|
10670
|
-
}
|
|
10671
|
-
async askFor(name, request) {
|
|
10672
|
-
return askHost(await this.pathOf(name), request);
|
|
10673
|
-
}
|
|
10674
|
-
async create(opts) {
|
|
10675
|
-
const running = (await this.list()).find((t) => t.name === opts.name && !t.exited);
|
|
10676
|
-
if (running) throw new HerdError(`A terminal named ${opts.name} is already running.`);
|
|
10677
|
-
let host = this.current();
|
|
10678
|
-
if (!host) {
|
|
10679
|
-
await this.ensureHost();
|
|
10680
|
-
host = this.current();
|
|
10681
|
-
}
|
|
10682
|
-
if (!host) throw new HerdError("The terminal host didn't start.");
|
|
10683
|
-
const { terminal } = await askHost(host.path, {
|
|
10684
|
-
op: "create",
|
|
10685
|
-
...opts
|
|
10686
|
-
});
|
|
10687
|
-
this.where.set(opts.name, host.path);
|
|
10688
|
-
return terminal;
|
|
10689
|
-
}
|
|
10690
|
-
async screen(name) {
|
|
10691
|
-
return (await this.askFor(name, { op: "screen", name })).screen;
|
|
10692
|
-
}
|
|
10693
|
-
async snapshot(name) {
|
|
10694
|
-
return (await this.askFor(name, { op: "snapshot", name })).snapshot;
|
|
10695
|
-
}
|
|
10696
|
-
async type(name, text4) {
|
|
10697
|
-
await this.askFor(name, { op: "type", name, text: text4 });
|
|
10698
|
-
}
|
|
10699
|
-
async press(name, key) {
|
|
10700
|
-
await this.askFor(name, { op: "press", name, key });
|
|
10701
|
-
}
|
|
10702
|
-
async exitStatus(name) {
|
|
10703
|
-
const terminal = (await this.list()).find((candidate) => candidate.name === name);
|
|
10704
|
-
if (!terminal) throw new HerdError(`There's no terminal named ${name}.`);
|
|
10705
|
-
return terminal.exited ? terminal.exitCode : null;
|
|
10706
|
-
}
|
|
10707
|
-
async kill(name) {
|
|
10708
|
-
await this.askFor(name, { op: "kill", name });
|
|
10709
|
-
}
|
|
10710
|
-
async retire(canStop) {
|
|
10711
|
-
const stopped2 = [];
|
|
10712
|
-
for (const host of await this.discover()) {
|
|
10713
|
-
if (host.current) continue;
|
|
10714
|
-
const version = host.hello.version ?? "unversioned";
|
|
10715
|
-
if (host.speaks) {
|
|
10716
|
-
const terminals = await askHost(host.path, { op: "list" }).then((reply) => reply.terminals).catch(() => null);
|
|
10717
|
-
if (!terminals) continue;
|
|
10718
|
-
if (terminals.some((t) => !t.exited || (t.viewers ?? 0) > 0)) continue;
|
|
10719
|
-
} else if (!host.hello.version || !canStop(host.hello.version)) {
|
|
10720
|
-
continue;
|
|
10721
|
-
}
|
|
10722
|
-
if (await this.stop(host)) {
|
|
10723
|
-
stopped2.push(version);
|
|
10724
|
-
this.log(`Retired terminal host ${version}, which had nothing left running.`);
|
|
10725
|
-
}
|
|
10726
|
-
}
|
|
10727
|
-
if (stopped2.length > 0) await this.discover();
|
|
10728
|
-
return stopped2;
|
|
10729
|
-
}
|
|
10730
|
-
/** Stops a host: politely where it knows how, and otherwise by its process. */
|
|
10731
|
-
async stop(host) {
|
|
10732
|
-
if (host.speaks && host.hello.ops?.includes("shutdown")) {
|
|
10733
|
-
return askHost(host.path, { op: "shutdown" }).then(
|
|
10734
|
-
() => true,
|
|
10735
|
-
() => false
|
|
10736
|
-
);
|
|
10737
|
-
}
|
|
10738
|
-
const hello = await askHost(host.path, { op: "hello" }).catch(() => null);
|
|
10739
|
-
if (!hello || hello.pid !== host.hello.pid) return false;
|
|
10740
|
-
try {
|
|
10741
|
-
process.kill(hello.pid, "SIGTERM");
|
|
10742
|
-
} catch {
|
|
10743
|
-
return false;
|
|
10744
|
-
}
|
|
10745
|
-
if (host.path === legacyHostSocketPath()) {
|
|
10746
|
-
for (let attempt = 0; attempt < 20; attempt++) {
|
|
10747
|
-
await sleep6(100);
|
|
10748
|
-
if (!await askHost(host.path, { op: "hello" }).catch(() => null)) break;
|
|
10749
|
-
}
|
|
10750
|
-
rmSync16(host.path, { force: true });
|
|
10751
|
-
}
|
|
10752
|
-
return true;
|
|
10753
|
-
}
|
|
10754
|
-
};
|
|
10755
|
-
|
|
10756
10765
|
// src/usage-reporter.ts
|
|
10757
10766
|
init_define_HERD_PACKAGE();
|
|
10758
10767
|
var USAGE_REPORT_MS = 5 * 60 * 1e3;
|
|
@@ -11962,7 +11971,7 @@ async function watchOne(ws, conversationId, terminals, rows = []) {
|
|
|
11962
11971
|
const dialog = listed?.dialog ?? reading?.dialog ?? null;
|
|
11963
11972
|
await followInvestigation(ws, workspace);
|
|
11964
11973
|
workspace = ws.get(conversationId) ?? workspace;
|
|
11965
|
-
if (needsInvestigator(ws, workspace, reading)) {
|
|
11974
|
+
if ((lead?.viewers ?? 0) === 0 && needsInvestigator(ws, workspace, reading)) {
|
|
11966
11975
|
ws.inBackground(
|
|
11967
11976
|
openInvestigator(ws, workspace).catch(
|
|
11968
11977
|
(error) => ws.log(`${conversationId}: the investigator didn't start: ${error}`)
|
|
@@ -12015,6 +12024,7 @@ async function watchOne(ws, conversationId, terminals, rows = []) {
|
|
|
12015
12024
|
const owed = workspace.terminalNotice;
|
|
12016
12025
|
const retryAt = (workspace.noticeFailedAt ?? 0) + (ws.options.noticeRetryMs ?? NOTICE_RETRY_MS);
|
|
12017
12026
|
if (!owed && Date.now() < retryAt) return;
|
|
12027
|
+
if (someoneDriving(lead, ws.options.driverQuietMs)) return;
|
|
12018
12028
|
ws.update(conversationId, { deferred: false, terminalNotice: false });
|
|
12019
12029
|
const claimed = workspace.deferred && await claim(ws, workspace);
|
|
12020
12030
|
if (claimed || owed) {
|
|
@@ -12033,7 +12043,7 @@ async function watchOne(ws, conversationId, terminals, rows = []) {
|
|
|
12033
12043
|
if (idle.idleSince === null && !idle.claimId) {
|
|
12034
12044
|
idle = ws.update(conversationId, { idleSince: Date.now() });
|
|
12035
12045
|
}
|
|
12036
|
-
await shutDownIfDone(ws, idle, reading.draft, lead
|
|
12046
|
+
await shutDownIfDone(ws, idle, reading.draft, lookingAt(lead));
|
|
12037
12047
|
}
|
|
12038
12048
|
}
|
|
12039
12049
|
function unreachable(ws, workspace) {
|
|
@@ -12095,7 +12105,7 @@ async function endTurn(ws, workspace) {
|
|
|
12095
12105
|
async function shutDownIfDone(ws, workspace, draft, viewers) {
|
|
12096
12106
|
if (workspace.idleSince === null || workspace.claimId) return;
|
|
12097
12107
|
if (!workspace.exitRequested && Date.now() - workspace.idleSince < ws.idleMs) return;
|
|
12098
|
-
if (draft ||
|
|
12108
|
+
if (draft || viewers > 0) return;
|
|
12099
12109
|
await ws.driverFor(workspace).exit(workspace.lead);
|
|
12100
12110
|
await ws.options.terminals.kill(workspace.lead);
|
|
12101
12111
|
await ws.report(workspace.conversationId, "exited");
|
|
@@ -12161,9 +12171,13 @@ async function deliver(ws, conversationId, delivery) {
|
|
|
12161
12171
|
if (!workspace.startProven) workspace = ws.update(conversationId, { checkInFrom: Date.now() });
|
|
12162
12172
|
return afterSubmit(ws, workspace, result2);
|
|
12163
12173
|
}
|
|
12174
|
+
const lead = (await ws.options.terminals.list()).find((t) => t.name === workspace.lead);
|
|
12175
|
+
if (someoneDriving(lead, ws.options.driverQuietMs)) {
|
|
12176
|
+
ws.update(conversationId, { deferred: true });
|
|
12177
|
+
return;
|
|
12178
|
+
}
|
|
12164
12179
|
let reading = await ws.driverFor(workspace).read(workspace.lead);
|
|
12165
12180
|
if (reading.knownDialog) {
|
|
12166
|
-
const lead = (await ws.options.terminals.list()).find((t) => t.name === workspace.lead);
|
|
12167
12181
|
if (await closeKnownDialog(ws, workspace, reading.knownDialog, lead?.viewers ?? 0)) {
|
|
12168
12182
|
reading = await ws.driverFor(workspace).read(workspace.lead);
|
|
12169
12183
|
}
|
|
@@ -13883,6 +13897,7 @@ async function runDaemon(args) {
|
|
|
13883
13897
|
...timing.agyIdleSettleMs === void 0 ? {} : { agyIdleSettleMs: timing.agyIdleSettleMs },
|
|
13884
13898
|
...timing.promptPollMs === void 0 ? {} : { promptPollMs: timing.promptPollMs },
|
|
13885
13899
|
...timing.noticeRetryMs === void 0 ? {} : { noticeRetryMs: timing.noticeRetryMs },
|
|
13900
|
+
...timing.driverQuietMs === void 0 ? {} : { driverQuietMs: timing.driverQuietMs },
|
|
13886
13901
|
...timing.investigateAfterMs === void 0 ? {} : { investigateAfterMs: timing.investigateAfterMs },
|
|
13887
13902
|
config: () => loadConfig(),
|
|
13888
13903
|
root: () => machineRoot(),
|
|
@@ -14580,7 +14595,7 @@ init_define_HERD_PACKAGE();
|
|
|
14580
14595
|
init_define_HERD_PACKAGE();
|
|
14581
14596
|
import { connect as connect4 } from "node:net";
|
|
14582
14597
|
var DETACH_KEY = "";
|
|
14583
|
-
function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: process.stdout }, path = currentHostSocketPath()) {
|
|
14598
|
+
function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: process.stdout }, path = currentHostSocketPath(), { watch: watch2 = false } = {}) {
|
|
14584
14599
|
return new Promise((resolve8, reject) => {
|
|
14585
14600
|
const socket = connect4(path);
|
|
14586
14601
|
const size = () => ({ cols: stdout.columns || 120, rows: stdout.rows || 40 });
|
|
@@ -14601,7 +14616,7 @@ function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: proces
|
|
|
14601
14616
|
const data = chunk.toString("utf8");
|
|
14602
14617
|
const detach = data.indexOf(DETACH_KEY);
|
|
14603
14618
|
const before = detach >= 0 ? data.slice(0, detach) : data;
|
|
14604
|
-
if (before) socket.write(`${JSON.stringify({ type: "input", data: before })}
|
|
14619
|
+
if (before && !watch2) socket.write(`${JSON.stringify({ type: "input", data: before })}
|
|
14605
14620
|
`);
|
|
14606
14621
|
if (detach >= 0) {
|
|
14607
14622
|
stdout.write("\r\n[detached; it's still running]\r\n");
|
|
@@ -14612,8 +14627,10 @@ function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: proces
|
|
|
14612
14627
|
`);
|
|
14613
14628
|
socket.on(
|
|
14614
14629
|
"connect",
|
|
14615
|
-
() => socket.write(
|
|
14616
|
-
|
|
14630
|
+
() => socket.write(
|
|
14631
|
+
`${JSON.stringify(watch2 ? { op: "watch", name } : { op: "attach", name, ...size() })}
|
|
14632
|
+
`
|
|
14633
|
+
)
|
|
14617
14634
|
);
|
|
14618
14635
|
socket.on("data", (chunk) => {
|
|
14619
14636
|
buffer += chunk;
|
|
@@ -14634,7 +14651,7 @@ function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: proces
|
|
|
14634
14651
|
if (stdin.isTTY) stdin.setRawMode(true);
|
|
14635
14652
|
stdin.on("data", onInput);
|
|
14636
14653
|
stdin.resume();
|
|
14637
|
-
stdout.on("resize", onResize);
|
|
14654
|
+
if (!watch2) stdout.on("resize", onResize);
|
|
14638
14655
|
}
|
|
14639
14656
|
} else if (message.type === "data") {
|
|
14640
14657
|
stdout.write(message.data ?? "");
|
|
@@ -14657,7 +14674,8 @@ function attach(name, { stdin, stdout } = { stdin: process.stdin, stdout: proces
|
|
|
14657
14674
|
import { serveHost } from "./terminal-host.mjs";
|
|
14658
14675
|
async function runAttach(rest) {
|
|
14659
14676
|
const terminals = new HostTerminals();
|
|
14660
|
-
const
|
|
14677
|
+
const watch2 = rest.includes("--watch");
|
|
14678
|
+
const name = rest.find((arg) => arg !== "--watch");
|
|
14661
14679
|
if (!name) {
|
|
14662
14680
|
const running = (await terminals.list().catch(() => [])).filter((t) => !t.exited);
|
|
14663
14681
|
console.log(
|
|
@@ -14666,7 +14684,7 @@ ${running.map((t) => ` ${t.name} (${t.cwd})`).join("\n")}` : "No terminals are
|
|
|
14666
14684
|
);
|
|
14667
14685
|
return 0;
|
|
14668
14686
|
}
|
|
14669
|
-
const result = await attach(name, void 0, await terminals.pathOf(name));
|
|
14687
|
+
const result = await attach(name, void 0, await terminals.pathOf(name), { watch: watch2 });
|
|
14670
14688
|
return result === "detached" ? 0 : result.exitCode ?? 1;
|
|
14671
14689
|
}
|
|
14672
14690
|
async function runTerminalHost(rest) {
|
|
@@ -15241,7 +15259,7 @@ On this computer:
|
|
|
15241
15259
|
daemon [--repo <path>]...
|
|
15242
15260
|
Run this machine's daemon, working from its root folder. Repos given with
|
|
15243
15261
|
--repo are only listed as known, since herd works in its own clones
|
|
15244
|
-
attach [<terminal>]
|
|
15262
|
+
attach [<terminal>] Take over a terminal, or only watch it with --watch. Ctrl+\\ detaches. With no name, lists them
|
|
15245
15263
|
builds [register|speed]
|
|
15246
15264
|
Whether this machine is a build runner, register it, with its owner's
|
|
15247
15265
|
approval in the browser, or show how long builds take. See builds --help
|