@zhushanwen/pi-evolve-daily 0.1.6 → 0.1.8
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/package.json +2 -1
- package/src/detectors/compact.ts +7 -1
- package/src/detectors/goal-quality.ts +7 -1
- package/src/detectors/param-error.ts +9 -2
- package/src/detectors/subagent-result.ts +7 -1
- package/src/index.ts +25 -13
- package/src/problems.ts +50 -33
- package/src/trackers/core.ts +162 -84
- package/src/trackers/skill-execution.ts +22 -22
- package/src/trackers/types.ts +3 -3
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-evolve-daily",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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": [
|
package/src/detectors/compact.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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,
|
|
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(
|
|
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 {
|
|
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,
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
114
|
+
(pi as unknown as PiOnAny).on(
|
|
105
115
|
"tool_result",
|
|
106
|
-
async (event:
|
|
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(
|
|
110
|
-
const item = detector.createItem(
|
|
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,17 +49,34 @@ 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",
|
|
55
|
-
name: "Compact
|
|
72
|
+
name: "Compact Frequency",
|
|
56
73
|
category: "context",
|
|
57
74
|
severity: {
|
|
58
75
|
metric: "custom",
|
|
59
76
|
custom: (data) => {
|
|
60
77
|
const rate = data.compactsPerSession as number;
|
|
61
|
-
if (rate >=
|
|
62
|
-
if (rate >=
|
|
78
|
+
if (rate >= COMPACT_HIGH_THRESHOLD) return "high";
|
|
79
|
+
if (rate >= COMPACT_MEDIUM_THRESHOLD) return "medium";
|
|
63
80
|
return "low";
|
|
64
81
|
},
|
|
65
82
|
},
|
|
@@ -68,103 +85,103 @@ export const PROBLEM_REGISTRY: ProblemDefinition[] = [
|
|
|
68
85
|
match: { custom: "compactDetector" },
|
|
69
86
|
template: { category: "context-pressure" },
|
|
70
87
|
steering:
|
|
71
|
-
"
|
|
88
|
+
"Compact triggered (id={{id}}). Evaluate whether critical context was lost. If lost, update status=error, detail='what was lost'. If unaffected, update status=completed.",
|
|
72
89
|
},
|
|
73
90
|
analysis: {
|
|
74
91
|
extractor: "compact",
|
|
75
92
|
minerRules: ["compact-high-frequency", "compact-early-trigger"],
|
|
76
93
|
},
|
|
77
94
|
suggestion: {
|
|
78
|
-
title: "
|
|
79
|
-
description: "
|
|
95
|
+
title: "Optimize Compact Frequency",
|
|
96
|
+
description: "High compact frequency indicates inefficient context management",
|
|
80
97
|
defaultSeverity: "medium",
|
|
81
98
|
},
|
|
82
99
|
},
|
|
83
100
|
{
|
|
84
101
|
id: "context-utilization",
|
|
85
|
-
name: "
|
|
102
|
+
name: "Context Window Utilization",
|
|
86
103
|
category: "context",
|
|
87
104
|
severity: {
|
|
88
105
|
metric: "rate",
|
|
89
|
-
thresholds: { medium:
|
|
106
|
+
thresholds: { medium: CONTEXT_UTILIZATION_MEDIUM, high: CONTEXT_UTILIZATION_HIGH },
|
|
90
107
|
},
|
|
91
108
|
detector: {
|
|
92
109
|
events: ["turn_end"],
|
|
93
110
|
match: { custom: "contextUtilizationMatcher" },
|
|
94
111
|
template: { category: "context-pressure" },
|
|
95
112
|
steering:
|
|
96
|
-
"
|
|
113
|
+
"Current context utilization {{usageRate}} (id={{id}}). If near limit, update status=completed, detail='needs compact'. If sufficient, update status=dismissed.",
|
|
97
114
|
},
|
|
98
115
|
analysis: {
|
|
99
116
|
extractor: "context",
|
|
100
117
|
minerRules: ["context-high-utilization"],
|
|
101
118
|
},
|
|
102
119
|
suggestion: {
|
|
103
|
-
title: "
|
|
104
|
-
description: "
|
|
120
|
+
title: "Optimize Context Utilization",
|
|
121
|
+
description: "Persistently high context utilization triggers frequent compacts",
|
|
105
122
|
defaultSeverity: "medium",
|
|
106
123
|
},
|
|
107
124
|
},
|
|
108
125
|
{
|
|
109
126
|
id: "subagent-efficiency",
|
|
110
|
-
name: "Subagent
|
|
127
|
+
name: "Subagent Scheduling Efficiency",
|
|
111
128
|
category: "subagent",
|
|
112
129
|
severity: {
|
|
113
130
|
metric: "rate",
|
|
114
|
-
thresholds: { medium:
|
|
131
|
+
thresholds: { medium: SUBAGENT_FAILURE_MEDIUM, high: SUBAGENT_FAILURE_HIGH },
|
|
115
132
|
},
|
|
116
133
|
detector: {
|
|
117
134
|
events: ["tool_result"],
|
|
118
135
|
match: { toolName: "subagent", custom: "subagentResultMatcher" },
|
|
119
136
|
template: { category: "subagent" },
|
|
120
137
|
steering:
|
|
121
|
-
"Subagent
|
|
138
|
+
"Subagent task completed (id={{id}}). exitCode={{exitCode}}, duration={{duration}}. If satisfactory, update status=completed. If retry needed, update status=error, detail='reason'.",
|
|
122
139
|
},
|
|
123
140
|
analysis: {
|
|
124
141
|
extractor: "subagent",
|
|
125
142
|
minerRules: ["subagent-failure-rate", "subagent-high-retry"],
|
|
126
143
|
},
|
|
127
144
|
suggestion: {
|
|
128
|
-
title: "
|
|
129
|
-
description: "
|
|
145
|
+
title: "Optimize Subagent Scheduling",
|
|
146
|
+
description: "High subagent failure or retry rate",
|
|
130
147
|
defaultSeverity: "medium",
|
|
131
148
|
},
|
|
132
149
|
},
|
|
133
150
|
{
|
|
134
151
|
id: "tool-param-validation",
|
|
135
|
-
name: "
|
|
152
|
+
name: "Tool Parameter Validation Failure",
|
|
136
153
|
category: "tool",
|
|
137
154
|
severity: {
|
|
138
155
|
metric: "rate",
|
|
139
|
-
thresholds: { medium:
|
|
156
|
+
thresholds: { medium: PARAM_ERROR_MEDIUM, high: PARAM_ERROR_HIGH },
|
|
140
157
|
},
|
|
141
158
|
detector: {
|
|
142
159
|
events: ["tool_result"],
|
|
143
160
|
match: { isError: true, custom: "paramErrorMatcher" },
|
|
144
161
|
template: { category: "tool-error" },
|
|
145
162
|
steering:
|
|
146
|
-
"
|
|
163
|
+
"Detected {{toolName}} parameter error (id={{id}}). Error: {{errorPreview}}. If cause understood, update status=completed, detail='cause and fix'. If unclear, update status=error.",
|
|
147
164
|
},
|
|
148
165
|
analysis: {
|
|
149
166
|
extractor: "tool_errors",
|
|
150
167
|
minerRules: ["param-error-rate", "edit-match-failure", "low-self-correction"],
|
|
151
168
|
},
|
|
152
169
|
suggestion: {
|
|
153
|
-
title: "
|
|
154
|
-
description: "
|
|
170
|
+
title: "Reduce Tool Parameter Error Rate",
|
|
171
|
+
description: "High parameter error rate suggests the AI does not understand tool usage",
|
|
155
172
|
defaultSeverity: "high",
|
|
156
173
|
},
|
|
157
174
|
},
|
|
158
175
|
{
|
|
159
176
|
id: "workflow-phase-duration",
|
|
160
|
-
name: "
|
|
177
|
+
name: "Workflow Phase Duration",
|
|
161
178
|
category: "workflow",
|
|
162
179
|
severity: {
|
|
163
180
|
metric: "custom",
|
|
164
181
|
custom: (data) => {
|
|
165
182
|
const maxPhaseRatio = data.maxPhaseDurationRatio as number;
|
|
166
|
-
if (maxPhaseRatio >
|
|
167
|
-
if (maxPhaseRatio >
|
|
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
|
},
|
|
@@ -173,29 +190,29 @@ export const PROBLEM_REGISTRY: ProblemDefinition[] = [
|
|
|
173
190
|
match: { toolName: ["coding-workflow-gate", "coding-workflow-phase-start"] },
|
|
174
191
|
template: { category: "workflow" },
|
|
175
192
|
steering:
|
|
176
|
-
"
|
|
193
|
+
"Workflow phase {{phase}} completed (id={{id}}). gate={{gateResult}}, duration={{duration}}. If smooth, update status=completed. If issues, update status=error, detail='issue description'.",
|
|
177
194
|
},
|
|
178
195
|
analysis: {
|
|
179
196
|
extractor: "workflow",
|
|
180
197
|
minerRules: ["workflow-slow-phase", "workflow-gate-retry"],
|
|
181
198
|
},
|
|
182
199
|
suggestion: {
|
|
183
|
-
title: "
|
|
184
|
-
description: "
|
|
200
|
+
title: "Optimize Workflow Phase Efficiency",
|
|
201
|
+
description: "Disproportionate phase duration or frequent gate retries",
|
|
185
202
|
defaultSeverity: "medium",
|
|
186
203
|
},
|
|
187
204
|
},
|
|
188
205
|
{
|
|
189
206
|
id: "goal-task-quality",
|
|
190
|
-
name: "Goal
|
|
207
|
+
name: "Goal Task Decomposition Quality",
|
|
191
208
|
category: "workflow",
|
|
192
209
|
severity: {
|
|
193
210
|
metric: "custom",
|
|
194
211
|
custom: (data) => {
|
|
195
212
|
const completionRate = data.taskCompletionRate as number;
|
|
196
213
|
const cancelRate = data.taskCancelRate as number;
|
|
197
|
-
if (completionRate <
|
|
198
|
-
if (completionRate <
|
|
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
|
},
|
|
@@ -204,7 +221,7 @@ export const PROBLEM_REGISTRY: ProblemDefinition[] = [
|
|
|
204
221
|
match: { toolName: "goal_manager", custom: "goalQualityMatcher" },
|
|
205
222
|
template: { category: "workflow" },
|
|
206
223
|
steering:
|
|
207
|
-
"Goal
|
|
224
|
+
"Goal task updated (id={{id}}). Completion rate={{completionRate}}. If objective met, update status=completed, detail='completion summary'. If blocked, update status=error, detail='blocker description'.",
|
|
208
225
|
},
|
|
209
226
|
analysis: {
|
|
210
227
|
extractor: "goal_quality",
|
|
@@ -216,8 +233,8 @@ export const PROBLEM_REGISTRY: ProblemDefinition[] = [
|
|
|
216
233
|
],
|
|
217
234
|
},
|
|
218
235
|
suggestion: {
|
|
219
|
-
title: "
|
|
220
|
-
description: "
|
|
236
|
+
title: "Optimize Goal Task Decomposition",
|
|
237
|
+
description: "Low task completion rate or poor evidence quality",
|
|
221
238
|
defaultSeverity: "high",
|
|
222
239
|
},
|
|
223
240
|
},
|
package/src/trackers/core.ts
CHANGED
|
@@ -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>> {
|
|
@@ -78,7 +111,7 @@ function formatItemList<TMeta>(
|
|
|
78
111
|
items: TrackedItem<TMeta>[],
|
|
79
112
|
trackerName: string,
|
|
80
113
|
): string {
|
|
81
|
-
if (items.length === 0) return
|
|
114
|
+
if (items.length === 0) return `No active tracked items (${trackerName}).`;
|
|
82
115
|
return items
|
|
83
116
|
.map(
|
|
84
117
|
(item) =>
|
|
@@ -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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
185
|
-
(pi as any).on(
|
|
308
|
+
(pi as unknown as PiOnAny).on(
|
|
186
309
|
config.triggerEvent,
|
|
187
|
-
async (event
|
|
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
|
-
|
|
227
|
-
(pi as any).on(
|
|
350
|
+
(pi as unknown as PiOnAny).on(
|
|
228
351
|
"turn_end",
|
|
229
|
-
async (
|
|
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
|
-
|
|
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:
|
|
320
|
-
_signal:
|
|
321
|
-
_onUpdate:
|
|
442
|
+
params: Static<typeof TrackerParams>,
|
|
443
|
+
_signal: AbortSignal | undefined,
|
|
444
|
+
_onUpdate: unknown,
|
|
322
445
|
ctx: ExtensionContext,
|
|
323
|
-
): Promise<
|
|
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
|
-
|
|
343
|
-
|
|
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 (
|
|
346
|
-
return { content: [{ type: "text", text: "update
|
|
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 ===
|
|
475
|
+
(item) => item.id === updateId,
|
|
351
476
|
);
|
|
352
477
|
if (itemIndex === -1) {
|
|
353
|
-
return { content: [{ type: "text", text: `TrackedItem id=${
|
|
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,
|
|
358
|
-
return { content: [{ type: "text", text:
|
|
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 =
|
|
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 (
|
|
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), {
|
|
@@ -373,7 +498,7 @@ export function createTracker<TMeta>(
|
|
|
373
498
|
|
|
374
499
|
persistState(ctx);
|
|
375
500
|
|
|
376
|
-
const statusText = isTerminalStatus(item.status) ? "
|
|
501
|
+
const statusText = isTerminalStatus(item.status) ? " (terminal)" : "";
|
|
377
502
|
return {
|
|
378
503
|
content: [
|
|
379
504
|
{
|
|
@@ -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:
|
|
519
|
+
args: Record<string, unknown>,
|
|
396
520
|
theme: Theme,
|
|
397
521
|
_context?: unknown,
|
|
398
522
|
) {
|
|
399
|
-
|
|
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:
|
|
415
|
-
|
|
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
|
-
|
|
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}] 错误: ${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
|
|
|
@@ -32,9 +32,9 @@ export function extractSkillName(path: string): string | null {
|
|
|
32
32
|
|
|
33
33
|
function loadedSteeringPrompt(name: string, id: number): string {
|
|
34
34
|
return (
|
|
35
|
-
`[SKILL-STATE] skill "${name}"
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
`[SKILL-STATE] skill "${name}" loaded and tracking started (id=${id}).\n` +
|
|
36
|
+
`When done, call skill_state(action=update, id=${id}, status=completed).\n` +
|
|
37
|
+
`If blocked, call skill_state(action=update, id=${id}, status=error, detail="reason").`
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -42,17 +42,17 @@ function remindSteeringPrompt(
|
|
|
42
42
|
name: string,
|
|
43
43
|
turnsSinceLoad: number,
|
|
44
44
|
): string {
|
|
45
|
-
return `[SKILL-STATE] skill "${name}"
|
|
45
|
+
return `[SKILL-STATE] skill "${name}" loaded ${turnsSinceLoad} turns ago without reaching terminal state. Please call skill_state to update its status.`;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
function errorForceRecordPrompt(item: TrackedItem<SkillMeta>): string {
|
|
49
49
|
return (
|
|
50
|
-
`[SKILL-STATE] skill "${item.name}"
|
|
51
|
-
|
|
52
|
-
`1.
|
|
53
|
-
`2.
|
|
54
|
-
`3.
|
|
55
|
-
|
|
50
|
+
`[SKILL-STATE] skill "${item.name}" has reached ${item.errorCount} errors — issue recording required.\n` +
|
|
51
|
+
`Immediately call the subagent tool (background mode) with this task:\n` +
|
|
52
|
+
`1. Read ${item.metadata.skillMdPath}\n` +
|
|
53
|
+
`2. Analyze issues encountered during skill "${item.name}" execution based on current session context\n` +
|
|
54
|
+
`3. Generate a structured issue record (skill name, error count, issue description, improvement suggestions)\n` +
|
|
55
|
+
`After completion, call skill_state(action=update, id=${item.id}, status=recorded).`
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -62,7 +62,7 @@ function agentStartContextPrompt(items: TrackedItem<SkillMeta>[]): string {
|
|
|
62
62
|
(item) => ` - "${item.name}" (id=${item.id}, status=${item.status})`,
|
|
63
63
|
);
|
|
64
64
|
return (
|
|
65
|
-
`[SKILL-STATE]
|
|
65
|
+
`[SKILL-STATE] The following skills are being tracked — call skill_state to update their status when appropriate:\n` +
|
|
66
66
|
lines.join("\n")
|
|
67
67
|
);
|
|
68
68
|
}
|
|
@@ -74,17 +74,17 @@ export const skillExecutionConfig: TrackerConfig<SkillMeta> = {
|
|
|
74
74
|
toolName: "skill_state",
|
|
75
75
|
label: "Skill State",
|
|
76
76
|
description:
|
|
77
|
-
"
|
|
78
|
-
"\n\
|
|
79
|
-
"\n- list
|
|
80
|
-
"\n- update
|
|
81
|
-
promptSnippet: "
|
|
77
|
+
"Manage skill execution tracking state." +
|
|
78
|
+
"\n\nAvailable actions:" +
|
|
79
|
+
"\n- list: View all TrackedItems" +
|
|
80
|
+
"\n- update: Update TrackedItem status (requires id and status)",
|
|
81
|
+
promptSnippet: "Track skill execution status with automatic skill load detection",
|
|
82
82
|
promptGuidelines: [
|
|
83
|
-
"[
|
|
84
|
-
"[
|
|
85
|
-
"[
|
|
86
|
-
"[
|
|
87
|
-
"[
|
|
83
|
+
"[Trigger] Tracking is auto-created when a skill loads — no manual creation needed",
|
|
84
|
+
"[Transition] After execution, use update status=completed to mark success",
|
|
85
|
+
"[Error] When blocked, use update status=error to mark the exception",
|
|
86
|
+
"[Record] After 2 accumulated errors, the system requests issue recording — when done, update status=recorded",
|
|
87
|
+
"[Query] Use list anytime to view all tracking states",
|
|
88
88
|
],
|
|
89
89
|
|
|
90
90
|
triggerEvent: "tool_call",
|
package/src/trackers/types.ts
CHANGED
|
@@ -79,15 +79,15 @@ export interface TrackerDetails<
|
|
|
79
79
|
export const TrackerParams = Type.Object({
|
|
80
80
|
action: StringEnum(["update", "list"] as const),
|
|
81
81
|
id: Type.Optional(
|
|
82
|
-
Type.Number({ description: "TrackedItem ID
|
|
82
|
+
Type.Number({ description: "TrackedItem ID (required for update)" }),
|
|
83
83
|
),
|
|
84
84
|
status: Type.Optional(
|
|
85
85
|
StringEnum(["completed", "error", "recorded"] as const, {
|
|
86
|
-
description: "
|
|
86
|
+
description: "Target status (required for update)",
|
|
87
87
|
}),
|
|
88
88
|
),
|
|
89
89
|
detail: Type.Optional(
|
|
90
|
-
Type.String({ description: "
|
|
90
|
+
Type.String({ description: "Additional notes (e.g. error reason)" }),
|
|
91
91
|
),
|
|
92
92
|
});
|
|
93
93
|
|