ccdeck 3.29.11 → 3.31.0
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 +4 -3
- package/bin/agent-dag.js +67 -118
- package/bin/cli/help.js +67 -0
- package/bin/cli/login-item.js +182 -0
- package/bin/cli/one-shot.js +254 -0
- package/bin/cli/package.js +39 -0
- package/bin/cli/pulse.js +98 -0
- package/bin/cli/restart.js +179 -0
- package/bin/cli/screen.js +166 -0
- package/bin/cli/second-start.js +120 -0
- package/bin/cli/startup.js +459 -0
- package/bin/cli/uninstall.js +169 -0
- package/bin/deck.js +112 -1559
- package/dist/web/assets/BrowserWatchModal-n63GjLhv.js +1 -0
- package/dist/web/assets/UsageHistoryModal-DEwBX4Fk.js +1 -0
- package/dist/web/assets/index-CZJf1Cpp.js +299 -0
- package/dist/web/assets/index-D4q1ElXU.css +1 -0
- package/dist/web/index.html +2 -2
- package/hook/hook.js +15 -14
- package/package.json +1 -1
- package/release-notes.json +40 -0
- package/src/server/account-projects.mjs +25 -11
- package/src/server/account-routes.mjs +254 -0
- package/src/server/activity.mjs +3 -3
- package/src/server/append-budget.mjs +185 -0
- package/src/server/append-failures.mjs +147 -0
- package/src/server/args.mjs +37 -5
- package/src/server/atomic-write.mjs +300 -0
- package/src/server/block-notify.mjs +0 -5
- package/src/server/boot-lock.mjs +1 -1
- package/src/server/brand.mjs +7 -6
- package/src/server/browser-react.mjs +10 -2
- package/src/server/browser-watch-decks.mjs +146 -0
- package/src/server/browser-watch-feed.mjs +69 -0
- package/src/server/browser-watch-log.mjs +173 -0
- package/src/server/browser-watch-mtime.mjs +18 -0
- package/src/server/browser-watch-relay.mjs +112 -0
- package/src/server/browser-watch-routes.mjs +172 -0
- package/src/server/browser-watch-seen.mjs +127 -0
- package/src/server/browser-watch-store.mjs +14 -143
- package/src/server/browser-watch.mjs +278 -626
- package/src/server/canonical-path.mjs +150 -0
- package/src/server/ccusage-failure.mjs +64 -0
- package/src/server/ccusage-install.mjs +459 -0
- package/src/server/ccusage-runner.mjs +271 -0
- package/src/server/ccusage.mjs +21 -756
- package/src/server/claude-accounts.mjs +220 -464
- package/src/server/claude-collector.mjs +189 -0
- package/src/server/claude-dir.mjs +7 -3
- package/src/server/claude-identity.mjs +5 -4
- package/src/server/claude-reset-credits.mjs +130 -0
- package/src/server/claude-verdicts.mjs +174 -0
- package/src/server/clear-route.mjs +191 -0
- package/src/server/codex-auth.mjs +4 -4
- package/src/server/codex-dir.mjs +52 -8
- package/src/server/codex-enrichment.mjs +310 -0
- package/src/server/codex-quota.mjs +5 -3
- package/src/server/codex-translate.mjs +407 -0
- package/src/server/codex-usage.mjs +8 -5
- package/src/server/codex-watch.mjs +496 -0
- package/src/server/cswap-auto-loop.mjs +342 -0
- package/src/server/cswap-auto-readers.mjs +303 -0
- package/src/server/cswap-auto.mjs +51 -609
- package/src/server/cswap-install.mjs +69 -265
- package/src/server/cswap-layout.mjs +242 -0
- package/src/server/deck-home.mjs +2 -2
- package/src/server/deck-prefs.mjs +58 -184
- package/src/server/deck-scope.mjs +52 -0
- package/src/server/desktop-update-routes.mjs +75 -0
- package/src/server/detach.mjs +78 -0
- package/src/server/discovery.mjs +298 -0
- package/src/server/event-log.mjs +328 -0
- package/src/server/event-pipeline.mjs +235 -0
- package/src/server/event-ring.mjs +214 -0
- package/src/server/event-routes.mjs +352 -0
- package/src/server/event-sink.mjs +26 -0
- package/src/server/exec-children.mjs +128 -0
- package/src/server/exec-not-found.mjs +213 -0
- package/src/server/exec-output.mjs +23 -0
- package/src/server/exec-spec.mjs +341 -0
- package/src/server/exec.mjs +74 -661
- package/src/server/health-route.mjs +42 -0
- package/src/server/hook-challenge.mjs +37 -0
- package/src/server/http-io.mjs +104 -0
- package/src/server/index.mjs +174 -7620
- package/src/server/install-layout.mjs +506 -0
- package/src/server/installer.mjs +79 -608
- package/src/server/jsonl-chunks.mjs +139 -0
- package/src/server/lan-accounts.mjs +27 -0
- package/src/server/lan-beacon.mjs +457 -0
- package/src/server/lan-call.mjs +239 -0
- package/src/server/lan-copies.mjs +270 -0
- package/src/server/lan-deck.mjs +583 -0
- package/src/server/lan-dials.mjs +161 -0
- package/src/server/lan-engine.mjs +475 -1024
- package/src/server/lan-hearing.mjs +49 -0
- package/src/server/lan-inbound.mjs +100 -0
- package/src/server/lan-invite-offer.mjs +77 -0
- package/src/server/lan-invite.mjs +207 -0
- package/src/server/lan-lines.mjs +73 -0
- package/src/server/lan-manifest.mjs +74 -0
- package/src/server/lan-reach.mjs +82 -69
- package/src/server/lan-requests.mjs +249 -0
- package/src/server/lan-round-record.mjs +45 -0
- package/src/server/lan-round-timer.mjs +62 -0
- package/src/server/lan-routes.mjs +143 -0
- package/src/server/lan-serve.mjs +112 -0
- package/src/server/lan-socket.mjs +243 -925
- package/src/server/lan-sync.mjs +58 -1026
- package/src/server/lan-tailnet-poll.mjs +57 -0
- package/src/server/lan-turns.mjs +80 -0
- package/src/server/lan-wire.mjs +620 -0
- package/src/server/lifecycle.mjs +421 -0
- package/src/server/listen.mjs +110 -0
- package/src/server/live-decks.mjs +138 -0
- package/src/server/load-average.mjs +32 -0
- package/src/server/log-election.mjs +235 -0
- package/src/server/log-replay.mjs +374 -0
- package/src/server/log-writer.mjs +72 -557
- package/src/server/login-service.mjs +3 -2
- package/src/server/macmon.mjs +1 -1
- package/src/server/memory-files.mjs +163 -0
- package/src/server/memory-metrics.mjs +178 -0
- package/src/server/metrics-history.mjs +94 -0
- package/src/server/metrics-run.mjs +100 -0
- package/src/server/music-routes.mjs +134 -0
- package/src/server/network-metrics.mjs +5 -3
- package/src/server/network-sampler.mjs +256 -0
- package/src/server/npm-latest.mjs +385 -0
- package/src/server/npm-upgrade.mjs +352 -0
- package/src/server/npx.mjs +41 -7
- package/src/server/output-watch.mjs +5 -4
- package/src/server/pinned-build.mjs +110 -0
- package/src/server/prefs-give-back.mjs +206 -0
- package/src/server/prefs-path.mjs +14 -0
- package/src/server/prefs-refusal.mjs +86 -0
- package/src/server/prefs-routes.mjs +99 -0
- package/src/server/prefs-set-aside.mjs +86 -0
- package/src/server/prefs-state.mjs +70 -0
- package/src/server/process-command.mjs +104 -0
- package/src/server/process-list.mjs +547 -0
- package/src/server/provider-status.mjs +307 -0
- package/src/server/pulse-line.mjs +132 -0
- package/src/server/purge-key.mjs +7 -7
- package/src/server/quota-cli.mjs +174 -0
- package/src/server/quota-oauth.mjs +190 -0
- package/src/server/quota-shape.mjs +229 -0
- package/src/server/quota-store-resets.mjs +206 -0
- package/src/server/quota.mjs +123 -509
- package/src/server/relay-guard.mjs +3 -3
- package/src/server/request-gates.mjs +441 -0
- package/src/server/restart-note.mjs +180 -0
- package/src/server/retire-sound-hook.mjs +3 -3
- package/src/server/ring-bounds.mjs +215 -0
- package/src/server/running-deck.mjs +1 -1
- package/src/server/running-version.mjs +24 -0
- package/src/server/self-update.mjs +39 -1371
- package/src/server/session-enrichment.mjs +622 -0
- package/src/server/session-read-gate.mjs +49 -0
- package/src/server/session-tracking.mjs +242 -0
- package/src/server/sse-clients.mjs +222 -0
- package/src/server/stale-discovery.mjs +30 -0
- package/src/server/static-serve.mjs +81 -0
- package/src/server/supervisor.mjs +73 -12
- package/src/server/system-metrics.mjs +169 -1935
- package/src/server/term.mjs +11 -249
- package/src/server/thermal-metrics.mjs +520 -0
- package/src/server/thermal-sampler.mjs +204 -0
- package/src/server/token-redact.mjs +123 -0
- package/src/server/transcript-gate.mjs +107 -0
- package/src/server/transcript-scan.mjs +563 -0
- package/src/server/usage-routes.mjs +108 -0
- package/src/server/uv-bootstrap.mjs +1 -1
- package/src/server/wordmark.mjs +127 -0
- package/dist/web/assets/BrowserWatchModal-CyAFTvAY.js +0 -1
- package/dist/web/assets/UsageHistoryModal-KJfli0oa.js +0 -1
- package/dist/web/assets/index-Ck2RAla3.css +0 -1
- package/dist/web/assets/index-jmrcEsCj.js +0 -298
|
@@ -69,7 +69,7 @@ import { existsSync } from "node:fs";
|
|
|
69
69
|
import { join } from "node:path";
|
|
70
70
|
import { homedir } from "node:os";
|
|
71
71
|
import { claudeConfigDir } from "./claude-dir.mjs";
|
|
72
|
-
import { readSettingsForWrite, writeFileAtomic } from "./
|
|
72
|
+
import { readSettingsForWrite, writeFileAtomic } from "./atomic-write.mjs";
|
|
73
73
|
|
|
74
74
|
const CLAUDE_DIR = claudeConfigDir();
|
|
75
75
|
const SETTINGS_PATH = join(CLAUDE_DIR, "settings.json");
|
|
@@ -122,8 +122,8 @@ const commandsOf = (entry) =>
|
|
|
122
122
|
* The last two segments are fixed whatever the prefix is — the install
|
|
123
123
|
* directory is always `<config dir>/agent-dag` — and they contain no character
|
|
124
124
|
* any quoting rewrites. Case is folded where the filesystem folds it, which is
|
|
125
|
-
* the other half:
|
|
126
|
-
* paths are", and this comparison did not.
|
|
125
|
+
* the other half: exec-not-found.mjs's own `sameCommand` lowercases "because
|
|
126
|
+
* Windows paths are", and this comparison did not.
|
|
127
127
|
*/
|
|
128
128
|
const SCRIPT_TAILS = ["agent-dag/notify.mjs", "agent-dag/notify.js"];
|
|
129
129
|
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// How much the event ring may hold, and what one event is charged against it.
|
|
2
|
+
//
|
|
3
|
+
// These lived in src/server/index.mjs at the head of the ring buffer section.
|
|
4
|
+
// The ring itself is event-ring.mjs's — the array, the running totals that
|
|
5
|
+
// have to move with it, and the eviction in admitEvent that keeps these bounds
|
|
6
|
+
// — and the boot replay, which sizes its read against the same two numbers,
|
|
7
|
+
// imports them from here as well. The numbers and the walk are unchanged.
|
|
8
|
+
|
|
9
|
+
// MAX_BUFFER is how many events a late SSE subscriber can be replayed.
|
|
10
|
+
// MAX_BUFFER_CHARS is how much they are allowed to weigh, and until #625 there
|
|
11
|
+
// was no such thing — which is the whole defect, because a count is not a bound
|
|
12
|
+
// on memory when the thing being counted has no size of its own. `POST
|
|
13
|
+
// /api/event` admits a body of 5,000,000 characters and nothing between that
|
|
14
|
+
// door and this array shrinks it: hook/hook.js forwards the payload whole, and
|
|
15
|
+
// log-writer.mjs already says in as many words that a PostToolUse carrying a
|
|
16
|
+
// large Read or Bash response is routinely a good fraction of that. So the real
|
|
17
|
+
// ceiling was MAX_BUFFER multiplied by the largest event ingest accepts.
|
|
18
|
+
//
|
|
19
|
+
// Measured on Node 22.14 / macOS, posting 4,900,000-character bodies to a real
|
|
20
|
+
// server on loopback and reading process.memoryUsage() after a forced GC:
|
|
21
|
+
//
|
|
22
|
+
// after 20 events: heapUsed 107MB rss 292MB per-event 4.94MB
|
|
23
|
+
// after 100 events: heapUsed 481MB rss 739MB per-event 4.73MB
|
|
24
|
+
// after 200 events: heapUsed 947MB rss 1231MB per-event 4.69MB
|
|
25
|
+
//
|
|
26
|
+
// 4.69 MB retained per buffered event, flat as the ring fills. A full ring of
|
|
27
|
+
// 2000 of those is 9.4 GB against the 4144 MB heap limit V8 picks on a 32 GB
|
|
28
|
+
// machine, so the count cap was not reachable on any developer machine — and
|
|
29
|
+
// what happened instead of reaching it was not degradation. The same harness
|
|
30
|
+
// under `--max-old-space-size=2048`, roughly the heap an 8 GB laptop gives
|
|
31
|
+
// itself:
|
|
32
|
+
//
|
|
33
|
+
// after 420 events: heapUsed 1975MB rss 2247MB
|
|
34
|
+
// FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
|
|
35
|
+
//
|
|
36
|
+
// 429 events in, 1571 short of the cap. That abort is not catchable, so the SSE
|
|
37
|
+
// stream, the hook ingest and the log all stop together — and `/api/event` is a
|
|
38
|
+
// deliberate OPEN_MUTATION, so about 430 posts from a local process holding no
|
|
39
|
+
// credential at all end the deck. The comment at OPEN_MUTATIONS says the worst
|
|
40
|
+
// a caller does with that route is draw a session that is not there; this is
|
|
41
|
+
// the sentence that made it false.
|
|
42
|
+
//
|
|
43
|
+
// Both bounds are now enforced on every push, evicting oldest-first until each
|
|
44
|
+
// holds. See the eviction in admitEvent for why it is one splice and why the
|
|
45
|
+
// newest event is never the one evicted.
|
|
46
|
+
export const MAX_BUFFER = 2000; // recent HOOK events kept for late SSE subscribers
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The enrichment this deck derives rather than receives, where only the newest
|
|
50
|
+
* one per session has ever been worth anything.
|
|
51
|
+
*
|
|
52
|
+
* These four are last-value-wins STATE, not history: a session's model, its
|
|
53
|
+
* token usage, its context fill and its name. Replaying an older UsageObserved
|
|
54
|
+
* on top of a newer one changes nothing a reader can see, which is precisely
|
|
55
|
+
* why they do not deserve a slot in a window measured in events.
|
|
56
|
+
*
|
|
57
|
+
* MEASURED, at one hook event per session every 3s — an ordinary tool-call
|
|
58
|
+
* cadence, well under every documented cap. Four scanners fire per hook event,
|
|
59
|
+
* throttled at 2.5s, 2.5s and 4s:
|
|
60
|
+
*
|
|
61
|
+
* sessions=10 posted=60 seq_delta=160 amplification=2.67
|
|
62
|
+
* sessions=200 posted=2000 seq_delta=5190 amplification=2.60
|
|
63
|
+
*
|
|
64
|
+
* Flat, near enough: every hook event costs about 2.6 ring slots. At 200
|
|
65
|
+
* sessions the 2000-entry ring held
|
|
66
|
+
* {"PostToolUse":800,"UsageObserved":800,"ContextObserved":400} and spanned
|
|
67
|
+
* 9.5 SECONDS — 60% of the resume window spent on state that is superseded
|
|
68
|
+
* moments later. Close the lid, switch networks, or let a tab sleep for fifteen
|
|
69
|
+
* seconds, and the Last-Event-ID is older than the ring's head: handleSse
|
|
70
|
+
* replays what is left and the client steps lastSeq over the gap, so the tool
|
|
71
|
+
* calls made in those seconds are never drawn and nothing reports the hole.
|
|
72
|
+
*
|
|
73
|
+
* `OutputObserved` is deliberately NOT here. It says something landed at a
|
|
74
|
+
* given moment, which is history — two of them are two events, not one value
|
|
75
|
+
* twice.
|
|
76
|
+
*/
|
|
77
|
+
const LAST_VALUE_WINS = new Set(["ModelObserved", "UsageObserved", "ContextObserved", "SessionNamed"]);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The ring's array-length backstop, distinct from its event budget.
|
|
81
|
+
*
|
|
82
|
+
* MAX_BUFFER now counts hook events only, so the array holds those plus
|
|
83
|
+
* whatever enrichment is interleaved among them — about 2.6x the budget at the
|
|
84
|
+
* amplification measured above. This is the ceiling for a ratio nobody has
|
|
85
|
+
* measured yet: a future scanner, or a cadence that makes enrichment denser
|
|
86
|
+
* still, must not be able to grow the array without bound. It is not the
|
|
87
|
+
* working limit and should never be the binding one; MAX_BUFFER_CHARS remains
|
|
88
|
+
* the memory bound.
|
|
89
|
+
*/
|
|
90
|
+
export const MAX_RING_ENTRIES = MAX_BUFFER * 4;
|
|
91
|
+
|
|
92
|
+
// The byte budget, counted in CHARACTERS — the unit the server already measures
|
|
93
|
+
// payloads in, and the unit MAX_CLIENT_BUFFER_BYTES in sse-clients.mjs is
|
|
94
|
+
// really written in despite its name (there is a long note there about why,
|
|
95
|
+
// which applies here unchanged: a character is one byte of heap while the
|
|
96
|
+
// string stays one-byte and two once it does not).
|
|
97
|
+
//
|
|
98
|
+
// 128 MiB, and the three readings that pick it:
|
|
99
|
+
//
|
|
100
|
+
// - It is 26 times the largest single event ingest can admit (5,000,000
|
|
101
|
+
// characters plus this deck's envelope), so a burst of maximum-size tool
|
|
102
|
+
// responses — eight subagents each returning a big Read — is held whole
|
|
103
|
+
// rather than collapsing the ring to nothing.
|
|
104
|
+
// - It is thirteen times a completely FULL 2000-event ring of ordinary
|
|
105
|
+
// traffic. The mean serialized event is about 5 KB, measured over 4.7k real
|
|
106
|
+
// payloads in a 21 MB events.jsonl (the same sample redactDeckToken's note
|
|
107
|
+
// quotes), so 2000 of them are 10 MB. Ordinary traffic therefore never
|
|
108
|
+
// meets this bound at all and keeps the full count-based replay depth; only
|
|
109
|
+
// the traffic that used to kill the process ever sees it.
|
|
110
|
+
// - Its worst case is a bounded fraction of the heap rather than a multiple
|
|
111
|
+
// of it. 128 MiB of charged characters is at most about 320 MiB of retained
|
|
112
|
+
// heap — the charge below tracks real retention within 2.5x across every
|
|
113
|
+
// payload shape measured — which is 16% of the 2 GB heap an 8 GB laptop
|
|
114
|
+
// picks, against the 9.4 GB the count alone permitted.
|
|
115
|
+
//
|
|
116
|
+
// Exported for the same reason MAX_CLIENT_BUFFER_BYTES is: a bound whose only
|
|
117
|
+
// observable failure is the process running out of memory is a bound no test
|
|
118
|
+
// can assert. See event-ring-byte-cap.test.ts, which pins all three readings.
|
|
119
|
+
export const MAX_BUFFER_CHARS = 128 * 1024 * 1024;
|
|
120
|
+
|
|
121
|
+
// What one envelope costs on top of its payload — seq, epoch, receivedAt,
|
|
122
|
+
// source and the JSON around them. Measured at 127 characters, the same figure
|
|
123
|
+
// MAX_CLIENT_BUFFER_BYTES is sized against; 128 here so that an event with an
|
|
124
|
+
// empty payload still costs something and a flood of them cannot be free.
|
|
125
|
+
const ENVELOPE_CHARS = 128;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* What this payload will cost the ring, charged in characters.
|
|
129
|
+
*
|
|
130
|
+
* Not `JSON.stringify(raw).length`, which is the obvious answer and is wrong
|
|
131
|
+
* here twice over. It allocates a full copy of a payload that can be five
|
|
132
|
+
* megabytes, on the hottest path in the process; and it would defeat the
|
|
133
|
+
* deliberate optimization in pushEvent that skips serializing ENTIRELY when
|
|
134
|
+
* nothing is subscribed and nothing is being logged — a headless deck, and the
|
|
135
|
+
* boot replay of a log that only rotates at 50 MB. That optimization is pinned
|
|
136
|
+
* by sse-serialize-once.test.ts, so serializing here would fail the suite as
|
|
137
|
+
* well as the machine.
|
|
138
|
+
*
|
|
139
|
+
* So it is a walk, allocating nothing, in the same iterative shape and for the
|
|
140
|
+
* same stack-overflow reason as redactDeckToken (token-redact.mjs): a body from JSON.parse
|
|
141
|
+
* is free to nest as deeply as it likes and a recursive scan would be a new way
|
|
142
|
+
* to blow the stack inside the request listener, where nothing catches it.
|
|
143
|
+
*
|
|
144
|
+
* WHAT IT CHARGES, and why it is not just the string lengths. Measured on
|
|
145
|
+
* Node 22.14 by parsing twenty copies of a 4.9M-character body of each shape
|
|
146
|
+
* and reading heapUsed after a forced GC — retained per copy, against what
|
|
147
|
+
* string characters alone would have charged:
|
|
148
|
+
*
|
|
149
|
+
* one long string 4.67MB retained 4.67M charged 1.0x
|
|
150
|
+
* array of 8-char strings 3.91MB retained 3.40M charged 1.2x
|
|
151
|
+
* array of small numbers 4.67MB retained 0.00M charged ∞
|
|
152
|
+
* array of tiny objects 10.20MB retained 0.85M charged 12.0x
|
|
153
|
+
* object of distinct keys 12.38MB retained 2.86M charged 4.3x
|
|
154
|
+
*
|
|
155
|
+
* A payload of numbers is INVISIBLE to a string-length charge while retaining
|
|
156
|
+
* 4.67 MB, and an array of small objects is under-charged twelvefold — so a
|
|
157
|
+
* ring bounded that way would have been the same OOM behind a different
|
|
158
|
+
* payload shape. Adding 8 characters per value (V8 spends a tagged slot on
|
|
159
|
+
* each, and small objects and packed arrays measured at 8–46 bytes an entry)
|
|
160
|
+
* and the length of every key brings the same five shapes to 1.0x, 0.6x, 1.0x,
|
|
161
|
+
* 1.7x and 2.5x — i.e. never blind, and never more than 2.5x under the truth.
|
|
162
|
+
* That 2.5x is what MAX_BUFFER_CHARS is sized against. The one direction it
|
|
163
|
+
* over-charges is arrays of short strings, which shortens replay depth and
|
|
164
|
+
* never the other way.
|
|
165
|
+
*
|
|
166
|
+
* Two-byte strings cost twice what they are charged, exactly as they do for
|
|
167
|
+
* MAX_CLIENT_BUFFER_BYTES: a 4.8M-character CJK payload retains 9.35 MB and is
|
|
168
|
+
* charged 4.67M. Folded into the same 2.5x.
|
|
169
|
+
*
|
|
170
|
+
* Cost, measured against the JSON.parse the same event already pays for:
|
|
171
|
+
* 0.21 µs for a realistic 5 KB hook payload, 0.04 µs for a 4.9M-character
|
|
172
|
+
* single string (it is one node), and 22.6 ms for the pathological 222k-tiny-
|
|
173
|
+
* object body — against 96.4 ms to JSON.parse that same body. So the walk is a
|
|
174
|
+
* fifth of a parse the ingest path is paying anyway, and cheaper than
|
|
175
|
+
* redactDeckToken's walk, which does substring searches this one does not.
|
|
176
|
+
*
|
|
177
|
+
* Exported so the charge itself can be asserted rather than inferred from the
|
|
178
|
+
* ring's behaviour.
|
|
179
|
+
*/
|
|
180
|
+
export function payloadChars(raw) {
|
|
181
|
+
if (typeof raw === "string") return raw.length;
|
|
182
|
+
// A top-level primitive is a legal body for `POST /api/event`, same as it is
|
|
183
|
+
// for redactDeckToken. One slot's worth.
|
|
184
|
+
if (raw === null || typeof raw !== "object") return 8;
|
|
185
|
+
|
|
186
|
+
let n = 0;
|
|
187
|
+
const stack = [raw];
|
|
188
|
+
while (stack.length > 0) {
|
|
189
|
+
const node = stack.pop();
|
|
190
|
+
// Arrays and objects walked separately for the reason redactDeckToken gives:
|
|
191
|
+
// an indexed loop is markedly cheaper than `for…in`, and a single
|
|
192
|
+
// PostToolUse response can be an array of thousands.
|
|
193
|
+
if (Array.isArray(node)) {
|
|
194
|
+
n += 8 * node.length;
|
|
195
|
+
for (let i = 0; i < node.length; i++) {
|
|
196
|
+
const v = node[i];
|
|
197
|
+
if (typeof v === "string") n += v.length;
|
|
198
|
+
else if (v !== null && typeof v === "object") stack.push(v);
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
for (const k in node) {
|
|
202
|
+
const v = node[k];
|
|
203
|
+
n += 8 + k.length;
|
|
204
|
+
if (typeof v === "string") n += v.length;
|
|
205
|
+
else if (v !== null && typeof v === "object") stack.push(v);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return n;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// What index.mjs reads besides the four exported above. Listed rather than
|
|
213
|
+
// marked at each declaration, so the declarations read as they did where they
|
|
214
|
+
// came from.
|
|
215
|
+
export { ENVELOPE_CHARS, LAST_VALUE_WINS };
|
|
@@ -61,7 +61,7 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
61
61
|
import { join } from "node:path";
|
|
62
62
|
import { claudeConfigDir } from "./claude-dir.mjs";
|
|
63
63
|
import { challengeDeck, isProcessAlive } from "./deck-probe.mjs";
|
|
64
|
-
import { sameCodexTree } from "./log-
|
|
64
|
+
import { sameCodexTree } from "./log-election.mjs";
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Where every deck on this machine registers itself.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// The version this process is running, read once, at import.
|
|
2
|
+
//
|
|
3
|
+
// Two modules report it and must agree: index.mjs, which answers /api/version
|
|
4
|
+
// with it and hands it to the away-update, and lan-deck.mjs, whose card tells
|
|
5
|
+
// paired decks what this one runs. It was a constant in index.mjs until the
|
|
6
|
+
// LAN engine moved out, and a second read in the second file would be a second
|
|
7
|
+
// answer to "what is running", taken at a slightly different moment. So it is
|
|
8
|
+
// read here, and both import it; index.mjs imports this statically, so the
|
|
9
|
+
// read still happens at boot.
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { dirname, join, resolve } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
|
|
14
|
+
// Resolved the way pinned-build.mjs resolves it, from a file in the same
|
|
15
|
+
// directory.
|
|
16
|
+
const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
17
|
+
|
|
18
|
+
// Read at import — i.e. at boot, before an upgrade can overwrite these files.
|
|
19
|
+
// Reading it later would report whatever npm has since installed and hide the
|
|
20
|
+
// exact drift /api/version exists to expose. See src/server/self-update.mjs.
|
|
21
|
+
export const RUNNING_VERSION = (() => {
|
|
22
|
+
try { return JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8"))?.version ?? null; }
|
|
23
|
+
catch { return null; }
|
|
24
|
+
})();
|