@thehammer/danx-session-telemetry 0.1.16 → 0.1.18
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/activity-observer.d.ts +26 -20
- package/dist/dispatch-usage.d.ts +8 -1
- package/dist/dispatch-usage.js +17 -1
- package/dist/session-log-watcher.d.ts +1 -1
- package/dist/session-log-watcher.js +1 -1
- package/dist/stats-wire.d.ts +14 -11
- package/dist/stats-wire.js +8 -6
- package/package.json +1 -1
|
@@ -11,36 +11,42 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Claude Code's `SubagentStart` / `SubagentStop` hooks carry strictly more (an
|
|
13
13
|
* `agent_type` before the sub-agent has written anything, and — the part this
|
|
14
|
-
* module cannot supply — a FINISH).
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* `
|
|
19
|
-
* a set that is permanently empty (`subagent_count` 0 on every dispatch, a
|
|
20
|
-
* REGRESSION against the transcript-derived counter this replaced).
|
|
14
|
+
* module cannot supply — a FINISH). They ship from the danxbot plugin in the
|
|
15
|
+
* claude-plugins repo (DX-3284, `danxbot/scripts/activity-report.mjs`), run the
|
|
16
|
+
* `activity` subcommand of `@thehammer/danx-dashboard-mcp`, and reach the server
|
|
17
|
+
* through `POST /api/plan-sessions/me/activity` — which writes the SAME
|
|
18
|
+
* `dispatch_activities` rows through the same one server writer.
|
|
21
19
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* The transcript stays the producer because it needs no hook to be installed: a
|
|
21
|
+
* session with no plugin, and every WORKER dispatch, still gets a start row for
|
|
22
|
+
* each sub-agent from here. The hooks are an ENRICHMENT layered on the same rows:
|
|
24
23
|
*
|
|
25
24
|
* | Signal | Sub-agent start | Sub-agent finish | Background script |
|
|
26
25
|
* |---|---|---|---|
|
|
27
26
|
* | Transcript (here) | file discovered | dispatch terminal (`finishOpenActivities`) | not visible |
|
|
28
|
-
* | Hooks (DX-3284) | `SubagentStart` | `SubagentStop` | `PostToolUse` start; end at dispatch terminal (no documented completion event) |
|
|
27
|
+
* | Hooks (DX-3284, operator sessions) | `SubagentStart` | `SubagentStop` | `PostToolUse` start; end at dispatch terminal (no documented completion event) |
|
|
29
28
|
*
|
|
30
|
-
* **
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
29
|
+
* **Where "running" closes.** An operator session's sub-agent closes at its
|
|
30
|
+
* `SubagentStop` hook. A WORKER dispatch has no hook reporting yet
|
|
31
|
+
* (`/me/activity` refuses a worker's session id as `session_is_worker`), so its
|
|
32
|
+
* sub-agents stay in the running set until the dispatch itself ends, and the
|
|
33
|
+
* silent-too-long warning fires `RUNNING_ACTIVITY_SILENT_AFTER_MS` after a
|
|
34
|
+
* sub-agent that has in fact finished. A worker-side `SubagentStop` report is the
|
|
35
|
+
* fix, and is not built.
|
|
35
36
|
*
|
|
36
37
|
* ## Why the two producers cannot double-count
|
|
37
38
|
*
|
|
38
39
|
* Every row is an upsert keyed `(dispatch_id, kind, activity_id)`, and BOTH
|
|
39
40
|
* producers derive `activity_id` from the same thing: the sub-agent's transcript
|
|
40
|
-
* file basename
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* file basename, `agent-<agent_id>`. The watcher has it directly; a hook has the
|
|
42
|
+
* `agent_id` and builds the same basename from it (`subagentActivityId` in
|
|
43
|
+
* danx-dashboard-mcp's `activity.ts`). A hook's `transcript_path` is NOT the
|
|
44
|
+
* sub-agent's file — on these events it is the MAIN session's transcript, and only
|
|
45
|
+
* `SubagentStop` also carries the sub-agent's own as `agent_transcript_path`,
|
|
46
|
+
* which the subcommand uses to refuse a key that disagrees. That basename is also
|
|
47
|
+
* the key the ledger credits the sub-agent's tokens under, which is how
|
|
48
|
+
* per-sub-agent tokens join to the running set without this table carrying a
|
|
49
|
+
* token column.
|
|
44
50
|
*
|
|
45
51
|
* ## What this module deliberately does NOT do
|
|
46
52
|
*
|
|
@@ -49,7 +55,7 @@
|
|
|
49
55
|
* indistinguishable from a slow one. Guessing a finish from an idle gap would
|
|
50
56
|
* mark a long-thinking sub-agent as complete. So open rows are closed exactly
|
|
51
57
|
* once, at the dispatch's own terminal transition (`finishOpenActivities`),
|
|
52
|
-
* unless
|
|
58
|
+
* unless the `SubagentStop` hook (DX-3284) closes a row first.
|
|
53
59
|
*/
|
|
54
60
|
import type { ActivityChange } from "./stats-wire.js";
|
|
55
61
|
import type { EntryMeta, SourceDiscoveredInfo } from "./session-log-watcher.js";
|
package/dist/dispatch-usage.d.ts
CHANGED
|
@@ -50,6 +50,13 @@ export declare class DispatchUsage {
|
|
|
50
50
|
*/
|
|
51
51
|
private startCursors;
|
|
52
52
|
private startCursorsPersisted;
|
|
53
|
+
/**
|
|
54
|
+
* DX-2954 — set the moment the first flush begins. `attachCursors` after that
|
|
55
|
+
* point would set the starting cursors AFTER a write that carried none, and
|
|
56
|
+
* `startCursorsPersisted` (set by any landed write) would then keep them from
|
|
57
|
+
* ever being carried — the exact AC 34324 loss, silently. So it fails loud.
|
|
58
|
+
*/
|
|
59
|
+
private flushBegan;
|
|
53
60
|
/** Past-EOF resets to report once, then forget. */
|
|
54
61
|
private pastEof;
|
|
55
62
|
/**
|
|
@@ -134,7 +141,7 @@ export declare class DispatchUsage {
|
|
|
134
141
|
*
|
|
135
142
|
* So every source in the cursors this process STARTED from that the watcher
|
|
136
143
|
* has not opened yet is reported VERBATIM — the exact `{acked, delivered}`
|
|
137
|
-
* pair it resumed from — until
|
|
144
|
+
* pair it resumed from — until a write that carries them has landed. That is
|
|
138
145
|
* what makes "every attach persists its starting cursors" true on the FIRST
|
|
139
146
|
* write rather than one stats tick (15-30 s) later: a reattach of this row
|
|
140
147
|
* inside its first interval reads them back (start-order rule 1) and starts
|
package/dist/dispatch-usage.js
CHANGED
|
@@ -70,6 +70,13 @@ export class DispatchUsage {
|
|
|
70
70
|
*/
|
|
71
71
|
startCursors = {};
|
|
72
72
|
startCursorsPersisted = false;
|
|
73
|
+
/**
|
|
74
|
+
* DX-2954 — set the moment the first flush begins. `attachCursors` after that
|
|
75
|
+
* point would set the starting cursors AFTER a write that carried none, and
|
|
76
|
+
* `startCursorsPersisted` (set by any landed write) would then keep them from
|
|
77
|
+
* ever being carried — the exact AC 34324 loss, silently. So it fails loud.
|
|
78
|
+
*/
|
|
79
|
+
flushBegan = false;
|
|
73
80
|
/** Past-EOF resets to report once, then forget. */
|
|
74
81
|
pastEof = [];
|
|
75
82
|
/**
|
|
@@ -93,6 +100,11 @@ export class DispatchUsage {
|
|
|
93
100
|
* each write.
|
|
94
101
|
*/
|
|
95
102
|
attachCursors(readCursorState, startCursors) {
|
|
103
|
+
// DX-2954 (AC 34324): the starting cursors ride the process-start write, so
|
|
104
|
+
// they must be attached BEFORE any write is attempted.
|
|
105
|
+
if (this.flushBegan) {
|
|
106
|
+
throw new Error("DispatchUsage.attachCursors: called after the first stats write began — the starting cursors would never be persisted");
|
|
107
|
+
}
|
|
96
108
|
this.readCursorState = readCursorState;
|
|
97
109
|
this.startCursors = startCursors;
|
|
98
110
|
for (const [source, cursor] of Object.entries(startCursors)) {
|
|
@@ -173,7 +185,7 @@ export class DispatchUsage {
|
|
|
173
185
|
*
|
|
174
186
|
* So every source in the cursors this process STARTED from that the watcher
|
|
175
187
|
* has not opened yet is reported VERBATIM — the exact `{acked, delivered}`
|
|
176
|
-
* pair it resumed from — until
|
|
188
|
+
* pair it resumed from — until a write that carries them has landed. That is
|
|
177
189
|
* what makes "every attach persists its starting cursors" true on the FIRST
|
|
178
190
|
* write rather than one stats tick (15-30 s) later: a reattach of this row
|
|
179
191
|
* inside its first interval reads them back (start-order rule 1) and starts
|
|
@@ -289,6 +301,7 @@ export class DispatchUsage {
|
|
|
289
301
|
return this.queue(async () => {
|
|
290
302
|
if (this.refused)
|
|
291
303
|
return "refused";
|
|
304
|
+
this.flushBegan = true;
|
|
292
305
|
const changed = this.counter.takeChanged();
|
|
293
306
|
const batches = chunkEntries(changed);
|
|
294
307
|
const { reports, deliveredAtSend, reportedResets, carriesStartCursors } = this.buildCursorReports();
|
|
@@ -360,6 +373,9 @@ export class DispatchUsage {
|
|
|
360
373
|
}
|
|
361
374
|
if (payload.transcriptPath !== undefined)
|
|
362
375
|
this.transcriptPathSent = true;
|
|
376
|
+
// Safe on ANY landed write: `attachCursors` refuses to run after the
|
|
377
|
+
// first flush began, so every starting cursor was either carried by
|
|
378
|
+
// this write or is superseded by a live one that this write reported.
|
|
363
379
|
this.startCursorsPersisted = true;
|
|
364
380
|
}
|
|
365
381
|
}
|
|
@@ -76,7 +76,7 @@ export interface SessionLogWatcherOptions {
|
|
|
76
76
|
* That lost every line between the last write and the restart; a cursor does
|
|
77
77
|
* not.
|
|
78
78
|
*/
|
|
79
|
-
startCursors
|
|
79
|
+
startCursors: Record<string, SourceCursorStart>;
|
|
80
80
|
/**
|
|
81
81
|
* DX-2954 — rule 4 of the start order: start every ALREADY-EXISTING file at
|
|
82
82
|
* its current end.
|
|
@@ -507,7 +507,7 @@ export class SessionLogWatcher {
|
|
|
507
507
|
this.sessionId = options.sessionId;
|
|
508
508
|
this.dispatchId = options.dispatchId;
|
|
509
509
|
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
510
|
-
this.startCursors = options.startCursors
|
|
510
|
+
this.startCursors = options.startCursors;
|
|
511
511
|
this.startExistingFilesAtEof = options.startExistingFilesAtEof ?? false;
|
|
512
512
|
this.onCursorPastEof = options.onCursorPastEof;
|
|
513
513
|
this.onSourceDiscovered = options.onSourceDiscovered;
|
package/dist/stats-wire.d.ts
CHANGED
|
@@ -61,12 +61,14 @@ export type SourceCursorReports = Record<string, SourceCursorReport>;
|
|
|
61
61
|
* DX-2954 — the running set's two kinds. `agent` is a sub-agent, produced from
|
|
62
62
|
* the TRANSCRIPT: one row per sub-agent transcript file the session-log watcher
|
|
63
63
|
* discovers (`packages/danx-session-telemetry/src/activity-observer.ts`; orchestrator decision, DX-2954
|
|
64
|
-
* comment 8004)
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
64
|
+
* comment 8004), and — for an operator session — from the plugin's
|
|
65
|
+
* `SubagentStart` / `SubagentStop` hooks, which add the finish. `bash` is a
|
|
66
|
+
* background script, produced only by the plugin's `PostToolUse` hook on a
|
|
67
|
+
* backgrounded Bash call (DX-3284; the transcript cannot see it). No hook event
|
|
68
|
+
* observes a background shell FINISHING (verified against the hooks reference —
|
|
69
|
+
* see the Step 0 note on DX-2954), so an open `bash` row is closed by the
|
|
70
|
+
* dispatch's own terminal write, never by inferring an end from transcript
|
|
71
|
+
* lines.
|
|
70
72
|
*/
|
|
71
73
|
export declare const ACTIVITY_KINDS: readonly ["agent", "bash"];
|
|
72
74
|
export type ActivityKind = (typeof ACTIVITY_KINDS)[number];
|
|
@@ -76,12 +78,13 @@ export type ActivityKind = (typeof ACTIVITY_KINDS)[number];
|
|
|
76
78
|
* fires twice, or a batch resent after a failed flush, must not duplicate it.
|
|
77
79
|
*
|
|
78
80
|
* `activityId` for a sub-agent is its transcript file's basename
|
|
79
|
-
* (`agent-<
|
|
81
|
+
* (`agent-<agent_id>`) — the SAME value the ledger credits its tokens under as
|
|
80
82
|
* `source`, which is how per-sub-agent tokens join to the running set without
|
|
81
|
-
* this table carrying a token column.
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* background script it is the Bash call's
|
|
83
|
+
* this table carrying a token column. A hook producer builds it from the hook's
|
|
84
|
+
* `agent_id` as `agent-${agent_id}` (never the bare `agent_id`, and never from
|
|
85
|
+
* `transcript_path`, which on these events is the MAIN session's transcript) or
|
|
86
|
+
* it upserts a duplicate row. For a background script it is the Bash call's
|
|
87
|
+
* `tool_use_id`.
|
|
85
88
|
*/
|
|
86
89
|
export interface ActivityChange {
|
|
87
90
|
kind: ActivityKind;
|
package/dist/stats-wire.js
CHANGED
|
@@ -9,12 +9,14 @@ export const STATS_BATCH_CAP = 500;
|
|
|
9
9
|
* DX-2954 — the running set's two kinds. `agent` is a sub-agent, produced from
|
|
10
10
|
* the TRANSCRIPT: one row per sub-agent transcript file the session-log watcher
|
|
11
11
|
* discovers (`packages/danx-session-telemetry/src/activity-observer.ts`; orchestrator decision, DX-2954
|
|
12
|
-
* comment 8004)
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
12
|
+
* comment 8004), and — for an operator session — from the plugin's
|
|
13
|
+
* `SubagentStart` / `SubagentStop` hooks, which add the finish. `bash` is a
|
|
14
|
+
* background script, produced only by the plugin's `PostToolUse` hook on a
|
|
15
|
+
* backgrounded Bash call (DX-3284; the transcript cannot see it). No hook event
|
|
16
|
+
* observes a background shell FINISHING (verified against the hooks reference —
|
|
17
|
+
* see the Step 0 note on DX-2954), so an open `bash` row is closed by the
|
|
18
|
+
* dispatch's own terminal write, never by inferring an end from transcript
|
|
19
|
+
* lines.
|
|
18
20
|
*/
|
|
19
21
|
export const ACTIVITY_KINDS = ["agent", "bash"];
|
|
20
22
|
export function chunkEntries(entries, cap = STATS_BATCH_CAP) {
|
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.18",
|
|
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",
|