@thehammer/danx-session-telemetry 0.1.9 → 0.1.11
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/dist/session-log-watcher.d.ts +14 -13
- package/dist/session-log-watcher.js +16 -15
- package/dist/usage-ledger.js +15 -11
- package/package.json +1 -1
|
@@ -90,20 +90,21 @@ export interface SessionLogWatcherOptions {
|
|
|
90
90
|
* The `.`/`_` encoding matters: a hidden-dir segment (leading `.`) or a
|
|
91
91
|
* clean-room key's `__` delimiter that the encoder does NOT collapse produces
|
|
92
92
|
* a directory Claude Code never writes to — silently breaking
|
|
93
|
-
* SessionLogWatcher attachment
|
|
94
|
-
*
|
|
93
|
+
* SessionLogWatcher attachment. The separator-encoding regression tests
|
|
94
|
+
* below pin this.
|
|
95
95
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
96
|
+
* Claude 2.1.104+ (GitHub #51488 — symlinks under `~/.claude/projects/` are
|
|
97
|
+
* no longer followed) writes its transcript to this exact realpath-encoded
|
|
98
|
+
* dir as a REAL directory; the watcher reads the same path, so they converge
|
|
99
|
+
* with no symlink. A danxbot worker container binds `<repo>/claude-projects/`
|
|
100
|
+
* at `/home/danxbot/.claude/projects/` and relies on the same encoding for
|
|
101
|
+
* the bound subdir name; an operator's own interactive session on its
|
|
102
|
+
* machine uses the native dir. Keep the encoding logic centralized here so
|
|
103
|
+
* every caller stays in lockstep.
|
|
103
104
|
*
|
|
104
|
-
* DX-3069 — Windows
|
|
105
|
-
*
|
|
106
|
-
*
|
|
105
|
+
* DX-3069 — a Windows caller (an operator session on a Windows machine)
|
|
106
|
+
* passes an ALREADY-ABSOLUTE Windows cwd (`C:\Users\...`), which never
|
|
107
|
+
* starts with `/`. The prior
|
|
107
108
|
* `cwd.startsWith("/") ? cwd : "/" + cwd` unconditionally prepended `/`,
|
|
108
109
|
* producing a malformed path (`/C:\Users\...`) that `realpathSync` cannot
|
|
109
110
|
* resolve (ENOENT — Windows reads the leading `/C:` as its own root segment),
|
|
@@ -112,7 +113,7 @@ export interface SessionLogWatcherOptions {
|
|
|
112
113
|
* drive-letter colon survived verbatim into the "encoded" dir name, and
|
|
113
114
|
* `join(homedir(), ".claude", "projects", encoded)` then produced a NESTED,
|
|
114
115
|
* DOUBLED path — `C:\Users\...\.claude\projects\-C:\Users\...` — a directory
|
|
115
|
-
* Claude Code never writes to, so every
|
|
116
|
+
* Claude Code never writes to, so every watcher on Windows silently
|
|
116
117
|
* never found its own transcript. Verified empirically against a real Claude
|
|
117
118
|
* Code install on this machine: `C:\Users\newms\projects\danxbot` encodes on
|
|
118
119
|
* disk to `C--Users-newms-projects-danxbot` (`ls ~/.claude/projects`) — no
|
|
@@ -58,20 +58,21 @@ export const DISPATCH_TAG_PREFIX = "<!-- danxbot-dispatch:";
|
|
|
58
58
|
* The `.`/`_` encoding matters: a hidden-dir segment (leading `.`) or a
|
|
59
59
|
* clean-room key's `__` delimiter that the encoder does NOT collapse produces
|
|
60
60
|
* a directory Claude Code never writes to — silently breaking
|
|
61
|
-
* SessionLogWatcher attachment
|
|
62
|
-
*
|
|
61
|
+
* SessionLogWatcher attachment. The separator-encoding regression tests
|
|
62
|
+
* below pin this.
|
|
63
63
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
64
|
+
* Claude 2.1.104+ (GitHub #51488 — symlinks under `~/.claude/projects/` are
|
|
65
|
+
* no longer followed) writes its transcript to this exact realpath-encoded
|
|
66
|
+
* dir as a REAL directory; the watcher reads the same path, so they converge
|
|
67
|
+
* with no symlink. A danxbot worker container binds `<repo>/claude-projects/`
|
|
68
|
+
* at `/home/danxbot/.claude/projects/` and relies on the same encoding for
|
|
69
|
+
* the bound subdir name; an operator's own interactive session on its
|
|
70
|
+
* machine uses the native dir. Keep the encoding logic centralized here so
|
|
71
|
+
* every caller stays in lockstep.
|
|
71
72
|
*
|
|
72
|
-
* DX-3069 — Windows
|
|
73
|
-
*
|
|
74
|
-
*
|
|
73
|
+
* DX-3069 — a Windows caller (an operator session on a Windows machine)
|
|
74
|
+
* passes an ALREADY-ABSOLUTE Windows cwd (`C:\Users\...`), which never
|
|
75
|
+
* starts with `/`. The prior
|
|
75
76
|
* `cwd.startsWith("/") ? cwd : "/" + cwd` unconditionally prepended `/`,
|
|
76
77
|
* producing a malformed path (`/C:\Users\...`) that `realpathSync` cannot
|
|
77
78
|
* resolve (ENOENT — Windows reads the leading `/C:` as its own root segment),
|
|
@@ -80,7 +81,7 @@ export const DISPATCH_TAG_PREFIX = "<!-- danxbot-dispatch:";
|
|
|
80
81
|
* drive-letter colon survived verbatim into the "encoded" dir name, and
|
|
81
82
|
* `join(homedir(), ".claude", "projects", encoded)` then produced a NESTED,
|
|
82
83
|
* DOUBLED path — `C:\Users\...\.claude\projects\-C:\Users\...` — a directory
|
|
83
|
-
* Claude Code never writes to, so every
|
|
84
|
+
* Claude Code never writes to, so every watcher on Windows silently
|
|
84
85
|
* never found its own transcript. Verified empirically against a real Claude
|
|
85
86
|
* Code install on this machine: `C:\Users\newms\projects\danxbot` encodes on
|
|
86
87
|
* disk to `C--Users-newms-projects-danxbot` (`ls ~/.claude/projects`) — no
|
|
@@ -239,8 +240,8 @@ export async function findSessionFileByDispatchId(dir, dispatchId) {
|
|
|
239
240
|
catch (err) {
|
|
240
241
|
// DX-1201 — a single unreadable SIBLING file must NOT abort the
|
|
241
242
|
// whole scan. Dispatched agents share a busy session dir (e.g.
|
|
242
|
-
//
|
|
243
|
-
// `~/.claude/projects
|
|
243
|
+
// every issue-worker dispatch of one repo writing under the same
|
|
244
|
+
// encoded `~/.claude/projects/<cwd>/` dir), so any one of the
|
|
244
245
|
// co-located multi-MB files can momentarily fail an `open`/`stat`/
|
|
245
246
|
// `read`: ENOENT (sibling rotated/deleted mid-scan), ESTALE (a
|
|
246
247
|
// worktree torn down under the agent), EACCES (perms race), EMFILE
|
package/dist/usage-ledger.js
CHANGED
|
@@ -292,27 +292,31 @@ export function ledgerLineFromLogEntry(entry) {
|
|
|
292
292
|
? d.content
|
|
293
293
|
: [];
|
|
294
294
|
const hasMessageId = typeof d.messageId === "string" && d.messageId.length > 0;
|
|
295
|
+
const hasUsage = Boolean(d.usage && typeof d.usage === "object");
|
|
296
|
+
const toolUseIds = content
|
|
297
|
+
.filter((b) => b.type === "tool_use" && typeof b.id === "string")
|
|
298
|
+
.map((b) => b.id);
|
|
295
299
|
// DX-3261 — `byteOffset` only matters as the synthetic key for a line with
|
|
296
300
|
// no `message.id` (`syntheticMessageKey`); a missing/invalid one used to
|
|
297
301
|
// silently default to `0`, collapsing EVERY no-id reply from this source
|
|
298
302
|
// onto one key so only the largest single reply was ever credited. Fail
|
|
299
|
-
// loud rather than repeat that silent collapse
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
|
|
303
|
+
// loud rather than repeat that silent collapse — but ONLY when the line
|
|
304
|
+
// will actually reach that keying step: `UsageLedgerCounter.observe()`
|
|
305
|
+
// discards a no-id line with neither usage nor a tool-use id before ever
|
|
306
|
+
// building a key (a tool-call block with no reported `id`, an empty
|
|
307
|
+
// wrapper entry), so requiring a valid `byteOffset` on THOSE too would
|
|
308
|
+
// reject lines the counter was always going to ignore. Harmless when a
|
|
309
|
+
// real `messageId` is present (byteOffset is never used to key that line).
|
|
310
|
+
if (!hasMessageId && (hasUsage || toolUseIds.length > 0) && typeof d.byte_offset !== "number") {
|
|
311
|
+
throw new LedgerLineDataError(`ledgerLineFromLogEntry: missing/invalid byte_offset on a no-message-id line carrying usage or a tool-use id (source=${typeof d.subagent_id === "string" ? d.subagent_id : MAIN_SOURCE})`);
|
|
304
312
|
}
|
|
305
313
|
return {
|
|
306
314
|
messageId: hasMessageId ? d.messageId : undefined,
|
|
307
315
|
source: typeof d.subagent_id === "string" ? d.subagent_id : MAIN_SOURCE,
|
|
308
316
|
byteOffset: typeof d.byte_offset === "number" ? d.byte_offset : 0,
|
|
309
317
|
model: typeof d.model === "string" && d.model.length > 0 ? d.model : undefined,
|
|
310
|
-
usage:
|
|
311
|
-
? d.usage
|
|
312
|
-
: undefined,
|
|
318
|
+
usage: hasUsage ? d.usage : undefined,
|
|
313
319
|
timestamp: entry.timestamp,
|
|
314
|
-
toolUseIds
|
|
315
|
-
.filter((b) => b.type === "tool_use" && typeof b.id === "string")
|
|
316
|
-
.map((b) => b.id),
|
|
320
|
+
toolUseIds,
|
|
317
321
|
};
|
|
318
322
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thehammer/danx-session-telemetry",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Shared Claude Code transcript collector (DX-2962) — incremental JSONL tailing for the danxbot worker and the danx-dashboard-mcp desktop bridge, so both run identical collector code.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|