@agentstrack/collector 0.2.1 → 0.4.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/CHANGELOG.md +209 -1
- package/README.md +114 -38
- package/dist/adapters/claude.d.ts +18 -0
- package/dist/adapters/claude.js +153 -45
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/codex.d.ts +15 -1
- package/dist/adapters/codex.js +87 -34
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/opencode.d.ts +17 -6
- package/dist/adapters/opencode.js +72 -26
- package/dist/adapters/opencode.js.map +1 -1
- package/dist/adapters/types.d.ts +16 -0
- package/dist/adapters/types.js +61 -0
- package/dist/adapters/types.js.map +1 -1
- package/dist/cli.js +164 -33
- package/dist/cli.js.map +1 -1
- package/dist/commands/service.js +40 -10
- package/dist/commands/service.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +18 -5
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +73 -21
- package/dist/daemon.js +350 -119
- package/dist/daemon.js.map +1 -1
- package/dist/git/commits.d.ts +7 -1
- package/dist/git/commits.js +36 -17
- package/dist/git/commits.js.map +1 -1
- package/dist/git/repo.d.ts +13 -4
- package/dist/git/repo.js +34 -20
- package/dist/git/repo.js.map +1 -1
- package/dist/machine.d.ts +27 -0
- package/dist/machine.js +46 -0
- package/dist/machine.js.map +1 -0
- package/dist/privacy/pipeline.d.ts +6 -0
- package/dist/privacy/pipeline.js +41 -7
- package/dist/privacy/pipeline.js.map +1 -1
- package/dist/privacy/redact.d.ts +15 -2
- package/dist/privacy/redact.js +45 -6
- package/dist/privacy/redact.js.map +1 -1
- package/dist/queue/event-id.d.ts +9 -0
- package/dist/queue/event-id.js +15 -0
- package/dist/queue/event-id.js.map +1 -0
- package/dist/queue/spool.d.ts +24 -5
- package/dist/queue/spool.js +89 -33
- package/dist/queue/spool.js.map +1 -1
- package/dist/queue/tailer.d.ts +27 -4
- package/dist/queue/tailer.js +89 -28
- package/dist/queue/tailer.js.map +1 -1
- package/dist/sessions/title.d.ts +14 -2
- package/dist/sessions/title.js +18 -6
- package/dist/sessions/title.js.map +1 -1
- package/dist/transport/client.d.ts +37 -13
- package/dist/transport/client.js +50 -3
- package/dist/transport/client.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,204 @@ released as a major version, with a migration note in this file.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.4.1] — 2026-09-05
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **A session title could ship a piece of a secret (security).** `derived_title` is the first
|
|
18
|
+
meaningful line of your prompt, cut to 120 characters. Until now that cut was made on the **raw**
|
|
19
|
+
prompt and the secret scan ran afterwards, on the already-shortened title. If a key happened to
|
|
20
|
+
straddle the 120-character boundary, the cut split it in two, the leftover head no longer looked
|
|
21
|
+
like a key to any pattern, and it was uploaded as ordinary title text. A prompt of a hundred
|
|
22
|
+
characters followed by an `sk-ant-…` key produced a title ending in `sk-ant-api03-AAAAA…` while
|
|
23
|
+
the event dutifully reported `secrets_redacted: [{ anthropic_key, 1 }]` — the tally was right and
|
|
24
|
+
the title still carried a fragment.
|
|
25
|
+
|
|
26
|
+
The prompt is now redacted **before** the title is taken from it, in all three adapters (Claude
|
|
27
|
+
Code, Codex, OpenCode), so a truncation can only ever cut through a `[REDACTED:…]` marker. The
|
|
28
|
+
privacy pipeline still redacts `derived_title` afterwards; that pass is now a no-op and stays in
|
|
29
|
+
place as defence in depth.
|
|
30
|
+
|
|
31
|
+
The same cut-then-scan mistake applied to a Codex `error` event's `message`, truncated to 1000
|
|
32
|
+
characters; it is redacted before truncation now too.
|
|
33
|
+
|
|
34
|
+
**What to do:** only a partial value could escape, never a whole one, and only when a secret sat
|
|
35
|
+
across the cut. But a fragment is enough to identify which key was pasted, and the rest of it may
|
|
36
|
+
be guessable from context. If you have used `analytics` or `full` mode, look through your existing
|
|
37
|
+
session titles for key-shaped fragments, and rotate anything you find. Titles produced from 0.4.1
|
|
38
|
+
on are safe.
|
|
39
|
+
|
|
40
|
+
Fragments already uploaded stay in the product until you delete those sessions — upgrading the
|
|
41
|
+
collector does not rewrite history.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- Documented the ordering (redact, then truncate) in the README and `docs/EVENT_SCHEMA.md`, and
|
|
46
|
+
corrected two stale lines there that still said `privacy.prompts: never` leaves `derived_title`
|
|
47
|
+
in place under `mode: full`. It has not since 0.4.0 — `never` drops it in every mode.
|
|
48
|
+
|
|
49
|
+
## [0.4.0] — 2026-09-05
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
- **Secret exposure is reported as metadata.** When local redaction fires, the event now carries
|
|
53
|
+
`secrets_redacted: [{ kind, count }]` — which pattern matched and how many times, sorted by kind
|
|
54
|
+
and omitted entirely when nothing fired. The matched value never travels: not the text, not a
|
|
55
|
+
prefix of it, not a hash, not the surrounding context. The tally is computed **before** the mode
|
|
56
|
+
strip, so it survives `metadata` mode — the mode where a team most wants to know a credential was
|
|
57
|
+
typed into an agent and least wants the credential itself. An org-supplied rule reports as the
|
|
58
|
+
single generic kind `org_rule`, because a rule name can itself describe the shape of that
|
|
59
|
+
organization's secrets.
|
|
60
|
+
|
|
61
|
+
## [0.3.0] — 2026-09-05
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
- **Skills, sub-agents and workflows are named.** A Claude Code `tool_use` named `Skill` carries
|
|
65
|
+
`skill`; `Agent` carries `subagent_type` and `description`; `Workflow` carries `workflow_name`
|
|
66
|
+
(the `name` from the script's `meta` header, when it is that simple). The `Agent` call's prompt and the
|
|
67
|
+
script body are never copied out of the call; a sub-agent transcript's own opening prompt is a
|
|
68
|
+
prompt like any other and follows `privacy.prompts`. The extras ride on `tool.started` and on the matching
|
|
69
|
+
`tool.completed` / `tool.failed`.
|
|
70
|
+
- **Sub-agent transcripts are attributed.** Claude Code writes each sub-agent to
|
|
71
|
+
`<session-uuid>/subagents/[workflows/<wf>/]agent-<id>.jsonl` with the parent's session id; the
|
|
72
|
+
tailer already walked them, but nothing said which lines were the sub-agent's. Every event from
|
|
73
|
+
such a file (or any line with `isSidechain: true`) now carries `sidechain: true`, `agent_id`,
|
|
74
|
+
`agent_kind` (`subagent` | `workflow`) and, from the sibling `agent-<id>.meta.json`, `agent_type`
|
|
75
|
+
— so the sub-agent's own `model.response` usage can be attributed to it server-side.
|
|
76
|
+
- **`user.prompted.ultracode`**, `true` when the prompt contains the whole word `ultracode`
|
|
77
|
+
(case-insensitive). Computed locally before redaction, so it survives `metadata` mode; the prompt
|
|
78
|
+
does not travel to be inspected.
|
|
79
|
+
- **Machine info on register and health.** Alongside `hostname`, `os` and `arch` the collector now
|
|
80
|
+
sends `os_release` and `machine_kind` — `ci` (a CI env var), `container`
|
|
81
|
+
(`/.dockerenv` or a docker/containerd/kubepods cgroup), `workstation` (macOS, Windows, or Linux
|
|
82
|
+
with a display), `server` (headless Linux), else `unknown`. Health repeats it, so a device that
|
|
83
|
+
changes shape is updated. See the README privacy section for what the server does with it.
|
|
84
|
+
|
|
85
|
+
### Changed
|
|
86
|
+
- `description` (the Agent tool's one-line label) is treated like a title: secret-redacted, and
|
|
87
|
+
dropped in `metadata` mode.
|
|
88
|
+
|
|
89
|
+
### Security
|
|
90
|
+
- **`api_url` must be `https`.** The bearer API key rides on every request, so `http` is now rejected
|
|
91
|
+
for any host except `localhost`/`127.0.0.1`/`[::1]`. `login` prints the URL it is about to use.
|
|
92
|
+
- **The API key can stay off the command line.** `agentstrack login` now takes the key as an optional
|
|
93
|
+
argument and otherwise reads `AGENTSTRACK_API_KEY`, an echo-off terminal prompt, or stdin
|
|
94
|
+
(`agentstrack login < key.txt`), keeping it out of shell history and `ps`.
|
|
95
|
+
- **`config.yaml` is written atomically at mode 600** — a temp file created 0600 then renamed over the
|
|
96
|
+
target, so a crash can no longer leave a truncated config or a brief world-readable window.
|
|
97
|
+
- **Org redaction rules are bounded.** A server-supplied pattern that is malformed, over 256
|
|
98
|
+
characters, or using a backreference is skipped (as a malformed rule already was), the common
|
|
99
|
+
catastrophic nested-quantifier shapes (`(a+)+`, `(a|aa)+`, `((a+)b)+`) are rejected, and org rules
|
|
100
|
+
match only the first 64 KB of a value — a pathological pattern is much less likely to hang the
|
|
101
|
+
single-threaded daemon.
|
|
102
|
+
|
|
103
|
+
### Fixed
|
|
104
|
+
- **Claude Code tokens and cost were inflated ~1.8x.** Claude Code writes one `assistant` line per
|
|
105
|
+
content block of a single response, each repeating the same `message.id` and usage; every line
|
|
106
|
+
became a `model.response`. Usage is now emitted once per `message.id`.
|
|
107
|
+
- **Claude Code tool outcomes were all named `unknown`.** `tool_result` blocks carry only a
|
|
108
|
+
`tool_use_id`; the name is now resolved from the `tool_use` that started the call.
|
|
109
|
+
- **Claude Code prompts pasted with an image (or as text blocks) were never counted**, and slash
|
|
110
|
+
command echoes (`<command-name>`, `<command-message>`, `<local-command-stdout>`,
|
|
111
|
+
`<task-notification>`) were counted as human prompts. Both fixed.
|
|
112
|
+
- **Codex sessions went dark after a collector restart.** The session id lived only in memory from
|
|
113
|
+
`session_meta`; it is now seeded from the rollout file name, which carries the same uuid.
|
|
114
|
+
- **Codex cached tokens were billed twice.** `cached_input_tokens` is a subset of `input_tokens`
|
|
115
|
+
in Codex; the collector now subtracts it so the two are exclusive, as the schema documents.
|
|
116
|
+
- **Codex shell commands landed as `shell` with `duration_ms: 0` and never failed.**
|
|
117
|
+
`exec_command_end` carries an argv list, a `{secs,nanos}` duration and a numeric exit code;
|
|
118
|
+
`function_call_output.output` is a string. Both are read as they really are, and `tool.failed`
|
|
119
|
+
is emitted on a non-zero exit.
|
|
120
|
+
- **OpenCode `session.ended` was rejected by the server on every emit** (missing
|
|
121
|
+
`external_session_id`, `reason` outside the enum). It now sends `reason: normal` with the
|
|
122
|
+
archived/compacted distinction in `end_kind`.
|
|
123
|
+
- **OpenCode resumed sessions never got a `session.started`.** Sessions are fetched by
|
|
124
|
+
`time_updated` but starts were gated on a `time_created` cursor; a per-session marker in the
|
|
125
|
+
spool's meta store replaces it.
|
|
126
|
+
- **`logout` now tears the service down first.** It previously left the launchd/systemd unit
|
|
127
|
+
installed, so the supervisor kept restarting an unauthenticated collector. Both units now restart
|
|
128
|
+
only on a crash (launchd `KeepAlive`/`SuccessfulExit`, systemd `Restart=on-failure` with a
|
|
129
|
+
5-in-5-minutes start limit), and the unauthenticated foreground path exits cleanly so the
|
|
130
|
+
supervisor idles.
|
|
131
|
+
- **Service units handle paths with spaces and non-ASCII characters.** The CLI path is resolved with
|
|
132
|
+
`fileURLToPath` instead of a percent-encoded `URL.pathname`, plist strings are XML-escaped, and
|
|
133
|
+
systemd `ExecStart` arguments are quoted. `AGENTSTRACK_HOME` is written into the unit when set.
|
|
134
|
+
- **A second foreground `start` refuses to run** when one is already collecting (checked via the pid
|
|
135
|
+
file, created with `wx`), preventing two collectors from racing on one spool.
|
|
136
|
+
- **`stop` verifies the pid still belongs to a collector** (via `ps`) before signalling it, so a
|
|
137
|
+
recycled pid in a stale pid file is not killed.
|
|
138
|
+
- **`doctor` reports the real scan window.** It now prints `modified in the last N day(s)` using
|
|
139
|
+
`tracking.max_age_days` instead of a hard-coded "7 days".
|
|
140
|
+
- **`git.commit` polling stops re-diffing history every tick.** Each repo's `git log --since` now
|
|
141
|
+
starts from its last poll, SHAs are listed before any diffstat so `git show --numstat` runs only for
|
|
142
|
+
commits not yet emitted, and the emitted-SHA guard is pruned by age instead of cleared wholesale, so
|
|
143
|
+
a commit inside the lookback window is never re-emitted.
|
|
144
|
+
|
|
145
|
+
### Changed
|
|
146
|
+
- `detect()` reads only the first 16 KB of the newest transcript (by mtime) for the agent version,
|
|
147
|
+
cached on mtime, instead of the whole file every 5 s. OpenCode keeps one read-only database
|
|
148
|
+
handle with prepared statements rather than opening and closing one per query, and caches the
|
|
149
|
+
version for 60 s.
|
|
150
|
+
- The contract test now runs every adapter fixture through the server's own payload schemas when
|
|
151
|
+
the server checkout is present, and fails (rather than skips) when `AGENTSTRACK_SERVER_REPO` is
|
|
152
|
+
set but missing.
|
|
153
|
+
- **Node floor is `>=22`** (was `>=24`), matching `.nvmrc` and the runtime the code actually needs;
|
|
154
|
+
CI now tests Node 22 and 24.
|
|
155
|
+
- `status` shows an upload-paused reason when one is present.
|
|
156
|
+
|
|
157
|
+
### Fixed — daemon, queue and transport
|
|
158
|
+
- **The upload failure policy ran once per concurrent batch, not once per wave.** With
|
|
159
|
+
`upload.concurrency: 4` a dead API escalated the backoff counter by four per wave (5-minute waits
|
|
160
|
+
after two waves), slept inside the wave so tailing froze for the duration, and a sibling's success
|
|
161
|
+
reset the counter or undid a `413` halving. `sendBatch` now returns a pure outcome and `flush()`
|
|
162
|
+
applies the policy once on the wave: halve once, one backoff step, strikes only for poison
|
|
163
|
+
batches, counter reset only when the whole wave succeeded. Backoff sets a next-upload time instead
|
|
164
|
+
of sleeping, honours `Retry-After` / `Retry-After-ingest` as the minimum, and a daemon tick sends
|
|
165
|
+
at most five waves before scanning again. Covered by `src/queue/flush.test.ts`.
|
|
166
|
+
- **`401`/`403` no longer count strikes against telemetry.** They pause uploads (`status` shows the
|
|
167
|
+
reason), as does an over-quota `200` — previously acked and dropped locally with the `quota` block
|
|
168
|
+
ignored — and a `200` that rejects every event as malformed, which now logs a version-mismatch
|
|
169
|
+
error instead of deleting the batch.
|
|
170
|
+
- **Checkpoints were written before the events were spooled.** A throw between the two lost those
|
|
171
|
+
lines for good. The tailer now streams in 4 MB chunks and commits each chunk's checkpoint in the
|
|
172
|
+
same transaction as its events; a line over 8 MB is skipped to the next newline and counted, at
|
|
173
|
+
most 64 MB per file is read per scan, short reads are looped, and one unreadable file no longer
|
|
174
|
+
aborts the scan for every file after it.
|
|
175
|
+
- **Re-reading a transcript double-counted on the server.** `event_id` was a fresh `randomUUID` per
|
|
176
|
+
spool write; it is now derived from the adapter, file, byte offset and line content (a UUID v8
|
|
177
|
+
shape), so a rotated inode, a purged spool or a second collector on the same files dedupes.
|
|
178
|
+
- **`VERSION` was hard-coded `0.1.0`.** It is read from `package.json`; the CLI, register and health
|
|
179
|
+
report the published version, and every request carries `User-Agent: agentstrack-collector/<v>`.
|
|
180
|
+
- **A Claude Code response spanning a restart was billed twice.** `model.response` is now keyed on
|
|
181
|
+
`message.id` (the daemon's deterministic id, seeded by the adapter), not on the line, so the
|
|
182
|
+
server's dedupe absorbs the second line's copy after a restart.
|
|
183
|
+
- **One rejected event paused every upload as a "schema mismatch".** A single-event batch the
|
|
184
|
+
server rejects is now struck like any other poison event; the pause is reserved for a whole
|
|
185
|
+
batch rejected without a quota reason.
|
|
186
|
+
- **A failed commit left the checkpoint cache ahead of disk.** `setCheckpoint()` updated the
|
|
187
|
+
in-memory Map before COMMIT; the cache is now reloaded from the table when a transaction throws.
|
|
188
|
+
- **OpenCode cursors and started-markers were written before the events they covered.** They are
|
|
189
|
+
now buffered and committed in the same transaction as the enqueue, so a full disk cannot mark a
|
|
190
|
+
session started that was never spooled.
|
|
191
|
+
- **systemd `WorkingDirectory=` was quoted.** Path-typed settings are not unquoted by systemd; the
|
|
192
|
+
unit now writes the bare path (`ExecStart=` keeps its quoting).
|
|
193
|
+
- **Claude Code and Codex sessions never ended.** The daemon emits `session.ended`
|
|
194
|
+
(`reason: timeout`) for a session quiet longer than `tracking.idle_timeout_seconds`, stamped at
|
|
195
|
+
the moment the timeout elapsed, and `reason: unknown` for anything still open on shutdown.
|
|
196
|
+
- The batch response is validated with zod at the trust boundary; an unreadable body is retried,
|
|
197
|
+
never acked. Rejected `allSettled` outcomes are logged instead of swallowed. Local `batch_size` is
|
|
198
|
+
clamped to the server's `max_batch_events`.
|
|
199
|
+
|
|
200
|
+
### Changed — daemon, queue and transport
|
|
201
|
+
- The spool is `VACUUM`ed at open when the freelist is both over 2048 pages and more than half the
|
|
202
|
+
file (a 206 MB spool holding 78 events was observed), the WAL is capped at 8 MB
|
|
203
|
+
(`journal_size_limit`), statements are prepared once, checkpoints are cached in memory so an
|
|
204
|
+
unchanged file costs no SQL, and WAL/SHM are chmod 600 after they exist.
|
|
205
|
+
- `describeRepo` is cached per scan pass; `flush()`, `reportHealth()` and `depth()` are inside the
|
|
206
|
+
loop's try/catch; the log rotates once at 5 MB to `collector.log.1` and a scan pass writes one
|
|
207
|
+
`Queued N events across M files` line instead of one per file.
|
|
208
|
+
- Org redaction rules are compiled once per server-config refresh and handed to the privacy
|
|
209
|
+
pipeline pre-compiled, instead of being recompiled for every event.
|
|
210
|
+
|
|
13
211
|
## [0.2.1] — 2026-08-31
|
|
14
212
|
|
|
15
213
|
### Fixed
|
|
@@ -203,5 +401,15 @@ As released. Several of these have since been fixed — see `## Unreleased` abov
|
|
|
203
401
|
- The service installer supports launchd and systemd only. `agentstrack start --foreground` works
|
|
204
402
|
anywhere Node 20+ does.
|
|
205
403
|
|
|
206
|
-
|
|
404
|
+
<!-- 0.2.0 and 0.4.0 have no tag: this repository's history was squashed before
|
|
405
|
+
it was made public, and no commit in it carries either version. The releases
|
|
406
|
+
were real (0.2.0 is on npm) and their notes stay above; the compare links
|
|
407
|
+
simply skip to the neighbouring tag that does exist. -->
|
|
408
|
+
|
|
409
|
+
[Unreleased]: https://github.com/agentstrack/collector/compare/v0.4.1...HEAD
|
|
410
|
+
[0.4.1]: https://github.com/agentstrack/collector/compare/v0.3.0...v0.4.1
|
|
411
|
+
[0.4.0]: https://github.com/agentstrack/collector/compare/v0.3.0...v0.4.1
|
|
412
|
+
[0.3.0]: https://github.com/agentstrack/collector/compare/v0.2.1...v0.3.0
|
|
413
|
+
[0.2.1]: https://github.com/agentstrack/collector/compare/v0.1.0...v0.2.1
|
|
414
|
+
[0.2.0]: https://github.com/agentstrack/collector/compare/v0.1.0...v0.2.1
|
|
207
415
|
[0.1.0]: https://github.com/agentstrack/collector/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@agentstrack/collector)
|
|
6
6
|
[](https://github.com/agentstrack/collector/actions/workflows/ci.yml)
|
|
7
7
|
[](./LICENSE)
|
|
8
|
-
[](https://nodejs.org)
|
|
9
9
|
|
|
10
10
|
`@agentstrack/collector` turns the records Claude Code, Codex and OpenCode already keep on your
|
|
11
11
|
machine into a normalized event stream:
|
|
@@ -30,7 +30,7 @@ before anything is: [Verify it yourself](#verify-it-yourself).
|
|
|
30
30
|
## Quick start
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
npm install -g @agentstrack/collector # requires Node >=
|
|
33
|
+
npm install -g @agentstrack/collector # requires Node >= 22
|
|
34
34
|
|
|
35
35
|
agentstrack login at_live_xxxxxxxx_xxxxxxxx # key from Settings → API keys
|
|
36
36
|
agentstrack start # installs a login service and starts collecting
|
|
@@ -86,8 +86,10 @@ Nothing showing up? Run `agentstrack doctor`.
|
|
|
86
86
|
| File paths, **relative to the project root by default** | Send absolute paths — which leak your username and your clients' names — unless you opt in |
|
|
87
87
|
| Lines added/removed per edit, computed locally from the tool input | Send the lines themselves |
|
|
88
88
|
| Git branch, commit SHA, additions/deletions/files changed | Send your git remote URL (only a SHA-256 of it) or commit messages and diffs |
|
|
89
|
-
|
|
|
90
|
-
| Your hostname, OS, arch and each detected agent's version —
|
|
89
|
+
| A session title — the first line of the prompt, secret-redacted and then capped at 120 chars (`analytics` mode and above) | Send the rest of the prompt those titles were taken from |
|
|
90
|
+
| Your hostname, OS and release, arch, Node version, a coarse machine kind (`workstation` / `server` / `container` / `ci`) and each detected agent's version — at registration and with each health report | Show your hostname or IP address in the product — see [Machine info](#machine-info) |
|
|
91
|
+
| That local redaction fired: which pattern matched and how many times (`secrets_redacted`), in every privacy mode | Send the secret it matched — not the text, not a prefix of it, not a hash of it, not the characters around it |
|
|
92
|
+
| Which skill, sub-agent type or workflow a Claude Code session invoked, and which events came from a sub-agent | Copy the `Agent` call's prompt or a workflow script's body out of the call (a sub-agent transcript's own opening prompt follows `privacy.prompts` like any other prompt) |
|
|
91
93
|
| | Install hooks or modify `~/.claude/settings.json` / `~/.codex/hooks.json` |
|
|
92
94
|
| | Send `organization_id` or `user_id` — they are not in the wire format at all |
|
|
93
95
|
| | Watch your keyboard, your screen, or any process on your machine |
|
|
@@ -177,7 +179,7 @@ agentstrack doctor --json # structured diagnostics, safe to paste
|
|
|
177
179
|
▼
|
|
178
180
|
┌────────────────────────┐
|
|
179
181
|
│ privacy pipeline │ mode-based content strip
|
|
180
|
-
│ │ →
|
|
182
|
+
│ │ → 16 built-in secret rules + org rules
|
|
181
183
|
│ │ → path normalization
|
|
182
184
|
│ │ → raw content DISCARDED HERE
|
|
183
185
|
└───────────┬────────────┘
|
|
@@ -198,8 +200,20 @@ collector keeps parsing and keeps spooling; when the network returns it drains o
|
|
|
198
200
|
over 1 KB are gzipped.
|
|
199
201
|
|
|
200
202
|
**Restart is safe.** File read offsets live in the same SQLite database as the queue, keyed by
|
|
201
|
-
`(path, inode)
|
|
202
|
-
|
|
203
|
+
`(path, inode)`, and a chunk's offset is committed in the **same transaction** as the events parsed
|
|
204
|
+
from it — a crash or a full disk between "read" and "queued" re-reads those lines rather than losing
|
|
205
|
+
them. A restart resumes mid-file. If a file is replaced (new inode) or truncated (offset past the
|
|
206
|
+
end), it is re-read from the start rather than silently skipped. Re-reading never double-counts:
|
|
207
|
+
every event's `event_id` is derived from the file, the line's byte offset and the line's content, so
|
|
208
|
+
the server's dedupe absorbs a replay. A Claude Code `model.response` is keyed on its `message.id`
|
|
209
|
+
instead, since one response spans several lines; an idle `session.ended` on the agent, session and
|
|
210
|
+
last-activity time. Only events with no source line (`git.commit`, database-backed agents) get a
|
|
211
|
+
random id.
|
|
212
|
+
|
|
213
|
+
**Big files are streamed, not slurped.** Transcripts are read in 4 MB chunks with the partial line
|
|
214
|
+
carried across the boundary, at most 64 MB per file per scan so a first import keeps yielding to the
|
|
215
|
+
upload loop. A single line over 8 MB is skipped to the next newline and counted in the log — never
|
|
216
|
+
buffered, never logged. One unreadable file is logged and skipped; it cannot stall the other files.
|
|
203
217
|
|
|
204
218
|
**A line the agent is still writing is never consumed.** The tailer advances its checkpoint only as
|
|
205
219
|
far as the **last complete newline**; a partial trailing line is left unread and picked up whole on
|
|
@@ -209,9 +223,18 @@ so both halves would fail to parse and that event would be lost. Byte offsets ar
|
|
|
209
223
|
raw buffer, not from decoded text, so a multi-byte character cannot desynchronise the position
|
|
210
224
|
either.
|
|
211
225
|
|
|
212
|
-
**Backpressure is handled
|
|
213
|
-
|
|
214
|
-
|
|
226
|
+
**Backpressure is handled, once per wave.** Batches go out `upload.concurrency` at a time, and the
|
|
227
|
+
failure policy runs on the wave's collected outcomes rather than inside each request — so four
|
|
228
|
+
failing siblings cost one backoff step, not four, and a sibling's success cannot undo a `413`
|
|
229
|
+
shrink. A `413` halves the batch size once (never above the server's `max_batch_events`) and it
|
|
230
|
+
creeps back up on success. A `5xx`, a timeout, a `408` or a `429` sets the next upload time with
|
|
231
|
+
jittered exponential backoff (1s base, capped at 5 minutes, or the server's `Retry-After` if longer)
|
|
232
|
+
— the daemon never sleeps on it, so tailing continues meanwhile. A daemon tick sends at most five
|
|
233
|
+
waves before scanning again. Three responses **pause** uploads instead: a `401`/`403` (the key, not
|
|
234
|
+
the events, is the problem), a `200` whose `quota.exceeded` says the org is over its monthly cap,
|
|
235
|
+
and a `200` that rejects every event as malformed (the collector is probably older than the server).
|
|
236
|
+
Nothing is acked or dropped while paused; `agentstrack status` shows the reason, and the pause lifts
|
|
237
|
+
by itself once a wave succeeds. A `4xx` that is none of those means the server will never accept the
|
|
215
238
|
batch: **the attempt counter is incremented for the events in that batch only, and one of them is
|
|
216
239
|
deleted once it reaches `upload.max_retries` (default 8)**. It is not parked and it does not come
|
|
217
240
|
back — a poison event must not be able to block the queue forever.
|
|
@@ -243,6 +266,9 @@ Two properties of that deletion are worth stating explicitly, because both are e
|
|
|
243
266
|
|
|
244
267
|
`analytics` is the honest middle: the title is computed **on your machine** from the prompt, and then
|
|
245
268
|
the prompt is deleted. The server receives `"fix flaky auth test"`, never the 900 words you typed.
|
|
269
|
+
The prompt is redacted **before** the title is cut out of it, so the cut can only ever land inside a
|
|
270
|
+
`[REDACTED:…]` marker and never through the middle of a key. Before 0.4.1 it was cut first, which
|
|
271
|
+
could ship half of a secret — see the CHANGELOG.
|
|
246
272
|
|
|
247
273
|
If even the title is too much, `privacy.prompts: never` drops that too: in `analytics` it strips
|
|
248
274
|
`derived_title`, so nothing derived from a prompt leaves the machine, without giving up token, tool
|
|
@@ -266,8 +292,8 @@ daemon cannot drift.
|
|
|
266
292
|
|
|
267
293
|
### Built-in secret redaction
|
|
268
294
|
|
|
269
|
-
Every free-text field
|
|
270
|
-
|
|
295
|
+
Every free-text field (`prompt_text`, `derived_title`, `message`, `command`, `description`) passes
|
|
296
|
+
through these 16 rules, most-specific first, on your machine:
|
|
271
297
|
|
|
272
298
|
| Rule | Catches |
|
|
273
299
|
|---|---|
|
|
@@ -284,19 +310,43 @@ Every free-text field that survives the mode strip (`prompt_text`, `derived_titl
|
|
|
284
310
|
| `jwt` | three-segment `eyJ…` tokens |
|
|
285
311
|
| `bearer_header` | `Bearer <token>` → `Bearer [REDACTED]` |
|
|
286
312
|
| `basic_auth_url` | `https://user:pw@host` → `https://[REDACTED]@host` |
|
|
313
|
+
| `inline_password_flag` | `-pSECRET`, `--password=SECRET`, `--password "SECRET"` |
|
|
287
314
|
| `env_assignment` | `*SECRET*=`, `*TOKEN*=`, `*PASSWORD*=`, `*PASSWD*=`, `*APIKEY*=`, `*API_KEY*=`, `*ACCESS_KEY*=`, `*PRIVATE_KEY*=` |
|
|
288
315
|
| `generic_hex_secret` | bare hex strings of 40+ characters |
|
|
289
316
|
|
|
290
|
-
A match is replaced in place, and most rules substitute `[REDACTED:rule_name]`.
|
|
317
|
+
A match is replaced in place, and most rules substitute `[REDACTED:rule_name]`. Five do not:
|
|
291
318
|
`bearer_header` → `Bearer [REDACTED]` and `basic_auth_url` → `scheme://[REDACTED]@host` keep the
|
|
292
|
-
surrounding syntax so the shape of the command survives, `env_assignment` → `NAME=[REDACTED]`
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
319
|
+
surrounding syntax so the shape of the command survives, `env_assignment` → `NAME=[REDACTED]` and
|
|
320
|
+
`inline_password_flag` → `--password=[REDACTED]` keep the variable or flag name, and
|
|
321
|
+
`generic_hex_secret` substitutes the shorter `[REDACTED:hex]`. Your
|
|
322
|
+
organization can add patterns server-side; an org pattern that is malformed, longer than 256
|
|
323
|
+
characters, or using a backreference is skipped, the common catastrophic nested-quantifier shapes
|
|
324
|
+
(`(a+)+`, `(a|aa)+`, `((a+)b)+`) are rejected — a heuristic, not a proof — and org patterns are
|
|
325
|
+
matched against at most the first 64 KB of any value.
|
|
296
326
|
|
|
297
327
|
Redaction is defence in depth, not the primary control. The primary control is that in `metadata`
|
|
298
328
|
and `analytics` modes the content is **deleted locally** and never enters the pipeline at all.
|
|
299
329
|
|
|
330
|
+
### What a redaction reports
|
|
331
|
+
|
|
332
|
+
When a rule fires, the event carries `secrets_redacted` — a list of `{ kind, count }`, sorted by
|
|
333
|
+
kind, absent when nothing fired:
|
|
334
|
+
|
|
335
|
+
```json
|
|
336
|
+
"secrets_redacted": [{ "kind": "aws_access_key", "count": 1 }]
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Plainly: **we report that a secret-shaped string was found and which pattern matched it. We never
|
|
340
|
+
send the value.** Not the matched text, not a prefix of it, not a hash of it, not the surrounding
|
|
341
|
+
context — there is nothing in the payload to reverse. A rule your organization added reports as the
|
|
342
|
+
single generic kind `org_rule`, because a rule name (`acme_prod_db_password`) can itself describe
|
|
343
|
+
the shape of your secrets.
|
|
344
|
+
|
|
345
|
+
This travels in **every** mode, `metadata` included: the tally is computed before the mode strip
|
|
346
|
+
deletes the text it was computed from. A count is metadata; the prompt it came from is not. And
|
|
347
|
+
`metadata` is exactly the mode where a team most wants to know that a live credential was pasted
|
|
348
|
+
into an agent — the point being to go rotate it, which needs no copy of it.
|
|
349
|
+
|
|
300
350
|
### How file paths are handled
|
|
301
351
|
|
|
302
352
|
With `file_paths: relative` (the default):
|
|
@@ -311,6 +361,22 @@ The project root itself (`repo.project_path`) is **dropped entirely** in `never`
|
|
|
311
361
|
modes — it is only transmitted if you opt into `file_paths: absolute`. Repositories are correlated
|
|
312
362
|
by `remote_hash`, a SHA-256 of the normalized remote URL, not by path.
|
|
313
363
|
|
|
364
|
+
### Machine info
|
|
365
|
+
|
|
366
|
+
`login` (registration) and the daemon's health report, once a minute, send: `hostname`, `os`
|
|
367
|
+
(`darwin` / `linux` / `win32`), `os_release` (`os.release()`), `arch`, and
|
|
368
|
+
`machine_kind` — `ci` when `CI`, `GITHUB_ACTIONS` or `GITLAB_CI` is set; `container` when
|
|
369
|
+
`/.dockerenv` exists or `/proc/1/cgroup` mentions docker, containerd or kubepods; `workstation` on
|
|
370
|
+
macOS and Windows, or Linux with `DISPLAY` / `WAYLAND_DISPLAY` / a graphical `XDG_SESSION_TYPE`;
|
|
371
|
+
`server` for headless Linux; `unknown` otherwise. It is derived from those probes only.
|
|
372
|
+
|
|
373
|
+
The server keeps the hostname (raw, plus a SHA-256 that keys registration) and the IP address it
|
|
374
|
+
saw the register, health and upload requests come from, **for operations and abuse prevention
|
|
375
|
+
only** — telling one machine's collector from another, and shutting off a key that is being abused.
|
|
376
|
+
Neither is returned by any user-facing endpoint or shown anywhere in the product; the dashboard
|
|
377
|
+
identifies a device by its label and machine kind. Everything else in the list (OS, arch, kind,
|
|
378
|
+
versions) is what the device page shows.
|
|
379
|
+
|
|
314
380
|
### Excluding a project entirely
|
|
315
381
|
|
|
316
382
|
```yaml
|
|
@@ -345,14 +411,18 @@ events at all** — not even counts. Edit the YAML and restart the collector.
|
|
|
345
411
|
agentstrack login <api-key> [--api-url <url>] [--label <name>]
|
|
346
412
|
```
|
|
347
413
|
|
|
348
|
-
The key is
|
|
349
|
-
|
|
414
|
+
The key argument is **optional**. Passing it on the command line leaves it in your shell history and
|
|
415
|
+
in `ps`, so `login` also reads `AGENTSTRACK_API_KEY`, or prompts on the terminal (echo off), or takes
|
|
416
|
+
the key on stdin — `agentstrack login < key.txt`. `--api-url` points at a self-hosted instance and
|
|
417
|
+
**must be `https`** (plain `http` is accepted only for `localhost`); `login` prints the URL it is
|
|
418
|
+
about to use. `--label` names this machine in the dashboard.
|
|
350
419
|
Registration is idempotent on (user, hostname hash), so re-running `login` on the same machine reuses
|
|
351
420
|
the existing collector instead of fragmenting its history. The config file is written mode `600`, in
|
|
352
421
|
a directory created mode `700`.
|
|
353
422
|
|
|
354
|
-
The registration payload is `hostname`, `label`, `os`, `arch`,
|
|
355
|
-
|
|
423
|
+
The registration payload is `hostname`, `label`, `os`, `os_release`, `arch`, `machine_kind`,
|
|
424
|
+
(see [Machine info](#machine-info)), the collector's own version, and one entry per
|
|
425
|
+
configured agent: `{ agent, version }`. The **agent version is the real one**, read out of
|
|
356
426
|
a transcript the agent already wrote (`2.1.247`, say, from Claude Code's `version` field). Where an
|
|
357
427
|
adapter cannot cheaply establish a version at detection time the field is simply **omitted** rather
|
|
358
428
|
than filled with a placeholder, so a missing version in the dashboard means "not reported", never
|
|
@@ -372,8 +442,11 @@ If your local privacy mode is stricter than the org's, login says so and keeps y
|
|
|
372
442
|
|
|
373
443
|
`agentstrack start` writes a **launchd** agent on macOS (`~/Library/LaunchAgents/ai.agentstrack.collector.plist`)
|
|
374
444
|
or a **systemd user unit** on Linux (`~/.config/systemd/user/agentstrack.service`), loads it, and
|
|
375
|
-
returns. Neither needs root.
|
|
376
|
-
|
|
445
|
+
returns. Neither needs root. The unit restarts the collector only on a **crash**, not after a clean
|
|
446
|
+
exit — after `logout` the collector exits cleanly and the supervisor leaves it stopped instead of
|
|
447
|
+
respawning it every few seconds (launchd `KeepAlive`/`SuccessfulExit`, systemd `Restart=on-failure`
|
|
448
|
+
with a 5-in-5-minutes start limit). `-f` / `--foreground` runs in the terminal instead — best for a
|
|
449
|
+
first run, and the only mode where `status` reports `Running: yes`.
|
|
377
450
|
|
|
378
451
|
`agentstrack stop` removes the service unit *and* signals a foreground collector. There is no
|
|
379
452
|
"stop but keep the unit"; use `agentstrack service install` to put it back.
|
|
@@ -389,9 +462,9 @@ Configuration
|
|
|
389
462
|
|
|
390
463
|
Agents
|
|
391
464
|
✓ claude_code transcripts found
|
|
392
|
-
225 file(s) modified in the last 7 days
|
|
465
|
+
225 file(s) modified in the last 7 days # window is tracking.max_age_days (default 7)
|
|
393
466
|
✓ codex transcripts found
|
|
394
|
-
3 file(s) modified in the last 7 days
|
|
467
|
+
3 file(s) modified in the last 7 days # window is tracking.max_age_days (default 7)
|
|
395
468
|
|
|
396
469
|
Connectivity
|
|
397
470
|
✗ API reachable at https://api.agentstrack.ai
|
|
@@ -507,10 +580,10 @@ privacy:
|
|
|
507
580
|
|
|
508
581
|
# never | local_summary_only (default) | full
|
|
509
582
|
# `full` is what keeps `mode: full` from uploading prompt text unless you also
|
|
510
|
-
# ask for it here. `never` suppresses prompt text in every mode, and
|
|
511
|
-
#
|
|
512
|
-
# nothing derived from a prompt leaves the
|
|
513
|
-
#
|
|
583
|
+
# ask for it here. `never` suppresses prompt text in every mode, and it
|
|
584
|
+
# additionally drops the locally derived `derived_title` in every mode too —
|
|
585
|
+
# `analytics` and `full` alike — so nothing derived from a prompt leaves the
|
|
586
|
+
# machine at all. See the privacy section.
|
|
514
587
|
prompts: local_summary_only
|
|
515
588
|
|
|
516
589
|
# never (default) | full
|
|
@@ -569,7 +642,7 @@ database filename or an absolute path — the same override OpenCode itself hono
|
|
|
569
642
|
|
|
570
643
|
| Agent | Status | Reads |
|
|
571
644
|
|---|---|---|
|
|
572
|
-
| **Claude Code** | ✅ Stable | `~/.claude/projects/<slug>/<session-uuid>.jsonl` |
|
|
645
|
+
| **Claude Code** | ✅ Stable | `~/.claude/projects/<slug>/<session-uuid>.jsonl`, plus `<session-uuid>/subagents/**/agent-<id>.jsonl` |
|
|
573
646
|
| **Codex** | ✅ Stable | `~/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl` |
|
|
574
647
|
| **OpenCode** | ✅ Stable | `~/.local/share/opencode/opencode.db` — SQLite, opened **read-only** |
|
|
575
648
|
| Gemini CLI · Cursor · Cline · Copilot CLI | 🗓 Planned | ids reserved in the schema, no adapter yet |
|
|
@@ -593,6 +666,7 @@ identical things:
|
|
|
593
666
|
| Commits | ✅ (from `git log`, not the transcript) | ✅ (same) | ✅ (same) |
|
|
594
667
|
| Plan / subscription type | ➖ | ✅ `plan_type` | ➖ |
|
|
595
668
|
| Account attribution | ✅ from `~/.claude.json` | ➖ no account file | ✅ from `account.json`, per provider |
|
|
669
|
+
| Skills / sub-agents / workflows | ✅ `Skill`, `Agent`, `Workflow` tool calls named; sub-agent transcripts stamped `sidechain` | ➖ Codex's `spawn_agent` collaboration tools are defined in its prompt but no rollout on hand shows one invoked, so nothing is parsed yet | ➖ `parent_session_id` only |
|
|
596
670
|
|
|
597
671
|
Every adapter is read-only. Adapter formats drift between agent releases: an unparseable line is
|
|
598
672
|
skipped, never fatal to the file.
|
|
@@ -678,8 +752,10 @@ gzipped (`content-encoding: gzip`).
|
|
|
678
752
|
|
|
679
753
|
| Symptom | Cause | Fix |
|
|
680
754
|
|---|---|---|
|
|
681
|
-
| `
|
|
682
|
-
| `403` | Key lacks ingest permission | Issue a new key |
|
|
755
|
+
| `Uploads paused (auth)` — `401` | Key revoked or wrong | `agentstrack login <new-key>`; nothing was dropped |
|
|
756
|
+
| `Uploads paused (auth)` — `403` | Key lacks ingest permission | Issue a new key; nothing was dropped |
|
|
757
|
+
| `Uploads paused (quota)` | Org over its monthly event cap | Events stay spooled and resume when the cap resets or the plan changes |
|
|
758
|
+
| `Uploads paused (schema)` | Server rejects every event — collector older than the API | Upgrade the collector; nothing was dropped |
|
|
683
759
|
| `fetch failed`, `ETIMEDOUT` | Network, VPN or proxy | Set `HTTPS_PROXY`; events keep spooling meanwhile |
|
|
684
760
|
| `Server rejected the batch as too large` | Batch above the server's limit | Automatic — batch size halves and recovers |
|
|
685
761
|
| `Batch permanently rejected: … (dropped N)` | Non-retryable `4xx` | N events **in that batch** hit `max_retries` and were deleted. Nothing outside the batch is touched. Check the API version matches the collector's schema. |
|
|
@@ -720,7 +796,8 @@ grep -i "error\|failed\|rejected" ~/.agentstrack/collector.log | tail -20
|
|
|
720
796
|
```
|
|
721
797
|
|
|
722
798
|
The log records counts, queue depths and `event_id`s — never payloads, prompts, code or keys. That is
|
|
723
|
-
what makes it safe to attach to an issue.
|
|
799
|
+
what makes it safe to attach to an issue. It rotates once at 5 MB to `collector.log.1`; a scan pass
|
|
800
|
+
writes one `Queued N events across M files` line, not one per file. Please attach `agentstrack doctor --json` too.
|
|
724
801
|
|
|
725
802
|
### Complete reset
|
|
726
803
|
|
|
@@ -739,15 +816,14 @@ Honest list of things that are **not** in 0.1.0, so you do not go looking for th
|
|
|
739
816
|
[ROADMAP.md](./ROADMAP.md) has the same list with the design constraints and what "help wanted"
|
|
740
817
|
means for each.
|
|
741
818
|
|
|
742
|
-
- **Backfill window control** (`sync --since 30d`). Today the
|
|
819
|
+
- **Backfill window control** (`sync --since 30d`). Today the window is `tracking.max_age_days` (default 7) for every run; there is no per-invocation override.
|
|
743
820
|
- **`config get` / `config set` / `config edit`** — edit the YAML by hand for now.
|
|
744
821
|
- **`--verbose` logging** and per-run agent selection (`start --agent codex`); use `tracking.agents`.
|
|
745
|
-
- **Local time accounting.** Human-active / agent-active / idle windows are derived server-side from the event stream; `tracking.idle_timeout_seconds`
|
|
822
|
+
- **Local time accounting.** Human-active / agent-active / idle windows are derived server-side from the event stream; the collector uses `tracking.idle_timeout_seconds` only to decide when a quiet session has ended.
|
|
746
823
|
- **Process metrics.** `tracking.process_metrics` is accepted and ignored.
|
|
747
|
-
- **`
|
|
824
|
+
- **`heartbeat`, `model.request` and `git.branch_changed`** are in the schema but no adapter emits them yet. `session.ended` is not read from any Claude Code or Codex transcript either — the daemon emits it after `tracking.idle_timeout_seconds` of quiet (`reason: timeout`) or on shutdown (`reason: unknown`).
|
|
748
825
|
- **Local task classification** (`task_category`) — the field exists in the schema; the collector only derives a title.
|
|
749
|
-
- **Windows.** The service installer covers launchd and systemd only; `--foreground` works anywhere Node
|
|
750
|
-
- **Content-derived `event_id`.** Ids are random per enqueue, so retrying a batch is safe but re-reading a truncated transcript would create duplicates.
|
|
826
|
+
- **Windows.** The service installer covers launchd and systemd only; `--foreground` works anywhere Node 22+ does.
|
|
751
827
|
- **`MultiEdit`.** The Claude Code adapter derives file changes from `Edit`, `Write`, `NotebookEdit` and `Read`; a `MultiEdit` call is still recorded as `tool.started`/`tool.completed`, but produces no `file.changed` events and no line counts.
|
|
752
828
|
|
|
753
829
|
---
|
|
@@ -14,10 +14,22 @@ import { type TokenUsage } from '../schema.js';
|
|
|
14
14
|
* "output_tokens_details":{"thinking_tokens":257}}},"timestamp":"…"}
|
|
15
15
|
*
|
|
16
16
|
* Tool calls appear as tool_use / tool_result blocks inside message.content.
|
|
17
|
+
* One API response is written as SEVERAL assistant lines — one per content
|
|
18
|
+
* block (thinking, text, tool_use) — each repeating the same message.id and
|
|
19
|
+
* the same usage. Usage is therefore billed once per message.id, not per line.
|
|
20
|
+
*
|
|
21
|
+
* Sub-agents (the Agent tool, and Workflow scripts) write their own transcript
|
|
22
|
+
* under <session-uuid>/subagents/[workflows/<wf>/]agent-<id>.jsonl, with the
|
|
23
|
+
* parent's sessionId on every line, isSidechain: true and an agentId. A sibling
|
|
24
|
+
* agent-<id>.meta.json carries {"agentType","description","toolUseId",…}. Every
|
|
25
|
+
* event from such a line is stamped sidechain/agent_id/agent_kind/agent_type
|
|
26
|
+
* so the server can attribute the sub-agent's own usage to it.
|
|
17
27
|
*/
|
|
18
28
|
export declare const CLAUDE_DIR: string;
|
|
19
29
|
export declare class ClaudeCodeAdapter implements AgentAdapter {
|
|
20
30
|
readonly id = "claude_code";
|
|
31
|
+
/** Cross-line facts within one transcript: a message's lines are contiguous. */
|
|
32
|
+
private readonly fileState;
|
|
21
33
|
detect(): Promise<DetectionResult>;
|
|
22
34
|
/**
|
|
23
35
|
* The account Claude Code is signed in as right now.
|
|
@@ -34,6 +46,12 @@ export declare class ClaudeCodeAdapter implements AgentAdapter {
|
|
|
34
46
|
private normalizeUser;
|
|
35
47
|
private normalizeAssistant;
|
|
36
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Maps Claude Code's usage block onto the normalized shape.
|
|
51
|
+
*
|
|
52
|
+
* thinking_tokens sits under output_tokens_details and is a SUBSET of
|
|
53
|
+
* output_tokens — it must not be added on top, or thinking gets billed twice.
|
|
54
|
+
*/
|
|
37
55
|
export declare function readUsage(raw: unknown): TokenUsage;
|
|
38
56
|
/**
|
|
39
57
|
* Line deltas for one edit, derived from the tool input.
|