agent-yes 1.241.0 → 1.242.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.
@@ -1,11 +1,11 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-BzfCavve.js";
3
- import "./ts-CyUQ58Ht.js";
2
+ import "./versionChecker-BZYWL6Lx.js";
3
+ import "./ts-j4WgXrcI.js";
4
4
  import "./todoAutomation-Buo457VO.js";
5
5
  import "./JsonlStore-CPGIR0Wy.js";
6
6
  import "./globalPidIndex-D9DOSd-c.js";
7
7
  import "./pidStore-6EZd5DUv.js";
8
8
  import "./messageLog-C-YIzhRj.js";
9
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-BXTRXVhU.js";
9
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-xa0nxpMJ.js";
10
10
 
11
11
  export { SUPPORTED_CLIS };
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-CyUQ58Ht.js";
1
+ import { t as CLIS_CONFIG } from "./ts-j4WgXrcI.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-BXTRXVhU.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-xa0nxpMJ.js.map
@@ -3,7 +3,7 @@ import "./globalPidIndex-D9DOSd-c.js";
3
3
  import "./messageLog-C-YIzhRj.js";
4
4
  import "./e2e-CEIdJGZ7.js";
5
5
  import "./configShared-9wVlbPyC.js";
6
- import { P as resolveOne } from "./subcommands-BPTVjPEs.js";
6
+ import { P as resolveOne } from "./subcommands-f56kovUW.js";
7
7
  import "./webrtcLink-BMYneHbu.js";
8
8
  import "./remotes-BxQnNxQv.js";
9
9
  import { a as startShare } from "./share-BGl3wutJ.js";
@@ -229,4 +229,4 @@ function transformEvent(rawEvent, agentId, forwarded) {
229
229
 
230
230
  //#endregion
231
231
  export { createScopedShare, listShares, revokeAllShares, revokeShare };
232
- //# sourceMappingURL=agentShare-B7pv4n5U.js.map
232
+ //# sourceMappingURL=agentShare-DYsbDchq.js.map
@@ -1,5 +1,5 @@
1
1
  import { t as agentYesHome } from "./agentYesHome-CtmsY6I-.js";
2
- import { P as resolveOne } from "./subcommands-BPTVjPEs.js";
2
+ import { P as resolveOne } from "./subcommands-f56kovUW.js";
3
3
  import { a as mintCapability, o as parseExpires } from "./callbackCore-Dd28_cBM.js";
4
4
  import { randomBytes } from "node:crypto";
5
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
@@ -72,7 +72,7 @@ async function resolveBase(flag) {
72
72
  if (flag) return flag;
73
73
  if (process.env.AGENT_YES_CALLBACK_BASE) return process.env.AGENT_YES_CALLBACK_BASE;
74
74
  try {
75
- const { resolveLocalServeUrl } = await import("./serve-DpzVMaXc.js");
75
+ const { resolveLocalServeUrl } = await import("./serve-D4bcgVCC.js");
76
76
  const url = await resolveLocalServeUrl();
77
77
  if (url) return url;
78
78
  } catch {}
@@ -187,4 +187,4 @@ async function cmdCallback(rest) {
187
187
 
188
188
  //#endregion
189
189
  export { loadOrCreateCallbackSecret as i, isCallbackRevoked as n, loadCallbackSecretReadOnly as r, cmdCallback as t };
190
- //# sourceMappingURL=callback-KOcQklM_.js.map
190
+ //# sourceMappingURL=callback-CBj0KfiA.js.map
@@ -3,10 +3,10 @@ import "./globalPidIndex-D9DOSd-c.js";
3
3
  import "./messageLog-C-YIzhRj.js";
4
4
  import "./e2e-CEIdJGZ7.js";
5
5
  import "./configShared-9wVlbPyC.js";
6
- import "./subcommands-BPTVjPEs.js";
6
+ import "./subcommands-f56kovUW.js";
7
7
  import "./webrtcLink-BMYneHbu.js";
8
8
  import "./remotes-BxQnNxQv.js";
9
9
  import "./callbackCore-Dd28_cBM.js";
10
- import { i as loadOrCreateCallbackSecret, n as isCallbackRevoked, r as loadCallbackSecretReadOnly, t as cmdCallback } from "./callback-KOcQklM_.js";
10
+ import { i as loadOrCreateCallbackSecret, n as isCallbackRevoked, r as loadCallbackSecretReadOnly, t as cmdCallback } from "./callback-CBj0KfiA.js";
11
11
 
12
12
  export { cmdCallback };
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env bun
2
2
  import { t as invokedCliName } from "./invokedCli-uqM2YYA7.js";
3
3
  import { n as logger } from "./logger-CDIsZ-Pp.js";
4
- import { i as versionString, n as displayVersion, t as checkAndAutoUpdate } from "./versionChecker-BzfCavve.js";
5
- import { n as getRustBinary } from "./rustBinary-CFpO-1Si.js";
4
+ import { i as versionString, n as displayVersion, t as checkAndAutoUpdate } from "./versionChecker-BZYWL6Lx.js";
5
+ import { n as getRustBinary } from "./rustBinary-ZvpTl8MZ.js";
6
6
  import { argv } from "process";
7
7
  import { spawn } from "child_process";
8
8
  import ms from "ms";
@@ -285,7 +285,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
285
285
  const rawArg = process.argv[2];
286
286
  const managerCommands = !invokedCliName(process.argv);
287
287
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
288
- const { isSubcommand, runSubcommand, cmdHelp, isUnknownManagerToken } = await import("./subcommands-BHQFZh43.js");
288
+ const { isSubcommand, runSubcommand, cmdHelp, isUnknownManagerToken } = await import("./subcommands-CLdWqycU.js");
289
289
  if (isHelpFlag && process.argv.length === 3) {
290
290
  await cmdHelp(managerCommands);
291
291
  process.exit(0);
@@ -295,7 +295,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
295
295
  process.exit(code ?? 0);
296
296
  }
297
297
  {
298
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-D2o95DmW.js");
298
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-CFROHmym.js");
299
299
  if (isUnknownManagerToken(rawArg, managerCommands, SUPPORTED_CLIS)) {
300
300
  process.stderr.write(`ay: unknown subcommand or CLI '${rawArg}'.\n See 'ay help' for subcommands. To run an agent, name a CLI:\n 'ay <cli> …' (e.g. 'ay claude …') or 'ay --cli <cli> …'.\n`);
301
301
  process.exit(1);
@@ -346,7 +346,7 @@ if (config.useRust) {
346
346
  }
347
347
  }
348
348
  if (rustBinary) {
349
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-D2o95DmW.js");
349
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-CFROHmym.js");
350
350
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
351
351
  if (config.verbose) {
352
352
  console.log(`[rust] Using binary: ${rustBinary}`);
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-BzfCavve.js";
3
- import { a as hasTodoStore, c as AgentContext, i as answerAsk, n as agentYes, o as listAsks, r as config, s as listAsksForProject, t as CLIS_CONFIG } from "./ts-CyUQ58Ht.js";
2
+ import "./versionChecker-BZYWL6Lx.js";
3
+ import { a as hasTodoStore, c as AgentContext, i as answerAsk, n as agentYes, o as listAsks, r as config, s as listAsksForProject, t as CLIS_CONFIG } from "./ts-j4WgXrcI.js";
4
4
  import { _ as isKnownKind, a as renderTree, b as statesOf, c as monitorHint, d as openStore, f as DONE_STATE, g as initialState, h as canTransition, i as renderDigest, l as CycleError, m as ORPHANED_STATE, o as unblockedTasks, p as LIFECYCLES, r as openBlockers, s as describeBlock, t as reconcileTodos, u as TodoStore, v as nextStates, x as removeControlCharacters, y as requiredGate } from "./todoAutomation-Buo457VO.js";
5
5
  import "./JsonlStore-CPGIR0Wy.js";
6
6
  import "./globalPidIndex-D9DOSd-c.js";
@@ -3,7 +3,7 @@ import "./globalPidIndex-D9DOSd-c.js";
3
3
  import "./messageLog-C-YIzhRj.js";
4
4
  import "./e2e-CEIdJGZ7.js";
5
5
  import "./configShared-9wVlbPyC.js";
6
- import { $ as shouldStealLock, C as listRecords, J as hostId, K as appendEvent, Q as readInbox, X as listInboxParents, Z as liveWatchers, _ as isPidAlive, c as deriveLiveState, et as daemonLockDir, j as renderLogTailLines, nt as notifyDir, q as gcInboxes, tt as daemonLockOwnerPath } from "./subcommands-BPTVjPEs.js";
6
+ import { $ as shouldStealLock, C as listRecords, J as hostId, K as appendEvent, Q as readInbox, X as listInboxParents, Z as liveWatchers, _ as isPidAlive, c as deriveLiveState, et as daemonLockDir, j as renderLogTailLines, nt as notifyDir, q as gcInboxes, tt as daemonLockOwnerPath } from "./subcommands-f56kovUW.js";
7
7
  import "./webrtcLink-BMYneHbu.js";
8
8
  import "./remotes-BxQnNxQv.js";
9
9
  import { mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
@@ -589,4 +589,4 @@ async function ensureDaemon() {
589
589
 
590
590
  //#endregion
591
591
  export { daemonStatus, ensureDaemon, requestDaemonStop, runDaemon };
592
- //# sourceMappingURL=notifyDaemon-CeDkisCv.js.map
592
+ //# sourceMappingURL=notifyDaemon-DQHnX3a7.js.map
@@ -1,4 +1,4 @@
1
- import { r as getInstalledPackage } from "./versionChecker-BzfCavve.js";
1
+ import { r as getInstalledPackage } from "./versionChecker-BZYWL6Lx.js";
2
2
  import { execFileSync } from "child_process";
3
3
  import { existsSync, mkdirSync, unlinkSync } from "fs";
4
4
  import { chmod, copyFile } from "fs/promises";
@@ -225,4 +225,4 @@ async function getRustBinary(options = {}) {
225
225
 
226
226
  //#endregion
227
227
  export { getRustBinary as n, findSpawnHiddenLauncher as t };
228
- //# sourceMappingURL=rustBinary-CFpO-1Si.js.map
228
+ //# sourceMappingURL=rustBinary-ZvpTl8MZ.js.map
@@ -1,12 +1,12 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-BzfCavve.js";
3
- import "./ts-CyUQ58Ht.js";
2
+ import "./versionChecker-BZYWL6Lx.js";
3
+ import "./ts-j4WgXrcI.js";
4
4
  import "./todoAutomation-Buo457VO.js";
5
5
  import "./JsonlStore-CPGIR0Wy.js";
6
6
  import "./globalPidIndex-D9DOSd-c.js";
7
7
  import "./pidStore-6EZd5DUv.js";
8
8
  import "./messageLog-C-YIzhRj.js";
9
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-BXTRXVhU.js";
9
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-xa0nxpMJ.js";
10
10
  import { d as resolveSpawnCwd } from "./workspaceConfig-DOHygA2x.js";
11
11
  import { n as liveEnv, t as ensureNodeRuntime } from "./nodeRuntime-BtL6Oo1o.js";
12
12
  import { createHash } from "node:crypto";
@@ -153,4 +153,4 @@ async function cmdSchedule(rest) {
153
153
 
154
154
  //#endregion
155
155
  export { cmdSchedule };
156
- //# sourceMappingURL=schedule-DJJre7xc.js.map
156
+ //# sourceMappingURL=schedule-Dvrs07PE.js.map
@@ -1,7 +1,7 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import { r as getInstalledPackage } from "./versionChecker-BzfCavve.js";
3
- import { t as findSpawnHiddenLauncher } from "./rustBinary-CFpO-1Si.js";
4
- import { i as answerAsk, l as CLAUDE_SESSION_PIN_ENV, o as listAsks } from "./ts-CyUQ58Ht.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-BZYWL6Lx.js";
3
+ import { t as findSpawnHiddenLauncher } from "./rustBinary-ZvpTl8MZ.js";
4
+ import { i as answerAsk, l as CLAUDE_SESSION_PIN_ENV, o as listAsks } from "./ts-j4WgXrcI.js";
5
5
  import { t as agentYesHome$1 } from "./agentYesHome-CtmsY6I-.js";
6
6
  import { x as removeControlCharacters } from "./todoAutomation-Buo457VO.js";
7
7
  import "./JsonlStore-CPGIR0Wy.js";
@@ -11,17 +11,17 @@ import { r as recordInbox } from "./messageLog-C-YIzhRj.js";
11
11
  import { t as pgidForWrapper } from "./reaper-DP49VCKx.js";
12
12
  import "./e2e-CEIdJGZ7.js";
13
13
  import "./configShared-9wVlbPyC.js";
14
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-BXTRXVhU.js";
15
- import { A as recentReadEdges, C as listRecords, D as readNotes, G as writeToIpc, M as renderRawLog, N as renderRawLogLines, O as readPtysize, P as resolveOne, Y as isTransientLockMkdirError, f as extractNeedsInput, j as renderLogTailLines, k as recentMessageEdges, l as deriveLiveStatus, o as controlCodeFromName, p as extractTaskCounts, rt as TYPING_BADGE, u as extractBadges, x as isUserTyping, z as snapshotStatus } from "./subcommands-BPTVjPEs.js";
14
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-xa0nxpMJ.js";
15
+ import { A as recentReadEdges, C as listRecords, D as readNotes, G as writeToIpc, M as renderRawLog, N as renderRawLogLines, O as readPtysize, P as resolveOne, Y as isTransientLockMkdirError, f as extractNeedsInput, j as renderLogTailLines, k as recentMessageEdges, l as deriveLiveStatus, o as controlCodeFromName, p as extractTaskCounts, rt as TYPING_BADGE, u as extractBadges, x as isUserTyping, z as snapshotStatus } from "./subcommands-f56kovUW.js";
16
16
  import "./webrtcLink-BMYneHbu.js";
17
17
  import "./remotes-BxQnNxQv.js";
18
18
  import { i as frameVisitorMessage, s as verifyCapability, t as MAX_CALLBACK_MSG_BYTES } from "./callbackCore-Dd28_cBM.js";
19
- import { n as isCallbackRevoked, r as loadCallbackSecretReadOnly } from "./callback-KOcQklM_.js";
19
+ import { n as isCallbackRevoked, r as loadCallbackSecretReadOnly } from "./callback-CBj0KfiA.js";
20
20
  import { a as getSpawnHook, c as hasProvisionHook, d as resolveSpawnCwd, i as getProvisionRoot, l as hasSpawnHook, r as getProvisionHook, u as isProvisionAllowed } from "./workspaceConfig-DOHygA2x.js";
21
21
  import { n as liveEnv, t as ensureNodeRuntime } from "./nodeRuntime-BtL6Oo1o.js";
22
22
  import { r as spawnRejectionReason } from "./spawnGate-BxgS_GlF.js";
23
23
  import yargs from "yargs";
24
- import { appendFile, mkdir, open, readFile, rename, rm, stat, unlink, writeFile } from "fs/promises";
24
+ import { appendFile, mkdir, open, readFile, readdir, rename, rm, stat, unlink, writeFile } from "fs/promises";
25
25
  import { arch, cpus, freemem, homedir, hostname, loadavg, platform, totalmem, uptime, userInfo } from "os";
26
26
  import path from "path";
27
27
  import { execFileSync } from "node:child_process";
@@ -1720,6 +1720,46 @@ Options:
1720
1720
  };
1721
1721
  const presence = /* @__PURE__ */ new Map();
1722
1722
  const PRESENCE_TTL_MS = 12e3;
1723
+ const CAP_TTL_MS = 12e3;
1724
+ const daemonTag = `d${process.pid}`;
1725
+ const capsDir = (pid) => path.join(agentYesHome(), "caps", String(pid));
1726
+ const capFile = (pid, key) => path.join(capsDir(pid), key.replace(/[^\w.:-]/g, "_"));
1727
+ const publishCap = async (pid, viewer, cap, role) => {
1728
+ try {
1729
+ await mkdir(capsDir(pid), { recursive: true });
1730
+ await writeFile(capFile(pid, `${daemonTag}:${viewer}`), `${cap.cols} ${cap.rows} ${Date.now()} ${role}\n`);
1731
+ } catch {}
1732
+ };
1733
+ const withdrawCap = async (pid, viewer) => {
1734
+ await unlink(capFile(pid, `${daemonTag}:${viewer}`)).catch(() => {});
1735
+ };
1736
+ const readSharedCaps = async (pid) => {
1737
+ let files;
1738
+ try {
1739
+ files = await readdir(capsDir(pid));
1740
+ } catch {
1741
+ return [];
1742
+ }
1743
+ const now = Date.now();
1744
+ const caps = [];
1745
+ for (const f of files) {
1746
+ const fp = path.join(capsDir(pid), f);
1747
+ try {
1748
+ const parts = (await readFile(fp, "utf-8")).trim().split(/\s+/);
1749
+ const ts = Number(parts[2]);
1750
+ if (!Number.isFinite(ts) || now - ts > CAP_TTL_MS) {
1751
+ await unlink(fp).catch(() => {});
1752
+ continue;
1753
+ }
1754
+ const cap = sanitizeCap({
1755
+ cols: Number(parts[0]),
1756
+ rows: Number(parts[1])
1757
+ });
1758
+ if (cap) caps.push(cap);
1759
+ } catch {}
1760
+ }
1761
+ return caps;
1762
+ };
1723
1763
  const negoApplied = /* @__PURE__ */ new Map();
1724
1764
  const negoTimers = /* @__PURE__ */ new Map();
1725
1765
  const negoCapsGoneAt = /* @__PURE__ */ new Map();
@@ -1740,24 +1780,29 @@ Options:
1740
1780
  }
1741
1781
  };
1742
1782
  const winsizePathFor = (pid) => path.join(process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes"), "winsize", String(pid));
1743
- const liveCapsFor = (pid) => {
1744
- const now = Date.now();
1745
- const caps = [];
1746
- for (const v of presence.values()) if (now - v.ts <= PRESENCE_TTL_MS && String(v.agent) === String(pid) && v.cap) caps.push(v.cap);
1747
- return caps;
1748
- };
1749
1783
  const applyNego = async (pid) => {
1750
1784
  negoTimers.delete(pid);
1751
- const eff = negotiateSize(liveCapsFor(pid));
1785
+ const caps = await readSharedCaps(pid);
1786
+ const eff = negotiateSize(caps);
1752
1787
  const file = winsizePathFor(pid);
1753
1788
  const prev = negoApplied.get(pid);
1754
1789
  try {
1755
1790
  if (eff) {
1756
1791
  negoCapsGoneAt.delete(pid);
1757
1792
  if (prev && Math.abs(prev.cols - eff.cols) <= 1 && Math.abs(prev.rows - eff.rows) <= 1) return;
1793
+ try {
1794
+ const curr = (await readFile(file, "utf-8")).trim().split(/\s+/);
1795
+ if (Number(curr[0]) === eff.cols && Number(curr[1]) === eff.rows) {
1796
+ negoApplied.set(pid, {
1797
+ cols: eff.cols,
1798
+ rows: eff.rows,
1799
+ content: ""
1800
+ });
1801
+ return;
1802
+ }
1803
+ } catch {}
1758
1804
  const content = `${eff.cols} ${eff.rows} ${Date.now()}\n`;
1759
- const srcViewers = [...presence.values()].filter((v) => Date.now() - v.ts <= PRESENCE_TTL_MS && String(v.agent) === String(pid) && v.cap).map((v) => v.viewer).join(",");
1760
- process.stderr.write(`[api/resize] pid=${pid} ${eff.cols}x${eff.rows} src=presence-nego caps=${liveCapsFor(pid).length} viewers=${srcViewers || "-"}\n`);
1805
+ process.stderr.write(`[api/resize] pid=${pid} ${eff.cols}x${eff.rows} src=presence-nego daemon=${daemonTag} caps=${caps.length}\n`);
1761
1806
  await mkdir(path.dirname(file), { recursive: true });
1762
1807
  await writeFile(file, content);
1763
1808
  negoApplied.set(pid, {
@@ -2248,7 +2293,7 @@ Options:
2248
2293
  }
2249
2294
  });
2250
2295
  if (req.method === "GET" && p === "/api/ws") {
2251
- const ws = await import("./ws-G1j8No5N.js");
2296
+ const ws = await import("./ws-BvL0FY6E.js");
2252
2297
  try {
2253
2298
  await ws.loadProvision();
2254
2299
  } catch (e) {
@@ -2268,7 +2313,7 @@ Options:
2268
2313
  if (req.method === "GET" && p === "/api/ws/status") {
2269
2314
  const dirRaw = url.searchParams.get("path");
2270
2315
  if (!dirRaw) return new Response("missing ?path=<workspace dir>", { status: 400 });
2271
- const ws = await import("./ws-G1j8No5N.js");
2316
+ const ws = await import("./ws-BvL0FY6E.js");
2272
2317
  let prov;
2273
2318
  try {
2274
2319
  prov = await ws.loadProvision();
@@ -2779,6 +2824,7 @@ Options:
2779
2824
  const viewer = String(b.viewer ?? "").slice(0, 64);
2780
2825
  if (!viewer) return new Response("missing viewer", { status: 400 });
2781
2826
  const prevAgent = presence.get(viewer)?.agent;
2827
+ const cap = sanitizeCap(b.cap);
2782
2828
  if (b.agent == null) presence.delete(viewer);
2783
2829
  else presence.set(viewer, {
2784
2830
  viewer,
@@ -2786,10 +2832,15 @@ Options:
2786
2832
  cols: Math.max(0, Math.floor(Number(b.cols) || 0)),
2787
2833
  rows: Math.max(0, Math.floor(Number(b.rows) || 0)),
2788
2834
  sel: typeof b.sel === "string" ? b.sel.slice(0, 200) : null,
2789
- cap: sanitizeCap(b.cap),
2835
+ cap,
2790
2836
  ts: Date.now()
2791
2837
  });
2792
- if (b.agent != null) scheduleNego(Number(b.agent));
2838
+ if (prevAgent != null && String(prevAgent) !== String(b.agent ?? "")) await withdrawCap(Number(prevAgent), viewer);
2839
+ if (b.agent != null) {
2840
+ if (cap) await publishCap(Number(b.agent), viewer, cap, "viewer");
2841
+ else await withdrawCap(Number(b.agent), viewer);
2842
+ scheduleNego(Number(b.agent));
2843
+ }
2793
2844
  if (prevAgent != null && String(prevAgent) !== String(b.agent ?? "")) scheduleNego(Number(prevAgent));
2794
2845
  return new Response(null, { status: 204 });
2795
2846
  }
@@ -2992,7 +3043,7 @@ Options:
2992
3043
  const perm = body.perm ?? "r";
2993
3044
  if (perm !== "r" && perm !== "rw") return new Response(`invalid perm ${perm} (want r or rw)`, { status: 400 });
2994
3045
  try {
2995
- const { createScopedShare } = await import("./agentShare-B7pv4n5U.js");
3046
+ const { createScopedShare } = await import("./agentShare-DYsbDchq.js");
2996
3047
  const share = await createScopedShare({
2997
3048
  agent: body.agent,
2998
3049
  perm,
@@ -3007,12 +3058,12 @@ Options:
3007
3058
  }
3008
3059
  }
3009
3060
  if (req.method === "GET" && p === "/api/shares") {
3010
- const { listShares } = await import("./agentShare-B7pv4n5U.js");
3061
+ const { listShares } = await import("./agentShare-DYsbDchq.js");
3011
3062
  return Response.json(listShares());
3012
3063
  }
3013
3064
  const revokeM = /^\/api\/share\/([^/]+)$/.exec(p);
3014
3065
  if (req.method === "DELETE" && revokeM) {
3015
- const { revokeShare } = await import("./agentShare-B7pv4n5U.js");
3066
+ const { revokeShare } = await import("./agentShare-DYsbDchq.js");
3016
3067
  const ok = revokeShare(decodeURIComponent(revokeM[1]));
3017
3068
  return new Response(ok ? "revoked" : "no such share", { status: ok ? 200 : 404 });
3018
3069
  }
@@ -3314,7 +3365,7 @@ Options:
3314
3365
  const shutdown = (resolve) => {
3315
3366
  if (heartbeat) clearInterval(heartbeat);
3316
3367
  closeShare?.();
3317
- import("./agentShare-B7pv4n5U.js").then((m) => m.revokeAllShares()).catch(() => {});
3368
+ import("./agentShare-DYsbDchq.js").then((m) => m.revokeAllShares()).catch(() => {});
3318
3369
  server?.stop();
3319
3370
  Promise.resolve(releaseHostLock?.()).catch(() => {}).then(resolve);
3320
3371
  };
@@ -3327,4 +3378,4 @@ Options:
3327
3378
 
3328
3379
  //#endregion
3329
3380
  export { cmdServe, loadTokenReadOnly, mintScopedTermToken, resolveDaemonHttpBase, resolveLocalServeUrl };
3330
- //# sourceMappingURL=serve-DpzVMaXc.js.map
3381
+ //# sourceMappingURL=serve-D4bcgVCC.js.map
@@ -32,7 +32,7 @@ async function cmdSetup(rest) {
32
32
  if (!existsSync(abs)) process.stderr.write(` note: that directory doesn't exist yet — create it, or agents spawned there will fail\n`);
33
33
  if (noShare) return 0;
34
34
  process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
35
- const { cmdServe } = await import("./serve-DpzVMaXc.js");
35
+ const { cmdServe } = await import("./serve-D4bcgVCC.js");
36
36
  return cmdServe([
37
37
  "install",
38
38
  "--share",
@@ -42,4 +42,4 @@ async function cmdSetup(rest) {
42
42
 
43
43
  //#endregion
44
44
  export { cmdSetup };
45
- //# sourceMappingURL=setup-C0_DmU6X.js.map
45
+ //# sourceMappingURL=setup-eVOQADh0.js.map
@@ -3,7 +3,7 @@ import "./globalPidIndex-D9DOSd-c.js";
3
3
  import "./messageLog-C-YIzhRj.js";
4
4
  import "./e2e-CEIdJGZ7.js";
5
5
  import "./configShared-9wVlbPyC.js";
6
- import { A as recentReadEdges, B as stdinActivityPath, C as listRecords, D as readNotes, E as readAgentPtysize, F as resolveReadWindow, G as writeToIpc, H as submitAndConfirm, I as resolveResumeArgs, L as restartHintLines, M as renderRawLog, N as renderRawLogLines, O as readPtysize, P as resolveOne, R as runSubcommand, S as lastStdinAt, T as menuSelectKeys, U as waitForLogQuiet, V as stopTipForCli, W as writeKeysPaced, _ as isPidAlive, a as cmdHelp, b as isUnknownManagerToken, c as deriveLiveState, d as extractMenu, f as extractNeedsInput, g as isExitRequest, h as isAgentStuck, i as backoffWhileTyping, j as renderLogTailLines, k as recentMessageEdges, l as deriveLiveStatus, m as finalizedLines, n as READ_PAGE_DEFAULT, o as controlCodeFromName, p as extractTaskCounts, r as TYPING_WINDOW_MS, s as cursorAbs, t as GRACEFUL_EXIT_COMMANDS, u as extractBadges, v as isSlashCommand, w as matchKeyword, x as isUserTyping, y as isSubcommand, z as snapshotStatus } from "./subcommands-BPTVjPEs.js";
6
+ import { A as recentReadEdges, B as stdinActivityPath, C as listRecords, D as readNotes, E as readAgentPtysize, F as resolveReadWindow, G as writeToIpc, H as submitAndConfirm, I as resolveResumeArgs, L as restartHintLines, M as renderRawLog, N as renderRawLogLines, O as readPtysize, P as resolveOne, R as runSubcommand, S as lastStdinAt, T as menuSelectKeys, U as waitForLogQuiet, V as stopTipForCli, W as writeKeysPaced, _ as isPidAlive, a as cmdHelp, b as isUnknownManagerToken, c as deriveLiveState, d as extractMenu, f as extractNeedsInput, g as isExitRequest, h as isAgentStuck, i as backoffWhileTyping, j as renderLogTailLines, k as recentMessageEdges, l as deriveLiveStatus, m as finalizedLines, n as READ_PAGE_DEFAULT, o as controlCodeFromName, p as extractTaskCounts, r as TYPING_WINDOW_MS, s as cursorAbs, t as GRACEFUL_EXIT_COMMANDS, u as extractBadges, v as isSlashCommand, w as matchKeyword, x as isUserTyping, y as isSubcommand, z as snapshotStatus } from "./subcommands-f56kovUW.js";
7
7
  import "./webrtcLink-BMYneHbu.js";
8
8
  import "./remotes-BxQnNxQv.js";
9
9
 
@@ -1267,31 +1267,31 @@ async function runSubcommand(argv) {
1267
1267
  return cmdCh(rest);
1268
1268
  }
1269
1269
  case "term": {
1270
- const { cmdTerm } = await import("./terminal-Byuiy9nW.js");
1270
+ const { cmdTerm } = await import("./terminal-hCYv3dcQ.js");
1271
1271
  return cmdTerm(rest);
1272
1272
  }
1273
1273
  case "widget": {
1274
- const { cmdWidget } = await import("./widget-Bx7xH9AE.js");
1274
+ const { cmdWidget } = await import("./widget-C6eDuybh.js");
1275
1275
  return cmdWidget(rest);
1276
1276
  }
1277
1277
  case "mint": {
1278
- const { cmdMint } = await import("./widget-Bx7xH9AE.js");
1278
+ const { cmdMint } = await import("./widget-C6eDuybh.js");
1279
1279
  return cmdMint(rest);
1280
1280
  }
1281
1281
  case "serve": {
1282
- const { cmdServe } = await import("./serve-DpzVMaXc.js");
1282
+ const { cmdServe } = await import("./serve-D4bcgVCC.js");
1283
1283
  return cmdServe(rest);
1284
1284
  }
1285
1285
  case "setup": {
1286
- const { cmdSetup } = await import("./setup-C0_DmU6X.js");
1286
+ const { cmdSetup } = await import("./setup-eVOQADh0.js");
1287
1287
  return cmdSetup(rest);
1288
1288
  }
1289
1289
  case "ws": {
1290
- const { cmdWs } = await import("./ws-G1j8No5N.js");
1290
+ const { cmdWs } = await import("./ws-BvL0FY6E.js");
1291
1291
  return cmdWs(rest);
1292
1292
  }
1293
1293
  case "schedule": {
1294
- const { cmdSchedule } = await import("./schedule-DJJre7xc.js");
1294
+ const { cmdSchedule } = await import("./schedule-Dvrs07PE.js");
1295
1295
  return cmdSchedule(rest);
1296
1296
  }
1297
1297
  case "remote": {
@@ -1303,7 +1303,7 @@ async function runSubcommand(argv) {
1303
1303
  return cmdExpose(rest);
1304
1304
  }
1305
1305
  case "callback": {
1306
- const { cmdCallback } = await import("./callback-LzenuKtz.js");
1306
+ const { cmdCallback } = await import("./callback-DHGpuhz3.js");
1307
1307
  return cmdCallback(rest);
1308
1308
  }
1309
1309
  case "reap":
@@ -4342,7 +4342,7 @@ async function cmdNotify(rest) {
4342
4342
  }
4343
4343
  const ensure = async () => {
4344
4344
  if (!argv["ensure-daemon"]) return;
4345
- const { ensureDaemon } = await import("./notifyDaemon-CeDkisCv.js");
4345
+ const { ensureDaemon } = await import("./notifyDaemon-DQHnX3a7.js");
4346
4346
  await ensureDaemon().catch(() => null);
4347
4347
  };
4348
4348
  await heartbeatWatcher(parent, selfStartedAt);
@@ -4416,7 +4416,7 @@ async function cmdNotifyCursor(args) {
4416
4416
  }
4417
4417
  async function cmdNotifyd(rest) {
4418
4418
  const sub = rest[0] ?? "status";
4419
- const daemon = await import("./notifyDaemon-CeDkisCv.js");
4419
+ const daemon = await import("./notifyDaemon-DQHnX3a7.js");
4420
4420
  switch (sub) {
4421
4421
  case "run": return daemon.runDaemon();
4422
4422
  case "once": return daemon.runDaemon({ once: true });
@@ -4443,4 +4443,4 @@ async function cmdNotifyd(rest) {
4443
4443
 
4444
4444
  //#endregion
4445
4445
  export { shouldStealLock as $, recentReadEdges as A, stdinActivityPath as B, listRecords as C, readNotes as D, readAgentPtysize as E, resolveReadWindow as F, writeToIpc as G, submitAndConfirm as H, resolveResumeArgs as I, hostId as J, appendEvent as K, restartHintLines as L, renderRawLog as M, renderRawLogLines as N, readPtysize as O, resolveOne as P, readInbox as Q, runSubcommand as R, lastStdinAt as S, menuSelectKeys as T, waitForLogQuiet as U, stopTipForCli as V, writeKeysPaced as W, listInboxParents as X, isTransientLockMkdirError as Y, liveWatchers as Z, isPidAlive as _, cmdHelp as a, isUnknownManagerToken as b, deriveLiveState as c, extractMenu as d, daemonLockDir as et, extractNeedsInput as f, isExitRequest as g, isAgentStuck as h, backoffWhileTyping as i, renderLogTailLines as j, recentMessageEdges as k, deriveLiveStatus as l, finalizedLines as m, READ_PAGE_DEFAULT as n, notifyDir as nt, controlCodeFromName as o, extractTaskCounts as p, gcInboxes as q, TYPING_WINDOW_MS as r, TYPING_BADGE as rt, cursorAbs as s, GRACEFUL_EXIT_COMMANDS as t, daemonLockOwnerPath as tt, extractBadges as u, isSlashCommand as v, matchKeyword as w, isUserTyping as x, isSubcommand as y, snapshotStatus as z };
4446
- //# sourceMappingURL=subcommands-BPTVjPEs.js.map
4446
+ //# sourceMappingURL=subcommands-f56kovUW.js.map
@@ -98,7 +98,7 @@ async function cmdTermMint(args) {
98
98
  if (flags.ro && flags.interactive) throw new Error("--ro and --interactive are mutually exclusive");
99
99
  const ttlSec = parseTtlSec(typeof flags.ttl === "string" ? flags.ttl : "15m");
100
100
  const canSend = flags.interactive === true;
101
- const { mintScopedTermToken } = await import("./serve-DpzVMaXc.js");
101
+ const { mintScopedTermToken } = await import("./serve-D4bcgVCC.js");
102
102
  const r = await mintScopedTermToken(pid, {
103
103
  ttlSec,
104
104
  canSend
@@ -135,4 +135,4 @@ async function cmdTerm(args) {
135
135
 
136
136
  //#endregion
137
137
  export { cmdTerm };
138
- //# sourceMappingURL=terminal-Byuiy9nW.js.map
138
+ //# sourceMappingURL=terminal-hCYv3dcQ.js.map
@@ -1,6 +1,6 @@
1
1
  import { n as __esmMin, r as __exportAll } from "./chunk-cZBX9HZv.js";
2
2
  import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
3
- import { r as getInstalledPackage } from "./versionChecker-BzfCavve.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-BZYWL6Lx.js";
4
4
  import { t as agentYesHome } from "./agentYesHome-CtmsY6I-.js";
5
5
  import { d as openStore, p as LIFECYCLES, x as removeControlCharacters } from "./todoAutomation-Buo457VO.js";
6
6
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-DBoONoM-.js";
@@ -2027,4 +2027,4 @@ function sleep(ms) {
2027
2027
 
2028
2028
  //#endregion
2029
2029
  export { hasTodoStore as a, AgentContext as c, answerAsk as i, CLAUDE_SESSION_PIN_ENV as l, agentYes as n, listAsks as o, config as r, listAsksForProject as s, CLIS_CONFIG as t };
2030
- //# sourceMappingURL=ts-CyUQ58Ht.js.map
2030
+ //# sourceMappingURL=ts-j4WgXrcI.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.241.0";
10
+ var version = "1.242.0";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -215,4 +215,4 @@ async function displayVersion() {
215
215
 
216
216
  //#endregion
217
217
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
218
- //# sourceMappingURL=versionChecker-BzfCavve.js.map
218
+ //# sourceMappingURL=versionChecker-BZYWL6Lx.js.map
@@ -28,7 +28,7 @@ function parseFlags(args, known) {
28
28
  }
29
29
  /** Resolve the local daemon base URL + token (flags override discovery). */
30
30
  async function daemonTarget(flags) {
31
- const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-DpzVMaXc.js");
31
+ const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-D4bcgVCC.js");
32
32
  const base = typeof flags.base === "string" ? flags.base : await resolveDaemonHttpBase();
33
33
  if (!base) throw new Error("no running ay serve daemon found — start `ay serve` or pass --base <url> (e.g. http://127.0.0.1:PORT)");
34
34
  const token = typeof flags.token === "string" ? flags.token : await loadTokenReadOnly() ?? "";
@@ -186,7 +186,7 @@ async function cmdMint(args) {
186
186
  const bad = caps.filter((c) => !KNOWN_CAPS.has(c));
187
187
  if (bad.length) throw new Error(`unknown cap(s): ${bad.join(", ")} (valid: ${[...KNOWN_CAPS].join(", ")})`);
188
188
  const ttlSec = parseTtlSec(typeof flags.ttl === "string" ? flags.ttl : "15m");
189
- const { mintScopedTermToken } = await import("./serve-DpzVMaXc.js");
189
+ const { mintScopedTermToken } = await import("./serve-D4bcgVCC.js");
190
190
  const r = await mintScopedTermToken(target, {
191
191
  ttlSec,
192
192
  caps
@@ -202,4 +202,4 @@ async function cmdMint(args) {
202
202
 
203
203
  //#endregion
204
204
  export { cmdMint, cmdWidget };
205
- //# sourceMappingURL=widget-Bx7xH9AE.js.map
205
+ //# sourceMappingURL=widget-C6eDuybh.js.map
@@ -3,7 +3,7 @@ import "./globalPidIndex-D9DOSd-c.js";
3
3
  import "./messageLog-C-YIzhRj.js";
4
4
  import "./e2e-CEIdJGZ7.js";
5
5
  import "./configShared-9wVlbPyC.js";
6
- import { C as listRecords } from "./subcommands-BPTVjPEs.js";
6
+ import { C as listRecords } from "./subcommands-f56kovUW.js";
7
7
  import "./webrtcLink-BMYneHbu.js";
8
8
  import "./remotes-BxQnNxQv.js";
9
9
  import { i as getProvisionRoot } from "./workspaceConfig-DOHygA2x.js";
@@ -418,4 +418,4 @@ async function cmdWs(args) {
418
418
 
419
419
  //#endregion
420
420
  export { WS_JSON_SCHEMA, cmdWs, collectWorkspaces, isPathInside, loadProvision, workspaceStatus };
421
- //# sourceMappingURL=ws-G1j8No5N.js.map
421
+ //# sourceMappingURL=ws-BvL0FY6E.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.241.0",
3
+ "version": "1.242.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
@@ -1,4 +1,4 @@
1
- import { appendFile, mkdir, open, readFile, stat, unlink, writeFile } from "fs/promises";
1
+ import { appendFile, mkdir, open, readdir, readFile, stat, unlink, writeFile } from "fs/promises";
2
2
  import { existsSync, renameSync, watch, writeFileSync } from "node:fs";
3
3
  import { execFileSync } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
@@ -2092,6 +2092,63 @@ export async function cmdServe(rest: string[]): Promise<number> {
2092
2092
  >();
2093
2093
  const PRESENCE_TTL_MS = 12_000;
2094
2094
 
2095
+ // ── Shared cap store (3a: multi-daemon size negotiation) ────────────────────
2096
+ // Presence is in-memory PER DAEMON, so multiple `ay serve` processes (the HTTP
2097
+ // daemon + each `--webrtc share` daemon) used to negotiate independently and
2098
+ // fight over the single winsize file (cross-process last-writer-wins). Instead
2099
+ // every daemon publishes each viewer's size cap to a SHARED on-disk store —
2100
+ // ~/.agent-yes/caps/<pid>/<daemon>:<viewer> — and negotiation reads the WHOLE
2101
+ // store (all daemons' viewers + the local-tty cap, 3b). Deterministic min +
2102
+ // read-before-write means every daemon converges on the same winsize instead of
2103
+ // clobbering each other. Lock-free: one writer per file (a daemon owns its own
2104
+ // viewers' entries), short TTL prunes tabs that vanished without a clean clear.
2105
+ const CAP_TTL_MS = 12_000;
2106
+ const daemonTag = `d${process.pid}`; // unique per daemon process
2107
+ const capsDir = (pid: number) => path.join(agentYesHome(), "caps", String(pid));
2108
+ const capFile = (pid: number, key: string) =>
2109
+ path.join(capsDir(pid), key.replace(/[^\w.:-]/g, "_"));
2110
+ const publishCap = async (pid: number, viewer: string, cap: SizeCap, role: string) => {
2111
+ try {
2112
+ await mkdir(capsDir(pid), { recursive: true });
2113
+ await writeFile(
2114
+ capFile(pid, `${daemonTag}:${viewer}`),
2115
+ `${cap.cols} ${cap.rows} ${Date.now()} ${role}\n`,
2116
+ );
2117
+ } catch {
2118
+ /* best-effort */
2119
+ }
2120
+ };
2121
+ const withdrawCap = async (pid: number, viewer: string) => {
2122
+ await unlink(capFile(pid, `${daemonTag}:${viewer}`)).catch(() => {});
2123
+ };
2124
+ // Read every live cap for a pid across ALL daemons; prune expired files.
2125
+ const readSharedCaps = async (pid: number): Promise<SizeCap[]> => {
2126
+ let files: string[];
2127
+ try {
2128
+ files = await readdir(capsDir(pid));
2129
+ } catch {
2130
+ return [];
2131
+ }
2132
+ const now = Date.now();
2133
+ const caps: SizeCap[] = [];
2134
+ for (const f of files) {
2135
+ const fp = path.join(capsDir(pid), f);
2136
+ try {
2137
+ const parts = (await readFile(fp, "utf-8")).trim().split(/\s+/);
2138
+ const ts = Number(parts[2]);
2139
+ if (!Number.isFinite(ts) || now - ts > CAP_TTL_MS) {
2140
+ await unlink(fp).catch(() => {}); // stale (its writer vanished) — prune
2141
+ continue;
2142
+ }
2143
+ const cap = sanitizeCap({ cols: Number(parts[0]), rows: Number(parts[1]) });
2144
+ if (cap) caps.push(cap);
2145
+ } catch {
2146
+ /* unreadable/racing — skip */
2147
+ }
2148
+ }
2149
+ return caps;
2150
+ };
2151
+
2095
2152
  // ── PTY size negotiation — tmux's "smallest client wins" ──────────────────
2096
2153
  // Writable viewers report their readable capacity (`cap`) alongside their
2097
2154
  // presence; we resize the agent's PTY to the elementwise min across live caps
@@ -2137,17 +2194,12 @@ export async function cmdServe(rest: string[]): Promise<number> {
2137
2194
  "winsize",
2138
2195
  String(pid),
2139
2196
  );
2140
- const liveCapsFor = (pid: number): SizeCap[] => {
2141
- const now = Date.now();
2142
- const caps: SizeCap[] = [];
2143
- for (const v of presence.values())
2144
- if (now - v.ts <= PRESENCE_TTL_MS && String(v.agent) === String(pid) && v.cap)
2145
- caps.push(v.cap);
2146
- return caps;
2147
- };
2148
2197
  const applyNego = async (pid: number) => {
2149
2198
  negoTimers.delete(pid);
2150
- const eff = negotiateSize(liveCapsFor(pid));
2199
+ // Negotiate over the SHARED cap store — every daemon's viewers (+ the local
2200
+ // tty cap, 3b) — so all daemons compute the same min and converge.
2201
+ const caps = await readSharedCaps(pid);
2202
+ const eff = negotiateSize(caps);
2151
2203
  const file = winsizePathFor(pid);
2152
2204
  const prev = negoApplied.get(pid);
2153
2205
  try {
@@ -2158,20 +2210,25 @@ export async function cmdServe(rest: string[]): Promise<number> {
2158
2210
  // SIGWINCH reflow on every re-visit is exactly the churn users notice.
2159
2211
  if (prev && Math.abs(prev.cols - eff.cols) <= 1 && Math.abs(prev.rows - eff.rows) <= 1)
2160
2212
  return;
2213
+ // Cross-daemon convergence: if the winsize file ALREADY holds this size (a
2214
+ // sibling daemon negotiated the same min), do NOT rewrite/re-SIGWINCH — else
2215
+ // two daemons thrash the PTY with same-size, different-timestamp writes.
2216
+ try {
2217
+ const curr = (await readFile(file, "utf-8")).trim().split(/\s+/);
2218
+ if (Number(curr[0]) === eff.cols && Number(curr[1]) === eff.rows) {
2219
+ negoApplied.set(pid, { cols: eff.cols, rows: eff.rows, content: "" });
2220
+ return;
2221
+ }
2222
+ } catch {
2223
+ /* no winsize file yet — fall through and write it */
2224
+ }
2161
2225
  const content = `${eff.cols} ${eff.rows} ${Date.now()}\n`;
2162
- // Log this presence-negotiated resize (mirrors the POST /api/resize log) so
2163
- // resize-fights are traceable across ALL channels, not just direct HTTP a
2164
- // remote console reporting a big viewport cap resizes the shared PTY THROUGH
2165
- // HERE, which otherwise leaves no trace and looks like "nobody pushed".
2166
- const srcViewers = [...presence.values()]
2167
- .filter(
2168
- (v) => Date.now() - v.ts <= PRESENCE_TTL_MS && String(v.agent) === String(pid) && v.cap,
2169
- )
2170
- .map((v) => v.viewer)
2171
- .join(",");
2226
+ // Trace the negotiated resize + this daemon's identity (multi-daemon topology:
2227
+ // the HTTP daemon and each --webrtc share daemon each negotiate off the shared
2228
+ // store; the trace shows which one wrote, so a resize-fight is diagnosable).
2172
2229
  process.stderr.write(
2173
2230
  `[api/resize] pid=${pid} ${eff.cols}x${eff.rows} src=presence-nego ` +
2174
- `caps=${liveCapsFor(pid).length} viewers=${srcViewers || "-"}\n`,
2231
+ `daemon=${daemonTag} caps=${caps.length}\n`,
2175
2232
  );
2176
2233
  await mkdir(path.dirname(file), { recursive: true });
2177
2234
  await writeFile(file, content);
@@ -3586,6 +3643,7 @@ export async function cmdServe(rest: string[]): Promise<number> {
3586
3643
  // The agent this viewer WAS on — if the entry moves (agent switch) or
3587
3644
  // clears, that agent's negotiated size must be recomputed (grow-back).
3588
3645
  const prevAgent = presence.get(viewer)?.agent;
3646
+ const cap = sanitizeCap(b.cap);
3589
3647
  if (b.agent == null) presence.delete(viewer);
3590
3648
  else
3591
3649
  presence.set(viewer, {
@@ -3594,10 +3652,19 @@ export async function cmdServe(rest: string[]): Promise<number> {
3594
3652
  cols: Math.max(0, Math.floor(Number(b.cols) || 0)),
3595
3653
  rows: Math.max(0, Math.floor(Number(b.rows) || 0)),
3596
3654
  sel: typeof b.sel === "string" ? b.sel.slice(0, 200) : null,
3597
- cap: sanitizeCap(b.cap),
3655
+ cap,
3598
3656
  ts: Date.now(),
3599
3657
  });
3600
- if (b.agent != null) scheduleNego(Number(b.agent));
3658
+ // Mirror this viewer's SIZE cap into the shared cap store so sibling daemons
3659
+ // negotiate off it too. A cleared/agent-switched/cap-less viewer withdraws its
3660
+ // cap (from the old agent) so its size stops counting immediately, not on TTL.
3661
+ if (prevAgent != null && String(prevAgent) !== String(b.agent ?? ""))
3662
+ await withdrawCap(Number(prevAgent), viewer);
3663
+ if (b.agent != null) {
3664
+ if (cap) await publishCap(Number(b.agent), viewer, cap, "viewer");
3665
+ else await withdrawCap(Number(b.agent), viewer);
3666
+ scheduleNego(Number(b.agent));
3667
+ }
3601
3668
  if (prevAgent != null && String(prevAgent) !== String(b.agent ?? ""))
3602
3669
  scheduleNego(Number(prevAgent));
3603
3670
  return new Response(null, { status: 204 });