agent-dag 1.35.36 → 1.35.38
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/web/index.html
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
document.documentElement.setAttribute("data-theme", stored === "light" ? "light" : "dark");
|
|
41
41
|
})();
|
|
42
42
|
</script>
|
|
43
|
-
<script type="module" crossorigin src="/assets/index-
|
|
43
|
+
<script type="module" crossorigin src="/assets/index-_Jvl7GLr.js"></script>
|
|
44
44
|
<link rel="stylesheet" crossorigin href="/assets/index-C2ruFt14.css">
|
|
45
45
|
</head>
|
|
46
46
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-dag",
|
|
3
|
-
"version": "1.35.
|
|
3
|
+
"version": "1.35.38",
|
|
4
4
|
"description": "Live deck of Claude Code and Codex agents — watch tool calls, token spend and every Claude Code subagent on one calm canvas. Also available as npx ccdeck and npx agent-dag.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/server/ccusage.mjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// the current call always serves from the already-installed copy. If the
|
|
12
12
|
// managed install is missing/broken we fall back to the old npx path so the
|
|
13
13
|
// feature still works on a fresh machine.
|
|
14
|
-
import { spawn
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
15
|
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import os from "node:os";
|
|
@@ -107,7 +107,7 @@ let _checkedThisRun = false; // only kick the daily check once per process boot
|
|
|
107
107
|
|
|
108
108
|
// The last `npm install` that failed, in one line of its own words.
|
|
109
109
|
//
|
|
110
|
-
// Module scope rather than a value thrown out of
|
|
110
|
+
// Module scope rather than a value thrown out of the install, because the two
|
|
111
111
|
// callers that matter never see that throw. primeCcusage runs the install at
|
|
112
112
|
// boot and only logs the rejection; a second modal open that arrives while an
|
|
113
113
|
// install is in flight awaits the SHARED promise, whose rejection has already
|
|
@@ -125,12 +125,26 @@ const INSTALL_ERROR_ROOM = 240;
|
|
|
125
125
|
* Start the one shared install, remembering how it ended.
|
|
126
126
|
*
|
|
127
127
|
* Deduped through `_installing` the way it always was, so concurrent callers
|
|
128
|
-
* cost one `npm install` between them
|
|
129
|
-
*
|
|
128
|
+
* cost one `npm install` between them, and the outcome survives the promise.
|
|
129
|
+
*
|
|
130
|
+
* This used to read `_installing = (async () => { installSync(spec); })()`, and
|
|
131
|
+
* that wrapper was the whole of #476: an async function body runs synchronously
|
|
132
|
+
* up to its first `await`, and there was none, so the IIFE turned a throw into
|
|
133
|
+
* a rejection and bought no asynchrony at all. `installSync` was a `spawnSync`
|
|
134
|
+
* of `npm install` with a two-minute deadline, and it ran on the caller's
|
|
135
|
+
* stack — which at boot is bin/deck.js's, beside jobs that really are async.
|
|
136
|
+
* primeCcusage answered `{ state: "installing" }`, a sentence that says the
|
|
137
|
+
* wait was deferred, while the process could not accept an HTTP connection,
|
|
138
|
+
* write an SSE frame, ingest a hook or repaint the pulse line until npm exited.
|
|
139
|
+
* On a first run, with nothing cached, that is the one boot a new user judges
|
|
140
|
+
* the tool by.
|
|
141
|
+
*
|
|
142
|
+
* `install` below is the fix, and there is now exactly one install mechanism in
|
|
143
|
+
* this module rather than a synchronous one and a background one.
|
|
130
144
|
*/
|
|
131
145
|
function startInstall(spec = "latest") {
|
|
132
146
|
if (!_installing) {
|
|
133
|
-
_installing = (
|
|
147
|
+
_installing = install(spec)
|
|
134
148
|
.then(() => { _lastInstallError = null; })
|
|
135
149
|
.catch(e => {
|
|
136
150
|
_lastInstallError = oneLine(e?.message ?? e, INSTALL_ERROR_ROOM);
|
|
@@ -238,17 +252,21 @@ export function resolveEntry() {
|
|
|
238
252
|
}
|
|
239
253
|
|
|
240
254
|
/**
|
|
241
|
-
* Everything
|
|
255
|
+
* Everything a failed install is known to have said, in the order the answer is
|
|
242
256
|
* usually in.
|
|
243
257
|
*
|
|
244
258
|
* The old line read `(r.stderr || "").trim() || r.status`, which threw away the
|
|
245
|
-
* two things most likely to be the whole story. `
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
259
|
+
* two things most likely to be the whole story. `error` is where a failure to
|
|
260
|
+
* LAUNCH lands — ENOENT for a comspec that is not there, EINVAL for a .cmd Node
|
|
261
|
+
* refuses to spawn directly, and the expired deadline — and it comes with no
|
|
262
|
+
* status at all, so what reached the terminal in every one of those cases was
|
|
263
|
+
* the word "null". And npm has never confined itself to stderr: a shim that
|
|
264
|
+
* dies before npm starts writes wherever Node chose, and `npm ERR!` blocks have
|
|
265
|
+
* landed on stdout across majors.
|
|
266
|
+
*
|
|
267
|
+
* The four fields are spawnSync's, because that is where they were first read
|
|
268
|
+
* off; `install` above now fills the same shape from the 'error' event, the
|
|
269
|
+
* exit status and the two collected streams.
|
|
252
270
|
*/
|
|
253
271
|
function installFailureText(r) {
|
|
254
272
|
const parts = [];
|
|
@@ -290,46 +308,95 @@ function installTreeReport() {
|
|
|
290
308
|
return "the package is there but its bin entry could not be read or does not point inside it";
|
|
291
309
|
}
|
|
292
310
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
311
|
+
/**
|
|
312
|
+
* Run `npm install ccusage@<spec> --prefix CACHE_DIR`, off this stack.
|
|
313
|
+
*
|
|
314
|
+
* The ONE install in this module, awaited by startInstall and dropped on the
|
|
315
|
+
* floor by the daily update path below. It was two — a `spawnSync` for the cold
|
|
316
|
+
* path and a fire-and-forget `spawn` for the background one — and the sync half
|
|
317
|
+
* is what #476 removes: nothing here needs an answer before the next tick, and
|
|
318
|
+
* a two-minute `spawnSync` at boot is two minutes of a dead process.
|
|
319
|
+
*
|
|
320
|
+
* Everything the sync path was careful about is carried over unchanged, because
|
|
321
|
+
* every one of those cares was bought with a bug report:
|
|
322
|
+
*
|
|
323
|
+
* - the vector is `installSpec`'s, spread into spawn exactly as spawnSync got
|
|
324
|
+
* it, which is what keeps #456's absolute `npm.cmd` and #362's per-argument
|
|
325
|
+
* quoting on Windows. `opts` is spread LAST for the same reason it was
|
|
326
|
+
* before: it carries windowsVerbatimArguments, and nothing above it may win.
|
|
327
|
+
* - `windowsHide`, so no console window flashes up.
|
|
328
|
+
* - INSTALL_TIMEOUT_MS, as a deadline this module enforces itself rather than
|
|
329
|
+
* spawn's own `timeout` option. That option sends one signal to the process
|
|
330
|
+
* it started, and on Windows a `.cmd` runs THROUGH cmd.exe — so the signal
|
|
331
|
+
* would land on the wrapper and leave npm downloading, which is the same
|
|
332
|
+
* distinction exec.mjs's `run` states and the reason killTree exists. Same
|
|
333
|
+
* shape as runOnce below, so this file has one deadline pattern rather than
|
|
334
|
+
* two.
|
|
335
|
+
*
|
|
336
|
+
* Diagnosis is unchanged too: `installFailureText` is handed the same four
|
|
337
|
+
* fields spawnSync used to hand it — a failure to LAUNCH in `error`, the exit
|
|
338
|
+
* status, and both output streams, because npm has never confined itself to
|
|
339
|
+
* stderr.
|
|
340
|
+
*/
|
|
341
|
+
function install(spec = "latest") {
|
|
342
|
+
return new Promise((resolve, reject) => {
|
|
323
343
|
mkdirSync(CACHE_DIR, { recursive: true });
|
|
324
344
|
const { file, args, opts } = installSpec(spec);
|
|
325
|
-
const child = spawn(
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
345
|
+
const child = spawn(file, args, { windowsHide: true, ...opts });
|
|
346
|
+
let out = "", err = "", settled = false;
|
|
347
|
+
let timer = null;
|
|
348
|
+
const finish = (fn, value) => {
|
|
349
|
+
if (settled) return;
|
|
350
|
+
settled = true;
|
|
351
|
+
clearTimeout(timer);
|
|
352
|
+
fn(value);
|
|
353
|
+
};
|
|
354
|
+
// A child that dies emits 'error' AND THEN 'close', and a deadline that
|
|
355
|
+
// fires kills the child and so provokes both — hence `settled`. Whichever
|
|
356
|
+
// arrives first is the account that travels.
|
|
357
|
+
const failed = (r) => finish(reject, new Error(`npm install ccusage failed: ${installFailureText(r)}`));
|
|
358
|
+
timer = setTimeout(() => {
|
|
359
|
+
// The verdict is stated before the kill, the order exec.mjs's `run` uses:
|
|
360
|
+
// the answer must not depend on the killed child cooperating.
|
|
361
|
+
failed({
|
|
362
|
+
error: { message: `npm install timed out after ${INSTALL_TIMEOUT_MS}ms` },
|
|
363
|
+
stdout: out,
|
|
364
|
+
stderr: err,
|
|
365
|
+
});
|
|
366
|
+
killTree(child);
|
|
367
|
+
}, INSTALL_TIMEOUT_MS);
|
|
368
|
+
timer.unref?.();
|
|
369
|
+
child.stdout?.on("data", d => { out += d; });
|
|
370
|
+
child.stderr?.on("data", d => { err += d; });
|
|
371
|
+
child.on("error", e => failed({ error: e, stdout: out, stderr: err }));
|
|
372
|
+
child.on("close", status => {
|
|
373
|
+
if (status !== 0) return failed({ status, stdout: out, stderr: err });
|
|
374
|
+
// A zero exit is npm's opinion, not a fact about the disk, and the caller
|
|
375
|
+
// treats "the install resolved" as "there is something to run". Checking
|
|
376
|
+
// here is what stops a silent success: getRunner used to call
|
|
377
|
+
// resolveEntry(), get null, and fall through to the npx fallback with
|
|
378
|
+
// NOTHING recorded anywhere about why — so the modal explained the
|
|
379
|
+
// fallback's stderr and the install's half of the story was never written
|
|
380
|
+
// down at all.
|
|
381
|
+
if (!resolveEntry()) {
|
|
382
|
+
return finish(reject, new Error(
|
|
383
|
+
`npm install ccusage exited 0 but left nothing runnable under ${CACHE_DIR}: `
|
|
384
|
+
+ `${installTreeReport()}`,
|
|
385
|
+
));
|
|
386
|
+
}
|
|
387
|
+
finish(resolve, undefined);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// The daily update path's install: the same one above, with nobody listening.
|
|
393
|
+
//
|
|
394
|
+
// It has no shared promise and no `_lastInstallError` on purpose. This runs
|
|
395
|
+
// behind a ccusage that already works, so a failed upgrade is not news the
|
|
396
|
+
// modal should lead with — the copy on disk still answers, and the check comes
|
|
397
|
+
// round again tomorrow.
|
|
398
|
+
function installInBackground(spec = "latest") {
|
|
399
|
+
install(spec).catch(() => { /* best-effort */ });
|
|
333
400
|
}
|
|
334
401
|
|
|
335
402
|
// True at most once per UPDATE_CHECK_MS, gated by the marker file's mtime so the
|
|
@@ -363,7 +430,7 @@ function maybeBackgroundUpdate(installedVersion) {
|
|
|
363
430
|
child.on("error", () => {});
|
|
364
431
|
child.on("close", () => {
|
|
365
432
|
const latest = out.trim();
|
|
366
|
-
if (latest && latest !== installedVersion)
|
|
433
|
+
if (latest && latest !== installedVersion) installInBackground(latest);
|
|
367
434
|
});
|
|
368
435
|
} catch { /* ignore */ }
|
|
369
436
|
}
|
|
@@ -831,7 +898,10 @@ export async function fetchCcusageDaily({ since, until, force = false } = {}) {
|
|
|
831
898
|
*
|
|
832
899
|
* Returns { state: "present" | "user" | "installing" | "updating" |
|
|
833
900
|
* "unavailable" }. Never throws and never blocks on the install itself — a slow
|
|
834
|
-
* registry must not hold up the server.
|
|
901
|
+
* registry must not hold up the server. That second half was a claim rather
|
|
902
|
+
* than a fact until #476: `startInstall` wrapped a `spawnSync` in an async IIFE
|
|
903
|
+
* with nothing to await in it, so `{ state: "installing" }` was returned only
|
|
904
|
+
* after the install had already happened, on this stack.
|
|
835
905
|
*
|
|
836
906
|
* The order here is getRunner's order, and it has to be: a boot that installs a
|
|
837
907
|
* managed copy while the user already has one on PATH would make getRunner's
|
package/src/server/index.mjs
CHANGED
|
@@ -152,7 +152,18 @@ const transcriptScans = new Map(); // path -> scan state
|
|
|
152
152
|
const transcriptScanInFlight = new Map(); // path -> in-progress scan promise
|
|
153
153
|
const MAX_TRANSCRIPT_SCANS = 256; // bound the per-path state
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
// The transcript's `message.model`, and the only filter standing between it and
|
|
156
|
+
// every model the deck shows. Bedrock and Mantle put a provider namespace in
|
|
157
|
+
// front of the id — `us.anthropic.claude-opus-5`, `anthropic.claude-opus-5` —
|
|
158
|
+
// so a bare `^claude` dropped every line a Bedrock session writes and the deck
|
|
159
|
+
// showed those users no model, no context window and no cost at all (#475).
|
|
160
|
+
//
|
|
161
|
+
// The prefix list is `VENDOR_PREFIX_RE` in src/web/model-id.ts, written out a
|
|
162
|
+
// second time here rather than imported: this file is plain .mjs that node runs
|
|
163
|
+
// straight off disk with no build step, so it cannot reach a `.ts` module.
|
|
164
|
+
// bedrock-model-ids.test.ts sweeps both against one list of ids so the copies
|
|
165
|
+
// cannot drift apart.
|
|
166
|
+
const MODEL_ID_RE = /^(?:(?:us-gov|global|apac|us|eu|jp|au)\.anthropic\.|anthropic\.)?claude[-_]/i;
|
|
156
167
|
const USAGE_BLOCK_RE = /"usage"\s*:\s*\{([^}]+)\}/g;
|
|
157
168
|
// CC `/clear` and `/compact` write a marker into the transcript and reset the
|
|
158
169
|
// context window to ~0 while the JSONL keeps growing — everything before the
|