eminiar 0.9.0 → 0.10.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/.claude-plugin/plugin.json +1 -1
- package/README.md +15 -4
- package/dist/cli.js +3603 -343
- package/dist/hook.js +2442 -256
- package/dist/server.js +3030 -260
- package/dist/ui/app.global.js +60 -60
- package/dist/ui/styles.css +91 -0
- package/eminiar.config.schema.json +64 -0
- package/package.json +1 -1
- package/skills/em-purge/SKILL.md +2 -2
- package/skills/em-usage/SKILL.md +21 -0
package/dist/hook.js
CHANGED
|
@@ -174,14 +174,14 @@ var require_is_extglob = __commonJS({
|
|
|
174
174
|
"node_modules/is-extglob/index.js"(exports, module) {
|
|
175
175
|
"use strict";
|
|
176
176
|
init_esm_shims();
|
|
177
|
-
module.exports = function isExtglob(
|
|
178
|
-
if (typeof
|
|
177
|
+
module.exports = function isExtglob(str2) {
|
|
178
|
+
if (typeof str2 !== "string" || str2 === "") {
|
|
179
179
|
return false;
|
|
180
180
|
}
|
|
181
181
|
var match;
|
|
182
|
-
while (match = /(\\).|([@?!+*]\(.*\))/g.exec(
|
|
182
|
+
while (match = /(\\).|([@?!+*]\(.*\))/g.exec(str2)) {
|
|
183
183
|
if (match[2]) return true;
|
|
184
|
-
|
|
184
|
+
str2 = str2.slice(match.index + match[0].length);
|
|
185
185
|
}
|
|
186
186
|
return false;
|
|
187
187
|
};
|
|
@@ -195,8 +195,8 @@ var require_is_glob = __commonJS({
|
|
|
195
195
|
init_esm_shims();
|
|
196
196
|
var isExtglob = require_is_extglob();
|
|
197
197
|
var chars = { "{": "}", "(": ")", "[": "]" };
|
|
198
|
-
var strictCheck = function(
|
|
199
|
-
if (
|
|
198
|
+
var strictCheck = function(str2) {
|
|
199
|
+
if (str2[0] === "!") {
|
|
200
200
|
return true;
|
|
201
201
|
}
|
|
202
202
|
var index = 0;
|
|
@@ -205,70 +205,70 @@ var require_is_glob = __commonJS({
|
|
|
205
205
|
var closeCurlyIndex = -2;
|
|
206
206
|
var closeParenIndex = -2;
|
|
207
207
|
var backSlashIndex = -2;
|
|
208
|
-
while (index <
|
|
209
|
-
if (
|
|
208
|
+
while (index < str2.length) {
|
|
209
|
+
if (str2[index] === "*") {
|
|
210
210
|
return true;
|
|
211
211
|
}
|
|
212
|
-
if (
|
|
212
|
+
if (str2[index + 1] === "?" && /[\].+)]/.test(str2[index])) {
|
|
213
213
|
return true;
|
|
214
214
|
}
|
|
215
|
-
if (closeSquareIndex !== -1 &&
|
|
215
|
+
if (closeSquareIndex !== -1 && str2[index] === "[" && str2[index + 1] !== "]") {
|
|
216
216
|
if (closeSquareIndex < index) {
|
|
217
|
-
closeSquareIndex =
|
|
217
|
+
closeSquareIndex = str2.indexOf("]", index);
|
|
218
218
|
}
|
|
219
219
|
if (closeSquareIndex > index) {
|
|
220
220
|
if (backSlashIndex === -1 || backSlashIndex > closeSquareIndex) {
|
|
221
221
|
return true;
|
|
222
222
|
}
|
|
223
|
-
backSlashIndex =
|
|
223
|
+
backSlashIndex = str2.indexOf("\\", index);
|
|
224
224
|
if (backSlashIndex === -1 || backSlashIndex > closeSquareIndex) {
|
|
225
225
|
return true;
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
|
-
if (closeCurlyIndex !== -1 &&
|
|
230
|
-
closeCurlyIndex =
|
|
229
|
+
if (closeCurlyIndex !== -1 && str2[index] === "{" && str2[index + 1] !== "}") {
|
|
230
|
+
closeCurlyIndex = str2.indexOf("}", index);
|
|
231
231
|
if (closeCurlyIndex > index) {
|
|
232
|
-
backSlashIndex =
|
|
232
|
+
backSlashIndex = str2.indexOf("\\", index);
|
|
233
233
|
if (backSlashIndex === -1 || backSlashIndex > closeCurlyIndex) {
|
|
234
234
|
return true;
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
-
if (closeParenIndex !== -1 &&
|
|
239
|
-
closeParenIndex =
|
|
238
|
+
if (closeParenIndex !== -1 && str2[index] === "(" && str2[index + 1] === "?" && /[:!=]/.test(str2[index + 2]) && str2[index + 3] !== ")") {
|
|
239
|
+
closeParenIndex = str2.indexOf(")", index);
|
|
240
240
|
if (closeParenIndex > index) {
|
|
241
|
-
backSlashIndex =
|
|
241
|
+
backSlashIndex = str2.indexOf("\\", index);
|
|
242
242
|
if (backSlashIndex === -1 || backSlashIndex > closeParenIndex) {
|
|
243
243
|
return true;
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
|
-
if (pipeIndex !== -1 &&
|
|
247
|
+
if (pipeIndex !== -1 && str2[index] === "(" && str2[index + 1] !== "|") {
|
|
248
248
|
if (pipeIndex < index) {
|
|
249
|
-
pipeIndex =
|
|
249
|
+
pipeIndex = str2.indexOf("|", index);
|
|
250
250
|
}
|
|
251
|
-
if (pipeIndex !== -1 &&
|
|
252
|
-
closeParenIndex =
|
|
251
|
+
if (pipeIndex !== -1 && str2[pipeIndex + 1] !== ")") {
|
|
252
|
+
closeParenIndex = str2.indexOf(")", pipeIndex);
|
|
253
253
|
if (closeParenIndex > pipeIndex) {
|
|
254
|
-
backSlashIndex =
|
|
254
|
+
backSlashIndex = str2.indexOf("\\", pipeIndex);
|
|
255
255
|
if (backSlashIndex === -1 || backSlashIndex > closeParenIndex) {
|
|
256
256
|
return true;
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
|
-
if (
|
|
262
|
-
var open4 =
|
|
261
|
+
if (str2[index] === "\\") {
|
|
262
|
+
var open4 = str2[index + 1];
|
|
263
263
|
index += 2;
|
|
264
264
|
var close = chars[open4];
|
|
265
265
|
if (close) {
|
|
266
|
-
var n =
|
|
266
|
+
var n = str2.indexOf(close, index);
|
|
267
267
|
if (n !== -1) {
|
|
268
268
|
index = n + 1;
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
|
-
if (
|
|
271
|
+
if (str2[index] === "!") {
|
|
272
272
|
return true;
|
|
273
273
|
}
|
|
274
274
|
} else {
|
|
@@ -277,26 +277,26 @@ var require_is_glob = __commonJS({
|
|
|
277
277
|
}
|
|
278
278
|
return false;
|
|
279
279
|
};
|
|
280
|
-
var relaxedCheck = function(
|
|
281
|
-
if (
|
|
280
|
+
var relaxedCheck = function(str2) {
|
|
281
|
+
if (str2[0] === "!") {
|
|
282
282
|
return true;
|
|
283
283
|
}
|
|
284
284
|
var index = 0;
|
|
285
|
-
while (index <
|
|
286
|
-
if (/[*?{}()[\]]/.test(
|
|
285
|
+
while (index < str2.length) {
|
|
286
|
+
if (/[*?{}()[\]]/.test(str2[index])) {
|
|
287
287
|
return true;
|
|
288
288
|
}
|
|
289
|
-
if (
|
|
290
|
-
var open4 =
|
|
289
|
+
if (str2[index] === "\\") {
|
|
290
|
+
var open4 = str2[index + 1];
|
|
291
291
|
index += 2;
|
|
292
292
|
var close = chars[open4];
|
|
293
293
|
if (close) {
|
|
294
|
-
var n =
|
|
294
|
+
var n = str2.indexOf(close, index);
|
|
295
295
|
if (n !== -1) {
|
|
296
296
|
index = n + 1;
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
|
-
if (
|
|
299
|
+
if (str2[index] === "!") {
|
|
300
300
|
return true;
|
|
301
301
|
}
|
|
302
302
|
} else {
|
|
@@ -305,18 +305,18 @@ var require_is_glob = __commonJS({
|
|
|
305
305
|
}
|
|
306
306
|
return false;
|
|
307
307
|
};
|
|
308
|
-
module.exports = function isGlob(
|
|
309
|
-
if (typeof
|
|
308
|
+
module.exports = function isGlob(str2, options) {
|
|
309
|
+
if (typeof str2 !== "string" || str2 === "") {
|
|
310
310
|
return false;
|
|
311
311
|
}
|
|
312
|
-
if (isExtglob(
|
|
312
|
+
if (isExtglob(str2)) {
|
|
313
313
|
return true;
|
|
314
314
|
}
|
|
315
315
|
var check2 = strictCheck;
|
|
316
316
|
if (options && options.strict === false) {
|
|
317
317
|
check2 = relaxedCheck;
|
|
318
318
|
}
|
|
319
|
-
return check2(
|
|
319
|
+
return check2(str2);
|
|
320
320
|
};
|
|
321
321
|
}
|
|
322
322
|
});
|
|
@@ -334,19 +334,19 @@ var require_glob_parent = __commonJS({
|
|
|
334
334
|
var enclosure = /[\{\[].*[\}\]]$/;
|
|
335
335
|
var globby = /(^|[^\\])([\{\[]|\([^\)]+$)/;
|
|
336
336
|
var escaped = /\\([\!\*\?\|\[\]\(\)\{\}])/g;
|
|
337
|
-
module.exports = function globParent(
|
|
337
|
+
module.exports = function globParent(str2, opts) {
|
|
338
338
|
var options = Object.assign({ flipBackslashes: true }, opts);
|
|
339
|
-
if (options.flipBackslashes && isWin32 &&
|
|
340
|
-
|
|
339
|
+
if (options.flipBackslashes && isWin32 && str2.indexOf(slash) < 0) {
|
|
340
|
+
str2 = str2.replace(backslash, slash);
|
|
341
341
|
}
|
|
342
|
-
if (enclosure.test(
|
|
343
|
-
|
|
342
|
+
if (enclosure.test(str2)) {
|
|
343
|
+
str2 += slash;
|
|
344
344
|
}
|
|
345
|
-
|
|
345
|
+
str2 += "a";
|
|
346
346
|
do {
|
|
347
|
-
|
|
348
|
-
} while (isGlob(
|
|
349
|
-
return
|
|
347
|
+
str2 = pathPosixDirname(str2);
|
|
348
|
+
} while (isGlob(str2) || globby.test(str2));
|
|
349
|
+
return str2.replace(escaped, "$1");
|
|
350
350
|
};
|
|
351
351
|
}
|
|
352
352
|
});
|
|
@@ -356,12 +356,12 @@ var require_utils = __commonJS({
|
|
|
356
356
|
"node_modules/braces/lib/utils.js"(exports) {
|
|
357
357
|
"use strict";
|
|
358
358
|
init_esm_shims();
|
|
359
|
-
exports.isInteger = (
|
|
360
|
-
if (typeof
|
|
361
|
-
return Number.isInteger(
|
|
359
|
+
exports.isInteger = (num4) => {
|
|
360
|
+
if (typeof num4 === "number") {
|
|
361
|
+
return Number.isInteger(num4);
|
|
362
362
|
}
|
|
363
|
-
if (typeof
|
|
364
|
-
return Number.isInteger(Number(
|
|
363
|
+
if (typeof num4 === "string" && num4.trim() !== "") {
|
|
364
|
+
return Number.isInteger(Number(num4));
|
|
365
365
|
}
|
|
366
366
|
return false;
|
|
367
367
|
};
|
|
@@ -471,12 +471,12 @@ var require_is_number = __commonJS({
|
|
|
471
471
|
"node_modules/is-number/index.js"(exports, module) {
|
|
472
472
|
"use strict";
|
|
473
473
|
init_esm_shims();
|
|
474
|
-
module.exports = function(
|
|
475
|
-
if (typeof
|
|
476
|
-
return
|
|
474
|
+
module.exports = function(num4) {
|
|
475
|
+
if (typeof num4 === "number") {
|
|
476
|
+
return num4 - num4 === 0;
|
|
477
477
|
}
|
|
478
|
-
if (typeof
|
|
479
|
-
return Number.isFinite ? Number.isFinite(+
|
|
478
|
+
if (typeof num4 === "string" && num4.trim() !== "") {
|
|
479
|
+
return Number.isFinite ? Number.isFinite(+num4) : isFinite(+num4);
|
|
480
480
|
}
|
|
481
481
|
return false;
|
|
482
482
|
};
|
|
@@ -600,7 +600,7 @@ var require_to_regex_range = __commonJS({
|
|
|
600
600
|
}
|
|
601
601
|
return { pattern, count: [count], digits };
|
|
602
602
|
}
|
|
603
|
-
function splitToPatterns(min, max,
|
|
603
|
+
function splitToPatterns(min, max, tok2, options) {
|
|
604
604
|
let ranges = splitToRanges(min, max);
|
|
605
605
|
let tokens = [];
|
|
606
606
|
let start = min;
|
|
@@ -609,7 +609,7 @@ var require_to_regex_range = __commonJS({
|
|
|
609
609
|
let max2 = ranges[i];
|
|
610
610
|
let obj = rangeToPattern(String(start), String(max2), options);
|
|
611
611
|
let zeros = "";
|
|
612
|
-
if (!
|
|
612
|
+
if (!tok2.isPadded && prev && prev.pattern === obj.pattern) {
|
|
613
613
|
if (prev.count.length > 1) {
|
|
614
614
|
prev.count.pop();
|
|
615
615
|
}
|
|
@@ -618,8 +618,8 @@ var require_to_regex_range = __commonJS({
|
|
|
618
618
|
start = max2 + 1;
|
|
619
619
|
continue;
|
|
620
620
|
}
|
|
621
|
-
if (
|
|
622
|
-
zeros = padZeros(max2,
|
|
621
|
+
if (tok2.isPadded) {
|
|
622
|
+
zeros = padZeros(max2, tok2, options);
|
|
623
623
|
}
|
|
624
624
|
obj.string = zeros + obj.pattern + toQuantifier(obj.count);
|
|
625
625
|
tokens.push(obj);
|
|
@@ -668,14 +668,14 @@ var require_to_regex_range = __commonJS({
|
|
|
668
668
|
function toCharacterClass(a, b, options) {
|
|
669
669
|
return `[${a}${b - a === 1 ? "" : "-"}${b}]`;
|
|
670
670
|
}
|
|
671
|
-
function hasPadding(
|
|
672
|
-
return /^-?(0+)\d/.test(
|
|
671
|
+
function hasPadding(str2) {
|
|
672
|
+
return /^-?(0+)\d/.test(str2);
|
|
673
673
|
}
|
|
674
|
-
function padZeros(value,
|
|
675
|
-
if (!
|
|
674
|
+
function padZeros(value, tok2, options) {
|
|
675
|
+
if (!tok2.isPadded) {
|
|
676
676
|
return value;
|
|
677
677
|
}
|
|
678
|
-
let diff = Math.abs(
|
|
678
|
+
let diff = Math.abs(tok2.maxLen - String(value).length);
|
|
679
679
|
let relax = options.relaxZeros !== false;
|
|
680
680
|
switch (diff) {
|
|
681
681
|
case 0:
|
|
@@ -709,7 +709,7 @@ var require_fill_range = __commonJS({
|
|
|
709
709
|
var isValidValue = (value) => {
|
|
710
710
|
return typeof value === "number" || typeof value === "string" && value !== "";
|
|
711
711
|
};
|
|
712
|
-
var isNumber = (
|
|
712
|
+
var isNumber = (num4) => Number.isInteger(+num4);
|
|
713
713
|
var zeros = (input2) => {
|
|
714
714
|
let value = `${input2}`;
|
|
715
715
|
let index = -1;
|
|
@@ -819,7 +819,7 @@ var require_fill_range = __commonJS({
|
|
|
819
819
|
return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options);
|
|
820
820
|
}
|
|
821
821
|
let parts = { negatives: [], positives: [] };
|
|
822
|
-
let push = (
|
|
822
|
+
let push = (num4) => parts[num4 < 0 ? "negatives" : "positives"].push(Math.abs(num4));
|
|
823
823
|
let range = [];
|
|
824
824
|
let index = 0;
|
|
825
825
|
while (descending ? a >= b : a <= b) {
|
|
@@ -1659,12 +1659,12 @@ var require_utils2 = __commonJS({
|
|
|
1659
1659
|
REGEX_SPECIAL_CHARS_GLOBAL
|
|
1660
1660
|
} = require_constants2();
|
|
1661
1661
|
exports.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
|
|
1662
|
-
exports.hasRegexChars = (
|
|
1663
|
-
exports.isRegexChar = (
|
|
1664
|
-
exports.escapeRegex = (
|
|
1665
|
-
exports.toPosixSlashes = (
|
|
1666
|
-
exports.removeBackslashes = (
|
|
1667
|
-
return
|
|
1662
|
+
exports.hasRegexChars = (str2) => REGEX_SPECIAL_CHARS.test(str2);
|
|
1663
|
+
exports.isRegexChar = (str2) => str2.length === 1 && exports.hasRegexChars(str2);
|
|
1664
|
+
exports.escapeRegex = (str2) => str2.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1");
|
|
1665
|
+
exports.toPosixSlashes = (str2) => str2.replace(REGEX_BACKSLASH, "/");
|
|
1666
|
+
exports.removeBackslashes = (str2) => {
|
|
1667
|
+
return str2.replace(REGEX_REMOVE_BACKSLASH, (match) => {
|
|
1668
1668
|
return match === "\\" ? "" : match;
|
|
1669
1669
|
});
|
|
1670
1670
|
};
|
|
@@ -1760,7 +1760,7 @@ var require_scan = __commonJS({
|
|
|
1760
1760
|
const slashes = [];
|
|
1761
1761
|
const tokens = [];
|
|
1762
1762
|
const parts = [];
|
|
1763
|
-
let
|
|
1763
|
+
let str2 = input2;
|
|
1764
1764
|
let index = -1;
|
|
1765
1765
|
let start = 0;
|
|
1766
1766
|
let lastIndex = 0;
|
|
@@ -1779,10 +1779,10 @@ var require_scan = __commonJS({
|
|
|
1779
1779
|
let code;
|
|
1780
1780
|
let token = { value: "", depth: 0, isGlob: false };
|
|
1781
1781
|
const eos = () => index >= length;
|
|
1782
|
-
const peek = () =>
|
|
1782
|
+
const peek = () => str2.charCodeAt(index + 1);
|
|
1783
1783
|
const advance = () => {
|
|
1784
1784
|
prev = code;
|
|
1785
|
-
return
|
|
1785
|
+
return str2.charCodeAt(++index);
|
|
1786
1786
|
};
|
|
1787
1787
|
while (index < length) {
|
|
1788
1788
|
code = advance();
|
|
@@ -1950,24 +1950,24 @@ var require_scan = __commonJS({
|
|
|
1950
1950
|
isExtglob = false;
|
|
1951
1951
|
isGlob = false;
|
|
1952
1952
|
}
|
|
1953
|
-
let base =
|
|
1953
|
+
let base = str2;
|
|
1954
1954
|
let prefix = "";
|
|
1955
1955
|
let glob = "";
|
|
1956
1956
|
if (start > 0) {
|
|
1957
|
-
prefix =
|
|
1958
|
-
|
|
1957
|
+
prefix = str2.slice(0, start);
|
|
1958
|
+
str2 = str2.slice(start);
|
|
1959
1959
|
lastIndex -= start;
|
|
1960
1960
|
}
|
|
1961
1961
|
if (base && isGlob === true && lastIndex > 0) {
|
|
1962
|
-
base =
|
|
1963
|
-
glob =
|
|
1962
|
+
base = str2.slice(0, lastIndex);
|
|
1963
|
+
glob = str2.slice(lastIndex);
|
|
1964
1964
|
} else if (isGlob === true) {
|
|
1965
1965
|
base = "";
|
|
1966
|
-
glob =
|
|
1966
|
+
glob = str2;
|
|
1967
1967
|
} else {
|
|
1968
|
-
base =
|
|
1968
|
+
base = str2;
|
|
1969
1969
|
}
|
|
1970
|
-
if (base && base !== "" && base !== "/" && base !==
|
|
1970
|
+
if (base && base !== "" && base !== "/" && base !== str2) {
|
|
1971
1971
|
if (isPathSeparator(base.charCodeAt(base.length - 1))) {
|
|
1972
1972
|
base = base.slice(0, -1);
|
|
1973
1973
|
}
|
|
@@ -2346,9 +2346,9 @@ var require_parse2 = __commonJS({
|
|
|
2346
2346
|
const peek = state.peek = (n = 1) => input2[state.index + n];
|
|
2347
2347
|
const advance = state.advance = () => input2[++state.index] || "";
|
|
2348
2348
|
const remaining = () => input2.slice(state.index + 1);
|
|
2349
|
-
const consume = (value2 = "",
|
|
2349
|
+
const consume = (value2 = "", num4 = 0) => {
|
|
2350
2350
|
state.consumed += value2;
|
|
2351
|
-
state.index +=
|
|
2351
|
+
state.index += num4;
|
|
2352
2352
|
};
|
|
2353
2353
|
const append = (token) => {
|
|
2354
2354
|
state.output += token.output != null ? token.output : token.value;
|
|
@@ -2376,11 +2376,11 @@ var require_parse2 = __commonJS({
|
|
|
2376
2376
|
state[type]--;
|
|
2377
2377
|
stack.pop();
|
|
2378
2378
|
};
|
|
2379
|
-
const push = (
|
|
2379
|
+
const push = (tok2) => {
|
|
2380
2380
|
if (prev.type === "globstar") {
|
|
2381
|
-
const isBrace = state.braces > 0 && (
|
|
2382
|
-
const isExtglob =
|
|
2383
|
-
if (
|
|
2381
|
+
const isBrace = state.braces > 0 && (tok2.type === "comma" || tok2.type === "brace");
|
|
2382
|
+
const isExtglob = tok2.extglob === true || extglobs.length && (tok2.type === "pipe" || tok2.type === "paren");
|
|
2383
|
+
if (tok2.type !== "slash" && tok2.type !== "paren" && !isBrace && !isExtglob) {
|
|
2384
2384
|
state.output = state.output.slice(0, -prev.output.length);
|
|
2385
2385
|
prev.type = "star";
|
|
2386
2386
|
prev.value = "*";
|
|
@@ -2388,18 +2388,18 @@ var require_parse2 = __commonJS({
|
|
|
2388
2388
|
state.output += prev.output;
|
|
2389
2389
|
}
|
|
2390
2390
|
}
|
|
2391
|
-
if (extglobs.length &&
|
|
2392
|
-
extglobs[extglobs.length - 1].inner +=
|
|
2391
|
+
if (extglobs.length && tok2.type !== "paren") {
|
|
2392
|
+
extglobs[extglobs.length - 1].inner += tok2.value;
|
|
2393
2393
|
}
|
|
2394
|
-
if (
|
|
2395
|
-
if (prev && prev.type === "text" &&
|
|
2396
|
-
prev.value +=
|
|
2397
|
-
prev.output = (prev.output || "") +
|
|
2394
|
+
if (tok2.value || tok2.output) append(tok2);
|
|
2395
|
+
if (prev && prev.type === "text" && tok2.type === "text") {
|
|
2396
|
+
prev.value += tok2.value;
|
|
2397
|
+
prev.output = (prev.output || "") + tok2.value;
|
|
2398
2398
|
return;
|
|
2399
2399
|
}
|
|
2400
|
-
|
|
2401
|
-
tokens.push(
|
|
2402
|
-
prev =
|
|
2400
|
+
tok2.prev = prev;
|
|
2401
|
+
tokens.push(tok2);
|
|
2402
|
+
prev = tok2;
|
|
2403
2403
|
};
|
|
2404
2404
|
const extglobOpen = (type, value2) => {
|
|
2405
2405
|
const token = { ...EXTGLOB_CHARS[value2], conditions: 1, inner: "" };
|
|
@@ -3003,8 +3003,8 @@ var require_parse2 = __commonJS({
|
|
|
3003
3003
|
if (opts2.noglobstar === true) return star;
|
|
3004
3004
|
return `(${capture}(?:(?!${START_ANCHOR}${opts2.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
|
|
3005
3005
|
};
|
|
3006
|
-
const create = (
|
|
3007
|
-
switch (
|
|
3006
|
+
const create = (str2) => {
|
|
3007
|
+
switch (str2) {
|
|
3008
3008
|
case "*":
|
|
3009
3009
|
return `${nodot}${ONE_CHAR}${star}`;
|
|
3010
3010
|
case ".*":
|
|
@@ -3022,7 +3022,7 @@ var require_parse2 = __commonJS({
|
|
|
3022
3022
|
case "**/.*":
|
|
3023
3023
|
return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
|
|
3024
3024
|
default: {
|
|
3025
|
-
const match = /^(.*?)\.(\w+)$/.exec(
|
|
3025
|
+
const match = /^(.*?)\.(\w+)$/.exec(str2);
|
|
3026
3026
|
if (!match) return;
|
|
3027
3027
|
const source2 = create(match[1]);
|
|
3028
3028
|
if (!source2) return;
|
|
@@ -3055,9 +3055,9 @@ var require_picomatch = __commonJS({
|
|
|
3055
3055
|
var picomatch = (glob, options, returnState = false) => {
|
|
3056
3056
|
if (Array.isArray(glob)) {
|
|
3057
3057
|
const fns = glob.map((input2) => picomatch(input2, options, returnState));
|
|
3058
|
-
const arrayMatcher = (
|
|
3058
|
+
const arrayMatcher = (str2) => {
|
|
3059
3059
|
for (const isMatch of fns) {
|
|
3060
|
-
const state2 = isMatch(
|
|
3060
|
+
const state2 = isMatch(str2);
|
|
3061
3061
|
if (state2) return state2;
|
|
3062
3062
|
}
|
|
3063
3063
|
return false;
|
|
@@ -3133,7 +3133,7 @@ var require_picomatch = __commonJS({
|
|
|
3133
3133
|
const regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options);
|
|
3134
3134
|
return regex.test(path2.basename(input2));
|
|
3135
3135
|
};
|
|
3136
|
-
picomatch.isMatch = (
|
|
3136
|
+
picomatch.isMatch = (str2, patterns, options) => picomatch(patterns, options)(str2);
|
|
3137
3137
|
picomatch.parse = (pattern, options) => {
|
|
3138
3138
|
if (Array.isArray(pattern)) return pattern.map((p) => picomatch.parse(p, options));
|
|
3139
3139
|
return parse4(pattern, { ...options, fastpaths: false });
|
|
@@ -3249,7 +3249,7 @@ var require_micromatch = __commonJS({
|
|
|
3249
3249
|
};
|
|
3250
3250
|
micromatch.match = micromatch;
|
|
3251
3251
|
micromatch.matcher = (pattern, options) => picomatch(pattern, options);
|
|
3252
|
-
micromatch.isMatch = (
|
|
3252
|
+
micromatch.isMatch = (str2, patterns, options) => picomatch(patterns, options)(str2);
|
|
3253
3253
|
micromatch.any = micromatch.isMatch;
|
|
3254
3254
|
micromatch.not = (list, patterns, options = {}) => {
|
|
3255
3255
|
patterns = [].concat(patterns).map(String);
|
|
@@ -3267,22 +3267,22 @@ var require_micromatch = __commonJS({
|
|
|
3267
3267
|
}
|
|
3268
3268
|
return [...result];
|
|
3269
3269
|
};
|
|
3270
|
-
micromatch.contains = (
|
|
3271
|
-
if (typeof
|
|
3272
|
-
throw new TypeError(`Expected a string: "${util.inspect(
|
|
3270
|
+
micromatch.contains = (str2, pattern, options) => {
|
|
3271
|
+
if (typeof str2 !== "string") {
|
|
3272
|
+
throw new TypeError(`Expected a string: "${util.inspect(str2)}"`);
|
|
3273
3273
|
}
|
|
3274
3274
|
if (Array.isArray(pattern)) {
|
|
3275
|
-
return pattern.some((p) => micromatch.contains(
|
|
3275
|
+
return pattern.some((p) => micromatch.contains(str2, p, options));
|
|
3276
3276
|
}
|
|
3277
3277
|
if (typeof pattern === "string") {
|
|
3278
|
-
if (isEmptyString(
|
|
3278
|
+
if (isEmptyString(str2) || isEmptyString(pattern)) {
|
|
3279
3279
|
return false;
|
|
3280
3280
|
}
|
|
3281
|
-
if (
|
|
3281
|
+
if (str2.includes(pattern) || str2.startsWith("./") && str2.slice(2).includes(pattern)) {
|
|
3282
3282
|
return true;
|
|
3283
3283
|
}
|
|
3284
3284
|
}
|
|
3285
|
-
return micromatch.isMatch(
|
|
3285
|
+
return micromatch.isMatch(str2, pattern, { ...options, contains: true });
|
|
3286
3286
|
};
|
|
3287
3287
|
micromatch.matchKeys = (obj, patterns, options) => {
|
|
3288
3288
|
if (!utils.isObject(obj)) {
|
|
@@ -3313,11 +3313,11 @@ var require_micromatch = __commonJS({
|
|
|
3313
3313
|
}
|
|
3314
3314
|
return true;
|
|
3315
3315
|
};
|
|
3316
|
-
micromatch.all = (
|
|
3317
|
-
if (typeof
|
|
3318
|
-
throw new TypeError(`Expected a string: "${util.inspect(
|
|
3316
|
+
micromatch.all = (str2, patterns, options) => {
|
|
3317
|
+
if (typeof str2 !== "string") {
|
|
3318
|
+
throw new TypeError(`Expected a string: "${util.inspect(str2)}"`);
|
|
3319
3319
|
}
|
|
3320
|
-
return [].concat(patterns).every((p) => picomatch(p, options)(
|
|
3320
|
+
return [].concat(patterns).every((p) => picomatch(p, options)(str2));
|
|
3321
3321
|
};
|
|
3322
3322
|
micromatch.capture = (glob, input2, options) => {
|
|
3323
3323
|
let posix = utils.isWindows(options);
|
|
@@ -3332,8 +3332,8 @@ var require_micromatch = __commonJS({
|
|
|
3332
3332
|
micromatch.parse = (patterns, options) => {
|
|
3333
3333
|
let res = [];
|
|
3334
3334
|
for (let pattern of [].concat(patterns || [])) {
|
|
3335
|
-
for (let
|
|
3336
|
-
res.push(picomatch.parse(
|
|
3335
|
+
for (let str2 of braces(String(pattern), options)) {
|
|
3336
|
+
res.push(picomatch.parse(str2, options));
|
|
3337
3337
|
}
|
|
3338
3338
|
}
|
|
3339
3339
|
return res;
|
|
@@ -3456,8 +3456,8 @@ var require_pattern = __commonJS({
|
|
|
3456
3456
|
}
|
|
3457
3457
|
exports.endsWithSlashGlobStar = endsWithSlashGlobStar;
|
|
3458
3458
|
function isAffectDepthOfReadingPattern(pattern) {
|
|
3459
|
-
const
|
|
3460
|
-
return endsWithSlashGlobStar(pattern) || isStaticPattern(
|
|
3459
|
+
const basename2 = path2.basename(pattern);
|
|
3460
|
+
return endsWithSlashGlobStar(pattern) || isStaticPattern(basename2);
|
|
3461
3461
|
}
|
|
3462
3462
|
exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
|
|
3463
3463
|
function expandPatternsWithBraceExpansion(patterns) {
|
|
@@ -3941,11 +3941,11 @@ var require_out = __commonJS({
|
|
|
3941
3941
|
async.read(path2, getSettings(optionsOrSettingsOrCallback), callback);
|
|
3942
3942
|
}
|
|
3943
3943
|
exports.stat = stat;
|
|
3944
|
-
function
|
|
3944
|
+
function statSync7(path2, optionsOrSettings) {
|
|
3945
3945
|
const settings = getSettings(optionsOrSettings);
|
|
3946
3946
|
return sync.read(path2, settings);
|
|
3947
3947
|
}
|
|
3948
|
-
exports.statSync =
|
|
3948
|
+
exports.statSync = statSync7;
|
|
3949
3949
|
function getSettings(settingsOrOptions = {}) {
|
|
3950
3950
|
if (settingsOrOptions instanceof settings_1.default) {
|
|
3951
3951
|
return settingsOrOptions;
|
|
@@ -4626,41 +4626,41 @@ var require_queue = __commonJS({
|
|
|
4626
4626
|
queue.drained = drained;
|
|
4627
4627
|
return queue;
|
|
4628
4628
|
function push(value) {
|
|
4629
|
-
var p = new Promise(function(
|
|
4629
|
+
var p = new Promise(function(resolve5, reject) {
|
|
4630
4630
|
pushCb(value, function(err, result) {
|
|
4631
4631
|
if (err) {
|
|
4632
4632
|
reject(err);
|
|
4633
4633
|
return;
|
|
4634
4634
|
}
|
|
4635
|
-
|
|
4635
|
+
resolve5(result);
|
|
4636
4636
|
});
|
|
4637
4637
|
});
|
|
4638
4638
|
p.catch(noop);
|
|
4639
4639
|
return p;
|
|
4640
4640
|
}
|
|
4641
4641
|
function unshift(value) {
|
|
4642
|
-
var p = new Promise(function(
|
|
4642
|
+
var p = new Promise(function(resolve5, reject) {
|
|
4643
4643
|
unshiftCb(value, function(err, result) {
|
|
4644
4644
|
if (err) {
|
|
4645
4645
|
reject(err);
|
|
4646
4646
|
return;
|
|
4647
4647
|
}
|
|
4648
|
-
|
|
4648
|
+
resolve5(result);
|
|
4649
4649
|
});
|
|
4650
4650
|
});
|
|
4651
4651
|
p.catch(noop);
|
|
4652
4652
|
return p;
|
|
4653
4653
|
}
|
|
4654
4654
|
function drained() {
|
|
4655
|
-
var p = new Promise(function(
|
|
4655
|
+
var p = new Promise(function(resolve5) {
|
|
4656
4656
|
process.nextTick(function() {
|
|
4657
4657
|
if (queue.idle()) {
|
|
4658
|
-
|
|
4658
|
+
resolve5();
|
|
4659
4659
|
} else {
|
|
4660
4660
|
var previousDrain = queue.drain;
|
|
4661
4661
|
queue.drain = function() {
|
|
4662
4662
|
if (typeof previousDrain === "function") previousDrain();
|
|
4663
|
-
|
|
4663
|
+
resolve5();
|
|
4664
4664
|
queue.drain = previousDrain;
|
|
4665
4665
|
};
|
|
4666
4666
|
}
|
|
@@ -5157,9 +5157,9 @@ var require_stream3 = __commonJS({
|
|
|
5157
5157
|
});
|
|
5158
5158
|
}
|
|
5159
5159
|
_getStat(filepath) {
|
|
5160
|
-
return new Promise((
|
|
5160
|
+
return new Promise((resolve5, reject) => {
|
|
5161
5161
|
this._stat(filepath, this._fsStatSettings, (error62, stats) => {
|
|
5162
|
-
return error62 === null ?
|
|
5162
|
+
return error62 === null ? resolve5(stats) : reject(error62);
|
|
5163
5163
|
});
|
|
5164
5164
|
});
|
|
5165
5165
|
}
|
|
@@ -5184,10 +5184,10 @@ var require_async5 = __commonJS({
|
|
|
5184
5184
|
this._readerStream = new stream_1.default(this._settings);
|
|
5185
5185
|
}
|
|
5186
5186
|
dynamic(root, options) {
|
|
5187
|
-
return new Promise((
|
|
5187
|
+
return new Promise((resolve5, reject) => {
|
|
5188
5188
|
this._walkAsync(root, options, (error62, entries) => {
|
|
5189
5189
|
if (error62 === null) {
|
|
5190
|
-
|
|
5190
|
+
resolve5(entries);
|
|
5191
5191
|
} else {
|
|
5192
5192
|
reject(error62);
|
|
5193
5193
|
}
|
|
@@ -5197,10 +5197,10 @@ var require_async5 = __commonJS({
|
|
|
5197
5197
|
async static(patterns, options) {
|
|
5198
5198
|
const entries = [];
|
|
5199
5199
|
const stream = this._readerStream.static(patterns, options);
|
|
5200
|
-
return new Promise((
|
|
5200
|
+
return new Promise((resolve5, reject) => {
|
|
5201
5201
|
stream.once("error", reject);
|
|
5202
5202
|
stream.on("data", (entry) => entries.push(entry));
|
|
5203
|
-
stream.once("end", () =>
|
|
5203
|
+
stream.once("end", () => resolve5(entries));
|
|
5204
5204
|
});
|
|
5205
5205
|
}
|
|
5206
5206
|
};
|
|
@@ -5919,7 +5919,9 @@ var CATEGORIES = {
|
|
|
5919
5919
|
tool_call: 4,
|
|
5920
5920
|
session_start: 4,
|
|
5921
5921
|
/** A proactive recall decision (fired or quiet) and what it showed; never in the guide. */
|
|
5922
|
-
recall: 4
|
|
5922
|
+
recall: 4,
|
|
5923
|
+
/** A routing decision (allow / annotate / deny) for one native call; never in the guide. */
|
|
5924
|
+
routing: 4
|
|
5923
5925
|
};
|
|
5924
5926
|
var GUIDE_ORDER = [
|
|
5925
5927
|
{ title: "Last Request", categories: ["user_prompt"] },
|
|
@@ -5968,8 +5970,8 @@ function withPriority(sessionId, drafts) {
|
|
|
5968
5970
|
return drafts.map((d) => ({ ...d, sessionId, priority: CATEGORIES[d.category] }));
|
|
5969
5971
|
}
|
|
5970
5972
|
var clip = (s, n) => {
|
|
5971
|
-
const
|
|
5972
|
-
return
|
|
5973
|
+
const str2 = typeof s === "string" ? s : JSON.stringify(s ?? "");
|
|
5974
|
+
return str2.length > n ? `${str2.slice(0, n - 1)}\u2026` : str2;
|
|
5973
5975
|
};
|
|
5974
5976
|
var DECISION = /\b(let'?s go with|we'?ll use|decided|decision:|go with|use (?:the )?\w+ instead|stick with|switch(?:ing)? to|chose|prefer(?:red)?)\b/i;
|
|
5975
5977
|
var CONSTRAINT = /\b(must(?: not)?|never|always|only|do not|don'?t|should not|required?|hard requirement|no\s+\w+ allowed)\b/i;
|
|
@@ -6964,14 +6966,14 @@ function promiseAllObject(promisesObj) {
|
|
|
6964
6966
|
}
|
|
6965
6967
|
function randomString(length = 10) {
|
|
6966
6968
|
const chars = "abcdefghijklmnopqrstuvwxyz";
|
|
6967
|
-
let
|
|
6969
|
+
let str2 = "";
|
|
6968
6970
|
for (let i = 0; i < length; i++) {
|
|
6969
|
-
|
|
6971
|
+
str2 += chars[Math.floor(Math.random() * chars.length)];
|
|
6970
6972
|
}
|
|
6971
|
-
return
|
|
6973
|
+
return str2;
|
|
6972
6974
|
}
|
|
6973
|
-
function esc(
|
|
6974
|
-
return JSON.stringify(
|
|
6975
|
+
function esc(str2) {
|
|
6976
|
+
return JSON.stringify(str2);
|
|
6975
6977
|
}
|
|
6976
6978
|
function slugify(input2) {
|
|
6977
6979
|
return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -7085,8 +7087,8 @@ var primitiveTypes = /* @__PURE__ */ new Set([
|
|
|
7085
7087
|
"symbol",
|
|
7086
7088
|
"undefined"
|
|
7087
7089
|
]);
|
|
7088
|
-
function escapeRegex(
|
|
7089
|
-
return
|
|
7090
|
+
function escapeRegex(str2) {
|
|
7091
|
+
return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7090
7092
|
}
|
|
7091
7093
|
function clone(inst, def, params) {
|
|
7092
7094
|
const cl = new inst._zod.constr(def ?? inst._zod.def);
|
|
@@ -7352,13 +7354,13 @@ function getSizableOrigin(input2) {
|
|
|
7352
7354
|
return "unknown";
|
|
7353
7355
|
}
|
|
7354
7356
|
var highSurrogate = /[\uD800-\uDBFF]/;
|
|
7355
|
-
function codePointLength(
|
|
7356
|
-
const units =
|
|
7357
|
-
if (!highSurrogate.test(
|
|
7357
|
+
function codePointLength(str2) {
|
|
7358
|
+
const units = str2.length;
|
|
7359
|
+
if (!highSurrogate.test(str2))
|
|
7358
7360
|
return units;
|
|
7359
7361
|
let count = units;
|
|
7360
7362
|
for (let i = 0; i < units - 1; i++) {
|
|
7361
|
-
if ((
|
|
7363
|
+
if ((str2.charCodeAt(i) & 64512) === 55296 && (str2.charCodeAt(i + 1) & 64512) === 56320) {
|
|
7362
7364
|
count--;
|
|
7363
7365
|
i++;
|
|
7364
7366
|
}
|
|
@@ -11210,7 +11212,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
11210
11212
|
var NONE = 0;
|
|
11211
11213
|
var ASSUMED = 1;
|
|
11212
11214
|
var PROVEN = 2;
|
|
11213
|
-
function isRecursive(inst, stack,
|
|
11215
|
+
function isRecursive(inst, stack, resolve5) {
|
|
11214
11216
|
const cached3 = recursive.get(inst);
|
|
11215
11217
|
if (cached3 !== void 0)
|
|
11216
11218
|
return cached3 ? PROVEN : NONE;
|
|
@@ -11220,7 +11222,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
11220
11222
|
let result = NONE;
|
|
11221
11223
|
const check2 = (child) => {
|
|
11222
11224
|
if (result !== PROVEN && child?._zod) {
|
|
11223
|
-
const answer = isRecursive(child, stack,
|
|
11225
|
+
const answer = isRecursive(child, stack, resolve5);
|
|
11224
11226
|
if (answer > result)
|
|
11225
11227
|
result = answer;
|
|
11226
11228
|
}
|
|
@@ -11231,7 +11233,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
11231
11233
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
11232
11234
|
if (spread && !desc.enumerable)
|
|
11233
11235
|
continue;
|
|
11234
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
11236
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve5) : NONE;
|
|
11235
11237
|
if (child > answer)
|
|
11236
11238
|
answer = child;
|
|
11237
11239
|
}
|
|
@@ -11295,7 +11297,7 @@ function isRecursive(inst, stack, resolve4) {
|
|
|
11295
11297
|
break;
|
|
11296
11298
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
11297
11299
|
case "lazy": {
|
|
11298
|
-
const inner = def._cachedInner ?? (
|
|
11300
|
+
const inner = def._cachedInner ?? (resolve5 ? inst._zod.innerType : void 0);
|
|
11299
11301
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
11300
11302
|
break;
|
|
11301
11303
|
}
|
|
@@ -26145,6 +26147,33 @@ var ConfigSchema = external_exports.object({
|
|
|
26145
26147
|
}).optional()
|
|
26146
26148
|
})
|
|
26147
26149
|
).default([])
|
|
26150
|
+
}).prefault({}),
|
|
26151
|
+
usage: external_exports.object({
|
|
26152
|
+
/** Ingest Claude Code transcripts (tokens, cache, cost per model call) and record statusline snapshots. */
|
|
26153
|
+
enabled: external_exports.boolean().default(true),
|
|
26154
|
+
/** Soft budgets; crossing one adds a one-line warning to the next prompt (never blocks). */
|
|
26155
|
+
budgets: external_exports.object({
|
|
26156
|
+
sessionUsd: external_exports.number().positive().optional(),
|
|
26157
|
+
dailyUsd: external_exports.number().positive().optional(),
|
|
26158
|
+
/** A single API call above this is flagged as an expensive turn. */
|
|
26159
|
+
turnUsd: external_exports.number().positive().optional()
|
|
26160
|
+
}).prefault({}),
|
|
26161
|
+
/**
|
|
26162
|
+
* Price overrides per model, USD per million tokens, matched by substring of the model id
|
|
26163
|
+
* (first match wins). Missing fields fall back to the bundled table.
|
|
26164
|
+
*/
|
|
26165
|
+
pricing: external_exports.record(
|
|
26166
|
+
external_exports.string(),
|
|
26167
|
+
external_exports.object({
|
|
26168
|
+
input: external_exports.number().nonnegative().optional(),
|
|
26169
|
+
output: external_exports.number().nonnegative().optional(),
|
|
26170
|
+
cacheWrite5m: external_exports.number().nonnegative().optional(),
|
|
26171
|
+
cacheWrite1h: external_exports.number().nonnegative().optional(),
|
|
26172
|
+
cacheRead: external_exports.number().nonnegative().optional()
|
|
26173
|
+
})
|
|
26174
|
+
).default({}),
|
|
26175
|
+
/** Show cost, context and plan usage segments in `eminiar statusline`. */
|
|
26176
|
+
statusline: external_exports.boolean().default(true)
|
|
26148
26177
|
}).prefault({})
|
|
26149
26178
|
});
|
|
26150
26179
|
function readJson(file3) {
|
|
@@ -27855,6 +27884,107 @@ CREATE TABLE IF NOT EXISTS index_runs (
|
|
|
27855
27884
|
head_sha TEXT,
|
|
27856
27885
|
detail TEXT
|
|
27857
27886
|
);
|
|
27887
|
+
`
|
|
27888
|
+
},
|
|
27889
|
+
{
|
|
27890
|
+
// Usage insights: api_calls mirrors one API request per Claude Code transcript entry
|
|
27891
|
+
// (deduped on request_id + message_id; cost cached at ingest), tool_uses pairs each
|
|
27892
|
+
// tool_use block with its tool_result size, context_events records compactions,
|
|
27893
|
+
// transcript_files keeps the byte offset so ingest only reads new bytes, and
|
|
27894
|
+
// statusline_snapshots stores what `eminiar statusline` saw. Column names = row fields.
|
|
27895
|
+
version: 5,
|
|
27896
|
+
sql: `
|
|
27897
|
+
CREATE TABLE IF NOT EXISTS api_calls (
|
|
27898
|
+
request_id TEXT NOT NULL,
|
|
27899
|
+
message_id TEXT NOT NULL,
|
|
27900
|
+
session_id TEXT NOT NULL,
|
|
27901
|
+
agent_id TEXT,
|
|
27902
|
+
sidechain INTEGER NOT NULL DEFAULT 0,
|
|
27903
|
+
ts INTEGER NOT NULL,
|
|
27904
|
+
model TEXT NOT NULL,
|
|
27905
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
27906
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
27907
|
+
cache_create_5m INTEGER NOT NULL DEFAULT 0,
|
|
27908
|
+
cache_create_1h INTEGER NOT NULL DEFAULT 0,
|
|
27909
|
+
cache_read INTEGER NOT NULL DEFAULT 0,
|
|
27910
|
+
thinking_tokens INTEGER NOT NULL DEFAULT 0,
|
|
27911
|
+
web_searches INTEGER NOT NULL DEFAULT 0,
|
|
27912
|
+
web_fetches INTEGER NOT NULL DEFAULT 0,
|
|
27913
|
+
service_tier TEXT,
|
|
27914
|
+
speed TEXT,
|
|
27915
|
+
stop_reason TEXT,
|
|
27916
|
+
prompt_id TEXT,
|
|
27917
|
+
git_branch TEXT,
|
|
27918
|
+
cwd TEXT,
|
|
27919
|
+
effort TEXT,
|
|
27920
|
+
version TEXT,
|
|
27921
|
+
entrypoint TEXT,
|
|
27922
|
+
cost_usd REAL,
|
|
27923
|
+
priced INTEGER NOT NULL DEFAULT 0,
|
|
27924
|
+
PRIMARY KEY (request_id, message_id)
|
|
27925
|
+
);
|
|
27926
|
+
CREATE INDEX IF NOT EXISTS api_calls_session_ts ON api_calls(session_id, ts);
|
|
27927
|
+
CREATE INDEX IF NOT EXISTS api_calls_ts ON api_calls(ts);
|
|
27928
|
+
|
|
27929
|
+
CREATE TABLE IF NOT EXISTS tool_uses (
|
|
27930
|
+
tool_use_id TEXT PRIMARY KEY,
|
|
27931
|
+
session_id TEXT NOT NULL,
|
|
27932
|
+
agent_id TEXT,
|
|
27933
|
+
ts INTEGER NOT NULL,
|
|
27934
|
+
tool TEXT NOT NULL,
|
|
27935
|
+
input_chars INTEGER NOT NULL DEFAULT 0,
|
|
27936
|
+
result_chars INTEGER,
|
|
27937
|
+
is_error INTEGER NOT NULL DEFAULT 0,
|
|
27938
|
+
request_id TEXT
|
|
27939
|
+
);
|
|
27940
|
+
CREATE INDEX IF NOT EXISTS tool_uses_session_ts ON tool_uses(session_id, ts);
|
|
27941
|
+
CREATE INDEX IF NOT EXISTS tool_uses_ts ON tool_uses(ts);
|
|
27942
|
+
|
|
27943
|
+
CREATE TABLE IF NOT EXISTS context_events (
|
|
27944
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
27945
|
+
session_id TEXT NOT NULL,
|
|
27946
|
+
ts INTEGER NOT NULL,
|
|
27947
|
+
kind TEXT NOT NULL,
|
|
27948
|
+
trigger TEXT,
|
|
27949
|
+
pre_tokens INTEGER,
|
|
27950
|
+
post_tokens INTEGER
|
|
27951
|
+
);
|
|
27952
|
+
CREATE INDEX IF NOT EXISTS context_events_session_ts ON context_events(session_id, ts);
|
|
27953
|
+
CREATE INDEX IF NOT EXISTS context_events_ts ON context_events(ts);
|
|
27954
|
+
|
|
27955
|
+
CREATE TABLE IF NOT EXISTS transcript_files (
|
|
27956
|
+
path TEXT PRIMARY KEY,
|
|
27957
|
+
session_id TEXT,
|
|
27958
|
+
offset INTEGER NOT NULL DEFAULT 0,
|
|
27959
|
+
size INTEGER NOT NULL DEFAULT 0,
|
|
27960
|
+
mtime INTEGER NOT NULL DEFAULT 0,
|
|
27961
|
+
head_hash TEXT,
|
|
27962
|
+
ingested_at INTEGER NOT NULL
|
|
27963
|
+
);
|
|
27964
|
+
|
|
27965
|
+
CREATE TABLE IF NOT EXISTS statusline_snapshots (
|
|
27966
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
27967
|
+
session_id TEXT NOT NULL,
|
|
27968
|
+
ts INTEGER NOT NULL,
|
|
27969
|
+
model_id TEXT,
|
|
27970
|
+
cost_usd REAL,
|
|
27971
|
+
api_ms INTEGER,
|
|
27972
|
+
duration_ms INTEGER,
|
|
27973
|
+
ctx_input_tokens INTEGER,
|
|
27974
|
+
ctx_output_tokens INTEGER,
|
|
27975
|
+
ctx_size INTEGER,
|
|
27976
|
+
ctx_used_pct REAL,
|
|
27977
|
+
cache_warm INTEGER,
|
|
27978
|
+
cache_ttl TEXT,
|
|
27979
|
+
five_hour_pct REAL,
|
|
27980
|
+
five_hour_resets_at INTEGER,
|
|
27981
|
+
seven_day_pct REAL,
|
|
27982
|
+
seven_day_resets_at INTEGER,
|
|
27983
|
+
lines_added INTEGER,
|
|
27984
|
+
lines_removed INTEGER
|
|
27985
|
+
);
|
|
27986
|
+
CREATE INDEX IF NOT EXISTS statusline_snapshots_session_ts ON statusline_snapshots(session_id, ts);
|
|
27987
|
+
CREATE INDEX IF NOT EXISTS statusline_snapshots_ts ON statusline_snapshots(ts);
|
|
27858
27988
|
`
|
|
27859
27989
|
}
|
|
27860
27990
|
];
|
|
@@ -27894,6 +28024,27 @@ function filterSql(f) {
|
|
|
27894
28024
|
}
|
|
27895
28025
|
return { where: clauses.length ? ` AND ${clauses.join(" AND ")}` : "", params };
|
|
27896
28026
|
}
|
|
28027
|
+
function usageWhere(f, opts = {}) {
|
|
28028
|
+
const clauses = ["1=1"];
|
|
28029
|
+
const params = [];
|
|
28030
|
+
if (f.sessionId) {
|
|
28031
|
+
clauses.push("session_id = ?");
|
|
28032
|
+
params.push(f.sessionId);
|
|
28033
|
+
}
|
|
28034
|
+
if (f.since !== void 0) {
|
|
28035
|
+
clauses.push("ts >= ?");
|
|
28036
|
+
params.push(f.since);
|
|
28037
|
+
}
|
|
28038
|
+
if (f.until !== void 0) {
|
|
28039
|
+
clauses.push("ts < ?");
|
|
28040
|
+
params.push(f.until);
|
|
28041
|
+
}
|
|
28042
|
+
if (f.sidechain !== void 0 && opts.sidechain !== null) {
|
|
28043
|
+
const expr = opts.sidechain ?? "sidechain = 1";
|
|
28044
|
+
clauses.push(f.sidechain ? expr : `NOT (${expr})`);
|
|
28045
|
+
}
|
|
28046
|
+
return { where: clauses.join(" AND "), params };
|
|
28047
|
+
}
|
|
27897
28048
|
var SqliteAdapter = class _SqliteAdapter {
|
|
27898
28049
|
constructor(path2, opts = {}) {
|
|
27899
28050
|
this.path = path2;
|
|
@@ -28769,6 +28920,233 @@ var SqliteAdapter = class _SqliteAdapter {
|
|
|
28769
28920
|
memoryCount() {
|
|
28770
28921
|
return this.db.prepare("SELECT COUNT(*) n FROM memories").get().n;
|
|
28771
28922
|
}
|
|
28923
|
+
// --- usage: model calls, tool results, context events, statusline ------------
|
|
28924
|
+
apiCallsUpsert(rows) {
|
|
28925
|
+
if (!rows.length) return 0;
|
|
28926
|
+
const ins = this.prep(
|
|
28927
|
+
`INSERT INTO api_calls (request_id, message_id, session_id, agent_id, sidechain, ts, model,
|
|
28928
|
+
input_tokens, output_tokens, cache_create_5m, cache_create_1h, cache_read, thinking_tokens,
|
|
28929
|
+
web_searches, web_fetches, service_tier, speed, stop_reason, prompt_id, git_branch, cwd, effort,
|
|
28930
|
+
version, entrypoint, cost_usd, priced)
|
|
28931
|
+
VALUES (@request_id, @message_id, @session_id, @agent_id, @sidechain, @ts, @model,
|
|
28932
|
+
@input_tokens, @output_tokens, @cache_create_5m, @cache_create_1h, @cache_read, @thinking_tokens,
|
|
28933
|
+
@web_searches, @web_fetches, @service_tier, @speed, @stop_reason, @prompt_id, @git_branch, @cwd, @effort,
|
|
28934
|
+
@version, @entrypoint, @cost_usd, @priced)
|
|
28935
|
+
ON CONFLICT(request_id, message_id) DO UPDATE SET
|
|
28936
|
+
cost_usd = CASE WHEN excluded.output_tokens >= api_calls.output_tokens THEN excluded.cost_usd ELSE api_calls.cost_usd END,
|
|
28937
|
+
priced = CASE WHEN excluded.output_tokens >= api_calls.output_tokens THEN excluded.priced ELSE api_calls.priced END,
|
|
28938
|
+
input_tokens = MAX(api_calls.input_tokens, excluded.input_tokens),
|
|
28939
|
+
output_tokens = MAX(api_calls.output_tokens, excluded.output_tokens),
|
|
28940
|
+
cache_create_5m = MAX(api_calls.cache_create_5m, excluded.cache_create_5m),
|
|
28941
|
+
cache_create_1h = MAX(api_calls.cache_create_1h, excluded.cache_create_1h),
|
|
28942
|
+
cache_read = MAX(api_calls.cache_read, excluded.cache_read),
|
|
28943
|
+
thinking_tokens = MAX(api_calls.thinking_tokens, excluded.thinking_tokens),
|
|
28944
|
+
web_searches = MAX(api_calls.web_searches, excluded.web_searches),
|
|
28945
|
+
web_fetches = MAX(api_calls.web_fetches, excluded.web_fetches),
|
|
28946
|
+
stop_reason = COALESCE(excluded.stop_reason, api_calls.stop_reason),
|
|
28947
|
+
service_tier = COALESCE(excluded.service_tier, api_calls.service_tier),
|
|
28948
|
+
speed = COALESCE(excluded.speed, api_calls.speed),
|
|
28949
|
+
prompt_id = COALESCE(api_calls.prompt_id, excluded.prompt_id),
|
|
28950
|
+
effort = COALESCE(api_calls.effort, excluded.effort)`
|
|
28951
|
+
);
|
|
28952
|
+
const count = this.prep("SELECT COUNT(*) AS n FROM api_calls");
|
|
28953
|
+
return this.transaction(() => {
|
|
28954
|
+
const before = count.get().n;
|
|
28955
|
+
for (const r of rows) {
|
|
28956
|
+
ins.run({
|
|
28957
|
+
request_id: r.request_id,
|
|
28958
|
+
message_id: r.message_id,
|
|
28959
|
+
session_id: r.session_id,
|
|
28960
|
+
agent_id: r.agent_id ?? null,
|
|
28961
|
+
sidechain: r.sidechain ? 1 : 0,
|
|
28962
|
+
ts: r.ts,
|
|
28963
|
+
model: r.model,
|
|
28964
|
+
input_tokens: r.input_tokens,
|
|
28965
|
+
output_tokens: r.output_tokens,
|
|
28966
|
+
cache_create_5m: r.cache_create_5m,
|
|
28967
|
+
cache_create_1h: r.cache_create_1h,
|
|
28968
|
+
cache_read: r.cache_read,
|
|
28969
|
+
thinking_tokens: r.thinking_tokens,
|
|
28970
|
+
web_searches: r.web_searches,
|
|
28971
|
+
web_fetches: r.web_fetches,
|
|
28972
|
+
service_tier: r.service_tier ?? null,
|
|
28973
|
+
speed: r.speed ?? null,
|
|
28974
|
+
stop_reason: r.stop_reason ?? null,
|
|
28975
|
+
prompt_id: r.prompt_id ?? null,
|
|
28976
|
+
git_branch: r.git_branch ?? null,
|
|
28977
|
+
cwd: r.cwd ?? null,
|
|
28978
|
+
effort: r.effort ?? null,
|
|
28979
|
+
version: r.version ?? null,
|
|
28980
|
+
entrypoint: r.entrypoint ?? null,
|
|
28981
|
+
cost_usd: r.cost_usd ?? null,
|
|
28982
|
+
priced: r.priced ?? 0
|
|
28983
|
+
});
|
|
28984
|
+
}
|
|
28985
|
+
return count.get().n - before;
|
|
28986
|
+
});
|
|
28987
|
+
}
|
|
28988
|
+
apiCalls(filter = {}) {
|
|
28989
|
+
const { where, params } = usageWhere(filter);
|
|
28990
|
+
params.push(filter.limit ?? 1e5);
|
|
28991
|
+
return this.prep(
|
|
28992
|
+
`SELECT * FROM api_calls WHERE ${where} ORDER BY ts ASC, rowid ASC LIMIT ?`
|
|
28993
|
+
).all(...params);
|
|
28994
|
+
}
|
|
28995
|
+
apiCallsLatestTs() {
|
|
28996
|
+
const row = this.prep("SELECT MAX(ts) AS ts FROM api_calls").get();
|
|
28997
|
+
return row?.ts ?? void 0;
|
|
28998
|
+
}
|
|
28999
|
+
toolUsesUpsert(rows) {
|
|
29000
|
+
if (!rows.length) return 0;
|
|
29001
|
+
const ins = this.prep(
|
|
29002
|
+
`INSERT INTO tool_uses (tool_use_id, session_id, agent_id, ts, tool, input_chars, result_chars, is_error, request_id)
|
|
29003
|
+
VALUES (@tool_use_id, @session_id, @agent_id, @ts, @tool, @input_chars, @result_chars, @is_error, @request_id)
|
|
29004
|
+
ON CONFLICT(tool_use_id) DO UPDATE SET
|
|
29005
|
+
tool = CASE WHEN tool_uses.tool = '' THEN excluded.tool ELSE tool_uses.tool END,
|
|
29006
|
+
input_chars = MAX(tool_uses.input_chars, excluded.input_chars),
|
|
29007
|
+
result_chars = COALESCE(excluded.result_chars, tool_uses.result_chars),
|
|
29008
|
+
is_error = MAX(tool_uses.is_error, excluded.is_error),
|
|
29009
|
+
request_id = COALESCE(tool_uses.request_id, excluded.request_id),
|
|
29010
|
+
agent_id = COALESCE(tool_uses.agent_id, excluded.agent_id),
|
|
29011
|
+
ts = MIN(tool_uses.ts, excluded.ts)`
|
|
29012
|
+
);
|
|
29013
|
+
const count = this.prep("SELECT COUNT(*) AS n FROM tool_uses");
|
|
29014
|
+
return this.transaction(() => {
|
|
29015
|
+
const before = count.get().n;
|
|
29016
|
+
for (const r of rows) {
|
|
29017
|
+
ins.run({
|
|
29018
|
+
tool_use_id: r.tool_use_id,
|
|
29019
|
+
session_id: r.session_id,
|
|
29020
|
+
agent_id: r.agent_id ?? null,
|
|
29021
|
+
ts: r.ts,
|
|
29022
|
+
tool: r.tool,
|
|
29023
|
+
input_chars: r.input_chars,
|
|
29024
|
+
result_chars: r.result_chars ?? null,
|
|
29025
|
+
is_error: r.is_error ? 1 : 0,
|
|
29026
|
+
request_id: r.request_id ?? null
|
|
29027
|
+
});
|
|
29028
|
+
}
|
|
29029
|
+
return count.get().n - before;
|
|
29030
|
+
});
|
|
29031
|
+
}
|
|
29032
|
+
toolUses(filter = {}) {
|
|
29033
|
+
const { where, params } = usageWhere(filter, { sidechain: "agent_id IS NOT NULL" });
|
|
29034
|
+
params.push(filter.limit ?? 1e5);
|
|
29035
|
+
return this.prep(
|
|
29036
|
+
`SELECT * FROM tool_uses WHERE ${where} ORDER BY ts ASC, rowid ASC LIMIT ?`
|
|
29037
|
+
).all(...params);
|
|
29038
|
+
}
|
|
29039
|
+
contextEventInsert(e) {
|
|
29040
|
+
const info = this.prep(
|
|
29041
|
+
`INSERT INTO context_events (session_id, ts, kind, trigger, pre_tokens, post_tokens)
|
|
29042
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
29043
|
+
).run(
|
|
29044
|
+
e.session_id,
|
|
29045
|
+
e.ts,
|
|
29046
|
+
e.kind,
|
|
29047
|
+
e.trigger ?? null,
|
|
29048
|
+
e.pre_tokens ?? null,
|
|
29049
|
+
e.post_tokens ?? null
|
|
29050
|
+
);
|
|
29051
|
+
return Number(info.lastInsertRowid);
|
|
29052
|
+
}
|
|
29053
|
+
contextEvents(filter = {}) {
|
|
29054
|
+
const { where, params } = usageWhere(filter, { sidechain: null });
|
|
29055
|
+
params.push(filter.limit ?? 1e5);
|
|
29056
|
+
return this.prep(
|
|
29057
|
+
`SELECT * FROM context_events WHERE ${where} ORDER BY ts ASC, id ASC LIMIT ?`
|
|
29058
|
+
).all(...params);
|
|
29059
|
+
}
|
|
29060
|
+
transcriptFileGet(path2) {
|
|
29061
|
+
return this.prep("SELECT * FROM transcript_files WHERE path = ?").get(path2);
|
|
29062
|
+
}
|
|
29063
|
+
transcriptFileSet(row) {
|
|
29064
|
+
this.prep(
|
|
29065
|
+
`INSERT INTO transcript_files (path, session_id, offset, size, mtime, head_hash, ingested_at)
|
|
29066
|
+
VALUES (@path, @session_id, @offset, @size, @mtime, @head_hash, @ingested_at)
|
|
29067
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
29068
|
+
session_id = COALESCE(excluded.session_id, transcript_files.session_id),
|
|
29069
|
+
offset = excluded.offset, size = excluded.size, mtime = excluded.mtime,
|
|
29070
|
+
head_hash = excluded.head_hash, ingested_at = excluded.ingested_at`
|
|
29071
|
+
).run({
|
|
29072
|
+
path: row.path,
|
|
29073
|
+
session_id: row.session_id ?? null,
|
|
29074
|
+
offset: row.offset,
|
|
29075
|
+
size: row.size,
|
|
29076
|
+
mtime: row.mtime,
|
|
29077
|
+
head_hash: row.head_hash ?? null,
|
|
29078
|
+
ingested_at: row.ingested_at
|
|
29079
|
+
});
|
|
29080
|
+
}
|
|
29081
|
+
transcriptFiles() {
|
|
29082
|
+
return this.prep("SELECT * FROM transcript_files ORDER BY path").all();
|
|
29083
|
+
}
|
|
29084
|
+
statuslineSnapshotInsert(s) {
|
|
29085
|
+
const info = this.prep(
|
|
29086
|
+
`INSERT INTO statusline_snapshots (session_id, ts, model_id, cost_usd, api_ms, duration_ms,
|
|
29087
|
+
ctx_input_tokens, ctx_output_tokens, ctx_size, ctx_used_pct, cache_warm, cache_ttl,
|
|
29088
|
+
five_hour_pct, five_hour_resets_at, seven_day_pct, seven_day_resets_at, lines_added, lines_removed)
|
|
29089
|
+
VALUES (@session_id, @ts, @model_id, @cost_usd, @api_ms, @duration_ms,
|
|
29090
|
+
@ctx_input_tokens, @ctx_output_tokens, @ctx_size, @ctx_used_pct, @cache_warm, @cache_ttl,
|
|
29091
|
+
@five_hour_pct, @five_hour_resets_at, @seven_day_pct, @seven_day_resets_at, @lines_added, @lines_removed)`
|
|
29092
|
+
).run({
|
|
29093
|
+
session_id: s.session_id,
|
|
29094
|
+
ts: s.ts,
|
|
29095
|
+
model_id: s.model_id ?? null,
|
|
29096
|
+
cost_usd: s.cost_usd ?? null,
|
|
29097
|
+
api_ms: s.api_ms ?? null,
|
|
29098
|
+
duration_ms: s.duration_ms ?? null,
|
|
29099
|
+
ctx_input_tokens: s.ctx_input_tokens ?? null,
|
|
29100
|
+
ctx_output_tokens: s.ctx_output_tokens ?? null,
|
|
29101
|
+
ctx_size: s.ctx_size ?? null,
|
|
29102
|
+
ctx_used_pct: s.ctx_used_pct ?? null,
|
|
29103
|
+
cache_warm: s.cache_warm ?? null,
|
|
29104
|
+
cache_ttl: s.cache_ttl ?? null,
|
|
29105
|
+
five_hour_pct: s.five_hour_pct ?? null,
|
|
29106
|
+
five_hour_resets_at: s.five_hour_resets_at ?? null,
|
|
29107
|
+
seven_day_pct: s.seven_day_pct ?? null,
|
|
29108
|
+
seven_day_resets_at: s.seven_day_resets_at ?? null,
|
|
29109
|
+
lines_added: s.lines_added ?? null,
|
|
29110
|
+
lines_removed: s.lines_removed ?? null
|
|
29111
|
+
});
|
|
29112
|
+
return Number(info.lastInsertRowid);
|
|
29113
|
+
}
|
|
29114
|
+
statuslineSnapshots(filter = {}) {
|
|
29115
|
+
const { where, params } = usageWhere(filter, { sidechain: null });
|
|
29116
|
+
params.push(filter.limit ?? 1e5);
|
|
29117
|
+
return this.prep(
|
|
29118
|
+
`SELECT * FROM statusline_snapshots WHERE ${where} ORDER BY ts ASC, id ASC LIMIT ?`
|
|
29119
|
+
).all(...params);
|
|
29120
|
+
}
|
|
29121
|
+
statuslineLatest(sessionId) {
|
|
29122
|
+
if (sessionId) {
|
|
29123
|
+
return this.prep(
|
|
29124
|
+
"SELECT * FROM statusline_snapshots WHERE session_id = ? ORDER BY ts DESC, id DESC LIMIT 1"
|
|
29125
|
+
).get(sessionId);
|
|
29126
|
+
}
|
|
29127
|
+
return this.prep(
|
|
29128
|
+
"SELECT * FROM statusline_snapshots ORDER BY ts DESC, id DESC LIMIT 1"
|
|
29129
|
+
).get();
|
|
29130
|
+
}
|
|
29131
|
+
statuslineSnapshotsPrune(opts) {
|
|
29132
|
+
const keep = Math.max(0, Math.floor(opts.keepPerSession));
|
|
29133
|
+
return this.transaction(() => {
|
|
29134
|
+
let n = this.prep("DELETE FROM statusline_snapshots WHERE ts < ?").run(
|
|
29135
|
+
opts.olderThan
|
|
29136
|
+
).changes;
|
|
29137
|
+
n += this.prep(
|
|
29138
|
+
`DELETE FROM statusline_snapshots WHERE id IN (
|
|
29139
|
+
SELECT s.id FROM statusline_snapshots s
|
|
29140
|
+
WHERE s.id NOT IN (
|
|
29141
|
+
SELECT k.id FROM statusline_snapshots k
|
|
29142
|
+
WHERE k.session_id = s.session_id
|
|
29143
|
+
ORDER BY k.ts DESC, k.id DESC LIMIT ?
|
|
29144
|
+
)
|
|
29145
|
+
)`
|
|
29146
|
+
).run(keep).changes;
|
|
29147
|
+
return n;
|
|
29148
|
+
});
|
|
29149
|
+
}
|
|
28772
29150
|
// --- misc --------------------------------------------------------------
|
|
28773
29151
|
metaGet(key) {
|
|
28774
29152
|
const row = this.db.prepare("SELECT value FROM meta WHERE key = ?").get(key);
|
|
@@ -28799,6 +29177,11 @@ var SqliteAdapter = class _SqliteAdapter {
|
|
|
28799
29177
|
"DELETE FROM graph_edges; DELETE FROM graph_nodes; DELETE FROM graph_metrics;"
|
|
28800
29178
|
);
|
|
28801
29179
|
}
|
|
29180
|
+
if (scope === "usage" || scope === "all") {
|
|
29181
|
+
this.db.exec(
|
|
29182
|
+
"DELETE FROM api_calls; DELETE FROM tool_uses; DELETE FROM context_events; DELETE FROM transcript_files; DELETE FROM statusline_snapshots;"
|
|
29183
|
+
);
|
|
29184
|
+
}
|
|
28802
29185
|
if (scope === "memories" || scope === "all") {
|
|
28803
29186
|
this.db.exec("DELETE FROM memories;");
|
|
28804
29187
|
if (scope === "memories") {
|
|
@@ -28834,6 +29217,1544 @@ function getStorage(ctx, opts = {}) {
|
|
|
28834
29217
|
return adapter;
|
|
28835
29218
|
}
|
|
28836
29219
|
|
|
29220
|
+
// src/usage/brief.ts
|
|
29221
|
+
init_esm_shims();
|
|
29222
|
+
|
|
29223
|
+
// src/usage/aggregate.ts
|
|
29224
|
+
init_esm_shims();
|
|
29225
|
+
|
|
29226
|
+
// src/usage/blocks.ts
|
|
29227
|
+
init_esm_shims();
|
|
29228
|
+
|
|
29229
|
+
// src/usage/pricing.ts
|
|
29230
|
+
init_esm_shims();
|
|
29231
|
+
var WEB_SEARCH_PER_1K = 10;
|
|
29232
|
+
function price(input2, output2, cacheRead, extra = {}) {
|
|
29233
|
+
return {
|
|
29234
|
+
input: input2,
|
|
29235
|
+
output: output2,
|
|
29236
|
+
cacheWrite5m: input2 * 1.25,
|
|
29237
|
+
cacheWrite1h: input2 * 2,
|
|
29238
|
+
cacheRead,
|
|
29239
|
+
webSearch: WEB_SEARCH_PER_1K,
|
|
29240
|
+
...extra
|
|
29241
|
+
};
|
|
29242
|
+
}
|
|
29243
|
+
var FABLE_5_1 = price(10, 50, 0.25);
|
|
29244
|
+
var FABLE_5 = price(10, 50, 1);
|
|
29245
|
+
var OPUS_5_5 = price(4, 20, 0.2);
|
|
29246
|
+
var OPUS_5 = price(5, 25, 0.5);
|
|
29247
|
+
var OPUS_4_LATE = price(5, 25, 0.5);
|
|
29248
|
+
var OPUS_4_EARLY = price(15, 75, 1.5);
|
|
29249
|
+
var SONNET_5_5 = price(2, 10, 0.1);
|
|
29250
|
+
var SONNET_5 = price(2, 10, 0.2);
|
|
29251
|
+
var SONNET_4 = price(3, 15, 0.3);
|
|
29252
|
+
var HAIKU_5_5 = price(0.1, 0.5, 0.01, {
|
|
29253
|
+
longContext: { threshold: 1e5, input: 0.5, output: 2.5 }
|
|
29254
|
+
});
|
|
29255
|
+
var HAIKU_4_5 = price(1, 5, 0.1);
|
|
29256
|
+
var HAIKU_3_5 = price(0.8, 4, 0.08);
|
|
29257
|
+
var FAMILIES = [
|
|
29258
|
+
["fable-5-1", FABLE_5_1],
|
|
29259
|
+
["mythos-5-1", FABLE_5_1],
|
|
29260
|
+
["fable-5", FABLE_5],
|
|
29261
|
+
["mythos-5", FABLE_5],
|
|
29262
|
+
["opus-5-5", OPUS_5_5],
|
|
29263
|
+
["opus-5", OPUS_5],
|
|
29264
|
+
["opus-4-8", OPUS_4_LATE],
|
|
29265
|
+
["opus-4-7", OPUS_4_LATE],
|
|
29266
|
+
["opus-4-6", OPUS_4_LATE],
|
|
29267
|
+
["opus-4-5", OPUS_4_LATE],
|
|
29268
|
+
["opus-4-1", OPUS_4_EARLY],
|
|
29269
|
+
["opus-4", OPUS_4_EARLY],
|
|
29270
|
+
["sonnet-5-5", SONNET_5_5],
|
|
29271
|
+
["sonnet-5", SONNET_5],
|
|
29272
|
+
["sonnet-4-6", SONNET_4],
|
|
29273
|
+
["sonnet-4-5", SONNET_4],
|
|
29274
|
+
["sonnet-4", SONNET_4],
|
|
29275
|
+
["haiku-5-5", HAIKU_5_5],
|
|
29276
|
+
["haiku-4-5", HAIKU_4_5],
|
|
29277
|
+
["3-5-haiku", HAIKU_3_5]
|
|
29278
|
+
];
|
|
29279
|
+
var ALIASES = [
|
|
29280
|
+
["fable", FABLE_5_1],
|
|
29281
|
+
["mythos", FABLE_5_1],
|
|
29282
|
+
["opus", OPUS_5_5],
|
|
29283
|
+
["sonnet", SONNET_5_5],
|
|
29284
|
+
["haiku", HAIKU_4_5]
|
|
29285
|
+
];
|
|
29286
|
+
function bundledPrice(id) {
|
|
29287
|
+
for (const [pattern, p] of FAMILIES) if (id.includes(pattern)) return p;
|
|
29288
|
+
if (/\d/.test(id)) return void 0;
|
|
29289
|
+
for (const [pattern, p] of ALIASES) if (id.includes(pattern)) return p;
|
|
29290
|
+
return void 0;
|
|
29291
|
+
}
|
|
29292
|
+
function finite(n) {
|
|
29293
|
+
return typeof n === "number" && Number.isFinite(n) && n >= 0;
|
|
29294
|
+
}
|
|
29295
|
+
function resolvePrice(model, overrides) {
|
|
29296
|
+
const id = String(model ?? "").toLowerCase();
|
|
29297
|
+
if (!id) return void 0;
|
|
29298
|
+
const base = bundledPrice(id);
|
|
29299
|
+
if (!overrides) return base;
|
|
29300
|
+
for (const [pattern, o] of Object.entries(overrides)) {
|
|
29301
|
+
if (!pattern || !id.includes(pattern.toLowerCase())) continue;
|
|
29302
|
+
const input2 = finite(o.input) ? o.input : base?.input;
|
|
29303
|
+
const output2 = finite(o.output) ? o.output : base?.output;
|
|
29304
|
+
if (input2 === void 0 || output2 === void 0) return base;
|
|
29305
|
+
const merged = {
|
|
29306
|
+
input: input2,
|
|
29307
|
+
output: output2,
|
|
29308
|
+
cacheWrite5m: finite(o.cacheWrite5m) ? o.cacheWrite5m : base && !finite(o.input) ? base.cacheWrite5m : input2 * 1.25,
|
|
29309
|
+
cacheWrite1h: finite(o.cacheWrite1h) ? o.cacheWrite1h : base && !finite(o.input) ? base.cacheWrite1h : input2 * 2,
|
|
29310
|
+
cacheRead: finite(o.cacheRead) ? o.cacheRead : base && !finite(o.input) ? base.cacheRead : input2 * 0.1,
|
|
29311
|
+
webSearch: finite(o.webSearch) ? o.webSearch : base?.webSearch ?? WEB_SEARCH_PER_1K
|
|
29312
|
+
};
|
|
29313
|
+
if (o.longContext) merged.longContext = o.longContext;
|
|
29314
|
+
else if (base?.longContext) merged.longContext = base.longContext;
|
|
29315
|
+
return merged;
|
|
29316
|
+
}
|
|
29317
|
+
return base;
|
|
29318
|
+
}
|
|
29319
|
+
var tok = (n) => finite(n) ? n : 0;
|
|
29320
|
+
function costOfCall(call, price2) {
|
|
29321
|
+
const input2 = tok(call.input_tokens);
|
|
29322
|
+
const output2 = tok(call.output_tokens);
|
|
29323
|
+
const c5m = tok(call.cache_create_5m);
|
|
29324
|
+
const c1h = tok(call.cache_create_1h);
|
|
29325
|
+
const read = tok(call.cache_read);
|
|
29326
|
+
const searches = tok(call.web_searches);
|
|
29327
|
+
let p = price2;
|
|
29328
|
+
const lc = price2.longContext;
|
|
29329
|
+
if (lc && input2 + c5m + c1h + read > lc.threshold) {
|
|
29330
|
+
const ratio = price2.input > 0 ? lc.input / price2.input : 1;
|
|
29331
|
+
p = {
|
|
29332
|
+
...price2,
|
|
29333
|
+
input: lc.input,
|
|
29334
|
+
output: lc.output,
|
|
29335
|
+
cacheWrite5m: price2.cacheWrite5m * ratio,
|
|
29336
|
+
cacheWrite1h: price2.cacheWrite1h * ratio,
|
|
29337
|
+
cacheRead: price2.cacheRead * ratio
|
|
29338
|
+
};
|
|
29339
|
+
}
|
|
29340
|
+
const M = 1e6;
|
|
29341
|
+
const breakdown = {
|
|
29342
|
+
input: input2 * p.input / M,
|
|
29343
|
+
output: output2 * p.output / M,
|
|
29344
|
+
cacheWrite: (c5m * p.cacheWrite5m + c1h * p.cacheWrite1h) / M,
|
|
29345
|
+
cacheRead: read * p.cacheRead / M,
|
|
29346
|
+
tools: searches / 1e3 * (p.webSearch ?? 0)
|
|
29347
|
+
};
|
|
29348
|
+
return {
|
|
29349
|
+
...breakdown,
|
|
29350
|
+
total: breakdown.input + breakdown.output + breakdown.cacheWrite + breakdown.cacheRead + breakdown.tools
|
|
29351
|
+
};
|
|
29352
|
+
}
|
|
29353
|
+
function formatUsd(n) {
|
|
29354
|
+
if (!Number.isFinite(n) || n === 0) return "$0.00";
|
|
29355
|
+
const sign = n < 0 ? "-" : "";
|
|
29356
|
+
const v = Math.abs(n);
|
|
29357
|
+
if (v < 1e-4) return sign ? "-$0.0001" : "<$0.0001";
|
|
29358
|
+
if (v < 1) {
|
|
29359
|
+
const fixed = v.toFixed(4);
|
|
29360
|
+
if (fixed.startsWith("1")) return `${sign}$1.00`;
|
|
29361
|
+
return `${sign}$${fixed.replace(/0+$/, "").replace(/\.(\d)$/, ".$10")}`;
|
|
29362
|
+
}
|
|
29363
|
+
const two = v.toFixed(2);
|
|
29364
|
+
if (Number(two) >= 1e3) return `${sign}$${Math.round(v).toLocaleString("en-US")}`;
|
|
29365
|
+
return `${sign}$${two}`;
|
|
29366
|
+
}
|
|
29367
|
+
function estTokensFromChars(chars) {
|
|
29368
|
+
return finite(chars) ? Math.ceil(chars / 4) : 0;
|
|
29369
|
+
}
|
|
29370
|
+
|
|
29371
|
+
// src/usage/aggregate.ts
|
|
29372
|
+
var num = (n) => typeof n === "number" && Number.isFinite(n) ? n : 0;
|
|
29373
|
+
var isRealCall = (c) => c.model !== "<synthetic>";
|
|
29374
|
+
function totals(calls) {
|
|
29375
|
+
const t = {
|
|
29376
|
+
calls: 0,
|
|
29377
|
+
input: 0,
|
|
29378
|
+
output: 0,
|
|
29379
|
+
cacheCreate5m: 0,
|
|
29380
|
+
cacheCreate1h: 0,
|
|
29381
|
+
cacheRead: 0,
|
|
29382
|
+
thinking: 0,
|
|
29383
|
+
costUsd: 0,
|
|
29384
|
+
unpricedCalls: 0,
|
|
29385
|
+
cacheHitRate: 0,
|
|
29386
|
+
models: {}
|
|
29387
|
+
};
|
|
29388
|
+
for (const c of calls) {
|
|
29389
|
+
if (!isRealCall(c)) continue;
|
|
29390
|
+
t.calls += 1;
|
|
29391
|
+
t.input += num(c.input_tokens);
|
|
29392
|
+
t.output += num(c.output_tokens);
|
|
29393
|
+
t.cacheCreate5m += num(c.cache_create_5m);
|
|
29394
|
+
t.cacheCreate1h += num(c.cache_create_1h);
|
|
29395
|
+
t.cacheRead += num(c.cache_read);
|
|
29396
|
+
t.thinking += num(c.thinking_tokens);
|
|
29397
|
+
if (c.priced && c.cost_usd != null && Number.isFinite(c.cost_usd)) t.costUsd += c.cost_usd;
|
|
29398
|
+
else t.unpricedCalls += 1;
|
|
29399
|
+
const m = c.model || "unknown";
|
|
29400
|
+
t.models[m] = (t.models[m] ?? 0) + 1;
|
|
29401
|
+
}
|
|
29402
|
+
const denom = t.input + t.cacheCreate5m + t.cacheCreate1h + t.cacheRead;
|
|
29403
|
+
t.cacheHitRate = denom > 0 ? t.cacheRead / denom : 0;
|
|
29404
|
+
return t;
|
|
29405
|
+
}
|
|
29406
|
+
function groupBy(calls, keyFn) {
|
|
29407
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
29408
|
+
for (const c of calls) {
|
|
29409
|
+
if (!isRealCall(c)) continue;
|
|
29410
|
+
const k = keyFn(c);
|
|
29411
|
+
if (k == null) continue;
|
|
29412
|
+
const list = buckets.get(k);
|
|
29413
|
+
if (list) list.push(c);
|
|
29414
|
+
else buckets.set(k, [c]);
|
|
29415
|
+
}
|
|
29416
|
+
return [...buckets.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, rows]) => ({ key, totals: totals(rows) }));
|
|
29417
|
+
}
|
|
29418
|
+
var fmtCache = /* @__PURE__ */ new Map();
|
|
29419
|
+
function formatter(tz) {
|
|
29420
|
+
const key = tz ?? "";
|
|
29421
|
+
let f = fmtCache.get(key);
|
|
29422
|
+
if (!f) {
|
|
29423
|
+
f = new Intl.DateTimeFormat("en-CA", {
|
|
29424
|
+
year: "numeric",
|
|
29425
|
+
month: "2-digit",
|
|
29426
|
+
day: "2-digit",
|
|
29427
|
+
...tz ? { timeZone: tz } : {}
|
|
29428
|
+
});
|
|
29429
|
+
fmtCache.set(key, f);
|
|
29430
|
+
}
|
|
29431
|
+
return f;
|
|
29432
|
+
}
|
|
29433
|
+
function localYmd(ts, tz) {
|
|
29434
|
+
const parts = formatter(tz).formatToParts(new Date(ts));
|
|
29435
|
+
const get = (type) => Number(parts.find((p) => p.type === type)?.value ?? 0);
|
|
29436
|
+
return { y: get("year"), m: get("month"), d: get("day") };
|
|
29437
|
+
}
|
|
29438
|
+
var pad2 = (n) => String(n).padStart(2, "0");
|
|
29439
|
+
var ymdKey = ({ y, m, d }) => `${y}-${pad2(m)}-${pad2(d)}`;
|
|
29440
|
+
function dayKey(ts, tz) {
|
|
29441
|
+
return ymdKey(localYmd(ts, tz));
|
|
29442
|
+
}
|
|
29443
|
+
function byAgent(calls) {
|
|
29444
|
+
const mainRows = calls.filter((c) => !c.sidechain);
|
|
29445
|
+
const side = calls.filter((c) => !!c.sidechain);
|
|
29446
|
+
const main2 = totals(mainRows);
|
|
29447
|
+
const subagents = groupBy(side, (c) => c.agent_id || "unknown").map((g) => ({ agentId: g.key, totals: g.totals })).sort((a, b) => b.totals.costUsd - a.totals.costUsd || b.totals.calls - a.totals.calls);
|
|
29448
|
+
const subCost = subagents.reduce((s, a) => s + a.totals.costUsd, 0);
|
|
29449
|
+
const total = main2.costUsd + subCost;
|
|
29450
|
+
return { main: main2, subagents, delegationShare: total > 0 ? subCost / total : 0 };
|
|
29451
|
+
}
|
|
29452
|
+
|
|
29453
|
+
// src/usage/insights.ts
|
|
29454
|
+
init_esm_shims();
|
|
29455
|
+
|
|
29456
|
+
// src/usage/savings.ts
|
|
29457
|
+
init_esm_shims();
|
|
29458
|
+
var FALLBACK_MODEL = "claude-sonnet-5-5";
|
|
29459
|
+
|
|
29460
|
+
// src/usage/insights.ts
|
|
29461
|
+
var THRASH_WRITE_SHARE = 0.2;
|
|
29462
|
+
var THRASH_RUN = 3;
|
|
29463
|
+
var TTL_5M_MS = 5 * 6e4;
|
|
29464
|
+
var TTL_1H_MS = 60 * 6e4;
|
|
29465
|
+
var COLD_WRITE_SHARE = 0.5;
|
|
29466
|
+
var BLOAT_TOKENS = 8e3;
|
|
29467
|
+
var BLOAT_TOOLS = /* @__PURE__ */ new Set(["Read", "Bash", "Grep", "WebFetch", "Glob"]);
|
|
29468
|
+
var CHURN_COMPACTIONS = 3;
|
|
29469
|
+
var CHURN_EARLY_SHARE = 0.5;
|
|
29470
|
+
var DEFAULT_WINDOW = 2e5;
|
|
29471
|
+
var TRIVIAL_OUTPUT = 100;
|
|
29472
|
+
var TRIVIAL_SHARE = 0.3;
|
|
29473
|
+
var MIN_PREMIUM_CALLS = 5;
|
|
29474
|
+
var FAST_COST_SHARE = 0.25;
|
|
29475
|
+
var THINKING_SHARE = 0.4;
|
|
29476
|
+
var MIN_OUTPUT_FOR_THINKING = 1e3;
|
|
29477
|
+
var DELEGATION_SHARE = 0.6;
|
|
29478
|
+
var DEFAULT_TURN_USD = 1;
|
|
29479
|
+
var ERROR_RUN = 3;
|
|
29480
|
+
var NEAR_LIMIT_PCT = 85;
|
|
29481
|
+
var NEAR_LIMIT_HIGH = 95;
|
|
29482
|
+
var num2 = (n) => typeof n === "number" && Number.isFinite(n) ? n : 0;
|
|
29483
|
+
var pct = (x) => `${Math.round(x * 100)}%`;
|
|
29484
|
+
var usd = (n) => n >= 1 ? `$${n.toFixed(2)}` : `$${n.toFixed(3)}`;
|
|
29485
|
+
var round4 = (n) => Math.round(n * 1e4) / 1e4;
|
|
29486
|
+
var clamp = (n) => Math.max(0, Math.min(100, Math.round(n)));
|
|
29487
|
+
var fmtK = (n) => n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1e3 ? `${Math.round(n / 1e3)}k` : `${n}`;
|
|
29488
|
+
var contextOf = (c) => num2(c.input_tokens) + num2(c.cache_create_5m) + num2(c.cache_create_1h) + num2(c.cache_read);
|
|
29489
|
+
var writesOf = (c) => num2(c.cache_create_5m) + num2(c.cache_create_1h);
|
|
29490
|
+
var costOf = (c) => c.priced && c.cost_usd != null && Number.isFinite(c.cost_usd) ? c.cost_usd : 0;
|
|
29491
|
+
var isPremium = (model) => /opus|fable|mythos/i.test(model);
|
|
29492
|
+
function threads(calls) {
|
|
29493
|
+
const out = /* @__PURE__ */ new Map();
|
|
29494
|
+
for (const c of calls) {
|
|
29495
|
+
const key = `${c.session_id}\0${c.sidechain ? c.agent_id ?? "sidechain" : ""}`;
|
|
29496
|
+
const list = out.get(key);
|
|
29497
|
+
if (list) list.push(c);
|
|
29498
|
+
else out.set(key, [c]);
|
|
29499
|
+
}
|
|
29500
|
+
for (const list of out.values()) list.sort((a, b) => a.ts - b.ts);
|
|
29501
|
+
return out;
|
|
29502
|
+
}
|
|
29503
|
+
function groupBySession(rows) {
|
|
29504
|
+
const out = /* @__PURE__ */ new Map();
|
|
29505
|
+
for (const r of rows) {
|
|
29506
|
+
const list = out.get(r.session_id);
|
|
29507
|
+
if (list) list.push(r);
|
|
29508
|
+
else out.set(r.session_id, [r]);
|
|
29509
|
+
}
|
|
29510
|
+
return out;
|
|
29511
|
+
}
|
|
29512
|
+
function dominantModel(calls) {
|
|
29513
|
+
const counts = /* @__PURE__ */ new Map();
|
|
29514
|
+
for (const c of calls) counts.set(c.model, (counts.get(c.model) ?? 0) + 1);
|
|
29515
|
+
let best;
|
|
29516
|
+
let n = 0;
|
|
29517
|
+
for (const [m, k] of counts)
|
|
29518
|
+
if (k > n) {
|
|
29519
|
+
best = m;
|
|
29520
|
+
n = k;
|
|
29521
|
+
}
|
|
29522
|
+
return best;
|
|
29523
|
+
}
|
|
29524
|
+
function writePrice(c, p) {
|
|
29525
|
+
return num2(c.cache_create_1h) > num2(c.cache_create_5m) ? p.cacheWrite1h : p.cacheWrite5m;
|
|
29526
|
+
}
|
|
29527
|
+
function scopeFor(sessionIds) {
|
|
29528
|
+
const ids = [...new Set(sessionIds)];
|
|
29529
|
+
return ids.length === 1 ? { scope: "session", sessionId: ids[0] } : { scope: "project" };
|
|
29530
|
+
}
|
|
29531
|
+
function withScope(base, sessionIds) {
|
|
29532
|
+
const s = scopeFor(sessionIds);
|
|
29533
|
+
const out = { ...base, scope: s.scope };
|
|
29534
|
+
if (s.sessionId) out.sessionId = s.sessionId;
|
|
29535
|
+
return out;
|
|
29536
|
+
}
|
|
29537
|
+
var SEVERITY_RANK = { high: 0, warn: 1, info: 2 };
|
|
29538
|
+
function hasCompactionBetween(ctx, sessionId, from, to) {
|
|
29539
|
+
const list = ctx.compactionsBySession.get(sessionId);
|
|
29540
|
+
if (!list) return false;
|
|
29541
|
+
return list.some((t) => t > from && t <= to);
|
|
29542
|
+
}
|
|
29543
|
+
function detectCacheThrash(ctx) {
|
|
29544
|
+
let runs = 0;
|
|
29545
|
+
let longest = 0;
|
|
29546
|
+
let rewritten = 0;
|
|
29547
|
+
let savings = 0;
|
|
29548
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
29549
|
+
for (const list of ctx.threads.values()) {
|
|
29550
|
+
let run = [];
|
|
29551
|
+
const flush = () => {
|
|
29552
|
+
if (run.length >= THRASH_RUN) {
|
|
29553
|
+
runs += 1;
|
|
29554
|
+
longest = Math.max(longest, run.length);
|
|
29555
|
+
for (const c of run) {
|
|
29556
|
+
const w = writesOf(c);
|
|
29557
|
+
rewritten += w;
|
|
29558
|
+
sessions.add(c.session_id);
|
|
29559
|
+
const p = ctx.priceOf(c.model);
|
|
29560
|
+
if (p) savings += w * (writePrice(c, p) - p.cacheRead) / 1e6;
|
|
29561
|
+
}
|
|
29562
|
+
}
|
|
29563
|
+
run = [];
|
|
29564
|
+
};
|
|
29565
|
+
for (let i = 1; i < list.length; i++) {
|
|
29566
|
+
const c = list[i];
|
|
29567
|
+
const prev = list[i - 1];
|
|
29568
|
+
const ctxTokens = contextOf(c);
|
|
29569
|
+
const heavy = ctxTokens > 0 && writesOf(c) / ctxTokens >= THRASH_WRITE_SHARE;
|
|
29570
|
+
const broken = hasCompactionBetween(ctx, c.session_id, prev.ts, c.ts) || c.ts - prev.ts > ttlFor(list);
|
|
29571
|
+
if (!heavy || broken) {
|
|
29572
|
+
flush();
|
|
29573
|
+
continue;
|
|
29574
|
+
}
|
|
29575
|
+
run.push(c);
|
|
29576
|
+
}
|
|
29577
|
+
flush();
|
|
29578
|
+
}
|
|
29579
|
+
if (runs === 0) return void 0;
|
|
29580
|
+
return withScope(
|
|
29581
|
+
{
|
|
29582
|
+
id: "cache-thrash",
|
|
29583
|
+
severity: runs >= 3 || rewritten >= 5e5 ? "high" : "warn",
|
|
29584
|
+
title: "Prompt cache keeps being rewritten",
|
|
29585
|
+
reason: `${runs} run${runs === 1 ? "" : "s"} of ${THRASH_RUN}+ consecutive calls rewrote \u2265 ${pct(THRASH_WRITE_SHARE)} of their context (longest run ${longest} calls, ${fmtK(rewritten)} tokens written again).`,
|
|
29586
|
+
evidence: { runs, longestRun: longest, tokensRewritten: rewritten },
|
|
29587
|
+
estimatedSavingsUsd: round4(savings),
|
|
29588
|
+
recommendation: "Something early in the prompt changes every turn (system prompt, tool list, a file re-read with a timestamp); keep the stable prefix stable so later calls hit the cache."
|
|
29589
|
+
},
|
|
29590
|
+
sessions
|
|
29591
|
+
);
|
|
29592
|
+
}
|
|
29593
|
+
function ttlFor(list) {
|
|
29594
|
+
let w5 = 0;
|
|
29595
|
+
let w1 = 0;
|
|
29596
|
+
for (const c of list) {
|
|
29597
|
+
w5 += num2(c.cache_create_5m);
|
|
29598
|
+
w1 += num2(c.cache_create_1h);
|
|
29599
|
+
}
|
|
29600
|
+
return w1 > w5 ? TTL_1H_MS : TTL_5M_MS;
|
|
29601
|
+
}
|
|
29602
|
+
function detectCacheCold(ctx) {
|
|
29603
|
+
let restarts = 0;
|
|
29604
|
+
let rewritten = 0;
|
|
29605
|
+
let savings = 0;
|
|
29606
|
+
let longestGap = 0;
|
|
29607
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
29608
|
+
for (const list of ctx.threads.values()) {
|
|
29609
|
+
const ttl = ttlFor(list);
|
|
29610
|
+
for (let i = 1; i < list.length; i++) {
|
|
29611
|
+
const c = list[i];
|
|
29612
|
+
const prev = list[i - 1];
|
|
29613
|
+
const gap = c.ts - prev.ts;
|
|
29614
|
+
if (gap <= ttl) continue;
|
|
29615
|
+
if (hasCompactionBetween(ctx, c.session_id, prev.ts, c.ts)) continue;
|
|
29616
|
+
const ctxTokens = contextOf(c);
|
|
29617
|
+
const w = writesOf(c);
|
|
29618
|
+
if (ctxTokens === 0 || w / ctxTokens < COLD_WRITE_SHARE) continue;
|
|
29619
|
+
restarts += 1;
|
|
29620
|
+
rewritten += w;
|
|
29621
|
+
longestGap = Math.max(longestGap, gap);
|
|
29622
|
+
sessions.add(c.session_id);
|
|
29623
|
+
const p = ctx.priceOf(c.model);
|
|
29624
|
+
if (p) savings += w * (writePrice(c, p) - p.cacheRead) / 1e6;
|
|
29625
|
+
}
|
|
29626
|
+
}
|
|
29627
|
+
if (restarts === 0) return void 0;
|
|
29628
|
+
const minutes = Math.round(longestGap / 6e4);
|
|
29629
|
+
return withScope(
|
|
29630
|
+
{
|
|
29631
|
+
id: "cache-cold",
|
|
29632
|
+
severity: restarts >= 2 ? "warn" : "info",
|
|
29633
|
+
title: `Cache went cold ${restarts === 1 ? "once" : `${restarts} times`}`,
|
|
29634
|
+
reason: `${restarts} pause${restarts === 1 ? "" : "s"} longer than the cache TTL (longest ${minutes} min) forced ${fmtK(rewritten)} tokens to be written to cache again (~${usd(savings)}).`,
|
|
29635
|
+
evidence: { restarts, tokensRewritten: rewritten, longestGapMinutes: minutes },
|
|
29636
|
+
estimatedSavingsUsd: round4(savings),
|
|
29637
|
+
recommendation: "Long pauses mid-session re-pay the cache write; for walk-away breaks start a new session, or keep the turn cadence inside the TTL."
|
|
29638
|
+
},
|
|
29639
|
+
sessions
|
|
29640
|
+
);
|
|
29641
|
+
}
|
|
29642
|
+
function detectContextBloat(ctx) {
|
|
29643
|
+
const bySession = groupBySession(ctx.calls);
|
|
29644
|
+
const modelFor = /* @__PURE__ */ new Map();
|
|
29645
|
+
for (const [sid, rows] of bySession) modelFor.set(sid, dominantModel(rows));
|
|
29646
|
+
let count = 0;
|
|
29647
|
+
let tokens = 0;
|
|
29648
|
+
let savings = 0;
|
|
29649
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
29650
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
29651
|
+
for (const u of ctx.toolUses) {
|
|
29652
|
+
if (!BLOAT_TOOLS.has(u.tool)) continue;
|
|
29653
|
+
const est = estTokensFromChars(num2(u.result_chars));
|
|
29654
|
+
if (est <= BLOAT_TOKENS) continue;
|
|
29655
|
+
count += 1;
|
|
29656
|
+
tokens += est;
|
|
29657
|
+
byTool.set(u.tool, (byTool.get(u.tool) ?? 0) + 1);
|
|
29658
|
+
sessions.add(u.session_id);
|
|
29659
|
+
const p = ctx.priceOf(modelFor.get(u.session_id) ?? FALLBACK_MODEL) ?? ctx.priceOf(FALLBACK_MODEL);
|
|
29660
|
+
if (p) {
|
|
29661
|
+
const later = ctx.laterCalls(u.session_id, u.ts);
|
|
29662
|
+
savings += (est * p.cacheWrite5m + est * p.cacheRead * later) / 1e6;
|
|
29663
|
+
}
|
|
29664
|
+
}
|
|
29665
|
+
if (count === 0) return void 0;
|
|
29666
|
+
const top = [...byTool.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
29667
|
+
return withScope(
|
|
29668
|
+
{
|
|
29669
|
+
id: "context-bloat",
|
|
29670
|
+
severity: count >= 5 || tokens >= 1e5 ? "high" : "warn",
|
|
29671
|
+
title: "Oversized tool results in context",
|
|
29672
|
+
reason: `${count} ${[...byTool.keys()].join("/")} result${count === 1 ? "" : "s"} above ${fmtK(BLOAT_TOKENS)} tokens put ~${fmtK(tokens)} tokens into context (mostly ${top[0]}, ${top[1]}\xD7), re-read on every later call.`,
|
|
29673
|
+
evidence: { results: count, estTokens: tokens, topTool: top[0], topToolCount: top[1] },
|
|
29674
|
+
estimatedSavingsUsd: round4(savings),
|
|
29675
|
+
recommendation: "Route large outputs through em_execute / em_execute_file / em_search so only the matching sections come back."
|
|
29676
|
+
},
|
|
29677
|
+
sessions
|
|
29678
|
+
);
|
|
29679
|
+
}
|
|
29680
|
+
function detectCompactionChurn(ctx) {
|
|
29681
|
+
const windowFor = /* @__PURE__ */ new Map();
|
|
29682
|
+
for (const s of ctx.snapshots) {
|
|
29683
|
+
if (num2(s.ctx_size) > 0) {
|
|
29684
|
+
const prev = windowFor.get(s.session_id);
|
|
29685
|
+
if (prev === void 0) windowFor.set(s.session_id, num2(s.ctx_size));
|
|
29686
|
+
}
|
|
29687
|
+
}
|
|
29688
|
+
let worstSession;
|
|
29689
|
+
let worstCount = 0;
|
|
29690
|
+
let early = 0;
|
|
29691
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
29692
|
+
for (const [sid, evs] of groupBySession(ctx.contextEvents)) {
|
|
29693
|
+
const compactions = evs.filter((e) => e.kind === "compact");
|
|
29694
|
+
const window2 = windowFor.get(sid) ?? DEFAULT_WINDOW;
|
|
29695
|
+
const earlyHere = compactions.filter(
|
|
29696
|
+
(e) => e.pre_tokens != null && e.pre_tokens > 0 && e.pre_tokens < window2 * CHURN_EARLY_SHARE
|
|
29697
|
+
).length;
|
|
29698
|
+
if (compactions.length >= CHURN_COMPACTIONS || earlyHere > 0) {
|
|
29699
|
+
sessions.add(sid);
|
|
29700
|
+
early += earlyHere;
|
|
29701
|
+
if (compactions.length > worstCount) {
|
|
29702
|
+
worstCount = compactions.length;
|
|
29703
|
+
worstSession = sid;
|
|
29704
|
+
}
|
|
29705
|
+
}
|
|
29706
|
+
}
|
|
29707
|
+
if (sessions.size === 0) return void 0;
|
|
29708
|
+
return withScope(
|
|
29709
|
+
{
|
|
29710
|
+
id: "compaction-churn",
|
|
29711
|
+
severity: worstCount >= CHURN_COMPACTIONS * 2 ? "high" : "warn",
|
|
29712
|
+
title: "Frequent context compactions",
|
|
29713
|
+
reason: `${sessions.size} session${sessions.size === 1 ? "" : "s"} compacted repeatedly (up to ${worstCount} times in ${(worstSession ?? "").slice(0, 8)}; ${early} below ${pct(CHURN_EARLY_SHARE)} of the window).`,
|
|
29714
|
+
evidence: { sessions: sessions.size, maxCompactions: worstCount, earlyCompactions: early },
|
|
29715
|
+
recommendation: "Each compaction rewrites the whole cache and loses detail; keep raw output out of context (em_* tools) and split long tasks into sessions."
|
|
29716
|
+
},
|
|
29717
|
+
sessions
|
|
29718
|
+
);
|
|
29719
|
+
}
|
|
29720
|
+
function detectOpusForTrivial(ctx) {
|
|
29721
|
+
const toolRequests = /* @__PURE__ */ new Set();
|
|
29722
|
+
for (const u of ctx.toolUses) if (u.request_id) toolRequests.add(u.request_id);
|
|
29723
|
+
const premium = ctx.calls.filter((c) => isPremium(c.model));
|
|
29724
|
+
if (premium.length < MIN_PREMIUM_CALLS) return void 0;
|
|
29725
|
+
const trivial = premium.filter(
|
|
29726
|
+
(c) => num2(c.output_tokens) < TRIVIAL_OUTPUT && c.stop_reason !== "tool_use" && !toolRequests.has(c.request_id)
|
|
29727
|
+
);
|
|
29728
|
+
const share = trivial.length / premium.length;
|
|
29729
|
+
if (share <= TRIVIAL_SHARE) return void 0;
|
|
29730
|
+
const sonnet = resolvePrice(FALLBACK_MODEL, ctx.overrides);
|
|
29731
|
+
let spent = 0;
|
|
29732
|
+
let repriced = 0;
|
|
29733
|
+
for (const c of trivial) {
|
|
29734
|
+
spent += costOf(c);
|
|
29735
|
+
if (sonnet) repriced += costOfCall(c, sonnet).total;
|
|
29736
|
+
}
|
|
29737
|
+
const savings = Math.max(0, spent - repriced);
|
|
29738
|
+
return withScope(
|
|
29739
|
+
{
|
|
29740
|
+
id: "opus-for-trivial",
|
|
29741
|
+
severity: share >= 0.6 ? "warn" : "info",
|
|
29742
|
+
title: "Premium model on trivial turns",
|
|
29743
|
+
reason: `${trivial.length} of ${premium.length} premium-model calls (${pct(share)}) produced under ${TRIVIAL_OUTPUT} output tokens with no tool use, costing ${usd(spent)} (~${usd(repriced)} at Sonnet).`,
|
|
29744
|
+
evidence: {
|
|
29745
|
+
trivialCalls: trivial.length,
|
|
29746
|
+
premiumCalls: premium.length,
|
|
29747
|
+
share: Math.round(share * 100),
|
|
29748
|
+
spentUsd: round4(spent)
|
|
29749
|
+
},
|
|
29750
|
+
estimatedSavingsUsd: round4(savings),
|
|
29751
|
+
recommendation: "Short acknowledgements and lookups do not need the premium model; delegate routine turns to a Sonnet/Haiku subagent or switch model for simple work."
|
|
29752
|
+
},
|
|
29753
|
+
trivial.map((c) => c.session_id)
|
|
29754
|
+
);
|
|
29755
|
+
}
|
|
29756
|
+
function detectFastModeShare(ctx) {
|
|
29757
|
+
let fast = 0;
|
|
29758
|
+
let fastCalls = 0;
|
|
29759
|
+
let total = 0;
|
|
29760
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
29761
|
+
for (const c of ctx.calls) {
|
|
29762
|
+
const cost = costOf(c);
|
|
29763
|
+
total += cost;
|
|
29764
|
+
if (c.speed === "fast") {
|
|
29765
|
+
fast += cost;
|
|
29766
|
+
fastCalls += 1;
|
|
29767
|
+
sessions.add(c.session_id);
|
|
29768
|
+
}
|
|
29769
|
+
}
|
|
29770
|
+
if (total <= 0) return void 0;
|
|
29771
|
+
const share = fast / total;
|
|
29772
|
+
if (share <= FAST_COST_SHARE) return void 0;
|
|
29773
|
+
return withScope(
|
|
29774
|
+
{
|
|
29775
|
+
id: "fast-mode-share",
|
|
29776
|
+
severity: "info",
|
|
29777
|
+
title: "Fast mode is a large share of spend",
|
|
29778
|
+
reason: `${fastCalls} fast-mode calls account for ${usd(fast)} of ${usd(total)} (${pct(share)}) of priced cost.`,
|
|
29779
|
+
evidence: {
|
|
29780
|
+
fastCalls,
|
|
29781
|
+
fastUsd: round4(fast),
|
|
29782
|
+
totalUsd: round4(total),
|
|
29783
|
+
share: Math.round(share * 100)
|
|
29784
|
+
},
|
|
29785
|
+
recommendation: "Fast mode is billed at a premium; reserve it for interactive turns where latency matters."
|
|
29786
|
+
},
|
|
29787
|
+
sessions
|
|
29788
|
+
);
|
|
29789
|
+
}
|
|
29790
|
+
function detectThinkingShare(ctx) {
|
|
29791
|
+
const t = totals(ctx.calls);
|
|
29792
|
+
if (t.output < MIN_OUTPUT_FOR_THINKING || t.thinking <= 0) return void 0;
|
|
29793
|
+
const share = t.thinking / t.output;
|
|
29794
|
+
if (share <= THINKING_SHARE) return void 0;
|
|
29795
|
+
return withScope(
|
|
29796
|
+
{
|
|
29797
|
+
id: "thinking-share",
|
|
29798
|
+
severity: "info",
|
|
29799
|
+
title: "Most output tokens are thinking",
|
|
29800
|
+
reason: `${fmtK(t.thinking)} of ${fmtK(t.output)} output tokens (${pct(share)}) were thinking, billed at the output rate.`,
|
|
29801
|
+
evidence: {
|
|
29802
|
+
thinkingTokens: t.thinking,
|
|
29803
|
+
outputTokens: t.output,
|
|
29804
|
+
share: Math.round(share * 100)
|
|
29805
|
+
},
|
|
29806
|
+
recommendation: "Lower the effort level for routine edits and lookups; keep high effort for design and debugging."
|
|
29807
|
+
},
|
|
29808
|
+
ctx.calls.map((c) => c.session_id)
|
|
29809
|
+
);
|
|
29810
|
+
}
|
|
29811
|
+
function detectSubagentCost(ctx) {
|
|
29812
|
+
const a = byAgent(ctx.calls);
|
|
29813
|
+
if (a.delegationShare <= DELEGATION_SHARE) return void 0;
|
|
29814
|
+
const subCost = a.subagents.reduce((s, x) => s + x.totals.costUsd, 0);
|
|
29815
|
+
const subCalls = a.subagents.reduce((s, x) => s + x.totals.calls, 0);
|
|
29816
|
+
return withScope(
|
|
29817
|
+
{
|
|
29818
|
+
id: "subagent-cost",
|
|
29819
|
+
severity: "info",
|
|
29820
|
+
title: "Subagents dominate spend",
|
|
29821
|
+
reason: `${a.subagents.length} subagent${a.subagents.length === 1 ? "" : "s"} made ${subCalls} calls for ${usd(subCost)}, ${pct(a.delegationShare)} of priced cost (main thread ${usd(a.main.costUsd)}).`,
|
|
29822
|
+
evidence: {
|
|
29823
|
+
subagents: a.subagents.length,
|
|
29824
|
+
subagentCalls: subCalls,
|
|
29825
|
+
subagentUsd: round4(subCost),
|
|
29826
|
+
share: Math.round(a.delegationShare * 100)
|
|
29827
|
+
},
|
|
29828
|
+
recommendation: "Check that subagents run on the cheapest model that fits (model: sonnet/haiku) and return summaries, not transcripts."
|
|
29829
|
+
},
|
|
29830
|
+
ctx.calls.filter((c) => c.sidechain).map((c) => c.session_id)
|
|
29831
|
+
);
|
|
29832
|
+
}
|
|
29833
|
+
var DRIVER_ADVICE = {
|
|
29834
|
+
"cache-write": {
|
|
29835
|
+
what: "cache writes",
|
|
29836
|
+
recommendation: "Cache writes dominate: a long idle gap or a cache-busting change (edited system prompt, reordered tools) re-wrote the prefix. Keep the prefix stable, or use the 1h cache deliberately for long pauses."
|
|
29837
|
+
},
|
|
29838
|
+
output: {
|
|
29839
|
+
what: "output",
|
|
29840
|
+
recommendation: "Output dominates: a long answer or heavy thinking. Lower the effort level or ask for a shorter answer; keep high effort for design and debugging."
|
|
29841
|
+
},
|
|
29842
|
+
context: {
|
|
29843
|
+
what: "context",
|
|
29844
|
+
recommendation: "Context dominates: the call re-read a large conversation. Compact, start a fresh session, or move the bulk into a subagent."
|
|
29845
|
+
}
|
|
29846
|
+
};
|
|
29847
|
+
function detectExpensiveTurn(ctx) {
|
|
29848
|
+
const limit = ctx.budgets.turnUsd ?? DEFAULT_TURN_USD;
|
|
29849
|
+
const hits = ctx.calls.filter((c) => costOf(c) > limit).sort((a, b2) => costOf(b2) - costOf(a));
|
|
29850
|
+
if (hits.length === 0) return void 0;
|
|
29851
|
+
const top = hits[0];
|
|
29852
|
+
const price2 = ctx.priceOf(top.model);
|
|
29853
|
+
const b = price2 ? costOfCall(top, price2) : { input: 0, output: 0, cacheWrite: 0, cacheRead: 0, tools: 0, total: costOf(top) };
|
|
29854
|
+
const contextUsd = b.input + b.cacheRead;
|
|
29855
|
+
const driver = b.cacheWrite > b.output && b.cacheWrite > contextUsd ? "cache-write" : b.output > contextUsd ? "output" : "context";
|
|
29856
|
+
const advice = DRIVER_ADVICE[driver];
|
|
29857
|
+
const n = hits.length;
|
|
29858
|
+
return withScope(
|
|
29859
|
+
{
|
|
29860
|
+
id: "expensive-turn",
|
|
29861
|
+
severity: costOf(top) > limit * 3 ? "high" : "warn",
|
|
29862
|
+
title: `${n} call${n === 1 ? "" : "s"} over ${usd(limit)}`,
|
|
29863
|
+
reason: `${n} call${n === 1 ? "" : "s"} cost more than ${usd(limit)} each; the largest was ${usd(costOf(top))} on ${top.model} with ${fmtK(contextOf(top))} context tokens, driven by ${advice.what} (context ${usd(contextUsd)}, output ${usd(b.output)}, cache writes ${usd(b.cacheWrite)}).`,
|
|
29864
|
+
evidence: {
|
|
29865
|
+
calls: n,
|
|
29866
|
+
maxUsd: round4(costOf(top)),
|
|
29867
|
+
model: top.model,
|
|
29868
|
+
contextTokens: contextOf(top),
|
|
29869
|
+
limitUsd: limit,
|
|
29870
|
+
driver,
|
|
29871
|
+
contextUsd: round4(contextUsd),
|
|
29872
|
+
outputUsd: round4(b.output),
|
|
29873
|
+
cacheWriteUsd: round4(b.cacheWrite)
|
|
29874
|
+
},
|
|
29875
|
+
recommendation: advice.recommendation
|
|
29876
|
+
},
|
|
29877
|
+
hits.map((c) => c.session_id)
|
|
29878
|
+
);
|
|
29879
|
+
}
|
|
29880
|
+
function detectErrorLoop(ctx) {
|
|
29881
|
+
const loops = ctx.events.filter((e) => e.category === "iteration_loop");
|
|
29882
|
+
const sessions = new Set(loops.map((e) => e.session_id));
|
|
29883
|
+
let worstTool = "";
|
|
29884
|
+
let worstRun = 0;
|
|
29885
|
+
const byThread = /* @__PURE__ */ new Map();
|
|
29886
|
+
for (const u of ctx.toolUses) {
|
|
29887
|
+
const key = `${u.session_id}\0${u.agent_id ?? ""}`;
|
|
29888
|
+
const list = byThread.get(key);
|
|
29889
|
+
if (list) list.push(u);
|
|
29890
|
+
else byThread.set(key, [u]);
|
|
29891
|
+
}
|
|
29892
|
+
for (const list of byThread.values()) {
|
|
29893
|
+
list.sort((a, b) => a.ts - b.ts);
|
|
29894
|
+
let run = 0;
|
|
29895
|
+
let tool = "";
|
|
29896
|
+
for (const u of list) {
|
|
29897
|
+
if (u.is_error && u.tool === tool) run += 1;
|
|
29898
|
+
else {
|
|
29899
|
+
run = u.is_error ? 1 : 0;
|
|
29900
|
+
tool = u.is_error ? u.tool : "";
|
|
29901
|
+
}
|
|
29902
|
+
if (run >= ERROR_RUN) {
|
|
29903
|
+
sessions.add(u.session_id);
|
|
29904
|
+
if (run > worstRun) {
|
|
29905
|
+
worstRun = run;
|
|
29906
|
+
worstTool = tool;
|
|
29907
|
+
}
|
|
29908
|
+
}
|
|
29909
|
+
}
|
|
29910
|
+
}
|
|
29911
|
+
if (loops.length === 0 && worstRun === 0) return void 0;
|
|
29912
|
+
const parts = [];
|
|
29913
|
+
if (loops.length)
|
|
29914
|
+
parts.push(`${loops.length} repeated-call loop${loops.length === 1 ? "" : "s"} recorded`);
|
|
29915
|
+
if (worstRun) parts.push(`${worstRun} consecutive ${worstTool} failures`);
|
|
29916
|
+
return withScope(
|
|
29917
|
+
{
|
|
29918
|
+
id: "error-loop",
|
|
29919
|
+
severity: "warn",
|
|
29920
|
+
title: "Repeated failures or loops",
|
|
29921
|
+
reason: `${parts.join(" and ")}; every retry re-reads the whole context.`,
|
|
29922
|
+
evidence: { loops: loops.length, longestErrorRun: worstRun, tool: worstTool },
|
|
29923
|
+
recommendation: "After two identical failures stop and change approach (em_recall for an earlier fix, read the error, try a different tool)."
|
|
29924
|
+
},
|
|
29925
|
+
sessions
|
|
29926
|
+
);
|
|
29927
|
+
}
|
|
29928
|
+
function detectBudget(ctx) {
|
|
29929
|
+
const out = [];
|
|
29930
|
+
const { sessionUsd, dailyUsd } = ctx.budgets;
|
|
29931
|
+
if (sessionUsd) {
|
|
29932
|
+
for (const [sid, rows] of groupBySession(ctx.calls)) {
|
|
29933
|
+
const cost = totals(rows).costUsd;
|
|
29934
|
+
if (cost <= sessionUsd) continue;
|
|
29935
|
+
out.push({
|
|
29936
|
+
id: "budget",
|
|
29937
|
+
severity: "high",
|
|
29938
|
+
title: "Session budget exceeded",
|
|
29939
|
+
reason: `Session ${sid.slice(0, 8)} has cost ${usd(cost)}, over the ${usd(sessionUsd)} session budget (${pct(cost / sessionUsd)}).`,
|
|
29940
|
+
evidence: { sessionUsd: round4(cost), budgetUsd: sessionUsd },
|
|
29941
|
+
scope: "session",
|
|
29942
|
+
sessionId: sid
|
|
29943
|
+
});
|
|
29944
|
+
}
|
|
29945
|
+
}
|
|
29946
|
+
if (dailyUsd) {
|
|
29947
|
+
const today = dayKey(ctx.now);
|
|
29948
|
+
const cost = totals(ctx.calls.filter((c) => dayKey(c.ts) === today)).costUsd;
|
|
29949
|
+
if (cost > dailyUsd)
|
|
29950
|
+
out.push({
|
|
29951
|
+
id: "budget",
|
|
29952
|
+
severity: "high",
|
|
29953
|
+
title: "Daily budget exceeded",
|
|
29954
|
+
reason: `Today (${today}) has cost ${usd(cost)}, over the ${usd(dailyUsd)} daily budget (${pct(cost / dailyUsd)}).`,
|
|
29955
|
+
evidence: { day: today, todayUsd: round4(cost), budgetUsd: dailyUsd },
|
|
29956
|
+
scope: "project"
|
|
29957
|
+
});
|
|
29958
|
+
}
|
|
29959
|
+
return out;
|
|
29960
|
+
}
|
|
29961
|
+
function detectUnpricedModel(ctx) {
|
|
29962
|
+
const unpriced = ctx.calls.filter((c) => !c.priced);
|
|
29963
|
+
if (unpriced.length === 0) return void 0;
|
|
29964
|
+
const models = [...new Set(unpriced.map((c) => c.model || "unknown"))];
|
|
29965
|
+
return withScope(
|
|
29966
|
+
{
|
|
29967
|
+
id: "unpriced-model",
|
|
29968
|
+
severity: "info",
|
|
29969
|
+
title: "Some calls are unpriced",
|
|
29970
|
+
reason: `${unpriced.length} of ${ctx.calls.length} calls use ${models.length} model${models.length === 1 ? "" : "s"} without a price (${models.slice(0, 3).join(", ")}), so cost totals undercount.`,
|
|
29971
|
+
evidence: { calls: unpriced.length, models: models.join(", ") },
|
|
29972
|
+
recommendation: 'Add the model to config.usage.pricing, e.g. {"pricing": {"<model substring>": {"input": 3, "output": 15}}} (USD per MTok).'
|
|
29973
|
+
},
|
|
29974
|
+
unpriced.map((c) => c.session_id)
|
|
29975
|
+
);
|
|
29976
|
+
}
|
|
29977
|
+
function detectContextNearLimit(ctx) {
|
|
29978
|
+
let latest;
|
|
29979
|
+
for (const s of ctx.snapshots) if (!latest || s.ts > latest.ts) latest = s;
|
|
29980
|
+
if (!latest || latest.ctx_used_pct == null || latest.ctx_used_pct < NEAR_LIMIT_PCT)
|
|
29981
|
+
return void 0;
|
|
29982
|
+
const p = latest.ctx_used_pct;
|
|
29983
|
+
return {
|
|
29984
|
+
id: "context-near-limit",
|
|
29985
|
+
severity: p >= NEAR_LIMIT_HIGH ? "high" : "warn",
|
|
29986
|
+
title: "Context window nearly full",
|
|
29987
|
+
reason: `The latest statusline reports ${Math.round(p)}% of the ${fmtK(num2(latest.ctx_size) || DEFAULT_WINDOW)}-token window in use; the next compaction is close.`,
|
|
29988
|
+
evidence: { ctxUsedPct: Math.round(p), ctxSize: num2(latest.ctx_size) },
|
|
29989
|
+
recommendation: "Save durable findings with em_remember now, then compact or start a fresh session.",
|
|
29990
|
+
scope: "session",
|
|
29991
|
+
sessionId: latest.session_id
|
|
29992
|
+
};
|
|
29993
|
+
}
|
|
29994
|
+
function findInsight(insights, id) {
|
|
29995
|
+
return insights.find((i) => i.id === id);
|
|
29996
|
+
}
|
|
29997
|
+
var ev2 = (i, key) => {
|
|
29998
|
+
const v = i?.evidence?.[key];
|
|
29999
|
+
return typeof v === "number" ? v : 0;
|
|
30000
|
+
};
|
|
30001
|
+
function scoreContextHealth(ctx, insights) {
|
|
30002
|
+
const reasons = [];
|
|
30003
|
+
let v = 100;
|
|
30004
|
+
const bloat = ev2(findInsight(insights, "context-bloat"), "results");
|
|
30005
|
+
if (bloat) {
|
|
30006
|
+
const d = Math.min(30, bloat * 5);
|
|
30007
|
+
v -= d;
|
|
30008
|
+
reasons.push(`${bloat} oversized tool result${bloat === 1 ? "" : "s"} in context (\u2212${d})`);
|
|
30009
|
+
}
|
|
30010
|
+
let extra = 0;
|
|
30011
|
+
for (const evs of groupBySession(ctx.contextEvents).values())
|
|
30012
|
+
extra += Math.max(0, evs.filter((e) => e.kind === "compact").length - 1);
|
|
30013
|
+
if (extra) {
|
|
30014
|
+
const d = Math.min(30, extra * 10);
|
|
30015
|
+
v -= d;
|
|
30016
|
+
reasons.push(
|
|
30017
|
+
`${extra} compaction${extra === 1 ? "" : "s"} beyond the first per session (\u2212${d})`
|
|
30018
|
+
);
|
|
30019
|
+
}
|
|
30020
|
+
const near = findInsight(insights, "context-near-limit");
|
|
30021
|
+
if (near) {
|
|
30022
|
+
v -= 20;
|
|
30023
|
+
reasons.push(`context ${ev2(near, "ctxUsedPct")}% full (\u221220)`);
|
|
30024
|
+
}
|
|
30025
|
+
const loop = findInsight(insights, "error-loop");
|
|
30026
|
+
if (loop) {
|
|
30027
|
+
const n = ev2(loop, "loops") + (ev2(loop, "longestErrorRun") ? 1 : 0);
|
|
30028
|
+
const d = Math.min(20, n * 10);
|
|
30029
|
+
v -= d;
|
|
30030
|
+
reasons.push(`${n} error loop${n === 1 ? "" : "s"} (\u2212${d})`);
|
|
30031
|
+
}
|
|
30032
|
+
if (!reasons.length)
|
|
30033
|
+
reasons.push(
|
|
30034
|
+
ctx.calls.length ? "no oversized results, compaction churn or loops" : "no usage recorded yet"
|
|
30035
|
+
);
|
|
30036
|
+
return { id: "context-health", value: clamp(v), reasons };
|
|
30037
|
+
}
|
|
30038
|
+
function scoreCostEfficiency(ctx, insights) {
|
|
30039
|
+
const reasons = [];
|
|
30040
|
+
let v = 100;
|
|
30041
|
+
const trivial = findInsight(insights, "opus-for-trivial");
|
|
30042
|
+
if (trivial) {
|
|
30043
|
+
const d = Math.round(ev2(trivial, "share") / 100 * 50);
|
|
30044
|
+
v -= d;
|
|
30045
|
+
reasons.push(`${ev2(trivial, "share")}% of premium-model calls were trivial (\u2212${d})`);
|
|
30046
|
+
}
|
|
30047
|
+
const fast = findInsight(insights, "fast-mode-share");
|
|
30048
|
+
if (fast) {
|
|
30049
|
+
const d = Math.round(ev2(fast, "share") / 100 * 40);
|
|
30050
|
+
v -= d;
|
|
30051
|
+
reasons.push(`fast mode is ${ev2(fast, "share")}% of spend (\u2212${d})`);
|
|
30052
|
+
}
|
|
30053
|
+
const exp = findInsight(insights, "expensive-turn");
|
|
30054
|
+
if (exp) {
|
|
30055
|
+
const n = ev2(exp, "calls");
|
|
30056
|
+
const d = Math.min(30, n * 10);
|
|
30057
|
+
v -= d;
|
|
30058
|
+
reasons.push(`${n} call${n === 1 ? "" : "s"} over ${usd(ev2(exp, "limitUsd"))} (\u2212${d})`);
|
|
30059
|
+
}
|
|
30060
|
+
const unpriced = findInsight(insights, "unpriced-model");
|
|
30061
|
+
if (unpriced && ctx.calls.length) {
|
|
30062
|
+
const share = ev2(unpriced, "calls") / ctx.calls.length;
|
|
30063
|
+
const d = Math.round(share * 20);
|
|
30064
|
+
v -= d;
|
|
30065
|
+
reasons.push(`${pct(share)} of calls unpriced (\u2212${d})`);
|
|
30066
|
+
}
|
|
30067
|
+
const budgets = insights.filter((i) => i.id === "budget").length;
|
|
30068
|
+
if (budgets) {
|
|
30069
|
+
const d = Math.min(40, budgets * 20);
|
|
30070
|
+
v -= d;
|
|
30071
|
+
reasons.push(`${budgets} budget${budgets === 1 ? "" : "s"} exceeded (\u2212${d})`);
|
|
30072
|
+
}
|
|
30073
|
+
if (!reasons.length)
|
|
30074
|
+
reasons.push(
|
|
30075
|
+
ctx.calls.length ? "no premium waste, fast-mode or budget issues" : "no usage recorded yet"
|
|
30076
|
+
);
|
|
30077
|
+
return { id: "cost-efficiency", value: clamp(v), reasons };
|
|
30078
|
+
}
|
|
30079
|
+
function scoreCacheEfficiency(ctx, insights) {
|
|
30080
|
+
const reasons = [];
|
|
30081
|
+
const t = totals(ctx.calls);
|
|
30082
|
+
if (t.calls === 0)
|
|
30083
|
+
return { id: "cache-efficiency", value: 0, reasons: ["no usage recorded yet"] };
|
|
30084
|
+
let v = t.cacheHitRate * 100;
|
|
30085
|
+
reasons.push(`cache hit rate ${pct(t.cacheHitRate)} over ${t.calls} calls`);
|
|
30086
|
+
const cold = ev2(findInsight(insights, "cache-cold"), "restarts");
|
|
30087
|
+
if (cold) {
|
|
30088
|
+
const d = Math.min(25, cold * 5);
|
|
30089
|
+
v -= d;
|
|
30090
|
+
reasons.push(`${cold} cold restart${cold === 1 ? "" : "s"} (\u2212${d})`);
|
|
30091
|
+
}
|
|
30092
|
+
const thrash = ev2(findInsight(insights, "cache-thrash"), "runs");
|
|
30093
|
+
if (thrash) {
|
|
30094
|
+
const d = Math.min(30, thrash * 10);
|
|
30095
|
+
v -= d;
|
|
30096
|
+
reasons.push(`${thrash} cache-rewrite run${thrash === 1 ? "" : "s"} (\u2212${d})`);
|
|
30097
|
+
}
|
|
30098
|
+
return { id: "cache-efficiency", value: clamp(v), reasons };
|
|
30099
|
+
}
|
|
30100
|
+
function scoreDelegation(ctx) {
|
|
30101
|
+
const reasons = [];
|
|
30102
|
+
const a = byAgent(ctx.calls);
|
|
30103
|
+
if (ctx.calls.length === 0)
|
|
30104
|
+
return { id: "delegation", value: 0, reasons: ["no usage recorded yet"] };
|
|
30105
|
+
if (a.subagents.length === 0)
|
|
30106
|
+
return {
|
|
30107
|
+
id: "delegation",
|
|
30108
|
+
value: 60,
|
|
30109
|
+
reasons: ["no subagent calls: the main thread carries every tool result itself"]
|
|
30110
|
+
};
|
|
30111
|
+
let v;
|
|
30112
|
+
if (a.delegationShare <= DELEGATION_SHARE) {
|
|
30113
|
+
v = 100;
|
|
30114
|
+
reasons.push(
|
|
30115
|
+
`subagents are ${pct(a.delegationShare)} of cost across ${a.subagents.length} agent${a.subagents.length === 1 ? "" : "s"}`
|
|
30116
|
+
);
|
|
30117
|
+
} else {
|
|
30118
|
+
v = Math.max(20, 100 - 150 * (a.delegationShare - DELEGATION_SHARE));
|
|
30119
|
+
reasons.push(
|
|
30120
|
+
`subagents are ${pct(a.delegationShare)} of cost, above ${pct(DELEGATION_SHARE)} (\u2212${100 - Math.round(v)})`
|
|
30121
|
+
);
|
|
30122
|
+
}
|
|
30123
|
+
const sub = totals(ctx.calls.filter((c) => c.sidechain));
|
|
30124
|
+
if (sub.calls && sub.cacheHitRate < 0.5) {
|
|
30125
|
+
v -= 15;
|
|
30126
|
+
reasons.push(`subagent cache hit rate ${pct(sub.cacheHitRate)} (\u221215)`);
|
|
30127
|
+
}
|
|
30128
|
+
return { id: "delegation", value: clamp(v), reasons };
|
|
30129
|
+
}
|
|
30130
|
+
function detectInsights(input2) {
|
|
30131
|
+
const calls = input2.calls.filter(isRealCall);
|
|
30132
|
+
const compactionsBySession = /* @__PURE__ */ new Map();
|
|
30133
|
+
for (const e of input2.contextEvents) {
|
|
30134
|
+
const list = compactionsBySession.get(e.session_id);
|
|
30135
|
+
if (list) list.push(e.ts);
|
|
30136
|
+
else compactionsBySession.set(e.session_id, [e.ts]);
|
|
30137
|
+
}
|
|
30138
|
+
for (const list of compactionsBySession.values()) list.sort((a, b) => a - b);
|
|
30139
|
+
const callTs = /* @__PURE__ */ new Map();
|
|
30140
|
+
for (const [sid, rows] of groupBySession(calls))
|
|
30141
|
+
callTs.set(
|
|
30142
|
+
sid,
|
|
30143
|
+
rows.map((c) => c.ts).sort((a, b) => a - b)
|
|
30144
|
+
);
|
|
30145
|
+
const priceCache = /* @__PURE__ */ new Map();
|
|
30146
|
+
const overrides = input2.overrides;
|
|
30147
|
+
const ctx = {
|
|
30148
|
+
calls,
|
|
30149
|
+
threads: threads(calls),
|
|
30150
|
+
toolUses: input2.toolUses,
|
|
30151
|
+
contextEvents: input2.contextEvents,
|
|
30152
|
+
stats: input2.stats,
|
|
30153
|
+
events: input2.events,
|
|
30154
|
+
snapshots: input2.snapshots,
|
|
30155
|
+
now: input2.now ?? Date.now(),
|
|
30156
|
+
budgets: input2.budgets ?? {},
|
|
30157
|
+
overrides,
|
|
30158
|
+
priceOf: (model) => {
|
|
30159
|
+
if (!priceCache.has(model)) priceCache.set(model, resolvePrice(model, overrides));
|
|
30160
|
+
return priceCache.get(model);
|
|
30161
|
+
},
|
|
30162
|
+
compactionsBySession,
|
|
30163
|
+
laterCalls: (sid, ts) => (callTs.get(sid) ?? []).filter((t) => t > ts).length
|
|
30164
|
+
};
|
|
30165
|
+
const insights = [];
|
|
30166
|
+
const push = (i) => {
|
|
30167
|
+
if (i) insights.push(i);
|
|
30168
|
+
};
|
|
30169
|
+
push(detectCacheThrash(ctx));
|
|
30170
|
+
push(detectCacheCold(ctx));
|
|
30171
|
+
push(detectContextBloat(ctx));
|
|
30172
|
+
push(detectCompactionChurn(ctx));
|
|
30173
|
+
push(detectOpusForTrivial(ctx));
|
|
30174
|
+
push(detectFastModeShare(ctx));
|
|
30175
|
+
push(detectThinkingShare(ctx));
|
|
30176
|
+
push(detectSubagentCost(ctx));
|
|
30177
|
+
push(detectExpensiveTurn(ctx));
|
|
30178
|
+
push(detectErrorLoop(ctx));
|
|
30179
|
+
for (const b of detectBudget(ctx)) push(b);
|
|
30180
|
+
push(detectUnpricedModel(ctx));
|
|
30181
|
+
push(detectContextNearLimit(ctx));
|
|
30182
|
+
insights.sort(
|
|
30183
|
+
(a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || (b.estimatedSavingsUsd ?? 0) - (a.estimatedSavingsUsd ?? 0) || a.id.localeCompare(b.id)
|
|
30184
|
+
);
|
|
30185
|
+
const scores = [
|
|
30186
|
+
scoreContextHealth(ctx, insights),
|
|
30187
|
+
scoreCostEfficiency(ctx, insights),
|
|
30188
|
+
scoreCacheEfficiency(ctx, insights),
|
|
30189
|
+
scoreDelegation(ctx)
|
|
30190
|
+
];
|
|
30191
|
+
return { insights, scores };
|
|
30192
|
+
}
|
|
30193
|
+
var INSIGHT_EVENT_CATEGORIES = ["iteration_loop", "error", "tool_call"];
|
|
30194
|
+
function loadInsightInput(db, ctx, opts = {}) {
|
|
30195
|
+
const filter = { sessionId: opts.sessionId, since: opts.since };
|
|
30196
|
+
const usage = ctx.config.usage;
|
|
30197
|
+
return {
|
|
30198
|
+
calls: db.apiCalls(filter),
|
|
30199
|
+
toolUses: db.toolUses(filter),
|
|
30200
|
+
contextEvents: db.contextEvents(filter),
|
|
30201
|
+
snapshots: db.statuslineSnapshots(filter),
|
|
30202
|
+
stats: opts.sessionId ? db.statsBySession(opts.sessionId) : db.statsAll(),
|
|
30203
|
+
events: opts.sessionId ? db.eventsBySession(opts.sessionId, { categories: INSIGHT_EVENT_CATEGORIES }) : db.eventsRecent(500, INSIGHT_EVENT_CATEGORIES).filter((e) => !opts.since || e.ts >= opts.since),
|
|
30204
|
+
now: opts.now ?? Date.now(),
|
|
30205
|
+
budgets: usage.budgets,
|
|
30206
|
+
overrides: usage.pricing
|
|
30207
|
+
};
|
|
30208
|
+
}
|
|
30209
|
+
|
|
30210
|
+
// src/usage/brief.ts
|
|
30211
|
+
var DAY_MS = 864e5;
|
|
30212
|
+
function todaysCalls(db, now) {
|
|
30213
|
+
const today = dayKey(now);
|
|
30214
|
+
return db.apiCalls({ since: now - 1.5 * DAY_MS }).filter((c) => dayKey(c.ts) === today);
|
|
30215
|
+
}
|
|
30216
|
+
function usageBrief(db, ctx, opts = {}) {
|
|
30217
|
+
const now = opts.now ?? Date.now();
|
|
30218
|
+
const today = totals(todaysCalls(db, now));
|
|
30219
|
+
const session = totals(opts.sessionId ? db.apiCalls({ sessionId: opts.sessionId }) : []);
|
|
30220
|
+
const cacheHitRate = today.calls ? today.cacheHitRate : session.cacheHitRate;
|
|
30221
|
+
const out = {
|
|
30222
|
+
line: "",
|
|
30223
|
+
sessionUsd: session.costUsd,
|
|
30224
|
+
todayUsd: today.costUsd,
|
|
30225
|
+
cacheHitRate
|
|
30226
|
+
};
|
|
30227
|
+
if (today.calls === 0 && session.calls === 0) return out;
|
|
30228
|
+
const parts = [
|
|
30229
|
+
`today ${formatUsd(today.costUsd)} (${today.calls} call${today.calls === 1 ? "" : "s"}, ${Math.round(cacheHitRate * 100)}% cache hits)`
|
|
30230
|
+
];
|
|
30231
|
+
if (session.calls > 0) parts.push(`session ${formatUsd(session.costUsd)}`);
|
|
30232
|
+
try {
|
|
30233
|
+
const scope = session.calls > 0 ? { sessionId: opts.sessionId } : { since: now - 1.5 * DAY_MS };
|
|
30234
|
+
const { insights } = detectInsights(loadInsightInput(db, ctx, { ...scope, now }));
|
|
30235
|
+
const top = insights[0];
|
|
30236
|
+
if (top) {
|
|
30237
|
+
out.topInsight = top;
|
|
30238
|
+
const saving = top.estimatedSavingsUsd && top.estimatedSavingsUsd >= 5e-3 ? ` (~${formatUsd(top.estimatedSavingsUsd)})` : "";
|
|
30239
|
+
parts.push(`top insight: ${top.title.charAt(0).toLowerCase()}${top.title.slice(1)}${saving}`);
|
|
30240
|
+
}
|
|
30241
|
+
} catch {
|
|
30242
|
+
}
|
|
30243
|
+
out.line = `usage: ${parts.join(" \xB7 ")}`;
|
|
30244
|
+
return out;
|
|
30245
|
+
}
|
|
30246
|
+
function budgetWarnings(db, ctx, sessionId, now = Date.now()) {
|
|
30247
|
+
const { sessionUsd, dailyUsd } = ctx.config.usage.budgets;
|
|
30248
|
+
if (!sessionUsd && !dailyUsd) return [];
|
|
30249
|
+
const out = [];
|
|
30250
|
+
if (sessionUsd) {
|
|
30251
|
+
const key = `usage_alert:session:${sessionId}`;
|
|
30252
|
+
const cost = totals(db.apiCalls({ sessionId })).costUsd;
|
|
30253
|
+
if (cost >= sessionUsd && !db.metaGet(key)) {
|
|
30254
|
+
db.metaSet(key, String(now));
|
|
30255
|
+
out.push(
|
|
30256
|
+
`eminiar usage \u2014 this session has cost ${formatUsd(cost)}, over its ${formatUsd(sessionUsd)} budget (config.usage.budgets.sessionUsd). Keep raw output out of context, delegate bulk work to a cheaper model, or start a fresh session.`
|
|
30257
|
+
);
|
|
30258
|
+
}
|
|
30259
|
+
}
|
|
30260
|
+
if (dailyUsd) {
|
|
30261
|
+
const day = dayKey(now);
|
|
30262
|
+
const key = `usage_alert:daily:${day}`;
|
|
30263
|
+
const cost = totals(todaysCalls(db, now)).costUsd;
|
|
30264
|
+
if (cost >= dailyUsd && !db.metaGet(key)) {
|
|
30265
|
+
db.metaSet(key, String(now));
|
|
30266
|
+
out.push(
|
|
30267
|
+
`eminiar usage \u2014 today's sessions have cost ${formatUsd(cost)}, over the ${formatUsd(dailyUsd)} daily budget (config.usage.budgets.dailyUsd). em_usage {view:"insights"} shows where it went.`
|
|
30268
|
+
);
|
|
30269
|
+
}
|
|
30270
|
+
}
|
|
30271
|
+
return out;
|
|
30272
|
+
}
|
|
30273
|
+
|
|
30274
|
+
// src/usage/ingest.ts
|
|
30275
|
+
init_esm_shims();
|
|
30276
|
+
import { createHash as createHash4 } from "crypto";
|
|
30277
|
+
import { closeSync, existsSync as existsSync3, openSync, readdirSync, readSync, statSync as statSync2 } from "fs";
|
|
30278
|
+
import { homedir as homedir2 } from "os";
|
|
30279
|
+
import { basename, dirname as dirname2, join as join4, resolve as resolve3, sep } from "path";
|
|
30280
|
+
|
|
30281
|
+
// src/usage/transcript.ts
|
|
30282
|
+
init_esm_shims();
|
|
30283
|
+
var NON_TEXT_PART_CHARS = 6400;
|
|
30284
|
+
var DENIAL_RE = /^\s*PreToolUse:|hook error|\bdenied\b/i;
|
|
30285
|
+
function isObj(v) {
|
|
30286
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
30287
|
+
}
|
|
30288
|
+
function str(v) {
|
|
30289
|
+
return typeof v === "string" && v.length ? v : null;
|
|
30290
|
+
}
|
|
30291
|
+
function num3(v) {
|
|
30292
|
+
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
30293
|
+
}
|
|
30294
|
+
function numOrNull(v) {
|
|
30295
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
30296
|
+
}
|
|
30297
|
+
function tsOf(e) {
|
|
30298
|
+
const t = e.timestamp;
|
|
30299
|
+
if (typeof t === "string") {
|
|
30300
|
+
const ms = Date.parse(t);
|
|
30301
|
+
if (Number.isFinite(ms) && ms > 0) return ms;
|
|
30302
|
+
return void 0;
|
|
30303
|
+
}
|
|
30304
|
+
if (typeof t === "number" && Number.isFinite(t) && t > 0) return t < 1e12 ? t * 1e3 : t;
|
|
30305
|
+
return void 0;
|
|
30306
|
+
}
|
|
30307
|
+
function resultChars(content) {
|
|
30308
|
+
if (typeof content === "string") return content.length;
|
|
30309
|
+
if (Array.isArray(content)) {
|
|
30310
|
+
let n = 0;
|
|
30311
|
+
for (const part of content) {
|
|
30312
|
+
if (!isObj(part)) {
|
|
30313
|
+
n += safeJsonLength(part);
|
|
30314
|
+
continue;
|
|
30315
|
+
}
|
|
30316
|
+
const kind = str(part.type);
|
|
30317
|
+
if (kind === "text" || typeof part.text === "string") n += str(part.text)?.length ?? 0;
|
|
30318
|
+
else if (kind === "image" || kind === "document" || kind === "tool_reference")
|
|
30319
|
+
n += NON_TEXT_PART_CHARS;
|
|
30320
|
+
else n += safeJsonLength(part);
|
|
30321
|
+
}
|
|
30322
|
+
return n;
|
|
30323
|
+
}
|
|
30324
|
+
if (content === void 0 || content === null) return 0;
|
|
30325
|
+
return safeJsonLength(content);
|
|
30326
|
+
}
|
|
30327
|
+
function resultText(content) {
|
|
30328
|
+
if (typeof content === "string") return content;
|
|
30329
|
+
if (Array.isArray(content)) {
|
|
30330
|
+
const parts = [];
|
|
30331
|
+
for (const part of content)
|
|
30332
|
+
if (isObj(part) && typeof part.text === "string") parts.push(part.text);
|
|
30333
|
+
return parts.join("\n");
|
|
30334
|
+
}
|
|
30335
|
+
return "";
|
|
30336
|
+
}
|
|
30337
|
+
function safeJsonLength(v) {
|
|
30338
|
+
try {
|
|
30339
|
+
return JSON.stringify(v)?.length ?? 0;
|
|
30340
|
+
} catch {
|
|
30341
|
+
return 0;
|
|
30342
|
+
}
|
|
30343
|
+
}
|
|
30344
|
+
function parseTranscriptLines(lines, ctx) {
|
|
30345
|
+
const calls = /* @__PURE__ */ new Map();
|
|
30346
|
+
const toolUses = /* @__PURE__ */ new Map();
|
|
30347
|
+
const contextEvents = [];
|
|
30348
|
+
let lastTs;
|
|
30349
|
+
let prevTs;
|
|
30350
|
+
const fallbackTs = typeof ctx.fallbackTs === "number" && Number.isFinite(ctx.fallbackTs) && ctx.fallbackTs > 0 ? ctx.fallbackTs : Date.now();
|
|
30351
|
+
let denied = 0;
|
|
30352
|
+
for (const raw of lines) {
|
|
30353
|
+
const line2 = raw.trim();
|
|
30354
|
+
if (!line2) continue;
|
|
30355
|
+
let e;
|
|
30356
|
+
try {
|
|
30357
|
+
e = JSON.parse(line2);
|
|
30358
|
+
} catch {
|
|
30359
|
+
continue;
|
|
30360
|
+
}
|
|
30361
|
+
if (!isObj(e)) continue;
|
|
30362
|
+
const type = str(e.type);
|
|
30363
|
+
if (!type) continue;
|
|
30364
|
+
const sessionId = str(e.sessionId) ?? ctx.sessionId;
|
|
30365
|
+
const agentId = str(e.agentId);
|
|
30366
|
+
const sidechain = e.isSidechain === true ? 1 : 0;
|
|
30367
|
+
const real = tsOf(e);
|
|
30368
|
+
const ts = real ?? prevTs ?? fallbackTs;
|
|
30369
|
+
prevTs = ts;
|
|
30370
|
+
if (real !== void 0) lastTs = lastTs === void 0 ? real : Math.max(lastTs, real);
|
|
30371
|
+
if (type === "assistant") {
|
|
30372
|
+
const msg = isObj(e.message) ? e.message : void 0;
|
|
30373
|
+
if (!msg) continue;
|
|
30374
|
+
const messageId = str(msg.id);
|
|
30375
|
+
const requestId = str(e.requestId) ?? messageId;
|
|
30376
|
+
if (Array.isArray(msg.content)) {
|
|
30377
|
+
for (const block of msg.content) {
|
|
30378
|
+
if (!isObj(block) || block.type !== "tool_use") continue;
|
|
30379
|
+
const id = str(block.id);
|
|
30380
|
+
if (!id) continue;
|
|
30381
|
+
const prev2 = toolUses.get(id);
|
|
30382
|
+
toolUses.set(id, {
|
|
30383
|
+
tool_use_id: id,
|
|
30384
|
+
session_id: prev2?.session_id ?? sessionId,
|
|
30385
|
+
agent_id: prev2?.agent_id ?? agentId,
|
|
30386
|
+
ts: prev2?.ts ?? ts,
|
|
30387
|
+
tool: str(block.name) ?? prev2?.tool ?? "",
|
|
30388
|
+
input_chars: block.input === void 0 ? 0 : safeJsonLength(block.input),
|
|
30389
|
+
result_chars: prev2?.result_chars ?? null,
|
|
30390
|
+
is_error: prev2?.is_error ?? 0,
|
|
30391
|
+
request_id: requestId ?? prev2?.request_id ?? null
|
|
30392
|
+
});
|
|
30393
|
+
}
|
|
30394
|
+
}
|
|
30395
|
+
const usage = isObj(msg.usage) ? msg.usage : void 0;
|
|
30396
|
+
if (!usage || !messageId || !requestId) continue;
|
|
30397
|
+
const model = str(msg.model) ?? "unknown";
|
|
30398
|
+
const cc = isObj(usage.cache_creation) ? usage.cache_creation : void 0;
|
|
30399
|
+
const create5m = cc ? num3(cc.ephemeral_5m_input_tokens) : num3(usage.cache_creation_input_tokens);
|
|
30400
|
+
const create1h = cc ? num3(cc.ephemeral_1h_input_tokens) : 0;
|
|
30401
|
+
const details = isObj(usage.output_tokens_details) ? usage.output_tokens_details : void 0;
|
|
30402
|
+
const server = isObj(usage.server_tool_use) ? usage.server_tool_use : void 0;
|
|
30403
|
+
const key = `${requestId}\0${messageId}`;
|
|
30404
|
+
const prev = calls.get(key);
|
|
30405
|
+
calls.set(key, {
|
|
30406
|
+
request_id: requestId,
|
|
30407
|
+
message_id: messageId,
|
|
30408
|
+
session_id: prev?.session_id ?? sessionId,
|
|
30409
|
+
agent_id: agentId ?? prev?.agent_id ?? null,
|
|
30410
|
+
sidechain: prev?.sidechain || sidechain,
|
|
30411
|
+
ts: prev?.ts ?? ts,
|
|
30412
|
+
model,
|
|
30413
|
+
input_tokens: num3(usage.input_tokens),
|
|
30414
|
+
output_tokens: num3(usage.output_tokens),
|
|
30415
|
+
cache_create_5m: create5m,
|
|
30416
|
+
cache_create_1h: create1h,
|
|
30417
|
+
cache_read: num3(usage.cache_read_input_tokens),
|
|
30418
|
+
thinking_tokens: details ? num3(details.thinking_tokens) : 0,
|
|
30419
|
+
web_searches: server ? num3(server.web_search_requests) : 0,
|
|
30420
|
+
web_fetches: server ? num3(server.web_fetch_requests) : 0,
|
|
30421
|
+
service_tier: str(usage.service_tier),
|
|
30422
|
+
speed: str(usage.speed),
|
|
30423
|
+
stop_reason: str(msg.stop_reason) ?? prev?.stop_reason ?? null,
|
|
30424
|
+
prompt_id: str(e.promptId) ?? prev?.prompt_id ?? null,
|
|
30425
|
+
git_branch: str(e.gitBranch) ?? prev?.git_branch ?? null,
|
|
30426
|
+
cwd: str(e.cwd) ?? prev?.cwd ?? null,
|
|
30427
|
+
effort: str(e.effort) ?? prev?.effort ?? null,
|
|
30428
|
+
version: str(e.version) ?? prev?.version ?? null,
|
|
30429
|
+
entrypoint: str(e.entrypoint) ?? prev?.entrypoint ?? null
|
|
30430
|
+
});
|
|
30431
|
+
continue;
|
|
30432
|
+
}
|
|
30433
|
+
if (type === "user") {
|
|
30434
|
+
const msg = isObj(e.message) ? e.message : void 0;
|
|
30435
|
+
if (!msg || !Array.isArray(msg.content)) continue;
|
|
30436
|
+
for (const block of msg.content) {
|
|
30437
|
+
if (!isObj(block) || block.type !== "tool_result") continue;
|
|
30438
|
+
const id = str(block.tool_use_id);
|
|
30439
|
+
if (!id) continue;
|
|
30440
|
+
const prev = toolUses.get(id);
|
|
30441
|
+
let isError = prev?.is_error ?? 0;
|
|
30442
|
+
if (block.is_error === true) {
|
|
30443
|
+
if (DENIAL_RE.test(resultText(block.content))) denied += 1;
|
|
30444
|
+
else isError = 1;
|
|
30445
|
+
}
|
|
30446
|
+
toolUses.set(id, {
|
|
30447
|
+
tool_use_id: id,
|
|
30448
|
+
session_id: prev?.session_id ?? sessionId,
|
|
30449
|
+
agent_id: prev?.agent_id ?? agentId,
|
|
30450
|
+
ts: prev?.ts ?? ts,
|
|
30451
|
+
tool: prev?.tool ?? "",
|
|
30452
|
+
input_chars: prev?.input_chars ?? 0,
|
|
30453
|
+
result_chars: resultChars(block.content),
|
|
30454
|
+
is_error: isError,
|
|
30455
|
+
request_id: prev?.request_id ?? null
|
|
30456
|
+
});
|
|
30457
|
+
}
|
|
30458
|
+
continue;
|
|
30459
|
+
}
|
|
30460
|
+
if (type === "system") {
|
|
30461
|
+
const subtype = str(e.subtype);
|
|
30462
|
+
if (subtype === "compact_boundary") {
|
|
30463
|
+
const meta3 = isObj(e.compactMetadata) ? e.compactMetadata : void 0;
|
|
30464
|
+
contextEvents.push({
|
|
30465
|
+
session_id: sessionId,
|
|
30466
|
+
ts,
|
|
30467
|
+
kind: "compact",
|
|
30468
|
+
trigger: meta3 ? str(meta3.trigger) : null,
|
|
30469
|
+
pre_tokens: meta3 ? numOrNull(meta3.preTokens) : null,
|
|
30470
|
+
post_tokens: meta3 ? numOrNull(meta3.postTokens) : null
|
|
30471
|
+
});
|
|
30472
|
+
} else if (subtype === "local_command") {
|
|
30473
|
+
const run = isObj(e.commandRun) ? e.commandRun : void 0;
|
|
30474
|
+
if (run && str(run.command) === "clear") {
|
|
30475
|
+
contextEvents.push({
|
|
30476
|
+
session_id: sessionId,
|
|
30477
|
+
ts,
|
|
30478
|
+
kind: "clear",
|
|
30479
|
+
trigger: "manual",
|
|
30480
|
+
pre_tokens: null,
|
|
30481
|
+
post_tokens: null
|
|
30482
|
+
});
|
|
30483
|
+
}
|
|
30484
|
+
}
|
|
30485
|
+
}
|
|
30486
|
+
}
|
|
30487
|
+
const out = {
|
|
30488
|
+
calls: [...calls.values()],
|
|
30489
|
+
toolUses: [...toolUses.values()],
|
|
30490
|
+
contextEvents,
|
|
30491
|
+
denied
|
|
30492
|
+
};
|
|
30493
|
+
if (lastTs !== void 0) out.lastTs = lastTs;
|
|
30494
|
+
return out;
|
|
30495
|
+
}
|
|
30496
|
+
|
|
30497
|
+
// src/usage/ingest.ts
|
|
30498
|
+
var ZERO = { calls: 0, toolUses: 0, contextEvents: 0 };
|
|
30499
|
+
var HEAD_BYTES = 512;
|
|
30500
|
+
var SNAPSHOT_MAX_AGE_MS = 30 * 24 * 60 * 6e4;
|
|
30501
|
+
var SNAPSHOT_KEEP_PER_SESSION = 500;
|
|
30502
|
+
var SESSION_ID_RE = /^[A-Za-z0-9_-]{1,80}$/;
|
|
30503
|
+
function isValidSessionId(id) {
|
|
30504
|
+
return SESSION_ID_RE.test(id);
|
|
30505
|
+
}
|
|
30506
|
+
function claudeDir() {
|
|
30507
|
+
return process.env.EMINIAR_CLAUDE_DIR || join4(homedir2(), ".claude");
|
|
30508
|
+
}
|
|
30509
|
+
function insideDir(path2, dir) {
|
|
30510
|
+
const d = resolve3(dir);
|
|
30511
|
+
const p = resolve3(path2);
|
|
30512
|
+
return p === d || p.startsWith(d.endsWith(sep) ? d : d + sep);
|
|
30513
|
+
}
|
|
30514
|
+
function isTranscriptFile(path2) {
|
|
30515
|
+
if (!path2.endsWith(".jsonl")) return false;
|
|
30516
|
+
try {
|
|
30517
|
+
return statSync2(path2).isFile();
|
|
30518
|
+
} catch {
|
|
30519
|
+
return false;
|
|
30520
|
+
}
|
|
30521
|
+
}
|
|
30522
|
+
function transcriptSlug(root) {
|
|
30523
|
+
return root.replace(/[^A-Za-z0-9]/g, "-");
|
|
30524
|
+
}
|
|
30525
|
+
function transcriptDirFor(root) {
|
|
30526
|
+
return join4(claudeDir(), "projects", transcriptSlug(root));
|
|
30527
|
+
}
|
|
30528
|
+
function usageEnabled(ctx) {
|
|
30529
|
+
return ctx.config.usage.enabled !== false;
|
|
30530
|
+
}
|
|
30531
|
+
function priceCalls(calls, overrides) {
|
|
30532
|
+
return calls.map((c) => {
|
|
30533
|
+
const price2 = resolvePrice(c.model, overrides);
|
|
30534
|
+
if (!price2) return { ...c, cost_usd: null, priced: 0 };
|
|
30535
|
+
return { ...c, cost_usd: costOfCall(c, price2).total, priced: 1 };
|
|
30536
|
+
});
|
|
30537
|
+
}
|
|
30538
|
+
function readCompleteLines(path2, offset, size) {
|
|
30539
|
+
const length = size - offset;
|
|
30540
|
+
if (length <= 0) return { lines: [], nextOffset: offset };
|
|
30541
|
+
const buf = Buffer.alloc(length);
|
|
30542
|
+
const fd = openSync(path2, "r");
|
|
30543
|
+
let read = 0;
|
|
30544
|
+
try {
|
|
30545
|
+
while (read < length) {
|
|
30546
|
+
const n = readSync(fd, buf, read, length - read, offset + read);
|
|
30547
|
+
if (n <= 0) break;
|
|
30548
|
+
read += n;
|
|
30549
|
+
}
|
|
30550
|
+
} finally {
|
|
30551
|
+
closeSync(fd);
|
|
30552
|
+
}
|
|
30553
|
+
const end = buf.lastIndexOf(10, read - 1);
|
|
30554
|
+
if (end < 0) return { lines: [], nextOffset: offset };
|
|
30555
|
+
const text = buf.toString("utf8", 0, end + 1);
|
|
30556
|
+
return { lines: text.split("\n"), nextOffset: offset + end + 1 };
|
|
30557
|
+
}
|
|
30558
|
+
function headHash(path2, length) {
|
|
30559
|
+
if (length <= 0) return null;
|
|
30560
|
+
const buf = Buffer.alloc(length);
|
|
30561
|
+
let read = 0;
|
|
30562
|
+
const fd = openSync(path2, "r");
|
|
30563
|
+
try {
|
|
30564
|
+
while (read < length) {
|
|
30565
|
+
const n = readSync(fd, buf, read, length - read, read);
|
|
30566
|
+
if (n <= 0) break;
|
|
30567
|
+
read += n;
|
|
30568
|
+
}
|
|
30569
|
+
} finally {
|
|
30570
|
+
closeSync(fd);
|
|
30571
|
+
}
|
|
30572
|
+
return createHash4("sha1").update(buf.subarray(0, read)).digest("hex");
|
|
30573
|
+
}
|
|
30574
|
+
function byteAt(path2, pos) {
|
|
30575
|
+
if (pos < 0) return void 0;
|
|
30576
|
+
const buf = Buffer.alloc(1);
|
|
30577
|
+
const fd = openSync(path2, "r");
|
|
30578
|
+
try {
|
|
30579
|
+
return readSync(fd, buf, 0, 1, pos) === 1 ? buf[0] : void 0;
|
|
30580
|
+
} finally {
|
|
30581
|
+
closeSync(fd);
|
|
30582
|
+
}
|
|
30583
|
+
}
|
|
30584
|
+
function sessionIdFromName(path2) {
|
|
30585
|
+
const dir = dirname2(path2);
|
|
30586
|
+
if (basename(dir) === "subagents") return basename(dirname2(dir));
|
|
30587
|
+
const name = basename(path2, ".jsonl");
|
|
30588
|
+
return name.startsWith("agent-") ? void 0 : name;
|
|
30589
|
+
}
|
|
30590
|
+
function contextEventExists(db, e) {
|
|
30591
|
+
return db.contextEvents({ sessionId: e.session_id, since: e.ts, until: e.ts + 1 }).some((x) => x.kind === e.kind);
|
|
30592
|
+
}
|
|
30593
|
+
function resumeOffset(path2, prev, size, mtime) {
|
|
30594
|
+
if (!prev || prev.offset <= 0) return 0;
|
|
30595
|
+
if (size < prev.offset) return 0;
|
|
30596
|
+
if (prev.head_hash) {
|
|
30597
|
+
const want = Math.min(HEAD_BYTES, prev.size);
|
|
30598
|
+
try {
|
|
30599
|
+
if (headHash(path2, want) !== prev.head_hash) return 0;
|
|
30600
|
+
} catch {
|
|
30601
|
+
return 0;
|
|
30602
|
+
}
|
|
30603
|
+
}
|
|
30604
|
+
if (prev.mtime !== mtime) {
|
|
30605
|
+
try {
|
|
30606
|
+
if (byteAt(path2, prev.offset - 1) !== 10) return 0;
|
|
30607
|
+
} catch {
|
|
30608
|
+
return 0;
|
|
30609
|
+
}
|
|
30610
|
+
}
|
|
30611
|
+
return prev.offset;
|
|
30612
|
+
}
|
|
30613
|
+
function ingestTranscript(db, path2, opts = {}) {
|
|
30614
|
+
if (!path2.endsWith(".jsonl")) return ZERO;
|
|
30615
|
+
let size;
|
|
30616
|
+
let mtime;
|
|
30617
|
+
try {
|
|
30618
|
+
const st = statSync2(path2);
|
|
30619
|
+
if (!st.isFile()) return ZERO;
|
|
30620
|
+
size = st.size;
|
|
30621
|
+
mtime = Math.floor(st.mtimeMs);
|
|
30622
|
+
} catch {
|
|
30623
|
+
return ZERO;
|
|
30624
|
+
}
|
|
30625
|
+
const prev = db.transcriptFileGet(path2);
|
|
30626
|
+
const offset = resumeOffset(path2, prev, size, mtime);
|
|
30627
|
+
const sessionHint = opts.sessionId ?? prev?.session_id ?? sessionIdFromName(path2);
|
|
30628
|
+
if (offset === size) {
|
|
30629
|
+
if (!prev || prev.size !== size || prev.mtime !== mtime || sessionHint && !prev.session_id)
|
|
30630
|
+
db.transcriptFileSet({
|
|
30631
|
+
path: path2,
|
|
30632
|
+
session_id: sessionHint ?? null,
|
|
30633
|
+
offset,
|
|
30634
|
+
size,
|
|
30635
|
+
mtime,
|
|
30636
|
+
head_hash: prev?.size === size ? prev.head_hash ?? null : fingerprint(path2, size),
|
|
30637
|
+
ingested_at: Date.now()
|
|
30638
|
+
});
|
|
30639
|
+
return ZERO;
|
|
30640
|
+
}
|
|
30641
|
+
const { lines, nextOffset } = readCompleteLines(path2, offset, size);
|
|
30642
|
+
const parsed = parseTranscriptLines(lines, {
|
|
30643
|
+
sessionId: sessionHint ?? "unknown",
|
|
30644
|
+
path: path2,
|
|
30645
|
+
fallbackTs: mtime
|
|
30646
|
+
});
|
|
30647
|
+
const calls = priceCalls(parsed.calls, opts.overrides);
|
|
30648
|
+
const counts = { calls: 0, toolUses: 0, contextEvents: 0 };
|
|
30649
|
+
const sessionSeen = parsed.calls[0]?.session_id ?? parsed.toolUses[0]?.session_id;
|
|
30650
|
+
const head = fingerprint(path2, size);
|
|
30651
|
+
db.transaction(() => {
|
|
30652
|
+
counts.calls = db.apiCallsUpsert(calls);
|
|
30653
|
+
counts.toolUses = db.toolUsesUpsert(parsed.toolUses);
|
|
30654
|
+
for (const e of parsed.contextEvents) {
|
|
30655
|
+
if (contextEventExists(db, e)) continue;
|
|
30656
|
+
db.contextEventInsert(e);
|
|
30657
|
+
counts.contextEvents++;
|
|
30658
|
+
}
|
|
30659
|
+
db.transcriptFileSet({
|
|
30660
|
+
path: path2,
|
|
30661
|
+
session_id: sessionHint ?? sessionSeen ?? null,
|
|
30662
|
+
offset: nextOffset,
|
|
30663
|
+
size,
|
|
30664
|
+
mtime,
|
|
30665
|
+
head_hash: head,
|
|
30666
|
+
ingested_at: Date.now()
|
|
30667
|
+
});
|
|
30668
|
+
});
|
|
30669
|
+
return counts;
|
|
30670
|
+
}
|
|
30671
|
+
function fingerprint(path2, size) {
|
|
30672
|
+
try {
|
|
30673
|
+
return headHash(path2, Math.min(HEAD_BYTES, size));
|
|
30674
|
+
} catch {
|
|
30675
|
+
return null;
|
|
30676
|
+
}
|
|
30677
|
+
}
|
|
30678
|
+
function pruneSnapshots(db) {
|
|
30679
|
+
try {
|
|
30680
|
+
db.statuslineSnapshotsPrune({
|
|
30681
|
+
olderThan: Date.now() - SNAPSHOT_MAX_AGE_MS,
|
|
30682
|
+
keepPerSession: SNAPSHOT_KEEP_PER_SESSION
|
|
30683
|
+
});
|
|
30684
|
+
} catch {
|
|
30685
|
+
}
|
|
30686
|
+
}
|
|
30687
|
+
function add(a, b) {
|
|
30688
|
+
return {
|
|
30689
|
+
calls: a.calls + b.calls,
|
|
30690
|
+
toolUses: a.toolUses + b.toolUses,
|
|
30691
|
+
contextEvents: a.contextEvents + b.contextEvents
|
|
30692
|
+
};
|
|
30693
|
+
}
|
|
30694
|
+
function listJsonl(dir) {
|
|
30695
|
+
try {
|
|
30696
|
+
return readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => join4(dir, f));
|
|
30697
|
+
} catch {
|
|
30698
|
+
return [];
|
|
30699
|
+
}
|
|
30700
|
+
}
|
|
30701
|
+
function firstEntrySessionId(path2) {
|
|
30702
|
+
try {
|
|
30703
|
+
const { lines } = readCompleteLines(path2, 0, Math.min(statSync2(path2).size, 64 * 1024));
|
|
30704
|
+
for (const line2 of lines) {
|
|
30705
|
+
if (!line2.trim()) continue;
|
|
30706
|
+
const e = JSON.parse(line2);
|
|
30707
|
+
return typeof e.sessionId === "string" ? e.sessionId : void 0;
|
|
30708
|
+
}
|
|
30709
|
+
} catch {
|
|
30710
|
+
}
|
|
30711
|
+
return void 0;
|
|
30712
|
+
}
|
|
30713
|
+
function sidechainFilesFor(db, dir, sessionId) {
|
|
30714
|
+
if (!isValidSessionId(sessionId)) return [];
|
|
30715
|
+
const inside = (p) => insideDir(p, dir) && p.endsWith(".jsonl");
|
|
30716
|
+
const out = listJsonl(join4(dir, sessionId, "subagents")).filter(inside);
|
|
30717
|
+
for (const path2 of listJsonl(dir)) {
|
|
30718
|
+
if (!inside(path2) || !basename(path2).startsWith("agent-")) continue;
|
|
30719
|
+
const known = db.transcriptFileGet(path2)?.session_id;
|
|
30720
|
+
let owner = known ?? void 0;
|
|
30721
|
+
if (!owner) {
|
|
30722
|
+
owner = firstEntrySessionId(path2);
|
|
30723
|
+
if (owner) {
|
|
30724
|
+
db.transcriptFileSet({
|
|
30725
|
+
path: path2,
|
|
30726
|
+
session_id: owner,
|
|
30727
|
+
offset: 0,
|
|
30728
|
+
size: 0,
|
|
30729
|
+
mtime: 0,
|
|
30730
|
+
ingested_at: Date.now()
|
|
30731
|
+
});
|
|
30732
|
+
}
|
|
30733
|
+
}
|
|
30734
|
+
if (owner === sessionId) out.push(path2);
|
|
30735
|
+
}
|
|
30736
|
+
return out;
|
|
30737
|
+
}
|
|
30738
|
+
function ingestSession(db, root, sessionId, transcriptPath, opts = {}) {
|
|
30739
|
+
if (!isValidSessionId(sessionId)) return ZERO;
|
|
30740
|
+
let main2;
|
|
30741
|
+
const trusted = transcriptPath ?? db.sessionGet(sessionId)?.transcript_path ?? void 0;
|
|
30742
|
+
if (trusted) {
|
|
30743
|
+
if (!isTranscriptFile(trusted)) return ZERO;
|
|
30744
|
+
main2 = resolve3(trusted);
|
|
30745
|
+
} else {
|
|
30746
|
+
const built = resolve3(transcriptDirFor(root), `${sessionId}.jsonl`);
|
|
30747
|
+
if (!insideDir(built, claudeDir())) return ZERO;
|
|
30748
|
+
main2 = built;
|
|
30749
|
+
}
|
|
30750
|
+
let total = ingestTranscript(db, main2, { sessionId, overrides: opts.overrides });
|
|
30751
|
+
for (const path2 of sidechainFilesFor(db, dirname2(main2), sessionId)) {
|
|
30752
|
+
total = add(total, ingestTranscript(db, path2, { sessionId, overrides: opts.overrides }));
|
|
30753
|
+
}
|
|
30754
|
+
pruneSnapshots(db);
|
|
30755
|
+
return total;
|
|
30756
|
+
}
|
|
30757
|
+
|
|
28837
30758
|
// src/hooks/capture.ts
|
|
28838
30759
|
var INDEXED = /* @__PURE__ */ new Set([
|
|
28839
30760
|
"decision",
|
|
@@ -28876,30 +30797,51 @@ function open3(input2) {
|
|
|
28876
30797
|
});
|
|
28877
30798
|
return { db, root: ctx.root, sessionId };
|
|
28878
30799
|
}
|
|
30800
|
+
function ingestUsage(db, input2, sessionId) {
|
|
30801
|
+
try {
|
|
30802
|
+
const ctx = getContext(input2.cwd);
|
|
30803
|
+
if (!usageEnabled(ctx)) return;
|
|
30804
|
+
const overrides = ctx.config.usage.pricing;
|
|
30805
|
+
ingestSession(db, ctx.root, sessionId, input2.transcript_path, { overrides });
|
|
30806
|
+
if (input2.agent_transcript_path)
|
|
30807
|
+
ingestTranscript(db, input2.agent_transcript_path, { sessionId, overrides });
|
|
30808
|
+
} catch {
|
|
30809
|
+
}
|
|
30810
|
+
}
|
|
28879
30811
|
var userPromptSubmit = async (input2) => {
|
|
28880
30812
|
if (!input2.prompt?.trim()) return void 0;
|
|
28881
30813
|
const { db, sessionId } = open3(input2);
|
|
28882
30814
|
db.sessionUpsert({ id: sessionId, last_prompt: clip(input2.prompt, 600) });
|
|
28883
30815
|
persist(db, sessionId, withPriority(sessionId, classifyPrompt(input2.prompt)));
|
|
28884
|
-
const
|
|
28885
|
-
|
|
30816
|
+
const ctx = getContext(input2.cwd);
|
|
30817
|
+
const { recall } = ctx.config;
|
|
30818
|
+
const parts = [];
|
|
30819
|
+
if (recall.onPrompt) {
|
|
30820
|
+
try {
|
|
30821
|
+
const r = await recallForText(db, input2.prompt, {
|
|
30822
|
+
maxMemories: recall.maxMemories,
|
|
30823
|
+
maxDocs: recall.maxDocs,
|
|
30824
|
+
sessionId,
|
|
30825
|
+
repeatAfter: recall.repeatAfter,
|
|
30826
|
+
trace: true,
|
|
30827
|
+
path: "prompt"
|
|
30828
|
+
});
|
|
30829
|
+
recordShown(db, sessionId, r.keys, { path: "prompt", trace: r.trace });
|
|
30830
|
+
if (r.text) parts.push(r.text);
|
|
30831
|
+
} catch {
|
|
30832
|
+
}
|
|
30833
|
+
}
|
|
28886
30834
|
try {
|
|
28887
|
-
|
|
28888
|
-
maxMemories: recall.maxMemories,
|
|
28889
|
-
maxDocs: recall.maxDocs,
|
|
28890
|
-
sessionId,
|
|
28891
|
-
repeatAfter: recall.repeatAfter,
|
|
28892
|
-
trace: true,
|
|
28893
|
-
path: "prompt"
|
|
28894
|
-
});
|
|
28895
|
-
recordShown(db, sessionId, r.keys, { path: "prompt", trace: r.trace });
|
|
28896
|
-
if (!r.text) return void 0;
|
|
28897
|
-
return {
|
|
28898
|
-
hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: r.text }
|
|
28899
|
-
};
|
|
30835
|
+
if (usageEnabled(ctx)) parts.push(...budgetWarnings(db, ctx, sessionId));
|
|
28900
30836
|
} catch {
|
|
28901
|
-
return void 0;
|
|
28902
30837
|
}
|
|
30838
|
+
if (!parts.length) return void 0;
|
|
30839
|
+
return {
|
|
30840
|
+
hookSpecificOutput: {
|
|
30841
|
+
hookEventName: "UserPromptSubmit",
|
|
30842
|
+
additionalContext: parts.join("\n\n")
|
|
30843
|
+
}
|
|
30844
|
+
};
|
|
28903
30845
|
};
|
|
28904
30846
|
function detectLoop(db, sessionId, key, summary) {
|
|
28905
30847
|
const recent = db.eventsRecent(30, ["tool_call"]).filter((e) => e.session_id === sessionId);
|
|
@@ -28965,23 +30907,26 @@ var postToolUseFailure = async (input2) => {
|
|
|
28965
30907
|
var subagentStop = (input2) => {
|
|
28966
30908
|
const { db, sessionId } = open3(input2);
|
|
28967
30909
|
const msg = input2.last_assistant_message ?? "";
|
|
28968
|
-
if (
|
|
28969
|
-
|
|
28970
|
-
|
|
28971
|
-
|
|
28972
|
-
|
|
28973
|
-
|
|
28974
|
-
|
|
28975
|
-
|
|
28976
|
-
|
|
28977
|
-
|
|
28978
|
-
|
|
30910
|
+
if (msg.trim()) {
|
|
30911
|
+
persist(
|
|
30912
|
+
db,
|
|
30913
|
+
sessionId,
|
|
30914
|
+
withPriority(sessionId, [
|
|
30915
|
+
ev("agent_findings", {
|
|
30916
|
+
agent: input2.agent_type ?? input2.agent_id,
|
|
30917
|
+
text: clip(msg.replace(/\s+/g, " "), 1e3)
|
|
30918
|
+
})
|
|
30919
|
+
])
|
|
30920
|
+
);
|
|
30921
|
+
}
|
|
30922
|
+
ingestUsage(db, input2, sessionId);
|
|
28979
30923
|
return void 0;
|
|
28980
30924
|
};
|
|
28981
30925
|
var stop = (input2) => {
|
|
28982
30926
|
const { db, sessionId } = open3(input2);
|
|
28983
30927
|
const msg = input2.last_assistant_message ?? "";
|
|
28984
30928
|
persist(db, sessionId, withPriority(sessionId, classifyAssistant(msg)));
|
|
30929
|
+
ingestUsage(db, input2, sessionId);
|
|
28985
30930
|
return void 0;
|
|
28986
30931
|
};
|
|
28987
30932
|
var sessionEnd = (input2) => {
|
|
@@ -28990,6 +30935,7 @@ var sessionEnd = (input2) => {
|
|
|
28990
30935
|
db.eventInsert({
|
|
28991
30936
|
...withPriority(sessionId, [ev("session_start", { ended: input2.reason ?? "unknown" })])[0]
|
|
28992
30937
|
});
|
|
30938
|
+
ingestUsage(db, input2, sessionId);
|
|
28993
30939
|
globalDb();
|
|
28994
30940
|
return void 0;
|
|
28995
30941
|
};
|
|
@@ -29118,19 +31064,20 @@ var preCompact = (input2) => {
|
|
|
29118
31064
|
|
|
29119
31065
|
// src/hooks/routing.ts
|
|
29120
31066
|
init_esm_shims();
|
|
29121
|
-
import { realpathSync as realpathSync4, statSync as
|
|
29122
|
-
import {
|
|
31067
|
+
import { realpathSync as realpathSync4, statSync as statSync5 } from "fs";
|
|
31068
|
+
import { homedir as homedir5 } from "os";
|
|
31069
|
+
import { extname as extname2, join as join12, relative as relative4, resolve as resolve4 } from "path";
|
|
29123
31070
|
|
|
29124
31071
|
// src/docs/manifest.ts
|
|
29125
31072
|
init_esm_shims();
|
|
29126
|
-
import { existsSync as
|
|
29127
|
-
import { dirname as
|
|
31073
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
31074
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
29128
31075
|
var NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
29129
31076
|
function isValidName(name) {
|
|
29130
31077
|
return NAME_RE.test(name);
|
|
29131
31078
|
}
|
|
29132
31079
|
function manifestPathAt(root, rel = ".eminiar/docs.json") {
|
|
29133
|
-
return
|
|
31080
|
+
return join5(root, rel);
|
|
29134
31081
|
}
|
|
29135
31082
|
function manifestPath(ctx) {
|
|
29136
31083
|
return manifestPathAt(ctx.root, ctx.config.docs.manifest);
|
|
@@ -29158,7 +31105,7 @@ function normalize(raw) {
|
|
|
29158
31105
|
return { version: 1, sources: out };
|
|
29159
31106
|
}
|
|
29160
31107
|
function readManifestAt(file3) {
|
|
29161
|
-
if (!
|
|
31108
|
+
if (!existsSync4(file3)) return empty();
|
|
29162
31109
|
try {
|
|
29163
31110
|
return normalize(JSON.parse(readFileSync3(file3, "utf8")));
|
|
29164
31111
|
} catch {
|
|
@@ -29171,9 +31118,9 @@ function readManifest(ctx) {
|
|
|
29171
31118
|
|
|
29172
31119
|
// src/permissions/settings.ts
|
|
29173
31120
|
init_esm_shims();
|
|
29174
|
-
import { existsSync as
|
|
29175
|
-
import { homedir as
|
|
29176
|
-
import { join as
|
|
31121
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync as realpathSync3, statSync as statSync3 } from "fs";
|
|
31122
|
+
import { homedir as homedir3 } from "os";
|
|
31123
|
+
import { join as join6 } from "path";
|
|
29177
31124
|
|
|
29178
31125
|
// src/permissions/rules.ts
|
|
29179
31126
|
init_esm_shims();
|
|
@@ -29210,7 +31157,7 @@ init_esm_shims();
|
|
|
29210
31157
|
|
|
29211
31158
|
// src/permissions/settings.ts
|
|
29212
31159
|
function readRules(file3) {
|
|
29213
|
-
if (!
|
|
31160
|
+
if (!existsSync5(file3)) return {};
|
|
29214
31161
|
try {
|
|
29215
31162
|
const s = JSON.parse(readFileSync4(file3, "utf8"));
|
|
29216
31163
|
return s.permissions ?? {};
|
|
@@ -29220,7 +31167,7 @@ function readRules(file3) {
|
|
|
29220
31167
|
}
|
|
29221
31168
|
function stamp(file3) {
|
|
29222
31169
|
try {
|
|
29223
|
-
return
|
|
31170
|
+
return statSync3(file3).mtimeMs;
|
|
29224
31171
|
} catch {
|
|
29225
31172
|
return -1;
|
|
29226
31173
|
}
|
|
@@ -29228,9 +31175,9 @@ function stamp(file3) {
|
|
|
29228
31175
|
var cache2 = /* @__PURE__ */ new Map();
|
|
29229
31176
|
function loadPermissionRules(root, extraDeny = []) {
|
|
29230
31177
|
const files = [
|
|
29231
|
-
|
|
29232
|
-
|
|
29233
|
-
|
|
31178
|
+
join6(homedir3(), ".claude", "settings.json"),
|
|
31179
|
+
join6(root, ".claude", "settings.json"),
|
|
31180
|
+
join6(root, ".claude", "settings.local.json")
|
|
29234
31181
|
];
|
|
29235
31182
|
const key = `${root}\0${extraDeny.join("\0")}`;
|
|
29236
31183
|
const stamps = files.map(stamp);
|
|
@@ -29268,8 +31215,8 @@ function checkReadPath(rules, abs) {
|
|
|
29268
31215
|
|
|
29269
31216
|
// src/session/brief.ts
|
|
29270
31217
|
init_esm_shims();
|
|
29271
|
-
import { existsSync as
|
|
29272
|
-
import { join as
|
|
31218
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
31219
|
+
import { join as join8 } from "path";
|
|
29273
31220
|
|
|
29274
31221
|
// src/memory/frontmatter.ts
|
|
29275
31222
|
init_esm_shims();
|
|
@@ -29303,15 +31250,15 @@ function parseMemoryFile(text) {
|
|
|
29303
31250
|
init_esm_shims();
|
|
29304
31251
|
var import_fast_glob2 = __toESM(require_out4(), 1);
|
|
29305
31252
|
import {
|
|
29306
|
-
existsSync as
|
|
31253
|
+
existsSync as existsSync6,
|
|
29307
31254
|
mkdirSync as mkdirSync3,
|
|
29308
31255
|
readFileSync as readFileSync5,
|
|
29309
31256
|
renameSync,
|
|
29310
|
-
statSync as
|
|
31257
|
+
statSync as statSync4,
|
|
29311
31258
|
unlinkSync,
|
|
29312
31259
|
writeFileSync as writeFileSync2
|
|
29313
31260
|
} from "fs";
|
|
29314
|
-
import { dirname as
|
|
31261
|
+
import { dirname as dirname4, join as join7, relative as relative3, sep as sep2 } from "path";
|
|
29315
31262
|
|
|
29316
31263
|
// src/storage/types.ts
|
|
29317
31264
|
init_esm_shims();
|
|
@@ -29323,7 +31270,7 @@ function memoriesRel(ctx) {
|
|
|
29323
31270
|
return ctx.config.memories.dir.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
29324
31271
|
}
|
|
29325
31272
|
function memoriesDir(ctx) {
|
|
29326
|
-
return
|
|
31273
|
+
return join7(ctx.root, memoriesRel(ctx));
|
|
29327
31274
|
}
|
|
29328
31275
|
|
|
29329
31276
|
// src/session/brief.ts
|
|
@@ -29366,8 +31313,8 @@ function historyLine(db) {
|
|
|
29366
31313
|
return `${parts.join("; ")}. em_history {action:"file", path} before editing, {action:"risk"} before committing.`;
|
|
29367
31314
|
}
|
|
29368
31315
|
function overviewParagraph(ctx) {
|
|
29369
|
-
const file3 =
|
|
29370
|
-
if (!
|
|
31316
|
+
const file3 = join8(memoriesDir(ctx), "onboarding", "overview.md");
|
|
31317
|
+
if (!existsSync7(file3)) return void 0;
|
|
29371
31318
|
try {
|
|
29372
31319
|
const body = parseMemoryFile(readFileSync6(file3, "utf8")).body.trim();
|
|
29373
31320
|
const para = body.split(/\n\s*\n/).map(
|
|
@@ -29416,12 +31363,32 @@ function fitBudget(lines, budget) {
|
|
|
29416
31363
|
return out.length > budget ? `${out.slice(0, budget - 1)}\u2026` : out;
|
|
29417
31364
|
}
|
|
29418
31365
|
|
|
31366
|
+
// src/session/routing-stats.ts
|
|
31367
|
+
init_esm_shims();
|
|
31368
|
+
var ROUTING_CATEGORY = "routing";
|
|
31369
|
+
function recordRouting(db, sessionId, e) {
|
|
31370
|
+
db.eventInsert({
|
|
31371
|
+
sessionId,
|
|
31372
|
+
category: ROUTING_CATEGORY,
|
|
31373
|
+
priority: 4,
|
|
31374
|
+
tool: e.tool,
|
|
31375
|
+
payload: {
|
|
31376
|
+
tool: e.tool,
|
|
31377
|
+
decision: e.decision,
|
|
31378
|
+
...e.target ? { target: e.target } : {},
|
|
31379
|
+
...e.rule ? { rule: e.rule } : {},
|
|
31380
|
+
...e.why ? { why: e.why } : {},
|
|
31381
|
+
...e.head ? { head: e.head.slice(0, 120) } : {}
|
|
31382
|
+
}
|
|
31383
|
+
});
|
|
31384
|
+
}
|
|
31385
|
+
|
|
29419
31386
|
// src/session/server-alive.ts
|
|
29420
31387
|
init_esm_shims();
|
|
29421
|
-
import { existsSync as
|
|
29422
|
-
import { join as
|
|
31388
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
31389
|
+
import { join as join9 } from "path";
|
|
29423
31390
|
function markersDir(ctx) {
|
|
29424
|
-
return
|
|
31391
|
+
return join9(projectDir(ctx.id), "servers");
|
|
29425
31392
|
}
|
|
29426
31393
|
function processAlive(pid) {
|
|
29427
31394
|
try {
|
|
@@ -29433,13 +31400,13 @@ function processAlive(pid) {
|
|
|
29433
31400
|
}
|
|
29434
31401
|
function liveServers(ctx) {
|
|
29435
31402
|
const dir = markersDir(ctx);
|
|
29436
|
-
if (!
|
|
31403
|
+
if (!existsSync8(dir)) return [];
|
|
29437
31404
|
const live2 = [];
|
|
29438
|
-
for (const name of
|
|
31405
|
+
for (const name of readdirSync2(dir)) {
|
|
29439
31406
|
const pid = Number(name);
|
|
29440
31407
|
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
29441
31408
|
if (processAlive(pid)) live2.push(pid);
|
|
29442
|
-
else rmSync(
|
|
31409
|
+
else rmSync(join9(dir, name), { force: true });
|
|
29443
31410
|
}
|
|
29444
31411
|
return live2;
|
|
29445
31412
|
}
|
|
@@ -29450,29 +31417,29 @@ function serverAlive(ctx) {
|
|
|
29450
31417
|
// src/hooks/coexist.ts
|
|
29451
31418
|
init_esm_shims();
|
|
29452
31419
|
import { readFileSync as readFileSync8 } from "fs";
|
|
29453
|
-
import { join as
|
|
31420
|
+
import { join as join11 } from "path";
|
|
29454
31421
|
|
|
29455
31422
|
// src/install/claude-config.ts
|
|
29456
31423
|
init_esm_shims();
|
|
29457
31424
|
import {
|
|
29458
31425
|
copyFileSync,
|
|
29459
|
-
existsSync as
|
|
31426
|
+
existsSync as existsSync9,
|
|
29460
31427
|
mkdirSync as mkdirSync5,
|
|
29461
|
-
readdirSync as
|
|
31428
|
+
readdirSync as readdirSync3,
|
|
29462
31429
|
readFileSync as readFileSync7,
|
|
29463
31430
|
writeFileSync as writeFileSync4
|
|
29464
31431
|
} from "fs";
|
|
29465
|
-
import { homedir as
|
|
29466
|
-
import { dirname as
|
|
29467
|
-
var
|
|
29468
|
-
var pluginsDir = () =>
|
|
31432
|
+
import { homedir as homedir4 } from "os";
|
|
31433
|
+
import { dirname as dirname5, join as join10 } from "path";
|
|
31434
|
+
var claudeDir2 = () => process.env.CLAUDE_CONFIG_DIR || join10(homedir4(), ".claude");
|
|
31435
|
+
var pluginsDir = () => join10(claudeDir2(), "plugins");
|
|
29469
31436
|
|
|
29470
31437
|
// src/hooks/coexist.ts
|
|
29471
31438
|
var cached2;
|
|
29472
31439
|
function contextModeInstalled() {
|
|
29473
31440
|
if (cached2 !== void 0) return cached2;
|
|
29474
31441
|
try {
|
|
29475
|
-
const reg = JSON.parse(readFileSync8(
|
|
31442
|
+
const reg = JSON.parse(readFileSync8(join11(pluginsDir(), "installed_plugins.json"), "utf8"));
|
|
29476
31443
|
cached2 = Object.keys(reg.plugins ?? {}).some((id) => id.startsWith("context-mode@"));
|
|
29477
31444
|
} catch {
|
|
29478
31445
|
cached2 = false;
|
|
@@ -29568,7 +31535,7 @@ function historyCall(cmd) {
|
|
|
29568
31535
|
}
|
|
29569
31536
|
function docsSources(root) {
|
|
29570
31537
|
try {
|
|
29571
|
-
return readManifestAt(
|
|
31538
|
+
return readManifestAt(join12(root, ".eminiar", "docs.json")).sources;
|
|
29572
31539
|
} catch {
|
|
29573
31540
|
return [];
|
|
29574
31541
|
}
|
|
@@ -29609,40 +31576,213 @@ var BIG_TEXT_EXT = /* @__PURE__ */ new Set([
|
|
|
29609
31576
|
function withoutHeredocs(cmd) {
|
|
29610
31577
|
return cmd.replace(/<<-?\s*(['"]?)(\w+)\1[^\n]*\n[\s\S]*?\n\s*\2\s*(?=\n|$)/g, "<<$2");
|
|
29611
31578
|
}
|
|
31579
|
+
function splitUnquoted(s, sep3) {
|
|
31580
|
+
const out = [];
|
|
31581
|
+
let cur = "";
|
|
31582
|
+
let quote2;
|
|
31583
|
+
for (let i = 0; i < s.length; i++) {
|
|
31584
|
+
const ch = s[i];
|
|
31585
|
+
if (quote2) {
|
|
31586
|
+
cur += ch;
|
|
31587
|
+
if (ch === quote2 && s[i - 1] !== "\\") quote2 = void 0;
|
|
31588
|
+
continue;
|
|
31589
|
+
}
|
|
31590
|
+
if (ch === "'" || ch === '"') {
|
|
31591
|
+
quote2 = ch;
|
|
31592
|
+
cur += ch;
|
|
31593
|
+
continue;
|
|
31594
|
+
}
|
|
31595
|
+
const n = sep3(s, i);
|
|
31596
|
+
if (n) {
|
|
31597
|
+
out.push(cur);
|
|
31598
|
+
cur = "";
|
|
31599
|
+
i += n - 1;
|
|
31600
|
+
continue;
|
|
31601
|
+
}
|
|
31602
|
+
cur += ch;
|
|
31603
|
+
}
|
|
31604
|
+
out.push(cur);
|
|
31605
|
+
return out.map((p) => p.trim()).filter(Boolean);
|
|
31606
|
+
}
|
|
31607
|
+
function commandSegments(cmd) {
|
|
31608
|
+
return splitUnquoted(cmd, (s, i) => {
|
|
31609
|
+
const ch = s[i];
|
|
31610
|
+
if (ch === ";" || ch === "\n") return 1;
|
|
31611
|
+
if ((ch === "&" || ch === "|") && s[i + 1] === ch) return 2;
|
|
31612
|
+
return 0;
|
|
31613
|
+
});
|
|
31614
|
+
}
|
|
31615
|
+
function pipeStages(segment) {
|
|
31616
|
+
return splitUnquoted(segment, (s, i) => s[i] === "|" && s[i + 1] !== "|" ? 1 : 0);
|
|
31617
|
+
}
|
|
31618
|
+
function headLines(stage) {
|
|
31619
|
+
const m = /(?:^|\s)(?:-n\s*=?\s*|-)(\d+)(?=\s|$)|--lines[= ](\d+)/.exec(stage);
|
|
31620
|
+
return m ? Number(m[1] ?? m[2]) : 10;
|
|
31621
|
+
}
|
|
31622
|
+
var BOUNDED_LINES = 400;
|
|
31623
|
+
function bounds(stage) {
|
|
31624
|
+
if (/^\s*wc\b/.test(stage)) return true;
|
|
31625
|
+
if (!/^\s*(head|tail)\b/.test(stage) || /(^|\s)-c\b|--bytes\b/.test(stage)) return false;
|
|
31626
|
+
return headLines(stage) <= BOUNDED_LINES;
|
|
31627
|
+
}
|
|
31628
|
+
var MUTATING = /^\s*(?:(?:sudo|env|[A-Z_][A-Z0-9_]*=\S*)\s+)*(git\s+(add|commit|checkout|switch|push|pull|fetch|merge|rebase|stash|reset|restore|cherry-pick|revert|tag|init|clone|worktree|mv|rm|am|apply|submodule)|npm\s+(install|i|ci|uninstall|un|link|publish|version|update|up|dedupe)|pnpm\s+(install|i|add|remove|rm|update|up|link)|yarn\s+(add|remove|install|upgrade)|pip3?\s+(install|uninstall)|cargo\s+(add|install)|brew\s+(install|uninstall|upgrade)|gh\s+(pr|issue|release|repo)\s+(create|edit|merge|close|comment|delete|ready)|rm|mv|cp|mkdir|rmdir|touch|chmod|chown|ln|sed\s+(?:-\S+\s+)*-i|tee)\b/;
|
|
31629
|
+
var WRITES_FILE = /(?:^|\s|\d)>{1,2}\s*(?!&)(?!\/dev\/null)\S/;
|
|
31630
|
+
function mutates(segment) {
|
|
31631
|
+
return MUTATING.test(segment) || WRITES_FILE.test(segment);
|
|
31632
|
+
}
|
|
31633
|
+
var READ_LIMIT = 200 * 1024;
|
|
31634
|
+
var BIG_TEXT_LIMIT = 20 * 1024;
|
|
31635
|
+
var GREP_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
31636
|
+
"-e",
|
|
31637
|
+
"-f",
|
|
31638
|
+
"-m",
|
|
31639
|
+
"-A",
|
|
31640
|
+
"-B",
|
|
31641
|
+
"-C",
|
|
31642
|
+
"-d",
|
|
31643
|
+
"-g",
|
|
31644
|
+
"-t",
|
|
31645
|
+
"-T",
|
|
31646
|
+
"--regexp",
|
|
31647
|
+
"--file",
|
|
31648
|
+
"--max-count",
|
|
31649
|
+
"--after-context",
|
|
31650
|
+
"--before-context",
|
|
31651
|
+
"--context",
|
|
31652
|
+
"--include",
|
|
31653
|
+
"--exclude",
|
|
31654
|
+
"--glob",
|
|
31655
|
+
"--type"
|
|
31656
|
+
]);
|
|
31657
|
+
function fileOperands(stage, cwd) {
|
|
31658
|
+
const tokens = stage.trim().split(/\s+/);
|
|
31659
|
+
const cmd = tokens[0];
|
|
31660
|
+
const isGrep = /^(grep|egrep|fgrep|rg)$/.test(cmd);
|
|
31661
|
+
const files = [];
|
|
31662
|
+
let patternGiven = !isGrep;
|
|
31663
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
31664
|
+
const t = tokens[i];
|
|
31665
|
+
if (t === "--") {
|
|
31666
|
+
files.push(...tokens.slice(i + 1));
|
|
31667
|
+
break;
|
|
31668
|
+
}
|
|
31669
|
+
if (t.startsWith("-") && t.length > 1) {
|
|
31670
|
+
if (/^-[ef]$|^--(regexp|file)$/.test(t)) patternGiven = true;
|
|
31671
|
+
if (GREP_VALUE_FLAGS.has(t)) i++;
|
|
31672
|
+
continue;
|
|
31673
|
+
}
|
|
31674
|
+
if (!patternGiven) {
|
|
31675
|
+
patternGiven = true;
|
|
31676
|
+
continue;
|
|
31677
|
+
}
|
|
31678
|
+
files.push(t);
|
|
31679
|
+
}
|
|
31680
|
+
if (!files.length) return void 0;
|
|
31681
|
+
const out = [];
|
|
31682
|
+
for (let f of files) {
|
|
31683
|
+
if (/[*?[\]{}$`\\]/.test(f)) return void 0;
|
|
31684
|
+
f = f.replace(/^['"]|['"]$/g, "");
|
|
31685
|
+
if (f.startsWith("~/")) f = join12(homedir5(), f.slice(2));
|
|
31686
|
+
out.push(resolve4(cwd, f));
|
|
31687
|
+
}
|
|
31688
|
+
return out;
|
|
31689
|
+
}
|
|
31690
|
+
function stageBytes(stage, cwd) {
|
|
31691
|
+
const files = fileOperands(stage, cwd);
|
|
31692
|
+
if (!files) return void 0;
|
|
31693
|
+
let total = 0;
|
|
31694
|
+
for (const f of files) {
|
|
31695
|
+
try {
|
|
31696
|
+
const st = statSync5(f);
|
|
31697
|
+
if (st.isDirectory()) return void 0;
|
|
31698
|
+
if (BIG_TEXT_EXT.has(extname2(f).toLowerCase()) && st.size > BIG_TEXT_LIMIT)
|
|
31699
|
+
return READ_LIMIT + st.size;
|
|
31700
|
+
total += st.size;
|
|
31701
|
+
} catch {
|
|
31702
|
+
}
|
|
31703
|
+
}
|
|
31704
|
+
return total;
|
|
31705
|
+
}
|
|
31706
|
+
function sedRangeLines(stage) {
|
|
31707
|
+
const m = /^\s*sed\s+(?:-\S+\s+)*-n\s+['"]?(\d+),(\d+)p['"]?/.exec(stage);
|
|
31708
|
+
return m ? Number(m[2]) - Number(m[1]) + 1 : void 0;
|
|
31709
|
+
}
|
|
31710
|
+
function firstStageBound(first, cwd) {
|
|
31711
|
+
if (/^\s*(head|tail)\s/.test(first)) return bounds(first) || void 0;
|
|
31712
|
+
const sed = sedRangeLines(first);
|
|
31713
|
+
if (sed !== void 0) return sed <= BOUNDED_LINES || void 0;
|
|
31714
|
+
if (/^\s*du\s+-[a-zA-Z]*s/.test(first)) return true;
|
|
31715
|
+
if (/^\s*git\s+log\b/.test(first) && ONE_COMMIT.test(first)) return true;
|
|
31716
|
+
if (/^\s*(grep|egrep|fgrep|rg)\s/.test(first) && /(^|\s)-[a-zA-Z]*[lLcq][a-zA-Z]*(?=\s|$)|--(files-with-matches|files-without-match|count|quiet)\b/.test(
|
|
31717
|
+
first
|
|
31718
|
+
))
|
|
31719
|
+
return true;
|
|
31720
|
+
if (/^\s*(cat|grep|egrep|fgrep|rg)\s/.test(first)) {
|
|
31721
|
+
const bytes = stageBytes(first, cwd);
|
|
31722
|
+
if (bytes === void 0) return void 0;
|
|
31723
|
+
if (bytes <= READ_LIMIT) return true;
|
|
31724
|
+
return { rule: "big-read", text: `reads about ${Math.round(bytes / 1024)} KB` };
|
|
31725
|
+
}
|
|
31726
|
+
return void 0;
|
|
31727
|
+
}
|
|
31728
|
+
function heavySegment(segment, cwd) {
|
|
31729
|
+
if (TINY_BASH.test(segment)) return void 0;
|
|
31730
|
+
const stages = pipeStages(segment);
|
|
31731
|
+
const first = stages[0];
|
|
31732
|
+
if (!first) return void 0;
|
|
31733
|
+
if (stages.length > 1 && bounds(stages[stages.length - 1])) return void 0;
|
|
31734
|
+
const bound = firstStageBound(first, cwd);
|
|
31735
|
+
if (bound === true) return void 0;
|
|
31736
|
+
if (bound) return bound;
|
|
31737
|
+
if (HEAVY_BASH.test(first)) return { rule: "heavy", text: "usually prints a lot" };
|
|
31738
|
+
if (stages.slice(1).some((s) => /^\s*(grep|rg|sort|uniq|jq|less)\b/.test(s)))
|
|
31739
|
+
return { rule: "pipe", text: "streams an unbounded output through a filter" };
|
|
31740
|
+
return void 0;
|
|
31741
|
+
}
|
|
31742
|
+
function headOf(segment, n = 60) {
|
|
31743
|
+
const one = segment.replace(/\s+/g, " ").trim();
|
|
31744
|
+
return one.length > n ? `${one.slice(0, n - 1)}\u2026` : one;
|
|
31745
|
+
}
|
|
31746
|
+
function bashSuggestion(raw, cwd) {
|
|
31747
|
+
const cmd = withoutHeredocs(raw);
|
|
31748
|
+
if (!cmd.trim()) return void 0;
|
|
31749
|
+
const segments = commandSegments(cmd);
|
|
31750
|
+
if (segments.some(mutates)) return void 0;
|
|
31751
|
+
const history = historyCall(cmd);
|
|
31752
|
+
if (history) {
|
|
31753
|
+
return {
|
|
31754
|
+
tool: "em_history",
|
|
31755
|
+
rule: "history",
|
|
31756
|
+
reason: `the git history is already classified (commit kinds, fixes traced to the change that caused them, risk per file); em_history ${history} answers this without the raw log`
|
|
31757
|
+
};
|
|
31758
|
+
}
|
|
31759
|
+
for (const segment of segments) {
|
|
31760
|
+
const heavy = heavySegment(segment, cwd);
|
|
31761
|
+
if (!heavy) continue;
|
|
31762
|
+
return {
|
|
31763
|
+
tool: "em_execute",
|
|
31764
|
+
rule: heavy.rule,
|
|
31765
|
+
reason: `\`${headOf(segment)}\` ${heavy.text}; em_execute {language:"shell", code, intent} keeps the output out of context and returns only matching sections`
|
|
31766
|
+
};
|
|
31767
|
+
}
|
|
31768
|
+
return void 0;
|
|
31769
|
+
}
|
|
29612
31770
|
function suggest(toolName, input2, root, extra = {}) {
|
|
29613
31771
|
switch (toolName) {
|
|
29614
|
-
case "Bash":
|
|
29615
|
-
|
|
29616
|
-
if (!cmd || TINY_BASH.test(cmd)) return void 0;
|
|
29617
|
-
if (/^\s*cat\b[^|\n]*>/.test(cmd) && !cmd.includes("|")) return void 0;
|
|
29618
|
-
const history = historyCall(cmd);
|
|
29619
|
-
if (history) {
|
|
29620
|
-
return {
|
|
29621
|
-
tool: "em_history",
|
|
29622
|
-
reason: `the git history is already classified (commit kinds, fixes traced to the change that caused them, risk per file); em_history ${history} answers this without the raw log`
|
|
29623
|
-
};
|
|
29624
|
-
}
|
|
29625
|
-
if (/^\s*git\s+log\b/.test(cmd) && ONE_COMMIT.test(cmd) && !cmd.includes("|"))
|
|
29626
|
-
return void 0;
|
|
29627
|
-
if (HEAVY_BASH.test(cmd) || /\|\s*(head|tail|grep|wc|sort|uniq|jq|less)\b/.test(cmd)) {
|
|
29628
|
-
return {
|
|
29629
|
-
tool: "em_execute",
|
|
29630
|
-
reason: `this command is likely to print a lot; em_execute {language:"shell", code, intent} keeps the output out of context and returns only matching sections`
|
|
29631
|
-
};
|
|
29632
|
-
}
|
|
29633
|
-
return void 0;
|
|
29634
|
-
}
|
|
31772
|
+
case "Bash":
|
|
31773
|
+
return bashSuggestion(String(input2.command ?? ""), extra.cwd ?? root);
|
|
29635
31774
|
case "Read": {
|
|
29636
31775
|
const fp = String(input2.file_path ?? "");
|
|
29637
31776
|
if (!fp) return void 0;
|
|
29638
31777
|
try {
|
|
29639
|
-
const abs =
|
|
29640
|
-
const st =
|
|
31778
|
+
const abs = resolve4(root, fp);
|
|
31779
|
+
const st = statSync5(abs);
|
|
29641
31780
|
const ext = extname2(fp).toLowerCase();
|
|
29642
31781
|
if (st.size > 200 * 1024 || BIG_TEXT_EXT.has(ext) && st.size > 20 * 1024) {
|
|
29643
31782
|
if (!checkReadPath(loadPermissionRules(root), abs).allowed) return void 0;
|
|
29644
31783
|
return {
|
|
29645
31784
|
tool: "em_execute_file",
|
|
31785
|
+
rule: "big-read",
|
|
29646
31786
|
reason: `${fp} is ${Math.round(st.size / 1024)} KB; em_execute_file {path, code} processes it in the sandbox so only what you print enters context`
|
|
29647
31787
|
};
|
|
29648
31788
|
}
|
|
@@ -29655,6 +31795,7 @@ function suggest(toolName, input2, root, extra = {}) {
|
|
|
29655
31795
|
if (extra.symbol) {
|
|
29656
31796
|
return {
|
|
29657
31797
|
tool: "em_symbols",
|
|
31798
|
+
rule: "symbol",
|
|
29658
31799
|
annotateOnly: !content,
|
|
29659
31800
|
reason: `the graph indexes "${extra.symbol}": em_symbols {name_path:"${extra.symbol}", mode:"references"} lists its uses, {include_body:true} its definition; em_search {queries:["${extra.symbol}"], source:"code"} finds the code by symbol`
|
|
29660
31801
|
};
|
|
@@ -29662,6 +31803,7 @@ function suggest(toolName, input2, root, extra = {}) {
|
|
|
29662
31803
|
if (content) {
|
|
29663
31804
|
return {
|
|
29664
31805
|
tool: "em_execute",
|
|
31806
|
+
rule: "content-grep",
|
|
29665
31807
|
reason: `content-mode grep can be large; em_execute with rg and an intent returns only matching sections (em_search {queries, mode:"regex"} scans the indexed files with context)`
|
|
29666
31808
|
};
|
|
29667
31809
|
}
|
|
@@ -29698,7 +31840,7 @@ function suggest(toolName, input2, root, extra = {}) {
|
|
|
29698
31840
|
}
|
|
29699
31841
|
function projectRel(ctx, fp) {
|
|
29700
31842
|
if (!fp) return void 0;
|
|
29701
|
-
let abs =
|
|
31843
|
+
let abs = resolve4(ctx.root, fp);
|
|
29702
31844
|
try {
|
|
29703
31845
|
abs = realpathSync4(abs);
|
|
29704
31846
|
} catch {
|
|
@@ -29830,9 +31972,16 @@ var preToolUse = async (input2) => {
|
|
|
29830
31972
|
const graph = graphContext(ctx, input2);
|
|
29831
31973
|
const hint = joinHints(graph, memo2);
|
|
29832
31974
|
const symbol2 = input2.tool_name === "Grep" && graph ? literalOf(String(input2.tool_input.pattern ?? "")) : void 0;
|
|
29833
|
-
const s = suggest(input2.tool_name, input2.tool_input, ctx.root, { symbol: symbol2 });
|
|
29834
|
-
|
|
29835
|
-
if (s
|
|
31975
|
+
const s = suggest(input2.tool_name, input2.tool_input, ctx.root, { symbol: symbol2, cwd: input2.cwd });
|
|
31976
|
+
const record2 = routingRecorder(ctx, input2, s);
|
|
31977
|
+
if (!s) {
|
|
31978
|
+
record2("allow");
|
|
31979
|
+
return withContext(void 0, hint);
|
|
31980
|
+
}
|
|
31981
|
+
if (s.annotateOnly) {
|
|
31982
|
+
record2("annotate", "annotate-only");
|
|
31983
|
+
return withContext(annotate(s), hint);
|
|
31984
|
+
}
|
|
29836
31985
|
if (mode === "enforce") {
|
|
29837
31986
|
if (s.prefixPrompt) {
|
|
29838
31987
|
const prompt = String(input2.tool_input.prompt ?? "");
|
|
@@ -29853,6 +32002,7 @@ var preToolUse = async (input2) => {
|
|
|
29853
32002
|
"info",
|
|
29854
32003
|
`enforce \u2192 annotate for ${input2.tool_name}: no eminiar MCP server running for ${ctx.root}`
|
|
29855
32004
|
);
|
|
32005
|
+
record2("annotate", "no-server");
|
|
29856
32006
|
return withContext(
|
|
29857
32007
|
annotate(
|
|
29858
32008
|
s,
|
|
@@ -29861,6 +32011,8 @@ var preToolUse = async (input2) => {
|
|
|
29861
32011
|
hint
|
|
29862
32012
|
);
|
|
29863
32013
|
}
|
|
32014
|
+
record2("deny");
|
|
32015
|
+
hookLog("info", `deny ${input2.tool_name} \u2192 ${s.tool} [${s.rule ?? "?"}]: ${callHead(input2)}`);
|
|
29864
32016
|
return {
|
|
29865
32017
|
hookSpecificOutput: {
|
|
29866
32018
|
hookEventName: "PreToolUse",
|
|
@@ -29869,9 +32021,35 @@ var preToolUse = async (input2) => {
|
|
|
29869
32021
|
}
|
|
29870
32022
|
};
|
|
29871
32023
|
}
|
|
32024
|
+
record2("annotate", "mode");
|
|
29872
32025
|
const extra = s.prefixPrompt ? joinHints((await briefFor(ctx, String(input2.tool_input.prompt ?? ""))).trim(), hint) : hint;
|
|
29873
32026
|
return withContext(annotate(s), extra);
|
|
29874
32027
|
};
|
|
32028
|
+
var ROUTED_TOOLS = /* @__PURE__ */ new Set(["Bash", "Read", "Grep", "WebFetch", "WebSearch"]);
|
|
32029
|
+
function callHead(input2) {
|
|
32030
|
+
const t = input2.tool_input ?? {};
|
|
32031
|
+
const v = t.command ?? t.file_path ?? t.pattern ?? t.url ?? t.query ?? "";
|
|
32032
|
+
return headOf(String(v), 120);
|
|
32033
|
+
}
|
|
32034
|
+
function routingRecorder(ctx, input2, s) {
|
|
32035
|
+
const tool = input2.tool_name ?? "";
|
|
32036
|
+
const sessionId = input2.session_id;
|
|
32037
|
+
if (!sessionId || !ROUTED_TOOLS.has(tool)) return () => {
|
|
32038
|
+
};
|
|
32039
|
+
return (decision, why) => {
|
|
32040
|
+
try {
|
|
32041
|
+
recordRouting(getStorage(ctx, { vectors: false }), sessionId, {
|
|
32042
|
+
tool,
|
|
32043
|
+
decision,
|
|
32044
|
+
target: s?.tool,
|
|
32045
|
+
rule: s?.rule,
|
|
32046
|
+
why,
|
|
32047
|
+
head: callHead(input2)
|
|
32048
|
+
});
|
|
32049
|
+
} catch {
|
|
32050
|
+
}
|
|
32051
|
+
};
|
|
32052
|
+
}
|
|
29875
32053
|
async function briefFor(ctx, task) {
|
|
29876
32054
|
const parts = [];
|
|
29877
32055
|
try {
|
|
@@ -29909,15 +32087,15 @@ function annotate(s, note = "") {
|
|
|
29909
32087
|
|
|
29910
32088
|
// src/hooks/session-start.ts
|
|
29911
32089
|
init_esm_shims();
|
|
29912
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
29913
|
-
import { join as
|
|
32090
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
|
|
32091
|
+
import { join as join14 } from "path";
|
|
29914
32092
|
|
|
29915
32093
|
// src/session/current.ts
|
|
29916
32094
|
init_esm_shims();
|
|
29917
|
-
import { readFileSync as readFileSync9, statSync as
|
|
29918
|
-
import { join as
|
|
32095
|
+
import { readFileSync as readFileSync9, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
32096
|
+
import { join as join13 } from "path";
|
|
29919
32097
|
function file2(ctx) {
|
|
29920
|
-
return
|
|
32098
|
+
return join13(projectDir(ctx.id), "current-session");
|
|
29921
32099
|
}
|
|
29922
32100
|
function setCurrentSession(ctx, sessionId) {
|
|
29923
32101
|
writeFileSync5(file2(ctx), sessionId);
|
|
@@ -29944,7 +32122,7 @@ function routingGuidance(mode) {
|
|
|
29944
32122
|
'- em_docs: the documentation knowledge base (local docs plus the libraries, services and APIs the project uses); {action:"discover"} / {action:"add", pick:"recommended"} build it, {action:"sync"} refreshes it.',
|
|
29945
32123
|
'- em_search {queries, sort:"timeline"}: everything indexed plus session memory.',
|
|
29946
32124
|
"- em_remember {content, kind, name?, status?}: memories are markdown under .eminiar/memories (survive compaction, shareable via git); em_memory {action: list|read|edit|rename|delete|status} manages them.",
|
|
29947
|
-
'- em_architecture {aspects?}: the project map (folders, entry points, routes, hotspots, decisions) in one call; em_symbols {name_path | mode:"overview", file} for a symbol or a file outline; em_graph_impact {node} before a refactor; em_graph_changes for what the current diff reaches; em_graph_query {pattern | preset} to walk the graph; em_index_status for coverage. em_stats shows savings.',
|
|
32125
|
+
'- em_architecture {aspects?}: the project map (folders, entry points, routes, hotspots, decisions) in one call; em_symbols {name_path | mode:"overview", file} for a symbol or a file outline; em_graph_impact {node} before a refactor; em_graph_changes for what the current diff reaches; em_graph_query {pattern | preset} to walk the graph; em_index_status for coverage. em_stats shows savings; em_usage {view} shows model calls, cost, cache and insights.',
|
|
29948
32126
|
"- em_history {action: overview | file {path} | risk | commit {sha} | branch {name} | symbol {name_path} | search {query}}: the git history classified (feat/fix/refactor\u2026), branches vs base, and each fix traced back to the commit that caused it \u2014 ask it before editing a file or committing instead of running git log/blame/show (those are routed to it); commit messages are searchable via em_search (source git).",
|
|
29949
32127
|
'- A Grep for an identifier the graph knows is routed to em_symbols {name_path, mode:"references"} / em_search {queries, source:"code"}: symbol-level answers instead of raw matches.',
|
|
29950
32128
|
"Plain Bash/Read/Grep remain right for short fixed output and files you are about to edit."
|
|
@@ -29978,6 +32156,7 @@ export EMINIAR_PROJECT_ROOT=${JSON.stringify(ctx.root)}
|
|
|
29978
32156
|
} catch {
|
|
29979
32157
|
}
|
|
29980
32158
|
}
|
|
32159
|
+
if (input2.source === "compact" || input2.source === "resume") ingestUsage(db, input2, sessionId);
|
|
29981
32160
|
const parts = [];
|
|
29982
32161
|
if (input2.source === "compact" || input2.source === "resume") {
|
|
29983
32162
|
const snap = db.snapshotLatestUnconsumed(sessionId);
|
|
@@ -30001,8 +32180,15 @@ ${guide}`);
|
|
|
30001
32180
|
} catch {
|
|
30002
32181
|
}
|
|
30003
32182
|
try {
|
|
30004
|
-
|
|
30005
|
-
|
|
32183
|
+
if (ctx.config.usage.enabled) {
|
|
32184
|
+
const { line: line2 } = usageBrief(db, ctx, { sessionId });
|
|
32185
|
+
if (line2) parts.push(`eminiar usage \u2014 ${line2}`);
|
|
32186
|
+
}
|
|
32187
|
+
} catch {
|
|
32188
|
+
}
|
|
32189
|
+
try {
|
|
32190
|
+
const onboarding = join14(memoriesDir(ctx), "onboarding");
|
|
32191
|
+
const onboarded = existsSync10(onboarding) && readdirSync4(onboarding).some((f) => f.endsWith(".md"));
|
|
30006
32192
|
if (!onboarded)
|
|
30007
32193
|
parts.push(
|
|
30008
32194
|
"No onboarding memories yet: run /eminiar:em-onboard once so future sessions start with the project's structure, build and test commands."
|
|
@@ -30014,7 +32200,7 @@ ${guide}`);
|
|
|
30014
32200
|
} catch {
|
|
30015
32201
|
}
|
|
30016
32202
|
try {
|
|
30017
|
-
if (
|
|
32203
|
+
if (existsSync10(join14(ctx.root, ".git")) && !db.metaGet("history_files"))
|
|
30018
32204
|
parts.push(
|
|
30019
32205
|
'Git history is not classified yet: em_history {action:"overview"} builds it (the server also does this in the background shortly after start); bug-prone files are flagged before you read or edit them once it exists.'
|
|
30020
32206
|
);
|