@chipschat/hermes 1.0.3 → 1.0.5
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 +29 -4
- package/dist/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/main.js +1397 -169
- 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 existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
|
|
13
|
+
import { createServer as createServer2 } from "node:net";
|
|
14
|
+
import { homedir as homedir7 } from "node:os";
|
|
15
|
+
import { dirname as dirname5, join as join11, resolve as resolve5 } 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)?)??$/;
|
|
@@ -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 },
|
|
@@ -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, resolve6) {
|
|
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, resolve6);
|
|
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, resolve6) : 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 ?? (resolve6 ? 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;
|
|
@@ -20392,7 +20394,7 @@ var serverFacts = external_exports.object({
|
|
|
20392
20394
|
// node_modules/@chipschat/protocol/dist/src/room-files.js
|
|
20393
20395
|
var ROOM_FILES_IN_CONTEXT = 20;
|
|
20394
20396
|
var ROOM_FILES_CEILING = 200;
|
|
20395
|
-
var ROOM_FILES_HEADING = "Files in this
|
|
20397
|
+
var ROOM_FILES_HEADING = "Files in this space:";
|
|
20396
20398
|
var roomFile = external_exports.object({
|
|
20397
20399
|
name: external_exports.string().min(1).max(255).describe("The file's name, which its versions share."),
|
|
20398
20400
|
version: external_exports.int().positive().describe("Which version this is: 1 for the first, counting each change of its bytes."),
|
|
@@ -20483,7 +20485,7 @@ var MONTHS = [
|
|
|
20483
20485
|
|
|
20484
20486
|
// node_modules/@chipschat/protocol/dist/src/context.js
|
|
20485
20487
|
var CONTEXT_CEILING = { lines: 200, chars: 32e3 };
|
|
20486
|
-
var CONTEXT_HEADING = "Said in this
|
|
20488
|
+
var CONTEXT_HEADING = "Said in this space since you last spoke:";
|
|
20487
20489
|
var contextBudget = external_exports.object({
|
|
20488
20490
|
lines: external_exports.int().nonnegative().optional().describe("The most lines to be handed, 20 when absent. Chips hands over 200 at most, and none when this is 0."),
|
|
20489
20491
|
chars: external_exports.int().nonnegative().optional().describe("The most characters those lines may run to, as `Name: words` one per line, 4,000 when absent. Chips hands over 32,000 at most.")
|
|
@@ -20551,24 +20553,24 @@ function renderLine(line) {
|
|
|
20551
20553
|
case "said":
|
|
20552
20554
|
return `${withRole(name2, line.from)}: ${wordsOf(line)}`;
|
|
20553
20555
|
case "joined":
|
|
20554
|
-
return `(${name2} joined the
|
|
20556
|
+
return `(${name2} joined the space)`;
|
|
20555
20557
|
case "left":
|
|
20556
|
-
return `(${name2} left the
|
|
20558
|
+
return `(${name2} left the space)`;
|
|
20557
20559
|
case "renamed":
|
|
20558
20560
|
if (line.title !== void 0)
|
|
20559
|
-
return `(${name2} renamed the
|
|
20561
|
+
return `(${name2} renamed the space "${line.title}")`;
|
|
20560
20562
|
return `(${line.previous ?? name2} is now called ${line.name ?? name2})`;
|
|
20561
20563
|
case "archived":
|
|
20562
|
-
return `(${name2} archived the
|
|
20564
|
+
return `(${name2} archived the space)`;
|
|
20563
20565
|
default:
|
|
20564
|
-
return `(${name2} unarchived the
|
|
20566
|
+
return `(${name2} unarchived the space)`;
|
|
20565
20567
|
}
|
|
20566
20568
|
}
|
|
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 ?? "";
|
|
@@ -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.");
|
|
@@ -21179,7 +21184,7 @@ function namesInWords(names) {
|
|
|
21179
21184
|
function renderProjectNote(facts, options) {
|
|
21180
21185
|
const files = `${facts.files} file${facts.files === 1 ? "" : "s"}`;
|
|
21181
21186
|
const lines2 = [
|
|
21182
|
-
`The
|
|
21187
|
+
`The space's project is a git repository. Your copy is ${facts.folder} (${files}), ${facts.trouble === void 0 ? "brought up to date just now" : "not brought up to date"}.`
|
|
21183
21188
|
];
|
|
21184
21189
|
if (facts.changed.length > 0) {
|
|
21185
21190
|
const by = facts.by.length > 0 ? `, by ${namesInWords(facts.by)}` : "";
|
|
@@ -21189,7 +21194,7 @@ function renderProjectNote(facts, options) {
|
|
|
21189
21194
|
lines2.push(`It couldn't be: ${facts.trouble}`);
|
|
21190
21195
|
if (facts.clashes.length > 0) {
|
|
21191
21196
|
const whose = facts.clashedWith.length > 0 ? `${namesInWords(facts.clashedWith)}'s` : "another change";
|
|
21192
|
-
lines2.push(`Your last change to ${namesInWords(facts.clashes)} clashed with ${whose}, which reached the
|
|
21197
|
+
lines2.push(`Your last change to ${namesInWords(facts.clashes)} clashed with ${whose}, which reached the space first. Both sides are in those files now: yours between "<<<<<<< yours" and "=======", theirs from there to the ">>>>>>>" line that names them. Settle each, keeping what both meant, and take the marks out. Nothing of yours is sent until they're gone.`);
|
|
21193
21198
|
}
|
|
21194
21199
|
for (const key of facts.keys) {
|
|
21195
21200
|
lines2.push(`${key.path} holds what looks like ${key.what}, so your last change wasn't sent: take it out and read it from outside the project.`);
|
|
@@ -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",
|
|
@@ -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((resolve6, reject) => {
|
|
22850
|
+
waiting.set(key, { event, resolve: resolve6, 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((resolve6) => setTimeout(resolve6, 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,7 +22949,7 @@ 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((line) => line !== "");
|
|
22865
22953
|
var unique = (names) => [...new Set(names)];
|
|
22866
22954
|
function subjectOf(words4) {
|
|
22867
22955
|
const first = words4.split("\n").find((line) => line.trim() !== "")?.trim() ?? "";
|
|
@@ -22870,11 +22958,11 @@ function subjectOf(words4) {
|
|
|
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((line) => line.startsWith("remote: ")).map((line) => line.slice("remote: ".length).trim()).filter((line) => line !== "" && !/^error: hook declined/.test(line));
|
|
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) {
|
|
@@ -22904,28 +22992,28 @@ function heldNote(pushed, name2) {
|
|
|
22904
22992
|
return void 0;
|
|
22905
22993
|
case "clash": {
|
|
22906
22994
|
const whose = pushed.with.length === 0 ? "another change" : `${namesInWords(pushed.with)}'s change`;
|
|
22907
|
-
return `(Not in the
|
|
22995
|
+
return `(Not in the space's project yet: ${namesInWords(pushed.files)} clashed with ${whose}. ${name2} is asked to settle it next turn.)`;
|
|
22908
22996
|
}
|
|
22909
22997
|
case "keys": {
|
|
22910
22998
|
const [first] = pushed.keys;
|
|
22911
22999
|
const held = pushed.keys.length === 1 && first !== void 0 ? `${first.path} looks like it holds ${first.what.replace(/, by its name$/, "")}` : `${namesInWords(pushed.keys.map((key) => key.path))} look like they hold keys`;
|
|
22912
|
-
return `(Not in the
|
|
23000
|
+
return `(Not in the space's project: ${held}, so nothing was sent.)`;
|
|
22913
23001
|
}
|
|
22914
23002
|
case "refused":
|
|
22915
|
-
return `(Not in the
|
|
23003
|
+
return `(Not in the space's project: it was turned back: ${stopped(pushed.why)})`;
|
|
22916
23004
|
case "failed":
|
|
22917
|
-
return `(Not in the
|
|
23005
|
+
return `(Not in the space's project: it couldn't be sent: ${stopped(pushed.why)})`;
|
|
22918
23006
|
}
|
|
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((resolve6) => {
|
|
22923
23011
|
execFile(git2, ["--version"], { env: { PATH: path } }, (error62, stdout) => {
|
|
22924
23012
|
if (error62 !== null)
|
|
22925
|
-
return
|
|
23013
|
+
return resolve6({ 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
|
+
resolve6(enough ? { ok: true, version: mine.join(".") } : { ok: false, why: `${stdout.trim()} is older than ${PROJECT_GIT_VERSION}` });
|
|
22929
23017
|
});
|
|
22930
23018
|
});
|
|
22931
23019
|
}
|
|
@@ -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}`)]);
|
|
@@ -23232,7 +23320,7 @@ ${sent.err}`;
|
|
|
23232
23320
|
const clashes = lines((await this.git(["diff", "--name-only", "--diff-filter=U"])).out);
|
|
23233
23321
|
if (clashes.length === 0)
|
|
23234
23322
|
return merged.ok ? {} : { trouble: remoteWords(merged.err) };
|
|
23235
|
-
const label2 = theirs.length === 1 ? theirs[0] ?? "the
|
|
23323
|
+
const label2 = theirs.length === 1 ? theirs[0] ?? "the space" : "the space";
|
|
23236
23324
|
for (const path of clashes)
|
|
23237
23325
|
await this.label(path, onto, label2);
|
|
23238
23326
|
return { clashes, clashedWith: 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((resolve6) => {
|
|
23463
|
+
execFile(options.git ?? "git", [...args], { cwd: this.copy, env, maxBuffer: 64 * 1024 * 1024 }, (error62, stdout, stderr) => resolve6({ 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((resolve6) => {
|
|
23487
|
+
this.settle = resolve6;
|
|
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((resolve6) => setTimeout(resolve6, 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((resolve6) => {
|
|
24415
|
+
this.waiters.add({ match, resolve: resolve6 });
|
|
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((resolve6, 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, resolve6, 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, resolve6, 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(resolve6);
|
|
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((resolve6) => {
|
|
24488
24576
|
const timer = setTimeout(() => {
|
|
24489
24577
|
this.pongs.delete(ts);
|
|
24490
|
-
|
|
24578
|
+
resolve6();
|
|
24491
24579
|
}, 2e3);
|
|
24492
24580
|
this.pongs.set(ts, () => {
|
|
24493
24581
|
clearTimeout(timer);
|
|
24494
24582
|
this.pongs.delete(ts);
|
|
24495
|
-
|
|
24583
|
+
resolve6();
|
|
24496
24584
|
});
|
|
24497
24585
|
socket.send(JSON.stringify({ frame: "ping", ts }));
|
|
24498
24586
|
});
|
|
@@ -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((resolve6, reject) => {
|
|
25048
|
+
const timer = setTimeout(resolve6, ms);
|
|
24961
25049
|
signal?.addEventListener("abort", () => {
|
|
24962
25050
|
clearTimeout(timer);
|
|
24963
25051
|
reject(new JoinError("stopped", "Stopped before a person decided."));
|
|
@@ -25073,8 +25161,8 @@ import { homedir as homedir2 } from "node:os";
|
|
|
25073
25161
|
import { basename as basename2, isAbsolute, resolve, sep } from "node:path";
|
|
25074
25162
|
import { fileURLToPath } from "node:url";
|
|
25075
25163
|
function sendingFiles(options = {}) {
|
|
25076
|
-
const which = options.project === true ? "A file that isn't in the
|
|
25077
|
-
return `${which} reaches this
|
|
25164
|
+
const which = options.project === true ? "A file that isn't in the space's project" : "A file";
|
|
25165
|
+
return `${which} reaches this space only when you send it: put MEDIA:<path> on a line of its own in your answer, one line for each file, from your own folder. A path, or a link to this machine, reaches nobody: the people and agents here cannot open your folder.`;
|
|
25078
25166
|
}
|
|
25079
25167
|
var SENDING_FILES = sendingFiles();
|
|
25080
25168
|
var MOST_FILES = 20;
|
|
@@ -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,810 @@ 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 line of readFileSync5(path, "utf8").split("\n")) {
|
|
25600
|
+
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(line);
|
|
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(line) {
|
|
25617
|
+
const indent = line.length - line.trimStart().length;
|
|
25618
|
+
let quote;
|
|
25619
|
+
for (let at = indent; at < line.length; at += 1) {
|
|
25620
|
+
const char = line[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(line[at - 1] ?? ""))) {
|
|
25626
|
+
return { indent, text: line.slice(indent, at).trimEnd() };
|
|
25627
|
+
}
|
|
25628
|
+
}
|
|
25629
|
+
return { indent, text: line.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: line } = uncommented(lines2[at] ?? "");
|
|
25652
|
+
if (indent !== 0 || !line.startsWith(`${section}:`)) continue;
|
|
25653
|
+
const rest = line.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
|
+
if (pid === void 0) return void 0;
|
|
25726
|
+
try {
|
|
25727
|
+
if (process.platform === "linux") return readlinkSync(`/proc/${pid}/cwd`);
|
|
25728
|
+
const out = execFileSync3("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
|
|
25729
|
+
encoding: "utf8",
|
|
25730
|
+
timeout: 3e3,
|
|
25731
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
25732
|
+
});
|
|
25733
|
+
const line = out.split("\n").find((each) => each.startsWith("n"));
|
|
25734
|
+
return line === void 0 ? void 0 : line.slice(1);
|
|
25735
|
+
} catch {
|
|
25736
|
+
return void 0;
|
|
25737
|
+
}
|
|
25738
|
+
}
|
|
25739
|
+
|
|
25740
|
+
// src/context.ts
|
|
25741
|
+
var FILES_SHOWN = 8;
|
|
25742
|
+
var FILE_BYTES = 65536;
|
|
25743
|
+
var READ_NOTE = "Hermes reads these when a conversation starts. A space already talking picks up a change after it next compacts.";
|
|
25744
|
+
var ENTRY_DELIMITER = "\n\xA7\n";
|
|
25745
|
+
var MEMORY_LIMIT = 2200;
|
|
25746
|
+
var USER_LIMIT = 1375;
|
|
25747
|
+
var PROJECT_FILES = [
|
|
25748
|
+
".hermes.md",
|
|
25749
|
+
"HERMES.md",
|
|
25750
|
+
"AGENTS.override.md",
|
|
25751
|
+
"AGENTS.md",
|
|
25752
|
+
"agents.md",
|
|
25753
|
+
"CLAUDE.md",
|
|
25754
|
+
"claude.md",
|
|
25755
|
+
".cursorrules"
|
|
25756
|
+
];
|
|
25757
|
+
var ROWS = {
|
|
25758
|
+
soul: { title: "Who it is", role: "identity" },
|
|
25759
|
+
project: { title: "Project instructions", role: "instructions" },
|
|
25760
|
+
user: { title: "About you", role: "about_you" },
|
|
25761
|
+
memory: { title: "Memory", role: "memory" }
|
|
25762
|
+
};
|
|
25763
|
+
var ORDER = ["soul", "project", "user", "memory"];
|
|
25764
|
+
function readText(path) {
|
|
25765
|
+
try {
|
|
25766
|
+
return readFileSync6(path, "utf8").replace(/^/, "");
|
|
25767
|
+
} catch {
|
|
25768
|
+
return "";
|
|
25769
|
+
}
|
|
25770
|
+
}
|
|
25771
|
+
function hashOf(text) {
|
|
25772
|
+
return createHash2("sha256").update(text, "utf8").digest("hex");
|
|
25773
|
+
}
|
|
25774
|
+
function length(text) {
|
|
25775
|
+
let count = 0;
|
|
25776
|
+
for (const _ of text) count += 1;
|
|
25777
|
+
return count;
|
|
25778
|
+
}
|
|
25779
|
+
var said = (count) => count.toLocaleString("en-US");
|
|
25780
|
+
function entriesOf(text) {
|
|
25781
|
+
return [...new Set(text.split(ENTRY_DELIMITER).map((each) => each.trim()).filter((each) => each !== ""))];
|
|
25782
|
+
}
|
|
25783
|
+
function memoryProblem(text, limit, what) {
|
|
25784
|
+
const total = length(entriesOf(text).join(ENTRY_DELIMITER));
|
|
25785
|
+
if (total > limit) {
|
|
25786
|
+
return `That is ${said(total)} characters, and Hermes keeps ${what} to ${said(limit)}. Take out ${said(total - limit)}.`;
|
|
25787
|
+
}
|
|
25788
|
+
return void 0;
|
|
25789
|
+
}
|
|
25790
|
+
function memorySettings(home2) {
|
|
25791
|
+
const text = configText(home2);
|
|
25792
|
+
const value = (key) => yamlValue(text, "memory", key);
|
|
25793
|
+
return {
|
|
25794
|
+
...yamlBool(value("memory_enabled")) === false ? {} : { memory: yamlNumber(value("memory_char_limit")) ?? MEMORY_LIMIT },
|
|
25795
|
+
...yamlBool(value("user_profile_enabled")) === false ? {} : { user: yamlNumber(value("user_char_limit")) ?? USER_LIMIT }
|
|
25796
|
+
};
|
|
25797
|
+
}
|
|
25798
|
+
function sourcesOf(profile, env = process.env) {
|
|
25799
|
+
const { name: name2, home: home2 } = profile;
|
|
25800
|
+
const found = [];
|
|
25801
|
+
const soul = join6(home2, "SOUL.md");
|
|
25802
|
+
if (existsSync6(soul)) found.push({ path: soul, kind: "soul", file: "SOUL.md", profile: name2 });
|
|
25803
|
+
const folder2 = workFolder(home2, env);
|
|
25804
|
+
const project = folder2 === void 0 ? void 0 : PROJECT_FILES.find((file3) => readText(join6(folder2, file3)).trim() !== "");
|
|
25805
|
+
if (folder2 !== void 0 && project !== void 0) {
|
|
25806
|
+
found.push({ path: join6(folder2, project), kind: "project", file: project, profile: name2 });
|
|
25807
|
+
}
|
|
25808
|
+
const memory = memorySettings(home2);
|
|
25809
|
+
if (memory.user !== void 0) {
|
|
25810
|
+
found.push({ path: join6(home2, "memories", "USER.md"), kind: "user", file: "USER.md", profile: name2, limit: memory.user });
|
|
25811
|
+
}
|
|
25812
|
+
if (memory.memory !== void 0) {
|
|
25813
|
+
found.push({ path: join6(home2, "memories", "MEMORY.md"), kind: "memory", file: "MEMORY.md", profile: name2, limit: memory.memory });
|
|
25814
|
+
}
|
|
25815
|
+
return found;
|
|
25816
|
+
}
|
|
25817
|
+
function aboutOf(source2) {
|
|
25818
|
+
if (source2.limit === void 0) return void 0;
|
|
25819
|
+
const what = source2.kind === "user" ? "What it knows of you" : "Its notes";
|
|
25820
|
+
return `${what}, up to ${said(source2.limit)} characters. A line with only \xA7 starts the next entry.`;
|
|
25821
|
+
}
|
|
25822
|
+
function fileOf(source2, name2) {
|
|
25823
|
+
const text = readText(source2.path);
|
|
25824
|
+
const about = aboutOf(source2);
|
|
25825
|
+
return {
|
|
25826
|
+
tooLarge: Buffer.byteLength(text, "utf8") > FILE_BYTES,
|
|
25827
|
+
file: {
|
|
25828
|
+
name: name2,
|
|
25829
|
+
title: ROWS[source2.kind].title,
|
|
25830
|
+
role: ROWS[source2.kind].role,
|
|
25831
|
+
...about === void 0 ? {} : { about },
|
|
25832
|
+
text,
|
|
25833
|
+
hash: hashOf(text),
|
|
25834
|
+
editable: true
|
|
25835
|
+
}
|
|
25836
|
+
};
|
|
25837
|
+
}
|
|
25838
|
+
var byOrder = (a, b) => ORDER.indexOf(a.kind) - ORDER.indexOf(b.kind) || a.profile.localeCompare(b.profile);
|
|
25839
|
+
var Picture = class {
|
|
25840
|
+
/** Each file by its path, and the agents that read it. */
|
|
25841
|
+
readers = /* @__PURE__ */ new Map();
|
|
25842
|
+
/** The shared files, each with its id and the name the set shows. */
|
|
25843
|
+
shared = /* @__PURE__ */ new Map();
|
|
25844
|
+
/** The agents and the files each reads, in page order. */
|
|
25845
|
+
agents = /* @__PURE__ */ new Map();
|
|
25846
|
+
constructor(readers, first, env = process.env) {
|
|
25847
|
+
const byProfile = /* @__PURE__ */ new Map();
|
|
25848
|
+
const sourcesFor = (profile) => {
|
|
25849
|
+
let sources = byProfile.get(profile.home);
|
|
25850
|
+
if (sources === void 0) {
|
|
25851
|
+
sources = sourcesOf(profile, env);
|
|
25852
|
+
byProfile.set(profile.home, sources);
|
|
25853
|
+
}
|
|
25854
|
+
return sources;
|
|
25855
|
+
};
|
|
25856
|
+
if (first !== void 0) for (const source2 of sourcesFor(first)) this.readers.set(source2.path, { source: source2, agents: [] });
|
|
25857
|
+
for (const reader of readers) {
|
|
25858
|
+
const sources = sourcesFor(reader.profile);
|
|
25859
|
+
this.agents.set(reader.agentId, [...sources].sort(byOrder));
|
|
25860
|
+
for (const source2 of sources) {
|
|
25861
|
+
const entry = this.readers.get(source2.path) ?? { source: source2, agents: [] };
|
|
25862
|
+
if (!entry.agents.includes(reader.agentId)) entry.agents.push(reader.agentId);
|
|
25863
|
+
this.readers.set(source2.path, entry);
|
|
25864
|
+
}
|
|
25865
|
+
}
|
|
25866
|
+
const taken = /* @__PURE__ */ new Set();
|
|
25867
|
+
const ids2 = /* @__PURE__ */ new Set();
|
|
25868
|
+
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));
|
|
25869
|
+
for (const { source: source2 } of sharing) {
|
|
25870
|
+
const name2 = taken.has(source2.file) ? `${source2.file} (${source2.profile})` : source2.file;
|
|
25871
|
+
taken.add(name2);
|
|
25872
|
+
let id2 = `${source2.profile}-${source2.kind}`.slice(0, 60);
|
|
25873
|
+
for (let count = 2; ids2.has(id2); count += 1) id2 = `${source2.profile}-${source2.kind}-${count}`.slice(0, 64);
|
|
25874
|
+
ids2.add(id2);
|
|
25875
|
+
this.shared.set(source2.path, { id: id2, name: name2 });
|
|
25876
|
+
}
|
|
25877
|
+
}
|
|
25878
|
+
/** The harness's own set, as `PUT /v1/harnesses/:id/context` takes it. */
|
|
25879
|
+
harnessCopy(first) {
|
|
25880
|
+
const shown2 = [];
|
|
25881
|
+
const notShown = [];
|
|
25882
|
+
for (const [path, { id: id2, name: name2 }] of this.shared) {
|
|
25883
|
+
const entry = this.readers.get(path);
|
|
25884
|
+
if (entry === void 0) continue;
|
|
25885
|
+
const { source: source2, agents } = entry;
|
|
25886
|
+
const { file: file3, tooLarge } = fileOf(source2, name2);
|
|
25887
|
+
if (tooLarge || shown2.length >= FILES_SHOWN) {
|
|
25888
|
+
notShown.push({ name: name2, title: ROWS[source2.kind].title, reason: tooLarge ? "too_large" : "count" });
|
|
25889
|
+
continue;
|
|
25890
|
+
}
|
|
25891
|
+
shown2.push({
|
|
25892
|
+
...file3,
|
|
25893
|
+
id: id2,
|
|
25894
|
+
used_by: agents.length,
|
|
25895
|
+
// A new agent runs on the first profile: it reads that profile's files and its project file.
|
|
25896
|
+
...source2.profile === first?.name ? { for_new_agents: true } : {}
|
|
25897
|
+
});
|
|
25898
|
+
}
|
|
25899
|
+
return {
|
|
25900
|
+
shared: shown2,
|
|
25901
|
+
...notShown.length === 0 ? {} : { not_shown: notShown }
|
|
25902
|
+
};
|
|
25903
|
+
}
|
|
25904
|
+
/** One agent's files, as `PUT /v1/agents/:id/context` takes them: its own, and the shared it reads. */
|
|
25905
|
+
agentCopy(agentId2) {
|
|
25906
|
+
const sources = this.agents.get(agentId2);
|
|
25907
|
+
if (sources === void 0) return void 0;
|
|
25908
|
+
const files = [];
|
|
25909
|
+
const notShown = [];
|
|
25910
|
+
for (const source2 of sources) {
|
|
25911
|
+
const shared = this.shared.get(source2.path);
|
|
25912
|
+
if (files.length >= FILES_SHOWN) {
|
|
25913
|
+
notShown.push({ name: shared?.name ?? source2.file, title: ROWS[source2.kind].title, reason: "count" });
|
|
25914
|
+
continue;
|
|
25915
|
+
}
|
|
25916
|
+
if (shared !== void 0) {
|
|
25917
|
+
files.push({
|
|
25918
|
+
name: shared.name,
|
|
25919
|
+
title: ROWS[source2.kind].title,
|
|
25920
|
+
role: ROWS[source2.kind].role,
|
|
25921
|
+
editable: true,
|
|
25922
|
+
shared: shared.id,
|
|
25923
|
+
replaceable: false
|
|
25924
|
+
});
|
|
25925
|
+
continue;
|
|
25926
|
+
}
|
|
25927
|
+
const { file: file3, tooLarge } = fileOf(source2, source2.file);
|
|
25928
|
+
if (tooLarge) notShown.push({ name: source2.file, title: ROWS[source2.kind].title, reason: "too_large" });
|
|
25929
|
+
else files.push(file3);
|
|
25930
|
+
}
|
|
25931
|
+
return {
|
|
25932
|
+
files,
|
|
25933
|
+
...notShown.length === 0 ? {} : { not_shown: notShown },
|
|
25934
|
+
read_note: READ_NOTE
|
|
25935
|
+
};
|
|
25936
|
+
}
|
|
25937
|
+
/** The file an edit names: an agent's own, or one of the harness's set; with whether it is shared. */
|
|
25938
|
+
find(agentId2, name2) {
|
|
25939
|
+
if (agentId2 === void 0) {
|
|
25940
|
+
for (const [path, { name: shown2 }] of this.shared) {
|
|
25941
|
+
const source2 = this.readers.get(path)?.source;
|
|
25942
|
+
if (shown2 === name2 && source2 !== void 0) return { source: source2, shared: true };
|
|
25943
|
+
}
|
|
25944
|
+
return void 0;
|
|
25945
|
+
}
|
|
25946
|
+
for (const source2 of this.agents.get(agentId2) ?? []) {
|
|
25947
|
+
const shared = this.shared.get(source2.path);
|
|
25948
|
+
if ((shared?.name ?? source2.file) === name2) return { source: source2, shared: shared !== void 0 };
|
|
25949
|
+
}
|
|
25950
|
+
return void 0;
|
|
25951
|
+
}
|
|
25952
|
+
};
|
|
25953
|
+
var LOCK_WAIT_MS = 1e4;
|
|
25954
|
+
var LOCKER = [
|
|
25955
|
+
"use Fcntl qw(:flock O_RDWR O_CREAT O_NOFOLLOW);",
|
|
25956
|
+
'sysopen(my $fh, $ARGV[0], O_RDWR|O_CREAT|O_NOFOLLOW, 0600) or die "open: $!\\n";',
|
|
25957
|
+
"chmod 0600, $fh;",
|
|
25958
|
+
'flock($fh, LOCK_EX) or die "flock: $!\\n";',
|
|
25959
|
+
'$| = 1; print "locked\\n";',
|
|
25960
|
+
"while (<STDIN>) {}",
|
|
25961
|
+
"flock($fh, LOCK_UN);"
|
|
25962
|
+
].join(" ");
|
|
25963
|
+
async function withLock(lockPath, work, waitMs = LOCK_WAIT_MS) {
|
|
25964
|
+
const child = spawn("perl", ["-e", LOCKER, lockPath], { stdio: ["pipe", "pipe", "pipe"] });
|
|
25965
|
+
const exited = new Promise((done) => {
|
|
25966
|
+
child.on("close", () => done());
|
|
25967
|
+
child.on("error", () => done());
|
|
25968
|
+
});
|
|
25969
|
+
try {
|
|
25970
|
+
await new Promise((locked, failed) => {
|
|
25971
|
+
const timer = setTimeout(() => failed(new Error("Hermes is writing its memory right now. Try again in a moment.")), waitMs);
|
|
25972
|
+
let out = "";
|
|
25973
|
+
child.stdout.on("data", (chunk) => {
|
|
25974
|
+
out += chunk.toString("utf8");
|
|
25975
|
+
if (out.includes("locked\n")) {
|
|
25976
|
+
clearTimeout(timer);
|
|
25977
|
+
locked();
|
|
25978
|
+
}
|
|
25979
|
+
});
|
|
25980
|
+
child.on("error", () => {
|
|
25981
|
+
clearTimeout(timer);
|
|
25982
|
+
failed(new Error("The bridge couldn't lock Hermes's memory file, so it saved nothing: this machine has no perl."));
|
|
25983
|
+
});
|
|
25984
|
+
child.on("close", () => {
|
|
25985
|
+
clearTimeout(timer);
|
|
25986
|
+
failed(new Error("The bridge couldn't lock Hermes's memory file, so it saved nothing."));
|
|
25987
|
+
});
|
|
25988
|
+
});
|
|
25989
|
+
return await work();
|
|
25990
|
+
} finally {
|
|
25991
|
+
child.stdin.end();
|
|
25992
|
+
if (child.exitCode === null) {
|
|
25993
|
+
const killer = setTimeout(() => child.kill(), 2e3);
|
|
25994
|
+
await exited;
|
|
25995
|
+
clearTimeout(killer);
|
|
25996
|
+
}
|
|
25997
|
+
}
|
|
25998
|
+
}
|
|
25999
|
+
function writeWhole(path, text) {
|
|
26000
|
+
const target = existsSync6(path) && lstatSync(path).isSymbolicLink() ? realpathSync(path) : path;
|
|
26001
|
+
const mode = existsSync6(target) ? statSync2(target).mode & 511 : 384;
|
|
26002
|
+
const temp = join6(dirname4(target), `.chips_${basename3(target)}.${randomBytes(4).toString("hex")}`);
|
|
26003
|
+
const fd = openSync(temp, "w", mode);
|
|
26004
|
+
try {
|
|
26005
|
+
writeSync2(fd, text, null, "utf8");
|
|
26006
|
+
fsyncSync(fd);
|
|
26007
|
+
} catch (error62) {
|
|
26008
|
+
closeSync(fd);
|
|
26009
|
+
rmSync3(temp, { force: true });
|
|
26010
|
+
throw error62;
|
|
26011
|
+
}
|
|
26012
|
+
closeSync(fd);
|
|
26013
|
+
try {
|
|
26014
|
+
renameSync2(temp, target);
|
|
26015
|
+
} catch (error62) {
|
|
26016
|
+
rmSync3(temp, { force: true });
|
|
26017
|
+
throw error62;
|
|
26018
|
+
}
|
|
26019
|
+
}
|
|
26020
|
+
async function saveEdit(source2, edit) {
|
|
26021
|
+
const memory = source2.limit !== void 0;
|
|
26022
|
+
if (memory) {
|
|
26023
|
+
const problem = memoryProblem(edit.text, source2.limit ?? MEMORY_LIMIT, source2.kind === "user" ? "what it knows of you" : "its memory");
|
|
26024
|
+
if (problem !== void 0) throw new Refuse(problem, void 0, "failed");
|
|
26025
|
+
}
|
|
26026
|
+
const save = () => {
|
|
26027
|
+
const now = readText(source2.path);
|
|
26028
|
+
if (edit.base_hash !== null && hashOf(now) !== edit.base_hash) {
|
|
26029
|
+
throw new Refuse("", void 0, "changed");
|
|
26030
|
+
}
|
|
26031
|
+
writeWhole(source2.path, memory ? entriesOf(edit.text).join(ENTRY_DELIMITER) : edit.text);
|
|
26032
|
+
};
|
|
26033
|
+
if (!memory) {
|
|
26034
|
+
save();
|
|
26035
|
+
return;
|
|
26036
|
+
}
|
|
26037
|
+
const folder2 = dirname4(source2.path);
|
|
26038
|
+
if (!existsSync6(dirname4(folder2))) throw new Refuse(`Hermes has no profile at ${dirname4(folder2)}.`, void 0, "failed");
|
|
26039
|
+
mkdirSync4(folder2, { recursive: true, mode: 448 });
|
|
26040
|
+
await withLock(`${source2.path}.lock`, save);
|
|
26041
|
+
}
|
|
26042
|
+
var Contexts = class {
|
|
26043
|
+
constructor(roster, port, log2, env = process.env) {
|
|
26044
|
+
this.roster = roster;
|
|
26045
|
+
this.port = port;
|
|
26046
|
+
this.log = log2;
|
|
26047
|
+
this.env = env;
|
|
26048
|
+
}
|
|
26049
|
+
roster;
|
|
26050
|
+
port;
|
|
26051
|
+
log;
|
|
26052
|
+
env;
|
|
26053
|
+
chain = Promise.resolve();
|
|
26054
|
+
/** Every copy read again, and sent where it moved. */
|
|
26055
|
+
refresh() {
|
|
26056
|
+
return this.inLine(async () => {
|
|
26057
|
+
const { readers, first } = this.roster();
|
|
26058
|
+
const picture = new Picture(readers, first, this.env);
|
|
26059
|
+
await this.port.harness(picture.harnessCopy(first)).catch((error62) => {
|
|
26060
|
+
this.log(`the harness's context not sent: ${messageOf(error62)}`);
|
|
26061
|
+
});
|
|
26062
|
+
for (const { agentId: agentId2 } of readers) {
|
|
26063
|
+
const copy = picture.agentCopy(agentId2);
|
|
26064
|
+
if (copy === void 0) continue;
|
|
26065
|
+
await this.port.agent(agentId2, copy).catch((error62) => {
|
|
26066
|
+
this.log(`${agentId2}'s context not sent: ${messageOf(error62)}`);
|
|
26067
|
+
});
|
|
26068
|
+
}
|
|
26069
|
+
}).catch((error62) => this.log(`the context wasn't read: ${messageOf(error62)}`));
|
|
26070
|
+
}
|
|
26071
|
+
/**
|
|
26072
|
+
* A person's edit from Chips: saved, and answered with the copy it is in now; or refused, the
|
|
26073
|
+
* copy as it is now beside the reason. The other copies the file is in follow.
|
|
26074
|
+
*/
|
|
26075
|
+
async edit(frame) {
|
|
26076
|
+
const answer = await this.inLine(async () => {
|
|
26077
|
+
const { readers, first } = this.roster();
|
|
26078
|
+
const copyNow = () => {
|
|
26079
|
+
const picture = new Picture(readers, first, this.env);
|
|
26080
|
+
return frame.agent_id === void 0 ? picture.harnessCopy(first) : picture.agentCopy(frame.agent_id);
|
|
26081
|
+
};
|
|
26082
|
+
const found = new Picture(readers, first, this.env).find(frame.agent_id, frame.name);
|
|
26083
|
+
if (found === void 0) {
|
|
26084
|
+
throw new Refuse(`Hermes gives this agent no file called ${frame.name}.`, copyNow(), "failed");
|
|
26085
|
+
}
|
|
26086
|
+
const { source: source2, shared } = found;
|
|
26087
|
+
if (frame.base_hash === null) {
|
|
26088
|
+
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.`;
|
|
26089
|
+
throw new Refuse(why, copyNow(), "failed");
|
|
26090
|
+
}
|
|
26091
|
+
try {
|
|
26092
|
+
await saveEdit(source2, frame);
|
|
26093
|
+
} catch (error62) {
|
|
26094
|
+
const reason = error62 instanceof Refuse ? error62.reason : "failed";
|
|
26095
|
+
this.log(`${source2.path} not saved from Chips: ${reason === "changed" ? "it changed since" : messageOf(error62)}`);
|
|
26096
|
+
throw new Refuse(error62 instanceof Refuse ? error62.message : messageOf(error62), copyNow(), reason);
|
|
26097
|
+
}
|
|
26098
|
+
this.log(`${source2.path} saved from Chips${shared ? ", for every agent that reads it" : ""}`);
|
|
26099
|
+
const copy = copyNow();
|
|
26100
|
+
if (copy === void 0) throw new Refuse("Saved, but the agent is gone.", void 0, "failed");
|
|
26101
|
+
return copy;
|
|
26102
|
+
});
|
|
26103
|
+
void this.refresh();
|
|
26104
|
+
return answer;
|
|
26105
|
+
}
|
|
26106
|
+
inLine(work) {
|
|
26107
|
+
const done = this.chain.catch(() => void 0).then(work);
|
|
26108
|
+
this.chain = done.catch(() => void 0);
|
|
26109
|
+
return done;
|
|
26110
|
+
}
|
|
26111
|
+
};
|
|
26112
|
+
function messageOf(error62) {
|
|
26113
|
+
return error62 instanceof Error ? error62.message : String(error62);
|
|
26114
|
+
}
|
|
26115
|
+
|
|
26116
|
+
// src/fills.ts
|
|
26117
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
26118
|
+
import { createServer } from "node:http";
|
|
26119
|
+
var HOOK_PATH = "/chips-hermes/";
|
|
26120
|
+
var MAX_BODY = 4 * 1024 * 1024;
|
|
26121
|
+
var DROP = 0.7;
|
|
26122
|
+
var SEND_EVERY_MS = 2e3;
|
|
26123
|
+
var THRESHOLD = 0.5;
|
|
26124
|
+
var SMALL_WINDOW = 512e3;
|
|
26125
|
+
var SMALL_THRESHOLD = 0.75;
|
|
26126
|
+
var WHY_NOT = "Hermes can't be asked to compact from Chips.";
|
|
26127
|
+
var whyReadOnly = (profile) => `Hermes sets this for every agent on its ${profile} profile.`;
|
|
26128
|
+
var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
26129
|
+
function limitOf(home2, window) {
|
|
26130
|
+
const text = configText(home2);
|
|
26131
|
+
const enabled = yamlBool(yamlValue(text, "compression", "enabled")) ?? true;
|
|
26132
|
+
const threshold = yamlNumber(yamlValue(text, "compression", "threshold")) ?? THRESHOLD;
|
|
26133
|
+
let ratio = window < SMALL_WINDOW ? Math.max(threshold, SMALL_THRESHOLD) : threshold;
|
|
26134
|
+
const tokens2 = yamlNumber(yamlValue(text, "compression", "threshold_tokens"));
|
|
26135
|
+
if (tokens2 !== void 0 && tokens2 > 0 && window > 0) ratio = Math.min(ratio, tokens2 / window);
|
|
26136
|
+
const at = Math.min(99, Math.max(1, Math.round(ratio * 100)));
|
|
26137
|
+
return enabled ? { at, choices: [at], default: at, may_turn_off: false } : { at: null, choices: [at], default: null, may_turn_off: true };
|
|
26138
|
+
}
|
|
26139
|
+
function signedBy(body, header, secret) {
|
|
26140
|
+
if (header === void 0 || !header.startsWith("sha256=")) return false;
|
|
26141
|
+
const given = Buffer.from(header.slice("sha256=".length), "hex");
|
|
26142
|
+
const made = createHmac("sha256", secret).update(body).digest();
|
|
26143
|
+
return given.length === made.length && timingSafeEqual(given, made);
|
|
26144
|
+
}
|
|
26145
|
+
function numbersOf(profile, body) {
|
|
26146
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
26147
|
+
const top = body;
|
|
26148
|
+
if (top["hook_event_name"] !== "post_api_request") return void 0;
|
|
26149
|
+
const extra = typeof top["extra"] === "object" && top["extra"] !== null ? top["extra"] : {};
|
|
26150
|
+
const named = typeof top["session_id"] === "string" ? top["session_id"] : "";
|
|
26151
|
+
const task2 = typeof extra["task_id"] === "string" && extra["task_id"] !== "" ? extra["task_id"] : void 0;
|
|
26152
|
+
const session = task2 ?? named;
|
|
26153
|
+
if (session === "") return void 0;
|
|
26154
|
+
const usage2 = typeof extra["usage"] === "object" && extra["usage"] !== null ? extra["usage"] : {};
|
|
26155
|
+
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));
|
|
26156
|
+
const window = num(extra["context_length"]);
|
|
26157
|
+
if (prompt === void 0 || prompt <= 0 || window === void 0 || window <= 0) return void 0;
|
|
26158
|
+
const ended = num(extra["ended_at"]);
|
|
26159
|
+
const stamped = typeof top["timestamp"] === "string" && !Number.isNaN(Date.parse(top["timestamp"])) ? top["timestamp"] : void 0;
|
|
26160
|
+
const at = ended !== void 0 ? new Date(ended * 1e3).toISOString() : stamped ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
26161
|
+
const model = typeof extra["model"] === "string" && extra["model"] !== "" ? extra["model"].slice(0, 120) : void 0;
|
|
26162
|
+
const turn = typeof extra["turn_id"] === "string" && extra["turn_id"] !== "" ? extra["turn_id"] : void 0;
|
|
26163
|
+
return {
|
|
26164
|
+
profile,
|
|
26165
|
+
session,
|
|
26166
|
+
turn,
|
|
26167
|
+
first: num(extra["api_call_count"]) === 1,
|
|
26168
|
+
used: Math.round(prompt),
|
|
26169
|
+
window: Math.round(window),
|
|
26170
|
+
model,
|
|
26171
|
+
at: new Date(at).toISOString()
|
|
26172
|
+
};
|
|
26173
|
+
}
|
|
26174
|
+
var Fills = class {
|
|
26175
|
+
constructor(options) {
|
|
26176
|
+
this.options = options;
|
|
26177
|
+
const kept = options.kept;
|
|
26178
|
+
kept.readings ??= {};
|
|
26179
|
+
kept.lasts ??= {};
|
|
26180
|
+
kept.longTurns ??= {};
|
|
26181
|
+
for (const reading of Object.values(kept.readings)) this.lastOf.set(reading.session, reading);
|
|
26182
|
+
}
|
|
26183
|
+
options;
|
|
26184
|
+
server;
|
|
26185
|
+
/** The last reading of each session, for a compaction's drop. */
|
|
26186
|
+
lastOf = /* @__PURE__ */ new Map();
|
|
26187
|
+
turns = /* @__PURE__ */ new Map();
|
|
26188
|
+
timers = /* @__PURE__ */ new Map();
|
|
26189
|
+
sentAt = /* @__PURE__ */ new Map();
|
|
26190
|
+
/** The profiles whose unsigned or wrongly signed deliveries have been said once. */
|
|
26191
|
+
badSaid = /* @__PURE__ */ new Set();
|
|
26192
|
+
heard = false;
|
|
26193
|
+
/** Whether any reading has come since the bridge started. */
|
|
26194
|
+
get anyHeard() {
|
|
26195
|
+
return this.heard;
|
|
26196
|
+
}
|
|
26197
|
+
/** Hear Hermes on `127.0.0.1:port`: the port it took (one of its own for 0). */
|
|
26198
|
+
listen(port, host = "127.0.0.1") {
|
|
26199
|
+
const server = createServer((request, response) => this.handle(request, response));
|
|
26200
|
+
this.server = server;
|
|
26201
|
+
return new Promise((done, failed) => {
|
|
26202
|
+
server.once("error", failed);
|
|
26203
|
+
server.listen(port, host, () => {
|
|
26204
|
+
server.off("error", failed);
|
|
26205
|
+
done(server.address().port);
|
|
26206
|
+
});
|
|
26207
|
+
});
|
|
26208
|
+
}
|
|
26209
|
+
async close() {
|
|
26210
|
+
for (const timer of this.timers.values()) clearTimeout(timer);
|
|
26211
|
+
if (this.timers.size > 0) this.options.save();
|
|
26212
|
+
this.timers.clear();
|
|
26213
|
+
const server = this.server;
|
|
26214
|
+
this.server = void 0;
|
|
26215
|
+
if (server === void 0) return;
|
|
26216
|
+
server.closeAllConnections();
|
|
26217
|
+
await new Promise((done) => server.close(() => done()));
|
|
26218
|
+
}
|
|
26219
|
+
/** An agent's copy, from what is known of it, or nothing when no reading has come. */
|
|
26220
|
+
copyOf(agentId2) {
|
|
26221
|
+
const { kept } = this.options;
|
|
26222
|
+
const reading = kept.readings?.[agentId2];
|
|
26223
|
+
const profile = this.options.profileOf(agentId2);
|
|
26224
|
+
if (reading === void 0 || profile === void 0) return void 0;
|
|
26225
|
+
const last = kept.lasts?.[agentId2];
|
|
26226
|
+
const long = kept.longTurns?.[agentId2];
|
|
26227
|
+
return {
|
|
26228
|
+
used: reading.used,
|
|
26229
|
+
window: reading.window,
|
|
26230
|
+
...reading.model === void 0 ? {} : { model: reading.model },
|
|
26231
|
+
as_of: reading.at,
|
|
26232
|
+
...last === void 0 ? {} : { last },
|
|
26233
|
+
...long === void 0 ? {} : { long_turn: long },
|
|
26234
|
+
compacts: false,
|
|
26235
|
+
why_not: WHY_NOT,
|
|
26236
|
+
limit: limitOf(profile.home, reading.window),
|
|
26237
|
+
editable: false,
|
|
26238
|
+
why_read_only: whyReadOnly(profile.profile)
|
|
26239
|
+
};
|
|
26240
|
+
}
|
|
26241
|
+
/** Each agent's kept copy, sent: at start, so a restart shows what was known. */
|
|
26242
|
+
async sendAll() {
|
|
26243
|
+
for (const agentId2 of Object.keys(this.options.kept.readings ?? {})) await this.send(agentId2);
|
|
26244
|
+
}
|
|
26245
|
+
/** An agent gone: nothing more is sent for it, and nothing kept. */
|
|
26246
|
+
forget(agentId2) {
|
|
26247
|
+
const { kept } = this.options;
|
|
26248
|
+
clearTimeout(this.timers.get(agentId2));
|
|
26249
|
+
this.timers.delete(agentId2);
|
|
26250
|
+
if (kept.readings?.[agentId2] === void 0 && kept.lasts?.[agentId2] === void 0) return;
|
|
26251
|
+
delete kept.readings?.[agentId2];
|
|
26252
|
+
delete kept.lasts?.[agentId2];
|
|
26253
|
+
delete kept.longTurns?.[agentId2];
|
|
26254
|
+
this.options.save();
|
|
26255
|
+
}
|
|
26256
|
+
/** One delivery: read whole up to its limit, checked, answered at once, then taken in. */
|
|
26257
|
+
handle(request, response) {
|
|
26258
|
+
const url2 = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
26259
|
+
const profile = url2.pathname.startsWith(HOOK_PATH) ? decodeURIComponent(url2.pathname.slice(HOOK_PATH.length)) : "";
|
|
26260
|
+
if (request.method !== "POST" || profile === "" || profile.includes("/")) {
|
|
26261
|
+
response.writeHead(404).end();
|
|
26262
|
+
return;
|
|
26263
|
+
}
|
|
26264
|
+
const chunks = [];
|
|
26265
|
+
let size = 0;
|
|
26266
|
+
let over = false;
|
|
26267
|
+
request.on("data", (chunk) => {
|
|
26268
|
+
if (over) return;
|
|
26269
|
+
size += chunk.length;
|
|
26270
|
+
if (size > MAX_BODY) {
|
|
26271
|
+
over = true;
|
|
26272
|
+
response.writeHead(413, { connection: "close" }).end();
|
|
26273
|
+
request.destroy();
|
|
26274
|
+
return;
|
|
26275
|
+
}
|
|
26276
|
+
chunks.push(chunk);
|
|
26277
|
+
});
|
|
26278
|
+
request.on("end", () => {
|
|
26279
|
+
if (over) return;
|
|
26280
|
+
const body = Buffer.concat(chunks);
|
|
26281
|
+
const secret = this.options.secretFor(profile);
|
|
26282
|
+
const header = request.headers["x-hermes-signature-256"];
|
|
26283
|
+
if (secret === void 0 || !signedBy(body, typeof header === "string" ? header : void 0, secret)) {
|
|
26284
|
+
response.writeHead(401).end();
|
|
26285
|
+
if (!this.badSaid.has(profile)) {
|
|
26286
|
+
this.badSaid.add(profile);
|
|
26287
|
+
this.options.log(
|
|
26288
|
+
`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`
|
|
26289
|
+
);
|
|
26290
|
+
}
|
|
26291
|
+
return;
|
|
26292
|
+
}
|
|
26293
|
+
response.writeHead(204).end();
|
|
26294
|
+
let parsed;
|
|
26295
|
+
try {
|
|
26296
|
+
parsed = JSON.parse(body.toString("utf8"));
|
|
26297
|
+
} catch {
|
|
26298
|
+
return;
|
|
26299
|
+
}
|
|
26300
|
+
const numbers = numbersOf(profile, parsed);
|
|
26301
|
+
if (numbers !== void 0) this.take(numbers);
|
|
26302
|
+
});
|
|
26303
|
+
}
|
|
26304
|
+
/** A reading taken in: the agent's latest, a compaction when it fell, the turn's peak. */
|
|
26305
|
+
take(numbers) {
|
|
26306
|
+
const whose = this.options.whose(numbers.session);
|
|
26307
|
+
if (whose === void 0) return;
|
|
26308
|
+
this.heard = true;
|
|
26309
|
+
const { kept } = this.options;
|
|
26310
|
+
const { agentId: agentId2 } = whose;
|
|
26311
|
+
const reading = {
|
|
26312
|
+
used: numbers.used,
|
|
26313
|
+
window: numbers.window,
|
|
26314
|
+
...numbers.model === void 0 ? {} : { model: numbers.model },
|
|
26315
|
+
at: numbers.at,
|
|
26316
|
+
session: numbers.session
|
|
26317
|
+
};
|
|
26318
|
+
const before = this.lastOf.get(numbers.session);
|
|
26319
|
+
const fell = before !== void 0 && before.model === reading.model && reading.used < before.used * DROP;
|
|
26320
|
+
if (fell && before !== void 0) {
|
|
26321
|
+
(kept.lasts ??= {})[agentId2] = { at: reading.at, from: before.used, to: reading.used, by: "itself" };
|
|
26322
|
+
this.options.log(`${agentId2} compacted in Hermes, ${before.used} to ${reading.used} tokens`);
|
|
26323
|
+
}
|
|
26324
|
+
let turn = this.turns.get(numbers.session);
|
|
26325
|
+
const fresh = numbers.turn === void 0 ? numbers.first || turn === void 0 : turn?.id !== numbers.turn;
|
|
26326
|
+
if (turn === void 0 || fresh) {
|
|
26327
|
+
turn = { id: numbers.turn, peak: 0, folds: 0 };
|
|
26328
|
+
this.turns.set(numbers.session, turn);
|
|
26329
|
+
}
|
|
26330
|
+
turn.peak = Math.max(turn.peak, reading.used);
|
|
26331
|
+
if (fell) turn.folds += 1;
|
|
26332
|
+
if (turn.peak > reading.window / 2) {
|
|
26333
|
+
(kept.longTurns ??= {})[agentId2] = { at: reading.at, peak: turn.peak, folds: Math.min(1e3, turn.folds) };
|
|
26334
|
+
}
|
|
26335
|
+
this.lastOf.set(numbers.session, reading);
|
|
26336
|
+
(kept.readings ??= {})[agentId2] = reading;
|
|
26337
|
+
this.schedule(agentId2);
|
|
26338
|
+
}
|
|
26339
|
+
/** The agent's copy sent now, or once readings slow down, and what is kept saved with it. */
|
|
26340
|
+
schedule(agentId2) {
|
|
26341
|
+
if (this.timers.has(agentId2)) return;
|
|
26342
|
+
const every = this.options.sendEveryMs ?? SEND_EVERY_MS;
|
|
26343
|
+
const wait2 = Math.max(0, (this.sentAt.get(agentId2) ?? 0) + every - Date.now());
|
|
26344
|
+
const timer = setTimeout(() => {
|
|
26345
|
+
this.timers.delete(agentId2);
|
|
26346
|
+
this.sentAt.set(agentId2, Date.now());
|
|
26347
|
+
this.options.save();
|
|
26348
|
+
void this.send(agentId2);
|
|
26349
|
+
}, wait2);
|
|
26350
|
+
timer.unref?.();
|
|
26351
|
+
this.timers.set(agentId2, timer);
|
|
26352
|
+
}
|
|
26353
|
+
async send(agentId2) {
|
|
26354
|
+
const copy = this.copyOf(agentId2);
|
|
26355
|
+
if (copy === void 0) return;
|
|
26356
|
+
await this.options.send(agentId2, copy).catch((error62) => {
|
|
26357
|
+
this.options.log(`${agentId2}'s context window not sent: ${error62 instanceof Error ? error62.message : String(error62)}`);
|
|
26358
|
+
});
|
|
26359
|
+
}
|
|
26360
|
+
};
|
|
25471
26361
|
|
|
25472
26362
|
// src/hermes.ts
|
|
25473
26363
|
var TERMINAL = /* @__PURE__ */ new Set([
|
|
@@ -25490,9 +26380,9 @@ var HermesClient = class {
|
|
|
25490
26380
|
constructor(address, fetcher = fetch) {
|
|
25491
26381
|
this.address = address;
|
|
25492
26382
|
this.fetcher = fetcher;
|
|
25493
|
-
const
|
|
26383
|
+
const root2 = address.url.replace(/\/+$/, "");
|
|
25494
26384
|
const profile = address.profile;
|
|
25495
|
-
this.base = profile === void 0 || profile === "default" ?
|
|
26385
|
+
this.base = profile === void 0 || profile === "default" ? root2 : `${root2}/p/${encodeURIComponent(profile)}`;
|
|
25496
26386
|
}
|
|
25497
26387
|
address;
|
|
25498
26388
|
fetcher;
|
|
@@ -25665,8 +26555,126 @@ async function failureOf(answer) {
|
|
|
25665
26555
|
return new HermesError(message, answer.status, code);
|
|
25666
26556
|
}
|
|
25667
26557
|
|
|
26558
|
+
// src/hooks.ts
|
|
26559
|
+
import { appendFileSync, chmodSync as chmodSync3, existsSync as existsSync7, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
26560
|
+
import { join as join7 } from "node:path";
|
|
26561
|
+
var HOOK_NAME = "chips";
|
|
26562
|
+
var SECRET_ENV = "CHIPS_HOOK_SECRET";
|
|
26563
|
+
function hookUrl(port, profile) {
|
|
26564
|
+
return `http://127.0.0.1:${port}${HOOK_PATH}${profile}`;
|
|
26565
|
+
}
|
|
26566
|
+
function linesOf2(text) {
|
|
26567
|
+
return text.split("\n").map((line) => {
|
|
26568
|
+
const indent = line.length - line.trimStart().length;
|
|
26569
|
+
const words4 = line.trimStart().startsWith("#") ? "" : line.trim().replace(/\s+#.*$/, "");
|
|
26570
|
+
return { indent, words: words4 };
|
|
26571
|
+
});
|
|
26572
|
+
}
|
|
26573
|
+
function hookPortIn(home2) {
|
|
26574
|
+
const path = join7(home2, "config.yaml");
|
|
26575
|
+
if (!existsSync7(path)) return void 0;
|
|
26576
|
+
const escaped = HOOK_PATH.replace(/[/]/g, "\\/");
|
|
26577
|
+
const match = new RegExp(`^\\s*(?:-\\s*)?url:\\s*["']?http:\\/\\/127\\.0\\.0\\.1:(\\d+)${escaped}`, "m").exec(
|
|
26578
|
+
readFileSync7(path, "utf8")
|
|
26579
|
+
);
|
|
26580
|
+
const port = match === null ? void 0 : Number(match[1]);
|
|
26581
|
+
return port !== void 0 && Number.isInteger(port) && port > 0 && port < 65536 ? port : void 0;
|
|
26582
|
+
}
|
|
26583
|
+
function entryLines(indent, url2, secret) {
|
|
26584
|
+
return [
|
|
26585
|
+
`${indent}outbound:`,
|
|
26586
|
+
`${indent} - name: ${HOOK_NAME}`,
|
|
26587
|
+
`${indent} url: ${url2}`,
|
|
26588
|
+
`${indent} events: [post_api_request]`,
|
|
26589
|
+
"env" in secret ? `${indent} secret_env: ${secret.env}` : `${indent} secret: "${secret.inline}"`,
|
|
26590
|
+
`${indent} timeout: 5`
|
|
26591
|
+
];
|
|
26592
|
+
}
|
|
26593
|
+
function secretInEnv(home2, secret) {
|
|
26594
|
+
const path = join7(home2, ".env");
|
|
26595
|
+
if (!existsSync7(path)) return false;
|
|
26596
|
+
if (envValue(home2, SECRET_ENV) !== void 0) return true;
|
|
26597
|
+
const text = readFileSync7(path, "utf8");
|
|
26598
|
+
const mode = statSync3(path).mode & 511;
|
|
26599
|
+
appendFileSync(path, `${text === "" || text.endsWith("\n") ? "" : "\n"}${SECRET_ENV}=${secret}
|
|
26600
|
+
`);
|
|
26601
|
+
chmodSync3(path, mode);
|
|
26602
|
+
return true;
|
|
26603
|
+
}
|
|
26604
|
+
function addHook(home2, port, profile, secret) {
|
|
26605
|
+
const path = join7(home2, "config.yaml");
|
|
26606
|
+
if (!existsSync7(home2)) return { state: "no_home", path };
|
|
26607
|
+
const url2 = hookUrl(port, profile);
|
|
26608
|
+
const text = existsSync7(path) ? readFileSync7(path, "utf8") : "";
|
|
26609
|
+
const lines2 = linesOf2(text);
|
|
26610
|
+
const ours2 = new RegExp(`^(?:-\\s*)?url:\\s*["']?${url2.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}["']?$`);
|
|
26611
|
+
if (lines2.some((line) => ours2.test(line.words))) return { state: "there", path };
|
|
26612
|
+
const where = lines2.findIndex((line) => line.indent === 0 && /^["']?hooks["']?\s*:/.test(line.words));
|
|
26613
|
+
const byHand = (why) => {
|
|
26614
|
+
const inEnv2 = secretInEnv(home2, secret);
|
|
26615
|
+
const shown2 = entryLines(" ", url2, inEnv2 ? { env: SECRET_ENV } : { inline: secret });
|
|
26616
|
+
return { state: "by_hand", path, why, lines: ["hooks:", ...shown2].join("\n") };
|
|
26617
|
+
};
|
|
26618
|
+
const plain = /^(?:"[^"]+"|'[^']+'|[A-Za-z_][\w.-]*)\s*:(?:\s|$)/;
|
|
26619
|
+
if (lines2.some((line) => line.indent === 0 && line.words !== "" && !plain.test(line.words))) {
|
|
26620
|
+
return byHand("its top level isn't laid out as this bridge expects");
|
|
26621
|
+
}
|
|
26622
|
+
if (where === -1) {
|
|
26623
|
+
const inEnv2 = secretInEnv(home2, secret);
|
|
26624
|
+
const block = [
|
|
26625
|
+
"# Chips: how full each agent's context is, sent to the chips-hermes bridge after each model call.",
|
|
26626
|
+
"hooks:",
|
|
26627
|
+
...entryLines(" ", url2, inEnv2 ? { env: SECRET_ENV } : { inline: secret })
|
|
26628
|
+
];
|
|
26629
|
+
const before = text === "" ? "" : text.endsWith("\n") ? "\n" : "\n\n";
|
|
26630
|
+
if (text === "") writeFileSync4(path, `${block.join("\n")}
|
|
26631
|
+
`, { mode: 384 });
|
|
26632
|
+
else writeWhole(path, `${text}${before}${block.join("\n")}
|
|
26633
|
+
`);
|
|
26634
|
+
return { state: "added", path };
|
|
26635
|
+
}
|
|
26636
|
+
const head = lines2[where]?.words ?? "";
|
|
26637
|
+
if (head.replace(/^["']?hooks["']?\s*:/, "").trim() !== "") return byHand("its hooks: is written on one line");
|
|
26638
|
+
const children = [];
|
|
26639
|
+
for (const line of lines2.slice(where + 1)) {
|
|
26640
|
+
if (line.words === "") continue;
|
|
26641
|
+
if (line.indent === 0) break;
|
|
26642
|
+
children.push(line);
|
|
26643
|
+
}
|
|
26644
|
+
const indent = children[0]?.indent ?? 2;
|
|
26645
|
+
const named = new RegExp(`^(?:-\\s*)?name:\\s*["']?${HOOK_NAME}["']?$`);
|
|
26646
|
+
if (children.some((line) => named.test(line.words))) {
|
|
26647
|
+
return byHand(`its hooks have an entry called ${HOOK_NAME} with another address`);
|
|
26648
|
+
}
|
|
26649
|
+
if (children.some((line) => line.indent === indent && /^["']?outbound["']?\s*:/.test(line.words))) {
|
|
26650
|
+
return byHand("its hooks already have an outbound list");
|
|
26651
|
+
}
|
|
26652
|
+
if (children.some((line) => line.indent !== indent && line.indent < indent) || children[0]?.words.startsWith("-")) {
|
|
26653
|
+
return byHand("its hooks: isn't laid out as this bridge expects");
|
|
26654
|
+
}
|
|
26655
|
+
const inEnv = secretInEnv(home2, secret);
|
|
26656
|
+
const raw = text.split("\n");
|
|
26657
|
+
raw.splice(where + 1, 0, ...entryLines(" ".repeat(indent), url2, inEnv ? { env: SECRET_ENV } : { inline: secret }));
|
|
26658
|
+
writeWhole(path, raw.join("\n"));
|
|
26659
|
+
return { state: "added", path };
|
|
26660
|
+
}
|
|
26661
|
+
|
|
25668
26662
|
// src/bridge.ts
|
|
25669
|
-
var WANTS = ["agents", "new_agents", "forks"];
|
|
26663
|
+
var WANTS = ["agents", "new_agents", "forks", "context_files", "context_windows"];
|
|
26664
|
+
function settingsFor(active) {
|
|
26665
|
+
const bridge2 = () => {
|
|
26666
|
+
const there = active();
|
|
26667
|
+
if (there === void 0) throw new Refuse("The bridge is starting. Try again in a moment.");
|
|
26668
|
+
return there;
|
|
26669
|
+
};
|
|
26670
|
+
return {
|
|
26671
|
+
context: (edit) => bridge2().onContextEdit(edit),
|
|
26672
|
+
contextWindows: {
|
|
26673
|
+
compact: async (ask2) => bridge2().onWindowCompact(ask2),
|
|
26674
|
+
change: async (ask2) => bridge2().onWindowChange(ask2)
|
|
26675
|
+
}
|
|
26676
|
+
};
|
|
26677
|
+
}
|
|
25670
26678
|
function filesHere(project = false) {
|
|
25671
26679
|
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
26680
|
}
|
|
@@ -25675,10 +26683,10 @@ function instructionsFor(folder2, project = false) {
|
|
|
25675
26683
|
const files = filesHere(project);
|
|
25676
26684
|
return folder2 === void 0 ? files : `${files} Your own folder is ${folder2}: make and keep your files there, and run your commands from there.`;
|
|
25677
26685
|
}
|
|
25678
|
-
function freeFolder(
|
|
26686
|
+
function freeFolder(root2, name2, taken) {
|
|
25679
26687
|
const slug = name2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
25680
26688
|
for (let count = 1; ; count += 1) {
|
|
25681
|
-
const folder2 =
|
|
26689
|
+
const folder2 = join8(root2, count === 1 ? slug : `${slug}-${count}`);
|
|
25682
26690
|
if (!taken.includes(folder2)) return folder2;
|
|
25683
26691
|
}
|
|
25684
26692
|
}
|
|
@@ -25709,6 +26717,32 @@ var Bridge = class {
|
|
|
25709
26717
|
constructor(options) {
|
|
25710
26718
|
this.options = options;
|
|
25711
26719
|
this.log = options.log ?? (() => void 0);
|
|
26720
|
+
const { chips: chips2, hermesRoot: hermesRoot2 } = options;
|
|
26721
|
+
if (hermesRoot2 === void 0) return;
|
|
26722
|
+
this.contexts = new Contexts(
|
|
26723
|
+
() => this.roster(),
|
|
26724
|
+
{
|
|
26725
|
+
agent: (agentId2, copy) => chips2.settings.context(agentId2, copy),
|
|
26726
|
+
harness: (copy) => chips2.settings.harnessContext(copy)
|
|
26727
|
+
},
|
|
26728
|
+
(line) => this.log(`context: ${line}`),
|
|
26729
|
+
options.env ?? process.env
|
|
26730
|
+
);
|
|
26731
|
+
if (options.fills === void 0) return;
|
|
26732
|
+
const kept = options.state.fills ??= {};
|
|
26733
|
+
this.fills = new Fills({
|
|
26734
|
+
whose: (sessionId) => this.whoseSession(sessionId),
|
|
26735
|
+
profileOf: (agentId2) => {
|
|
26736
|
+
const profile = this.profileFor(agentId2);
|
|
26737
|
+
return profile === void 0 ? void 0 : { profile: profile.name, home: profile.home };
|
|
26738
|
+
},
|
|
26739
|
+
secretFor: (profile) => envValue(profileHome(hermesRoot2, profile), SECRET_ENV) ?? kept.secret ?? options.fills?.secret,
|
|
26740
|
+
send: (agentId2, copy) => chips2.settings.contextWindow(agentId2, copy),
|
|
26741
|
+
kept,
|
|
26742
|
+
save: () => options.saveState(options.state),
|
|
26743
|
+
log: (line) => this.log(`context window: ${line}`),
|
|
26744
|
+
sendEveryMs: options.fillsEveryMs
|
|
26745
|
+
});
|
|
25712
26746
|
}
|
|
25713
26747
|
options;
|
|
25714
26748
|
agents = [];
|
|
@@ -25723,6 +26757,16 @@ var Bridge = class {
|
|
|
25723
26757
|
copying = Promise.resolve();
|
|
25724
26758
|
log;
|
|
25725
26759
|
health;
|
|
26760
|
+
/** The harness's agents in Chips as last listed: whose context and window copies are sent. */
|
|
26761
|
+
listed = [];
|
|
26762
|
+
/** The agents' context files (Chips ADR 0134), when the bridge knows where Hermes keeps them. */
|
|
26763
|
+
contexts;
|
|
26764
|
+
contextTimer;
|
|
26765
|
+
/** The agents' context windows (Chips ADR 0146), when Hermes's webhook has somewhere to reach. */
|
|
26766
|
+
fills;
|
|
26767
|
+
/** The port Hermes's webhook reaches the bridge on, once it listens. */
|
|
26768
|
+
fillsPort;
|
|
26769
|
+
toldNoReadings = false;
|
|
25726
26770
|
/** Register the agents, start hearing what is said to them, and say how their gateways are. */
|
|
25727
26771
|
async start() {
|
|
25728
26772
|
const { chips: chips2 } = this.options;
|
|
@@ -25736,20 +26780,124 @@ var Bridge = class {
|
|
|
25736
26780
|
this.ours = /* @__PURE__ */ new Set([...known.map((agent) => agent.agentId), ...this.agents.map((agent) => agent.agentId)]);
|
|
25737
26781
|
chips2.on("message.new", (message) => void this.hear(message));
|
|
25738
26782
|
chips2.on("agent.asked", (asked) => {
|
|
25739
|
-
this.make(asked).catch((error62) => this.log(`a new agent went wrong: ${
|
|
26783
|
+
this.make(asked).catch((error62) => this.log(`a new agent went wrong: ${messageOf2(error62)}`));
|
|
25740
26784
|
});
|
|
25741
26785
|
chips2.on("agent.fork", (fork) => {
|
|
25742
|
-
this.copying = this.copying.then(() => this.copy(fork)).catch((error62) => this.log(`a copy went wrong: ${
|
|
26786
|
+
this.copying = this.copying.then(() => this.copy(fork)).catch((error62) => this.log(`a copy went wrong: ${messageOf2(error62)}`));
|
|
25743
26787
|
});
|
|
25744
26788
|
chips2.on("feedback", (feedback2) => {
|
|
25745
|
-
this.notRight(feedback2).catch((error62) => this.log(`a note on a reply went wrong: ${
|
|
26789
|
+
this.notRight(feedback2).catch((error62) => this.log(`a note on a reply went wrong: ${messageOf2(error62)}`));
|
|
25746
26790
|
});
|
|
26791
|
+
chips2.on("agent.attached", () => void this.agentsMoved());
|
|
26792
|
+
chips2.on("agent.released", () => void this.agentsMoved());
|
|
25747
26793
|
await this.sayHowItIs();
|
|
25748
26794
|
this.health = setInterval(() => void this.sayHowItIs(), this.options.healthEveryMs ?? 3e4);
|
|
25749
26795
|
this.health.unref();
|
|
26796
|
+
await this.startSettings();
|
|
25750
26797
|
}
|
|
25751
26798
|
stop() {
|
|
25752
26799
|
if (this.health !== void 0) clearInterval(this.health);
|
|
26800
|
+
if (this.contextTimer !== void 0) clearInterval(this.contextTimer);
|
|
26801
|
+
void this.fills?.close();
|
|
26802
|
+
}
|
|
26803
|
+
// ------------------------------------------------------------------ an agent's page
|
|
26804
|
+
/**
|
|
26805
|
+
* The agents' context and context windows, sent at start and kept current: the files read
|
|
26806
|
+
* again every so often and after each turn, a window copy on each reading Hermes sends.
|
|
26807
|
+
*/
|
|
26808
|
+
async startSettings() {
|
|
26809
|
+
if (this.contexts === void 0) return;
|
|
26810
|
+
this.listed = await this.options.chips.agents.list().catch(() => this.listed);
|
|
26811
|
+
await this.contexts.refresh();
|
|
26812
|
+
this.contextTimer = setInterval(() => void this.contexts?.refresh(), this.options.contextEveryMs ?? 15e3);
|
|
26813
|
+
this.contextTimer.unref();
|
|
26814
|
+
const fills2 = this.fills;
|
|
26815
|
+
const asked = this.options.fills;
|
|
26816
|
+
if (fills2 === void 0 || asked === void 0) return;
|
|
26817
|
+
const kept = this.options.state.fills ??= {};
|
|
26818
|
+
try {
|
|
26819
|
+
this.fillsPort = await fills2.listen(asked.port);
|
|
26820
|
+
if (kept.port !== this.fillsPort) {
|
|
26821
|
+
kept.port = this.fillsPort;
|
|
26822
|
+
this.options.saveState(this.options.state);
|
|
26823
|
+
}
|
|
26824
|
+
} catch (error62) {
|
|
26825
|
+
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`);
|
|
26826
|
+
}
|
|
26827
|
+
await fills2.sendAll();
|
|
26828
|
+
}
|
|
26829
|
+
/** The address Hermes's webhook POSTs a profile's readings to, once the bridge listens. */
|
|
26830
|
+
hookUrl(profile = "default") {
|
|
26831
|
+
return this.fillsPort === void 0 ? void 0 : hookUrl(this.fillsPort, profileName(profile));
|
|
26832
|
+
}
|
|
26833
|
+
/** The harness's agents listed again, the copies of any gone forgotten, and the context sent. */
|
|
26834
|
+
async agentsMoved() {
|
|
26835
|
+
if (this.contexts === void 0) return;
|
|
26836
|
+
const before = this.listed;
|
|
26837
|
+
this.listed = await this.options.chips.agents.list().catch(() => this.listed);
|
|
26838
|
+
const now = new Set(this.active().map((agent) => agent.agentId));
|
|
26839
|
+
for (const gone of before) {
|
|
26840
|
+
if (now.has(gone.agentId)) continue;
|
|
26841
|
+
this.options.chips.settings.forget(gone.agentId);
|
|
26842
|
+
this.fills?.forget(gone.agentId);
|
|
26843
|
+
}
|
|
26844
|
+
await this.contexts.refresh();
|
|
26845
|
+
}
|
|
26846
|
+
/** The harness's agents that read anything now: the active ones listed. */
|
|
26847
|
+
active() {
|
|
26848
|
+
return this.listed.filter((agent) => agent.state === "active");
|
|
26849
|
+
}
|
|
26850
|
+
/** A profile as the context needs it: its name and its home under Hermes's root. */
|
|
26851
|
+
profileOfRunning(agent) {
|
|
26852
|
+
const root2 = this.options.hermesRoot;
|
|
26853
|
+
if (root2 === void 0) return void 0;
|
|
26854
|
+
return { name: profileName(agent.config.address.profile), home: profileHome(root2, agent.config.address.profile) };
|
|
26855
|
+
}
|
|
26856
|
+
/** The profile an agent runs on: its own, a copy's original's, else the first (as `profileOf` picks). */
|
|
26857
|
+
profileFor(agentId2) {
|
|
26858
|
+
const own2 = this.agents.find((agent) => agent.agentId === agentId2);
|
|
26859
|
+
const copied = this.options.state.profiles?.[agentId2];
|
|
26860
|
+
const original = this.agents.find((agent) => copied !== void 0 && agent.key === copied);
|
|
26861
|
+
const running = own2 ?? original ?? this.agents[0];
|
|
26862
|
+
return running === void 0 ? void 0 : this.profileOfRunning(running);
|
|
26863
|
+
}
|
|
26864
|
+
/** Who reads what: each active agent with its profile, and the first profile, which new agents run on. */
|
|
26865
|
+
roster() {
|
|
26866
|
+
const readers = [];
|
|
26867
|
+
const ids2 = /* @__PURE__ */ new Set([...this.active().map((agent) => agent.agentId), ...this.agents.map((agent) => agent.agentId)]);
|
|
26868
|
+
for (const agentId2 of ids2) {
|
|
26869
|
+
if (this.listed.some((agent) => agent.agentId === agentId2 && agent.state !== "active")) continue;
|
|
26870
|
+
const profile = this.profileFor(agentId2);
|
|
26871
|
+
if (profile !== void 0) readers.push({ agentId: agentId2, profile });
|
|
26872
|
+
}
|
|
26873
|
+
const first = this.agents[0];
|
|
26874
|
+
return { readers, first: first === void 0 ? void 0 : this.profileOfRunning(first) };
|
|
26875
|
+
}
|
|
26876
|
+
/** The agent a Hermes session the bridge keeps is, with its profile: nothing for any other. */
|
|
26877
|
+
whoseSession(sessionId) {
|
|
26878
|
+
const entry = Object.entries(this.options.state.sessions).find(([, session]) => session === sessionId);
|
|
26879
|
+
if (entry === void 0) return void 0;
|
|
26880
|
+
const who = entry[0].slice(0, entry[0].lastIndexOf("|"));
|
|
26881
|
+
const agentId2 = this.agents.find((agent) => agent.key === who)?.agentId ?? who;
|
|
26882
|
+
const profile = this.profileFor(agentId2);
|
|
26883
|
+
return profile === void 0 ? void 0 : { agentId: agentId2, profile: profile.name, home: profile.home };
|
|
26884
|
+
}
|
|
26885
|
+
/** A person's edit of a context file from Chips (Chips ADR 0134), saved and answered. */
|
|
26886
|
+
async onContextEdit(frame) {
|
|
26887
|
+
if (this.contexts === void 0) throw new Refuse("This bridge reads no context files.");
|
|
26888
|
+
if (frame.agent_id !== void 0 && !this.listed.some((agent) => agent.agentId === frame.agent_id)) {
|
|
26889
|
+
this.listed = await this.options.chips.agents.list().catch(() => this.listed);
|
|
26890
|
+
}
|
|
26891
|
+
return this.contexts.edit(frame);
|
|
26892
|
+
}
|
|
26893
|
+
/** A person's Compact now (Chips ADR 0146): Hermes can't be asked to compact from outside a run. */
|
|
26894
|
+
onWindowCompact(frame) {
|
|
26895
|
+
throw new Refuse(WHY_NOT, this.fills?.copyOf(frame.agent_id));
|
|
26896
|
+
}
|
|
26897
|
+
/** A person's limit (Chips ADR 0146): Hermes sets it in its profile's config, for all its agents. */
|
|
26898
|
+
onWindowChange(frame) {
|
|
26899
|
+
const profile = this.profileFor(frame.agent_id);
|
|
26900
|
+
throw new Refuse(whyReadOnly(profile?.name ?? "default"), this.fills?.copyOf(frame.agent_id));
|
|
25753
26901
|
}
|
|
25754
26902
|
/** The Main agent, the first registered, once started: who says the harness's first words. */
|
|
25755
26903
|
get mainAgentId() {
|
|
@@ -25801,6 +26949,7 @@ var Bridge = class {
|
|
|
25801
26949
|
this.ours.add(agent.agentId);
|
|
25802
26950
|
this.names.set(agent.agentId, agent.name);
|
|
25803
26951
|
this.log(`${agent.name} made${agent.attached === false ? `, not in the room: ${agent.why ?? "too late"}` : ""}`);
|
|
26952
|
+
void this.agentsMoved();
|
|
25804
26953
|
}
|
|
25805
26954
|
/**
|
|
25806
26955
|
* A person asked for a copy of one of the harness's agents in another room (ADR 0087, Fork): the
|
|
@@ -25834,7 +26983,7 @@ var Bridge = class {
|
|
|
25834
26983
|
const room = listed.find((agent) => agent.agentId === fork.agent_id)?.room ?? void 0;
|
|
25835
26984
|
const from = (room === void 0 ? void 0 : state2.sessions[`${who}|${room}`]) ?? Object.entries(state2.sessions).findLast(([id2]) => id2.startsWith(`${who}|`))?.[1];
|
|
25836
26985
|
if (from === void 0) this.log(`${name2} never spoke: its copy remembers nothing`);
|
|
25837
|
-
const session = from === void 0 ? void 0 : await original.client.forkSession(from, { id: `chips-fork-${fork.id}`, title: `Chips
|
|
26986
|
+
const session = from === void 0 ? void 0 : await original.client.forkSession(from, { id: `chips-fork-${fork.id}`, title: `Chips space ${spaceId} \xB7 ${key}` });
|
|
25838
26987
|
const folder2 = this.folderOf(original, fork.agent_id, name2);
|
|
25839
26988
|
const copy = await chips2.agents.register({ key, name: forkName(name2), forkedFrom: fork.agent_id, spaceId });
|
|
25840
26989
|
if (session !== void 0) state2.sessions[`${copy.agentId}|${spaceId}`] ??= session;
|
|
@@ -25845,6 +26994,7 @@ var Bridge = class {
|
|
|
25845
26994
|
this.names.set(copy.agentId, copy.name);
|
|
25846
26995
|
const away = copy.attached === false && !copy.spaces.includes(spaceId);
|
|
25847
26996
|
this.log(`${copy.name}, a copy of ${name2}, made${away ? `, not in the room: ${copy.why ?? "too late"}` : ""}`);
|
|
26997
|
+
void this.agentsMoved();
|
|
25848
26998
|
}
|
|
25849
26999
|
/** An agent's name, from Chips's list when it isn't known yet. */
|
|
25850
27000
|
async nameOf(agentId2, otherwise) {
|
|
@@ -25863,11 +27013,11 @@ var Bridge = class {
|
|
|
25863
27013
|
*/
|
|
25864
27014
|
folderOf(agent, agentId2, name2) {
|
|
25865
27015
|
if (agent.config.folder !== void 0) return agent.config.folder;
|
|
25866
|
-
const { folders:
|
|
25867
|
-
if (
|
|
27016
|
+
const { folders: root2, state: state2 } = this.options;
|
|
27017
|
+
if (root2 === void 0) return void 0;
|
|
25868
27018
|
const kept = state2.folders?.[agentId2];
|
|
25869
|
-
const folder2 = kept ?? freeFolder(
|
|
25870
|
-
|
|
27019
|
+
const folder2 = kept ?? freeFolder(root2, name2, Object.values(state2.folders ?? {}));
|
|
27020
|
+
mkdirSync5(folder2, { recursive: true });
|
|
25871
27021
|
if (kept === void 0) {
|
|
25872
27022
|
state2.folders = { ...state2.folders, [agentId2]: folder2 };
|
|
25873
27023
|
this.options.saveState(state2);
|
|
@@ -25889,7 +27039,7 @@ var Bridge = class {
|
|
|
25889
27039
|
turn.project = { copy: project, name: name2 };
|
|
25890
27040
|
return facts;
|
|
25891
27041
|
} catch (error62) {
|
|
25892
|
-
this.log(`${name2}'s copy of the room's project couldn't be readied: ${
|
|
27042
|
+
this.log(`${name2}'s copy of the room's project couldn't be readied: ${messageOf2(error62)}`);
|
|
25893
27043
|
return void 0;
|
|
25894
27044
|
}
|
|
25895
27045
|
}
|
|
@@ -25908,8 +27058,8 @@ var Bridge = class {
|
|
|
25908
27058
|
const note = heldNote(pushed, name2);
|
|
25909
27059
|
return note === void 0 ? {} : { note };
|
|
25910
27060
|
} catch (error62) {
|
|
25911
|
-
this.log(`${name2}'s project couldn't be sent: ${
|
|
25912
|
-
const note = heldNote({ state: "failed", why:
|
|
27061
|
+
this.log(`${name2}'s project couldn't be sent: ${messageOf2(error62)}`);
|
|
27062
|
+
const note = heldNote({ state: "failed", why: messageOf2(error62) }, name2);
|
|
25913
27063
|
return note === void 0 ? {} : { note };
|
|
25914
27064
|
}
|
|
25915
27065
|
}
|
|
@@ -25918,8 +27068,8 @@ var Bridge = class {
|
|
|
25918
27068
|
for (const agent of this.agents) agent.up = await agent.client.healthy();
|
|
25919
27069
|
const down = this.agents.filter((agent) => !agent.up);
|
|
25920
27070
|
const chips2 = this.options.chips;
|
|
25921
|
-
const
|
|
25922
|
-
await
|
|
27071
|
+
const said2 = down.length === 0 ? chips2.reportOk() : chips2.reportTrouble(`Hermes isn't answering for ${down.map((agent) => agent.config.name).join(", ")}`);
|
|
27072
|
+
await said2.catch(() => void 0);
|
|
25923
27073
|
}
|
|
25924
27074
|
// ------------------------------------------------------------------ what is said to it
|
|
25925
27075
|
/** A message for some of its agents: each takes a turn in its room, one after another there. */
|
|
@@ -26014,9 +27164,10 @@ var Bridge = class {
|
|
|
26014
27164
|
try {
|
|
26015
27165
|
await this.turn(room, asked);
|
|
26016
27166
|
} catch (error62) {
|
|
26017
|
-
this.log(`a turn of ${room.agent.config.name} went wrong: ${
|
|
27167
|
+
this.log(`a turn of ${room.agent.config.name} went wrong: ${messageOf2(error62)}`);
|
|
26018
27168
|
} finally {
|
|
26019
27169
|
room.busy = false;
|
|
27170
|
+
void this.contexts?.refresh();
|
|
26020
27171
|
void this.pump(room);
|
|
26021
27172
|
}
|
|
26022
27173
|
}
|
|
@@ -26051,7 +27202,7 @@ var Bridge = class {
|
|
|
26051
27202
|
try {
|
|
26052
27203
|
session = await this.sessionFor(room);
|
|
26053
27204
|
} catch (error62) {
|
|
26054
|
-
await this.failed(turn, `${agent.config.name} couldn't open a session in Hermes: ${
|
|
27205
|
+
await this.failed(turn, `${agent.config.name} couldn't open a session in Hermes: ${messageOf2(error62)}`, true);
|
|
26055
27206
|
return;
|
|
26056
27207
|
}
|
|
26057
27208
|
const name2 = speakerId === void 0 ? agent.config.name : await this.nameOf(speakerId, agent.config.name);
|
|
@@ -26080,7 +27231,7 @@ var Bridge = class {
|
|
|
26080
27231
|
try {
|
|
26081
27232
|
turn.hermesRun = await agent.client.startRun({ text, sessionId: session, model: agent.config.model, key, instructions: instructionsFor(turn.folder, turn.project !== void 0) });
|
|
26082
27233
|
} catch (error62) {
|
|
26083
|
-
await this.failed(turn, `${agent.config.name} couldn't start in Hermes: ${
|
|
27234
|
+
await this.failed(turn, `${agent.config.name} couldn't start in Hermes: ${messageOf2(error62)}`, true);
|
|
26084
27235
|
return void 0;
|
|
26085
27236
|
}
|
|
26086
27237
|
if (turn.cancelled) await agent.client.stop(turn.hermesRun).catch(() => void 0);
|
|
@@ -26088,7 +27239,7 @@ var Bridge = class {
|
|
|
26088
27239
|
const last = await agent.client.events(turn.hermesRun, (event) => this.heard(turn, event));
|
|
26089
27240
|
return last ?? await this.endOf(turn);
|
|
26090
27241
|
} catch (error62) {
|
|
26091
|
-
this.log(`lost ${agent.config.name}'s run ${turn.hermesRun}: ${
|
|
27242
|
+
this.log(`lost ${agent.config.name}'s run ${turn.hermesRun}: ${messageOf2(error62)}`);
|
|
26092
27243
|
return await this.endOf(turn);
|
|
26093
27244
|
}
|
|
26094
27245
|
}
|
|
@@ -26101,7 +27252,7 @@ var Bridge = class {
|
|
|
26101
27252
|
if (status !== void 0 && TERMINAL.has(`run.${status}`)) {
|
|
26102
27253
|
return { ...state2, event: `run.${status}` };
|
|
26103
27254
|
}
|
|
26104
|
-
await new Promise((
|
|
27255
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
26105
27256
|
}
|
|
26106
27257
|
await this.failed(turn, `${turn.agent.config.name}'s run in Hermes stopped answering.`, true);
|
|
26107
27258
|
return void 0;
|
|
@@ -26111,7 +27262,7 @@ var Bridge = class {
|
|
|
26111
27262
|
const kept = this.options.state.sessions[id2];
|
|
26112
27263
|
if (kept !== void 0) return kept;
|
|
26113
27264
|
const who = id2.slice(0, id2.lastIndexOf("|"));
|
|
26114
|
-
const made = await agent.client.newSession(`Chips
|
|
27265
|
+
const made = await agent.client.newSession(`Chips space ${spaceId}${who === agent.key ? "" : ` \xB7 ${who}`}`);
|
|
26115
27266
|
this.options.state.sessions[id2] = made;
|
|
26116
27267
|
this.options.saveState(this.options.state);
|
|
26117
27268
|
return made;
|
|
@@ -26133,7 +27284,7 @@ var Bridge = class {
|
|
|
26133
27284
|
await turn.agent.client.steer(turn.hermesRun, text);
|
|
26134
27285
|
return;
|
|
26135
27286
|
} catch (error62) {
|
|
26136
|
-
this.log(`Hermes didn't take a steer mid-run (${
|
|
27287
|
+
this.log(`Hermes didn't take a steer mid-run (${messageOf2(error62)}); saying it next`);
|
|
26137
27288
|
}
|
|
26138
27289
|
}
|
|
26139
27290
|
turn.next.push(text);
|
|
@@ -26182,11 +27333,11 @@ var Bridge = class {
|
|
|
26182
27333
|
const stepId = callId ?? `${tool}-${turn.counted}`;
|
|
26183
27334
|
const lane = callId ?? (sub ? "subagent" : `tool:${tool}`);
|
|
26184
27335
|
turn.open.set(lane, [...turn.open.get(lane) ?? [], stepId]);
|
|
26185
|
-
const
|
|
27336
|
+
const said2 = sub ? event["goal"] : event["preview"];
|
|
26186
27337
|
await turn.run.step({
|
|
26187
27338
|
stepId,
|
|
26188
27339
|
status: "started",
|
|
26189
|
-
title: `${tool}${typeof
|
|
27340
|
+
title: `${tool}${typeof said2 === "string" && said2 !== "" ? ` \xB7 ${said2}` : ""}`.slice(0, 300),
|
|
26190
27341
|
tool,
|
|
26191
27342
|
...KINDS[tool] === void 0 ? {} : { kind: KINDS[tool] }
|
|
26192
27343
|
}).catch(() => void 0);
|
|
@@ -26241,7 +27392,7 @@ var Bridge = class {
|
|
|
26241
27392
|
${command}
|
|
26242
27393
|
\`\`\`` } }
|
|
26243
27394
|
});
|
|
26244
|
-
const stopped2 = new Promise((
|
|
27395
|
+
const stopped2 = new Promise((resolve6) => turn.run.on("cancel", () => resolve6(void 0)));
|
|
26245
27396
|
const decision = await Promise.race([asked, stopped2]);
|
|
26246
27397
|
const pending = turn.approvals.get(requestId ?? approvalId);
|
|
26247
27398
|
if (decision === void 0 || pending === void 0 || pending.answered) return;
|
|
@@ -26252,7 +27403,7 @@ ${command}
|
|
|
26252
27403
|
choice: choiceOf(decision, choices),
|
|
26253
27404
|
...requestId === void 0 ? {} : { request_id: requestId },
|
|
26254
27405
|
...typeof generation === "number" ? { execution_generation: generation } : {}
|
|
26255
|
-
}).catch((error62) => this.log(`Hermes didn't take the answer: ${
|
|
27406
|
+
}).catch((error62) => this.log(`Hermes didn't take the answer: ${messageOf2(error62)}`));
|
|
26256
27407
|
}
|
|
26257
27408
|
/** An approval answered in Hermes's own tools: settled in Chips, once, so the card cools (ADR 0079). */
|
|
26258
27409
|
async settled(turn, event) {
|
|
@@ -26297,10 +27448,27 @@ ${command}
|
|
|
26297
27448
|
if (reply !== "" || post.uploadIds.length > 0) {
|
|
26298
27449
|
await turn.speaker.post({ spaceId: run2.spaceId, runId: run2.id, text: reply, uploadIds: post.uploadIds, change });
|
|
26299
27450
|
}
|
|
26300
|
-
const
|
|
26301
|
-
const summary =
|
|
27451
|
+
const said2 = post.summary;
|
|
27452
|
+
const summary = said2 === "" ? "Done." : said2.length <= 2e3 ? said2 : `${said2.slice(0, 1990)}\u2026`;
|
|
26302
27453
|
await run2.complete({ summary });
|
|
26303
27454
|
this.log(`${agent.config.name} answered in ${run2.spaceId}`);
|
|
27455
|
+
this.missingReadings();
|
|
27456
|
+
}
|
|
27457
|
+
/**
|
|
27458
|
+
* A turn ended and Hermes sent no reading at all since the bridge started: its gateway most
|
|
27459
|
+
* likely started before Chips's webhook was in its config. Said once, a little after, since a
|
|
27460
|
+
* reading comes a moment after its model call.
|
|
27461
|
+
*/
|
|
27462
|
+
missingReadings() {
|
|
27463
|
+
if (this.fills === void 0 || this.fillsPort === void 0 || this.fills.anyHeard || this.toldNoReadings) return;
|
|
27464
|
+
this.toldNoReadings = true;
|
|
27465
|
+
setTimeout(() => {
|
|
27466
|
+
if (this.fills?.anyHeard === false) {
|
|
27467
|
+
this.log(
|
|
27468
|
+
"context window: Hermes sent no reading after a turn. Start hermes gateway again once, so it takes Chips's webhook from its config."
|
|
27469
|
+
);
|
|
27470
|
+
}
|
|
27471
|
+
}, 1e4).unref();
|
|
26304
27472
|
}
|
|
26305
27473
|
/** A run that failed, was stopped or was cut short in Hermes. */
|
|
26306
27474
|
async ended(turn, last) {
|
|
@@ -26359,7 +27527,7 @@ function idOf(event) {
|
|
|
26359
27527
|
const id2 = event["request_id"] ?? event["prompt_id"];
|
|
26360
27528
|
return typeof id2 === "string" && id2 !== "" ? id2 : void 0;
|
|
26361
27529
|
}
|
|
26362
|
-
function
|
|
27530
|
+
function messageOf2(error62) {
|
|
26363
27531
|
if (error62 instanceof HermesError) return `${error62.message} (${error62.status})`;
|
|
26364
27532
|
return error62 instanceof Error ? error62.message : String(error62);
|
|
26365
27533
|
}
|
|
@@ -26375,11 +27543,11 @@ function goalOf(message, name2) {
|
|
|
26375
27543
|
var REPLY_SHOWN = 1500;
|
|
26376
27544
|
var GOAL_SHOWN = 120;
|
|
26377
27545
|
function lookAgain(who, reply, note) {
|
|
26378
|
-
const
|
|
27546
|
+
const said2 = reply?.trim() ?? "";
|
|
26379
27547
|
return [
|
|
26380
|
-
`${who} says your reply in this
|
|
26381
|
-
...
|
|
26382
|
-
\xAB${cut(
|
|
27548
|
+
`${who} says your reply in this space wasn't right.`,
|
|
27549
|
+
...said2 === "" ? [] : [`Your reply:
|
|
27550
|
+
\xAB${cut(said2, REPLY_SHOWN)}\xBB`],
|
|
26383
27551
|
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
27552
|
"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
27553
|
].join("\n\n");
|
|
@@ -26401,8 +27569,8 @@ function resultWords(preview2) {
|
|
|
26401
27569
|
}
|
|
26402
27570
|
if (typeof result !== "object" || result === null || !("output" in result)) return preview2;
|
|
26403
27571
|
const { output: output2, error: error62 } = result;
|
|
26404
|
-
const
|
|
26405
|
-
return typeof
|
|
27572
|
+
const said2 = [output2, error62].find((each) => typeof each === "string" && each.trim() !== "");
|
|
27573
|
+
return typeof said2 === "string" ? said2.trim() : "";
|
|
26406
27574
|
}
|
|
26407
27575
|
function lastLine(text) {
|
|
26408
27576
|
const lines2 = text.split(/(?<=[.!?])\s+|\n+/).map((line) => line.trim()).filter((line) => line !== "");
|
|
@@ -26413,16 +27581,16 @@ function lowerFirst(text) {
|
|
|
26413
27581
|
}
|
|
26414
27582
|
|
|
26415
27583
|
// src/keep.ts
|
|
26416
|
-
import { homedir as
|
|
26417
|
-
import { basename as
|
|
27584
|
+
import { homedir as homedir5 } from "node:os";
|
|
27585
|
+
import { basename as basename4, join as join9, resolve as resolve3 } from "node:path";
|
|
26418
27586
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
26419
27587
|
var flags = process.argv.slice(2);
|
|
26420
27588
|
var words3 = flags.filter((word) => !word.startsWith("--"));
|
|
26421
|
-
var whole = (word) =>
|
|
27589
|
+
var whole = (word) => resolve3(word.startsWith("~/") ? join9(homedir5(), word.slice(2)) : word);
|
|
26422
27590
|
var here = fileURLToPath2(new URL("..", import.meta.url)).replace(/\/$/, "");
|
|
26423
27591
|
var self = fileURLToPath2(import.meta.url);
|
|
26424
27592
|
var run = self.endsWith(".js") ? [self] : ["--import", "tsx", "src/main.ts"];
|
|
26425
|
-
var again = process.env["npm_lifecycle_event"] === "keep" ? "npm run keep --" :
|
|
27593
|
+
var again = process.env["npm_lifecycle_event"] === "keep" ? "npm run keep --" : basename4(process.argv[1] ?? "chips");
|
|
26426
27594
|
var stopLine = process.env["CHIPS_STOP_COMMAND"] || [again, ...words3, "--stop"].join(" ");
|
|
26427
27595
|
var end = (line, code) => {
|
|
26428
27596
|
(code === 0 ? process.stdout : process.stderr).write(`${line}
|
|
@@ -26464,9 +27632,9 @@ function keepIfAsked(label2, url2, names, log2, before = () => void 0) {
|
|
|
26464
27632
|
}
|
|
26465
27633
|
|
|
26466
27634
|
// src/setup.ts
|
|
26467
|
-
import { existsSync as
|
|
26468
|
-
import { homedir as
|
|
26469
|
-
import { resolve as
|
|
27635
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
|
|
27636
|
+
import { homedir as homedir6 } from "node:os";
|
|
27637
|
+
import { join as join10, resolve as resolve4 } from "node:path";
|
|
26470
27638
|
var SetupError = class extends Error {
|
|
26471
27639
|
constructor(message, words4 = {}) {
|
|
26472
27640
|
super(message);
|
|
@@ -26475,37 +27643,70 @@ var SetupError = class extends Error {
|
|
|
26475
27643
|
words;
|
|
26476
27644
|
name = "SetupError";
|
|
26477
27645
|
};
|
|
26478
|
-
var shown = (path) => path.startsWith(`${
|
|
27646
|
+
var shown = (path) => path.startsWith(`${homedir6()}/`) ? `~${path.slice(homedir6().length)}` : path;
|
|
26479
27647
|
var portOf = (url2) => {
|
|
26480
27648
|
const parsed = new URL(url2);
|
|
26481
27649
|
return parsed.port || (parsed.protocol === "https:" ? "443" : "80");
|
|
26482
27650
|
};
|
|
26483
|
-
function checkedWords(config3) {
|
|
27651
|
+
function checkedWords(config3, restart = false) {
|
|
26484
27652
|
const urls = new Set(config3.agents.map((agent) => agent.address.url));
|
|
26485
27653
|
const [url2] = urls;
|
|
26486
|
-
|
|
27654
|
+
const found = urls.size === 1 && url2 !== void 0 ? `Its API server answers on port ${portOf(url2)}` : `Its API server answers for each of ${config3.agents.length} agents`;
|
|
27655
|
+
return restart ? `${found}. ${RESTART}` : found;
|
|
27656
|
+
}
|
|
27657
|
+
var RESTART = "Start hermes gateway again once, so it tells Chips how full each agent's context is.";
|
|
27658
|
+
function readyHooks(config3, options) {
|
|
27659
|
+
const lines2 = [];
|
|
27660
|
+
let restart = false;
|
|
27661
|
+
const done = /* @__PURE__ */ new Set();
|
|
27662
|
+
for (const agent of config3.agents) {
|
|
27663
|
+
const profile = profileName(agent.address.profile);
|
|
27664
|
+
if (done.has(profile)) continue;
|
|
27665
|
+
done.add(profile);
|
|
27666
|
+
const home2 = profileHome(options.root, agent.address.profile);
|
|
27667
|
+
let outcome;
|
|
27668
|
+
try {
|
|
27669
|
+
outcome = addHook(home2, options.port, profile, options.secret);
|
|
27670
|
+
} catch (error62) {
|
|
27671
|
+
lines2.push(`Chips's webhook wasn't added to ${shown(join10(home2, "config.yaml"))}: ${error62 instanceof Error ? error62.message : String(error62)}`);
|
|
27672
|
+
continue;
|
|
27673
|
+
}
|
|
27674
|
+
if (outcome.state === "added") {
|
|
27675
|
+
lines2.push(`Added Chips's webhook to ${shown(outcome.path)}, so Hermes tells Chips how full each agent's context is.`);
|
|
27676
|
+
restart ||= options.gatewayUp;
|
|
27677
|
+
} else if (outcome.state === "by_hand") {
|
|
27678
|
+
lines2.push(
|
|
27679
|
+
`${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:
|
|
27680
|
+
${outcome.lines}`
|
|
27681
|
+
);
|
|
27682
|
+
} else if (outcome.state === "no_home") {
|
|
27683
|
+
lines2.push(`Hermes has no ${profile} profile at ${shown(home2)}, so no webhook was added for it.`);
|
|
27684
|
+
}
|
|
27685
|
+
}
|
|
27686
|
+
if (restart) lines2.push(RESTART);
|
|
27687
|
+
return { lines: lines2, restart };
|
|
26487
27688
|
}
|
|
26488
27689
|
var STILL_GOOD = "The link is still good: run the same command again once that's done.";
|
|
26489
27690
|
var EXAMPLE = '{ "agents": [{ "name": "Hermes", "profile": "default", "keyEnv": "API_SERVER_KEY" }] }';
|
|
26490
27691
|
var PLAIN = { agents: [{ name: "Hermes", profile: "default", keyEnv: "API_SERVER_KEY" }] };
|
|
26491
27692
|
function hermesEnvPath(env = process.env) {
|
|
26492
|
-
return
|
|
27693
|
+
return resolve4(env["HERMES_HOME"] ?? resolve4(homedir6(), ".hermes"), ".env");
|
|
26493
27694
|
}
|
|
26494
27695
|
function fromHermes(name2, env) {
|
|
26495
27696
|
const path = hermesEnvPath(env);
|
|
26496
|
-
if (!
|
|
26497
|
-
for (const line of
|
|
27697
|
+
if (!existsSync8(path)) return void 0;
|
|
27698
|
+
for (const line of readFileSync8(path, "utf8").split("\n")) {
|
|
26498
27699
|
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(line);
|
|
26499
27700
|
if (match?.[1] === name2) return match[2]?.replace(/^(["'])(.*)\1$/, "$2");
|
|
26500
27701
|
}
|
|
26501
27702
|
return void 0;
|
|
26502
27703
|
}
|
|
26503
|
-
function folderOf(
|
|
26504
|
-
if (typeof
|
|
26505
|
-
return
|
|
27704
|
+
function folderOf(said2) {
|
|
27705
|
+
if (typeof said2 !== "string" || said2 === "") return void 0;
|
|
27706
|
+
return resolve4(said2 === "~" || said2.startsWith("~/") ? homedir6() + said2.slice(1) : said2);
|
|
26506
27707
|
}
|
|
26507
27708
|
function readConfig(path, env = process.env) {
|
|
26508
|
-
const raw =
|
|
27709
|
+
const raw = existsSync8(path) ? JSON.parse(readFileSync8(path, "utf8")) : PLAIN;
|
|
26509
27710
|
const url2 = typeof raw.url === "string" ? raw.url : "http://127.0.0.1:8642";
|
|
26510
27711
|
const folder2 = folderOf(raw.folder);
|
|
26511
27712
|
if (!Array.isArray(raw.agents) || raw.agents.length === 0) {
|
|
@@ -26540,10 +27741,12 @@ function readConfig(path, env = process.env) {
|
|
|
26540
27741
|
...own2 === void 0 ? {} : { folder: own2 }
|
|
26541
27742
|
};
|
|
26542
27743
|
});
|
|
27744
|
+
const hookPort = raw.hookPort;
|
|
26543
27745
|
return {
|
|
26544
27746
|
...typeof raw.name === "string" ? { name: raw.name } : {},
|
|
26545
27747
|
agents,
|
|
26546
|
-
folders: folderOf(raw.folders) ??
|
|
27748
|
+
folders: folderOf(raw.folders) ?? resolve4(homedir6(), "chips-hermes", "agents"),
|
|
27749
|
+
...typeof hookPort === "number" && Number.isInteger(hookPort) && hookPort > 0 && hookPort < 65536 ? { hookPort } : {}
|
|
26547
27750
|
};
|
|
26548
27751
|
}
|
|
26549
27752
|
async function checkHermes(config3, clientFor = (agent) => new HermesClient(agent.address)) {
|
|
@@ -26574,19 +27777,46 @@ async function checkHermes(config3, clientFor = (agent) => new HermesClient(agen
|
|
|
26574
27777
|
}
|
|
26575
27778
|
|
|
26576
27779
|
// src/main.ts
|
|
26577
|
-
var home = (path) => path.startsWith("~/") ?
|
|
27780
|
+
var home = (path) => path.startsWith("~/") ? join11(homedir7(), path.slice(2)) : path;
|
|
26578
27781
|
var link = process.env["CHIPS_JOIN_LINK"] || void 0;
|
|
26579
27782
|
var LABEL = "chat.chips.hermes";
|
|
26580
27783
|
stopIfAsked(LABEL);
|
|
26581
|
-
var configPath =
|
|
27784
|
+
var configPath = resolve5(home(words3[0] ?? "~/.chips-hermes/config.json"));
|
|
27785
|
+
var folder = dirname5(configPath);
|
|
27786
|
+
var statePath = join11(folder, "sessions.json");
|
|
27787
|
+
var state = existsSync9(statePath) ? JSON.parse(readFileSync9(statePath, "utf8")) : { sessions: {} };
|
|
27788
|
+
var saveState = (next) => {
|
|
27789
|
+
mkdirSync6(folder, { recursive: true, mode: 448 });
|
|
27790
|
+
writeFileSync5(statePath, `${JSON.stringify(next, null, 2)}
|
|
27791
|
+
`, { mode: 384 });
|
|
27792
|
+
};
|
|
27793
|
+
var log = (line) => process.stdout.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
27794
|
+
`);
|
|
27795
|
+
var freePort = () => new Promise((done, failed) => {
|
|
27796
|
+
const server = createServer2();
|
|
27797
|
+
server.once("error", failed);
|
|
27798
|
+
server.listen(0, "127.0.0.1", () => {
|
|
27799
|
+
const address = server.address();
|
|
27800
|
+
server.close(() => done(typeof address === "object" && address !== null ? address.port : 0));
|
|
27801
|
+
});
|
|
27802
|
+
});
|
|
26582
27803
|
var config2;
|
|
27804
|
+
var root = hermesRoot();
|
|
27805
|
+
var fills = state.fills ??= {};
|
|
26583
27806
|
try {
|
|
26584
27807
|
config2 = readConfig(configPath);
|
|
26585
27808
|
if (link !== void 0) {
|
|
26586
27809
|
reportStep("checking", { detail: "Looking for its API server" });
|
|
26587
27810
|
await checkHermes(config2);
|
|
26588
|
-
reportStep("checked", { detail: checkedWords(config2) });
|
|
26589
27811
|
}
|
|
27812
|
+
fills.secret ??= randomBytes2(32).toString("hex");
|
|
27813
|
+
fills.port = config2.hookPort ?? fills.port ?? hookPortIn(profileHome(root, config2.agents[0]?.address.profile)) ?? await freePort();
|
|
27814
|
+
saveState(state);
|
|
27815
|
+
const first = config2.agents[0];
|
|
27816
|
+
const gatewayUp = link !== void 0 || first !== void 0 && await new HermesClient(first.address).healthy();
|
|
27817
|
+
const hooks = readyHooks(config2, { root, port: fills.port, secret: fills.secret, gatewayUp });
|
|
27818
|
+
for (const line of hooks.lines) log(line);
|
|
27819
|
+
if (link !== void 0) reportStep("checked", { detail: checkedWords(config2, hooks.restart) });
|
|
26590
27820
|
} catch (error62) {
|
|
26591
27821
|
if (!(error62 instanceof SetupError)) throw error62;
|
|
26592
27822
|
reportStep("failed", error62.words);
|
|
@@ -26595,10 +27825,6 @@ try {
|
|
|
26595
27825
|
process.exit(1);
|
|
26596
27826
|
}
|
|
26597
27827
|
var name = config2.name ?? "Hermes";
|
|
26598
|
-
var folder = dirname4(configPath);
|
|
26599
|
-
var statePath = join7(folder, "sessions.json");
|
|
26600
|
-
var log = (line) => process.stdout.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
26601
|
-
`);
|
|
26602
27828
|
var refused2 = (error62) => {
|
|
26603
27829
|
if (!(error62 instanceof JoinError)) throw error62;
|
|
26604
27830
|
process.stderr.write(`${error62.message}
|
|
@@ -26614,25 +27840,27 @@ var joined = await join3({
|
|
|
26614
27840
|
onCode: (code) => log(`Allow this harness in Chips: ${code}`)
|
|
26615
27841
|
}).catch(refused2);
|
|
26616
27842
|
keepIfAsked(LABEL, joined.url, ["HERMES_HOME", ...config2.agents.map((agent) => agent.keyEnv ?? "API_SERVER_KEY")], log);
|
|
26617
|
-
var
|
|
27843
|
+
var bridge;
|
|
26618
27844
|
var chips = await connect({
|
|
26619
27845
|
url: joined.url,
|
|
26620
27846
|
token: joined.token,
|
|
26621
27847
|
client: { name: "chips-hermes", version: "1.0.0" },
|
|
26622
|
-
store: fileStore(
|
|
27848
|
+
store: fileStore(join11(folder, "chips-state.json")),
|
|
27849
|
+
settings: settingsFor(() => bridge)
|
|
26623
27850
|
});
|
|
26624
27851
|
var git = await checkGit();
|
|
26625
27852
|
if (!git.ok) log(`rooms' projects are off: ${git.why}`);
|
|
26626
|
-
|
|
27853
|
+
bridge = new Bridge({
|
|
26627
27854
|
folders: config2.folders,
|
|
26628
27855
|
projects: git.ok,
|
|
26629
27856
|
chips,
|
|
26630
27857
|
agents: config2.agents,
|
|
26631
27858
|
home: joined.spaceId,
|
|
26632
27859
|
state,
|
|
26633
|
-
saveState
|
|
26634
|
-
|
|
26635
|
-
|
|
27860
|
+
saveState,
|
|
27861
|
+
log,
|
|
27862
|
+
hermesRoot: root,
|
|
27863
|
+
fills: { port: fills.port, secret: fills.secret }
|
|
26636
27864
|
});
|
|
26637
27865
|
await bridge.start();
|
|
26638
27866
|
log(`answering for ${config2.agents.map((agent) => agent.name).join(", ")} through Hermes`);
|
|
@@ -26646,7 +27874,7 @@ await helloOnce(joined, {
|
|
|
26646
27874
|
}).catch((error62) => log(`hello not said: ${String(error62)}`));
|
|
26647
27875
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
26648
27876
|
process.on(signal, () => {
|
|
26649
|
-
bridge
|
|
27877
|
+
bridge?.stop();
|
|
26650
27878
|
chips.close();
|
|
26651
27879
|
process.exit(0);
|
|
26652
27880
|
});
|