@jmtrin/opencode-kevin 0.1.4 → 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.
- package/README.md +50 -16
- package/dist/migrations/003_v02_signal.sql +58 -0
- package/dist/plugin/ContextInjector.d.ts +13 -2
- package/dist/plugin/ContextInjector.js +42 -23
- package/dist/plugin/ContextInjector.js.map +1 -1
- package/dist/plugin/MemoryService.d.ts +49 -2
- package/dist/plugin/MemoryService.js +182 -15
- package/dist/plugin/MemoryService.js.map +1 -1
- package/dist/plugin/Migrate.d.ts +4 -1
- package/dist/plugin/Migrate.js +28 -1
- package/dist/plugin/Migrate.js.map +1 -1
- package/dist/plugin/PatternMiner.d.ts +49 -0
- package/dist/plugin/PatternMiner.js +133 -0
- package/dist/plugin/PatternMiner.js.map +1 -0
- package/dist/plugin/Reflector.d.ts +29 -2
- package/dist/plugin/Reflector.js +90 -7
- package/dist/plugin/Reflector.js.map +1 -1
- package/dist/plugin/Retrospective.d.ts +4 -1
- package/dist/plugin/Retrospective.js +72 -5
- package/dist/plugin/Retrospective.js.map +1 -1
- package/dist/plugin/Store.d.ts +15 -0
- package/dist/plugin/Store.js +15 -0
- package/dist/plugin/Store.js.map +1 -1
- package/dist/plugin/ToolCallObserver.d.ts +4 -1
- package/dist/plugin/ToolCallObserver.js +39 -7
- package/dist/plugin/ToolCallObserver.js.map +1 -1
- package/dist/plugin/fingerprint.d.ts +27 -0
- package/dist/plugin/fingerprint.js +74 -0
- package/dist/plugin/fingerprint.js.map +1 -0
- package/dist/plugin/index.js +95 -24
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/memory-format.d.ts +8 -0
- package/dist/plugin/memory-format.js +20 -0
- package/dist/plugin/memory-format.js.map +1 -0
- package/dist/plugin/metrics.d.ts +60 -0
- package/dist/plugin/metrics.js +159 -0
- package/dist/plugin/metrics.js.map +1 -0
- package/dist/plugin/redact.d.ts +1 -0
- package/dist/plugin/redact.js +7 -0
- package/dist/plugin/redact.js.map +1 -1
- package/migrations/003_v02_signal.sql +58 -0
- package/package.json +48 -48
package/dist/plugin/Reflector.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fingerprint as computeFingerprint } from "./fingerprint.js";
|
|
1
2
|
import { redactPaths as redactPathsText } from "./redact.js";
|
|
2
3
|
const DEFAULT_THROTTLE_MS = 60_000;
|
|
3
4
|
const MAX_CONTENT_CHARS = 4096;
|
|
@@ -6,6 +7,12 @@ const TRUNC_SUFFIX = "... [truncated]";
|
|
|
6
7
|
const CONTEXT_PREFIX = "\n\nContext:\n";
|
|
7
8
|
export const ERROR_LINE_RE = /\b(error|failed|fail|cannot find|cannot resolve|TS\d{4,}|exception|traceback|panic|fatal|referenceerror|typeerror|syntaxerror|command failed|non-zero exit)\b/i;
|
|
8
9
|
export const STRONG_ERROR_RE = /\b(cannot find|cannot resolve|TS\d{4,}|error TS\d|command failed|non-zero exit|exit code [1-9]\d*|traceback|referenceerror|typeerror|syntaxerror|fatal error|exception|failed to compile|build failed|compilation failed)\b/i;
|
|
10
|
+
/** v0.1.x fallback table — keyed by `errorType`. RETAINED in v0.2.0 as the
|
|
11
|
+
* fallback for memos whose output does not match a deterministic code rule.
|
|
12
|
+
* The v0.2.0 per-error-code rule table below layers ON TOP of this fallback:
|
|
13
|
+
* when a code is matched, the hint is appended to the v0.1.x suggestion as a
|
|
14
|
+
* 'Likely cause:' line; when no code is matched, output is identical to v0.1.x.
|
|
15
|
+
*/
|
|
9
16
|
const SUGGESTIONS = {
|
|
10
17
|
typecheck: "Verify types and imports before running.",
|
|
11
18
|
lint: "Run linter and fix warnings before committing.",
|
|
@@ -14,6 +21,22 @@ const SUGGESTIONS = {
|
|
|
14
21
|
timeout: "Check for infinite loops or long-running operations.",
|
|
15
22
|
unknown: "Review the error output for details.",
|
|
16
23
|
};
|
|
24
|
+
// --- v0.2.0 (K2-018) lesson v2 — per-error-code deterministic dispatch (D2-09).
|
|
25
|
+
// Pure TS, NO LLM hop. Order of dispatch matches the plan §B6.4 priority list:
|
|
26
|
+
// (1) TS\d{4,5} > (2) Python lint > (3) syscall > (4) generic `Error: <Name>` >
|
|
27
|
+
// (5) `Command "<cmd>" failed` > (6) v0.1.x SUGGESTIONS fallback.
|
|
28
|
+
const TS_CODE_RULES = {
|
|
29
|
+
"2304": "import or typo",
|
|
30
|
+
"2322": "type mismatch",
|
|
31
|
+
"2740": "missing or wrong property",
|
|
32
|
+
"2552": "undefined identifier",
|
|
33
|
+
"18047": "possibly null",
|
|
34
|
+
};
|
|
35
|
+
const TS_CODE_RE = /\bTS(\d{4,5})\b/;
|
|
36
|
+
const PY_LINT_RE = /\b(ELIF\d{0,4})\b|\b(F\d{3,4})\b|flake8:\s+(\S+)/;
|
|
37
|
+
const SYSCALL_RE = /\b(EADDRINUSE|ENOENT|EACCES|EPERM)\b/;
|
|
38
|
+
const GENERIC_ERROR_RE = /\bError:\s+(\w+)/;
|
|
39
|
+
const COMMAND_FAILED_RE = /Command\s+"([^"]+)"\s+failed/;
|
|
17
40
|
const SECRET_PATTERNS = [
|
|
18
41
|
/(API_KEY|SECRET|PASSWORD|TOKEN)\s*[=:]\s*\S+/gi,
|
|
19
42
|
/\bBearer\s+\S+/gi,
|
|
@@ -23,18 +46,16 @@ const SECRET_VALUE_PATTERN = /\s*=\s*\S+(.*)$/;
|
|
|
23
46
|
const PATH_PATTERNS_DEPRECATED = null;
|
|
24
47
|
export class Reflector {
|
|
25
48
|
memoryService;
|
|
26
|
-
|
|
49
|
+
lastReflectionByFp = new Map();
|
|
27
50
|
throttleMs;
|
|
28
|
-
|
|
51
|
+
metrics;
|
|
52
|
+
constructor(memoryService, options, metrics) {
|
|
29
53
|
this.memoryService = memoryService;
|
|
30
54
|
this.throttleMs = options?.throttleMs ?? DEFAULT_THROTTLE_MS;
|
|
55
|
+
this.metrics = metrics ?? null;
|
|
31
56
|
}
|
|
32
57
|
async invoke(input) {
|
|
33
58
|
const now = Date.now();
|
|
34
|
-
if (now - this.lastReflectionTs < this.throttleMs) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
this.lastReflectionTs = now;
|
|
38
59
|
const redactedStderr = this.redactSecrets(this.redactPaths(input.stderr));
|
|
39
60
|
const redactedStdout = this.redactSecrets(this.redactPaths(input.stdout));
|
|
40
61
|
const sourceOutput = redactedStderr.length > 0 ? redactedStderr : redactedStdout;
|
|
@@ -43,6 +64,7 @@ export class Reflector {
|
|
|
43
64
|
toolName: input.toolName,
|
|
44
65
|
errorType: input.errorType,
|
|
45
66
|
firstErrorLine,
|
|
67
|
+
dispatched: this.dispatchLesson(redactedStderr, redactedStdout, input.errorType),
|
|
46
68
|
});
|
|
47
69
|
const metadata = {};
|
|
48
70
|
let finalContent;
|
|
@@ -64,6 +86,19 @@ export class Reflector {
|
|
|
64
86
|
else {
|
|
65
87
|
finalContent = lesson;
|
|
66
88
|
}
|
|
89
|
+
// K2-007: per-fingerprint throttle keyed by (project_id-salted) hash of
|
|
90
|
+
// the source output (or lesson when the output is empty). The same
|
|
91
|
+
// fingerprint is also passed to MemoryService.save so dedup and throttle
|
|
92
|
+
// agree on identity.
|
|
93
|
+
const projectId = input.projectId ?? null;
|
|
94
|
+
const fpContent = sourceOutput.length > 0 ? sourceOutput : lesson;
|
|
95
|
+
const fp = computeFingerprint(fpContent, projectId ?? undefined);
|
|
96
|
+
const last = this.lastReflectionByFp.get(fp) ?? 0;
|
|
97
|
+
if (now - last < this.throttleMs) {
|
|
98
|
+
this.metrics?.incr("reflections_throttled", 1);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
this.lastReflectionByFp.set(fp, now);
|
|
67
102
|
const id = this.memoryService.save({
|
|
68
103
|
type: "error",
|
|
69
104
|
content: finalContent,
|
|
@@ -71,15 +106,63 @@ export class Reflector {
|
|
|
71
106
|
sourceTool: input.toolName,
|
|
72
107
|
sourceSession: input.sessionId,
|
|
73
108
|
metadata,
|
|
109
|
+
origin: "reflector",
|
|
110
|
+
projectId: projectId ?? undefined,
|
|
111
|
+
fingerprint: fp,
|
|
74
112
|
});
|
|
75
113
|
return id;
|
|
76
114
|
}
|
|
77
115
|
generateHeuristicLesson(input) {
|
|
116
|
+
const dispatched = input.dispatched ??
|
|
117
|
+
this.dispatchLesson(input.firstErrorLine, "", input.errorType);
|
|
78
118
|
const suggestion = SUGGESTIONS[input.errorType] ?? SUGGESTIONS.unknown;
|
|
79
119
|
const line = input.firstErrorLine.length > MAX_ERROR_LINE_CHARS
|
|
80
120
|
? `${input.firstErrorLine.slice(0, MAX_ERROR_LINE_CHARS)}...`
|
|
81
121
|
: input.firstErrorLine;
|
|
82
|
-
|
|
122
|
+
let lesson = `When ${input.toolName} fails with ${input.errorType}: ${line}\nSuggestion: ${suggestion}`;
|
|
123
|
+
if (dispatched.code && dispatched.hint) {
|
|
124
|
+
lesson += `\nLikely cause: ${dispatched.hint} (code ${dispatched.code})`;
|
|
125
|
+
}
|
|
126
|
+
return lesson;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* v0.2.0 (K2-018 / D2-09) — deterministic per-error-code rule dispatch.
|
|
130
|
+
* Pure regex sweep over `stderr + '\n' + stdout` with NO LLM hop. Returns
|
|
131
|
+
* a short stable `code` + `hint` pair when a known rule matches, or
|
|
132
|
+
* `{ code: null, hint: null }` for the v0.1.x fallback path (which keeps
|
|
133
|
+
* the v0.1.x `SUGGESTIONS[errorType]` suggestion verbatim).
|
|
134
|
+
*
|
|
135
|
+
* Exported for unit testing (K2-019).
|
|
136
|
+
*/
|
|
137
|
+
dispatchLesson(stderr, stdout, errorType) {
|
|
138
|
+
const combined = `${stderr}\n${stdout}`;
|
|
139
|
+
const tsMatch = combined.match(TS_CODE_RE);
|
|
140
|
+
if (tsMatch) {
|
|
141
|
+
const num = tsMatch[1];
|
|
142
|
+
const hint = TS_CODE_RULES[num] ?? `review TS${num}`;
|
|
143
|
+
return { code: `TS${num}`, hint };
|
|
144
|
+
}
|
|
145
|
+
const pyMatch = combined.match(PY_LINT_RE);
|
|
146
|
+
if (pyMatch) {
|
|
147
|
+
const rule = pyMatch[1] || pyMatch[2] || pyMatch[3] || "unknown";
|
|
148
|
+
return { code: rule, hint: `review python lint: ${rule}` };
|
|
149
|
+
}
|
|
150
|
+
const sysMatch = combined.match(SYSCALL_RE);
|
|
151
|
+
if (sysMatch) {
|
|
152
|
+
const code = sysMatch[1];
|
|
153
|
+
return { code, hint: `review syscall: ${code}` };
|
|
154
|
+
}
|
|
155
|
+
const errMatch = combined.match(GENERIC_ERROR_RE);
|
|
156
|
+
if (errMatch) {
|
|
157
|
+
const name = errMatch[1];
|
|
158
|
+
return { code: name, hint: `review error class: ${name}` };
|
|
159
|
+
}
|
|
160
|
+
const cmdMatch = combined.match(COMMAND_FAILED_RE);
|
|
161
|
+
if (cmdMatch) {
|
|
162
|
+
const cmd = cmdMatch[1];
|
|
163
|
+
return { code: cmd, hint: `review failing command: ${cmd}` };
|
|
164
|
+
}
|
|
165
|
+
return { code: null, hint: null };
|
|
83
166
|
}
|
|
84
167
|
redactPaths(text) {
|
|
85
168
|
return redactPathsText(text);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Reflector.js","sourceRoot":"","sources":["../../plugin/Reflector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,IAAI,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"Reflector.js","sourceRoot":"","sources":["../../plugin/Reflector.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { MemoryService } from "./MemoryService.js";
|
|
2
2
|
import type { Store } from "./Store.js";
|
|
3
|
+
import type { Metrics } from "./metrics.js";
|
|
3
4
|
export interface RetrospectiveOptions {
|
|
4
5
|
dir?: string;
|
|
5
6
|
}
|
|
@@ -7,7 +8,9 @@ export declare class Retrospective {
|
|
|
7
8
|
private store;
|
|
8
9
|
private memoryService;
|
|
9
10
|
private retrospectivesDir;
|
|
10
|
-
|
|
11
|
+
private metrics;
|
|
12
|
+
constructor(store: Store, memoryService: MemoryService, options?: RetrospectiveOptions, metrics?: Metrics | null);
|
|
11
13
|
generate(sessionId: string): Promise<string | null>;
|
|
14
|
+
private collectFalsePositives;
|
|
12
15
|
private buildMarkdown;
|
|
13
16
|
}
|
|
@@ -2,15 +2,34 @@ import { mkdirSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { uuidv7 } from "./uuid.js";
|
|
5
|
+
const METRIC_KEY_LABELS = {
|
|
6
|
+
tokens_injected_pre_prompt: "Tokens inyectados (pre-prompt)",
|
|
7
|
+
tokens_injected_compacting: "Tokens inyectados (compacting)",
|
|
8
|
+
reflections_throttled: "Reflexiones throttled",
|
|
9
|
+
duplicate_suppressions: "Supresiones por dedup",
|
|
10
|
+
tool_calls_deduped: "Tool calls deduped",
|
|
11
|
+
patterns_mined: "Patrones minados",
|
|
12
|
+
};
|
|
13
|
+
function originLabel(origin) {
|
|
14
|
+
if (origin === "reflector")
|
|
15
|
+
return "reflector";
|
|
16
|
+
if (origin === "pattern")
|
|
17
|
+
return "pattern";
|
|
18
|
+
if (origin === "retrospective")
|
|
19
|
+
return "retrospective";
|
|
20
|
+
return "agent";
|
|
21
|
+
}
|
|
5
22
|
export class Retrospective {
|
|
6
23
|
store;
|
|
7
24
|
memoryService;
|
|
8
25
|
retrospectivesDir;
|
|
9
|
-
|
|
26
|
+
metrics;
|
|
27
|
+
constructor(store, memoryService, options, metrics) {
|
|
10
28
|
this.store = store;
|
|
11
29
|
this.memoryService = memoryService;
|
|
12
30
|
this.retrospectivesDir =
|
|
13
31
|
options?.dir ?? join(homedir(), ".opencode-kevin", "retrospectives");
|
|
32
|
+
this.metrics = metrics ?? null;
|
|
14
33
|
}
|
|
15
34
|
async generate(sessionId) {
|
|
16
35
|
const existing = this.store
|
|
@@ -37,11 +56,14 @@ export class Retrospective {
|
|
|
37
56
|
ORDER BY ts ASC`)
|
|
38
57
|
.all(sessionId);
|
|
39
58
|
const lessons = this.store
|
|
40
|
-
.prepare(`SELECT content
|
|
59
|
+
.prepare(`SELECT id, content, origin, fingerprint, project_id
|
|
60
|
+
FROM memories
|
|
41
61
|
WHERE type = 'error' AND source_session = ?
|
|
42
62
|
ORDER BY created_at ASC`)
|
|
43
63
|
.all(sessionId);
|
|
44
|
-
const
|
|
64
|
+
const falsePositives = this.collectFalsePositives(sessionId, lessons);
|
|
65
|
+
const metricsSnapshot = this.metrics?.snapshot() ?? null;
|
|
66
|
+
const md = this.buildMarkdown(sessionId, total, successCount, failureCount, failedTools, lessons, falsePositives, metricsSnapshot);
|
|
45
67
|
mkdirSync(this.retrospectivesDir, { recursive: true });
|
|
46
68
|
const filePath = join(this.retrospectivesDir, `${sessionId}.md`);
|
|
47
69
|
writeFileSync(filePath, md, "utf8");
|
|
@@ -53,7 +75,33 @@ export class Retrospective {
|
|
|
53
75
|
.run(id, sessionId, failureCount, successCount, lessons.length, filePath, null);
|
|
54
76
|
return filePath;
|
|
55
77
|
}
|
|
56
|
-
|
|
78
|
+
collectFalsePositives(_sessionId, lessons) {
|
|
79
|
+
const reflectorLessons = lessons.filter((l) => l.origin === "reflector" && l.fingerprint !== null);
|
|
80
|
+
if (reflectorLessons.length === 0)
|
|
81
|
+
return [];
|
|
82
|
+
const result = [];
|
|
83
|
+
for (const lesson of reflectorLessons) {
|
|
84
|
+
const fp = lesson.fingerprint;
|
|
85
|
+
const row = this.store
|
|
86
|
+
.prepare(`SELECT COUNT(*) AS c FROM tool_calls
|
|
87
|
+
WHERE fingerprint = ?
|
|
88
|
+
AND success = 0
|
|
89
|
+
AND (project_id IS ? OR (project_id IS NULL AND ? IS NULL))`)
|
|
90
|
+
.get(fp, lesson.project_id, lesson.project_id);
|
|
91
|
+
const recurrenceCount = row?.c ?? 0;
|
|
92
|
+
if (recurrenceCount > 0) {
|
|
93
|
+
result.push({
|
|
94
|
+
id: lesson.id,
|
|
95
|
+
content: lesson.content,
|
|
96
|
+
fingerprint: fp,
|
|
97
|
+
project_id: lesson.project_id,
|
|
98
|
+
recurrence_count: recurrenceCount,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
buildMarkdown(sessionId, total, successCount, failureCount, failedTools, lessons, falsePositives, metricsSnapshot) {
|
|
57
105
|
const lines = [];
|
|
58
106
|
lines.push(`# Retrospective — Session ${sessionId}`);
|
|
59
107
|
lines.push("");
|
|
@@ -69,9 +117,28 @@ export class Retrospective {
|
|
|
69
117
|
lines.push("");
|
|
70
118
|
lines.push("## Lecciones generadas");
|
|
71
119
|
for (const lesson of lessons) {
|
|
72
|
-
|
|
120
|
+
const label = originLabel(lesson.origin);
|
|
121
|
+
lines.push(`- [${label}] ${lesson.content}`);
|
|
73
122
|
}
|
|
74
123
|
lines.push("");
|
|
124
|
+
lines.push("## False-positive recap");
|
|
125
|
+
if (falsePositives.length === 0) {
|
|
126
|
+
lines.push("- Ninguna lección reflector-sourceada recurrrió en tool_calls.");
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
for (const fp of falsePositives) {
|
|
130
|
+
lines.push(`- [reflector] ${fp.content} (fingerprint ${fp.fingerprint}, recurrencias: ${fp.recurrence_count})`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
lines.push("");
|
|
134
|
+
if (metricsSnapshot !== null) {
|
|
135
|
+
lines.push("## Métricas");
|
|
136
|
+
for (const [key, value] of Object.entries(metricsSnapshot)) {
|
|
137
|
+
const label = METRIC_KEY_LABELS[key] ?? key;
|
|
138
|
+
lines.push(`- ${label}: ${value}`);
|
|
139
|
+
}
|
|
140
|
+
lines.push("");
|
|
141
|
+
}
|
|
75
142
|
return lines.join("\n");
|
|
76
143
|
}
|
|
77
144
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Retrospective.js","sourceRoot":"","sources":["../../plugin/Retrospective.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"Retrospective.js","sourceRoot":"","sources":["../../plugin/Retrospective.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin/Store.d.ts
CHANGED
|
@@ -2,6 +2,21 @@ import { type SqliteAdapter } from "./sqlite-adapter.js";
|
|
|
2
2
|
export interface StoreOptions {
|
|
3
3
|
path: string;
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Thin generic wrapper around {@link SqliteAdapter}. The Store deliberately
|
|
7
|
+
* does NOT pre-prepare statements: callers (MemoryService, ToolCallObserver,
|
|
8
|
+
* Reflector, Retrospective, Metrics, …) prepare SQL on demand so that any
|
|
9
|
+
* migration can add columns/tables without requiring coordinated edits here.
|
|
10
|
+
*
|
|
11
|
+
* v0.2.0 (migration 003_v02_signal.sql) introduces the `kevin_metrics` and
|
|
12
|
+
* `kevin_settings` tables plus nullable columns on `memories` and
|
|
13
|
+
* `tool_calls`. All v0.2.0 callers read/write those rows via the existing
|
|
14
|
+
* {@link Store.prepare | prepare()} / {@link Store.exec | exec()} /
|
|
15
|
+
* {@link Store.transaction | transaction()} surface — no new member is
|
|
16
|
+
* required on Store. K2-005 confirms this contract by exercising the new
|
|
17
|
+
* tables/columns through the generic helpers (see
|
|
18
|
+
* `tests/unit/store-prepare-003.test.ts`).
|
|
19
|
+
*/
|
|
5
20
|
export declare class Store {
|
|
6
21
|
private db;
|
|
7
22
|
private closed;
|
package/dist/plugin/Store.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { createDatabase } from "./sqlite-adapter.js";
|
|
2
|
+
/**
|
|
3
|
+
* Thin generic wrapper around {@link SqliteAdapter}. The Store deliberately
|
|
4
|
+
* does NOT pre-prepare statements: callers (MemoryService, ToolCallObserver,
|
|
5
|
+
* Reflector, Retrospective, Metrics, …) prepare SQL on demand so that any
|
|
6
|
+
* migration can add columns/tables without requiring coordinated edits here.
|
|
7
|
+
*
|
|
8
|
+
* v0.2.0 (migration 003_v02_signal.sql) introduces the `kevin_metrics` and
|
|
9
|
+
* `kevin_settings` tables plus nullable columns on `memories` and
|
|
10
|
+
* `tool_calls`. All v0.2.0 callers read/write those rows via the existing
|
|
11
|
+
* {@link Store.prepare | prepare()} / {@link Store.exec | exec()} /
|
|
12
|
+
* {@link Store.transaction | transaction()} surface — no new member is
|
|
13
|
+
* required on Store. K2-005 confirms this contract by exercising the new
|
|
14
|
+
* tables/columns through the generic helpers (see
|
|
15
|
+
* `tests/unit/store-prepare-003.test.ts`).
|
|
16
|
+
*/
|
|
2
17
|
export class Store {
|
|
3
18
|
db;
|
|
4
19
|
closed = false;
|
package/dist/plugin/Store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Store.js","sourceRoot":"","sources":["../../plugin/Store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAMzE,MAAM,OAAO,KAAK;IACT,EAAE,CAAgB;IAClB,MAAM,GAAG,KAAK,CAAC;IAEvB,YAAY,OAAqB;QAChC,IAAI,CAAC,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QAC1C,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,CAAC,GAAW;QAClB,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,WAAW,CAAI,EAAW;QACzB,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACpD,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACnC,OAAO,EAAE,EAAE,CAAC;IACb,CAAC;IAED,IAAI,CAAC,GAAW;QACf,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACpD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IAED,KAAK;QACJ,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;IAED,IAAI,GAAG;QACN,OAAO,IAAI,CAAC,EAAE,CAAC;IAChB,CAAC;CACD"}
|
|
1
|
+
{"version":3,"file":"Store.js","sourceRoot":"","sources":["../../plugin/Store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAMzE;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,KAAK;IACT,EAAE,CAAgB;IAClB,MAAM,GAAG,KAAK,CAAC;IAEvB,YAAY,OAAqB;QAChC,IAAI,CAAC,EAAE,GAAG,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QAC1C,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,CAAC,GAAW;QAClB,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,WAAW,CAAI,EAAW;QACzB,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACpD,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACnC,OAAO,EAAE,EAAE,CAAC;IACb,CAAC;IAED,IAAI,CAAC,GAAW;QACf,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACpD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IAED,KAAK;QACJ,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;IAED,IAAI,GAAG;QACN,OAAO,IAAI,CAAC,EAAE,CAAC;IAChB,CAAC;CACD"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Store } from "./Store.js";
|
|
2
|
+
import type { Metrics } from "./metrics.js";
|
|
2
3
|
export interface ToolExecuteInput {
|
|
3
4
|
tool: string;
|
|
4
5
|
args?: Record<string, unknown>;
|
|
@@ -15,9 +16,11 @@ export interface ToolExecuteOutput {
|
|
|
15
16
|
export declare class ToolCallObserver {
|
|
16
17
|
private store;
|
|
17
18
|
private startTs;
|
|
18
|
-
|
|
19
|
+
private readonly metrics;
|
|
20
|
+
constructor(store: Store, metrics?: Metrics | null);
|
|
19
21
|
onBefore(input: ToolExecuteInput, _output: ToolExecuteOutput): void;
|
|
20
22
|
onAfter(input: ToolExecuteInput, output: ToolExecuteOutput): void;
|
|
23
|
+
private isDedupEnabled;
|
|
21
24
|
redactSecrets(text: string): string;
|
|
22
25
|
summarizeArgs(args: Record<string, unknown>): string;
|
|
23
26
|
inferErrorType(stderr: string, stdout: string, exitCode?: number): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fingerprint as computeFingerprint } from "./fingerprint.js";
|
|
2
|
+
import { redactPaths, stripPrivate } from "./redact.js";
|
|
2
3
|
import { uuidv7 } from "./uuid.js";
|
|
3
4
|
const SECRET_PATTERNS = [
|
|
4
5
|
/(API_KEY|SECRET|PASSWORD|TOKEN)\s*[=:]\s*\S+/gi,
|
|
@@ -9,8 +10,10 @@ const SECRET_VALUE_PATTERN = /\s*=\s*\S+(.*)$/;
|
|
|
9
10
|
export class ToolCallObserver {
|
|
10
11
|
store;
|
|
11
12
|
startTs = new Map();
|
|
12
|
-
|
|
13
|
+
metrics;
|
|
14
|
+
constructor(store, metrics) {
|
|
13
15
|
this.store = store;
|
|
16
|
+
this.metrics = metrics ?? null;
|
|
14
17
|
}
|
|
15
18
|
onBefore(input, _output) {
|
|
16
19
|
const key = this.key(input);
|
|
@@ -25,15 +28,43 @@ export class ToolCallObserver {
|
|
|
25
28
|
const argsSummary = this.summarizeArgs(input.args ?? {});
|
|
26
29
|
const success = output.success === true ? 1 : 0;
|
|
27
30
|
const agent = input.agent ?? null;
|
|
31
|
+
const stderr = stripPrivate(output.stderr ?? "");
|
|
32
|
+
const stdout = stripPrivate(output.stdout ?? "");
|
|
28
33
|
const errorType = output.success === false
|
|
29
|
-
? this.inferErrorType(
|
|
34
|
+
? this.inferErrorType(stderr, stdout, output.exitCode)
|
|
30
35
|
: null;
|
|
31
36
|
const metadata = JSON.stringify(this.redactArgs(input.args ?? {}));
|
|
37
|
+
const projectId = null;
|
|
38
|
+
const fp = computeFingerprint(`${input.tool}|${argsSummary}|${success}`, projectId ?? undefined);
|
|
39
|
+
if (this.isDedupEnabled()) {
|
|
40
|
+
const existing = this.store
|
|
41
|
+
.prepare(`SELECT 1 FROM tool_calls
|
|
42
|
+
WHERE fingerprint = ?
|
|
43
|
+
AND (project_id IS ? OR (project_id IS NULL AND ? IS NULL))
|
|
44
|
+
AND strftime('%Y-%m-%d %H:%M', ts) = strftime('%Y-%m-%d %H:%M', 'now')
|
|
45
|
+
LIMIT 1`)
|
|
46
|
+
.get(fp, projectId, projectId);
|
|
47
|
+
if (existing) {
|
|
48
|
+
this.metrics?.incr("tool_calls_deduped", 1);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
32
52
|
this.store
|
|
33
53
|
.prepare(`INSERT INTO tool_calls
|
|
34
|
-
(id, session_id, ts, tool, args_summary, success, duration_ms, agent, error_type, metadata)
|
|
35
|
-
VALUES (?, ?, datetime('now'), ?, ?, ?, ?, ?, ?, ?)`)
|
|
36
|
-
.run(uuidv7(), sessionId, input.tool, argsSummary, success, durationMs, agent, errorType, metadata);
|
|
54
|
+
(id, session_id, ts, tool, args_summary, success, duration_ms, agent, error_type, metadata, project_id, fingerprint)
|
|
55
|
+
VALUES (?, ?, datetime('now'), ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
56
|
+
.run(uuidv7(), sessionId, input.tool, argsSummary, success, durationMs, agent, errorType, metadata, projectId, fp);
|
|
57
|
+
}
|
|
58
|
+
isDedupEnabled() {
|
|
59
|
+
try {
|
|
60
|
+
const row = this.store
|
|
61
|
+
.prepare("SELECT value FROM kevin_settings WHERE key = 'tool_calls_dedup_enabled'")
|
|
62
|
+
.get();
|
|
63
|
+
return row?.value === "1";
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
37
68
|
}
|
|
38
69
|
redactSecrets(text) {
|
|
39
70
|
let out = text;
|
|
@@ -122,7 +153,8 @@ export class ToolCallObserver {
|
|
|
122
153
|
}
|
|
123
154
|
redactValue(v, truncateAt) {
|
|
124
155
|
if (typeof v === "string") {
|
|
125
|
-
let s =
|
|
156
|
+
let s = stripPrivate(v);
|
|
157
|
+
s = redactPaths(s);
|
|
126
158
|
s = this.redactSecrets(s);
|
|
127
159
|
if (truncateAt !== undefined && s.length > truncateAt) {
|
|
128
160
|
s = `${s.slice(0, truncateAt)}...`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToolCallObserver.js","sourceRoot":"","sources":["../../plugin/ToolCallObserver.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"ToolCallObserver.js","sourceRoot":"","sources":["../../plugin/ToolCallObserver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize an error/output string before fingerprinting.
|
|
3
|
+
*
|
|
4
|
+
* Steps:
|
|
5
|
+
* 1. Strip ANSI CSI sequences.
|
|
6
|
+
* 2. Lowercase (for case-insensitive matching of paths/extensions).
|
|
7
|
+
* 3. Replace `<path>.<ext>:<line>[:<col>` with `.<ext>` so the same
|
|
8
|
+
* error thrown from different files hashes the same.
|
|
9
|
+
* 4. Collapse all whitespace runs to a single space and trim.
|
|
10
|
+
*/
|
|
11
|
+
export declare function normalize(content: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Compute the FNV-1a 64-bit hash of `input`, returned as a 16-char
|
|
14
|
+
* lowercase hex string (zero-padded to 64 bits).
|
|
15
|
+
*
|
|
16
|
+
* FNV-1a: hash = offset_basis; for each byte: hash ^= byte; hash *= prime (mod 2^64).
|
|
17
|
+
*/
|
|
18
|
+
export declare function fnv1a64(input: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Fingerprint an error/output string. Optionally salt the hash with
|
|
21
|
+
* `project_id` so the same error text in two different projects
|
|
22
|
+
* produces two distinct fingerprints (per decision D2-11/D2-14).
|
|
23
|
+
*
|
|
24
|
+
* Salt is prepended and separated by a NUL byte to avoid accidental
|
|
25
|
+
* collisions where one project's name is a prefix of another's.
|
|
26
|
+
*/
|
|
27
|
+
export declare function fingerprint(content: string, project_id?: string): string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Kevin 0.2.0 — fingerprint (K2-003)
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Deterministic, zero-dependency content hash used to dedup
|
|
5
|
+
// error memories and to throttle per-fingerprint reflection.
|
|
6
|
+
//
|
|
7
|
+
// We deliberately avoid node:crypto so the module is portable
|
|
8
|
+
// across plugin hosts and stays cheap. FNV-1a 64-bit gives a
|
|
9
|
+
// decent spread for short-to-medium error strings and is
|
|
10
|
+
// reproducible across runs and platforms (BigInt arithmetic).
|
|
11
|
+
// ============================================================
|
|
12
|
+
const FNV_OFFSET_BASIS = 0xcbf29ce484222325n;
|
|
13
|
+
const FNV_PRIME = 0x100000001b3n;
|
|
14
|
+
const FNV_MOD = 1n << 64n;
|
|
15
|
+
// ANSI CSI sequences (color codes, cursor moves, etc.).
|
|
16
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI stripping requires matching the ESC (\x1b) control char by definition.
|
|
17
|
+
const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
18
|
+
// Path-with-line-and-column references: `src/foo.ts:42:7` -> `.ts`.
|
|
19
|
+
// Matches any non-whitespace path prefix followed by a dotted
|
|
20
|
+
// extension and `:NN` or `:NN:NN` line/column markers. Useful for
|
|
21
|
+
// stripping absolute or per-repo paths before fingerprinting so
|
|
22
|
+
// the same error text in two repos/copy-paste edits hashes alike.
|
|
23
|
+
const PATH_LINE_RE = /[^\s:]*\.([a-z0-9]+):\d+(?::\d+)?/g;
|
|
24
|
+
// Whitespace collapse — preserves token order but ignores
|
|
25
|
+
// line-break and indentation differences.
|
|
26
|
+
const WS_RE = /\s+/g;
|
|
27
|
+
/**
|
|
28
|
+
* Normalize an error/output string before fingerprinting.
|
|
29
|
+
*
|
|
30
|
+
* Steps:
|
|
31
|
+
* 1. Strip ANSI CSI sequences.
|
|
32
|
+
* 2. Lowercase (for case-insensitive matching of paths/extensions).
|
|
33
|
+
* 3. Replace `<path>.<ext>:<line>[:<col>` with `.<ext>` so the same
|
|
34
|
+
* error thrown from different files hashes the same.
|
|
35
|
+
* 4. Collapse all whitespace runs to a single space and trim.
|
|
36
|
+
*/
|
|
37
|
+
export function normalize(content) {
|
|
38
|
+
return content
|
|
39
|
+
.replace(ANSI_RE, "")
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.replace(PATH_LINE_RE, ".$1")
|
|
42
|
+
.replace(WS_RE, " ")
|
|
43
|
+
.trim();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Compute the FNV-1a 64-bit hash of `input`, returned as a 16-char
|
|
47
|
+
* lowercase hex string (zero-padded to 64 bits).
|
|
48
|
+
*
|
|
49
|
+
* FNV-1a: hash = offset_basis; for each byte: hash ^= byte; hash *= prime (mod 2^64).
|
|
50
|
+
*/
|
|
51
|
+
export function fnv1a64(input) {
|
|
52
|
+
let hash = FNV_OFFSET_BASIS;
|
|
53
|
+
for (let i = 0; i < input.length; i++) {
|
|
54
|
+
const byte = input.charCodeAt(i) & 0xff;
|
|
55
|
+
hash ^= BigInt(byte);
|
|
56
|
+
hash = (hash * FNV_PRIME) % FNV_MOD;
|
|
57
|
+
}
|
|
58
|
+
// Encode as 16-char lowercase hex.
|
|
59
|
+
return hash.toString(16).padStart(16, "0");
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Fingerprint an error/output string. Optionally salt the hash with
|
|
63
|
+
* `project_id` so the same error text in two different projects
|
|
64
|
+
* produces two distinct fingerprints (per decision D2-11/D2-14).
|
|
65
|
+
*
|
|
66
|
+
* Salt is prepended and separated by a NUL byte to avoid accidental
|
|
67
|
+
* collisions where one project's name is a prefix of another's.
|
|
68
|
+
*/
|
|
69
|
+
export function fingerprint(content, project_id) {
|
|
70
|
+
const normalized = normalize(content);
|
|
71
|
+
const salted = project_id ? `${project_id}\u0000${normalized}` : normalized;
|
|
72
|
+
return fnv1a64(salted);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../plugin/fingerprint.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,qCAAqC;AACrC,+DAA+D;AAC/D,4DAA4D;AAC5D,6DAA6D;AAC7D,EAAE;AACF,8DAA8D;AAC9D,6DAA6D;AAC7D,yDAAyD;AACzD,8DAA8D;AAC9D,+DAA+D;AAE/D,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAC7C,MAAM,SAAS,GAAG,cAAc,CAAC;AACjC,MAAM,OAAO,GAAG,EAAE,IAAI,GAAG,CAAC;AAE1B,wDAAwD;AACxD,uIAAuI;AACvI,MAAM,OAAO,GAAG,wBAAwB,CAAC;AAEzC,oEAAoE;AACpE,8DAA8D;AAC9D,kEAAkE;AAClE,gEAAgE;AAChE,kEAAkE;AAClE,MAAM,YAAY,GAAG,oCAAoC,CAAC;AAE1D,0DAA0D;AAC1D,0CAA0C;AAC1C,MAAM,KAAK,GAAG,MAAM,CAAC;AAErB;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe;IACxC,OAAO,OAAO;SACZ,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;SACpB,WAAW,EAAE;SACb,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC;SAC5B,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,IAAI,EAAE,CAAC;AACV,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACpC,IAAI,IAAI,GAAG,gBAAgB,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QACxC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;QACrB,IAAI,GAAG,CAAC,IAAI,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC;IACrC,CAAC;IACD,mCAAmC;IACnC,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,UAAmB;IAC/D,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,SAAS,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;IAC5E,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC"}
|