opencode-metrics-plugin 0.1.5 → 0.2.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.
Files changed (41) hide show
  1. package/README.md +108 -390
  2. package/dist/index.d.ts +5 -51
  3. package/dist/index.js +5 -61
  4. package/dist/metrics/dirs.d.ts +23 -0
  5. package/dist/{dirs.js → metrics/dirs.js} +4 -10
  6. package/dist/metrics/engine/engine.d.ts +15 -0
  7. package/dist/{metrics-engine.js → metrics/engine/engine.js} +33 -146
  8. package/dist/{metrics-handlers.d.ts → metrics/engine/handlers.d.ts} +1 -1
  9. package/dist/{metrics-handlers.js → metrics/engine/handlers.js} +2 -2
  10. package/dist/metrics/engine/state.d.ts +79 -0
  11. package/dist/{metrics-types.js → metrics/engine/state.js} +1 -19
  12. package/dist/{event-logger.d.ts → metrics/eventlog/event-logger.d.ts} +0 -5
  13. package/dist/{event-logger.js → metrics/eventlog/event-logger.js} +2 -18
  14. package/dist/metrics/index.d.ts +9 -0
  15. package/dist/metrics/index.js +6 -0
  16. package/dist/metrics/runtime.d.ts +25 -0
  17. package/dist/metrics/runtime.js +28 -0
  18. package/dist/metrics/snapshot/flush.d.ts +6 -0
  19. package/dist/{metrics-output.js → metrics/snapshot/flush.js} +6 -290
  20. package/dist/metrics/snapshot/merge.d.ts +9 -0
  21. package/dist/metrics/snapshot/merge.js +282 -0
  22. package/dist/{metrics-steps.d.ts → metrics/snapshot/steps.d.ts} +1 -1
  23. package/dist/{metrics-steps.js → metrics/snapshot/steps.js} +1 -1
  24. package/dist/{metrics-types.d.ts → metrics/types.d.ts} +3 -81
  25. package/dist/metrics/types.js +20 -0
  26. package/dist/plugin.d.ts +22 -0
  27. package/dist/plugin.js +49 -0
  28. package/dist/{logger.d.ts → shared/log.d.ts} +2 -0
  29. package/dist/{logger.js → shared/log.js} +9 -2
  30. package/package.json +8 -4
  31. package/dist/backfill-cli.d.ts +0 -1
  32. package/dist/backfill-cli.js +0 -74
  33. package/dist/backfill.d.ts +0 -58
  34. package/dist/backfill.js +0 -550
  35. package/dist/dirs.d.ts +0 -34
  36. package/dist/metrics-engine.d.ts +0 -37
  37. package/dist/metrics-output.d.ts +0 -11
  38. package/dist/summary-store.d.ts +0 -150
  39. package/dist/summary-store.js +0 -560
  40. /package/dist/{compile-analyzer.d.ts → metrics/analysis/hvigor.d.ts} +0 -0
  41. /package/dist/{compile-analyzer.js → metrics/analysis/hvigor.js} +0 -0
@@ -0,0 +1,23 @@
1
+ /** metrics/events 写入目录集。host 可自定义;默认按应用名 env paths。 */
2
+ export interface MetricsDirs {
3
+ /** 会话快照 JSON 目录(<sessionId>.json 直接落这里;多引擎必须隔离,避免多写者冲突) */
4
+ metricsDir: string;
5
+ /** events 逐会话日志目录(steps 全文的数据源) */
6
+ eventsDir: string;
7
+ /** 运行日志文件(可选;logger 无文件权限时回退 console) */
8
+ logFile: string;
9
+ }
10
+ export declare function defaultDirs(appName?: string): MetricsDirs;
11
+ /**
12
+ * 供 host 设定进程级默认目录(未显式传 dirs 的引擎/事件记录器使用)。
13
+ * 多引擎场景请优先在 createMetricsEngine/createEventLogger 实例级传 dirs,
14
+ * 进程级配置仅作默认兜底,避免多宿主互相覆盖。
15
+ */
16
+ export declare function configureDirs(dirs: Partial<MetricsDirs>): void;
17
+ /** 当前进程级默认目录(defaultDirs 与 configureDirs 的合并结果)。 */
18
+ export declare function getDirs(): MetricsDirs;
19
+ /** 解析实例目录:defaultDirs → 进程级默认(configureDirs)→ 实例传入,三层合并。 */
20
+ export declare function resolveDirs(partial?: Partial<MetricsDirs>): MetricsDirs;
21
+ export declare function getMetricsDir(): string;
22
+ export declare function getEventsDir(): string;
23
+ export declare function getLogFile(): string;
@@ -1,32 +1,29 @@
1
1
  import envPaths from 'env-paths';
2
2
  import path from 'path';
3
+ import { setLogFile } from '../shared/log.js';
3
4
  export function defaultDirs(appName = 'opencode-metrics-plugin') {
4
5
  const base = envPaths(appName, { suffix: '' });
5
6
  return {
6
7
  metricsDir: path.join(base.log, 'metrics'),
7
8
  eventsDir: path.join(base.log, 'events'),
8
9
  logFile: path.join(base.log, 'plugin.log'),
9
- summaryFile: path.join(base.log, 'summary.db'),
10
10
  };
11
11
  }
12
12
  let _dirs = defaultDirs();
13
13
  /**
14
14
  * 供 host 设定进程级默认目录(未显式传 dirs 的引擎/事件记录器使用)。
15
- * 注意:多引擎场景请优先在 createMetricsEngine/createEventLogger 实例级传 dirs,
15
+ * 多引擎场景请优先在 createMetricsEngine/createEventLogger 实例级传 dirs,
16
16
  * 进程级配置仅作默认兜底,避免多宿主互相覆盖。
17
- * summaryFile 未显式配置时保持包级全局路径(跨宿主聚合语义)。
18
17
  */
19
18
  export function configureDirs(dirs) {
20
19
  _dirs = { ...defaultDirs(), ...dirs };
20
+ setLogFile(_dirs.logFile);
21
21
  }
22
22
  /** 当前进程级默认目录(defaultDirs 与 configureDirs 的合并结果)。 */
23
23
  export function getDirs() {
24
24
  return _dirs;
25
25
  }
26
- /**
27
- * 解析实例目录:defaultDirs → 进程级默认(configureDirs)→ 实例传入,三层合并。
28
- * summaryFile 不随 metricsDir 派生:缺省恒为全局共享库(实例/进程级显式指定才能改写)。
29
- */
26
+ /** 解析实例目录:defaultDirs → 进程级默认(configureDirs)→ 实例传入,三层合并。 */
30
27
  export function resolveDirs(partial) {
31
28
  return partial ? { ...getDirs(), ...partial } : { ...getDirs() };
32
29
  }
@@ -39,6 +36,3 @@ export function getEventsDir() {
39
36
  export function getLogFile() {
40
37
  return _dirs.logFile;
41
38
  }
42
- export function getSummaryFile() {
43
- return _dirs.summaryFile;
44
- }
@@ -0,0 +1,15 @@
1
+ import type { MetricsEngine, MetricsOutput } from "../types.js";
2
+ import type { MetricsDirs } from "../dirs.js";
3
+ export type { TokenUsage, ToolStats, StageType, StageInfo, MessageEntry, ToolCallEntry, SessionMeta, CompileStats, SkillCallEntry, SkillSearchEntry, StepData, RoundSnapshot, SubAgentOutput, MetricsOutput, MetricsEngine, } from "../types.js";
4
+ export interface MetricsEngineOptions {
5
+ enabled?: boolean;
6
+ /** 本引擎输出目录(多引擎各自独立);缺省回退进程级默认(configureDirs),构造时解析固化 */
7
+ dirs?: Partial<MetricsDirs>;
8
+ /** flush 会话快照后回调(宿主可在此把快照上报自有系统) */
9
+ onFlush?: (sessionId: string, snapshot: MetricsOutput) => void;
10
+ }
11
+ /**
12
+ * 创建指标引擎:订阅事件流,按会话累计状态,idle/flush/dispose 产出快照 JSON。
13
+ * 0.2.0 起移除 scope 门控——记录全部会话;子会话(parentID)自动并入父会话。
14
+ */
15
+ export declare function createMetricsEngine(opts?: MetricsEngineOptions): MetricsEngine;
@@ -1,93 +1,23 @@
1
- import { createSessionMetrics, TRACKED_EVENT_TYPES } from "./metrics-types.js";
2
- import { handlePartUpdated, handleSessionUpdated, handleMessageUpdated, handleSubAgentParts } from "./metrics-handlers.js";
3
- import { handleSessionIdle, flushMetrics, mergeChildMetrics } from "./metrics-output.js";
4
- import { resolveDirs } from "./dirs.js";
5
- import { log } from "./logger.js";
1
+ import { TRACKED_EVENT_TYPES } from "../types.js";
2
+ import { createSessionMetrics } from "./state.js";
3
+ import { handlePartUpdated, handleSessionUpdated, handleMessageUpdated, handleSubAgentParts } from "./handlers.js";
4
+ import { handleSessionIdle, flushMetrics } from "../snapshot/flush.js";
5
+ import { mergeChildMetrics } from "../snapshot/merge.js";
6
+ import { resolveDirs } from "../dirs.js";
7
+ import { log } from "../../shared/log.js";
6
8
  /**
7
- * 创建指标引擎。
8
- * 兼容旧布尔入参:true = scope all,false = scope none。
9
- * 统计范围 = 策略 + 宿主声明(claimSession)的交集判定:
10
- * - mode all —— 全部(配合 agents 即只统计白名单 agent)
11
- * - mode none —— 默认不统计;宿主 claimSession 才统计
12
- * - mode declared—— 仅宿主 claimSession 的会话(或 agents 白名单命中)
13
- * - mode cwd —— 仅工作目录命中 cwdPrefixes
14
- * 子会话(parentID 归属已统计父会话)自动随父会话统计。
9
+ * 创建指标引擎:订阅事件流,按会话累计状态,idle/flush/dispose 产出快照 JSON。
10
+ * 0.2.0 起移除 scope 门控——记录全部会话;子会话(parentID)自动并入父会话。
15
11
  */
16
- export function createMetricsEngine(opts = { enabled: true, scope: { mode: "all" } }) {
17
- const options = typeof opts === "boolean"
18
- ? { enabled: opts, scope: { mode: opts ? "all" : "none" } }
19
- : opts;
20
- const enabled = options.enabled !== false;
21
- const scope = options.scope ?? { mode: "all" };
22
- // 实例目录:构造时按 defaultDirs → 进程级默认 → 实例传入 三层合并固化;未传则 flush 时动态回退进程级默认
23
- const instanceDirs = options.dirs ? resolveDirs(options.dirs) : undefined;
24
- const agents = scope.agents && scope.agents.length > 0 ? new Set(scope.agents) : null;
25
- const cwdPrefixes = scope.cwdPrefixes ?? [];
12
+ export function createMetricsEngine(opts = {}) {
13
+ const enabled = opts.enabled !== false;
14
+ const instanceDirs = opts.dirs ? resolveDirs(opts.dirs) : undefined;
26
15
  const sessions = new Map();
27
16
  const childToParent = new Map();
28
17
  const childSessionIds = new Set();
29
- const claimed = new Set();
30
- // 已定案的会话统计与否(false 不缓存“信息未齐”的暂定,等 session.created 带 agent/dir 再定)
31
- const resolved = new Map();
32
- // 工作目录缓存(session.created 记录;cwd 策略与删除时反查用)
33
- const sessionDirs = new Map();
34
- const sessionAgents = new Map();
35
- function decideFresh(id, agent, directory) {
36
- if (claimed.has(id))
37
- return true;
38
- if (agents) {
39
- if (!agent || !agents.has(agent))
40
- return false;
41
- }
42
- switch (scope.mode) {
43
- case "all": return true;
44
- case "none": return false;
45
- case "declared": return false;
46
- case "cwd": return directory != null && cwdPrefixes.some((p) => directory.startsWith(p));
47
- default: return false;
48
- }
49
- }
50
- /** 需等待 agent/directory 才可定案(事件带信息前先跳过,session.created 到达后定案)。 */
51
- function needsInfo(id, agent, directory) {
52
- if (agents && !agent)
53
- return true;
54
- if (scope.mode === "cwd" && directory == null)
55
- return true;
56
- return false;
57
- }
58
18
  function resolveSessionId(rawId) {
59
19
  return childToParent.get(rawId) ?? rawId;
60
20
  }
61
- /** 判断事件目标会话是否纳入统计(子会话跟随父会话定案)。 */
62
- function isTracked(id, eventType, props) {
63
- let target = id;
64
- if (id.startsWith("__migrate__:")) {
65
- target = id.split(":")[2] ?? "";
66
- }
67
- else {
68
- target = childToParent.get(id) ?? id;
69
- }
70
- if (!target)
71
- return false;
72
- const info = (props?.info ?? {});
73
- const agent = typeof info.agent === "string" ? info.agent : sessionAgents.get(target);
74
- const directory = typeof info.directory === "string" ? info.directory : sessionDirs.get(target);
75
- // 记录目录/agent(含迁移解析出的父子关系场景)
76
- if (eventType === "session.created" || eventType === "session.updated") {
77
- if (typeof info.directory === "string")
78
- sessionDirs.set(target, info.directory);
79
- if (typeof info.agent === "string")
80
- sessionAgents.set(target, info.agent);
81
- }
82
- const cached = resolved.get(target);
83
- if (cached !== undefined)
84
- return cached;
85
- if (needsInfo(target, agent, directory))
86
- return false;
87
- const decision = decideFresh(target, agent, directory);
88
- resolved.set(target, decision);
89
- return decision;
90
- }
91
21
  function extractSessionId(event) {
92
22
  const props = event.properties;
93
23
  if (!props) {
@@ -104,7 +34,7 @@ export function createMetricsEngine(opts = { enabled: true, scope: { mode: "all"
104
34
  childSessionIds.add(id);
105
35
  const agentName = info.agent || "";
106
36
  const title = info.title || "";
107
- log.info("[Metrics] MIGRATE detected", { type: event.type, id, parentID, agentName, title });
37
+ log.info("[Metrics] sub-agent detected", { type: event.type, id, parentID, agentName, title });
108
38
  return `__migrate__:${id}:${parentID}:${agentName}:${title}`;
109
39
  }
110
40
  if (id)
@@ -117,7 +47,7 @@ export function createMetricsEngine(opts = { enabled: true, scope: { mode: "all"
117
47
  const info = props.info;
118
48
  if (info?.id)
119
49
  return resolveSessionId(info.id);
120
- log.info("[Metrics] extractSessionId: no sessionID found", { type: event.type, hasSessionID: !!props.sessionID, hasInfo: !!info, hasInfoId: !!info?.id });
50
+ log.info("[Metrics] extractSessionId: no sessionID found", { type: event.type });
121
51
  return undefined;
122
52
  }
123
53
  function getOrCreateSession(sessionId) {
@@ -128,6 +58,16 @@ export function createMetricsEngine(opts = { enabled: true, scope: { mode: "all"
128
58
  }
129
59
  return state;
130
60
  }
61
+ function emitFlush(sessionId, snapshot) {
62
+ if (snapshot && opts.onFlush) {
63
+ try {
64
+ opts.onFlush(sessionId, snapshot);
65
+ }
66
+ catch (err) {
67
+ log.error("[Metrics] onFlush failed", { sessionId, error: String(err) });
68
+ }
69
+ }
70
+ }
131
71
  return {
132
72
  ingest(event) {
133
73
  if (!enabled)
@@ -140,10 +80,7 @@ export function createMetricsEngine(opts = { enabled: true, scope: { mode: "all"
140
80
  return;
141
81
  }
142
82
  const props = event.properties;
143
- // 统计范围门:未纳入范围的会话整条丢弃(含其子代理)
144
- if (!isTracked(sessionId, event.type, props))
145
- return;
146
- // Handle buffer migration when sub-agent relationship is discovered
83
+ // 子代理关系发现时迁移既有缓冲状态
147
84
  if (sessionId.startsWith("__migrate__:")) {
148
85
  const parts = sessionId.split(":");
149
86
  const childId = parts[1];
@@ -152,7 +89,6 @@ export function createMetricsEngine(opts = { enabled: true, scope: { mode: "all"
152
89
  const title = parts[4] || "";
153
90
  sessionId = parentId;
154
91
  const parentState = getOrCreateSession(parentId);
155
- // Merge child's existing metrics to parent
156
92
  const childState = sessions.get(childId);
157
93
  if (childState) {
158
94
  log.info("[Metrics] Migration: merging existing child state", { childId, parentId });
@@ -179,7 +115,7 @@ export function createMetricsEngine(opts = { enabled: true, scope: { mode: "all"
179
115
  }
180
116
  }
181
117
  const state = getOrCreateSession(sessionId);
182
- // Sub-agent routing: check if the original event was from a child session
118
+ // 子代理事件路由:原始事件来自子会话时进入对应 subAgents 条目
183
119
  const originalSessionId = props?.sessionID || "";
184
120
  if (childSessionIds.has(originalSessionId)) {
185
121
  const sub = state.subAgents.get(originalSessionId);
@@ -191,21 +127,18 @@ export function createMetricsEngine(opts = { enabled: true, scope: { mode: "all"
191
127
  log.info("[Metrics] Sub-agent routing MISS — entry not found", { eventType: event.type, originalSessionId, subAgentsKeys: [...state.subAgents.keys()] });
192
128
  }
193
129
  }
194
- // Extract directory from session.created
130
+ // 提取工作目录
195
131
  if (event.type === "session.created" && props) {
196
132
  const info = props.info;
197
133
  const directory = info?.directory;
198
- if (directory) {
134
+ if (directory)
199
135
  state.sessionMeta.workingDirectory = directory;
200
- sessionDirs.set(sessionId, directory);
201
- }
202
136
  }
203
- // Track per-round first event time
137
+ // 轮内首个事件时间
204
138
  if (!state._hasFirstEvent) {
205
139
  state._hasFirstEvent = true;
206
140
  state._firstEventTime = Date.now();
207
141
  }
208
- // Route to handler by event type
209
142
  if (event.type === "message.part.updated" && props) {
210
143
  handlePartUpdated(state, props);
211
144
  }
@@ -213,15 +146,7 @@ export function createMetricsEngine(opts = { enabled: true, scope: { mode: "all"
213
146
  handleSessionUpdated(state, props);
214
147
  }
215
148
  if (event.type === "session.idle") {
216
- const snapshot = handleSessionIdle(state, sessionId, instanceDirs);
217
- if (options.onFlush && snapshot) {
218
- try {
219
- options.onFlush(sessionId, snapshot);
220
- }
221
- catch (err) {
222
- log.error("[Metrics] onFlush failed", { sessionId, error: String(err) });
223
- }
224
- }
149
+ emitFlush(sessionId, handleSessionIdle(state, sessionId, instanceDirs));
225
150
  }
226
151
  if (event.type === "session.compacted") {
227
152
  state.compactions++;
@@ -244,13 +169,7 @@ export function createMetricsEngine(opts = { enabled: true, scope: { mode: "all"
244
169
  ingestPrompt(sessionId, modelId, system) {
245
170
  if (!enabled)
246
171
  return;
247
- const resolvedId = resolveSessionId(sessionId);
248
- const allowed = (scope.mode === "all" && !agents) ||
249
- claimed.has(resolvedId) ||
250
- resolved.get(resolvedId) === true;
251
- if (!allowed)
252
- return;
253
- const state = getOrCreateSession(resolvedId);
172
+ const state = getOrCreateSession(resolveSessionId(sessionId));
254
173
  state.systemPrompts.set(modelId, system);
255
174
  },
256
175
  isSubAgent(sessionId) {
@@ -262,15 +181,7 @@ export function createMetricsEngine(opts = { enabled: true, scope: { mode: "all"
262
181
  return;
263
182
  const now = Date.now();
264
183
  state.endTime = now;
265
- const snapshot = flushMetrics(sessionId, state, now, instanceDirs);
266
- if (options.onFlush && snapshot) {
267
- try {
268
- options.onFlush(sessionId, snapshot);
269
- }
270
- catch (err) {
271
- log.error("[Metrics] onFlush failed", { sessionId, error: String(err) });
272
- }
273
- }
184
+ emitFlush(sessionId, flushMetrics(sessionId, state, now, instanceDirs));
274
185
  },
275
186
  dispose() {
276
187
  const now = Date.now();
@@ -297,35 +208,11 @@ export function createMetricsEngine(opts = { enabled: true, scope: { mode: "all"
297
208
  state.compileStats._pendingFix = null;
298
209
  }
299
210
  state.endTime = now;
300
- const snapshot = flushMetrics(sessionId, state, now, instanceDirs);
301
- if (options.onFlush && snapshot) {
302
- try {
303
- options.onFlush(sessionId, snapshot);
304
- }
305
- catch (err) {
306
- log.error("[Metrics] onFlush failed", { sessionId, error: String(err) });
307
- }
308
- }
211
+ emitFlush(sessionId, flushMetrics(sessionId, state, now, instanceDirs));
309
212
  }
310
213
  sessions.clear();
311
214
  childToParent.clear();
312
215
  childSessionIds.clear();
313
216
  },
314
- claimSession(sessionId, meta) {
315
- claimed.add(sessionId);
316
- if (meta?.agent)
317
- sessionAgents.set(sessionId, meta.agent);
318
- // 允许 claim 推翻早前“信息未齐暂不统计”的判定;宿主显式声明即强制纳入
319
- resolved.set(sessionId, true);
320
- },
321
- scopeOf(sessionId) {
322
- const v = resolved.get(sessionId);
323
- if (v !== undefined)
324
- return v;
325
- return claimed.has(sessionId);
326
- },
327
- shouldTrack(sessionId, eventType, props) {
328
- return isTracked(sessionId, eventType, props);
329
- },
330
217
  };
331
218
  }
@@ -1,4 +1,4 @@
1
- import type { SessionMetricsState, SubAgentState } from "./metrics-types.js";
1
+ import type { SessionMetricsState, SubAgentState } from "./state.js";
2
2
  declare function handlePartUpdated(state: SessionMetricsState, props: Record<string, unknown>): void;
3
3
  declare function handleSessionUpdated(state: SessionMetricsState, props: Record<string, unknown>): void;
4
4
  declare function handleMessageUpdated(state: SessionMetricsState, props: Record<string, unknown>): void;
@@ -1,5 +1,5 @@
1
- import { createTokenUsage } from "./metrics-types.js";
2
- import { parseHvigorwOutput } from "./compile-analyzer.js";
1
+ import { createTokenUsage } from "../types.js";
2
+ import { parseHvigorwOutput } from "../analysis/hvigor.js";
3
3
  // ─── Input Simplification ────────────────────────────────────────────────────
4
4
  function simplifyInput(raw) {
5
5
  if (!raw)
@@ -0,0 +1,79 @@
1
+ import type { MessageEntry, PlanningCall, RoundSnapshot, SkillCallEntry, SkillSearchEntry, TokenUsage, ToolCallEntry, SessionMeta, CompileStats, StageInfo, ToolStats } from "../types.js";
2
+ export interface SubAgentState {
3
+ sessionId: string;
4
+ agentName: string;
5
+ title: string;
6
+ messageMap: Map<string, MessageEntry>;
7
+ toolCallMap: Map<string, ToolCallEntry>;
8
+ textLengthMap: Map<string, number>;
9
+ orderCounter: number;
10
+ _userMessageIds: Set<string>;
11
+ _pendingUserMessage: string[];
12
+ }
13
+ export interface SessionMetricsState {
14
+ sessionId: string;
15
+ startTime: number;
16
+ endTime: number;
17
+ rounds: RoundSnapshot[];
18
+ tokens: TokenUsage;
19
+ tools: {
20
+ totalCalls: number;
21
+ invalidCalls: number;
22
+ completedCalls: number;
23
+ errorCalls: number;
24
+ distribution: Map<string, ToolStats>;
25
+ slowestCall: {
26
+ tool: string;
27
+ duration: number;
28
+ };
29
+ };
30
+ agent: {
31
+ current: string;
32
+ switches: number;
33
+ lastSwitchTime: number;
34
+ usage: Map<string, number>;
35
+ };
36
+ model: {
37
+ current: string;
38
+ switches: number;
39
+ tokenDistribution: Map<string, TokenUsage>;
40
+ };
41
+ compactions: number;
42
+ anomaly: {
43
+ triggered: boolean;
44
+ events: string[];
45
+ };
46
+ currentStage: StageInfo | null;
47
+ stages: StageInfo[];
48
+ lastStageEndTime: number;
49
+ _stageTokens: TokenUsage;
50
+ _roundStartTime: number;
51
+ _firstEventTime: number;
52
+ _firstTextTime: number;
53
+ _hasFirstEvent: boolean;
54
+ _hasTextPart: boolean;
55
+ _roundTokens: TokenUsage;
56
+ _roundToolCalls: number;
57
+ _roundErrors: number;
58
+ _roundFirstBuildTracked: boolean;
59
+ _roundIdleProcessed: boolean;
60
+ _idleFlushed: boolean;
61
+ messageMap: Map<string, MessageEntry>;
62
+ toolCallMap: Map<string, ToolCallEntry>;
63
+ textLengthMap: Map<string, number>;
64
+ sessionMeta: SessionMeta;
65
+ compileStats: CompileStats;
66
+ orderCounter: number;
67
+ skillCallMap: Map<string, SkillCallEntry>;
68
+ htmlPreviewMsgId: string | null;
69
+ skillSearchMap: Map<string, SkillSearchEntry>;
70
+ _pendingReadChain: {
71
+ searchCallID: string;
72
+ } | null;
73
+ _userMessageIds: Set<string>;
74
+ _pendingUserMessage: string[];
75
+ subAgents: Map<string, SubAgentState>;
76
+ planningCalls: PlanningCall[];
77
+ systemPrompts: Map<string, string[]>;
78
+ }
79
+ export declare function createSessionMetrics(sessionId: string): SessionMetricsState;
@@ -1,23 +1,5 @@
1
- export const TRACKED_EVENT_TYPES = new Set([
2
- "session.created",
3
- "session.updated",
4
- "session.idle",
5
- "session.deleted",
6
- "session.compacted",
7
- "session.status",
8
- "session.error",
9
- "message.updated",
10
- "message.removed",
11
- "message.part.updated",
12
- "message.part.removed",
13
- "permission.asked",
14
- "permission.replied",
15
- "command.executed",
16
- ]);
1
+ import { createTokenUsage } from "../types.js";
17
2
  // ─── Factory Functions ───────────────────────────────────────────────────────
18
- export function createTokenUsage() {
19
- return { input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
20
- }
21
3
  export function createSessionMetrics(sessionId) {
22
4
  const now = Date.now();
23
5
  return {
@@ -11,10 +11,5 @@ export interface EventLogger {
11
11
  export interface EventLoggerOptions {
12
12
  /** 本实例事件日志目录(多引擎场景各自独立);缺省回退进程级默认(configureDirs) */
13
13
  eventsDir?: string;
14
- /** 会话归属门控:返回 false 的事件不写入本实例日志(与引擎共用同一 scope 判定) */
15
- isTracked?: (sessionId: string, event: {
16
- type: string;
17
- properties?: Record<string, unknown>;
18
- }) => boolean;
19
14
  }
20
15
  export declare function createEventLogger(enabled: boolean, cleanupEnabled?: boolean, opts?: EventLoggerOptions): EventLogger;
@@ -1,7 +1,7 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { getEventsDir } from "./dirs.js";
4
- import { TRACKED_EVENT_TYPES } from "./metrics-types.js";
3
+ import { getEventsDir } from "../dirs.js";
4
+ import { TRACKED_EVENT_TYPES } from "../types.js";
5
5
  // Rotation constants
6
6
  const MAX_LOG_SIZE_BYTES = 20 * 1024 * 1024;
7
7
  const MAX_LOG_AGE_DAYS = 7;
@@ -9,7 +9,6 @@ const MAX_ROTATED_FILES = 5;
9
9
  const FLUSH_INTERVAL_MS = 500;
10
10
  const BUFFER_SIZE_LIMIT = 50;
11
11
  const SEPARATOR = "=".repeat(72);
12
- // 鈹€鈹€鈹€ Helpers 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
13
12
  function formatTimestamp(date) {
14
13
  const pad = (n) => String(n).padStart(2, "0");
15
14
  const ms = String(Math.floor(date.getMilliseconds() / 10)).padStart(2, "0");
@@ -239,18 +238,6 @@ export function createEventLogger(enabled, cleanupEnabled = false, opts = {}) {
239
238
  if (sessionId.startsWith("__migrate__:")) {
240
239
  const [, childId, parentId] = sessionId.split(":");
241
240
  sessionId = parentId;
242
- // 子会话归属随父:父未纳入统计时丢弃子缓冲,不产生日志文件
243
- if (opts.isTracked && !opts.isTracked(parentId, event)) {
244
- const dropBuf = sessionBuffers.get(childId);
245
- if (dropBuf) {
246
- if (dropBuf.flushTimer) {
247
- clearTimeout(dropBuf.flushTimer);
248
- dropBuf.flushTimer = null;
249
- }
250
- sessionBuffers.delete(childId);
251
- }
252
- return;
253
- }
254
241
  // Move child's existing buffer to parent
255
242
  const childBuf = sessionBuffers.get(childId);
256
243
  if (childBuf) {
@@ -273,9 +260,6 @@ export function createEventLogger(enabled, cleanupEnabled = false, opts = {}) {
273
260
  subAgent = { childId: rawSessionId };
274
261
  }
275
262
  }
276
- // 归属门控:未纳入统计范围的会话不记录事件
277
- if (opts.isTracked && !opts.isTracked(sessionId, event))
278
- return;
279
263
  const buf = getOrCreateBuffer(sessionId);
280
264
  const ts = formatTimestamp(new Date());
281
265
  // Write formatted event block
@@ -0,0 +1,9 @@
1
+ export { createMetricsEngine } from './engine/engine.js';
2
+ export type { MetricsEngineOptions } from './engine/engine.js';
3
+ export { configureDirs, defaultDirs, getDirs, resolveDirs, getMetricsDir, getEventsDir, getLogFile } from './dirs.js';
4
+ export type { MetricsDirs } from './dirs.js';
5
+ export { createEventLogger } from './eventlog/event-logger.js';
6
+ export type { EventLogger, EventLoggerOptions } from './eventlog/event-logger.js';
7
+ export { createMetricsRuntime } from './runtime.js';
8
+ export type { MetricsRuntime, MetricsRuntimeOptions } from './runtime.js';
9
+ export * from './types.js';
@@ -0,0 +1,6 @@
1
+ // metrics 域出口:会话指标采集(引擎/快照/事件日志/编译分析/目录管理/运行时组装)
2
+ export { createMetricsEngine } from './engine/engine.js';
3
+ export { configureDirs, defaultDirs, getDirs, resolveDirs, getMetricsDir, getEventsDir, getLogFile } from './dirs.js';
4
+ export { createEventLogger } from './eventlog/event-logger.js';
5
+ export { createMetricsRuntime } from './runtime.js';
6
+ export * from './types.js';
@@ -0,0 +1,25 @@
1
+ import type { MetricsEngineOptions } from './engine/engine.js';
2
+ import type { MetricsDirs } from './dirs.js';
3
+ export interface MetricsRuntimeOptions extends Omit<MetricsEngineOptions, 'enabled'> {
4
+ dirs?: Partial<MetricsDirs>;
5
+ enabled?: boolean;
6
+ /** 事件写盘(events/<sessionId>.log;steps 全文的数据源) */
7
+ eventLogging?: boolean;
8
+ }
9
+ /**
10
+ * 组装一整套"通用统计插件"的运行时:事件记录 + 指标引擎。
11
+ * 目录为实例级(不产生全局副作用):defaultDirs → 进程级默认(configureDirs)→ opts.dirs 三层合并。
12
+ */
13
+ export declare function createMetricsRuntime(opts?: MetricsRuntimeOptions): {
14
+ dirs: MetricsDirs;
15
+ eventLogger: import("./eventlog/event-logger.js").EventLogger | null;
16
+ engine: import("./types.js").MetricsEngine;
17
+ /** opencode event hook:`async ({ event }) => { runtime.event({ event }) }` */
18
+ event(input: {
19
+ event: {
20
+ type: string;
21
+ properties?: Record<string, unknown>;
22
+ };
23
+ }): void;
24
+ };
25
+ export type MetricsRuntime = ReturnType<typeof createMetricsRuntime>;
@@ -0,0 +1,28 @@
1
+ import { createMetricsEngine } from './engine/engine.js';
2
+ import { createEventLogger } from './eventlog/event-logger.js';
3
+ import { resolveDirs } from './dirs.js';
4
+ /**
5
+ * 组装一整套"通用统计插件"的运行时:事件记录 + 指标引擎。
6
+ * 目录为实例级(不产生全局副作用):defaultDirs → 进程级默认(configureDirs)→ opts.dirs 三层合并。
7
+ */
8
+ export function createMetricsRuntime(opts = {}) {
9
+ const dirs = resolveDirs(opts.dirs);
10
+ const engine = createMetricsEngine({
11
+ enabled: opts.enabled,
12
+ dirs,
13
+ onFlush: opts.onFlush,
14
+ });
15
+ const eventLogger = (opts.eventLogging ?? true)
16
+ ? createEventLogger(true, false, { eventsDir: dirs.eventsDir })
17
+ : null;
18
+ return {
19
+ dirs,
20
+ eventLogger,
21
+ engine,
22
+ /** opencode event hook:`async ({ event }) => { runtime.event({ event }) }` */
23
+ event(input) {
24
+ eventLogger?.log(input.event);
25
+ engine.ingest(input.event);
26
+ },
27
+ };
28
+ }
@@ -0,0 +1,6 @@
1
+ import type { MetricsDirs } from "../dirs.js";
2
+ import type { SessionMetricsState } from "../engine/state.js";
3
+ import type { MetricsOutput } from "../types.js";
4
+ declare function flushMetrics(sessionId: string, state: SessionMetricsState, now: number, dirs?: Partial<MetricsDirs>): MetricsOutput;
5
+ declare function handleSessionIdle(state: SessionMetricsState, sessionId: string, dirs?: Partial<MetricsDirs>): MetricsOutput | undefined;
6
+ export { flushMetrics, handleSessionIdle };