@relayburn/sdk 1.6.2 → 1.7.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/CHANGELOG.md +6 -0
- package/index.d.ts +15 -1
- package/index.js +73 -6
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to `@relayburn/sdk`.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.7.0] - 2026-05-02
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `hotspots({ patterns })` now also surfaces `tool-output-bloat`, `ghost-surface`, and `tool-call-pattern` findings (previously only the core `detectPatterns` set). Each side-channel detector loads its own inputs (Claude settings, tool-result events, on-disk surface) lazily based on the requested patterns.
|
|
12
|
+
|
|
7
13
|
## [1.5.0] - 2026-05-01
|
|
8
14
|
|
|
9
15
|
### Added
|
package/index.d.ts
CHANGED
|
@@ -7,5 +7,19 @@ export declare function ingest(opts?: IngestOptions): Promise<unknown>
|
|
|
7
7
|
export interface SummaryOptions { session?: string; project?: string; since?: string; ledgerHome?: string }
|
|
8
8
|
export declare function summary(opts?: SummaryOptions): Promise<{ totalTokens: number; totalCost: number; byTool: Array<{tool:string;tokens:number;cost:number;count:number}>; byModel: Array<{model:string;tokens:number;cost:number}> }>
|
|
9
9
|
|
|
10
|
-
export interface HotspotsOptions {
|
|
10
|
+
export interface HotspotsOptions {
|
|
11
|
+
session?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Pattern kinds to detect. Supported kinds:
|
|
14
|
+
* - core (via `detectPatterns`): `retry-loop`, `failure-run`,
|
|
15
|
+
* `cancellation-run`, `compaction-loss`, `edit-revert`, `edit-heavy`,
|
|
16
|
+
* `skill-recall-dup`, `skill-pruning-protection`, `system-prompt-tax`
|
|
17
|
+
* - side-channel: `tool-output-bloat`, `ghost-surface`, `tool-call-pattern`
|
|
18
|
+
*
|
|
19
|
+
* When omitted or empty, returns the attribution result instead of a
|
|
20
|
+
* findings array.
|
|
21
|
+
*/
|
|
22
|
+
patterns?: string[];
|
|
23
|
+
ledgerHome?: string;
|
|
24
|
+
}
|
|
11
25
|
export declare function hotspots(opts?: HotspotsOptions): Promise<unknown>
|
package/index.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
import { queryAll, queryUserTurns } from '@relayburn/ledger';
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { queryAll, queryUserTurns, queryToolResultEvents } from '@relayburn/ledger';
|
|
2
|
+
import {
|
|
3
|
+
loadPricing,
|
|
4
|
+
costForTurn,
|
|
5
|
+
attributeHotspots,
|
|
6
|
+
detectPatterns,
|
|
7
|
+
findingsFromPatterns,
|
|
8
|
+
detectToolOutputBloat,
|
|
9
|
+
toolOutputBloatToFinding,
|
|
10
|
+
detectGhostSurface,
|
|
11
|
+
ghostSurfaceToFinding,
|
|
12
|
+
detectToolCallPatterns,
|
|
13
|
+
toolCallPatternToFinding,
|
|
14
|
+
loadClaudeSettings,
|
|
15
|
+
userClaudeSettingsPath,
|
|
16
|
+
projectClaudeSettingsPath,
|
|
17
|
+
} from '@relayburn/analyze';
|
|
18
|
+
import { ingestAll, buildGhostSurfaceInputs } from '@relayburn/cli';
|
|
4
19
|
|
|
5
20
|
function withHome(home, fn) {
|
|
6
21
|
const prev = process.env.RELAYBURN_HOME;
|
|
@@ -71,11 +86,63 @@ export async function hotspots(opts = {}) {
|
|
|
71
86
|
return withHome(opts.ledgerHome, async () => {
|
|
72
87
|
const turns = await queryAll({ sessionId: opts.session });
|
|
73
88
|
const userTurns = await queryUserTurns({ sessionId: opts.session });
|
|
74
|
-
const
|
|
89
|
+
const pricing = await loadPricing();
|
|
90
|
+
const userTurnsBySession = bucketBySession(userTurns);
|
|
91
|
+
const attribution = attributeHotspots(turns, { pricing, userTurnsBySession });
|
|
75
92
|
|
|
76
93
|
if (!opts.patterns || opts.patterns.length === 0) return attribution;
|
|
77
94
|
|
|
78
|
-
const
|
|
79
|
-
|
|
95
|
+
const wanted = new Set(opts.patterns);
|
|
96
|
+
const findings = [];
|
|
97
|
+
|
|
98
|
+
// Core patterns (retries, failures, edit-heavy, etc.) flow through
|
|
99
|
+
// detectPatterns + findingsFromPatterns; non-matching kinds are filtered.
|
|
100
|
+
const detected = detectPatterns(turns, { pricing, userTurnsBySession });
|
|
101
|
+
for (const f of findingsFromPatterns(detected)) {
|
|
102
|
+
if (wanted.has(f.kind)) findings.push(f);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Side-channel detectors live outside detectPatterns. Each one reads its
|
|
106
|
+
// own slice of state, so we run them lazily based on `wanted`.
|
|
107
|
+
|
|
108
|
+
if (wanted.has('tool-output-bloat')) {
|
|
109
|
+
const settings = [];
|
|
110
|
+
const userLoaded = await loadClaudeSettings(userClaudeSettingsPath());
|
|
111
|
+
if (userLoaded) settings.push(userLoaded);
|
|
112
|
+
const projectLoaded = await loadClaudeSettings(projectClaudeSettingsPath());
|
|
113
|
+
if (projectLoaded) settings.push(projectLoaded);
|
|
114
|
+
const toolResultEvents = await queryToolResultEvents({ sessionId: opts.session });
|
|
115
|
+
const bloats = detectToolOutputBloat({
|
|
116
|
+
settings,
|
|
117
|
+
toolResultEvents,
|
|
118
|
+
userTurns,
|
|
119
|
+
turns,
|
|
120
|
+
pricing,
|
|
121
|
+
});
|
|
122
|
+
for (const b of bloats) findings.push(toolOutputBloatToFinding(b));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (wanted.has('ghost-surface')) {
|
|
126
|
+
const ghostInputs = await buildGhostSurfaceInputs(turns, pricing);
|
|
127
|
+
const ghosts = await detectGhostSurface(ghostInputs);
|
|
128
|
+
for (const g of ghosts) findings.push(ghostSurfaceToFinding(g));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (wanted.has('tool-call-pattern')) {
|
|
132
|
+
const patterns = detectToolCallPatterns(turns, { pricing });
|
|
133
|
+
for (const p of patterns) findings.push(toolCallPatternToFinding(p));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return findings;
|
|
80
137
|
});
|
|
81
138
|
}
|
|
139
|
+
|
|
140
|
+
function bucketBySession(userTurns) {
|
|
141
|
+
const out = new Map();
|
|
142
|
+
for (const ut of userTurns) {
|
|
143
|
+
const list = out.get(ut.sessionId);
|
|
144
|
+
if (list) list.push(ut);
|
|
145
|
+
else out.set(ut.sessionId, [ut]);
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@relayburn/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Embeddable Relayburn SDK for in-process ingest, summary, and hotspots queries",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"node": ">=22"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@relayburn/
|
|
20
|
-
"@relayburn/
|
|
21
|
-
"@relayburn/ledger": "1.
|
|
19
|
+
"@relayburn/analyze": "1.7.0",
|
|
20
|
+
"@relayburn/cli": "1.7.0",
|
|
21
|
+
"@relayburn/ledger": "1.7.0"
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|