agent-dag 3.22.0 → 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-3FWd7g_W.css +0 -1
  10. package/dist/web/assets/index-BOwtoP02.js +0 -266
  11. package/dist/web/index.html +0 -49
  12. package/hook/hook.js +0 -542
  13. package/release-notes.json +0 -392
  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,256 +0,0 @@
1
- // Why another deck cannot reach this one — read off the machine, not guessed.
2
- //
3
- // THE FAILURE THIS EXISTS FOR. Two decks on one router, LAN sync on at both
4
- // ends, and one of them shows an empty list forever. Reported as "he sees me
5
- // and I see nobody", which is the shape that gives it away: Windows lets a
6
- // process send without asking anybody, and drops unsolicited inbound unless a
7
- // rule says otherwise. So the beacon leaves, the far deck hears it and draws a
8
- // row — and the answer coming back is dropped at this machine's edge. Both
9
- // halves of the feature look broken from here and neither is.
10
- //
11
- // WHY IT CANNOT BE FIXED FROM HERE, and this is settled rather than open.
12
- // Creating a firewall rule needs elevation, and `relay-guard.mjs` already wrote
13
- // down why no route in this server may raise a password dialog: `index.mjs`
14
- // deliberately trusts a request carrying no Origin header so hook.js and curl
15
- // keep working, on the reasoning that a process able to POST to loopback can
16
- // already run anything as the user. That holds only while no route can do
17
- // something the caller could not do for itself. One route that elevates hands
18
- // every local process a way to make an authentication prompt appear wearing
19
- // ccdeck's name, at a moment ccdeck chose. So this module reads, decides, and
20
- // hands back a command as TEXT. It is the same shape and the same refusal.
21
- //
22
- // WHY IT IS WORTH SAYING ANYTHING AT ALL, given a manual address already makes
23
- // the feature work without any of this: because "nothing here yet" and "this
24
- // machine cannot be reached" are different facts, and an empty list says the
25
- // first while meaning the second. A person reading it concludes the feature is
26
- // broken, or that the other machine is off. Neither is true and neither is
27
- // actionable.
28
- //
29
- // EVERYTHING BELOW IS A PURE FUNCTION. The probe is one PowerShell call that
30
- // knows nothing and reports three lists; every judgement is here, where the
31
- // suite can run it on any platform. That is the same division lan-sync.mjs and
32
- // lan-socket.mjs already keep, for the same reason.
33
-
34
- /** What the probe asks Windows for. Three reads, none of them privileged —
35
- * measured: `Get-NetConnectionProfile`, `Get-NetFirewallProfile` and
36
- * `Get-NetFirewallApplicationFilter` all answer for an ordinary user, while
37
- * `Get-NetFirewallPortFilter` returns "Access is denied". So the rule this
38
- * looks for is scoped to a PROGRAM rather than to a port, which is also the
39
- * only kind that keeps working: the sync listener's port is ephemeral and is
40
- * a different number after every restart. */
41
- export const PROBE_PS = [
42
- "$ErrorActionPreference='SilentlyContinue'",
43
- "$nets = @(Get-NetConnectionProfile | ForEach-Object { [pscustomobject]@{ alias=$_.InterfaceAlias; category=[string]$_.NetworkCategory; v4=[string]$_.IPv4Connectivity } })",
44
- "$profiles = @(Get-NetFirewallProfile | ForEach-Object { [pscustomobject]@{ name=[string]$_.Name; enabled=[bool]$_.Enabled } })",
45
- "$rules = @(Get-NetFirewallApplicationFilter | Where-Object { $_.Program -eq $env:CCDECK_EXE } | ForEach-Object { $_ | Get-NetFirewallRule } | ForEach-Object { [pscustomobject]@{ direction=[string]$_.Direction; action=[string]$_.Action; enabled=[bool]$_.Enabled; profile=[string]$_.Profile } })",
46
- // WHICH INTERFACE IS THE LAN, asked of the routing table rather than guessed
47
- // from a name. The beacon goes to 255.255.255.255, so the interface that
48
- // carries the broadcast IS the one this feature lives or dies on — and on a
49
- // machine with Tailscale beside wifi the two have different categories, so
50
- // picking the wrong one reports Private and clears a machine that is blocked.
51
- "$bcast = [string](Find-NetRoute -RemoteIPAddress 255.255.255.255 | Select-Object -First 1 -ExpandProperty InterfaceAlias)",
52
- "[pscustomobject]@{ nets=$nets; profiles=$profiles; rules=$rules; bcast=$bcast } | ConvertTo-Json -Depth 4 -Compress",
53
- ].join("; ");
54
-
55
- /**
56
- * Read what the probe printed, or null.
57
- *
58
- * REFUSES RATHER THAN GUESSES. Every field here comes back from a shell on
59
- * somebody else's machine, where a policy, a missing cmdlet or a locale can
60
- * turn any of it into something this did not expect. A null answer means the
61
- * panel says nothing, which is what it did before this module existed — the
62
- * one outcome that cannot make anything worse.
63
- *
64
- * `ConvertTo-Json` collapses a one-element array to a bare object, which is
65
- * the classic way a Windows probe passes its own tests and fails on the one
66
- * machine that has exactly one network. Normalised here rather than worked
67
- * around at each use.
68
- */
69
- export function readProbe(stdout) {
70
- let raw = null;
71
- try { raw = JSON.parse(String(stdout ?? "").trim()); }
72
- catch { return null; }
73
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
74
- const list = v => (Array.isArray(v) ? v : v == null ? [] : [v]).filter(x => x && typeof x === "object");
75
- return {
76
- nets: list(raw.nets).map(n => ({
77
- alias: typeof n.alias === "string" ? n.alias : "",
78
- category: typeof n.category === "string" ? n.category : "",
79
- v4: typeof n.v4 === "string" ? n.v4 : "",
80
- })).filter(n => n.alias),
81
- profiles: list(raw.profiles).map(p => ({
82
- name: typeof p.name === "string" ? p.name : "",
83
- enabled: p.enabled === true,
84
- })).filter(p => p.name),
85
- rules: list(raw.rules).map(r => ({
86
- direction: typeof r.direction === "string" ? r.direction : "",
87
- action: typeof r.action === "string" ? r.action : "",
88
- enabled: r.enabled === true,
89
- profile: typeof r.profile === "string" ? r.profile : "",
90
- })),
91
- bcast: typeof raw.bcast === "string" ? raw.bcast.trim() : "",
92
- };
93
- }
94
-
95
- /** The interfaces this machine holds a routable v4 address on, as the names
96
- * Windows knows them by. `faces` is passed in rather than read, for the reason
97
- * everything else here is pure. Measured on Windows 11 26200: node's key and
98
- * `Get-NetConnectionProfile`'s `InterfaceAlias` are the same string, which is
99
- * what makes matching them worth doing at all. */
100
- export function localAliases(faces) {
101
- const out = [];
102
- for (const [name, list] of Object.entries(faces ?? {})) {
103
- for (const n of list ?? []) {
104
- if (n?.internal) continue;
105
- if (n?.family !== "IPv4" && n?.family !== 4) continue;
106
- if (typeof n.address !== "string" || n.address.startsWith("169.254.")) continue;
107
- if (!out.includes(name)) out.push(name);
108
- }
109
- }
110
- return out;
111
- }
112
-
113
- /** A connection profile's category is spelled `DomainAuthenticated`; the
114
- * firewall profile it answers to is spelled `Domain`. One mapping, in one
115
- * place, because getting it wrong reads the wrong profile's `Enabled` and
116
- * produces a confident wrong answer rather than a visible failure. */
117
- export function profileFor(category) {
118
- const c = String(category ?? "").trim().toLowerCase();
119
- if (c === "public") return "Public";
120
- if (c === "private") return "Private";
121
- if (c === "domainauthenticated" || c === "domain") return "Domain";
122
- return null;
123
- }
124
-
125
- /**
126
- * The network this deck is actually on.
127
- *
128
- * A machine with Tailscale, a container bridge and wifi has several profiles
129
- * and only one of them is the LAN somebody means.
130
- *
131
- * `aliases` IS IN PREFERENCE ORDER and is walked in that order rather than
132
- * matched as a set. The caller puts the broadcast route's interface first,
133
- * because that is the one the beacon actually leaves by; matching whichever
134
- * happened to come first in the probe's own output is how the machine this was
135
- * written on reports Tailscale's Private and clears a wifi that is Public.
136
- */
137
- export function lanNet(nets, aliases = []) {
138
- const rows = Array.isArray(nets) ? nets : [];
139
- for (const a of (Array.isArray(aliases) ? aliases : [])) {
140
- const want = String(a ?? "").trim().toLowerCase();
141
- if (!want) continue;
142
- const hit = rows.find(n => n.alias.toLowerCase() === want);
143
- if (hit) return hit;
144
- }
145
- return rows.find(n => n.v4 && n.v4 !== "Disconnected") ?? rows[0] ?? null;
146
- }
147
-
148
- /** Does an inbound allow rule for this program already cover that profile?
149
- * A rule's `Profile` is a comma-joined list, and `Any` means all of them. */
150
- export function ruleCovers(rules, profileName) {
151
- const want = String(profileName ?? "").toLowerCase();
152
- return (Array.isArray(rules) ? rules : []).some(r => {
153
- if (!r.enabled) return false;
154
- if (r.direction.toLowerCase() !== "inbound") return false;
155
- if (r.action.toLowerCase() !== "allow") return false;
156
- const on = r.profile.split(",").map(s => s.trim().toLowerCase());
157
- return on.includes("any") || on.includes(want);
158
- });
159
- }
160
-
161
- /**
162
- * The command that would change it — as text, for a person to paste into an
163
- * elevated shell. Never run from here; see the header.
164
- *
165
- * ONE RULE, SCOPED TO THE PROGRAM. It covers the discovery socket and the sync
166
- * listener at once, and it goes on covering the sync listener after a restart
167
- * moves it — a rule written against today's port is a rule that works today.
168
- *
169
- * The category line is only offered when the network is Public, and it is
170
- * offered rather than assumed: Public is the right setting for a café and the
171
- * wrong one for the router at home, and this module cannot tell which one
172
- * somebody is sitting in. The panel says so beside it.
173
- */
174
- export function fixSteps({ category, alias, exePath }) {
175
- const steps = [];
176
- if (profileFor(category) === "Public" && alias) {
177
- steps.push(`Set-NetConnectionProfile -InterfaceAlias "${alias}" -NetworkCategory Private`);
178
- }
179
- steps.push(
180
- "New-NetFirewallRule -DisplayName \"ccdeck (local network)\" -Direction Inbound"
181
- + ` -Program "${exePath}" -Action Allow -Profile Private`,
182
- );
183
- return steps;
184
- }
185
-
186
- /**
187
- * Whether other decks can reach this one, and what to do when they cannot.
188
- *
189
- * `null` means "no opinion", and it is the answer for every machine this cannot
190
- * speak about: anything that is not Windows, and any probe that did not come
191
- * back. Silence rather than a hedge — a panel line that says "possibly" about a
192
- * thing it did not measure is worse than no line.
193
- *
194
- * `blocked: false` is a real finding and is worth returning: it lets the panel
195
- * stop blaming the firewall for an empty list and say the other thing instead,
196
- * which is that nobody else is running.
197
- */
198
- export function reachability({ platform, probe, aliases = [], exePath = "" } = {}) {
199
- if (platform !== "win32") return null;
200
- if (!probe) return null;
201
- // The broadcast route first, then the interfaces the deck holds an address
202
- // on. The routing table is the authority on where a beacon goes; the deck's
203
- // own addresses are the fallback for a machine where that read came back
204
- // empty, and the probe's first connected network is the fallback for that.
205
- const net = lanNet(probe.nets, [probe.bcast, ...aliases]);
206
- const category = net?.category ?? "";
207
- const name = profileFor(category);
208
- if (!name) return null;
209
- const prof = probe.profiles.find(p => p.name.toLowerCase() === name.toLowerCase());
210
- // A firewall that is off blocks nothing, and saying otherwise would send
211
- // somebody to add a rule that changes nothing.
212
- if (prof && !prof.enabled) return { blocked: false, why: "firewall off", category, alias: net?.alias ?? "" };
213
- if (ruleCovers(probe.rules, name)) {
214
- return { blocked: false, why: "rule present", category, alias: net?.alias ?? "" };
215
- }
216
- return {
217
- blocked: true,
218
- why: "no inbound rule",
219
- category,
220
- alias: net?.alias ?? "",
221
- // The reason in the reader's terms, not the registry's. What they need to
222
- // know is which half is broken, because the other half is what makes the
223
- // workaround below obvious rather than magic.
224
- text: name === "Public"
225
- ? "This network is set to Public, and Windows drops what other decks send. They can still hear this deck — that is why one of them may already show it."
226
- : "Windows has no inbound rule for this deck, so it drops what other decks send. They can still hear it — that is why one of them may already show this machine.",
227
- steps: fixSteps({ category, alias: net?.alias ?? "", exePath }),
228
- };
229
- }
230
-
231
- /**
232
- * The way out that needs no rule at all, and the reason this module is not the
233
- * whole answer.
234
- *
235
- * A round is one OUTBOUND connection — `roundWith` dials, both sides prove
236
- * themselves over that socket, and the manifest and any transfer ride it home.
237
- * Nothing in a round needs this machine to accept a connection. So a deck that
238
- * cannot be reached can still do every part of this by dialling first, and the
239
- * two controls that make it dial are an address somebody typed and an invite
240
- * somebody else minted.
241
- *
242
- * WHICH WAY THE INVITE GOES IS THE WHOLE OF IT, and nothing said so before.
243
- * An invite carries the addresses of the deck that MINTED it, and the deck that
244
- * PASTES it dials them. So a blocked machine that mints an invite is asking to
245
- * be called, which is the one thing it cannot receive; the same pair works on
246
- * the first try if the invite is minted at the other end and pasted here. Same
247
- * two controls, opposite order, and only one of the orders works.
248
- */
249
- export function workaround(blocked) {
250
- if (!blocked) return null;
251
- return {
252
- dial: "This deck can still reach out, and one connection is all a round needs.",
253
- invite: "Ask them to make the invite and paste it here — an invite is dialled by whoever pastes it, so it has to be pasted on this machine rather than made on it.",
254
- address: "Or type their address below, and this deck will call them.",
255
- };
256
- }