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.
- package/README.md +6 -477
- package/package.json +14 -48
- package/shim.js +107 -0
- package/LICENSE +0 -661
- package/LICENSING.md +0 -82
- package/THIRD_PARTY_NOTICES.md +0 -395
- package/bin/agent-dag.js +0 -626
- package/bin/deck.js +0 -1805
- package/dist/web/assets/index-CJYsv0lr.css +0 -1
- package/dist/web/assets/index-Ifm23DDC.js +0 -270
- package/dist/web/index.html +0 -49
- package/hook/hook.js +0 -542
- package/release-notes.json +0 -398
- package/src/server/activity.mjs +0 -52
- package/src/server/agent-activity.mjs +0 -522
- package/src/server/args.mjs +0 -183
- package/src/server/auto-update.mjs +0 -79
- package/src/server/block-notify.mjs +0 -173
- package/src/server/boot-deadline.mjs +0 -127
- package/src/server/brand.mjs +0 -16
- package/src/server/browser-history.mjs +0 -497
- package/src/server/browser-presence.mjs +0 -211
- package/src/server/browser-profiles.mjs +0 -279
- package/src/server/browser-react.mjs +0 -284
- package/src/server/browser-watch-store.mjs +0 -350
- package/src/server/browser-watch.mjs +0 -905
- package/src/server/ccusage.mjs +0 -1168
- package/src/server/claude-accounts.mjs +0 -951
- package/src/server/claude-dir.mjs +0 -213
- package/src/server/codex-auth.mjs +0 -388
- package/src/server/codex-dir.mjs +0 -171
- package/src/server/codex-quota.mjs +0 -449
- package/src/server/codex-usage.mjs +0 -512
- package/src/server/cswap-admin.mjs +0 -1562
- package/src/server/cswap-auto.mjs +0 -658
- package/src/server/cswap-install.mjs +0 -641
- package/src/server/deck-home.mjs +0 -243
- package/src/server/deck-prefs.mjs +0 -301
- package/src/server/deck-probe.mjs +0 -111
- package/src/server/detach.mjs +0 -244
- package/src/server/exec.mjs +0 -996
- package/src/server/global-install.mjs +0 -67
- package/src/server/hwmonitor.mjs +0 -56
- package/src/server/index.mjs +0 -6043
- package/src/server/installer.mjs +0 -912
- package/src/server/invoked-as.mjs +0 -144
- package/src/server/lan-about.mjs +0 -119
- package/src/server/lan-engine.mjs +0 -952
- package/src/server/lan-reach.mjs +0 -256
- package/src/server/lan-socket.mjs +0 -682
- package/src/server/lan-sync.mjs +0 -941
- package/src/server/lhm-parse.mjs +0 -91
- package/src/server/log-tail.mjs +0 -139
- package/src/server/log-writer.mjs +0 -322
- package/src/server/login-service.mjs +0 -473
- package/src/server/macmon.mjs +0 -310
- package/src/server/npx.mjs +0 -264
- package/src/server/open-url.mjs +0 -242
- package/src/server/presence.mjs +0 -40
- package/src/server/quota.mjs +0 -792
- package/src/server/relay-guard.mjs +0 -507
- package/src/server/reset-label.mjs +0 -78
- package/src/server/retire-sound-hook.mjs +0 -349
- package/src/server/running-deck.mjs +0 -234
- package/src/server/self-update.mjs +0 -1380
- package/src/server/stop-deck.mjs +0 -171
- package/src/server/supervisor.mjs +0 -392
- package/src/server/system-metrics.mjs +0 -1825
- package/src/server/term.mjs +0 -686
- package/src/server/uv-bootstrap.mjs +0 -337
package/src/server/detach.mjs
DELETED
|
@@ -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
|
-
}
|