opencode-metrics-plugin 0.3.5 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +1 -0
- package/dist/metrics/analysis/codelines.d.ts +39 -0
- package/dist/metrics/analysis/codelines.js +225 -0
- package/dist/metrics/analysis/hvigor.d.ts +7 -0
- package/dist/metrics/analysis/hvigor.js +20 -0
- package/dist/metrics/engine/engine.js +73 -1
- package/dist/metrics/engine/handlers.js +45 -15
- package/dist/metrics/engine/state.d.ts +26 -0
- package/dist/metrics/engine/state.js +11 -1
- package/dist/metrics/runtime.d.ts +11 -0
- package/dist/metrics/runtime.js +6 -0
- package/dist/metrics/sessionFilter.d.ts +12 -0
- package/dist/metrics/sessionFilter.js +104 -0
- package/dist/metrics/snapshot/flush.d.ts +14 -8
- package/dist/metrics/snapshot/flush.js +105 -4
- package/dist/metrics/snapshot/merge.js +25 -5
- package/dist/metrics/types.d.ts +38 -1
- package/dist/metrics/upload/scenarios.js +3 -1
- package/dist/metrics/upload/uploadManager.d.ts +20 -2
- package/dist/metrics/upload/uploadManager.js +49 -19
- package/dist/plugin.js +4 -1
- package/package.json +1 -1
|
@@ -353,11 +353,17 @@ function dedupePaths(paths) {
|
|
|
353
353
|
* 启动清扫暂存目录顶层:崩溃遗留的完整三件套(tar.gz + json + meta.json)
|
|
354
354
|
* 移入 failed/ 同构目录并入队重传;孤立文件与组装目录直接删除。
|
|
355
355
|
*
|
|
356
|
+
* 两遍扫描:第一遍只处理 .tar.gz(三件套校验 → rescue 或删 tar.gz,并标记
|
|
357
|
+
* 三个文件名已处理)。字母序下 .json/.meta.json 先于 .tar.gz 迭代,若单遍
|
|
358
|
+
* 边迭代边删 .json,会在 tar.gz 校验前破坏三件套——完整崩溃遗留包必然被
|
|
359
|
+
* 误判"不完整"而销毁(曾连续销毁终态包)。第二遍才清理无 tar.gz 对应的
|
|
360
|
+
* 孤儿 .json/meta 与组装残留。
|
|
361
|
+
*
|
|
356
362
|
* @param stagingDir 暂存目录
|
|
357
363
|
* @param rescueEnabled 失败保留开关(false 时完整三件套也直接删除,旧行为)
|
|
358
364
|
* @returns 可补偿重传的包列表(顶层遗留部分)
|
|
359
365
|
*/
|
|
360
|
-
function sweepStagingDirectory(stagingDir, rescueEnabled) {
|
|
366
|
+
export function sweepStagingDirectory(stagingDir, rescueEnabled) {
|
|
361
367
|
const rescued = [];
|
|
362
368
|
try {
|
|
363
369
|
const entries = fs.readdirSync(stagingDir);
|
|
@@ -365,26 +371,31 @@ function sweepStagingDirectory(stagingDir, rescueEnabled) {
|
|
|
365
371
|
const handled = new Set(["failed"]);
|
|
366
372
|
let removed = 0;
|
|
367
373
|
const removedNames = [];
|
|
374
|
+
// 第一遍:.tar.gz 三件套判定(必须先于 .json 清理,见函数注释)
|
|
368
375
|
for (const entry of entries) {
|
|
369
376
|
if (handled.has(entry))
|
|
370
377
|
continue;
|
|
371
|
-
if (entry.endsWith(".tar.gz"))
|
|
372
|
-
const base = entry.slice(0, -".tar.gz".length);
|
|
373
|
-
const pkg = rescueEnabled ? loadIntactPackage(stagingDir, base, names) : undefined;
|
|
374
|
-
if (pkg) {
|
|
375
|
-
movePackageIntoFailed(stagingDir, pkg, path.join(stagingDir, `${base}.meta.json`));
|
|
376
|
-
rescued.push(pkg);
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
fs.rmSync(path.join(stagingDir, entry), { force: true });
|
|
380
|
-
removed++;
|
|
381
|
-
removedNames.push(entry);
|
|
382
|
-
}
|
|
383
|
-
handled.add(entry);
|
|
384
|
-
handled.add(`${base}.json`);
|
|
385
|
-
handled.add(`${base}.meta.json`);
|
|
378
|
+
if (!entry.endsWith(".tar.gz"))
|
|
386
379
|
continue;
|
|
380
|
+
const base = entry.slice(0, -".tar.gz".length);
|
|
381
|
+
const pkg = rescueEnabled ? loadIntactPackage(stagingDir, base, names) : undefined;
|
|
382
|
+
if (pkg) {
|
|
383
|
+
movePackageIntoFailed(stagingDir, pkg, path.join(stagingDir, `${base}.meta.json`));
|
|
384
|
+
rescued.push(pkg);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
fs.rmSync(path.join(stagingDir, entry), { force: true });
|
|
388
|
+
removed++;
|
|
389
|
+
removedNames.push(entry);
|
|
387
390
|
}
|
|
391
|
+
handled.add(entry);
|
|
392
|
+
handled.add(`${base}.json`);
|
|
393
|
+
handled.add(`${base}.meta.json`);
|
|
394
|
+
}
|
|
395
|
+
// 第二遍:清理剩余孤儿(无对应 tar.gz 的快照副本/meta、组装残留目录)
|
|
396
|
+
for (const entry of entries) {
|
|
397
|
+
if (handled.has(entry))
|
|
398
|
+
continue;
|
|
388
399
|
if (entry.endsWith(".json") || entry.startsWith(".assembly-")) {
|
|
389
400
|
fs.rmSync(path.join(stagingDir, entry), { recursive: true, force: true });
|
|
390
401
|
removed++;
|
|
@@ -560,6 +571,8 @@ function removeQuietly(filePath) {
|
|
|
560
571
|
// ─── 孤儿快照补传(§13 失败补偿链路) ─────────────────────────────────────────
|
|
561
572
|
/** 已传终态标记目录名(metrics/.sent/<sessionId>) */
|
|
562
573
|
const SENT_MARKER_DIR = ".sent";
|
|
574
|
+
/** 已传标记时效缓冲:快照 mtime 超出标记时间该值仍视为"标记后更新过"需补传 */
|
|
575
|
+
const SENT_NEWER_GRACE_MS = 60_000;
|
|
563
576
|
/**
|
|
564
577
|
* 记录会话终态包已成功上云。
|
|
565
578
|
* 快照原件(metrics/<sessionId>.json)会永久保留为本地记录,
|
|
@@ -578,9 +591,23 @@ export function markSessionUploaded(metricsDir, sessionId) {
|
|
|
578
591
|
export function isSessionUploaded(metricsDir, sessionId) {
|
|
579
592
|
return fs.existsSync(path.join(metricsDir, SENT_MARKER_DIR, sessionId));
|
|
580
593
|
}
|
|
594
|
+
/** 读取已传标记时间戳(内容为上传完成时刻的毫秒值);无标记/内容非法返回 null */
|
|
595
|
+
function sentTimestamp(metricsDir, sessionId) {
|
|
596
|
+
try {
|
|
597
|
+
const content = fs.readFileSync(path.join(metricsDir, SENT_MARKER_DIR, sessionId), "utf-8").trim();
|
|
598
|
+
const time = Number(content);
|
|
599
|
+
return Number.isFinite(time) && time > 0 ? time : null;
|
|
600
|
+
}
|
|
601
|
+
catch {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
581
605
|
/**
|
|
582
606
|
* 扫描目录选出孤儿快照候选:mtime 静默 [idleMs, maxAgeMs] 且不在活跃集的快照。
|
|
583
|
-
*
|
|
607
|
+
* 快照原件永久保留为本地记录,"已传标记"是防重复上传的依据。
|
|
608
|
+
* 时效判断:标记只证明"标记时刻之前的快照已上云"——会话在中途终态触发
|
|
609
|
+
* (task 完成/session.deleted)上传后仍可能继续运行并更新快照,因此快照
|
|
610
|
+
* mtime 晚于标记时间(超出 60s 缓冲)仍视为孤儿,补传最终态。
|
|
584
611
|
*
|
|
585
612
|
* @param metricsDir 快照目录
|
|
586
613
|
* @param idleMs 静默下限(毫秒)
|
|
@@ -602,11 +629,14 @@ export async function selectOrphanSnapshots(metricsDir, idleMs, maxAgeMs, isActi
|
|
|
602
629
|
if (!entry.endsWith(".json"))
|
|
603
630
|
continue;
|
|
604
631
|
const sessionId = entry.slice(0, -".json".length);
|
|
605
|
-
if (isSessionUploaded(metricsDir, sessionId))
|
|
606
|
-
continue;
|
|
607
632
|
const filePath = path.join(metricsDir, entry);
|
|
608
633
|
try {
|
|
609
634
|
const stats = await fs.promises.stat(filePath);
|
|
635
|
+
// 已传标记时效判断:标记只证明"标记时刻之前的快照已上云",快照 mtime
|
|
636
|
+
// 晚于标记时间(超出 60s 缓冲)说明会话其后仍更新过,最终态未上云,仍补传
|
|
637
|
+
const sentAt = sentTimestamp(metricsDir, sessionId);
|
|
638
|
+
if (sentAt !== null && stats.mtimeMs <= sentAt + SENT_NEWER_GRACE_MS)
|
|
639
|
+
continue;
|
|
610
640
|
const age = now - stats.mtimeMs;
|
|
611
641
|
if (age < idleMs || age > maxAgeMs)
|
|
612
642
|
continue;
|
package/dist/plugin.js
CHANGED
|
@@ -23,6 +23,7 @@ export const MetricsPlugin = async (input, options) => {
|
|
|
23
23
|
eventsDir: runtime.dirs.eventsDir,
|
|
24
24
|
enabled: config.enabled !== false,
|
|
25
25
|
upload: Boolean(runtime.uploadManager),
|
|
26
|
+
agents: runtime.sessionFilter.active ? config.agents : null,
|
|
26
27
|
configSource: source,
|
|
27
28
|
});
|
|
28
29
|
const hooks = {
|
|
@@ -31,11 +32,13 @@ export const MetricsPlugin = async (input, options) => {
|
|
|
31
32
|
event: event,
|
|
32
33
|
});
|
|
33
34
|
},
|
|
34
|
-
// 记录各模型 system prompt
|
|
35
|
+
// 记录各模型 system prompt(只读不改写、不返回;子代理跳过;agents 过滤外的会话跳过)
|
|
35
36
|
"experimental.chat.system.transform": async (input, output) => {
|
|
36
37
|
const sessionId = input.sessionID;
|
|
37
38
|
if (!sessionId)
|
|
38
39
|
return;
|
|
40
|
+
if (!runtime.sessionFilter.shouldTrackSession(sessionId))
|
|
41
|
+
return;
|
|
39
42
|
if (runtime.eventLogger?.isSubAgent(sessionId))
|
|
40
43
|
return;
|
|
41
44
|
const modelId = input.model?.id ?? "unknown";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-metrics-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "opencode plugin: auto-records every session's metrics (tokens/rounds/steps/sub-agents/hvigor builds) to snapshot JSON. Zero DB, zero config. Pair with session-viewer for visualization.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|