agent-yes 1.204.0 → 1.206.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/README.md +2 -0
- package/dist/SUPPORTED_CLIS-CQ6e94ln.js +9 -0
- package/dist/{SUPPORTED_CLIS-DqsPHFU9.js → SUPPORTED_CLIS-DQSgURGW.js} +2 -2
- package/dist/{agentShare-TJXTia4N.js → agentShare-k8224qiZ.js} +3 -3
- package/dist/cli.js +4 -4
- package/dist/index.js +2 -2
- package/dist/{notifyDaemon-DAD9Dt9H.js → notifyDaemon-BbVnNvlo.js} +2 -2
- package/dist/{rustBinary-DMx8ljD0.js → rustBinary-BjRvvEs8.js} +2 -2
- package/dist/{schedule-DGxbss_D.js → schedule-DJ-v4yVa.js} +4 -4
- package/dist/{serve-eqRnhz8Z.js → serve-crLypY_c.js} +328 -42
- package/dist/{setup-BgD5RFXj.js → setup-BPNkeSmY.js} +2 -2
- package/dist/{share-Ciw1mWVN.js → share-_QYjPN7q.js} +1 -1
- package/dist/{share-BfIU8t_h.js → share-vchIyVd8.js} +98 -5
- package/dist/{subcommands-3GIzND1f.js → subcommands-DCGZjr4t.js} +1 -1
- package/dist/{subcommands-B7NvTmdk.js → subcommands-DkrMRVNh.js} +8 -8
- package/dist/{ts-D9LzuGpU.js → ts-DnqRreF4.js} +2 -2
- package/dist/{versionChecker-C8ScoMqV.js → versionChecker-d5c6_vgS.js} +2 -2
- package/dist/{ws-BfqEcKyO.js → ws-BmHpWaG_.js} +2 -2
- package/lab/ui/console-logic.js +3 -1
- package/lab/ui/index.html +39 -5
- package/package.json +1 -1
- package/ts/serve.spec.ts +13 -1
- package/ts/serve.ts +200 -29
- package/ts/serveLock.spec.ts +219 -0
- package/ts/serveLock.ts +172 -0
- package/ts/share.ts +80 -1
- package/ts/statusText.spec.ts +17 -0
- package/ts/statusText.ts +19 -0
- package/dist/SUPPORTED_CLIS-BUxPt2CD.js +0 -9
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import "./ts-
|
|
1
|
+
import "./ts-DnqRreF4.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
4
|
-
import { t as findSpawnHiddenLauncher } from "./rustBinary-
|
|
3
|
+
import { r as getInstalledPackage } from "./versionChecker-d5c6_vgS.js";
|
|
4
|
+
import { t as findSpawnHiddenLauncher } from "./rustBinary-BjRvvEs8.js";
|
|
5
|
+
import { t as agentYesHome$1 } from "./agentYesHome-CtHb5b71.js";
|
|
5
6
|
import "./pidStore-BIvsBQ8X.js";
|
|
6
7
|
import { a as updateGlobalPidStatus } from "./globalPidIndex-CoNr7tS8.js";
|
|
7
8
|
import { r as recordInbox } from "./messageLog-CxrKJj77.js";
|
|
8
9
|
import { t as pgidForWrapper } from "./reaper-BUHCyxdF.js";
|
|
9
10
|
import "./configShared-aKTg-sa5.js";
|
|
10
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
11
|
-
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,
|
|
11
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DQSgURGW.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-DkrMRVNh.js";
|
|
12
13
|
import "./e2e-jb0Hp43q.js";
|
|
13
14
|
import "./webrtcLink-B7REGtK2.js";
|
|
14
15
|
import "./remotes-Cim0dBU7.js";
|
|
@@ -16,7 +17,7 @@ import { a as getSpawnHook, c as hasProvisionHook, d as resolveSpawnCwd, i as ge
|
|
|
16
17
|
import { n as liveEnv, t as ensureNodeRuntime } from "./nodeRuntime-CwNJuwH5.js";
|
|
17
18
|
import { r as spawnRejectionReason } from "./spawnGate-CXjEz6Pf.js";
|
|
18
19
|
import yargs from "yargs";
|
|
19
|
-
import { mkdir, open, readFile, stat, unlink, writeFile } from "fs/promises";
|
|
20
|
+
import { mkdir, open, readFile, rename, rm, stat, unlink, writeFile } from "fs/promises";
|
|
20
21
|
import { arch, cpus, freemem, homedir, hostname, loadavg, platform, totalmem, uptime, userInfo } from "os";
|
|
21
22
|
import path from "path";
|
|
22
23
|
import { execFileSync } from "node:child_process";
|
|
@@ -42,6 +43,170 @@ import { existsSync, renameSync, watch, writeFileSync } from "node:fs";
|
|
|
42
43
|
*/
|
|
43
44
|
const isTerminalReply = (s) => /^(?:\x1b\[(?:\??\d+(?:;\d+)*R|\?[\d;]*c|>[\d;]*c|\d*n))+$/.test(s);
|
|
44
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
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region ts/serveLock.ts
|
|
68
|
+
/**
|
|
69
|
+
* Singleton lock for the WebRTC host role: one `ay serve` host per
|
|
70
|
+
* ~/.agent-yes. Two hosts sharing the persisted room (~/.agent-yes/.share-room)
|
|
71
|
+
* fight over every viewer connection — the exact outage seen live: an orphaned
|
|
72
|
+
* `ay serve --webrtc` from a previous manager era plus a manual run left the
|
|
73
|
+
* managed daemon crash-looping (12 watchdog restarts) and the share link
|
|
74
|
+
* unloadable. The lock makes the loser fail FAST with a pointer to the owner
|
|
75
|
+
* instead of silently contending.
|
|
76
|
+
*
|
|
77
|
+
* mkdir-based (atomic on every platform), owner JSON alongside, heartbeat
|
|
78
|
+
* refresh while held. A stale owner — dead pid, or a heartbeat older than
|
|
79
|
+
* SERVE_LOCK_STALE_MS (a SIGKILLed host can't clean up) — is stolen, so a
|
|
80
|
+
* crashed host never wedges the next start.
|
|
81
|
+
*/
|
|
82
|
+
const SERVE_LOCK_STALE_MS = 2e4;
|
|
83
|
+
const SERVE_LOCK_BEAT_MS = 5e3;
|
|
84
|
+
const SERVE_LOCK_GRACE_MS = 12e3;
|
|
85
|
+
function lockDir() {
|
|
86
|
+
return path.join(agentYesHome$1(), "webrtc-host.lock");
|
|
87
|
+
}
|
|
88
|
+
function ownerPath() {
|
|
89
|
+
return path.join(lockDir(), "owner.json");
|
|
90
|
+
}
|
|
91
|
+
function pidAlive(pid) {
|
|
92
|
+
try {
|
|
93
|
+
process.kill(pid, 0);
|
|
94
|
+
return true;
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return e.code === "EPERM";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function readOwner() {
|
|
100
|
+
try {
|
|
101
|
+
const o = JSON.parse(await readFile(ownerPath(), "utf-8"));
|
|
102
|
+
return typeof o?.pid === "number" ? o : null;
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Pure staleness decision, exported for tests. */
|
|
108
|
+
function isOwnerStale(owner, now, alive, staleMs = SERVE_LOCK_STALE_MS) {
|
|
109
|
+
if (!owner) return true;
|
|
110
|
+
if (!alive(owner.pid)) return true;
|
|
111
|
+
return now - owner.beat_at > staleMs;
|
|
112
|
+
}
|
|
113
|
+
async function stampOwner(startedAt) {
|
|
114
|
+
const tmp = `${ownerPath()}.${process.pid}.tmp`;
|
|
115
|
+
try {
|
|
116
|
+
await writeFile(tmp, JSON.stringify({
|
|
117
|
+
pid: process.pid,
|
|
118
|
+
started_at: startedAt,
|
|
119
|
+
beat_at: Date.now()
|
|
120
|
+
}));
|
|
121
|
+
await rename(tmp, ownerPath());
|
|
122
|
+
return true;
|
|
123
|
+
} catch {
|
|
124
|
+
await rm(tmp, { force: true }).catch(() => {});
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Acquire the host lock, retrying for up to `graceMs`. Returns fast on a live
|
|
130
|
+
* owner once the grace expires (never blocks a daemon boot forever). With
|
|
131
|
+
* `takeover`, a live owner is SIGTERM'd (then SIGKILL'd) and the lock stolen.
|
|
132
|
+
*/
|
|
133
|
+
async function acquireWebrtcHostLock(opts) {
|
|
134
|
+
const graceMs = opts?.graceMs ?? SERVE_LOCK_GRACE_MS;
|
|
135
|
+
const staleMs = opts?.staleMs ?? SERVE_LOCK_STALE_MS;
|
|
136
|
+
const beatMs = opts?.beatMs ?? SERVE_LOCK_BEAT_MS;
|
|
137
|
+
const startedAt = Date.now();
|
|
138
|
+
const deadline = startedAt + graceMs;
|
|
139
|
+
let tookOver = false;
|
|
140
|
+
for (;;) try {
|
|
141
|
+
await mkdir(lockDir(), { recursive: false });
|
|
142
|
+
if (!await stampOwner(startedAt)) {
|
|
143
|
+
await rm(lockDir(), {
|
|
144
|
+
recursive: true,
|
|
145
|
+
force: true
|
|
146
|
+
}).catch(() => {});
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
owner: null
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const beat = setInterval(() => {
|
|
153
|
+
(async () => {
|
|
154
|
+
if ((await readOwner())?.pid !== process.pid) {
|
|
155
|
+
clearInterval(beat);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
await stampOwner(startedAt);
|
|
159
|
+
})();
|
|
160
|
+
}, beatMs);
|
|
161
|
+
if (typeof beat.unref === "function") beat.unref();
|
|
162
|
+
return {
|
|
163
|
+
ok: true,
|
|
164
|
+
release: async () => {
|
|
165
|
+
clearInterval(beat);
|
|
166
|
+
if ((await readOwner())?.pid === process.pid) await rm(lockDir(), {
|
|
167
|
+
recursive: true,
|
|
168
|
+
force: true
|
|
169
|
+
}).catch(() => {});
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
} catch (e) {
|
|
173
|
+
const code = e.code;
|
|
174
|
+
if (!isTransientLockMkdirError(code)) throw e;
|
|
175
|
+
if (code !== "EEXIST") {
|
|
176
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const owner = await readOwner();
|
|
180
|
+
if (isOwnerStale(owner, Date.now(), pidAlive, staleMs)) {
|
|
181
|
+
await rm(lockDir(), {
|
|
182
|
+
recursive: true,
|
|
183
|
+
force: true
|
|
184
|
+
}).catch(() => {});
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (opts?.takeover && owner && !tookOver) {
|
|
188
|
+
tookOver = true;
|
|
189
|
+
try {
|
|
190
|
+
process.kill(owner.pid, "SIGTERM");
|
|
191
|
+
} catch {}
|
|
192
|
+
await new Promise((r) => setTimeout(r, opts?.takeoverWaitMs ?? 2e3));
|
|
193
|
+
if (pidAlive(owner.pid)) try {
|
|
194
|
+
process.kill(owner.pid, "SIGKILL");
|
|
195
|
+
} catch {}
|
|
196
|
+
await rm(lockDir(), {
|
|
197
|
+
recursive: true,
|
|
198
|
+
force: true
|
|
199
|
+
}).catch(() => {});
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (Date.now() >= deadline) return {
|
|
203
|
+
ok: false,
|
|
204
|
+
owner
|
|
205
|
+
};
|
|
206
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
45
210
|
//#endregion
|
|
46
211
|
//#region ts/strayServe.ts
|
|
47
212
|
const MANAGEMENT_COMMAND_RE = /\b(?:install|uninstall|status|logs|restart)\b/;
|
|
@@ -91,7 +256,49 @@ async function listStrayServeProcesses() {
|
|
|
91
256
|
|
|
92
257
|
//#endregion
|
|
93
258
|
//#region ts/serve.ts
|
|
94
|
-
const DEFAULT_PORT =
|
|
259
|
+
const DEFAULT_PORT = 0;
|
|
260
|
+
const PORTLESS_APP_NAME = "agent-yes";
|
|
261
|
+
const PORTLESS_CHILD_ENV = "AGENT_YES_PORTLESS_CHILD";
|
|
262
|
+
function portlessConsoleUrl(token) {
|
|
263
|
+
return portlessConsoleUrlForOrigin(process.env.PORTLESS_URL?.replace(/\/$/, "") || `https://${PORTLESS_APP_NAME}.localhost`, token);
|
|
264
|
+
}
|
|
265
|
+
function portlessConsoleUrlForOrigin(origin, token) {
|
|
266
|
+
return `${origin}${token ? `/#k=${encodeURIComponent(token)}` : "/"}`;
|
|
267
|
+
}
|
|
268
|
+
async function resolvedPortlessConsoleUrl(token) {
|
|
269
|
+
if (process.env.PORTLESS_URL) return portlessConsoleUrl(token);
|
|
270
|
+
try {
|
|
271
|
+
const port = Number((await readFile(path.join(homedir(), ".portless", "proxy.port"), "utf-8")).trim());
|
|
272
|
+
if (Number.isInteger(port) && port > 0) return portlessConsoleUrlForOrigin(`https://${PORTLESS_APP_NAME}.localhost${port === 443 ? "" : `:${port}`}`, token);
|
|
273
|
+
} catch {}
|
|
274
|
+
return portlessConsoleUrl(token);
|
|
275
|
+
}
|
|
276
|
+
async function runWithPortless(rest) {
|
|
277
|
+
const portless = Bun.which("portless");
|
|
278
|
+
if (!portless) {
|
|
279
|
+
process.stderr.write("ay serve: Portless is required for local HTTPS. Install it with: npm install -g portless\n");
|
|
280
|
+
return 1;
|
|
281
|
+
}
|
|
282
|
+
const script = process.argv[1];
|
|
283
|
+
const self = script && /\.[cm]?[jt]s$/.test(script) ? [process.execPath, script] : [process.execPath];
|
|
284
|
+
return await Bun.spawn([
|
|
285
|
+
portless,
|
|
286
|
+
PORTLESS_APP_NAME,
|
|
287
|
+
...self,
|
|
288
|
+
"serve",
|
|
289
|
+
...rest
|
|
290
|
+
], {
|
|
291
|
+
stdio: [
|
|
292
|
+
"inherit",
|
|
293
|
+
"inherit",
|
|
294
|
+
"inherit"
|
|
295
|
+
],
|
|
296
|
+
env: {
|
|
297
|
+
...liveEnv(),
|
|
298
|
+
[PORTLESS_CHILD_ENV]: "1"
|
|
299
|
+
}
|
|
300
|
+
}).exited ?? 1;
|
|
301
|
+
}
|
|
95
302
|
/**
|
|
96
303
|
* Normalize a user-supplied GitHub-ish source into the standard
|
|
97
304
|
* `<owner>/<repo>/tree/<branch>` path that codehost/provision's `parseSpec`
|
|
@@ -565,7 +772,18 @@ async function readDaemonServeArgs(mgr) {
|
|
|
565
772
|
}
|
|
566
773
|
function portFromArgs(args) {
|
|
567
774
|
const m = /--port[=\s](\d+)/.exec(args.join(" "));
|
|
568
|
-
return m ? Number(m[1]) :
|
|
775
|
+
return m ? Number(m[1]) : null;
|
|
776
|
+
}
|
|
777
|
+
function argsUsePortless(args) {
|
|
778
|
+
return (args.some((a) => a.startsWith("--http") || a.startsWith("--share")) || !args.some((a) => a.startsWith("--webrtc"))) && portFromArgs(args) === null;
|
|
779
|
+
}
|
|
780
|
+
async function portlessAppPort() {
|
|
781
|
+
try {
|
|
782
|
+
const route = JSON.parse(await readFile(path.join(homedir(), ".portless", "routes.json"), "utf-8")).find((r) => r.hostname === `${PORTLESS_APP_NAME}.localhost`);
|
|
783
|
+
return typeof route?.port === "number" ? route.port : null;
|
|
784
|
+
} catch {
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
569
787
|
}
|
|
570
788
|
async function warnStrayServeProcesses() {
|
|
571
789
|
const stray = await listStrayServeProcesses();
|
|
@@ -586,6 +804,7 @@ function explicitWebrtcUrl(args) {
|
|
|
586
804
|
}
|
|
587
805
|
}
|
|
588
806
|
async function fetchDaemonVersion(port, token) {
|
|
807
|
+
if (port === null) return null;
|
|
589
808
|
try {
|
|
590
809
|
const r = await fetch(`http://127.0.0.1:${port}/api/version`, {
|
|
591
810
|
headers: { Authorization: `Bearer ${token}` },
|
|
@@ -673,7 +892,7 @@ async function cmdServeDaemon(sub, args) {
|
|
|
673
892
|
let shareLink = null;
|
|
674
893
|
let shareLinkMinted = false;
|
|
675
894
|
if (webrtcDaemon) try {
|
|
676
|
-
const { loadOrCreateShareRoom, shareLinkFromRoomUrl } = await import("./share-
|
|
895
|
+
const { loadOrCreateShareRoom, shareLinkFromRoomUrl } = await import("./share-_QYjPN7q.js");
|
|
677
896
|
const explicit = explicitWebrtcUrl(effArgs);
|
|
678
897
|
shareLink = shareLinkFromRoomUrl(explicit ?? await loadOrCreateShareRoom());
|
|
679
898
|
shareLinkMinted = !explicit;
|
|
@@ -688,7 +907,7 @@ async function cmdServeDaemon(sub, args) {
|
|
|
688
907
|
};
|
|
689
908
|
if (priorArgs !== null) {
|
|
690
909
|
const sameConfig = JSON.stringify(effArgs) === JSON.stringify(priorArgs);
|
|
691
|
-
const runningVer = await fetchDaemonVersion(portFromArgs(effArgs), token);
|
|
910
|
+
const runningVer = await fetchDaemonVersion(argsUsePortless(effArgs) ? await portlessAppPort() : portFromArgs(effArgs), token);
|
|
692
911
|
if (runningVer === current && sameConfig) {
|
|
693
912
|
await ensureBootAutostart(mgr);
|
|
694
913
|
process.stdout.write(`'${DAEMON_NAME}' already running v${current} (up to date)\n`);
|
|
@@ -755,7 +974,8 @@ async function cmdServeDaemon(sub, args) {
|
|
|
755
974
|
}).exited;
|
|
756
975
|
if (code === 0) {
|
|
757
976
|
const onBoot = await ensureBootAutostart(mgr);
|
|
758
|
-
const port = portFromArgs(effArgs);
|
|
977
|
+
const port = argsUsePortless(effArgs) ? await portlessAppPort() : portFromArgs(effArgs);
|
|
978
|
+
const localUrl = argsUsePortless(effArgs) ? await resolvedPortlessConsoleUrl(token) : null;
|
|
759
979
|
const httpish = effArgs.some((a) => a.startsWith("--http") || a.startsWith("--share")) || !effArgs.some((a) => a.startsWith("--webrtc"));
|
|
760
980
|
process.stdout.write(`\n${priorArgs !== null ? `rolled '${DAEMON_NAME}' forward to` : `installed '${DAEMON_NAME}' as a daemon via ${mgr.id} —`} v${current}\n`);
|
|
761
981
|
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`);
|
|
@@ -764,8 +984,11 @@ async function cmdServeDaemon(sub, args) {
|
|
|
764
984
|
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`);
|
|
765
985
|
process.stdout.write(`token: ${token}\n\n`);
|
|
766
986
|
if (httpish) {
|
|
767
|
-
process.stdout.write(`
|
|
768
|
-
|
|
987
|
+
if (argsUsePortless(effArgs)) process.stdout.write(` web console: ${localUrl}\n`);
|
|
988
|
+
else if (port !== null) {
|
|
989
|
+
process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
|
|
990
|
+
process.stdout.write(` ay remote add <alias> http://${token}@<host>:${port}\n`);
|
|
991
|
+
}
|
|
769
992
|
}
|
|
770
993
|
process.stdout.write(` ay serve logs # view server logs\n`);
|
|
771
994
|
process.stdout.write(` ay serve uninstall # remove daemon\n`);
|
|
@@ -796,7 +1019,9 @@ async function cmdServeStatus(args) {
|
|
|
796
1019
|
const daemonArgs = mgr ? await readDaemonServeArgs(mgr) : null;
|
|
797
1020
|
const installed = daemonArgs !== null;
|
|
798
1021
|
const a = daemonArgs ?? [];
|
|
799
|
-
const
|
|
1022
|
+
const local = argsUsePortless(a);
|
|
1023
|
+
const port = local ? await portlessAppPort() : portFromArgs(a);
|
|
1024
|
+
const localUrl = local ? await resolvedPortlessConsoleUrl(token ?? void 0) : null;
|
|
800
1025
|
const webrtcish = a.some((x) => x.startsWith("--webrtc") || x.startsWith("--share"));
|
|
801
1026
|
const httpish = a.some((x) => x.startsWith("--http") || x.startsWith("--share")) || !a.some((x) => x.startsWith("--webrtc"));
|
|
802
1027
|
const mode = httpish && webrtcish ? "http+webrtc" : webrtcish ? "webrtc" : "http";
|
|
@@ -808,6 +1033,7 @@ async function cmdServeStatus(args) {
|
|
|
808
1033
|
installed,
|
|
809
1034
|
mode,
|
|
810
1035
|
port: httpish ? port : null,
|
|
1036
|
+
localUrl: httpish ? localUrl : null,
|
|
811
1037
|
reachable: runningVersion !== null,
|
|
812
1038
|
runningVersion,
|
|
813
1039
|
currentVersion: current,
|
|
@@ -823,18 +1049,23 @@ async function cmdServeStatus(args) {
|
|
|
823
1049
|
w(`manager: ${mgr ? mgr.id : "none — install pm2 or oxmgr to daemonize"}`);
|
|
824
1050
|
if (installed) {
|
|
825
1051
|
w(`installed: yes (via ${mgr.id})`);
|
|
826
|
-
w(`mode: ${mode}${httpish ? ` (port ${port})` : ""}`);
|
|
1052
|
+
w(`mode: ${mode}${httpish ? local ? ` (${localUrl})` : ` (port ${port})` : ""}`);
|
|
827
1053
|
if (a.length) w(`args: ${a.join(" ")}`);
|
|
828
1054
|
} else w(`installed: no — start a daemon with: ay serve install [--share]`);
|
|
829
|
-
if (runningVersion !== null)
|
|
830
|
-
|
|
831
|
-
|
|
1055
|
+
if (runningVersion !== null) {
|
|
1056
|
+
const tag = runningVersion === current ? "up to date" : `outdated (current v${current})`;
|
|
1057
|
+
w(`http api: reachable ${local ? `via ${localUrl}` : `on 127.0.0.1:${port}`} — v${runningVersion} (${tag})`);
|
|
1058
|
+
} else if (mode === "webrtc") w(`http api: none (webrtc-only)`);
|
|
1059
|
+
else w(`http api: not reachable ${local ? `via ${localUrl}` : `on 127.0.0.1:${port}`} (not running)`);
|
|
832
1060
|
w(`token: ${token ?? "(none yet — created on first serve)"}`);
|
|
833
1061
|
if (shareLink) w(`share link: ${shareLink}`);
|
|
834
1062
|
if (token && httpish) {
|
|
835
1063
|
w();
|
|
836
|
-
w(`
|
|
837
|
-
|
|
1064
|
+
if (local) w(`console: ${localUrl}`);
|
|
1065
|
+
else if (port !== null) {
|
|
1066
|
+
w(`connect: ay ls ${token}@<host>:${port}`);
|
|
1067
|
+
w(` ay remote add <alias> http://${token}@<host>:${port}`);
|
|
1068
|
+
}
|
|
838
1069
|
}
|
|
839
1070
|
return 0;
|
|
840
1071
|
}
|
|
@@ -855,7 +1086,13 @@ Modes (default: --http):
|
|
|
855
1086
|
--share [URL] Legacy alias for --http --webrtc
|
|
856
1087
|
|
|
857
1088
|
Options:
|
|
858
|
-
--port N
|
|
1089
|
+
--port N Bypass Portless and listen on a fixed HTTP port
|
|
1090
|
+
--host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)
|
|
1091
|
+
--token TOKEN Auth token (auto-generated and saved if omitted)
|
|
1092
|
+
-d, --daemon Install these flags as a background daemon (pm2/oxmgr)
|
|
1093
|
+
(same as: ay serve install <flags>)
|
|
1094
|
+
--local Explicitly use Portless (the default for HTTP mode)
|
|
1095
|
+
${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`);
|
|
859
1096
|
return 0;
|
|
860
1097
|
}
|
|
861
1098
|
const sub = rest[0];
|
|
@@ -875,8 +1112,7 @@ Options:
|
|
|
875
1112
|
if (sub === "install" || sub === "uninstall" || sub === "logs") return cmdServeDaemon(sub, rest.slice(1));
|
|
876
1113
|
const argv = await yargs(rest).usage("Usage: ay serve [options]").option("port", {
|
|
877
1114
|
type: "number",
|
|
878
|
-
|
|
879
|
-
description: "Port to listen on"
|
|
1115
|
+
description: "Bypass Portless and listen on a fixed port"
|
|
880
1116
|
}).option("host", {
|
|
881
1117
|
type: "string",
|
|
882
1118
|
default: "127.0.0.1",
|
|
@@ -904,19 +1140,36 @@ Options:
|
|
|
904
1140
|
type: "boolean",
|
|
905
1141
|
default: false,
|
|
906
1142
|
description: "Install as a background daemon (same as: ay serve install <flags>)"
|
|
1143
|
+
}).option("local", {
|
|
1144
|
+
type: "boolean",
|
|
1145
|
+
default: false,
|
|
1146
|
+
description: `Use Portless at ${portlessConsoleUrl()} (default for HTTP mode)`
|
|
907
1147
|
}).option("allow-spawn", {
|
|
908
1148
|
type: "boolean",
|
|
909
1149
|
default: false,
|
|
910
1150
|
description: "Deprecated no-op — the console can always spawn agents"
|
|
1151
|
+
}).option("takeover", {
|
|
1152
|
+
type: "boolean",
|
|
1153
|
+
default: false,
|
|
1154
|
+
description: "If another ay serve already hosts this home's WebRTC room, stop it and take the room"
|
|
911
1155
|
}).help(false).version(false).exitProcess(false).parseAsync();
|
|
912
1156
|
if (argv.daemon) return cmdServeDaemon("install", rest.filter((a) => a !== "--daemon" && a !== "-d"));
|
|
1157
|
+
const wantWebrtc = argv.webrtc !== void 0 || argv.share !== void 0;
|
|
1158
|
+
const wantHttp = argv.http === true || argv.share !== void 0 || argv.webrtc === void 0;
|
|
1159
|
+
const fixedPort = typeof argv.port === "number";
|
|
1160
|
+
const usePortless = wantHttp && !fixedPort;
|
|
1161
|
+
if (argv.local === true && fixedPort) {
|
|
1162
|
+
process.stderr.write("ay serve: --local and --port cannot be used together\n");
|
|
1163
|
+
return 1;
|
|
1164
|
+
}
|
|
1165
|
+
if (usePortless && process.env[PORTLESS_CHILD_ENV] !== "1" && process.env.PORTLESS !== "0") return runWithPortless(rest);
|
|
913
1166
|
const stray = argv._.map(String);
|
|
914
1167
|
if (stray.length) {
|
|
915
1168
|
const hint = stray.includes("share") ? " (did you mean --share?)" : "";
|
|
916
1169
|
process.stderr.write(`ay serve: ignoring unknown argument${stray.length > 1 ? "s" : ""}: ${stray.join(" ")}${hint}\n`);
|
|
917
1170
|
}
|
|
918
1171
|
delete process.env.AGENT_YES_PID;
|
|
919
|
-
const port = argv.port ?? DEFAULT_PORT;
|
|
1172
|
+
const port = argv.port ?? (Number(process.env.PORT) || DEFAULT_PORT);
|
|
920
1173
|
const host = argv.host ?? "127.0.0.1";
|
|
921
1174
|
const tokenFlag = typeof argv.token === "string" ? argv.token : void 0;
|
|
922
1175
|
const certPath = typeof argv["tls-cert"] === "string" ? argv["tls-cert"] : void 0;
|
|
@@ -927,8 +1180,17 @@ Options:
|
|
|
927
1180
|
}
|
|
928
1181
|
const useHttps = !!(certPath && keyPath);
|
|
929
1182
|
const scheme = useHttps ? "https" : "http";
|
|
930
|
-
|
|
931
|
-
|
|
1183
|
+
let releaseHostLock = null;
|
|
1184
|
+
if (wantWebrtc && process.env.AGENT_YES_NO_SERVE_LOCK !== "1") {
|
|
1185
|
+
const lock = await acquireWebrtcHostLock({ takeover: argv.takeover === true });
|
|
1186
|
+
if (!lock.ok) {
|
|
1187
|
+
const o = lock.owner;
|
|
1188
|
+
const since = o ? new Date(o.started_at).toLocaleString() : "unknown";
|
|
1189
|
+
process.stderr.write(`ay serve: another instance${o ? ` (pid ${o.pid}, started ${since})` : ""} already hosts this home's WebRTC room\n ay serve status # inspect it\n ay serve --webrtc --takeover # stop it and take the room\n AGENT_YES_NO_SERVE_LOCK=1 # opt out (multi-room setups only)\n`);
|
|
1190
|
+
return 1;
|
|
1191
|
+
}
|
|
1192
|
+
releaseHostLock = lock.release;
|
|
1193
|
+
}
|
|
932
1194
|
if (wantHttp && host !== "127.0.0.1" && host !== "localhost") process.stderr.write("ay serve: warning: binding to non-loopback — ensure your network is trusted or use Tailscale/VPN\n");
|
|
933
1195
|
const token = await loadOrCreateToken(tokenFlag);
|
|
934
1196
|
const titleCache = /* @__PURE__ */ new Map();
|
|
@@ -1017,6 +1279,25 @@ Options:
|
|
|
1017
1279
|
return [];
|
|
1018
1280
|
}
|
|
1019
1281
|
};
|
|
1282
|
+
const statusTextCache = /* @__PURE__ */ new Map();
|
|
1283
|
+
const logStatusText = async (logFile) => {
|
|
1284
|
+
if (!logFile) return null;
|
|
1285
|
+
try {
|
|
1286
|
+
const { size, mtimeMs } = await stat(logFile);
|
|
1287
|
+
const hit = statusTextCache.get(logFile);
|
|
1288
|
+
if (hit && hit.size === size && hit.mtimeMs === mtimeMs) return hit.statusText;
|
|
1289
|
+
const lines = await renderLogTailLines(logFile, 40);
|
|
1290
|
+
const statusText = lines ? parseStatusText(lines) : null;
|
|
1291
|
+
statusTextCache.set(logFile, {
|
|
1292
|
+
size,
|
|
1293
|
+
mtimeMs,
|
|
1294
|
+
statusText
|
|
1295
|
+
});
|
|
1296
|
+
return statusText;
|
|
1297
|
+
} catch {
|
|
1298
|
+
return null;
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1020
1301
|
const niCache = /* @__PURE__ */ new Map();
|
|
1021
1302
|
const logNeedsInput = async (logFile, cli) => {
|
|
1022
1303
|
if (!logFile) return null;
|
|
@@ -1178,6 +1459,7 @@ Options:
|
|
|
1178
1459
|
status: status === "exited" ? status : r.unresponsive ? "stuck" : question ? "needs_input" : status,
|
|
1179
1460
|
question,
|
|
1180
1461
|
title: await logTitle(r.log_file),
|
|
1462
|
+
status_text: status === "exited" ? null : await logStatusText(r.log_file),
|
|
1181
1463
|
git: status === "exited" ? null : await gitStatus(r.cwd),
|
|
1182
1464
|
tasks: status === "exited" ? null : await logTasks(r.log_file),
|
|
1183
1465
|
badges: status === "exited" ? [] : await logBadges(r.log_file).then(async (b) => await isUserTyping(r.pid) ? [...b, TYPING_BADGE.id] : b)
|
|
@@ -1497,7 +1779,7 @@ Options:
|
|
|
1497
1779
|
}
|
|
1498
1780
|
});
|
|
1499
1781
|
if (req.method === "GET" && p === "/api/ws") {
|
|
1500
|
-
const ws = await import("./ws-
|
|
1782
|
+
const ws = await import("./ws-BmHpWaG_.js");
|
|
1501
1783
|
try {
|
|
1502
1784
|
await ws.loadProvision();
|
|
1503
1785
|
} catch (e) {
|
|
@@ -1517,7 +1799,7 @@ Options:
|
|
|
1517
1799
|
if (req.method === "GET" && p === "/api/ws/status") {
|
|
1518
1800
|
const dirRaw = url.searchParams.get("path");
|
|
1519
1801
|
if (!dirRaw) return new Response("missing ?path=<workspace dir>", { status: 400 });
|
|
1520
|
-
const ws = await import("./ws-
|
|
1802
|
+
const ws = await import("./ws-BmHpWaG_.js");
|
|
1521
1803
|
let prov;
|
|
1522
1804
|
try {
|
|
1523
1805
|
prov = await ws.loadProvision();
|
|
@@ -2073,7 +2355,7 @@ Options:
|
|
|
2073
2355
|
const perm = body.perm ?? "r";
|
|
2074
2356
|
if (perm !== "r" && perm !== "rw") return new Response(`invalid perm ${perm} (want r or rw)`, { status: 400 });
|
|
2075
2357
|
try {
|
|
2076
|
-
const { createScopedShare } = await import("./agentShare-
|
|
2358
|
+
const { createScopedShare } = await import("./agentShare-k8224qiZ.js");
|
|
2077
2359
|
const share = await createScopedShare({
|
|
2078
2360
|
agent: body.agent,
|
|
2079
2361
|
perm,
|
|
@@ -2088,12 +2370,12 @@ Options:
|
|
|
2088
2370
|
}
|
|
2089
2371
|
}
|
|
2090
2372
|
if (req.method === "GET" && p === "/api/shares") {
|
|
2091
|
-
const { listShares } = await import("./agentShare-
|
|
2373
|
+
const { listShares } = await import("./agentShare-k8224qiZ.js");
|
|
2092
2374
|
return Response.json(listShares());
|
|
2093
2375
|
}
|
|
2094
2376
|
const revokeM = /^\/api\/share\/([^/]+)$/.exec(p);
|
|
2095
2377
|
if (req.method === "DELETE" && revokeM) {
|
|
2096
|
-
const { revokeShare } = await import("./agentShare-
|
|
2378
|
+
const { revokeShare } = await import("./agentShare-k8224qiZ.js");
|
|
2097
2379
|
const ok = revokeShare(decodeURIComponent(revokeM[1]));
|
|
2098
2380
|
return new Response(ok ? "revoked" : "no such share", { status: ok ? 200 : 404 });
|
|
2099
2381
|
}
|
|
@@ -2209,17 +2491,18 @@ Options:
|
|
|
2209
2491
|
throw e;
|
|
2210
2492
|
}
|
|
2211
2493
|
if (server) {
|
|
2494
|
+
const listenPort = server.port;
|
|
2212
2495
|
const uiHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
2213
|
-
process.stdout.write(`ay serve ${scheme}://${host}:${
|
|
2496
|
+
process.stdout.write(`ay serve ${scheme}://${host}:${listenPort}\n`);
|
|
2214
2497
|
process.stdout.write(`token: ${token}\n\n`);
|
|
2215
2498
|
process.stdout.write(`web console (token in the # is eaten on open):\n`);
|
|
2216
|
-
process.stdout.write(` ${scheme}://${uiHost}:${
|
|
2499
|
+
process.stdout.write(` ${usePortless ? portlessConsoleUrl(token) : `${scheme}://${uiHost}:${listenPort}/#k=${token}`}\n\n`);
|
|
2217
2500
|
process.stdout.write(`connect from another machine:\n`);
|
|
2218
|
-
process.stdout.write(` ay ls ${token}@<host>:${
|
|
2219
|
-
process.stdout.write(` ay tail ${token}@<host>:${
|
|
2220
|
-
process.stdout.write(` ay send ${token}@<host>:${
|
|
2501
|
+
process.stdout.write(` ay ls ${token}@<host>:${listenPort}\n`);
|
|
2502
|
+
process.stdout.write(` ay tail ${token}@<host>:${listenPort}:<keyword>\n`);
|
|
2503
|
+
process.stdout.write(` ay send ${token}@<host>:${listenPort}:<keyword> "message"\n\n`);
|
|
2221
2504
|
process.stdout.write(`save as alias:\n`);
|
|
2222
|
-
process.stdout.write(` ay remote add <alias> ${scheme}://${token}@<host>:${
|
|
2505
|
+
process.stdout.write(` ay remote add <alias> ${scheme}://${token}@<host>:${listenPort}\n\n`);
|
|
2223
2506
|
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");
|
|
2224
2507
|
}
|
|
2225
2508
|
}
|
|
@@ -2228,7 +2511,7 @@ Options:
|
|
|
2228
2511
|
const webrtcVal = argv.webrtc ?? argv.share;
|
|
2229
2512
|
const explicitUrl = typeof webrtcVal === "string" && webrtcVal.startsWith("webrtc://") ? webrtcVal : void 0;
|
|
2230
2513
|
try {
|
|
2231
|
-
const { startShare, loadOrCreateShareRoom } = await import("./share-
|
|
2514
|
+
const { startShare, loadOrCreateShareRoom } = await import("./share-_QYjPN7q.js");
|
|
2232
2515
|
const linkFile = path.join(process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes"), ".share-link");
|
|
2233
2516
|
const announce = async (room, link, rotated) => {
|
|
2234
2517
|
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)";
|
|
@@ -2256,7 +2539,10 @@ Options:
|
|
|
2256
2539
|
await announce(room, link, false);
|
|
2257
2540
|
} catch (e) {
|
|
2258
2541
|
process.stderr.write(`ay serve --webrtc failed: ${e.message}\n`);
|
|
2259
|
-
if (!wantHttp)
|
|
2542
|
+
if (!wantHttp) {
|
|
2543
|
+
await releaseHostLock?.().catch(() => {});
|
|
2544
|
+
return 1;
|
|
2545
|
+
}
|
|
2260
2546
|
}
|
|
2261
2547
|
}
|
|
2262
2548
|
let heartbeat;
|
|
@@ -2275,9 +2561,9 @@ Options:
|
|
|
2275
2561
|
const shutdown = (resolve) => {
|
|
2276
2562
|
if (heartbeat) clearInterval(heartbeat);
|
|
2277
2563
|
closeShare?.();
|
|
2278
|
-
import("./agentShare-
|
|
2564
|
+
import("./agentShare-k8224qiZ.js").then((m) => m.revokeAllShares()).catch(() => {});
|
|
2279
2565
|
server?.stop();
|
|
2280
|
-
resolve();
|
|
2566
|
+
Promise.resolve(releaseHostLock?.()).catch(() => {}).then(resolve);
|
|
2281
2567
|
};
|
|
2282
2568
|
await new Promise((resolve) => {
|
|
2283
2569
|
process.on("SIGINT", () => shutdown(resolve));
|
|
@@ -2288,4 +2574,4 @@ Options:
|
|
|
2288
2574
|
|
|
2289
2575
|
//#endregion
|
|
2290
2576
|
export { cmdServe };
|
|
2291
|
-
//# sourceMappingURL=serve-
|
|
2577
|
+
//# sourceMappingURL=serve-crLypY_c.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-
|
|
35
|
+
const { cmdServe } = await import("./serve-crLypY_c.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-
|
|
45
|
+
//# sourceMappingURL=setup-BPNkeSmY.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-
|
|
2
|
+
import { n as shareLinkFromRoomUrl, r as startShare, t as loadOrCreateShareRoom } from "./share-vchIyVd8.js";
|
|
3
3
|
|
|
4
4
|
export { loadOrCreateShareRoom, shareLinkFromRoomUrl, startShare };
|