@chipschat/hermes 1.0.4 → 1.0.7
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 +54 -7
- package/dist/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/main.js +1928 -196
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -8,9 +8,11 @@ var __export = (target, all) => {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
// src/main.ts
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
11
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
12
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
13
|
+
import { createServer as createServer2 } from "node:net";
|
|
14
|
+
import { homedir as homedir8 } from "node:os";
|
|
15
|
+
import { dirname as dirname6, join as join12, resolve as resolve6 } from "node:path";
|
|
14
16
|
|
|
15
17
|
// node_modules/zod/v4/classic/external.js
|
|
16
18
|
var external_exports = {};
|
|
@@ -845,10 +847,10 @@ function promiseAllObject(promisesObj) {
|
|
|
845
847
|
return resolvedObj;
|
|
846
848
|
});
|
|
847
849
|
}
|
|
848
|
-
function randomString(
|
|
850
|
+
function randomString(length2 = 10) {
|
|
849
851
|
const chars = "abcdefghijklmnopqrstuvwxyz";
|
|
850
852
|
let str = "";
|
|
851
|
-
for (let i = 0; i <
|
|
853
|
+
for (let i = 0; i < length2; i++) {
|
|
852
854
|
str += chars[Math.floor(Math.random() * chars.length)];
|
|
853
855
|
}
|
|
854
856
|
return str;
|
|
@@ -1996,8 +1998,8 @@ var ulid = /^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/;
|
|
|
1996
1998
|
var xid = /^[0-9a-vA-V]{20}$/;
|
|
1997
1999
|
var ksuid = /^[A-Za-z0-9]{27}$/;
|
|
1998
2000
|
var nanoid = /^[a-zA-Z0-9_-]{21}$/;
|
|
1999
|
-
function nanoidOfLength(
|
|
2000
|
-
return new RegExp(`^[a-zA-Z0-9_-]{${
|
|
2001
|
+
function nanoidOfLength(length2) {
|
|
2002
|
+
return new RegExp(`^[a-zA-Z0-9_-]{${length2}}$`);
|
|
2001
2003
|
}
|
|
2002
2004
|
var duration = /^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/;
|
|
2003
2005
|
var extendedDuration = /^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/;
|
|
@@ -2022,8 +2024,8 @@ function emoji() {
|
|
|
2022
2024
|
}
|
|
2023
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])$/;
|
|
2024
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}|:))$/;
|
|
2025
|
-
var mac = (
|
|
2026
|
-
const escapedDelim = escapeRegex(
|
|
2027
|
+
var mac = (delimiter3) => {
|
|
2028
|
+
const escapedDelim = escapeRegex(delimiter3 ?? ":");
|
|
2027
2029
|
return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
|
|
2028
2030
|
};
|
|
2029
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])$/;
|
|
@@ -2075,8 +2077,8 @@ var hex = /^[0-9a-fA-F]*$/;
|
|
|
2075
2077
|
function fixedBase64(bodyLength, padding) {
|
|
2076
2078
|
return new RegExp(`^[A-Za-z0-9+/]{${bodyLength}}${padding}$`);
|
|
2077
2079
|
}
|
|
2078
|
-
function fixedBase64url(
|
|
2079
|
-
return new RegExp(`^[A-Za-z0-9_-]{${
|
|
2080
|
+
function fixedBase64url(length2) {
|
|
2081
|
+
return new RegExp(`^[A-Za-z0-9_-]{${length2}}$`);
|
|
2080
2082
|
}
|
|
2081
2083
|
var md5_hex = /^[0-9a-fA-F]{32}$/;
|
|
2082
2084
|
var md5_base64 = /* @__PURE__ */ fixedBase64(22, "==");
|
|
@@ -2339,8 +2341,8 @@ var $ZodCheckMaxLength = /* @__PURE__ */ $constructor("$ZodCheckMaxLength", (ins
|
|
|
2339
2341
|
inst._zod.check = (payload) => {
|
|
2340
2342
|
const input2 = payload.value;
|
|
2341
2343
|
const units = input2.length;
|
|
2342
|
-
const
|
|
2343
|
-
if (
|
|
2344
|
+
const length2 = typeof input2 === "string" && units > def.maximum ? codePointLength(input2) : units;
|
|
2345
|
+
if (length2 <= def.maximum)
|
|
2344
2346
|
return;
|
|
2345
2347
|
const origin = getLengthableOrigin(input2);
|
|
2346
2348
|
payload.issues.push({
|
|
@@ -2361,8 +2363,8 @@ var $ZodCheckMinLength = /* @__PURE__ */ $constructor("$ZodCheckMinLength", (ins
|
|
|
2361
2363
|
inst._zod.check = (payload) => {
|
|
2362
2364
|
const input2 = payload.value;
|
|
2363
2365
|
const units = input2.length;
|
|
2364
|
-
const
|
|
2365
|
-
if (
|
|
2366
|
+
const length2 = typeof input2 === "string" && units >= def.minimum && units < def.minimum * 2 ? codePointLength(input2) : units;
|
|
2367
|
+
if (length2 >= def.minimum)
|
|
2366
2368
|
return;
|
|
2367
2369
|
const origin = getLengthableOrigin(input2);
|
|
2368
2370
|
payload.issues.push({
|
|
@@ -2383,11 +2385,11 @@ var $ZodCheckLengthEquals = /* @__PURE__ */ $constructor("$ZodCheckLengthEquals"
|
|
|
2383
2385
|
inst._zod.check = (payload) => {
|
|
2384
2386
|
const input2 = payload.value;
|
|
2385
2387
|
const units = input2.length;
|
|
2386
|
-
const
|
|
2387
|
-
if (
|
|
2388
|
+
const length2 = typeof input2 === "string" && units >= def.length && units <= def.length * 2 ? codePointLength(input2) : units;
|
|
2389
|
+
if (length2 === def.length)
|
|
2388
2390
|
return;
|
|
2389
2391
|
const origin = getLengthableOrigin(input2);
|
|
2390
|
-
const tooBig =
|
|
2392
|
+
const tooBig = length2 > def.length;
|
|
2391
2393
|
payload.issues.push({
|
|
2392
2394
|
origin,
|
|
2393
2395
|
...tooBig ? { code: "too_big", maximum: def.length } : { code: "too_small", minimum: def.length },
|
|
@@ -2597,8 +2599,8 @@ var Doc = class {
|
|
|
2597
2599
|
const lines2 = content.split("\n").filter((x) => x);
|
|
2598
2600
|
const minIndent = Math.min(...lines2.map((x) => x.length - x.trimStart().length));
|
|
2599
2601
|
const dedented = lines2.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x);
|
|
2600
|
-
for (const
|
|
2601
|
-
this.content.push(
|
|
2602
|
+
for (const line2 of dedented) {
|
|
2603
|
+
this.content.push(line2);
|
|
2602
2604
|
}
|
|
2603
2605
|
}
|
|
2604
2606
|
compile() {
|
|
@@ -3079,11 +3081,11 @@ var $ZodE164 = /* @__PURE__ */ $constructor("$ZodE164", (inst, def) => {
|
|
|
3079
3081
|
});
|
|
3080
3082
|
var CC_SANITIZE = /[- ]/g;
|
|
3081
3083
|
function isLuhnAlgo(digits) {
|
|
3082
|
-
let
|
|
3084
|
+
let length2 = digits.length;
|
|
3083
3085
|
let bit = 1;
|
|
3084
3086
|
let sum = 0;
|
|
3085
|
-
while (
|
|
3086
|
-
const value = digits.charCodeAt(--
|
|
3087
|
+
while (length2) {
|
|
3088
|
+
const value = digits.charCodeAt(--length2) - 48;
|
|
3087
3089
|
bit ^= 1;
|
|
3088
3090
|
sum += bit ? [0, 2, 4, 6, 8, 1, 3, 5, 7, 9][value] : value;
|
|
3089
3091
|
}
|
|
@@ -5079,7 +5081,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
5079
5081
|
var NONE = 0;
|
|
5080
5082
|
var ASSUMED = 1;
|
|
5081
5083
|
var PROVEN = 2;
|
|
5082
|
-
function isRecursive(inst, stack,
|
|
5084
|
+
function isRecursive(inst, stack, resolve7) {
|
|
5083
5085
|
const cached2 = recursive.get(inst);
|
|
5084
5086
|
if (cached2 !== void 0)
|
|
5085
5087
|
return cached2 ? PROVEN : NONE;
|
|
@@ -5089,7 +5091,7 @@ function isRecursive(inst, stack, resolve5) {
|
|
|
5089
5091
|
let result = NONE;
|
|
5090
5092
|
const check2 = (child) => {
|
|
5091
5093
|
if (result !== PROVEN && child?._zod) {
|
|
5092
|
-
const answer = isRecursive(child, stack,
|
|
5094
|
+
const answer = isRecursive(child, stack, resolve7);
|
|
5093
5095
|
if (answer > result)
|
|
5094
5096
|
result = answer;
|
|
5095
5097
|
}
|
|
@@ -5100,7 +5102,7 @@ function isRecursive(inst, stack, resolve5) {
|
|
|
5100
5102
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
5101
5103
|
if (spread && !desc.enumerable)
|
|
5102
5104
|
continue;
|
|
5103
|
-
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;
|
|
5104
5106
|
if (child > answer)
|
|
5105
5107
|
answer = child;
|
|
5106
5108
|
}
|
|
@@ -5164,7 +5166,7 @@ function isRecursive(inst, stack, resolve5) {
|
|
|
5164
5166
|
break;
|
|
5165
5167
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
5166
5168
|
case "lazy": {
|
|
5167
|
-
const inner = def._cachedInner ?? (
|
|
5169
|
+
const inner = def._cachedInner ?? (resolve7 ? inst._zod.innerType : void 0);
|
|
5168
5170
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
5169
5171
|
break;
|
|
5170
5172
|
}
|
|
@@ -15157,11 +15159,11 @@ function _minLength(minimum, params) {
|
|
|
15157
15159
|
});
|
|
15158
15160
|
}
|
|
15159
15161
|
// @__NO_SIDE_EFFECTS__
|
|
15160
|
-
function _length(
|
|
15162
|
+
function _length(length2, params) {
|
|
15161
15163
|
return new $ZodCheckLengthEquals({
|
|
15162
15164
|
check: "length_equals",
|
|
15163
15165
|
...normalizeParams(params),
|
|
15164
|
-
length
|
|
15166
|
+
length: length2
|
|
15165
15167
|
});
|
|
15166
15168
|
}
|
|
15167
15169
|
// @__NO_SIDE_EFFECTS__
|
|
@@ -15728,8 +15730,8 @@ function encodeJSONPointerSegment(segment) {
|
|
|
15728
15730
|
return segment.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
15729
15731
|
}
|
|
15730
15732
|
function extractDefs(ctx, schema) {
|
|
15731
|
-
const
|
|
15732
|
-
if (!
|
|
15733
|
+
const root2 = ctx.seen.get(schema);
|
|
15734
|
+
if (!root2)
|
|
15733
15735
|
throw new Error("Unprocessed schema. This is a bug in Zod.");
|
|
15734
15736
|
if (ctx.external && ctx.sharedDefsExtractedFor === ctx.external)
|
|
15735
15737
|
return;
|
|
@@ -15758,7 +15760,7 @@ function extractDefs(ctx, schema) {
|
|
|
15758
15760
|
}
|
|
15759
15761
|
const uriPrefix = `#`;
|
|
15760
15762
|
const defUriPrefix = `${uriPrefix}/${defsSegment}/`;
|
|
15761
|
-
if (entry[1] ===
|
|
15763
|
+
if (entry[1] === root2 && !entry[1].schema.id) {
|
|
15762
15764
|
return { ref: uriPrefix };
|
|
15763
15765
|
}
|
|
15764
15766
|
const defId = entry[1].schema.id ?? `__schema${ctx.counter++}`;
|
|
@@ -15929,8 +15931,8 @@ function foldIntersection(json2) {
|
|
|
15929
15931
|
assignProps(json2, folded);
|
|
15930
15932
|
}
|
|
15931
15933
|
function finalize(ctx, schema) {
|
|
15932
|
-
const
|
|
15933
|
-
if (!
|
|
15934
|
+
const root2 = ctx.seen.get(schema);
|
|
15935
|
+
if (!root2)
|
|
15934
15936
|
throw new Error("Unprocessed schema. This is a bug in Zod.");
|
|
15935
15937
|
const flattenRef = (zodSchema) => {
|
|
15936
15938
|
const seen = ctx.seen.get(zodSchema);
|
|
@@ -16041,7 +16043,7 @@ function finalize(ctx, schema) {
|
|
|
16041
16043
|
throw new Error("Schema is missing an `id` property");
|
|
16042
16044
|
result.$id = ctx.external.uri(id2);
|
|
16043
16045
|
}
|
|
16044
|
-
assignProps(result,
|
|
16046
|
+
assignProps(result, root2.defId ? root2.schema : root2.def ?? root2.schema);
|
|
16045
16047
|
const rootMetaId = ctx.metadataRegistry.get(schema)?.id;
|
|
16046
16048
|
if (rootMetaId !== void 0 && result.id === rootMetaId)
|
|
16047
16049
|
delete result.id;
|
|
@@ -20533,31 +20535,31 @@ function withRole(name2, from) {
|
|
|
20533
20535
|
function flat(text) {
|
|
20534
20536
|
return text.replace(/\s*\n\s*/g, " ").trim();
|
|
20535
20537
|
}
|
|
20536
|
-
function wordsOf(
|
|
20537
|
-
const text = flat(
|
|
20538
|
-
const heard =
|
|
20538
|
+
function wordsOf(line2) {
|
|
20539
|
+
const text = flat(line2.text ?? "");
|
|
20540
|
+
const heard = line2.transcript === void 0 ? "" : flat(line2.transcript);
|
|
20539
20541
|
const parts = [text];
|
|
20540
20542
|
if (heard !== "")
|
|
20541
20543
|
parts.push(text === "" ? `(voice note) ${heard}` : `(voice note: ${heard})`);
|
|
20542
|
-
if (heard === "" &&
|
|
20543
|
-
parts.push(`(sent ${
|
|
20544
|
-
if (
|
|
20544
|
+
if (heard === "" && line2.files !== void 0)
|
|
20545
|
+
parts.push(`(sent ${line2.files.join(", ")})`);
|
|
20546
|
+
if (line2.held === true)
|
|
20545
20547
|
parts.push("(held by Chips until a person acts)");
|
|
20546
20548
|
return parts.filter((part) => part !== "").join(" ");
|
|
20547
20549
|
}
|
|
20548
|
-
function renderLine(
|
|
20549
|
-
const name2 =
|
|
20550
|
-
switch (
|
|
20550
|
+
function renderLine(line2) {
|
|
20551
|
+
const name2 = line2.from.name;
|
|
20552
|
+
switch (line2.kind) {
|
|
20551
20553
|
case "said":
|
|
20552
|
-
return `${withRole(name2,
|
|
20554
|
+
return `${withRole(name2, line2.from)}: ${wordsOf(line2)}`;
|
|
20553
20555
|
case "joined":
|
|
20554
20556
|
return `(${name2} joined the space)`;
|
|
20555
20557
|
case "left":
|
|
20556
20558
|
return `(${name2} left the space)`;
|
|
20557
20559
|
case "renamed":
|
|
20558
|
-
if (
|
|
20559
|
-
return `(${name2} renamed the space "${
|
|
20560
|
-
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})`;
|
|
20561
20563
|
case "archived":
|
|
20562
20564
|
return `(${name2} archived the space)`;
|
|
20563
20565
|
default:
|
|
@@ -20567,8 +20569,8 @@ function renderLine(line) {
|
|
|
20567
20569
|
function renderContext(context) {
|
|
20568
20570
|
if (context === void 0)
|
|
20569
20571
|
return "";
|
|
20570
|
-
const
|
|
20571
|
-
return [
|
|
20572
|
+
const said2 = context.lines.length === 0 ? "" : [CONTEXT_HEADING, ...context.lines.map(renderLine)].join("\n");
|
|
20573
|
+
return [said2, renderRoomFiles(context.files)].filter((part) => part !== "").join("\n\n");
|
|
20572
20574
|
}
|
|
20573
20575
|
function renderAttachment(file3) {
|
|
20574
20576
|
const heard = file3.transcript === void 0 ? "" : `, which says: "${file3.transcript}"`;
|
|
@@ -20582,17 +20584,17 @@ function renderReplyingTo(replying, reading) {
|
|
|
20582
20584
|
const whose = author === reading.taker ? "your message" : author === reading.sender.id ? "their own message" : `${withRole(replying.from.name, replying.from)}'s message`;
|
|
20583
20585
|
const gone = replying.deleted === true ? ", which has since been deleted" : "";
|
|
20584
20586
|
const replier = withRole(reading.sender.name, reading.sender);
|
|
20585
|
-
const
|
|
20587
|
+
const said2 = [
|
|
20586
20588
|
`${replier} is replying to ${whose} from ${saidWhen(replying.at, reading)}${gone}.`
|
|
20587
20589
|
];
|
|
20588
20590
|
if (replying.before !== void 0 && replying.before.length > 0) {
|
|
20589
|
-
|
|
20591
|
+
said2.push(REPLY_BEFORE_HEADING, ...replying.before.map(renderLine));
|
|
20590
20592
|
}
|
|
20591
20593
|
if (replying.deleted === true)
|
|
20592
|
-
return
|
|
20594
|
+
return said2.join("\n");
|
|
20593
20595
|
const edited = replying.edited === true ? " (edited)" : "";
|
|
20594
|
-
|
|
20595
|
-
return
|
|
20596
|
+
said2.push(`${whose[0]?.toUpperCase()}${whose.slice(1)}${edited}:`, ...quoteLines(replying));
|
|
20597
|
+
return said2.join("\n");
|
|
20596
20598
|
}
|
|
20597
20599
|
function quoteLines(replying) {
|
|
20598
20600
|
const text = replying.text ?? "";
|
|
@@ -20608,7 +20610,7 @@ function quoteLines(replying) {
|
|
|
20608
20610
|
body.push(`(sent ${replying.files.join(", ")})`);
|
|
20609
20611
|
}
|
|
20610
20612
|
}
|
|
20611
|
-
return body.map((
|
|
20613
|
+
return body.map((line2) => line2 === "" ? ">" : `> ${line2}`);
|
|
20612
20614
|
}
|
|
20613
20615
|
var DAY_MS = 864e5;
|
|
20614
20616
|
function saidWhen(at, reading) {
|
|
@@ -20698,12 +20700,14 @@ var contextAnswer = {
|
|
|
20698
20700
|
message: external_exports.string().min(1).max(300).optional().describe("What went wrong, for the person.")
|
|
20699
20701
|
}).optional().describe("An edit not made. The copy beside it is the file as it is now.")
|
|
20700
20702
|
};
|
|
20703
|
+
var readNote = external_exports.string().min(1).max(300).describe("When the agent reads these, in the harness's words: `Hermes reads these when a conversation starts.` Absent: at the start of every turn.");
|
|
20701
20704
|
function uniqueNames(files) {
|
|
20702
20705
|
return new Set(files.map((file3) => file3.name)).size === files.length;
|
|
20703
20706
|
}
|
|
20704
20707
|
var agentContextPut = external_exports.object({
|
|
20705
20708
|
files: external_exports.array(contextFile).max(CONTEXT_FILES_SHOWN).refine(uniqueNames, { message: "Each file is named once." }).describe("The agent's files, in the order its page shows them: eight at most."),
|
|
20706
20709
|
not_shown: external_exports.array(notShownFile).max(CONTEXT_NOT_SHOWN_MAX).optional(),
|
|
20710
|
+
read_note: readNote.optional(),
|
|
20707
20711
|
...contextAnswer
|
|
20708
20712
|
}).describe("`PUT /v1/agents/:id/context`: a harness's copy of one of its agents' files.");
|
|
20709
20713
|
var harnessContextPut = external_exports.object({
|
|
@@ -20736,6 +20740,7 @@ var agentContextView = external_exports.object({
|
|
|
20736
20740
|
harness_id: id("harness"),
|
|
20737
20741
|
files: external_exports.array(contextFile),
|
|
20738
20742
|
not_shown: external_exports.array(notShownFile),
|
|
20743
|
+
read_note: readNote.optional(),
|
|
20739
20744
|
read_at: isoTimestamp.describe("When the harness last sent its copy."),
|
|
20740
20745
|
away: external_exports.boolean().describe("No socket of the harness hears edits now: read, but not saved.")
|
|
20741
20746
|
}).describe("`GET /v1/agents/:id/context`, for the harness's owner.");
|
|
@@ -22401,6 +22406,89 @@ var EVENT_TYPES = EVENTS.map((event) => event.type);
|
|
|
22401
22406
|
var byType = new Map(EVENTS.map((event) => [event.type, event]));
|
|
22402
22407
|
var schemas = new Map(EVENTS.map((event) => [event.type, eventSchema(event)]));
|
|
22403
22408
|
|
|
22409
|
+
// node_modules/@chipschat/protocol/dist/src/names.js
|
|
22410
|
+
var LOOK_LIKE = {
|
|
22411
|
+
A: "\u0410\u0391",
|
|
22412
|
+
// Cyrillic А, Greek Α
|
|
22413
|
+
B: "\u0412\u0392",
|
|
22414
|
+
// Cyrillic В, Greek Β
|
|
22415
|
+
C: "\u0421",
|
|
22416
|
+
// Cyrillic С
|
|
22417
|
+
E: "\u0415\u0395",
|
|
22418
|
+
// Cyrillic Е, Greek Ε
|
|
22419
|
+
H: "\u041D\u0397",
|
|
22420
|
+
// Cyrillic Н, Greek Η
|
|
22421
|
+
I: "\u0406\u04C0\u0399",
|
|
22422
|
+
// Cyrillic І and Ӏ, Greek Ι
|
|
22423
|
+
J: "\u0408",
|
|
22424
|
+
// Cyrillic Ј
|
|
22425
|
+
K: "\u041A\u039A",
|
|
22426
|
+
// Cyrillic К, Greek Κ
|
|
22427
|
+
M: "\u041C\u039C",
|
|
22428
|
+
// Cyrillic М, Greek Μ
|
|
22429
|
+
N: "\u039D",
|
|
22430
|
+
// Greek Ν
|
|
22431
|
+
O: "\u041E\u039F",
|
|
22432
|
+
// Cyrillic О, Greek Ο
|
|
22433
|
+
P: "\u0420\u03A1",
|
|
22434
|
+
// Cyrillic Р, Greek Ρ
|
|
22435
|
+
Q: "\u051A",
|
|
22436
|
+
// Cyrillic Ԛ
|
|
22437
|
+
S: "\u0405",
|
|
22438
|
+
// Cyrillic Ѕ
|
|
22439
|
+
T: "\u0422\u03A4",
|
|
22440
|
+
// Cyrillic Т, Greek Τ
|
|
22441
|
+
W: "\u051C",
|
|
22442
|
+
// Cyrillic Ԝ
|
|
22443
|
+
X: "\u0425\u03A7",
|
|
22444
|
+
// Cyrillic Х, Greek Χ
|
|
22445
|
+
Y: "\u0423\u04AE\u03A5",
|
|
22446
|
+
// Cyrillic У and Ү, Greek Υ
|
|
22447
|
+
Z: "\u0396",
|
|
22448
|
+
// Greek Ζ
|
|
22449
|
+
a: "\u0430\u03B1",
|
|
22450
|
+
// Cyrillic а, Greek α
|
|
22451
|
+
c: "\u0441",
|
|
22452
|
+
// Cyrillic с
|
|
22453
|
+
d: "\u0501",
|
|
22454
|
+
// Cyrillic ԁ
|
|
22455
|
+
e: "\u0435",
|
|
22456
|
+
// Cyrillic е
|
|
22457
|
+
h: "\u04BB",
|
|
22458
|
+
// Cyrillic һ
|
|
22459
|
+
i: "\u0456\u03B9\u0131",
|
|
22460
|
+
// Cyrillic і, Greek ι, Latin dotless ı
|
|
22461
|
+
j: "\u0458",
|
|
22462
|
+
// Cyrillic ј
|
|
22463
|
+
k: "\u03BA",
|
|
22464
|
+
// Greek κ
|
|
22465
|
+
l: "\u04CF",
|
|
22466
|
+
// Cyrillic ӏ
|
|
22467
|
+
o: "\u043E\u03BF",
|
|
22468
|
+
// Cyrillic о, Greek ο
|
|
22469
|
+
p: "\u0440\u03C1",
|
|
22470
|
+
// Cyrillic р, Greek ρ
|
|
22471
|
+
q: "\u051B",
|
|
22472
|
+
// Cyrillic ԛ
|
|
22473
|
+
s: "\u0455",
|
|
22474
|
+
// Cyrillic ѕ
|
|
22475
|
+
u: "\u03C5",
|
|
22476
|
+
// Greek υ
|
|
22477
|
+
v: "\u03BD",
|
|
22478
|
+
// Greek ν
|
|
22479
|
+
w: "\u051D",
|
|
22480
|
+
// Cyrillic ԝ
|
|
22481
|
+
x: "\u0445\u03C7",
|
|
22482
|
+
// Cyrillic х, Greek χ
|
|
22483
|
+
y: "\u0443\u04AF"
|
|
22484
|
+
// Cyrillic у and ү
|
|
22485
|
+
};
|
|
22486
|
+
var LATIN_OF = new Map(Object.entries(LOOK_LIKE).flatMap(([latin, others]) => [...others].map((other) => [other, latin])));
|
|
22487
|
+
var HIDDEN = "\\p{Cc}\\p{Cf}\\u034f\\u115f\\u1160\\u3164\\uffa0\\u2800";
|
|
22488
|
+
var UNSEEN = new RegExp(`[${HIDDEN}\\ufe00-\\ufe0f\\u{e0100}-\\u{e01ef}]`, "gu");
|
|
22489
|
+
var HIDDEN_ONE = new RegExp(`[${HIDDEN}]`, "u");
|
|
22490
|
+
var ALPHABETS = [new RegExp("\\p{Script=Latin}", "u"), new RegExp("\\p{Script=Cyrillic}", "u"), new RegExp("\\p{Script=Greek}", "u")];
|
|
22491
|
+
|
|
22404
22492
|
// node_modules/@chipschat/protocol/dist/src/first-names.js
|
|
22405
22493
|
var AGENT_FIRST_NAMES = [
|
|
22406
22494
|
"Abel Aldo Amir Amos Anders Ansel Anton Ari Arlo Arne Aron Arvid Asher Avi Axel Aziz",
|
|
@@ -22429,7 +22517,7 @@ var AGENT_FIRST_NAMES = [
|
|
|
22429
22517
|
"Xander Xavi Xavier",
|
|
22430
22518
|
"Yann Yaro Yosef Yuri Yusuf Yves",
|
|
22431
22519
|
"Zaid Zane Zeke Zeno Zev Ziggy"
|
|
22432
|
-
].flatMap((
|
|
22520
|
+
].flatMap((line2) => line2.split(" "));
|
|
22433
22521
|
|
|
22434
22522
|
// node_modules/@chipschat/protocol/dist/src/frames.js
|
|
22435
22523
|
var clientHelloFrame = external_exports.object({
|
|
@@ -22758,8 +22846,8 @@ function createOutbox() {
|
|
|
22758
22846
|
const waiting = /* @__PURE__ */ new Map();
|
|
22759
22847
|
return {
|
|
22760
22848
|
add(key, event) {
|
|
22761
|
-
return new Promise((
|
|
22762
|
-
waiting.set(key, { event, resolve:
|
|
22849
|
+
return new Promise((resolve7, reject) => {
|
|
22850
|
+
waiting.set(key, { event, resolve: resolve7, reject });
|
|
22763
22851
|
});
|
|
22764
22852
|
},
|
|
22765
22853
|
unanswered: () => [...waiting.values()].map((entry) => entry.event),
|
|
@@ -22835,7 +22923,7 @@ async function inPieces(call, path, bytes) {
|
|
|
22835
22923
|
async function afterLoss(error62, failures) {
|
|
22836
22924
|
if (error62 instanceof RefusedError || failures >= PIECE_TRIES)
|
|
22837
22925
|
throw error62;
|
|
22838
|
-
await new Promise((
|
|
22926
|
+
await new Promise((resolve7) => setTimeout(resolve7, 250 * failures));
|
|
22839
22927
|
}
|
|
22840
22928
|
function sendPiece(call, path, bytes, offset) {
|
|
22841
22929
|
const piece = bytes.subarray(offset, Math.min(offset + PIECE_BYTES, bytes.byteLength));
|
|
@@ -22861,20 +22949,20 @@ var TRIES = 4;
|
|
|
22861
22949
|
var MARKS = /^(<<<<<<<|>>>>>>>) /m;
|
|
22862
22950
|
var REJECTED = /\[rejected\]|fetch first|non-fast-forward/;
|
|
22863
22951
|
var OWN_CONFIG = /^(core\.(repositoryformatversion|filemode|bare|logallrefupdates|ignorecase|precomposeunicode|symlinks)|remote\.origin\.(url|fetch)|branch\..+\.(remote|merge)|user\.(name|email))$/;
|
|
22864
|
-
var lines = (
|
|
22952
|
+
var lines = (said2) => said2.split("\n").filter((line2) => line2 !== "");
|
|
22865
22953
|
var unique = (names) => [...new Set(names)];
|
|
22866
22954
|
function subjectOf(words4) {
|
|
22867
|
-
const first = words4.split("\n").find((
|
|
22955
|
+
const first = words4.split("\n").find((line2) => line2.trim() !== "")?.trim() ?? "";
|
|
22868
22956
|
if (first === "")
|
|
22869
22957
|
return "A change to the project";
|
|
22870
22958
|
return first.length <= 72 ? first : `${first.slice(0, 71)}\u2026`;
|
|
22871
22959
|
}
|
|
22872
22960
|
function remoteWords(err) {
|
|
22873
|
-
const
|
|
22874
|
-
return
|
|
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
|
+
return said2.length > 0 ? said2.join(" ") : lines(err)[0] ?? "git said nothing";
|
|
22875
22963
|
}
|
|
22876
|
-
function versionIn(
|
|
22877
|
-
const found = /(\d+)\.(\d+)/.exec(
|
|
22964
|
+
function versionIn(said2) {
|
|
22965
|
+
const found = /(\d+)\.(\d+)/.exec(said2);
|
|
22878
22966
|
return found === null ? void 0 : [Number(found[1]), Number(found[2])];
|
|
22879
22967
|
}
|
|
22880
22968
|
function pushedInWords(pushed) {
|
|
@@ -22919,13 +23007,13 @@ function heldNote(pushed, name2) {
|
|
|
22919
23007
|
}
|
|
22920
23008
|
async function checkGit(git2 = "git", path = process.env["PATH"] ?? "/usr/bin:/bin") {
|
|
22921
23009
|
const [major, minor] = versionIn(PROJECT_GIT_VERSION) ?? [0, 0];
|
|
22922
|
-
return new Promise((
|
|
23010
|
+
return new Promise((resolve7) => {
|
|
22923
23011
|
execFile(git2, ["--version"], { env: { PATH: path } }, (error62, stdout) => {
|
|
22924
23012
|
if (error62 !== null)
|
|
22925
|
-
return
|
|
23013
|
+
return resolve7({ ok: false, why: "git was not found on this machine" });
|
|
22926
23014
|
const mine = versionIn(stdout);
|
|
22927
23015
|
const enough = mine !== void 0 && (mine[0] > major || mine[0] === major && mine[1] >= minor);
|
|
22928
|
-
|
|
23016
|
+
resolve7(enough ? { ok: true, version: mine.join(".") } : { ok: false, why: `${stdout.trim()} is older than ${PROJECT_GIT_VERSION}` });
|
|
22929
23017
|
});
|
|
22930
23018
|
});
|
|
22931
23019
|
}
|
|
@@ -23054,8 +23142,8 @@ var Project = class {
|
|
|
23054
23142
|
async standing(remote) {
|
|
23055
23143
|
const mine = await this.head("HEAD");
|
|
23056
23144
|
const base = await this.baseOf(mine, remote);
|
|
23057
|
-
const counted = lines((await this.git(["diff", "--numstat", "--no-renames", "-z", base])).out.replace(/\0/g, "\n")).map((
|
|
23058
|
-
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(" ");
|
|
23059
23147
|
return { path: path.join(" "), added: Number(added2) || 0, removed: Number(removed) || 0 };
|
|
23060
23148
|
});
|
|
23061
23149
|
const added = lines((await this.git(["ls-files", "--others", "--exclude-standard", "-z"])).out.replace(/\0/g, "\n"));
|
|
@@ -23101,8 +23189,8 @@ var Project = class {
|
|
|
23101
23189
|
async markedLines(path) {
|
|
23102
23190
|
try {
|
|
23103
23191
|
const all = (await readFile(join(this.copy, path), "utf8")).split("\n");
|
|
23104
|
-
const start = all.findIndex((
|
|
23105
|
-
const end2 = all.findIndex((
|
|
23192
|
+
const start = all.findIndex((line2) => line2.startsWith("<<<<<<< "));
|
|
23193
|
+
const end2 = all.findIndex((line2, at) => at > start && line2.startsWith(">>>>>>> "));
|
|
23106
23194
|
if (start < 0 || end2 < 0)
|
|
23107
23195
|
return void 0;
|
|
23108
23196
|
const shown2 = all.slice(Math.max(0, start - 1), end2 + 2);
|
|
@@ -23141,10 +23229,10 @@ var Project = class {
|
|
|
23141
23229
|
const files = lines((await this.git(["diff", "--name-only", from, commit])).out);
|
|
23142
23230
|
return { state: "sent", commit, files, over: earlier };
|
|
23143
23231
|
}
|
|
23144
|
-
const
|
|
23232
|
+
const said2 = `${sent.out}
|
|
23145
23233
|
${sent.err}`;
|
|
23146
|
-
if (!REJECTED.test(
|
|
23147
|
-
return { state: "refused", why: remoteWords(
|
|
23234
|
+
if (!REJECTED.test(said2))
|
|
23235
|
+
return { state: "refused", why: remoteWords(said2) };
|
|
23148
23236
|
await this.fetch();
|
|
23149
23237
|
const theirs = await this.head("refs/remotes/origin/main");
|
|
23150
23238
|
const over = unique([...earlier, ...await this.authors(`HEAD..${theirs}`)]);
|
|
@@ -23371,8 +23459,8 @@ ${sent.err}`;
|
|
|
23371
23459
|
env[`GIT_CONFIG_KEY_${at}`] = key;
|
|
23372
23460
|
env[`GIT_CONFIG_VALUE_${at}`] = value;
|
|
23373
23461
|
});
|
|
23374
|
-
return new Promise((
|
|
23375
|
-
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 }));
|
|
23376
23464
|
});
|
|
23377
23465
|
}
|
|
23378
23466
|
};
|
|
@@ -23395,8 +23483,8 @@ var Lane = class {
|
|
|
23395
23483
|
constructor(id2, wire) {
|
|
23396
23484
|
this.id = id2;
|
|
23397
23485
|
this.wire = wire;
|
|
23398
|
-
this.result = new Promise((
|
|
23399
|
-
this.settle =
|
|
23486
|
+
this.result = new Promise((resolve7) => {
|
|
23487
|
+
this.settle = resolve7;
|
|
23400
23488
|
});
|
|
23401
23489
|
this.stop = wire.hear(id2, (event) => this.onUpdated(event));
|
|
23402
23490
|
}
|
|
@@ -23539,7 +23627,7 @@ var Run = class {
|
|
|
23539
23627
|
/** Resolve once the run is not paused. A stopped run resolves too: check `over` after. */
|
|
23540
23628
|
async whenRunning() {
|
|
23541
23629
|
while (this.held && !this.ended) {
|
|
23542
|
-
await new Promise((
|
|
23630
|
+
await new Promise((resolve7) => setTimeout(resolve7, 50));
|
|
23543
23631
|
}
|
|
23544
23632
|
}
|
|
23545
23633
|
status(input2) {
|
|
@@ -24323,8 +24411,8 @@ var Chips = class _Chips {
|
|
|
24323
24411
|
}
|
|
24324
24412
|
/** Resolve with the first event that matches, now or later. */
|
|
24325
24413
|
waitFor(match) {
|
|
24326
|
-
return new Promise((
|
|
24327
|
-
this.waiters.add({ match, resolve:
|
|
24414
|
+
return new Promise((resolve7) => {
|
|
24415
|
+
this.waiters.add({ match, resolve: resolve7 });
|
|
24328
24416
|
});
|
|
24329
24417
|
}
|
|
24330
24418
|
close() {
|
|
@@ -24351,7 +24439,7 @@ var Chips = class _Chips {
|
|
|
24351
24439
|
return `${base}/v1/ws?ticket=${encodeURIComponent(String(ticket))}`;
|
|
24352
24440
|
}
|
|
24353
24441
|
open(first) {
|
|
24354
|
-
return new Promise((
|
|
24442
|
+
return new Promise((resolve7, reject) => {
|
|
24355
24443
|
this.setState("connecting");
|
|
24356
24444
|
const failed = (what, error62) => {
|
|
24357
24445
|
if (first)
|
|
@@ -24369,13 +24457,13 @@ var Chips = class _Chips {
|
|
|
24369
24457
|
}
|
|
24370
24458
|
this.socket = socket;
|
|
24371
24459
|
this.listen(socket, {
|
|
24372
|
-
settled: (hello2) => this.settled(socket, hello2, first,
|
|
24460
|
+
settled: (hello2) => this.settled(socket, hello2, first, resolve7, reject)
|
|
24373
24461
|
});
|
|
24374
24462
|
}, (error62) => failed("could not get a socket ticket", error62));
|
|
24375
24463
|
});
|
|
24376
24464
|
}
|
|
24377
24465
|
/** The handshake's outcome: the hello, a refusal, or the socket gone before either. */
|
|
24378
|
-
settled(socket, hello2, first,
|
|
24466
|
+
settled(socket, hello2, first, resolve7, reject) {
|
|
24379
24467
|
if (hello2 === void 0) {
|
|
24380
24468
|
if (first)
|
|
24381
24469
|
reject(new ConnectError("the server closed the socket before saying hello"));
|
|
@@ -24389,7 +24477,7 @@ var Chips = class _Chips {
|
|
|
24389
24477
|
return;
|
|
24390
24478
|
}
|
|
24391
24479
|
this.onHello(socket, hello2);
|
|
24392
|
-
void this.subscribed(socket).then(
|
|
24480
|
+
void this.subscribed(socket).then(resolve7);
|
|
24393
24481
|
}
|
|
24394
24482
|
/** The socket's four events. `settled` fires once: with the hello, a refusal, or nothing. */
|
|
24395
24483
|
listen(socket, on) {
|
|
@@ -24484,15 +24572,15 @@ var Chips = class _Chips {
|
|
|
24484
24572
|
subscribed(socket) {
|
|
24485
24573
|
this.pings += 1;
|
|
24486
24574
|
const ts = Date.now() + this.pings;
|
|
24487
|
-
return new Promise((
|
|
24575
|
+
return new Promise((resolve7) => {
|
|
24488
24576
|
const timer = setTimeout(() => {
|
|
24489
24577
|
this.pongs.delete(ts);
|
|
24490
|
-
|
|
24578
|
+
resolve7();
|
|
24491
24579
|
}, 2e3);
|
|
24492
24580
|
this.pongs.set(ts, () => {
|
|
24493
24581
|
clearTimeout(timer);
|
|
24494
24582
|
this.pongs.delete(ts);
|
|
24495
|
-
|
|
24583
|
+
resolve7();
|
|
24496
24584
|
});
|
|
24497
24585
|
socket.send(JSON.stringify({ frame: "ping", ts }));
|
|
24498
24586
|
});
|
|
@@ -24875,7 +24963,7 @@ function openKeyStore(env, platform = process.platform) {
|
|
|
24875
24963
|
// node_modules/@chipschat/harness/dist/steps.js
|
|
24876
24964
|
import { writeSync } from "node:fs";
|
|
24877
24965
|
var STEP_MARK = "chips-step ";
|
|
24878
|
-
function reportStep(step, detail = {}, env = process.env, write = (
|
|
24966
|
+
function reportStep(step, detail = {}, env = process.env, write = (line2) => void writeSync(1, line2)) {
|
|
24879
24967
|
if (env["CHIPS_STEPS"] !== "json")
|
|
24880
24968
|
return;
|
|
24881
24969
|
try {
|
|
@@ -24896,8 +24984,8 @@ var JoinError = class extends Error {
|
|
|
24896
24984
|
};
|
|
24897
24985
|
function serverJsonUrl(env) {
|
|
24898
24986
|
try {
|
|
24899
|
-
const
|
|
24900
|
-
return typeof
|
|
24987
|
+
const said2 = JSON.parse(readFileSync2(joinPath(configHome(env), "server.json"), "utf8"));
|
|
24988
|
+
return typeof said2.url === "string" && said2.url !== "" ? said2.url : void 0;
|
|
24901
24989
|
} catch {
|
|
24902
24990
|
return void 0;
|
|
24903
24991
|
}
|
|
@@ -24956,8 +25044,8 @@ async function refused(response) {
|
|
|
24956
25044
|
const body = await response.json().catch(() => ({}));
|
|
24957
25045
|
return new JoinError(body.error?.refusal ?? body.error?.code ?? `http_${response.status}`, body.error?.message ?? `Chips answered ${response.status}.`);
|
|
24958
25046
|
}
|
|
24959
|
-
var wait = (ms, signal) => new Promise((
|
|
24960
|
-
const timer = setTimeout(
|
|
25047
|
+
var wait = (ms, signal) => new Promise((resolve7, reject) => {
|
|
25048
|
+
const timer = setTimeout(resolve7, ms);
|
|
24961
25049
|
signal?.addEventListener("abort", () => {
|
|
24962
25050
|
clearTimeout(timer);
|
|
24963
25051
|
reject(new JoinError("stopped", "Stopped before a person decided."));
|
|
@@ -25081,8 +25169,8 @@ var MOST_FILES = 20;
|
|
|
25081
25169
|
var MOST_FILE_BYTES = 100 * 1024 * 1024;
|
|
25082
25170
|
function filesNamedIn(answer) {
|
|
25083
25171
|
const named = [];
|
|
25084
|
-
const kept = answer.split("\n").filter((
|
|
25085
|
-
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);
|
|
25086
25174
|
if (media?.[1] === void 0)
|
|
25087
25175
|
return true;
|
|
25088
25176
|
if (!named.includes(media[1]))
|
|
@@ -25097,21 +25185,21 @@ function filesNamedIn(answer) {
|
|
|
25097
25185
|
function inside(path, folder2) {
|
|
25098
25186
|
return path === folder2 || path.startsWith(folder2.endsWith(sep) ? folder2 : folder2 + sep);
|
|
25099
25187
|
}
|
|
25100
|
-
function pathOf(
|
|
25101
|
-
if (/^https?:\/\//i.test(
|
|
25102
|
-
return { kind: "link", url:
|
|
25103
|
-
if (/^file:/i.test(
|
|
25188
|
+
function pathOf(said2) {
|
|
25189
|
+
if (/^https?:\/\//i.test(said2))
|
|
25190
|
+
return { kind: "link", url: said2 };
|
|
25191
|
+
if (/^file:/i.test(said2)) {
|
|
25104
25192
|
try {
|
|
25105
|
-
return fileURLToPath(
|
|
25193
|
+
return fileURLToPath(said2);
|
|
25106
25194
|
} catch {
|
|
25107
|
-
return { kind: "refused", name:
|
|
25195
|
+
return { kind: "refused", name: said2, why: "its address is not a file's" };
|
|
25108
25196
|
}
|
|
25109
25197
|
}
|
|
25110
|
-
return
|
|
25198
|
+
return said2 === "~" || said2.startsWith("~/") ? homedir2() + said2.slice(1) : said2;
|
|
25111
25199
|
}
|
|
25112
25200
|
async function refusalOf2(real, folders) {
|
|
25113
25201
|
const roots = await Promise.all(folders.map((folder2) => realpath(folder2).catch(() => void 0)));
|
|
25114
|
-
if (!roots.some((
|
|
25202
|
+
if (!roots.some((root2) => root2 !== void 0 && inside(real, root2))) {
|
|
25115
25203
|
return "it is outside the agent's folder";
|
|
25116
25204
|
}
|
|
25117
25205
|
const info = await stat2(real);
|
|
@@ -25124,11 +25212,11 @@ async function refusalOf2(real, folders) {
|
|
|
25124
25212
|
return void 0;
|
|
25125
25213
|
}
|
|
25126
25214
|
async function readSendable(reference, folders) {
|
|
25127
|
-
const
|
|
25128
|
-
const path = pathOf(
|
|
25215
|
+
const said2 = reference.trim().replace(/^<(.*)>$/, "$1");
|
|
25216
|
+
const path = pathOf(said2);
|
|
25129
25217
|
if (typeof path !== "string")
|
|
25130
25218
|
return path;
|
|
25131
|
-
const name2 = basename2(path) ||
|
|
25219
|
+
const name2 = basename2(path) || said2;
|
|
25132
25220
|
const first = folders[0];
|
|
25133
25221
|
if (first === void 0) {
|
|
25134
25222
|
return { kind: "refused", name: name2, why: "this agent has no folder its files are sent from" };
|
|
@@ -25466,8 +25554,812 @@ function fileStore(path) {
|
|
|
25466
25554
|
}
|
|
25467
25555
|
|
|
25468
25556
|
// src/bridge.ts
|
|
25469
|
-
import { mkdirSync as
|
|
25470
|
-
import { join as
|
|
25557
|
+
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
25558
|
+
import { join as join8 } from "node:path";
|
|
25559
|
+
|
|
25560
|
+
// src/context.ts
|
|
25561
|
+
import { spawn } from "node:child_process";
|
|
25562
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
25563
|
+
import {
|
|
25564
|
+
closeSync,
|
|
25565
|
+
existsSync as existsSync6,
|
|
25566
|
+
fsyncSync,
|
|
25567
|
+
lstatSync,
|
|
25568
|
+
mkdirSync as mkdirSync4,
|
|
25569
|
+
openSync,
|
|
25570
|
+
readFileSync as readFileSync6,
|
|
25571
|
+
realpathSync,
|
|
25572
|
+
renameSync as renameSync2,
|
|
25573
|
+
rmSync as rmSync3,
|
|
25574
|
+
statSync as statSync2,
|
|
25575
|
+
writeSync as writeSync2
|
|
25576
|
+
} from "node:fs";
|
|
25577
|
+
import { basename as basename3, dirname as dirname4, join as join6 } from "node:path";
|
|
25578
|
+
|
|
25579
|
+
// src/home.ts
|
|
25580
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
25581
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, readlinkSync, statSync } from "node:fs";
|
|
25582
|
+
import { homedir as homedir4 } from "node:os";
|
|
25583
|
+
import { join as join5, resolve as resolve2 } from "node:path";
|
|
25584
|
+
function hermesRoot(env = process.env) {
|
|
25585
|
+
return resolve2(env["HERMES_HOME"] || join5(homedir4(), ".hermes"));
|
|
25586
|
+
}
|
|
25587
|
+
function profileHome(root2, profile) {
|
|
25588
|
+
const name2 = (profile ?? "default").trim().toLowerCase();
|
|
25589
|
+
return name2 === "" || name2 === "default" ? root2 : join5(root2, "profiles", name2);
|
|
25590
|
+
}
|
|
25591
|
+
function profileName(profile) {
|
|
25592
|
+
const name2 = (profile ?? "default").trim().toLowerCase();
|
|
25593
|
+
return name2 === "" ? "default" : name2;
|
|
25594
|
+
}
|
|
25595
|
+
function envValue(home2, name2) {
|
|
25596
|
+
const path = join5(home2, ".env");
|
|
25597
|
+
if (!existsSync5(path)) return void 0;
|
|
25598
|
+
let found;
|
|
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
|
+
if (match?.[1] !== name2) continue;
|
|
25602
|
+
const raw = match[2] ?? "";
|
|
25603
|
+
const quoted2 = /^(["'])(.*)\1/.exec(raw);
|
|
25604
|
+
found = quoted2 !== null ? quoted2[2] ?? "" : raw.replace(/\s+#.*$/, "");
|
|
25605
|
+
}
|
|
25606
|
+
return found;
|
|
25607
|
+
}
|
|
25608
|
+
function configText(home2) {
|
|
25609
|
+
const path = join5(home2, "config.yaml");
|
|
25610
|
+
try {
|
|
25611
|
+
return existsSync5(path) ? readFileSync5(path, "utf8") : "";
|
|
25612
|
+
} catch {
|
|
25613
|
+
return "";
|
|
25614
|
+
}
|
|
25615
|
+
}
|
|
25616
|
+
function uncommented(line2) {
|
|
25617
|
+
const indent = line2.length - line2.trimStart().length;
|
|
25618
|
+
let quote;
|
|
25619
|
+
for (let at = indent; at < line2.length; at += 1) {
|
|
25620
|
+
const char = line2[at];
|
|
25621
|
+
if (quote !== void 0) {
|
|
25622
|
+
if (char === quote) quote = void 0;
|
|
25623
|
+
} else if (char === '"' || char === "'") {
|
|
25624
|
+
quote = char;
|
|
25625
|
+
} else if (char === "#" && (at === indent || /\s/.test(line2[at - 1] ?? ""))) {
|
|
25626
|
+
return { indent, text: line2.slice(indent, at).trimEnd() };
|
|
25627
|
+
}
|
|
25628
|
+
}
|
|
25629
|
+
return { indent, text: line2.slice(indent).trimEnd() };
|
|
25630
|
+
}
|
|
25631
|
+
function scalar(text) {
|
|
25632
|
+
const trimmed = text.trim();
|
|
25633
|
+
const quoted2 = /^(["'])(.*)\1$/.exec(trimmed);
|
|
25634
|
+
return quoted2 !== null ? quoted2[2] ?? "" : trimmed;
|
|
25635
|
+
}
|
|
25636
|
+
function flowPairs(text) {
|
|
25637
|
+
const inner = /^\{(.*)\}$/.exec(text.trim())?.[1];
|
|
25638
|
+
if (inner === void 0) return void 0;
|
|
25639
|
+
const pairs = /* @__PURE__ */ new Map();
|
|
25640
|
+
for (const part of inner.split(",")) {
|
|
25641
|
+
if (part.trim() === "") continue;
|
|
25642
|
+
const at = part.indexOf(":");
|
|
25643
|
+
if (at === -1) return void 0;
|
|
25644
|
+
pairs.set(part.slice(0, at).trim(), scalar(part.slice(at + 1)));
|
|
25645
|
+
}
|
|
25646
|
+
return pairs;
|
|
25647
|
+
}
|
|
25648
|
+
function yamlValue(text, section, key) {
|
|
25649
|
+
const lines2 = text.split("\n");
|
|
25650
|
+
for (let at = 0; at < lines2.length; at += 1) {
|
|
25651
|
+
const { indent, text: line2 } = uncommented(lines2[at] ?? "");
|
|
25652
|
+
if (indent !== 0 || !line2.startsWith(`${section}:`)) continue;
|
|
25653
|
+
const rest = line2.slice(section.length + 1).trim();
|
|
25654
|
+
if (rest !== "") return flowPairs(rest)?.get(key);
|
|
25655
|
+
let child;
|
|
25656
|
+
for (let below = at + 1; below < lines2.length; below += 1) {
|
|
25657
|
+
const next = uncommented(lines2[below] ?? "");
|
|
25658
|
+
if (next.text === "") continue;
|
|
25659
|
+
if (next.indent === 0) return void 0;
|
|
25660
|
+
child ??= next.indent;
|
|
25661
|
+
if (next.indent !== child) continue;
|
|
25662
|
+
const match = /^([A-Za-z0-9_.-]+):(.*)$/.exec(next.text);
|
|
25663
|
+
if (match?.[1] === key) return scalar(match[2] ?? "");
|
|
25664
|
+
}
|
|
25665
|
+
return void 0;
|
|
25666
|
+
}
|
|
25667
|
+
return void 0;
|
|
25668
|
+
}
|
|
25669
|
+
function yamlBool(value) {
|
|
25670
|
+
if (value === void 0) return void 0;
|
|
25671
|
+
const word = value.toLowerCase();
|
|
25672
|
+
if (["true", "yes", "on", "y"].includes(word)) return true;
|
|
25673
|
+
if (["false", "no", "off", "n"].includes(word)) return false;
|
|
25674
|
+
return void 0;
|
|
25675
|
+
}
|
|
25676
|
+
function yamlNumber(value) {
|
|
25677
|
+
if (value === void 0 || value === "" || value === "null" || value === "~") return void 0;
|
|
25678
|
+
const number4 = Number(value.replace(/_/g, ""));
|
|
25679
|
+
return Number.isFinite(number4) ? number4 : void 0;
|
|
25680
|
+
}
|
|
25681
|
+
function expanded(path) {
|
|
25682
|
+
return resolve2(path === "~" || path.startsWith("~/") ? homedir4() + path.slice(1) : path);
|
|
25683
|
+
}
|
|
25684
|
+
function isFolder(path) {
|
|
25685
|
+
try {
|
|
25686
|
+
return statSync(path).isDirectory();
|
|
25687
|
+
} catch {
|
|
25688
|
+
return false;
|
|
25689
|
+
}
|
|
25690
|
+
}
|
|
25691
|
+
function workFolder(home2, env = process.env) {
|
|
25692
|
+
const configured = yamlValue(configText(home2), "terminal", "cwd");
|
|
25693
|
+
if (configured !== void 0 && configured !== "" && configured !== ".") {
|
|
25694
|
+
const folder2 = expanded(configured);
|
|
25695
|
+
if (isFolder(folder2)) return folder2;
|
|
25696
|
+
}
|
|
25697
|
+
for (const name2 of ["TERMINAL_CWD", "MESSAGING_CWD"]) {
|
|
25698
|
+
const said2 = envValue(home2, name2) ?? env[name2];
|
|
25699
|
+
if (said2 !== void 0 && said2 !== "" && isFolder(expanded(said2))) return expanded(said2);
|
|
25700
|
+
}
|
|
25701
|
+
return gatewayFolder(home2);
|
|
25702
|
+
}
|
|
25703
|
+
var gateways = /* @__PURE__ */ new Map();
|
|
25704
|
+
function gatewayFolder(home2) {
|
|
25705
|
+
let raw;
|
|
25706
|
+
try {
|
|
25707
|
+
raw = readFileSync5(join5(home2, "gateway.pid"), "utf8").trim();
|
|
25708
|
+
} catch {
|
|
25709
|
+
return void 0;
|
|
25710
|
+
}
|
|
25711
|
+
const known = gateways.get(home2);
|
|
25712
|
+
if (known?.pidFile === raw) return known.folder;
|
|
25713
|
+
const folder2 = folderOfPid(raw);
|
|
25714
|
+
gateways.set(home2, { pidFile: raw, folder: folder2 });
|
|
25715
|
+
return folder2;
|
|
25716
|
+
}
|
|
25717
|
+
function folderOfPid(raw) {
|
|
25718
|
+
let pid;
|
|
25719
|
+
try {
|
|
25720
|
+
const said2 = raw.startsWith("{") ? JSON.parse(raw).pid : Number(raw);
|
|
25721
|
+
pid = typeof said2 === "number" && Number.isInteger(said2) && said2 > 0 ? said2 : void 0;
|
|
25722
|
+
} catch {
|
|
25723
|
+
return void 0;
|
|
25724
|
+
}
|
|
25725
|
+
return pid === void 0 ? void 0 : processFolder(pid);
|
|
25726
|
+
}
|
|
25727
|
+
function processFolder(pid) {
|
|
25728
|
+
try {
|
|
25729
|
+
if (process.platform === "linux") return readlinkSync(`/proc/${pid}/cwd`);
|
|
25730
|
+
const out = execFileSync3("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
|
|
25731
|
+
encoding: "utf8",
|
|
25732
|
+
timeout: 3e3,
|
|
25733
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
25734
|
+
});
|
|
25735
|
+
const line2 = out.split("\n").find((each) => each.startsWith("n"));
|
|
25736
|
+
return line2 === void 0 ? void 0 : line2.slice(1);
|
|
25737
|
+
} catch {
|
|
25738
|
+
return void 0;
|
|
25739
|
+
}
|
|
25740
|
+
}
|
|
25741
|
+
|
|
25742
|
+
// src/context.ts
|
|
25743
|
+
var FILES_SHOWN = 8;
|
|
25744
|
+
var FILE_BYTES = 65536;
|
|
25745
|
+
var READ_NOTE = "Hermes reads these when a conversation starts. A space already talking picks up a change after it next compacts.";
|
|
25746
|
+
var ENTRY_DELIMITER = "\n\xA7\n";
|
|
25747
|
+
var MEMORY_LIMIT = 2200;
|
|
25748
|
+
var USER_LIMIT = 1375;
|
|
25749
|
+
var PROJECT_FILES = [
|
|
25750
|
+
".hermes.md",
|
|
25751
|
+
"HERMES.md",
|
|
25752
|
+
"AGENTS.override.md",
|
|
25753
|
+
"AGENTS.md",
|
|
25754
|
+
"agents.md",
|
|
25755
|
+
"CLAUDE.md",
|
|
25756
|
+
"claude.md",
|
|
25757
|
+
".cursorrules"
|
|
25758
|
+
];
|
|
25759
|
+
var ROWS = {
|
|
25760
|
+
soul: { title: "Who it is", role: "identity" },
|
|
25761
|
+
project: { title: "Project instructions", role: "instructions" },
|
|
25762
|
+
user: { title: "About you", role: "about_you" },
|
|
25763
|
+
memory: { title: "Memory", role: "memory" }
|
|
25764
|
+
};
|
|
25765
|
+
var ORDER = ["soul", "project", "user", "memory"];
|
|
25766
|
+
function readText(path) {
|
|
25767
|
+
try {
|
|
25768
|
+
return readFileSync6(path, "utf8").replace(/^/, "");
|
|
25769
|
+
} catch {
|
|
25770
|
+
return "";
|
|
25771
|
+
}
|
|
25772
|
+
}
|
|
25773
|
+
function hashOf(text) {
|
|
25774
|
+
return createHash2("sha256").update(text, "utf8").digest("hex");
|
|
25775
|
+
}
|
|
25776
|
+
function length(text) {
|
|
25777
|
+
let count = 0;
|
|
25778
|
+
for (const _ of text) count += 1;
|
|
25779
|
+
return count;
|
|
25780
|
+
}
|
|
25781
|
+
var said = (count) => count.toLocaleString("en-US");
|
|
25782
|
+
function entriesOf(text) {
|
|
25783
|
+
return [...new Set(text.split(ENTRY_DELIMITER).map((each) => each.trim()).filter((each) => each !== ""))];
|
|
25784
|
+
}
|
|
25785
|
+
function memoryProblem(text, limit, what) {
|
|
25786
|
+
const total = length(entriesOf(text).join(ENTRY_DELIMITER));
|
|
25787
|
+
if (total > limit) {
|
|
25788
|
+
return `That is ${said(total)} characters, and Hermes keeps ${what} to ${said(limit)}. Take out ${said(total - limit)}.`;
|
|
25789
|
+
}
|
|
25790
|
+
return void 0;
|
|
25791
|
+
}
|
|
25792
|
+
function memorySettings(home2) {
|
|
25793
|
+
const text = configText(home2);
|
|
25794
|
+
const value = (key) => yamlValue(text, "memory", key);
|
|
25795
|
+
return {
|
|
25796
|
+
...yamlBool(value("memory_enabled")) === false ? {} : { memory: yamlNumber(value("memory_char_limit")) ?? MEMORY_LIMIT },
|
|
25797
|
+
...yamlBool(value("user_profile_enabled")) === false ? {} : { user: yamlNumber(value("user_char_limit")) ?? USER_LIMIT }
|
|
25798
|
+
};
|
|
25799
|
+
}
|
|
25800
|
+
function sourcesOf(profile, env = process.env) {
|
|
25801
|
+
const { name: name2, home: home2 } = profile;
|
|
25802
|
+
const found = [];
|
|
25803
|
+
const soul = join6(home2, "SOUL.md");
|
|
25804
|
+
if (existsSync6(soul)) found.push({ path: soul, kind: "soul", file: "SOUL.md", profile: name2 });
|
|
25805
|
+
const folder2 = workFolder(home2, env);
|
|
25806
|
+
const project = folder2 === void 0 ? void 0 : PROJECT_FILES.find((file3) => readText(join6(folder2, file3)).trim() !== "");
|
|
25807
|
+
if (folder2 !== void 0 && project !== void 0) {
|
|
25808
|
+
found.push({ path: join6(folder2, project), kind: "project", file: project, profile: name2 });
|
|
25809
|
+
}
|
|
25810
|
+
const memory = memorySettings(home2);
|
|
25811
|
+
if (memory.user !== void 0) {
|
|
25812
|
+
found.push({ path: join6(home2, "memories", "USER.md"), kind: "user", file: "USER.md", profile: name2, limit: memory.user });
|
|
25813
|
+
}
|
|
25814
|
+
if (memory.memory !== void 0) {
|
|
25815
|
+
found.push({ path: join6(home2, "memories", "MEMORY.md"), kind: "memory", file: "MEMORY.md", profile: name2, limit: memory.memory });
|
|
25816
|
+
}
|
|
25817
|
+
return found;
|
|
25818
|
+
}
|
|
25819
|
+
function aboutOf(source2) {
|
|
25820
|
+
if (source2.limit === void 0) return void 0;
|
|
25821
|
+
const what = source2.kind === "user" ? "What it knows of you" : "Its notes";
|
|
25822
|
+
return `${what}, up to ${said(source2.limit)} characters. A line with only \xA7 starts the next entry.`;
|
|
25823
|
+
}
|
|
25824
|
+
function fileOf(source2, name2) {
|
|
25825
|
+
const text = readText(source2.path);
|
|
25826
|
+
const about = aboutOf(source2);
|
|
25827
|
+
return {
|
|
25828
|
+
tooLarge: Buffer.byteLength(text, "utf8") > FILE_BYTES,
|
|
25829
|
+
file: {
|
|
25830
|
+
name: name2,
|
|
25831
|
+
title: ROWS[source2.kind].title,
|
|
25832
|
+
role: ROWS[source2.kind].role,
|
|
25833
|
+
...about === void 0 ? {} : { about },
|
|
25834
|
+
text,
|
|
25835
|
+
hash: hashOf(text),
|
|
25836
|
+
editable: true
|
|
25837
|
+
}
|
|
25838
|
+
};
|
|
25839
|
+
}
|
|
25840
|
+
var byOrder = (a, b) => ORDER.indexOf(a.kind) - ORDER.indexOf(b.kind) || a.profile.localeCompare(b.profile);
|
|
25841
|
+
var Picture = class {
|
|
25842
|
+
/** Each file by its path, and the agents that read it. */
|
|
25843
|
+
readers = /* @__PURE__ */ new Map();
|
|
25844
|
+
/** The shared files, each with its id and the name the set shows. */
|
|
25845
|
+
shared = /* @__PURE__ */ new Map();
|
|
25846
|
+
/** The agents and the files each reads, in page order. */
|
|
25847
|
+
agents = /* @__PURE__ */ new Map();
|
|
25848
|
+
constructor(readers, first, env = process.env) {
|
|
25849
|
+
const byProfile = /* @__PURE__ */ new Map();
|
|
25850
|
+
const sourcesFor = (profile) => {
|
|
25851
|
+
let sources = byProfile.get(profile.home);
|
|
25852
|
+
if (sources === void 0) {
|
|
25853
|
+
sources = sourcesOf(profile, env);
|
|
25854
|
+
byProfile.set(profile.home, sources);
|
|
25855
|
+
}
|
|
25856
|
+
return sources;
|
|
25857
|
+
};
|
|
25858
|
+
if (first !== void 0) for (const source2 of sourcesFor(first)) this.readers.set(source2.path, { source: source2, agents: [] });
|
|
25859
|
+
for (const reader of readers) {
|
|
25860
|
+
const sources = sourcesFor(reader.profile);
|
|
25861
|
+
this.agents.set(reader.agentId, [...sources].sort(byOrder));
|
|
25862
|
+
for (const source2 of sources) {
|
|
25863
|
+
const entry = this.readers.get(source2.path) ?? { source: source2, agents: [] };
|
|
25864
|
+
if (!entry.agents.includes(reader.agentId)) entry.agents.push(reader.agentId);
|
|
25865
|
+
this.readers.set(source2.path, entry);
|
|
25866
|
+
}
|
|
25867
|
+
}
|
|
25868
|
+
const taken = /* @__PURE__ */ new Set();
|
|
25869
|
+
const ids2 = /* @__PURE__ */ new Set();
|
|
25870
|
+
const sharing = [...this.readers.values()].filter(({ source: source2, agents }) => source2.kind === "project" || source2.profile === first?.name || 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));
|
|
25871
|
+
for (const { source: source2 } of sharing) {
|
|
25872
|
+
const name2 = taken.has(source2.file) ? `${source2.file} (${source2.profile})` : source2.file;
|
|
25873
|
+
taken.add(name2);
|
|
25874
|
+
let id2 = `${source2.profile}-${source2.kind}`.slice(0, 60);
|
|
25875
|
+
for (let count = 2; ids2.has(id2); count += 1) id2 = `${source2.profile}-${source2.kind}-${count}`.slice(0, 64);
|
|
25876
|
+
ids2.add(id2);
|
|
25877
|
+
this.shared.set(source2.path, { id: id2, name: name2 });
|
|
25878
|
+
}
|
|
25879
|
+
}
|
|
25880
|
+
/** The harness's own set, as `PUT /v1/harnesses/:id/context` takes it. */
|
|
25881
|
+
harnessCopy(first) {
|
|
25882
|
+
const shown2 = [];
|
|
25883
|
+
const notShown = [];
|
|
25884
|
+
for (const [path, { id: id2, name: name2 }] of this.shared) {
|
|
25885
|
+
const entry = this.readers.get(path);
|
|
25886
|
+
if (entry === void 0) continue;
|
|
25887
|
+
const { source: source2, agents } = entry;
|
|
25888
|
+
const { file: file3, tooLarge } = fileOf(source2, name2);
|
|
25889
|
+
if (tooLarge || shown2.length >= FILES_SHOWN) {
|
|
25890
|
+
notShown.push({ name: name2, title: ROWS[source2.kind].title, reason: tooLarge ? "too_large" : "count" });
|
|
25891
|
+
continue;
|
|
25892
|
+
}
|
|
25893
|
+
shown2.push({
|
|
25894
|
+
...file3,
|
|
25895
|
+
id: id2,
|
|
25896
|
+
used_by: agents.length,
|
|
25897
|
+
// A new agent runs on the first profile: it reads that profile's files and its project file.
|
|
25898
|
+
...source2.profile === first?.name ? { for_new_agents: true } : {}
|
|
25899
|
+
});
|
|
25900
|
+
}
|
|
25901
|
+
return {
|
|
25902
|
+
shared: shown2,
|
|
25903
|
+
...notShown.length === 0 ? {} : { not_shown: notShown }
|
|
25904
|
+
};
|
|
25905
|
+
}
|
|
25906
|
+
/** One agent's files, as `PUT /v1/agents/:id/context` takes them: its own, and the shared it reads. */
|
|
25907
|
+
agentCopy(agentId2) {
|
|
25908
|
+
const sources = this.agents.get(agentId2);
|
|
25909
|
+
if (sources === void 0) return void 0;
|
|
25910
|
+
const files = [];
|
|
25911
|
+
const notShown = [];
|
|
25912
|
+
for (const source2 of sources) {
|
|
25913
|
+
const shared = this.shared.get(source2.path);
|
|
25914
|
+
if (files.length >= FILES_SHOWN) {
|
|
25915
|
+
notShown.push({ name: shared?.name ?? source2.file, title: ROWS[source2.kind].title, reason: "count" });
|
|
25916
|
+
continue;
|
|
25917
|
+
}
|
|
25918
|
+
if (shared !== void 0) {
|
|
25919
|
+
files.push({
|
|
25920
|
+
name: shared.name,
|
|
25921
|
+
title: ROWS[source2.kind].title,
|
|
25922
|
+
role: ROWS[source2.kind].role,
|
|
25923
|
+
editable: true,
|
|
25924
|
+
shared: shared.id,
|
|
25925
|
+
replaceable: false
|
|
25926
|
+
});
|
|
25927
|
+
continue;
|
|
25928
|
+
}
|
|
25929
|
+
const { file: file3, tooLarge } = fileOf(source2, source2.file);
|
|
25930
|
+
if (tooLarge) notShown.push({ name: source2.file, title: ROWS[source2.kind].title, reason: "too_large" });
|
|
25931
|
+
else files.push(file3);
|
|
25932
|
+
}
|
|
25933
|
+
return {
|
|
25934
|
+
files,
|
|
25935
|
+
...notShown.length === 0 ? {} : { not_shown: notShown },
|
|
25936
|
+
read_note: READ_NOTE
|
|
25937
|
+
};
|
|
25938
|
+
}
|
|
25939
|
+
/** The file an edit names: an agent's own, or one of the harness's set; with whether it is shared. */
|
|
25940
|
+
find(agentId2, name2) {
|
|
25941
|
+
if (agentId2 === void 0) {
|
|
25942
|
+
for (const [path, { name: shown2 }] of this.shared) {
|
|
25943
|
+
const source2 = this.readers.get(path)?.source;
|
|
25944
|
+
if (shown2 === name2 && source2 !== void 0) return { source: source2, shared: true };
|
|
25945
|
+
}
|
|
25946
|
+
return void 0;
|
|
25947
|
+
}
|
|
25948
|
+
for (const source2 of this.agents.get(agentId2) ?? []) {
|
|
25949
|
+
const shared = this.shared.get(source2.path);
|
|
25950
|
+
if ((shared?.name ?? source2.file) === name2) return { source: source2, shared: shared !== void 0 };
|
|
25951
|
+
}
|
|
25952
|
+
return void 0;
|
|
25953
|
+
}
|
|
25954
|
+
};
|
|
25955
|
+
var LOCK_WAIT_MS = 1e4;
|
|
25956
|
+
var LOCKER = [
|
|
25957
|
+
"use Fcntl qw(:flock O_RDWR O_CREAT O_NOFOLLOW);",
|
|
25958
|
+
'sysopen(my $fh, $ARGV[0], O_RDWR|O_CREAT|O_NOFOLLOW, 0600) or die "open: $!\\n";',
|
|
25959
|
+
"chmod 0600, $fh;",
|
|
25960
|
+
'flock($fh, LOCK_EX) or die "flock: $!\\n";',
|
|
25961
|
+
'$| = 1; print "locked\\n";',
|
|
25962
|
+
"while (<STDIN>) {}",
|
|
25963
|
+
"flock($fh, LOCK_UN);"
|
|
25964
|
+
].join(" ");
|
|
25965
|
+
async function withLock(lockPath, work, waitMs = LOCK_WAIT_MS) {
|
|
25966
|
+
const child = spawn("perl", ["-e", LOCKER, lockPath], { stdio: ["pipe", "pipe", "pipe"] });
|
|
25967
|
+
const exited = new Promise((done) => {
|
|
25968
|
+
child.on("close", () => done());
|
|
25969
|
+
child.on("error", () => done());
|
|
25970
|
+
});
|
|
25971
|
+
try {
|
|
25972
|
+
await new Promise((locked, failed) => {
|
|
25973
|
+
const timer = setTimeout(() => failed(new Error("Hermes is writing its memory right now. Try again in a moment.")), waitMs);
|
|
25974
|
+
let out = "";
|
|
25975
|
+
child.stdout.on("data", (chunk) => {
|
|
25976
|
+
out += chunk.toString("utf8");
|
|
25977
|
+
if (out.includes("locked\n")) {
|
|
25978
|
+
clearTimeout(timer);
|
|
25979
|
+
locked();
|
|
25980
|
+
}
|
|
25981
|
+
});
|
|
25982
|
+
child.on("error", () => {
|
|
25983
|
+
clearTimeout(timer);
|
|
25984
|
+
failed(new Error("The bridge couldn't lock Hermes's memory file, so it saved nothing: this machine has no perl."));
|
|
25985
|
+
});
|
|
25986
|
+
child.on("close", () => {
|
|
25987
|
+
clearTimeout(timer);
|
|
25988
|
+
failed(new Error("The bridge couldn't lock Hermes's memory file, so it saved nothing."));
|
|
25989
|
+
});
|
|
25990
|
+
});
|
|
25991
|
+
return await work();
|
|
25992
|
+
} finally {
|
|
25993
|
+
child.stdin.end();
|
|
25994
|
+
if (child.exitCode === null) {
|
|
25995
|
+
const killer = setTimeout(() => child.kill(), 2e3);
|
|
25996
|
+
await exited;
|
|
25997
|
+
clearTimeout(killer);
|
|
25998
|
+
}
|
|
25999
|
+
}
|
|
26000
|
+
}
|
|
26001
|
+
function writeWhole(path, text) {
|
|
26002
|
+
const target = existsSync6(path) && lstatSync(path).isSymbolicLink() ? realpathSync(path) : path;
|
|
26003
|
+
const mode = existsSync6(target) ? statSync2(target).mode & 511 : 384;
|
|
26004
|
+
const temp = join6(dirname4(target), `.chips_${basename3(target)}.${randomBytes(4).toString("hex")}`);
|
|
26005
|
+
const fd = openSync(temp, "w", mode);
|
|
26006
|
+
try {
|
|
26007
|
+
writeSync2(fd, text, null, "utf8");
|
|
26008
|
+
fsyncSync(fd);
|
|
26009
|
+
} catch (error62) {
|
|
26010
|
+
closeSync(fd);
|
|
26011
|
+
rmSync3(temp, { force: true });
|
|
26012
|
+
throw error62;
|
|
26013
|
+
}
|
|
26014
|
+
closeSync(fd);
|
|
26015
|
+
try {
|
|
26016
|
+
renameSync2(temp, target);
|
|
26017
|
+
} catch (error62) {
|
|
26018
|
+
rmSync3(temp, { force: true });
|
|
26019
|
+
throw error62;
|
|
26020
|
+
}
|
|
26021
|
+
}
|
|
26022
|
+
async function saveEdit(source2, edit) {
|
|
26023
|
+
const memory = source2.limit !== void 0;
|
|
26024
|
+
if (memory) {
|
|
26025
|
+
const problem = memoryProblem(edit.text, source2.limit ?? MEMORY_LIMIT, source2.kind === "user" ? "what it knows of you" : "its memory");
|
|
26026
|
+
if (problem !== void 0) throw new Refuse(problem, void 0, "failed");
|
|
26027
|
+
}
|
|
26028
|
+
const save = () => {
|
|
26029
|
+
const now = readText(source2.path);
|
|
26030
|
+
if (edit.base_hash !== null && hashOf(now) !== edit.base_hash) {
|
|
26031
|
+
throw new Refuse("", void 0, "changed");
|
|
26032
|
+
}
|
|
26033
|
+
writeWhole(source2.path, memory ? entriesOf(edit.text).join(ENTRY_DELIMITER) : edit.text);
|
|
26034
|
+
};
|
|
26035
|
+
if (!memory) {
|
|
26036
|
+
save();
|
|
26037
|
+
return;
|
|
26038
|
+
}
|
|
26039
|
+
const folder2 = dirname4(source2.path);
|
|
26040
|
+
if (!existsSync6(dirname4(folder2))) throw new Refuse(`Hermes has no profile at ${dirname4(folder2)}.`, void 0, "failed");
|
|
26041
|
+
mkdirSync4(folder2, { recursive: true, mode: 448 });
|
|
26042
|
+
await withLock(`${source2.path}.lock`, save);
|
|
26043
|
+
}
|
|
26044
|
+
var Contexts = class {
|
|
26045
|
+
constructor(roster, port, log2, env = process.env) {
|
|
26046
|
+
this.roster = roster;
|
|
26047
|
+
this.port = port;
|
|
26048
|
+
this.log = log2;
|
|
26049
|
+
this.env = env;
|
|
26050
|
+
}
|
|
26051
|
+
roster;
|
|
26052
|
+
port;
|
|
26053
|
+
log;
|
|
26054
|
+
env;
|
|
26055
|
+
chain = Promise.resolve();
|
|
26056
|
+
/** Every copy read again, and sent where it moved. */
|
|
26057
|
+
refresh() {
|
|
26058
|
+
return this.inLine(async () => {
|
|
26059
|
+
const { readers, first } = this.roster();
|
|
26060
|
+
const picture = new Picture(readers, first, this.env);
|
|
26061
|
+
await this.port.harness(picture.harnessCopy(first)).catch((error62) => {
|
|
26062
|
+
this.log(`the harness's context not sent: ${messageOf(error62)}`);
|
|
26063
|
+
});
|
|
26064
|
+
for (const { agentId: agentId2 } of readers) {
|
|
26065
|
+
const copy = picture.agentCopy(agentId2);
|
|
26066
|
+
if (copy === void 0) continue;
|
|
26067
|
+
await this.port.agent(agentId2, copy).catch((error62) => {
|
|
26068
|
+
this.log(`${agentId2}'s context not sent: ${messageOf(error62)}`);
|
|
26069
|
+
});
|
|
26070
|
+
}
|
|
26071
|
+
}).catch((error62) => this.log(`the context wasn't read: ${messageOf(error62)}`));
|
|
26072
|
+
}
|
|
26073
|
+
/**
|
|
26074
|
+
* A person's edit from Chips: saved, and answered with the copy it is in now; or refused, the
|
|
26075
|
+
* copy as it is now beside the reason. The other copies the file is in follow.
|
|
26076
|
+
*/
|
|
26077
|
+
async edit(frame) {
|
|
26078
|
+
const answer = await this.inLine(async () => {
|
|
26079
|
+
const { readers, first } = this.roster();
|
|
26080
|
+
const copyNow = () => {
|
|
26081
|
+
const picture = new Picture(readers, first, this.env);
|
|
26082
|
+
return frame.agent_id === void 0 ? picture.harnessCopy(first) : picture.agentCopy(frame.agent_id);
|
|
26083
|
+
};
|
|
26084
|
+
const found = new Picture(readers, first, this.env).find(frame.agent_id, frame.name);
|
|
26085
|
+
if (found === void 0) {
|
|
26086
|
+
throw new Refuse(`Hermes gives this agent no file called ${frame.name}.`, copyNow(), "failed");
|
|
26087
|
+
}
|
|
26088
|
+
const { source: source2, shared } = found;
|
|
26089
|
+
if (frame.base_hash === null) {
|
|
26090
|
+
const why = source2.kind === "project" ? "Every agent on Hermes's gateway reads the same project file, so one can't have its own." : `Every agent on Hermes's ${source2.profile} profile reads the same ${source2.file}, so one can't have its own.`;
|
|
26091
|
+
throw new Refuse(why, copyNow(), "failed");
|
|
26092
|
+
}
|
|
26093
|
+
try {
|
|
26094
|
+
await saveEdit(source2, frame);
|
|
26095
|
+
} catch (error62) {
|
|
26096
|
+
const reason = error62 instanceof Refuse ? error62.reason : "failed";
|
|
26097
|
+
this.log(`${source2.path} not saved from Chips: ${reason === "changed" ? "it changed since" : messageOf(error62)}`);
|
|
26098
|
+
throw new Refuse(error62 instanceof Refuse ? error62.message : messageOf(error62), copyNow(), reason);
|
|
26099
|
+
}
|
|
26100
|
+
this.log(`${source2.path} saved from Chips${shared ? ", for every agent that reads it" : ""}`);
|
|
26101
|
+
const copy = copyNow();
|
|
26102
|
+
if (copy === void 0) throw new Refuse("Saved, but the agent is gone.", void 0, "failed");
|
|
26103
|
+
return copy;
|
|
26104
|
+
});
|
|
26105
|
+
void this.refresh();
|
|
26106
|
+
return answer;
|
|
26107
|
+
}
|
|
26108
|
+
inLine(work) {
|
|
26109
|
+
const done = this.chain.catch(() => void 0).then(work);
|
|
26110
|
+
this.chain = done.catch(() => void 0);
|
|
26111
|
+
return done;
|
|
26112
|
+
}
|
|
26113
|
+
};
|
|
26114
|
+
function messageOf(error62) {
|
|
26115
|
+
return error62 instanceof Error ? error62.message : String(error62);
|
|
26116
|
+
}
|
|
26117
|
+
|
|
26118
|
+
// src/fills.ts
|
|
26119
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
26120
|
+
import { createServer } from "node:http";
|
|
26121
|
+
var HOOK_PATH = "/chips-hermes/";
|
|
26122
|
+
var MAX_BODY = 4 * 1024 * 1024;
|
|
26123
|
+
var DROP = 0.7;
|
|
26124
|
+
var SEND_EVERY_MS = 2e3;
|
|
26125
|
+
var THRESHOLD = 0.5;
|
|
26126
|
+
var SMALL_WINDOW = 512e3;
|
|
26127
|
+
var SMALL_THRESHOLD = 0.75;
|
|
26128
|
+
var WHY_NOT = "Hermes can't be asked to compact from Chips.";
|
|
26129
|
+
var whyReadOnly = (profile) => `Hermes sets this for every agent on its ${profile} profile.`;
|
|
26130
|
+
var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
26131
|
+
function limitOf(home2, window) {
|
|
26132
|
+
const text = configText(home2);
|
|
26133
|
+
const enabled = yamlBool(yamlValue(text, "compression", "enabled")) ?? true;
|
|
26134
|
+
const threshold = yamlNumber(yamlValue(text, "compression", "threshold")) ?? THRESHOLD;
|
|
26135
|
+
let ratio = window < SMALL_WINDOW ? Math.max(threshold, SMALL_THRESHOLD) : threshold;
|
|
26136
|
+
const tokens2 = yamlNumber(yamlValue(text, "compression", "threshold_tokens"));
|
|
26137
|
+
if (tokens2 !== void 0 && tokens2 > 0 && window > 0) ratio = Math.min(ratio, tokens2 / window);
|
|
26138
|
+
const at = Math.min(99, Math.max(1, Math.round(ratio * 100)));
|
|
26139
|
+
return enabled ? { at, choices: [at], default: at, may_turn_off: false } : { at: null, choices: [at], default: null, may_turn_off: true };
|
|
26140
|
+
}
|
|
26141
|
+
function signedBy(body, header, secret) {
|
|
26142
|
+
if (header === void 0 || !header.startsWith("sha256=")) return false;
|
|
26143
|
+
const given = Buffer.from(header.slice("sha256=".length), "hex");
|
|
26144
|
+
const made = createHmac("sha256", secret).update(body).digest();
|
|
26145
|
+
return given.length === made.length && timingSafeEqual(given, made);
|
|
26146
|
+
}
|
|
26147
|
+
function numbersOf(profile, body) {
|
|
26148
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
26149
|
+
const top = body;
|
|
26150
|
+
if (top["hook_event_name"] !== "post_api_request") return void 0;
|
|
26151
|
+
const extra = typeof top["extra"] === "object" && top["extra"] !== null ? top["extra"] : {};
|
|
26152
|
+
const named = typeof top["session_id"] === "string" ? top["session_id"] : "";
|
|
26153
|
+
const task2 = typeof extra["task_id"] === "string" && extra["task_id"] !== "" ? extra["task_id"] : void 0;
|
|
26154
|
+
const session = task2 ?? named;
|
|
26155
|
+
if (session === "") return void 0;
|
|
26156
|
+
const usage2 = typeof extra["usage"] === "object" && extra["usage"] !== null ? extra["usage"] : {};
|
|
26157
|
+
const prompt = num(usage2["prompt_tokens"]) ?? (num(usage2["input_tokens"]) === void 0 ? void 0 : (num(usage2["input_tokens"]) ?? 0) + (num(usage2["cache_read_tokens"]) ?? 0) + (num(usage2["cache_write_tokens"]) ?? 0));
|
|
26158
|
+
const window = num(extra["context_length"]);
|
|
26159
|
+
if (prompt === void 0 || prompt <= 0 || window === void 0 || window <= 0) return void 0;
|
|
26160
|
+
const ended = num(extra["ended_at"]);
|
|
26161
|
+
const stamped = typeof top["timestamp"] === "string" && !Number.isNaN(Date.parse(top["timestamp"])) ? top["timestamp"] : void 0;
|
|
26162
|
+
const at = ended !== void 0 ? new Date(ended * 1e3).toISOString() : stamped ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
26163
|
+
const model = typeof extra["model"] === "string" && extra["model"] !== "" ? extra["model"].slice(0, 120) : void 0;
|
|
26164
|
+
const turn = typeof extra["turn_id"] === "string" && extra["turn_id"] !== "" ? extra["turn_id"] : void 0;
|
|
26165
|
+
return {
|
|
26166
|
+
profile,
|
|
26167
|
+
session,
|
|
26168
|
+
turn,
|
|
26169
|
+
first: num(extra["api_call_count"]) === 1,
|
|
26170
|
+
used: Math.round(prompt),
|
|
26171
|
+
window: Math.round(window),
|
|
26172
|
+
model,
|
|
26173
|
+
at: new Date(at).toISOString()
|
|
26174
|
+
};
|
|
26175
|
+
}
|
|
26176
|
+
var Fills = class {
|
|
26177
|
+
constructor(options) {
|
|
26178
|
+
this.options = options;
|
|
26179
|
+
const kept = options.kept;
|
|
26180
|
+
kept.readings ??= {};
|
|
26181
|
+
kept.lasts ??= {};
|
|
26182
|
+
kept.longTurns ??= {};
|
|
26183
|
+
for (const reading of Object.values(kept.readings)) this.lastOf.set(reading.session, reading);
|
|
26184
|
+
}
|
|
26185
|
+
options;
|
|
26186
|
+
server;
|
|
26187
|
+
/** The last reading of each session, for a compaction's drop. */
|
|
26188
|
+
lastOf = /* @__PURE__ */ new Map();
|
|
26189
|
+
turns = /* @__PURE__ */ new Map();
|
|
26190
|
+
timers = /* @__PURE__ */ new Map();
|
|
26191
|
+
sentAt = /* @__PURE__ */ new Map();
|
|
26192
|
+
/** The profiles whose unsigned or wrongly signed deliveries have been said once. */
|
|
26193
|
+
badSaid = /* @__PURE__ */ new Set();
|
|
26194
|
+
heard = false;
|
|
26195
|
+
/** Whether any reading has come since the bridge started. */
|
|
26196
|
+
get anyHeard() {
|
|
26197
|
+
return this.heard;
|
|
26198
|
+
}
|
|
26199
|
+
/** Hear Hermes on `127.0.0.1:port`: the port it took (one of its own for 0). */
|
|
26200
|
+
listen(port, host = "127.0.0.1") {
|
|
26201
|
+
const server = createServer((request, response) => this.handle(request, response));
|
|
26202
|
+
this.server = server;
|
|
26203
|
+
return new Promise((done, failed) => {
|
|
26204
|
+
server.once("error", failed);
|
|
26205
|
+
server.listen(port, host, () => {
|
|
26206
|
+
server.off("error", failed);
|
|
26207
|
+
done(server.address().port);
|
|
26208
|
+
});
|
|
26209
|
+
});
|
|
26210
|
+
}
|
|
26211
|
+
async close() {
|
|
26212
|
+
for (const timer of this.timers.values()) clearTimeout(timer);
|
|
26213
|
+
if (this.timers.size > 0) this.options.save();
|
|
26214
|
+
this.timers.clear();
|
|
26215
|
+
const server = this.server;
|
|
26216
|
+
this.server = void 0;
|
|
26217
|
+
if (server === void 0) return;
|
|
26218
|
+
server.closeAllConnections();
|
|
26219
|
+
await new Promise((done) => server.close(() => done()));
|
|
26220
|
+
}
|
|
26221
|
+
/** An agent's copy, from what is known of it, or nothing when no reading has come. */
|
|
26222
|
+
copyOf(agentId2) {
|
|
26223
|
+
const { kept } = this.options;
|
|
26224
|
+
const reading = kept.readings?.[agentId2];
|
|
26225
|
+
const profile = this.options.profileOf(agentId2);
|
|
26226
|
+
if (reading === void 0 || profile === void 0) return void 0;
|
|
26227
|
+
const last = kept.lasts?.[agentId2];
|
|
26228
|
+
const long = kept.longTurns?.[agentId2];
|
|
26229
|
+
return {
|
|
26230
|
+
used: reading.used,
|
|
26231
|
+
window: reading.window,
|
|
26232
|
+
...reading.model === void 0 ? {} : { model: reading.model },
|
|
26233
|
+
as_of: reading.at,
|
|
26234
|
+
...last === void 0 ? {} : { last },
|
|
26235
|
+
...long === void 0 ? {} : { long_turn: long },
|
|
26236
|
+
compacts: false,
|
|
26237
|
+
why_not: WHY_NOT,
|
|
26238
|
+
limit: limitOf(profile.home, reading.window),
|
|
26239
|
+
editable: false,
|
|
26240
|
+
why_read_only: whyReadOnly(profile.profile)
|
|
26241
|
+
};
|
|
26242
|
+
}
|
|
26243
|
+
/** Each agent's kept copy, sent: at start, so a restart shows what was known. */
|
|
26244
|
+
async sendAll() {
|
|
26245
|
+
for (const agentId2 of Object.keys(this.options.kept.readings ?? {})) await this.send(agentId2);
|
|
26246
|
+
}
|
|
26247
|
+
/** An agent gone: nothing more is sent for it, and nothing kept. */
|
|
26248
|
+
forget(agentId2) {
|
|
26249
|
+
const { kept } = this.options;
|
|
26250
|
+
clearTimeout(this.timers.get(agentId2));
|
|
26251
|
+
this.timers.delete(agentId2);
|
|
26252
|
+
if (kept.readings?.[agentId2] === void 0 && kept.lasts?.[agentId2] === void 0) return;
|
|
26253
|
+
delete kept.readings?.[agentId2];
|
|
26254
|
+
delete kept.lasts?.[agentId2];
|
|
26255
|
+
delete kept.longTurns?.[agentId2];
|
|
26256
|
+
this.options.save();
|
|
26257
|
+
}
|
|
26258
|
+
/** One delivery: read whole up to its limit, checked, answered at once, then taken in. */
|
|
26259
|
+
handle(request, response) {
|
|
26260
|
+
const url2 = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
26261
|
+
const profile = url2.pathname.startsWith(HOOK_PATH) ? decodeURIComponent(url2.pathname.slice(HOOK_PATH.length)) : "";
|
|
26262
|
+
if (request.method !== "POST" || profile === "" || profile.includes("/")) {
|
|
26263
|
+
response.writeHead(404).end();
|
|
26264
|
+
return;
|
|
26265
|
+
}
|
|
26266
|
+
const chunks = [];
|
|
26267
|
+
let size = 0;
|
|
26268
|
+
let over = false;
|
|
26269
|
+
request.on("data", (chunk) => {
|
|
26270
|
+
if (over) return;
|
|
26271
|
+
size += chunk.length;
|
|
26272
|
+
if (size > MAX_BODY) {
|
|
26273
|
+
over = true;
|
|
26274
|
+
response.writeHead(413, { connection: "close" }).end();
|
|
26275
|
+
request.destroy();
|
|
26276
|
+
return;
|
|
26277
|
+
}
|
|
26278
|
+
chunks.push(chunk);
|
|
26279
|
+
});
|
|
26280
|
+
request.on("end", () => {
|
|
26281
|
+
if (over) return;
|
|
26282
|
+
const body = Buffer.concat(chunks);
|
|
26283
|
+
const secret = this.options.secretFor(profile);
|
|
26284
|
+
const header = request.headers["x-hermes-signature-256"];
|
|
26285
|
+
if (secret === void 0 || !signedBy(body, typeof header === "string" ? header : void 0, secret)) {
|
|
26286
|
+
response.writeHead(401).end();
|
|
26287
|
+
if (!this.badSaid.has(profile)) {
|
|
26288
|
+
this.badSaid.add(profile);
|
|
26289
|
+
this.options.log(
|
|
26290
|
+
`a reading from Hermes's ${profile} profile wasn't signed with Chips's secret, and was dropped: start hermes gateway again if its .env changed`
|
|
26291
|
+
);
|
|
26292
|
+
}
|
|
26293
|
+
return;
|
|
26294
|
+
}
|
|
26295
|
+
response.writeHead(204).end();
|
|
26296
|
+
let parsed;
|
|
26297
|
+
try {
|
|
26298
|
+
parsed = JSON.parse(body.toString("utf8"));
|
|
26299
|
+
} catch {
|
|
26300
|
+
return;
|
|
26301
|
+
}
|
|
26302
|
+
const numbers = numbersOf(profile, parsed);
|
|
26303
|
+
if (numbers !== void 0) this.take(numbers);
|
|
26304
|
+
});
|
|
26305
|
+
}
|
|
26306
|
+
/** A reading taken in: the agent's latest, a compaction when it fell, the turn's peak. */
|
|
26307
|
+
take(numbers) {
|
|
26308
|
+
const whose = this.options.whose(numbers.session);
|
|
26309
|
+
if (whose === void 0) return;
|
|
26310
|
+
this.heard = true;
|
|
26311
|
+
const { kept } = this.options;
|
|
26312
|
+
const { agentId: agentId2 } = whose;
|
|
26313
|
+
const reading = {
|
|
26314
|
+
used: numbers.used,
|
|
26315
|
+
window: numbers.window,
|
|
26316
|
+
...numbers.model === void 0 ? {} : { model: numbers.model },
|
|
26317
|
+
at: numbers.at,
|
|
26318
|
+
session: numbers.session
|
|
26319
|
+
};
|
|
26320
|
+
const before = this.lastOf.get(numbers.session);
|
|
26321
|
+
const fell = before !== void 0 && before.model === reading.model && reading.used < before.used * DROP;
|
|
26322
|
+
if (fell && before !== void 0) {
|
|
26323
|
+
(kept.lasts ??= {})[agentId2] = { at: reading.at, from: before.used, to: reading.used, by: "itself" };
|
|
26324
|
+
this.options.log(`${agentId2} compacted in Hermes, ${before.used} to ${reading.used} tokens`);
|
|
26325
|
+
}
|
|
26326
|
+
let turn = this.turns.get(numbers.session);
|
|
26327
|
+
const fresh = numbers.turn === void 0 ? numbers.first || turn === void 0 : turn?.id !== numbers.turn;
|
|
26328
|
+
if (turn === void 0 || fresh) {
|
|
26329
|
+
turn = { id: numbers.turn, peak: 0, folds: 0 };
|
|
26330
|
+
this.turns.set(numbers.session, turn);
|
|
26331
|
+
}
|
|
26332
|
+
turn.peak = Math.max(turn.peak, reading.used);
|
|
26333
|
+
if (fell) turn.folds += 1;
|
|
26334
|
+
if (turn.peak > reading.window / 2) {
|
|
26335
|
+
(kept.longTurns ??= {})[agentId2] = { at: reading.at, peak: turn.peak, folds: Math.min(1e3, turn.folds) };
|
|
26336
|
+
}
|
|
26337
|
+
this.lastOf.set(numbers.session, reading);
|
|
26338
|
+
(kept.readings ??= {})[agentId2] = reading;
|
|
26339
|
+
this.schedule(agentId2);
|
|
26340
|
+
}
|
|
26341
|
+
/** The agent's copy sent now, or once readings slow down, and what is kept saved with it. */
|
|
26342
|
+
schedule(agentId2) {
|
|
26343
|
+
if (this.timers.has(agentId2)) return;
|
|
26344
|
+
const every = this.options.sendEveryMs ?? SEND_EVERY_MS;
|
|
26345
|
+
const wait2 = Math.max(0, (this.sentAt.get(agentId2) ?? 0) + every - Date.now());
|
|
26346
|
+
const timer = setTimeout(() => {
|
|
26347
|
+
this.timers.delete(agentId2);
|
|
26348
|
+
this.sentAt.set(agentId2, Date.now());
|
|
26349
|
+
this.options.save();
|
|
26350
|
+
void this.send(agentId2);
|
|
26351
|
+
}, wait2);
|
|
26352
|
+
timer.unref?.();
|
|
26353
|
+
this.timers.set(agentId2, timer);
|
|
26354
|
+
}
|
|
26355
|
+
async send(agentId2) {
|
|
26356
|
+
const copy = this.copyOf(agentId2);
|
|
26357
|
+
if (copy === void 0) return;
|
|
26358
|
+
await this.options.send(agentId2, copy).catch((error62) => {
|
|
26359
|
+
this.options.log(`${agentId2}'s context window not sent: ${error62 instanceof Error ? error62.message : String(error62)}`);
|
|
26360
|
+
});
|
|
26361
|
+
}
|
|
26362
|
+
};
|
|
25471
26363
|
|
|
25472
26364
|
// src/hermes.ts
|
|
25473
26365
|
var TERMINAL = /* @__PURE__ */ new Set([
|
|
@@ -25490,9 +26382,9 @@ var HermesClient = class {
|
|
|
25490
26382
|
constructor(address, fetcher = fetch) {
|
|
25491
26383
|
this.address = address;
|
|
25492
26384
|
this.fetcher = fetcher;
|
|
25493
|
-
const
|
|
26385
|
+
const root2 = address.url.replace(/\/+$/, "");
|
|
25494
26386
|
const profile = address.profile;
|
|
25495
|
-
this.base = profile === void 0 || profile === "default" ?
|
|
26387
|
+
this.base = profile === void 0 || profile === "default" ? root2 : `${root2}/p/${encodeURIComponent(profile)}`;
|
|
25496
26388
|
}
|
|
25497
26389
|
address;
|
|
25498
26390
|
fetcher;
|
|
@@ -25639,7 +26531,7 @@ function sessionIdOf(made) {
|
|
|
25639
26531
|
return typeof id2 === "string" && id2 !== "" ? id2 : void 0;
|
|
25640
26532
|
}
|
|
25641
26533
|
function eventOf(block) {
|
|
25642
|
-
const data = block.split("\n").filter((
|
|
26534
|
+
const data = block.split("\n").filter((line2) => line2.startsWith("data:")).map((line2) => line2.slice(5).replace(/^ /, "")).join("\n");
|
|
25643
26535
|
if (data === "") return void 0;
|
|
25644
26536
|
try {
|
|
25645
26537
|
const parsed = JSON.parse(data);
|
|
@@ -25665,8 +26557,126 @@ async function failureOf(answer) {
|
|
|
25665
26557
|
return new HermesError(message, answer.status, code);
|
|
25666
26558
|
}
|
|
25667
26559
|
|
|
26560
|
+
// src/hooks.ts
|
|
26561
|
+
import { appendFileSync, chmodSync as chmodSync3, existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
26562
|
+
import { join as join7 } from "node:path";
|
|
26563
|
+
var HOOK_NAME = "chips";
|
|
26564
|
+
var SECRET_ENV = "CHIPS_HOOK_SECRET";
|
|
26565
|
+
function hookUrl(port, profile) {
|
|
26566
|
+
return `http://127.0.0.1:${port}${HOOK_PATH}${profile}`;
|
|
26567
|
+
}
|
|
26568
|
+
function linesOf2(text) {
|
|
26569
|
+
return text.split("\n").map((line2) => {
|
|
26570
|
+
const indent = line2.length - line2.trimStart().length;
|
|
26571
|
+
const words4 = line2.trimStart().startsWith("#") ? "" : line2.trim().replace(/\s+#.*$/, "");
|
|
26572
|
+
return { indent, words: words4 };
|
|
26573
|
+
});
|
|
26574
|
+
}
|
|
26575
|
+
function hookPortIn(home2) {
|
|
26576
|
+
const path = join7(home2, "config.yaml");
|
|
26577
|
+
if (!existsSync7(path)) return void 0;
|
|
26578
|
+
const escaped = HOOK_PATH.replace(/[/]/g, "\\/");
|
|
26579
|
+
const match = new RegExp(`^\\s*(?:-\\s*)?url:\\s*["']?http:\\/\\/127\\.0\\.0\\.1:(\\d+)${escaped}`, "m").exec(
|
|
26580
|
+
readFileSync7(path, "utf8")
|
|
26581
|
+
);
|
|
26582
|
+
const port = match === null ? void 0 : Number(match[1]);
|
|
26583
|
+
return port !== void 0 && Number.isInteger(port) && port > 0 && port < 65536 ? port : void 0;
|
|
26584
|
+
}
|
|
26585
|
+
function entryLines(indent, url2, secret) {
|
|
26586
|
+
return [
|
|
26587
|
+
`${indent}outbound:`,
|
|
26588
|
+
`${indent} - name: ${HOOK_NAME}`,
|
|
26589
|
+
`${indent} url: ${url2}`,
|
|
26590
|
+
`${indent} events: [post_api_request]`,
|
|
26591
|
+
"env" in secret ? `${indent} secret_env: ${secret.env}` : `${indent} secret: "${secret.inline}"`,
|
|
26592
|
+
`${indent} timeout: 5`
|
|
26593
|
+
];
|
|
26594
|
+
}
|
|
26595
|
+
function secretInEnv(home2, secret) {
|
|
26596
|
+
const path = join7(home2, ".env");
|
|
26597
|
+
if (!existsSync7(path)) return false;
|
|
26598
|
+
if (envValue(home2, SECRET_ENV) !== void 0) return true;
|
|
26599
|
+
const text = readFileSync7(path, "utf8");
|
|
26600
|
+
const mode = statSync3(path).mode & 511;
|
|
26601
|
+
appendFileSync(path, `${text === "" || text.endsWith("\n") ? "" : "\n"}${SECRET_ENV}=${secret}
|
|
26602
|
+
`);
|
|
26603
|
+
chmodSync3(path, mode);
|
|
26604
|
+
return true;
|
|
26605
|
+
}
|
|
26606
|
+
function addHook(home2, port, profile, secret) {
|
|
26607
|
+
const path = join7(home2, "config.yaml");
|
|
26608
|
+
if (!existsSync7(home2)) return { state: "no_home", path };
|
|
26609
|
+
const url2 = hookUrl(port, profile);
|
|
26610
|
+
const text = existsSync7(path) ? readFileSync7(path, "utf8") : "";
|
|
26611
|
+
const lines2 = linesOf2(text);
|
|
26612
|
+
const ours2 = new RegExp(`^(?:-\\s*)?url:\\s*["']?${url2.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}["']?$`);
|
|
26613
|
+
if (lines2.some((line2) => ours2.test(line2.words))) return { state: "there", path };
|
|
26614
|
+
const where = lines2.findIndex((line2) => line2.indent === 0 && /^["']?hooks["']?\s*:/.test(line2.words));
|
|
26615
|
+
const byHand = (why) => {
|
|
26616
|
+
const inEnv2 = secretInEnv(home2, secret);
|
|
26617
|
+
const shown2 = entryLines(" ", url2, inEnv2 ? { env: SECRET_ENV } : { inline: secret });
|
|
26618
|
+
return { state: "by_hand", path, why, lines: ["hooks:", ...shown2].join("\n") };
|
|
26619
|
+
};
|
|
26620
|
+
const plain = /^(?:"[^"]+"|'[^']+'|[A-Za-z_][\w.-]*)\s*:(?:\s|$)/;
|
|
26621
|
+
if (lines2.some((line2) => line2.indent === 0 && line2.words !== "" && !plain.test(line2.words))) {
|
|
26622
|
+
return byHand("its top level isn't laid out as this bridge expects");
|
|
26623
|
+
}
|
|
26624
|
+
if (where === -1) {
|
|
26625
|
+
const inEnv2 = secretInEnv(home2, secret);
|
|
26626
|
+
const block = [
|
|
26627
|
+
"# Chips: how full each agent's context is, sent to the chips-hermes bridge after each model call.",
|
|
26628
|
+
"hooks:",
|
|
26629
|
+
...entryLines(" ", url2, inEnv2 ? { env: SECRET_ENV } : { inline: secret })
|
|
26630
|
+
];
|
|
26631
|
+
const before = text === "" ? "" : text.endsWith("\n") ? "\n" : "\n\n";
|
|
26632
|
+
if (text === "") writeFileSync4(path, `${block.join("\n")}
|
|
26633
|
+
`, { mode: 384 });
|
|
26634
|
+
else writeWhole(path, `${text}${before}${block.join("\n")}
|
|
26635
|
+
`);
|
|
26636
|
+
return { state: "added", path };
|
|
26637
|
+
}
|
|
26638
|
+
const head = lines2[where]?.words ?? "";
|
|
26639
|
+
if (head.replace(/^["']?hooks["']?\s*:/, "").trim() !== "") return byHand("its hooks: is written on one line");
|
|
26640
|
+
const children = [];
|
|
26641
|
+
for (const line2 of lines2.slice(where + 1)) {
|
|
26642
|
+
if (line2.words === "") continue;
|
|
26643
|
+
if (line2.indent === 0) break;
|
|
26644
|
+
children.push(line2);
|
|
26645
|
+
}
|
|
26646
|
+
const indent = children[0]?.indent ?? 2;
|
|
26647
|
+
const named = new RegExp(`^(?:-\\s*)?name:\\s*["']?${HOOK_NAME}["']?$`);
|
|
26648
|
+
if (children.some((line2) => named.test(line2.words))) {
|
|
26649
|
+
return byHand(`its hooks have an entry called ${HOOK_NAME} with another address`);
|
|
26650
|
+
}
|
|
26651
|
+
if (children.some((line2) => line2.indent === indent && /^["']?outbound["']?\s*:/.test(line2.words))) {
|
|
26652
|
+
return byHand("its hooks already have an outbound list");
|
|
26653
|
+
}
|
|
26654
|
+
if (children.some((line2) => line2.indent !== indent && line2.indent < indent) || children[0]?.words.startsWith("-")) {
|
|
26655
|
+
return byHand("its hooks: isn't laid out as this bridge expects");
|
|
26656
|
+
}
|
|
26657
|
+
const inEnv = secretInEnv(home2, secret);
|
|
26658
|
+
const raw = text.split("\n");
|
|
26659
|
+
raw.splice(where + 1, 0, ...entryLines(" ".repeat(indent), url2, inEnv ? { env: SECRET_ENV } : { inline: secret }));
|
|
26660
|
+
writeWhole(path, raw.join("\n"));
|
|
26661
|
+
return { state: "added", path };
|
|
26662
|
+
}
|
|
26663
|
+
|
|
25668
26664
|
// src/bridge.ts
|
|
25669
|
-
var WANTS = ["agents", "new_agents", "forks"];
|
|
26665
|
+
var WANTS = ["agents", "new_agents", "forks", "context_files", "context_windows", "restarts"];
|
|
26666
|
+
function settingsFor(active) {
|
|
26667
|
+
const bridge2 = () => {
|
|
26668
|
+
const there = active();
|
|
26669
|
+
if (there === void 0) throw new Refuse("The bridge is starting. Try again in a moment.");
|
|
26670
|
+
return there;
|
|
26671
|
+
};
|
|
26672
|
+
return {
|
|
26673
|
+
context: (edit) => bridge2().onContextEdit(edit),
|
|
26674
|
+
contextWindows: {
|
|
26675
|
+
compact: async (ask2) => bridge2().onWindowCompact(ask2),
|
|
26676
|
+
change: async (ask2) => bridge2().onWindowChange(ask2)
|
|
26677
|
+
}
|
|
26678
|
+
};
|
|
26679
|
+
}
|
|
25670
26680
|
function filesHere(project = false) {
|
|
25671
26681
|
return `${sendingFiles({ project })} Hermes's note that this API server sends only pictures, and shows a MEDIA: tag on the runs endpoint as text, does not hold for these runs: they come from Chips, whose bridge takes each MEDIA: line out of your answer and attaches that file to your message, whatever its type, up to 100 MB.`;
|
|
25672
26682
|
}
|
|
@@ -25675,10 +26685,10 @@ function instructionsFor(folder2, project = false) {
|
|
|
25675
26685
|
const files = filesHere(project);
|
|
25676
26686
|
return folder2 === void 0 ? files : `${files} Your own folder is ${folder2}: make and keep your files there, and run your commands from there.`;
|
|
25677
26687
|
}
|
|
25678
|
-
function freeFolder(
|
|
26688
|
+
function freeFolder(root2, name2, taken) {
|
|
25679
26689
|
const slug = name2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
25680
26690
|
for (let count = 1; ; count += 1) {
|
|
25681
|
-
const folder2 =
|
|
26691
|
+
const folder2 = join8(root2, count === 1 ? slug : `${slug}-${count}`);
|
|
25682
26692
|
if (!taken.includes(folder2)) return folder2;
|
|
25683
26693
|
}
|
|
25684
26694
|
}
|
|
@@ -25709,6 +26719,32 @@ var Bridge = class {
|
|
|
25709
26719
|
constructor(options) {
|
|
25710
26720
|
this.options = options;
|
|
25711
26721
|
this.log = options.log ?? (() => void 0);
|
|
26722
|
+
const { chips: chips2, hermesRoot: hermesRoot2 } = options;
|
|
26723
|
+
if (hermesRoot2 === void 0) return;
|
|
26724
|
+
this.contexts = new Contexts(
|
|
26725
|
+
() => this.roster(),
|
|
26726
|
+
{
|
|
26727
|
+
agent: (agentId2, copy) => chips2.settings.context(agentId2, copy),
|
|
26728
|
+
harness: (copy) => chips2.settings.harnessContext(copy)
|
|
26729
|
+
},
|
|
26730
|
+
(line2) => this.log(`context: ${line2}`),
|
|
26731
|
+
options.env ?? process.env
|
|
26732
|
+
);
|
|
26733
|
+
if (options.fills === void 0) return;
|
|
26734
|
+
const kept = options.state.fills ??= {};
|
|
26735
|
+
this.fills = new Fills({
|
|
26736
|
+
whose: (sessionId) => this.whoseSession(sessionId),
|
|
26737
|
+
profileOf: (agentId2) => {
|
|
26738
|
+
const profile = this.profileFor(agentId2);
|
|
26739
|
+
return profile === void 0 ? void 0 : { profile: profile.name, home: profile.home };
|
|
26740
|
+
},
|
|
26741
|
+
secretFor: (profile) => envValue(profileHome(hermesRoot2, profile), SECRET_ENV) ?? kept.secret ?? options.fills?.secret,
|
|
26742
|
+
send: (agentId2, copy) => chips2.settings.contextWindow(agentId2, copy),
|
|
26743
|
+
kept,
|
|
26744
|
+
save: () => options.saveState(options.state),
|
|
26745
|
+
log: (line2) => this.log(`context window: ${line2}`),
|
|
26746
|
+
sendEveryMs: options.fillsEveryMs
|
|
26747
|
+
});
|
|
25712
26748
|
}
|
|
25713
26749
|
options;
|
|
25714
26750
|
agents = [];
|
|
@@ -25723,6 +26759,20 @@ var Bridge = class {
|
|
|
25723
26759
|
copying = Promise.resolve();
|
|
25724
26760
|
log;
|
|
25725
26761
|
health;
|
|
26762
|
+
/** The harness's agents in Chips as last listed: whose context and window copies are sent. */
|
|
26763
|
+
listed = [];
|
|
26764
|
+
/** The agents' context files (Chips ADR 0134), when the bridge knows where Hermes keeps them. */
|
|
26765
|
+
contexts;
|
|
26766
|
+
contextTimer;
|
|
26767
|
+
/** The agents' context windows (Chips ADR 0146), when Hermes's webhook has somewhere to reach. */
|
|
26768
|
+
fills;
|
|
26769
|
+
/** The port Hermes's webhook reaches the bridge on, once it listens. */
|
|
26770
|
+
fillsPort;
|
|
26771
|
+
toldNoReadings = false;
|
|
26772
|
+
/** While Hermes's gateway restarts (Chips ADR 0094), the health watch says nothing: the restart says how it went. */
|
|
26773
|
+
held = false;
|
|
26774
|
+
/** What went wrong in the last restart, said in place of the watch's own words while Hermes doesn't answer. */
|
|
26775
|
+
why;
|
|
25726
26776
|
/** Register the agents, start hearing what is said to them, and say how their gateways are. */
|
|
25727
26777
|
async start() {
|
|
25728
26778
|
const { chips: chips2 } = this.options;
|
|
@@ -25736,20 +26786,138 @@ var Bridge = class {
|
|
|
25736
26786
|
this.ours = /* @__PURE__ */ new Set([...known.map((agent) => agent.agentId), ...this.agents.map((agent) => agent.agentId)]);
|
|
25737
26787
|
chips2.on("message.new", (message) => void this.hear(message));
|
|
25738
26788
|
chips2.on("agent.asked", (asked) => {
|
|
25739
|
-
this.make(asked).catch((error62) => this.log(`a new agent went wrong: ${
|
|
26789
|
+
this.make(asked).catch((error62) => this.log(`a new agent went wrong: ${messageOf2(error62)}`));
|
|
25740
26790
|
});
|
|
25741
26791
|
chips2.on("agent.fork", (fork) => {
|
|
25742
|
-
this.copying = this.copying.then(() => this.copy(fork)).catch((error62) => this.log(`a copy went wrong: ${
|
|
26792
|
+
this.copying = this.copying.then(() => this.copy(fork)).catch((error62) => this.log(`a copy went wrong: ${messageOf2(error62)}`));
|
|
25743
26793
|
});
|
|
25744
26794
|
chips2.on("feedback", (feedback2) => {
|
|
25745
|
-
this.notRight(feedback2).catch((error62) => this.log(`a note on a reply went wrong: ${
|
|
26795
|
+
this.notRight(feedback2).catch((error62) => this.log(`a note on a reply went wrong: ${messageOf2(error62)}`));
|
|
25746
26796
|
});
|
|
26797
|
+
chips2.on("agent.attached", () => void this.agentsMoved());
|
|
26798
|
+
chips2.on("agent.released", () => void this.agentsMoved());
|
|
25747
26799
|
await this.sayHowItIs();
|
|
25748
26800
|
this.health = setInterval(() => void this.sayHowItIs(), this.options.healthEveryMs ?? 3e4);
|
|
25749
26801
|
this.health.unref();
|
|
26802
|
+
await this.startSettings();
|
|
26803
|
+
}
|
|
26804
|
+
/** Hold the health watch's words while Hermes's gateway restarts, and let them go after. */
|
|
26805
|
+
hold(held) {
|
|
26806
|
+
this.held = held;
|
|
26807
|
+
}
|
|
26808
|
+
/**
|
|
26809
|
+
* How a restart went (Chips ADR 0094): fine as the gateways now are, or its words, which stand
|
|
26810
|
+
* while Hermes doesn't answer. The health watch speaks for itself again after.
|
|
26811
|
+
*/
|
|
26812
|
+
async restarted(trouble) {
|
|
26813
|
+
this.held = false;
|
|
26814
|
+
this.why = trouble;
|
|
26815
|
+
if (trouble === void 0) return this.sayHowItIs();
|
|
26816
|
+
await this.options.chips.reportTrouble(trouble).catch(() => void 0);
|
|
25750
26817
|
}
|
|
25751
26818
|
stop() {
|
|
25752
26819
|
if (this.health !== void 0) clearInterval(this.health);
|
|
26820
|
+
if (this.contextTimer !== void 0) clearInterval(this.contextTimer);
|
|
26821
|
+
void this.fills?.close();
|
|
26822
|
+
}
|
|
26823
|
+
// ------------------------------------------------------------------ an agent's page
|
|
26824
|
+
/**
|
|
26825
|
+
* The agents' context and context windows, sent at start and kept current: the files read
|
|
26826
|
+
* again every so often and after each turn, a window copy on each reading Hermes sends.
|
|
26827
|
+
*/
|
|
26828
|
+
async startSettings() {
|
|
26829
|
+
if (this.contexts === void 0) return;
|
|
26830
|
+
this.listed = await this.options.chips.agents.list().catch(() => this.listed);
|
|
26831
|
+
await this.contexts.refresh();
|
|
26832
|
+
this.contextTimer = setInterval(() => void this.contexts?.refresh(), this.options.contextEveryMs ?? 15e3);
|
|
26833
|
+
this.contextTimer.unref();
|
|
26834
|
+
const fills2 = this.fills;
|
|
26835
|
+
const asked = this.options.fills;
|
|
26836
|
+
if (fills2 === void 0 || asked === void 0) return;
|
|
26837
|
+
const kept = this.options.state.fills ??= {};
|
|
26838
|
+
try {
|
|
26839
|
+
this.fillsPort = await fills2.listen(asked.port);
|
|
26840
|
+
if (kept.port !== this.fillsPort) {
|
|
26841
|
+
kept.port = this.fillsPort;
|
|
26842
|
+
this.options.saveState(this.options.state);
|
|
26843
|
+
}
|
|
26844
|
+
} catch (error62) {
|
|
26845
|
+
this.log(`context window: nothing can listen on port ${asked.port} (${messageOf2(error62)}), so Chips won't show how full each agent's context is`);
|
|
26846
|
+
}
|
|
26847
|
+
await fills2.sendAll();
|
|
26848
|
+
}
|
|
26849
|
+
/** The address Hermes's webhook POSTs a profile's readings to, once the bridge listens. */
|
|
26850
|
+
hookUrl(profile = "default") {
|
|
26851
|
+
return this.fillsPort === void 0 ? void 0 : hookUrl(this.fillsPort, profileName(profile));
|
|
26852
|
+
}
|
|
26853
|
+
/** The harness's agents listed again, the copies of any gone forgotten, and the context sent. */
|
|
26854
|
+
async agentsMoved() {
|
|
26855
|
+
if (this.contexts === void 0) return;
|
|
26856
|
+
const before = this.listed;
|
|
26857
|
+
this.listed = await this.options.chips.agents.list().catch(() => this.listed);
|
|
26858
|
+
const now = new Set(this.active().map((agent) => agent.agentId));
|
|
26859
|
+
for (const gone of before) {
|
|
26860
|
+
if (now.has(gone.agentId)) continue;
|
|
26861
|
+
this.options.chips.settings.forget(gone.agentId);
|
|
26862
|
+
this.fills?.forget(gone.agentId);
|
|
26863
|
+
}
|
|
26864
|
+
await this.contexts.refresh();
|
|
26865
|
+
}
|
|
26866
|
+
/** The harness's agents that read anything now: the active ones listed. */
|
|
26867
|
+
active() {
|
|
26868
|
+
return this.listed.filter((agent) => agent.state === "active");
|
|
26869
|
+
}
|
|
26870
|
+
/** A profile as the context needs it: its name and its home under Hermes's root. */
|
|
26871
|
+
profileOfRunning(agent) {
|
|
26872
|
+
const root2 = this.options.hermesRoot;
|
|
26873
|
+
if (root2 === void 0) return void 0;
|
|
26874
|
+
return { name: profileName(agent.config.address.profile), home: profileHome(root2, agent.config.address.profile) };
|
|
26875
|
+
}
|
|
26876
|
+
/** The profile an agent runs on: its own, a copy's original's, else the first (as `profileOf` picks). */
|
|
26877
|
+
profileFor(agentId2) {
|
|
26878
|
+
const own2 = this.agents.find((agent) => agent.agentId === agentId2);
|
|
26879
|
+
const copied = this.options.state.profiles?.[agentId2];
|
|
26880
|
+
const original = this.agents.find((agent) => copied !== void 0 && agent.key === copied);
|
|
26881
|
+
const running = own2 ?? original ?? this.agents[0];
|
|
26882
|
+
return running === void 0 ? void 0 : this.profileOfRunning(running);
|
|
26883
|
+
}
|
|
26884
|
+
/** Who reads what: each active agent with its profile, and the first profile, which new agents run on. */
|
|
26885
|
+
roster() {
|
|
26886
|
+
const readers = [];
|
|
26887
|
+
const ids2 = /* @__PURE__ */ new Set([...this.active().map((agent) => agent.agentId), ...this.agents.map((agent) => agent.agentId)]);
|
|
26888
|
+
for (const agentId2 of ids2) {
|
|
26889
|
+
if (this.listed.some((agent) => agent.agentId === agentId2 && agent.state !== "active")) continue;
|
|
26890
|
+
const profile = this.profileFor(agentId2);
|
|
26891
|
+
if (profile !== void 0) readers.push({ agentId: agentId2, profile });
|
|
26892
|
+
}
|
|
26893
|
+
const first = this.agents[0];
|
|
26894
|
+
return { readers, first: first === void 0 ? void 0 : this.profileOfRunning(first) };
|
|
26895
|
+
}
|
|
26896
|
+
/** The agent a Hermes session the bridge keeps is, with its profile: nothing for any other. */
|
|
26897
|
+
whoseSession(sessionId) {
|
|
26898
|
+
const entry = Object.entries(this.options.state.sessions).find(([, session]) => session === sessionId);
|
|
26899
|
+
if (entry === void 0) return void 0;
|
|
26900
|
+
const who = entry[0].slice(0, entry[0].lastIndexOf("|"));
|
|
26901
|
+
const agentId2 = this.agents.find((agent) => agent.key === who)?.agentId ?? who;
|
|
26902
|
+
const profile = this.profileFor(agentId2);
|
|
26903
|
+
return profile === void 0 ? void 0 : { agentId: agentId2, profile: profile.name, home: profile.home };
|
|
26904
|
+
}
|
|
26905
|
+
/** A person's edit of a context file from Chips (Chips ADR 0134), saved and answered. */
|
|
26906
|
+
async onContextEdit(frame) {
|
|
26907
|
+
if (this.contexts === void 0) throw new Refuse("This bridge reads no context files.");
|
|
26908
|
+
if (frame.agent_id !== void 0 && !this.listed.some((agent) => agent.agentId === frame.agent_id)) {
|
|
26909
|
+
this.listed = await this.options.chips.agents.list().catch(() => this.listed);
|
|
26910
|
+
}
|
|
26911
|
+
return this.contexts.edit(frame);
|
|
26912
|
+
}
|
|
26913
|
+
/** A person's Compact now (Chips ADR 0146): Hermes can't be asked to compact from outside a run. */
|
|
26914
|
+
onWindowCompact(frame) {
|
|
26915
|
+
throw new Refuse(WHY_NOT, this.fills?.copyOf(frame.agent_id));
|
|
26916
|
+
}
|
|
26917
|
+
/** A person's limit (Chips ADR 0146): Hermes sets it in its profile's config, for all its agents. */
|
|
26918
|
+
onWindowChange(frame) {
|
|
26919
|
+
const profile = this.profileFor(frame.agent_id);
|
|
26920
|
+
throw new Refuse(whyReadOnly(profile?.name ?? "default"), this.fills?.copyOf(frame.agent_id));
|
|
25753
26921
|
}
|
|
25754
26922
|
/** The Main agent, the first registered, once started: who says the harness's first words. */
|
|
25755
26923
|
get mainAgentId() {
|
|
@@ -25801,6 +26969,7 @@ var Bridge = class {
|
|
|
25801
26969
|
this.ours.add(agent.agentId);
|
|
25802
26970
|
this.names.set(agent.agentId, agent.name);
|
|
25803
26971
|
this.log(`${agent.name} made${agent.attached === false ? `, not in the room: ${agent.why ?? "too late"}` : ""}`);
|
|
26972
|
+
void this.agentsMoved();
|
|
25804
26973
|
}
|
|
25805
26974
|
/**
|
|
25806
26975
|
* A person asked for a copy of one of the harness's agents in another room (ADR 0087, Fork): the
|
|
@@ -25845,6 +27014,7 @@ var Bridge = class {
|
|
|
25845
27014
|
this.names.set(copy.agentId, copy.name);
|
|
25846
27015
|
const away = copy.attached === false && !copy.spaces.includes(spaceId);
|
|
25847
27016
|
this.log(`${copy.name}, a copy of ${name2}, made${away ? `, not in the room: ${copy.why ?? "too late"}` : ""}`);
|
|
27017
|
+
void this.agentsMoved();
|
|
25848
27018
|
}
|
|
25849
27019
|
/** An agent's name, from Chips's list when it isn't known yet. */
|
|
25850
27020
|
async nameOf(agentId2, otherwise) {
|
|
@@ -25863,11 +27033,11 @@ var Bridge = class {
|
|
|
25863
27033
|
*/
|
|
25864
27034
|
folderOf(agent, agentId2, name2) {
|
|
25865
27035
|
if (agent.config.folder !== void 0) return agent.config.folder;
|
|
25866
|
-
const { folders:
|
|
25867
|
-
if (
|
|
27036
|
+
const { folders: root2, state: state2 } = this.options;
|
|
27037
|
+
if (root2 === void 0) return void 0;
|
|
25868
27038
|
const kept = state2.folders?.[agentId2];
|
|
25869
|
-
const folder2 = kept ?? freeFolder(
|
|
25870
|
-
|
|
27039
|
+
const folder2 = kept ?? freeFolder(root2, name2, Object.values(state2.folders ?? {}));
|
|
27040
|
+
mkdirSync5(folder2, { recursive: true });
|
|
25871
27041
|
if (kept === void 0) {
|
|
25872
27042
|
state2.folders = { ...state2.folders, [agentId2]: folder2 };
|
|
25873
27043
|
this.options.saveState(state2);
|
|
@@ -25884,12 +27054,12 @@ var Bridge = class {
|
|
|
25884
27054
|
const folder2 = turn.folder;
|
|
25885
27055
|
if (folder2 === void 0) return void 0;
|
|
25886
27056
|
try {
|
|
25887
|
-
const project = this.options.chips.project({ spaceId, agentId: agentId2, name: name2, folder: folder2, log: (
|
|
27057
|
+
const project = this.options.chips.project({ spaceId, agentId: agentId2, name: name2, folder: folder2, log: (line2) => this.log(line2) });
|
|
25888
27058
|
const facts = await project.pull();
|
|
25889
27059
|
turn.project = { copy: project, name: name2 };
|
|
25890
27060
|
return facts;
|
|
25891
27061
|
} catch (error62) {
|
|
25892
|
-
this.log(`${name2}'s copy of the room's project couldn't be readied: ${
|
|
27062
|
+
this.log(`${name2}'s copy of the room's project couldn't be readied: ${messageOf2(error62)}`);
|
|
25893
27063
|
return void 0;
|
|
25894
27064
|
}
|
|
25895
27065
|
}
|
|
@@ -25908,18 +27078,21 @@ var Bridge = class {
|
|
|
25908
27078
|
const note = heldNote(pushed, name2);
|
|
25909
27079
|
return note === void 0 ? {} : { note };
|
|
25910
27080
|
} catch (error62) {
|
|
25911
|
-
this.log(`${name2}'s project couldn't be sent: ${
|
|
25912
|
-
const note = heldNote({ state: "failed", why:
|
|
27081
|
+
this.log(`${name2}'s project couldn't be sent: ${messageOf2(error62)}`);
|
|
27082
|
+
const note = heldNote({ state: "failed", why: messageOf2(error62) }, name2);
|
|
25913
27083
|
return note === void 0 ? {} : { note };
|
|
25914
27084
|
}
|
|
25915
27085
|
}
|
|
25916
|
-
/** Trouble while a profile's gateway doesn't answer, and fine once all do (ADR 0083). */
|
|
27086
|
+
/** Trouble while a profile's gateway doesn't answer, and fine once all do (ADR 0083); nothing while a restart runs. */
|
|
25917
27087
|
async sayHowItIs() {
|
|
27088
|
+
if (this.held) return;
|
|
25918
27089
|
for (const agent of this.agents) agent.up = await agent.client.healthy();
|
|
27090
|
+
if (this.held) return;
|
|
25919
27091
|
const down = this.agents.filter((agent) => !agent.up);
|
|
27092
|
+
if (down.length === 0) this.why = void 0;
|
|
25920
27093
|
const chips2 = this.options.chips;
|
|
25921
|
-
const
|
|
25922
|
-
await
|
|
27094
|
+
const said2 = down.length === 0 ? chips2.reportOk() : chips2.reportTrouble(this.why ?? `Hermes isn't answering for ${down.map((agent) => agent.config.name).join(", ")}`);
|
|
27095
|
+
await said2.catch(() => void 0);
|
|
25923
27096
|
}
|
|
25924
27097
|
// ------------------------------------------------------------------ what is said to it
|
|
25925
27098
|
/** A message for some of its agents: each takes a turn in its room, one after another there. */
|
|
@@ -25952,15 +27125,15 @@ var Bridge = class {
|
|
|
25952
27125
|
this.lookedAt.delete(old);
|
|
25953
27126
|
}
|
|
25954
27127
|
const context = await this.options.chips.roomContext(spaceId, { limit: 200 }).catch(() => void 0);
|
|
25955
|
-
const
|
|
27128
|
+
const line2 = context?.lines.find((each) => each.message_id === messageId);
|
|
25956
27129
|
const message = {
|
|
25957
27130
|
type: "message.new",
|
|
25958
27131
|
space_id: spaceId,
|
|
25959
27132
|
// An id of its own, so nothing of the turn is taken for the reply's.
|
|
25960
27133
|
message_id: messageIdOf(feedback2.id),
|
|
25961
|
-
text: lookAgain(feedback2.from.display_name,
|
|
27134
|
+
text: lookAgain(feedback2.from.display_name, line2?.text, note),
|
|
25962
27135
|
from: feedback2.from,
|
|
25963
|
-
...
|
|
27136
|
+
...line2 === void 0 ? {} : { for: [line2.from.id] }
|
|
25964
27137
|
};
|
|
25965
27138
|
await this.hear(message, { goal: lookingAgain(note), sayWho: false });
|
|
25966
27139
|
}
|
|
@@ -26014,9 +27187,10 @@ var Bridge = class {
|
|
|
26014
27187
|
try {
|
|
26015
27188
|
await this.turn(room, asked);
|
|
26016
27189
|
} catch (error62) {
|
|
26017
|
-
this.log(`a turn of ${room.agent.config.name} went wrong: ${
|
|
27190
|
+
this.log(`a turn of ${room.agent.config.name} went wrong: ${messageOf2(error62)}`);
|
|
26018
27191
|
} finally {
|
|
26019
27192
|
room.busy = false;
|
|
27193
|
+
void this.contexts?.refresh();
|
|
26020
27194
|
void this.pump(room);
|
|
26021
27195
|
}
|
|
26022
27196
|
}
|
|
@@ -26051,7 +27225,7 @@ var Bridge = class {
|
|
|
26051
27225
|
try {
|
|
26052
27226
|
session = await this.sessionFor(room);
|
|
26053
27227
|
} catch (error62) {
|
|
26054
|
-
await this.failed(turn, `${agent.config.name} couldn't open a session in Hermes: ${
|
|
27228
|
+
await this.failed(turn, `${agent.config.name} couldn't open a session in Hermes: ${messageOf2(error62)}`, true);
|
|
26055
27229
|
return;
|
|
26056
27230
|
}
|
|
26057
27231
|
const name2 = speakerId === void 0 ? agent.config.name : await this.nameOf(speakerId, agent.config.name);
|
|
@@ -26080,7 +27254,7 @@ var Bridge = class {
|
|
|
26080
27254
|
try {
|
|
26081
27255
|
turn.hermesRun = await agent.client.startRun({ text, sessionId: session, model: agent.config.model, key, instructions: instructionsFor(turn.folder, turn.project !== void 0) });
|
|
26082
27256
|
} catch (error62) {
|
|
26083
|
-
await this.failed(turn, `${agent.config.name} couldn't start in Hermes: ${
|
|
27257
|
+
await this.failed(turn, `${agent.config.name} couldn't start in Hermes: ${messageOf2(error62)}`, true);
|
|
26084
27258
|
return void 0;
|
|
26085
27259
|
}
|
|
26086
27260
|
if (turn.cancelled) await agent.client.stop(turn.hermesRun).catch(() => void 0);
|
|
@@ -26088,7 +27262,7 @@ var Bridge = class {
|
|
|
26088
27262
|
const last = await agent.client.events(turn.hermesRun, (event) => this.heard(turn, event));
|
|
26089
27263
|
return last ?? await this.endOf(turn);
|
|
26090
27264
|
} catch (error62) {
|
|
26091
|
-
this.log(`lost ${agent.config.name}'s run ${turn.hermesRun}: ${
|
|
27265
|
+
this.log(`lost ${agent.config.name}'s run ${turn.hermesRun}: ${messageOf2(error62)}`);
|
|
26092
27266
|
return await this.endOf(turn);
|
|
26093
27267
|
}
|
|
26094
27268
|
}
|
|
@@ -26101,7 +27275,7 @@ var Bridge = class {
|
|
|
26101
27275
|
if (status !== void 0 && TERMINAL.has(`run.${status}`)) {
|
|
26102
27276
|
return { ...state2, event: `run.${status}` };
|
|
26103
27277
|
}
|
|
26104
|
-
await new Promise((
|
|
27278
|
+
await new Promise((resolve7) => setTimeout(resolve7, 1e3));
|
|
26105
27279
|
}
|
|
26106
27280
|
await this.failed(turn, `${turn.agent.config.name}'s run in Hermes stopped answering.`, true);
|
|
26107
27281
|
return void 0;
|
|
@@ -26133,7 +27307,7 @@ var Bridge = class {
|
|
|
26133
27307
|
await turn.agent.client.steer(turn.hermesRun, text);
|
|
26134
27308
|
return;
|
|
26135
27309
|
} catch (error62) {
|
|
26136
|
-
this.log(`Hermes didn't take a steer mid-run (${
|
|
27310
|
+
this.log(`Hermes didn't take a steer mid-run (${messageOf2(error62)}); saying it next`);
|
|
26137
27311
|
}
|
|
26138
27312
|
}
|
|
26139
27313
|
turn.next.push(text);
|
|
@@ -26169,10 +27343,10 @@ var Bridge = class {
|
|
|
26169
27343
|
}
|
|
26170
27344
|
sayThought(turn, text, phase) {
|
|
26171
27345
|
const now = Date.now();
|
|
26172
|
-
const
|
|
26173
|
-
if (
|
|
27346
|
+
const line2 = lastLine(text);
|
|
27347
|
+
if (line2 === "" || now - turn.saidAt < QUIET_MS) return;
|
|
26174
27348
|
turn.saidAt = now;
|
|
26175
|
-
void turn.run.status({ phase, verbPhrase:
|
|
27349
|
+
void turn.run.status({ phase, verbPhrase: line2.slice(0, 200) }).catch(() => void 0);
|
|
26176
27350
|
}
|
|
26177
27351
|
async started(turn, event) {
|
|
26178
27352
|
const sub = event.event === "subagent.start";
|
|
@@ -26182,11 +27356,11 @@ var Bridge = class {
|
|
|
26182
27356
|
const stepId = callId ?? `${tool}-${turn.counted}`;
|
|
26183
27357
|
const lane = callId ?? (sub ? "subagent" : `tool:${tool}`);
|
|
26184
27358
|
turn.open.set(lane, [...turn.open.get(lane) ?? [], stepId]);
|
|
26185
|
-
const
|
|
27359
|
+
const said2 = sub ? event["goal"] : event["preview"];
|
|
26186
27360
|
await turn.run.step({
|
|
26187
27361
|
stepId,
|
|
26188
27362
|
status: "started",
|
|
26189
|
-
title: `${tool}${typeof
|
|
27363
|
+
title: `${tool}${typeof said2 === "string" && said2 !== "" ? ` \xB7 ${said2}` : ""}`.slice(0, 300),
|
|
26190
27364
|
tool,
|
|
26191
27365
|
...KINDS[tool] === void 0 ? {} : { kind: KINDS[tool] }
|
|
26192
27366
|
}).catch(() => void 0);
|
|
@@ -26241,7 +27415,7 @@ var Bridge = class {
|
|
|
26241
27415
|
${command}
|
|
26242
27416
|
\`\`\`` } }
|
|
26243
27417
|
});
|
|
26244
|
-
const stopped2 = new Promise((
|
|
27418
|
+
const stopped2 = new Promise((resolve7) => turn.run.on("cancel", () => resolve7(void 0)));
|
|
26245
27419
|
const decision = await Promise.race([asked, stopped2]);
|
|
26246
27420
|
const pending = turn.approvals.get(requestId ?? approvalId);
|
|
26247
27421
|
if (decision === void 0 || pending === void 0 || pending.answered) return;
|
|
@@ -26249,10 +27423,10 @@ ${command}
|
|
|
26249
27423
|
if (decision.allowed) turn.allowed.push(approvalId);
|
|
26250
27424
|
const generation = event["execution_generation"];
|
|
26251
27425
|
await turn.agent.client.approve(turn.hermesRun, {
|
|
26252
|
-
choice: choiceOf(decision, choices),
|
|
27426
|
+
choice: choiceOf(decision, choices, command !== void 0),
|
|
26253
27427
|
...requestId === void 0 ? {} : { request_id: requestId },
|
|
26254
27428
|
...typeof generation === "number" ? { execution_generation: generation } : {}
|
|
26255
|
-
}).catch((error62) => this.log(`Hermes didn't take the answer: ${
|
|
27429
|
+
}).catch((error62) => this.log(`Hermes didn't take the answer: ${messageOf2(error62)}`));
|
|
26256
27430
|
}
|
|
26257
27431
|
/** An approval answered in Hermes's own tools: settled in Chips, once, so the card cools (ADR 0079). */
|
|
26258
27432
|
async settled(turn, event) {
|
|
@@ -26297,10 +27471,27 @@ ${command}
|
|
|
26297
27471
|
if (reply !== "" || post.uploadIds.length > 0) {
|
|
26298
27472
|
await turn.speaker.post({ spaceId: run2.spaceId, runId: run2.id, text: reply, uploadIds: post.uploadIds, change });
|
|
26299
27473
|
}
|
|
26300
|
-
const
|
|
26301
|
-
const summary =
|
|
27474
|
+
const said2 = post.summary;
|
|
27475
|
+
const summary = said2 === "" ? "Done." : said2.length <= 2e3 ? said2 : `${said2.slice(0, 1990)}\u2026`;
|
|
26302
27476
|
await run2.complete({ summary });
|
|
26303
27477
|
this.log(`${agent.config.name} answered in ${run2.spaceId}`);
|
|
27478
|
+
this.missingReadings();
|
|
27479
|
+
}
|
|
27480
|
+
/**
|
|
27481
|
+
* A turn ended and Hermes sent no reading at all since the bridge started: its gateway most
|
|
27482
|
+
* likely started before Chips's webhook was in its config. Said once, a little after, since a
|
|
27483
|
+
* reading comes a moment after its model call.
|
|
27484
|
+
*/
|
|
27485
|
+
missingReadings() {
|
|
27486
|
+
if (this.fills === void 0 || this.fillsPort === void 0 || this.fills.anyHeard || this.toldNoReadings) return;
|
|
27487
|
+
this.toldNoReadings = true;
|
|
27488
|
+
setTimeout(() => {
|
|
27489
|
+
if (this.fills?.anyHeard === false) {
|
|
27490
|
+
this.log(
|
|
27491
|
+
"context window: Hermes sent no reading after a turn. Start hermes gateway again once, so it takes Chips's webhook from its config."
|
|
27492
|
+
);
|
|
27493
|
+
}
|
|
27494
|
+
}, 1e4).unref();
|
|
26304
27495
|
}
|
|
26305
27496
|
/** A run that failed, was stopped or was cut short in Hermes. */
|
|
26306
27497
|
async ended(turn, last) {
|
|
@@ -26349,8 +27540,9 @@ ${command}
|
|
|
26349
27540
|
if (retryable) void this.sayHowItIs();
|
|
26350
27541
|
}
|
|
26351
27542
|
};
|
|
26352
|
-
function choiceOf(decision, choices) {
|
|
27543
|
+
function choiceOf(decision, choices, command = false) {
|
|
26353
27544
|
if (!decision.allowed) return "deny";
|
|
27545
|
+
if (command) return "once";
|
|
26354
27546
|
if (decision.scope === "always" && choices.includes("always")) return "always";
|
|
26355
27547
|
if (decision.scope !== "once" && choices.includes("session")) return "session";
|
|
26356
27548
|
return "once";
|
|
@@ -26359,7 +27551,7 @@ function idOf(event) {
|
|
|
26359
27551
|
const id2 = event["request_id"] ?? event["prompt_id"];
|
|
26360
27552
|
return typeof id2 === "string" && id2 !== "" ? id2 : void 0;
|
|
26361
27553
|
}
|
|
26362
|
-
function
|
|
27554
|
+
function messageOf2(error62) {
|
|
26363
27555
|
if (error62 instanceof HermesError) return `${error62.message} (${error62.status})`;
|
|
26364
27556
|
return error62 instanceof Error ? error62.message : String(error62);
|
|
26365
27557
|
}
|
|
@@ -26367,7 +27559,7 @@ function forkName(original) {
|
|
|
26367
27559
|
return `${original.trim().replace(/ · fork( \d+)?$/, "")} \xB7 fork`;
|
|
26368
27560
|
}
|
|
26369
27561
|
function goalOf(message, name2) {
|
|
26370
|
-
const first = message.text.split("\n").find((
|
|
27562
|
+
const first = message.text.split("\n").find((line2) => line2.trim() !== "")?.trim() ?? "";
|
|
26371
27563
|
const named = first.toLowerCase().startsWith(`@${name2.toLowerCase()}`);
|
|
26372
27564
|
const goal = named ? first.slice(name2.length + 1).trim() : first.replace(/^@\S+\s*/, "");
|
|
26373
27565
|
return (goal === "" ? `Answer ${message.from.display_name}` : goal).slice(0, 300);
|
|
@@ -26375,17 +27567,17 @@ function goalOf(message, name2) {
|
|
|
26375
27567
|
var REPLY_SHOWN = 1500;
|
|
26376
27568
|
var GOAL_SHOWN = 120;
|
|
26377
27569
|
function lookAgain(who, reply, note) {
|
|
26378
|
-
const
|
|
27570
|
+
const said2 = reply?.trim() ?? "";
|
|
26379
27571
|
return [
|
|
26380
27572
|
`${who} says your reply in this space wasn't right.`,
|
|
26381
|
-
...
|
|
26382
|
-
\xAB${cut(
|
|
27573
|
+
...said2 === "" ? [] : [`Your reply:
|
|
27574
|
+
\xAB${cut(said2, REPLY_SHOWN)}\xBB`],
|
|
26383
27575
|
note === void 0 || note === "" ? "They didn't say what. Read your reply again and find what was wrong." : `What wasn't right: \xAB${note}\xBB`,
|
|
26384
27576
|
"Look at it again and answer properly in its place. If it is not clear what was wrong, ask them in one short line. Say plainly what you changed."
|
|
26385
27577
|
].join("\n\n");
|
|
26386
27578
|
}
|
|
26387
27579
|
function lookingAgain(note) {
|
|
26388
|
-
const first = note?.split("\n").find((
|
|
27580
|
+
const first = note?.split("\n").find((line2) => line2.trim() !== "")?.trim();
|
|
26389
27581
|
return first === void 0 ? "Looking again at its reply" : cut(`Looking again: ${first}`, GOAL_SHOWN);
|
|
26390
27582
|
}
|
|
26391
27583
|
function cut(text, most) {
|
|
@@ -26401,31 +27593,492 @@ function resultWords(preview2) {
|
|
|
26401
27593
|
}
|
|
26402
27594
|
if (typeof result !== "object" || result === null || !("output" in result)) return preview2;
|
|
26403
27595
|
const { output: output2, error: error62 } = result;
|
|
26404
|
-
const
|
|
26405
|
-
return typeof
|
|
27596
|
+
const said2 = [output2, error62].find((each) => typeof each === "string" && each.trim() !== "");
|
|
27597
|
+
return typeof said2 === "string" ? said2.trim() : "";
|
|
26406
27598
|
}
|
|
26407
27599
|
function lastLine(text) {
|
|
26408
|
-
const lines2 = text.split(/(?<=[.!?])\s+|\n+/).map((
|
|
27600
|
+
const lines2 = text.split(/(?<=[.!?])\s+|\n+/).map((line2) => line2.trim()).filter((line2) => line2 !== "");
|
|
26409
27601
|
return lines2.at(-1) ?? "";
|
|
26410
27602
|
}
|
|
26411
27603
|
function lowerFirst(text) {
|
|
26412
27604
|
return text.length === 0 ? text : `${text[0]?.toLowerCase() ?? ""}${text.slice(1)}`;
|
|
26413
27605
|
}
|
|
26414
27606
|
|
|
27607
|
+
// src/gateway.ts
|
|
27608
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
27609
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
27610
|
+
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 statSync4 } from "node:fs";
|
|
27611
|
+
import { homedir as homedir5 } from "node:os";
|
|
27612
|
+
import { basename as basename4, delimiter as delimiter2, dirname as dirname5, join as join9, resolve as resolve3 } from "node:path";
|
|
27613
|
+
var TIMING = {
|
|
27614
|
+
stopMs: 15e3,
|
|
27615
|
+
killMs: 5e3,
|
|
27616
|
+
earlyMs: 1e4,
|
|
27617
|
+
upMs: 9e4,
|
|
27618
|
+
serviceMs: 15e4,
|
|
27619
|
+
pollMs: 500
|
|
27620
|
+
};
|
|
27621
|
+
var EXPAT = "/opt/homebrew/opt/expat/lib/libexpat.1.dylib";
|
|
27622
|
+
var EXPAT_SYMBOL = "_XML_SetAllocTrackerActivationThreshold";
|
|
27623
|
+
function gatewaysOf(agents) {
|
|
27624
|
+
return [...new Set(agents.map(({ address }) => address.url.replace(/\/+$/, "")))].map((url2) => ({ url: url2 }));
|
|
27625
|
+
}
|
|
27626
|
+
function isLocal(url2) {
|
|
27627
|
+
try {
|
|
27628
|
+
return ["127.0.0.1", "localhost", "[::1]", "::1"].includes(new URL(url2).hostname);
|
|
27629
|
+
} catch {
|
|
27630
|
+
return false;
|
|
27631
|
+
}
|
|
27632
|
+
}
|
|
27633
|
+
function portOf(url2) {
|
|
27634
|
+
const parsed = new URL(url2);
|
|
27635
|
+
return Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80));
|
|
27636
|
+
}
|
|
27637
|
+
function line(words4) {
|
|
27638
|
+
const one = words4.replace(/\s+/g, " ").trim();
|
|
27639
|
+
return one.length <= 200 ? one : `${one.slice(0, 199)}\u2026`;
|
|
27640
|
+
}
|
|
27641
|
+
function whyFrom(output2, code) {
|
|
27642
|
+
const lines2 = output2.split("\n").map((each) => each.trim()).filter((each) => each !== "");
|
|
27643
|
+
const why = lines2.findLast((each) => /^[A-Za-z_][\w.]*(Error|Exception)\b/.test(each)) ?? lines2.at(-1);
|
|
27644
|
+
if (why === void 0) return code === null || code === void 0 ? "it was stopped" : `it exited ${code}`;
|
|
27645
|
+
return why.length <= 200 ? why : why.replace(/\/(?:[^\s/(),:'"]+\/)+([^\s/(),:'"]+)/g, "$1");
|
|
27646
|
+
}
|
|
27647
|
+
function gatewayRunArgs(command) {
|
|
27648
|
+
const words4 = command.trim().split(/\s+/);
|
|
27649
|
+
const at = words4.findIndex((word) => basename4(word) === "hermes");
|
|
27650
|
+
if (at === -1) return void 0;
|
|
27651
|
+
const args = words4.slice(at + 1);
|
|
27652
|
+
const plain = [];
|
|
27653
|
+
for (let next = 0; next < args.length; next += 1) {
|
|
27654
|
+
const word = args[next] ?? "";
|
|
27655
|
+
if (word === "-p" || word === "--profile") next += 1;
|
|
27656
|
+
else if (!word.startsWith("-")) plain.push(word);
|
|
27657
|
+
}
|
|
27658
|
+
return plain[0] === "gateway" && plain[1] === "run" ? args : void 0;
|
|
27659
|
+
}
|
|
27660
|
+
function serviceOf(system, root2, env = {}) {
|
|
27661
|
+
const home2 = resolve3(root2);
|
|
27662
|
+
const native = resolve3(system.home, ".hermes");
|
|
27663
|
+
const suffix = home2 === native ? "" : dirname5(home2) === join9(native, "profiles") ? basename4(home2) : createHash3("sha256").update(home2).digest("hex").slice(0, 8);
|
|
27664
|
+
if (system.platform === "darwin") {
|
|
27665
|
+
const path = join9(system.home, "Library", "LaunchAgents", `ai.hermes.gateway${suffix ? `-${suffix}` : ""}.plist`);
|
|
27666
|
+
return system.exists(path) ? { kind: "launchd", path } : void 0;
|
|
27667
|
+
}
|
|
27668
|
+
if (system.platform === "linux") {
|
|
27669
|
+
const unit = `hermes-gateway${suffix ? `-${suffix}` : ""}.service`;
|
|
27670
|
+
const user = join9(env["XDG_CONFIG_HOME"] || join9(system.home, ".config"), "systemd", "user", unit);
|
|
27671
|
+
if (system.exists(user)) return { kind: "systemd", path: user };
|
|
27672
|
+
const machine = join9("/etc/systemd/system", unit);
|
|
27673
|
+
if (system.exists(machine)) return { kind: "systemd", path: machine, system: true };
|
|
27674
|
+
}
|
|
27675
|
+
return void 0;
|
|
27676
|
+
}
|
|
27677
|
+
function expand(text, env) {
|
|
27678
|
+
let out = "";
|
|
27679
|
+
let at = 0;
|
|
27680
|
+
while (at < text.length) {
|
|
27681
|
+
const char = text[at] ?? "";
|
|
27682
|
+
if (char === "\\" && at + 1 < text.length) {
|
|
27683
|
+
out += text[at + 1];
|
|
27684
|
+
at += 2;
|
|
27685
|
+
} else if (char !== "$") {
|
|
27686
|
+
out += char;
|
|
27687
|
+
at += 1;
|
|
27688
|
+
} else if (text[at + 1] === "{") {
|
|
27689
|
+
let depth = 1;
|
|
27690
|
+
let end2 = at + 2;
|
|
27691
|
+
for (; end2 < text.length; end2 += 1) {
|
|
27692
|
+
if (text[end2] === "{") depth += 1;
|
|
27693
|
+
else if (text[end2] === "}" && --depth === 0) break;
|
|
27694
|
+
}
|
|
27695
|
+
const inner = /^([A-Za-z_][A-Za-z0-9_]*)(?:(:?)-([\s\S]*))?$/.exec(text.slice(at + 2, end2));
|
|
27696
|
+
if (inner !== null) {
|
|
27697
|
+
const value = env[inner[1] ?? ""];
|
|
27698
|
+
const unset = inner[2] === ":" ? value === void 0 || value === "" : value === void 0;
|
|
27699
|
+
out += inner[3] !== void 0 && unset ? expand(inner[3], env) : value ?? "";
|
|
27700
|
+
}
|
|
27701
|
+
at = end2 + 1;
|
|
27702
|
+
} else {
|
|
27703
|
+
const name2 = /^[A-Za-z_][A-Za-z0-9_]*/.exec(text.slice(at + 1))?.[0];
|
|
27704
|
+
out += name2 === void 0 ? "$" : env[name2] ?? "";
|
|
27705
|
+
at += 1 + (name2?.length ?? 0);
|
|
27706
|
+
}
|
|
27707
|
+
}
|
|
27708
|
+
return out;
|
|
27709
|
+
}
|
|
27710
|
+
function wrapperOf(text, env) {
|
|
27711
|
+
for (const each of text.split("\n")) {
|
|
27712
|
+
const found = /^\s*((?:[A-Za-z_][A-Za-z0-9_]*="(?:[^"\\]|\\.)*"\s+)*)exec\s+"([^"]+)"/.exec(each);
|
|
27713
|
+
if (found === null) continue;
|
|
27714
|
+
const vars = {};
|
|
27715
|
+
for (const set2 of (found[1] ?? "").matchAll(/([A-Za-z_][A-Za-z0-9_]*)="((?:[^"\\]|\\.)*)"/g)) {
|
|
27716
|
+
vars[set2[1] ?? ""] = expand(set2[2] ?? "", env);
|
|
27717
|
+
}
|
|
27718
|
+
return { entry: expand(found[2] ?? "", env), vars };
|
|
27719
|
+
}
|
|
27720
|
+
return void 0;
|
|
27721
|
+
}
|
|
27722
|
+
var NO_HERMES = "The hermes command isn't on this machine's PATH, so Hermes's gateway can't be started.";
|
|
27723
|
+
var messageOf3 = (error62) => error62 instanceof Error ? error62.message : String(error62);
|
|
27724
|
+
function gatewayEnv(env) {
|
|
27725
|
+
return Object.fromEntries(Object.entries(env).filter(([name2]) => !name2.startsWith("CHIPS_")));
|
|
27726
|
+
}
|
|
27727
|
+
var Gateways = class {
|
|
27728
|
+
constructor(options) {
|
|
27729
|
+
this.options = options;
|
|
27730
|
+
}
|
|
27731
|
+
options;
|
|
27732
|
+
timing = TIMING;
|
|
27733
|
+
/** Each gateway restarted, one after another: what went wrong, in a line, or nothing. */
|
|
27734
|
+
async restart() {
|
|
27735
|
+
const troubles = [];
|
|
27736
|
+
for (const gateway of this.options.gateways) {
|
|
27737
|
+
let trouble;
|
|
27738
|
+
try {
|
|
27739
|
+
trouble = await this.restartOne(gateway);
|
|
27740
|
+
} catch (error62) {
|
|
27741
|
+
trouble = `Restart failed: ${messageOf3(error62)}`;
|
|
27742
|
+
}
|
|
27743
|
+
if (trouble !== void 0) troubles.push(trouble);
|
|
27744
|
+
}
|
|
27745
|
+
return troubles.length === 0 ? void 0 : line(troubles.join(" "));
|
|
27746
|
+
}
|
|
27747
|
+
/** Each local gateway that doesn't answer, started: the bridge is starting. */
|
|
27748
|
+
async startIfDown() {
|
|
27749
|
+
const { system, log: log2 } = this.options;
|
|
27750
|
+
for (const gateway of this.options.gateways) {
|
|
27751
|
+
if (!isLocal(gateway.url)) continue;
|
|
27752
|
+
const port = portOf(gateway.url);
|
|
27753
|
+
try {
|
|
27754
|
+
if (await system.healthy(gateway.url)) continue;
|
|
27755
|
+
let trouble;
|
|
27756
|
+
if (this.serviceFor(gateway) !== void 0) {
|
|
27757
|
+
log2(`Hermes isn't answering on port ${port}: starting its service, hermes gateway start`);
|
|
27758
|
+
trouble = await this.byService(gateway, "start", void 0);
|
|
27759
|
+
} else {
|
|
27760
|
+
const pid = await system.listener(port);
|
|
27761
|
+
if (pid !== void 0) {
|
|
27762
|
+
log2(`Hermes isn't answering on port ${port}, but pid ${pid} listens there: no second gateway started`);
|
|
27763
|
+
continue;
|
|
27764
|
+
}
|
|
27765
|
+
log2(`Hermes isn't answering on port ${port}: starting hermes gateway run`);
|
|
27766
|
+
trouble = await this.start(gateway, ["gateway", "run"], void 0);
|
|
27767
|
+
}
|
|
27768
|
+
if (trouble !== void 0) log2(trouble);
|
|
27769
|
+
} catch (error62) {
|
|
27770
|
+
log2(`Hermes's gateway on port ${port} couldn't be started: ${messageOf3(error62)}`);
|
|
27771
|
+
}
|
|
27772
|
+
}
|
|
27773
|
+
}
|
|
27774
|
+
/**
|
|
27775
|
+
* The service of Hermes's root, for the Main agent's gateway: a gateway at another address is
|
|
27776
|
+
* another profile's own, which `hermes gateway status` of the root doesn't speak for.
|
|
27777
|
+
*/
|
|
27778
|
+
serviceFor(gateway) {
|
|
27779
|
+
if (gateway !== this.options.gateways[0]) return void 0;
|
|
27780
|
+
return serviceOf(this.options.system, this.options.root, this.options.env);
|
|
27781
|
+
}
|
|
27782
|
+
async restartOne(gateway) {
|
|
27783
|
+
const { system, log: log2 } = this.options;
|
|
27784
|
+
if (!isLocal(gateway.url)) {
|
|
27785
|
+
return `Hermes's gateway at ${new URL(gateway.url).host} isn't on this machine, so it can't be restarted from here.`;
|
|
27786
|
+
}
|
|
27787
|
+
const port = portOf(gateway.url);
|
|
27788
|
+
const pid = await system.listener(port);
|
|
27789
|
+
if (this.serviceFor(gateway) !== void 0) {
|
|
27790
|
+
log2("restarting Hermes's service: hermes gateway restart");
|
|
27791
|
+
return this.byService(gateway, "restart", pid);
|
|
27792
|
+
}
|
|
27793
|
+
if (pid === void 0) {
|
|
27794
|
+
if (await system.healthy(gateway.url)) return `Can't tell what answers on port ${port}, so nothing was restarted.`;
|
|
27795
|
+
log2(`nothing listens on port ${port}: starting hermes gateway run`);
|
|
27796
|
+
return this.start(gateway, ["gateway", "run"], void 0);
|
|
27797
|
+
}
|
|
27798
|
+
const info = await system.describe(pid);
|
|
27799
|
+
if (info === void 0) return this.start(gateway, ["gateway", "run"], void 0);
|
|
27800
|
+
const args = this.ours(info);
|
|
27801
|
+
if (args === void 0) {
|
|
27802
|
+
log2(`pid ${pid} on port ${port} isn't this user's hermes gateway run, so it was left alone: ${info.command.slice(0, 160)}`);
|
|
27803
|
+
return `Port ${port} is held by something other than hermes gateway run (pid ${pid}), so nothing was restarted.`;
|
|
27804
|
+
}
|
|
27805
|
+
if (system.which("hermes", this.options.env) === void 0) return NO_HERMES;
|
|
27806
|
+
log2(`stopping Hermes's gateway, pid ${pid}`);
|
|
27807
|
+
if (!await this.stop(pid)) return `Hermes's gateway (pid ${pid}) didn't stop, so it wasn't started again.`;
|
|
27808
|
+
return this.start(gateway, args, info.cwd);
|
|
27809
|
+
}
|
|
27810
|
+
/** A `hermes gateway run` of this user's: the words to start it again with. */
|
|
27811
|
+
ours(info) {
|
|
27812
|
+
return info.uid === this.options.system.uid ? gatewayRunArgs(info.command) : void 0;
|
|
27813
|
+
}
|
|
27814
|
+
/** SIGTERM, then SIGKILL once it has had a while, and only while it is still that gateway. */
|
|
27815
|
+
async stop(pid) {
|
|
27816
|
+
const { system, log: log2 } = this.options;
|
|
27817
|
+
system.signal(pid, "SIGTERM");
|
|
27818
|
+
if (await this.gone(pid, this.timing.stopMs)) return true;
|
|
27819
|
+
const still = await system.describe(pid);
|
|
27820
|
+
if (still === void 0) return true;
|
|
27821
|
+
if (this.ours(still) === void 0) return false;
|
|
27822
|
+
log2(`Hermes's gateway, pid ${pid}, is still running ${this.timing.stopMs / 1e3} s after SIGTERM: SIGKILL`);
|
|
27823
|
+
system.signal(pid, "SIGKILL");
|
|
27824
|
+
return this.gone(pid, this.timing.killMs);
|
|
27825
|
+
}
|
|
27826
|
+
async gone(pid, withinMs) {
|
|
27827
|
+
const { system } = this.options;
|
|
27828
|
+
const by = system.now() + withinMs;
|
|
27829
|
+
while (system.alive(pid)) {
|
|
27830
|
+
if (system.now() >= by) return false;
|
|
27831
|
+
await system.sleep(this.timing.pollMs);
|
|
27832
|
+
}
|
|
27833
|
+
return true;
|
|
27834
|
+
}
|
|
27835
|
+
/** Hermes's own `hermes gateway restart|start`, then the gateway answering, from a new process. */
|
|
27836
|
+
async byService(gateway, verb, before) {
|
|
27837
|
+
const { system, env } = this.options;
|
|
27838
|
+
const hermes = system.which("hermes", env);
|
|
27839
|
+
if (hermes === void 0) return NO_HERMES;
|
|
27840
|
+
const scope = this.serviceFor(gateway)?.system === true ? ["--system"] : [];
|
|
27841
|
+
const ran = await system.run(hermes, ["gateway", verb, ...scope], gatewayEnv(env), this.timing.serviceMs);
|
|
27842
|
+
if (ran.code !== 0) return line(`hermes gateway ${verb} failed: ${whyFrom(ran.output, ran.code)}`);
|
|
27843
|
+
const by = system.now() + this.timing.serviceMs;
|
|
27844
|
+
for (; ; ) {
|
|
27845
|
+
const old = before !== void 0 && system.alive(before);
|
|
27846
|
+
if (!old && await system.healthy(gateway.url)) return void 0;
|
|
27847
|
+
if (system.now() >= by) {
|
|
27848
|
+
if (await system.healthy(gateway.url)) return void 0;
|
|
27849
|
+
return `Hermes's service ${verb === "start" ? "started" : "restarted"}, but nothing answers on port ${portOf(gateway.url)}.`;
|
|
27850
|
+
}
|
|
27851
|
+
await system.sleep(this.timing.pollMs);
|
|
27852
|
+
}
|
|
27853
|
+
}
|
|
27854
|
+
/**
|
|
27855
|
+
* `hermes <args>` (`gateway run`), on its own, in `cwd` (else the home), then through Homebrew's
|
|
27856
|
+
* expat when it died of macOS's.
|
|
27857
|
+
*/
|
|
27858
|
+
async start(gateway, args, cwd) {
|
|
27859
|
+
const { system, env, log: log2 } = this.options;
|
|
27860
|
+
const port = portOf(gateway.url);
|
|
27861
|
+
const hermes = system.which("hermes", env);
|
|
27862
|
+
if (hermes === void 0) return NO_HERMES;
|
|
27863
|
+
const folder2 = cwd ?? system.home;
|
|
27864
|
+
const plain = gatewayEnv(env);
|
|
27865
|
+
let outcome = await this.launch(gateway, hermes, args, plain, folder2, `hermes ${args.join(" ")}`);
|
|
27866
|
+
if (outcome.state === "ended" && outcome.early && outcome.output.includes(EXPAT_SYMBOL) && system.exists(EXPAT)) {
|
|
27867
|
+
const wrapper = wrapperOf(system.read(hermes) ?? "", plain);
|
|
27868
|
+
if (wrapper !== void 0) {
|
|
27869
|
+
const expat = dirname5(EXPAT);
|
|
27870
|
+
log2(
|
|
27871
|
+
`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}.`
|
|
27872
|
+
);
|
|
27873
|
+
const libraries = [expat, plain["DYLD_LIBRARY_PATH"]].filter((each) => each !== void 0 && each !== "").join(":");
|
|
27874
|
+
outcome = await this.launch(
|
|
27875
|
+
gateway,
|
|
27876
|
+
wrapper.entry,
|
|
27877
|
+
args,
|
|
27878
|
+
{ ...plain, ...wrapper.vars, DYLD_LIBRARY_PATH: libraries },
|
|
27879
|
+
folder2,
|
|
27880
|
+
`${wrapper.entry} ${args.join(" ")} with DYLD_LIBRARY_PATH=${expat}`
|
|
27881
|
+
);
|
|
27882
|
+
}
|
|
27883
|
+
}
|
|
27884
|
+
if (outcome.state === "up") {
|
|
27885
|
+
log2(`Hermes's gateway${outcome.pid === void 0 ? "" : `, pid ${outcome.pid},`} answers on port ${port}. It logs to ${this.options.logFile}.`);
|
|
27886
|
+
return void 0;
|
|
27887
|
+
}
|
|
27888
|
+
if (outcome.state === "silent") return `Hermes's gateway started, but nothing answers on port ${port}.`;
|
|
27889
|
+
return line(`Hermes's gateway stopped as it started: ${whyFrom(outcome.output, outcome.code)}`);
|
|
27890
|
+
}
|
|
27891
|
+
/** A program started, and watched until it answers, ends, or has had its time. */
|
|
27892
|
+
async launch(gateway, command, args, env, cwd, what) {
|
|
27893
|
+
const { system, logFile } = this.options;
|
|
27894
|
+
system.note(logFile, `--- ${new Date(system.now()).toISOString()} chips-hermes started ${what}`);
|
|
27895
|
+
const from = system.size(logFile);
|
|
27896
|
+
const startedAt = system.now();
|
|
27897
|
+
const child = system.launch({ command, args, env, cwd, log: logFile });
|
|
27898
|
+
let ended;
|
|
27899
|
+
void child.exited.then(({ code }) => {
|
|
27900
|
+
ended = { code, at: system.now() };
|
|
27901
|
+
});
|
|
27902
|
+
for (; ; ) {
|
|
27903
|
+
await Promise.resolve();
|
|
27904
|
+
if (ended !== void 0) {
|
|
27905
|
+
return { state: "ended", early: ended.at - startedAt <= this.timing.earlyMs, output: system.since(logFile, from), code: ended.code };
|
|
27906
|
+
}
|
|
27907
|
+
if (await system.healthy(gateway.url)) return { state: "up", pid: child.pid };
|
|
27908
|
+
if (system.now() - startedAt >= this.timing.upMs) return { state: "silent" };
|
|
27909
|
+
await system.sleep(this.timing.pollMs);
|
|
27910
|
+
}
|
|
27911
|
+
}
|
|
27912
|
+
};
|
|
27913
|
+
function restarter(deps) {
|
|
27914
|
+
let running;
|
|
27915
|
+
return (ask2) => {
|
|
27916
|
+
if (running !== void 0) {
|
|
27917
|
+
deps.log(`restart asked by ${ask2.askedBy} while one runs: it is the same restart`);
|
|
27918
|
+
return running;
|
|
27919
|
+
}
|
|
27920
|
+
running = (async () => {
|
|
27921
|
+
deps.log(`restart asked by ${ask2.askedBy}: restarting Hermes's gateway`);
|
|
27922
|
+
deps.hold(true);
|
|
27923
|
+
let trouble;
|
|
27924
|
+
try {
|
|
27925
|
+
trouble = await deps.restart();
|
|
27926
|
+
} catch (error62) {
|
|
27927
|
+
trouble = line(`Restart failed: ${error62 instanceof Error ? error62.message : String(error62)}`);
|
|
27928
|
+
}
|
|
27929
|
+
deps.log(trouble === void 0 ? "restarted, and Hermes answers" : `restart: ${trouble}`);
|
|
27930
|
+
try {
|
|
27931
|
+
await deps.say(trouble);
|
|
27932
|
+
} catch (error62) {
|
|
27933
|
+
deps.log(`could not tell Chips how the restart went: ${String(error62)}`);
|
|
27934
|
+
} finally {
|
|
27935
|
+
deps.hold(false);
|
|
27936
|
+
}
|
|
27937
|
+
})().finally(() => {
|
|
27938
|
+
running = void 0;
|
|
27939
|
+
});
|
|
27940
|
+
return running;
|
|
27941
|
+
};
|
|
27942
|
+
}
|
|
27943
|
+
function gatewayLog(configFolder, platform = process.platform) {
|
|
27944
|
+
return platform === "darwin" ? join9(homedir5(), "Library", "Logs", "chips-hermes-gateway.log") : join9(configFolder, "gateway.log");
|
|
27945
|
+
}
|
|
27946
|
+
function runToEnd(command, args, options) {
|
|
27947
|
+
return new Promise((done) => {
|
|
27948
|
+
const child = spawn2(command, args, { env: options.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
27949
|
+
let output2 = "";
|
|
27950
|
+
const keep = (data) => {
|
|
27951
|
+
output2 = (output2 + String(data)).slice(-8e3);
|
|
27952
|
+
};
|
|
27953
|
+
child.stdout.on("data", keep);
|
|
27954
|
+
child.stderr.on("data", keep);
|
|
27955
|
+
const limit = setTimeout(() => child.kill("SIGTERM"), options.timeoutMs);
|
|
27956
|
+
child.on("error", (error62) => {
|
|
27957
|
+
clearTimeout(limit);
|
|
27958
|
+
done({ code: null, output: error62.message });
|
|
27959
|
+
});
|
|
27960
|
+
child.on("close", (code) => {
|
|
27961
|
+
clearTimeout(limit);
|
|
27962
|
+
done({ code, output: output2 });
|
|
27963
|
+
});
|
|
27964
|
+
});
|
|
27965
|
+
}
|
|
27966
|
+
function thisMachine() {
|
|
27967
|
+
return {
|
|
27968
|
+
platform: process.platform,
|
|
27969
|
+
uid: process.getuid?.() ?? -1,
|
|
27970
|
+
home: homedir5(),
|
|
27971
|
+
exists: existsSync8,
|
|
27972
|
+
read(path) {
|
|
27973
|
+
try {
|
|
27974
|
+
return readFileSync8(path, "utf8");
|
|
27975
|
+
} catch {
|
|
27976
|
+
return void 0;
|
|
27977
|
+
}
|
|
27978
|
+
},
|
|
27979
|
+
which(command, env) {
|
|
27980
|
+
for (const folder2 of (env["PATH"] ?? "").split(delimiter2)) {
|
|
27981
|
+
if (folder2 === "") continue;
|
|
27982
|
+
const path = join9(folder2, command);
|
|
27983
|
+
try {
|
|
27984
|
+
accessSync2(path, constants2.X_OK);
|
|
27985
|
+
return path;
|
|
27986
|
+
} catch {
|
|
27987
|
+
}
|
|
27988
|
+
}
|
|
27989
|
+
return void 0;
|
|
27990
|
+
},
|
|
27991
|
+
async listener(port) {
|
|
27992
|
+
const { output: output2 } = await runToEnd("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-Fp"], { timeoutMs: 5e3 });
|
|
27993
|
+
const pid = Number(/^p(\d+)$/m.exec(output2)?.[1]);
|
|
27994
|
+
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
27995
|
+
},
|
|
27996
|
+
async describe(pid) {
|
|
27997
|
+
const { code, output: output2 } = await runToEnd("ps", ["-ww", "-o", "uid=", "-o", "command=", "-p", String(pid)], { timeoutMs: 5e3 });
|
|
27998
|
+
const found = /^\s*(\d+)\s+(.*)$/m.exec(output2);
|
|
27999
|
+
if (code !== 0 || found === null) return void 0;
|
|
28000
|
+
return { uid: Number(found[1]), command: found[2] ?? "", cwd: processFolder(pid) };
|
|
28001
|
+
},
|
|
28002
|
+
alive(pid) {
|
|
28003
|
+
try {
|
|
28004
|
+
process.kill(pid, 0);
|
|
28005
|
+
return true;
|
|
28006
|
+
} catch (error62) {
|
|
28007
|
+
return error62.code === "EPERM";
|
|
28008
|
+
}
|
|
28009
|
+
},
|
|
28010
|
+
signal(pid, signal) {
|
|
28011
|
+
try {
|
|
28012
|
+
process.kill(pid, signal);
|
|
28013
|
+
} catch {
|
|
28014
|
+
}
|
|
28015
|
+
},
|
|
28016
|
+
run: (command, args, env, timeoutMs) => runToEnd(command, args, { env, timeoutMs }),
|
|
28017
|
+
launch({ command, args, env, cwd, log: log2 }) {
|
|
28018
|
+
const out = openSync2(log2, "a");
|
|
28019
|
+
try {
|
|
28020
|
+
const child = spawn2(command, args, { cwd, env, detached: true, stdio: ["ignore", out, out] });
|
|
28021
|
+
const exited = new Promise((done) => {
|
|
28022
|
+
child.once("exit", (code, signal) => done({ code, signal }));
|
|
28023
|
+
child.once("error", (error62) => {
|
|
28024
|
+
appendFileSync2(log2, `${error62.message}
|
|
28025
|
+
`);
|
|
28026
|
+
done({ code: null, signal: null });
|
|
28027
|
+
});
|
|
28028
|
+
});
|
|
28029
|
+
child.unref();
|
|
28030
|
+
return { pid: child.pid, exited };
|
|
28031
|
+
} finally {
|
|
28032
|
+
closeSync2(out);
|
|
28033
|
+
}
|
|
28034
|
+
},
|
|
28035
|
+
note(log2, words4) {
|
|
28036
|
+
appendFileSync2(log2, `${words4}
|
|
28037
|
+
`);
|
|
28038
|
+
},
|
|
28039
|
+
size(log2) {
|
|
28040
|
+
try {
|
|
28041
|
+
return statSync4(log2).size;
|
|
28042
|
+
} catch {
|
|
28043
|
+
return 0;
|
|
28044
|
+
}
|
|
28045
|
+
},
|
|
28046
|
+
since(log2, from) {
|
|
28047
|
+
try {
|
|
28048
|
+
const to = statSync4(log2).size;
|
|
28049
|
+
const start = Math.max(from, to - 64e3);
|
|
28050
|
+
const buffer = Buffer.alloc(Math.max(0, to - start));
|
|
28051
|
+
const fd = openSync2(log2, "r");
|
|
28052
|
+
try {
|
|
28053
|
+
readSync(fd, buffer, 0, buffer.length, start);
|
|
28054
|
+
} finally {
|
|
28055
|
+
closeSync2(fd);
|
|
28056
|
+
}
|
|
28057
|
+
return buffer.toString("utf8");
|
|
28058
|
+
} catch {
|
|
28059
|
+
return "";
|
|
28060
|
+
}
|
|
28061
|
+
},
|
|
28062
|
+
healthy: (url2) => new HermesClient({ url: url2, key: "" }, (input2, init) => fetch(input2, { ...init, signal: AbortSignal.timeout(5e3) })).healthy(),
|
|
28063
|
+
sleep: (ms) => new Promise((done) => setTimeout(done, ms)),
|
|
28064
|
+
now: () => Date.now()
|
|
28065
|
+
};
|
|
28066
|
+
}
|
|
28067
|
+
|
|
26415
28068
|
// src/keep.ts
|
|
26416
|
-
import { homedir as
|
|
26417
|
-
import { basename as
|
|
28069
|
+
import { homedir as homedir6 } from "node:os";
|
|
28070
|
+
import { basename as basename5, join as join10, resolve as resolve4 } from "node:path";
|
|
26418
28071
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
26419
28072
|
var flags = process.argv.slice(2);
|
|
26420
28073
|
var words3 = flags.filter((word) => !word.startsWith("--"));
|
|
26421
|
-
var whole = (word) =>
|
|
28074
|
+
var whole = (word) => resolve4(word.startsWith("~/") ? join10(homedir6(), word.slice(2)) : word);
|
|
26422
28075
|
var here = fileURLToPath2(new URL("..", import.meta.url)).replace(/\/$/, "");
|
|
26423
28076
|
var self = fileURLToPath2(import.meta.url);
|
|
26424
28077
|
var run = self.endsWith(".js") ? [self] : ["--import", "tsx", "src/main.ts"];
|
|
26425
|
-
var again = process.env["npm_lifecycle_event"] === "keep" ? "npm run keep --" :
|
|
28078
|
+
var again = process.env["npm_lifecycle_event"] === "keep" ? "npm run keep --" : basename5(process.argv[1] ?? "chips");
|
|
26426
28079
|
var stopLine = process.env["CHIPS_STOP_COMMAND"] || [again, ...words3, "--stop"].join(" ");
|
|
26427
|
-
var end = (
|
|
26428
|
-
(code === 0 ? process.stdout : process.stderr).write(`${
|
|
28080
|
+
var end = (line2, code) => {
|
|
28081
|
+
(code === 0 ? process.stdout : process.stderr).write(`${line2}
|
|
26429
28082
|
`);
|
|
26430
28083
|
process.exit(code);
|
|
26431
28084
|
};
|
|
@@ -26464,9 +28117,9 @@ function keepIfAsked(label2, url2, names, log2, before = () => void 0) {
|
|
|
26464
28117
|
}
|
|
26465
28118
|
|
|
26466
28119
|
// src/setup.ts
|
|
26467
|
-
import { existsSync as
|
|
26468
|
-
import { homedir as
|
|
26469
|
-
import { resolve as
|
|
28120
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
|
|
28121
|
+
import { homedir as homedir7 } from "node:os";
|
|
28122
|
+
import { join as join11, resolve as resolve5 } from "node:path";
|
|
26470
28123
|
var SetupError = class extends Error {
|
|
26471
28124
|
constructor(message, words4 = {}) {
|
|
26472
28125
|
super(message);
|
|
@@ -26475,37 +28128,70 @@ var SetupError = class extends Error {
|
|
|
26475
28128
|
words;
|
|
26476
28129
|
name = "SetupError";
|
|
26477
28130
|
};
|
|
26478
|
-
var shown = (path) => path.startsWith(`${
|
|
26479
|
-
var
|
|
28131
|
+
var shown = (path) => path.startsWith(`${homedir7()}/`) ? `~${path.slice(homedir7().length)}` : path;
|
|
28132
|
+
var portOf2 = (url2) => {
|
|
26480
28133
|
const parsed = new URL(url2);
|
|
26481
28134
|
return parsed.port || (parsed.protocol === "https:" ? "443" : "80");
|
|
26482
28135
|
};
|
|
26483
|
-
function checkedWords(config3) {
|
|
28136
|
+
function checkedWords(config3, restart2 = false) {
|
|
26484
28137
|
const urls = new Set(config3.agents.map((agent) => agent.address.url));
|
|
26485
28138
|
const [url2] = urls;
|
|
26486
|
-
|
|
28139
|
+
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`;
|
|
28140
|
+
return restart2 ? `${found}. ${RESTART}` : found;
|
|
28141
|
+
}
|
|
28142
|
+
var RESTART = "Start hermes gateway again once, so it tells Chips how full each agent's context is.";
|
|
28143
|
+
function readyHooks(config3, options) {
|
|
28144
|
+
const lines2 = [];
|
|
28145
|
+
let restart2 = false;
|
|
28146
|
+
const done = /* @__PURE__ */ new Set();
|
|
28147
|
+
for (const agent of config3.agents) {
|
|
28148
|
+
const profile = profileName(agent.address.profile);
|
|
28149
|
+
if (done.has(profile)) continue;
|
|
28150
|
+
done.add(profile);
|
|
28151
|
+
const home2 = profileHome(options.root, agent.address.profile);
|
|
28152
|
+
let outcome;
|
|
28153
|
+
try {
|
|
28154
|
+
outcome = addHook(home2, options.port, profile, options.secret);
|
|
28155
|
+
} catch (error62) {
|
|
28156
|
+
lines2.push(`Chips's webhook wasn't added to ${shown(join11(home2, "config.yaml"))}: ${error62 instanceof Error ? error62.message : String(error62)}`);
|
|
28157
|
+
continue;
|
|
28158
|
+
}
|
|
28159
|
+
if (outcome.state === "added") {
|
|
28160
|
+
lines2.push(`Added Chips's webhook to ${shown(outcome.path)}, so Hermes tells Chips how full each agent's context is.`);
|
|
28161
|
+
restart2 ||= options.gatewayUp;
|
|
28162
|
+
} else if (outcome.state === "by_hand") {
|
|
28163
|
+
lines2.push(
|
|
28164
|
+
`${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:
|
|
28165
|
+
${outcome.lines}`
|
|
28166
|
+
);
|
|
28167
|
+
} else if (outcome.state === "no_home") {
|
|
28168
|
+
lines2.push(`Hermes has no ${profile} profile at ${shown(home2)}, so no webhook was added for it.`);
|
|
28169
|
+
}
|
|
28170
|
+
}
|
|
28171
|
+
if (restart2) lines2.push(RESTART);
|
|
28172
|
+
return { lines: lines2, restart: restart2 };
|
|
26487
28173
|
}
|
|
26488
28174
|
var STILL_GOOD = "The link is still good: run the same command again once that's done.";
|
|
26489
28175
|
var EXAMPLE = '{ "agents": [{ "name": "Hermes", "profile": "default", "keyEnv": "API_SERVER_KEY" }] }';
|
|
26490
28176
|
var PLAIN = { agents: [{ name: "Hermes", profile: "default", keyEnv: "API_SERVER_KEY" }] };
|
|
26491
28177
|
function hermesEnvPath(env = process.env) {
|
|
26492
|
-
return
|
|
28178
|
+
return resolve5(env["HERMES_HOME"] ?? resolve5(homedir7(), ".hermes"), ".env");
|
|
26493
28179
|
}
|
|
26494
28180
|
function fromHermes(name2, env) {
|
|
26495
28181
|
const path = hermesEnvPath(env);
|
|
26496
|
-
if (!
|
|
26497
|
-
for (const
|
|
26498
|
-
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(
|
|
28182
|
+
if (!existsSync9(path)) return void 0;
|
|
28183
|
+
for (const line2 of readFileSync9(path, "utf8").split("\n")) {
|
|
28184
|
+
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(line2);
|
|
26499
28185
|
if (match?.[1] === name2) return match[2]?.replace(/^(["'])(.*)\1$/, "$2");
|
|
26500
28186
|
}
|
|
26501
28187
|
return void 0;
|
|
26502
28188
|
}
|
|
26503
|
-
function folderOf(
|
|
26504
|
-
if (typeof
|
|
26505
|
-
return
|
|
28189
|
+
function folderOf(said2) {
|
|
28190
|
+
if (typeof said2 !== "string" || said2 === "") return void 0;
|
|
28191
|
+
return resolve5(said2 === "~" || said2.startsWith("~/") ? homedir7() + said2.slice(1) : said2);
|
|
26506
28192
|
}
|
|
26507
28193
|
function readConfig(path, env = process.env) {
|
|
26508
|
-
const raw =
|
|
28194
|
+
const raw = existsSync9(path) ? JSON.parse(readFileSync9(path, "utf8")) : PLAIN;
|
|
26509
28195
|
const url2 = typeof raw.url === "string" ? raw.url : "http://127.0.0.1:8642";
|
|
26510
28196
|
const folder2 = folderOf(raw.folder);
|
|
26511
28197
|
if (!Array.isArray(raw.agents) || raw.agents.length === 0) {
|
|
@@ -26540,10 +28226,12 @@ function readConfig(path, env = process.env) {
|
|
|
26540
28226
|
...own2 === void 0 ? {} : { folder: own2 }
|
|
26541
28227
|
};
|
|
26542
28228
|
});
|
|
28229
|
+
const hookPort = raw.hookPort;
|
|
26543
28230
|
return {
|
|
26544
28231
|
...typeof raw.name === "string" ? { name: raw.name } : {},
|
|
26545
28232
|
agents,
|
|
26546
|
-
folders: folderOf(raw.folders) ??
|
|
28233
|
+
folders: folderOf(raw.folders) ?? resolve5(homedir7(), "chips-hermes", "agents"),
|
|
28234
|
+
...typeof hookPort === "number" && Number.isInteger(hookPort) && hookPort > 0 && hookPort < 65536 ? { hookPort } : {}
|
|
26547
28235
|
};
|
|
26548
28236
|
}
|
|
26549
28237
|
async function checkHermes(config3, clientFor = (agent) => new HermesClient(agent.address)) {
|
|
@@ -26553,7 +28241,7 @@ async function checkHermes(config3, clientFor = (agent) => new HermesClient(agen
|
|
|
26553
28241
|
throw new SetupError(
|
|
26554
28242
|
`Hermes isn't answering for ${agent.name} at ${agent.address.url}: start its API server (API_SERVER_ENABLED=true, then hermes gateway). ${STILL_GOOD}`,
|
|
26555
28243
|
{
|
|
26556
|
-
detail: `Nothing answers on port ${
|
|
28244
|
+
detail: `Nothing answers on port ${portOf2(agent.address.url)}`,
|
|
26557
28245
|
says: "Hermes's API server is off.",
|
|
26558
28246
|
doing: `Add this line to ${shown(hermesEnvPath())}, then start Hermes with hermes gateway.`,
|
|
26559
28247
|
copy: "API_SERVER_ENABLED=true"
|
|
@@ -26574,19 +28262,46 @@ async function checkHermes(config3, clientFor = (agent) => new HermesClient(agen
|
|
|
26574
28262
|
}
|
|
26575
28263
|
|
|
26576
28264
|
// src/main.ts
|
|
26577
|
-
var home = (path) => path.startsWith("~/") ?
|
|
28265
|
+
var home = (path) => path.startsWith("~/") ? join12(homedir8(), path.slice(2)) : path;
|
|
26578
28266
|
var link = process.env["CHIPS_JOIN_LINK"] || void 0;
|
|
26579
28267
|
var LABEL = "chat.chips.hermes";
|
|
26580
28268
|
stopIfAsked(LABEL);
|
|
26581
|
-
var configPath =
|
|
28269
|
+
var configPath = resolve6(home(words3[0] ?? "~/.chips-hermes/config.json"));
|
|
28270
|
+
var folder = dirname6(configPath);
|
|
28271
|
+
var statePath = join12(folder, "sessions.json");
|
|
28272
|
+
var state = existsSync10(statePath) ? JSON.parse(readFileSync10(statePath, "utf8")) : { sessions: {} };
|
|
28273
|
+
var saveState = (next) => {
|
|
28274
|
+
mkdirSync6(folder, { recursive: true, mode: 448 });
|
|
28275
|
+
writeFileSync5(statePath, `${JSON.stringify(next, null, 2)}
|
|
28276
|
+
`, { mode: 384 });
|
|
28277
|
+
};
|
|
28278
|
+
var log = (line2) => process.stdout.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${line2}
|
|
28279
|
+
`);
|
|
28280
|
+
var freePort = () => new Promise((done, failed) => {
|
|
28281
|
+
const server = createServer2();
|
|
28282
|
+
server.once("error", failed);
|
|
28283
|
+
server.listen(0, "127.0.0.1", () => {
|
|
28284
|
+
const address = server.address();
|
|
28285
|
+
server.close(() => done(typeof address === "object" && address !== null ? address.port : 0));
|
|
28286
|
+
});
|
|
28287
|
+
});
|
|
26582
28288
|
var config2;
|
|
28289
|
+
var root = hermesRoot();
|
|
28290
|
+
var fills = state.fills ??= {};
|
|
26583
28291
|
try {
|
|
26584
28292
|
config2 = readConfig(configPath);
|
|
26585
28293
|
if (link !== void 0) {
|
|
26586
28294
|
reportStep("checking", { detail: "Looking for its API server" });
|
|
26587
28295
|
await checkHermes(config2);
|
|
26588
|
-
reportStep("checked", { detail: checkedWords(config2) });
|
|
26589
28296
|
}
|
|
28297
|
+
fills.secret ??= randomBytes2(32).toString("hex");
|
|
28298
|
+
fills.port = config2.hookPort ?? fills.port ?? hookPortIn(profileHome(root, config2.agents[0]?.address.profile)) ?? await freePort();
|
|
28299
|
+
saveState(state);
|
|
28300
|
+
const first = config2.agents[0];
|
|
28301
|
+
const gatewayUp = link !== void 0 || first !== void 0 && await new HermesClient(first.address).healthy();
|
|
28302
|
+
const hooks = readyHooks(config2, { root, port: fills.port, secret: fills.secret, gatewayUp });
|
|
28303
|
+
for (const line2 of hooks.lines) log(line2);
|
|
28304
|
+
if (link !== void 0) reportStep("checked", { detail: checkedWords(config2, hooks.restart) });
|
|
26590
28305
|
} catch (error62) {
|
|
26591
28306
|
if (!(error62 instanceof SetupError)) throw error62;
|
|
26592
28307
|
reportStep("failed", error62.words);
|
|
@@ -26595,10 +28310,15 @@ try {
|
|
|
26595
28310
|
process.exit(1);
|
|
26596
28311
|
}
|
|
26597
28312
|
var name = config2.name ?? "Hermes";
|
|
26598
|
-
var
|
|
26599
|
-
|
|
26600
|
-
|
|
26601
|
-
|
|
28313
|
+
var gateways2 = new Gateways({
|
|
28314
|
+
gateways: gatewaysOf(config2.agents),
|
|
28315
|
+
root,
|
|
28316
|
+
env: process.env,
|
|
28317
|
+
system: thisMachine(),
|
|
28318
|
+
log: (line2) => log(`gateway: ${line2}`),
|
|
28319
|
+
logFile: gatewayLog(folder)
|
|
28320
|
+
});
|
|
28321
|
+
if (link === void 0) await gateways2.startIfDown();
|
|
26602
28322
|
var refused2 = (error62) => {
|
|
26603
28323
|
if (!(error62 instanceof JoinError)) throw error62;
|
|
26604
28324
|
process.stderr.write(`${error62.message}
|
|
@@ -26614,25 +28334,37 @@ var joined = await join3({
|
|
|
26614
28334
|
onCode: (code) => log(`Allow this harness in Chips: ${code}`)
|
|
26615
28335
|
}).catch(refused2);
|
|
26616
28336
|
keepIfAsked(LABEL, joined.url, ["HERMES_HOME", ...config2.agents.map((agent) => agent.keyEnv ?? "API_SERVER_KEY")], log);
|
|
26617
|
-
var
|
|
28337
|
+
var bridge;
|
|
28338
|
+
var restart = restarter({
|
|
28339
|
+
restart: () => gateways2.restart(),
|
|
28340
|
+
hold: (held) => bridge?.hold(held),
|
|
28341
|
+
say: async (trouble) => {
|
|
28342
|
+
if (bridge !== void 0) return bridge.restarted(trouble);
|
|
28343
|
+
await (trouble === void 0 ? chips.reportOk() : chips.reportTrouble(trouble));
|
|
28344
|
+
},
|
|
28345
|
+
log: (line2) => log(`gateway: ${line2}`)
|
|
28346
|
+
});
|
|
26618
28347
|
var chips = await connect({
|
|
26619
28348
|
url: joined.url,
|
|
26620
28349
|
token: joined.token,
|
|
26621
28350
|
client: { name: "chips-hermes", version: "1.0.0" },
|
|
26622
|
-
store: fileStore(
|
|
28351
|
+
store: fileStore(join12(folder, "chips-state.json")),
|
|
28352
|
+
onRestart: restart,
|
|
28353
|
+
settings: settingsFor(() => bridge)
|
|
26623
28354
|
});
|
|
26624
28355
|
var git = await checkGit();
|
|
26625
28356
|
if (!git.ok) log(`rooms' projects are off: ${git.why}`);
|
|
26626
|
-
|
|
28357
|
+
bridge = new Bridge({
|
|
26627
28358
|
folders: config2.folders,
|
|
26628
28359
|
projects: git.ok,
|
|
26629
28360
|
chips,
|
|
26630
28361
|
agents: config2.agents,
|
|
26631
28362
|
home: joined.spaceId,
|
|
26632
28363
|
state,
|
|
26633
|
-
saveState
|
|
26634
|
-
|
|
26635
|
-
|
|
28364
|
+
saveState,
|
|
28365
|
+
log,
|
|
28366
|
+
hermesRoot: root,
|
|
28367
|
+
fills: { port: fills.port, secret: fills.secret }
|
|
26636
28368
|
});
|
|
26637
28369
|
await bridge.start();
|
|
26638
28370
|
log(`answering for ${config2.agents.map((agent) => agent.name).join(", ")} through Hermes`);
|
|
@@ -26646,7 +28378,7 @@ await helloOnce(joined, {
|
|
|
26646
28378
|
}).catch((error62) => log(`hello not said: ${String(error62)}`));
|
|
26647
28379
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
26648
28380
|
process.on(signal, () => {
|
|
26649
|
-
bridge
|
|
28381
|
+
bridge?.stop();
|
|
26650
28382
|
chips.close();
|
|
26651
28383
|
process.exit(0);
|
|
26652
28384
|
});
|