@chipschat/hermes 1.0.5 → 1.0.8

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