agent-yes 1.205.0 → 1.207.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.
Files changed (31) hide show
  1. package/README.md +2 -0
  2. package/dist/{SUPPORTED_CLIS-C8-qh98a.js → SUPPORTED_CLIS-B1CDJwYn.js} +2 -2
  3. package/dist/SUPPORTED_CLIS-vH8hvcZr.js +9 -0
  4. package/dist/{agentShare-99IGGOkv.js → agentShare-DdEbHtAn.js} +3 -3
  5. package/dist/cli.js +4 -4
  6. package/dist/index.js +2 -2
  7. package/dist/{notifyDaemon-DK1HOMjj.js → notifyDaemon-DivHVzZx.js} +2 -2
  8. package/dist/{rustBinary-Tgn948GR.js → rustBinary-CuBIr-zX.js} +2 -2
  9. package/dist/{schedule-CBoM67Vy.js → schedule-Bg_CDt2j.js} +4 -4
  10. package/dist/{serve-IvOlaL-7.js → serve-B-ZCy67S.js} +258 -41
  11. package/dist/{setup-BaKdS-3i.js → setup-D6UBzk4-.js} +2 -2
  12. package/dist/{share-Ciw1mWVN.js → share-_QYjPN7q.js} +1 -1
  13. package/dist/{share-BfIU8t_h.js → share-vchIyVd8.js} +98 -5
  14. package/dist/{subcommands-Bs7wJRBB.js → subcommands-6AQ8egRn.js} +7 -7
  15. package/dist/{subcommands-DyDHZ5YI.js → subcommands-CeRH9-_h.js} +1 -1
  16. package/dist/{ts-Dm6jjLwC.js → ts-DZWZvbLM.js} +2 -2
  17. package/dist/{versionChecker-BomfUpSo.js → versionChecker-C2CV9VOX.js} +2 -2
  18. package/dist/{ws-CSkP2mkD.js → ws-CGNmxCPD.js} +2 -2
  19. package/lab/ui/console-logic.js +23 -1
  20. package/lab/ui/index.html +188 -29
  21. package/package.json +1 -1
  22. package/ts/serve.spec.ts +13 -1
  23. package/ts/serve.ts +263 -34
  24. package/ts/serveLock.spec.ts +15 -14
  25. package/ts/share.ts +80 -1
  26. package/ts/sizeNego.spec.ts +61 -0
  27. package/ts/sizeNego.ts +59 -0
  28. package/ts/statusText.spec.ts +17 -0
  29. package/ts/statusText.ts +19 -0
  30. package/ts/ws.spec.ts +8 -10
  31. package/dist/SUPPORTED_CLIS-D3XTFfS7.js +0 -9
@@ -1,15 +1,15 @@
1
- import "./ts-Dm6jjLwC.js";
1
+ import "./ts-DZWZvbLM.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import { r as getInstalledPackage } from "./versionChecker-BomfUpSo.js";
4
- import { t as findSpawnHiddenLauncher } from "./rustBinary-Tgn948GR.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-C2CV9VOX.js";
4
+ import { t as findSpawnHiddenLauncher } from "./rustBinary-CuBIr-zX.js";
5
5
  import { t as agentYesHome$1 } from "./agentYesHome-CtHb5b71.js";
6
6
  import "./pidStore-BIvsBQ8X.js";
7
7
  import { a as updateGlobalPidStatus } from "./globalPidIndex-CoNr7tS8.js";
8
8
  import { r as recordInbox } from "./messageLog-CxrKJj77.js";
9
9
  import { t as pgidForWrapper } from "./reaper-BUHCyxdF.js";
10
10
  import "./configShared-aKTg-sa5.js";
11
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-C8-qh98a.js";
12
- import { A as renderRawLog, D as recentMessageEdges, E as readPtysize, L as snapshotStatus, M as resolveOne, O as recentReadEdges, S as listRecords, T as readNotes, U as writeToIpc, b as isUserTyping, f as extractNeedsInput, k as renderLogTailLines, l as deriveLiveStatus, o as controlCodeFromName, p as extractTaskCounts, q as isTransientLockMkdirError, tt as TYPING_BADGE, u as extractBadges } from "./subcommands-Bs7wJRBB.js";
11
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-B1CDJwYn.js";
12
+ import { A as renderRawLog, D as recentMessageEdges, E as readPtysize, L as snapshotStatus, M as resolveOne, O as recentReadEdges, S as listRecords, T as readNotes, U as writeToIpc, b as isUserTyping, f as extractNeedsInput, k as renderLogTailLines, l as deriveLiveStatus, o as controlCodeFromName, p as extractTaskCounts, q as isTransientLockMkdirError, tt as TYPING_BADGE, u as extractBadges } from "./subcommands-6AQ8egRn.js";
13
13
  import "./e2e-jb0Hp43q.js";
14
14
  import "./webrtcLink-B7REGtK2.js";
15
15
  import "./remotes-Cim0dBU7.js";
@@ -43,6 +43,26 @@ import { existsSync, renameSync, watch, writeFileSync } from "node:fs";
43
43
  */
44
44
  const isTerminalReply = (s) => /^(?:\x1b\[(?:\??\d+(?:;\d+)*R|\?[\d;]*c|>[\d;]*c|\d*n))+$/.test(s);
45
45
 
46
+ //#endregion
47
+ //#region ts/statusText.ts
48
+ /**
49
+ * Extract a short "what is the agent doing right now?" line from the rendered
50
+ * terminal screen. Claude Code paints this as a spinner/status line, e.g.
51
+ * "✶ Verifying calendar meetings with real data… (6m 30s · ↓ 19.5k tokens)".
52
+ */
53
+ const SPINNER_PREFIX = /^[\u2800-\u28ff✶✻✢✳✽✦✧✩✷✸✹✺✼·•●◐◓◒◑]\s+/u;
54
+ const CONTROL = /[\x00-\x1f\x7f-\x9f]/g;
55
+ function parseStatusText(lines) {
56
+ for (let i = lines.length - 1; i >= 0; i--) {
57
+ const line = lines[i]?.replace(CONTROL, "").trim();
58
+ if (!line || line.length < 3) continue;
59
+ if (!SPINNER_PREFIX.test(line)) continue;
60
+ if (/^(?:[•·]\s*)?(?:esc|ctrl|enter|return|shift|tab)\b/i.test(line)) continue;
61
+ return line.slice(0, 220).trim();
62
+ }
63
+ return null;
64
+ }
65
+
46
66
  //#endregion
47
67
  //#region ts/serveLock.ts
48
68
  /**
@@ -187,6 +207,48 @@ async function acquireWebrtcHostLock(opts) {
187
207
  }
188
208
  }
189
209
 
210
+ //#endregion
211
+ //#region ts/sizeNego.ts
212
+ /** Reject junk caps (a viewer mid-layout can report 0×0 or absurd numbers). */
213
+ const CAP_MIN_COLS = 20;
214
+ const CAP_MIN_ROWS = 5;
215
+ const CAP_MAX_COLS = 500;
216
+ const CAP_MAX_ROWS = 200;
217
+ /** Never negotiate below this — TUIs (claude, codex) break down when the grid
218
+ * gets absurdly narrow, so a tiny watch-sized viewer can't wedge the agent. */
219
+ const NEGO_FLOOR_COLS = 40;
220
+ const NEGO_FLOOR_ROWS = 10;
221
+ /** Parse a presence-reported cap into a sane SizeCap, or null if unusable. */
222
+ function sanitizeCap(cap) {
223
+ if (typeof cap !== "object" || cap === null) return null;
224
+ const c = Math.floor(Number(cap.cols) || 0);
225
+ const r = Math.floor(Number(cap.rows) || 0);
226
+ if (c < CAP_MIN_COLS || c > CAP_MAX_COLS) return null;
227
+ if (r < CAP_MIN_ROWS || r > CAP_MAX_ROWS) return null;
228
+ return {
229
+ cols: c,
230
+ rows: r
231
+ };
232
+ }
233
+ /**
234
+ * Elementwise minimum over the live viewers' capacities, clamped to the floor.
235
+ * Returns null when no viewer reports a capacity — meaning "withdraw the
236
+ * negotiated size, let the real tty size rule again".
237
+ */
238
+ function negotiateSize(caps) {
239
+ let cols = Infinity;
240
+ let rows = Infinity;
241
+ for (const cap of caps) {
242
+ if (cap.cols < cols) cols = cap.cols;
243
+ if (cap.rows < rows) rows = cap.rows;
244
+ }
245
+ if (!Number.isFinite(cols) || !Number.isFinite(rows)) return null;
246
+ return {
247
+ cols: Math.max(NEGO_FLOOR_COLS, cols),
248
+ rows: Math.max(NEGO_FLOOR_ROWS, rows)
249
+ };
250
+ }
251
+
190
252
  //#endregion
191
253
  //#region ts/strayServe.ts
192
254
  const MANAGEMENT_COMMAND_RE = /\b(?:install|uninstall|status|logs|restart)\b/;
@@ -236,7 +298,49 @@ async function listStrayServeProcesses() {
236
298
 
237
299
  //#endregion
238
300
  //#region ts/serve.ts
239
- const DEFAULT_PORT = 7432;
301
+ const DEFAULT_PORT = 0;
302
+ const PORTLESS_APP_NAME = "agent-yes";
303
+ const PORTLESS_CHILD_ENV = "AGENT_YES_PORTLESS_CHILD";
304
+ function portlessConsoleUrl(token) {
305
+ return portlessConsoleUrlForOrigin(process.env.PORTLESS_URL?.replace(/\/$/, "") || `https://${PORTLESS_APP_NAME}.localhost`, token);
306
+ }
307
+ function portlessConsoleUrlForOrigin(origin, token) {
308
+ return `${origin}${token ? `/#k=${encodeURIComponent(token)}` : "/"}`;
309
+ }
310
+ async function resolvedPortlessConsoleUrl(token) {
311
+ if (process.env.PORTLESS_URL) return portlessConsoleUrl(token);
312
+ try {
313
+ const port = Number((await readFile(path.join(homedir(), ".portless", "proxy.port"), "utf-8")).trim());
314
+ if (Number.isInteger(port) && port > 0) return portlessConsoleUrlForOrigin(`https://${PORTLESS_APP_NAME}.localhost${port === 443 ? "" : `:${port}`}`, token);
315
+ } catch {}
316
+ return portlessConsoleUrl(token);
317
+ }
318
+ async function runWithPortless(rest) {
319
+ const portless = Bun.which("portless");
320
+ if (!portless) {
321
+ process.stderr.write("ay serve: Portless is required for local HTTPS. Install it with: npm install -g portless\n");
322
+ return 1;
323
+ }
324
+ const script = process.argv[1];
325
+ const self = script && /\.[cm]?[jt]s$/.test(script) ? [process.execPath, script] : [process.execPath];
326
+ return await Bun.spawn([
327
+ portless,
328
+ PORTLESS_APP_NAME,
329
+ ...self,
330
+ "serve",
331
+ ...rest
332
+ ], {
333
+ stdio: [
334
+ "inherit",
335
+ "inherit",
336
+ "inherit"
337
+ ],
338
+ env: {
339
+ ...liveEnv(),
340
+ [PORTLESS_CHILD_ENV]: "1"
341
+ }
342
+ }).exited ?? 1;
343
+ }
240
344
  /**
241
345
  * Normalize a user-supplied GitHub-ish source into the standard
242
346
  * `<owner>/<repo>/tree/<branch>` path that codehost/provision's `parseSpec`
@@ -325,7 +429,7 @@ function loginShellEnv() {
325
429
  const delim = "_AY_SHELL_ENV_DELIM_";
326
430
  const out = Bun.spawnSync([
327
431
  shell,
328
- "-ilc",
432
+ "-lc",
329
433
  `printf %s "${delim}"; env -0; printf %s "${delim}"`
330
434
  ], {
331
435
  stdin: "ignore",
@@ -710,7 +814,18 @@ async function readDaemonServeArgs(mgr) {
710
814
  }
711
815
  function portFromArgs(args) {
712
816
  const m = /--port[=\s](\d+)/.exec(args.join(" "));
713
- return m ? Number(m[1]) : DEFAULT_PORT;
817
+ return m ? Number(m[1]) : null;
818
+ }
819
+ function argsUsePortless(args) {
820
+ return (args.some((a) => a.startsWith("--http") || a.startsWith("--share")) || !args.some((a) => a.startsWith("--webrtc"))) && portFromArgs(args) === null;
821
+ }
822
+ async function portlessAppPort() {
823
+ try {
824
+ const route = JSON.parse(await readFile(path.join(homedir(), ".portless", "routes.json"), "utf-8")).find((r) => r.hostname === `${PORTLESS_APP_NAME}.localhost`);
825
+ return typeof route?.port === "number" ? route.port : null;
826
+ } catch {
827
+ return null;
828
+ }
714
829
  }
715
830
  async function warnStrayServeProcesses() {
716
831
  const stray = await listStrayServeProcesses();
@@ -731,6 +846,7 @@ function explicitWebrtcUrl(args) {
731
846
  }
732
847
  }
733
848
  async function fetchDaemonVersion(port, token) {
849
+ if (port === null) return null;
734
850
  try {
735
851
  const r = await fetch(`http://127.0.0.1:${port}/api/version`, {
736
852
  headers: { Authorization: `Bearer ${token}` },
@@ -818,7 +934,7 @@ async function cmdServeDaemon(sub, args) {
818
934
  let shareLink = null;
819
935
  let shareLinkMinted = false;
820
936
  if (webrtcDaemon) try {
821
- const { loadOrCreateShareRoom, shareLinkFromRoomUrl } = await import("./share-Ciw1mWVN.js");
937
+ const { loadOrCreateShareRoom, shareLinkFromRoomUrl } = await import("./share-_QYjPN7q.js");
822
938
  const explicit = explicitWebrtcUrl(effArgs);
823
939
  shareLink = shareLinkFromRoomUrl(explicit ?? await loadOrCreateShareRoom());
824
940
  shareLinkMinted = !explicit;
@@ -833,7 +949,7 @@ async function cmdServeDaemon(sub, args) {
833
949
  };
834
950
  if (priorArgs !== null) {
835
951
  const sameConfig = JSON.stringify(effArgs) === JSON.stringify(priorArgs);
836
- const runningVer = await fetchDaemonVersion(portFromArgs(effArgs), token);
952
+ const runningVer = await fetchDaemonVersion(argsUsePortless(effArgs) ? await portlessAppPort() : portFromArgs(effArgs), token);
837
953
  if (runningVer === current && sameConfig) {
838
954
  await ensureBootAutostart(mgr);
839
955
  process.stdout.write(`'${DAEMON_NAME}' already running v${current} (up to date)\n`);
@@ -900,7 +1016,8 @@ async function cmdServeDaemon(sub, args) {
900
1016
  }).exited;
901
1017
  if (code === 0) {
902
1018
  const onBoot = await ensureBootAutostart(mgr);
903
- const port = portFromArgs(effArgs);
1019
+ const port = argsUsePortless(effArgs) ? await portlessAppPort() : portFromArgs(effArgs);
1020
+ const localUrl = argsUsePortless(effArgs) ? await resolvedPortlessConsoleUrl(token) : null;
904
1021
  const httpish = effArgs.some((a) => a.startsWith("--http") || a.startsWith("--share")) || !effArgs.some((a) => a.startsWith("--webrtc"));
905
1022
  process.stdout.write(`\n${priorArgs !== null ? `rolled '${DAEMON_NAME}' forward to` : `installed '${DAEMON_NAME}' as a daemon via ${mgr.id} —`} v${current}\n`);
906
1023
  if (mgr.id === "oxmgr" && process.platform === "win32") process.stdout.write(onBoot ? `start-on-login: enabled (Task Scheduler ONLOGON runs \`oxmgr daemon run\`)\n` : `start-on-login: not registered — run \`oxmgr service install\` to enable\n`);
@@ -909,8 +1026,11 @@ async function cmdServeDaemon(sub, args) {
909
1026
  else process.stdout.write(onBoot ? `start-on-boot: enabled (pm2 startup registered with the system init)\n` : `start-on-boot: not registered — run \`pm2 startup\` (may need sudo) to enable\n`);
910
1027
  process.stdout.write(`token: ${token}\n\n`);
911
1028
  if (httpish) {
912
- process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
913
- process.stdout.write(` ay remote add <alias> http://${token}@<host>:${port}\n`);
1029
+ if (argsUsePortless(effArgs)) process.stdout.write(` web console: ${localUrl}\n`);
1030
+ else if (port !== null) {
1031
+ process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
1032
+ process.stdout.write(` ay remote add <alias> http://${token}@<host>:${port}\n`);
1033
+ }
914
1034
  }
915
1035
  process.stdout.write(` ay serve logs # view server logs\n`);
916
1036
  process.stdout.write(` ay serve uninstall # remove daemon\n`);
@@ -941,7 +1061,9 @@ async function cmdServeStatus(args) {
941
1061
  const daemonArgs = mgr ? await readDaemonServeArgs(mgr) : null;
942
1062
  const installed = daemonArgs !== null;
943
1063
  const a = daemonArgs ?? [];
944
- const port = portFromArgs(a);
1064
+ const local = argsUsePortless(a);
1065
+ const port = local ? await portlessAppPort() : portFromArgs(a);
1066
+ const localUrl = local ? await resolvedPortlessConsoleUrl(token ?? void 0) : null;
945
1067
  const webrtcish = a.some((x) => x.startsWith("--webrtc") || x.startsWith("--share"));
946
1068
  const httpish = a.some((x) => x.startsWith("--http") || x.startsWith("--share")) || !a.some((x) => x.startsWith("--webrtc"));
947
1069
  const mode = httpish && webrtcish ? "http+webrtc" : webrtcish ? "webrtc" : "http";
@@ -953,6 +1075,7 @@ async function cmdServeStatus(args) {
953
1075
  installed,
954
1076
  mode,
955
1077
  port: httpish ? port : null,
1078
+ localUrl: httpish ? localUrl : null,
956
1079
  reachable: runningVersion !== null,
957
1080
  runningVersion,
958
1081
  currentVersion: current,
@@ -968,18 +1091,23 @@ async function cmdServeStatus(args) {
968
1091
  w(`manager: ${mgr ? mgr.id : "none — install pm2 or oxmgr to daemonize"}`);
969
1092
  if (installed) {
970
1093
  w(`installed: yes (via ${mgr.id})`);
971
- w(`mode: ${mode}${httpish ? ` (port ${port})` : ""}`);
1094
+ w(`mode: ${mode}${httpish ? local ? ` (${localUrl})` : ` (port ${port})` : ""}`);
972
1095
  if (a.length) w(`args: ${a.join(" ")}`);
973
1096
  } else w(`installed: no — start a daemon with: ay serve install [--share]`);
974
- if (runningVersion !== null) w(`http api: reachable on 127.0.0.1:${port} — v${runningVersion} (${runningVersion === current ? "up to date" : `outdated (current v${current})`})`);
975
- else if (mode === "webrtc") w(`http api: none (webrtc-only)`);
976
- else w(`http api: not reachable on 127.0.0.1:${port} (not running)`);
1097
+ if (runningVersion !== null) {
1098
+ const tag = runningVersion === current ? "up to date" : `outdated (current v${current})`;
1099
+ w(`http api: reachable ${local ? `via ${localUrl}` : `on 127.0.0.1:${port}`} — v${runningVersion} (${tag})`);
1100
+ } else if (mode === "webrtc") w(`http api: none (webrtc-only)`);
1101
+ else w(`http api: not reachable ${local ? `via ${localUrl}` : `on 127.0.0.1:${port}`} (not running)`);
977
1102
  w(`token: ${token ?? "(none yet — created on first serve)"}`);
978
1103
  if (shareLink) w(`share link: ${shareLink}`);
979
1104
  if (token && httpish) {
980
1105
  w();
981
- w(`connect: ay ls ${token}@<host>:${port}`);
982
- w(` ay remote add <alias> http://${token}@<host>:${port}`);
1106
+ if (local) w(`console: ${localUrl}`);
1107
+ else if (port !== null) {
1108
+ w(`connect: ay ls ${token}@<host>:${port}`);
1109
+ w(` ay remote add <alias> http://${token}@<host>:${port}`);
1110
+ }
983
1111
  }
984
1112
  return 0;
985
1113
  }
@@ -1000,7 +1128,13 @@ Modes (default: --http):
1000
1128
  --share [URL] Legacy alias for --http --webrtc
1001
1129
 
1002
1130
  Options:
1003
- --port N Port to listen on (default: ${DEFAULT_PORT})\n --host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)\n --token TOKEN Auth token (auto-generated and saved if omitted)\n -d, --daemon Install these flags as a background daemon (pm2/oxmgr)\n (same as: ay serve install <flags>)\n --allow-spawn Deprecated no-op — the console can always spawn agents\n --tls-cert FILE TLS certificate PEM\n --tls-key FILE TLS private key PEM\n\nSubcommands:\n ay serve install install as background daemon (oxmgr; pm2 fallback on Windows without the winfix build)\n ay serve status show daemon/server status (add --json for scripts)\n ay serve uninstall remove daemon\n ay serve logs view daemon logs\n\nOnce running, connect from another machine:\n ay ls <token>@<host>:${DEFAULT_PORT}\n ay remote add <alias> http://<token>@<host>:${DEFAULT_PORT}\n`);
1131
+ --port N Bypass Portless and listen on a fixed HTTP port
1132
+ --host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)
1133
+ --token TOKEN Auth token (auto-generated and saved if omitted)
1134
+ -d, --daemon Install these flags as a background daemon (pm2/oxmgr)
1135
+ (same as: ay serve install <flags>)
1136
+ --local Explicitly use Portless (the default for HTTP mode)
1137
+ ${portlessConsoleUrl()}\n --allow-spawn Deprecated no-op — the console can always spawn agents\n --tls-cert FILE TLS certificate PEM\n --tls-key FILE TLS private key PEM\n\nSubcommands:\n ay serve install install as background daemon (oxmgr; pm2 fallback on Windows without the winfix build)\n ay serve status show daemon/server status (add --json for scripts)\n ay serve uninstall remove daemon\n ay serve logs view daemon logs\n\nOnce running, connect from another machine:\n ay ls <token>@<host>:<port>\n ay remote add <alias> http://<token>@<host>:<port>\n`);
1004
1138
  return 0;
1005
1139
  }
1006
1140
  const sub = rest[0];
@@ -1020,8 +1154,7 @@ Options:
1020
1154
  if (sub === "install" || sub === "uninstall" || sub === "logs") return cmdServeDaemon(sub, rest.slice(1));
1021
1155
  const argv = await yargs(rest).usage("Usage: ay serve [options]").option("port", {
1022
1156
  type: "number",
1023
- default: DEFAULT_PORT,
1024
- description: "Port to listen on"
1157
+ description: "Bypass Portless and listen on a fixed port"
1025
1158
  }).option("host", {
1026
1159
  type: "string",
1027
1160
  default: "127.0.0.1",
@@ -1049,6 +1182,10 @@ Options:
1049
1182
  type: "boolean",
1050
1183
  default: false,
1051
1184
  description: "Install as a background daemon (same as: ay serve install <flags>)"
1185
+ }).option("local", {
1186
+ type: "boolean",
1187
+ default: false,
1188
+ description: `Use Portless at ${portlessConsoleUrl()} (default for HTTP mode)`
1052
1189
  }).option("allow-spawn", {
1053
1190
  type: "boolean",
1054
1191
  default: false,
@@ -1059,13 +1196,22 @@ Options:
1059
1196
  description: "If another ay serve already hosts this home's WebRTC room, stop it and take the room"
1060
1197
  }).help(false).version(false).exitProcess(false).parseAsync();
1061
1198
  if (argv.daemon) return cmdServeDaemon("install", rest.filter((a) => a !== "--daemon" && a !== "-d"));
1199
+ const wantWebrtc = argv.webrtc !== void 0 || argv.share !== void 0;
1200
+ const wantHttp = argv.http === true || argv.share !== void 0 || argv.webrtc === void 0;
1201
+ const fixedPort = typeof argv.port === "number";
1202
+ const usePortless = wantHttp && !fixedPort;
1203
+ if (argv.local === true && fixedPort) {
1204
+ process.stderr.write("ay serve: --local and --port cannot be used together\n");
1205
+ return 1;
1206
+ }
1207
+ if (usePortless && process.env[PORTLESS_CHILD_ENV] !== "1" && process.env.PORTLESS !== "0") return runWithPortless(rest);
1062
1208
  const stray = argv._.map(String);
1063
1209
  if (stray.length) {
1064
1210
  const hint = stray.includes("share") ? " (did you mean --share?)" : "";
1065
1211
  process.stderr.write(`ay serve: ignoring unknown argument${stray.length > 1 ? "s" : ""}: ${stray.join(" ")}${hint}\n`);
1066
1212
  }
1067
1213
  delete process.env.AGENT_YES_PID;
1068
- const port = argv.port ?? DEFAULT_PORT;
1214
+ const port = argv.port ?? (Number(process.env.PORT) || DEFAULT_PORT);
1069
1215
  const host = argv.host ?? "127.0.0.1";
1070
1216
  const tokenFlag = typeof argv.token === "string" ? argv.token : void 0;
1071
1217
  const certPath = typeof argv["tls-cert"] === "string" ? argv["tls-cert"] : void 0;
@@ -1076,8 +1222,6 @@ Options:
1076
1222
  }
1077
1223
  const useHttps = !!(certPath && keyPath);
1078
1224
  const scheme = useHttps ? "https" : "http";
1079
- const wantWebrtc = argv.webrtc !== void 0 || argv.share !== void 0;
1080
- const wantHttp = argv.http === true || argv.share !== void 0 || argv.webrtc === void 0;
1081
1225
  let releaseHostLock = null;
1082
1226
  if (wantWebrtc && process.env.AGENT_YES_NO_SERVE_LOCK !== "1") {
1083
1227
  const lock = await acquireWebrtcHostLock({ takeover: argv.takeover === true });
@@ -1177,6 +1321,25 @@ Options:
1177
1321
  return [];
1178
1322
  }
1179
1323
  };
1324
+ const statusTextCache = /* @__PURE__ */ new Map();
1325
+ const logStatusText = async (logFile) => {
1326
+ if (!logFile) return null;
1327
+ try {
1328
+ const { size, mtimeMs } = await stat(logFile);
1329
+ const hit = statusTextCache.get(logFile);
1330
+ if (hit && hit.size === size && hit.mtimeMs === mtimeMs) return hit.statusText;
1331
+ const lines = await renderLogTailLines(logFile, 40);
1332
+ const statusText = lines ? parseStatusText(lines) : null;
1333
+ statusTextCache.set(logFile, {
1334
+ size,
1335
+ mtimeMs,
1336
+ statusText
1337
+ });
1338
+ return statusText;
1339
+ } catch {
1340
+ return null;
1341
+ }
1342
+ };
1180
1343
  const niCache = /* @__PURE__ */ new Map();
1181
1344
  const logNeedsInput = async (logFile, cli) => {
1182
1345
  if (!logFile) return null;
@@ -1338,6 +1501,7 @@ Options:
1338
1501
  status: status === "exited" ? status : r.unresponsive ? "stuck" : question ? "needs_input" : status,
1339
1502
  question,
1340
1503
  title: await logTitle(r.log_file),
1504
+ status_text: status === "exited" ? null : await logStatusText(r.log_file),
1341
1505
  git: status === "exited" ? null : await gitStatus(r.cwd),
1342
1506
  tasks: status === "exited" ? null : await logTasks(r.log_file),
1343
1507
  badges: status === "exited" ? [] : await logBadges(r.log_file).then(async (b) => await isUserTyping(r.pid) ? [...b, TYPING_BADGE.id] : b)
@@ -1345,6 +1509,53 @@ Options:
1345
1509
  };
1346
1510
  const presence = /* @__PURE__ */ new Map();
1347
1511
  const PRESENCE_TTL_MS = 12e3;
1512
+ const negoApplied = /* @__PURE__ */ new Map();
1513
+ const negoTimers = /* @__PURE__ */ new Map();
1514
+ const NEGO_DEBOUNCE_MS = 350;
1515
+ const winsizePathFor = (pid) => path.join(process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes"), "winsize", String(pid));
1516
+ const liveCapsFor = (pid) => {
1517
+ const now = Date.now();
1518
+ const caps = [];
1519
+ for (const v of presence.values()) if (now - v.ts <= PRESENCE_TTL_MS && String(v.agent) === String(pid) && v.cap) caps.push(v.cap);
1520
+ return caps;
1521
+ };
1522
+ const applyNego = async (pid) => {
1523
+ negoTimers.delete(pid);
1524
+ const eff = negotiateSize(liveCapsFor(pid));
1525
+ const file = winsizePathFor(pid);
1526
+ const prev = negoApplied.get(pid);
1527
+ try {
1528
+ if (eff) {
1529
+ if (prev && prev.cols === eff.cols && prev.rows === eff.rows) return;
1530
+ const content = `${eff.cols} ${eff.rows} ${Date.now()}\n`;
1531
+ await mkdir(path.dirname(file), { recursive: true });
1532
+ await writeFile(file, content);
1533
+ negoApplied.set(pid, {
1534
+ ...eff,
1535
+ content
1536
+ });
1537
+ } else {
1538
+ if (!prev) return;
1539
+ negoApplied.delete(pid);
1540
+ try {
1541
+ if (await readFile(file, "utf-8") !== prev.content) return;
1542
+ await unlink(file);
1543
+ } catch {
1544
+ return;
1545
+ }
1546
+ }
1547
+ try {
1548
+ process.kill(pid, "SIGWINCH");
1549
+ } catch {}
1550
+ } catch {}
1551
+ };
1552
+ const scheduleNego = (pid) => {
1553
+ if (!Number.isFinite(pid) || pid <= 0 || negoTimers.has(pid)) return;
1554
+ negoTimers.set(pid, setTimeout(() => void applyNego(pid), NEGO_DEBOUNCE_MS));
1555
+ };
1556
+ setInterval(() => {
1557
+ for (const pid of negoApplied.keys()) scheduleNego(pid);
1558
+ }, 5e3);
1348
1559
  const apiFetch = async (req) => {
1349
1560
  if (!checkAuth(req, token)) return new Response("Unauthorized", { status: 401 });
1350
1561
  const url = new URL(req.url);
@@ -1657,7 +1868,7 @@ Options:
1657
1868
  }
1658
1869
  });
1659
1870
  if (req.method === "GET" && p === "/api/ws") {
1660
- const ws = await import("./ws-CSkP2mkD.js");
1871
+ const ws = await import("./ws-CGNmxCPD.js");
1661
1872
  try {
1662
1873
  await ws.loadProvision();
1663
1874
  } catch (e) {
@@ -1677,7 +1888,7 @@ Options:
1677
1888
  if (req.method === "GET" && p === "/api/ws/status") {
1678
1889
  const dirRaw = url.searchParams.get("path");
1679
1890
  if (!dirRaw) return new Response("missing ?path=<workspace dir>", { status: 400 });
1680
- const ws = await import("./ws-CSkP2mkD.js");
1891
+ const ws = await import("./ws-CGNmxCPD.js");
1681
1892
  let prov;
1682
1893
  try {
1683
1894
  prov = await ws.loadProvision();
@@ -1742,7 +1953,8 @@ Options:
1742
1953
  return Response.json({
1743
1954
  pid: record.pid,
1744
1955
  cols: size?.cols ?? null,
1745
- rows: size?.rows ?? null
1956
+ rows: size?.rows ?? null,
1957
+ nego: true
1746
1958
  });
1747
1959
  } catch (e) {
1748
1960
  return new Response(e.message, { status: 404 });
@@ -2023,6 +2235,7 @@ Options:
2023
2235
  }
2024
2236
  const viewer = String(b.viewer ?? "").slice(0, 64);
2025
2237
  if (!viewer) return new Response("missing viewer", { status: 400 });
2238
+ const prevAgent = presence.get(viewer)?.agent;
2026
2239
  if (b.agent == null) presence.delete(viewer);
2027
2240
  else presence.set(viewer, {
2028
2241
  viewer,
@@ -2030,8 +2243,11 @@ Options:
2030
2243
  cols: Math.max(0, Math.floor(Number(b.cols) || 0)),
2031
2244
  rows: Math.max(0, Math.floor(Number(b.rows) || 0)),
2032
2245
  sel: typeof b.sel === "string" ? b.sel.slice(0, 200) : null,
2246
+ cap: sanitizeCap(b.cap),
2033
2247
  ts: Date.now()
2034
2248
  });
2249
+ if (b.agent != null) scheduleNego(Number(b.agent));
2250
+ if (prevAgent != null && String(prevAgent) !== String(b.agent ?? "")) scheduleNego(Number(prevAgent));
2035
2251
  return new Response(null, { status: 204 });
2036
2252
  }
2037
2253
  if (req.method === "GET" && p === "/api/presence") {
@@ -2233,7 +2449,7 @@ Options:
2233
2449
  const perm = body.perm ?? "r";
2234
2450
  if (perm !== "r" && perm !== "rw") return new Response(`invalid perm ${perm} (want r or rw)`, { status: 400 });
2235
2451
  try {
2236
- const { createScopedShare } = await import("./agentShare-99IGGOkv.js");
2452
+ const { createScopedShare } = await import("./agentShare-DdEbHtAn.js");
2237
2453
  const share = await createScopedShare({
2238
2454
  agent: body.agent,
2239
2455
  perm,
@@ -2248,12 +2464,12 @@ Options:
2248
2464
  }
2249
2465
  }
2250
2466
  if (req.method === "GET" && p === "/api/shares") {
2251
- const { listShares } = await import("./agentShare-99IGGOkv.js");
2467
+ const { listShares } = await import("./agentShare-DdEbHtAn.js");
2252
2468
  return Response.json(listShares());
2253
2469
  }
2254
2470
  const revokeM = /^\/api\/share\/([^/]+)$/.exec(p);
2255
2471
  if (req.method === "DELETE" && revokeM) {
2256
- const { revokeShare } = await import("./agentShare-99IGGOkv.js");
2472
+ const { revokeShare } = await import("./agentShare-DdEbHtAn.js");
2257
2473
  const ok = revokeShare(decodeURIComponent(revokeM[1]));
2258
2474
  return new Response(ok ? "revoked" : "no such share", { status: ok ? 200 : 404 });
2259
2475
  }
@@ -2369,17 +2585,18 @@ Options:
2369
2585
  throw e;
2370
2586
  }
2371
2587
  if (server) {
2588
+ const listenPort = server.port;
2372
2589
  const uiHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
2373
- process.stdout.write(`ay serve ${scheme}://${host}:${port}\n`);
2590
+ process.stdout.write(`ay serve ${scheme}://${host}:${listenPort}\n`);
2374
2591
  process.stdout.write(`token: ${token}\n\n`);
2375
2592
  process.stdout.write(`web console (token in the # is eaten on open):\n`);
2376
- process.stdout.write(` ${scheme}://${uiHost}:${port}/#k=${token}\n\n`);
2593
+ process.stdout.write(` ${usePortless ? portlessConsoleUrl(token) : `${scheme}://${uiHost}:${listenPort}/#k=${token}`}\n\n`);
2377
2594
  process.stdout.write(`connect from another machine:\n`);
2378
- process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
2379
- process.stdout.write(` ay tail ${token}@<host>:${port}:<keyword>\n`);
2380
- process.stdout.write(` ay send ${token}@<host>:${port}:<keyword> "message"\n\n`);
2595
+ process.stdout.write(` ay ls ${token}@<host>:${listenPort}\n`);
2596
+ process.stdout.write(` ay tail ${token}@<host>:${listenPort}:<keyword>\n`);
2597
+ process.stdout.write(` ay send ${token}@<host>:${listenPort}:<keyword> "message"\n\n`);
2381
2598
  process.stdout.write(`save as alias:\n`);
2382
- process.stdout.write(` ay remote add <alias> ${scheme}://${token}@<host>:${port}\n\n`);
2599
+ process.stdout.write(` ay remote add <alias> ${scheme}://${token}@<host>:${listenPort}\n\n`);
2383
2600
  if (!useHttps) process.stdout.write("for HTTPS: ay serve --tls-cert cert.pem --tls-key key.pem\n openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'\n\n");
2384
2601
  }
2385
2602
  }
@@ -2388,7 +2605,7 @@ Options:
2388
2605
  const webrtcVal = argv.webrtc ?? argv.share;
2389
2606
  const explicitUrl = typeof webrtcVal === "string" && webrtcVal.startsWith("webrtc://") ? webrtcVal : void 0;
2390
2607
  try {
2391
- const { startShare, loadOrCreateShareRoom } = await import("./share-Ciw1mWVN.js");
2608
+ const { startShare, loadOrCreateShareRoom } = await import("./share-_QYjPN7q.js");
2392
2609
  const linkFile = path.join(process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes"), ".share-link");
2393
2610
  const announce = async (room, link, rotated) => {
2394
2611
  const lead = rotated ? "the room was rejected by signaling (stale generation) — rotated to a fresh link" : "shared over WebRTC — open this link (the token is eaten from the URL on open)";
@@ -2438,7 +2655,7 @@ Options:
2438
2655
  const shutdown = (resolve) => {
2439
2656
  if (heartbeat) clearInterval(heartbeat);
2440
2657
  closeShare?.();
2441
- import("./agentShare-99IGGOkv.js").then((m) => m.revokeAllShares()).catch(() => {});
2658
+ import("./agentShare-DdEbHtAn.js").then((m) => m.revokeAllShares()).catch(() => {});
2442
2659
  server?.stop();
2443
2660
  Promise.resolve(releaseHostLock?.()).catch(() => {}).then(resolve);
2444
2661
  };
@@ -2451,4 +2668,4 @@ Options:
2451
2668
 
2452
2669
  //#endregion
2453
2670
  export { cmdServe };
2454
- //# sourceMappingURL=serve-IvOlaL-7.js.map
2671
+ //# sourceMappingURL=serve-B-ZCy67S.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-IvOlaL-7.js");
35
+ const { cmdServe } = await import("./serve-B-ZCy67S.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-BaKdS-3i.js.map
45
+ //# sourceMappingURL=setup-D6UBzk4-.js.map
@@ -1,4 +1,4 @@
1
1
  import "./e2e-jb0Hp43q.js";
2
- import { n as shareLinkFromRoomUrl, r as startShare, t as loadOrCreateShareRoom } from "./share-BfIU8t_h.js";
2
+ import { n as shareLinkFromRoomUrl, r as startShare, t as loadOrCreateShareRoom } from "./share-vchIyVd8.js";
3
3
 
4
4
  export { loadOrCreateShareRoom, shareLinkFromRoomUrl, startShare };