agent-dag 3.2.0 → 3.3.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 +18 -6
- package/bin/deck.js +9 -1
- package/dist/web/assets/index-GLqupad3.css +1 -0
- package/dist/web/assets/index-Z0vOy1A6.js +165 -0
- package/dist/web/index.html +2 -2
- package/hook/hook.js +60 -14
- package/package.json +6 -3
- package/release-notes.json +32 -0
- package/src/server/agent-activity.mjs +27 -11
- package/src/server/browser-history.mjs +31 -5
- package/src/server/browser-presence.mjs +57 -12
- package/src/server/browser-react.mjs +31 -9
- package/src/server/browser-watch-store.mjs +56 -2
- package/src/server/browser-watch.mjs +90 -10
- package/src/server/ccusage.mjs +95 -2
- package/src/server/codex-quota.mjs +6 -2
- package/src/server/codex-usage.mjs +24 -1
- package/src/server/cswap-admin.mjs +0 -14
- package/src/server/cswap-auto.mjs +39 -1
- package/src/server/cswap-install.mjs +6 -1
- package/src/server/exec.mjs +44 -0
- package/src/server/index.mjs +127 -11
- package/src/server/installer.mjs +58 -3
- package/src/server/macmon.mjs +17 -6
- package/src/server/npx.mjs +6 -1
- package/src/server/open-url.mjs +25 -4
- package/src/server/quota.mjs +88 -26
- package/src/server/retire-sound-hook.mjs +36 -2
- package/src/server/self-update.mjs +17 -5
- package/src/server/system-metrics.mjs +28 -36
- package/src/server/uv-bootstrap.mjs +39 -3
- package/dist/web/assets/index-7UsJp5Ht.js +0 -143
- package/dist/web/assets/index-Bnn7d7u8.css +0 -1
|
@@ -1069,12 +1069,19 @@ function sweepOrphanedNotes(keep) {
|
|
|
1069
1069
|
}
|
|
1070
1070
|
}
|
|
1071
1071
|
|
|
1072
|
-
// Signal 0 delivers nothing; it
|
|
1073
|
-
//
|
|
1074
|
-
//
|
|
1072
|
+
// Signal 0 delivers nothing; it asks whether the pid could be signalled.
|
|
1073
|
+
//
|
|
1074
|
+
// BOTH ERRNOS, and the second one is the Windows spelling. POSIX `kill(2)`
|
|
1075
|
+
// answers EPERM for a process this account may not signal. On Windows
|
|
1076
|
+
// `uv_kill` calls `OpenProcess`, a denial is ERROR_ACCESS_DENIED, and libuv
|
|
1077
|
+
// maps that to EACCES — so a deck started from an elevated terminal, or under
|
|
1078
|
+
// another account, read as DEAD to every probe in this repo. What followed was
|
|
1079
|
+
// silent: the live deck's discovery file was unlinked on the next hook fire,
|
|
1080
|
+
// rewritten five seconds later by keepDiscovery, and its banner went on
|
|
1081
|
+
// claiming it was receiving events it had stopped receiving.
|
|
1075
1082
|
function processAlive(pid) {
|
|
1076
1083
|
try { process.kill(pid, 0); return true; }
|
|
1077
|
-
catch (e) { return e?.code === "EPERM"; }
|
|
1084
|
+
catch (e) { return e?.code === "EPERM" || e?.code === "EACCES"; }
|
|
1078
1085
|
}
|
|
1079
1086
|
|
|
1080
1087
|
/**
|
|
@@ -1198,7 +1205,12 @@ export function startUpgrade({ pkgRoot, name = "agents-deck" }) {
|
|
|
1198
1205
|
// installedVersion() disagree with the running one, and the ordinary
|
|
1199
1206
|
// drift path takes it from there — including its wait for an idle moment.
|
|
1200
1207
|
_upgrade = { state: "done", command, error: null, at: Date.now() };
|
|
1201
|
-
} else if (!timedOut) {
|
|
1208
|
+
} else if (!timedOut && _upgrade?.state !== "failed") {
|
|
1209
|
+
// Not over a failure the 'error' handler already explained. A missing npm
|
|
1210
|
+
// emits 'error' with ENOENT and THEN 'close' with a null code, and this
|
|
1211
|
+
// branch used to replace "spawn npm ENOENT" with "npm exited -2" — the
|
|
1212
|
+
// one message that says what is wrong, overwritten by the one that does
|
|
1213
|
+
// not.
|
|
1202
1214
|
_upgrade = { state: "failed", command, error: lastMeaningfulLine(err) || `npm exited ${code}`, at: Date.now() };
|
|
1203
1215
|
}
|
|
1204
1216
|
});
|
|
@@ -182,10 +182,29 @@ const C_LOCALE = { LC_ALL: "", LC_NUMERIC: "C" };
|
|
|
182
182
|
function run(file, args, timeoutMs = 2_000) {
|
|
183
183
|
return new Promise(resolve => {
|
|
184
184
|
let child;
|
|
185
|
-
try {
|
|
185
|
+
try {
|
|
186
|
+
child = spawn(file, args, {
|
|
187
|
+
windowsHide: true,
|
|
188
|
+
env: { ...process.env, ...C_LOCALE },
|
|
189
|
+
// stderr is PIPED AND NEVER READ, which is a deadlock waiting for a
|
|
190
|
+
// chatty child: a pipe nobody drains fills at 64 KB and the writer
|
|
191
|
+
// blocks there until this function's own deadline kills it. Nothing
|
|
192
|
+
// here has ever looked at it — `run` resolves on stdout or null — so
|
|
193
|
+
// the honest arrangement is not to open it.
|
|
194
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
195
|
+
});
|
|
196
|
+
}
|
|
186
197
|
catch { return resolve(null); }
|
|
187
198
|
let out = "";
|
|
188
199
|
const timer = setTimeout(() => { try { child.kill(); } catch {} resolve(null); }, timeoutMs);
|
|
200
|
+
// DECODE THE STREAM, NOT EACH CHUNK. `out += d` on a Buffer calls toString()
|
|
201
|
+
// per chunk, and a chunk boundary falls wherever the pipe happened to break
|
|
202
|
+
// — measured on `ps` output as three chunks of 8192/8192/5718 — so a
|
|
203
|
+
// multi-byte character split across two of them became two replacement
|
|
204
|
+
// characters. An application called `Яндекс Музыка` in the process list
|
|
205
|
+
// rendered as `Ян��екс Музыка`. setEncoding carries the partial sequence
|
|
206
|
+
// across the boundary, which is the whole reason it exists.
|
|
207
|
+
child.stdout?.setEncoding?.("utf8");
|
|
189
208
|
child.stdout?.on("data", d => { out += d; });
|
|
190
209
|
child.on("error", () => { clearTimeout(timer); resolve(null); });
|
|
191
210
|
child.on("close", code => { clearTimeout(timer); resolve(code === 0 ? out : null); });
|
|
@@ -599,7 +618,14 @@ async function readProcessesNow(platform) {
|
|
|
599
618
|
"-NoProfile", "-NonInteractive", "-Command",
|
|
600
619
|
"Get-Process | Select-Object Id,ProcessName,CPU,@{n='WorkingSetPrivate';e={$_.PrivateMemorySize64}} | ConvertTo-Json -Compress",
|
|
601
620
|
], 6_000);
|
|
602
|
-
|
|
621
|
+
// The same shape the POSIX branch below returns, and not a bare array: the
|
|
622
|
+
// caller reads `read.procs.length` to decide whether this was a real
|
|
623
|
+
// reading, so an array meant a TypeError rather than an empty list. Every
|
|
624
|
+
// Windows failure — a non-zero exit, a spawn that never started, the 6s
|
|
625
|
+
// deadline this module's own comment says the runtime sits right on —
|
|
626
|
+
// therefore answered `/api/system/processes` with a 500 and a logged stack,
|
|
627
|
+
// every four seconds for as long as the machine panel was open.
|
|
628
|
+
if (!out) return { procs: [], total: 0 };
|
|
603
629
|
const rows = parseGetProcessJson(out.trim(), os.totalmem());
|
|
604
630
|
const now = Date.now();
|
|
605
631
|
const ranked = cpuFromDeltas(rows, prevProcCpu, now - prevProcAt);
|
|
@@ -988,40 +1014,6 @@ export function zoneLabel(raw) {
|
|
|
988
1014
|
return name || "Thermal zone";
|
|
989
1015
|
}
|
|
990
1016
|
|
|
991
|
-
/**
|
|
992
|
-
* Windows thermal zones out of MSAcpi_ThermalZoneTemperature.
|
|
993
|
-
*
|
|
994
|
-
* The SECOND source, and only reachable by a deck that happens to be elevated:
|
|
995
|
-
* root\\wmi requires administrator and refuses an ordinary terminal outright.
|
|
996
|
-
* Kept because some boards publish a zone to ACPI and no counter, and because a
|
|
997
|
-
* deck launched from an elevated shell can read it.
|
|
998
|
-
*
|
|
999
|
-
* CurrentTemperature is in tenths of a Kelvin, the same unit as the counter
|
|
1000
|
-
* above, and reading it as anything else gives a number that is
|
|
1001
|
-
* plausible-looking and wrong.
|
|
1002
|
-
*
|
|
1003
|
-
* The zone is labelled "Thermal zone" when there is one and by its own name
|
|
1004
|
-
* when there are several, because ACPI does not say which zone is the CPU and
|
|
1005
|
-
* this module does not guess. `TZ00` is not a friendly label; it is an honest
|
|
1006
|
-
* one, and it only appears on a machine that has more than one.
|
|
1007
|
-
*/
|
|
1008
|
-
export function tempFromMsAcpiJson(json) {
|
|
1009
|
-
let rows;
|
|
1010
|
-
try { rows = typeof json === "string" ? JSON.parse(json) : json; }
|
|
1011
|
-
catch { return []; }
|
|
1012
|
-
if (!rows) return [];
|
|
1013
|
-
if (!Array.isArray(rows)) rows = [rows];
|
|
1014
|
-
const found = [];
|
|
1015
|
-
for (const r of rows) {
|
|
1016
|
-
const k = Number(r?.CurrentTemperature);
|
|
1017
|
-
if (!Number.isFinite(k)) continue;
|
|
1018
|
-
const celsius = Math.round(k / 10 - 273.15);
|
|
1019
|
-
if (!plausible(celsius)) continue;
|
|
1020
|
-
found.push({ label: zoneLabel(r?.InstanceName), celsius, warnAt: WARN_C, critAt: CRIT_C });
|
|
1021
|
-
}
|
|
1022
|
-
if (found.length === 1) found[0].label = "Thermal zone";
|
|
1023
|
-
return found.slice(0, 2);
|
|
1024
|
-
}
|
|
1025
1017
|
|
|
1026
1018
|
/**
|
|
1027
1019
|
* The macOS rows, from whatever the three sources answered.
|
|
@@ -46,16 +46,52 @@ const DOWNLOAD_TIMEOUT_MS = 120_000;
|
|
|
46
46
|
* Rosetta is deliberately not special-cased: an x64 Node on Apple Silicon
|
|
47
47
|
* reports x64 and gets the x64 build, which runs.
|
|
48
48
|
*/
|
|
49
|
-
export function assetName(platform = process.platform, arch = process.arch) {
|
|
50
|
-
|
|
49
|
+
export function assetName(platform = process.platform, arch = process.arch, libc = detectLibc(platform)) {
|
|
50
|
+
// `arm` is 32-bit ARM — a Raspberry Pi on the armhf image, which uv publishes
|
|
51
|
+
// as armv7. Left out, that machine got `unsupported_platform` for a target
|
|
52
|
+
// that exists.
|
|
53
|
+
const a = { x64: "x86_64", arm64: "aarch64", ia32: "i686", arm: "armv7" }[arch];
|
|
51
54
|
if (!a) return null;
|
|
52
55
|
// Apple publishes no i686 build, and never will.
|
|
53
56
|
if (platform === "darwin") return a === "i686" ? null : `uv-${a}-apple-darwin.tar.gz`;
|
|
54
57
|
if (platform === "win32") return `uv-${a}-pc-windows-msvc.zip`;
|
|
55
|
-
if (platform === "linux")
|
|
58
|
+
if (platform === "linux") {
|
|
59
|
+
// MUSL IS ITS OWN TARGET, and asking for the gnu build on Alpine is not a
|
|
60
|
+
// degraded install, it is an endless one: the glibc ELF interpreter is
|
|
61
|
+
// absent, so the downloaded binary cannot start, `bootstrapUv` returns
|
|
62
|
+
// `does_not_run`, and because ensureCswap runs whenever cswapVersion() is
|
|
63
|
+
// null the 35 MB archive is fetched, hashed and extracted again on EVERY
|
|
64
|
+
// launch — with the accounts panel permanently unavailable. `node:alpine`
|
|
65
|
+
// is a common enough base image for this to be somebody's whole experience
|
|
66
|
+
// of the deck.
|
|
67
|
+
if (libc === "musl") return `uv-${a}-unknown-linux-musl.tar.gz`;
|
|
68
|
+
return `uv-${a}-unknown-linux-gnu.tar.gz`;
|
|
69
|
+
}
|
|
56
70
|
return null;
|
|
57
71
|
}
|
|
58
72
|
|
|
73
|
+
/**
|
|
74
|
+
* "glibc" or "musl" for this process.
|
|
75
|
+
*
|
|
76
|
+
* Node's own report names the glibc it is linked against, and a musl build has
|
|
77
|
+
* no such field — which is the check every native-addon loader uses, and needs
|
|
78
|
+
* no child process. Anything unexpected reads as glibc, because that is the
|
|
79
|
+
* overwhelmingly common case and the wrong guess there is the state this code
|
|
80
|
+
* was already in.
|
|
81
|
+
*/
|
|
82
|
+
export function detectLibc(platform = process.platform, report = () => process.report?.getReport?.()) {
|
|
83
|
+
// The question only exists on Linux. macOS and Windows have no glibc field
|
|
84
|
+
// either, so asking there and reading the absence as musl would answer "musl"
|
|
85
|
+
// for every Mac — which is why the platform comes first.
|
|
86
|
+
if (platform !== "linux") return "glibc";
|
|
87
|
+
try {
|
|
88
|
+
const header = report()?.header;
|
|
89
|
+
if (!header) return "glibc";
|
|
90
|
+
if (typeof header.glibcVersionRuntime === "string") return "glibc";
|
|
91
|
+
return "musl";
|
|
92
|
+
} catch { return "glibc"; }
|
|
93
|
+
}
|
|
94
|
+
|
|
59
95
|
/** Path to a uv this function installed earlier, or null. */
|
|
60
96
|
export function existingBootstrappedUv() {
|
|
61
97
|
const bin = join(UV_DIR, process.platform === "win32" ? "uv.exe" : "uv");
|