@chipschat/hermes 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -9,10 +9,10 @@ var __export = (target, all) => {
9
9
 
10
10
  // src/main.ts
11
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";
12
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
13
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
+ import { homedir as homedir8 } from "node:os";
15
+ import { dirname as dirname6, join as join12, resolve as resolve6 } from "node:path";
16
16
 
17
17
  // node_modules/zod/v4/classic/external.js
18
18
  var external_exports = {};
@@ -2024,8 +2024,8 @@ function emoji() {
2024
2024
  }
2025
2025
  var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
2026
2026
  var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
2027
- var mac = (delimiter2) => {
2028
- const escapedDelim = escapeRegex(delimiter2 ?? ":");
2027
+ var mac = (delimiter3) => {
2028
+ const escapedDelim = escapeRegex(delimiter3 ?? ":");
2029
2029
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
2030
2030
  };
2031
2031
  var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -2599,8 +2599,8 @@ var Doc = class {
2599
2599
  const lines2 = content.split("\n").filter((x) => x);
2600
2600
  const minIndent = Math.min(...lines2.map((x) => x.length - x.trimStart().length));
2601
2601
  const dedented = lines2.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x);
2602
- for (const line of dedented) {
2603
- this.content.push(line);
2602
+ for (const line2 of dedented) {
2603
+ this.content.push(line2);
2604
2604
  }
2605
2605
  }
2606
2606
  compile() {
@@ -5081,7 +5081,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
5081
5081
  var NONE = 0;
5082
5082
  var ASSUMED = 1;
5083
5083
  var PROVEN = 2;
5084
- function isRecursive(inst, stack, resolve6) {
5084
+ function isRecursive(inst, stack, resolve7) {
5085
5085
  const cached2 = recursive.get(inst);
5086
5086
  if (cached2 !== void 0)
5087
5087
  return cached2 ? PROVEN : NONE;
@@ -5091,7 +5091,7 @@ function isRecursive(inst, stack, resolve6) {
5091
5091
  let result = NONE;
5092
5092
  const check2 = (child) => {
5093
5093
  if (result !== PROVEN && child?._zod) {
5094
- const answer = isRecursive(child, stack, resolve6);
5094
+ const answer = isRecursive(child, stack, resolve7);
5095
5095
  if (answer > result)
5096
5096
  result = answer;
5097
5097
  }
@@ -5102,7 +5102,7 @@ function isRecursive(inst, stack, resolve6) {
5102
5102
  const desc = Object.getOwnPropertyDescriptor(sh, key);
5103
5103
  if (spread && !desc.enumerable)
5104
5104
  continue;
5105
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve6) : NONE;
5105
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve7) : NONE;
5106
5106
  if (child > answer)
5107
5107
  answer = child;
5108
5108
  }
@@ -5166,7 +5166,7 @@ function isRecursive(inst, stack, resolve6) {
5166
5166
  break;
5167
5167
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
5168
5168
  case "lazy": {
5169
- const inner = def._cachedInner ?? (resolve6 ? inst._zod.innerType : void 0);
5169
+ const inner = def._cachedInner ?? (resolve7 ? inst._zod.innerType : void 0);
5170
5170
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
5171
5171
  break;
5172
5172
  }
@@ -20535,31 +20535,31 @@ function withRole(name2, from) {
20535
20535
  function flat(text) {
20536
20536
  return text.replace(/\s*\n\s*/g, " ").trim();
20537
20537
  }
20538
- function wordsOf(line) {
20539
- const text = flat(line.text ?? "");
20540
- const heard = line.transcript === void 0 ? "" : flat(line.transcript);
20538
+ function wordsOf(line2) {
20539
+ const text = flat(line2.text ?? "");
20540
+ const heard = line2.transcript === void 0 ? "" : flat(line2.transcript);
20541
20541
  const parts = [text];
20542
20542
  if (heard !== "")
20543
20543
  parts.push(text === "" ? `(voice note) ${heard}` : `(voice note: ${heard})`);
20544
- if (heard === "" && line.files !== void 0)
20545
- parts.push(`(sent ${line.files.join(", ")})`);
20546
- if (line.held === true)
20544
+ if (heard === "" && line2.files !== void 0)
20545
+ parts.push(`(sent ${line2.files.join(", ")})`);
20546
+ if (line2.held === true)
20547
20547
  parts.push("(held by Chips until a person acts)");
20548
20548
  return parts.filter((part) => part !== "").join(" ");
20549
20549
  }
20550
- function renderLine(line) {
20551
- const name2 = line.from.name;
20552
- switch (line.kind) {
20550
+ function renderLine(line2) {
20551
+ const name2 = line2.from.name;
20552
+ switch (line2.kind) {
20553
20553
  case "said":
20554
- return `${withRole(name2, line.from)}: ${wordsOf(line)}`;
20554
+ return `${withRole(name2, line2.from)}: ${wordsOf(line2)}`;
20555
20555
  case "joined":
20556
20556
  return `(${name2} joined the space)`;
20557
20557
  case "left":
20558
20558
  return `(${name2} left the space)`;
20559
20559
  case "renamed":
20560
- if (line.title !== void 0)
20561
- return `(${name2} renamed the space "${line.title}")`;
20562
- return `(${line.previous ?? name2} is now called ${line.name ?? name2})`;
20560
+ if (line2.title !== void 0)
20561
+ return `(${name2} renamed the space "${line2.title}")`;
20562
+ return `(${line2.previous ?? name2} is now called ${line2.name ?? name2})`;
20563
20563
  case "archived":
20564
20564
  return `(${name2} archived the space)`;
20565
20565
  default:
@@ -20610,7 +20610,7 @@ function quoteLines(replying) {
20610
20610
  body.push(`(sent ${replying.files.join(", ")})`);
20611
20611
  }
20612
20612
  }
20613
- return body.map((line) => line === "" ? ">" : `> ${line}`);
20613
+ return body.map((line2) => line2 === "" ? ">" : `> ${line2}`);
20614
20614
  }
20615
20615
  var DAY_MS = 864e5;
20616
20616
  function saidWhen(at, reading) {
@@ -22517,7 +22517,7 @@ var AGENT_FIRST_NAMES = [
22517
22517
  "Xander Xavi Xavier",
22518
22518
  "Yann Yaro Yosef Yuri Yusuf Yves",
22519
22519
  "Zaid Zane Zeke Zeno Zev Ziggy"
22520
- ].flatMap((line) => line.split(" "));
22520
+ ].flatMap((line2) => line2.split(" "));
22521
22521
 
22522
22522
  // node_modules/@chipschat/protocol/dist/src/frames.js
22523
22523
  var clientHelloFrame = external_exports.object({
@@ -22846,8 +22846,8 @@ function createOutbox() {
22846
22846
  const waiting = /* @__PURE__ */ new Map();
22847
22847
  return {
22848
22848
  add(key, event) {
22849
- return new Promise((resolve6, reject) => {
22850
- waiting.set(key, { event, resolve: resolve6, reject });
22849
+ return new Promise((resolve7, reject) => {
22850
+ waiting.set(key, { event, resolve: resolve7, reject });
22851
22851
  });
22852
22852
  },
22853
22853
  unanswered: () => [...waiting.values()].map((entry) => entry.event),
@@ -22923,7 +22923,7 @@ async function inPieces(call, path, bytes) {
22923
22923
  async function afterLoss(error62, failures) {
22924
22924
  if (error62 instanceof RefusedError || failures >= PIECE_TRIES)
22925
22925
  throw error62;
22926
- await new Promise((resolve6) => setTimeout(resolve6, 250 * failures));
22926
+ await new Promise((resolve7) => setTimeout(resolve7, 250 * failures));
22927
22927
  }
22928
22928
  function sendPiece(call, path, bytes, offset) {
22929
22929
  const piece = bytes.subarray(offset, Math.min(offset + PIECE_BYTES, bytes.byteLength));
@@ -22949,16 +22949,16 @@ var TRIES = 4;
22949
22949
  var MARKS = /^(<<<<<<<|>>>>>>>) /m;
22950
22950
  var REJECTED = /\[rejected\]|fetch first|non-fast-forward/;
22951
22951
  var OWN_CONFIG = /^(core\.(repositoryformatversion|filemode|bare|logallrefupdates|ignorecase|precomposeunicode|symlinks)|remote\.origin\.(url|fetch)|branch\..+\.(remote|merge)|user\.(name|email))$/;
22952
- var lines = (said2) => said2.split("\n").filter((line) => line !== "");
22952
+ var lines = (said2) => said2.split("\n").filter((line2) => line2 !== "");
22953
22953
  var unique = (names) => [...new Set(names)];
22954
22954
  function subjectOf(words4) {
22955
- const first = words4.split("\n").find((line) => line.trim() !== "")?.trim() ?? "";
22955
+ const first = words4.split("\n").find((line2) => line2.trim() !== "")?.trim() ?? "";
22956
22956
  if (first === "")
22957
22957
  return "A change to the project";
22958
22958
  return first.length <= 72 ? first : `${first.slice(0, 71)}\u2026`;
22959
22959
  }
22960
22960
  function remoteWords(err) {
22961
- const said2 = lines(err).filter((line) => line.startsWith("remote: ")).map((line) => line.slice("remote: ".length).trim()).filter((line) => line !== "" && !/^error: hook declined/.test(line));
22961
+ const said2 = lines(err).filter((line2) => line2.startsWith("remote: ")).map((line2) => line2.slice("remote: ".length).trim()).filter((line2) => line2 !== "" && !/^error: hook declined/.test(line2));
22962
22962
  return said2.length > 0 ? said2.join(" ") : lines(err)[0] ?? "git said nothing";
22963
22963
  }
22964
22964
  function versionIn(said2) {
@@ -23007,13 +23007,13 @@ function heldNote(pushed, name2) {
23007
23007
  }
23008
23008
  async function checkGit(git2 = "git", path = process.env["PATH"] ?? "/usr/bin:/bin") {
23009
23009
  const [major, minor] = versionIn(PROJECT_GIT_VERSION) ?? [0, 0];
23010
- return new Promise((resolve6) => {
23010
+ return new Promise((resolve7) => {
23011
23011
  execFile(git2, ["--version"], { env: { PATH: path } }, (error62, stdout) => {
23012
23012
  if (error62 !== null)
23013
- return resolve6({ ok: false, why: "git was not found on this machine" });
23013
+ return resolve7({ ok: false, why: "git was not found on this machine" });
23014
23014
  const mine = versionIn(stdout);
23015
23015
  const enough = mine !== void 0 && (mine[0] > major || mine[0] === major && mine[1] >= minor);
23016
- resolve6(enough ? { ok: true, version: mine.join(".") } : { ok: false, why: `${stdout.trim()} is older than ${PROJECT_GIT_VERSION}` });
23016
+ resolve7(enough ? { ok: true, version: mine.join(".") } : { ok: false, why: `${stdout.trim()} is older than ${PROJECT_GIT_VERSION}` });
23017
23017
  });
23018
23018
  });
23019
23019
  }
@@ -23142,8 +23142,8 @@ var Project = class {
23142
23142
  async standing(remote) {
23143
23143
  const mine = await this.head("HEAD");
23144
23144
  const base = await this.baseOf(mine, remote);
23145
- const counted = lines((await this.git(["diff", "--numstat", "--no-renames", "-z", base])).out.replace(/\0/g, "\n")).map((line) => {
23146
- const [added2 = "0", removed = "0", ...path] = line.split(" ");
23145
+ const counted = lines((await this.git(["diff", "--numstat", "--no-renames", "-z", base])).out.replace(/\0/g, "\n")).map((line2) => {
23146
+ const [added2 = "0", removed = "0", ...path] = line2.split(" ");
23147
23147
  return { path: path.join(" "), added: Number(added2) || 0, removed: Number(removed) || 0 };
23148
23148
  });
23149
23149
  const added = lines((await this.git(["ls-files", "--others", "--exclude-standard", "-z"])).out.replace(/\0/g, "\n"));
@@ -23189,8 +23189,8 @@ var Project = class {
23189
23189
  async markedLines(path) {
23190
23190
  try {
23191
23191
  const all = (await readFile(join(this.copy, path), "utf8")).split("\n");
23192
- const start = all.findIndex((line) => line.startsWith("<<<<<<< "));
23193
- const end2 = all.findIndex((line, at) => at > start && line.startsWith(">>>>>>> "));
23192
+ const start = all.findIndex((line2) => line2.startsWith("<<<<<<< "));
23193
+ const end2 = all.findIndex((line2, at) => at > start && line2.startsWith(">>>>>>> "));
23194
23194
  if (start < 0 || end2 < 0)
23195
23195
  return void 0;
23196
23196
  const shown2 = all.slice(Math.max(0, start - 1), end2 + 2);
@@ -23459,8 +23459,8 @@ ${sent.err}`;
23459
23459
  env[`GIT_CONFIG_KEY_${at}`] = key;
23460
23460
  env[`GIT_CONFIG_VALUE_${at}`] = value;
23461
23461
  });
23462
- return new Promise((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 }));
23462
+ return new Promise((resolve7) => {
23463
+ execFile(options.git ?? "git", [...args], { cwd: this.copy, env, maxBuffer: 64 * 1024 * 1024 }, (error62, stdout, stderr) => resolve7({ ok: error62 === null, out: stdout, err: stderr }));
23464
23464
  });
23465
23465
  }
23466
23466
  };
@@ -23483,8 +23483,8 @@ var Lane = class {
23483
23483
  constructor(id2, wire) {
23484
23484
  this.id = id2;
23485
23485
  this.wire = wire;
23486
- this.result = new Promise((resolve6) => {
23487
- this.settle = resolve6;
23486
+ this.result = new Promise((resolve7) => {
23487
+ this.settle = resolve7;
23488
23488
  });
23489
23489
  this.stop = wire.hear(id2, (event) => this.onUpdated(event));
23490
23490
  }
@@ -23627,7 +23627,7 @@ var Run = class {
23627
23627
  /** Resolve once the run is not paused. A stopped run resolves too: check `over` after. */
23628
23628
  async whenRunning() {
23629
23629
  while (this.held && !this.ended) {
23630
- await new Promise((resolve6) => setTimeout(resolve6, 50));
23630
+ await new Promise((resolve7) => setTimeout(resolve7, 50));
23631
23631
  }
23632
23632
  }
23633
23633
  status(input2) {
@@ -24411,8 +24411,8 @@ var Chips = class _Chips {
24411
24411
  }
24412
24412
  /** Resolve with the first event that matches, now or later. */
24413
24413
  waitFor(match) {
24414
- return new Promise((resolve6) => {
24415
- this.waiters.add({ match, resolve: resolve6 });
24414
+ return new Promise((resolve7) => {
24415
+ this.waiters.add({ match, resolve: resolve7 });
24416
24416
  });
24417
24417
  }
24418
24418
  close() {
@@ -24439,7 +24439,7 @@ var Chips = class _Chips {
24439
24439
  return `${base}/v1/ws?ticket=${encodeURIComponent(String(ticket))}`;
24440
24440
  }
24441
24441
  open(first) {
24442
- return new Promise((resolve6, reject) => {
24442
+ return new Promise((resolve7, reject) => {
24443
24443
  this.setState("connecting");
24444
24444
  const failed = (what, error62) => {
24445
24445
  if (first)
@@ -24457,13 +24457,13 @@ var Chips = class _Chips {
24457
24457
  }
24458
24458
  this.socket = socket;
24459
24459
  this.listen(socket, {
24460
- settled: (hello2) => this.settled(socket, hello2, first, resolve6, reject)
24460
+ settled: (hello2) => this.settled(socket, hello2, first, resolve7, reject)
24461
24461
  });
24462
24462
  }, (error62) => failed("could not get a socket ticket", error62));
24463
24463
  });
24464
24464
  }
24465
24465
  /** The handshake's outcome: the hello, a refusal, or the socket gone before either. */
24466
- settled(socket, hello2, first, resolve6, reject) {
24466
+ settled(socket, hello2, first, resolve7, reject) {
24467
24467
  if (hello2 === void 0) {
24468
24468
  if (first)
24469
24469
  reject(new ConnectError("the server closed the socket before saying hello"));
@@ -24477,7 +24477,7 @@ var Chips = class _Chips {
24477
24477
  return;
24478
24478
  }
24479
24479
  this.onHello(socket, hello2);
24480
- void this.subscribed(socket).then(resolve6);
24480
+ void this.subscribed(socket).then(resolve7);
24481
24481
  }
24482
24482
  /** The socket's four events. `settled` fires once: with the hello, a refusal, or nothing. */
24483
24483
  listen(socket, on) {
@@ -24572,15 +24572,15 @@ var Chips = class _Chips {
24572
24572
  subscribed(socket) {
24573
24573
  this.pings += 1;
24574
24574
  const ts = Date.now() + this.pings;
24575
- return new Promise((resolve6) => {
24575
+ return new Promise((resolve7) => {
24576
24576
  const timer = setTimeout(() => {
24577
24577
  this.pongs.delete(ts);
24578
- resolve6();
24578
+ resolve7();
24579
24579
  }, 2e3);
24580
24580
  this.pongs.set(ts, () => {
24581
24581
  clearTimeout(timer);
24582
24582
  this.pongs.delete(ts);
24583
- resolve6();
24583
+ resolve7();
24584
24584
  });
24585
24585
  socket.send(JSON.stringify({ frame: "ping", ts }));
24586
24586
  });
@@ -24963,7 +24963,7 @@ function openKeyStore(env, platform = process.platform) {
24963
24963
  // node_modules/@chipschat/harness/dist/steps.js
24964
24964
  import { writeSync } from "node:fs";
24965
24965
  var STEP_MARK = "chips-step ";
24966
- function reportStep(step, detail = {}, env = process.env, write = (line) => void writeSync(1, line)) {
24966
+ function reportStep(step, detail = {}, env = process.env, write = (line2) => void writeSync(1, line2)) {
24967
24967
  if (env["CHIPS_STEPS"] !== "json")
24968
24968
  return;
24969
24969
  try {
@@ -25044,8 +25044,8 @@ async function refused(response) {
25044
25044
  const body = await response.json().catch(() => ({}));
25045
25045
  return new JoinError(body.error?.refusal ?? body.error?.code ?? `http_${response.status}`, body.error?.message ?? `Chips answered ${response.status}.`);
25046
25046
  }
25047
- var wait = (ms, signal) => new Promise((resolve6, reject) => {
25048
- const timer = setTimeout(resolve6, ms);
25047
+ var wait = (ms, signal) => new Promise((resolve7, reject) => {
25048
+ const timer = setTimeout(resolve7, ms);
25049
25049
  signal?.addEventListener("abort", () => {
25050
25050
  clearTimeout(timer);
25051
25051
  reject(new JoinError("stopped", "Stopped before a person decided."));
@@ -25169,8 +25169,8 @@ var MOST_FILES = 20;
25169
25169
  var MOST_FILE_BYTES = 100 * 1024 * 1024;
25170
25170
  function filesNamedIn(answer) {
25171
25171
  const named = [];
25172
- const kept = answer.split("\n").filter((line) => {
25173
- const media = /^\s*MEDIA:\s*(\S.*?)\s*$/.exec(line);
25172
+ const kept = answer.split("\n").filter((line2) => {
25173
+ const media = /^\s*MEDIA:\s*(\S.*?)\s*$/.exec(line2);
25174
25174
  if (media?.[1] === void 0)
25175
25175
  return true;
25176
25176
  if (!named.includes(media[1]))
@@ -25596,8 +25596,8 @@ function envValue(home2, name2) {
25596
25596
  const path = join5(home2, ".env");
25597
25597
  if (!existsSync5(path)) return void 0;
25598
25598
  let found;
25599
- for (const 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);
25599
+ for (const line2 of readFileSync5(path, "utf8").split("\n")) {
25600
+ const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(line2);
25601
25601
  if (match?.[1] !== name2) continue;
25602
25602
  const raw = match[2] ?? "";
25603
25603
  const quoted2 = /^(["'])(.*)\1/.exec(raw);
@@ -25613,20 +25613,20 @@ function configText(home2) {
25613
25613
  return "";
25614
25614
  }
25615
25615
  }
25616
- function uncommented(line) {
25617
- const indent = line.length - line.trimStart().length;
25616
+ function uncommented(line2) {
25617
+ const indent = line2.length - line2.trimStart().length;
25618
25618
  let quote;
25619
- for (let at = indent; at < line.length; at += 1) {
25620
- const char = line[at];
25619
+ for (let at = indent; at < line2.length; at += 1) {
25620
+ const char = line2[at];
25621
25621
  if (quote !== void 0) {
25622
25622
  if (char === quote) quote = void 0;
25623
25623
  } else if (char === '"' || char === "'") {
25624
25624
  quote = char;
25625
- } else if (char === "#" && (at === indent || /\s/.test(line[at - 1] ?? ""))) {
25626
- return { indent, text: line.slice(indent, at).trimEnd() };
25625
+ } else if (char === "#" && (at === indent || /\s/.test(line2[at - 1] ?? ""))) {
25626
+ return { indent, text: line2.slice(indent, at).trimEnd() };
25627
25627
  }
25628
25628
  }
25629
- return { indent, text: line.slice(indent).trimEnd() };
25629
+ return { indent, text: line2.slice(indent).trimEnd() };
25630
25630
  }
25631
25631
  function scalar(text) {
25632
25632
  const trimmed = text.trim();
@@ -25648,9 +25648,9 @@ function flowPairs(text) {
25648
25648
  function yamlValue(text, section, key) {
25649
25649
  const lines2 = text.split("\n");
25650
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();
25651
+ const { indent, text: line2 } = uncommented(lines2[at] ?? "");
25652
+ if (indent !== 0 || !line2.startsWith(`${section}:`)) continue;
25653
+ const rest = line2.slice(section.length + 1).trim();
25654
25654
  if (rest !== "") return flowPairs(rest)?.get(key);
25655
25655
  let child;
25656
25656
  for (let below = at + 1; below < lines2.length; below += 1) {
@@ -25722,7 +25722,9 @@ function folderOfPid(raw) {
25722
25722
  } catch {
25723
25723
  return void 0;
25724
25724
  }
25725
- if (pid === void 0) return void 0;
25725
+ return pid === void 0 ? void 0 : processFolder(pid);
25726
+ }
25727
+ function processFolder(pid) {
25726
25728
  try {
25727
25729
  if (process.platform === "linux") return readlinkSync(`/proc/${pid}/cwd`);
25728
25730
  const out = execFileSync3("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
@@ -25730,8 +25732,8 @@ function folderOfPid(raw) {
25730
25732
  timeout: 3e3,
25731
25733
  stdio: ["ignore", "pipe", "ignore"]
25732
25734
  });
25733
- const line = out.split("\n").find((each) => each.startsWith("n"));
25734
- return line === void 0 ? void 0 : line.slice(1);
25735
+ const line2 = out.split("\n").find((each) => each.startsWith("n"));
25736
+ return line2 === void 0 ? void 0 : line2.slice(1);
25735
25737
  } catch {
25736
25738
  return void 0;
25737
25739
  }
@@ -26529,7 +26531,7 @@ function sessionIdOf(made) {
26529
26531
  return typeof id2 === "string" && id2 !== "" ? id2 : void 0;
26530
26532
  }
26531
26533
  function eventOf(block) {
26532
- const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).replace(/^ /, "")).join("\n");
26534
+ const data = block.split("\n").filter((line2) => line2.startsWith("data:")).map((line2) => line2.slice(5).replace(/^ /, "")).join("\n");
26533
26535
  if (data === "") return void 0;
26534
26536
  try {
26535
26537
  const parsed = JSON.parse(data);
@@ -26564,9 +26566,9 @@ function hookUrl(port, profile) {
26564
26566
  return `http://127.0.0.1:${port}${HOOK_PATH}${profile}`;
26565
26567
  }
26566
26568
  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+#.*$/, "");
26569
+ return text.split("\n").map((line2) => {
26570
+ const indent = line2.length - line2.trimStart().length;
26571
+ const words4 = line2.trimStart().startsWith("#") ? "" : line2.trim().replace(/\s+#.*$/, "");
26570
26572
  return { indent, words: words4 };
26571
26573
  });
26572
26574
  }
@@ -26608,15 +26610,15 @@ function addHook(home2, port, profile, secret) {
26608
26610
  const text = existsSync7(path) ? readFileSync7(path, "utf8") : "";
26609
26611
  const lines2 = linesOf2(text);
26610
26612
  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
+ if (lines2.some((line2) => ours2.test(line2.words))) return { state: "there", path };
26614
+ const where = lines2.findIndex((line2) => line2.indent === 0 && /^["']?hooks["']?\s*:/.test(line2.words));
26613
26615
  const byHand = (why) => {
26614
26616
  const inEnv2 = secretInEnv(home2, secret);
26615
26617
  const shown2 = entryLines(" ", url2, inEnv2 ? { env: SECRET_ENV } : { inline: secret });
26616
26618
  return { state: "by_hand", path, why, lines: ["hooks:", ...shown2].join("\n") };
26617
26619
  };
26618
26620
  const plain = /^(?:"[^"]+"|'[^']+'|[A-Za-z_][\w.-]*)\s*:(?:\s|$)/;
26619
- if (lines2.some((line) => line.indent === 0 && line.words !== "" && !plain.test(line.words))) {
26621
+ if (lines2.some((line2) => line2.indent === 0 && line2.words !== "" && !plain.test(line2.words))) {
26620
26622
  return byHand("its top level isn't laid out as this bridge expects");
26621
26623
  }
26622
26624
  if (where === -1) {
@@ -26636,20 +26638,20 @@ function addHook(home2, port, profile, secret) {
26636
26638
  const head = lines2[where]?.words ?? "";
26637
26639
  if (head.replace(/^["']?hooks["']?\s*:/, "").trim() !== "") return byHand("its hooks: is written on one line");
26638
26640
  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);
26641
+ for (const line2 of lines2.slice(where + 1)) {
26642
+ if (line2.words === "") continue;
26643
+ if (line2.indent === 0) break;
26644
+ children.push(line2);
26643
26645
  }
26644
26646
  const indent = children[0]?.indent ?? 2;
26645
26647
  const named = new RegExp(`^(?:-\\s*)?name:\\s*["']?${HOOK_NAME}["']?$`);
26646
- if (children.some((line) => named.test(line.words))) {
26648
+ if (children.some((line2) => named.test(line2.words))) {
26647
26649
  return byHand(`its hooks have an entry called ${HOOK_NAME} with another address`);
26648
26650
  }
26649
- if (children.some((line) => line.indent === indent && /^["']?outbound["']?\s*:/.test(line.words))) {
26651
+ if (children.some((line2) => line2.indent === indent && /^["']?outbound["']?\s*:/.test(line2.words))) {
26650
26652
  return byHand("its hooks already have an outbound list");
26651
26653
  }
26652
- if (children.some((line) => line.indent !== indent && line.indent < indent) || children[0]?.words.startsWith("-")) {
26654
+ if (children.some((line2) => line2.indent !== indent && line2.indent < indent) || children[0]?.words.startsWith("-")) {
26653
26655
  return byHand("its hooks: isn't laid out as this bridge expects");
26654
26656
  }
26655
26657
  const inEnv = secretInEnv(home2, secret);
@@ -26660,7 +26662,7 @@ function addHook(home2, port, profile, secret) {
26660
26662
  }
26661
26663
 
26662
26664
  // src/bridge.ts
26663
- var WANTS = ["agents", "new_agents", "forks", "context_files", "context_windows"];
26665
+ var WANTS = ["agents", "new_agents", "forks", "context_files", "context_windows", "restarts"];
26664
26666
  function settingsFor(active) {
26665
26667
  const bridge2 = () => {
26666
26668
  const there = active();
@@ -26725,7 +26727,7 @@ var Bridge = class {
26725
26727
  agent: (agentId2, copy) => chips2.settings.context(agentId2, copy),
26726
26728
  harness: (copy) => chips2.settings.harnessContext(copy)
26727
26729
  },
26728
- (line) => this.log(`context: ${line}`),
26730
+ (line2) => this.log(`context: ${line2}`),
26729
26731
  options.env ?? process.env
26730
26732
  );
26731
26733
  if (options.fills === void 0) return;
@@ -26740,7 +26742,7 @@ var Bridge = class {
26740
26742
  send: (agentId2, copy) => chips2.settings.contextWindow(agentId2, copy),
26741
26743
  kept,
26742
26744
  save: () => options.saveState(options.state),
26743
- log: (line) => this.log(`context window: ${line}`),
26745
+ log: (line2) => this.log(`context window: ${line2}`),
26744
26746
  sendEveryMs: options.fillsEveryMs
26745
26747
  });
26746
26748
  }
@@ -26767,6 +26769,10 @@ var Bridge = class {
26767
26769
  /** The port Hermes's webhook reaches the bridge on, once it listens. */
26768
26770
  fillsPort;
26769
26771
  toldNoReadings = false;
26772
+ /** While Hermes's gateway restarts (Chips ADR 0094), the health watch says nothing: the restart says how it went. */
26773
+ held = false;
26774
+ /** What went wrong in the last restart, said in place of the watch's own words while Hermes doesn't answer. */
26775
+ why;
26770
26776
  /** Register the agents, start hearing what is said to them, and say how their gateways are. */
26771
26777
  async start() {
26772
26778
  const { chips: chips2 } = this.options;
@@ -26795,6 +26801,20 @@ var Bridge = class {
26795
26801
  this.health.unref();
26796
26802
  await this.startSettings();
26797
26803
  }
26804
+ /** Hold the health watch's words while Hermes's gateway restarts, and let them go after. */
26805
+ hold(held) {
26806
+ this.held = held;
26807
+ }
26808
+ /**
26809
+ * How a restart went (Chips ADR 0094): fine as the gateways now are, or its words, which stand
26810
+ * while Hermes doesn't answer. The health watch speaks for itself again after.
26811
+ */
26812
+ async restarted(trouble) {
26813
+ this.held = false;
26814
+ this.why = trouble;
26815
+ if (trouble === void 0) return this.sayHowItIs();
26816
+ await this.options.chips.reportTrouble(trouble).catch(() => void 0);
26817
+ }
26798
26818
  stop() {
26799
26819
  if (this.health !== void 0) clearInterval(this.health);
26800
26820
  if (this.contextTimer !== void 0) clearInterval(this.contextTimer);
@@ -27034,7 +27054,7 @@ var Bridge = class {
27034
27054
  const folder2 = turn.folder;
27035
27055
  if (folder2 === void 0) return void 0;
27036
27056
  try {
27037
- const project = this.options.chips.project({ spaceId, agentId: agentId2, name: name2, folder: folder2, log: (line) => this.log(line) });
27057
+ const project = this.options.chips.project({ spaceId, agentId: agentId2, name: name2, folder: folder2, log: (line2) => this.log(line2) });
27038
27058
  const facts = await project.pull();
27039
27059
  turn.project = { copy: project, name: name2 };
27040
27060
  return facts;
@@ -27063,12 +27083,15 @@ var Bridge = class {
27063
27083
  return note === void 0 ? {} : { note };
27064
27084
  }
27065
27085
  }
27066
- /** Trouble while a profile's gateway doesn't answer, and fine once all do (ADR 0083). */
27086
+ /** Trouble while a profile's gateway doesn't answer, and fine once all do (ADR 0083); nothing while a restart runs. */
27067
27087
  async sayHowItIs() {
27088
+ if (this.held) return;
27068
27089
  for (const agent of this.agents) agent.up = await agent.client.healthy();
27090
+ if (this.held) return;
27069
27091
  const down = this.agents.filter((agent) => !agent.up);
27092
+ if (down.length === 0) this.why = void 0;
27070
27093
  const chips2 = this.options.chips;
27071
- const said2 = down.length === 0 ? chips2.reportOk() : chips2.reportTrouble(`Hermes isn't answering for ${down.map((agent) => agent.config.name).join(", ")}`);
27094
+ const said2 = down.length === 0 ? chips2.reportOk() : chips2.reportTrouble(this.why ?? `Hermes isn't answering for ${down.map((agent) => agent.config.name).join(", ")}`);
27072
27095
  await said2.catch(() => void 0);
27073
27096
  }
27074
27097
  // ------------------------------------------------------------------ what is said to it
@@ -27102,15 +27125,15 @@ var Bridge = class {
27102
27125
  this.lookedAt.delete(old);
27103
27126
  }
27104
27127
  const context = await this.options.chips.roomContext(spaceId, { limit: 200 }).catch(() => void 0);
27105
- const line = context?.lines.find((each) => each.message_id === messageId);
27128
+ const line2 = context?.lines.find((each) => each.message_id === messageId);
27106
27129
  const message = {
27107
27130
  type: "message.new",
27108
27131
  space_id: spaceId,
27109
27132
  // An id of its own, so nothing of the turn is taken for the reply's.
27110
27133
  message_id: messageIdOf(feedback2.id),
27111
- text: lookAgain(feedback2.from.display_name, line?.text, note),
27134
+ text: lookAgain(feedback2.from.display_name, line2?.text, note),
27112
27135
  from: feedback2.from,
27113
- ...line === void 0 ? {} : { for: [line.from.id] }
27136
+ ...line2 === void 0 ? {} : { for: [line2.from.id] }
27114
27137
  };
27115
27138
  await this.hear(message, { goal: lookingAgain(note), sayWho: false });
27116
27139
  }
@@ -27252,7 +27275,7 @@ var Bridge = class {
27252
27275
  if (status !== void 0 && TERMINAL.has(`run.${status}`)) {
27253
27276
  return { ...state2, event: `run.${status}` };
27254
27277
  }
27255
- await new Promise((resolve6) => setTimeout(resolve6, 1e3));
27278
+ await new Promise((resolve7) => setTimeout(resolve7, 1e3));
27256
27279
  }
27257
27280
  await this.failed(turn, `${turn.agent.config.name}'s run in Hermes stopped answering.`, true);
27258
27281
  return void 0;
@@ -27320,10 +27343,10 @@ var Bridge = class {
27320
27343
  }
27321
27344
  sayThought(turn, text, phase) {
27322
27345
  const now = Date.now();
27323
- const line = lastLine(text);
27324
- if (line === "" || now - turn.saidAt < QUIET_MS) return;
27346
+ const line2 = lastLine(text);
27347
+ if (line2 === "" || now - turn.saidAt < QUIET_MS) return;
27325
27348
  turn.saidAt = now;
27326
- void turn.run.status({ phase, verbPhrase: line.slice(0, 200) }).catch(() => void 0);
27349
+ void turn.run.status({ phase, verbPhrase: line2.slice(0, 200) }).catch(() => void 0);
27327
27350
  }
27328
27351
  async started(turn, event) {
27329
27352
  const sub = event.event === "subagent.start";
@@ -27392,7 +27415,7 @@ var Bridge = class {
27392
27415
  ${command}
27393
27416
  \`\`\`` } }
27394
27417
  });
27395
- const stopped2 = new Promise((resolve6) => turn.run.on("cancel", () => resolve6(void 0)));
27418
+ const stopped2 = new Promise((resolve7) => turn.run.on("cancel", () => resolve7(void 0)));
27396
27419
  const decision = await Promise.race([asked, stopped2]);
27397
27420
  const pending = turn.approvals.get(requestId ?? approvalId);
27398
27421
  if (decision === void 0 || pending === void 0 || pending.answered) return;
@@ -27400,7 +27423,7 @@ ${command}
27400
27423
  if (decision.allowed) turn.allowed.push(approvalId);
27401
27424
  const generation = event["execution_generation"];
27402
27425
  await turn.agent.client.approve(turn.hermesRun, {
27403
- choice: choiceOf(decision, choices),
27426
+ choice: choiceOf(decision, choices, command !== void 0),
27404
27427
  ...requestId === void 0 ? {} : { request_id: requestId },
27405
27428
  ...typeof generation === "number" ? { execution_generation: generation } : {}
27406
27429
  }).catch((error62) => this.log(`Hermes didn't take the answer: ${messageOf2(error62)}`));
@@ -27517,8 +27540,9 @@ ${command}
27517
27540
  if (retryable) void this.sayHowItIs();
27518
27541
  }
27519
27542
  };
27520
- function choiceOf(decision, choices) {
27543
+ function choiceOf(decision, choices, command = false) {
27521
27544
  if (!decision.allowed) return "deny";
27545
+ if (command) return "once";
27522
27546
  if (decision.scope === "always" && choices.includes("always")) return "always";
27523
27547
  if (decision.scope !== "once" && choices.includes("session")) return "session";
27524
27548
  return "once";
@@ -27535,7 +27559,7 @@ function forkName(original) {
27535
27559
  return `${original.trim().replace(/ · fork( \d+)?$/, "")} \xB7 fork`;
27536
27560
  }
27537
27561
  function goalOf(message, name2) {
27538
- const first = message.text.split("\n").find((line) => line.trim() !== "")?.trim() ?? "";
27562
+ const first = message.text.split("\n").find((line2) => line2.trim() !== "")?.trim() ?? "";
27539
27563
  const named = first.toLowerCase().startsWith(`@${name2.toLowerCase()}`);
27540
27564
  const goal = named ? first.slice(name2.length + 1).trim() : first.replace(/^@\S+\s*/, "");
27541
27565
  return (goal === "" ? `Answer ${message.from.display_name}` : goal).slice(0, 300);
@@ -27553,7 +27577,7 @@ function lookAgain(who, reply, note) {
27553
27577
  ].join("\n\n");
27554
27578
  }
27555
27579
  function lookingAgain(note) {
27556
- const first = note?.split("\n").find((line) => line.trim() !== "")?.trim();
27580
+ const first = note?.split("\n").find((line2) => line2.trim() !== "")?.trim();
27557
27581
  return first === void 0 ? "Looking again at its reply" : cut(`Looking again: ${first}`, GOAL_SHOWN);
27558
27582
  }
27559
27583
  function cut(text, most) {
@@ -27573,27 +27597,488 @@ function resultWords(preview2) {
27573
27597
  return typeof said2 === "string" ? said2.trim() : "";
27574
27598
  }
27575
27599
  function lastLine(text) {
27576
- const lines2 = text.split(/(?<=[.!?])\s+|\n+/).map((line) => line.trim()).filter((line) => line !== "");
27600
+ const lines2 = text.split(/(?<=[.!?])\s+|\n+/).map((line2) => line2.trim()).filter((line2) => line2 !== "");
27577
27601
  return lines2.at(-1) ?? "";
27578
27602
  }
27579
27603
  function lowerFirst(text) {
27580
27604
  return text.length === 0 ? text : `${text[0]?.toLowerCase() ?? ""}${text.slice(1)}`;
27581
27605
  }
27582
27606
 
27583
- // src/keep.ts
27607
+ // src/gateway.ts
27608
+ import { spawn as spawn2 } from "node:child_process";
27609
+ import { createHash as createHash3 } from "node:crypto";
27610
+ import { accessSync as accessSync2, appendFileSync as appendFileSync2, closeSync as closeSync2, constants as constants2, existsSync as existsSync8, openSync as openSync2, readFileSync as readFileSync8, readSync, statSync as statSync4 } from "node:fs";
27584
27611
  import { homedir as homedir5 } from "node:os";
27585
- import { basename as basename4, join as join9, resolve as resolve3 } from "node:path";
27612
+ import { basename as basename4, delimiter as delimiter2, dirname as dirname5, join as join9, resolve as resolve3 } from "node:path";
27613
+ var TIMING = {
27614
+ stopMs: 15e3,
27615
+ killMs: 5e3,
27616
+ earlyMs: 1e4,
27617
+ upMs: 9e4,
27618
+ serviceMs: 15e4,
27619
+ pollMs: 500
27620
+ };
27621
+ var EXPAT = "/opt/homebrew/opt/expat/lib/libexpat.1.dylib";
27622
+ var EXPAT_SYMBOL = "_XML_SetAllocTrackerActivationThreshold";
27623
+ function gatewaysOf(agents) {
27624
+ return [...new Set(agents.map(({ address }) => address.url.replace(/\/+$/, "")))].map((url2) => ({ url: url2 }));
27625
+ }
27626
+ function isLocal(url2) {
27627
+ try {
27628
+ return ["127.0.0.1", "localhost", "[::1]", "::1"].includes(new URL(url2).hostname);
27629
+ } catch {
27630
+ return false;
27631
+ }
27632
+ }
27633
+ function portOf(url2) {
27634
+ const parsed = new URL(url2);
27635
+ return Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80));
27636
+ }
27637
+ function line(words4) {
27638
+ const one = words4.replace(/\s+/g, " ").trim();
27639
+ return one.length <= 200 ? one : `${one.slice(0, 199)}\u2026`;
27640
+ }
27641
+ function whyFrom(output2, code) {
27642
+ const lines2 = output2.split("\n").map((each) => each.trim()).filter((each) => each !== "");
27643
+ const why = lines2.findLast((each) => /^[A-Za-z_][\w.]*(Error|Exception)\b/.test(each)) ?? lines2.at(-1);
27644
+ if (why === void 0) return code === null || code === void 0 ? "it was stopped" : `it exited ${code}`;
27645
+ return why.length <= 200 ? why : why.replace(/\/(?:[^\s/(),:'"]+\/)+([^\s/(),:'"]+)/g, "$1");
27646
+ }
27647
+ function gatewayRunArgs(command) {
27648
+ const words4 = command.trim().split(/\s+/);
27649
+ const at = words4.findIndex((word) => basename4(word) === "hermes");
27650
+ if (at === -1) return void 0;
27651
+ const args = words4.slice(at + 1);
27652
+ const plain = [];
27653
+ for (let next = 0; next < args.length; next += 1) {
27654
+ const word = args[next] ?? "";
27655
+ if (word === "-p" || word === "--profile") next += 1;
27656
+ else if (!word.startsWith("-")) plain.push(word);
27657
+ }
27658
+ return plain[0] === "gateway" && plain[1] === "run" ? args : void 0;
27659
+ }
27660
+ function serviceOf(system, root2, env = {}) {
27661
+ const home2 = resolve3(root2);
27662
+ const native = resolve3(system.home, ".hermes");
27663
+ const suffix = home2 === native ? "" : dirname5(home2) === join9(native, "profiles") ? basename4(home2) : createHash3("sha256").update(home2).digest("hex").slice(0, 8);
27664
+ if (system.platform === "darwin") {
27665
+ const path = join9(system.home, "Library", "LaunchAgents", `ai.hermes.gateway${suffix ? `-${suffix}` : ""}.plist`);
27666
+ return system.exists(path) ? { kind: "launchd", path } : void 0;
27667
+ }
27668
+ if (system.platform === "linux") {
27669
+ const unit = `hermes-gateway${suffix ? `-${suffix}` : ""}.service`;
27670
+ const user = join9(env["XDG_CONFIG_HOME"] || join9(system.home, ".config"), "systemd", "user", unit);
27671
+ if (system.exists(user)) return { kind: "systemd", path: user };
27672
+ const machine = join9("/etc/systemd/system", unit);
27673
+ if (system.exists(machine)) return { kind: "systemd", path: machine, system: true };
27674
+ }
27675
+ return void 0;
27676
+ }
27677
+ function expand(text, env) {
27678
+ let out = "";
27679
+ let at = 0;
27680
+ while (at < text.length) {
27681
+ const char = text[at] ?? "";
27682
+ if (char === "\\" && at + 1 < text.length) {
27683
+ out += text[at + 1];
27684
+ at += 2;
27685
+ } else if (char !== "$") {
27686
+ out += char;
27687
+ at += 1;
27688
+ } else if (text[at + 1] === "{") {
27689
+ let depth = 1;
27690
+ let end2 = at + 2;
27691
+ for (; end2 < text.length; end2 += 1) {
27692
+ if (text[end2] === "{") depth += 1;
27693
+ else if (text[end2] === "}" && --depth === 0) break;
27694
+ }
27695
+ const inner = /^([A-Za-z_][A-Za-z0-9_]*)(?:(:?)-([\s\S]*))?$/.exec(text.slice(at + 2, end2));
27696
+ if (inner !== null) {
27697
+ const value = env[inner[1] ?? ""];
27698
+ const unset = inner[2] === ":" ? value === void 0 || value === "" : value === void 0;
27699
+ out += inner[3] !== void 0 && unset ? expand(inner[3], env) : value ?? "";
27700
+ }
27701
+ at = end2 + 1;
27702
+ } else {
27703
+ const name2 = /^[A-Za-z_][A-Za-z0-9_]*/.exec(text.slice(at + 1))?.[0];
27704
+ out += name2 === void 0 ? "$" : env[name2] ?? "";
27705
+ at += 1 + (name2?.length ?? 0);
27706
+ }
27707
+ }
27708
+ return out;
27709
+ }
27710
+ function wrapperOf(text, env) {
27711
+ for (const each of text.split("\n")) {
27712
+ const found = /^\s*((?:[A-Za-z_][A-Za-z0-9_]*="(?:[^"\\]|\\.)*"\s+)*)exec\s+"([^"]+)"/.exec(each);
27713
+ if (found === null) continue;
27714
+ const vars = {};
27715
+ for (const set2 of (found[1] ?? "").matchAll(/([A-Za-z_][A-Za-z0-9_]*)="((?:[^"\\]|\\.)*)"/g)) {
27716
+ vars[set2[1] ?? ""] = expand(set2[2] ?? "", env);
27717
+ }
27718
+ return { entry: expand(found[2] ?? "", env), vars };
27719
+ }
27720
+ return void 0;
27721
+ }
27722
+ var NO_HERMES = "The hermes command isn't on this machine's PATH, so Hermes's gateway can't be started.";
27723
+ var messageOf3 = (error62) => error62 instanceof Error ? error62.message : String(error62);
27724
+ function gatewayEnv(env) {
27725
+ return Object.fromEntries(Object.entries(env).filter(([name2]) => !name2.startsWith("CHIPS_")));
27726
+ }
27727
+ var Gateways = class {
27728
+ constructor(options) {
27729
+ this.options = options;
27730
+ }
27731
+ options;
27732
+ timing = TIMING;
27733
+ /** Each gateway restarted, one after another: what went wrong, in a line, or nothing. */
27734
+ async restart() {
27735
+ const troubles = [];
27736
+ for (const gateway of this.options.gateways) {
27737
+ let trouble;
27738
+ try {
27739
+ trouble = await this.restartOne(gateway);
27740
+ } catch (error62) {
27741
+ trouble = `Restart failed: ${messageOf3(error62)}`;
27742
+ }
27743
+ if (trouble !== void 0) troubles.push(trouble);
27744
+ }
27745
+ return troubles.length === 0 ? void 0 : line(troubles.join(" "));
27746
+ }
27747
+ /** Each local gateway that doesn't answer, started: the bridge is starting. */
27748
+ async startIfDown() {
27749
+ const { system, log: log2 } = this.options;
27750
+ for (const gateway of this.options.gateways) {
27751
+ if (!isLocal(gateway.url)) continue;
27752
+ const port = portOf(gateway.url);
27753
+ try {
27754
+ if (await system.healthy(gateway.url)) continue;
27755
+ let trouble;
27756
+ if (this.serviceFor(gateway) !== void 0) {
27757
+ log2(`Hermes isn't answering on port ${port}: starting its service, hermes gateway start`);
27758
+ trouble = await this.byService(gateway, "start", void 0);
27759
+ } else {
27760
+ const pid = await system.listener(port);
27761
+ if (pid !== void 0) {
27762
+ log2(`Hermes isn't answering on port ${port}, but pid ${pid} listens there: no second gateway started`);
27763
+ continue;
27764
+ }
27765
+ log2(`Hermes isn't answering on port ${port}: starting hermes gateway run`);
27766
+ trouble = await this.start(gateway, ["gateway", "run"], void 0);
27767
+ }
27768
+ if (trouble !== void 0) log2(trouble);
27769
+ } catch (error62) {
27770
+ log2(`Hermes's gateway on port ${port} couldn't be started: ${messageOf3(error62)}`);
27771
+ }
27772
+ }
27773
+ }
27774
+ /**
27775
+ * The service of Hermes's root, for the Main agent's gateway: a gateway at another address is
27776
+ * another profile's own, which `hermes gateway status` of the root doesn't speak for.
27777
+ */
27778
+ serviceFor(gateway) {
27779
+ if (gateway !== this.options.gateways[0]) return void 0;
27780
+ return serviceOf(this.options.system, this.options.root, this.options.env);
27781
+ }
27782
+ async restartOne(gateway) {
27783
+ const { system, log: log2 } = this.options;
27784
+ if (!isLocal(gateway.url)) {
27785
+ return `Hermes's gateway at ${new URL(gateway.url).host} isn't on this machine, so it can't be restarted from here.`;
27786
+ }
27787
+ const port = portOf(gateway.url);
27788
+ const pid = await system.listener(port);
27789
+ if (this.serviceFor(gateway) !== void 0) {
27790
+ log2("restarting Hermes's service: hermes gateway restart");
27791
+ return this.byService(gateway, "restart", pid);
27792
+ }
27793
+ if (pid === void 0) {
27794
+ if (await system.healthy(gateway.url)) return `Can't tell what answers on port ${port}, so nothing was restarted.`;
27795
+ log2(`nothing listens on port ${port}: starting hermes gateway run`);
27796
+ return this.start(gateway, ["gateway", "run"], void 0);
27797
+ }
27798
+ const info = await system.describe(pid);
27799
+ if (info === void 0) return this.start(gateway, ["gateway", "run"], void 0);
27800
+ const args = this.ours(info);
27801
+ if (args === void 0) {
27802
+ log2(`pid ${pid} on port ${port} isn't this user's hermes gateway run, so it was left alone: ${info.command.slice(0, 160)}`);
27803
+ return `Port ${port} is held by something other than hermes gateway run (pid ${pid}), so nothing was restarted.`;
27804
+ }
27805
+ if (system.which("hermes", this.options.env) === void 0) return NO_HERMES;
27806
+ log2(`stopping Hermes's gateway, pid ${pid}`);
27807
+ if (!await this.stop(pid)) return `Hermes's gateway (pid ${pid}) didn't stop, so it wasn't started again.`;
27808
+ return this.start(gateway, args, info.cwd);
27809
+ }
27810
+ /** A `hermes gateway run` of this user's: the words to start it again with. */
27811
+ ours(info) {
27812
+ return info.uid === this.options.system.uid ? gatewayRunArgs(info.command) : void 0;
27813
+ }
27814
+ /** SIGTERM, then SIGKILL once it has had a while, and only while it is still that gateway. */
27815
+ async stop(pid) {
27816
+ const { system, log: log2 } = this.options;
27817
+ system.signal(pid, "SIGTERM");
27818
+ if (await this.gone(pid, this.timing.stopMs)) return true;
27819
+ const still = await system.describe(pid);
27820
+ if (still === void 0) return true;
27821
+ if (this.ours(still) === void 0) return false;
27822
+ log2(`Hermes's gateway, pid ${pid}, is still running ${this.timing.stopMs / 1e3} s after SIGTERM: SIGKILL`);
27823
+ system.signal(pid, "SIGKILL");
27824
+ return this.gone(pid, this.timing.killMs);
27825
+ }
27826
+ async gone(pid, withinMs) {
27827
+ const { system } = this.options;
27828
+ const by = system.now() + withinMs;
27829
+ while (system.alive(pid)) {
27830
+ if (system.now() >= by) return false;
27831
+ await system.sleep(this.timing.pollMs);
27832
+ }
27833
+ return true;
27834
+ }
27835
+ /** Hermes's own `hermes gateway restart|start`, then the gateway answering, from a new process. */
27836
+ async byService(gateway, verb, before) {
27837
+ const { system, env } = this.options;
27838
+ const hermes = system.which("hermes", env);
27839
+ if (hermes === void 0) return NO_HERMES;
27840
+ const scope = this.serviceFor(gateway)?.system === true ? ["--system"] : [];
27841
+ const ran = await system.run(hermes, ["gateway", verb, ...scope], gatewayEnv(env), this.timing.serviceMs);
27842
+ if (ran.code !== 0) return line(`hermes gateway ${verb} failed: ${whyFrom(ran.output, ran.code)}`);
27843
+ const by = system.now() + this.timing.serviceMs;
27844
+ for (; ; ) {
27845
+ const old = before !== void 0 && system.alive(before);
27846
+ if (!old && await system.healthy(gateway.url)) return void 0;
27847
+ if (system.now() >= by) {
27848
+ if (await system.healthy(gateway.url)) return void 0;
27849
+ return `Hermes's service ${verb === "start" ? "started" : "restarted"}, but nothing answers on port ${portOf(gateway.url)}.`;
27850
+ }
27851
+ await system.sleep(this.timing.pollMs);
27852
+ }
27853
+ }
27854
+ /**
27855
+ * `hermes <args>` (`gateway run`), on its own, in `cwd` (else the home), then through Homebrew's
27856
+ * expat when it died of macOS's.
27857
+ */
27858
+ async start(gateway, args, cwd) {
27859
+ const { system, env, log: log2 } = this.options;
27860
+ const port = portOf(gateway.url);
27861
+ const hermes = system.which("hermes", env);
27862
+ if (hermes === void 0) return NO_HERMES;
27863
+ const folder2 = cwd ?? system.home;
27864
+ const plain = gatewayEnv(env);
27865
+ let outcome = await this.launch(gateway, hermes, args, plain, folder2, `hermes ${args.join(" ")}`);
27866
+ if (outcome.state === "ended" && outcome.early && outcome.output.includes(EXPAT_SYMBOL) && system.exists(EXPAT)) {
27867
+ const wrapper = wrapperOf(system.read(hermes) ?? "", plain);
27868
+ if (wrapper !== void 0) {
27869
+ const expat = dirname5(EXPAT);
27870
+ log2(
27871
+ `hermes gateway run stopped at once: Homebrew's Python can't load macOS's expat (${EXPAT_SYMBOL}). Starting ${wrapper.entry} directly with DYLD_LIBRARY_PATH=${expat}.`
27872
+ );
27873
+ const libraries = [expat, plain["DYLD_LIBRARY_PATH"]].filter((each) => each !== void 0 && each !== "").join(":");
27874
+ outcome = await this.launch(
27875
+ gateway,
27876
+ wrapper.entry,
27877
+ args,
27878
+ { ...plain, ...wrapper.vars, DYLD_LIBRARY_PATH: libraries },
27879
+ folder2,
27880
+ `${wrapper.entry} ${args.join(" ")} with DYLD_LIBRARY_PATH=${expat}`
27881
+ );
27882
+ }
27883
+ }
27884
+ if (outcome.state === "up") {
27885
+ log2(`Hermes's gateway${outcome.pid === void 0 ? "" : `, pid ${outcome.pid},`} answers on port ${port}. It logs to ${this.options.logFile}.`);
27886
+ return void 0;
27887
+ }
27888
+ if (outcome.state === "silent") return `Hermes's gateway started, but nothing answers on port ${port}.`;
27889
+ return line(`Hermes's gateway stopped as it started: ${whyFrom(outcome.output, outcome.code)}`);
27890
+ }
27891
+ /** A program started, and watched until it answers, ends, or has had its time. */
27892
+ async launch(gateway, command, args, env, cwd, what) {
27893
+ const { system, logFile } = this.options;
27894
+ system.note(logFile, `--- ${new Date(system.now()).toISOString()} chips-hermes started ${what}`);
27895
+ const from = system.size(logFile);
27896
+ const startedAt = system.now();
27897
+ const child = system.launch({ command, args, env, cwd, log: logFile });
27898
+ let ended;
27899
+ void child.exited.then(({ code }) => {
27900
+ ended = { code, at: system.now() };
27901
+ });
27902
+ for (; ; ) {
27903
+ await Promise.resolve();
27904
+ if (ended !== void 0) {
27905
+ return { state: "ended", early: ended.at - startedAt <= this.timing.earlyMs, output: system.since(logFile, from), code: ended.code };
27906
+ }
27907
+ if (await system.healthy(gateway.url)) return { state: "up", pid: child.pid };
27908
+ if (system.now() - startedAt >= this.timing.upMs) return { state: "silent" };
27909
+ await system.sleep(this.timing.pollMs);
27910
+ }
27911
+ }
27912
+ };
27913
+ function restarter(deps) {
27914
+ let running;
27915
+ return (ask2) => {
27916
+ if (running !== void 0) {
27917
+ deps.log(`restart asked by ${ask2.askedBy} while one runs: it is the same restart`);
27918
+ return running;
27919
+ }
27920
+ running = (async () => {
27921
+ deps.log(`restart asked by ${ask2.askedBy}: restarting Hermes's gateway`);
27922
+ deps.hold(true);
27923
+ let trouble;
27924
+ try {
27925
+ trouble = await deps.restart();
27926
+ } catch (error62) {
27927
+ trouble = line(`Restart failed: ${error62 instanceof Error ? error62.message : String(error62)}`);
27928
+ }
27929
+ deps.log(trouble === void 0 ? "restarted, and Hermes answers" : `restart: ${trouble}`);
27930
+ try {
27931
+ await deps.say(trouble);
27932
+ } catch (error62) {
27933
+ deps.log(`could not tell Chips how the restart went: ${String(error62)}`);
27934
+ } finally {
27935
+ deps.hold(false);
27936
+ }
27937
+ })().finally(() => {
27938
+ running = void 0;
27939
+ });
27940
+ return running;
27941
+ };
27942
+ }
27943
+ function gatewayLog(configFolder, platform = process.platform) {
27944
+ return platform === "darwin" ? join9(homedir5(), "Library", "Logs", "chips-hermes-gateway.log") : join9(configFolder, "gateway.log");
27945
+ }
27946
+ function runToEnd(command, args, options) {
27947
+ return new Promise((done) => {
27948
+ const child = spawn2(command, args, { env: options.env, stdio: ["ignore", "pipe", "pipe"] });
27949
+ let output2 = "";
27950
+ const keep = (data) => {
27951
+ output2 = (output2 + String(data)).slice(-8e3);
27952
+ };
27953
+ child.stdout.on("data", keep);
27954
+ child.stderr.on("data", keep);
27955
+ const limit = setTimeout(() => child.kill("SIGTERM"), options.timeoutMs);
27956
+ child.on("error", (error62) => {
27957
+ clearTimeout(limit);
27958
+ done({ code: null, output: error62.message });
27959
+ });
27960
+ child.on("close", (code) => {
27961
+ clearTimeout(limit);
27962
+ done({ code, output: output2 });
27963
+ });
27964
+ });
27965
+ }
27966
+ function thisMachine() {
27967
+ return {
27968
+ platform: process.platform,
27969
+ uid: process.getuid?.() ?? -1,
27970
+ home: homedir5(),
27971
+ exists: existsSync8,
27972
+ read(path) {
27973
+ try {
27974
+ return readFileSync8(path, "utf8");
27975
+ } catch {
27976
+ return void 0;
27977
+ }
27978
+ },
27979
+ which(command, env) {
27980
+ for (const folder2 of (env["PATH"] ?? "").split(delimiter2)) {
27981
+ if (folder2 === "") continue;
27982
+ const path = join9(folder2, command);
27983
+ try {
27984
+ accessSync2(path, constants2.X_OK);
27985
+ return path;
27986
+ } catch {
27987
+ }
27988
+ }
27989
+ return void 0;
27990
+ },
27991
+ async listener(port) {
27992
+ const { output: output2 } = await runToEnd("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-Fp"], { timeoutMs: 5e3 });
27993
+ const pid = Number(/^p(\d+)$/m.exec(output2)?.[1]);
27994
+ return Number.isInteger(pid) && pid > 0 ? pid : void 0;
27995
+ },
27996
+ async describe(pid) {
27997
+ const { code, output: output2 } = await runToEnd("ps", ["-ww", "-o", "uid=", "-o", "command=", "-p", String(pid)], { timeoutMs: 5e3 });
27998
+ const found = /^\s*(\d+)\s+(.*)$/m.exec(output2);
27999
+ if (code !== 0 || found === null) return void 0;
28000
+ return { uid: Number(found[1]), command: found[2] ?? "", cwd: processFolder(pid) };
28001
+ },
28002
+ alive(pid) {
28003
+ try {
28004
+ process.kill(pid, 0);
28005
+ return true;
28006
+ } catch (error62) {
28007
+ return error62.code === "EPERM";
28008
+ }
28009
+ },
28010
+ signal(pid, signal) {
28011
+ try {
28012
+ process.kill(pid, signal);
28013
+ } catch {
28014
+ }
28015
+ },
28016
+ run: (command, args, env, timeoutMs) => runToEnd(command, args, { env, timeoutMs }),
28017
+ launch({ command, args, env, cwd, log: log2 }) {
28018
+ const out = openSync2(log2, "a");
28019
+ try {
28020
+ const child = spawn2(command, args, { cwd, env, detached: true, stdio: ["ignore", out, out] });
28021
+ const exited = new Promise((done) => {
28022
+ child.once("exit", (code, signal) => done({ code, signal }));
28023
+ child.once("error", (error62) => {
28024
+ appendFileSync2(log2, `${error62.message}
28025
+ `);
28026
+ done({ code: null, signal: null });
28027
+ });
28028
+ });
28029
+ child.unref();
28030
+ return { pid: child.pid, exited };
28031
+ } finally {
28032
+ closeSync2(out);
28033
+ }
28034
+ },
28035
+ note(log2, words4) {
28036
+ appendFileSync2(log2, `${words4}
28037
+ `);
28038
+ },
28039
+ size(log2) {
28040
+ try {
28041
+ return statSync4(log2).size;
28042
+ } catch {
28043
+ return 0;
28044
+ }
28045
+ },
28046
+ since(log2, from) {
28047
+ try {
28048
+ const to = statSync4(log2).size;
28049
+ const start = Math.max(from, to - 64e3);
28050
+ const buffer = Buffer.alloc(Math.max(0, to - start));
28051
+ const fd = openSync2(log2, "r");
28052
+ try {
28053
+ readSync(fd, buffer, 0, buffer.length, start);
28054
+ } finally {
28055
+ closeSync2(fd);
28056
+ }
28057
+ return buffer.toString("utf8");
28058
+ } catch {
28059
+ return "";
28060
+ }
28061
+ },
28062
+ healthy: (url2) => new HermesClient({ url: url2, key: "" }, (input2, init) => fetch(input2, { ...init, signal: AbortSignal.timeout(5e3) })).healthy(),
28063
+ sleep: (ms) => new Promise((done) => setTimeout(done, ms)),
28064
+ now: () => Date.now()
28065
+ };
28066
+ }
28067
+
28068
+ // src/keep.ts
28069
+ import { homedir as homedir6 } from "node:os";
28070
+ import { basename as basename5, join as join10, resolve as resolve4 } from "node:path";
27586
28071
  import { fileURLToPath as fileURLToPath2 } from "node:url";
27587
28072
  var flags = process.argv.slice(2);
27588
28073
  var words3 = flags.filter((word) => !word.startsWith("--"));
27589
- var whole = (word) => resolve3(word.startsWith("~/") ? join9(homedir5(), word.slice(2)) : word);
28074
+ var whole = (word) => resolve4(word.startsWith("~/") ? join10(homedir6(), word.slice(2)) : word);
27590
28075
  var here = fileURLToPath2(new URL("..", import.meta.url)).replace(/\/$/, "");
27591
28076
  var self = fileURLToPath2(import.meta.url);
27592
28077
  var run = self.endsWith(".js") ? [self] : ["--import", "tsx", "src/main.ts"];
27593
- var again = process.env["npm_lifecycle_event"] === "keep" ? "npm run keep --" : basename4(process.argv[1] ?? "chips");
28078
+ var again = process.env["npm_lifecycle_event"] === "keep" ? "npm run keep --" : basename5(process.argv[1] ?? "chips");
27594
28079
  var stopLine = process.env["CHIPS_STOP_COMMAND"] || [again, ...words3, "--stop"].join(" ");
27595
- var end = (line, code) => {
27596
- (code === 0 ? process.stdout : process.stderr).write(`${line}
28080
+ var end = (line2, code) => {
28081
+ (code === 0 ? process.stdout : process.stderr).write(`${line2}
27597
28082
  `);
27598
28083
  process.exit(code);
27599
28084
  };
@@ -27632,9 +28117,9 @@ function keepIfAsked(label2, url2, names, log2, before = () => void 0) {
27632
28117
  }
27633
28118
 
27634
28119
  // src/setup.ts
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";
28120
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
28121
+ import { homedir as homedir7 } from "node:os";
28122
+ import { join as join11, resolve as resolve5 } from "node:path";
27638
28123
  var SetupError = class extends Error {
27639
28124
  constructor(message, words4 = {}) {
27640
28125
  super(message);
@@ -27643,21 +28128,21 @@ var SetupError = class extends Error {
27643
28128
  words;
27644
28129
  name = "SetupError";
27645
28130
  };
27646
- var shown = (path) => path.startsWith(`${homedir6()}/`) ? `~${path.slice(homedir6().length)}` : path;
27647
- var portOf = (url2) => {
28131
+ var shown = (path) => path.startsWith(`${homedir7()}/`) ? `~${path.slice(homedir7().length)}` : path;
28132
+ var portOf2 = (url2) => {
27648
28133
  const parsed = new URL(url2);
27649
28134
  return parsed.port || (parsed.protocol === "https:" ? "443" : "80");
27650
28135
  };
27651
- function checkedWords(config3, restart = false) {
28136
+ function checkedWords(config3, restart2 = false) {
27652
28137
  const urls = new Set(config3.agents.map((agent) => agent.address.url));
27653
28138
  const [url2] = urls;
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;
28139
+ const found = urls.size === 1 && url2 !== void 0 ? `Its API server answers on port ${portOf2(url2)}` : `Its API server answers for each of ${config3.agents.length} agents`;
28140
+ return restart2 ? `${found}. ${RESTART}` : found;
27656
28141
  }
27657
28142
  var RESTART = "Start hermes gateway again once, so it tells Chips how full each agent's context is.";
27658
28143
  function readyHooks(config3, options) {
27659
28144
  const lines2 = [];
27660
- let restart = false;
28145
+ let restart2 = false;
27661
28146
  const done = /* @__PURE__ */ new Set();
27662
28147
  for (const agent of config3.agents) {
27663
28148
  const profile = profileName(agent.address.profile);
@@ -27668,12 +28153,12 @@ function readyHooks(config3, options) {
27668
28153
  try {
27669
28154
  outcome = addHook(home2, options.port, profile, options.secret);
27670
28155
  } catch (error62) {
27671
- lines2.push(`Chips's webhook wasn't added to ${shown(join10(home2, "config.yaml"))}: ${error62 instanceof Error ? error62.message : String(error62)}`);
28156
+ lines2.push(`Chips's webhook wasn't added to ${shown(join11(home2, "config.yaml"))}: ${error62 instanceof Error ? error62.message : String(error62)}`);
27672
28157
  continue;
27673
28158
  }
27674
28159
  if (outcome.state === "added") {
27675
28160
  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;
28161
+ restart2 ||= options.gatewayUp;
27677
28162
  } else if (outcome.state === "by_hand") {
27678
28163
  lines2.push(
27679
28164
  `${shown(outcome.path)} was left as it is: ${outcome.why}. For Chips to show how full each agent's context is, add these lines to it, then start hermes gateway again:
@@ -27683,30 +28168,30 @@ ${outcome.lines}`
27683
28168
  lines2.push(`Hermes has no ${profile} profile at ${shown(home2)}, so no webhook was added for it.`);
27684
28169
  }
27685
28170
  }
27686
- if (restart) lines2.push(RESTART);
27687
- return { lines: lines2, restart };
28171
+ if (restart2) lines2.push(RESTART);
28172
+ return { lines: lines2, restart: restart2 };
27688
28173
  }
27689
28174
  var STILL_GOOD = "The link is still good: run the same command again once that's done.";
27690
28175
  var EXAMPLE = '{ "agents": [{ "name": "Hermes", "profile": "default", "keyEnv": "API_SERVER_KEY" }] }';
27691
28176
  var PLAIN = { agents: [{ name: "Hermes", profile: "default", keyEnv: "API_SERVER_KEY" }] };
27692
28177
  function hermesEnvPath(env = process.env) {
27693
- return resolve4(env["HERMES_HOME"] ?? resolve4(homedir6(), ".hermes"), ".env");
28178
+ return resolve5(env["HERMES_HOME"] ?? resolve5(homedir7(), ".hermes"), ".env");
27694
28179
  }
27695
28180
  function fromHermes(name2, env) {
27696
28181
  const path = hermesEnvPath(env);
27697
- if (!existsSync8(path)) return void 0;
27698
- for (const line of readFileSync8(path, "utf8").split("\n")) {
27699
- const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(line);
28182
+ if (!existsSync9(path)) return void 0;
28183
+ for (const line2 of readFileSync9(path, "utf8").split("\n")) {
28184
+ const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(line2);
27700
28185
  if (match?.[1] === name2) return match[2]?.replace(/^(["'])(.*)\1$/, "$2");
27701
28186
  }
27702
28187
  return void 0;
27703
28188
  }
27704
28189
  function folderOf(said2) {
27705
28190
  if (typeof said2 !== "string" || said2 === "") return void 0;
27706
- return resolve4(said2 === "~" || said2.startsWith("~/") ? homedir6() + said2.slice(1) : said2);
28191
+ return resolve5(said2 === "~" || said2.startsWith("~/") ? homedir7() + said2.slice(1) : said2);
27707
28192
  }
27708
28193
  function readConfig(path, env = process.env) {
27709
- const raw = existsSync8(path) ? JSON.parse(readFileSync8(path, "utf8")) : PLAIN;
28194
+ const raw = existsSync9(path) ? JSON.parse(readFileSync9(path, "utf8")) : PLAIN;
27710
28195
  const url2 = typeof raw.url === "string" ? raw.url : "http://127.0.0.1:8642";
27711
28196
  const folder2 = folderOf(raw.folder);
27712
28197
  if (!Array.isArray(raw.agents) || raw.agents.length === 0) {
@@ -27745,7 +28230,7 @@ function readConfig(path, env = process.env) {
27745
28230
  return {
27746
28231
  ...typeof raw.name === "string" ? { name: raw.name } : {},
27747
28232
  agents,
27748
- folders: folderOf(raw.folders) ?? resolve4(homedir6(), "chips-hermes", "agents"),
28233
+ folders: folderOf(raw.folders) ?? resolve5(homedir7(), "chips-hermes", "agents"),
27749
28234
  ...typeof hookPort === "number" && Number.isInteger(hookPort) && hookPort > 0 && hookPort < 65536 ? { hookPort } : {}
27750
28235
  };
27751
28236
  }
@@ -27756,7 +28241,7 @@ async function checkHermes(config3, clientFor = (agent) => new HermesClient(agen
27756
28241
  throw new SetupError(
27757
28242
  `Hermes isn't answering for ${agent.name} at ${agent.address.url}: start its API server (API_SERVER_ENABLED=true, then hermes gateway). ${STILL_GOOD}`,
27758
28243
  {
27759
- detail: `Nothing answers on port ${portOf(agent.address.url)}`,
28244
+ detail: `Nothing answers on port ${portOf2(agent.address.url)}`,
27760
28245
  says: "Hermes's API server is off.",
27761
28246
  doing: `Add this line to ${shown(hermesEnvPath())}, then start Hermes with hermes gateway.`,
27762
28247
  copy: "API_SERVER_ENABLED=true"
@@ -27777,20 +28262,20 @@ async function checkHermes(config3, clientFor = (agent) => new HermesClient(agen
27777
28262
  }
27778
28263
 
27779
28264
  // src/main.ts
27780
- var home = (path) => path.startsWith("~/") ? join11(homedir7(), path.slice(2)) : path;
28265
+ var home = (path) => path.startsWith("~/") ? join12(homedir8(), path.slice(2)) : path;
27781
28266
  var link = process.env["CHIPS_JOIN_LINK"] || void 0;
27782
28267
  var LABEL = "chat.chips.hermes";
27783
28268
  stopIfAsked(LABEL);
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: {} };
28269
+ var configPath = resolve6(home(words3[0] ?? "~/.chips-hermes/config.json"));
28270
+ var folder = dirname6(configPath);
28271
+ var statePath = join12(folder, "sessions.json");
28272
+ var state = existsSync10(statePath) ? JSON.parse(readFileSync10(statePath, "utf8")) : { sessions: {} };
27788
28273
  var saveState = (next) => {
27789
28274
  mkdirSync6(folder, { recursive: true, mode: 448 });
27790
28275
  writeFileSync5(statePath, `${JSON.stringify(next, null, 2)}
27791
28276
  `, { mode: 384 });
27792
28277
  };
27793
- var log = (line) => process.stdout.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${line}
28278
+ var log = (line2) => process.stdout.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${line2}
27794
28279
  `);
27795
28280
  var freePort = () => new Promise((done, failed) => {
27796
28281
  const server = createServer2();
@@ -27815,7 +28300,7 @@ try {
27815
28300
  const first = config2.agents[0];
27816
28301
  const gatewayUp = link !== void 0 || first !== void 0 && await new HermesClient(first.address).healthy();
27817
28302
  const hooks = readyHooks(config2, { root, port: fills.port, secret: fills.secret, gatewayUp });
27818
- for (const line of hooks.lines) log(line);
28303
+ for (const line2 of hooks.lines) log(line2);
27819
28304
  if (link !== void 0) reportStep("checked", { detail: checkedWords(config2, hooks.restart) });
27820
28305
  } catch (error62) {
27821
28306
  if (!(error62 instanceof SetupError)) throw error62;
@@ -27825,6 +28310,15 @@ try {
27825
28310
  process.exit(1);
27826
28311
  }
27827
28312
  var name = config2.name ?? "Hermes";
28313
+ var gateways2 = new Gateways({
28314
+ gateways: gatewaysOf(config2.agents),
28315
+ root,
28316
+ env: process.env,
28317
+ system: thisMachine(),
28318
+ log: (line2) => log(`gateway: ${line2}`),
28319
+ logFile: gatewayLog(folder)
28320
+ });
28321
+ if (link === void 0) await gateways2.startIfDown();
27828
28322
  var refused2 = (error62) => {
27829
28323
  if (!(error62 instanceof JoinError)) throw error62;
27830
28324
  process.stderr.write(`${error62.message}
@@ -27841,11 +28335,21 @@ var joined = await join3({
27841
28335
  }).catch(refused2);
27842
28336
  keepIfAsked(LABEL, joined.url, ["HERMES_HOME", ...config2.agents.map((agent) => agent.keyEnv ?? "API_SERVER_KEY")], log);
27843
28337
  var bridge;
28338
+ var restart = restarter({
28339
+ restart: () => gateways2.restart(),
28340
+ hold: (held) => bridge?.hold(held),
28341
+ say: async (trouble) => {
28342
+ if (bridge !== void 0) return bridge.restarted(trouble);
28343
+ await (trouble === void 0 ? chips.reportOk() : chips.reportTrouble(trouble));
28344
+ },
28345
+ log: (line2) => log(`gateway: ${line2}`)
28346
+ });
27844
28347
  var chips = await connect({
27845
28348
  url: joined.url,
27846
28349
  token: joined.token,
27847
28350
  client: { name: "chips-hermes", version: "1.0.0" },
27848
- store: fileStore(join11(folder, "chips-state.json")),
28351
+ store: fileStore(join12(folder, "chips-state.json")),
28352
+ onRestart: restart,
27849
28353
  settings: settingsFor(() => bridge)
27850
28354
  });
27851
28355
  var git = await checkGit();