agent-dag 3.22.1 → 3.22.3

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.
Files changed (70) hide show
  1. package/README.md +6 -477
  2. package/package.json +14 -48
  3. package/shim.js +107 -0
  4. package/LICENSE +0 -661
  5. package/LICENSING.md +0 -82
  6. package/THIRD_PARTY_NOTICES.md +0 -395
  7. package/bin/agent-dag.js +0 -626
  8. package/bin/deck.js +0 -1805
  9. package/dist/web/assets/index-CJYsv0lr.css +0 -1
  10. package/dist/web/assets/index-Ifm23DDC.js +0 -270
  11. package/dist/web/index.html +0 -49
  12. package/hook/hook.js +0 -542
  13. package/release-notes.json +0 -398
  14. package/src/server/activity.mjs +0 -52
  15. package/src/server/agent-activity.mjs +0 -522
  16. package/src/server/args.mjs +0 -183
  17. package/src/server/auto-update.mjs +0 -79
  18. package/src/server/block-notify.mjs +0 -173
  19. package/src/server/boot-deadline.mjs +0 -127
  20. package/src/server/brand.mjs +0 -16
  21. package/src/server/browser-history.mjs +0 -497
  22. package/src/server/browser-presence.mjs +0 -211
  23. package/src/server/browser-profiles.mjs +0 -279
  24. package/src/server/browser-react.mjs +0 -284
  25. package/src/server/browser-watch-store.mjs +0 -350
  26. package/src/server/browser-watch.mjs +0 -905
  27. package/src/server/ccusage.mjs +0 -1168
  28. package/src/server/claude-accounts.mjs +0 -951
  29. package/src/server/claude-dir.mjs +0 -213
  30. package/src/server/codex-auth.mjs +0 -388
  31. package/src/server/codex-dir.mjs +0 -171
  32. package/src/server/codex-quota.mjs +0 -449
  33. package/src/server/codex-usage.mjs +0 -512
  34. package/src/server/cswap-admin.mjs +0 -1562
  35. package/src/server/cswap-auto.mjs +0 -658
  36. package/src/server/cswap-install.mjs +0 -641
  37. package/src/server/deck-home.mjs +0 -243
  38. package/src/server/deck-prefs.mjs +0 -301
  39. package/src/server/deck-probe.mjs +0 -111
  40. package/src/server/detach.mjs +0 -244
  41. package/src/server/exec.mjs +0 -996
  42. package/src/server/global-install.mjs +0 -67
  43. package/src/server/hwmonitor.mjs +0 -56
  44. package/src/server/index.mjs +0 -6043
  45. package/src/server/installer.mjs +0 -912
  46. package/src/server/invoked-as.mjs +0 -144
  47. package/src/server/lan-about.mjs +0 -119
  48. package/src/server/lan-engine.mjs +0 -952
  49. package/src/server/lan-reach.mjs +0 -256
  50. package/src/server/lan-socket.mjs +0 -682
  51. package/src/server/lan-sync.mjs +0 -941
  52. package/src/server/lhm-parse.mjs +0 -91
  53. package/src/server/log-tail.mjs +0 -139
  54. package/src/server/log-writer.mjs +0 -322
  55. package/src/server/login-service.mjs +0 -473
  56. package/src/server/macmon.mjs +0 -310
  57. package/src/server/npx.mjs +0 -264
  58. package/src/server/open-url.mjs +0 -242
  59. package/src/server/presence.mjs +0 -40
  60. package/src/server/quota.mjs +0 -792
  61. package/src/server/relay-guard.mjs +0 -507
  62. package/src/server/reset-label.mjs +0 -78
  63. package/src/server/retire-sound-hook.mjs +0 -349
  64. package/src/server/running-deck.mjs +0 -234
  65. package/src/server/self-update.mjs +0 -1380
  66. package/src/server/stop-deck.mjs +0 -171
  67. package/src/server/supervisor.mjs +0 -392
  68. package/src/server/system-metrics.mjs +0 -1825
  69. package/src/server/term.mjs +0 -686
  70. package/src/server/uv-bootstrap.mjs +0 -337
@@ -1,244 +0,0 @@
1
- // How `ccdeck` stops dying with the terminal it was typed into.
2
- //
3
- // The deck was a daemon in everything but its lifecycle. It holds the hooks
4
- // Claude Code posts every event to, it answers the LAN beacon that lets paired
5
- // machines repair each other's expired logins, and it watches the quota that
6
- // auto-switch trips on — and all three stopped the moment a window closed. That
7
- // is the wrong unit: those things should be true for as long as the machine is
8
- // awake, not for as long as somebody keeps a tab of a terminal open.
9
- //
10
- // NODE CANNOT DAEMONISE ITSELF. There is no fork(2), so "run in the background"
11
- // is always a detached CHILD plus a parent that leaves. This file is the parent.
12
- // It spawns bin/agent-dag.js again — marked, so the copy does not do this a
13
- // second time — in its own process group, watches it come up, and exits.
14
- //
15
- // WHERE THE CHILD'S OUTPUT GOES, and why it is not a pipe. A pipe dies with this
16
- // process: the moment the launcher exits, the read end closes and every later
17
- // write in the deck is an EPIPE on an unhandled 'error' — which ends the deck we
18
- // just detached, minutes or hours later, for no reason a user could ever trace.
19
- // So the child's stdout and stderr are a FILE from the first instruction, and
20
- // the terminal is fed by TAILING that file rather than by holding a pipe open.
21
- // One sink, no handover, nothing to break when the launcher goes.
22
- //
23
- // That file is `deck.log`, and it is truncated only when no deck is registered
24
- // on this machine. A running deck holds an open descriptor into it, so
25
- // truncating underneath one would punch a hole in the log of a deck nobody
26
- // asked to disturb — and the case that reaches here with a deck already up is
27
- // the attach, which has six lines to say and no business erasing anything.
28
- //
29
- // ONE PLATFORM WHERE THIS IS NOT ENOUGH, and it is worth knowing before someone
30
- // reports it as a bug. Windows OpenSSH puts a session's processes in a job
31
- // object it terminates when the session ends, and breaking out of one needs
32
- // CREATE_BREAKAWAY_FROM_JOB, which Node does not expose — `detached` there is
33
- // DETACHED_PROCESS and CREATE_NEW_PROCESS_GROUP, neither of which is a
34
- // breakaway. So a deck started over ssh on Windows dies with that ssh session.
35
- // Measured, and then measured the other way: the same deck started by the login
36
- // task, outside any such job, was still serving from a later ssh session. An
37
- // ordinary Windows terminal is the console-control-event mechanism rather than
38
- // a job object, and a detached process has no console to receive one.
39
- //
40
- // COLOUR IS PASSED DOWN, MOTION IS NOT. Writing to a file makes `isTTY` false in
41
- // the child, which switches off both — so FORCE_COLOR and COLUMNS are handed
42
- // over to bring the colour and the layout back, and the pulse line stays off
43
- // because motionOK asks isTTY directly and nothing overrides it. The trade is
44
- // that deck.log carries escape sequences: it is a mirror of the terminal the
45
- // deck was started from, which is what makes `--logs` worth reading, and the
46
- // launcher passes nothing at all when it is not itself a terminal.
47
- import { spawn } from "node:child_process";
48
- import { closeSync, mkdirSync, openSync, readSync, statSync } from "node:fs";
49
- import { join } from "node:path";
50
-
51
- /** The marker that stops the child doing this again. A fork bomb is the only
52
- * way this file can fail catastrophically, so the guard is one variable with
53
- * one spelling, read in exactly one place. */
54
- export const DETACHED_ENV = "AGENTS_DECK_DETACHED";
55
-
56
- /** What the deck wrote where a terminal would have shown it. Beside
57
- * `events.jsonl`, which is the agents' events rather than the deck's own
58
- * voice — two different logs, and conflating them was never on the table. */
59
- export const DECK_LOG = "deck.log";
60
-
61
- /**
62
- * Truncate this log, or append to it?
63
- *
64
- * Truncated when nothing is registered, which is the ordinary start and the
65
- * only moment the previous contents are certainly nobody's. Appended otherwise,
66
- * because a registered deck is holding an open descriptor at some offset into
67
- * this very file: truncating under it does not make it start again at zero, it
68
- * makes its next write land past the end and leave a hole.
69
- */
70
- export function logMode(liveCount) {
71
- return liveCount > 0 ? "a" : "w";
72
- }
73
-
74
- /**
75
- * What to add to the child's environment.
76
- *
77
- * Only ever additions, and only the two the child cannot work out for itself.
78
- * Nothing is passed when the launcher is not a terminal — a piped or CI start
79
- * gets a plain log file, which is what a log file should be when nobody is
80
- * watching it be written.
81
- */
82
- export function detachEnv({ isTTY = false, profile = "none", columns = 0 } = {}) {
83
- const out = { [DETACHED_ENV]: "1" };
84
- if (!isTTY || profile === "none") return out;
85
- // The tier this terminal was detected at, in FORCE_COLOR's own grammar, so
86
- // the child's colorProfile lands on the same answer rather than on "colour,
87
- // at the safe floor".
88
- out.FORCE_COLOR = profile === "truecolor" ? "3" : profile === "ansi256" ? "2" : "1";
89
- if (Number.isInteger(columns) && columns > 0) out.COLUMNS = String(columns);
90
- return out;
91
- }
92
-
93
- /**
94
- * The command that ends it, spelled the way this user would have to type it.
95
- *
96
- * An npx run has no `ccdeck` on PATH — that is the whole point of npx — so
97
- * telling one to run `ccdeck --stop` is telling them to run something that does
98
- * not exist on their machine. `invokedAs` already answers which of the three
99
- * published names was typed; this only decides whether `npx ` goes in front.
100
- */
101
- export function stopCommand({ npx = false, invokedAs = null, product = "ccdeck" } = {}) {
102
- const name = invokedAs || product;
103
- return npx ? `npx ${name} --stop` : `${name} --stop`;
104
- }
105
-
106
- /**
107
- * Copy a growing file to a stream, from an offset, until told to stop.
108
- *
109
- * Polled rather than watched. `fs.watch` is three different implementations
110
- * with three different truths about append-only writes, and the thing being
111
- * watched here lives for a few seconds; 40ms of latency on a boot report is
112
- * invisible and the code is the same on every platform.
113
- */
114
- export function tailFile(path, out, { from = 0, everyMs = 40 } = {}) {
115
- let fd = null;
116
- let at = from;
117
- let stopped = false;
118
- const buf = Buffer.alloc(64 * 1024);
119
- const pump = () => {
120
- if (fd === null) {
121
- try { fd = openSync(path, "r"); } catch { return; }
122
- }
123
- for (;;) {
124
- let n = 0;
125
- try { n = readSync(fd, buf, 0, buf.length, at); } catch { return; }
126
- if (n <= 0) return;
127
- at += n;
128
- try { out.write(Buffer.from(buf.subarray(0, n))); } catch { return; }
129
- }
130
- };
131
- const timer = setInterval(pump, everyMs);
132
- timer.unref?.();
133
- return {
134
- pump,
135
- stop() {
136
- if (stopped) return;
137
- stopped = true;
138
- clearInterval(timer);
139
- // One last read, always: the deck's final lines are usually written in the
140
- // same tick as the message that says it is up.
141
- pump();
142
- try { if (fd !== null) closeSync(fd); } catch { /* nothing to close */ }
143
- },
144
- };
145
- }
146
-
147
- /**
148
- * Spawn the detached copy, show its boot, and leave.
149
- *
150
- * Never returns. Every way out of this function is an exit, because the whole
151
- * contract of the launcher is that the terminal comes back:
152
- *
153
- * the deck came up → print the background line, unref, exit 0
154
- * the child exited first → whatever it wrote is already on screen (the attach
155
- * prints and exits 0 through exactly this path), so
156
- * exit with its code
157
- * Ctrl+C while waiting → the child is in its own process group and the
158
- * terminal's signal never reached it, so it is ended
159
- * here. You interrupted a start, not a running deck.
160
- */
161
- export async function detachAndWatch({
162
- file,
163
- argv = [],
164
- logDir,
165
- liveCount = 0,
166
- env = process.env,
167
- out = process.stdout,
168
- isTTY = false,
169
- profile = "none",
170
- columns = 0,
171
- execPath = process.execPath,
172
- spawnFn = spawn,
173
- backgroundLine = "",
174
- exit = (code) => process.exit(code),
175
- } = {}) {
176
- const path = join(logDir, DECK_LOG);
177
- try { mkdirSync(logDir, { recursive: true }); } catch { /* reported by the open below */ }
178
-
179
- const mode = logMode(liveCount);
180
- // Where the terminal starts reading. An append leaves the previous deck's log
181
- // alone and shows only what this child writes.
182
- const from = mode === "a" ? (() => { try { return statSync(path).size; } catch { return 0; } })() : 0;
183
-
184
- let fd;
185
- try {
186
- fd = openSync(path, mode, 0o600);
187
- } catch (err) {
188
- // A log we cannot open is not a reason to refuse to start. Say so once and
189
- // run in the foreground, which is exactly what every version before this
190
- // one did.
191
- return { ok: false, reason: err?.code ?? "log_unwritable" };
192
- }
193
-
194
- const child = spawnFn(execPath, [file, ...argv], {
195
- detached: true,
196
- // ONE FILE for both streams, and an IPC channel that is the only thing this
197
- // launcher and that deck ever say to each other: "I am up".
198
- stdio: ["ignore", fd, fd, "ipc"],
199
- env: { ...env, ...detachEnv({ isTTY, profile, columns }) },
200
- windowsHide: true,
201
- });
202
- // Ours to close: the child holds its own duplicate.
203
- try { closeSync(fd); } catch { /* already gone */ }
204
-
205
- const tail = tailFile(path, out, { from });
206
- let done = false;
207
- const finish = (code) => {
208
- if (done) return;
209
- done = true;
210
- tail.stop();
211
- exit(code);
212
- };
213
-
214
- child.on("message", (m) => {
215
- // `booted`, not `listening`. The port is bound well before the report is
216
- // finished — the server-ready row, the log row and the browser line are all
217
- // written after it — so leaving on `listening` would cut the last three
218
- // lines of every boot off the terminal and leave them in the file.
219
- if (!m || typeof m !== "object" || m.type !== "booted" || done) return;
220
- tail.stop();
221
- if (backgroundLine) { try { out.write(backgroundLine); } catch { /* the terminal went */ } }
222
- // Nothing more will be said to each other. Disconnect before unref so the
223
- // child is not left holding a channel to a process that has exited.
224
- try { child.disconnect(); } catch { /* already closed */ }
225
- child.unref();
226
- finish(0);
227
- });
228
-
229
- child.on("exit", (code, signal) => finish(signal ? 1 : (code ?? 0)));
230
- child.on("error", () => finish(1));
231
-
232
- for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
233
- process.on(sig, () => {
234
- // It is in its own process group, so the terminal's Ctrl+C did NOT reach
235
- // it. Without this the interrupted start would carry on in the background
236
- // and the user would have interrupted nothing at all.
237
- try { child.kill("SIGTERM"); } catch { /* already gone */ }
238
- finish(130);
239
- });
240
- }
241
-
242
- // Held open by the child handles above until one of them exits the process.
243
- return new Promise(() => {});
244
- }