@chipschat/hermes 1.0.16 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -152,11 +152,18 @@ point (the one the `hermes` wrapper runs) directly, with the wrapper's variables
152
152
  `DYLD_LIBRARY_PATH=/opt/homebrew/opt/expat/lib`, which macOS would drop at the wrapper, and its log
153
153
  says so.
154
154
 
155
- **Folders.** Each agent gets a folder of its own, `~/chips-hermes/agents/<its name>`, made the
155
+ **Folders.** Each agent gets a folder of its own, `~/Chips/Agents/hermes/<its name>`, made the
156
156
  first time it is spoken to and kept by its Chips agent after a rename. Each run tells the agent it
157
157
  is its folder, in the run's `instructions`. The files an answer sends go to the room from there and
158
158
  from nowhere else.
159
159
 
160
+ Everything the bridge keeps is under `~/Chips` (Chips ADR 0283): its config, sessions and state in
161
+ `~/Chips/.bridges/hermes`, the agents' folders in `~/Chips/Agents/hermes`. Until 1.0.17 they were
162
+ `~/.chips-hermes` and `~/chips-hermes/agents`. The first start of 1.0.17 moves them, and the folder
163
+ paths its sessions saved. It renames and never copies; where something is at the new place already
164
+ it moves in what isn't there yet and says in its log what it left. A file in `~/chips-hermes` it
165
+ didn't make stays there.
166
+
160
167
  **Each new agent, a profile of its own.** An agent made in Chips (Add, then a new one, or a
161
168
  space from the +) gets a Hermes profile, made by Hermes with
162
169
  `hermes profile create <name> --clone-from <the Main agent's> --no-alias`:
@@ -172,7 +179,7 @@ space from the +) gets a Hermes profile, made by Hermes with
172
179
  The profile is named after the agent when it is made, in lowercase letters, digits and `-`
173
180
  (`nova`, `nova-2` when Hermes has a `nova` already), and keeps that name after a rename, as the
174
181
  agent's folder does. Deleting the agent in Chips leaves its profile in Hermes: what it learned is
175
- yours to delete, with `hermes profile delete <name>`. `~/.chips-hermes/sessions.json` remembers
182
+ yours to delete, with `hermes profile delete <name>`. `~/Chips/.bridges/hermes/sessions.json` remembers
176
183
  which profile is whose.
177
184
 
178
185
  The one gateway serves every profile under `/p/<name>`, and takes a new one at once when the bridge
@@ -183,7 +190,7 @@ on it serves every profile. Hermes's gateway also keeps sending a new profile's
183
190
  Main agent's address until it starts again, which the bridge drops. Where Hermes doesn't make a
184
191
  profile, the agent runs on the first profile, in a session of its own, and the log says why.
185
192
 
186
- **More than one agent.** Name them in `~/.chips-hermes/config.json` (see `config.example.json`).
193
+ **More than one agent.** Name them in `~/Chips/.bridges/hermes/config.json` (see `config.example.json`).
187
194
  Each agent is one of three:
188
195
  - the default profile;
189
196
  - a profile under `/p/<profile>`, with `gateway.multiplex_profiles` on and that profile's own key;
@@ -1,6 +1,6 @@
1
1
  # Third-party notices
2
2
 
3
- `dist/main.js` in @chipschat/hermes 1.0.16 carries these packages, bundled in. Each one's
3
+ `dist/main.js` in @chipschat/hermes 1.0.17 carries these packages, bundled in. Each one's
4
4
  licence follows.
5
5
 
6
6
  ## @chipschat/harness 1.0.2 (Apache-2.0)
package/dist/main.js CHANGED
@@ -9,10 +9,9 @@ var __export = (target, all) => {
9
9
 
10
10
  // src/main.ts
11
11
  import { randomBytes as randomBytes3 } from "node:crypto";
12
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "node:fs";
12
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "node:fs";
13
13
  import { createServer as createServer2 } from "node:net";
14
- import { homedir as homedir8 } from "node:os";
15
- import { dirname as dirname6, join as join13, resolve as resolve6 } from "node:path";
14
+ import { dirname as dirname7, join as join14 } from "node:path";
16
15
 
17
16
  // node_modules/zod/v4/classic/external.js
18
17
  var external_exports = {};
@@ -5108,7 +5107,7 @@ function isRecursive(inst, stack, resolve7) {
5108
5107
  }
5109
5108
  return answer;
5110
5109
  };
5111
- const merge2 = (answer) => {
5110
+ const merge3 = (answer) => {
5112
5111
  if (answer > result)
5113
5112
  result = answer;
5114
5113
  };
@@ -5117,7 +5116,7 @@ function isRecursive(inst, stack, resolve7) {
5117
5116
  switch (kind) {
5118
5117
  case "object": {
5119
5118
  const raw = rawShape(def);
5120
- merge2(raw ? shape(raw, true) : ASSUMED);
5119
+ merge3(raw ? shape(raw, true) : ASSUMED);
5121
5120
  check2(def.catchall);
5122
5121
  break;
5123
5122
  }
@@ -5167,7 +5166,7 @@ function isRecursive(inst, stack, resolve7) {
5167
5166
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
5168
5167
  case "lazy": {
5169
5168
  const inner = def._cachedInner ?? (resolve7 ? inst._zod.innerType : void 0);
5170
- merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
5169
+ merge3(inner ? isRecursive(inner, stack, false) : ASSUMED);
5171
5170
  break;
5172
5171
  }
5173
5172
  // a leaf by choice: `parts` are regex fragments, not data positions
@@ -23400,8 +23399,8 @@ var Project = class {
23400
23399
  const end2 = all.findIndex((line2, at) => at > start && line2.startsWith(">>>>>>> "));
23401
23400
  if (start < 0 || end2 < 0)
23402
23401
  return void 0;
23403
- const shown2 = all.slice(Math.max(0, start - 1), end2 + 2);
23404
- return shown2.length <= MARKED_LINES ? shown2.join("\n") : [...shown2.slice(0, MARKED_LINES - 1), "\u2026"].join("\n");
23402
+ const shown3 = all.slice(Math.max(0, start - 1), end2 + 2);
23403
+ return shown3.length <= MARKED_LINES ? shown3.join("\n") : [...shown3.slice(0, MARKED_LINES - 1), "\u2026"].join("\n");
23405
23404
  } catch {
23406
23405
  return void 0;
23407
23406
  }
@@ -25325,7 +25324,7 @@ async function ask(url2, options, send, machine) {
25325
25324
  if (asked.status !== 201)
25326
25325
  throw await refused(asked);
25327
25326
  const { request_id, secret, code, interval } = await asked.json();
25328
- const show = options.onCode ?? ((shown2, where) => process.stderr.write(`Allow ${options.name} in Chips at ${where}. Its code is ${shown2}.
25327
+ const show = options.onCode ?? ((shown3, where) => process.stderr.write(`Allow ${options.name} in Chips at ${where}. Its code is ${shown3}.
25329
25328
  `));
25330
25329
  show(code, url2);
25331
25330
  for (; ; ) {
@@ -25902,8 +25901,8 @@ function profileName(profile) {
25902
25901
  const name2 = (profile ?? "default").trim().toLowerCase();
25903
25902
  return name2 === "" ? "default" : name2;
25904
25903
  }
25905
- function envValue(home2, name2) {
25906
- const path = join5(home2, ".env");
25904
+ function envValue(home, name2) {
25905
+ const path = join5(home, ".env");
25907
25906
  if (!existsSync5(path)) return void 0;
25908
25907
  let found;
25909
25908
  for (const line2 of readFileSync5(path, "utf8").split("\n")) {
@@ -25915,16 +25914,16 @@ function envValue(home2, name2) {
25915
25914
  }
25916
25915
  return found;
25917
25916
  }
25918
- function appendEnv(home2, name2, value) {
25919
- const path = join5(home2, ".env");
25917
+ function appendEnv(home, name2, value) {
25918
+ const path = join5(home, ".env");
25920
25919
  const text = readFileSync5(path, "utf8");
25921
25920
  const mode = statSync(path).mode & 511;
25922
25921
  appendFileSync(path, `${text === "" || text.endsWith("\n") ? "" : "\n"}${name2}=${value}
25923
25922
  `);
25924
25923
  chmodSync3(path, mode);
25925
25924
  }
25926
- function configText(home2) {
25927
- const path = join5(home2, "config.yaml");
25925
+ function configText(home) {
25926
+ const path = join5(home, "config.yaml");
25928
25927
  try {
25929
25928
  return existsSync5(path) ? readFileSync5(path, "utf8") : "";
25930
25929
  } catch {
@@ -26006,30 +26005,30 @@ function isFolder(path) {
26006
26005
  return false;
26007
26006
  }
26008
26007
  }
26009
- function workFolder(home2, env = process.env) {
26010
- const configured = yamlValue(configText(home2), "terminal", "cwd");
26008
+ function workFolder(home, env = process.env) {
26009
+ const configured = yamlValue(configText(home), "terminal", "cwd");
26011
26010
  if (configured !== void 0 && configured !== "" && configured !== ".") {
26012
26011
  const folder2 = expanded(configured);
26013
26012
  if (isFolder(folder2)) return folder2;
26014
26013
  }
26015
26014
  for (const name2 of ["TERMINAL_CWD", "MESSAGING_CWD"]) {
26016
- const said2 = envValue(home2, name2) ?? env[name2];
26015
+ const said2 = envValue(home, name2) ?? env[name2];
26017
26016
  if (said2 !== void 0 && said2 !== "" && isFolder(expanded(said2))) return expanded(said2);
26018
26017
  }
26019
- return gatewayFolder(home2);
26018
+ return gatewayFolder(home);
26020
26019
  }
26021
26020
  var gateways = /* @__PURE__ */ new Map();
26022
- function gatewayFolder(home2) {
26021
+ function gatewayFolder(home) {
26023
26022
  let raw;
26024
26023
  try {
26025
- raw = readFileSync5(join5(home2, "gateway.pid"), "utf8").trim();
26024
+ raw = readFileSync5(join5(home, "gateway.pid"), "utf8").trim();
26026
26025
  } catch {
26027
26026
  return void 0;
26028
26027
  }
26029
- const known = gateways.get(home2);
26028
+ const known = gateways.get(home);
26030
26029
  if (known?.pidFile === raw) return known.folder;
26031
26030
  const folder2 = folderOfPid(raw);
26032
- gateways.set(home2, { pidFile: raw, folder: folder2 });
26031
+ gateways.set(home, { pidFile: raw, folder: folder2 });
26033
26032
  return folder2;
26034
26033
  }
26035
26034
  function folderOfPid(raw) {
@@ -26107,8 +26106,8 @@ function memoryProblem(text, limit, what) {
26107
26106
  }
26108
26107
  return void 0;
26109
26108
  }
26110
- function memorySettings(home2) {
26111
- const text = configText(home2);
26109
+ function memorySettings(home) {
26110
+ const text = configText(home);
26112
26111
  const value = (key) => yamlValue(text, "memory", key);
26113
26112
  return {
26114
26113
  ...yamlBool(value("memory_enabled")) === false ? {} : { memory: yamlNumber(value("memory_char_limit")) ?? MEMORY_LIMIT },
@@ -26116,21 +26115,21 @@ function memorySettings(home2) {
26116
26115
  };
26117
26116
  }
26118
26117
  function sourcesOf(profile, env = process.env) {
26119
- const { name: name2, home: home2 } = profile;
26118
+ const { name: name2, home } = profile;
26120
26119
  const found = [];
26121
- const soul = join6(home2, "SOUL.md");
26120
+ const soul = join6(home, "SOUL.md");
26122
26121
  if (existsSync6(soul)) found.push({ path: soul, kind: "soul", file: "SOUL.md", profile: name2 });
26123
- const folder2 = workFolder(home2, env);
26122
+ const folder2 = workFolder(home, env);
26124
26123
  const project = folder2 === void 0 ? void 0 : PROJECT_FILES.find((file3) => readText(join6(folder2, file3)).trim() !== "");
26125
26124
  if (folder2 !== void 0 && project !== void 0) {
26126
26125
  found.push({ path: join6(folder2, project), kind: "project", file: project, profile: name2 });
26127
26126
  }
26128
- const memory = memorySettings(home2);
26127
+ const memory = memorySettings(home);
26129
26128
  if (memory.user !== void 0) {
26130
- found.push({ path: join6(home2, "memories", "USER.md"), kind: "user", file: "USER.md", profile: name2, limit: memory.user });
26129
+ found.push({ path: join6(home, "memories", "USER.md"), kind: "user", file: "USER.md", profile: name2, limit: memory.user });
26131
26130
  }
26132
26131
  if (memory.memory !== void 0) {
26133
- found.push({ path: join6(home2, "memories", "MEMORY.md"), kind: "memory", file: "MEMORY.md", profile: name2, limit: memory.memory });
26132
+ found.push({ path: join6(home, "memories", "MEMORY.md"), kind: "memory", file: "MEMORY.md", profile: name2, limit: memory.memory });
26134
26133
  }
26135
26134
  return found;
26136
26135
  }
@@ -26209,18 +26208,18 @@ var Picture = class {
26209
26208
  }
26210
26209
  /** The harness's own set, as `PUT /v1/harnesses/:id/context` takes it. */
26211
26210
  harnessCopy() {
26212
- const shown2 = [];
26211
+ const shown3 = [];
26213
26212
  const notShown = [];
26214
26213
  for (const [path, { id: id2, name: name2 }] of this.shared) {
26215
26214
  const entry = this.readers.get(path);
26216
26215
  if (entry === void 0) continue;
26217
26216
  const { source: source2, agents } = entry;
26218
26217
  const { file: file3, tooLarge } = fileOf(source2, name2);
26219
- if (tooLarge || shown2.length >= FILES_SHOWN) {
26218
+ if (tooLarge || shown3.length >= FILES_SHOWN) {
26220
26219
  notShown.push({ name: name2, title: ROWS[source2.kind].title, reason: tooLarge ? "too_large" : "count" });
26221
26220
  continue;
26222
26221
  }
26223
- shown2.push({
26222
+ shown3.push({
26224
26223
  ...file3,
26225
26224
  id: id2,
26226
26225
  used_by: agents.length,
@@ -26228,7 +26227,7 @@ var Picture = class {
26228
26227
  });
26229
26228
  }
26230
26229
  return {
26231
- shared: shown2,
26230
+ shared: shown3,
26232
26231
  ...notShown.length === 0 ? {} : { not_shown: notShown }
26233
26232
  };
26234
26233
  }
@@ -26268,9 +26267,9 @@ var Picture = class {
26268
26267
  /** The file an edit names: an agent's own, or one of the harness's set; with whether it is shared. */
26269
26268
  find(agentId2, name2) {
26270
26269
  if (agentId2 === void 0) {
26271
- for (const [path, { name: shown2 }] of this.shared) {
26270
+ for (const [path, { name: shown3 }] of this.shared) {
26272
26271
  const source2 = this.readers.get(path)?.source;
26273
- if (shown2 === name2 && source2 !== void 0) return { source: source2, shared: true };
26272
+ if (shown3 === name2 && source2 !== void 0) return { source: source2, shared: true };
26274
26273
  }
26275
26274
  return void 0;
26276
26275
  }
@@ -26457,8 +26456,8 @@ var SMALL_THRESHOLD = 0.75;
26457
26456
  var WHY_NOT = "Hermes can't be asked to compact from Chips.";
26458
26457
  var whyReadOnly = (profile) => `Hermes sets this for every agent on its ${profile} profile.`;
26459
26458
  var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
26460
- function limitOf(home2, window) {
26461
- const text = configText(home2);
26459
+ function limitOf(home, window) {
26460
+ const text = configText(home);
26462
26461
  const enabled = yamlBool(yamlValue(text, "compression", "enabled")) ?? true;
26463
26462
  const threshold = yamlNumber(yamlValue(text, "compression", "threshold")) ?? THRESHOLD;
26464
26463
  let ratio = window < SMALL_WINDOW ? Math.max(threshold, SMALL_THRESHOLD) : threshold;
@@ -26911,8 +26910,8 @@ function linesOf2(text) {
26911
26910
  return { indent, words: words4 };
26912
26911
  });
26913
26912
  }
26914
- function hookPortIn(home2) {
26915
- const path = join7(home2, "config.yaml");
26913
+ function hookPortIn(home) {
26914
+ const path = join7(home, "config.yaml");
26916
26915
  if (!existsSync7(path)) return void 0;
26917
26916
  const escaped = HOOK_PATH.replace(/[/]/g, "\\/");
26918
26917
  const match = new RegExp(`^\\s*(?:-\\s*)?url:\\s*["']?http:\\/\\/127\\.0\\.0\\.1:(\\d+)${escaped}`, "m").exec(
@@ -26931,15 +26930,15 @@ function entryLines(indent, url2, secret) {
26931
26930
  `${indent} timeout: 5`
26932
26931
  ];
26933
26932
  }
26934
- function secretInEnv(home2, secret) {
26935
- const path = join7(home2, ".env");
26933
+ function secretInEnv(home, secret) {
26934
+ const path = join7(home, ".env");
26936
26935
  if (!existsSync7(path)) return false;
26937
- if (envValue(home2, SECRET_ENV) === void 0) appendEnv(home2, SECRET_ENV, secret);
26936
+ if (envValue(home, SECRET_ENV) === void 0) appendEnv(home, SECRET_ENV, secret);
26938
26937
  return true;
26939
26938
  }
26940
- function addHook(home2, port, profile, secret) {
26941
- const path = join7(home2, "config.yaml");
26942
- if (!existsSync7(home2)) return { state: "no_home", path };
26939
+ function addHook(home, port, profile, secret) {
26940
+ const path = join7(home, "config.yaml");
26941
+ if (!existsSync7(home)) return { state: "no_home", path };
26943
26942
  const url2 = hookUrl(port, profile);
26944
26943
  const text = existsSync7(path) ? readFileSync7(path, "utf8") : "";
26945
26944
  const lines2 = linesOf2(text);
@@ -26947,16 +26946,16 @@ function addHook(home2, port, profile, secret) {
26947
26946
  if (lines2.some((line2) => ours2.test(line2.words))) return { state: "there", path };
26948
26947
  const where = lines2.findIndex((line2) => line2.indent === 0 && /^["']?hooks["']?\s*:/.test(line2.words));
26949
26948
  const byHand = (why) => {
26950
- const inEnv2 = secretInEnv(home2, secret);
26951
- const shown2 = entryLines(" ", url2, inEnv2 ? { env: SECRET_ENV } : { inline: secret });
26952
- return { state: "by_hand", path, why, lines: ["hooks:", ...shown2].join("\n") };
26949
+ const inEnv2 = secretInEnv(home, secret);
26950
+ const shown3 = entryLines(" ", url2, inEnv2 ? { env: SECRET_ENV } : { inline: secret });
26951
+ return { state: "by_hand", path, why, lines: ["hooks:", ...shown3].join("\n") };
26953
26952
  };
26954
26953
  const plain = /^(?:"[^"]+"|'[^']+'|[A-Za-z_][\w.-]*)\s*:(?:\s|$)/;
26955
26954
  if (lines2.some((line2) => line2.indent === 0 && line2.words !== "" && !plain.test(line2.words))) {
26956
26955
  return byHand("its top level isn't laid out as this bridge expects");
26957
26956
  }
26958
26957
  if (where === -1) {
26959
- const inEnv2 = secretInEnv(home2, secret);
26958
+ const inEnv2 = secretInEnv(home, secret);
26960
26959
  const block = [
26961
26960
  "# Chips: how full each agent's context is, sent to the chips-hermes bridge after each model call.",
26962
26961
  "hooks:",
@@ -26988,7 +26987,7 @@ function addHook(home2, port, profile, secret) {
26988
26987
  if (children.some((line2) => line2.indent !== indent && line2.indent < indent) || children[0]?.words.startsWith("-")) {
26989
26988
  return byHand("its hooks: isn't laid out as this bridge expects");
26990
26989
  }
26991
- const inEnv = secretInEnv(home2, secret);
26990
+ const inEnv = secretInEnv(home, secret);
26992
26991
  const raw = text.split("\n");
26993
26992
  raw.splice(where + 1, 0, ...entryLines(" ".repeat(indent), url2, inEnv ? { env: SECRET_ENV } : { inline: secret }));
26994
26993
  writeWhole(path, raw.join("\n"));
@@ -27115,9 +27114,9 @@ function gatewayRunArgs(command) {
27115
27114
  return plain[0] === "gateway" && plain[1] === "run" ? args : void 0;
27116
27115
  }
27117
27116
  function serviceOf(system, root2, env = {}) {
27118
- const home2 = resolve3(root2);
27117
+ const home = resolve3(root2);
27119
27118
  const native = resolve3(system.home, ".hermes");
27120
- const suffix = home2 === native ? "" : dirname5(home2) === join8(native, "profiles") ? basename4(home2) : createHash3("sha256").update(home2).digest("hex").slice(0, 8);
27119
+ const suffix = home === native ? "" : dirname5(home) === join8(native, "profiles") ? basename4(home) : createHash3("sha256").update(home).digest("hex").slice(0, 8);
27121
27120
  if (system.platform === "darwin") {
27122
27121
  const path = join8(system.home, "Library", "LaunchAgents", `ai.hermes.gateway${suffix ? `-${suffix}` : ""}.plist`);
27123
27122
  return system.exists(path) ? { kind: "launchd", path } : void 0;
@@ -27549,18 +27548,18 @@ function freeProfile(root2, name2, taken) {
27549
27548
  if (!taken.includes(profile) && !existsSync9(join9(root2, "profiles", profile))) return profile;
27550
27549
  }
27551
27550
  }
27552
- function readyProfile(home2, names, hook) {
27553
- for (const file3 of ["MEMORY.md", "USER.md"]) rmSync4(join9(home2, "memories", file3), { force: true });
27554
- if (envValue(home2, KEY_ENV) === void 0) appendEnv(home2, KEY_ENV, randomBytes2(32).toString("hex"));
27551
+ function readyProfile(home, names, hook) {
27552
+ for (const file3 of ["MEMORY.md", "USER.md"]) rmSync4(join9(home, "memories", file3), { force: true });
27553
+ if (envValue(home, KEY_ENV) === void 0) appendEnv(home, KEY_ENV, randomBytes2(32).toString("hex"));
27555
27554
  if (hook === void 0) return void 0;
27556
- const path = join9(home2, "config.yaml");
27555
+ const path = join9(home, "config.yaml");
27557
27556
  if (existsSync9(path)) {
27558
27557
  const theirs = hookUrl(hook.port, names.from).replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
27559
27558
  const entry = new RegExp(`^(\\s*(?:-\\s*)?url:\\s*["']?)${theirs}(["']?\\s*(?:#.*)?)$`, "m");
27560
27559
  const text = readFileSync9(path, "utf8");
27561
27560
  if (entry.test(text)) writeWhole(path, text.replace(entry, `$1${hookUrl(hook.port, names.profile)}$2`));
27562
27561
  }
27563
- return addHook(home2, hook.port, names.profile, hook.secret);
27562
+ return addHook(home, hook.port, names.profile, hook.secret);
27564
27563
  }
27565
27564
  function hermesProfiles(options) {
27566
27565
  return {
@@ -27633,9 +27632,9 @@ var WANTS = [
27633
27632
  ];
27634
27633
  function settingsFor(active) {
27635
27634
  const bridge2 = () => {
27636
- const there = active();
27637
- if (there === void 0) throw new Refuse("The bridge is starting. Try again in a moment.");
27638
- return there;
27635
+ const there2 = active();
27636
+ if (there2 === void 0) throw new Refuse("The bridge is starting. Try again in a moment.");
27637
+ return there2;
27639
27638
  };
27640
27639
  return {
27641
27640
  context: (edit) => bridge2().onContextEdit(edit),
@@ -28026,10 +28025,10 @@ var Bridge = class {
28026
28025
  }
28027
28026
  /** The Main agent into the harness's own room, in the place of whichever of ours is there. */
28028
28027
  async goHome(main2, known) {
28029
- const { chips: chips2, home: home2 } = this.options;
28030
- if (home2 === void 0 || main2.spaces.includes(home2)) return;
28031
- const there = known.find((agent) => agent.agentId !== main2.agentId && agent.spaces.includes(home2));
28032
- await chips2.agents.attach(main2.agentId, { spaceId: home2, replacing: there?.agentId ?? chips2.harnessId }).catch((error62) => this.log(`${main2.name} stays out of its home: ${String(error62)}`));
28028
+ const { chips: chips2, home } = this.options;
28029
+ if (home === void 0 || main2.spaces.includes(home)) return;
28030
+ const there2 = known.find((agent) => agent.agentId !== main2.agentId && agent.spaces.includes(home));
28031
+ await chips2.agents.attach(main2.agentId, { spaceId: home, replacing: there2?.agentId ?? chips2.harnessId }).catch((error62) => this.log(`${main2.name} stays out of its home: ${String(error62)}`));
28033
28032
  }
28034
28033
  /**
28035
28034
  * A person asked for a new agent in a room (Chips ADR 0108): one of the harness's, keyed by the
@@ -28065,12 +28064,12 @@ var Bridge = class {
28065
28064
  if (profiles === void 0 || root2 === void 0 || main2 === void 0 || this.own.has(agentId2)) return;
28066
28065
  const from = profileName(main2.config.address.profile);
28067
28066
  const profile = freeProfile(root2, name2, Object.values(state2.made ?? {}));
28068
- const home2 = profileHome(root2, profile);
28067
+ const home = profileHome(root2, profile);
28069
28068
  const secret = state2.fills?.secret ?? this.options.fills?.secret;
28070
28069
  try {
28071
28070
  await profiles.create(profile, from);
28072
28071
  const port = this.fillsPort;
28073
- const hook = readyProfile(home2, { from, profile }, port === void 0 || secret === void 0 ? void 0 : { port, secret });
28072
+ const hook = readyProfile(home, { from, profile }, port === void 0 || secret === void 0 ? void 0 : { port, secret });
28074
28073
  if (hook?.state === "by_hand") this.log(`context window: ${hook.path} was left as it is: ${hook.why}. Add these lines to it:
28075
28074
  ${hook.lines}`);
28076
28075
  } catch (error62) {
@@ -28098,12 +28097,12 @@ ${hook.lines}`);
28098
28097
  const main2 = this.agents[0];
28099
28098
  const root2 = this.options.hermesRoot;
28100
28099
  if (main2 === void 0 || root2 === void 0) return void 0;
28101
- const home2 = profileHome(root2, profile);
28100
+ const home = profileHome(root2, profile);
28102
28101
  const config3 = {
28103
28102
  name: name2,
28104
- address: { url: main2.config.address.url, profile, key: envValue(home2, KEY_ENV) ?? "" },
28103
+ address: { url: main2.config.address.url, profile, key: envValue(home, KEY_ENV) ?? "" },
28105
28104
  keyEnv: KEY_ENV,
28106
- keyFrom: join10(home2, ".env")
28105
+ keyFrom: join10(home, ".env")
28107
28106
  };
28108
28107
  const client = this.options.clientFor?.(config3) ?? new HermesClient(config3.address);
28109
28108
  return { config: config3, key: agentId2, client, agentId: agentId2, up: true };
@@ -28850,12 +28849,55 @@ function lowerFirst(text) {
28850
28849
  }
28851
28850
 
28852
28851
  // src/keep.ts
28853
- import { homedir as homedir6 } from "node:os";
28854
- import { basename as basename5, join as join11, resolve as resolve4 } from "node:path";
28852
+ import { basename as basename5 } from "node:path";
28855
28853
  import { fileURLToPath as fileURLToPath2 } from "node:url";
28854
+
28855
+ // src/places.ts
28856
+ import { existsSync as existsSync10 } from "node:fs";
28857
+ import { homedir as homedir6 } from "node:os";
28858
+ import { isAbsolute as isAbsolute2, join as join11, relative, resolve as resolve4, sep as sep2 } from "node:path";
28859
+ function placesOf(home = homedir6()) {
28860
+ return {
28861
+ settings: join11(home, "Chips", ".bridges", "hermes"),
28862
+ agents: join11(home, "Chips", "Agents", "hermes"),
28863
+ oldSettings: join11(home, ".chips-hermes"),
28864
+ oldAgents: join11(home, "chips-hermes", "agents")
28865
+ };
28866
+ }
28867
+ var fromHome = (path, home = homedir6()) => path === "~" ? home : path.startsWith("~/") ? join11(home, path.slice(2)) : path;
28868
+ function inside2(root2, path) {
28869
+ const under = relative(root2, path);
28870
+ return under === ".." || under.startsWith(`..${sep2}`) || isAbsolute2(under) ? void 0 : under;
28871
+ }
28872
+ function nowAt(path, before, after) {
28873
+ const under = inside2(before, path);
28874
+ if (under === void 0) return path;
28875
+ const moved = join11(after, under);
28876
+ return existsSync10(path) && !existsSync10(moved) ? path : moved;
28877
+ }
28878
+ var configOf = (word, places, home) => resolve4(fromHome(word ?? join11(places.settings, "config.json"), home));
28879
+ function configTargetOf(word, home = homedir6()) {
28880
+ const places = placesOf(home);
28881
+ const path = configOf(word, places, home);
28882
+ const under = inside2(places.oldSettings, path);
28883
+ return under === void 0 ? path : join11(places.settings, under);
28884
+ }
28885
+ function configPathOf(word, home = homedir6()) {
28886
+ const places = placesOf(home);
28887
+ const target = configTargetOf(word, home);
28888
+ const under = inside2(places.settings, target);
28889
+ const old = under === void 0 ? void 0 : join11(places.oldSettings, under);
28890
+ return old !== void 0 && existsSync10(old) && !existsSync10(target) ? old : target;
28891
+ }
28892
+ function folderPathOf(path, home = homedir6()) {
28893
+ const places = placesOf(home);
28894
+ return nowAt(resolve4(fromHome(path, home)), places.oldAgents, places.agents);
28895
+ }
28896
+
28897
+ // src/keep.ts
28856
28898
  var flags = process.argv.slice(2);
28857
28899
  var words3 = flags.filter((word) => !word.startsWith("--"));
28858
- var whole = (word) => resolve4(word.startsWith("~/") ? join11(homedir6(), word.slice(2)) : word);
28900
+ var whole = (word) => configPathOf(word);
28859
28901
  var here = fileURLToPath2(new URL("..", import.meta.url)).replace(/\/$/, "");
28860
28902
  var self = fileURLToPath2(import.meta.url);
28861
28903
  var run = self.endsWith(".js") ? [self] : ["--import", "tsx", "src/main.ts"];
@@ -28900,10 +28942,130 @@ function keepIfAsked(label2, url2, names, log2, before = () => void 0) {
28900
28942
  );
28901
28943
  }
28902
28944
 
28903
- // src/setup.ts
28904
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
28945
+ // src/move.ts
28946
+ import { lstatSync as lstatSync2, mkdirSync as mkdirSync6, readdirSync, readFileSync as readFileSync10, renameSync as renameSync3, rmdirSync, writeFileSync as writeFileSync5 } from "node:fs";
28905
28947
  import { homedir as homedir7 } from "node:os";
28906
- import { join as join12, resolve as resolve5 } from "node:path";
28948
+ import { dirname as dirname6, join as join12, resolve as resolve5 } from "node:path";
28949
+ var there = (path) => {
28950
+ try {
28951
+ lstatSync2(path);
28952
+ return true;
28953
+ } catch {
28954
+ return false;
28955
+ }
28956
+ };
28957
+ var isFolder2 = (path) => lstatSync2(path).isDirectory();
28958
+ var shown = (path, home) => path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;
28959
+ function merge2(from, to, log2, home = homedir7(), quiet = false) {
28960
+ if (!there(from)) return;
28961
+ try {
28962
+ if (!there(to)) {
28963
+ mkdirSync6(dirname6(to), { recursive: true });
28964
+ renameSync3(from, to);
28965
+ if (!quiet) log2(`moved ${shown(from, home)} to ${shown(to, home)}`);
28966
+ return;
28967
+ }
28968
+ if (!isFolder2(from) || !isFolder2(to)) {
28969
+ log2(`${shown(from, home)} was left where it is: ${shown(to, home)} is there already`);
28970
+ return;
28971
+ }
28972
+ for (const entry of readdirSync(from)) merge2(join12(from, entry), join12(to, entry), log2, home, true);
28973
+ if (readdirSync(from).length === 0) rmdirSync(from);
28974
+ if (!quiet) log2(`moved what was in ${shown(from, home)} into ${shown(to, home)}`);
28975
+ } catch (error62) {
28976
+ log2(`${shown(from, home)} couldn't be moved to ${shown(to, home)}, so it is used where it is: ${error62.message}`);
28977
+ }
28978
+ }
28979
+ function removeIfEmpty(path) {
28980
+ try {
28981
+ if (readdirSync(path).length === 0) rmdirSync(path);
28982
+ } catch {
28983
+ }
28984
+ }
28985
+ function pairOf(path, places) {
28986
+ const old = inside2(places.oldAgents, path);
28987
+ if (old !== void 0) return { old: path, now: join12(places.agents, old) };
28988
+ const now = inside2(places.agents, path);
28989
+ return now === void 0 ? void 0 : { old: join12(places.oldAgents, now), now: path };
28990
+ }
28991
+ function writeJson(path, value) {
28992
+ const next = `${path}.next`;
28993
+ writeFileSync5(next, `${JSON.stringify(value, null, 2)}
28994
+ `, { mode: 384 });
28995
+ renameSync3(next, path);
28996
+ }
28997
+ var readJson = (path) => {
28998
+ try {
28999
+ const read = JSON.parse(readFileSync10(path, "utf8"));
29000
+ return typeof read === "object" && read !== null ? read : void 0;
29001
+ } catch {
29002
+ return void 0;
29003
+ }
29004
+ };
29005
+ function movedWords(said2, places, home) {
29006
+ if (typeof said2 !== "string" || said2 === "") return void 0;
29007
+ const path = resolve5(fromHome(said2, home));
29008
+ const moved = nowAt(path, places.oldAgents, places.agents);
29009
+ return moved === path ? void 0 : `~/${moved.slice(home.length + 1)}`;
29010
+ }
29011
+ function moveOld(configPath2, log2, home = homedir7()) {
29012
+ const places = placesOf(home);
29013
+ const settings = dirname6(configPath2);
29014
+ const under = inside2(places.settings, settings);
29015
+ if (under !== void 0) {
29016
+ mkdirSync6(dirname6(dirname6(places.settings)), { recursive: true });
29017
+ mkdirSync6(dirname6(places.settings), { recursive: true, mode: 448 });
29018
+ merge2(join12(places.oldSettings, under), settings, log2, home);
29019
+ }
29020
+ const config3 = readJson(configPath2);
29021
+ const agents = Array.isArray(config3?.["agents"]) ? config3["agents"] : [];
29022
+ const roots = [config3?.["folders"] ?? places.agents, config3?.["folder"], ...agents.map((agent) => agent["folder"])].filter((root2) => typeof root2 === "string" && root2 !== "").map((root2) => resolve5(fromHome(root2, home)));
29023
+ const pairs = roots.map((root2) => pairOf(root2, places)).filter((pair) => pair !== void 0);
29024
+ for (const { old, now } of pairs) merge2(old, now, log2, home);
29025
+ if (config3 !== void 0) {
29026
+ let changed = false;
29027
+ const folders = movedWords(config3["folders"], places, home);
29028
+ if (folders !== void 0) {
29029
+ config3["folders"] = folders;
29030
+ changed = true;
29031
+ }
29032
+ const folder2 = movedWords(config3["folder"], places, home);
29033
+ if (folder2 !== void 0) {
29034
+ config3["folder"] = folder2;
29035
+ changed = true;
29036
+ }
29037
+ for (const agent of agents) {
29038
+ const folder3 = movedWords(agent["folder"], places, home);
29039
+ if (folder3 === void 0) continue;
29040
+ agent["folder"] = folder3;
29041
+ changed = true;
29042
+ }
29043
+ if (changed) writeJson(configPath2, config3);
29044
+ }
29045
+ const statePath2 = join12(settings, "sessions.json");
29046
+ const state2 = readJson(statePath2);
29047
+ const saved = state2?.["folders"];
29048
+ if (state2 !== void 0 && typeof saved === "object" && saved !== null) {
29049
+ const folders = saved;
29050
+ let changed = false;
29051
+ for (const [agentId2, folder2] of Object.entries(folders)) {
29052
+ if (typeof folder2 !== "string") continue;
29053
+ const moved = nowAt(folder2, places.oldAgents, places.agents);
29054
+ if (moved === folder2) continue;
29055
+ folders[agentId2] = moved;
29056
+ changed = true;
29057
+ }
29058
+ if (changed) writeJson(statePath2, state2);
29059
+ }
29060
+ removeIfEmpty(places.oldSettings);
29061
+ removeIfEmpty(places.oldAgents);
29062
+ removeIfEmpty(dirname6(places.oldAgents));
29063
+ }
29064
+
29065
+ // src/setup.ts
29066
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
29067
+ import { homedir as homedir8 } from "node:os";
29068
+ import { join as join13, resolve as resolve6 } from "node:path";
28907
29069
  var SetupError = class extends Error {
28908
29070
  constructor(message, words4 = {}) {
28909
29071
  super(message);
@@ -28912,7 +29074,7 @@ var SetupError = class extends Error {
28912
29074
  words;
28913
29075
  name = "SetupError";
28914
29076
  };
28915
- var shown = (path) => path.startsWith(`${homedir7()}/`) ? `~${path.slice(homedir7().length)}` : path;
29077
+ var shown2 = (path) => path.startsWith(`${homedir8()}/`) ? `~${path.slice(homedir8().length)}` : path;
28916
29078
  var portOf2 = (url2) => {
28917
29079
  const parsed = new URL(url2);
28918
29080
  return parsed.port || (parsed.protocol === "https:" ? "443" : "80");
@@ -28932,24 +29094,24 @@ function readyHooks(config3, options) {
28932
29094
  const profile = profileName(named);
28933
29095
  if (done.has(profile)) continue;
28934
29096
  done.add(profile);
28935
- const home2 = profileHome(options.root, named);
29097
+ const home = profileHome(options.root, named);
28936
29098
  let outcome;
28937
29099
  try {
28938
- outcome = addHook(home2, options.port, profile, options.secret);
29100
+ outcome = addHook(home, options.port, profile, options.secret);
28939
29101
  } catch (error62) {
28940
- lines2.push(`Chips's webhook wasn't added to ${shown(join12(home2, "config.yaml"))}: ${error62 instanceof Error ? error62.message : String(error62)}`);
29102
+ lines2.push(`Chips's webhook wasn't added to ${shown2(join13(home, "config.yaml"))}: ${error62 instanceof Error ? error62.message : String(error62)}`);
28941
29103
  continue;
28942
29104
  }
28943
29105
  if (outcome.state === "added") {
28944
- lines2.push(`Added Chips's webhook to ${shown(outcome.path)}, so Hermes tells Chips how full each agent's context is.`);
29106
+ lines2.push(`Added Chips's webhook to ${shown2(outcome.path)}, so Hermes tells Chips how full each agent's context is.`);
28945
29107
  restart2 ||= options.gatewayUp;
28946
29108
  } else if (outcome.state === "by_hand") {
28947
29109
  lines2.push(
28948
- `${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:
29110
+ `${shown2(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:
28949
29111
  ${outcome.lines}`
28950
29112
  );
28951
29113
  } else if (outcome.state === "no_home") {
28952
- lines2.push(`Hermes has no ${profile} profile at ${shown(home2)}, so no webhook was added for it.`);
29114
+ lines2.push(`Hermes has no ${profile} profile at ${shown2(home)}, so no webhook was added for it.`);
28953
29115
  }
28954
29116
  }
28955
29117
  if (restart2) lines2.push(RESTART);
@@ -28959,12 +29121,12 @@ var STILL_GOOD = "The link is still good: run the same command again once that's
28959
29121
  var EXAMPLE = '{ "agents": [{ "name": "Hermes", "profile": "default", "keyEnv": "API_SERVER_KEY" }] }';
28960
29122
  var PLAIN = { agents: [{ name: "Hermes", profile: "default", keyEnv: "API_SERVER_KEY" }] };
28961
29123
  function hermesEnvPath(env = process.env) {
28962
- return resolve5(env["HERMES_HOME"] ?? resolve5(homedir7(), ".hermes"), ".env");
29124
+ return resolve6(env["HERMES_HOME"] ?? resolve6(homedir8(), ".hermes"), ".env");
28963
29125
  }
28964
29126
  function fromHermes(name2, env) {
28965
29127
  const path = hermesEnvPath(env);
28966
- if (!existsSync10(path)) return void 0;
28967
- for (const line2 of readFileSync10(path, "utf8").split("\n")) {
29128
+ if (!existsSync11(path)) return void 0;
29129
+ for (const line2 of readFileSync11(path, "utf8").split("\n")) {
28968
29130
  const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(line2);
28969
29131
  if (match?.[1] === name2) return match[2]?.replace(/^(["'])(.*)\1$/, "$2");
28970
29132
  }
@@ -28972,10 +29134,10 @@ function fromHermes(name2, env) {
28972
29134
  }
28973
29135
  function folderOf(said2) {
28974
29136
  if (typeof said2 !== "string" || said2 === "") return void 0;
28975
- return resolve5(said2 === "~" || said2.startsWith("~/") ? homedir7() + said2.slice(1) : said2);
29137
+ return folderPathOf(said2);
28976
29138
  }
28977
29139
  function readConfig(path, env = process.env) {
28978
- const raw = existsSync10(path) ? JSON.parse(readFileSync10(path, "utf8")) : PLAIN;
29140
+ const raw = existsSync11(path) ? JSON.parse(readFileSync11(path, "utf8")) : PLAIN;
28979
29141
  const url2 = typeof raw.url === "string" ? raw.url : "http://127.0.0.1:8642";
28980
29142
  const folder2 = folderOf(raw.folder);
28981
29143
  if (!Array.isArray(raw.agents) || raw.agents.length === 0) {
@@ -28993,7 +29155,7 @@ function readConfig(path, env = process.env) {
28993
29155
  {
28994
29156
  detail: "No key for its API server",
28995
29157
  says: "Hermes's API server isn't set up.",
28996
- doing: `Add API_SERVER_ENABLED=true and an API_SERVER_KEY, a long random key, to ${shown(hermesEnvPath(env))}. Then start Hermes with hermes gateway.`
29158
+ doing: `Add API_SERVER_ENABLED=true and an API_SERVER_KEY, a long random key, to ${shown2(hermesEnvPath(env))}. Then start Hermes with hermes gateway.`
28997
29159
  }
28998
29160
  );
28999
29161
  }
@@ -29014,7 +29176,7 @@ function readConfig(path, env = process.env) {
29014
29176
  return {
29015
29177
  ...typeof raw.name === "string" ? { name: raw.name } : {},
29016
29178
  agents,
29017
- folders: folderOf(raw.folders) ?? resolve5(homedir7(), "chips-hermes", "agents"),
29179
+ folders: folderOf(raw.folders) ?? placesOf().agents,
29018
29180
  ...typeof hookPort === "number" && Number.isInteger(hookPort) && hookPort > 0 && hookPort < 65536 ? { hookPort } : {}
29019
29181
  };
29020
29182
  }
@@ -29027,7 +29189,7 @@ async function checkHermes(config3, clientFor = (agent) => new HermesClient(agen
29027
29189
  {
29028
29190
  detail: `Nothing answers on port ${portOf2(agent.address.url)}`,
29029
29191
  says: "Hermes's API server is off.",
29030
- doing: `Add this line to ${shown(hermesEnvPath())}, then start Hermes with hermes gateway.`,
29192
+ doing: `Add this line to ${shown2(hermesEnvPath())}, then start Hermes with hermes gateway.`,
29031
29193
  copy: "API_SERVER_ENABLED=true"
29032
29194
  }
29033
29195
  );
@@ -29046,25 +29208,25 @@ async function checkHermes(config3, clientFor = (agent) => new HermesClient(agen
29046
29208
  }
29047
29209
 
29048
29210
  // src/version.ts
29049
- import { readFileSync as readFileSync11 } from "node:fs";
29050
- var VERSION = JSON.parse(readFileSync11(new URL("../package.json", import.meta.url), "utf8")).version;
29211
+ import { readFileSync as readFileSync12 } from "node:fs";
29212
+ var VERSION = JSON.parse(readFileSync12(new URL("../package.json", import.meta.url), "utf8")).version;
29051
29213
 
29052
29214
  // src/main.ts
29053
- var home = (path) => path.startsWith("~/") ? join13(homedir8(), path.slice(2)) : path;
29054
29215
  var link = process.env["CHIPS_JOIN_LINK"] || void 0;
29055
29216
  var LABEL = "chat.chips.hermes";
29056
29217
  stopIfAsked(LABEL);
29057
- var configPath = resolve6(home(words3[0] ?? "~/.chips-hermes/config.json"));
29058
- var folder = dirname6(configPath);
29059
- var statePath = join13(folder, "sessions.json");
29060
- var state = existsSync11(statePath) ? JSON.parse(readFileSync12(statePath, "utf8")) : { sessions: {} };
29218
+ var log = (line2) => process.stdout.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${line2}
29219
+ `);
29220
+ moveOld(configTargetOf(words3[0]), log);
29221
+ var configPath = configPathOf(words3[0]);
29222
+ var folder = dirname7(configPath);
29223
+ var statePath = join14(folder, "sessions.json");
29224
+ var state = existsSync12(statePath) ? JSON.parse(readFileSync13(statePath, "utf8")) : { sessions: {} };
29061
29225
  var saveState = (next) => {
29062
- mkdirSync6(folder, { recursive: true, mode: 448 });
29063
- writeFileSync5(statePath, `${JSON.stringify(next, null, 2)}
29226
+ mkdirSync7(folder, { recursive: true, mode: 448 });
29227
+ writeFileSync6(statePath, `${JSON.stringify(next, null, 2)}
29064
29228
  `, { mode: 384 });
29065
29229
  };
29066
- var log = (line2) => process.stdout.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${line2}
29067
- `);
29068
29230
  var freePort = () => new Promise((done, failed) => {
29069
29231
  const server = createServer2();
29070
29232
  server.once("error", failed);
@@ -29137,7 +29299,7 @@ var chips = await connect({
29137
29299
  url: joined.url,
29138
29300
  token: joined.token,
29139
29301
  client: { name: "chips-hermes", version: VERSION },
29140
- store: fileStore(join13(folder, "chips-state.json")),
29302
+ store: fileStore(join14(folder, "chips-state.json")),
29141
29303
  onRestart: restart,
29142
29304
  settings: settingsFor(() => bridge)
29143
29305
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chipschat/hermes",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "Hermes Agent as a Chips harness, on its Runs API: each profile a Chips agent, each room a Hermes session.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {