ccc-notifier 0.3.0 → 0.4.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/README.md +6 -4
- package/dist/{budget-KKTFYAXH.js → budget-EHWPEYXY.js} +1 -1
- package/dist/{chunk-ECADO26T.js → chunk-26CISNOE.js} +10 -2
- package/dist/{chunk-QX5KIRSU.js → chunk-2VPBBIDW.js} +167 -53
- package/dist/{chunk-7KVOQ4UZ.js → chunk-5LHZOZZO.js} +1 -1
- package/dist/{chunk-DSV75EF7.js → chunk-CKVK3UCA.js} +1 -1
- package/dist/chunk-J6Y3RMMC.js +172 -0
- package/dist/{chunk-4RZ6OGTD.js → chunk-TUZVISLD.js} +1 -1
- package/dist/chunk-ZHIZZ6V5.js +92 -0
- package/dist/cli.js +13 -13
- package/dist/{dashboard-TH3NOVT3.js → dashboard-GYKABTTN.js} +4 -2
- package/dist/history-6OUJLXJT.js +162 -0
- package/dist/{mute-EPLYH67P.js → mute-GBDNN5PB.js} +2 -2
- package/dist/{setup-PTO5N46B.js → setup-W3CSTHJC.js} +2 -2
- package/dist/{sweep-25EZAOGC.js → sweep-IBSV4LRD.js} +134 -102
- package/dist/track-QT2U3E2R.js +235 -0
- package/package.json +1 -1
- package/dist/history-DLFYQJFM.js +0 -114
- package/dist/track-PXFSGMGL.js +0 -184
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
collectSubagentUsage,
|
|
8
8
|
splitIntoCodexTurnDrafts
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-CKVK3UCA.js";
|
|
10
10
|
import {
|
|
11
11
|
computeCost,
|
|
12
12
|
extractBucket,
|
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
loadPriceTable,
|
|
15
15
|
promptCandidate
|
|
16
16
|
} from "./chunk-LHKBGA5K.js";
|
|
17
|
+
import {
|
|
18
|
+
waitForDataLock
|
|
19
|
+
} from "./chunk-J6Y3RMMC.js";
|
|
17
20
|
import {
|
|
18
21
|
formatJPY,
|
|
19
22
|
formatUSD,
|
|
@@ -27,7 +30,7 @@ import {
|
|
|
27
30
|
readConfig,
|
|
28
31
|
sanitizeCursor,
|
|
29
32
|
saveCursor
|
|
30
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-26CISNOE.js";
|
|
31
34
|
|
|
32
35
|
// src/sweep.ts
|
|
33
36
|
import { readFile } from "fs/promises";
|
|
@@ -258,80 +261,90 @@ function mergeUnknownModels(rec, extra) {
|
|
|
258
261
|
for (const m of extra) if (!merged.includes(m)) merged.push(m);
|
|
259
262
|
rec.unknownModels = merged;
|
|
260
263
|
}
|
|
261
|
-
async function processTranscript(mainPath, table, fx, daysCutoff, dryRun, summary) {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (daysCutoff !== null) {
|
|
268
|
-
const tsMs = Date.parse(ts);
|
|
269
|
-
if (!Number.isFinite(tsMs) || tsMs < daysCutoff) continue;
|
|
270
|
-
}
|
|
271
|
-
records.push(draftToRecord(draft, ts, table, fx));
|
|
264
|
+
async function processTranscript(mainPath, table, fx, daysCutoff, dryRun, summary, lockProvider) {
|
|
265
|
+
const lock = dryRun ? null : await lockProvider();
|
|
266
|
+
if (!dryRun && lock === null) {
|
|
267
|
+
logError("sweep:data-lock", new Error(`data lock timeout: ${mainPath}`));
|
|
268
|
+
summary.lockTimeouts += 1;
|
|
269
|
+
return;
|
|
272
270
|
}
|
|
273
|
-
let sa = null;
|
|
274
271
|
try {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
tokens: emptyBuckets(),
|
|
304
|
-
sidechainTokens: null,
|
|
305
|
-
apiCalls: 0,
|
|
306
|
-
costUSD: 0,
|
|
307
|
-
costByModel: {},
|
|
308
|
-
costJPY: 0,
|
|
309
|
-
fxRate: fx.rate,
|
|
310
|
-
fxSource: fx.source,
|
|
311
|
-
prompt: "",
|
|
312
|
-
subagents: saBlock,
|
|
313
|
-
ingest: "sweep"
|
|
272
|
+
const cursor = sanitizeCursor(loadCursor(mainPath));
|
|
273
|
+
const { drafts, newCursor } = await splitIntoTurnDrafts(mainPath, cursor);
|
|
274
|
+
const records = [];
|
|
275
|
+
for (const draft of drafts) {
|
|
276
|
+
const ts = draft.lastTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
277
|
+
if (daysCutoff !== null) {
|
|
278
|
+
const tsMs = Date.parse(ts);
|
|
279
|
+
if (!Number.isFinite(tsMs) || tsMs < daysCutoff) continue;
|
|
280
|
+
}
|
|
281
|
+
records.push(draftToRecord(draft, ts, table, fx));
|
|
282
|
+
}
|
|
283
|
+
let sa = null;
|
|
284
|
+
try {
|
|
285
|
+
sa = await collectSubagentUsage(mainPath);
|
|
286
|
+
} catch (err) {
|
|
287
|
+
logError("sweep:subagents", err);
|
|
288
|
+
sa = null;
|
|
289
|
+
}
|
|
290
|
+
const saHasUsage = sa !== null && sa.apiCalls > 0;
|
|
291
|
+
if (saHasUsage) {
|
|
292
|
+
const saBreakdown = computeCost(sa.perModel, {}, table);
|
|
293
|
+
summary.subagentsUSD += saBreakdown.usd;
|
|
294
|
+
const saBlock = {
|
|
295
|
+
costUSD: saBreakdown.usd,
|
|
296
|
+
costByModel: saBreakdown.byModel,
|
|
297
|
+
tokens: sumBuckets(sa.perModel),
|
|
298
|
+
apiCalls: sa.apiCalls,
|
|
299
|
+
agentFiles: sa.agentFiles
|
|
314
300
|
};
|
|
315
|
-
|
|
316
|
-
|
|
301
|
+
if (records.length > 0) {
|
|
302
|
+
const last = records[records.length - 1];
|
|
303
|
+
last.subagents = saBlock;
|
|
304
|
+
mergeUnknownModels(last, saBreakdown.unknownModels);
|
|
305
|
+
} else {
|
|
306
|
+
const rec = {
|
|
307
|
+
schemaVersion: 1,
|
|
308
|
+
ts: maxCursorTs(sa.newCursors) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
309
|
+
sessionId: "",
|
|
310
|
+
project: "",
|
|
311
|
+
gitBranch: null,
|
|
312
|
+
models: collectModels(sa.perModel, {}),
|
|
313
|
+
tokens: emptyBuckets(),
|
|
314
|
+
sidechainTokens: null,
|
|
315
|
+
apiCalls: 0,
|
|
316
|
+
costUSD: 0,
|
|
317
|
+
costByModel: {},
|
|
318
|
+
costJPY: 0,
|
|
319
|
+
fxRate: fx.rate,
|
|
320
|
+
fxSource: fx.source,
|
|
321
|
+
prompt: "",
|
|
322
|
+
subagents: saBlock,
|
|
323
|
+
ingest: "sweep"
|
|
324
|
+
};
|
|
325
|
+
mergeUnknownModels(rec, saBreakdown.unknownModels);
|
|
326
|
+
records.push(rec);
|
|
327
|
+
}
|
|
328
|
+
summary.agentFiles += sa.agentFiles;
|
|
317
329
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
summary.byModel[m] = (summary.byModel[m] ?? 0) + c;
|
|
330
|
+
for (const rec of records) {
|
|
331
|
+
summary.newRecords += 1;
|
|
332
|
+
summary.totalUSD += rec.costUSD;
|
|
333
|
+
if (rec.costByModel) {
|
|
334
|
+
for (const [m, c] of Object.entries(rec.costByModel)) {
|
|
335
|
+
summary.byModel[m] = (summary.byModel[m] ?? 0) + c;
|
|
336
|
+
}
|
|
326
337
|
}
|
|
327
338
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
339
|
+
if (!dryRun) {
|
|
340
|
+
for (const rec of records) appendTurn(rec);
|
|
341
|
+
if (drafts.length > 0) saveCursor(mainPath, newCursor);
|
|
342
|
+
if (saHasUsage) {
|
|
343
|
+
for (const nc of sa.newCursors) saveCursor(nc.path, nc.cursor);
|
|
344
|
+
}
|
|
334
345
|
}
|
|
346
|
+
} finally {
|
|
347
|
+
lock?.release();
|
|
335
348
|
}
|
|
336
349
|
}
|
|
337
350
|
var CODEX_MAX_DEPTH = 4;
|
|
@@ -379,33 +392,43 @@ function codexDraftToRecord(draft, ts, table, fx) {
|
|
|
379
392
|
if (breakdown.unknownModels.length > 0) rec.unknownModels = breakdown.unknownModels;
|
|
380
393
|
return rec;
|
|
381
394
|
}
|
|
382
|
-
async function processCodexRollout(rolloutPath, table, fx, daysCutoff, dryRun, summary) {
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const ts = draft.endTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
389
|
-
if (daysCutoff !== null) {
|
|
390
|
-
const tsMs = Date.parse(ts);
|
|
391
|
-
if (!Number.isFinite(tsMs) || tsMs < daysCutoff) continue;
|
|
392
|
-
}
|
|
393
|
-
records.push(codexDraftToRecord(draft, ts, table, fx));
|
|
395
|
+
async function processCodexRollout(rolloutPath, table, fx, daysCutoff, dryRun, summary, lockProvider) {
|
|
396
|
+
const lock = dryRun ? null : await lockProvider();
|
|
397
|
+
if (!dryRun && lock === null) {
|
|
398
|
+
logError("sweep:data-lock", new Error(`data lock timeout: ${rolloutPath}`));
|
|
399
|
+
summary.lockTimeouts += 1;
|
|
400
|
+
return;
|
|
394
401
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
402
|
+
try {
|
|
403
|
+
const cursor = sanitizeCursor(loadCursor(rolloutPath));
|
|
404
|
+
const drafts = await splitIntoCodexTurnDrafts(rolloutPath, cursor);
|
|
405
|
+
if (drafts === null || drafts.length === 0) return;
|
|
406
|
+
const records = [];
|
|
407
|
+
for (const draft of drafts) {
|
|
408
|
+
const ts = draft.endTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
409
|
+
if (daysCutoff !== null) {
|
|
410
|
+
const tsMs = Date.parse(ts);
|
|
411
|
+
if (!Number.isFinite(tsMs) || tsMs < daysCutoff) continue;
|
|
403
412
|
}
|
|
413
|
+
records.push(codexDraftToRecord(draft, ts, table, fx));
|
|
404
414
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
415
|
+
for (const rec of records) {
|
|
416
|
+
summary.newRecords += 1;
|
|
417
|
+
summary.totalUSD += rec.costUSD;
|
|
418
|
+
summary.codexRecords += 1;
|
|
419
|
+
summary.codexUSD += rec.costUSD;
|
|
420
|
+
if (rec.costByModel) {
|
|
421
|
+
for (const [m, c] of Object.entries(rec.costByModel)) {
|
|
422
|
+
summary.byModel[m] = (summary.byModel[m] ?? 0) + c;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (!dryRun) {
|
|
427
|
+
for (const rec of records) appendTurn(rec);
|
|
428
|
+
saveCursor(rolloutPath, drafts[drafts.length - 1].agg.newCursor);
|
|
429
|
+
}
|
|
430
|
+
} finally {
|
|
431
|
+
lock?.release();
|
|
409
432
|
}
|
|
410
433
|
}
|
|
411
434
|
async function codexSessionsRoot() {
|
|
@@ -414,7 +437,7 @@ async function codexSessionsRoot() {
|
|
|
414
437
|
const isDir = await fsp.stat(sessionsRoot).then((st) => st.isDirectory()).catch(() => false);
|
|
415
438
|
return isDir ? sessionsRoot : null;
|
|
416
439
|
}
|
|
417
|
-
async function sweepCodex(summary, table, fx, daysCutoff, flags) {
|
|
440
|
+
async function sweepCodex(summary, table, fx, daysCutoff, flags, lockProvider) {
|
|
418
441
|
const sessionsRoot = await codexSessionsRoot();
|
|
419
442
|
if (sessionsRoot === null) return;
|
|
420
443
|
const rollouts = await listCodexRollouts(sessionsRoot);
|
|
@@ -424,7 +447,7 @@ async function sweepCodex(summary, table, fx, daysCutoff, flags) {
|
|
|
424
447
|
continue;
|
|
425
448
|
}
|
|
426
449
|
try {
|
|
427
|
-
await processCodexRollout(rolloutPath, table, fx, daysCutoff, flags.dryRun, summary);
|
|
450
|
+
await processCodexRollout(rolloutPath, table, fx, daysCutoff, flags.dryRun, summary, lockProvider);
|
|
428
451
|
} catch (err) {
|
|
429
452
|
logError("sweep:codex", err);
|
|
430
453
|
}
|
|
@@ -489,7 +512,12 @@ function printSweepSummary(summary, fx) {
|
|
|
489
512
|
`\u30B9\u30AD\u30C3\u30D7: ${summary.skippedActive} transcript(\u76F4\u8FD1${ACTIVE_GUARD_MIN}\u5206\u4EE5\u5185\u306B\u66F4\u65B0 = \u9032\u884C\u4E2D\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u53EF\u80FD\u6027)\u3002\u30BB\u30C3\u30B7\u30E7\u30F3\u5B8C\u4E86\u5F8C\u306B\u518D\u5B9F\u884C\u3059\u308B\u304B\u3001\u5B8C\u4E86\u6E08\u307F\u3068\u5206\u304B\u3063\u3066\u3044\u308B\u5834\u5408\u306F --include-active \u3067\u53D6\u308A\u8FBC\u3081\u307E\u3059`
|
|
490
513
|
);
|
|
491
514
|
}
|
|
492
|
-
if (summary.
|
|
515
|
+
if (summary.lockTimeouts > 0) {
|
|
516
|
+
console.log(
|
|
517
|
+
`\u672A\u5B8C\u4E86: ${summary.lockTimeouts} \u4EF6\u306Fdata lock\u3092\u53D6\u5F97\u3067\u304D\u305A\u672A\u51E6\u7406\u3067\u3059\u3002\u30AB\u30FC\u30BD\u30EB\u306F\u9032\u3081\u3066\u3044\u306A\u3044\u305F\u3081\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
if (summary.newRecords === 0 && summary.lockTimeouts === 0) {
|
|
493
521
|
console.log("\u65B0\u898F\u306F\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F");
|
|
494
522
|
} else {
|
|
495
523
|
console.log(
|
|
@@ -513,10 +541,12 @@ function printSweepSummary(summary, fx) {
|
|
|
513
541
|
}
|
|
514
542
|
}
|
|
515
543
|
}
|
|
516
|
-
|
|
544
|
+
if (summary.lockTimeouts === 0) {
|
|
545
|
+
console.log("\u65E2\u306B\u8A08\u4E0A\u6E08\u307F\u306E\u5206\u306F\u30B9\u30AD\u30C3\u30D7\u3055\u308C\u307E\u3057\u305F(\u4E8C\u91CD\u8A08\u4E0A\u306A\u3057)");
|
|
546
|
+
}
|
|
517
547
|
console.log(`\u5186\u63DB\u7B97\u30EC\u30FC\u30C8: 1USD = ${fx.rate}JPY(source=${fx.source})`);
|
|
518
548
|
}
|
|
519
|
-
async function runSweep(argv) {
|
|
549
|
+
async function runSweep(argv, deps = {}) {
|
|
520
550
|
const flags = parseSweepFlags(argv);
|
|
521
551
|
const root = projectsRoot(flags.projects);
|
|
522
552
|
const projectDirs = await listProjectDirs(root);
|
|
@@ -543,9 +573,11 @@ async function runSweep(argv) {
|
|
|
543
573
|
skippedActive: 0,
|
|
544
574
|
codexRecords: 0,
|
|
545
575
|
codexUSD: 0,
|
|
546
|
-
dryRun: flags.dryRun
|
|
576
|
+
dryRun: flags.dryRun,
|
|
577
|
+
lockTimeouts: 0
|
|
547
578
|
};
|
|
548
579
|
const daysCutoff = flags.days !== null ? Date.now() - flags.days * DAY_MS : null;
|
|
580
|
+
const lockProvider = deps.lockProvider ?? (() => waitForDataLock());
|
|
549
581
|
for (const projectDir of projectDirs ?? []) {
|
|
550
582
|
const transcripts = await listTranscripts(projectDir);
|
|
551
583
|
for (const mainPath of transcripts) {
|
|
@@ -555,16 +587,16 @@ async function runSweep(argv) {
|
|
|
555
587
|
continue;
|
|
556
588
|
}
|
|
557
589
|
try {
|
|
558
|
-
await processTranscript(mainPath, table, fx, daysCutoff, flags.dryRun, summary);
|
|
590
|
+
await processTranscript(mainPath, table, fx, daysCutoff, flags.dryRun, summary, lockProvider);
|
|
559
591
|
} catch (err) {
|
|
560
592
|
logError("sweep:transcript", err);
|
|
561
593
|
}
|
|
562
594
|
}
|
|
563
595
|
}
|
|
564
|
-
await sweepCodex(summary, table, fx, daysCutoff, flags);
|
|
596
|
+
await sweepCodex(summary, table, fx, daysCutoff, flags, lockProvider);
|
|
565
597
|
summary.totalJPY = summary.totalUSD * fx.rate;
|
|
566
598
|
printSweepSummary(summary, fx);
|
|
567
|
-
return 0;
|
|
599
|
+
return summary.lockTimeouts > 0 ? 1 : 0;
|
|
568
600
|
}
|
|
569
601
|
export {
|
|
570
602
|
runSweep,
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
notifyOS,
|
|
4
|
+
notifySlack
|
|
5
|
+
} from "./chunk-NV5UOHJA.js";
|
|
6
|
+
import {
|
|
7
|
+
writeDashboardHtml
|
|
8
|
+
} from "./chunk-2VPBBIDW.js";
|
|
9
|
+
import "./chunk-DGXUSPS4.js";
|
|
10
|
+
import {
|
|
11
|
+
aggregateCodexTurn,
|
|
12
|
+
collectSubagentUsage
|
|
13
|
+
} from "./chunk-CKVK3UCA.js";
|
|
14
|
+
import {
|
|
15
|
+
aggregateNewTurn,
|
|
16
|
+
computeCost,
|
|
17
|
+
getUsdJpy,
|
|
18
|
+
loadPriceTable
|
|
19
|
+
} from "./chunk-LHKBGA5K.js";
|
|
20
|
+
import {
|
|
21
|
+
isFullDashboardDue,
|
|
22
|
+
makeFullDashboardState,
|
|
23
|
+
writeFullDashboardStateAtomic
|
|
24
|
+
} from "./chunk-ZHIZZ6V5.js";
|
|
25
|
+
import {
|
|
26
|
+
waitForDataLock
|
|
27
|
+
} from "./chunk-J6Y3RMMC.js";
|
|
28
|
+
import "./chunk-J5QAYTFE.js";
|
|
29
|
+
import {
|
|
30
|
+
appendTurn,
|
|
31
|
+
isMuted,
|
|
32
|
+
loadCursor,
|
|
33
|
+
logError,
|
|
34
|
+
paths,
|
|
35
|
+
readConfig,
|
|
36
|
+
readTurns,
|
|
37
|
+
sanitizeCursor,
|
|
38
|
+
saveCursor,
|
|
39
|
+
todayTotalUSD
|
|
40
|
+
} from "./chunk-26CISNOE.js";
|
|
41
|
+
|
|
42
|
+
// src/track.ts
|
|
43
|
+
function isRecord(v) {
|
|
44
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
45
|
+
}
|
|
46
|
+
function emptyBuckets() {
|
|
47
|
+
return { input: 0, output: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheRead: 0 };
|
|
48
|
+
}
|
|
49
|
+
function sumBuckets(usage) {
|
|
50
|
+
const total = emptyBuckets();
|
|
51
|
+
for (const b of Object.values(usage)) {
|
|
52
|
+
total.input += b.input;
|
|
53
|
+
total.output += b.output;
|
|
54
|
+
total.cacheWrite5m += b.cacheWrite5m;
|
|
55
|
+
total.cacheWrite1h += b.cacheWrite1h;
|
|
56
|
+
total.cacheRead += b.cacheRead;
|
|
57
|
+
}
|
|
58
|
+
return total;
|
|
59
|
+
}
|
|
60
|
+
function collectModels(main, sidechain) {
|
|
61
|
+
const models = [];
|
|
62
|
+
for (const m of Object.keys(main)) {
|
|
63
|
+
if (!models.includes(m)) models.push(m);
|
|
64
|
+
}
|
|
65
|
+
for (const m of Object.keys(sidechain)) {
|
|
66
|
+
if (!models.includes(m)) models.push(m);
|
|
67
|
+
}
|
|
68
|
+
return models;
|
|
69
|
+
}
|
|
70
|
+
function withCodexModel(agg, payloadModel) {
|
|
71
|
+
const model = typeof payloadModel === "string" && payloadModel.length > 0 ? payloadModel : null;
|
|
72
|
+
if (model === null) return agg;
|
|
73
|
+
const buckets = Object.values(agg.main)[0] ?? emptyBuckets();
|
|
74
|
+
return { ...agg, main: { [model]: buckets } };
|
|
75
|
+
}
|
|
76
|
+
async function runTrack(stdinText, opts) {
|
|
77
|
+
try {
|
|
78
|
+
let parsed;
|
|
79
|
+
try {
|
|
80
|
+
parsed = JSON.parse(stdinText);
|
|
81
|
+
} catch {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!isRecord(parsed)) return;
|
|
85
|
+
const input = parsed;
|
|
86
|
+
const transcriptPath = input.transcript_path;
|
|
87
|
+
if (typeof transcriptPath !== "string") return;
|
|
88
|
+
const cfg = readConfig();
|
|
89
|
+
const cacheDir = paths().cacheDir;
|
|
90
|
+
const table = await loadPriceTable(cacheDir, { offline: true });
|
|
91
|
+
const fx = await getUsdJpy(cfg, cacheDir);
|
|
92
|
+
const isCodex = opts?.codex === true;
|
|
93
|
+
let record;
|
|
94
|
+
const commitLock = await waitForDataLock(1e3);
|
|
95
|
+
if (commitLock === null) {
|
|
96
|
+
logError("track:data-lock", new Error("data lock timeout; turn was not consumed"));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const cursor = sanitizeCursor(loadCursor(transcriptPath));
|
|
101
|
+
let agg = isCodex ? await aggregateCodexTurn(transcriptPath, cursor) : await aggregateNewTurn(transcriptPath, cursor);
|
|
102
|
+
if (agg === null) return;
|
|
103
|
+
if (isCodex) {
|
|
104
|
+
agg = withCodexModel(agg, input.model);
|
|
105
|
+
}
|
|
106
|
+
let sa = null;
|
|
107
|
+
if (!isCodex) {
|
|
108
|
+
try {
|
|
109
|
+
sa = await collectSubagentUsage(transcriptPath);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
logError("track:subagents", err);
|
|
112
|
+
sa = null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const breakdown = computeCost(agg.main, agg.sidechain, table);
|
|
116
|
+
const sessionId = agg.sessionId || (typeof input.session_id === "string" ? input.session_id : "") || "";
|
|
117
|
+
const project = agg.cwd ?? (typeof input.cwd === "string" ? input.cwd : void 0) ?? "";
|
|
118
|
+
const sidechainHasModels = Object.keys(agg.sidechain).length > 0;
|
|
119
|
+
record = {
|
|
120
|
+
schemaVersion: 1,
|
|
121
|
+
ts: agg.lastTs ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
122
|
+
sessionId,
|
|
123
|
+
project,
|
|
124
|
+
gitBranch: agg.gitBranch,
|
|
125
|
+
models: collectModels(agg.main, agg.sidechain),
|
|
126
|
+
tokens: sumBuckets(agg.main),
|
|
127
|
+
sidechainTokens: sidechainHasModels ? sumBuckets(agg.sidechain) : null,
|
|
128
|
+
apiCalls: agg.apiCalls,
|
|
129
|
+
costUSD: breakdown.usd,
|
|
130
|
+
costByModel: breakdown.byModel,
|
|
131
|
+
// モデル別 USD(main+sidechain 合算、丸めない)
|
|
132
|
+
costJPY: breakdown.usd * fx.rate,
|
|
133
|
+
// 丸めない(表示時に丸める)
|
|
134
|
+
fxRate: fx.rate,
|
|
135
|
+
fxSource: fx.source,
|
|
136
|
+
prompt: agg.prompt ?? ""
|
|
137
|
+
};
|
|
138
|
+
if (isCodex) {
|
|
139
|
+
record.source = "codex";
|
|
140
|
+
}
|
|
141
|
+
if (breakdown.unknownModels.length > 0) {
|
|
142
|
+
record.unknownModels = breakdown.unknownModels;
|
|
143
|
+
}
|
|
144
|
+
if (sa !== null && sa.apiCalls > 0) {
|
|
145
|
+
const saBreakdown = computeCost(sa.perModel, {}, table);
|
|
146
|
+
record.subagents = {
|
|
147
|
+
costUSD: saBreakdown.usd,
|
|
148
|
+
costByModel: saBreakdown.byModel,
|
|
149
|
+
tokens: sumBuckets(sa.perModel),
|
|
150
|
+
apiCalls: sa.apiCalls,
|
|
151
|
+
agentFiles: sa.agentFiles
|
|
152
|
+
};
|
|
153
|
+
if (saBreakdown.unknownModels.length > 0) {
|
|
154
|
+
const merged = record.unknownModels ? [...record.unknownModels] : [];
|
|
155
|
+
for (const m of saBreakdown.unknownModels) {
|
|
156
|
+
if (!merged.includes(m)) merged.push(m);
|
|
157
|
+
}
|
|
158
|
+
record.unknownModels = merged;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
appendTurn(record);
|
|
162
|
+
saveCursor(transcriptPath, agg.newCursor);
|
|
163
|
+
if (sa !== null) {
|
|
164
|
+
for (const nc of sa.newCursors) {
|
|
165
|
+
saveCursor(nc.path, nc.cursor);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} finally {
|
|
169
|
+
commitLock.release();
|
|
170
|
+
}
|
|
171
|
+
const tasks = [];
|
|
172
|
+
if ((cfg.notify.os || cfg.notify.slack !== null) && record.costUSD >= cfg.minNotifyUSD && !isMuted()) {
|
|
173
|
+
const todayUSD = cfg.includeDailyTotal ? todayTotalUSD() : void 0;
|
|
174
|
+
tasks.push(notifyOS(record, cfg, todayUSD));
|
|
175
|
+
tasks.push(notifySlack(record, cfg, todayUSD));
|
|
176
|
+
}
|
|
177
|
+
if (cfg.dashboard.autoRegenerate) {
|
|
178
|
+
tasks.push(
|
|
179
|
+
(async () => {
|
|
180
|
+
const now = /* @__PURE__ */ new Date();
|
|
181
|
+
const dashboardLock = await waitForDataLock(1e3);
|
|
182
|
+
if (dashboardLock === null) {
|
|
183
|
+
logError("track:dashboard-lock", new Error("data lock timeout; dashboard skipped"));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
let allTurns;
|
|
188
|
+
try {
|
|
189
|
+
allTurns = readTurns();
|
|
190
|
+
} catch (err) {
|
|
191
|
+
logError("track:dashboard-read", err);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
writeDashboardHtml({
|
|
196
|
+
days: cfg.dashboard.days,
|
|
197
|
+
outPath: paths().recentDashboardFile,
|
|
198
|
+
autoReloadSec: cfg.dashboard.autoReloadSec,
|
|
199
|
+
allTurns,
|
|
200
|
+
variant: "recent"
|
|
201
|
+
});
|
|
202
|
+
} catch (err) {
|
|
203
|
+
logError("track:dashboard-recent", err);
|
|
204
|
+
}
|
|
205
|
+
if (isFullDashboardDue(now)) {
|
|
206
|
+
try {
|
|
207
|
+
writeDashboardHtml({
|
|
208
|
+
days: null,
|
|
209
|
+
outPath: paths().fullDashboardFile,
|
|
210
|
+
autoReloadSec: cfg.dashboard.autoReloadSec,
|
|
211
|
+
allTurns,
|
|
212
|
+
variant: "full",
|
|
213
|
+
generatedAt: now.toISOString()
|
|
214
|
+
});
|
|
215
|
+
writeFullDashboardStateAtomic(makeFullDashboardState(now));
|
|
216
|
+
} catch (err) {
|
|
217
|
+
logError("track:dashboard-full", err);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} finally {
|
|
221
|
+
dashboardLock.release();
|
|
222
|
+
}
|
|
223
|
+
})()
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
if (tasks.length > 0) {
|
|
227
|
+
await Promise.allSettled(tasks);
|
|
228
|
+
}
|
|
229
|
+
} catch (err) {
|
|
230
|
+
logError("track", err);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
export {
|
|
234
|
+
runTrack
|
|
235
|
+
};
|
package/package.json
CHANGED