agent-yes 1.115.1 → 1.117.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-B4lhxCQx.js";
2
+ import "./logger-B9h0djqx.js";
3
+ import "./versionChecker-CS7qsffQ.js";
4
+ import "./pidStore-DBjlqzo8.js";
5
+ import "./globalPidIndex-yVd3mbsV.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DQYx5cvl.js";
7
+
8
+ export { SUPPORTED_CLIS };
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-8yOJK-_k.js";
1
+ import { t as CLIS_CONFIG } from "./ts-B4lhxCQx.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-Co1JpoEr.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-DQYx5cvl.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-Coc3Y6Q5.js";
3
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-CS7qsffQ.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-BpU68RGV.js");
485
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-BuLieGot.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-DF0iApn5.js");
518
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-DK9PO6Y6.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-8yOJK-_k.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-B4lhxCQx.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-Coc3Y6Q5.js";
3
+ import "./versionChecker-CS7qsffQ.js";
4
4
  import "./pidStore-DBjlqzo8.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
6
 
@@ -1,11 +1,11 @@
1
- import "./ts-8yOJK-_k.js";
1
+ import "./ts-B4lhxCQx.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-Coc3Y6Q5.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-CS7qsffQ.js";
4
4
  import "./pidStore-DBjlqzo8.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Co1JpoEr.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DQYx5cvl.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-b3HW9zZo.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-B_JJRHkV.js";
9
9
  import yargs from "yargs";
10
10
  import { mkdir, open, readFile, writeFile } from "fs/promises";
11
11
  import { homedir, hostname, userInfo } from "os";
@@ -51,7 +51,84 @@ const defaultOpts = (overrides = {}) => ({
51
51
  cwdScope: null,
52
52
  ...overrides
53
53
  });
54
+ const SESSION_PIN_ENV = new Set([
55
+ "CLAUDECODE",
56
+ "CLAUDE_CODE_SSE_PORT",
57
+ "CLAUDE_CODE_SESSION_ID",
58
+ "CLAUDE_CODE_CHILD_SESSION",
59
+ "CLAUDE_CODE_ENTRYPOINT"
60
+ ]);
61
+ function freshAgentEnv() {
62
+ const env = {};
63
+ for (const [k, v] of Object.entries(process.env)) {
64
+ if (v === void 0 || SESSION_PIN_ENV.has(k)) continue;
65
+ env[k] = v;
66
+ }
67
+ return env;
68
+ }
54
69
  const DAEMON_NAME = "agent-yes";
70
+ async function ensureBootAutostart(oxmgrBin) {
71
+ try {
72
+ return await Bun.spawn([
73
+ oxmgrBin,
74
+ "service",
75
+ "install"
76
+ ], { stdio: [
77
+ "ignore",
78
+ "ignore",
79
+ "ignore"
80
+ ] }).exited === 0;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+ async function spawnExit(cmd) {
86
+ try {
87
+ return await Bun.spawn(cmd, { stdio: [
88
+ "ignore",
89
+ "ignore",
90
+ "ignore"
91
+ ] }).exited ?? 1;
92
+ } catch {
93
+ return 1;
94
+ }
95
+ }
96
+ async function readDaemonServeArgs(oxmgrBin) {
97
+ try {
98
+ const p = Bun.spawn([
99
+ oxmgrBin,
100
+ "status",
101
+ DAEMON_NAME
102
+ ], {
103
+ stdout: "pipe",
104
+ stderr: "ignore"
105
+ });
106
+ const out = await new Response(p.stdout).text();
107
+ if (await p.exited !== 0) return null;
108
+ const m = /Command:\s*(.+)/.exec(out);
109
+ if (!m) return null;
110
+ const after = /\bserve\b\s*(.*)$/.exec(m[1].trim());
111
+ return after ? after[1].split(/\s+/).filter(Boolean) : [];
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+ function portFromArgs(args) {
117
+ const m = /--port[=\s](\d+)/.exec(args.join(" "));
118
+ return m ? Number(m[1]) : DEFAULT_PORT;
119
+ }
120
+ async function fetchDaemonVersion(port, token) {
121
+ try {
122
+ const r = await fetch(`http://127.0.0.1:${port}/api/version`, {
123
+ headers: { Authorization: `Bearer ${token}` },
124
+ signal: AbortSignal.timeout(3e3)
125
+ });
126
+ if (!r.ok) return null;
127
+ return (await r.json()).version ?? null;
128
+ } catch {
129
+ return null;
130
+ }
131
+ }
55
132
  async function cmdServeDaemon(sub, args) {
56
133
  const oxmgrBin = Bun.which("oxmgr");
57
134
  if (!oxmgrBin) {
@@ -60,11 +137,33 @@ async function cmdServeDaemon(sub, args) {
60
137
  }
61
138
  if (sub === "install") {
62
139
  const token = await loadOrCreateToken(void 0);
140
+ const priorArgs = await readDaemonServeArgs(oxmgrBin);
141
+ const effArgs = args.length ? args : priorArgs ?? [];
142
+ const current = getInstalledPackage().version;
143
+ if (priorArgs !== null) {
144
+ const runningVer = await fetchDaemonVersion(portFromArgs(effArgs), token);
145
+ if (runningVer === current) {
146
+ await ensureBootAutostart(oxmgrBin);
147
+ process.stdout.write(`'${DAEMON_NAME}' already running v${current} (up to date)\n`);
148
+ return 0;
149
+ }
150
+ process.stdout.write(`rolling '${DAEMON_NAME}' ${runningVer ? `v${runningVer}` : "(unknown)"} → v${current}…\n`);
151
+ await spawnExit([
152
+ oxmgrBin,
153
+ "stop",
154
+ DAEMON_NAME
155
+ ]);
156
+ await spawnExit([
157
+ oxmgrBin,
158
+ "delete",
159
+ DAEMON_NAME
160
+ ]);
161
+ }
63
162
  const ayBin = Bun.which("ay");
64
163
  const serveCmd = [
65
164
  ...ayBin ? [process.execPath, ayBin] : ["ay"],
66
165
  "serve",
67
- ...args
166
+ ...effArgs
68
167
  ].join(" ");
69
168
  const code = await Bun.spawn([
70
169
  oxmgrBin,
@@ -80,11 +179,12 @@ async function cmdServeDaemon(sub, args) {
80
179
  "inherit"
81
180
  ] }).exited;
82
181
  if (code === 0) {
83
- const portM = /--port[=\s](\d+)/.exec(args.join(" "));
84
- const port = portM ? Number(portM[1]) : DEFAULT_PORT;
85
- const webrtcish = args.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"));
86
- const httpish = args.some((a) => a.startsWith("--http") || a.startsWith("--share")) || !args.some((a) => a.startsWith("--webrtc"));
87
- process.stdout.write(`\ninstalled '${DAEMON_NAME}' as a daemon via oxmgr\n`);
182
+ const onBoot = await ensureBootAutostart(oxmgrBin);
183
+ const port = portFromArgs(effArgs);
184
+ const webrtcish = effArgs.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"));
185
+ const httpish = effArgs.some((a) => a.startsWith("--http") || a.startsWith("--share")) || !effArgs.some((a) => a.startsWith("--webrtc"));
186
+ process.stdout.write(`\n${priorArgs !== null ? `rolled '${DAEMON_NAME}' forward to` : `installed '${DAEMON_NAME}' as a daemon via oxmgr —`} v${current}\n`);
187
+ process.stdout.write(onBoot ? `start-on-boot: enabled (oxmgr registered with the system init)\n` : `start-on-boot: not registered — run \`oxmgr service install\` to enable\n`);
88
188
  process.stdout.write(`token: ${token}\n\n`);
89
189
  if (httpish) {
90
190
  process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
@@ -372,6 +472,7 @@ Options:
372
472
  const host = hostname();
373
473
  return Response.json({ host: user ? `${user}@${host}` : host });
374
474
  }
475
+ if (req.method === "GET" && p === "/api/version") return Response.json({ version: getInstalledPackage().version });
375
476
  if (req.method === "GET" && p === "/api/notes") {
376
477
  const notes = await readNotes();
377
478
  return Response.json(Object.fromEntries(notes));
@@ -583,6 +684,7 @@ Options:
583
684
  ...prompt ? ["--", prompt] : []
584
685
  ], {
585
686
  cwd,
687
+ env: freshAgentEnv(),
586
688
  stdin: "ignore",
587
689
  stdout: "ignore",
588
690
  stderr: "ignore"
@@ -687,4 +789,4 @@ Options:
687
789
 
688
790
  //#endregion
689
791
  export { cmdServe };
690
- //# sourceMappingURL=serve-D7Hj5j6W.js.map
792
+ //# sourceMappingURL=serve-DJthZQAN.js.map
@@ -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-D7Hj5j6W.js");
166
+ const { cmdServe } = await import("./serve-DJthZQAN.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-b3HW9zZo.js.map
1598
+ //# sourceMappingURL=subcommands-B_JJRHkV.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-b3HW9zZo.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-B_JJRHkV.js";
5
5
 
6
6
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
2
- import { r as getInstalledPackage } from "./versionChecker-Coc3Y6Q5.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-CS7qsffQ.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";
@@ -1713,4 +1713,4 @@ function sleep(ms) {
1713
1713
 
1714
1714
  //#endregion
1715
1715
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1716
- //# sourceMappingURL=ts-8yOJK-_k.js.map
1716
+ //# sourceMappingURL=ts-B4lhxCQx.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.115.1";
10
+ var version = "1.117.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-Coc3Y6Q5.js.map
224
+ //# sourceMappingURL=versionChecker-CS7qsffQ.js.map
package/lab/ui/index.html CHANGED
@@ -1545,6 +1545,15 @@
1545
1545
  try {
1546
1546
  localStorage.setItem("ay.sel", sel);
1547
1547
  } catch {}
1548
+ // Reflect it in the URL hash as #room:pid. The fragment is never sent in
1549
+ // any request, so — unlike the old ?pid= query that landed in the server's
1550
+ // access logs — the agent id stays client-side, while still surviving a
1551
+ // refresh and being copy-pasteable as a deep link.
1552
+ try {
1553
+ const want = "#" + e._room + ":" + e.pid;
1554
+ if (location.hash !== want)
1555
+ history.replaceState(null, document.title, location.pathname + want);
1556
+ } catch {}
1548
1557
  // pid + tx are how we talk to the agent's own host; sel (composite) is
1549
1558
  // only for UI identity/highlight, since pids can collide across rooms.
1550
1559
  const pid = e.pid;
@@ -1618,10 +1627,11 @@
1618
1627
  });
1619
1628
  // Adapt: drive the agent's PTY to the browser terminal size (POST
1620
1629
  // /api/resize → winsize + SIGWINCH) so its TUI reflows to match what we
1621
- // render. Suppressed while we're merely adopting the agent's OWN size.
1622
- let adoptingAgentSize = false;
1630
+ // render. Suppressed during the initial fit below, which does its own
1631
+ // one-shot "push only if the agent is out of sync" check.
1632
+ let suppressPush = true;
1623
1633
  const pushSize = () => {
1624
- if (term && sel === e._key && !adoptingAgentSize)
1634
+ if (term && sel === e._key && !suppressPush)
1625
1635
  tx.post("/api/resize/" + encodeURIComponent(pid), {
1626
1636
  cols: term.cols,
1627
1637
  rows: term.rows,
@@ -1642,28 +1652,32 @@
1642
1652
  };
1643
1653
  term.onData(fwd);
1644
1654
  term.onBinary(fwd);
1645
- // Render the existing buffer at the AGENT's current width first so its
1646
- // wrapping is correct, instead of forcing our viewport width onto stale
1647
- // content. The user adapts to the window by resizing it (fit push).
1655
+ // On switch, render at OUR pane size and make the agent match it: fit
1656
+ // xterm to the viewport, then if the agent's PTY winsize differs from
1657
+ // what we're now rendering push a resize so its TUI reflows to fill the
1658
+ // pane. The agent may have been sized by a different viewer/terminal, or
1659
+ // our window changed while this agent was in the background; either way a
1660
+ // mismatch leaves the TUI clipped or boxed-in until something nudges it.
1661
+ // Only push when it actually differs, so an already-matching agent isn't
1662
+ // poked with a redundant SIGWINCH. After this, live window resizes push
1663
+ // automatically (suppressPush flips off).
1648
1664
  const selKey = e._key;
1665
+ const fitAndSync = (sz) => {
1666
+ if (sel !== selKey || !term) return;
1667
+ try {
1668
+ fit.fit();
1669
+ } catch {}
1670
+ if (!sz || sz.cols !== term.cols || sz.rows !== term.rows) {
1671
+ tx.post("/api/resize/" + encodeURIComponent(pid), {
1672
+ cols: term.cols,
1673
+ rows: term.rows,
1674
+ }).catch(() => {});
1675
+ }
1676
+ suppressPush = false;
1677
+ };
1649
1678
  tx.fetchJSON("/api/size/" + encodeURIComponent(pid))
1650
- .then((sz) => {
1651
- if (sel !== selKey || !term) return;
1652
- if (sz && sz.cols && sz.rows) {
1653
- adoptingAgentSize = true;
1654
- term.resize(sz.cols, sz.rows);
1655
- adoptingAgentSize = false;
1656
- } else {
1657
- try {
1658
- fit.fit();
1659
- } catch {}
1660
- }
1661
- })
1662
- .catch(() => {
1663
- try {
1664
- fit.fit();
1665
- } catch {}
1666
- });
1679
+ .then(fitAndSync)
1680
+ .catch(() => fitAndSync(null));
1667
1681
 
1668
1682
  // True live tail via ay serve's SSE stream. First event is an xterm-rendered
1669
1683
  // tail snapshot; later events are incremental deltas. We normalise terminal
@@ -2251,7 +2265,23 @@
2251
2265
  }
2252
2266
  const full = pending ? null : /^([A-Za-z0-9_-]+):([^@]+)(?:@(.+))?$/.exec(h);
2253
2267
  const bare = /^([A-Za-z0-9_-]+)$/.exec(h);
2254
- if (full) {
2268
+ // Treat the colon form as #room:agentId only when there's no @host, the id
2269
+ // is a bare pid (digits), AND we can actually reconnect that room (it's
2270
+ // cached or local). Otherwise the second part must be the token we need to
2271
+ // connect — so a fresh browser still honours #room:token even when the token
2272
+ // is custom and happens to be all digits.
2273
+ // …and bounded to a plausible pid width (≤7 digits) so a long numeric
2274
+ // custom token can't be mistaken for an agent id. A returning user's room
2275
+ // token is persisted, so even a short numeric token reconnects fine from
2276
+ // cache; only the share-link form needs the token, which stays uneaten.
2277
+ const aidLike =
2278
+ full &&
2279
+ !full[3] &&
2280
+ /^\d{1,7}$/.test(full[2]) &&
2281
+ (full[1] === LOCAL || !!loadRooms()[full[1]]);
2282
+ if (full && !aidLike) {
2283
+ // #room:token — a share link. Connect, then eat the token so only #room
2284
+ // lingers.
2255
2285
  const [, room, token, host] = full;
2256
2286
  // SECURITY: strip the token from the URL immediately so it never lingers in
2257
2287
  // the omnibox, history, or a screenshot. Keep only the room mnemonic.
@@ -2261,6 +2291,16 @@
2261
2291
  location.pathname + location.search + "#" + room,
2262
2292
  );
2263
2293
  pending = { room, token, host };
2294
+ } else if (full) {
2295
+ // #room:agentId — a deep link to one agent in a (cached) room. The id is
2296
+ // not a secret, so it stays in the hash; reconnect the room from its
2297
+ // cached token and select the agent once it streams in. autoPid uses the
2298
+ // composite key so it picks the right host when pids collide across rooms.
2299
+ const [, room, aid] = full;
2300
+ autoPid = room + "#" + aid;
2301
+ autoPidExplicit = true;
2302
+ const r = loadRooms()[room];
2303
+ if (r) pending = { room, token: r.token, host: r.host };
2264
2304
  } else if (bare && loadRooms()[bare[1]]) {
2265
2305
  const r = loadRooms()[bare[1]];
2266
2306
  pending = { room: bare[1], token: r.token, host: r.host };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.115.1",
3
+ "version": "1.117.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",
package/ts/serve.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  type CommonOpts,
17
17
  } from "./subcommands.ts";
18
18
  import { SUPPORTED_CLIS } from "./SUPPORTED_CLIS.ts";
19
+ import { getInstalledPackage } from "./versionChecker.ts";
19
20
 
20
21
  const DEFAULT_PORT = 7432;
21
22
 
@@ -64,12 +65,104 @@ const defaultOpts = (overrides: Partial<CommonOpts> = {}): CommonOpts => ({
64
65
  ...overrides,
65
66
  });
66
67
 
68
+ // The vars that pin a process to a PARENT Claude Code session — NOT the many
69
+ // other CLAUDE_CODE_* settings that configure provider/auth/limits (USE_BEDROCK,
70
+ // USE_VERTEX, MAX_OUTPUT_TOKENS, …), which must pass through untouched.
71
+ const SESSION_PIN_ENV = new Set([
72
+ "CLAUDECODE",
73
+ "CLAUDE_CODE_SSE_PORT",
74
+ "CLAUDE_CODE_SESSION_ID",
75
+ "CLAUDE_CODE_CHILD_SESSION",
76
+ "CLAUDE_CODE_ENTRYPOINT",
77
+ ]);
78
+
79
+ // Env for a console-spawned agent, minus only the session-pinning vars above. If
80
+ // `ay serve` was launched from inside Claude Code (or any shell carrying these),
81
+ // it would otherwise leak the parent's SSE port / session id into every spawned
82
+ // agent — so the new `claude` thinks it's a nested child and tries to attach to a
83
+ // stale port, surfacing as "fail to connect". Dropping them makes each agent a
84
+ // clean top-level session; all config/provider env (CLAUDE_EFFORT, CLAUDE_CODE_*
85
+ // settings) is preserved.
86
+ function freshAgentEnv(): Record<string, string> {
87
+ const env: Record<string, string> = {};
88
+ for (const [k, v] of Object.entries(process.env)) {
89
+ if (v === undefined || SESSION_PIN_ENV.has(k)) continue;
90
+ env[k] = v;
91
+ }
92
+ return env;
93
+ }
94
+
67
95
  // ---------------------------------------------------------------------------
68
96
  // ay serve install / uninstall / logs (oxmgr daemon management)
69
97
  // ---------------------------------------------------------------------------
70
98
 
71
99
  const DAEMON_NAME = "agent-yes";
72
100
 
101
+ // Register the oxmgr daemon with the platform init system (launchd on macOS,
102
+ // systemd on Linux, Task Scheduler on Windows) so managed processes — including
103
+ // the agent-yes daemon — come back after a *reboot*, not just a crash. Idempotent:
104
+ // a no-op if the service is already installed. Best-effort: returns false on any
105
+ // failure (e.g. a system-level systemd unit that needs sudo) without aborting the
106
+ // install — the process is still managed, just not boot-persistent.
107
+ async function ensureBootAutostart(oxmgrBin: string): Promise<boolean> {
108
+ try {
109
+ // --system defaults to "auto" (launchd/systemd/Task Scheduler by platform);
110
+ // it's a `service`-level flag, so passing it after `install` is rejected.
111
+ const svc = Bun.spawn([oxmgrBin, "service", "install"], {
112
+ stdio: ["ignore", "ignore", "ignore"],
113
+ });
114
+ return (await svc.exited) === 0;
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
119
+
120
+ async function spawnExit(cmd: string[]): Promise<number> {
121
+ try {
122
+ return (await Bun.spawn(cmd, { stdio: ["ignore", "ignore", "ignore"] }).exited) ?? 1;
123
+ } catch {
124
+ return 1;
125
+ }
126
+ }
127
+
128
+ // The `serve` args the running daemon was started with, parsed out of oxmgr's
129
+ // stored command line (`… ay serve --share --port 7433`). null when no daemon is
130
+ // registered. Lets a bare `ay serve install` re-launch with the SAME args.
131
+ async function readDaemonServeArgs(oxmgrBin: string): Promise<string[] | null> {
132
+ try {
133
+ const p = Bun.spawn([oxmgrBin, "status", DAEMON_NAME], { stdout: "pipe", stderr: "ignore" });
134
+ const out = await new Response(p.stdout).text();
135
+ if ((await p.exited) !== 0) return null;
136
+ const m = /Command:\s*(.+)/.exec(out);
137
+ if (!m) return null;
138
+ const after = /\bserve\b\s*(.*)$/.exec(m[1]!.trim());
139
+ return after ? after[1]!.split(/\s+/).filter(Boolean) : [];
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+
145
+ function portFromArgs(args: string[]): number {
146
+ const m = /--port[=\s](\d+)/.exec(args.join(" "));
147
+ return m ? Number(m[1]) : DEFAULT_PORT;
148
+ }
149
+
150
+ // Ask the live daemon its version over the local HTTP API. null if it's not
151
+ // listening (webrtc-only) or too old to expose /api/version — both of which we
152
+ // treat as "outdated" so a re-install rolls it forward.
153
+ async function fetchDaemonVersion(port: number, token: string): Promise<string | null> {
154
+ try {
155
+ const r = await fetch(`http://127.0.0.1:${port}/api/version`, {
156
+ headers: { Authorization: `Bearer ${token}` },
157
+ signal: AbortSignal.timeout(3000),
158
+ });
159
+ if (!r.ok) return null;
160
+ return ((await r.json()) as { version?: string }).version ?? null;
161
+ } catch {
162
+ return null;
163
+ }
164
+ }
165
+
73
166
  async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
74
167
  const oxmgrBin = Bun.which("oxmgr");
75
168
  if (!oxmgrBin) {
@@ -83,25 +176,61 @@ async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
83
176
 
84
177
  if (sub === "install") {
85
178
  const token = await loadOrCreateToken(undefined);
179
+
180
+ // Re-running install rolls a stale daemon forward: reuse the args it was
181
+ // started with (so a bare `ay serve install` stays "the same daemon"), unless
182
+ // new args are given. The persisted room + token mean the share link is
183
+ // unchanged across the restart.
184
+ const priorArgs = await readDaemonServeArgs(oxmgrBin);
185
+ const effArgs = args.length ? args : (priorArgs ?? []);
186
+ const current = getInstalledPackage().version;
187
+
188
+ if (priorArgs !== null) {
189
+ // A daemon already exists — only disturb it if it's actually outdated.
190
+ const runningVer = await fetchDaemonVersion(portFromArgs(effArgs), token);
191
+ if (runningVer === current) {
192
+ await ensureBootAutostart(oxmgrBin);
193
+ process.stdout.write(`'${DAEMON_NAME}' already running v${current} (up to date)\n`);
194
+ return 0;
195
+ }
196
+ // Outdated (or unreachable/too-old to report) → graceful roll-forward.
197
+ // `oxmgr stop` sends SIGTERM, which cmdServe handles cleanly (closing share
198
+ // peers so browsers reconnect fast), then we re-create with the new binary.
199
+ process.stdout.write(
200
+ `rolling '${DAEMON_NAME}' ${runningVer ? `v${runningVer}` : "(unknown)"} → v${current}…\n`,
201
+ );
202
+ await spawnExit([oxmgrBin, "stop", DAEMON_NAME]);
203
+ await spawnExit([oxmgrBin, "delete", DAEMON_NAME]);
204
+ }
205
+
86
206
  // Build the ay serve command with forwarded args (port, host, --webrtc, etc.).
87
207
  // Absolute paths: oxmgr's daemon environment may not have ~/.bun/bin in
88
208
  // PATH, so a bare `ay` (or its `#!/usr/bin/env bun` shebang) fails to spawn.
89
209
  const ayBin = Bun.which("ay");
90
- const serveCmd = [...(ayBin ? [process.execPath, ayBin] : ["ay"]), "serve", ...args].join(" ");
210
+ const serveCmd = [...(ayBin ? [process.execPath, ayBin] : ["ay"]), "serve", ...effArgs].join(
211
+ " ",
212
+ );
91
213
  const proc = Bun.spawn(
92
214
  [oxmgrBin, "start", serveCmd, "--name", DAEMON_NAME, "--restart", "always"],
93
215
  { stdio: ["ignore", "inherit", "inherit"] },
94
216
  );
95
217
  const code = await proc.exited;
96
218
  if (code === 0) {
97
- const portM = /--port[=\s](\d+)/.exec(args.join(" "));
98
- const port = portM ? Number(portM[1]) : DEFAULT_PORT;
219
+ const onBoot = await ensureBootAutostart(oxmgrBin);
220
+ const port = portFromArgs(effArgs);
99
221
  // Mirror cmdServe's mode resolution: webrtc-only daemons open no HTTP port.
100
- const webrtcish = args.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"));
222
+ const webrtcish = effArgs.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"));
101
223
  const httpish =
102
- args.some((a) => a.startsWith("--http") || a.startsWith("--share")) ||
103
- !args.some((a) => a.startsWith("--webrtc"));
104
- process.stdout.write(`\ninstalled '${DAEMON_NAME}' as a daemon via oxmgr\n`);
224
+ effArgs.some((a) => a.startsWith("--http") || a.startsWith("--share")) ||
225
+ !effArgs.some((a) => a.startsWith("--webrtc"));
226
+ process.stdout.write(
227
+ `\n${priorArgs !== null ? `rolled '${DAEMON_NAME}' forward to` : `installed '${DAEMON_NAME}' as a daemon via oxmgr —`} v${current}\n`,
228
+ );
229
+ process.stdout.write(
230
+ onBoot
231
+ ? `start-on-boot: enabled (oxmgr registered with the system init)\n`
232
+ : `start-on-boot: not registered — run \`oxmgr service install\` to enable\n`,
233
+ );
105
234
  process.stdout.write(`token: ${token}\n\n`);
106
235
  if (httpish) {
107
236
  process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
@@ -486,6 +615,13 @@ export async function cmdServe(rest: string[]): Promise<number> {
486
615
  return Response.json({ host: user ? `${user}@${host}` : host });
487
616
  }
488
617
 
618
+ // GET /api/version — the running daemon's package version, so a re-run of
619
+ // `ay serve install` can tell whether the live daemon is stale and roll it
620
+ // forward. A daemon too old to expose this just 404s → treated as outdated.
621
+ if (req.method === "GET" && p === "/api/version") {
622
+ return Response.json({ version: getInstalledPackage().version });
623
+ }
624
+
489
625
  // GET /api/notes
490
626
  if (req.method === "GET" && p === "/api/notes") {
491
627
  const notes = await readNotes();
@@ -751,6 +887,7 @@ export async function cmdServe(rest: string[]): Promise<number> {
751
887
  try {
752
888
  const child = Bun.spawn(["ay", cli, ...(prompt ? ["--", prompt] : [])], {
753
889
  cwd,
890
+ env: freshAgentEnv(), // don't leak our Claude Code session into the agent
754
891
  stdin: "ignore",
755
892
  stdout: "ignore",
756
893
  stderr: "ignore",
@@ -1,8 +0,0 @@
1
- import "./ts-8yOJK-_k.js";
2
- import "./logger-B9h0djqx.js";
3
- import "./versionChecker-Coc3Y6Q5.js";
4
- import "./pidStore-DBjlqzo8.js";
5
- import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Co1JpoEr.js";
7
-
8
- export { SUPPORTED_CLIS };