@chipschat/opencode 1.0.13 → 1.0.15

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
@@ -92,7 +92,7 @@ each model call and tells the agent what changed, so an edit applies from its ne
92
92
 
93
93
  - The agent's own, which a person may change from its page: its folder's `AGENTS.md`, shown empty
94
94
  until there is one, and any file `instructions` finds in its folder.
95
- - The harness's own set, on its page: `~/chips-opencode/agents/AGENTS.md`, which every agent
95
+ - The harness's own set, on its page: `~/Chips/Agents/opencode/AGENTS.md`, which every agent
96
96
  reads and a person may change; any `AGENTS.md` above it; and your own, with the files
97
97
  `instructions` names elsewhere, which your own OpenCode reads too, so they are shown and not
98
98
  changed.
@@ -163,7 +163,7 @@ first. The steps below run the bridge from this checkout instead, to work on it.
163
163
  ```
164
164
 
165
165
  The bridge runs OpenCode's server itself (`opencode serve` on `127.0.0.1`, 4096 when it is
166
- free), with a password it makes and keeps in `~/.chips-opencode/server.json`, readable by you
166
+ free), with a password it makes and keeps in `~/Chips/.bridges/opencode/server.json`, readable by you
167
167
  only: nobody starts a server or makes up a password. While the bridge runs, a server that stops
168
168
  is started again; when the bridge stops, so does its server.
169
169
 
@@ -177,13 +177,22 @@ first. The steps below run the bridge from this checkout instead, to work on it.
177
177
  two answer twice. The key Chips gives it is kept; a new link from Chips replaces it. Or join with
178
178
  `CHIPS_URL` and a code a person allows.
179
179
 
180
- 3. Optionally, name the agents in `~/.chips-opencode/config.json` (see `config.example.json`).
180
+ 3. Optionally, name the agents in `~/Chips/.bridges/opencode/config.json` (see `config.example.json`).
181
181
  Without a config it is OpenCode's `build` agent, named OpenCode.
182
182
 
183
- Each agent's sessions work in a folder of its own, `~/chips-opencode/agents/<its name>`, made
183
+ Each agent's sessions work in a folder of its own, `~/Chips/Agents/opencode/<its name>`, made
184
184
  the first time it is spoken to and kept after a rename; `folders` moves them. A `directory`
185
185
  puts every session in that one project instead, a repository the agents share.
186
186
 
187
+ Everything the bridge keeps is under `~/Chips` (Chips ADR 0283): its config, sessions, server
188
+ password and state in `~/Chips/.bridges/opencode`, the agents' folders in
189
+ `~/Chips/Agents/opencode`, where its own server runs from. Until 1.0.15 they were
190
+ `~/.chips-opencode` and `~/chips-opencode/agents`. The first start of 1.0.15 moves them and the
191
+ folder paths it saved, and once OpenCode answers, asks it to move each session of a folder that
192
+ moved (`POST /api/session/:id/move`), with everything said in it: OpenCode won't use a session
193
+ whose folder is gone. It renames and never copies; where something is at the new place already it
194
+ moves in what isn't there yet and says in its log what it left.
195
+
187
196
  **A server of your own.** To use an `opencode serve` you run yourself, give its password in the
188
197
  environment variable `passwordEnv` names (`OPENCODE_PASSWORD` by default, never in the file),
189
198
  and its `url` in the config when it isn't `127.0.0.1:4096`. `npm run keep` takes the password
@@ -1,9 +1,9 @@
1
1
  # Third-party notices
2
2
 
3
- `dist/main.js` in @chipschat/opencode 1.0.13 carries these packages, bundled in. Each one's
3
+ `dist/main.js` in @chipschat/opencode 1.0.15 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
@@ -8,9 +8,8 @@ var __export = (target, all) => {
8
8
  };
9
9
 
10
10
  // src/main.ts
11
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
12
- import { homedir as homedir7 } from "node:os";
13
- import { dirname as dirname5, join as join10, resolve as resolve4 } from "node:path";
11
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
12
+ import { dirname as dirname6, join as join10 } from "node:path";
14
13
 
15
14
  // node_modules/zod/v4/classic/external.js
16
15
  var external_exports = {};
@@ -5106,7 +5105,7 @@ function isRecursive(inst, stack, resolve5) {
5106
5105
  }
5107
5106
  return answer;
5108
5107
  };
5109
- const merge2 = (answer) => {
5108
+ const merge3 = (answer) => {
5110
5109
  if (answer > result)
5111
5110
  result = answer;
5112
5111
  };
@@ -5115,7 +5114,7 @@ function isRecursive(inst, stack, resolve5) {
5115
5114
  switch (kind) {
5116
5115
  case "object": {
5117
5116
  const raw = rawShape(def);
5118
- merge2(raw ? shape(raw, true) : ASSUMED);
5117
+ merge3(raw ? shape(raw, true) : ASSUMED);
5119
5118
  check2(def.catchall);
5120
5119
  break;
5121
5120
  }
@@ -5165,7 +5164,7 @@ function isRecursive(inst, stack, resolve5) {
5165
5164
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
5166
5165
  case "lazy": {
5167
5166
  const inner = def._cachedInner ?? (resolve5 ? inst._zod.innerType : void 0);
5168
- merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
5167
+ merge3(inner ? isRecursive(inner, stack, false) : ASSUMED);
5169
5168
  break;
5170
5169
  }
5171
5170
  // a leaf by choice: `parts` are regex fragments, not data positions
@@ -20527,7 +20526,7 @@ var contextLine = external_exports.object({
20527
20526
  }).describe("One line of a room: something said, or something that happened to who is in it.");
20528
20527
  var roomContext = external_exports.object({
20529
20528
  lines: external_exports.array(contextLine).max(CONTEXT_CEILING.lines).describe("Oldest first, the newest that fit the budget."),
20530
- 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."),
20529
+ 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."),
20531
20530
  truncated: external_exports.boolean().describe("Whether older lines after `since_seq` were left out to fit the budget."),
20532
20531
  before: external_exports.int().positive().describe("A cursor: `GET /v1/spaces/:id/context?before=` this reads what came before these lines."),
20533
20532
  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."),
@@ -21675,6 +21674,7 @@ var pairingPayload = external_exports.object({
21675
21674
  var JOIN_POLL_SECONDS = 3;
21676
21675
  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).");
21677
21676
  var keyCode = external_exports.string().regex(/^[0-9]{6}$/, "six digits");
21677
+ var joinNonce = external_exports.string().regex(/^[A-Za-z0-9_-]{43}$/, "32 bytes, base64url");
21678
21678
  var requestCode = external_exports.union([pairingCode, keyCode]);
21679
21679
  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.");
21680
21680
  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.");
@@ -21710,12 +21710,15 @@ var joinAsk = external_exports.object({
21710
21710
  software: words2(120).optional(),
21711
21711
  machine: words2(120).optional(),
21712
21712
  wants: capabilities.optional().describe("For a harness: what it asks to be allowed."),
21713
- 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.")
21713
+ 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."),
21714
+ 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.")
21714
21715
  }).describe("A request to join.");
21715
21716
  var joinAsked = external_exports.object({
21716
21717
  request_id: id("join"),
21717
21718
  secret: external_exports.string().min(16).max(200).describe("The requester's alone: it polls with it."),
21718
- code: requestCode,
21719
+ code: requestCode.optional().describe("The code to show. Absent for a device that sent `commit`: it works its own out."),
21720
+ 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)."),
21721
+ nonce: joinNonce.optional().describe("For a device that sent `commit`: the server's nonce, for the code (ADR 0278)."),
21719
21722
  expires_in: external_exports.int().positive().describe("Seconds until it expires."),
21720
21723
  interval: external_exports.int().positive().describe("Seconds between polls.")
21721
21724
  });
@@ -22647,6 +22650,17 @@ var jobChangeFrame = external_exports.object({
22647
22650
  asked_at: isoTimestamp
22648
22651
  }).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`.");
22649
22652
 
22653
+ // node_modules/@chipschat/protocol/dist/src/read-marks.js
22654
+ var READ_MARKS_MAX = 1e3;
22655
+ 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, {
22656
+ message: `at most ${READ_MARKS_MAX} rooms`
22657
+ }).describe("How far each room is read, by room id.");
22658
+ var readMarksBody = external_exports.object({ spaces: readMarks });
22659
+ var readFrame = external_exports.object({
22660
+ frame: external_exports.literal("read"),
22661
+ spaces: readMarks.describe("The rooms whose marks moved, and where to.")
22662
+ }).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.");
22663
+
22650
22664
  // node_modules/@chipschat/protocol/dist/src/frames.js
22651
22665
  var clientHelloFrame = external_exports.object({
22652
22666
  frame: external_exports.literal("hello"),
@@ -22663,7 +22677,7 @@ var clientHelloFrame = external_exports.object({
22663
22677
  autonomy: autonomyLevel.optional().describe("The level the harness believes it has.")
22664
22678
  })).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."),
22665
22679
  since: external_exports.int().nonnegative().optional().describe("Not read by Chips: a stream resumes from `subscribe`'s `since`."),
22666
- 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."),
22680
+ 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."),
22667
22681
  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.")
22668
22682
  }).describe("The first frame a client sends.");
22669
22683
  var subscribeFrame = external_exports.object({
@@ -22789,6 +22803,7 @@ var serverFrame = external_exports.discriminatedUnion("frame", [
22789
22803
  folderOpenFrame,
22790
22804
  folderRunFrame,
22791
22805
  folderRunStateFrame,
22806
+ readFrame,
22792
22807
  contextEditFrame,
22793
22808
  modelChangeFrame,
22794
22809
  heartbeatChangeFrame,
@@ -22840,9 +22855,37 @@ var HTML_POLICY = "default-src 'none'; img-src data: blob:; font-src data:; medi
22840
22855
  var PAGE_SANDBOX = "allow-scripts allow-popups allow-popups-to-escape-sandbox";
22841
22856
  var PAGE_HEADER_POLICY = `${HTML_POLICY}; sandbox ${PAGE_SANDBOX}`;
22842
22857
 
22843
- // node_modules/@chipschat/harness/dist/connect.js
22844
- import { readFile as readFile2 } from "node:fs/promises";
22845
- import { basename } from "node:path";
22858
+ // node_modules/@chipschat/protocol/dist/src/push.js
22859
+ var PUSH_WORDS = {
22860
+ /** An ask of an agent's that waits on a person (ADR 0045). */
22861
+ decision: {
22862
+ title: "A decision is waiting",
22863
+ body: "Approve or deny from here.",
22864
+ categoryId: "decision"
22865
+ },
22866
+ /** A message that @s the person, or was sent to them (ADR 0158). */
22867
+ messageForYou: { title: "A message for you", body: "Open Chips to read it." },
22868
+ /** A message in a room they hear (ADR 0158). */
22869
+ message: { title: "A new message", body: "Open Chips to read it." },
22870
+ /** A join request for its maker or an owner (ADR 0122). */
22871
+ join: { title: "Something asks to join", body: "Check its code, then allow it or not." },
22872
+ /** The referee paused agents talking among themselves (ADR 0097). */
22873
+ held: { title: "Chips paused the agents", body: "Open the space to let them carry on." },
22874
+ warning: { title: "The agents need a look", body: "They have been talking among themselves." },
22875
+ /** A guest's ask to go direct, and its answers (ADR 0211). */
22876
+ directAsked: {
22877
+ title: "A guest wants to connect directly",
22878
+ body: "Share your server with them in Tailscale."
22879
+ },
22880
+ directShared: { title: "Your server is shared with you", body: "Open Chips to go direct." },
22881
+ directDone: {
22882
+ title: "Connected directly",
22883
+ body: "A guest you shared your server with is direct now."
22884
+ },
22885
+ /** An owner is turning the relay off (ADR 0211, Phase 4). */
22886
+ relayOff: { title: "The relay is going off", body: "Go direct to keep reaching the server." }
22887
+ };
22888
+ var KNOWN = new Set(Object.values(PUSH_WORDS).map((words4) => JSON.stringify([words4.title, words4.body, "categoryId" in words4 ? words4.categoryId : null])));
22846
22889
 
22847
22890
  // node_modules/@chipschat/harness/dist/asks.js
22848
22891
  var Asks = class {
@@ -22877,10 +22920,7 @@ var Asks = class {
22877
22920
  const from = event["from"];
22878
22921
  if (!this.listener.listening() || from?.kind !== "person")
22879
22922
  return false;
22880
- this.hand(spaceId, room, {
22881
- messageId: String(event["message_id"]),
22882
- text: String(event["text"] ?? "")
22883
- });
22923
+ this.hand(spaceId, room, { messageId: String(event["message_id"]), text: String(event["text"] ?? "") }, event);
22884
22924
  return true;
22885
22925
  }
22886
22926
  /**
@@ -22916,11 +22956,42 @@ var Asks = class {
22916
22956
  this.rooms.set(spaceId, room);
22917
22957
  return room;
22918
22958
  }
22919
- hand(spaceId, room, first) {
22959
+ hand(spaceId, room, first, message) {
22920
22960
  room.first = first.messageId;
22921
- this.listener.hand({ spaceId, askedBy: room.askedBy, firstMessage: first });
22961
+ this.listener.hand({
22962
+ spaceId,
22963
+ askedBy: room.askedBy,
22964
+ firstMessage: first,
22965
+ ...message === void 0 ? {} : { message }
22966
+ });
22922
22967
  }
22923
22968
  };
22969
+ function firstTurnOf(asked, agentId2) {
22970
+ const { message } = asked;
22971
+ if (message === void 0)
22972
+ return void 0;
22973
+ const room = message.room;
22974
+ const notes = room?.notes ?? {};
22975
+ const [place, ...more] = Object.keys(notes);
22976
+ if (room === void 0 || place === void 0 || more.length > 0) {
22977
+ return { ...message, for: [agentId2] };
22978
+ }
22979
+ const placed = (each) => each.participant_id === place ? { ...each, participant_id: agentId2 } : each;
22980
+ const note = notes[place];
22981
+ const rules = room.rules?.[place];
22982
+ return {
22983
+ ...message,
22984
+ for: [agentId2],
22985
+ room: {
22986
+ ...room,
22987
+ members: room.members.map(placed),
22988
+ notes: note === void 0 ? {} : { [agentId2]: note },
22989
+ ...room.rules === void 0 ? {} : { rules: rules === void 0 ? {} : { [agentId2]: rules } },
22990
+ ...room.working === void 0 ? {} : { working: room.working.map(placed) },
22991
+ ...room.idle === void 0 ? {} : { idle: room.idle.map(placed) }
22992
+ }
22993
+ };
22994
+ }
22924
22995
  function firstWords(history) {
22925
22996
  for (const event of history) {
22926
22997
  const actor2 = event["actor"];
@@ -22931,6 +23002,10 @@ function firstWords(history) {
22931
23002
  return void 0;
22932
23003
  }
22933
23004
 
23005
+ // node_modules/@chipschat/harness/dist/connect.js
23006
+ import { readFile as readFile2 } from "node:fs/promises";
23007
+ import { basename } from "node:path";
23008
+
22934
23009
  // node_modules/@chipschat/harness/dist/events.js
22935
23010
  var Emitter = class {
22936
23011
  handlers = /* @__PURE__ */ new Map();
@@ -23322,8 +23397,8 @@ var Project = class {
23322
23397
  const end2 = all.findIndex((line, at) => at > start && line.startsWith(">>>>>>> "));
23323
23398
  if (start < 0 || end2 < 0)
23324
23399
  return void 0;
23325
- const shown = all.slice(Math.max(0, start - 1), end2 + 2);
23326
- return shown.length <= MARKED_LINES ? shown.join("\n") : [...shown.slice(0, MARKED_LINES - 1), "\u2026"].join("\n");
23400
+ const shown2 = all.slice(Math.max(0, start - 1), end2 + 2);
23401
+ return shown2.length <= MARKED_LINES ? shown2.join("\n") : [...shown2.slice(0, MARKED_LINES - 1), "\u2026"].join("\n");
23327
23402
  } catch {
23328
23403
  return void 0;
23329
23404
  }
@@ -25228,7 +25303,7 @@ async function ask(url2, options, send, machine) {
25228
25303
  if (asked.status !== 201)
25229
25304
  throw await refused(asked);
25230
25305
  const { request_id, secret, code, interval } = await asked.json();
25231
- const show = options.onCode ?? ((shown, where) => process.stderr.write(`Allow ${options.name} in Chips at ${where}. Its code is ${shown}.
25306
+ const show = options.onCode ?? ((shown2, where) => process.stderr.write(`Allow ${options.name} in Chips at ${where}. Its code is ${shown2}.
25232
25307
  `));
25233
25308
  show(code, url2);
25234
25309
  for (; ; ) {
@@ -25795,9 +25870,9 @@ import { globSync, readFileSync as readFileSync5, statSync } from "node:fs";
25795
25870
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
25796
25871
  import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, relative, sep as sep2 } from "node:path";
25797
25872
  var AGENTS_MD = "AGENTS.md";
25798
- function placesOf(env, home3) {
25799
- const config3 = env["OPENCODE_CONFIG_DIR"] || join5(env["XDG_CONFIG_HOME"] || join5(home3, ".config"), "opencode");
25800
- return { home: home3, config: config3 };
25873
+ function placesOf(env, home) {
25874
+ const config3 = env["OPENCODE_CONFIG_DIR"] || join5(env["XDG_CONFIG_HOME"] || join5(home, ".config"), "opencode");
25875
+ return { home, config: config3 };
25801
25876
  }
25802
25877
  function hashOf(text2) {
25803
25878
  return createHash2("sha256").update(text2, "utf8").digest("hex");
@@ -25825,7 +25900,7 @@ function upTo(dir, stop2) {
25825
25900
  }
25826
25901
  return dirs;
25827
25902
  }
25828
- function namedFiles(sources, folder2, root, home3) {
25903
+ function namedFiles(sources, folder2, root, home) {
25829
25904
  const found = [];
25830
25905
  const glob = (pattern, cwd) => {
25831
25906
  try {
@@ -25836,7 +25911,7 @@ function namedFiles(sources, folder2, root, home3) {
25836
25911
  };
25837
25912
  for (const source2 of sources) {
25838
25913
  if (/^https?:\/\//i.test(source2)) continue;
25839
- const expanded = source2.startsWith("~/") ? join5(home3, source2.slice(2)) : source2;
25914
+ const expanded = source2.startsWith("~/") ? join5(home, source2.slice(2)) : source2;
25840
25915
  const matches = isAbsolute2(expanded) ? glob(expanded.slice(dirname4(expanded).length + 1), dirname4(expanded)) : upTo(folder2, within(folder2, root) ? root : folder2).flatMap((dir) => glob(expanded, dir));
25841
25916
  for (const path of matches.sort()) if (isFile(path) && !found.includes(path)) found.push(path);
25842
25917
  }
@@ -25856,15 +25931,15 @@ var ContextFiles = class {
25856
25931
  }
25857
25932
  places;
25858
25933
  folders;
25859
- /** `~/chips-opencode/agents/AGENTS.md`, or the whole path where it isn't in the home. */
25934
+ /** `~/Chips/Agents/opencode/AGENTS.md`, or the whole path where it isn't in the home. */
25860
25935
  tilde(path) {
25861
- const { home: home3 } = this.places;
25862
- return within(path, home3) ? `~${path.slice(home3.length)}` : path;
25936
+ const { home } = this.places;
25937
+ return within(path, home) ? `~${path.slice(home.length)}` : path;
25863
25938
  }
25864
25939
  /** A shared file's name: where it is, a step at a time (`~ › .config › opencode › AGENTS.md`), with no slash. */
25865
25940
  crumbs(path) {
25866
- const { home: home3 } = this.places;
25867
- const steps = within(path, home3) ? ["~", ...relative(home3, path).split(sep2)] : ["/", ...path.split(sep2)];
25941
+ const { home } = this.places;
25942
+ const steps = within(path, home) ? ["~", ...relative(home, path).split(sep2)] : ["/", ...path.split(sep2)];
25868
25943
  return steps.filter((step) => step !== "").join(" \u203A ").slice(0, 255);
25869
25944
  }
25870
25945
  /**
@@ -26173,6 +26248,13 @@ var OpenCodeClient = class {
26173
26248
  const metadata = data["metadata"];
26174
26249
  return { id: id2, metadata: typeof metadata === "object" && metadata !== null ? metadata : {} };
26175
26250
  }
26251
+ /**
26252
+ * A session moved to another folder (2.0.25), with everything said in it; the session's context
26253
+ * says it moved. It works where the old folder is gone, as after Chips ADR 0283's move.
26254
+ */
26255
+ async moveSession(sessionId, directory) {
26256
+ await this.call("POST", `/api/session/${encodeURIComponent(sessionId)}/move`, { directory });
26257
+ }
26176
26258
  /** A session's title, or its metadata, which this replaces whole. */
26177
26259
  async update(sessionId, changes) {
26178
26260
  await this.call("PATCH", `/api/session/${encodeURIComponent(sessionId)}`, {
@@ -26644,10 +26726,10 @@ var Bridge = class {
26644
26726
  }
26645
26727
  /** The Main agent into the harness's own room, in the place of whichever of ours is there. */
26646
26728
  async goHome(main2, known) {
26647
- const { chips: chips2, home: home3 } = this.options;
26648
- if (home3 === void 0 || main2.spaces.includes(home3)) return;
26649
- const there = known.find((agent) => agent.agentId !== main2.agentId && agent.spaces.includes(home3));
26650
- await chips2.agents.attach(main2.agentId, { spaceId: home3, replacing: there?.agentId ?? chips2.harnessId }).catch((error62) => this.log(`${main2.name} stays out of its home: ${String(error62)}`));
26729
+ const { chips: chips2, home } = this.options;
26730
+ if (home === void 0 || main2.spaces.includes(home)) return;
26731
+ const there2 = known.find((agent) => agent.agentId !== main2.agentId && agent.spaces.includes(home));
26732
+ 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)}`));
26651
26733
  }
26652
26734
  /**
26653
26735
  * Trouble while the server doesn't answer, and fine once it does (ADR 0083). Nothing while a
@@ -26728,7 +26810,7 @@ var Bridge = class {
26728
26810
  await this.sendContexts();
26729
26811
  const context = await chips2.roomContext(spaceId, { limit: 20 }).catch(() => void 0);
26730
26812
  const said = context?.lines.find((line) => line.message_id === firstMessage.messageId)?.from;
26731
- const message = {
26813
+ const message = firstTurnOf(asked, agent.agentId) ?? {
26732
26814
  type: "message.new",
26733
26815
  space_id: spaceId,
26734
26816
  message_id: firstMessage.messageId,
@@ -26762,7 +26844,7 @@ var Bridge = class {
26762
26844
  * that isn't one of the harness's has no copy, nor has an ask the room stopped waiting for.
26763
26845
  */
26764
26846
  async makeCopy(fork) {
26765
- const { chips: chips2, opencode: opencode2, state: state2, saveState } = this.options;
26847
+ const { chips: chips2, opencode: opencode2, state: state2, saveState: saveState2 } = this.options;
26766
26848
  const spaceId = fork.space_id;
26767
26849
  if (spaceId === void 0) return;
26768
26850
  const late = Date.now() - Date.parse(fork.ts);
@@ -26793,7 +26875,7 @@ var Bridge = class {
26793
26875
  const folder2 = await this.folderFor({ who, agent });
26794
26876
  kept = { ...copied === void 0 ? {} : { session: copied.id }, ...folder2 === void 0 ? {} : { folder: folder2 } };
26795
26877
  state2.forks = { ...state2.forks, [fork.id]: kept };
26796
- saveState(state2);
26878
+ saveState2(state2);
26797
26879
  }
26798
26880
  const copy = await chips2.agents.register({
26799
26881
  key: `opencode:fork:${fork.id}`,
@@ -26806,7 +26888,7 @@ var Bridge = class {
26806
26888
  if (kept.folder !== void 0) state2.folders = { ...state2.folders, [copy.agentId]: kept.folder };
26807
26889
  const pick2 = state2.models?.[who];
26808
26890
  if (pick2 !== void 0) state2.models = { ...state2.models, [copy.agentId]: pick2 };
26809
- saveState(state2);
26891
+ saveState2(state2);
26810
26892
  if (copied !== void 0) {
26811
26893
  await opencode2.update(copied.id, {
26812
26894
  title: `Chips space ${spaceId}`,
@@ -26871,7 +26953,7 @@ var Bridge = class {
26871
26953
  async modelChanged(change) {
26872
26954
  const whose = await this.whoseIs(change.agent_id);
26873
26955
  if (whose === void 0) throw new Refuse("This harness has no such agent.");
26874
- const { opencode: opencode2, state: state2, saveState } = this.options;
26956
+ const { opencode: opencode2, state: state2, saveState: saveState2 } = this.options;
26875
26957
  let pick2;
26876
26958
  try {
26877
26959
  pick2 = pickOf(change, await opencode2.models());
@@ -26882,7 +26964,7 @@ var Bridge = class {
26882
26964
  if (pick2 === null) delete models[whose.who];
26883
26965
  else models[whose.who] = pick2;
26884
26966
  state2.models = models;
26885
- saveState(state2);
26967
+ saveState2(state2);
26886
26968
  const name = await this.nameOf(whose);
26887
26969
  this.log(`${name} runs on ${pick2 === null ? "its default" : `${pick2.model}${pick2.effort === void 0 ? "" : ` at ${pick2.effort}`}`}, from Chips`);
26888
26970
  const copy = await this.modelCopyOf(change.agent_id);
@@ -26920,7 +27002,7 @@ var Bridge = class {
26920
27002
  */
26921
27003
  async readingOf(folder2) {
26922
27004
  const { opencode: opencode2 } = this.options;
26923
- const { home: home3 } = this.places;
27005
+ const { home } = this.places;
26924
27006
  let root = this.roots.get(folder2);
26925
27007
  if (root === void 0) {
26926
27008
  root = await opencode2.projectRoot(folder2).catch(() => folder2);
@@ -26931,7 +27013,7 @@ var Bridge = class {
26931
27013
  () => false
26932
27014
  );
26933
27015
  const sources = await this.readsNamed ? await opencode2.instructions(folder2).catch(() => []) : [];
26934
- return { stop: within(folder2, home3) ? home3 : root, named: namedFiles(sources, folder2, root, home3) };
27016
+ return { stop: within(folder2, home) ? home : root, named: namedFiles(sources, folder2, root, home) };
26935
27017
  }
26936
27018
  /** Each agent of the harness's, with the folder its sessions work in and what OpenCode reads there. */
26937
27019
  async agentReadings() {
@@ -27277,7 +27359,7 @@ var Bridge = class {
27277
27359
  }
27278
27360
  /** The words into the room's session: the one it had, or a new one when OpenCode no longer has it. */
27279
27361
  async give({ id: id2, who, agent, spaceId }, said, rules) {
27280
- const { opencode: opencode2, state: state2, saveState } = this.options;
27362
+ const { opencode: opencode2, state: state2, saveState: saveState2 } = this.options;
27281
27363
  let kept = state2.sessions[id2];
27282
27364
  if (kept === void 0 && this.copying.size > 0) {
27283
27365
  await Promise.allSettled(this.copying.values());
@@ -27301,7 +27383,7 @@ var Bridge = class {
27301
27383
  metadata: { chips_space: spaceId, chips_agent: who }
27302
27384
  });
27303
27385
  state2.sessions[id2] = made;
27304
- saveState(state2);
27386
+ saveState2(state2);
27305
27387
  return { sessionId: made, given: await this.prompted(made, said(made), rules) };
27306
27388
  }
27307
27389
  /**
@@ -27309,14 +27391,14 @@ var Bridge = class {
27309
27391
  * kept by whose it is, so a rename doesn't move it. None without `folders`: the project's.
27310
27392
  */
27311
27393
  async folderFor({ who, agent }) {
27312
- const { folders: root, state: state2, saveState } = this.options;
27394
+ const { folders: root, state: state2, saveState: saveState2 } = this.options;
27313
27395
  if (root === void 0) return void 0;
27314
27396
  let folder2 = state2.folders?.[who];
27315
27397
  if (folder2 === void 0) {
27316
27398
  const name = await this.nameOf({ who, agent });
27317
27399
  folder2 = freeFolder(root, name, Object.values(state2.folders ?? {}));
27318
27400
  state2.folders = { ...state2.folders, [who]: folder2 };
27319
- saveState(state2);
27401
+ saveState2(state2);
27320
27402
  }
27321
27403
  mkdirSync4(folder2, { recursive: true });
27322
27404
  return folder2;
@@ -27968,12 +28050,55 @@ function lastLine(said) {
27968
28050
  }
27969
28051
 
27970
28052
  // src/keep.ts
27971
- import { homedir as homedir5 } from "node:os";
27972
- import { basename as basename3, join as join7, resolve as resolve2 } from "node:path";
28053
+ import { basename as basename3 } from "node:path";
27973
28054
  import { fileURLToPath as fileURLToPath2 } from "node:url";
28055
+
28056
+ // src/places.ts
28057
+ import { existsSync as existsSync5 } from "node:fs";
28058
+ import { homedir as homedir5 } from "node:os";
28059
+ import { isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve2, sep as sep3 } from "node:path";
28060
+ function placesOf2(home = homedir5()) {
28061
+ return {
28062
+ settings: join7(home, "Chips", ".bridges", "opencode"),
28063
+ agents: join7(home, "Chips", "Agents", "opencode"),
28064
+ oldSettings: join7(home, ".chips-opencode"),
28065
+ oldAgents: join7(home, "chips-opencode", "agents")
28066
+ };
28067
+ }
28068
+ var fromHome = (path, home = homedir5()) => path === "~" ? home : path.startsWith("~/") ? join7(home, path.slice(2)) : path;
28069
+ function inside2(root, path) {
28070
+ const under = relative2(root, path);
28071
+ return under === ".." || under.startsWith(`..${sep3}`) || isAbsolute3(under) ? void 0 : under;
28072
+ }
28073
+ function nowAt(path, before2, after) {
28074
+ const under = inside2(before2, path);
28075
+ if (under === void 0) return path;
28076
+ const moved = join7(after, under);
28077
+ return existsSync5(path) && !existsSync5(moved) ? path : moved;
28078
+ }
28079
+ var configOf = (word, places, home) => resolve2(fromHome(word ?? join7(places.settings, "config.json"), home));
28080
+ function configTargetOf(word, home = homedir5()) {
28081
+ const places = placesOf2(home);
28082
+ const path = configOf(word, places, home);
28083
+ const under = inside2(places.oldSettings, path);
28084
+ return under === void 0 ? path : join7(places.settings, under);
28085
+ }
28086
+ function configPathOf(word, home = homedir5()) {
28087
+ const places = placesOf2(home);
28088
+ const target = configTargetOf(word, home);
28089
+ const under = inside2(places.settings, target);
28090
+ const old = under === void 0 ? void 0 : join7(places.oldSettings, under);
28091
+ return old !== void 0 && existsSync5(old) && !existsSync5(target) ? old : target;
28092
+ }
28093
+ function foldersPathOf(path, home = homedir5()) {
28094
+ const places = placesOf2(home);
28095
+ return nowAt(resolve2(fromHome(path, home)), places.oldAgents, places.agents);
28096
+ }
28097
+
28098
+ // src/keep.ts
27974
28099
  var flags = process.argv.slice(2);
27975
28100
  var words3 = flags.filter((word) => !word.startsWith("--"));
27976
- var whole = (word) => resolve2(word.startsWith("~/") ? join7(homedir5(), word.slice(2)) : word);
28101
+ var whole = (word) => configPathOf(word);
27977
28102
  var here = fileURLToPath2(new URL("..", import.meta.url)).replace(/\/$/, "");
27978
28103
  var self = fileURLToPath2(import.meta.url);
27979
28104
  var run = self.endsWith(".js") ? [self] : ["--import", "tsx", "src/main.ts"];
@@ -28018,12 +28143,147 @@ function keepIfAsked(label2, url2, names, log2, before2 = () => void 0) {
28018
28143
  );
28019
28144
  }
28020
28145
 
28146
+ // src/move.ts
28147
+ import { lstatSync, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync6, renameSync as renameSync2, rmdirSync, writeFileSync as writeFileSync4 } from "node:fs";
28148
+ import { homedir as homedir6 } from "node:os";
28149
+ import { dirname as dirname5, join as join8, resolve as resolve3 } from "node:path";
28150
+ var there = (path) => {
28151
+ try {
28152
+ lstatSync(path);
28153
+ return true;
28154
+ } catch {
28155
+ return false;
28156
+ }
28157
+ };
28158
+ var isFolder = (path) => lstatSync(path).isDirectory();
28159
+ var shown = (path, home) => path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;
28160
+ function merge2(from, to, log2, home = homedir6(), quiet = false) {
28161
+ if (!there(from)) return;
28162
+ try {
28163
+ if (!there(to)) {
28164
+ mkdirSync5(dirname5(to), { recursive: true });
28165
+ renameSync2(from, to);
28166
+ if (!quiet) log2(`moved ${shown(from, home)} to ${shown(to, home)}`);
28167
+ return;
28168
+ }
28169
+ if (!isFolder(from) || !isFolder(to)) {
28170
+ log2(`${shown(from, home)} was left where it is: ${shown(to, home)} is there already`);
28171
+ return;
28172
+ }
28173
+ for (const entry of readdirSync(from)) merge2(join8(from, entry), join8(to, entry), log2, home, true);
28174
+ if (readdirSync(from).length === 0) rmdirSync(from);
28175
+ if (!quiet) log2(`moved what was in ${shown(from, home)} into ${shown(to, home)}`);
28176
+ } catch (error62) {
28177
+ log2(`${shown(from, home)} couldn't be moved to ${shown(to, home)}, so it is used where it is: ${error62.message}`);
28178
+ }
28179
+ }
28180
+ function removeIfEmpty(path) {
28181
+ try {
28182
+ if (readdirSync(path).length === 0) rmdirSync(path);
28183
+ } catch {
28184
+ }
28185
+ }
28186
+ function pairOf(path, places) {
28187
+ const old = inside2(places.oldAgents, path);
28188
+ if (old !== void 0) return { old: path, now: join8(places.agents, old) };
28189
+ const now = inside2(places.agents, path);
28190
+ return now === void 0 ? void 0 : { old: join8(places.oldAgents, now), now: path };
28191
+ }
28192
+ function writeJson(path, value) {
28193
+ const next = `${path}.next`;
28194
+ writeFileSync4(next, `${JSON.stringify(value, null, 2)}
28195
+ `, { mode: 384 });
28196
+ renameSync2(next, path);
28197
+ }
28198
+ var readJson = (path) => {
28199
+ try {
28200
+ const read = JSON.parse(readFileSync6(path, "utf8"));
28201
+ return typeof read === "object" && read !== null ? read : void 0;
28202
+ } catch {
28203
+ return void 0;
28204
+ }
28205
+ };
28206
+ function moveOld(configPath2, log2, home = homedir6()) {
28207
+ const places = placesOf2(home);
28208
+ const settings = dirname5(configPath2);
28209
+ const under = inside2(places.settings, settings);
28210
+ if (under !== void 0) {
28211
+ mkdirSync5(dirname5(dirname5(places.settings)), { recursive: true });
28212
+ mkdirSync5(dirname5(places.settings), { recursive: true, mode: 448 });
28213
+ merge2(join8(places.oldSettings, under), settings, log2, home);
28214
+ }
28215
+ const config3 = readJson(configPath2);
28216
+ const said = config3?.["folders"];
28217
+ const root = typeof said === "string" && said !== "" ? resolve3(fromHome(said, home)) : places.agents;
28218
+ const pair = pairOf(root, places);
28219
+ if (pair !== void 0) merge2(pair.old, pair.now, log2, home);
28220
+ if (config3 !== void 0 && typeof said === "string") {
28221
+ const moved = nowAt(root, places.oldAgents, places.agents);
28222
+ if (moved !== root) writeJson(configPath2, { ...config3, folders: `~/${moved.slice(home.length + 1)}` });
28223
+ }
28224
+ const statePath2 = join8(settings, "sessions.json");
28225
+ const state2 = readJson(statePath2);
28226
+ if (state2 === void 0) return;
28227
+ const moving = { ...state2.moving };
28228
+ let changed = false;
28229
+ const folders = { ...state2.folders };
28230
+ for (const [who, folder2] of Object.entries(folders)) {
28231
+ const moved = nowAt(folder2, places.oldAgents, places.agents);
28232
+ if (moved === folder2) continue;
28233
+ folders[who] = moved;
28234
+ for (const [room, session] of Object.entries(state2.sessions ?? {})) {
28235
+ if (room.slice(0, room.lastIndexOf("|")) === who) moving[session] = moved;
28236
+ }
28237
+ changed = true;
28238
+ }
28239
+ const forks = { ...state2.forks };
28240
+ for (const [ask2, fork] of Object.entries(forks)) {
28241
+ if (fork.folder === void 0) continue;
28242
+ const moved = nowAt(fork.folder, places.oldAgents, places.agents);
28243
+ if (moved === fork.folder) continue;
28244
+ forks[ask2] = { ...fork, folder: moved };
28245
+ if (fork.session !== void 0) moving[fork.session] = moved;
28246
+ changed = true;
28247
+ }
28248
+ if (changed) {
28249
+ writeJson(statePath2, {
28250
+ ...state2,
28251
+ folders,
28252
+ ...state2.forks === void 0 ? {} : { forks },
28253
+ ...Object.keys(moving).length === 0 ? {} : { moving }
28254
+ });
28255
+ }
28256
+ removeIfEmpty(places.oldSettings);
28257
+ removeIfEmpty(places.oldAgents);
28258
+ removeIfEmpty(dirname5(places.oldAgents));
28259
+ }
28260
+ async function moveSessions(state2, opencode2, save, log2, home = homedir6()) {
28261
+ const moving = state2.moving;
28262
+ if (moving === void 0) return;
28263
+ for (const [session, folder2] of Object.entries(moving)) {
28264
+ try {
28265
+ await opencode2.moveSession(session, folder2);
28266
+ log2(`moved OpenCode's session ${session} to ${shown(folder2, home)}`);
28267
+ delete moving[session];
28268
+ } catch (error62) {
28269
+ if (!(error62 instanceof OpenCodeError)) {
28270
+ log2(`OpenCode's session ${session} wasn't moved to ${shown(folder2, home)}, for now: ${String(error62)}`);
28271
+ continue;
28272
+ }
28273
+ log2(`OpenCode wouldn't move its session ${session} to ${shown(folder2, home)}: ${error62.message}`);
28274
+ delete moving[session];
28275
+ }
28276
+ }
28277
+ if (Object.keys(moving).length === 0) delete state2.moving;
28278
+ save(state2);
28279
+ }
28280
+
28021
28281
  // src/server.ts
28022
28282
  import { spawn } from "node:child_process";
28023
28283
  import { randomBytes } from "node:crypto";
28024
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
28284
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
28025
28285
  import { createServer } from "node:net";
28026
- import { delimiter as delimiter2, join as join8 } from "node:path";
28286
+ import { delimiter as delimiter2, join as join9 } from "node:path";
28027
28287
  import { createInterface } from "node:readline";
28028
28288
  var ServerError = class extends Error {
28029
28289
  constructor(message, missing = false) {
@@ -28034,10 +28294,10 @@ var ServerError = class extends Error {
28034
28294
  name = "ServerError";
28035
28295
  };
28036
28296
  var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
28037
- var onPath = (command, env) => command.includes("/") ? existsSync5(command) : (env["PATH"] ?? "").split(delimiter2).some((folder2) => {
28297
+ var onPath = (command, env) => command.includes("/") ? existsSync6(command) : (env["PATH"] ?? "").split(delimiter2).some((folder2) => {
28038
28298
  if (folder2 === "") return false;
28039
28299
  try {
28040
- accessSync2(join8(folder2, command), constants2.X_OK);
28300
+ accessSync2(join9(folder2, command), constants2.X_OK);
28041
28301
  return true;
28042
28302
  } catch {
28043
28303
  return false;
@@ -28071,9 +28331,9 @@ async function answers2(url2, password) {
28071
28331
  return typeof info === "string" ? info : "ok";
28072
28332
  }
28073
28333
  function keptIn(path) {
28074
- if (!existsSync5(path)) return void 0;
28334
+ if (!existsSync6(path)) return void 0;
28075
28335
  try {
28076
- const kept = JSON.parse(readFileSync6(path, "utf8"));
28336
+ const kept = JSON.parse(readFileSync7(path, "utf8"));
28077
28337
  return typeof kept.port === "number" && typeof kept.password === "string" ? kept : void 0;
28078
28338
  } catch {
28079
28339
  return void 0;
@@ -28102,7 +28362,7 @@ async function ownServer(options) {
28102
28362
  const env = options.env ?? process.env;
28103
28363
  const command = options.command ?? "opencode";
28104
28364
  const log2 = options.log ?? (() => void 0);
28105
- const path = join8(options.folder, "server.json");
28365
+ const path = join9(options.folder, "server.json");
28106
28366
  let kept = keptIn(path);
28107
28367
  let before2 = false;
28108
28368
  if (kept !== void 0) {
@@ -28124,7 +28384,7 @@ async function ownServer(options) {
28124
28384
  let again2;
28125
28385
  const said = [];
28126
28386
  const start = () => {
28127
- writeFileSync4(path, `${JSON.stringify({ port, password }, null, 2)}
28387
+ writeFileSync5(path, `${JSON.stringify({ port, password }, null, 2)}
28128
28388
  `, { mode: 384 });
28129
28389
  const started = spawn(command, ["serve", "--hostname", "127.0.0.1", "--port", String(port)], {
28130
28390
  cwd: options.cwd,
@@ -28226,9 +28486,8 @@ async function ownServer(options) {
28226
28486
  }
28227
28487
 
28228
28488
  // src/setup.ts
28229
- import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
28230
- import { homedir as homedir6 } from "node:os";
28231
- import { join as join9, resolve as resolve3 } from "node:path";
28489
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
28490
+ import { resolve as resolve4 } from "node:path";
28232
28491
  var SetupError = class extends Error {
28233
28492
  constructor(message, words4 = {}) {
28234
28493
  super(message);
@@ -28245,9 +28504,8 @@ var hostOf = (url2) => {
28245
28504
  }
28246
28505
  };
28247
28506
  var STILL_GOOD = "The link is still good: run the same command again once that's done.";
28248
- var home = (path) => path.startsWith("~/") ? join9(homedir6(), path.slice(2)) : path;
28249
28507
  function readConfig(path, env = process.env) {
28250
- const raw = existsSync6(path) ? JSON.parse(readFileSync7(path, "utf8")) : {};
28508
+ const raw = existsSync7(path) ? JSON.parse(readFileSync8(path, "utf8")) : {};
28251
28509
  const passwordEnv = typeof raw["passwordEnv"] === "string" ? raw["passwordEnv"] : "OPENCODE_PASSWORD";
28252
28510
  const password = filledIn(env[passwordEnv]) ? env[passwordEnv] : void 0;
28253
28511
  const ownServer2 = password === void 0 && typeof raw["url"] !== "string";
@@ -28274,8 +28532,8 @@ function readConfig(path, env = process.env) {
28274
28532
  password,
28275
28533
  passwordEnv,
28276
28534
  ownServer: ownServer2,
28277
- directory: typeof raw["directory"] === "string" ? resolve3(home(raw["directory"])) : void 0,
28278
- folders: resolve3(home(typeof raw["folders"] === "string" ? raw["folders"] : "~/chips-opencode/agents")),
28535
+ directory: typeof raw["directory"] === "string" ? resolve4(fromHome(raw["directory"])) : void 0,
28536
+ folders: foldersPathOf(typeof raw["folders"] === "string" ? raw["folders"] : placesOf2().agents),
28279
28537
  agents
28280
28538
  };
28281
28539
  }
@@ -28303,17 +28561,17 @@ async function checkOpenCode(config3, client) {
28303
28561
  }
28304
28562
 
28305
28563
  // src/version.ts
28306
- import { readFileSync as readFileSync8 } from "node:fs";
28307
- var VERSION = JSON.parse(readFileSync8(new URL("../package.json", import.meta.url), "utf8")).version;
28564
+ import { readFileSync as readFileSync9 } from "node:fs";
28565
+ var VERSION = JSON.parse(readFileSync9(new URL("../package.json", import.meta.url), "utf8")).version;
28308
28566
 
28309
28567
  // src/main.ts
28310
- var home2 = (path) => path.startsWith("~/") ? join10(homedir7(), path.slice(2)) : path;
28311
28568
  var link = process.env["CHIPS_JOIN_LINK"] || void 0;
28312
28569
  var LABEL = "chat.chips.opencode";
28313
28570
  stopIfAsked(LABEL);
28314
- var configPath = resolve4(home2(words3[0] ?? "~/.chips-opencode/config.json"));
28315
28571
  var log = (line) => process.stdout.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${line}
28316
28572
  `);
28573
+ moveOld(configTargetOf(words3[0]), log);
28574
+ var configPath = configPathOf(words3[0]);
28317
28575
  var stop = (error62) => {
28318
28576
  if (!(error62 instanceof SetupError)) throw error62;
28319
28577
  reportStep("failed", error62.words);
@@ -28327,14 +28585,14 @@ try {
28327
28585
  } catch (error62) {
28328
28586
  config2 = stop(error62);
28329
28587
  }
28330
- var folder = dirname5(configPath);
28331
- mkdirSync5(folder, { recursive: true, mode: 448 });
28588
+ var folder = dirname6(configPath);
28589
+ mkdirSync6(folder, { recursive: true, mode: 448 });
28332
28590
  var statePath = join10(folder, "sessions.json");
28333
28591
  var server;
28334
28592
  if (link !== void 0) reportStep("checking", { detail: "Looking for OpenCode" });
28335
28593
  if (config2.ownServer) {
28336
- const cwd = config2.directory ?? dirname5(config2.folders);
28337
- mkdirSync5(cwd, { recursive: true });
28594
+ const cwd = config2.directory ?? config2.folders;
28595
+ mkdirSync6(cwd, { recursive: true });
28338
28596
  try {
28339
28597
  server = await ownServer({ folder, cwd, log });
28340
28598
  } catch (error62) {
@@ -28378,7 +28636,10 @@ var joined = await join3({
28378
28636
  onCode: (code) => log(`Allow this harness in Chips: ${code}`)
28379
28637
  }).catch(refused2);
28380
28638
  keepIfAsked(LABEL, joined.url, [config2.passwordEnv], log, () => server?.stop());
28381
- var state = existsSync7(statePath) ? JSON.parse(readFileSync9(statePath, "utf8")) : { sessions: {} };
28639
+ var state = existsSync8(statePath) ? JSON.parse(readFileSync10(statePath, "utf8")) : { sessions: {} };
28640
+ var saveState = (next) => writeFileSync6(statePath, `${JSON.stringify(next, null, 2)}
28641
+ `, { mode: 384 });
28642
+ if (offered !== void 0) await moveSessions(state, opencode, saveState, log);
28382
28643
  var bridge;
28383
28644
  var chips = await connect({
28384
28645
  url: joined.url,
@@ -28400,8 +28661,7 @@ bridge = new Bridge({
28400
28661
  agents: config2.agents,
28401
28662
  home: joined.spaceId,
28402
28663
  state,
28403
- saveState: (next) => writeFileSync5(statePath, `${JSON.stringify(next, null, 2)}
28404
- `, { mode: 384 }),
28664
+ saveState,
28405
28665
  log,
28406
28666
  restartServer: server?.restart,
28407
28667
  ...config2.directory === void 0 ? { folders: config2.folders } : { directory: config2.directory }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chipschat/opencode",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "OpenCode as a Chips harness, on its V2 server (opencode serve): each OpenCode agent a Chips agent, each room an OpenCode 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",