@chipschat/hermes 1.0.5 → 1.0.8
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/README.md +63 -9
- package/dist/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/main.js +1136 -217
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -8,11 +8,11 @@ var __export = (target, all) => {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
// src/main.ts
|
|
11
|
-
import { randomBytes as
|
|
12
|
-
import { existsSync as
|
|
11
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
12
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
13
13
|
import { createServer as createServer2 } from "node:net";
|
|
14
|
-
import { homedir as
|
|
15
|
-
import { dirname as
|
|
14
|
+
import { homedir as homedir8 } from "node:os";
|
|
15
|
+
import { dirname as dirname6, join as join13, resolve as resolve6 } from "node:path";
|
|
16
16
|
|
|
17
17
|
// node_modules/zod/v4/classic/external.js
|
|
18
18
|
var external_exports = {};
|
|
@@ -2024,8 +2024,8 @@ function emoji() {
|
|
|
2024
2024
|
}
|
|
2025
2025
|
var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
|
|
2026
2026
|
var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
|
|
2027
|
-
var mac = (
|
|
2028
|
-
const escapedDelim = escapeRegex(
|
|
2027
|
+
var mac = (delimiter3) => {
|
|
2028
|
+
const escapedDelim = escapeRegex(delimiter3 ?? ":");
|
|
2029
2029
|
return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
|
|
2030
2030
|
};
|
|
2031
2031
|
var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
|
|
@@ -2599,8 +2599,8 @@ var Doc = class {
|
|
|
2599
2599
|
const lines2 = content.split("\n").filter((x) => x);
|
|
2600
2600
|
const minIndent = Math.min(...lines2.map((x) => x.length - x.trimStart().length));
|
|
2601
2601
|
const dedented = lines2.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x);
|
|
2602
|
-
for (const
|
|
2603
|
-
this.content.push(
|
|
2602
|
+
for (const line2 of dedented) {
|
|
2603
|
+
this.content.push(line2);
|
|
2604
2604
|
}
|
|
2605
2605
|
}
|
|
2606
2606
|
compile() {
|
|
@@ -5081,7 +5081,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
5081
5081
|
var NONE = 0;
|
|
5082
5082
|
var ASSUMED = 1;
|
|
5083
5083
|
var PROVEN = 2;
|
|
5084
|
-
function isRecursive(inst, stack,
|
|
5084
|
+
function isRecursive(inst, stack, resolve7) {
|
|
5085
5085
|
const cached2 = recursive.get(inst);
|
|
5086
5086
|
if (cached2 !== void 0)
|
|
5087
5087
|
return cached2 ? PROVEN : NONE;
|
|
@@ -5091,7 +5091,7 @@ function isRecursive(inst, stack, resolve6) {
|
|
|
5091
5091
|
let result = NONE;
|
|
5092
5092
|
const check2 = (child) => {
|
|
5093
5093
|
if (result !== PROVEN && child?._zod) {
|
|
5094
|
-
const answer = isRecursive(child, stack,
|
|
5094
|
+
const answer = isRecursive(child, stack, resolve7);
|
|
5095
5095
|
if (answer > result)
|
|
5096
5096
|
result = answer;
|
|
5097
5097
|
}
|
|
@@ -5102,7 +5102,7 @@ function isRecursive(inst, stack, resolve6) {
|
|
|
5102
5102
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
5103
5103
|
if (spread && !desc.enumerable)
|
|
5104
5104
|
continue;
|
|
5105
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
5105
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve7) : NONE;
|
|
5106
5106
|
if (child > answer)
|
|
5107
5107
|
answer = child;
|
|
5108
5108
|
}
|
|
@@ -5166,7 +5166,7 @@ function isRecursive(inst, stack, resolve6) {
|
|
|
5166
5166
|
break;
|
|
5167
5167
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
5168
5168
|
case "lazy": {
|
|
5169
|
-
const inner = def._cachedInner ?? (
|
|
5169
|
+
const inner = def._cachedInner ?? (resolve7 ? inst._zod.innerType : void 0);
|
|
5170
5170
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
5171
5171
|
break;
|
|
5172
5172
|
}
|
|
@@ -20535,31 +20535,31 @@ function withRole(name2, from) {
|
|
|
20535
20535
|
function flat(text) {
|
|
20536
20536
|
return text.replace(/\s*\n\s*/g, " ").trim();
|
|
20537
20537
|
}
|
|
20538
|
-
function wordsOf(
|
|
20539
|
-
const text = flat(
|
|
20540
|
-
const heard =
|
|
20538
|
+
function wordsOf(line2) {
|
|
20539
|
+
const text = flat(line2.text ?? "");
|
|
20540
|
+
const heard = line2.transcript === void 0 ? "" : flat(line2.transcript);
|
|
20541
20541
|
const parts = [text];
|
|
20542
20542
|
if (heard !== "")
|
|
20543
20543
|
parts.push(text === "" ? `(voice note) ${heard}` : `(voice note: ${heard})`);
|
|
20544
|
-
if (heard === "" &&
|
|
20545
|
-
parts.push(`(sent ${
|
|
20546
|
-
if (
|
|
20544
|
+
if (heard === "" && line2.files !== void 0)
|
|
20545
|
+
parts.push(`(sent ${line2.files.join(", ")})`);
|
|
20546
|
+
if (line2.held === true)
|
|
20547
20547
|
parts.push("(held by Chips until a person acts)");
|
|
20548
20548
|
return parts.filter((part) => part !== "").join(" ");
|
|
20549
20549
|
}
|
|
20550
|
-
function renderLine(
|
|
20551
|
-
const name2 =
|
|
20552
|
-
switch (
|
|
20550
|
+
function renderLine(line2) {
|
|
20551
|
+
const name2 = line2.from.name;
|
|
20552
|
+
switch (line2.kind) {
|
|
20553
20553
|
case "said":
|
|
20554
|
-
return `${withRole(name2,
|
|
20554
|
+
return `${withRole(name2, line2.from)}: ${wordsOf(line2)}`;
|
|
20555
20555
|
case "joined":
|
|
20556
20556
|
return `(${name2} joined the space)`;
|
|
20557
20557
|
case "left":
|
|
20558
20558
|
return `(${name2} left the space)`;
|
|
20559
20559
|
case "renamed":
|
|
20560
|
-
if (
|
|
20561
|
-
return `(${name2} renamed the space "${
|
|
20562
|
-
return `(${
|
|
20560
|
+
if (line2.title !== void 0)
|
|
20561
|
+
return `(${name2} renamed the space "${line2.title}")`;
|
|
20562
|
+
return `(${line2.previous ?? name2} is now called ${line2.name ?? name2})`;
|
|
20563
20563
|
case "archived":
|
|
20564
20564
|
return `(${name2} archived the space)`;
|
|
20565
20565
|
default:
|
|
@@ -20610,7 +20610,7 @@ function quoteLines(replying) {
|
|
|
20610
20610
|
body.push(`(sent ${replying.files.join(", ")})`);
|
|
20611
20611
|
}
|
|
20612
20612
|
}
|
|
20613
|
-
return body.map((
|
|
20613
|
+
return body.map((line2) => line2 === "" ? ">" : `> ${line2}`);
|
|
20614
20614
|
}
|
|
20615
20615
|
var DAY_MS = 864e5;
|
|
20616
20616
|
function saidWhen(at, reading) {
|
|
@@ -22517,7 +22517,7 @@ var AGENT_FIRST_NAMES = [
|
|
|
22517
22517
|
"Xander Xavi Xavier",
|
|
22518
22518
|
"Yann Yaro Yosef Yuri Yusuf Yves",
|
|
22519
22519
|
"Zaid Zane Zeke Zeno Zev Ziggy"
|
|
22520
|
-
].flatMap((
|
|
22520
|
+
].flatMap((line2) => line2.split(" "));
|
|
22521
22521
|
|
|
22522
22522
|
// node_modules/@chipschat/protocol/dist/src/frames.js
|
|
22523
22523
|
var clientHelloFrame = external_exports.object({
|
|
@@ -22846,8 +22846,8 @@ function createOutbox() {
|
|
|
22846
22846
|
const waiting = /* @__PURE__ */ new Map();
|
|
22847
22847
|
return {
|
|
22848
22848
|
add(key, event) {
|
|
22849
|
-
return new Promise((
|
|
22850
|
-
waiting.set(key, { event, resolve:
|
|
22849
|
+
return new Promise((resolve7, reject) => {
|
|
22850
|
+
waiting.set(key, { event, resolve: resolve7, reject });
|
|
22851
22851
|
});
|
|
22852
22852
|
},
|
|
22853
22853
|
unanswered: () => [...waiting.values()].map((entry) => entry.event),
|
|
@@ -22923,7 +22923,7 @@ async function inPieces(call, path, bytes) {
|
|
|
22923
22923
|
async function afterLoss(error62, failures) {
|
|
22924
22924
|
if (error62 instanceof RefusedError || failures >= PIECE_TRIES)
|
|
22925
22925
|
throw error62;
|
|
22926
|
-
await new Promise((
|
|
22926
|
+
await new Promise((resolve7) => setTimeout(resolve7, 250 * failures));
|
|
22927
22927
|
}
|
|
22928
22928
|
function sendPiece(call, path, bytes, offset) {
|
|
22929
22929
|
const piece = bytes.subarray(offset, Math.min(offset + PIECE_BYTES, bytes.byteLength));
|
|
@@ -22949,16 +22949,16 @@ var TRIES = 4;
|
|
|
22949
22949
|
var MARKS = /^(<<<<<<<|>>>>>>>) /m;
|
|
22950
22950
|
var REJECTED = /\[rejected\]|fetch first|non-fast-forward/;
|
|
22951
22951
|
var OWN_CONFIG = /^(core\.(repositoryformatversion|filemode|bare|logallrefupdates|ignorecase|precomposeunicode|symlinks)|remote\.origin\.(url|fetch)|branch\..+\.(remote|merge)|user\.(name|email))$/;
|
|
22952
|
-
var lines = (said2) => said2.split("\n").filter((
|
|
22952
|
+
var lines = (said2) => said2.split("\n").filter((line2) => line2 !== "");
|
|
22953
22953
|
var unique = (names) => [...new Set(names)];
|
|
22954
22954
|
function subjectOf(words4) {
|
|
22955
|
-
const first = words4.split("\n").find((
|
|
22955
|
+
const first = words4.split("\n").find((line2) => line2.trim() !== "")?.trim() ?? "";
|
|
22956
22956
|
if (first === "")
|
|
22957
22957
|
return "A change to the project";
|
|
22958
22958
|
return first.length <= 72 ? first : `${first.slice(0, 71)}\u2026`;
|
|
22959
22959
|
}
|
|
22960
22960
|
function remoteWords(err) {
|
|
22961
|
-
const said2 = lines(err).filter((
|
|
22961
|
+
const said2 = lines(err).filter((line2) => line2.startsWith("remote: ")).map((line2) => line2.slice("remote: ".length).trim()).filter((line2) => line2 !== "" && !/^error: hook declined/.test(line2));
|
|
22962
22962
|
return said2.length > 0 ? said2.join(" ") : lines(err)[0] ?? "git said nothing";
|
|
22963
22963
|
}
|
|
22964
22964
|
function versionIn(said2) {
|
|
@@ -23007,13 +23007,13 @@ function heldNote(pushed, name2) {
|
|
|
23007
23007
|
}
|
|
23008
23008
|
async function checkGit(git2 = "git", path = process.env["PATH"] ?? "/usr/bin:/bin") {
|
|
23009
23009
|
const [major, minor] = versionIn(PROJECT_GIT_VERSION) ?? [0, 0];
|
|
23010
|
-
return new Promise((
|
|
23010
|
+
return new Promise((resolve7) => {
|
|
23011
23011
|
execFile(git2, ["--version"], { env: { PATH: path } }, (error62, stdout) => {
|
|
23012
23012
|
if (error62 !== null)
|
|
23013
|
-
return
|
|
23013
|
+
return resolve7({ ok: false, why: "git was not found on this machine" });
|
|
23014
23014
|
const mine = versionIn(stdout);
|
|
23015
23015
|
const enough = mine !== void 0 && (mine[0] > major || mine[0] === major && mine[1] >= minor);
|
|
23016
|
-
|
|
23016
|
+
resolve7(enough ? { ok: true, version: mine.join(".") } : { ok: false, why: `${stdout.trim()} is older than ${PROJECT_GIT_VERSION}` });
|
|
23017
23017
|
});
|
|
23018
23018
|
});
|
|
23019
23019
|
}
|
|
@@ -23142,8 +23142,8 @@ var Project = class {
|
|
|
23142
23142
|
async standing(remote) {
|
|
23143
23143
|
const mine = await this.head("HEAD");
|
|
23144
23144
|
const base = await this.baseOf(mine, remote);
|
|
23145
|
-
const counted = lines((await this.git(["diff", "--numstat", "--no-renames", "-z", base])).out.replace(/\0/g, "\n")).map((
|
|
23146
|
-
const [added2 = "0", removed = "0", ...path] =
|
|
23145
|
+
const counted = lines((await this.git(["diff", "--numstat", "--no-renames", "-z", base])).out.replace(/\0/g, "\n")).map((line2) => {
|
|
23146
|
+
const [added2 = "0", removed = "0", ...path] = line2.split(" ");
|
|
23147
23147
|
return { path: path.join(" "), added: Number(added2) || 0, removed: Number(removed) || 0 };
|
|
23148
23148
|
});
|
|
23149
23149
|
const added = lines((await this.git(["ls-files", "--others", "--exclude-standard", "-z"])).out.replace(/\0/g, "\n"));
|
|
@@ -23189,8 +23189,8 @@ var Project = class {
|
|
|
23189
23189
|
async markedLines(path) {
|
|
23190
23190
|
try {
|
|
23191
23191
|
const all = (await readFile(join(this.copy, path), "utf8")).split("\n");
|
|
23192
|
-
const start = all.findIndex((
|
|
23193
|
-
const end2 = all.findIndex((
|
|
23192
|
+
const start = all.findIndex((line2) => line2.startsWith("<<<<<<< "));
|
|
23193
|
+
const end2 = all.findIndex((line2, at) => at > start && line2.startsWith(">>>>>>> "));
|
|
23194
23194
|
if (start < 0 || end2 < 0)
|
|
23195
23195
|
return void 0;
|
|
23196
23196
|
const shown2 = all.slice(Math.max(0, start - 1), end2 + 2);
|
|
@@ -23459,8 +23459,8 @@ ${sent.err}`;
|
|
|
23459
23459
|
env[`GIT_CONFIG_KEY_${at}`] = key;
|
|
23460
23460
|
env[`GIT_CONFIG_VALUE_${at}`] = value;
|
|
23461
23461
|
});
|
|
23462
|
-
return new Promise((
|
|
23463
|
-
execFile(options.git ?? "git", [...args], { cwd: this.copy, env, maxBuffer: 64 * 1024 * 1024 }, (error62, stdout, stderr) =>
|
|
23462
|
+
return new Promise((resolve7) => {
|
|
23463
|
+
execFile(options.git ?? "git", [...args], { cwd: this.copy, env, maxBuffer: 64 * 1024 * 1024 }, (error62, stdout, stderr) => resolve7({ ok: error62 === null, out: stdout, err: stderr }));
|
|
23464
23464
|
});
|
|
23465
23465
|
}
|
|
23466
23466
|
};
|
|
@@ -23483,8 +23483,8 @@ var Lane = class {
|
|
|
23483
23483
|
constructor(id2, wire) {
|
|
23484
23484
|
this.id = id2;
|
|
23485
23485
|
this.wire = wire;
|
|
23486
|
-
this.result = new Promise((
|
|
23487
|
-
this.settle =
|
|
23486
|
+
this.result = new Promise((resolve7) => {
|
|
23487
|
+
this.settle = resolve7;
|
|
23488
23488
|
});
|
|
23489
23489
|
this.stop = wire.hear(id2, (event) => this.onUpdated(event));
|
|
23490
23490
|
}
|
|
@@ -23627,7 +23627,7 @@ var Run = class {
|
|
|
23627
23627
|
/** Resolve once the run is not paused. A stopped run resolves too: check `over` after. */
|
|
23628
23628
|
async whenRunning() {
|
|
23629
23629
|
while (this.held && !this.ended) {
|
|
23630
|
-
await new Promise((
|
|
23630
|
+
await new Promise((resolve7) => setTimeout(resolve7, 50));
|
|
23631
23631
|
}
|
|
23632
23632
|
}
|
|
23633
23633
|
status(input2) {
|
|
@@ -24411,8 +24411,8 @@ var Chips = class _Chips {
|
|
|
24411
24411
|
}
|
|
24412
24412
|
/** Resolve with the first event that matches, now or later. */
|
|
24413
24413
|
waitFor(match) {
|
|
24414
|
-
return new Promise((
|
|
24415
|
-
this.waiters.add({ match, resolve:
|
|
24414
|
+
return new Promise((resolve7) => {
|
|
24415
|
+
this.waiters.add({ match, resolve: resolve7 });
|
|
24416
24416
|
});
|
|
24417
24417
|
}
|
|
24418
24418
|
close() {
|
|
@@ -24439,7 +24439,7 @@ var Chips = class _Chips {
|
|
|
24439
24439
|
return `${base}/v1/ws?ticket=${encodeURIComponent(String(ticket))}`;
|
|
24440
24440
|
}
|
|
24441
24441
|
open(first) {
|
|
24442
|
-
return new Promise((
|
|
24442
|
+
return new Promise((resolve7, reject) => {
|
|
24443
24443
|
this.setState("connecting");
|
|
24444
24444
|
const failed = (what, error62) => {
|
|
24445
24445
|
if (first)
|
|
@@ -24457,13 +24457,13 @@ var Chips = class _Chips {
|
|
|
24457
24457
|
}
|
|
24458
24458
|
this.socket = socket;
|
|
24459
24459
|
this.listen(socket, {
|
|
24460
|
-
settled: (hello2) => this.settled(socket, hello2, first,
|
|
24460
|
+
settled: (hello2) => this.settled(socket, hello2, first, resolve7, reject)
|
|
24461
24461
|
});
|
|
24462
24462
|
}, (error62) => failed("could not get a socket ticket", error62));
|
|
24463
24463
|
});
|
|
24464
24464
|
}
|
|
24465
24465
|
/** The handshake's outcome: the hello, a refusal, or the socket gone before either. */
|
|
24466
|
-
settled(socket, hello2, first,
|
|
24466
|
+
settled(socket, hello2, first, resolve7, reject) {
|
|
24467
24467
|
if (hello2 === void 0) {
|
|
24468
24468
|
if (first)
|
|
24469
24469
|
reject(new ConnectError("the server closed the socket before saying hello"));
|
|
@@ -24477,7 +24477,7 @@ var Chips = class _Chips {
|
|
|
24477
24477
|
return;
|
|
24478
24478
|
}
|
|
24479
24479
|
this.onHello(socket, hello2);
|
|
24480
|
-
void this.subscribed(socket).then(
|
|
24480
|
+
void this.subscribed(socket).then(resolve7);
|
|
24481
24481
|
}
|
|
24482
24482
|
/** The socket's four events. `settled` fires once: with the hello, a refusal, or nothing. */
|
|
24483
24483
|
listen(socket, on) {
|
|
@@ -24572,15 +24572,15 @@ var Chips = class _Chips {
|
|
|
24572
24572
|
subscribed(socket) {
|
|
24573
24573
|
this.pings += 1;
|
|
24574
24574
|
const ts = Date.now() + this.pings;
|
|
24575
|
-
return new Promise((
|
|
24575
|
+
return new Promise((resolve7) => {
|
|
24576
24576
|
const timer = setTimeout(() => {
|
|
24577
24577
|
this.pongs.delete(ts);
|
|
24578
|
-
|
|
24578
|
+
resolve7();
|
|
24579
24579
|
}, 2e3);
|
|
24580
24580
|
this.pongs.set(ts, () => {
|
|
24581
24581
|
clearTimeout(timer);
|
|
24582
24582
|
this.pongs.delete(ts);
|
|
24583
|
-
|
|
24583
|
+
resolve7();
|
|
24584
24584
|
});
|
|
24585
24585
|
socket.send(JSON.stringify({ frame: "ping", ts }));
|
|
24586
24586
|
});
|
|
@@ -24963,7 +24963,7 @@ function openKeyStore(env, platform = process.platform) {
|
|
|
24963
24963
|
// node_modules/@chipschat/harness/dist/steps.js
|
|
24964
24964
|
import { writeSync } from "node:fs";
|
|
24965
24965
|
var STEP_MARK = "chips-step ";
|
|
24966
|
-
function reportStep(step, detail = {}, env = process.env, write = (
|
|
24966
|
+
function reportStep(step, detail = {}, env = process.env, write = (line2) => void writeSync(1, line2)) {
|
|
24967
24967
|
if (env["CHIPS_STEPS"] !== "json")
|
|
24968
24968
|
return;
|
|
24969
24969
|
try {
|
|
@@ -25044,8 +25044,8 @@ async function refused(response) {
|
|
|
25044
25044
|
const body = await response.json().catch(() => ({}));
|
|
25045
25045
|
return new JoinError(body.error?.refusal ?? body.error?.code ?? `http_${response.status}`, body.error?.message ?? `Chips answered ${response.status}.`);
|
|
25046
25046
|
}
|
|
25047
|
-
var wait = (ms, signal) => new Promise((
|
|
25048
|
-
const timer = setTimeout(
|
|
25047
|
+
var wait = (ms, signal) => new Promise((resolve7, reject) => {
|
|
25048
|
+
const timer = setTimeout(resolve7, ms);
|
|
25049
25049
|
signal?.addEventListener("abort", () => {
|
|
25050
25050
|
clearTimeout(timer);
|
|
25051
25051
|
reject(new JoinError("stopped", "Stopped before a person decided."));
|
|
@@ -25169,8 +25169,8 @@ var MOST_FILES = 20;
|
|
|
25169
25169
|
var MOST_FILE_BYTES = 100 * 1024 * 1024;
|
|
25170
25170
|
function filesNamedIn(answer) {
|
|
25171
25171
|
const named = [];
|
|
25172
|
-
const kept = answer.split("\n").filter((
|
|
25173
|
-
const media = /^\s*MEDIA:\s*(\S.*?)\s*$/.exec(
|
|
25172
|
+
const kept = answer.split("\n").filter((line2) => {
|
|
25173
|
+
const media = /^\s*MEDIA:\s*(\S.*?)\s*$/.exec(line2);
|
|
25174
25174
|
if (media?.[1] === void 0)
|
|
25175
25175
|
return true;
|
|
25176
25176
|
if (!named.includes(media[1]))
|
|
@@ -25555,7 +25555,7 @@ function fileStore(path) {
|
|
|
25555
25555
|
|
|
25556
25556
|
// src/bridge.ts
|
|
25557
25557
|
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
25558
|
-
import { join as
|
|
25558
|
+
import { join as join10 } from "node:path";
|
|
25559
25559
|
|
|
25560
25560
|
// src/context.ts
|
|
25561
25561
|
import { spawn } from "node:child_process";
|
|
@@ -25578,7 +25578,7 @@ import { basename as basename3, dirname as dirname4, join as join6 } from "node:
|
|
|
25578
25578
|
|
|
25579
25579
|
// src/home.ts
|
|
25580
25580
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
25581
|
-
import { existsSync as existsSync5, readFileSync as readFileSync5, readlinkSync, statSync } from "node:fs";
|
|
25581
|
+
import { appendFileSync, chmodSync as chmodSync3, existsSync as existsSync5, readFileSync as readFileSync5, readlinkSync, statSync } from "node:fs";
|
|
25582
25582
|
import { homedir as homedir4 } from "node:os";
|
|
25583
25583
|
import { join as join5, resolve as resolve2 } from "node:path";
|
|
25584
25584
|
function hermesRoot(env = process.env) {
|
|
@@ -25596,8 +25596,8 @@ function envValue(home2, name2) {
|
|
|
25596
25596
|
const path = join5(home2, ".env");
|
|
25597
25597
|
if (!existsSync5(path)) return void 0;
|
|
25598
25598
|
let found;
|
|
25599
|
-
for (const
|
|
25600
|
-
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(
|
|
25599
|
+
for (const line2 of readFileSync5(path, "utf8").split("\n")) {
|
|
25600
|
+
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(line2);
|
|
25601
25601
|
if (match?.[1] !== name2) continue;
|
|
25602
25602
|
const raw = match[2] ?? "";
|
|
25603
25603
|
const quoted2 = /^(["'])(.*)\1/.exec(raw);
|
|
@@ -25605,6 +25605,14 @@ function envValue(home2, name2) {
|
|
|
25605
25605
|
}
|
|
25606
25606
|
return found;
|
|
25607
25607
|
}
|
|
25608
|
+
function appendEnv(home2, name2, value) {
|
|
25609
|
+
const path = join5(home2, ".env");
|
|
25610
|
+
const text = readFileSync5(path, "utf8");
|
|
25611
|
+
const mode = statSync(path).mode & 511;
|
|
25612
|
+
appendFileSync(path, `${text === "" || text.endsWith("\n") ? "" : "\n"}${name2}=${value}
|
|
25613
|
+
`);
|
|
25614
|
+
chmodSync3(path, mode);
|
|
25615
|
+
}
|
|
25608
25616
|
function configText(home2) {
|
|
25609
25617
|
const path = join5(home2, "config.yaml");
|
|
25610
25618
|
try {
|
|
@@ -25613,20 +25621,20 @@ function configText(home2) {
|
|
|
25613
25621
|
return "";
|
|
25614
25622
|
}
|
|
25615
25623
|
}
|
|
25616
|
-
function uncommented(
|
|
25617
|
-
const indent =
|
|
25624
|
+
function uncommented(line2) {
|
|
25625
|
+
const indent = line2.length - line2.trimStart().length;
|
|
25618
25626
|
let quote;
|
|
25619
|
-
for (let at = indent; at <
|
|
25620
|
-
const char =
|
|
25627
|
+
for (let at = indent; at < line2.length; at += 1) {
|
|
25628
|
+
const char = line2[at];
|
|
25621
25629
|
if (quote !== void 0) {
|
|
25622
25630
|
if (char === quote) quote = void 0;
|
|
25623
25631
|
} else if (char === '"' || char === "'") {
|
|
25624
25632
|
quote = char;
|
|
25625
|
-
} else if (char === "#" && (at === indent || /\s/.test(
|
|
25626
|
-
return { indent, text:
|
|
25633
|
+
} else if (char === "#" && (at === indent || /\s/.test(line2[at - 1] ?? ""))) {
|
|
25634
|
+
return { indent, text: line2.slice(indent, at).trimEnd() };
|
|
25627
25635
|
}
|
|
25628
25636
|
}
|
|
25629
|
-
return { indent, text:
|
|
25637
|
+
return { indent, text: line2.slice(indent).trimEnd() };
|
|
25630
25638
|
}
|
|
25631
25639
|
function scalar(text) {
|
|
25632
25640
|
const trimmed = text.trim();
|
|
@@ -25648,9 +25656,9 @@ function flowPairs(text) {
|
|
|
25648
25656
|
function yamlValue(text, section, key) {
|
|
25649
25657
|
const lines2 = text.split("\n");
|
|
25650
25658
|
for (let at = 0; at < lines2.length; at += 1) {
|
|
25651
|
-
const { indent, text:
|
|
25652
|
-
if (indent !== 0 || !
|
|
25653
|
-
const rest =
|
|
25659
|
+
const { indent, text: line2 } = uncommented(lines2[at] ?? "");
|
|
25660
|
+
if (indent !== 0 || !line2.startsWith(`${section}:`)) continue;
|
|
25661
|
+
const rest = line2.slice(section.length + 1).trim();
|
|
25654
25662
|
if (rest !== "") return flowPairs(rest)?.get(key);
|
|
25655
25663
|
let child;
|
|
25656
25664
|
for (let below = at + 1; below < lines2.length; below += 1) {
|
|
@@ -25722,7 +25730,9 @@ function folderOfPid(raw) {
|
|
|
25722
25730
|
} catch {
|
|
25723
25731
|
return void 0;
|
|
25724
25732
|
}
|
|
25725
|
-
|
|
25733
|
+
return pid === void 0 ? void 0 : processFolder(pid);
|
|
25734
|
+
}
|
|
25735
|
+
function processFolder(pid) {
|
|
25726
25736
|
try {
|
|
25727
25737
|
if (process.platform === "linux") return readlinkSync(`/proc/${pid}/cwd`);
|
|
25728
25738
|
const out = execFileSync3("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
|
|
@@ -25730,8 +25740,8 @@ function folderOfPid(raw) {
|
|
|
25730
25740
|
timeout: 3e3,
|
|
25731
25741
|
stdio: ["ignore", "pipe", "ignore"]
|
|
25732
25742
|
});
|
|
25733
|
-
const
|
|
25734
|
-
return
|
|
25743
|
+
const line2 = out.split("\n").find((each) => each.startsWith("n"));
|
|
25744
|
+
return line2 === void 0 ? void 0 : line2.slice(1);
|
|
25735
25745
|
} catch {
|
|
25736
25746
|
return void 0;
|
|
25737
25747
|
}
|
|
@@ -25837,13 +25847,9 @@ function fileOf(source2, name2) {
|
|
|
25837
25847
|
}
|
|
25838
25848
|
var byOrder = (a, b) => ORDER.indexOf(a.kind) - ORDER.indexOf(b.kind) || a.profile.localeCompare(b.profile);
|
|
25839
25849
|
var Picture = class {
|
|
25840
|
-
|
|
25841
|
-
|
|
25842
|
-
|
|
25843
|
-
shared = /* @__PURE__ */ new Map();
|
|
25844
|
-
/** The agents and the files each reads, in page order. */
|
|
25845
|
-
agents = /* @__PURE__ */ new Map();
|
|
25846
|
-
constructor(readers, first, env = process.env) {
|
|
25850
|
+
constructor(readers, first, env = process.env, ownProfiles = false) {
|
|
25851
|
+
this.first = first;
|
|
25852
|
+
this.ownProfiles = ownProfiles;
|
|
25847
25853
|
const byProfile = /* @__PURE__ */ new Map();
|
|
25848
25854
|
const sourcesFor = (profile) => {
|
|
25849
25855
|
let sources = byProfile.get(profile.home);
|
|
@@ -25865,7 +25871,7 @@ var Picture = class {
|
|
|
25865
25871
|
}
|
|
25866
25872
|
const taken = /* @__PURE__ */ new Set();
|
|
25867
25873
|
const ids2 = /* @__PURE__ */ new Set();
|
|
25868
|
-
const sharing = [...this.readers.values()].filter(({ source: source2, agents }) => source2.kind === "project" || source2
|
|
25874
|
+
const sharing = [...this.readers.values()].filter(({ source: source2, agents }) => source2.kind === "project" || this.forNewAgents(source2) || agents.length > 1).sort((a, b) => (a.source.profile === first?.name ? 0 : 1) - (b.source.profile === first?.name ? 0 : 1) || byOrder(a.source, b.source));
|
|
25869
25875
|
for (const { source: source2 } of sharing) {
|
|
25870
25876
|
const name2 = taken.has(source2.file) ? `${source2.file} (${source2.profile})` : source2.file;
|
|
25871
25877
|
taken.add(name2);
|
|
@@ -25875,8 +25881,24 @@ var Picture = class {
|
|
|
25875
25881
|
this.shared.set(source2.path, { id: id2, name: name2 });
|
|
25876
25882
|
}
|
|
25877
25883
|
}
|
|
25884
|
+
first;
|
|
25885
|
+
ownProfiles;
|
|
25886
|
+
/** Each file by its path, and the agents that read it. */
|
|
25887
|
+
readers = /* @__PURE__ */ new Map();
|
|
25888
|
+
/** The shared files, each with its id and the name the set shows. */
|
|
25889
|
+
shared = /* @__PURE__ */ new Map();
|
|
25890
|
+
/** The agents and the files each reads, in page order. */
|
|
25891
|
+
agents = /* @__PURE__ */ new Map();
|
|
25892
|
+
/**
|
|
25893
|
+
* Whether each new agent starts with this file or a copy of it: the first profile's files, or
|
|
25894
|
+
* where each new agent gets a profile of its own, the first profile's SOUL.md and the project file.
|
|
25895
|
+
*/
|
|
25896
|
+
forNewAgents(source2) {
|
|
25897
|
+
if (source2.profile !== this.first?.name) return false;
|
|
25898
|
+
return !this.ownProfiles || source2.kind === "soul" || source2.kind === "project";
|
|
25899
|
+
}
|
|
25878
25900
|
/** The harness's own set, as `PUT /v1/harnesses/:id/context` takes it. */
|
|
25879
|
-
harnessCopy(
|
|
25901
|
+
harnessCopy() {
|
|
25880
25902
|
const shown2 = [];
|
|
25881
25903
|
const notShown = [];
|
|
25882
25904
|
for (const [path, { id: id2, name: name2 }] of this.shared) {
|
|
@@ -25892,8 +25914,7 @@ var Picture = class {
|
|
|
25892
25914
|
...file3,
|
|
25893
25915
|
id: id2,
|
|
25894
25916
|
used_by: agents.length,
|
|
25895
|
-
|
|
25896
|
-
...source2.profile === first?.name ? { for_new_agents: true } : {}
|
|
25917
|
+
...this.forNewAgents(source2) ? { for_new_agents: true } : {}
|
|
25897
25918
|
});
|
|
25898
25919
|
}
|
|
25899
25920
|
return {
|
|
@@ -26054,9 +26075,9 @@ var Contexts = class {
|
|
|
26054
26075
|
/** Every copy read again, and sent where it moved. */
|
|
26055
26076
|
refresh() {
|
|
26056
26077
|
return this.inLine(async () => {
|
|
26057
|
-
const { readers, first } = this.roster();
|
|
26058
|
-
const picture = new Picture(readers, first, this.env);
|
|
26059
|
-
await this.port.harness(picture.harnessCopy(
|
|
26078
|
+
const { readers, first, ownProfiles } = this.roster();
|
|
26079
|
+
const picture = new Picture(readers, first, this.env, ownProfiles);
|
|
26080
|
+
await this.port.harness(picture.harnessCopy()).catch((error62) => {
|
|
26060
26081
|
this.log(`the harness's context not sent: ${messageOf(error62)}`);
|
|
26061
26082
|
});
|
|
26062
26083
|
for (const { agentId: agentId2 } of readers) {
|
|
@@ -26074,12 +26095,12 @@ var Contexts = class {
|
|
|
26074
26095
|
*/
|
|
26075
26096
|
async edit(frame) {
|
|
26076
26097
|
const answer = await this.inLine(async () => {
|
|
26077
|
-
const { readers, first } = this.roster();
|
|
26098
|
+
const { readers, first, ownProfiles } = this.roster();
|
|
26078
26099
|
const copyNow = () => {
|
|
26079
|
-
const picture = new Picture(readers, first, this.env);
|
|
26080
|
-
return frame.agent_id === void 0 ? picture.harnessCopy(
|
|
26100
|
+
const picture = new Picture(readers, first, this.env, ownProfiles);
|
|
26101
|
+
return frame.agent_id === void 0 ? picture.harnessCopy() : picture.agentCopy(frame.agent_id);
|
|
26081
26102
|
};
|
|
26082
|
-
const found = new Picture(readers, first, this.env).find(frame.agent_id, frame.name);
|
|
26103
|
+
const found = new Picture(readers, first, this.env, ownProfiles).find(frame.agent_id, frame.name);
|
|
26083
26104
|
if (found === void 0) {
|
|
26084
26105
|
throw new Refuse(`Hermes gives this agent no file called ${frame.name}.`, copyNow(), "failed");
|
|
26085
26106
|
}
|
|
@@ -26146,6 +26167,8 @@ function numbersOf(profile, body) {
|
|
|
26146
26167
|
if (typeof body !== "object" || body === null) return void 0;
|
|
26147
26168
|
const top = body;
|
|
26148
26169
|
if (top["hook_event_name"] !== "post_api_request") return void 0;
|
|
26170
|
+
const sender = top["profile"];
|
|
26171
|
+
if (typeof sender === "string" && sender !== profile && sender !== "custom") return void 0;
|
|
26149
26172
|
const extra = typeof top["extra"] === "object" && top["extra"] !== null ? top["extra"] : {};
|
|
26150
26173
|
const named = typeof top["session_id"] === "string" ? top["session_id"] : "";
|
|
26151
26174
|
const task2 = typeof extra["task_id"] === "string" && extra["task_id"] !== "" ? extra["task_id"] : void 0;
|
|
@@ -26451,6 +26474,13 @@ var HermesClient = class {
|
|
|
26451
26474
|
const latest = read["session_id"];
|
|
26452
26475
|
return typeof latest === "string" && latest !== "" ? latest : sessionId;
|
|
26453
26476
|
}
|
|
26477
|
+
/**
|
|
26478
|
+
* The models Hermes has for this profile, as its own picker lists them: `GET /api/model/options`,
|
|
26479
|
+
* only the providers the profile is set up for (`models.ts`).
|
|
26480
|
+
*/
|
|
26481
|
+
async modelOptions() {
|
|
26482
|
+
return await this.call("GET", "/api/model/options?include_unconfigured=false");
|
|
26483
|
+
}
|
|
26454
26484
|
/** A run in a session: what it is told, and under a key a retry of the same turn repeats. */
|
|
26455
26485
|
async startRun(input2) {
|
|
26456
26486
|
const started = await this.call(
|
|
@@ -26460,6 +26490,7 @@ var HermesClient = class {
|
|
|
26460
26490
|
input: input2.text,
|
|
26461
26491
|
session_id: input2.sessionId,
|
|
26462
26492
|
...input2.model === void 0 ? {} : { model: input2.model },
|
|
26493
|
+
...input2.provider === void 0 ? {} : { provider: input2.provider },
|
|
26463
26494
|
...input2.instructions === void 0 ? {} : { instructions: input2.instructions }
|
|
26464
26495
|
},
|
|
26465
26496
|
{ "Idempotency-Key": input2.key.slice(0, 255) }
|
|
@@ -26529,7 +26560,7 @@ function sessionIdOf(made) {
|
|
|
26529
26560
|
return typeof id2 === "string" && id2 !== "" ? id2 : void 0;
|
|
26530
26561
|
}
|
|
26531
26562
|
function eventOf(block) {
|
|
26532
|
-
const data = block.split("\n").filter((
|
|
26563
|
+
const data = block.split("\n").filter((line2) => line2.startsWith("data:")).map((line2) => line2.slice(5).replace(/^ /, "")).join("\n");
|
|
26533
26564
|
if (data === "") return void 0;
|
|
26534
26565
|
try {
|
|
26535
26566
|
const parsed = JSON.parse(data);
|
|
@@ -26556,7 +26587,7 @@ async function failureOf(answer) {
|
|
|
26556
26587
|
}
|
|
26557
26588
|
|
|
26558
26589
|
// src/hooks.ts
|
|
26559
|
-
import {
|
|
26590
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
26560
26591
|
import { join as join7 } from "node:path";
|
|
26561
26592
|
var HOOK_NAME = "chips";
|
|
26562
26593
|
var SECRET_ENV = "CHIPS_HOOK_SECRET";
|
|
@@ -26564,9 +26595,9 @@ function hookUrl(port, profile) {
|
|
|
26564
26595
|
return `http://127.0.0.1:${port}${HOOK_PATH}${profile}`;
|
|
26565
26596
|
}
|
|
26566
26597
|
function linesOf2(text) {
|
|
26567
|
-
return text.split("\n").map((
|
|
26568
|
-
const indent =
|
|
26569
|
-
const words4 =
|
|
26598
|
+
return text.split("\n").map((line2) => {
|
|
26599
|
+
const indent = line2.length - line2.trimStart().length;
|
|
26600
|
+
const words4 = line2.trimStart().startsWith("#") ? "" : line2.trim().replace(/\s+#.*$/, "");
|
|
26570
26601
|
return { indent, words: words4 };
|
|
26571
26602
|
});
|
|
26572
26603
|
}
|
|
@@ -26593,12 +26624,7 @@ function entryLines(indent, url2, secret) {
|
|
|
26593
26624
|
function secretInEnv(home2, secret) {
|
|
26594
26625
|
const path = join7(home2, ".env");
|
|
26595
26626
|
if (!existsSync7(path)) return false;
|
|
26596
|
-
if (envValue(home2, SECRET_ENV)
|
|
26597
|
-
const text = readFileSync7(path, "utf8");
|
|
26598
|
-
const mode = statSync3(path).mode & 511;
|
|
26599
|
-
appendFileSync(path, `${text === "" || text.endsWith("\n") ? "" : "\n"}${SECRET_ENV}=${secret}
|
|
26600
|
-
`);
|
|
26601
|
-
chmodSync3(path, mode);
|
|
26627
|
+
if (envValue(home2, SECRET_ENV) === void 0) appendEnv(home2, SECRET_ENV, secret);
|
|
26602
26628
|
return true;
|
|
26603
26629
|
}
|
|
26604
26630
|
function addHook(home2, port, profile, secret) {
|
|
@@ -26608,15 +26634,15 @@ function addHook(home2, port, profile, secret) {
|
|
|
26608
26634
|
const text = existsSync7(path) ? readFileSync7(path, "utf8") : "";
|
|
26609
26635
|
const lines2 = linesOf2(text);
|
|
26610
26636
|
const ours2 = new RegExp(`^(?:-\\s*)?url:\\s*["']?${url2.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}["']?$`);
|
|
26611
|
-
if (lines2.some((
|
|
26612
|
-
const where = lines2.findIndex((
|
|
26637
|
+
if (lines2.some((line2) => ours2.test(line2.words))) return { state: "there", path };
|
|
26638
|
+
const where = lines2.findIndex((line2) => line2.indent === 0 && /^["']?hooks["']?\s*:/.test(line2.words));
|
|
26613
26639
|
const byHand = (why) => {
|
|
26614
26640
|
const inEnv2 = secretInEnv(home2, secret);
|
|
26615
26641
|
const shown2 = entryLines(" ", url2, inEnv2 ? { env: SECRET_ENV } : { inline: secret });
|
|
26616
26642
|
return { state: "by_hand", path, why, lines: ["hooks:", ...shown2].join("\n") };
|
|
26617
26643
|
};
|
|
26618
26644
|
const plain = /^(?:"[^"]+"|'[^']+'|[A-Za-z_][\w.-]*)\s*:(?:\s|$)/;
|
|
26619
|
-
if (lines2.some((
|
|
26645
|
+
if (lines2.some((line2) => line2.indent === 0 && line2.words !== "" && !plain.test(line2.words))) {
|
|
26620
26646
|
return byHand("its top level isn't laid out as this bridge expects");
|
|
26621
26647
|
}
|
|
26622
26648
|
if (where === -1) {
|
|
@@ -26636,20 +26662,20 @@ function addHook(home2, port, profile, secret) {
|
|
|
26636
26662
|
const head = lines2[where]?.words ?? "";
|
|
26637
26663
|
if (head.replace(/^["']?hooks["']?\s*:/, "").trim() !== "") return byHand("its hooks: is written on one line");
|
|
26638
26664
|
const children = [];
|
|
26639
|
-
for (const
|
|
26640
|
-
if (
|
|
26641
|
-
if (
|
|
26642
|
-
children.push(
|
|
26665
|
+
for (const line2 of lines2.slice(where + 1)) {
|
|
26666
|
+
if (line2.words === "") continue;
|
|
26667
|
+
if (line2.indent === 0) break;
|
|
26668
|
+
children.push(line2);
|
|
26643
26669
|
}
|
|
26644
26670
|
const indent = children[0]?.indent ?? 2;
|
|
26645
26671
|
const named = new RegExp(`^(?:-\\s*)?name:\\s*["']?${HOOK_NAME}["']?$`);
|
|
26646
|
-
if (children.some((
|
|
26672
|
+
if (children.some((line2) => named.test(line2.words))) {
|
|
26647
26673
|
return byHand(`its hooks have an entry called ${HOOK_NAME} with another address`);
|
|
26648
26674
|
}
|
|
26649
|
-
if (children.some((
|
|
26675
|
+
if (children.some((line2) => line2.indent === indent && /^["']?outbound["']?\s*:/.test(line2.words))) {
|
|
26650
26676
|
return byHand("its hooks already have an outbound list");
|
|
26651
26677
|
}
|
|
26652
|
-
if (children.some((
|
|
26678
|
+
if (children.some((line2) => line2.indent !== indent && line2.indent < indent) || children[0]?.words.startsWith("-")) {
|
|
26653
26679
|
return byHand("its hooks: isn't laid out as this bridge expects");
|
|
26654
26680
|
}
|
|
26655
26681
|
const inEnv = secretInEnv(home2, secret);
|
|
@@ -26659,8 +26685,642 @@ function addHook(home2, port, profile, secret) {
|
|
|
26659
26685
|
return { state: "added", path };
|
|
26660
26686
|
}
|
|
26661
26687
|
|
|
26688
|
+
// src/models.ts
|
|
26689
|
+
var MODEL_CHOICES_MAX2 = 40;
|
|
26690
|
+
var LABEL_MAX = 80;
|
|
26691
|
+
var DEFAULT_LABEL = "Hermes's default";
|
|
26692
|
+
function choiceId(provider, model) {
|
|
26693
|
+
return `${provider}/${model}`;
|
|
26694
|
+
}
|
|
26695
|
+
function runModelOf(id2) {
|
|
26696
|
+
const at = id2.indexOf("/");
|
|
26697
|
+
return at <= 0 ? { model: id2 } : { provider: id2.slice(0, at), model: id2.slice(at + 1) };
|
|
26698
|
+
}
|
|
26699
|
+
function providersOf(options) {
|
|
26700
|
+
const ready = (options.providers ?? []).filter(
|
|
26701
|
+
(provider) => provider.authenticated !== false && (provider.models ?? []).length > 0
|
|
26702
|
+
);
|
|
26703
|
+
const current = (provider) => provider.is_current === true || provider.slug === options.provider;
|
|
26704
|
+
return [...ready.filter(current), ...ready.filter((provider) => !current(provider))];
|
|
26705
|
+
}
|
|
26706
|
+
function choicesOf(options) {
|
|
26707
|
+
const choices = [];
|
|
26708
|
+
for (const provider of providersOf(options)) {
|
|
26709
|
+
for (const model of provider.models ?? []) {
|
|
26710
|
+
const id2 = choiceId(provider.slug, model);
|
|
26711
|
+
if (choices.some((choice) => choice.id === id2)) continue;
|
|
26712
|
+
choices.push({ id: id2, label: `${model} \xB7 ${provider.name ?? provider.slug}`.slice(0, LABEL_MAX), efforts: [] });
|
|
26713
|
+
}
|
|
26714
|
+
}
|
|
26715
|
+
return choices;
|
|
26716
|
+
}
|
|
26717
|
+
function defaultOf(options) {
|
|
26718
|
+
const model = options.model?.trim();
|
|
26719
|
+
if (model === void 0 || model === "") return void 0;
|
|
26720
|
+
return options.provider === void 0 || options.provider === "" ? model : choiceId(options.provider, model);
|
|
26721
|
+
}
|
|
26722
|
+
function matched(choices, model) {
|
|
26723
|
+
if (choices.some((choice) => choice.id === model)) return model;
|
|
26724
|
+
return choices.find((choice) => runModelOf(choice.id).model === model)?.id ?? model;
|
|
26725
|
+
}
|
|
26726
|
+
function copyOf(options, own2) {
|
|
26727
|
+
let choices = choicesOf(options);
|
|
26728
|
+
const fallback = defaultOf(options);
|
|
26729
|
+
const byDefault = fallback === void 0 ? void 0 : matched(choices, fallback);
|
|
26730
|
+
const mine = own2 === void 0 ? void 0 : matched(choices, own2);
|
|
26731
|
+
const model = mine ?? byDefault ?? choices[0]?.id;
|
|
26732
|
+
if (model === void 0) return void 0;
|
|
26733
|
+
const keep = [model, ...byDefault === void 0 ? [] : [byDefault]];
|
|
26734
|
+
for (const id2 of keep) {
|
|
26735
|
+
if (!choices.some((choice) => choice.id === id2)) choices = [{ id: id2, label: id2.slice(0, LABEL_MAX), efforts: [] }, ...choices];
|
|
26736
|
+
}
|
|
26737
|
+
let others = MODEL_CHOICES_MAX2 - keep.filter((id2, at) => keep.indexOf(id2) === at).length;
|
|
26738
|
+
choices = choices.filter((choice) => keep.includes(choice.id) || others-- > 0);
|
|
26739
|
+
return {
|
|
26740
|
+
model,
|
|
26741
|
+
own: mine !== void 0,
|
|
26742
|
+
...byDefault === void 0 ? {} : { default: { label: DEFAULT_LABEL, model: byDefault } },
|
|
26743
|
+
choices,
|
|
26744
|
+
editable: true
|
|
26745
|
+
};
|
|
26746
|
+
}
|
|
26747
|
+
|
|
26748
|
+
// src/profiles.ts
|
|
26749
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
26750
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, rmSync as rmSync4 } from "node:fs";
|
|
26751
|
+
import { createConnection } from "node:net";
|
|
26752
|
+
import { join as join9 } from "node:path";
|
|
26753
|
+
|
|
26754
|
+
// src/gateway.ts
|
|
26755
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
26756
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
26757
|
+
import { accessSync as accessSync2, appendFileSync as appendFileSync2, closeSync as closeSync2, constants as constants2, existsSync as existsSync8, openSync as openSync2, readFileSync as readFileSync8, readSync, statSync as statSync3 } from "node:fs";
|
|
26758
|
+
import { homedir as homedir5 } from "node:os";
|
|
26759
|
+
import { basename as basename4, delimiter as delimiter2, dirname as dirname5, join as join8, resolve as resolve3 } from "node:path";
|
|
26760
|
+
var TIMING = {
|
|
26761
|
+
stopMs: 15e3,
|
|
26762
|
+
killMs: 5e3,
|
|
26763
|
+
earlyMs: 1e4,
|
|
26764
|
+
upMs: 9e4,
|
|
26765
|
+
serviceMs: 15e4,
|
|
26766
|
+
pollMs: 500
|
|
26767
|
+
};
|
|
26768
|
+
var EXPAT = "/opt/homebrew/opt/expat/lib/libexpat.1.dylib";
|
|
26769
|
+
var EXPAT_SYMBOL = "_XML_SetAllocTrackerActivationThreshold";
|
|
26770
|
+
function gatewaysOf(agents) {
|
|
26771
|
+
return [...new Set(agents.map(({ address }) => address.url.replace(/\/+$/, "")))].map((url2) => ({ url: url2 }));
|
|
26772
|
+
}
|
|
26773
|
+
function isLocal(url2) {
|
|
26774
|
+
try {
|
|
26775
|
+
return ["127.0.0.1", "localhost", "[::1]", "::1"].includes(new URL(url2).hostname);
|
|
26776
|
+
} catch {
|
|
26777
|
+
return false;
|
|
26778
|
+
}
|
|
26779
|
+
}
|
|
26780
|
+
function portOf(url2) {
|
|
26781
|
+
const parsed = new URL(url2);
|
|
26782
|
+
return Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80));
|
|
26783
|
+
}
|
|
26784
|
+
function line(words4) {
|
|
26785
|
+
const one = words4.replace(/\s+/g, " ").trim();
|
|
26786
|
+
return one.length <= 200 ? one : `${one.slice(0, 199)}\u2026`;
|
|
26787
|
+
}
|
|
26788
|
+
function whyFrom(output2, code) {
|
|
26789
|
+
const lines2 = output2.split("\n").map((each) => each.trim()).filter((each) => each !== "");
|
|
26790
|
+
const why = lines2.findLast((each) => /^[A-Za-z_][\w.]*(Error|Exception)\b/.test(each)) ?? lines2.at(-1);
|
|
26791
|
+
if (why === void 0) return code === null || code === void 0 ? "it was stopped" : `it exited ${code}`;
|
|
26792
|
+
return why.length <= 200 ? why : why.replace(/\/(?:[^\s/(),:'"]+\/)+([^\s/(),:'"]+)/g, "$1");
|
|
26793
|
+
}
|
|
26794
|
+
function gatewayRunArgs(command) {
|
|
26795
|
+
const words4 = command.trim().split(/\s+/);
|
|
26796
|
+
const at = words4.findIndex((word) => basename4(word) === "hermes");
|
|
26797
|
+
if (at === -1) return void 0;
|
|
26798
|
+
const args = words4.slice(at + 1);
|
|
26799
|
+
const plain = [];
|
|
26800
|
+
for (let next = 0; next < args.length; next += 1) {
|
|
26801
|
+
const word = args[next] ?? "";
|
|
26802
|
+
if (word === "-p" || word === "--profile") next += 1;
|
|
26803
|
+
else if (!word.startsWith("-")) plain.push(word);
|
|
26804
|
+
}
|
|
26805
|
+
return plain[0] === "gateway" && plain[1] === "run" ? args : void 0;
|
|
26806
|
+
}
|
|
26807
|
+
function serviceOf(system, root2, env = {}) {
|
|
26808
|
+
const home2 = resolve3(root2);
|
|
26809
|
+
const native = resolve3(system.home, ".hermes");
|
|
26810
|
+
const suffix = home2 === native ? "" : dirname5(home2) === join8(native, "profiles") ? basename4(home2) : createHash3("sha256").update(home2).digest("hex").slice(0, 8);
|
|
26811
|
+
if (system.platform === "darwin") {
|
|
26812
|
+
const path = join8(system.home, "Library", "LaunchAgents", `ai.hermes.gateway${suffix ? `-${suffix}` : ""}.plist`);
|
|
26813
|
+
return system.exists(path) ? { kind: "launchd", path } : void 0;
|
|
26814
|
+
}
|
|
26815
|
+
if (system.platform === "linux") {
|
|
26816
|
+
const unit = `hermes-gateway${suffix ? `-${suffix}` : ""}.service`;
|
|
26817
|
+
const user = join8(env["XDG_CONFIG_HOME"] || join8(system.home, ".config"), "systemd", "user", unit);
|
|
26818
|
+
if (system.exists(user)) return { kind: "systemd", path: user };
|
|
26819
|
+
const machine = join8("/etc/systemd/system", unit);
|
|
26820
|
+
if (system.exists(machine)) return { kind: "systemd", path: machine, system: true };
|
|
26821
|
+
}
|
|
26822
|
+
return void 0;
|
|
26823
|
+
}
|
|
26824
|
+
function expand(text, env) {
|
|
26825
|
+
let out = "";
|
|
26826
|
+
let at = 0;
|
|
26827
|
+
while (at < text.length) {
|
|
26828
|
+
const char = text[at] ?? "";
|
|
26829
|
+
if (char === "\\" && at + 1 < text.length) {
|
|
26830
|
+
out += text[at + 1];
|
|
26831
|
+
at += 2;
|
|
26832
|
+
} else if (char !== "$") {
|
|
26833
|
+
out += char;
|
|
26834
|
+
at += 1;
|
|
26835
|
+
} else if (text[at + 1] === "{") {
|
|
26836
|
+
let depth = 1;
|
|
26837
|
+
let end2 = at + 2;
|
|
26838
|
+
for (; end2 < text.length; end2 += 1) {
|
|
26839
|
+
if (text[end2] === "{") depth += 1;
|
|
26840
|
+
else if (text[end2] === "}" && --depth === 0) break;
|
|
26841
|
+
}
|
|
26842
|
+
const inner = /^([A-Za-z_][A-Za-z0-9_]*)(?:(:?)-([\s\S]*))?$/.exec(text.slice(at + 2, end2));
|
|
26843
|
+
if (inner !== null) {
|
|
26844
|
+
const value = env[inner[1] ?? ""];
|
|
26845
|
+
const unset = inner[2] === ":" ? value === void 0 || value === "" : value === void 0;
|
|
26846
|
+
out += inner[3] !== void 0 && unset ? expand(inner[3], env) : value ?? "";
|
|
26847
|
+
}
|
|
26848
|
+
at = end2 + 1;
|
|
26849
|
+
} else {
|
|
26850
|
+
const name2 = /^[A-Za-z_][A-Za-z0-9_]*/.exec(text.slice(at + 1))?.[0];
|
|
26851
|
+
out += name2 === void 0 ? "$" : env[name2] ?? "";
|
|
26852
|
+
at += 1 + (name2?.length ?? 0);
|
|
26853
|
+
}
|
|
26854
|
+
}
|
|
26855
|
+
return out;
|
|
26856
|
+
}
|
|
26857
|
+
function wrapperOf(text, env) {
|
|
26858
|
+
for (const each of text.split("\n")) {
|
|
26859
|
+
const found = /^\s*((?:[A-Za-z_][A-Za-z0-9_]*="(?:[^"\\]|\\.)*"\s+)*)exec\s+"([^"]+)"/.exec(each);
|
|
26860
|
+
if (found === null) continue;
|
|
26861
|
+
const vars = {};
|
|
26862
|
+
for (const set2 of (found[1] ?? "").matchAll(/([A-Za-z_][A-Za-z0-9_]*)="((?:[^"\\]|\\.)*)"/g)) {
|
|
26863
|
+
vars[set2[1] ?? ""] = expand(set2[2] ?? "", env);
|
|
26864
|
+
}
|
|
26865
|
+
return { entry: expand(found[2] ?? "", env), vars };
|
|
26866
|
+
}
|
|
26867
|
+
return void 0;
|
|
26868
|
+
}
|
|
26869
|
+
var NO_HERMES = "The hermes command isn't on this machine's PATH, so Hermes's gateway can't be started.";
|
|
26870
|
+
var messageOf2 = (error62) => error62 instanceof Error ? error62.message : String(error62);
|
|
26871
|
+
function gatewayEnv(env) {
|
|
26872
|
+
return Object.fromEntries(Object.entries(env).filter(([name2]) => !name2.startsWith("CHIPS_")));
|
|
26873
|
+
}
|
|
26874
|
+
async function runHermes(system, env, args, timeoutMs, log2) {
|
|
26875
|
+
const hermes = system.which("hermes", env);
|
|
26876
|
+
if (hermes === void 0) return { code: null, output: "The hermes command isn't on this machine's PATH." };
|
|
26877
|
+
const plain = gatewayEnv(env);
|
|
26878
|
+
const ran = await system.run(hermes, args, plain, timeoutMs);
|
|
26879
|
+
if (ran.code === 0 || !ran.output.includes(EXPAT_SYMBOL) || !system.exists(EXPAT)) return ran;
|
|
26880
|
+
const wrapper = wrapperOf(system.read(hermes) ?? "", plain);
|
|
26881
|
+
if (wrapper === void 0) return ran;
|
|
26882
|
+
const expat = dirname5(EXPAT);
|
|
26883
|
+
log2(
|
|
26884
|
+
`hermes ${args.slice(0, 2).join(" ")} stopped at once: Homebrew's Python can't load macOS's expat (${EXPAT_SYMBOL}). Running ${wrapper.entry} directly with DYLD_LIBRARY_PATH=${expat}.`
|
|
26885
|
+
);
|
|
26886
|
+
const libraries = [expat, plain["DYLD_LIBRARY_PATH"]].filter((each) => each !== void 0 && each !== "").join(":");
|
|
26887
|
+
return system.run(wrapper.entry, args, { ...plain, ...wrapper.vars, DYLD_LIBRARY_PATH: libraries }, timeoutMs);
|
|
26888
|
+
}
|
|
26889
|
+
var Gateways = class {
|
|
26890
|
+
constructor(options) {
|
|
26891
|
+
this.options = options;
|
|
26892
|
+
}
|
|
26893
|
+
options;
|
|
26894
|
+
timing = TIMING;
|
|
26895
|
+
/** Each gateway restarted, one after another: what went wrong, in a line, or nothing. */
|
|
26896
|
+
async restart() {
|
|
26897
|
+
const troubles = [];
|
|
26898
|
+
for (const gateway of this.options.gateways) {
|
|
26899
|
+
let trouble;
|
|
26900
|
+
try {
|
|
26901
|
+
trouble = await this.restartOne(gateway);
|
|
26902
|
+
} catch (error62) {
|
|
26903
|
+
trouble = `Restart failed: ${messageOf2(error62)}`;
|
|
26904
|
+
}
|
|
26905
|
+
if (trouble !== void 0) troubles.push(trouble);
|
|
26906
|
+
}
|
|
26907
|
+
return troubles.length === 0 ? void 0 : line(troubles.join(" "));
|
|
26908
|
+
}
|
|
26909
|
+
/** Each local gateway that doesn't answer, started: the bridge is starting. */
|
|
26910
|
+
async startIfDown() {
|
|
26911
|
+
const { system, log: log2 } = this.options;
|
|
26912
|
+
for (const gateway of this.options.gateways) {
|
|
26913
|
+
if (!isLocal(gateway.url)) continue;
|
|
26914
|
+
const port = portOf(gateway.url);
|
|
26915
|
+
try {
|
|
26916
|
+
if (await system.healthy(gateway.url)) continue;
|
|
26917
|
+
let trouble;
|
|
26918
|
+
if (this.serviceFor(gateway) !== void 0) {
|
|
26919
|
+
log2(`Hermes isn't answering on port ${port}: starting its service, hermes gateway start`);
|
|
26920
|
+
trouble = await this.byService(gateway, "start", void 0);
|
|
26921
|
+
} else {
|
|
26922
|
+
const pid = await system.listener(port);
|
|
26923
|
+
if (pid !== void 0) {
|
|
26924
|
+
log2(`Hermes isn't answering on port ${port}, but pid ${pid} listens there: no second gateway started`);
|
|
26925
|
+
continue;
|
|
26926
|
+
}
|
|
26927
|
+
log2(`Hermes isn't answering on port ${port}: starting hermes gateway run`);
|
|
26928
|
+
trouble = await this.start(gateway, ["gateway", "run"], void 0);
|
|
26929
|
+
}
|
|
26930
|
+
if (trouble !== void 0) log2(trouble);
|
|
26931
|
+
} catch (error62) {
|
|
26932
|
+
log2(`Hermes's gateway on port ${port} couldn't be started: ${messageOf2(error62)}`);
|
|
26933
|
+
}
|
|
26934
|
+
}
|
|
26935
|
+
}
|
|
26936
|
+
/**
|
|
26937
|
+
* The service of Hermes's root, for the Main agent's gateway: a gateway at another address is
|
|
26938
|
+
* another profile's own, which `hermes gateway status` of the root doesn't speak for.
|
|
26939
|
+
*/
|
|
26940
|
+
serviceFor(gateway) {
|
|
26941
|
+
if (gateway !== this.options.gateways[0]) return void 0;
|
|
26942
|
+
return serviceOf(this.options.system, this.options.root, this.options.env);
|
|
26943
|
+
}
|
|
26944
|
+
async restartOne(gateway) {
|
|
26945
|
+
const { system, log: log2 } = this.options;
|
|
26946
|
+
if (!isLocal(gateway.url)) {
|
|
26947
|
+
return `Hermes's gateway at ${new URL(gateway.url).host} isn't on this machine, so it can't be restarted from here.`;
|
|
26948
|
+
}
|
|
26949
|
+
const port = portOf(gateway.url);
|
|
26950
|
+
const pid = await system.listener(port);
|
|
26951
|
+
if (this.serviceFor(gateway) !== void 0) {
|
|
26952
|
+
log2("restarting Hermes's service: hermes gateway restart");
|
|
26953
|
+
return this.byService(gateway, "restart", pid);
|
|
26954
|
+
}
|
|
26955
|
+
if (pid === void 0) {
|
|
26956
|
+
if (await system.healthy(gateway.url)) return `Can't tell what answers on port ${port}, so nothing was restarted.`;
|
|
26957
|
+
log2(`nothing listens on port ${port}: starting hermes gateway run`);
|
|
26958
|
+
return this.start(gateway, ["gateway", "run"], void 0);
|
|
26959
|
+
}
|
|
26960
|
+
const info = await system.describe(pid);
|
|
26961
|
+
if (info === void 0) return this.start(gateway, ["gateway", "run"], void 0);
|
|
26962
|
+
const args = this.ours(info);
|
|
26963
|
+
if (args === void 0) {
|
|
26964
|
+
log2(`pid ${pid} on port ${port} isn't this user's hermes gateway run, so it was left alone: ${info.command.slice(0, 160)}`);
|
|
26965
|
+
return `Port ${port} is held by something other than hermes gateway run (pid ${pid}), so nothing was restarted.`;
|
|
26966
|
+
}
|
|
26967
|
+
if (system.which("hermes", this.options.env) === void 0) return NO_HERMES;
|
|
26968
|
+
log2(`stopping Hermes's gateway, pid ${pid}`);
|
|
26969
|
+
if (!await this.stop(pid)) return `Hermes's gateway (pid ${pid}) didn't stop, so it wasn't started again.`;
|
|
26970
|
+
return this.start(gateway, args, info.cwd);
|
|
26971
|
+
}
|
|
26972
|
+
/** A `hermes gateway run` of this user's: the words to start it again with. */
|
|
26973
|
+
ours(info) {
|
|
26974
|
+
return info.uid === this.options.system.uid ? gatewayRunArgs(info.command) : void 0;
|
|
26975
|
+
}
|
|
26976
|
+
/** SIGTERM, then SIGKILL once it has had a while, and only while it is still that gateway. */
|
|
26977
|
+
async stop(pid) {
|
|
26978
|
+
const { system, log: log2 } = this.options;
|
|
26979
|
+
system.signal(pid, "SIGTERM");
|
|
26980
|
+
if (await this.gone(pid, this.timing.stopMs)) return true;
|
|
26981
|
+
const still = await system.describe(pid);
|
|
26982
|
+
if (still === void 0) return true;
|
|
26983
|
+
if (this.ours(still) === void 0) return false;
|
|
26984
|
+
log2(`Hermes's gateway, pid ${pid}, is still running ${this.timing.stopMs / 1e3} s after SIGTERM: SIGKILL`);
|
|
26985
|
+
system.signal(pid, "SIGKILL");
|
|
26986
|
+
return this.gone(pid, this.timing.killMs);
|
|
26987
|
+
}
|
|
26988
|
+
async gone(pid, withinMs) {
|
|
26989
|
+
const { system } = this.options;
|
|
26990
|
+
const by = system.now() + withinMs;
|
|
26991
|
+
while (system.alive(pid)) {
|
|
26992
|
+
if (system.now() >= by) return false;
|
|
26993
|
+
await system.sleep(this.timing.pollMs);
|
|
26994
|
+
}
|
|
26995
|
+
return true;
|
|
26996
|
+
}
|
|
26997
|
+
/** Hermes's own `hermes gateway restart|start`, then the gateway answering, from a new process. */
|
|
26998
|
+
async byService(gateway, verb, before) {
|
|
26999
|
+
const { system, env } = this.options;
|
|
27000
|
+
const hermes = system.which("hermes", env);
|
|
27001
|
+
if (hermes === void 0) return NO_HERMES;
|
|
27002
|
+
const scope = this.serviceFor(gateway)?.system === true ? ["--system"] : [];
|
|
27003
|
+
const ran = await system.run(hermes, ["gateway", verb, ...scope], gatewayEnv(env), this.timing.serviceMs);
|
|
27004
|
+
if (ran.code !== 0) return line(`hermes gateway ${verb} failed: ${whyFrom(ran.output, ran.code)}`);
|
|
27005
|
+
const by = system.now() + this.timing.serviceMs;
|
|
27006
|
+
for (; ; ) {
|
|
27007
|
+
const old = before !== void 0 && system.alive(before);
|
|
27008
|
+
if (!old && await system.healthy(gateway.url)) return void 0;
|
|
27009
|
+
if (system.now() >= by) {
|
|
27010
|
+
if (await system.healthy(gateway.url)) return void 0;
|
|
27011
|
+
return `Hermes's service ${verb === "start" ? "started" : "restarted"}, but nothing answers on port ${portOf(gateway.url)}.`;
|
|
27012
|
+
}
|
|
27013
|
+
await system.sleep(this.timing.pollMs);
|
|
27014
|
+
}
|
|
27015
|
+
}
|
|
27016
|
+
/**
|
|
27017
|
+
* `hermes <args>` (`gateway run`), on its own, in `cwd` (else the home), then through Homebrew's
|
|
27018
|
+
* expat when it died of macOS's.
|
|
27019
|
+
*/
|
|
27020
|
+
async start(gateway, args, cwd) {
|
|
27021
|
+
const { system, env, log: log2 } = this.options;
|
|
27022
|
+
const port = portOf(gateway.url);
|
|
27023
|
+
const hermes = system.which("hermes", env);
|
|
27024
|
+
if (hermes === void 0) return NO_HERMES;
|
|
27025
|
+
const folder2 = cwd ?? system.home;
|
|
27026
|
+
const plain = gatewayEnv(env);
|
|
27027
|
+
let outcome = await this.launch(gateway, hermes, args, plain, folder2, `hermes ${args.join(" ")}`);
|
|
27028
|
+
if (outcome.state === "ended" && outcome.early && outcome.output.includes(EXPAT_SYMBOL) && system.exists(EXPAT)) {
|
|
27029
|
+
const wrapper = wrapperOf(system.read(hermes) ?? "", plain);
|
|
27030
|
+
if (wrapper !== void 0) {
|
|
27031
|
+
const expat = dirname5(EXPAT);
|
|
27032
|
+
log2(
|
|
27033
|
+
`hermes gateway run stopped at once: Homebrew's Python can't load macOS's expat (${EXPAT_SYMBOL}). Starting ${wrapper.entry} directly with DYLD_LIBRARY_PATH=${expat}.`
|
|
27034
|
+
);
|
|
27035
|
+
const libraries = [expat, plain["DYLD_LIBRARY_PATH"]].filter((each) => each !== void 0 && each !== "").join(":");
|
|
27036
|
+
outcome = await this.launch(
|
|
27037
|
+
gateway,
|
|
27038
|
+
wrapper.entry,
|
|
27039
|
+
args,
|
|
27040
|
+
{ ...plain, ...wrapper.vars, DYLD_LIBRARY_PATH: libraries },
|
|
27041
|
+
folder2,
|
|
27042
|
+
`${wrapper.entry} ${args.join(" ")} with DYLD_LIBRARY_PATH=${expat}`
|
|
27043
|
+
);
|
|
27044
|
+
}
|
|
27045
|
+
}
|
|
27046
|
+
if (outcome.state === "up") {
|
|
27047
|
+
log2(`Hermes's gateway${outcome.pid === void 0 ? "" : `, pid ${outcome.pid},`} answers on port ${port}. It logs to ${this.options.logFile}.`);
|
|
27048
|
+
return void 0;
|
|
27049
|
+
}
|
|
27050
|
+
if (outcome.state === "silent") return `Hermes's gateway started, but nothing answers on port ${port}.`;
|
|
27051
|
+
return line(`Hermes's gateway stopped as it started: ${whyFrom(outcome.output, outcome.code)}`);
|
|
27052
|
+
}
|
|
27053
|
+
/** A program started, and watched until it answers, ends, or has had its time. */
|
|
27054
|
+
async launch(gateway, command, args, env, cwd, what) {
|
|
27055
|
+
const { system, logFile } = this.options;
|
|
27056
|
+
system.note(logFile, `--- ${new Date(system.now()).toISOString()} chips-hermes started ${what}`);
|
|
27057
|
+
const from = system.size(logFile);
|
|
27058
|
+
const startedAt = system.now();
|
|
27059
|
+
const child = system.launch({ command, args, env, cwd, log: logFile });
|
|
27060
|
+
let ended;
|
|
27061
|
+
void child.exited.then(({ code }) => {
|
|
27062
|
+
ended = { code, at: system.now() };
|
|
27063
|
+
});
|
|
27064
|
+
for (; ; ) {
|
|
27065
|
+
await Promise.resolve();
|
|
27066
|
+
if (ended !== void 0) {
|
|
27067
|
+
return { state: "ended", early: ended.at - startedAt <= this.timing.earlyMs, output: system.since(logFile, from), code: ended.code };
|
|
27068
|
+
}
|
|
27069
|
+
if (await system.healthy(gateway.url)) return { state: "up", pid: child.pid };
|
|
27070
|
+
if (system.now() - startedAt >= this.timing.upMs) return { state: "silent" };
|
|
27071
|
+
await system.sleep(this.timing.pollMs);
|
|
27072
|
+
}
|
|
27073
|
+
}
|
|
27074
|
+
};
|
|
27075
|
+
function restarter(deps) {
|
|
27076
|
+
let running;
|
|
27077
|
+
return (ask2) => {
|
|
27078
|
+
if (running !== void 0) {
|
|
27079
|
+
deps.log(`restart asked by ${ask2.askedBy} while one runs: it is the same restart`);
|
|
27080
|
+
return running;
|
|
27081
|
+
}
|
|
27082
|
+
running = (async () => {
|
|
27083
|
+
deps.log(`restart asked by ${ask2.askedBy}: restarting Hermes's gateway`);
|
|
27084
|
+
deps.hold(true);
|
|
27085
|
+
let trouble;
|
|
27086
|
+
try {
|
|
27087
|
+
trouble = await deps.restart();
|
|
27088
|
+
} catch (error62) {
|
|
27089
|
+
trouble = line(`Restart failed: ${error62 instanceof Error ? error62.message : String(error62)}`);
|
|
27090
|
+
}
|
|
27091
|
+
deps.log(trouble === void 0 ? "restarted, and Hermes answers" : `restart: ${trouble}`);
|
|
27092
|
+
try {
|
|
27093
|
+
await deps.say(trouble);
|
|
27094
|
+
} catch (error62) {
|
|
27095
|
+
deps.log(`could not tell Chips how the restart went: ${String(error62)}`);
|
|
27096
|
+
} finally {
|
|
27097
|
+
deps.hold(false);
|
|
27098
|
+
}
|
|
27099
|
+
})().finally(() => {
|
|
27100
|
+
running = void 0;
|
|
27101
|
+
});
|
|
27102
|
+
return running;
|
|
27103
|
+
};
|
|
27104
|
+
}
|
|
27105
|
+
function gatewayLog(configFolder, platform = process.platform) {
|
|
27106
|
+
return platform === "darwin" ? join8(homedir5(), "Library", "Logs", "chips-hermes-gateway.log") : join8(configFolder, "gateway.log");
|
|
27107
|
+
}
|
|
27108
|
+
function runToEnd(command, args, options) {
|
|
27109
|
+
return new Promise((done) => {
|
|
27110
|
+
const child = spawn2(command, args, { env: options.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
27111
|
+
let output2 = "";
|
|
27112
|
+
const keep = (data) => {
|
|
27113
|
+
output2 = (output2 + String(data)).slice(-8e3);
|
|
27114
|
+
};
|
|
27115
|
+
child.stdout.on("data", keep);
|
|
27116
|
+
child.stderr.on("data", keep);
|
|
27117
|
+
const limit = setTimeout(() => child.kill("SIGTERM"), options.timeoutMs);
|
|
27118
|
+
child.on("error", (error62) => {
|
|
27119
|
+
clearTimeout(limit);
|
|
27120
|
+
done({ code: null, output: error62.message });
|
|
27121
|
+
});
|
|
27122
|
+
child.on("close", (code) => {
|
|
27123
|
+
clearTimeout(limit);
|
|
27124
|
+
done({ code, output: output2 });
|
|
27125
|
+
});
|
|
27126
|
+
});
|
|
27127
|
+
}
|
|
27128
|
+
function thisMachine() {
|
|
27129
|
+
return {
|
|
27130
|
+
platform: process.platform,
|
|
27131
|
+
uid: process.getuid?.() ?? -1,
|
|
27132
|
+
home: homedir5(),
|
|
27133
|
+
exists: existsSync8,
|
|
27134
|
+
read(path) {
|
|
27135
|
+
try {
|
|
27136
|
+
return readFileSync8(path, "utf8");
|
|
27137
|
+
} catch {
|
|
27138
|
+
return void 0;
|
|
27139
|
+
}
|
|
27140
|
+
},
|
|
27141
|
+
which(command, env) {
|
|
27142
|
+
for (const folder2 of (env["PATH"] ?? "").split(delimiter2)) {
|
|
27143
|
+
if (folder2 === "") continue;
|
|
27144
|
+
const path = join8(folder2, command);
|
|
27145
|
+
try {
|
|
27146
|
+
accessSync2(path, constants2.X_OK);
|
|
27147
|
+
return path;
|
|
27148
|
+
} catch {
|
|
27149
|
+
}
|
|
27150
|
+
}
|
|
27151
|
+
return void 0;
|
|
27152
|
+
},
|
|
27153
|
+
async listener(port) {
|
|
27154
|
+
const { output: output2 } = await runToEnd("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-Fp"], { timeoutMs: 5e3 });
|
|
27155
|
+
const pid = Number(/^p(\d+)$/m.exec(output2)?.[1]);
|
|
27156
|
+
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
27157
|
+
},
|
|
27158
|
+
async describe(pid) {
|
|
27159
|
+
const { code, output: output2 } = await runToEnd("ps", ["-ww", "-o", "uid=", "-o", "command=", "-p", String(pid)], { timeoutMs: 5e3 });
|
|
27160
|
+
const found = /^\s*(\d+)\s+(.*)$/m.exec(output2);
|
|
27161
|
+
if (code !== 0 || found === null) return void 0;
|
|
27162
|
+
return { uid: Number(found[1]), command: found[2] ?? "", cwd: processFolder(pid) };
|
|
27163
|
+
},
|
|
27164
|
+
alive(pid) {
|
|
27165
|
+
try {
|
|
27166
|
+
process.kill(pid, 0);
|
|
27167
|
+
return true;
|
|
27168
|
+
} catch (error62) {
|
|
27169
|
+
return error62.code === "EPERM";
|
|
27170
|
+
}
|
|
27171
|
+
},
|
|
27172
|
+
signal(pid, signal) {
|
|
27173
|
+
try {
|
|
27174
|
+
process.kill(pid, signal);
|
|
27175
|
+
} catch {
|
|
27176
|
+
}
|
|
27177
|
+
},
|
|
27178
|
+
run: (command, args, env, timeoutMs) => runToEnd(command, args, { env, timeoutMs }),
|
|
27179
|
+
launch({ command, args, env, cwd, log: log2 }) {
|
|
27180
|
+
const out = openSync2(log2, "a");
|
|
27181
|
+
try {
|
|
27182
|
+
const child = spawn2(command, args, { cwd, env, detached: true, stdio: ["ignore", out, out] });
|
|
27183
|
+
const exited = new Promise((done) => {
|
|
27184
|
+
child.once("exit", (code, signal) => done({ code, signal }));
|
|
27185
|
+
child.once("error", (error62) => {
|
|
27186
|
+
appendFileSync2(log2, `${error62.message}
|
|
27187
|
+
`);
|
|
27188
|
+
done({ code: null, signal: null });
|
|
27189
|
+
});
|
|
27190
|
+
});
|
|
27191
|
+
child.unref();
|
|
27192
|
+
return { pid: child.pid, exited };
|
|
27193
|
+
} finally {
|
|
27194
|
+
closeSync2(out);
|
|
27195
|
+
}
|
|
27196
|
+
},
|
|
27197
|
+
note(log2, words4) {
|
|
27198
|
+
appendFileSync2(log2, `${words4}
|
|
27199
|
+
`);
|
|
27200
|
+
},
|
|
27201
|
+
size(log2) {
|
|
27202
|
+
try {
|
|
27203
|
+
return statSync3(log2).size;
|
|
27204
|
+
} catch {
|
|
27205
|
+
return 0;
|
|
27206
|
+
}
|
|
27207
|
+
},
|
|
27208
|
+
since(log2, from) {
|
|
27209
|
+
try {
|
|
27210
|
+
const to = statSync3(log2).size;
|
|
27211
|
+
const start = Math.max(from, to - 64e3);
|
|
27212
|
+
const buffer = Buffer.alloc(Math.max(0, to - start));
|
|
27213
|
+
const fd = openSync2(log2, "r");
|
|
27214
|
+
try {
|
|
27215
|
+
readSync(fd, buffer, 0, buffer.length, start);
|
|
27216
|
+
} finally {
|
|
27217
|
+
closeSync2(fd);
|
|
27218
|
+
}
|
|
27219
|
+
return buffer.toString("utf8");
|
|
27220
|
+
} catch {
|
|
27221
|
+
return "";
|
|
27222
|
+
}
|
|
27223
|
+
},
|
|
27224
|
+
healthy: (url2) => new HermesClient({ url: url2, key: "" }, (input2, init) => fetch(input2, { ...init, signal: AbortSignal.timeout(5e3) })).healthy(),
|
|
27225
|
+
sleep: (ms) => new Promise((done) => setTimeout(done, ms)),
|
|
27226
|
+
now: () => Date.now()
|
|
27227
|
+
};
|
|
27228
|
+
}
|
|
27229
|
+
|
|
27230
|
+
// src/profiles.ts
|
|
27231
|
+
var KEY_ENV = "API_SERVER_KEY";
|
|
27232
|
+
var RESERVED = /* @__PURE__ */ new Set(["hermes", "default", "test", "tmp", "root", "sudo"]);
|
|
27233
|
+
var CREATE_MS = 12e4;
|
|
27234
|
+
function freeProfile(root2, name2, taken) {
|
|
27235
|
+
const slug = name2.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 56).replace(/^-+|-+$/g, "") || "agent";
|
|
27236
|
+
for (let count = 1; ; count += 1) {
|
|
27237
|
+
if (count === 1 && RESERVED.has(slug)) continue;
|
|
27238
|
+
const profile = count === 1 ? slug : `${slug}-${count}`;
|
|
27239
|
+
if (!taken.includes(profile) && !existsSync9(join9(root2, "profiles", profile))) return profile;
|
|
27240
|
+
}
|
|
27241
|
+
}
|
|
27242
|
+
function readyProfile(home2, names, hook) {
|
|
27243
|
+
for (const file3 of ["MEMORY.md", "USER.md"]) rmSync4(join9(home2, "memories", file3), { force: true });
|
|
27244
|
+
if (envValue(home2, KEY_ENV) === void 0) appendEnv(home2, KEY_ENV, randomBytes2(32).toString("hex"));
|
|
27245
|
+
if (hook === void 0) return void 0;
|
|
27246
|
+
const path = join9(home2, "config.yaml");
|
|
27247
|
+
if (existsSync9(path)) {
|
|
27248
|
+
const theirs = hookUrl(hook.port, names.from).replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
27249
|
+
const entry = new RegExp(`^(\\s*(?:-\\s*)?url:\\s*["']?)${theirs}(["']?\\s*(?:#.*)?)$`, "m");
|
|
27250
|
+
const text = readFileSync9(path, "utf8");
|
|
27251
|
+
if (entry.test(text)) writeWhole(path, text.replace(entry, `$1${hookUrl(hook.port, names.profile)}$2`));
|
|
27252
|
+
}
|
|
27253
|
+
return addHook(home2, hook.port, names.profile, hook.secret);
|
|
27254
|
+
}
|
|
27255
|
+
function hermesProfiles(options) {
|
|
27256
|
+
return {
|
|
27257
|
+
async create(name2, from) {
|
|
27258
|
+
const args = ["profile", "create", name2, "--clone-from", from, "--no-alias"];
|
|
27259
|
+
const ran = await runHermes(options.system, options.env, args, CREATE_MS, options.log);
|
|
27260
|
+
if (ran.code !== 0) throw new Error(whyFrom(ran.output, ran.code).replace(/^Error:\s*/, ""));
|
|
27261
|
+
},
|
|
27262
|
+
async serve() {
|
|
27263
|
+
const result = await askGateway(options.root, "rescan-profiles");
|
|
27264
|
+
return typeof result?.["multiplex"] === "boolean" ? result["multiplex"] : void 0;
|
|
27265
|
+
}
|
|
27266
|
+
};
|
|
27267
|
+
}
|
|
27268
|
+
function controlSocketOf(root2) {
|
|
27269
|
+
const direct = join9(root2, "gateway.sock");
|
|
27270
|
+
if (existsSync9(direct)) return direct;
|
|
27271
|
+
try {
|
|
27272
|
+
const pointed = readFileSync9(join9(root2, "gateway.sock.path"), "utf8").trim();
|
|
27273
|
+
return pointed !== "" && existsSync9(pointed) ? pointed : void 0;
|
|
27274
|
+
} catch {
|
|
27275
|
+
return void 0;
|
|
27276
|
+
}
|
|
27277
|
+
}
|
|
27278
|
+
function askGateway(root2, verb, timeoutMs = 8e3) {
|
|
27279
|
+
const path = controlSocketOf(root2);
|
|
27280
|
+
if (path === void 0) return Promise.resolve(void 0);
|
|
27281
|
+
return new Promise((done) => {
|
|
27282
|
+
let heard = "";
|
|
27283
|
+
let over = false;
|
|
27284
|
+
const socket = createConnection(path);
|
|
27285
|
+
const finish = (line2) => {
|
|
27286
|
+
if (over) return;
|
|
27287
|
+
over = true;
|
|
27288
|
+
clearTimeout(timer);
|
|
27289
|
+
socket.destroy();
|
|
27290
|
+
done(line2 === void 0 ? void 0 : resultOf(line2));
|
|
27291
|
+
};
|
|
27292
|
+
const timer = setTimeout(() => finish(void 0), timeoutMs);
|
|
27293
|
+
socket.on("connect", () => socket.write(`${JSON.stringify({ verb, id: 1, protocol: 1 })}
|
|
27294
|
+
`));
|
|
27295
|
+
socket.on("data", (chunk) => {
|
|
27296
|
+
heard += String(chunk);
|
|
27297
|
+
const end2 = heard.indexOf("\n");
|
|
27298
|
+
if (end2 >= 0) finish(heard.slice(0, end2));
|
|
27299
|
+
});
|
|
27300
|
+
socket.on("end", () => finish(heard));
|
|
27301
|
+
socket.on("error", () => finish(void 0));
|
|
27302
|
+
});
|
|
27303
|
+
}
|
|
27304
|
+
function resultOf(line2) {
|
|
27305
|
+
try {
|
|
27306
|
+
const answer = JSON.parse(line2);
|
|
27307
|
+
return answer.ok === true && typeof answer.result === "object" && answer.result !== null ? answer.result : void 0;
|
|
27308
|
+
} catch {
|
|
27309
|
+
return void 0;
|
|
27310
|
+
}
|
|
27311
|
+
}
|
|
27312
|
+
|
|
26662
27313
|
// src/bridge.ts
|
|
26663
|
-
var WANTS = [
|
|
27314
|
+
var WANTS = [
|
|
27315
|
+
"agents",
|
|
27316
|
+
"new_spaces",
|
|
27317
|
+
"new_agents",
|
|
27318
|
+
"forks",
|
|
27319
|
+
"context_files",
|
|
27320
|
+
"models",
|
|
27321
|
+
"context_windows",
|
|
27322
|
+
"restarts"
|
|
27323
|
+
];
|
|
26664
27324
|
function settingsFor(active) {
|
|
26665
27325
|
const bridge2 = () => {
|
|
26666
27326
|
const there = active();
|
|
@@ -26669,6 +27329,7 @@ function settingsFor(active) {
|
|
|
26669
27329
|
};
|
|
26670
27330
|
return {
|
|
26671
27331
|
context: (edit) => bridge2().onContextEdit(edit),
|
|
27332
|
+
models: (change) => bridge2().onModelChange(change),
|
|
26672
27333
|
contextWindows: {
|
|
26673
27334
|
compact: async (ask2) => bridge2().onWindowCompact(ask2),
|
|
26674
27335
|
change: async (ask2) => bridge2().onWindowChange(ask2)
|
|
@@ -26686,7 +27347,7 @@ function instructionsFor(folder2, project = false) {
|
|
|
26686
27347
|
function freeFolder(root2, name2, taken) {
|
|
26687
27348
|
const slug = name2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
26688
27349
|
for (let count = 1; ; count += 1) {
|
|
26689
|
-
const folder2 =
|
|
27350
|
+
const folder2 = join10(root2, count === 1 ? slug : `${slug}-${count}`);
|
|
26690
27351
|
if (!taken.includes(folder2)) return folder2;
|
|
26691
27352
|
}
|
|
26692
27353
|
}
|
|
@@ -26725,7 +27386,7 @@ var Bridge = class {
|
|
|
26725
27386
|
agent: (agentId2, copy) => chips2.settings.context(agentId2, copy),
|
|
26726
27387
|
harness: (copy) => chips2.settings.harnessContext(copy)
|
|
26727
27388
|
},
|
|
26728
|
-
(
|
|
27389
|
+
(line2) => this.log(`context: ${line2}`),
|
|
26729
27390
|
options.env ?? process.env
|
|
26730
27391
|
);
|
|
26731
27392
|
if (options.fills === void 0) return;
|
|
@@ -26740,7 +27401,7 @@ var Bridge = class {
|
|
|
26740
27401
|
send: (agentId2, copy) => chips2.settings.contextWindow(agentId2, copy),
|
|
26741
27402
|
kept,
|
|
26742
27403
|
save: () => options.saveState(options.state),
|
|
26743
|
-
log: (
|
|
27404
|
+
log: (line2) => this.log(`context window: ${line2}`),
|
|
26744
27405
|
sendEveryMs: options.fillsEveryMs
|
|
26745
27406
|
});
|
|
26746
27407
|
}
|
|
@@ -26751,10 +27412,15 @@ var Bridge = class {
|
|
|
26751
27412
|
ours = /* @__PURE__ */ new Set();
|
|
26752
27413
|
/** The harness's agents' names, by Chips id, for the folders named after them. */
|
|
26753
27414
|
names = /* @__PURE__ */ new Map();
|
|
27415
|
+
/** The agents made in Chips with a profile of their own, by Chips id (Chips ADR 0241). */
|
|
27416
|
+
own = /* @__PURE__ */ new Map();
|
|
26754
27417
|
/** The feedback events answered with a turn, the newest `KEPT_FEEDBACK`. */
|
|
26755
27418
|
lookedAt = /* @__PURE__ */ new Set();
|
|
26756
|
-
/**
|
|
26757
|
-
|
|
27419
|
+
/**
|
|
27420
|
+
* The agents made, the spaces claimed and the copies made, one after another: a message for an
|
|
27421
|
+
* agent not settled yet waits for them (see `runnerOf`).
|
|
27422
|
+
*/
|
|
27423
|
+
making = Promise.resolve();
|
|
26758
27424
|
log;
|
|
26759
27425
|
health;
|
|
26760
27426
|
/** The harness's agents in Chips as last listed: whose context and window copies are sent. */
|
|
@@ -26767,6 +27433,12 @@ var Bridge = class {
|
|
|
26767
27433
|
/** The port Hermes's webhook reaches the bridge on, once it listens. */
|
|
26768
27434
|
fillsPort;
|
|
26769
27435
|
toldNoReadings = false;
|
|
27436
|
+
/** Whether the log has said once that a model copy wasn't sent. */
|
|
27437
|
+
toldModels = false;
|
|
27438
|
+
/** While Hermes's gateway restarts (Chips ADR 0094), the health watch says nothing: the restart says how it went. */
|
|
27439
|
+
held = false;
|
|
27440
|
+
/** What went wrong in the last restart, said in place of the watch's own words while Hermes doesn't answer. */
|
|
27441
|
+
why;
|
|
26770
27442
|
/** Register the agents, start hearing what is said to them, and say how their gateways are. */
|
|
26771
27443
|
async start() {
|
|
26772
27444
|
const { chips: chips2 } = this.options;
|
|
@@ -26777,16 +27449,23 @@ var Bridge = class {
|
|
|
26777
27449
|
const client = this.options.clientFor?.(config3) ?? new HermesClient(config3.address);
|
|
26778
27450
|
this.agents.push({ config: config3, key, client, agentId: registered.agentId, up: true });
|
|
26779
27451
|
}
|
|
27452
|
+
for (const [agentId2, profile] of Object.entries(this.options.state.made ?? {})) {
|
|
27453
|
+
const running = this.runningOn(agentId2, profile, known.find((agent) => agent.agentId === agentId2)?.name ?? profile);
|
|
27454
|
+
if (running !== void 0) this.own.set(agentId2, running);
|
|
27455
|
+
}
|
|
26780
27456
|
this.ours = /* @__PURE__ */ new Set([...known.map((agent) => agent.agentId), ...this.agents.map((agent) => agent.agentId)]);
|
|
26781
27457
|
chips2.on("message.new", (message) => void this.hear(message));
|
|
26782
27458
|
chips2.on("agent.asked", (asked) => {
|
|
26783
|
-
this.make(asked).catch((error62) => this.log(`a new agent went wrong: ${
|
|
27459
|
+
this.making = this.making.then(() => this.make(asked)).catch((error62) => this.log(`a new agent went wrong: ${messageOf3(error62)}`));
|
|
26784
27460
|
});
|
|
26785
27461
|
chips2.on("agent.fork", (fork) => {
|
|
26786
|
-
this.
|
|
27462
|
+
this.making = this.making.then(() => this.copy(fork)).catch((error62) => this.log(`a copy went wrong: ${messageOf3(error62)}`));
|
|
27463
|
+
});
|
|
27464
|
+
chips2.onSpaceAsked((asked) => {
|
|
27465
|
+
this.making = this.making.then(() => this.claim(asked)).catch((error62) => this.log(`a space from the + went wrong: ${messageOf3(error62)}`));
|
|
26787
27466
|
});
|
|
26788
27467
|
chips2.on("feedback", (feedback2) => {
|
|
26789
|
-
this.notRight(feedback2).catch((error62) => this.log(`a note on a reply went wrong: ${
|
|
27468
|
+
this.notRight(feedback2).catch((error62) => this.log(`a note on a reply went wrong: ${messageOf3(error62)}`));
|
|
26790
27469
|
});
|
|
26791
27470
|
chips2.on("agent.attached", () => void this.agentsMoved());
|
|
26792
27471
|
chips2.on("agent.released", () => void this.agentsMoved());
|
|
@@ -26795,6 +27474,20 @@ var Bridge = class {
|
|
|
26795
27474
|
this.health.unref();
|
|
26796
27475
|
await this.startSettings();
|
|
26797
27476
|
}
|
|
27477
|
+
/** Hold the health watch's words while Hermes's gateway restarts, and let them go after. */
|
|
27478
|
+
hold(held) {
|
|
27479
|
+
this.held = held;
|
|
27480
|
+
}
|
|
27481
|
+
/**
|
|
27482
|
+
* How a restart went (Chips ADR 0094): fine as the gateways now are, or its words, which stand
|
|
27483
|
+
* while Hermes doesn't answer. The health watch speaks for itself again after.
|
|
27484
|
+
*/
|
|
27485
|
+
async restarted(trouble) {
|
|
27486
|
+
this.held = false;
|
|
27487
|
+
this.why = trouble;
|
|
27488
|
+
if (trouble === void 0) return this.sayHowItIs();
|
|
27489
|
+
await this.options.chips.reportTrouble(trouble).catch(() => void 0);
|
|
27490
|
+
}
|
|
26798
27491
|
stop() {
|
|
26799
27492
|
if (this.health !== void 0) clearInterval(this.health);
|
|
26800
27493
|
if (this.contextTimer !== void 0) clearInterval(this.contextTimer);
|
|
@@ -26802,12 +27495,14 @@ var Bridge = class {
|
|
|
26802
27495
|
}
|
|
26803
27496
|
// ------------------------------------------------------------------ an agent's page
|
|
26804
27497
|
/**
|
|
26805
|
-
* The agents' context and context windows, sent at start and kept current: the
|
|
26806
|
-
* again every so often and after each turn, a
|
|
27498
|
+
* The agents' models, context and context windows, sent at start and kept current: the models
|
|
27499
|
+
* again when an agent comes or goes, the files read again every so often and after each turn, a
|
|
27500
|
+
* window copy on each reading Hermes sends.
|
|
26807
27501
|
*/
|
|
26808
27502
|
async startSettings() {
|
|
26809
|
-
if (this.contexts === void 0) return;
|
|
26810
27503
|
this.listed = await this.options.chips.agents.list().catch(() => this.listed);
|
|
27504
|
+
await this.sendModels();
|
|
27505
|
+
if (this.contexts === void 0) return;
|
|
26811
27506
|
await this.contexts.refresh();
|
|
26812
27507
|
this.contextTimer = setInterval(() => void this.contexts?.refresh(), this.options.contextEveryMs ?? 15e3);
|
|
26813
27508
|
this.contextTimer.unref();
|
|
@@ -26822,7 +27517,7 @@ var Bridge = class {
|
|
|
26822
27517
|
this.options.saveState(this.options.state);
|
|
26823
27518
|
}
|
|
26824
27519
|
} catch (error62) {
|
|
26825
|
-
this.log(`context window: nothing can listen on port ${asked.port} (${
|
|
27520
|
+
this.log(`context window: nothing can listen on port ${asked.port} (${messageOf3(error62)}), so Chips won't show how full each agent's context is`);
|
|
26826
27521
|
}
|
|
26827
27522
|
await fills2.sendAll();
|
|
26828
27523
|
}
|
|
@@ -26830,9 +27525,8 @@ var Bridge = class {
|
|
|
26830
27525
|
hookUrl(profile = "default") {
|
|
26831
27526
|
return this.fillsPort === void 0 ? void 0 : hookUrl(this.fillsPort, profileName(profile));
|
|
26832
27527
|
}
|
|
26833
|
-
/** The harness's agents listed again, the copies of any gone forgotten, and the context sent. */
|
|
27528
|
+
/** The harness's agents listed again, the copies of any gone forgotten, and the context and models sent. */
|
|
26834
27529
|
async agentsMoved() {
|
|
26835
|
-
if (this.contexts === void 0) return;
|
|
26836
27530
|
const before = this.listed;
|
|
26837
27531
|
this.listed = await this.options.chips.agents.list().catch(() => this.listed);
|
|
26838
27532
|
const now = new Set(this.active().map((agent) => agent.agentId));
|
|
@@ -26841,7 +27535,8 @@ var Bridge = class {
|
|
|
26841
27535
|
this.options.chips.settings.forget(gone.agentId);
|
|
26842
27536
|
this.fills?.forget(gone.agentId);
|
|
26843
27537
|
}
|
|
26844
|
-
await this.contexts
|
|
27538
|
+
await this.contexts?.refresh();
|
|
27539
|
+
await this.sendModels();
|
|
26845
27540
|
}
|
|
26846
27541
|
/** The harness's agents that read anything now: the active ones listed. */
|
|
26847
27542
|
active() {
|
|
@@ -26855,13 +27550,24 @@ var Bridge = class {
|
|
|
26855
27550
|
}
|
|
26856
27551
|
/** The profile an agent runs on: its own, a copy's original's, else the first (as `profileOf` picks). */
|
|
26857
27552
|
profileFor(agentId2) {
|
|
26858
|
-
const
|
|
26859
|
-
const copied = this.options.state.profiles?.[agentId2];
|
|
26860
|
-
const original = this.agents.find((agent) => copied !== void 0 && agent.key === copied);
|
|
26861
|
-
const running = own2 ?? original ?? this.agents[0];
|
|
27553
|
+
const running = this.runningFor(agentId2);
|
|
26862
27554
|
return running === void 0 ? void 0 : this.profileOfRunning(running);
|
|
26863
27555
|
}
|
|
26864
|
-
/**
|
|
27556
|
+
/** What answers for an agent: its own profile, a copy's original's, else the first. */
|
|
27557
|
+
runningFor(agentId2) {
|
|
27558
|
+
const own2 = this.agents.find((agent) => agent.agentId === agentId2) ?? this.own.get(agentId2);
|
|
27559
|
+
const copied = this.options.state.profiles?.[agentId2];
|
|
27560
|
+
return own2 ?? (copied === void 0 ? void 0 : this.byKey(copied)) ?? this.agents[0];
|
|
27561
|
+
}
|
|
27562
|
+
/** A profile the bridge speaks to, by its key: a configured agent's, or a made agent's Chips id. */
|
|
27563
|
+
byKey(key) {
|
|
27564
|
+
return this.agents.find((agent) => agent.key === key) ?? this.own.get(key);
|
|
27565
|
+
}
|
|
27566
|
+
/**
|
|
27567
|
+
* Who reads what: each active agent with its profile, and the first profile, whose SOUL.md each
|
|
27568
|
+
* new agent starts from (and whose every file each new agent reads, where none gets a profile of
|
|
27569
|
+
* its own).
|
|
27570
|
+
*/
|
|
26865
27571
|
roster() {
|
|
26866
27572
|
const readers = [];
|
|
26867
27573
|
const ids2 = /* @__PURE__ */ new Set([...this.active().map((agent) => agent.agentId), ...this.agents.map((agent) => agent.agentId)]);
|
|
@@ -26871,7 +27577,11 @@ var Bridge = class {
|
|
|
26871
27577
|
if (profile !== void 0) readers.push({ agentId: agentId2, profile });
|
|
26872
27578
|
}
|
|
26873
27579
|
const first = this.agents[0];
|
|
26874
|
-
return {
|
|
27580
|
+
return {
|
|
27581
|
+
readers,
|
|
27582
|
+
first: first === void 0 ? void 0 : this.profileOfRunning(first),
|
|
27583
|
+
ownProfiles: this.options.profiles !== void 0
|
|
27584
|
+
};
|
|
26875
27585
|
}
|
|
26876
27586
|
/** The agent a Hermes session the bridge keeps is, with its profile: nothing for any other. */
|
|
26877
27587
|
whoseSession(sessionId) {
|
|
@@ -26890,6 +27600,77 @@ var Bridge = class {
|
|
|
26890
27600
|
}
|
|
26891
27601
|
return this.contexts.edit(frame);
|
|
26892
27602
|
}
|
|
27603
|
+
// ------------------------------------------------------------------ an agent's model
|
|
27604
|
+
/**
|
|
27605
|
+
* The model a person picked for an agent, as its copy names it: a choice's id, or the model its
|
|
27606
|
+
* config says each of its profile's runs asks for; nothing for Hermes's default.
|
|
27607
|
+
*/
|
|
27608
|
+
ownModel(agentId2, running) {
|
|
27609
|
+
const picked = this.options.state.models?.[agentId2];
|
|
27610
|
+
return picked === null ? void 0 : picked ?? running.config.model;
|
|
27611
|
+
}
|
|
27612
|
+
/** What a turn's runs ask Hermes for: the agent's own model, else nothing, and the profile's runs. */
|
|
27613
|
+
runModel(turn) {
|
|
27614
|
+
const picked = this.options.state.models?.[turn.who];
|
|
27615
|
+
if (picked === null) return {};
|
|
27616
|
+
if (picked !== void 0) return runModelOf(picked);
|
|
27617
|
+
return turn.agent.config.model === void 0 ? {} : { model: turn.agent.config.model };
|
|
27618
|
+
}
|
|
27619
|
+
/**
|
|
27620
|
+
* Each agent's model, sent to Chips (Chips ADR 0141): the models Hermes has for its profile, read
|
|
27621
|
+
* once a profile, with the one it runs on. One whose Hermes doesn't answer is sent none, and the
|
|
27622
|
+
* log says so once.
|
|
27623
|
+
*/
|
|
27624
|
+
async sendModels() {
|
|
27625
|
+
const { chips: chips2 } = this.options;
|
|
27626
|
+
const ids2 = /* @__PURE__ */ new Set([...this.active().map((agent) => agent.agentId), ...this.agents.map((agent) => agent.agentId)]);
|
|
27627
|
+
const read = /* @__PURE__ */ new Map();
|
|
27628
|
+
for (const agentId2 of ids2) {
|
|
27629
|
+
const running = this.runningFor(agentId2);
|
|
27630
|
+
if (running === void 0) continue;
|
|
27631
|
+
try {
|
|
27632
|
+
let options = read.get(running);
|
|
27633
|
+
if (options === void 0) {
|
|
27634
|
+
options = running.client.modelOptions();
|
|
27635
|
+
read.set(running, options);
|
|
27636
|
+
}
|
|
27637
|
+
const copy = copyOf(await options, this.ownModel(agentId2, running));
|
|
27638
|
+
if (copy !== void 0) await chips2.settings.model(agentId2, copy);
|
|
27639
|
+
} catch (error62) {
|
|
27640
|
+
if (!this.toldModels) this.log(`models: none sent for ${running.config.name}: ${messageOf3(error62)}`);
|
|
27641
|
+
this.toldModels = true;
|
|
27642
|
+
}
|
|
27643
|
+
}
|
|
27644
|
+
}
|
|
27645
|
+
/**
|
|
27646
|
+
* A person picked an agent's model (Chips ADR 0141): one Hermes has for its profile, kept as the
|
|
27647
|
+
* model each of its runs asks for from now on, or Hermes's default (`null`), the profile's own.
|
|
27648
|
+
* The model it runs on already changes nothing. Hermes says no levels of effort, so none is taken.
|
|
27649
|
+
*/
|
|
27650
|
+
async onModelChange(frame) {
|
|
27651
|
+
const { state: state2 } = this.options;
|
|
27652
|
+
const agentId2 = frame.agent_id;
|
|
27653
|
+
const ours2 = this.ours.has(agentId2) || this.agents.some((agent) => agent.agentId === agentId2) || this.own.has(agentId2);
|
|
27654
|
+
const running = ours2 ? this.runningFor(agentId2) : void 0;
|
|
27655
|
+
if (running === void 0) throw new Refuse(`${agentId2} isn't one of this harness's agents.`);
|
|
27656
|
+
let options;
|
|
27657
|
+
try {
|
|
27658
|
+
options = await running.client.modelOptions();
|
|
27659
|
+
} catch (error62) {
|
|
27660
|
+
throw new Refuse(`Hermes didn't say which models it has: ${messageOf3(error62)}`);
|
|
27661
|
+
}
|
|
27662
|
+
const now = copyOf(options, this.ownModel(agentId2, running));
|
|
27663
|
+
if (now === void 0) throw new Refuse("Hermes has no model for this agent's profile.");
|
|
27664
|
+
if (frame.model === now.model) return now;
|
|
27665
|
+
if (frame.model !== null && !now.choices.some((choice) => choice.id === frame.model)) {
|
|
27666
|
+
throw new Refuse(`Hermes has no ${frame.model} for this agent's profile.`, now);
|
|
27667
|
+
}
|
|
27668
|
+
state2.models = { ...state2.models, [agentId2]: frame.model };
|
|
27669
|
+
this.options.saveState(state2);
|
|
27670
|
+
const name2 = await this.nameOf(agentId2, running.config.name);
|
|
27671
|
+
this.log(`${name2} runs on ${frame.model ?? `Hermes's default (${now.default?.model ?? "its profile's"})`}, from Chips`);
|
|
27672
|
+
return copyOf(options, this.ownModel(agentId2, running)) ?? now;
|
|
27673
|
+
}
|
|
26893
27674
|
/** A person's Compact now (Chips ADR 0146): Hermes can't be asked to compact from outside a run. */
|
|
26894
27675
|
onWindowCompact(frame) {
|
|
26895
27676
|
throw new Refuse(WHY_NOT, this.fills?.copyOf(frame.agent_id));
|
|
@@ -26935,7 +27716,7 @@ var Bridge = class {
|
|
|
26935
27716
|
/**
|
|
26936
27717
|
* A person asked for a new agent in a room (Chips ADR 0108): one of the harness's, keyed by the
|
|
26937
27718
|
* ask so an ask heard twice makes one, registered for the room, which Chips puts it in while the
|
|
26938
|
-
* room waits. It answers through
|
|
27719
|
+
* room waits. It answers through a Hermes profile of its own (`ownProfile`).
|
|
26939
27720
|
*/
|
|
26940
27721
|
async make(asked) {
|
|
26941
27722
|
const { chips: chips2 } = this.options;
|
|
@@ -26946,16 +27727,112 @@ var Bridge = class {
|
|
|
26946
27727
|
madeFor: asked.id,
|
|
26947
27728
|
spaceId: asked.space_id
|
|
26948
27729
|
});
|
|
26949
|
-
this.ours.add(agent.agentId);
|
|
26950
27730
|
this.names.set(agent.agentId, agent.name);
|
|
26951
27731
|
this.log(`${agent.name} made${agent.attached === false ? `, not in the room: ${agent.why ?? "too late"}` : ""}`);
|
|
27732
|
+
await this.ownProfile(agent.agentId, agent.name);
|
|
27733
|
+
this.ours.add(agent.agentId);
|
|
26952
27734
|
void this.agentsMoved();
|
|
26953
27735
|
}
|
|
27736
|
+
/**
|
|
27737
|
+
* A Hermes profile of its own for an agent made in Chips (Chips ADR 0241), so it remembers only
|
|
27738
|
+
* its own conversations: named after the agent, cloned from the Main agent's profile so it starts
|
|
27739
|
+
* with its model, keys, tools, skills and SOUL.md, its memory empty (`profiles.ts`). The gateway
|
|
27740
|
+
* is asked to serve it now; one that serves only the profile it started with, as Hermes's does
|
|
27741
|
+
* when it started with one, is restarted once. Where Hermes doesn't make one, the agent runs on
|
|
27742
|
+
* the first profile, in a session of its own, and the log says why.
|
|
27743
|
+
*/
|
|
27744
|
+
async ownProfile(agentId2, name2) {
|
|
27745
|
+
const { profiles, hermesRoot: root2, state: state2 } = this.options;
|
|
27746
|
+
const main2 = this.agents[0];
|
|
27747
|
+
if (profiles === void 0 || root2 === void 0 || main2 === void 0 || this.own.has(agentId2)) return;
|
|
27748
|
+
const from = profileName(main2.config.address.profile);
|
|
27749
|
+
const profile = freeProfile(root2, name2, Object.values(state2.made ?? {}));
|
|
27750
|
+
const home2 = profileHome(root2, profile);
|
|
27751
|
+
const secret = state2.fills?.secret ?? this.options.fills?.secret;
|
|
27752
|
+
try {
|
|
27753
|
+
await profiles.create(profile, from);
|
|
27754
|
+
const port = this.fillsPort;
|
|
27755
|
+
const hook = readyProfile(home2, { from, profile }, port === void 0 || secret === void 0 ? void 0 : { port, secret });
|
|
27756
|
+
if (hook?.state === "by_hand") this.log(`context window: ${hook.path} was left as it is: ${hook.why}. Add these lines to it:
|
|
27757
|
+
${hook.lines}`);
|
|
27758
|
+
} catch (error62) {
|
|
27759
|
+
this.log(`${name2} runs on the ${from} profile, in a session of its own: Hermes didn't make it one (${messageOf3(error62)})`);
|
|
27760
|
+
return;
|
|
27761
|
+
}
|
|
27762
|
+
state2.made = { ...state2.made, [agentId2]: profile };
|
|
27763
|
+
this.options.saveState(state2);
|
|
27764
|
+
const running = this.runningOn(agentId2, profile, name2);
|
|
27765
|
+
if (running !== void 0) this.own.set(agentId2, running);
|
|
27766
|
+
this.log(`${name2} has a Hermes profile of its own, ${profile}, cloned from ${from}`);
|
|
27767
|
+
const served = await profiles.serve().catch(() => void 0);
|
|
27768
|
+
if (served === false) {
|
|
27769
|
+
this.log(`Hermes's gateway serves only the profile it started with, as it does when there was one: restarting it once, so it serves ${profile} too`);
|
|
27770
|
+
await this.options.restartGateway?.();
|
|
27771
|
+
} else if (served === void 0) {
|
|
27772
|
+
this.log(`Hermes's gateway didn't answer on its control socket: it serves ${profile} once it looks again, or starts again`);
|
|
27773
|
+
}
|
|
27774
|
+
}
|
|
27775
|
+
/**
|
|
27776
|
+
* A profile made for an agent, as the bridge reaches it: under `/p/<profile>` of the gateway the
|
|
27777
|
+
* Main agent is reached at, with the key in the profile's own `.env`.
|
|
27778
|
+
*/
|
|
27779
|
+
runningOn(agentId2, profile, name2) {
|
|
27780
|
+
const main2 = this.agents[0];
|
|
27781
|
+
const root2 = this.options.hermesRoot;
|
|
27782
|
+
if (main2 === void 0 || root2 === void 0) return void 0;
|
|
27783
|
+
const home2 = profileHome(root2, profile);
|
|
27784
|
+
const config3 = {
|
|
27785
|
+
name: name2,
|
|
27786
|
+
address: { url: main2.config.address.url, profile, key: envValue(home2, KEY_ENV) ?? "" },
|
|
27787
|
+
keyEnv: KEY_ENV,
|
|
27788
|
+
keyFrom: join10(home2, ".env")
|
|
27789
|
+
};
|
|
27790
|
+
const client = this.options.clientFor?.(config3) ?? new HermesClient(config3.address);
|
|
27791
|
+
return { config: config3, key: agentId2, client, agentId: agentId2, up: true };
|
|
27792
|
+
}
|
|
27793
|
+
/**
|
|
27794
|
+
* A space a person asked the harness for from the + (Chips ADR 0241), as the ACP bridge takes
|
|
27795
|
+
* one: claimed under a key made from the space, so a space handed over twice is claimed once,
|
|
27796
|
+
* and named from its first words. Chips makes the agent of that key and puts it in the space; it
|
|
27797
|
+
* gets a Hermes profile of its own (`ownProfile`). It answers the first words, which are the
|
|
27798
|
+
* ask's and aren't heard again.
|
|
27799
|
+
*/
|
|
27800
|
+
async claim(asked) {
|
|
27801
|
+
const { chips: chips2 } = this.options;
|
|
27802
|
+
const { spaceId, firstMessage } = asked;
|
|
27803
|
+
const key = `hermes:space:${spaceId}`;
|
|
27804
|
+
await chips2.claimSpace(spaceId, key, titleFrom(firstMessage.text));
|
|
27805
|
+
const agent = (await chips2.agents.list()).find((each) => each.key === key);
|
|
27806
|
+
if (agent === void 0) {
|
|
27807
|
+
this.log(`claimed ${spaceId}, and Chips put no agent there`);
|
|
27808
|
+
return;
|
|
27809
|
+
}
|
|
27810
|
+
this.names.set(agent.agentId, agent.name);
|
|
27811
|
+
this.log(`${agent.name} claimed ${spaceId}`);
|
|
27812
|
+
await this.ownProfile(agent.agentId, agent.name);
|
|
27813
|
+
this.ours.add(agent.agentId);
|
|
27814
|
+
void this.agentsMoved();
|
|
27815
|
+
const message = {
|
|
27816
|
+
type: "message.new",
|
|
27817
|
+
space_id: spaceId,
|
|
27818
|
+
message_id: firstMessage.messageId,
|
|
27819
|
+
text: firstMessage.text,
|
|
27820
|
+
from: { participant_id: asked.askedBy, kind: "person", display_name: await this.whoSaid(asked), role: "member" },
|
|
27821
|
+
for: [agent.agentId]
|
|
27822
|
+
};
|
|
27823
|
+
void this.hear(message);
|
|
27824
|
+
}
|
|
27825
|
+
/** Who said a space's first words, from its lines. */
|
|
27826
|
+
async whoSaid(asked) {
|
|
27827
|
+
const context = await this.options.chips.roomContext(asked.spaceId, { limit: 20 }).catch(() => void 0);
|
|
27828
|
+
return context?.lines.find((line2) => line2.message_id === asked.firstMessage.messageId)?.from.name ?? "Someone";
|
|
27829
|
+
}
|
|
26954
27830
|
/**
|
|
26955
27831
|
* A person asked for a copy of one of the harness's agents in another room (ADR 0087, Fork): the
|
|
26956
27832
|
* agent's session in the room it is in, else the last it had, forked in Hermes, is the copy's
|
|
26957
27833
|
* session in the room asked. The copy answers through its original's profile, whose Hermes has
|
|
26958
|
-
* that session
|
|
27834
|
+
* that session (Hermes forks a session only within its profile, so a copy gets no profile of its
|
|
27835
|
+
* own: Chips ADR 0241), and works in its original's folder. It is registered with `forkedFrom` for the
|
|
26959
27836
|
* room, which Chips puts it in while the room waits. The fork and the copy are keyed by the ask,
|
|
26960
27837
|
* so an ask heard twice forks once (Hermes refuses the id again) and registers the same copy. An
|
|
26961
27838
|
* agent that never spoke is copied with nothing to remember. A turn still going is not in the
|
|
@@ -27034,12 +27911,12 @@ var Bridge = class {
|
|
|
27034
27911
|
const folder2 = turn.folder;
|
|
27035
27912
|
if (folder2 === void 0) return void 0;
|
|
27036
27913
|
try {
|
|
27037
|
-
const project = this.options.chips.project({ spaceId, agentId: agentId2, name: name2, folder: folder2, log: (
|
|
27914
|
+
const project = this.options.chips.project({ spaceId, agentId: agentId2, name: name2, folder: folder2, log: (line2) => this.log(line2) });
|
|
27038
27915
|
const facts = await project.pull();
|
|
27039
27916
|
turn.project = { copy: project, name: name2 };
|
|
27040
27917
|
return facts;
|
|
27041
27918
|
} catch (error62) {
|
|
27042
|
-
this.log(`${name2}'s copy of the room's project couldn't be readied: ${
|
|
27919
|
+
this.log(`${name2}'s copy of the room's project couldn't be readied: ${messageOf3(error62)}`);
|
|
27043
27920
|
return void 0;
|
|
27044
27921
|
}
|
|
27045
27922
|
}
|
|
@@ -27058,17 +27935,20 @@ var Bridge = class {
|
|
|
27058
27935
|
const note = heldNote(pushed, name2);
|
|
27059
27936
|
return note === void 0 ? {} : { note };
|
|
27060
27937
|
} catch (error62) {
|
|
27061
|
-
this.log(`${name2}'s project couldn't be sent: ${
|
|
27062
|
-
const note = heldNote({ state: "failed", why:
|
|
27938
|
+
this.log(`${name2}'s project couldn't be sent: ${messageOf3(error62)}`);
|
|
27939
|
+
const note = heldNote({ state: "failed", why: messageOf3(error62) }, name2);
|
|
27063
27940
|
return note === void 0 ? {} : { note };
|
|
27064
27941
|
}
|
|
27065
27942
|
}
|
|
27066
|
-
/** Trouble while a profile's gateway doesn't answer, and fine once all do (ADR 0083). */
|
|
27943
|
+
/** Trouble while a profile's gateway doesn't answer, and fine once all do (ADR 0083); nothing while a restart runs. */
|
|
27067
27944
|
async sayHowItIs() {
|
|
27945
|
+
if (this.held) return;
|
|
27068
27946
|
for (const agent of this.agents) agent.up = await agent.client.healthy();
|
|
27947
|
+
if (this.held) return;
|
|
27069
27948
|
const down = this.agents.filter((agent) => !agent.up);
|
|
27949
|
+
if (down.length === 0) this.why = void 0;
|
|
27070
27950
|
const chips2 = this.options.chips;
|
|
27071
|
-
const said2 = down.length === 0 ? chips2.reportOk() : chips2.reportTrouble(`Hermes isn't answering for ${down.map((agent) => agent.config.name).join(", ")}`);
|
|
27951
|
+
const said2 = down.length === 0 ? chips2.reportOk() : chips2.reportTrouble(this.why ?? `Hermes isn't answering for ${down.map((agent) => agent.config.name).join(", ")}`);
|
|
27072
27952
|
await said2.catch(() => void 0);
|
|
27073
27953
|
}
|
|
27074
27954
|
// ------------------------------------------------------------------ what is said to it
|
|
@@ -27102,36 +27982,38 @@ var Bridge = class {
|
|
|
27102
27982
|
this.lookedAt.delete(old);
|
|
27103
27983
|
}
|
|
27104
27984
|
const context = await this.options.chips.roomContext(spaceId, { limit: 200 }).catch(() => void 0);
|
|
27105
|
-
const
|
|
27985
|
+
const line2 = context?.lines.find((each) => each.message_id === messageId);
|
|
27106
27986
|
const message = {
|
|
27107
27987
|
type: "message.new",
|
|
27108
27988
|
space_id: spaceId,
|
|
27109
27989
|
// An id of its own, so nothing of the turn is taken for the reply's.
|
|
27110
27990
|
message_id: messageIdOf(feedback2.id),
|
|
27111
|
-
text: lookAgain(feedback2.from.display_name,
|
|
27991
|
+
text: lookAgain(feedback2.from.display_name, line2?.text, note),
|
|
27112
27992
|
from: feedback2.from,
|
|
27113
|
-
...
|
|
27993
|
+
...line2 === void 0 ? {} : { for: [line2.from.id] }
|
|
27114
27994
|
};
|
|
27115
27995
|
await this.hear(message, { goal: lookingAgain(note), sayWho: false });
|
|
27116
27996
|
}
|
|
27117
27997
|
/**
|
|
27118
|
-
* Which profile answers for a Chips agent, as `profileOf` says. An agent not
|
|
27119
|
-
*
|
|
27998
|
+
* Which profile answers for a Chips agent, as `profileOf` says. An agent not settled may be one
|
|
27999
|
+
* being made, or a copy, which is waited for, so that its first message is answered on its own
|
|
28000
|
+
* profile, or in the session it was forked.
|
|
27120
28001
|
*/
|
|
27121
28002
|
async runnerOf(taker) {
|
|
27122
|
-
|
|
28003
|
+
const settled = taker === this.options.chips.harnessId || this.own.has(taker) || this.agents.some((agent) => agent.agentId === taker);
|
|
28004
|
+
if (!settled) await this.making;
|
|
27123
28005
|
return this.profileOf(taker);
|
|
27124
28006
|
}
|
|
27125
28007
|
/**
|
|
27126
|
-
* Which profile answers for a Chips agent: its own; a copy's
|
|
27127
|
-
* harness itself and for any other of the harness's — its
|
|
27128
|
-
* read afresh once when a name is new.
|
|
28008
|
+
* Which profile answers for a Chips agent: its own, configured or made for it; a copy's
|
|
28009
|
+
* original's; or the first for the harness itself and for any other of the harness's — its
|
|
28010
|
+
* default agent, one made before profiles were — read afresh once when a name is new.
|
|
27129
28011
|
*/
|
|
27130
28012
|
async profileOf(taker) {
|
|
27131
|
-
const own2 = this.agents.find((agent) => agent.agentId === taker);
|
|
28013
|
+
const own2 = this.agents.find((agent) => agent.agentId === taker) ?? this.own.get(taker);
|
|
27132
28014
|
if (own2 !== void 0) return own2;
|
|
27133
28015
|
const copied = this.options.state.profiles?.[taker];
|
|
27134
|
-
const original =
|
|
28016
|
+
const original = copied === void 0 ? void 0 : this.byKey(copied);
|
|
27135
28017
|
if (original !== void 0) return original;
|
|
27136
28018
|
if (taker !== this.options.chips.harnessId && !this.ours.has(taker)) {
|
|
27137
28019
|
const listed = await this.options.chips.agents.list().catch(() => []);
|
|
@@ -27164,7 +28046,7 @@ var Bridge = class {
|
|
|
27164
28046
|
try {
|
|
27165
28047
|
await this.turn(room, asked);
|
|
27166
28048
|
} catch (error62) {
|
|
27167
|
-
this.log(`a turn of ${room.agent.config.name} went wrong: ${
|
|
28049
|
+
this.log(`a turn of ${room.agent.config.name} went wrong: ${messageOf3(error62)}`);
|
|
27168
28050
|
} finally {
|
|
27169
28051
|
room.busy = false;
|
|
27170
28052
|
void this.contexts?.refresh();
|
|
@@ -27179,6 +28061,7 @@ var Bridge = class {
|
|
|
27179
28061
|
run: run2,
|
|
27180
28062
|
speaker,
|
|
27181
28063
|
agent,
|
|
28064
|
+
who: speakerId ?? agent.agentId,
|
|
27182
28065
|
hermesRun: "",
|
|
27183
28066
|
reply: "",
|
|
27184
28067
|
saidAt: 0,
|
|
@@ -27202,7 +28085,7 @@ var Bridge = class {
|
|
|
27202
28085
|
try {
|
|
27203
28086
|
session = await this.sessionFor(room);
|
|
27204
28087
|
} catch (error62) {
|
|
27205
|
-
await this.failed(turn, `${agent.config.name} couldn't open a session in Hermes: ${
|
|
28088
|
+
await this.failed(turn, `${agent.config.name} couldn't open a session in Hermes: ${messageOf3(error62)}`, true);
|
|
27206
28089
|
return;
|
|
27207
28090
|
}
|
|
27208
28091
|
const name2 = speakerId === void 0 ? agent.config.name : await this.nameOf(speakerId, agent.config.name);
|
|
@@ -27229,9 +28112,15 @@ var Bridge = class {
|
|
|
27229
28112
|
async once(turn, session, text, key) {
|
|
27230
28113
|
const { agent } = turn;
|
|
27231
28114
|
try {
|
|
27232
|
-
turn.hermesRun = await agent.client.startRun({
|
|
28115
|
+
turn.hermesRun = await agent.client.startRun({
|
|
28116
|
+
text,
|
|
28117
|
+
sessionId: session,
|
|
28118
|
+
...this.runModel(turn),
|
|
28119
|
+
key,
|
|
28120
|
+
instructions: instructionsFor(turn.folder, turn.project !== void 0)
|
|
28121
|
+
});
|
|
27233
28122
|
} catch (error62) {
|
|
27234
|
-
await this.failed(turn, `${agent.config.name} couldn't start in Hermes: ${
|
|
28123
|
+
await this.failed(turn, `${agent.config.name} couldn't start in Hermes: ${messageOf3(error62)}`, true);
|
|
27235
28124
|
return void 0;
|
|
27236
28125
|
}
|
|
27237
28126
|
if (turn.cancelled) await agent.client.stop(turn.hermesRun).catch(() => void 0);
|
|
@@ -27239,7 +28128,7 @@ var Bridge = class {
|
|
|
27239
28128
|
const last = await agent.client.events(turn.hermesRun, (event) => this.heard(turn, event));
|
|
27240
28129
|
return last ?? await this.endOf(turn);
|
|
27241
28130
|
} catch (error62) {
|
|
27242
|
-
this.log(`lost ${agent.config.name}'s run ${turn.hermesRun}: ${
|
|
28131
|
+
this.log(`lost ${agent.config.name}'s run ${turn.hermesRun}: ${messageOf3(error62)}`);
|
|
27243
28132
|
return await this.endOf(turn);
|
|
27244
28133
|
}
|
|
27245
28134
|
}
|
|
@@ -27252,7 +28141,7 @@ var Bridge = class {
|
|
|
27252
28141
|
if (status !== void 0 && TERMINAL.has(`run.${status}`)) {
|
|
27253
28142
|
return { ...state2, event: `run.${status}` };
|
|
27254
28143
|
}
|
|
27255
|
-
await new Promise((
|
|
28144
|
+
await new Promise((resolve7) => setTimeout(resolve7, 1e3));
|
|
27256
28145
|
}
|
|
27257
28146
|
await this.failed(turn, `${turn.agent.config.name}'s run in Hermes stopped answering.`, true);
|
|
27258
28147
|
return void 0;
|
|
@@ -27284,7 +28173,7 @@ var Bridge = class {
|
|
|
27284
28173
|
await turn.agent.client.steer(turn.hermesRun, text);
|
|
27285
28174
|
return;
|
|
27286
28175
|
} catch (error62) {
|
|
27287
|
-
this.log(`Hermes didn't take a steer mid-run (${
|
|
28176
|
+
this.log(`Hermes didn't take a steer mid-run (${messageOf3(error62)}); saying it next`);
|
|
27288
28177
|
}
|
|
27289
28178
|
}
|
|
27290
28179
|
turn.next.push(text);
|
|
@@ -27320,10 +28209,10 @@ var Bridge = class {
|
|
|
27320
28209
|
}
|
|
27321
28210
|
sayThought(turn, text, phase) {
|
|
27322
28211
|
const now = Date.now();
|
|
27323
|
-
const
|
|
27324
|
-
if (
|
|
28212
|
+
const line2 = lastLine(text);
|
|
28213
|
+
if (line2 === "" || now - turn.saidAt < QUIET_MS) return;
|
|
27325
28214
|
turn.saidAt = now;
|
|
27326
|
-
void turn.run.status({ phase, verbPhrase:
|
|
28215
|
+
void turn.run.status({ phase, verbPhrase: line2.slice(0, 200) }).catch(() => void 0);
|
|
27327
28216
|
}
|
|
27328
28217
|
async started(turn, event) {
|
|
27329
28218
|
const sub = event.event === "subagent.start";
|
|
@@ -27392,7 +28281,7 @@ var Bridge = class {
|
|
|
27392
28281
|
${command}
|
|
27393
28282
|
\`\`\`` } }
|
|
27394
28283
|
});
|
|
27395
|
-
const stopped2 = new Promise((
|
|
28284
|
+
const stopped2 = new Promise((resolve7) => turn.run.on("cancel", () => resolve7(void 0)));
|
|
27396
28285
|
const decision = await Promise.race([asked, stopped2]);
|
|
27397
28286
|
const pending = turn.approvals.get(requestId ?? approvalId);
|
|
27398
28287
|
if (decision === void 0 || pending === void 0 || pending.answered) return;
|
|
@@ -27400,10 +28289,10 @@ ${command}
|
|
|
27400
28289
|
if (decision.allowed) turn.allowed.push(approvalId);
|
|
27401
28290
|
const generation = event["execution_generation"];
|
|
27402
28291
|
await turn.agent.client.approve(turn.hermesRun, {
|
|
27403
|
-
choice: choiceOf(decision, choices),
|
|
28292
|
+
choice: choiceOf(decision, choices, command !== void 0),
|
|
27404
28293
|
...requestId === void 0 ? {} : { request_id: requestId },
|
|
27405
28294
|
...typeof generation === "number" ? { execution_generation: generation } : {}
|
|
27406
|
-
}).catch((error62) => this.log(`Hermes didn't take the answer: ${
|
|
28295
|
+
}).catch((error62) => this.log(`Hermes didn't take the answer: ${messageOf3(error62)}`));
|
|
27407
28296
|
}
|
|
27408
28297
|
/** An approval answered in Hermes's own tools: settled in Chips, once, so the card cools (ADR 0079). */
|
|
27409
28298
|
async settled(turn, event) {
|
|
@@ -27496,7 +28385,7 @@ ${command}
|
|
|
27496
28385
|
const output2 = count("output_tokens") ?? 0;
|
|
27497
28386
|
if (input2 + output2 === 0) return;
|
|
27498
28387
|
const runtime = last["runtime"] ?? {};
|
|
27499
|
-
const model = typeof runtime["model"] === "string" ? runtime["model"] : turn.
|
|
28388
|
+
const model = typeof runtime["model"] === "string" ? runtime["model"] : this.runModel(turn).model ?? "hermes";
|
|
27500
28389
|
await turn.run.usage({
|
|
27501
28390
|
scope: "step",
|
|
27502
28391
|
model,
|
|
@@ -27517,8 +28406,9 @@ ${command}
|
|
|
27517
28406
|
if (retryable) void this.sayHowItIs();
|
|
27518
28407
|
}
|
|
27519
28408
|
};
|
|
27520
|
-
function choiceOf(decision, choices) {
|
|
28409
|
+
function choiceOf(decision, choices, command = false) {
|
|
27521
28410
|
if (!decision.allowed) return "deny";
|
|
28411
|
+
if (command) return "once";
|
|
27522
28412
|
if (decision.scope === "always" && choices.includes("always")) return "always";
|
|
27523
28413
|
if (decision.scope !== "once" && choices.includes("session")) return "session";
|
|
27524
28414
|
return "once";
|
|
@@ -27527,15 +28417,21 @@ function idOf(event) {
|
|
|
27527
28417
|
const id2 = event["request_id"] ?? event["prompt_id"];
|
|
27528
28418
|
return typeof id2 === "string" && id2 !== "" ? id2 : void 0;
|
|
27529
28419
|
}
|
|
27530
|
-
function
|
|
28420
|
+
function messageOf3(error62) {
|
|
27531
28421
|
if (error62 instanceof HermesError) return `${error62.message} (${error62.status})`;
|
|
27532
28422
|
return error62 instanceof Error ? error62.message : String(error62);
|
|
27533
28423
|
}
|
|
28424
|
+
function titleFrom(text) {
|
|
28425
|
+
const line2 = text.trim().split("\n")[0] ?? "";
|
|
28426
|
+
if (line2.length <= 48) return line2;
|
|
28427
|
+
const cut2 = line2.slice(0, 48);
|
|
28428
|
+
return `${cut2.slice(0, Math.max(cut2.lastIndexOf(" "), 24))}\u2026`;
|
|
28429
|
+
}
|
|
27534
28430
|
function forkName(original) {
|
|
27535
28431
|
return `${original.trim().replace(/ · fork( \d+)?$/, "")} \xB7 fork`;
|
|
27536
28432
|
}
|
|
27537
28433
|
function goalOf(message, name2) {
|
|
27538
|
-
const first = message.text.split("\n").find((
|
|
28434
|
+
const first = message.text.split("\n").find((line2) => line2.trim() !== "")?.trim() ?? "";
|
|
27539
28435
|
const named = first.toLowerCase().startsWith(`@${name2.toLowerCase()}`);
|
|
27540
28436
|
const goal = named ? first.slice(name2.length + 1).trim() : first.replace(/^@\S+\s*/, "");
|
|
27541
28437
|
return (goal === "" ? `Answer ${message.from.display_name}` : goal).slice(0, 300);
|
|
@@ -27553,7 +28449,7 @@ function lookAgain(who, reply, note) {
|
|
|
27553
28449
|
].join("\n\n");
|
|
27554
28450
|
}
|
|
27555
28451
|
function lookingAgain(note) {
|
|
27556
|
-
const first = note?.split("\n").find((
|
|
28452
|
+
const first = note?.split("\n").find((line2) => line2.trim() !== "")?.trim();
|
|
27557
28453
|
return first === void 0 ? "Looking again at its reply" : cut(`Looking again: ${first}`, GOAL_SHOWN);
|
|
27558
28454
|
}
|
|
27559
28455
|
function cut(text, most) {
|
|
@@ -27573,7 +28469,7 @@ function resultWords(preview2) {
|
|
|
27573
28469
|
return typeof said2 === "string" ? said2.trim() : "";
|
|
27574
28470
|
}
|
|
27575
28471
|
function lastLine(text) {
|
|
27576
|
-
const lines2 = text.split(/(?<=[.!?])\s+|\n+/).map((
|
|
28472
|
+
const lines2 = text.split(/(?<=[.!?])\s+|\n+/).map((line2) => line2.trim()).filter((line2) => line2 !== "");
|
|
27577
28473
|
return lines2.at(-1) ?? "";
|
|
27578
28474
|
}
|
|
27579
28475
|
function lowerFirst(text) {
|
|
@@ -27581,19 +28477,19 @@ function lowerFirst(text) {
|
|
|
27581
28477
|
}
|
|
27582
28478
|
|
|
27583
28479
|
// src/keep.ts
|
|
27584
|
-
import { homedir as
|
|
27585
|
-
import { basename as
|
|
28480
|
+
import { homedir as homedir6 } from "node:os";
|
|
28481
|
+
import { basename as basename5, join as join11, resolve as resolve4 } from "node:path";
|
|
27586
28482
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
27587
28483
|
var flags = process.argv.slice(2);
|
|
27588
28484
|
var words3 = flags.filter((word) => !word.startsWith("--"));
|
|
27589
|
-
var whole = (word) =>
|
|
28485
|
+
var whole = (word) => resolve4(word.startsWith("~/") ? join11(homedir6(), word.slice(2)) : word);
|
|
27590
28486
|
var here = fileURLToPath2(new URL("..", import.meta.url)).replace(/\/$/, "");
|
|
27591
28487
|
var self = fileURLToPath2(import.meta.url);
|
|
27592
28488
|
var run = self.endsWith(".js") ? [self] : ["--import", "tsx", "src/main.ts"];
|
|
27593
|
-
var again = process.env["npm_lifecycle_event"] === "keep" ? "npm run keep --" :
|
|
28489
|
+
var again = process.env["npm_lifecycle_event"] === "keep" ? "npm run keep --" : basename5(process.argv[1] ?? "chips");
|
|
27594
28490
|
var stopLine = process.env["CHIPS_STOP_COMMAND"] || [again, ...words3, "--stop"].join(" ");
|
|
27595
|
-
var end = (
|
|
27596
|
-
(code === 0 ? process.stdout : process.stderr).write(`${
|
|
28491
|
+
var end = (line2, code) => {
|
|
28492
|
+
(code === 0 ? process.stdout : process.stderr).write(`${line2}
|
|
27597
28493
|
`);
|
|
27598
28494
|
process.exit(code);
|
|
27599
28495
|
};
|
|
@@ -27632,9 +28528,9 @@ function keepIfAsked(label2, url2, names, log2, before = () => void 0) {
|
|
|
27632
28528
|
}
|
|
27633
28529
|
|
|
27634
28530
|
// src/setup.ts
|
|
27635
|
-
import { existsSync as
|
|
27636
|
-
import { homedir as
|
|
27637
|
-
import { join as
|
|
28531
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
|
|
28532
|
+
import { homedir as homedir7 } from "node:os";
|
|
28533
|
+
import { join as join12, resolve as resolve5 } from "node:path";
|
|
27638
28534
|
var SetupError = class extends Error {
|
|
27639
28535
|
constructor(message, words4 = {}) {
|
|
27640
28536
|
super(message);
|
|
@@ -27643,37 +28539,37 @@ var SetupError = class extends Error {
|
|
|
27643
28539
|
words;
|
|
27644
28540
|
name = "SetupError";
|
|
27645
28541
|
};
|
|
27646
|
-
var shown = (path) => path.startsWith(`${
|
|
27647
|
-
var
|
|
28542
|
+
var shown = (path) => path.startsWith(`${homedir7()}/`) ? `~${path.slice(homedir7().length)}` : path;
|
|
28543
|
+
var portOf2 = (url2) => {
|
|
27648
28544
|
const parsed = new URL(url2);
|
|
27649
28545
|
return parsed.port || (parsed.protocol === "https:" ? "443" : "80");
|
|
27650
28546
|
};
|
|
27651
|
-
function checkedWords(config3,
|
|
28547
|
+
function checkedWords(config3, restart2 = false) {
|
|
27652
28548
|
const urls = new Set(config3.agents.map((agent) => agent.address.url));
|
|
27653
28549
|
const [url2] = urls;
|
|
27654
|
-
const found = urls.size === 1 && url2 !== void 0 ? `Its API server answers on port ${
|
|
27655
|
-
return
|
|
28550
|
+
const found = urls.size === 1 && url2 !== void 0 ? `Its API server answers on port ${portOf2(url2)}` : `Its API server answers for each of ${config3.agents.length} agents`;
|
|
28551
|
+
return restart2 ? `${found}. ${RESTART}` : found;
|
|
27656
28552
|
}
|
|
27657
28553
|
var RESTART = "Start hermes gateway again once, so it tells Chips how full each agent's context is.";
|
|
27658
28554
|
function readyHooks(config3, options) {
|
|
27659
28555
|
const lines2 = [];
|
|
27660
|
-
let
|
|
28556
|
+
let restart2 = false;
|
|
27661
28557
|
const done = /* @__PURE__ */ new Set();
|
|
27662
|
-
for (const
|
|
27663
|
-
const profile = profileName(
|
|
28558
|
+
for (const named of [...config3.agents.map((agent) => agent.address.profile), ...options.made ?? []]) {
|
|
28559
|
+
const profile = profileName(named);
|
|
27664
28560
|
if (done.has(profile)) continue;
|
|
27665
28561
|
done.add(profile);
|
|
27666
|
-
const home2 = profileHome(options.root,
|
|
28562
|
+
const home2 = profileHome(options.root, named);
|
|
27667
28563
|
let outcome;
|
|
27668
28564
|
try {
|
|
27669
28565
|
outcome = addHook(home2, options.port, profile, options.secret);
|
|
27670
28566
|
} catch (error62) {
|
|
27671
|
-
lines2.push(`Chips's webhook wasn't added to ${shown(
|
|
28567
|
+
lines2.push(`Chips's webhook wasn't added to ${shown(join12(home2, "config.yaml"))}: ${error62 instanceof Error ? error62.message : String(error62)}`);
|
|
27672
28568
|
continue;
|
|
27673
28569
|
}
|
|
27674
28570
|
if (outcome.state === "added") {
|
|
27675
28571
|
lines2.push(`Added Chips's webhook to ${shown(outcome.path)}, so Hermes tells Chips how full each agent's context is.`);
|
|
27676
|
-
|
|
28572
|
+
restart2 ||= options.gatewayUp;
|
|
27677
28573
|
} else if (outcome.state === "by_hand") {
|
|
27678
28574
|
lines2.push(
|
|
27679
28575
|
`${shown(outcome.path)} was left as it is: ${outcome.why}. For Chips to show how full each agent's context is, add these lines to it, then start hermes gateway again:
|
|
@@ -27683,30 +28579,30 @@ ${outcome.lines}`
|
|
|
27683
28579
|
lines2.push(`Hermes has no ${profile} profile at ${shown(home2)}, so no webhook was added for it.`);
|
|
27684
28580
|
}
|
|
27685
28581
|
}
|
|
27686
|
-
if (
|
|
27687
|
-
return { lines: lines2, restart };
|
|
28582
|
+
if (restart2) lines2.push(RESTART);
|
|
28583
|
+
return { lines: lines2, restart: restart2 };
|
|
27688
28584
|
}
|
|
27689
28585
|
var STILL_GOOD = "The link is still good: run the same command again once that's done.";
|
|
27690
28586
|
var EXAMPLE = '{ "agents": [{ "name": "Hermes", "profile": "default", "keyEnv": "API_SERVER_KEY" }] }';
|
|
27691
28587
|
var PLAIN = { agents: [{ name: "Hermes", profile: "default", keyEnv: "API_SERVER_KEY" }] };
|
|
27692
28588
|
function hermesEnvPath(env = process.env) {
|
|
27693
|
-
return
|
|
28589
|
+
return resolve5(env["HERMES_HOME"] ?? resolve5(homedir7(), ".hermes"), ".env");
|
|
27694
28590
|
}
|
|
27695
28591
|
function fromHermes(name2, env) {
|
|
27696
28592
|
const path = hermesEnvPath(env);
|
|
27697
|
-
if (!
|
|
27698
|
-
for (const
|
|
27699
|
-
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(
|
|
28593
|
+
if (!existsSync10(path)) return void 0;
|
|
28594
|
+
for (const line2 of readFileSync10(path, "utf8").split("\n")) {
|
|
28595
|
+
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(line2);
|
|
27700
28596
|
if (match?.[1] === name2) return match[2]?.replace(/^(["'])(.*)\1$/, "$2");
|
|
27701
28597
|
}
|
|
27702
28598
|
return void 0;
|
|
27703
28599
|
}
|
|
27704
28600
|
function folderOf(said2) {
|
|
27705
28601
|
if (typeof said2 !== "string" || said2 === "") return void 0;
|
|
27706
|
-
return
|
|
28602
|
+
return resolve5(said2 === "~" || said2.startsWith("~/") ? homedir7() + said2.slice(1) : said2);
|
|
27707
28603
|
}
|
|
27708
28604
|
function readConfig(path, env = process.env) {
|
|
27709
|
-
const raw =
|
|
28605
|
+
const raw = existsSync10(path) ? JSON.parse(readFileSync10(path, "utf8")) : PLAIN;
|
|
27710
28606
|
const url2 = typeof raw.url === "string" ? raw.url : "http://127.0.0.1:8642";
|
|
27711
28607
|
const folder2 = folderOf(raw.folder);
|
|
27712
28608
|
if (!Array.isArray(raw.agents) || raw.agents.length === 0) {
|
|
@@ -27745,7 +28641,7 @@ function readConfig(path, env = process.env) {
|
|
|
27745
28641
|
return {
|
|
27746
28642
|
...typeof raw.name === "string" ? { name: raw.name } : {},
|
|
27747
28643
|
agents,
|
|
27748
|
-
folders: folderOf(raw.folders) ??
|
|
28644
|
+
folders: folderOf(raw.folders) ?? resolve5(homedir7(), "chips-hermes", "agents"),
|
|
27749
28645
|
...typeof hookPort === "number" && Number.isInteger(hookPort) && hookPort > 0 && hookPort < 65536 ? { hookPort } : {}
|
|
27750
28646
|
};
|
|
27751
28647
|
}
|
|
@@ -27756,7 +28652,7 @@ async function checkHermes(config3, clientFor = (agent) => new HermesClient(agen
|
|
|
27756
28652
|
throw new SetupError(
|
|
27757
28653
|
`Hermes isn't answering for ${agent.name} at ${agent.address.url}: start its API server (API_SERVER_ENABLED=true, then hermes gateway). ${STILL_GOOD}`,
|
|
27758
28654
|
{
|
|
27759
|
-
detail: `Nothing answers on port ${
|
|
28655
|
+
detail: `Nothing answers on port ${portOf2(agent.address.url)}`,
|
|
27760
28656
|
says: "Hermes's API server is off.",
|
|
27761
28657
|
doing: `Add this line to ${shown(hermesEnvPath())}, then start Hermes with hermes gateway.`,
|
|
27762
28658
|
copy: "API_SERVER_ENABLED=true"
|
|
@@ -27777,20 +28673,20 @@ async function checkHermes(config3, clientFor = (agent) => new HermesClient(agen
|
|
|
27777
28673
|
}
|
|
27778
28674
|
|
|
27779
28675
|
// src/main.ts
|
|
27780
|
-
var home = (path) => path.startsWith("~/") ?
|
|
28676
|
+
var home = (path) => path.startsWith("~/") ? join13(homedir8(), path.slice(2)) : path;
|
|
27781
28677
|
var link = process.env["CHIPS_JOIN_LINK"] || void 0;
|
|
27782
28678
|
var LABEL = "chat.chips.hermes";
|
|
27783
28679
|
stopIfAsked(LABEL);
|
|
27784
|
-
var configPath =
|
|
27785
|
-
var folder =
|
|
27786
|
-
var statePath =
|
|
27787
|
-
var state =
|
|
28680
|
+
var configPath = resolve6(home(words3[0] ?? "~/.chips-hermes/config.json"));
|
|
28681
|
+
var folder = dirname6(configPath);
|
|
28682
|
+
var statePath = join13(folder, "sessions.json");
|
|
28683
|
+
var state = existsSync11(statePath) ? JSON.parse(readFileSync11(statePath, "utf8")) : { sessions: {} };
|
|
27788
28684
|
var saveState = (next) => {
|
|
27789
28685
|
mkdirSync6(folder, { recursive: true, mode: 448 });
|
|
27790
28686
|
writeFileSync5(statePath, `${JSON.stringify(next, null, 2)}
|
|
27791
28687
|
`, { mode: 384 });
|
|
27792
28688
|
};
|
|
27793
|
-
var log = (
|
|
28689
|
+
var log = (line2) => process.stdout.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${line2}
|
|
27794
28690
|
`);
|
|
27795
28691
|
var freePort = () => new Promise((done, failed) => {
|
|
27796
28692
|
const server = createServer2();
|
|
@@ -27809,13 +28705,14 @@ try {
|
|
|
27809
28705
|
reportStep("checking", { detail: "Looking for its API server" });
|
|
27810
28706
|
await checkHermes(config2);
|
|
27811
28707
|
}
|
|
27812
|
-
fills.secret ??=
|
|
28708
|
+
fills.secret ??= randomBytes3(32).toString("hex");
|
|
27813
28709
|
fills.port = config2.hookPort ?? fills.port ?? hookPortIn(profileHome(root, config2.agents[0]?.address.profile)) ?? await freePort();
|
|
27814
28710
|
saveState(state);
|
|
27815
28711
|
const first = config2.agents[0];
|
|
27816
28712
|
const gatewayUp = link !== void 0 || first !== void 0 && await new HermesClient(first.address).healthy();
|
|
27817
|
-
const
|
|
27818
|
-
|
|
28713
|
+
const made = Object.values(state.made ?? {});
|
|
28714
|
+
const hooks = readyHooks(config2, { root, port: fills.port, secret: fills.secret, gatewayUp, made });
|
|
28715
|
+
for (const line2 of hooks.lines) log(line2);
|
|
27819
28716
|
if (link !== void 0) reportStep("checked", { detail: checkedWords(config2, hooks.restart) });
|
|
27820
28717
|
} catch (error62) {
|
|
27821
28718
|
if (!(error62 instanceof SetupError)) throw error62;
|
|
@@ -27825,6 +28722,15 @@ try {
|
|
|
27825
28722
|
process.exit(1);
|
|
27826
28723
|
}
|
|
27827
28724
|
var name = config2.name ?? "Hermes";
|
|
28725
|
+
var gateways2 = new Gateways({
|
|
28726
|
+
gateways: gatewaysOf(config2.agents),
|
|
28727
|
+
root,
|
|
28728
|
+
env: process.env,
|
|
28729
|
+
system: thisMachine(),
|
|
28730
|
+
log: (line2) => log(`gateway: ${line2}`),
|
|
28731
|
+
logFile: gatewayLog(folder)
|
|
28732
|
+
});
|
|
28733
|
+
if (link === void 0) await gateways2.startIfDown();
|
|
27828
28734
|
var refused2 = (error62) => {
|
|
27829
28735
|
if (!(error62 instanceof JoinError)) throw error62;
|
|
27830
28736
|
process.stderr.write(`${error62.message}
|
|
@@ -27841,11 +28747,21 @@ var joined = await join3({
|
|
|
27841
28747
|
}).catch(refused2);
|
|
27842
28748
|
keepIfAsked(LABEL, joined.url, ["HERMES_HOME", ...config2.agents.map((agent) => agent.keyEnv ?? "API_SERVER_KEY")], log);
|
|
27843
28749
|
var bridge;
|
|
28750
|
+
var restart = restarter({
|
|
28751
|
+
restart: () => gateways2.restart(),
|
|
28752
|
+
hold: (held) => bridge?.hold(held),
|
|
28753
|
+
say: async (trouble) => {
|
|
28754
|
+
if (bridge !== void 0) return bridge.restarted(trouble);
|
|
28755
|
+
await (trouble === void 0 ? chips.reportOk() : chips.reportTrouble(trouble));
|
|
28756
|
+
},
|
|
28757
|
+
log: (line2) => log(`gateway: ${line2}`)
|
|
28758
|
+
});
|
|
27844
28759
|
var chips = await connect({
|
|
27845
28760
|
url: joined.url,
|
|
27846
28761
|
token: joined.token,
|
|
27847
28762
|
client: { name: "chips-hermes", version: "1.0.0" },
|
|
27848
|
-
store: fileStore(
|
|
28763
|
+
store: fileStore(join13(folder, "chips-state.json")),
|
|
28764
|
+
onRestart: restart,
|
|
27849
28765
|
settings: settingsFor(() => bridge)
|
|
27850
28766
|
});
|
|
27851
28767
|
var git = await checkGit();
|
|
@@ -27860,7 +28776,10 @@ bridge = new Bridge({
|
|
|
27860
28776
|
saveState,
|
|
27861
28777
|
log,
|
|
27862
28778
|
hermesRoot: root,
|
|
27863
|
-
fills: { port: fills.port, secret: fills.secret }
|
|
28779
|
+
fills: { port: fills.port, secret: fills.secret },
|
|
28780
|
+
// Each agent made in Chips is a Hermes profile of its own (Chips ADR 0241).
|
|
28781
|
+
profiles: hermesProfiles({ root, env: process.env, system: thisMachine(), log: (line2) => log(`profiles: ${line2}`) }),
|
|
28782
|
+
restartGateway: () => restart({ askedBy: "chips-hermes, for a new agent's profile", askedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
27864
28783
|
});
|
|
27865
28784
|
await bridge.start();
|
|
27866
28785
|
log(`answering for ${config2.agents.map((agent) => agent.name).join(", ")} through Hermes`);
|