@zhushanwen/pi-evolve-daily 0.1.7 → 0.1.9

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.
@@ -42,7 +42,7 @@ def load_sessions(since_days: int = 1, input_file: str | None = None) -> list[di
42
42
  cutoff = datetime.now() - timedelta(days=since_days)
43
43
  sessions = []
44
44
 
45
- for session_file in sessions_dir.glob("*.jsonl"):
45
+ for session_file in sessions_dir.rglob("*.jsonl"):
46
46
  try:
47
47
  # 从文件名解析日期
48
48
  file_date = datetime.fromisoformat(session_file.stem[:10])
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-evolve-daily",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Daily evolution data collector — runs Python analyzer on first session of the day.",
5
5
  "type": "module",
6
+ "license": "MIT",
6
7
  "main": "src/index.ts",
7
8
  "pi": {
8
9
  "extensions": [
9
- "./src/index.ts"
10
+ "./index.ts"
10
11
  ],
11
12
  "skills": [
12
13
  "./skills"
@@ -12,6 +12,12 @@ export interface CompactTrackedItem {
12
12
  detail?: string;
13
13
  }
14
14
 
15
+ // ── ID generation constants ──────────────────────────
16
+
17
+ const RANDOM_ID_RADIX = 36;
18
+ const RANDOM_ID_SLICE_START = 2;
19
+ const RANDOM_ID_SLICE_END = 7;
20
+
15
21
  export function createCompactDetector(problem: ProblemDefinition) {
16
22
  return {
17
23
  problemId: problem.id,
@@ -25,7 +31,7 @@ export function createCompactDetector(problem: ProblemDefinition) {
25
31
  compactionEntry?: { tokensBefore?: number };
26
32
  }): CompactTrackedItem {
27
33
  return {
28
- id: `compact-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
34
+ id: `compact-${Date.now()}-${Math.random().toString(RANDOM_ID_RADIX).slice(RANDOM_ID_SLICE_START, RANDOM_ID_SLICE_END)}`,
29
35
  problemId: problem.id as "compact-frequency",
30
36
  sessionId: "",
31
37
  tokensBefore: event.compactionEntry?.tokensBefore ?? 0,
@@ -2,6 +2,12 @@
2
2
 
3
3
  import type { ProblemDefinition } from "../problems";
4
4
 
5
+ // ── ID generation constants ──────────────────────────
6
+
7
+ const RANDOM_ID_RADIX = 36;
8
+ const RANDOM_ID_SLICE_START = 2;
9
+ const RANDOM_ID_SLICE_END = 7;
10
+
5
11
  export interface GoalQualityTrackedItem {
6
12
  id: string;
7
13
  problemId: "goal-task-quality";
@@ -37,7 +43,7 @@ export function createGoalQualityDetector(problem: ProblemDefinition) {
37
43
  const total = tasks.length;
38
44
 
39
45
  return {
40
- id: `goal-quality-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
46
+ id: `goal-quality-${Date.now()}-${Math.random().toString(RANDOM_ID_RADIX).slice(RANDOM_ID_SLICE_START, RANDOM_ID_SLICE_END)}`,
41
47
  problemId: problem.id as "goal-task-quality",
42
48
  sessionId: "",
43
49
  goalId: "",
@@ -2,6 +2,13 @@
2
2
 
3
3
  import type { ProblemDefinition } from "../problems";
4
4
 
5
+ // ── ID generation constants ──────────────────────────
6
+
7
+ const RANDOM_ID_RADIX = 36;
8
+ const RANDOM_ID_SLICE_START = 2;
9
+ const RANDOM_ID_SLICE_END = 7;
10
+ const ERROR_PREVIEW_MAX_LENGTH = 200;
11
+
5
12
  export interface ParamErrorTrackedItem {
6
13
  id: string;
7
14
  problemId: "tool-param-validation";
@@ -70,12 +77,12 @@ export function createParamErrorDetector(problem: ProblemDefinition) {
70
77
  }): ParamErrorTrackedItem {
71
78
  const errorMessage = event.content ?? "";
72
79
  return {
73
- id: `param-error-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
80
+ id: `param-error-${Date.now()}-${Math.random().toString(RANDOM_ID_RADIX).slice(RANDOM_ID_SLICE_START, RANDOM_ID_SLICE_END)}`,
74
81
  problemId: problem.id as "tool-param-validation",
75
82
  sessionId: "",
76
83
  toolName: event.toolName ?? "unknown",
77
84
  errorType: classifyError(errorMessage),
78
- errorPreview: errorMessage.slice(0, 200),
85
+ errorPreview: errorMessage.slice(0, ERROR_PREVIEW_MAX_LENGTH),
79
86
  status: "pending",
80
87
  };
81
88
  },
@@ -2,6 +2,12 @@
2
2
 
3
3
  import type { ProblemDefinition } from "../problems";
4
4
 
5
+ // ── ID generation constants ──────────────────────────
6
+
7
+ const RANDOM_ID_RADIX = 36;
8
+ const RANDOM_ID_SLICE_START = 2;
9
+ const RANDOM_ID_SLICE_END = 7;
10
+
5
11
  export interface SubagentTrackedItem {
6
12
  id: string;
7
13
  problemId: "subagent-efficiency";
@@ -45,7 +51,7 @@ export function createSubagentDetector(problem: ProblemDefinition) {
45
51
  taskPrompt?: string;
46
52
  }): SubagentTrackedItem {
47
53
  return {
48
- id: `subagent-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
54
+ id: `subagent-${Date.now()}-${Math.random().toString(RANDOM_ID_RADIX).slice(RANDOM_ID_SLICE_START, RANDOM_ID_SLICE_END)}`,
49
55
  problemId: problem.id as "subagent-efficiency",
50
56
  sessionId: "",
51
57
  taskType: classifyTaskType(event.taskPrompt ?? ""),
package/src/index.ts CHANGED
@@ -1,16 +1,17 @@
1
1
  // packages/evolve-daily/src/index.ts
2
2
 
3
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
4
3
  import { existsSync, unlinkSync } from "node:fs";
5
4
  import { homedir } from "node:os";
6
5
  import { dirname, join } from "node:path";
7
6
  import { fileURLToPath } from "node:url";
8
7
 
9
- import { PROBLEM_REGISTRY } from "./problems";
8
+ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
9
+
10
10
  import { createCompactDetector } from "./detectors/compact";
11
- import { createSubagentDetector } from "./detectors/subagent-result";
12
- import { createParamErrorDetector } from "./detectors/param-error";
13
11
  import { createGoalQualityDetector } from "./detectors/goal-quality";
12
+ import { createParamErrorDetector } from "./detectors/param-error";
13
+ import { createSubagentDetector } from "./detectors/subagent-result";
14
+ import { PROBLEM_REGISTRY } from "./problems";
14
15
  import { createTracker } from "./trackers/core";
15
16
  import { skillExecutionConfig } from "./trackers/skill-execution";
16
17
 
@@ -21,6 +22,12 @@ const ANALYZER_PATH = join(EXT_DIR, "..", "analyzer", "analyze.py");
21
22
  // 运行时数据目录使用 Pi 平台约定路径(homedir + .pi/agent/)
22
23
  const REPORTS_DIR = join(homedir(), ".pi", "agent", "evolution-data", "daily-reports");
23
24
 
25
+ const DATE_SLICE_END = 10;
26
+ const ANALYZER_TIMEOUT_MS = 30_000;
27
+
28
+ /** Pi API 的 on 方法重载签名(覆盖非标事件名) */
29
+ type PiOnAny = { on(event: string, handler: (...args: unknown[]) => Promise<void> | void): void };
30
+
24
31
  /** tool_result 事件中匹配的工具结果 detector */
25
32
  interface ToolResultDetector {
26
33
  problemId: string;
@@ -30,8 +37,8 @@ interface ToolResultDetector {
30
37
 
31
38
  export default function evolveDailyExtension(pi: ExtensionAPI) {
32
39
  // ── L1: session_start 时调用 Python analyzer ──
33
- pi.on("session_start", async () => {
34
- const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
40
+ pi.on("session_start", async (_event: unknown, ctx: ExtensionContext) => {
41
+ const today = new Date().toISOString().slice(0, DATE_SLICE_END); // YYYY-MM-DD
35
42
  const reportPath = join(REPORTS_DIR, `${today}.json`);
36
43
 
37
44
  if (existsSync(reportPath)) return;
@@ -48,12 +55,13 @@ export default function evolveDailyExtension(pi: ExtensionAPI) {
48
55
  "--output",
49
56
  reportPath,
50
57
  ],
51
- { timeout: 30_000 }
58
+ { timeout: ANALYZER_TIMEOUT_MS, signal: ctx.signal }
52
59
  );
53
60
  } catch (e) {
54
61
  // Clean up partial output if analyzer failed mid-write
55
62
  try {
56
63
  unlinkSync(reportPath);
64
+ // eslint-disable-next-line taste/no-silent-catch
57
65
  } catch {
58
66
  /* already gone */
59
67
  }
@@ -69,9 +77,10 @@ export default function evolveDailyExtension(pi: ExtensionAPI) {
69
77
  PROBLEM_REGISTRY.find((p) => p.id === "compact-frequency")!
70
78
  );
71
79
 
72
- (pi.on as any)("session_compact", async (event: Record<string, unknown>) => {
80
+ (pi as unknown as PiOnAny).on("session_compact", async (event: unknown) => {
81
+ const ev = event as Record<string, unknown>;
73
82
  try {
74
- const item = compactDetector.createItem(event);
83
+ const item = compactDetector.createItem(ev);
75
84
  pi.appendEntry("evolve-feedback", {
76
85
  problemId: item.problemId,
77
86
  itemId: item.id,
@@ -79,6 +88,7 @@ export default function evolveDailyExtension(pi: ExtensionAPI) {
79
88
  detail: item.detail ?? null,
80
89
  timestamp: new Date().toISOString(),
81
90
  });
91
+ // eslint-disable-next-line taste/no-silent-catch
82
92
  } catch (e) {
83
93
  console.error(
84
94
  `[evolve-daily] compact detector error:`,
@@ -101,13 +111,14 @@ export default function evolveDailyExtension(pi: ExtensionAPI) {
101
111
  ),
102
112
  ];
103
113
 
104
- (pi.on as any)(
114
+ (pi as unknown as PiOnAny).on(
105
115
  "tool_result",
106
- async (event: Record<string, unknown>, _ctx?: unknown) => {
116
+ async (event: unknown, _ctx?: unknown) => {
117
+ const ev = event as Record<string, unknown>;
107
118
  for (const detector of toolDetectors) {
108
119
  try {
109
- if (detector.match(event)) {
110
- const item = detector.createItem(event);
120
+ if (detector.match(ev)) {
121
+ const item = detector.createItem(ev);
111
122
  pi.appendEntry("evolve-feedback", {
112
123
  problemId: item.problemId,
113
124
  itemId: item.id,
@@ -116,6 +127,7 @@ export default function evolveDailyExtension(pi: ExtensionAPI) {
116
127
  timestamp: new Date().toISOString(),
117
128
  });
118
129
  }
130
+ // eslint-disable-next-line taste/no-silent-catch
119
131
  } catch (e) {
120
132
  console.error(
121
133
  `[evolve-daily] detector ${detector.problemId} error:`,
package/src/problems.ts CHANGED
@@ -49,6 +49,23 @@ interface TrackedItemTemplate {
49
49
  status: string;
50
50
  }
51
51
 
52
+ // ── Severity thresholds ──────────────────────────────
53
+
54
+ const COMPACT_HIGH_THRESHOLD = 3;
55
+ const COMPACT_MEDIUM_THRESHOLD = 2;
56
+ const CONTEXT_UTILIZATION_MEDIUM = 0.7;
57
+ const CONTEXT_UTILIZATION_HIGH = 0.9;
58
+ const GOAL_LOW_COMPLETION_HIGH = 0.5;
59
+ const GOAL_HIGH_CANCEL = 0.4;
60
+ const GOAL_LOW_COMPLETION_MEDIUM = 0.7;
61
+ const GOAL_MEDIUM_CANCEL = 0.2;
62
+ const SUBAGENT_FAILURE_MEDIUM = 0.2;
63
+ const SUBAGENT_FAILURE_HIGH = 0.4;
64
+ const PARAM_ERROR_MEDIUM = 0.1;
65
+ const PARAM_ERROR_HIGH = 0.25;
66
+ const WORKFLOW_PHASE_RATIO_HIGH = 0.7;
67
+ const WORKFLOW_PHASE_RATIO_MEDIUM = 0.5;
68
+
52
69
  export const PROBLEM_REGISTRY: ProblemDefinition[] = [
53
70
  {
54
71
  id: "compact-frequency",
@@ -58,8 +75,8 @@ export const PROBLEM_REGISTRY: ProblemDefinition[] = [
58
75
  metric: "custom",
59
76
  custom: (data) => {
60
77
  const rate = data.compactsPerSession as number;
61
- if (rate >= 3) return "high";
62
- if (rate >= 2) return "medium";
78
+ if (rate >= COMPACT_HIGH_THRESHOLD) return "high";
79
+ if (rate >= COMPACT_MEDIUM_THRESHOLD) return "medium";
63
80
  return "low";
64
81
  },
65
82
  },
@@ -86,7 +103,7 @@ export const PROBLEM_REGISTRY: ProblemDefinition[] = [
86
103
  category: "context",
87
104
  severity: {
88
105
  metric: "rate",
89
- thresholds: { medium: 0.7, high: 0.9 },
106
+ thresholds: { medium: CONTEXT_UTILIZATION_MEDIUM, high: CONTEXT_UTILIZATION_HIGH },
90
107
  },
91
108
  detector: {
92
109
  events: ["turn_end"],
@@ -111,7 +128,7 @@ export const PROBLEM_REGISTRY: ProblemDefinition[] = [
111
128
  category: "subagent",
112
129
  severity: {
113
130
  metric: "rate",
114
- thresholds: { medium: 0.2, high: 0.4 },
131
+ thresholds: { medium: SUBAGENT_FAILURE_MEDIUM, high: SUBAGENT_FAILURE_HIGH },
115
132
  },
116
133
  detector: {
117
134
  events: ["tool_result"],
@@ -136,7 +153,7 @@ export const PROBLEM_REGISTRY: ProblemDefinition[] = [
136
153
  category: "tool",
137
154
  severity: {
138
155
  metric: "rate",
139
- thresholds: { medium: 0.1, high: 0.25 },
156
+ thresholds: { medium: PARAM_ERROR_MEDIUM, high: PARAM_ERROR_HIGH },
140
157
  },
141
158
  detector: {
142
159
  events: ["tool_result"],
@@ -163,8 +180,8 @@ export const PROBLEM_REGISTRY: ProblemDefinition[] = [
163
180
  metric: "custom",
164
181
  custom: (data) => {
165
182
  const maxPhaseRatio = data.maxPhaseDurationRatio as number;
166
- if (maxPhaseRatio > 0.7) return "high";
167
- if (maxPhaseRatio > 0.5) return "medium";
183
+ if (maxPhaseRatio > WORKFLOW_PHASE_RATIO_HIGH) return "high";
184
+ if (maxPhaseRatio > WORKFLOW_PHASE_RATIO_MEDIUM) return "medium";
168
185
  return "low";
169
186
  },
170
187
  },
@@ -194,8 +211,8 @@ export const PROBLEM_REGISTRY: ProblemDefinition[] = [
194
211
  custom: (data) => {
195
212
  const completionRate = data.taskCompletionRate as number;
196
213
  const cancelRate = data.taskCancelRate as number;
197
- if (completionRate < 0.5 || cancelRate > 0.4) return "high";
198
- if (completionRate < 0.7 || cancelRate > 0.2) return "medium";
214
+ if (completionRate < GOAL_LOW_COMPLETION_HIGH || cancelRate > GOAL_HIGH_CANCEL) return "high";
215
+ if (completionRate < GOAL_LOW_COMPLETION_MEDIUM || cancelRate > GOAL_MEDIUM_CANCEL) return "medium";
199
216
  return "low";
200
217
  },
201
218
  },
@@ -13,7 +13,7 @@ import type {
13
13
  Theme,
14
14
  } from "@mariozechner/pi-coding-agent";
15
15
  import { Text } from "@mariozechner/pi-tui";
16
-
16
+ import type { Static } from "typebox";
17
17
 
18
18
  import {
19
19
  canTransition,
@@ -21,13 +21,46 @@ import {
21
21
  deserializeState,
22
22
  isTerminalStatus,
23
23
  serializeState,
24
- TrackerParams,
25
-
26
24
  type TrackedItem,
25
+ type TrackedItemStatus,
27
26
  type TrackerDetails,
27
+ TrackerParams,
28
28
  type TrackerRuntimeState,
29
29
  } from "./types";
30
30
 
31
+ // ── Pi SDK custom event API type ──────────────────────
32
+
33
+ type PiOnAny = {
34
+ on(event: string, handler: (...args: unknown[]) => unknown): void;
35
+ };
36
+
37
+ // ── Stale context detection ──────────────────────────
38
+
39
+ const STALE_CONTEXT_PATTERNS = [
40
+ "Extension context no longer active",
41
+ "aborted",
42
+ "context canceled",
43
+ "stale context",
44
+ "stalecontext",
45
+ ];
46
+
47
+ /** Detect errors that indicate the Pi session has been torn down (compact/reload/exit). */
48
+ function isStaleContextError(error: unknown): boolean {
49
+ if (!(error instanceof Error)) return false;
50
+ const msg = error.message.toLowerCase();
51
+ return STALE_CONTEXT_PATTERNS.some((p) => msg.includes(p));
52
+ }
53
+
54
+ // ── Tool execute/render param types ──────────────────
55
+
56
+ type RenderOptions = { expanded?: boolean };
57
+
58
+ type ToolResult = {
59
+ content: Array<{ type: "text"; text: string }>;
60
+ details: Record<string, unknown> | undefined;
61
+ isError?: boolean;
62
+ };
63
+
31
64
  // ── Tracker 配置接口(避免 types.ts 引入 Pi API 类型)──
32
65
 
33
66
  export interface TrackerConfig<TMeta = Record<string, unknown>> {
@@ -89,6 +122,74 @@ function formatItemList<TMeta>(
89
122
  .join("\n");
90
123
  }
91
124
 
125
+ // ── Tool render helpers (extracted to keep createTracker ≤300 lines) ──
126
+
127
+ function renderTrackerCall<TMeta>(
128
+ args: Record<string, unknown>,
129
+ config: TrackerConfig<TMeta>,
130
+ theme: Theme,
131
+ ): Text {
132
+ const parts = [
133
+ theme.fg("toolTitle", theme.bold(`${config.toolName} `)),
134
+ theme.fg("muted", String(args.action ?? "")),
135
+ ];
136
+ if (args.id !== undefined)
137
+ parts.push(theme.fg("accent", `#${String(args.id)}`));
138
+ if (args.status !== undefined)
139
+ parts.push(theme.fg("warning", String(args.status)));
140
+ if (args.detail !== undefined)
141
+ parts.push(theme.fg("dim", `"${String(args.detail)}"`));
142
+ return new Text(parts.join(" "), 0, 0);
143
+ }
144
+
145
+ function renderTrackerResult<TMeta>(
146
+ result: ToolResult,
147
+ options: RenderOptions,
148
+ config: TrackerConfig<TMeta>,
149
+ theme: Theme,
150
+ ): Text {
151
+ if (config.renderResult && result.details) {
152
+ return config.renderResult(
153
+ result.details as unknown as TrackerDetails<TMeta>,
154
+ options,
155
+ theme,
156
+ );
157
+ }
158
+
159
+ if (!result.details) {
160
+ return new Text(theme.fg("dim", "(no details)"), 0, 0);
161
+ }
162
+
163
+ // 框架默认渲染
164
+ const details = result.details as unknown as TrackerDetails<TMeta>;
165
+ if (details.error) {
166
+ return new Text(
167
+ theme.fg("error", `[${config.name}] Error: ${details.error}`),
168
+ 0,
169
+ 0,
170
+ );
171
+ }
172
+
173
+ const prefix = theme.fg("accent", `[${config.name}] `);
174
+ const summary = `${details.action}: ${details.items.length} items`;
175
+
176
+ if (!options.expanded) {
177
+ return new Text(prefix + theme.fg("dim", summary), 0, 0);
178
+ }
179
+
180
+ const items = details.items
181
+ .map((item) => {
182
+ const terminal = isTerminalStatus(item.status) ? " ✓" : "";
183
+ return ` #${item.id} ${item.name} [${item.status}]${terminal}`;
184
+ })
185
+ .join("\n");
186
+ return new Text(
187
+ prefix + summary + "\n" + theme.fg("dim", items),
188
+ 0,
189
+ 0,
190
+ );
191
+ }
192
+
92
193
  // ── 工厂函数 ────────────────────────────────────────
93
194
 
94
195
  export function createTracker<TMeta>(
@@ -100,8 +201,19 @@ export function createTracker<TMeta>(
100
201
  // ── 持久化 + GC ───────────────────────────────────
101
202
 
102
203
  function persistState(ctx: ExtensionContext): void {
103
- pi.appendEntry(config.entryType, serializeState(state));
104
- const entries = ctx.sessionManager.getEntries();
204
+ let entries: SessionEntry[];
205
+ try {
206
+ pi.appendEntry(config.entryType, serializeState(state));
207
+ entries = ctx.sessionManager.getEntries();
208
+ } catch (e) {
209
+ if (isStaleContextError(e)) {
210
+ console.warn(
211
+ `[${config.name}] skip persist: stale context (${(e as Error).message})`,
212
+ );
213
+ return;
214
+ }
215
+ throw e;
216
+ }
105
217
  const staleIndices: number[] = [];
106
218
  let foundLatest = false;
107
219
  for (let i = entries.length - 1; i >= 0; i--) {
@@ -121,7 +233,19 @@ export function createTracker<TMeta>(
121
233
  // ── 状态恢复 ──────────────────────────────────────
122
234
 
123
235
  function reconstructState(ctx: ExtensionContext): void {
124
- const entries = ctx.sessionManager.getEntries();
236
+ let entries: SessionEntry[];
237
+ try {
238
+ entries = ctx.sessionManager.getEntries();
239
+ } catch (e) {
240
+ if (isStaleContextError(e)) {
241
+ console.warn(
242
+ `[${config.name}] skip reconstruct: stale context (${(e as Error).message})`,
243
+ );
244
+ state = createInitialState<TMeta>();
245
+ return;
246
+ }
247
+ throw e;
248
+ }
125
249
  const allTypes = [config.entryType, ...(config.legacyEntryTypes ?? [])];
126
250
 
127
251
  let latestData: Record<string, unknown> | undefined;
@@ -181,10 +305,10 @@ export function createTracker<TMeta>(
181
305
  // ── Event: triggerEvent (e.g. tool_call) ───────────
182
306
 
183
307
  // Pi 事件系统支持任意字符串事件名,但类型定义不完整(与 session_compact 同)
184
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
185
- (pi as any).on(
308
+ (pi as unknown as PiOnAny).on(
186
309
  config.triggerEvent,
187
- async (event: unknown, ctx: ExtensionContext) => {
310
+ async (event, nextCtx) => {
311
+ const ctx = nextCtx as ExtensionContext;
188
312
  const match = config.triggerMatch(event);
189
313
  if (!match) return;
190
314
 
@@ -223,10 +347,11 @@ export function createTracker<TMeta>(
223
347
 
224
348
  // ── Event: turn_end(remind 检查)─────────────────
225
349
 
226
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
227
- (pi as any).on(
350
+ (pi as unknown as PiOnAny).on(
228
351
  "turn_end",
229
- async (event: Record<string, unknown>, ctx: ExtensionContext) => {
352
+ async (rawEvent, nextCtx) => {
353
+ const event = rawEvent as Record<string, unknown>;
354
+ const ctx = nextCtx as ExtensionContext;
230
355
  const eventTurnIndex = event.turnIndex;
231
356
  if (typeof eventTurnIndex === "number") {
232
357
  state.currentTurnIndex = eventTurnIndex;
@@ -285,8 +410,7 @@ export function createTracker<TMeta>(
285
410
  pi.registerMessageRenderer(
286
411
  customType,
287
412
  (
288
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
289
- message: any,
413
+ message: { content: string | unknown },
290
414
  _options: unknown,
291
415
  theme: Theme,
292
416
  ) => {
@@ -313,14 +437,13 @@ export function createTracker<TMeta>(
313
437
  promptGuidelines: config.promptGuidelines,
314
438
  parameters: TrackerParams,
315
439
 
316
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
317
440
  async execute(
318
441
  _toolCallId: string,
319
- params: any,
320
- _signal: any,
321
- _onUpdate: any,
442
+ params: Static<typeof TrackerParams>,
443
+ _signal: AbortSignal | undefined,
444
+ _onUpdate: unknown,
322
445
  ctx: ExtensionContext,
323
- ): Promise<any> {
446
+ ): Promise<ToolResult> {
324
447
  // ── list ──
325
448
  if (params.action === "list") {
326
449
  return {
@@ -339,30 +462,32 @@ export function createTracker<TMeta>(
339
462
  }
340
463
 
341
464
  // ── update ──
342
- if (params.id === undefined) {
343
- return { content: [{ type: "text", text: "update action requires id parameter" }], isError: true };
465
+ const updateId = params.id as number | undefined;
466
+ const updateStatus = params.status as string | undefined;
467
+ if (updateId === undefined) {
468
+ return { content: [{ type: "text", text: "update action requires id parameter" }], details: undefined, isError: true };
344
469
  }
345
- if (params.status === undefined) {
346
- return { content: [{ type: "text", text: "update action requires status parameter" }], isError: true };
470
+ if (updateStatus === undefined) {
471
+ return { content: [{ type: "text", text: "update action requires status parameter" }], details: undefined, isError: true };
347
472
  }
348
473
 
349
474
  const itemIndex = state.items.findIndex(
350
- (item) => item.id === params.id,
475
+ (item) => item.id === updateId,
351
476
  );
352
477
  if (itemIndex === -1) {
353
- return { content: [{ type: "text", text: `TrackedItem id=${params.id} not found` }], isError: true };
478
+ return { content: [{ type: "text", text: `TrackedItem id=${updateId} not found` }], details: undefined, isError: true };
354
479
  }
355
480
 
356
481
  const item = state.items[itemIndex];
357
- if (!canTransition(item.status, params.status)) {
358
- return { content: [{ type: "text", text: `Invalid transition: ${item.status} → ${params.status} (current: ${item.status}, terminal states are immutable or path not allowed)` }], isError: true };
482
+ if (!canTransition(item.status, updateStatus as TrackedItemStatus)) {
483
+ return { content: [{ type: "text", text: `Invalid transition: ${item.status} → ${updateStatus} (current: ${item.status}, terminal states are immutable or path not allowed)` }], details: undefined, isError: true };
359
484
  }
360
485
 
361
486
  // 执行转换
362
- item.status = params.status;
363
- item.detail = params.detail ?? item.detail;
487
+ item.status = updateStatus as TrackedItemStatus;
488
+ item.detail = (params.detail as string | undefined | null) ?? item.detail;
364
489
 
365
- if (params.status === "error") {
490
+ if (updateStatus === "error") {
366
491
  item.errorCount += 1;
367
492
  if (item.errorCount >= config.errorThreshold) {
368
493
  await pi.sendUserMessage(config.steering.onError(item), {
@@ -390,68 +515,21 @@ export function createTracker<TMeta>(
390
515
  };
391
516
  },
392
517
 
393
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
394
518
  renderCall(
395
- args: any,
519
+ args: Record<string, unknown>,
396
520
  theme: Theme,
397
521
  _context?: unknown,
398
522
  ) {
399
- const parts = [
400
- theme.fg("toolTitle", theme.bold(`${config.toolName} `)),
401
- theme.fg("muted", String(args.action ?? "")),
402
- ];
403
- if (args.id !== undefined)
404
- parts.push(theme.fg("accent", `#${String(args.id)}`));
405
- if (args.status !== undefined)
406
- parts.push(theme.fg("warning", String(args.status)));
407
- if (args.detail !== undefined)
408
- parts.push(theme.fg("dim", `"${String(args.detail)}"`));
409
- return new Text(parts.join(" "), 0, 0);
523
+ return renderTrackerCall(args, config, theme);
410
524
  },
411
525
 
412
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
413
526
  renderResult(
414
- result: any,
415
- options: any,
527
+ result: { content: Array<{ type: "text"; text?: string } | { type: "image"; data: string; mimeType: string }>; details?: Record<string, unknown> },
528
+ _options: unknown,
416
529
  theme: Theme,
417
530
  _context?: unknown,
418
531
  ) {
419
- if (config.renderResult) {
420
- return config.renderResult(
421
- result.details as TrackerDetails<TMeta>,
422
- options,
423
- theme,
424
- );
425
- }
426
-
427
- // 框架默认渲染
428
- const details = result.details as TrackerDetails<TMeta>;
429
- if (details.error) {
430
- return new Text(
431
- theme.fg("error", `[${config.name}] Error: ${details.error}`),
432
- 0,
433
- 0,
434
- );
435
- }
436
-
437
- const prefix = theme.fg("accent", `[${config.name}] `);
438
- const summary = `${details.action}: ${details.items.length} items`;
439
-
440
- if (!options.expanded) {
441
- return new Text(prefix + theme.fg("dim", summary), 0, 0);
442
- }
443
-
444
- const items = details.items
445
- .map((item) => {
446
- const terminal = isTerminalStatus(item.status) ? " ✓" : "";
447
- return ` #${item.id} ${item.name} [${item.status}]${terminal}`;
448
- })
449
- .join("\n");
450
- return new Text(
451
- prefix + summary + "\n" + theme.fg("dim", items),
452
- 0,
453
- 0,
454
- );
532
+ return renderTrackerResult(result as ToolResult, { expanded: (_options as Record<string, unknown> | undefined)?.expanded as boolean | undefined }, config, theme);
455
533
  },
456
534
  });
457
535
  }
@@ -5,8 +5,8 @@
5
5
  * 追踪 skill 的加载、执行、异常、记录全生命周期。
6
6
  */
7
7
 
8
- import type { TrackedItem } from "./types";
9
8
  import type { TrackerConfig } from "./core";
9
+ import type { TrackedItem } from "./types";
10
10
 
11
11
  // ── Metadata 类型 ───────────────────────────────────
12
12