agent-yes 1.108.1 → 1.109.0

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.
@@ -0,0 +1,8 @@
1
+ import "./ts-BJvj36Z3.js";
2
+ import "./logger-B9h0djqx.js";
3
+ import "./versionChecker-DGSlVqgt.js";
4
+ import "./pidStore-DBjlqzo8.js";
5
+ import "./globalPidIndex-yVd3mbsV.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-C0AwpO1R.js";
7
+
8
+ export { SUPPORTED_CLIS };
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-DQiKBC0f.js";
1
+ import { t as CLIS_CONFIG } from "./ts-BJvj36Z3.js";
2
2
 
3
3
  //#region ts/SUPPORTED_CLIS.ts
4
4
  const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
5
5
 
6
6
  //#endregion
7
7
  export { SUPPORTED_CLIS as t };
8
- //# sourceMappingURL=SUPPORTED_CLIS-CcreSTgo.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-C0AwpO1R.js.map
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import { n as logger } from "./logger-B9h0djqx.js";
3
- import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-BQULRcxP.js";
3
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-DGSlVqgt.js";
4
4
  import { argv } from "process";
5
5
  import { execFileSync, spawn } from "child_process";
6
6
  import ms from "ms";
@@ -482,7 +482,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
482
482
  {
483
483
  const rawArg = process.argv[2];
484
484
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
485
- const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-BIz8LzhF.js");
485
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-B-1ABS7S.js");
486
486
  if (isHelpFlag && process.argv.length === 3) {
487
487
  cmdHelp();
488
488
  process.exit(0);
@@ -515,7 +515,7 @@ if (config.useRust) {
515
515
  }
516
516
  }
517
517
  if (rustBinary) {
518
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-atYBMnHG.js");
518
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-BlZnNglM.js");
519
519
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
520
520
  if (config.verbose) {
521
521
  console.log(`[rust] Using binary: ${rustBinary}`);
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-DQiKBC0f.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-BJvj36Z3.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-BQULRcxP.js";
3
+ import "./versionChecker-DGSlVqgt.js";
4
4
  import "./pidStore-DBjlqzo8.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
6
 
@@ -1,11 +1,11 @@
1
- import "./ts-DQiKBC0f.js";
1
+ import "./ts-BJvj36Z3.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-BQULRcxP.js";
3
+ import "./versionChecker-DGSlVqgt.js";
4
4
  import "./pidStore-DBjlqzo8.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CcreSTgo.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-C0AwpO1R.js";
7
7
  import "./remotes-C3xPRtfg.js";
8
- import { c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, m as snapshotStatus, r as controlCodeFromName, u as readNotes } from "./subcommands-DHPQVuWd.js";
8
+ import { c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, m as snapshotStatus, r as controlCodeFromName, u as readNotes } from "./subcommands-CPJDMI84.js";
9
9
  import yargs from "yargs";
10
10
  import { mkdir, open, readFile, writeFile } from "fs/promises";
11
11
  import { homedir } from "os";
@@ -552,4 +552,4 @@ Options:
552
552
 
553
553
  //#endregion
554
554
  export { cmdServe };
555
- //# sourceMappingURL=serve-BrT33v_v.js.map
555
+ //# sourceMappingURL=serve-AjtjceCu.js.map
@@ -1,6 +1,6 @@
1
1
  import "./logger-B9h0djqx.js";
2
2
  import "./globalPidIndex-yVd3mbsV.js";
3
3
  import "./remotes-C3xPRtfg.js";
4
- import { a as finalizedLines, c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, h as stopTipForCli, i as cursorAbs, l as matchKeyword, m as snapshotStatus, n as cmdHelp, o as isPidAlive, p as runSubcommand, r as controlCodeFromName, s as isSubcommand, t as GRACEFUL_EXIT_COMMANDS, u as readNotes } from "./subcommands-DHPQVuWd.js";
4
+ import { a as finalizedLines, c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, h as stopTipForCli, i as cursorAbs, l as matchKeyword, m as snapshotStatus, n as cmdHelp, o as isPidAlive, p as runSubcommand, r as controlCodeFromName, s as isSubcommand, t as GRACEFUL_EXIT_COMMANDS, u as readNotes } from "./subcommands-CPJDMI84.js";
5
5
 
6
6
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -163,7 +163,7 @@ async function runSubcommand(argv) {
163
163
  case "restart": return await cmdRestart(rest);
164
164
  case "note": return await cmdNote(rest);
165
165
  case "serve": {
166
- const { cmdServe } = await import("./serve-BrT33v_v.js");
166
+ const { cmdServe } = await import("./serve-AjtjceCu.js");
167
167
  return cmdServe(rest);
168
168
  }
169
169
  case "setup": {
@@ -1595,4 +1595,4 @@ async function cmdStatus(rest) {
1595
1595
 
1596
1596
  //#endregion
1597
1597
  export { finalizedLines as a, listRecords as c, renderRawLog as d, resolveOne as f, writeToIpc as g, stopTipForCli as h, cursorAbs as i, matchKeyword as l, snapshotStatus as m, cmdHelp as n, isPidAlive as o, runSubcommand as p, controlCodeFromName as r, isSubcommand as s, GRACEFUL_EXIT_COMMANDS as t, readNotes as u };
1598
- //# sourceMappingURL=subcommands-DHPQVuWd.js.map
1598
+ //# sourceMappingURL=subcommands-CPJDMI84.js.map
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
2
- import { r as getInstalledPackage } from "./versionChecker-BQULRcxP.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-DGSlVqgt.js";
3
3
  import { n as agentYesHome, t as PidStore } from "./pidStore-DBjlqzo8.js";
4
4
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-CJxsoGdb.js";
5
5
  import { i as readGlobalPids } from "./globalPidIndex-yVd3mbsV.js";
@@ -1714,4 +1714,4 @@ function sleep(ms) {
1714
1714
 
1715
1715
  //#endregion
1716
1716
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1717
- //# sourceMappingURL=ts-DQiKBC0f.js.map
1717
+ //# sourceMappingURL=ts-BJvj36Z3.js.map
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
7
7
 
8
8
  //#region package.json
9
9
  var name = "agent-yes";
10
- var version = "1.108.1";
10
+ var version = "1.109.0";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -221,4 +221,4 @@ async function displayVersion() {
221
221
 
222
222
  //#endregion
223
223
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
224
- //# sourceMappingURL=versionChecker-BQULRcxP.js.map
224
+ //# sourceMappingURL=versionChecker-DGSlVqgt.js.map
@@ -28,6 +28,80 @@ export function ident(e, cap) {
28
28
  return `${c(rb.repo)}/${c(rb.branch)}`;
29
29
  }
30
30
 
31
+ // ---- device-aware identity (multi-room) -----------------------------------
32
+ // When several machines' agents are aggregated into one list, an agent's full
33
+ // identity is user@host:owner/repo/branch. The device (user@host) comes from the
34
+ // codehost peer label on `_host`; the path (owner/repo/branch) from the cwd.
35
+
36
+ // Split a codehost device label into { user, host }. "sno@taka" → both parts;
37
+ // "taka" (no @) → host only; "" / missing → both empty (a local/unknown device).
38
+ export function deviceParts(host) {
39
+ if (!host) return { user: "", host: "" };
40
+ const at = String(host).indexOf("@");
41
+ return at >= 0
42
+ ? { user: String(host).slice(0, at), host: String(host).slice(at + 1) }
43
+ : { user: "", host: String(host) };
44
+ }
45
+
46
+ // The five identity fields, in display order, for one agent.
47
+ export function identFields(e) {
48
+ const d = deviceParts(e._host);
49
+ const rb = repoBranch(e) || { owner: "", repo: "", branch: "" };
50
+ return { user: d.user, host: d.host, owner: rb.owner, repo: rb.repo, branch: rb.branch };
51
+ }
52
+
53
+ const IDENT_ORDER = ["user", "host", "owner", "repo", "branch"];
54
+
55
+ // Precompute, over the whole shown list: which fields are uniform (identical for
56
+ // every agent — so they can be omitted) and whether any device info exists at
57
+ // all (if not, we render the legacy path-only identity, no user@host: prefix).
58
+ export function identContext(entries) {
59
+ const fields = entries.map(identFields);
60
+ const uniform = {};
61
+ for (const f of IDENT_ORDER) uniform[f] = new Set(fields.map((x) => x[f])).size <= 1;
62
+ const anyDevice = fields.some((x) => x.user || x.host);
63
+ return { uniform, anyDevice };
64
+ }
65
+
66
+ // Build an agent's compact identity against a precomputed identContext. Each
67
+ // field is clipped to `cap` chars (compact one-liner) and BLANKED when uniform
68
+ // across the list — but the separators (@ : / /) are kept so the string stays
69
+ // machine-parseable: e.g. all on one device → "@:age/mai", a mixed-device list →
70
+ // "sno@tak:age/mai". A purely local list (no devices anywhere) falls back to the
71
+ // legacy "own/rep/bra" with no device prefix.
72
+ export function compactIdent(e, ctx, cap = 3) {
73
+ const m = identFields(e);
74
+ const clip = (s) => (cap && s.length > cap ? s.slice(0, cap) : s);
75
+ const v = (f) => (ctx.uniform[f] ? "" : clip(m[f]));
76
+ const path = `${v("owner")}/${v("repo")}/${v("branch")}`;
77
+ return ctx.anyDevice ? `${v("user")}@${v("host")}:${path}` : path;
78
+ }
79
+
80
+ // The full, uncapped identity for a hover title — every field shown, device
81
+ // prefix only when this agent actually has device info.
82
+ export function fullIdent(e) {
83
+ const m = identFields(e);
84
+ const path = `${m.owner}/${m.repo}/${m.branch}`;
85
+ return m.user || m.host ? `${m.user}@${m.host}:${path}` : path;
86
+ }
87
+
88
+ // True when a compact identity carries at least one real character (not just
89
+ // separators) — used to decide whether to render the identity span at all.
90
+ export function hasIdent(s) {
91
+ return /[^@:/]/.test(s || "");
92
+ }
93
+
94
+ // Count of distinct devices (user@host) present in the list. >1 means "not
95
+ // alone" → worth showing the device tag in the detailed view.
96
+ export function deviceCount(entries) {
97
+ const set = new Set();
98
+ for (const e of entries) {
99
+ const { user, host } = deviceParts(e._host);
100
+ if (user || host) set.add(user + "@" + host);
101
+ }
102
+ return set.size;
103
+ }
104
+
31
105
  // Derive codehost-style mnemonic tags from a cwd like .../ws/<owner>/<repo>/tree/<wt>.
32
106
  export function tagsFor(e) {
33
107
  const t = [];
@@ -63,6 +137,15 @@ export function matches(e, toks) {
63
137
  if (ci > 0) {
64
138
  const k = tok.slice(0, ci),
65
139
  v = tok.slice(ci + 1);
140
+ // room: / device: filter the aggregation by source and machine.
141
+ if (k === "room")
142
+ return String(e._room || "")
143
+ .toLowerCase()
144
+ .includes(v);
145
+ if (k === "device" || k === "dev")
146
+ return String(e._host || "")
147
+ .toLowerCase()
148
+ .includes(v);
66
149
  return tagsFor(e).some(([tk, tv]) => tk === k && tv.toLowerCase().includes(v));
67
150
  }
68
151
  return hay.toLowerCase().includes(tok);
package/lab/ui/index.html CHANGED
@@ -186,6 +186,19 @@
186
186
  .rooms .ritem.cur {
187
187
  box-shadow: inset 2px 0 0 var(--green);
188
188
  }
189
+ .rooms .rstat {
190
+ font-family: var(--mono);
191
+ font-size: 10.5px;
192
+ flex: none;
193
+ min-width: 18px;
194
+ }
195
+ .rooms .rstat.on {
196
+ color: var(--green);
197
+ }
198
+ .rooms .rstat.off {
199
+ color: var(--muted);
200
+ opacity: 0.6;
201
+ }
189
202
  .rooms .rname {
190
203
  font-family: var(--mono);
191
204
  color: var(--accent);
@@ -787,11 +800,15 @@
787
800
  age,
788
801
  matches,
789
802
  nextIndex,
803
+ identContext,
804
+ compactIdent,
805
+ fullIdent,
806
+ hasIdent,
807
+ deviceCount,
790
808
  } from "./console-logic.js";
791
809
 
792
810
  let entries = [];
793
- let wantRemote = false; // a room hash/cached room is in play — don't poll the cloud origin's (nonexistent) /api before the tunnel opens
794
- let sel = null; // selected keyword (pid as string)
811
+ let sel = null; // selected agent's composite key (room#pid)
795
812
  let es = null; // live-tail subscription closer
796
813
  let term = null; // xterm.js Terminal rendering the raw PTY stream
797
814
  let fit = null;
@@ -1073,20 +1090,14 @@
1073
1090
  : path;
1074
1091
  };
1075
1092
 
1076
- const Conn = {
1077
- rtc: null, // RTCClient when remote (ay share), null when local
1078
- ch: null, // CodehostClient when viewing a codehost room
1093
+ // ---- transports: a uniform { fetchJSON, post, subscribe } over each wire ----
1094
+ // local (same-origin ay serve), an ay-share RTCClient, or a codehost room.
1095
+ // CodehostClient already exposes this shape, so it's used as a tx directly.
1096
+ const localTx = {
1079
1097
  async fetchJSON(path) {
1080
- if (this.ch) return this.ch.fetchJSON(path);
1081
- if (this.rtc) return JSON.parse((await this.rtc.req("GET", path)).text);
1082
1098
  return (await fetch(withTok(path))).json();
1083
1099
  },
1084
1100
  async post(path, bodyObj) {
1085
- if (this.ch) return this.ch.post(path, bodyObj);
1086
- if (this.rtc) {
1087
- const r = await this.rtc.req("POST", path, JSON.stringify(bodyObj));
1088
- return { ok: r.status >= 200 && r.status < 300, text: r.text };
1089
- }
1090
1101
  const r = await fetch(withTok(path), {
1091
1102
  method: "POST",
1092
1103
  headers: { "Content-Type": "application/json" },
@@ -1094,13 +1105,27 @@
1094
1105
  });
1095
1106
  return { ok: r.ok, text: await r.text() };
1096
1107
  },
1097
- // onText gets each parsed SSE data payload (same shape as the local EventSource path).
1098
1108
  subscribe(path, onText, onOpen, onError) {
1099
- if (this.ch) return this.ch.subscribe(path, onText, onOpen, onError);
1100
- if (this.rtc) {
1109
+ const ev = new EventSource(withTok(path));
1110
+ ev.onopen = () => onOpen && onOpen();
1111
+ ev.onmessage = (e) => onText(JSON.parse(e.data));
1112
+ ev.onerror = () => onError && onError();
1113
+ return () => ev.close();
1114
+ },
1115
+ };
1116
+ function rtcTx(rtc) {
1117
+ return {
1118
+ async fetchJSON(path) {
1119
+ return JSON.parse((await rtc.req("GET", path)).text);
1120
+ },
1121
+ async post(path, bodyObj) {
1122
+ const r = await rtc.req("POST", path, JSON.stringify(bodyObj));
1123
+ return { ok: r.status >= 200 && r.status < 300, text: r.text };
1124
+ },
1125
+ subscribe(path, onText, onOpen, onError) {
1101
1126
  onOpen && onOpen();
1102
1127
  let buf = "";
1103
- return this.rtc.subscribe(path, (raw) => {
1128
+ return rtc.subscribe(path, (raw) => {
1104
1129
  buf += raw;
1105
1130
  let i;
1106
1131
  while ((i = buf.indexOf("\n\n")) >= 0) {
@@ -1114,14 +1139,66 @@
1114
1139
  }
1115
1140
  }
1116
1141
  });
1117
- }
1118
- const ev = new EventSource(withTok(path));
1119
- ev.onopen = () => onOpen && onOpen();
1120
- ev.onmessage = (e) => onText(JSON.parse(e.data));
1121
- ev.onerror = () => onError && onError();
1122
- return () => ev.close();
1123
- },
1124
- };
1142
+ },
1143
+ };
1144
+ }
1145
+
1146
+ // ---- fleet: local + every saved room, all connected at once ------------
1147
+ // Each source contributes its agents to one merged list (tagged with the
1148
+ // owning source on `_room`/`_key`); per-agent ops route back via srcFor/txFor.
1149
+ // Live counts in the rooms panel come from each source's serverCount/devices.
1150
+ const LOCAL = "local";
1151
+ const sources = new Map(); // id -> { id, host, kind, tx, client, live, devices, serverCount }
1152
+ const srcFor = (e) => (e && sources.get(e._room)) || sources.get(LOCAL) || null;
1153
+ const txFor = (e) => srcFor(e)?.tx || localTx;
1154
+
1155
+ // The local source is only worth polling when this page is actually backed
1156
+ // by an ay serve: localhost, or served by `ay serve --http` (which leaves a
1157
+ // token), or when there are no rooms to fall back on. On the public origin
1158
+ // with rooms, skip it so we don't hammer a 404 every poll.
1159
+ function ensureLocalSource() {
1160
+ const isLocalhost = ["localhost", "127.0.0.1", "[::1]"].includes(location.hostname);
1161
+ const hasToken = !!localStorage.getItem("ay.localToken");
1162
+ const enabled = isLocalhost || hasToken || Object.keys(loadRooms()).length === 0;
1163
+ if (enabled && !sources.has(LOCAL)) {
1164
+ sources.set(LOCAL, {
1165
+ id: LOCAL,
1166
+ host: "local",
1167
+ kind: "local",
1168
+ tx: localTx,
1169
+ client: null,
1170
+ live: false,
1171
+ tried: true, // no connect phase — polled directly
1172
+ devices: new Set(),
1173
+ serverCount: 0,
1174
+ });
1175
+ } else if (!enabled) {
1176
+ sources.delete(LOCAL);
1177
+ }
1178
+ }
1179
+
1180
+ // Pull one source's agent list, tagging each row with its origin + a
1181
+ // composite key (pids can collide across rooms). Updates the source's live
1182
+ // flag, device set, and server count for the rooms panel.
1183
+ async function listSource(s) {
1184
+ try {
1185
+ const arr = await s.tx.fetchJSON("/api/ls?all=1");
1186
+ s.live = true;
1187
+ s.devices = new Set();
1188
+ const out = (Array.isArray(arr) ? arr : []).map((e) => {
1189
+ const host = e._host || "";
1190
+ if (host) s.devices.add(host);
1191
+ return { ...e, _room: s.id, _key: s.id + "#" + e.pid, _host: host };
1192
+ });
1193
+ s.serverCount =
1194
+ s.kind === "ch" ? s.client?.hosts().length || 0 : s.devices.size || (s.live ? 1 : 0);
1195
+ return out;
1196
+ } catch {
1197
+ s.live = false;
1198
+ s.serverCount = 0;
1199
+ return [];
1200
+ }
1201
+ }
1125
1202
 
1126
1203
  // Compact list: one line per agent (dot + cli + title), persisted per device.
1127
1204
  let compactList = localStorage.getItem("ay.compactList") === "1";
@@ -1131,16 +1208,21 @@
1131
1208
  const shown = entries.filter((e) => matches(e, toks));
1132
1209
  $("count").textContent = `${shown.length} / ${entries.length} agents`;
1133
1210
  $("viewbtn").classList.toggle("on", compactList);
1211
+ // identContext blanks any field uniform across the shown list (so a
1212
+ // single-device fleet shows no device); multiDevice gates the detailed
1213
+ // host tag so it only appears when machines are actually mixed.
1214
+ const ctx = identContext(shown);
1215
+ const multiDevice = deviceCount(shown) > 1;
1134
1216
  if (compactList) {
1135
1217
  $("list").innerHTML =
1136
1218
  shown
1137
1219
  .map((e) => {
1138
1220
  const t = e.title || e.prompt || "";
1139
- const id = ident(e, true);
1221
+ const id = compactIdent(e, ctx);
1140
1222
  const cli = cliLabel(e);
1141
- return `<div class="row crow ${String(e.pid) === sel ? "sel" : ""}" data-pid="${e.pid}">
1223
+ return `<div class="row crow ${e._key === sel ? "sel" : ""}" data-key="${esc(e._key)}">
1142
1224
  <span class="dot ${esc(e.status)}"></span>
1143
- ${id ? `<span class="cident" title="${esc(ident(e))}">${esc(id)}</span>` : ""}
1225
+ ${hasIdent(id) ? `<span class="cident" title="${esc(fullIdent(e))}">${esc(id)}</span>` : ""}
1144
1226
  ${cli ? `<span class="cname">${esc(cli)}</span>` : ""}
1145
1227
  <span class="ctitle ${e.title ? "" : "dim"}" title="${esc(t)}">${esc(t)}</span>
1146
1228
  <span class="age">${age(e)}</span></div>`;
@@ -1151,13 +1233,16 @@
1151
1233
  $("list").innerHTML =
1152
1234
  shown
1153
1235
  .map((e) => {
1236
+ // The host tag only earns its place when several machines are in
1237
+ // play; otherwise it's noise (every row would carry the same one).
1154
1238
  const tags = tagsFor(e)
1239
+ .filter(([k]) => k !== "host" || multiDevice)
1155
1240
  .map(
1156
1241
  ([k, v]) =>
1157
1242
  `<span class="rtag" data-k="${k}"><span style="opacity:.55">${k}:</span>${esc(v)}</span>`,
1158
1243
  )
1159
1244
  .join("");
1160
- return `<div class="row ${String(e.pid) === sel ? "sel" : ""}" data-pid="${e.pid}">
1245
+ return `<div class="row ${e._key === sel ? "sel" : ""}" data-key="${esc(e._key)}">
1161
1246
  <div class="r1"><span class="dot ${esc(e.status)}"></span>
1162
1247
  <span class="name">${esc(cliLabel(e) || ident(e) || "agent")}</span>
1163
1248
  <span class="badge">pid ${e.pid}</span>
@@ -1195,48 +1280,67 @@
1195
1280
  } catch {}
1196
1281
  }
1197
1282
 
1283
+ // Match a stored/linked selection token against an entry: either the full
1284
+ // composite key (room#pid) or a bare pid (?pid= deep links, legacy ay.sel).
1285
+ const matchSel = (e, token) => e._key === token || String(e.pid) === String(token);
1286
+
1198
1287
  async function loadList() {
1199
- const remote = Conn.ch || Conn.rtc;
1200
- // A room was requested but its WebRTC tunnel isn't up yet: the cloud
1201
- // origin serves only this static page, so a same-origin /api/ls fetch
1202
- // here just 404s. Skip it connectRoom() drives loadList once the data
1203
- // channel opens (and again on every interval tick thereafter).
1204
- if (wantRemote && !remote) {
1205
- renderList();
1206
- return;
1207
- }
1208
- try {
1209
- entries = await Conn.fetchJSON("/api/ls?all=1");
1210
- setConn(remote ? "● " + (curRoom || "remote") : "● local", "var(--green)");
1211
- } catch (e) {
1212
- setConn(remote ? "● peer down" : "● ay serve down", "var(--red)");
1288
+ const srcs = [...sources.values()];
1289
+ const lists = await Promise.all(srcs.map(listSource));
1290
+ entries = lists.flat();
1291
+ // Badge: total agents + how many rooms are live. Red only when nothing
1292
+ // at all is reachable (no source answered).
1293
+ const roomSrcs = srcs.filter((s) => s.id !== LOCAL);
1294
+ const liveRooms = roomSrcs.filter((s) => s.live).length;
1295
+ const anyLive = srcs.some((s) => s.live);
1296
+ const connecting = roomSrcs.some((s) => !s.tried);
1297
+ const n = entries.length;
1298
+ if (!srcs.length) {
1299
+ setConn("● no fleet", "var(--muted)");
1300
+ } else if (!anyLive) {
1301
+ if (connecting) setConn("● connecting…", "var(--amber)");
1302
+ else setConn(roomSrcs.length ? "● rooms offline" : "● ay serve down", "var(--red)");
1303
+ } else {
1304
+ const roomBit = liveRooms ? ` · ${liveRooms} room${liveRooms === 1 ? "" : "s"}` : "";
1305
+ setConn(`● ${n} agent${n === 1 ? "" : "s"}${roomBit}`, "var(--green)");
1213
1306
  }
1307
+ renderRoomsIfOpen();
1214
1308
  renderList();
1215
- if (autoPid && entries.some((x) => String(x.pid) === String(autoPid))) {
1216
- const pid = autoPid;
1309
+ if (autoPid && entries.some((x) => matchSel(x, autoPid))) {
1310
+ const tok = autoPid;
1217
1311
  autoPid = null;
1312
+ const e = entries.find((x) => matchSel(x, tok));
1218
1313
  // On a phone, a restored selection re-highlights the row but stays on
1219
1314
  // the list — opening it would flip straight into the full-screen
1220
1315
  // terminal (show-detail) and hide the list. An explicit ?pid= link is
1221
1316
  // a deliberate jump, so it still opens. Desktop shows both panes, so
1222
1317
  // there's nothing to scope — always open.
1223
1318
  if (autoPidExplicit || window.innerWidth > 720) {
1224
- select(pid);
1319
+ select(e._key);
1225
1320
  } else {
1226
- sel = pid;
1321
+ sel = e._key;
1227
1322
  renderList();
1228
1323
  }
1229
1324
  }
1230
1325
  }
1231
1326
 
1232
- function select(pid) {
1233
- sel = String(pid);
1327
+ function select(keyOrPid) {
1328
+ const e =
1329
+ entries.find((x) => x._key === keyOrPid) ||
1330
+ entries.find((x) => String(x.pid) === String(keyOrPid));
1331
+ if (!e) {
1332
+ sel = String(keyOrPid);
1333
+ return;
1334
+ }
1335
+ sel = e._key;
1234
1336
  // Remember the selection so a refresh re-opens this agent (see boot/autoPid).
1235
1337
  try {
1236
1338
  localStorage.setItem("ay.sel", sel);
1237
1339
  } catch {}
1238
- const e = entries.find((x) => String(x.pid) === sel);
1239
- if (!e) return;
1340
+ // pid + tx are how we talk to the agent's own host; sel (composite) is
1341
+ // only for UI identity/highlight, since pids can collide across rooms.
1342
+ const pid = e.pid;
1343
+ const tx = txFor(e);
1240
1344
  renderList();
1241
1345
  // Mobile: flip the single-column layout to the terminal ("detail") pane.
1242
1346
  // Done BEFORE term.open below so xterm measures a visible container.
@@ -1276,15 +1380,15 @@
1276
1380
  // feed it, so we just surface the latest title as the header name. Falls
1277
1381
  // back to the cli name when the agent never sets one.
1278
1382
  term.onTitleChange((t) => {
1279
- if (sel === String(e.pid) && t && t.trim()) $("rname").textContent = t.trim();
1383
+ if (sel === e._key && t && t.trim()) $("rname").textContent = t.trim();
1280
1384
  });
1281
1385
  // Adapt: drive the agent's PTY to the browser terminal size (POST
1282
1386
  // /api/resize → winsize + SIGWINCH) so its TUI reflows to match what we
1283
1387
  // render. Suppressed while we're merely adopting the agent's OWN size.
1284
1388
  let adoptingAgentSize = false;
1285
1389
  const pushSize = () => {
1286
- if (term && sel && !adoptingAgentSize)
1287
- Conn.post("/api/resize/" + encodeURIComponent(sel), {
1390
+ if (term && sel === e._key && !adoptingAgentSize)
1391
+ tx.post("/api/resize/" + encodeURIComponent(pid), {
1288
1392
  cols: term.cols,
1289
1393
  rows: term.rows,
1290
1394
  }).catch(() => {});
@@ -1296,17 +1400,18 @@
1296
1400
  // covers the UTF-8 mouse encoding (DECSET 1005). Verified end-to-end:
1297
1401
  // a drag emits \x1b[<0;..M / \x1b[<32;..M / \x1b[<0;..m, wheel \x1b[<64/65..M.
1298
1402
  const fwd = (d) => {
1299
- if (sel) Conn.post("/api/send", { keyword: sel, msg: d, code: "none" }).catch(() => {});
1403
+ if (sel === e._key)
1404
+ tx.post("/api/send", { keyword: pid, msg: d, code: "none" }).catch(() => {});
1300
1405
  };
1301
1406
  term.onData(fwd);
1302
1407
  term.onBinary(fwd);
1303
1408
  // Render the existing buffer at the AGENT's current width first so its
1304
1409
  // wrapping is correct, instead of forcing our viewport width onto stale
1305
1410
  // content. The user adapts to the window by resizing it (fit → push).
1306
- const selPid = sel;
1307
- Conn.fetchJSON("/api/size/" + encodeURIComponent(selPid))
1411
+ const selKey = e._key;
1412
+ tx.fetchJSON("/api/size/" + encodeURIComponent(pid))
1308
1413
  .then((sz) => {
1309
- if (sel !== selPid || !term) return;
1414
+ if (sel !== selKey || !term) return;
1310
1415
  if (sz && sz.cols && sz.rows) {
1311
1416
  adoptingAgentSize = true;
1312
1417
  term.resize(sz.cols, sz.rows);
@@ -1329,8 +1434,8 @@
1329
1434
  // viewer is pinned to the bottom, and cap the buffer so it can't grow forever.
1330
1435
  $("livedot").className = "dot idle";
1331
1436
  $("livetxt").textContent = "connecting…";
1332
- const close = Conn.subscribe(
1333
- "/api/tail/" + encodeURIComponent(sel) + "?raw=1",
1437
+ const close = tx.subscribe(
1438
+ "/api/tail/" + encodeURIComponent(pid) + "?raw=1",
1334
1439
  (raw) => {
1335
1440
  if (term) term.write(raw);
1336
1441
  },
@@ -1348,7 +1453,7 @@
1348
1453
 
1349
1454
  $("list").addEventListener("click", (ev) => {
1350
1455
  const row = ev.target.closest(".row");
1351
- if (row) select(row.dataset.pid);
1456
+ if (row) select(row.dataset.key);
1352
1457
  });
1353
1458
  // Mobile back button: return to the list pane. The tail keeps streaming in the
1354
1459
  // background (selection unchanged), so reopening the agent is instant.
@@ -1379,10 +1484,10 @@
1379
1484
  const toks = $("q").value.trim().split(/\s+/).filter(Boolean);
1380
1485
  const shown = entries.filter((e) => matches(e, toks));
1381
1486
  if (!shown.length) return;
1382
- const cur = shown.findIndex((e) => String(e.pid) === sel);
1487
+ const cur = shown.findIndex((e) => e._key === sel);
1383
1488
  const next = shown[nextIndex(shown.length, cur, dir)];
1384
- select(String(next.pid));
1385
- const row = $("list").querySelector('.row[data-pid="' + next.pid + '"]');
1489
+ select(next._key);
1490
+ const row = $("list").querySelector('.row[data-key="' + CSS.escape(next._key) + '"]');
1386
1491
  if (row) row.scrollIntoView({ block: "nearest" });
1387
1492
  }
1388
1493
  // Alt+ArrowDown / Alt+ArrowUp cycles agents. Capture phase on window so it
@@ -1401,7 +1506,6 @@
1401
1506
 
1402
1507
  // ---- rooms: localStorage cache + a manager you open by clicking the badge ----
1403
1508
  const ROOMS_KEY = "ay.rooms";
1404
- let curRoom = null;
1405
1509
  const loadRooms = () => {
1406
1510
  try {
1407
1511
  return JSON.parse(localStorage.getItem(ROOMS_KEY) || "{}");
@@ -1437,58 +1541,79 @@
1437
1541
  return "ch-" + (h >>> 0).toString(36).slice(0, 4).padStart(4, "0");
1438
1542
  }
1439
1543
 
1440
- function dropConn() {
1441
- if (Conn.rtc) {
1442
- try {
1443
- Conn.rtc.pc?.close();
1444
- } catch {}
1445
- Conn.rtc = null;
1446
- }
1447
- if (Conn.ch) {
1448
- try {
1449
- Conn.ch.close();
1450
- } catch {}
1451
- Conn.ch = null;
1452
- }
1544
+ function removeSource(room) {
1545
+ const s = sources.get(room);
1546
+ if (!s) return;
1547
+ try {
1548
+ s.client?.close?.();
1549
+ s.client?.pc?.close?.();
1550
+ } catch {}
1551
+ sources.delete(room);
1453
1552
  }
1454
1553
 
1455
- async function connectRoom(room, token, host) {
1554
+ // Add a room to the fleet and connect it — WITHOUT dropping the others, so
1555
+ // every saved room streams its agents at once. Idempotent: a room already
1556
+ // in the fleet just refreshes. The connection runs in the background; the
1557
+ // next poll picks up its agents once the tunnel is open.
1558
+ async function addRoomSource(room, token, host) {
1456
1559
  host = host || SIG_DEFAULT;
1457
- wantRemote = true; // from here on, loadList waits for the tunnel instead of hitting the origin
1458
1560
  saveRoom(room, token, host); // cache so the badge can list & reconnect later
1561
+ if (sources.has(room)) return;
1562
+ const s = {
1563
+ id: room,
1564
+ host,
1565
+ kind: host === CH_HOST ? "ch" : "rtc",
1566
+ tx: null,
1567
+ client: null,
1568
+ live: false,
1569
+ tried: false,
1570
+ devices: new Set(),
1571
+ serverCount: 0,
1572
+ };
1573
+ sources.set(room, s);
1574
+ renderRoomsIfOpen();
1459
1575
  try {
1460
- localStorage.setItem("ay.lastRoom", room); // reconnect here on a bare open
1461
- } catch {}
1462
- curRoom = room;
1463
- dropConn();
1464
- setConn("● connecting " + room + "…", "var(--amber)");
1465
- if (host === CH_HOST) {
1466
- const c = new CodehostClient(token);
1467
- c.onstate = (s) => {
1468
- if (s === "closed") setConn("● room lost", "var(--red)");
1469
- };
1470
- try {
1576
+ if (host === CH_HOST) {
1577
+ const c = new CodehostClient(token);
1578
+ c.onstate = (st) => {
1579
+ if (st === "closed") {
1580
+ s.live = false;
1581
+ renderRoomsIfOpen();
1582
+ }
1583
+ };
1471
1584
  await c.connect();
1472
- Conn.ch = c;
1473
- } catch (e) {
1474
- setConn("● connect failed", "var(--red)");
1585
+ s.client = c;
1586
+ s.tx = c;
1587
+ } else {
1588
+ const c = new RTCClient(host, room, token);
1589
+ c.onstate = (st) => {
1590
+ if (st === "failed" || st === "closed") {
1591
+ s.live = false;
1592
+ renderRoomsIfOpen();
1593
+ }
1594
+ };
1595
+ await c.connect();
1596
+ s.client = c;
1597
+ s.tx = rtcTx(c);
1475
1598
  }
1476
- loadList();
1477
- return;
1478
- }
1479
- const c = new RTCClient(host, room, token);
1480
- c.onstate = (s) => {
1481
- if (s === "failed" || s === "closed") setConn("● peer lost", "var(--red)");
1482
- };
1483
- try {
1484
- await c.connect();
1485
- Conn.rtc = c;
1599
+ s.live = true;
1486
1600
  } catch (e) {
1487
- setConn("● connect failed", "var(--red)");
1601
+ s.live = false;
1488
1602
  }
1603
+ s.tried = true;
1604
+ renderRoomsIfOpen();
1489
1605
  loadList();
1490
1606
  }
1491
1607
 
1608
+ // Connect to every saved room at once (called on boot).
1609
+ function connectAllRooms() {
1610
+ const r = loadRooms();
1611
+ for (const name of Object.keys(r)) addRoomSource(name, r[name].token, r[name].host);
1612
+ }
1613
+
1614
+ // Back-compat alias: a freshly pasted/linked room is added like any other.
1615
+ const connectRoom = (room, token, host) => addRoomSource(room, token, host);
1616
+
1492
1617
  function parseRoomInput(s) {
1493
1618
  s = s.trim();
1494
1619
  const hash = s.indexOf("#");
@@ -1503,13 +1628,32 @@
1503
1628
  return m ? { room: m[1], token: m[2], host: m[3] } : null;
1504
1629
  }
1505
1630
 
1631
+ // Re-render the rooms panel only if it's currently open (so live-count
1632
+ // updates from connect/poll land without forcing it open).
1633
+ function renderRoomsIfOpen() {
1634
+ if ($("rooms").style.display !== "none") renderRooms();
1635
+ }
1636
+
1637
+ // A room's live state: a room is reachable whenever we can join its
1638
+ // signaling server, so "live" really means "how many serving machines are
1639
+ // in it right now". The signaling layer can't tell us the count until we've
1640
+ // connected, so we show the source's last-known serverCount.
1641
+ function roomStatus(n) {
1642
+ const s = sources.get(n);
1643
+ if (!s) return `<span class="rstat off">○</span>`;
1644
+ if (!s.live) return `<span class="rstat off" title="offline">○</span>`;
1645
+ const c = s.serverCount;
1646
+ return `<span class="rstat on" title="${c} live ${c === 1 ? "server" : "servers"}">● ${c}</span>`;
1647
+ }
1648
+
1506
1649
  function renderRooms() {
1507
1650
  const r = loadRooms();
1508
1651
  const names = Object.keys(r).sort((a, b) => r[b].ts - r[a].ts);
1509
1652
  const items = names.length
1510
1653
  ? names
1511
1654
  .map(
1512
- (n) => `<div class="ritem ${n === curRoom ? "cur" : ""}">
1655
+ (n) => `<div class="ritem ${sources.get(n)?.live ? "cur" : ""}">
1656
+ ${roomStatus(n)}
1513
1657
  <span class="rname" data-room="${esc(n)}">${esc(n)}</span>
1514
1658
  <span class="rhost">${esc(r[n].host)}</span>
1515
1659
  <span class="rx" data-del="${esc(n)}" title="forget">✕</span></div>`,
@@ -1539,17 +1683,26 @@
1539
1683
  $("rooms").addEventListener("click", (ev) => {
1540
1684
  const name = ev.target.closest(".rname");
1541
1685
  if (name) {
1686
+ // Rooms are all connected at once now; clicking one filters the list to
1687
+ // it (and re-adds it if it was forgotten/offline), rather than switching.
1542
1688
  const r = loadRooms()[name.dataset.room];
1543
1689
  if (r) {
1544
1690
  connectRoom(name.dataset.room, r.token, r.host);
1691
+ $("q").value = "room:" + name.dataset.room;
1692
+ try {
1693
+ localStorage.setItem("ay.filter", $("q").value);
1694
+ } catch {}
1695
+ renderList();
1545
1696
  $("rooms").style.display = "none";
1546
1697
  }
1547
1698
  return;
1548
1699
  }
1549
1700
  const del = ev.target.closest(".rx");
1550
1701
  if (del) {
1702
+ removeSource(del.dataset.del);
1551
1703
  dropRoom(del.dataset.del);
1552
1704
  renderRooms();
1705
+ loadList();
1553
1706
  return;
1554
1707
  }
1555
1708
  if (ev.target.id === "roomadd") {
@@ -1597,16 +1750,33 @@
1597
1750
  $("launch").style.display = "flex";
1598
1751
  }
1599
1752
 
1600
- // Spawn a new agent on the CURRENT connection (local same-origin, or the
1601
- // connected remote fleet) and select it once it registers. Shared by the
1602
- // launch-URL flow and the "+ New agent" button. Returns false on a spawn
1603
- // error (the alert is already shown), true otherwise.
1604
- async function spawnAndSelect(spec) {
1753
+ // Pick the fleet to spawn on: an explicit roomId, else the selected agent's
1754
+ // source, else local, else any live source.
1755
+ function spawnTarget(roomId) {
1756
+ return (
1757
+ sources.get(roomId) ||
1758
+ srcFor(entries.find((e) => e._key === sel)) ||
1759
+ sources.get(LOCAL) ||
1760
+ [...sources.values()].find((s) => s.live) ||
1761
+ [...sources.values()][0] ||
1762
+ null
1763
+ );
1764
+ }
1765
+
1766
+ // Spawn a new agent on the chosen fleet and select it once it registers.
1767
+ // Shared by the launch-URL flow and the "+ New agent" button. Returns false
1768
+ // on a spawn error (the alert is already shown), true otherwise.
1769
+ async function spawnAndSelect(spec, roomId) {
1605
1770
  await loadList();
1606
- // Match by "newest agent that wasn't here before" — the spawn returns the
1607
- // wrapper pid, but the agent registers under the runtime's own pid.
1608
- const before = new Set(entries.map((e) => e.pid));
1609
- const res = await Conn.post("/api/spawn", {
1771
+ const target = spawnTarget(roomId);
1772
+ if (!target) {
1773
+ alert("no fleet to launch on connect a room or run `ay serve` locally");
1774
+ return false;
1775
+ }
1776
+ // Match by "newest agent that wasn't here before" ON THIS fleet — the
1777
+ // spawn returns the wrapper pid, but the agent registers under its own.
1778
+ const before = new Set(entries.filter((e) => e._room === target.id).map((e) => e.pid));
1779
+ const res = await target.tx.post("/api/spawn", {
1610
1780
  cli: spec.cli || "claude",
1611
1781
  cwd: spec.cwd || undefined,
1612
1782
  prompt: spec.prompt || undefined,
@@ -1618,10 +1788,10 @@
1618
1788
  for (let i = 0; i < 14; i++) {
1619
1789
  await loadList();
1620
1790
  const fresh = entries
1621
- .filter((e) => !before.has(e.pid))
1791
+ .filter((e) => e._room === target.id && !before.has(e.pid))
1622
1792
  .sort((a, b) => (b.started_at || 0) - (a.started_at || 0));
1623
1793
  if (fresh.length) {
1624
- select(fresh[0].pid);
1794
+ select(fresh[0]._key);
1625
1795
  return true;
1626
1796
  }
1627
1797
  await new Promise((r) => setTimeout(r, 800));
@@ -1634,7 +1804,7 @@
1634
1804
  if (!r) return;
1635
1805
  $("launch").style.display = "none";
1636
1806
  await connectRoom(room, r.token, r.host);
1637
- await spawnAndSelect(spec);
1807
+ await spawnAndSelect(spec, room);
1638
1808
  }
1639
1809
 
1640
1810
  $("launch").addEventListener("click", (ev) => {
@@ -1651,9 +1821,11 @@
1651
1821
  // when there is one, prompt optional) → POST /api/spawn on this connection.
1652
1822
  // Always allowed: the console already controls every running agent's stdin.
1653
1823
  function showNew() {
1654
- const here = entries.find((x) => String(x.pid) === sel);
1824
+ const here = entries.find((x) => x._key === sel);
1655
1825
  const cwd = here?.cwd || "";
1656
- const where = Conn.rtc ? curRoom || "remote fleet" : "local";
1826
+ const target = spawnTarget();
1827
+ const where = target ? (target.id === LOCAL ? "local" : target.id) : "local";
1828
+ $("newform").dataset.room = target ? target.id : "";
1657
1829
  $("newform").innerHTML = `<div class="lcard">
1658
1830
  <div class="ltitle">New agent · ${esc(where)}</div>
1659
1831
  <div class="nfield"><label>CLI</label><input id="nf-cli" value="claude" spellcheck="false" autocapitalize="off" /></div>
@@ -1678,7 +1850,7 @@
1678
1850
  };
1679
1851
  go.disabled = true;
1680
1852
  go.textContent = "launching…";
1681
- const ok = await spawnAndSelect(spec);
1853
+ const ok = await spawnAndSelect(spec, $("newform").dataset.room || undefined);
1682
1854
  if (ok) {
1683
1855
  $("newform").style.display = "none";
1684
1856
  } else {
@@ -1704,6 +1876,7 @@
1704
1876
  // boot: a launch URL opens the launcher; otherwise connect from the hash (then
1705
1877
  // eat the token); a bare #room reconnects from the cached token; else local.
1706
1878
  async function boot() {
1879
+ ensureLocalSource();
1707
1880
  const raw = location.hash.replace(/^#/, "");
1708
1881
  if (raw.startsWith("launch=")) {
1709
1882
  let spec = null;
@@ -1712,8 +1885,8 @@
1712
1885
  } catch {}
1713
1886
  history.replaceState(null, document.title, location.pathname + location.search); // eat launch params
1714
1887
  if (spec) showLaunch(spec);
1715
- setConn("● local", "var(--muted)");
1716
1888
  startPolling();
1889
+ connectAllRooms();
1717
1890
  return;
1718
1891
  }
1719
1892
  // #k=<token> — local-mode auth from `ay serve --http`'s printed link.
@@ -1724,8 +1897,9 @@
1724
1897
  } catch {}
1725
1898
  // SECURITY: strip the token from the URL immediately.
1726
1899
  history.replaceState(null, document.title, location.pathname + location.search);
1727
- setConn("● local", "var(--muted)");
1900
+ ensureLocalSource();
1728
1901
  startPolling();
1902
+ connectAllRooms();
1729
1903
  return;
1730
1904
  }
1731
1905
  const h = decodeURIComponent(raw);
@@ -1759,24 +1933,13 @@
1759
1933
  } else if (bare && loadRooms()[bare[1]]) {
1760
1934
  const r = loadRooms()[bare[1]];
1761
1935
  pending = { room: bare[1], token: r.token, host: r.host };
1762
- } else if (!raw) {
1763
- // No hash → reconnect to the last-used room (or the most recent saved
1764
- // one), so opening agent-yes.com brings back your list automatically.
1765
- const rooms = loadRooms();
1766
- const names = Object.keys(rooms);
1767
- if (names.length) {
1768
- const last = localStorage.getItem("ay.lastRoom");
1769
- const pick =
1770
- last && rooms[last] ? last : names.sort((a, b) => rooms[b].ts - rooms[a].ts)[0];
1771
- pending = { room: pick, token: rooms[pick].token, host: rooms[pick].host };
1772
- }
1773
1936
  }
1774
- // Render the UI immediately and refresh on a timer; connect to a room (if
1775
- // any) in the BACKGROUND so a dead/slow cached room never blanks the page.
1776
- if (!pending) setConn("● local", "var(--muted)");
1777
- else wantRemote = true; // remote room in play — loadList waits for the tunnel
1937
+ // Render the UI immediately and refresh on a timer; connect to every
1938
+ // saved room in the BACKGROUND so a dead/slow room never blanks the page,
1939
+ // and they all stream their agents into one list at once.
1778
1940
  startPolling();
1779
1941
  if (pending) connectRoom(pending.room, pending.token, pending.host);
1942
+ connectAllRooms();
1780
1943
  }
1781
1944
 
1782
1945
  // ---- activity-gated polling + auto-reload on new deploy ----------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.108.1",
3
+ "version": "1.109.0",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "ai",
@@ -1,8 +0,0 @@
1
- import "./ts-DQiKBC0f.js";
2
- import "./logger-B9h0djqx.js";
3
- import "./versionChecker-BQULRcxP.js";
4
- import "./pidStore-DBjlqzo8.js";
5
- import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CcreSTgo.js";
7
-
8
- export { SUPPORTED_CLIS };