vektor-slipstream 1.2.2 → 1.3.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/axon.js +389 -0
- package/cerebellum.js +438 -0
- package/cortex.js +221 -0
- package/index.js +480 -0
- package/package.json +114 -90
- package/token.js +322 -0
- package/vektor-slipstream.dxt +0 -0
package/package.json
CHANGED
|
@@ -1,90 +1,114 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "vektor-slipstream",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Hardware-accelerated persistent memory for AI agents. Local-first, zero cloud dependency, $0 embedding cost.",
|
|
5
|
+
"main": "slipstream-core.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vektor": "./vektor-cli.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./slipstream-core.js",
|
|
11
|
+
"./cloak": "./cloak-index.js",
|
|
12
|
+
"./cloak-warmup": "./cloak-warmup.js",
|
|
13
|
+
"./db": "./slipstream-db.js",
|
|
14
|
+
"./embedder": "./slipstream-embedder.js",
|
|
15
|
+
"./cloak-behaviour": "./cloak-behaviour.js",
|
|
16
|
+
"./cloak-pattern-store": "./cloak-pattern-store.js",
|
|
17
|
+
"./cloak-recorder-auto": "./cloak-recorder-auto.js",
|
|
18
|
+
"./cloak-recorder-snippet": "./cloak-recorder-snippet.js",
|
|
19
|
+
"./cloak-turbo-quant": "./cloak-turbo-quant.js",
|
|
20
|
+
"./cloak/cortex": "./cortex.js",
|
|
21
|
+
"./cloak/axon": "./axon.js",
|
|
22
|
+
"./cloak/cerebellum": "./cerebellum.js",
|
|
23
|
+
"./cloak/token": "./token.js",
|
|
24
|
+
"./cloak/mcp": "./index.js"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"ai",
|
|
28
|
+
"memory",
|
|
29
|
+
"agent",
|
|
30
|
+
"vector",
|
|
31
|
+
"sqlite",
|
|
32
|
+
"embeddings",
|
|
33
|
+
"langchain",
|
|
34
|
+
"openai",
|
|
35
|
+
"anthropic",
|
|
36
|
+
"claude",
|
|
37
|
+
"mcp",
|
|
38
|
+
"rag",
|
|
39
|
+
"persistent-memory",
|
|
40
|
+
"local-ai",
|
|
41
|
+
"onnx",
|
|
42
|
+
"mistral",
|
|
43
|
+
"cloak"
|
|
44
|
+
],
|
|
45
|
+
"author": "VEKTOR Memory <hello@vektormemory.com>",
|
|
46
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
47
|
+
"homepage": "https://vektormemory.com",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/Vektor-Memory/Vektor-memory"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"better-sqlite3": "^12.8.0",
|
|
57
|
+
"onnxruntime-node": "^1.17.3"
|
|
58
|
+
},
|
|
59
|
+
"optionalDependencies": {
|
|
60
|
+
"@anthropic-ai/sdk": "^0.82.0",
|
|
61
|
+
"@xenova/transformers": "^2.17.2",
|
|
62
|
+
"playwright": "^1.44.0",
|
|
63
|
+
"blessed": "^0.1.81",
|
|
64
|
+
"blessed-contrib": "^4.11.0",
|
|
65
|
+
"sqlite-vec-darwin-arm64": "^0.1.6",
|
|
66
|
+
"sqlite-vec-linux-x64": "^0.1.6",
|
|
67
|
+
"sqlite-vec-windows-x64": "^0.1.6"
|
|
68
|
+
},
|
|
69
|
+
"files": [
|
|
70
|
+
"slipstream-core.js",
|
|
71
|
+
"slipstream-db.js",
|
|
72
|
+
"slipstream-embedder.js",
|
|
73
|
+
"detect-hardware.js",
|
|
74
|
+
"vektor-licence.js",
|
|
75
|
+
"vektor-licence-prompt.js",
|
|
76
|
+
"vektor-cli.js",
|
|
77
|
+
"vektor-setup.js",
|
|
78
|
+
"vektor-banner-loader.js",
|
|
79
|
+
"vektor-tui.js",
|
|
80
|
+
"boot-screen.html",
|
|
81
|
+
"cloak-core.js",
|
|
82
|
+
"cloak-index.js",
|
|
83
|
+
"cloak-identity.js",
|
|
84
|
+
"cloak-captcha.js",
|
|
85
|
+
"cloak-llms.js",
|
|
86
|
+
"cloak-turbo-quant.js",
|
|
87
|
+
"cloak-warmup.js",
|
|
88
|
+
"cloak-behaviour.js",
|
|
89
|
+
"cloak-recorder-snippet.js",
|
|
90
|
+
"cloak-pattern-store.js",
|
|
91
|
+
"cloak-recorder-auto.js",
|
|
92
|
+
"sovereign.js",
|
|
93
|
+
"visualize.js",
|
|
94
|
+
"TENETS.md",
|
|
95
|
+
"README.md",
|
|
96
|
+
"LICENSE",
|
|
97
|
+
"cortex.js",
|
|
98
|
+
"axon.js",
|
|
99
|
+
"cerebellum.js",
|
|
100
|
+
"token.js",
|
|
101
|
+
"index.js",
|
|
102
|
+
"models/model_quantized.onnx",
|
|
103
|
+
"models/vocab.json",
|
|
104
|
+
"examples/",
|
|
105
|
+
"mistral/",
|
|
106
|
+
"vektor-slipstream.dxt"
|
|
107
|
+
],
|
|
108
|
+
"publishConfig": {
|
|
109
|
+
"access": "public"
|
|
110
|
+
},
|
|
111
|
+
"devDependencies": {
|
|
112
|
+
"javascript-obfuscator": "^5.4.1"
|
|
113
|
+
}
|
|
114
|
+
}
|
package/token.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cloak_token.js
|
|
3
|
+
* Token ledger — aggregates context budget consumption per session.
|
|
4
|
+
* Writes ONE summary node per session end (via cloak_axon integration).
|
|
5
|
+
* Detects waste patterns: repeated reads, oversized context injections.
|
|
6
|
+
*
|
|
7
|
+
* Design principles:
|
|
8
|
+
* - Aggregated, not per-operation. Never writes on every read/write.
|
|
9
|
+
* - Session total written ONCE at Stop (passed to cloak_axon's tokenCount).
|
|
10
|
+
* - Waste patterns flagged at session end, not in real time.
|
|
11
|
+
* - Uses character-to-token ratios (OpenWolf method, ±15% accurate).
|
|
12
|
+
*
|
|
13
|
+
* Architecture: CLOAK layer → Vektor temporal graph
|
|
14
|
+
* Research: OpenWolf token-ledger pattern
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Token estimation ratios (same as cortex.js for consistency)
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
const TOKEN_RATIOS = {
|
|
23
|
+
code : 3.5,
|
|
24
|
+
prose: 4.0,
|
|
25
|
+
mixed: 3.75,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const CODE_EXTS = new Set(['.js','.ts','.jsx','.tsx','.py','.go','.rs','.rb','.java','.c','.cpp']);
|
|
29
|
+
|
|
30
|
+
function estimateTokens(content, filePath = '') {
|
|
31
|
+
if (!content) return 0;
|
|
32
|
+
const ext = (filePath.match(/\.[^.]+$/) || [''])[0].toLowerCase();
|
|
33
|
+
const bytes = Buffer.byteLength(String(content), 'utf8');
|
|
34
|
+
if (CODE_EXTS.has(ext)) return Math.round(bytes / TOKEN_RATIOS.code);
|
|
35
|
+
return Math.round(bytes / TOKEN_RATIOS.mixed);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// In-RAM session ledger
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
const _ledger = {
|
|
42
|
+
sessionId : null,
|
|
43
|
+
reads : [], // { file, tokens, ts }
|
|
44
|
+
writes : [], // { file, tokens, ts }
|
|
45
|
+
wasteEvents : [], // { type, description, tokens_wasted }
|
|
46
|
+
totalReadTokens : 0,
|
|
47
|
+
totalWriteTokens: 0,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function _resetLedger() {
|
|
51
|
+
_ledger.sessionId = null;
|
|
52
|
+
_ledger.reads = [];
|
|
53
|
+
_ledger.writes = [];
|
|
54
|
+
_ledger.wasteEvents = [];
|
|
55
|
+
_ledger.totalReadTokens = 0;
|
|
56
|
+
_ledger.totalWriteTokens = 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Waste detection thresholds
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
const WASTE_RULES = {
|
|
63
|
+
REPEATED_READ_THRESHOLD : 2, // same file read > 2x = waste
|
|
64
|
+
LARGE_READ_THRESHOLD : 2000, // reading >2000 tokens when anatomy would suffice
|
|
65
|
+
REPEATED_READ_PENALTY : 0.8, // assume 80% of repeated read tokens are wasted
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Session lifecycle
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* startSession(sessionId)
|
|
74
|
+
* Called from cloak_axon onSessionStart. Resets ledger for new session.
|
|
75
|
+
*/
|
|
76
|
+
function startSession(sessionId) {
|
|
77
|
+
_resetLedger();
|
|
78
|
+
_ledger.sessionId = sessionId;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Event tracking — called from hook integration
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* trackRead({ filePath, content })
|
|
87
|
+
* Record a file read. Detects repeated reads and large reads.
|
|
88
|
+
*/
|
|
89
|
+
function trackRead({ filePath, content } = {}) {
|
|
90
|
+
if (!_ledger.sessionId) return;
|
|
91
|
+
|
|
92
|
+
const tokens = estimateTokens(content, filePath);
|
|
93
|
+
const file = filePath || 'unknown';
|
|
94
|
+
const ts = Date.now();
|
|
95
|
+
|
|
96
|
+
_ledger.reads.push({ file, tokens, ts });
|
|
97
|
+
_ledger.totalReadTokens += tokens;
|
|
98
|
+
|
|
99
|
+
// Detect repeated reads of same file this session
|
|
100
|
+
const readCount = _ledger.reads.filter(r => r.file === file).length;
|
|
101
|
+
if (readCount > WASTE_RULES.REPEATED_READ_THRESHOLD) {
|
|
102
|
+
const wastedTokens = Math.round(tokens * WASTE_RULES.REPEATED_READ_PENALTY);
|
|
103
|
+
_ledger.wasteEvents.push({
|
|
104
|
+
type : 'repeated_read',
|
|
105
|
+
description : `${file} read ${readCount}x this session`,
|
|
106
|
+
tokens_wasted : wastedTokens,
|
|
107
|
+
recommendation : 'Use cloak_cortex anatomy to check file before re-reading',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Detect large reads that cortex anatomy could have avoided
|
|
112
|
+
if (tokens > WASTE_RULES.LARGE_READ_THRESHOLD) {
|
|
113
|
+
_ledger.wasteEvents.push({
|
|
114
|
+
type : 'large_read',
|
|
115
|
+
description : `${file} read at ~${tokens} tokens`,
|
|
116
|
+
tokens_wasted : Math.round(tokens * 0.5), // assume 50% avoidable
|
|
117
|
+
recommendation : 'Check cloak_cortex anatomy description first',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* trackWrite({ filePath, content })
|
|
124
|
+
* Record a file write.
|
|
125
|
+
*/
|
|
126
|
+
function trackWrite({ filePath, content } = {}) {
|
|
127
|
+
if (!_ledger.sessionId) return;
|
|
128
|
+
|
|
129
|
+
const tokens = estimateTokens(content, filePath);
|
|
130
|
+
_ledger.writes.push({ file: filePath || 'unknown', tokens, ts: Date.now() });
|
|
131
|
+
_ledger.totalWriteTokens += tokens;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Session summary — called at session end, returns total for cloak_axon
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* getSessionSummary()
|
|
140
|
+
* Returns aggregated stats for the current session.
|
|
141
|
+
* This is passed as tokenCount to cloak_axon.onSessionStop().
|
|
142
|
+
*/
|
|
143
|
+
function getSessionSummary() {
|
|
144
|
+
if (!_ledger.sessionId) return null;
|
|
145
|
+
|
|
146
|
+
const totalTokens = _ledger.totalReadTokens + _ledger.totalWriteTokens;
|
|
147
|
+
const totalWasted = _ledger.wasteEvents.reduce((sum, e) => sum + (e.tokens_wasted || 0), 0);
|
|
148
|
+
const wasteRate = totalTokens > 0 ? Math.round((totalWasted / totalTokens) * 100) : 0;
|
|
149
|
+
|
|
150
|
+
// Top files by token consumption
|
|
151
|
+
const fileTotals = {};
|
|
152
|
+
[..._ledger.reads, ..._ledger.writes].forEach(({ file, tokens }) => {
|
|
153
|
+
fileTotals[file] = (fileTotals[file] || 0) + tokens;
|
|
154
|
+
});
|
|
155
|
+
const topFiles = Object.entries(fileTotals)
|
|
156
|
+
.sort((a, b) => b[1] - a[1])
|
|
157
|
+
.slice(0, 5)
|
|
158
|
+
.map(([file, tokens]) => `${file}:~${tokens}t`);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
sessionId : _ledger.sessionId,
|
|
162
|
+
totalTokens,
|
|
163
|
+
readTokens : _ledger.totalReadTokens,
|
|
164
|
+
writeTokens : _ledger.totalWriteTokens,
|
|
165
|
+
wastedTokens : totalWasted,
|
|
166
|
+
wasteRate : `${wasteRate}%`,
|
|
167
|
+
wasteEvents : _ledger.wasteEvents.length,
|
|
168
|
+
topFiles,
|
|
169
|
+
readCount : _ledger.reads.length,
|
|
170
|
+
writeCount : _ledger.writes.length,
|
|
171
|
+
uniqueFilesRead : new Set(_ledger.reads.map(r => r.file)).size,
|
|
172
|
+
uniqueFilesWritten: new Set(_ledger.writes.map(w => w.file)).size,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Temporal graph node builder — ONE write per session
|
|
178
|
+
// This is called by cloak_axon at session stop, not by token.js directly.
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* buildTokenNode(summary)
|
|
183
|
+
* Builds the Vektor memory string for the temporal graph.
|
|
184
|
+
* cloak_axon calls this and includes it in the MemCell, OR it can be
|
|
185
|
+
* written as a standalone temporal node. Either way: one write per session.
|
|
186
|
+
*/
|
|
187
|
+
function buildTokenNode(summary) {
|
|
188
|
+
if (!summary) return null;
|
|
189
|
+
|
|
190
|
+
return [
|
|
191
|
+
`[CLOAK_TOKEN_LEDGER]`,
|
|
192
|
+
`session:${summary.sessionId}`,
|
|
193
|
+
`total_tokens:~${summary.totalTokens}`,
|
|
194
|
+
`read_tokens:~${summary.readTokens}`,
|
|
195
|
+
`write_tokens:~${summary.writeTokens}`,
|
|
196
|
+
`wasted_tokens:~${summary.wastedTokens}`,
|
|
197
|
+
`waste_rate:${summary.wasteRate}`,
|
|
198
|
+
`waste_events:${summary.wasteEvents}`,
|
|
199
|
+
`top_files:${summary.topFiles.join(',')}`,
|
|
200
|
+
`logged_at:${new Date().toISOString()}`,
|
|
201
|
+
].join(' | ');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Lifetime waste report — queries Vektor for historical patterns
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* getWasteReport({ memory, days })
|
|
210
|
+
* Queries Vektor temporal graph for token waste patterns over N days.
|
|
211
|
+
* Returns actionable recommendations. Read-only — no writes.
|
|
212
|
+
*
|
|
213
|
+
* @param {object} memory - Vektor memory instance
|
|
214
|
+
* @param {number} days - Lookback window (default: 30)
|
|
215
|
+
*/
|
|
216
|
+
async function getWasteReport({ memory, days = 30 } = {}) {
|
|
217
|
+
if (!memory) throw new Error('cloak_token: memory instance is required');
|
|
218
|
+
|
|
219
|
+
let nodes;
|
|
220
|
+
try {
|
|
221
|
+
nodes = await memory.recall(
|
|
222
|
+
`[CLOAK_TOKEN_LEDGER]`,
|
|
223
|
+
{ limit: days }
|
|
224
|
+
);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
return { error: err.message, sessions: [] };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!nodes || nodes.length === 0) {
|
|
230
|
+
return { sessions: [], totalTokens: 0, totalWasted: 0, recommendations: [] };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Parse historical nodes
|
|
234
|
+
const sessions = nodes.map(n => {
|
|
235
|
+
const parts = n.content.split(' | ');
|
|
236
|
+
const get = key => {
|
|
237
|
+
const p = parts.find(p => p.startsWith(`${key}:`));
|
|
238
|
+
return p ? p.slice(key.length + 1) : null;
|
|
239
|
+
};
|
|
240
|
+
return {
|
|
241
|
+
sessionId : get('session'),
|
|
242
|
+
totalTokens : parseInt(get('total_tokens')?.replace('~','') || '0'),
|
|
243
|
+
wastedTokens : parseInt(get('wasted_tokens')?.replace('~','') || '0'),
|
|
244
|
+
wasteRate : get('waste_rate'),
|
|
245
|
+
wasteEvents : parseInt(get('waste_events') || '0'),
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const totalTokens = sessions.reduce((s, n) => s + n.totalTokens, 0);
|
|
250
|
+
const totalWasted = sessions.reduce((s, n) => s + n.wastedTokens, 0);
|
|
251
|
+
const avgWasteRate = totalTokens > 0 ? Math.round((totalWasted / totalTokens) * 100) : 0;
|
|
252
|
+
|
|
253
|
+
const recommendations = [];
|
|
254
|
+
if (avgWasteRate > 30) {
|
|
255
|
+
recommendations.push(`High waste rate (${avgWasteRate}%) — run cloak_cortex scan to refresh anatomy index`);
|
|
256
|
+
}
|
|
257
|
+
if (sessions.filter(s => s.wasteEvents > 3).length > 3) {
|
|
258
|
+
recommendations.push('Repeated waste events — check most-read files and add to anatomy');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
sessions : sessions.slice(0, 10),
|
|
263
|
+
totalTokens,
|
|
264
|
+
totalWasted,
|
|
265
|
+
avgWasteRate : `${avgWasteRate}%`,
|
|
266
|
+
recommendations,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// Claude Code hook integration
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* handleHookPayload({ payload, sessionId })
|
|
276
|
+
* Routes Claude Code hook events to trackRead / trackWrite.
|
|
277
|
+
* Called from the main MCP server hook handler.
|
|
278
|
+
*/
|
|
279
|
+
function handleHookPayload({ payload, sessionId } = {}) {
|
|
280
|
+
if (!payload) return null;
|
|
281
|
+
|
|
282
|
+
const hookName = payload?.hook_event_name || payload?.event;
|
|
283
|
+
const tool = payload?.tool_name;
|
|
284
|
+
const input = payload?.tool_input;
|
|
285
|
+
|
|
286
|
+
// Start ledger if we receive a session ID and haven't started yet
|
|
287
|
+
if (sessionId && !_ledger.sessionId) {
|
|
288
|
+
startSession(sessionId);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
switch (hookName) {
|
|
292
|
+
case 'PostToolUse':
|
|
293
|
+
case 'post_tool_use': {
|
|
294
|
+
if (tool === 'Read') {
|
|
295
|
+
trackRead({
|
|
296
|
+
filePath: input?.file_path || input?.path,
|
|
297
|
+
content : payload?.tool_response?.content || '',
|
|
298
|
+
});
|
|
299
|
+
} else if (['Write','Edit','MultiEdit'].includes(tool)) {
|
|
300
|
+
trackWrite({
|
|
301
|
+
filePath: input?.file_path || input?.path,
|
|
302
|
+
content : input?.new_string || input?.content || '',
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return null; // token tracking never returns hook responses
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
module.exports = {
|
|
313
|
+
startSession,
|
|
314
|
+
trackRead,
|
|
315
|
+
trackWrite,
|
|
316
|
+
getSessionSummary,
|
|
317
|
+
buildTokenNode,
|
|
318
|
+
getWasteReport,
|
|
319
|
+
handleHookPayload,
|
|
320
|
+
estimateTokens,
|
|
321
|
+
_ledger, // exported for testing and cloak_axon integration
|
|
322
|
+
};
|
|
Binary file
|