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.
- package/dist/SUPPORTED_CLIS-DK9PO6Y6.js +8 -0
- package/dist/{SUPPORTED_CLIS-Co1JpoEr.js → SUPPORTED_CLIS-DQYx5cvl.js} +2 -2
- package/dist/cli.js +3 -3
- package/dist/index.js +2 -2
- package/dist/{serve-D7Hj5j6W.js → serve-DJthZQAN.js} +113 -11
- package/dist/{subcommands-b3HW9zZo.js → subcommands-B_JJRHkV.js} +2 -2
- package/dist/{subcommands-BpU68RGV.js → subcommands-BuLieGot.js} +1 -1
- package/dist/{ts-8yOJK-_k.js → ts-B4lhxCQx.js} +2 -2
- package/dist/{versionChecker-Coc3Y6Q5.js → versionChecker-CS7qsffQ.js} +2 -2
- package/lab/ui/index.html +64 -24
- package/package.json +1 -1
- package/ts/serve.ts +144 -7
- package/dist/SUPPORTED_CLIS-DF0iApn5.js +0 -8
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
1
|
+
import "./ts-B4lhxCQx.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
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-
|
|
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-
|
|
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
|
-
...
|
|
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
|
|
84
|
-
const port =
|
|
85
|
-
const webrtcish =
|
|
86
|
-
const httpish =
|
|
87
|
-
process.stdout.write(`\
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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.
|
|
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-
|
|
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
|
|
1622
|
-
|
|
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 && !
|
|
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
|
-
//
|
|
1646
|
-
//
|
|
1647
|
-
//
|
|
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(
|
|
1651
|
-
|
|
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
|
-
|
|
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
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", ...
|
|
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
|
|
98
|
-
const 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 =
|
|
222
|
+
const webrtcish = effArgs.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"));
|
|
101
223
|
const httpish =
|
|
102
|
-
|
|
103
|
-
!
|
|
104
|
-
process.stdout.write(
|
|
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 };
|