ccc-notifier 0.4.0 → 0.5.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 +5 -3
- package/dist/{budget-EHWPEYXY.js → budget-TBWVHUKA.js} +1 -1
- package/dist/chunk-5PH7PPD6.js +1077 -0
- package/dist/{chunk-TUZVISLD.js → chunk-DDUK4EWQ.js} +1 -1
- package/dist/{chunk-ZHIZZ6V5.js → chunk-ENGUOLTD.js} +1 -1
- package/dist/chunk-HLJAJS2W.js +55 -0
- package/dist/{chunk-LHKBGA5K.js → chunk-LVMKY6JB.js} +304 -29
- package/dist/chunk-O2AR4PXU.js +82 -0
- package/dist/{chunk-J6Y3RMMC.js → chunk-O34L3NSI.js} +1 -1
- package/dist/{chunk-2VPBBIDW.js → chunk-QWBQN4XY.js} +24 -9
- package/dist/{chunk-5LHZOZZO.js → chunk-S3XKO7MY.js} +235 -108
- package/dist/cli.js +381 -72
- package/dist/{dashboard-GYKABTTN.js → dashboard-6F2QNUJT.js} +4 -4
- package/dist/{history-6OUJLXJT.js → history-7KN26LJH.js} +3 -3
- package/dist/{mute-GBDNN5PB.js → mute-MPX3K7QN.js} +2 -2
- package/dist/{setup-W3CSTHJC.js → setup-IXPMVWOE.js} +2 -2
- package/dist/subagent-store-A6MIN22X.js +31 -0
- package/dist/{sweep-IBSV4LRD.js → sweep-UIONM4UA.js} +14 -29
- package/dist/{track-QT2U3E2R.js → track-KEZON6KI.js} +19 -9
- package/package.json +1 -1
- package/dist/chunk-26CISNOE.js +0 -324
- package/dist/chunk-CKVK3UCA.js +0 -357
package/dist/chunk-CKVK3UCA.js
DELETED
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
aggregateNewTurn
|
|
4
|
-
} from "./chunk-LHKBGA5K.js";
|
|
5
|
-
import {
|
|
6
|
-
loadCursor,
|
|
7
|
-
logError,
|
|
8
|
-
sanitizeCursor
|
|
9
|
-
} from "./chunk-26CISNOE.js";
|
|
10
|
-
|
|
11
|
-
// src/codex/transcript.ts
|
|
12
|
-
import { readFile } from "fs/promises";
|
|
13
|
-
import { basename } from "path";
|
|
14
|
-
var NEWLINE = 10;
|
|
15
|
-
function isRecord(v) {
|
|
16
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
17
|
-
}
|
|
18
|
-
function numOf(v) {
|
|
19
|
-
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
20
|
-
}
|
|
21
|
-
function strOrNull(v) {
|
|
22
|
-
return typeof v === "string" ? v : null;
|
|
23
|
-
}
|
|
24
|
-
function zeroTotals() {
|
|
25
|
-
return { input: 0, cached: 0, output: 0 };
|
|
26
|
-
}
|
|
27
|
-
function isZeroTotals(t) {
|
|
28
|
-
return t.input === 0 && t.cached === 0 && t.output === 0;
|
|
29
|
-
}
|
|
30
|
-
function addTotals(target, d) {
|
|
31
|
-
target.input += d.input;
|
|
32
|
-
target.cached += d.cached;
|
|
33
|
-
target.output += d.output;
|
|
34
|
-
}
|
|
35
|
-
function readTotals(v) {
|
|
36
|
-
if (!isRecord(v)) return null;
|
|
37
|
-
return {
|
|
38
|
-
input: numOf(v.input_tokens),
|
|
39
|
-
cached: numOf(v.cached_input_tokens),
|
|
40
|
-
output: numOf(v.output_tokens)
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
function totalsToBuckets(acc) {
|
|
44
|
-
return {
|
|
45
|
-
input: Math.max(0, acc.input - acc.cached),
|
|
46
|
-
output: acc.output,
|
|
47
|
-
cacheWrite5m: 0,
|
|
48
|
-
cacheWrite1h: 0,
|
|
49
|
-
cacheRead: acc.cached
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
function sessionIdFromFilename(rolloutPath) {
|
|
53
|
-
const m = /^rollout-.*-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i.exec(
|
|
54
|
-
basename(rolloutPath)
|
|
55
|
-
);
|
|
56
|
-
return m !== null ? m[1] : "";
|
|
57
|
-
}
|
|
58
|
-
async function readAll(path) {
|
|
59
|
-
try {
|
|
60
|
-
return await readFile(path);
|
|
61
|
-
} catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function newSegmentBuf() {
|
|
66
|
-
return {
|
|
67
|
-
acc: zeroTotals(),
|
|
68
|
-
apiCalls: 0,
|
|
69
|
-
prompt: null,
|
|
70
|
-
turnCtxCwd: null,
|
|
71
|
-
firstTs: null,
|
|
72
|
-
endTs: null,
|
|
73
|
-
hasLines: false
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
async function scanWindow(rolloutPath, cursor) {
|
|
77
|
-
const buffer = await readAll(rolloutPath);
|
|
78
|
-
if (buffer === null) return null;
|
|
79
|
-
const fileSize = buffer.length;
|
|
80
|
-
let startOffset;
|
|
81
|
-
let rescan;
|
|
82
|
-
if (cursor !== null && cursor.offset > 0 && cursor.offset <= fileSize && buffer[cursor.offset - 1] === NEWLINE) {
|
|
83
|
-
startOffset = cursor.offset;
|
|
84
|
-
rescan = false;
|
|
85
|
-
} else {
|
|
86
|
-
startOffset = 0;
|
|
87
|
-
rescan = cursor !== null;
|
|
88
|
-
}
|
|
89
|
-
const tsFloor = cursor?.lastTs ?? null;
|
|
90
|
-
const initTotals = cursor?.codexTotals;
|
|
91
|
-
let prev = initTotals !== void 0 ? { ...initTotals } : zeroTotals();
|
|
92
|
-
const acc = zeroTotals();
|
|
93
|
-
let apiCalls = 0;
|
|
94
|
-
let lastModel = null;
|
|
95
|
-
let windowPrompt = null;
|
|
96
|
-
let windowTurnCtxCwd = null;
|
|
97
|
-
let sessionMetaCwd = null;
|
|
98
|
-
let sessionMetaSid = null;
|
|
99
|
-
let firstTs = null;
|
|
100
|
-
let lastTs = null;
|
|
101
|
-
const segments = [];
|
|
102
|
-
let seg = newSegmentBuf();
|
|
103
|
-
const snapshotSegment = (endOffset) => ({
|
|
104
|
-
acc: seg.acc,
|
|
105
|
-
apiCalls: seg.apiCalls,
|
|
106
|
-
prompt: seg.prompt,
|
|
107
|
-
model: lastModel,
|
|
108
|
-
cwd: seg.turnCtxCwd ?? sessionMetaCwd,
|
|
109
|
-
firstTs: seg.firstTs,
|
|
110
|
-
endTs: seg.endTs,
|
|
111
|
-
endOffset,
|
|
112
|
-
prevAtEnd: { ...prev },
|
|
113
|
-
lastTsAtEnd: lastTs
|
|
114
|
-
});
|
|
115
|
-
const handleLine = (raw, endOffset) => {
|
|
116
|
-
if (raw.trim().length === 0) return;
|
|
117
|
-
let obj;
|
|
118
|
-
try {
|
|
119
|
-
obj = JSON.parse(raw);
|
|
120
|
-
} catch {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
if (!isRecord(obj)) return;
|
|
124
|
-
const ts = strOrNull(obj.timestamp);
|
|
125
|
-
if (rescan && tsFloor !== null && ts !== null && ts <= tsFloor) return;
|
|
126
|
-
if (ts !== null) {
|
|
127
|
-
if (firstTs === null || ts < firstTs) firstTs = ts;
|
|
128
|
-
if (lastTs === null || ts > lastTs) lastTs = ts;
|
|
129
|
-
if (seg.firstTs === null || ts < seg.firstTs) seg.firstTs = ts;
|
|
130
|
-
seg.endTs = ts;
|
|
131
|
-
}
|
|
132
|
-
seg.hasLines = true;
|
|
133
|
-
const payload = isRecord(obj.payload) ? obj.payload : null;
|
|
134
|
-
if (payload === null) return;
|
|
135
|
-
const type = obj.type;
|
|
136
|
-
if (type === "session_meta") {
|
|
137
|
-
const sid = strOrNull(payload.session_id);
|
|
138
|
-
if (sid !== null) sessionMetaSid = sid;
|
|
139
|
-
const c = strOrNull(payload.cwd);
|
|
140
|
-
if (c !== null) sessionMetaCwd = c;
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
if (type === "turn_context") {
|
|
144
|
-
const m = strOrNull(payload.model);
|
|
145
|
-
if (m !== null) lastModel = m;
|
|
146
|
-
const c = strOrNull(payload.cwd);
|
|
147
|
-
if (c !== null) {
|
|
148
|
-
seg.turnCtxCwd = c;
|
|
149
|
-
windowTurnCtxCwd = c;
|
|
150
|
-
}
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (type !== "event_msg") return;
|
|
154
|
-
const kind = payload.type;
|
|
155
|
-
if (kind === "user_message") {
|
|
156
|
-
const msg = strOrNull(payload.message);
|
|
157
|
-
if (msg !== null) {
|
|
158
|
-
seg.prompt = msg;
|
|
159
|
-
windowPrompt = msg;
|
|
160
|
-
}
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
if (kind === "token_count") {
|
|
164
|
-
const info = isRecord(payload.info) ? payload.info : null;
|
|
165
|
-
if (info === null) return;
|
|
166
|
-
const total = readTotals(info.total_token_usage);
|
|
167
|
-
if (total === null) return;
|
|
168
|
-
let step = {
|
|
169
|
-
input: total.input - prev.input,
|
|
170
|
-
cached: total.cached - prev.cached,
|
|
171
|
-
output: total.output - prev.output
|
|
172
|
-
};
|
|
173
|
-
if (step.input < 0 || step.cached < 0 || step.output < 0) {
|
|
174
|
-
step = readTotals(info.last_token_usage) ?? zeroTotals();
|
|
175
|
-
}
|
|
176
|
-
addTotals(acc, step);
|
|
177
|
-
addTotals(seg.acc, step);
|
|
178
|
-
prev = total;
|
|
179
|
-
if (!isZeroTotals(step)) {
|
|
180
|
-
apiCalls++;
|
|
181
|
-
seg.apiCalls++;
|
|
182
|
-
}
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
if (kind === "task_complete") {
|
|
186
|
-
segments.push(snapshotSegment(endOffset));
|
|
187
|
-
seg = newSegmentBuf();
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
let lineStart = startOffset;
|
|
191
|
-
for (let pos = startOffset; pos < fileSize; pos++) {
|
|
192
|
-
if (buffer[pos] !== NEWLINE) continue;
|
|
193
|
-
handleLine(buffer.toString("utf8", lineStart, pos), pos + 1);
|
|
194
|
-
lineStart = pos + 1;
|
|
195
|
-
}
|
|
196
|
-
const newOffset = lineStart;
|
|
197
|
-
const open = seg.hasLines ? snapshotSegment(newOffset) : null;
|
|
198
|
-
return {
|
|
199
|
-
segments,
|
|
200
|
-
open,
|
|
201
|
-
acc,
|
|
202
|
-
prev,
|
|
203
|
-
apiCalls,
|
|
204
|
-
model: lastModel,
|
|
205
|
-
prompt: windowPrompt,
|
|
206
|
-
cwd: windowTurnCtxCwd ?? sessionMetaCwd,
|
|
207
|
-
sessionId: sessionMetaSid ?? sessionIdFromFilename(rolloutPath),
|
|
208
|
-
firstTs,
|
|
209
|
-
lastTs,
|
|
210
|
-
newOffset
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
function windowCursor(scan) {
|
|
214
|
-
return {
|
|
215
|
-
offset: scan.newOffset,
|
|
216
|
-
lastUuid: null,
|
|
217
|
-
// rollout に uuid 行は無い
|
|
218
|
-
lastTs: scan.lastTs,
|
|
219
|
-
seenMessageKeys: [],
|
|
220
|
-
// 去重は codexTotals の差分方式が担う
|
|
221
|
-
codexTotals: { ...scan.prev }
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
async function aggregateCodexTurn(rolloutPath, cursor) {
|
|
225
|
-
const scan = await scanWindow(rolloutPath, cursor);
|
|
226
|
-
if (scan === null || isZeroTotals(scan.acc)) return null;
|
|
227
|
-
return {
|
|
228
|
-
sessionId: scan.sessionId,
|
|
229
|
-
main: { [scan.model ?? "unknown"]: totalsToBuckets(scan.acc) },
|
|
230
|
-
sidechain: {},
|
|
231
|
-
// Codex にサブエージェント概念は無い
|
|
232
|
-
apiCalls: scan.apiCalls,
|
|
233
|
-
prompt: scan.prompt,
|
|
234
|
-
cwd: scan.cwd,
|
|
235
|
-
gitBranch: null,
|
|
236
|
-
// rollout に無い
|
|
237
|
-
firstTs: scan.firstTs,
|
|
238
|
-
lastTs: scan.lastTs,
|
|
239
|
-
newCursor: windowCursor(scan)
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
async function splitIntoCodexTurnDrafts(rolloutPath, cursor) {
|
|
243
|
-
const scan = await scanWindow(rolloutPath, cursor);
|
|
244
|
-
if (scan === null || isZeroTotals(scan.acc)) return null;
|
|
245
|
-
const picked = scan.segments.filter((s) => !isZeroTotals(s.acc));
|
|
246
|
-
if (scan.open !== null && !isZeroTotals(scan.open.acc)) {
|
|
247
|
-
const last = picked[picked.length - 1];
|
|
248
|
-
if (last !== void 0) {
|
|
249
|
-
addTotals(last.acc, scan.open.acc);
|
|
250
|
-
last.apiCalls += scan.open.apiCalls;
|
|
251
|
-
if (scan.open.endTs !== null) last.endTs = scan.open.endTs;
|
|
252
|
-
} else {
|
|
253
|
-
picked.push(scan.open);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
const lastIndex = picked.length - 1;
|
|
257
|
-
return picked.map((s, i) => ({
|
|
258
|
-
agg: {
|
|
259
|
-
sessionId: scan.sessionId,
|
|
260
|
-
// session_meta はファイル先頭にしか無いので全ドラフト共通
|
|
261
|
-
main: { [s.model ?? "unknown"]: totalsToBuckets(s.acc) },
|
|
262
|
-
sidechain: {},
|
|
263
|
-
apiCalls: s.apiCalls,
|
|
264
|
-
prompt: s.prompt,
|
|
265
|
-
cwd: s.cwd,
|
|
266
|
-
gitBranch: null,
|
|
267
|
-
firstTs: s.firstTs,
|
|
268
|
-
lastTs: s.endTs,
|
|
269
|
-
// 最後のドラフトはウィンドウ全体を消費した状態(= aggregateCodexTurn の newCursor と同一。
|
|
270
|
-
// 末尾の usage ゼロな行の読み捨てもここに含まれる)。途中のドラフトはそのセグメント末尾を
|
|
271
|
-
// 指す有効な再開点(そこから読み直せば残りが差分になる)。
|
|
272
|
-
newCursor: i === lastIndex ? windowCursor(scan) : {
|
|
273
|
-
offset: s.endOffset,
|
|
274
|
-
lastUuid: null,
|
|
275
|
-
lastTs: s.lastTsAtEnd,
|
|
276
|
-
seenMessageKeys: [],
|
|
277
|
-
codexTotals: { ...s.prevAtEnd }
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
endTs: s.endTs
|
|
281
|
-
}));
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// src/subagents.ts
|
|
285
|
-
import { promises as fs } from "fs";
|
|
286
|
-
import { join } from "path";
|
|
287
|
-
var MAX_AGENT_FILES = 200;
|
|
288
|
-
function emptyBuckets() {
|
|
289
|
-
return { input: 0, output: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheRead: 0 };
|
|
290
|
-
}
|
|
291
|
-
function addToModel(target, model, b) {
|
|
292
|
-
const cur = target[model] ?? emptyBuckets();
|
|
293
|
-
cur.input += b.input;
|
|
294
|
-
cur.output += b.output;
|
|
295
|
-
cur.cacheWrite5m += b.cacheWrite5m;
|
|
296
|
-
cur.cacheWrite1h += b.cacheWrite1h;
|
|
297
|
-
cur.cacheRead += b.cacheRead;
|
|
298
|
-
target[model] = cur;
|
|
299
|
-
}
|
|
300
|
-
function mergeUsage(target, src) {
|
|
301
|
-
for (const [model, b] of Object.entries(src)) addToModel(target, model, b);
|
|
302
|
-
}
|
|
303
|
-
function subagentsDirOf(mainTranscriptPath) {
|
|
304
|
-
const base = mainTranscriptPath.endsWith(".jsonl") ? mainTranscriptPath.slice(0, -".jsonl".length) : mainTranscriptPath;
|
|
305
|
-
return join(base, "subagents");
|
|
306
|
-
}
|
|
307
|
-
async function listAgentFiles(dir, entries) {
|
|
308
|
-
const files = entries.filter((e) => e.isFile() && e.name.startsWith("agent-") && e.name.endsWith(".jsonl")).map((e) => join(dir, e.name));
|
|
309
|
-
if (files.length <= MAX_AGENT_FILES) return files;
|
|
310
|
-
const withMtime = [];
|
|
311
|
-
for (const p of files) {
|
|
312
|
-
let mtime = 0;
|
|
313
|
-
try {
|
|
314
|
-
mtime = (await fs.stat(p)).mtimeMs;
|
|
315
|
-
} catch {
|
|
316
|
-
mtime = 0;
|
|
317
|
-
}
|
|
318
|
-
withMtime.push({ path: p, mtime });
|
|
319
|
-
}
|
|
320
|
-
withMtime.sort((a, b) => b.mtime - a.mtime);
|
|
321
|
-
return withMtime.slice(0, MAX_AGENT_FILES).map((x) => x.path);
|
|
322
|
-
}
|
|
323
|
-
async function collectSubagentUsage(mainTranscriptPath) {
|
|
324
|
-
const dir = subagentsDirOf(mainTranscriptPath);
|
|
325
|
-
let entries;
|
|
326
|
-
try {
|
|
327
|
-
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
328
|
-
} catch {
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
331
|
-
const files = await listAgentFiles(dir, entries);
|
|
332
|
-
const perModel = {};
|
|
333
|
-
let apiCalls = 0;
|
|
334
|
-
let agentFiles = 0;
|
|
335
|
-
const newCursors = [];
|
|
336
|
-
for (const filePath of files) {
|
|
337
|
-
try {
|
|
338
|
-
const cursor = sanitizeCursor(loadCursor(filePath));
|
|
339
|
-
const agg = await aggregateNewTurn(filePath, cursor);
|
|
340
|
-
if (agg === null) continue;
|
|
341
|
-
mergeUsage(perModel, agg.main);
|
|
342
|
-
mergeUsage(perModel, agg.sidechain);
|
|
343
|
-
apiCalls += agg.apiCalls;
|
|
344
|
-
agentFiles += 1;
|
|
345
|
-
newCursors.push({ path: filePath, cursor: agg.newCursor });
|
|
346
|
-
} catch (err) {
|
|
347
|
-
logError("subagents:file", err);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return { perModel, apiCalls, agentFiles, newCursors };
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
export {
|
|
354
|
-
aggregateCodexTurn,
|
|
355
|
-
splitIntoCodexTurnDrafts,
|
|
356
|
-
collectSubagentUsage
|
|
357
|
-
};
|