agent-yes 1.112.1 → 1.113.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-DxYk0BWq.js → SUPPORTED_CLIS-BpO7ZYx_.js} +2 -2
- package/dist/SUPPORTED_CLIS-DtJakX4G.js +8 -0
- package/dist/cli.js +3 -3
- package/dist/index.js +2 -2
- package/dist/{serve-B6jO9ifl.js → serve-YPd9gtRO.js} +78 -5
- package/dist/{subcommands-BdLzSIjI.js → subcommands-DU5RXU0g.js} +1 -1
- package/dist/{subcommands-CCV37_dc.js → subcommands-Za5uwCq6.js} +2 -2
- package/dist/{ts-gFuntNxO.js → ts-ChVJOKNr.js} +2 -2
- package/dist/{versionChecker-DlY0TAHI.js → versionChecker-wxlWV_VT.js} +2 -2
- package/lab/ui/index.html +95 -20
- package/package.json +1 -1
- package/ts/serve.ts +87 -0
- package/dist/SUPPORTED_CLIS-OeK9JJli.js +0 -8
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { t as CLIS_CONFIG } from "./ts-ChVJOKNr.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-BpO7ZYx_.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./ts-ChVJOKNr.js";
|
|
2
|
+
import "./logger-B9h0djqx.js";
|
|
3
|
+
import "./versionChecker-wxlWV_VT.js";
|
|
4
|
+
import "./pidStore-DBjlqzo8.js";
|
|
5
|
+
import "./globalPidIndex-yVd3mbsV.js";
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-BpO7ZYx_.js";
|
|
7
|
+
|
|
8
|
+
export { SUPPORTED_CLIS };
|
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-wxlWV_VT.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-DU5RXU0g.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-DtJakX4G.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-ChVJOKNr.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-wxlWV_VT.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-ChVJOKNr.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-wxlWV_VT.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-BpO7ZYx_.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-Za5uwCq6.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";
|
|
@@ -241,6 +241,79 @@ Options:
|
|
|
241
241
|
return new Response(e.message, { status: 500 });
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
|
+
if (req.method === "GET" && p === "/api/ls/subscribe") {
|
|
245
|
+
const keyword = url.searchParams.get("keyword") ?? void 0;
|
|
246
|
+
const opts = defaultOpts({
|
|
247
|
+
all: url.searchParams.get("all") === "1",
|
|
248
|
+
active: url.searchParams.get("active") === "1"
|
|
249
|
+
});
|
|
250
|
+
const enc = new TextEncoder();
|
|
251
|
+
const stream = new ReadableStream({ async start(ctrl) {
|
|
252
|
+
let closed = false;
|
|
253
|
+
const send = (obj) => {
|
|
254
|
+
try {
|
|
255
|
+
ctrl.enqueue(enc.encode(`data: ${JSON.stringify(obj)}\n\n`));
|
|
256
|
+
} catch {}
|
|
257
|
+
};
|
|
258
|
+
const sent = /* @__PURE__ */ new Map();
|
|
259
|
+
const compute = async () => {
|
|
260
|
+
const records = await listRecords(keyword, opts);
|
|
261
|
+
return Promise.all(records.map(async (r) => ({
|
|
262
|
+
...r,
|
|
263
|
+
title: await logTitle(r.log_file)
|
|
264
|
+
})));
|
|
265
|
+
};
|
|
266
|
+
const tick = async (first) => {
|
|
267
|
+
if (closed) return;
|
|
268
|
+
const list = await compute().catch(() => null);
|
|
269
|
+
if (!list) return;
|
|
270
|
+
const upsert = [];
|
|
271
|
+
const seen = /* @__PURE__ */ new Set();
|
|
272
|
+
for (const r of list) {
|
|
273
|
+
seen.add(r.pid);
|
|
274
|
+
const j = JSON.stringify(r);
|
|
275
|
+
if (sent.get(r.pid) !== j) {
|
|
276
|
+
upsert.push(r);
|
|
277
|
+
sent.set(r.pid, j);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const remove = [];
|
|
281
|
+
for (const pid of sent.keys()) if (!seen.has(pid)) {
|
|
282
|
+
remove.push(pid);
|
|
283
|
+
sent.delete(pid);
|
|
284
|
+
}
|
|
285
|
+
if (first) send({
|
|
286
|
+
full: true,
|
|
287
|
+
upsert: list,
|
|
288
|
+
remove: []
|
|
289
|
+
});
|
|
290
|
+
else if (upsert.length || remove.length) send({
|
|
291
|
+
upsert,
|
|
292
|
+
remove
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
await tick(true);
|
|
296
|
+
const timer = setInterval(() => void tick(false), 1e3);
|
|
297
|
+
const heartbeat = setInterval(() => {
|
|
298
|
+
try {
|
|
299
|
+
ctrl.enqueue(enc.encode(": ping\n\n"));
|
|
300
|
+
} catch {}
|
|
301
|
+
}, 15e3);
|
|
302
|
+
req.signal.addEventListener("abort", () => {
|
|
303
|
+
closed = true;
|
|
304
|
+
clearInterval(timer);
|
|
305
|
+
clearInterval(heartbeat);
|
|
306
|
+
try {
|
|
307
|
+
ctrl.close();
|
|
308
|
+
} catch {}
|
|
309
|
+
});
|
|
310
|
+
} });
|
|
311
|
+
return new Response(stream, { headers: {
|
|
312
|
+
"Content-Type": "text/event-stream",
|
|
313
|
+
"Cache-Control": "no-cache",
|
|
314
|
+
Connection: "keep-alive"
|
|
315
|
+
} });
|
|
316
|
+
}
|
|
244
317
|
if (req.method === "GET" && p === "/api/whoami") {
|
|
245
318
|
let user = "";
|
|
246
319
|
try {
|
|
@@ -560,4 +633,4 @@ Options:
|
|
|
560
633
|
|
|
561
634
|
//#endregion
|
|
562
635
|
export { cmdServe };
|
|
563
|
-
//# sourceMappingURL=serve-
|
|
636
|
+
//# sourceMappingURL=serve-YPd9gtRO.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-Za5uwCq6.js";
|
|
5
5
|
|
|
6
6
|
export { cmdHelp, isSubcommand, runSubcommand };
|
|
@@ -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-YPd9gtRO.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-Za5uwCq6.js.map
|
|
@@ -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-wxlWV_VT.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";
|
|
@@ -1714,4 +1714,4 @@ function sleep(ms) {
|
|
|
1714
1714
|
|
|
1715
1715
|
//#endregion
|
|
1716
1716
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1717
|
-
//# sourceMappingURL=ts-
|
|
1717
|
+
//# sourceMappingURL=ts-ChVJOKNr.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.113.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-wxlWV_VT.js.map
|
package/lab/ui/index.html
CHANGED
|
@@ -1227,7 +1227,7 @@
|
|
|
1227
1227
|
const hasToken = !!localStorage.getItem("ay.localToken");
|
|
1228
1228
|
const enabled = isLocalhost || hasToken || Object.keys(loadRooms()).length === 0;
|
|
1229
1229
|
if (enabled && !sources.has(LOCAL)) {
|
|
1230
|
-
|
|
1230
|
+
const s = {
|
|
1231
1231
|
id: LOCAL,
|
|
1232
1232
|
host: "local",
|
|
1233
1233
|
kind: "local",
|
|
@@ -1237,8 +1237,11 @@
|
|
|
1237
1237
|
tried: true, // no connect phase — polled directly
|
|
1238
1238
|
devices: new Set(),
|
|
1239
1239
|
serverCount: 0,
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1240
|
+
};
|
|
1241
|
+
sources.set(LOCAL, s);
|
|
1242
|
+
subscribeSource(s);
|
|
1243
|
+
} else if (!enabled && sources.has(LOCAL)) {
|
|
1244
|
+
unsubscribeSource(sources.get(LOCAL));
|
|
1242
1245
|
sources.delete(LOCAL);
|
|
1243
1246
|
}
|
|
1244
1247
|
}
|
|
@@ -1246,30 +1249,87 @@
|
|
|
1246
1249
|
// Pull one source's agent list, tagging each row with its origin + a
|
|
1247
1250
|
// composite key (pids can collide across rooms). Updates the source's live
|
|
1248
1251
|
// flag, device set, and server count for the rooms panel.
|
|
1252
|
+
// Stamp a source's raw /api/ls records with their origin + composite key
|
|
1253
|
+
// (pids collide across rooms) and recompute the source's device set + server
|
|
1254
|
+
// count for the rooms panel. Pure transform of the source's current records.
|
|
1255
|
+
function stampAgents(s) {
|
|
1256
|
+
s.devices = new Set();
|
|
1257
|
+
const out = [...(s.byPid?.values() || [])].map((e) => {
|
|
1258
|
+
// codehost stamps _host per agent; agent-yes share rooms fall back to
|
|
1259
|
+
// the room's device label (from /api/whoami). Local stays unlabelled
|
|
1260
|
+
// (no deviceLabel) so a single-machine view keeps its clean path-only
|
|
1261
|
+
// identity instead of a blank "@:" prefix.
|
|
1262
|
+
const host = e._host || s.deviceLabel || "";
|
|
1263
|
+
if (host) s.devices.add(host);
|
|
1264
|
+
return { ...e, _room: s.id, _key: s.id + "#" + e.pid, _host: host };
|
|
1265
|
+
});
|
|
1266
|
+
s.serverCount =
|
|
1267
|
+
s.kind === "ch" ? s.client?.hosts().length || 0 : s.devices.size || (s.live ? 1 : 0);
|
|
1268
|
+
s.agents = out;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// Replace a source's whole record set (full /api/ls fetch or a stream
|
|
1272
|
+
// snapshot). Resets byPid so later stream deltas diff against it cleanly.
|
|
1273
|
+
function applyFull(s, arr) {
|
|
1274
|
+
s.byPid = new Map((Array.isArray(arr) ? arr : []).map((r) => [r.pid, r]));
|
|
1275
|
+
s.live = true;
|
|
1276
|
+
stampAgents(s);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Apply one stream event: a full snapshot, or an incremental
|
|
1280
|
+
// { upsert:[changed], remove:[gone pids] } delta from /api/ls/subscribe.
|
|
1281
|
+
function applyDelta(s, ev) {
|
|
1282
|
+
if (ev.full) return applyFull(s, ev.upsert);
|
|
1283
|
+
if (!s.byPid) s.byPid = new Map();
|
|
1284
|
+
for (const r of ev.upsert || []) s.byPid.set(r.pid, r);
|
|
1285
|
+
for (const pid of ev.remove || []) s.byPid.delete(pid);
|
|
1286
|
+
s.live = true;
|
|
1287
|
+
stampAgents(s);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// Fallback poll for a source that isn't streaming (older host with no
|
|
1291
|
+
// /api/ls/subscribe, or before its first delta lands).
|
|
1249
1292
|
async function listSource(s) {
|
|
1250
1293
|
try {
|
|
1251
|
-
|
|
1252
|
-
s.
|
|
1253
|
-
s.devices = new Set();
|
|
1254
|
-
const out = (Array.isArray(arr) ? arr : []).map((e) => {
|
|
1255
|
-
// codehost stamps _host per agent; agent-yes share rooms fall back to
|
|
1256
|
-
// the room's device label (from /api/whoami). Local stays unlabelled
|
|
1257
|
-
// (no deviceLabel) so a single-machine view keeps its clean path-only
|
|
1258
|
-
// identity instead of a blank "@:" prefix.
|
|
1259
|
-
const host = e._host || s.deviceLabel || "";
|
|
1260
|
-
if (host) s.devices.add(host);
|
|
1261
|
-
return { ...e, _room: s.id, _key: s.id + "#" + e.pid, _host: host };
|
|
1262
|
-
});
|
|
1263
|
-
s.serverCount =
|
|
1264
|
-
s.kind === "ch" ? s.client?.hosts().length || 0 : s.devices.size || (s.live ? 1 : 0);
|
|
1265
|
-
return out;
|
|
1294
|
+
applyFull(s, await s.tx.fetchJSON("/api/ls?all=1"));
|
|
1295
|
+
return s.agents;
|
|
1266
1296
|
} catch {
|
|
1267
1297
|
s.live = false;
|
|
1268
1298
|
s.serverCount = 0;
|
|
1299
|
+
s.byPid = new Map();
|
|
1300
|
+
s.agents = [];
|
|
1269
1301
|
return [];
|
|
1270
1302
|
}
|
|
1271
1303
|
}
|
|
1272
1304
|
|
|
1305
|
+
// Subscribe to a source's throttled delta stream instead of re-polling its
|
|
1306
|
+
// whole list. The first event flips `streaming` on, so the reconcile poll
|
|
1307
|
+
// skips this source — no duplicate full-list bytes on the wire. Older hosts
|
|
1308
|
+
// without the endpoint never send an event, so the poll keeps covering them.
|
|
1309
|
+
function subscribeSource(s) {
|
|
1310
|
+
if (!s.tx || s.unsub) return;
|
|
1311
|
+
s.unsub = s.tx.subscribe(
|
|
1312
|
+
"/api/ls/subscribe?all=1",
|
|
1313
|
+
(ev) => {
|
|
1314
|
+
if (!ev || typeof ev !== "object") return;
|
|
1315
|
+
s.streaming = true;
|
|
1316
|
+
applyDelta(s, ev);
|
|
1317
|
+
mergeRender();
|
|
1318
|
+
},
|
|
1319
|
+
null,
|
|
1320
|
+
() => {
|
|
1321
|
+
s.streaming = false;
|
|
1322
|
+
},
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
function unsubscribeSource(s) {
|
|
1326
|
+
try {
|
|
1327
|
+
s.unsub?.();
|
|
1328
|
+
} catch {}
|
|
1329
|
+
s.unsub = null;
|
|
1330
|
+
s.streaming = false;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1273
1333
|
// Compact list: one line per agent (dot + cli + title), persisted per device.
|
|
1274
1334
|
let compactList = localStorage.getItem("ay.compactList") === "1";
|
|
1275
1335
|
|
|
@@ -1354,10 +1414,23 @@
|
|
|
1354
1414
|
// composite key (room#pid) or a bare pid (?pid= deep links, legacy ay.sel).
|
|
1355
1415
|
const matchSel = (e, token) => e._key === token || String(e.pid) === String(token);
|
|
1356
1416
|
|
|
1417
|
+
// Reconcile poll: refresh only the sources that aren't streaming (older
|
|
1418
|
+
// hosts, or ones whose stream hasn't delivered its first snapshot yet) and
|
|
1419
|
+
// re-render. Streaming sources keep themselves current via subscribeSource,
|
|
1420
|
+
// so this stays a no-op on the wire for a modern fleet — it still re-renders
|
|
1421
|
+
// every tick, which keeps the relative "age" column fresh.
|
|
1357
1422
|
async function loadList() {
|
|
1358
1423
|
const srcs = [...sources.values()];
|
|
1359
|
-
|
|
1360
|
-
|
|
1424
|
+
await Promise.all(srcs.filter((s) => !s.streaming).map(listSource));
|
|
1425
|
+
mergeRender();
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// Merge every source's stamped agents into the flat `entries` list, update
|
|
1429
|
+
// the connection badge + rooms panel, and re-render. Called after every poll
|
|
1430
|
+
// and every stream delta.
|
|
1431
|
+
function mergeRender() {
|
|
1432
|
+
const srcs = [...sources.values()];
|
|
1433
|
+
entries = srcs.flatMap((s) => s.agents || []);
|
|
1361
1434
|
// Badge: total agents + how many rooms are live. Red only when nothing
|
|
1362
1435
|
// at all is reachable (no source answered).
|
|
1363
1436
|
const roomSrcs = srcs.filter((s) => s.id !== LOCAL);
|
|
@@ -1636,6 +1709,7 @@
|
|
|
1636
1709
|
function removeSource(room) {
|
|
1637
1710
|
const s = sources.get(room);
|
|
1638
1711
|
if (!s) return;
|
|
1712
|
+
unsubscribeSource(s);
|
|
1639
1713
|
try {
|
|
1640
1714
|
s.client?.close?.();
|
|
1641
1715
|
s.client?.pc?.close?.();
|
|
@@ -1703,6 +1777,7 @@
|
|
|
1703
1777
|
}
|
|
1704
1778
|
s.tried = true;
|
|
1705
1779
|
renderRoomsIfOpen();
|
|
1780
|
+
if (s.live) subscribeSource(s);
|
|
1706
1781
|
loadList();
|
|
1707
1782
|
}
|
|
1708
1783
|
|
package/package.json
CHANGED
package/ts/serve.ts
CHANGED
|
@@ -320,6 +320,93 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
// GET /api/ls/subscribe — SSE: throttled live deltas of the agent list.
|
|
324
|
+
// The console used to re-poll /api/ls every 3s; this streams the SAME records
|
|
325
|
+
// (incl. each agent's OSC title) but only what CHANGED since the last tick, so
|
|
326
|
+
// an idle fleet costs ~nothing on the wire. The first event is a full snapshot
|
|
327
|
+
// ({ full:true, upsert:[all] }); each later event carries { upsert:[changed
|
|
328
|
+
// records], remove:[gone pids] }. listRecords is a couple of JSONL reads and
|
|
329
|
+
// logTitle is cached by (size,mtime), so the 1s tick stays cheap.
|
|
330
|
+
if (req.method === "GET" && p === "/api/ls/subscribe") {
|
|
331
|
+
const keyword = url.searchParams.get("keyword") ?? undefined;
|
|
332
|
+
const opts = defaultOpts({
|
|
333
|
+
all: url.searchParams.get("all") === "1",
|
|
334
|
+
active: url.searchParams.get("active") === "1",
|
|
335
|
+
});
|
|
336
|
+
const enc = new TextEncoder();
|
|
337
|
+
const stream = new ReadableStream({
|
|
338
|
+
async start(ctrl) {
|
|
339
|
+
let closed = false;
|
|
340
|
+
const send = (obj: unknown) => {
|
|
341
|
+
try {
|
|
342
|
+
ctrl.enqueue(enc.encode(`data: ${JSON.stringify(obj)}\n\n`));
|
|
343
|
+
} catch {
|
|
344
|
+
/* stream already closed */
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
// pid -> JSON of the last record we sent, for cheap change detection.
|
|
348
|
+
const sent = new Map<number, string>();
|
|
349
|
+
const compute = async () => {
|
|
350
|
+
const records = await listRecords(keyword, opts);
|
|
351
|
+
return Promise.all(
|
|
352
|
+
records.map(async (r) => ({ ...r, title: await logTitle(r.log_file) })),
|
|
353
|
+
);
|
|
354
|
+
};
|
|
355
|
+
const tick = async (first: boolean) => {
|
|
356
|
+
if (closed) return;
|
|
357
|
+
// Transient read error → skip this tick, retry on the next.
|
|
358
|
+
const list = await compute().catch(() => null);
|
|
359
|
+
if (!list) return;
|
|
360
|
+
const upsert: typeof list = [];
|
|
361
|
+
const seen = new Set<number>();
|
|
362
|
+
for (const r of list) {
|
|
363
|
+
seen.add(r.pid);
|
|
364
|
+
const j = JSON.stringify(r);
|
|
365
|
+
if (sent.get(r.pid) !== j) {
|
|
366
|
+
upsert.push(r);
|
|
367
|
+
sent.set(r.pid, j);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const remove: number[] = [];
|
|
371
|
+
for (const pid of sent.keys())
|
|
372
|
+
if (!seen.has(pid)) {
|
|
373
|
+
remove.push(pid);
|
|
374
|
+
sent.delete(pid);
|
|
375
|
+
}
|
|
376
|
+
if (first) send({ full: true, upsert: list, remove: [] });
|
|
377
|
+
else if (upsert.length || remove.length) send({ upsert, remove });
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
await tick(true);
|
|
381
|
+
const timer = setInterval(() => void tick(false), 1000);
|
|
382
|
+
const heartbeat = setInterval(() => {
|
|
383
|
+
try {
|
|
384
|
+
ctrl.enqueue(enc.encode(": ping\n\n"));
|
|
385
|
+
} catch {
|
|
386
|
+
/* closed */
|
|
387
|
+
}
|
|
388
|
+
}, 15_000);
|
|
389
|
+
req.signal.addEventListener("abort", () => {
|
|
390
|
+
closed = true;
|
|
391
|
+
clearInterval(timer);
|
|
392
|
+
clearInterval(heartbeat);
|
|
393
|
+
try {
|
|
394
|
+
ctrl.close();
|
|
395
|
+
} catch {
|
|
396
|
+
/* already closed */
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
return new Response(stream, {
|
|
402
|
+
headers: {
|
|
403
|
+
"Content-Type": "text/event-stream",
|
|
404
|
+
"Cache-Control": "no-cache",
|
|
405
|
+
Connection: "keep-alive",
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
323
410
|
// GET /api/whoami — this host's device label (user@host), so a remote
|
|
324
411
|
// console can tag each agent with the machine it came from. Unlike codehost,
|
|
325
412
|
// `ay serve --share` carries no per-agent device id; the viewer fetches this
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import "./ts-gFuntNxO.js";
|
|
2
|
-
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-DlY0TAHI.js";
|
|
4
|
-
import "./pidStore-DBjlqzo8.js";
|
|
5
|
-
import "./globalPidIndex-yVd3mbsV.js";
|
|
6
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DxYk0BWq.js";
|
|
7
|
-
|
|
8
|
-
export { SUPPORTED_CLIS };
|