@chipschat/hermes 1.0.15 → 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,9 +1,9 @@
1
1
  # Third-party notices
2
2
 
3
- `dist/main.js` in @chipschat/hermes 1.0.15 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
- ## @chipschat/harness 1.0.1 (Apache-2.0)
6
+ ## @chipschat/harness 1.0.2 (Apache-2.0)
7
7
 
8
8
  Apache License
9
9
  Version 2.0, January 2004
@@ -207,7 +207,7 @@ Apache License
207
207
  See the License for the specific language governing permissions and
208
208
  limitations under the License.
209
209
 
210
- ## @chipschat/protocol 1.0.1 (Apache-2.0)
210
+ ## @chipschat/protocol 1.0.2 (Apache-2.0)
211
211
 
212
212
  Apache License
213
213
  Version 2.0, January 2004
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
@@ -20529,7 +20528,7 @@ var contextLine = external_exports.object({
20529
20528
  }).describe("One line of a room: something said, or something that happened to who is in it.");
20530
20529
  var roomContext = external_exports.object({
20531
20530
  lines: external_exports.array(contextLine).max(CONTEXT_CEILING.lines).describe("Oldest first, the newest that fit the budget."),
20532
- since_seq: external_exports.int().nonnegative().describe("Where they start: after this `seq`. With a message, where the turn that said the harness's own last message in the room began (ADR 0105), else its joining; from the pull route, 0."),
20531
+ since_seq: external_exports.int().nonnegative().describe("Where they start: after this `seq`. With a message, where the harness's own last turn in the room began, one that said something or passed with NO_REPLY (ADR 0105), else its joining; from the pull route, 0."),
20533
20532
  truncated: external_exports.boolean().describe("Whether older lines after `since_seq` were left out to fit the budget."),
20534
20533
  before: external_exports.int().positive().describe("A cursor: `GET /v1/spaces/:id/context?before=` this reads what came before these lines."),
20535
20534
  files: roomFiles.max(ROOM_FILES_IN_CONTEXT).optional().describe("The room's files at their current version, newest first, the 20 newest (ADR 0099): with a message, as of it, its own files included; from the pull route, as of now. Each with a URL for this harness. Absent from a Chips before files, and when the room has none."),
@@ -21677,6 +21676,7 @@ var pairingPayload = external_exports.object({
21677
21676
  var JOIN_POLL_SECONDS = 3;
21678
21677
  var joinKind = external_exports.enum(["harness", "device", "invite", "pairing"]).describe("A harness asking for a key; a device asking to be paired; a person joining by an invite, or a device pairing by a code, that waits for whoever handed it out (ADR 0232).");
21679
21678
  var keyCode = external_exports.string().regex(/^[0-9]{6}$/, "six digits");
21679
+ var joinNonce = external_exports.string().regex(/^[A-Za-z0-9_-]{43}$/, "32 bytes, base64url");
21680
21680
  var requestCode = external_exports.union([pairingCode, keyCode]);
21681
21681
  var joinState = external_exports.enum(["waiting", "allowed", "denied", "expired", "redeemed"]).describe("`waiting` for a person; `allowed`, and not yet collected; `denied` by a person, or withdrawn; `expired` before it was collected; `redeemed`, which spends it.");
21682
21682
  var joinStartedBy = external_exports.enum(["requester", "person"]).describe("Who started it: the requester, with a code to compare, or a person, with a link that is allowed already.");
@@ -21712,12 +21712,15 @@ var joinAsk = external_exports.object({
21712
21712
  software: words2(120).optional(),
21713
21713
  machine: words2(120).optional(),
21714
21714
  wants: capabilities.optional().describe("For a harness: what it asks to be allowed."),
21715
- public_key: external_exports.string().regex(/^[A-Za-z0-9+/]{40,64}={0,2}$/).optional().describe("For a device: the public half of its Ed25519 key, SPKI DER, base64.")
21715
+ public_key: external_exports.string().regex(/^[A-Za-z0-9+/]{40,64}={0,2}$/).optional().describe("For a device: the public half of its Ed25519 key, SPKI DER, base64."),
21716
+ commit: joinNonce.optional().describe("For a device (S-1, ADR 0278): the SHA-256 of `joinCommitText` of a nonce it keeps, base64url. It sends the nonce with its first poll, once it holds the server's, and both ends work the code out.")
21716
21717
  }).describe("A request to join.");
21717
21718
  var joinAsked = external_exports.object({
21718
21719
  request_id: id("join"),
21719
21720
  secret: external_exports.string().min(16).max(200).describe("The requester's alone: it polls with it."),
21720
- code: requestCode,
21721
+ code: requestCode.optional().describe("The code to show. Absent for a device that sent `commit`: it works its own out."),
21722
+ server_key: external_exports.string().min(1).max(128).optional().describe("For a device that sent `commit`: the server's tunnel key, which the code is worked out from and the device pins once it is allowed (ADR 0191, 0278)."),
21723
+ nonce: joinNonce.optional().describe("For a device that sent `commit`: the server's nonce, for the code (ADR 0278)."),
21721
21724
  expires_in: external_exports.int().positive().describe("Seconds until it expires."),
21722
21725
  interval: external_exports.int().positive().describe("Seconds between polls.")
21723
21726
  });
@@ -22649,6 +22652,17 @@ var jobChangeFrame = external_exports.object({
22649
22652
  asked_at: isoTimestamp
22650
22653
  }).describe("A person switched one of a harness's own jobs, or changed how often it runs (ADR 0252). Sent to a harness's socket that said `jobs` in its `hello`, now or never: it is not in the log. The harness answers with its copy of all its jobs, `answers` or `refuses` naming `change_id`.");
22651
22654
 
22655
+ // node_modules/@chipschat/protocol/dist/src/read-marks.js
22656
+ var READ_MARKS_MAX = 1e3;
22657
+ var readMarks = external_exports.record(id("space"), external_exports.int().nonnegative().describe("The `seq` the room is read up to.")).refine((marks) => Object.keys(marks).length <= READ_MARKS_MAX, {
22658
+ message: `at most ${READ_MARKS_MAX} rooms`
22659
+ }).describe("How far each room is read, by room id.");
22660
+ var readMarksBody = external_exports.object({ spaces: readMarks });
22661
+ var readFrame = external_exports.object({
22662
+ frame: external_exports.literal("read"),
22663
+ spaces: readMarks.describe("The rooms whose marks moved, and where to.")
22664
+ }).describe("A person read further in some rooms, on one of their devices (ADR 0262). Sent to that person's sockets that said `reads` in their `hello`, never to anyone else's, now or never: `GET /v1/me/read` reads them again.");
22665
+
22652
22666
  // node_modules/@chipschat/protocol/dist/src/frames.js
22653
22667
  var clientHelloFrame = external_exports.object({
22654
22668
  frame: external_exports.literal("hello"),
@@ -22665,7 +22679,7 @@ var clientHelloFrame = external_exports.object({
22665
22679
  autonomy: autonomyLevel.optional().describe("The level the harness believes it has.")
22666
22680
  })).max(64).optional().describe("Not read by Chips: a connection is its token's, and the server's `hello` names the token's harness and its agents (ADR 0087). Absent for a person's client."),
22667
22681
  since: external_exports.int().nonnegative().optional().describe("Not read by Chips: a stream resumes from `subscribe`'s `since`."),
22668
- accepts: external_exports.array(external_exports.string().min(1).max(40)).max(32).optional().describe("Server frames beyond the base set this client understands, by the names in `SERVER_EXTENSIONS`. A name the server does not know is ignored rather than refused, so a client may name a frame a newer server sends and still be answered by an older one (ADR 0211, Phase 3). A client that says `events` is sent its replay in pages rather than one frame per event; one that says `presence` is sent an agent's state as it changes (ADR 0083); a harness's socket that says `restart` is sent `restart` when a person asks it to restart (ADR 0094); an owner's or admin's that says `join` is sent each join request as it changes (ADR 0122); a harness's that says `context` is sent `context.edit` when a person changes one of its context files (ADR 0134); a harness's that says `models` is sent `model.change` when a person changes an agent's model or effort (ADR 0140); a harness's that says `heartbeats` is sent `heartbeat.change` when a person switches an agent's heartbeat or changes how often it beats (ADR 0143); a harness's that says `context_windows` is sent `context_window.compact` and `context_window.change` when a person asks an agent to compact now or changes the limit it compacts itself at (ADR 0146); a harness's that says `jobs` is sent `job.change` when a person switches one of the jobs it runs on its own (ADR 0252); a person's that says `folders` is sent `folder.open` when they ask, from another of their devices, for a room's folder to be opened in Finder (ADR 0225), and `folder.run` when they ask for its project to be run or stopped there (ADR 0226); a person's that says `folder_runs` is sent `folder.run_state` when their Mac says how a run of theirs stands (ADR 0226); one that says nothing is sent what it was always sent, since an unknown frame is dropped in silence."),
22682
+ accepts: external_exports.array(external_exports.string().min(1).max(40)).max(32).optional().describe("Server frames beyond the base set this client understands, by the names in `SERVER_EXTENSIONS`. A name the server does not know is ignored rather than refused, so a client may name a frame a newer server sends and still be answered by an older one (ADR 0211, Phase 3). A client that says `events` is sent its replay in pages rather than one frame per event; one that says `presence` is sent an agent's state as it changes (ADR 0083); a harness's socket that says `restart` is sent `restart` when a person asks it to restart (ADR 0094); an owner's or admin's that says `join` is sent each join request as it changes (ADR 0122); a harness's that says `context` is sent `context.edit` when a person changes one of its context files (ADR 0134); a harness's that says `models` is sent `model.change` when a person changes an agent's model or effort (ADR 0140); a harness's that says `heartbeats` is sent `heartbeat.change` when a person switches an agent's heartbeat or changes how often it beats (ADR 0143); a harness's that says `context_windows` is sent `context_window.compact` and `context_window.change` when a person asks an agent to compact now or changes the limit it compacts itself at (ADR 0146); a harness's that says `jobs` is sent `job.change` when a person switches one of the jobs it runs on its own (ADR 0252); a person's that says `folders` is sent `folder.open` when they ask, from another of their devices, for a room's folder to be opened in Finder (ADR 0225), and `folder.run` when they ask for its project to be run or stopped there (ADR 0226); a person's that says `folder_runs` is sent `folder.run_state` when their Mac says how a run of theirs stands (ADR 0226); a person's that says `reads` is sent `read` when they read further on another of their devices (ADR 0262); one that says nothing is sent what it was always sent, since an unknown frame is dropped in silence."),
22669
22683
  context: contextBudget.optional().describe("How much of a room a harness is handed with each message for it (ADR 0088): 40 lines and 16,000 characters when absent, 200 and 32,000 at most. For this connection. A person's client is handed none.")
22670
22684
  }).describe("The first frame a client sends.");
22671
22685
  var subscribeFrame = external_exports.object({
@@ -22791,6 +22805,7 @@ var serverFrame = external_exports.discriminatedUnion("frame", [
22791
22805
  folderOpenFrame,
22792
22806
  folderRunFrame,
22793
22807
  folderRunStateFrame,
22808
+ readFrame,
22794
22809
  contextEditFrame,
22795
22810
  modelChangeFrame,
22796
22811
  heartbeatChangeFrame,
@@ -22842,9 +22857,37 @@ var HTML_POLICY = "default-src 'none'; img-src data: blob:; font-src data:; medi
22842
22857
  var PAGE_SANDBOX = "allow-scripts allow-popups allow-popups-to-escape-sandbox";
22843
22858
  var PAGE_HEADER_POLICY = `${HTML_POLICY}; sandbox ${PAGE_SANDBOX}`;
22844
22859
 
22845
- // node_modules/@chipschat/harness/dist/connect.js
22846
- import { readFile as readFile2 } from "node:fs/promises";
22847
- import { basename } from "node:path";
22860
+ // node_modules/@chipschat/protocol/dist/src/push.js
22861
+ var PUSH_WORDS = {
22862
+ /** An ask of an agent's that waits on a person (ADR 0045). */
22863
+ decision: {
22864
+ title: "A decision is waiting",
22865
+ body: "Approve or deny from here.",
22866
+ categoryId: "decision"
22867
+ },
22868
+ /** A message that @s the person, or was sent to them (ADR 0158). */
22869
+ messageForYou: { title: "A message for you", body: "Open Chips to read it." },
22870
+ /** A message in a room they hear (ADR 0158). */
22871
+ message: { title: "A new message", body: "Open Chips to read it." },
22872
+ /** A join request for its maker or an owner (ADR 0122). */
22873
+ join: { title: "Something asks to join", body: "Check its code, then allow it or not." },
22874
+ /** The referee paused agents talking among themselves (ADR 0097). */
22875
+ held: { title: "Chips paused the agents", body: "Open the space to let them carry on." },
22876
+ warning: { title: "The agents need a look", body: "They have been talking among themselves." },
22877
+ /** A guest's ask to go direct, and its answers (ADR 0211). */
22878
+ directAsked: {
22879
+ title: "A guest wants to connect directly",
22880
+ body: "Share your server with them in Tailscale."
22881
+ },
22882
+ directShared: { title: "Your server is shared with you", body: "Open Chips to go direct." },
22883
+ directDone: {
22884
+ title: "Connected directly",
22885
+ body: "A guest you shared your server with is direct now."
22886
+ },
22887
+ /** An owner is turning the relay off (ADR 0211, Phase 4). */
22888
+ relayOff: { title: "The relay is going off", body: "Go direct to keep reaching the server." }
22889
+ };
22890
+ var KNOWN = new Set(Object.values(PUSH_WORDS).map((words4) => JSON.stringify([words4.title, words4.body, "categoryId" in words4 ? words4.categoryId : null])));
22848
22891
 
22849
22892
  // node_modules/@chipschat/harness/dist/asks.js
22850
22893
  var Asks = class {
@@ -22879,10 +22922,7 @@ var Asks = class {
22879
22922
  const from = event["from"];
22880
22923
  if (!this.listener.listening() || from?.kind !== "person")
22881
22924
  return false;
22882
- this.hand(spaceId, room, {
22883
- messageId: String(event["message_id"]),
22884
- text: String(event["text"] ?? "")
22885
- });
22925
+ this.hand(spaceId, room, { messageId: String(event["message_id"]), text: String(event["text"] ?? "") }, event);
22886
22926
  return true;
22887
22927
  }
22888
22928
  /**
@@ -22918,11 +22958,42 @@ var Asks = class {
22918
22958
  this.rooms.set(spaceId, room);
22919
22959
  return room;
22920
22960
  }
22921
- hand(spaceId, room, first) {
22961
+ hand(spaceId, room, first, message) {
22922
22962
  room.first = first.messageId;
22923
- this.listener.hand({ spaceId, askedBy: room.askedBy, firstMessage: first });
22963
+ this.listener.hand({
22964
+ spaceId,
22965
+ askedBy: room.askedBy,
22966
+ firstMessage: first,
22967
+ ...message === void 0 ? {} : { message }
22968
+ });
22924
22969
  }
22925
22970
  };
22971
+ function firstTurnOf(asked, agentId2) {
22972
+ const { message } = asked;
22973
+ if (message === void 0)
22974
+ return void 0;
22975
+ const room = message.room;
22976
+ const notes = room?.notes ?? {};
22977
+ const [place, ...more] = Object.keys(notes);
22978
+ if (room === void 0 || place === void 0 || more.length > 0) {
22979
+ return { ...message, for: [agentId2] };
22980
+ }
22981
+ const placed = (each) => each.participant_id === place ? { ...each, participant_id: agentId2 } : each;
22982
+ const note = notes[place];
22983
+ const rules = room.rules?.[place];
22984
+ return {
22985
+ ...message,
22986
+ for: [agentId2],
22987
+ room: {
22988
+ ...room,
22989
+ members: room.members.map(placed),
22990
+ notes: note === void 0 ? {} : { [agentId2]: note },
22991
+ ...room.rules === void 0 ? {} : { rules: rules === void 0 ? {} : { [agentId2]: rules } },
22992
+ ...room.working === void 0 ? {} : { working: room.working.map(placed) },
22993
+ ...room.idle === void 0 ? {} : { idle: room.idle.map(placed) }
22994
+ }
22995
+ };
22996
+ }
22926
22997
  function firstWords(history) {
22927
22998
  for (const event of history) {
22928
22999
  const actor2 = event["actor"];
@@ -22933,6 +23004,10 @@ function firstWords(history) {
22933
23004
  return void 0;
22934
23005
  }
22935
23006
 
23007
+ // node_modules/@chipschat/harness/dist/connect.js
23008
+ import { readFile as readFile2 } from "node:fs/promises";
23009
+ import { basename } from "node:path";
23010
+
22936
23011
  // node_modules/@chipschat/harness/dist/events.js
22937
23012
  var Emitter = class {
22938
23013
  handlers = /* @__PURE__ */ new Map();
@@ -23324,8 +23399,8 @@ var Project = class {
23324
23399
  const end2 = all.findIndex((line2, at) => at > start && line2.startsWith(">>>>>>> "));
23325
23400
  if (start < 0 || end2 < 0)
23326
23401
  return void 0;
23327
- const shown2 = all.slice(Math.max(0, start - 1), end2 + 2);
23328
- 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");
23329
23404
  } catch {
23330
23405
  return void 0;
23331
23406
  }
@@ -25249,7 +25324,7 @@ async function ask(url2, options, send, machine) {
25249
25324
  if (asked.status !== 201)
25250
25325
  throw await refused(asked);
25251
25326
  const { request_id, secret, code, interval } = await asked.json();
25252
- 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}.
25253
25328
  `));
25254
25329
  show(code, url2);
25255
25330
  for (; ; ) {
@@ -25826,8 +25901,8 @@ function profileName(profile) {
25826
25901
  const name2 = (profile ?? "default").trim().toLowerCase();
25827
25902
  return name2 === "" ? "default" : name2;
25828
25903
  }
25829
- function envValue(home2, name2) {
25830
- const path = join5(home2, ".env");
25904
+ function envValue(home, name2) {
25905
+ const path = join5(home, ".env");
25831
25906
  if (!existsSync5(path)) return void 0;
25832
25907
  let found;
25833
25908
  for (const line2 of readFileSync5(path, "utf8").split("\n")) {
@@ -25839,16 +25914,16 @@ function envValue(home2, name2) {
25839
25914
  }
25840
25915
  return found;
25841
25916
  }
25842
- function appendEnv(home2, name2, value) {
25843
- const path = join5(home2, ".env");
25917
+ function appendEnv(home, name2, value) {
25918
+ const path = join5(home, ".env");
25844
25919
  const text = readFileSync5(path, "utf8");
25845
25920
  const mode = statSync(path).mode & 511;
25846
25921
  appendFileSync(path, `${text === "" || text.endsWith("\n") ? "" : "\n"}${name2}=${value}
25847
25922
  `);
25848
25923
  chmodSync3(path, mode);
25849
25924
  }
25850
- function configText(home2) {
25851
- const path = join5(home2, "config.yaml");
25925
+ function configText(home) {
25926
+ const path = join5(home, "config.yaml");
25852
25927
  try {
25853
25928
  return existsSync5(path) ? readFileSync5(path, "utf8") : "";
25854
25929
  } catch {
@@ -25930,30 +26005,30 @@ function isFolder(path) {
25930
26005
  return false;
25931
26006
  }
25932
26007
  }
25933
- function workFolder(home2, env = process.env) {
25934
- const configured = yamlValue(configText(home2), "terminal", "cwd");
26008
+ function workFolder(home, env = process.env) {
26009
+ const configured = yamlValue(configText(home), "terminal", "cwd");
25935
26010
  if (configured !== void 0 && configured !== "" && configured !== ".") {
25936
26011
  const folder2 = expanded(configured);
25937
26012
  if (isFolder(folder2)) return folder2;
25938
26013
  }
25939
26014
  for (const name2 of ["TERMINAL_CWD", "MESSAGING_CWD"]) {
25940
- const said2 = envValue(home2, name2) ?? env[name2];
26015
+ const said2 = envValue(home, name2) ?? env[name2];
25941
26016
  if (said2 !== void 0 && said2 !== "" && isFolder(expanded(said2))) return expanded(said2);
25942
26017
  }
25943
- return gatewayFolder(home2);
26018
+ return gatewayFolder(home);
25944
26019
  }
25945
26020
  var gateways = /* @__PURE__ */ new Map();
25946
- function gatewayFolder(home2) {
26021
+ function gatewayFolder(home) {
25947
26022
  let raw;
25948
26023
  try {
25949
- raw = readFileSync5(join5(home2, "gateway.pid"), "utf8").trim();
26024
+ raw = readFileSync5(join5(home, "gateway.pid"), "utf8").trim();
25950
26025
  } catch {
25951
26026
  return void 0;
25952
26027
  }
25953
- const known = gateways.get(home2);
26028
+ const known = gateways.get(home);
25954
26029
  if (known?.pidFile === raw) return known.folder;
25955
26030
  const folder2 = folderOfPid(raw);
25956
- gateways.set(home2, { pidFile: raw, folder: folder2 });
26031
+ gateways.set(home, { pidFile: raw, folder: folder2 });
25957
26032
  return folder2;
25958
26033
  }
25959
26034
  function folderOfPid(raw) {
@@ -26031,8 +26106,8 @@ function memoryProblem(text, limit, what) {
26031
26106
  }
26032
26107
  return void 0;
26033
26108
  }
26034
- function memorySettings(home2) {
26035
- const text = configText(home2);
26109
+ function memorySettings(home) {
26110
+ const text = configText(home);
26036
26111
  const value = (key) => yamlValue(text, "memory", key);
26037
26112
  return {
26038
26113
  ...yamlBool(value("memory_enabled")) === false ? {} : { memory: yamlNumber(value("memory_char_limit")) ?? MEMORY_LIMIT },
@@ -26040,21 +26115,21 @@ function memorySettings(home2) {
26040
26115
  };
26041
26116
  }
26042
26117
  function sourcesOf(profile, env = process.env) {
26043
- const { name: name2, home: home2 } = profile;
26118
+ const { name: name2, home } = profile;
26044
26119
  const found = [];
26045
- const soul = join6(home2, "SOUL.md");
26120
+ const soul = join6(home, "SOUL.md");
26046
26121
  if (existsSync6(soul)) found.push({ path: soul, kind: "soul", file: "SOUL.md", profile: name2 });
26047
- const folder2 = workFolder(home2, env);
26122
+ const folder2 = workFolder(home, env);
26048
26123
  const project = folder2 === void 0 ? void 0 : PROJECT_FILES.find((file3) => readText(join6(folder2, file3)).trim() !== "");
26049
26124
  if (folder2 !== void 0 && project !== void 0) {
26050
26125
  found.push({ path: join6(folder2, project), kind: "project", file: project, profile: name2 });
26051
26126
  }
26052
- const memory = memorySettings(home2);
26127
+ const memory = memorySettings(home);
26053
26128
  if (memory.user !== void 0) {
26054
- 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 });
26055
26130
  }
26056
26131
  if (memory.memory !== void 0) {
26057
- 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 });
26058
26133
  }
26059
26134
  return found;
26060
26135
  }
@@ -26133,18 +26208,18 @@ var Picture = class {
26133
26208
  }
26134
26209
  /** The harness's own set, as `PUT /v1/harnesses/:id/context` takes it. */
26135
26210
  harnessCopy() {
26136
- const shown2 = [];
26211
+ const shown3 = [];
26137
26212
  const notShown = [];
26138
26213
  for (const [path, { id: id2, name: name2 }] of this.shared) {
26139
26214
  const entry = this.readers.get(path);
26140
26215
  if (entry === void 0) continue;
26141
26216
  const { source: source2, agents } = entry;
26142
26217
  const { file: file3, tooLarge } = fileOf(source2, name2);
26143
- if (tooLarge || shown2.length >= FILES_SHOWN) {
26218
+ if (tooLarge || shown3.length >= FILES_SHOWN) {
26144
26219
  notShown.push({ name: name2, title: ROWS[source2.kind].title, reason: tooLarge ? "too_large" : "count" });
26145
26220
  continue;
26146
26221
  }
26147
- shown2.push({
26222
+ shown3.push({
26148
26223
  ...file3,
26149
26224
  id: id2,
26150
26225
  used_by: agents.length,
@@ -26152,7 +26227,7 @@ var Picture = class {
26152
26227
  });
26153
26228
  }
26154
26229
  return {
26155
- shared: shown2,
26230
+ shared: shown3,
26156
26231
  ...notShown.length === 0 ? {} : { not_shown: notShown }
26157
26232
  };
26158
26233
  }
@@ -26192,9 +26267,9 @@ var Picture = class {
26192
26267
  /** The file an edit names: an agent's own, or one of the harness's set; with whether it is shared. */
26193
26268
  find(agentId2, name2) {
26194
26269
  if (agentId2 === void 0) {
26195
- for (const [path, { name: shown2 }] of this.shared) {
26270
+ for (const [path, { name: shown3 }] of this.shared) {
26196
26271
  const source2 = this.readers.get(path)?.source;
26197
- if (shown2 === name2 && source2 !== void 0) return { source: source2, shared: true };
26272
+ if (shown3 === name2 && source2 !== void 0) return { source: source2, shared: true };
26198
26273
  }
26199
26274
  return void 0;
26200
26275
  }
@@ -26381,8 +26456,8 @@ var SMALL_THRESHOLD = 0.75;
26381
26456
  var WHY_NOT = "Hermes can't be asked to compact from Chips.";
26382
26457
  var whyReadOnly = (profile) => `Hermes sets this for every agent on its ${profile} profile.`;
26383
26458
  var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
26384
- function limitOf(home2, window) {
26385
- const text = configText(home2);
26459
+ function limitOf(home, window) {
26460
+ const text = configText(home);
26386
26461
  const enabled = yamlBool(yamlValue(text, "compression", "enabled")) ?? true;
26387
26462
  const threshold = yamlNumber(yamlValue(text, "compression", "threshold")) ?? THRESHOLD;
26388
26463
  let ratio = window < SMALL_WINDOW ? Math.max(threshold, SMALL_THRESHOLD) : threshold;
@@ -26835,8 +26910,8 @@ function linesOf2(text) {
26835
26910
  return { indent, words: words4 };
26836
26911
  });
26837
26912
  }
26838
- function hookPortIn(home2) {
26839
- const path = join7(home2, "config.yaml");
26913
+ function hookPortIn(home) {
26914
+ const path = join7(home, "config.yaml");
26840
26915
  if (!existsSync7(path)) return void 0;
26841
26916
  const escaped = HOOK_PATH.replace(/[/]/g, "\\/");
26842
26917
  const match = new RegExp(`^\\s*(?:-\\s*)?url:\\s*["']?http:\\/\\/127\\.0\\.0\\.1:(\\d+)${escaped}`, "m").exec(
@@ -26855,15 +26930,15 @@ function entryLines(indent, url2, secret) {
26855
26930
  `${indent} timeout: 5`
26856
26931
  ];
26857
26932
  }
26858
- function secretInEnv(home2, secret) {
26859
- const path = join7(home2, ".env");
26933
+ function secretInEnv(home, secret) {
26934
+ const path = join7(home, ".env");
26860
26935
  if (!existsSync7(path)) return false;
26861
- 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);
26862
26937
  return true;
26863
26938
  }
26864
- function addHook(home2, port, profile, secret) {
26865
- const path = join7(home2, "config.yaml");
26866
- 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 };
26867
26942
  const url2 = hookUrl(port, profile);
26868
26943
  const text = existsSync7(path) ? readFileSync7(path, "utf8") : "";
26869
26944
  const lines2 = linesOf2(text);
@@ -26871,16 +26946,16 @@ function addHook(home2, port, profile, secret) {
26871
26946
  if (lines2.some((line2) => ours2.test(line2.words))) return { state: "there", path };
26872
26947
  const where = lines2.findIndex((line2) => line2.indent === 0 && /^["']?hooks["']?\s*:/.test(line2.words));
26873
26948
  const byHand = (why) => {
26874
- const inEnv2 = secretInEnv(home2, secret);
26875
- const shown2 = entryLines(" ", url2, inEnv2 ? { env: SECRET_ENV } : { inline: secret });
26876
- 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") };
26877
26952
  };
26878
26953
  const plain = /^(?:"[^"]+"|'[^']+'|[A-Za-z_][\w.-]*)\s*:(?:\s|$)/;
26879
26954
  if (lines2.some((line2) => line2.indent === 0 && line2.words !== "" && !plain.test(line2.words))) {
26880
26955
  return byHand("its top level isn't laid out as this bridge expects");
26881
26956
  }
26882
26957
  if (where === -1) {
26883
- const inEnv2 = secretInEnv(home2, secret);
26958
+ const inEnv2 = secretInEnv(home, secret);
26884
26959
  const block = [
26885
26960
  "# Chips: how full each agent's context is, sent to the chips-hermes bridge after each model call.",
26886
26961
  "hooks:",
@@ -26912,7 +26987,7 @@ function addHook(home2, port, profile, secret) {
26912
26987
  if (children.some((line2) => line2.indent !== indent && line2.indent < indent) || children[0]?.words.startsWith("-")) {
26913
26988
  return byHand("its hooks: isn't laid out as this bridge expects");
26914
26989
  }
26915
- const inEnv = secretInEnv(home2, secret);
26990
+ const inEnv = secretInEnv(home, secret);
26916
26991
  const raw = text.split("\n");
26917
26992
  raw.splice(where + 1, 0, ...entryLines(" ".repeat(indent), url2, inEnv ? { env: SECRET_ENV } : { inline: secret }));
26918
26993
  writeWhole(path, raw.join("\n"));
@@ -27039,9 +27114,9 @@ function gatewayRunArgs(command) {
27039
27114
  return plain[0] === "gateway" && plain[1] === "run" ? args : void 0;
27040
27115
  }
27041
27116
  function serviceOf(system, root2, env = {}) {
27042
- const home2 = resolve3(root2);
27117
+ const home = resolve3(root2);
27043
27118
  const native = resolve3(system.home, ".hermes");
27044
- 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);
27045
27120
  if (system.platform === "darwin") {
27046
27121
  const path = join8(system.home, "Library", "LaunchAgents", `ai.hermes.gateway${suffix ? `-${suffix}` : ""}.plist`);
27047
27122
  return system.exists(path) ? { kind: "launchd", path } : void 0;
@@ -27473,18 +27548,18 @@ function freeProfile(root2, name2, taken) {
27473
27548
  if (!taken.includes(profile) && !existsSync9(join9(root2, "profiles", profile))) return profile;
27474
27549
  }
27475
27550
  }
27476
- function readyProfile(home2, names, hook) {
27477
- for (const file3 of ["MEMORY.md", "USER.md"]) rmSync4(join9(home2, "memories", file3), { force: true });
27478
- 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"));
27479
27554
  if (hook === void 0) return void 0;
27480
- const path = join9(home2, "config.yaml");
27555
+ const path = join9(home, "config.yaml");
27481
27556
  if (existsSync9(path)) {
27482
27557
  const theirs = hookUrl(hook.port, names.from).replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
27483
27558
  const entry = new RegExp(`^(\\s*(?:-\\s*)?url:\\s*["']?)${theirs}(["']?\\s*(?:#.*)?)$`, "m");
27484
27559
  const text = readFileSync9(path, "utf8");
27485
27560
  if (entry.test(text)) writeWhole(path, text.replace(entry, `$1${hookUrl(hook.port, names.profile)}$2`));
27486
27561
  }
27487
- return addHook(home2, hook.port, names.profile, hook.secret);
27562
+ return addHook(home, hook.port, names.profile, hook.secret);
27488
27563
  }
27489
27564
  function hermesProfiles(options) {
27490
27565
  return {
@@ -27557,9 +27632,9 @@ var WANTS = [
27557
27632
  ];
27558
27633
  function settingsFor(active) {
27559
27634
  const bridge2 = () => {
27560
- const there = active();
27561
- if (there === void 0) throw new Refuse("The bridge is starting. Try again in a moment.");
27562
- 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;
27563
27638
  };
27564
27639
  return {
27565
27640
  context: (edit) => bridge2().onContextEdit(edit),
@@ -27950,10 +28025,10 @@ var Bridge = class {
27950
28025
  }
27951
28026
  /** The Main agent into the harness's own room, in the place of whichever of ours is there. */
27952
28027
  async goHome(main2, known) {
27953
- const { chips: chips2, home: home2 } = this.options;
27954
- if (home2 === void 0 || main2.spaces.includes(home2)) return;
27955
- const there = known.find((agent) => agent.agentId !== main2.agentId && agent.spaces.includes(home2));
27956
- 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)}`));
27957
28032
  }
27958
28033
  /**
27959
28034
  * A person asked for a new agent in a room (Chips ADR 0108): one of the harness's, keyed by the
@@ -27989,12 +28064,12 @@ var Bridge = class {
27989
28064
  if (profiles === void 0 || root2 === void 0 || main2 === void 0 || this.own.has(agentId2)) return;
27990
28065
  const from = profileName(main2.config.address.profile);
27991
28066
  const profile = freeProfile(root2, name2, Object.values(state2.made ?? {}));
27992
- const home2 = profileHome(root2, profile);
28067
+ const home = profileHome(root2, profile);
27993
28068
  const secret = state2.fills?.secret ?? this.options.fills?.secret;
27994
28069
  try {
27995
28070
  await profiles.create(profile, from);
27996
28071
  const port = this.fillsPort;
27997
- 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 });
27998
28073
  if (hook?.state === "by_hand") this.log(`context window: ${hook.path} was left as it is: ${hook.why}. Add these lines to it:
27999
28074
  ${hook.lines}`);
28000
28075
  } catch (error62) {
@@ -28022,12 +28097,12 @@ ${hook.lines}`);
28022
28097
  const main2 = this.agents[0];
28023
28098
  const root2 = this.options.hermesRoot;
28024
28099
  if (main2 === void 0 || root2 === void 0) return void 0;
28025
- const home2 = profileHome(root2, profile);
28100
+ const home = profileHome(root2, profile);
28026
28101
  const config3 = {
28027
28102
  name: name2,
28028
- 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) ?? "" },
28029
28104
  keyEnv: KEY_ENV,
28030
- keyFrom: join10(home2, ".env")
28105
+ keyFrom: join10(home, ".env")
28031
28106
  };
28032
28107
  const client = this.options.clientFor?.(config3) ?? new HermesClient(config3.address);
28033
28108
  return { config: config3, key: agentId2, client, agentId: agentId2, up: true };
@@ -28054,7 +28129,7 @@ ${hook.lines}`);
28054
28129
  await this.ownProfile(agent.agentId, agent.name);
28055
28130
  this.ours.add(agent.agentId);
28056
28131
  void this.agentsMoved();
28057
- const message = {
28132
+ const message = firstTurnOf(asked, agent.agentId) ?? {
28058
28133
  type: "message.new",
28059
28134
  space_id: spaceId,
28060
28135
  message_id: firstMessage.messageId,
@@ -28774,12 +28849,55 @@ function lowerFirst(text) {
28774
28849
  }
28775
28850
 
28776
28851
  // src/keep.ts
28777
- import { homedir as homedir6 } from "node:os";
28778
- import { basename as basename5, join as join11, resolve as resolve4 } from "node:path";
28852
+ import { basename as basename5 } from "node:path";
28779
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
28780
28898
  var flags = process.argv.slice(2);
28781
28899
  var words3 = flags.filter((word) => !word.startsWith("--"));
28782
- var whole = (word) => resolve4(word.startsWith("~/") ? join11(homedir6(), word.slice(2)) : word);
28900
+ var whole = (word) => configPathOf(word);
28783
28901
  var here = fileURLToPath2(new URL("..", import.meta.url)).replace(/\/$/, "");
28784
28902
  var self = fileURLToPath2(import.meta.url);
28785
28903
  var run = self.endsWith(".js") ? [self] : ["--import", "tsx", "src/main.ts"];
@@ -28824,10 +28942,130 @@ function keepIfAsked(label2, url2, names, log2, before = () => void 0) {
28824
28942
  );
28825
28943
  }
28826
28944
 
28827
- // src/setup.ts
28828
- 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";
28829
28947
  import { homedir as homedir7 } from "node:os";
28830
- 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";
28831
29069
  var SetupError = class extends Error {
28832
29070
  constructor(message, words4 = {}) {
28833
29071
  super(message);
@@ -28836,7 +29074,7 @@ var SetupError = class extends Error {
28836
29074
  words;
28837
29075
  name = "SetupError";
28838
29076
  };
28839
- var shown = (path) => path.startsWith(`${homedir7()}/`) ? `~${path.slice(homedir7().length)}` : path;
29077
+ var shown2 = (path) => path.startsWith(`${homedir8()}/`) ? `~${path.slice(homedir8().length)}` : path;
28840
29078
  var portOf2 = (url2) => {
28841
29079
  const parsed = new URL(url2);
28842
29080
  return parsed.port || (parsed.protocol === "https:" ? "443" : "80");
@@ -28856,24 +29094,24 @@ function readyHooks(config3, options) {
28856
29094
  const profile = profileName(named);
28857
29095
  if (done.has(profile)) continue;
28858
29096
  done.add(profile);
28859
- const home2 = profileHome(options.root, named);
29097
+ const home = profileHome(options.root, named);
28860
29098
  let outcome;
28861
29099
  try {
28862
- outcome = addHook(home2, options.port, profile, options.secret);
29100
+ outcome = addHook(home, options.port, profile, options.secret);
28863
29101
  } catch (error62) {
28864
- 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)}`);
28865
29103
  continue;
28866
29104
  }
28867
29105
  if (outcome.state === "added") {
28868
- 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.`);
28869
29107
  restart2 ||= options.gatewayUp;
28870
29108
  } else if (outcome.state === "by_hand") {
28871
29109
  lines2.push(
28872
- `${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:
28873
29111
  ${outcome.lines}`
28874
29112
  );
28875
29113
  } else if (outcome.state === "no_home") {
28876
- 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.`);
28877
29115
  }
28878
29116
  }
28879
29117
  if (restart2) lines2.push(RESTART);
@@ -28883,12 +29121,12 @@ var STILL_GOOD = "The link is still good: run the same command again once that's
28883
29121
  var EXAMPLE = '{ "agents": [{ "name": "Hermes", "profile": "default", "keyEnv": "API_SERVER_KEY" }] }';
28884
29122
  var PLAIN = { agents: [{ name: "Hermes", profile: "default", keyEnv: "API_SERVER_KEY" }] };
28885
29123
  function hermesEnvPath(env = process.env) {
28886
- return resolve5(env["HERMES_HOME"] ?? resolve5(homedir7(), ".hermes"), ".env");
29124
+ return resolve6(env["HERMES_HOME"] ?? resolve6(homedir8(), ".hermes"), ".env");
28887
29125
  }
28888
29126
  function fromHermes(name2, env) {
28889
29127
  const path = hermesEnvPath(env);
28890
- if (!existsSync10(path)) return void 0;
28891
- 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")) {
28892
29130
  const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(line2);
28893
29131
  if (match?.[1] === name2) return match[2]?.replace(/^(["'])(.*)\1$/, "$2");
28894
29132
  }
@@ -28896,10 +29134,10 @@ function fromHermes(name2, env) {
28896
29134
  }
28897
29135
  function folderOf(said2) {
28898
29136
  if (typeof said2 !== "string" || said2 === "") return void 0;
28899
- return resolve5(said2 === "~" || said2.startsWith("~/") ? homedir7() + said2.slice(1) : said2);
29137
+ return folderPathOf(said2);
28900
29138
  }
28901
29139
  function readConfig(path, env = process.env) {
28902
- const raw = existsSync10(path) ? JSON.parse(readFileSync10(path, "utf8")) : PLAIN;
29140
+ const raw = existsSync11(path) ? JSON.parse(readFileSync11(path, "utf8")) : PLAIN;
28903
29141
  const url2 = typeof raw.url === "string" ? raw.url : "http://127.0.0.1:8642";
28904
29142
  const folder2 = folderOf(raw.folder);
28905
29143
  if (!Array.isArray(raw.agents) || raw.agents.length === 0) {
@@ -28917,7 +29155,7 @@ function readConfig(path, env = process.env) {
28917
29155
  {
28918
29156
  detail: "No key for its API server",
28919
29157
  says: "Hermes's API server isn't set up.",
28920
- 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.`
28921
29159
  }
28922
29160
  );
28923
29161
  }
@@ -28938,7 +29176,7 @@ function readConfig(path, env = process.env) {
28938
29176
  return {
28939
29177
  ...typeof raw.name === "string" ? { name: raw.name } : {},
28940
29178
  agents,
28941
- folders: folderOf(raw.folders) ?? resolve5(homedir7(), "chips-hermes", "agents"),
29179
+ folders: folderOf(raw.folders) ?? placesOf().agents,
28942
29180
  ...typeof hookPort === "number" && Number.isInteger(hookPort) && hookPort > 0 && hookPort < 65536 ? { hookPort } : {}
28943
29181
  };
28944
29182
  }
@@ -28951,7 +29189,7 @@ async function checkHermes(config3, clientFor = (agent) => new HermesClient(agen
28951
29189
  {
28952
29190
  detail: `Nothing answers on port ${portOf2(agent.address.url)}`,
28953
29191
  says: "Hermes's API server is off.",
28954
- 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.`,
28955
29193
  copy: "API_SERVER_ENABLED=true"
28956
29194
  }
28957
29195
  );
@@ -28970,25 +29208,25 @@ async function checkHermes(config3, clientFor = (agent) => new HermesClient(agen
28970
29208
  }
28971
29209
 
28972
29210
  // src/version.ts
28973
- import { readFileSync as readFileSync11 } from "node:fs";
28974
- 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;
28975
29213
 
28976
29214
  // src/main.ts
28977
- var home = (path) => path.startsWith("~/") ? join13(homedir8(), path.slice(2)) : path;
28978
29215
  var link = process.env["CHIPS_JOIN_LINK"] || void 0;
28979
29216
  var LABEL = "chat.chips.hermes";
28980
29217
  stopIfAsked(LABEL);
28981
- var configPath = resolve6(home(words3[0] ?? "~/.chips-hermes/config.json"));
28982
- var folder = dirname6(configPath);
28983
- var statePath = join13(folder, "sessions.json");
28984
- 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: {} };
28985
29225
  var saveState = (next) => {
28986
- mkdirSync6(folder, { recursive: true, mode: 448 });
28987
- writeFileSync5(statePath, `${JSON.stringify(next, null, 2)}
29226
+ mkdirSync7(folder, { recursive: true, mode: 448 });
29227
+ writeFileSync6(statePath, `${JSON.stringify(next, null, 2)}
28988
29228
  `, { mode: 384 });
28989
29229
  };
28990
- var log = (line2) => process.stdout.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${line2}
28991
- `);
28992
29230
  var freePort = () => new Promise((done, failed) => {
28993
29231
  const server = createServer2();
28994
29232
  server.once("error", failed);
@@ -29061,7 +29299,7 @@ var chips = await connect({
29061
29299
  url: joined.url,
29062
29300
  token: joined.token,
29063
29301
  client: { name: "chips-hermes", version: VERSION },
29064
- store: fileStore(join13(folder, "chips-state.json")),
29302
+ store: fileStore(join14(folder, "chips-state.json")),
29065
29303
  onRestart: restart,
29066
29304
  settings: settingsFor(() => bridge)
29067
29305
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chipschat/hermes",
3
- "version": "1.0.15",
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": {
@@ -33,9 +33,9 @@
33
33
  "release": "npm run build && npm test && npm pack"
34
34
  },
35
35
  "devDependencies": {
36
- "@chipschat/conformance": "1.0.1",
37
- "@chipschat/harness": "1.0.1",
38
- "@chipschat/protocol": "1.0.1",
36
+ "@chipschat/conformance": "1.0.2",
37
+ "@chipschat/harness": "1.0.2",
38
+ "@chipschat/protocol": "1.0.2",
39
39
  "@types/node": "22.19.0",
40
40
  "esbuild": "0.27.7",
41
41
  "tsx": "4.21.0",