agent-dag 1.29.2 → 1.30.1
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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>agents-deck</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='84' font-size='84'%3E%E2%97%89%3C/text%3E%3C/svg%3E" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DJb8KUBm.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-BLOOP_Fy.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-dag",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.1",
|
|
4
4
|
"description": "Live deck of Claude Code and Codex agents — watch parallel subagents fork, call tools, and return on one calm canvas. Also available as npx ccdeck and npx agent-dag.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
// means the deck cannot 429 the user's account, cannot burn a refresh token,
|
|
14
14
|
// and cannot lose a login. Switching shells out to `cswap` rather than
|
|
15
15
|
// reimplementing the lock protocol its correctness depends on.
|
|
16
|
-
import { readFile } from "node:fs/promises";
|
|
16
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
17
17
|
import { cswapBin, cswapVersion, installHint } from "./cswap-install.mjs";
|
|
18
18
|
import { run, runDetached } from "./exec.mjs";
|
|
19
|
-
import { join } from "node:path";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
import { existsSync } from "node:fs";
|
|
20
21
|
import { homedir, platform } from "node:os";
|
|
21
22
|
|
|
22
23
|
// claude-swap keeps its store under XDG on Linux and in the home directory
|
|
@@ -61,12 +62,28 @@ let _lastNudge = 0;
|
|
|
61
62
|
* Fire-and-forget. The result lands in the store for the next poll to pick up;
|
|
62
63
|
* making the caller wait would just delay the numbers it already has.
|
|
63
64
|
*/
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Whether anything is waiting to be collected.
|
|
67
|
+
*
|
|
68
|
+
* An account with a row is due when its own schedule says so. An account with
|
|
69
|
+
* NO row has never been fetched at all, which is due by definition — and that
|
|
70
|
+
* was the gap: a freshly added account has no usage.json entry, so nothing was
|
|
71
|
+
* ever "due", so the collector was never asked, so the row never appeared. The
|
|
72
|
+
* panel sat on "never fetched" indefinitely unless the user happened to run
|
|
73
|
+
* cswap themselves.
|
|
74
|
+
*/
|
|
75
|
+
export function collectionDue(rows, slots, now) {
|
|
76
|
+
for (const slot of slots) {
|
|
77
|
+
if (!rows[slot]) return true; // never fetched
|
|
78
|
+
}
|
|
79
|
+
return Object.values(rows).some(r =>
|
|
67
80
|
typeof r?.nextPollAt === "number" && r.nextPollAt * 1000 <= now
|
|
68
81
|
);
|
|
69
|
-
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function nudgeCollector(rows, slots, now) {
|
|
85
|
+
if (now - _lastNudge < NUDGE_EVERY_MS) return;
|
|
86
|
+
if (!collectionDue(rows, slots, now)) return;
|
|
70
87
|
|
|
71
88
|
_lastNudge = now;
|
|
72
89
|
// Fire-and-forget: this function is deliberately synchronous so callers never
|
|
@@ -131,8 +148,10 @@ export async function fetchClaudeAccounts({ force = false } = {}) {
|
|
|
131
148
|
// A schema bump means the rows may not mean what this code thinks they do.
|
|
132
149
|
const rows = usage?.schemaVersion === 2 ? (usage.accounts ?? {}) : {};
|
|
133
150
|
|
|
134
|
-
// Kick a collection for the NEXT poll if
|
|
135
|
-
|
|
151
|
+
// Kick a collection for the NEXT poll if anything is due — either because
|
|
152
|
+
// claude-swap's schedule says so, or because an account has never been
|
|
153
|
+
// fetched at all.
|
|
154
|
+
nudgeCollector(rows, Object.keys(seq.accounts), now);
|
|
136
155
|
|
|
137
156
|
const order = Array.isArray(seq.sequence) && seq.sequence.length
|
|
138
157
|
? seq.sequence.map(String)
|
|
@@ -220,3 +239,85 @@ export function switchClaudeAccount(accountNum) {
|
|
|
220
239
|
return { ok: false, reason, output: (r.stderr || r.stdout).trim().slice(0, 500) };
|
|
221
240
|
});
|
|
222
241
|
}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Register the account already signed in, the first time and only the first
|
|
246
|
+
* time.
|
|
247
|
+
*
|
|
248
|
+
* A fresh install leaves claude-swap with an empty store, so the panel comes up
|
|
249
|
+
* saying "no accounts added yet" and telling the user to run `cswap add`
|
|
250
|
+
* themselves — for the account they are already using, on the machine they are
|
|
251
|
+
* already on. Running it for them is the difference between the panel working
|
|
252
|
+
* and the panel being a to-do item.
|
|
253
|
+
*
|
|
254
|
+
* `cswap add` takes no arguments, prompts for nothing, and does not sign
|
|
255
|
+
* anyone in: it records the Claude Code session that already exists. Even so it
|
|
256
|
+
* is bounded tightly, because it writes to a credential store:
|
|
257
|
+
*
|
|
258
|
+
* - only when the store holds no accounts at all, so nothing can be
|
|
259
|
+
* overwritten or reordered;
|
|
260
|
+
* - only once ever, marked on disk, so a user who deliberately removes their
|
|
261
|
+
* last account does not get it added back on the next launch;
|
|
262
|
+
* - never when AGENTS_DECK_NO_INSTALL=1.
|
|
263
|
+
*
|
|
264
|
+
* Failure is normal and quiet: on a machine where Claude Code has never signed
|
|
265
|
+
* in there is nothing to record.
|
|
266
|
+
*/
|
|
267
|
+
const SEED_MARKER = join(homedir(), ".agents-deck", ".cswap-seeded");
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* How many accounts a sequence.json holds.
|
|
271
|
+
*
|
|
272
|
+
* claude-swap writes `accounts` as an object keyed by slot number — {"2": {…},
|
|
273
|
+
* "3": {…}} — not as a list. An Array.isArray guard here read that as "no
|
|
274
|
+
* accounts" and ran `cswap add` against a populated store, which is exactly
|
|
275
|
+
* what the guard existed to prevent. Both shapes are accepted now, and
|
|
276
|
+
* anything unrecognised counts as -1: unknown is not the same as empty, and
|
|
277
|
+
* only a confident zero may lead to a write.
|
|
278
|
+
*/
|
|
279
|
+
export function accountCount(seq) {
|
|
280
|
+
const a = seq?.accounts;
|
|
281
|
+
if (Array.isArray(a)) return a.length;
|
|
282
|
+
if (a && typeof a === "object") return Object.keys(a).length;
|
|
283
|
+
if (a == null && seq && typeof seq === "object") return 0; // store exists, no accounts yet
|
|
284
|
+
return -1; // unreadable — do nothing
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export async function seedFirstAccount() {
|
|
288
|
+
if (process.env.AGENTS_DECK_NO_INSTALL === "1") return { state: "skipped" };
|
|
289
|
+
if (existsSync(SEED_MARKER)) return { state: "already-tried" };
|
|
290
|
+
|
|
291
|
+
// Empty store only. A store with accounts in it is the user's, not ours, and
|
|
292
|
+
// a store we cannot parse is treated the same way.
|
|
293
|
+
// No file at all is the fresh install this exists for; a file that will not
|
|
294
|
+
// parse is a store in an unknown state. readJson answers null to both, so
|
|
295
|
+
// they are told apart before deciding, because they call for opposite
|
|
296
|
+
// decisions — seed, and keep well away.
|
|
297
|
+
const seqPath = join(backupRoot(), "sequence.json");
|
|
298
|
+
const before = existsSync(seqPath) ? accountCount(await readJson(seqPath)) : 0;
|
|
299
|
+
if (before > 0) return { state: "has-accounts", count: before };
|
|
300
|
+
if (before < 0) return { state: "unreadable-store" };
|
|
301
|
+
|
|
302
|
+
// Mark before running, not after: if `cswap add` half-succeeds or the process
|
|
303
|
+
// dies mid-way, the retry-forever loop is the worse outcome.
|
|
304
|
+
try {
|
|
305
|
+
await mkdir(dirname(SEED_MARKER), { recursive: true });
|
|
306
|
+
await writeFile(SEED_MARKER, new Date().toISOString());
|
|
307
|
+
} catch { /* best-effort — worst case it is attempted again */ }
|
|
308
|
+
|
|
309
|
+
const r = await run(await cswapBin(), ["add"], { timeout: 60_000 });
|
|
310
|
+
if (!r.ok) {
|
|
311
|
+
return { state: "failed", detail: (r.stderr || r.stdout).trim().slice(0, 200) };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
invalidateClaudeAccountsCache();
|
|
315
|
+
const count = existsSync(seqPath) ? accountCount(await readJson(seqPath)) : 0;
|
|
316
|
+
if (count > 0) {
|
|
317
|
+
// Collect straight away. Otherwise the first thing the user sees is their
|
|
318
|
+
// account listed with "never fetched" beside it, waiting on a poll cycle
|
|
319
|
+
// for numbers that are the reason the panel exists.
|
|
320
|
+
runDetached(await cswapBin(), ["list"]);
|
|
321
|
+
}
|
|
322
|
+
return count > 0 ? { state: "added", count } : { state: "nothing-to-add" };
|
|
323
|
+
}
|