@tyvm/knowhow 0.0.128 → 0.0.130
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 +5 -4
- package/scripts/cache-miss-debug.ts +365 -0
- package/src/agents/base/base.ts +159 -59
- package/src/agents/tools/execCommand.ts +26 -6
- package/src/agents/tools/index.ts +1 -0
- package/src/agents/tools/list.ts +2 -0
- package/src/agents/tools/minimal/callTool.ts +45 -0
- package/src/agents/tools/minimal/definitions.ts +50 -0
- package/src/agents/tools/minimal/index.ts +3 -0
- package/src/agents/tools/minimal/inspectTools.ts +27 -0
- package/src/agents/tools/skills.ts +79 -0
- package/src/chat/modules/AgentModule.ts +111 -9
- package/src/cli.ts +4 -0
- package/src/clients/anthropic.ts +59 -1
- package/src/clients/contextLimits.ts +9 -0
- package/src/clients/index.ts +6 -0
- package/src/clients/meta.ts +18 -0
- package/src/clients/openai.ts +28 -5
- package/src/clients/pricing/anthropic.ts +13 -0
- package/src/clients/pricing/fireworks.ts +18 -12
- package/src/clients/pricing/index.ts +1 -0
- package/src/clients/pricing/meta.ts +12 -0
- package/src/clients/pricing/openai.ts +16 -0
- package/src/clients/pricing/xai.ts +3 -0
- package/src/clients/types.ts +27 -4
- package/src/clients/xai.ts +2 -2
- package/src/commands/agent.ts +19 -0
- package/src/commands/behaviors.ts +47 -0
- package/src/commands/services.ts +7 -3
- package/src/commands/skills.ts +71 -0
- package/src/index.ts +2 -0
- package/src/plugins/PluginBase.ts +0 -2
- package/src/plugins/SkillsPlugin.ts +11 -124
- package/src/plugins/language.ts +0 -1
- package/src/plugins/plugins.ts +24 -13
- package/src/plugins/types.ts +8 -2
- package/src/processors/MinimalToolsMessageProcessor.ts +113 -0
- package/src/processors/TokenCompressor.ts +21 -4
- package/src/processors/index.ts +7 -1
- package/src/services/AgentSyncFs.ts +49 -0
- package/src/services/AgentSyncKnowhowWeb.ts +1 -1
- package/src/services/BehaviorsService.ts +432 -0
- package/src/services/KnowhowClient.ts +62 -1
- package/src/services/LazyToolsService.ts +12 -0
- package/src/services/MinimalToolsService.ts +167 -0
- package/src/services/SkillsService.ts +622 -0
- package/src/services/Tools.ts +11 -0
- package/src/services/index.ts +9 -2
- package/src/services/modules/types.ts +2 -0
- package/src/worker.ts +7 -0
- package/tests/processors/TokenCompressor.test.ts +45 -0
- package/tests/unit/services/MinimalToolsService.test.ts +424 -0
- package/ts_build/package.json +5 -4
- package/ts_build/src/agents/base/base.d.ts +13 -2
- package/ts_build/src/agents/base/base.js +91 -35
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
- package/ts_build/src/agents/tools/execCommand.js +18 -5
- package/ts_build/src/agents/tools/execCommand.js.map +1 -1
- package/ts_build/src/agents/tools/index.d.ts +1 -0
- package/ts_build/src/agents/tools/index.js +1 -0
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +2 -0
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/minimal/callTool.d.ts +5 -0
- package/ts_build/src/agents/tools/minimal/callTool.js +28 -0
- package/ts_build/src/agents/tools/minimal/callTool.js.map +1 -0
- package/ts_build/src/agents/tools/minimal/definitions.d.ts +4 -0
- package/ts_build/src/agents/tools/minimal/definitions.js +46 -0
- package/ts_build/src/agents/tools/minimal/definitions.js.map +1 -0
- package/ts_build/src/agents/tools/minimal/index.d.ts +3 -0
- package/ts_build/src/agents/tools/minimal/index.js +20 -0
- package/ts_build/src/agents/tools/minimal/index.js.map +1 -0
- package/ts_build/src/agents/tools/minimal/inspectTools.d.ts +2 -0
- package/ts_build/src/agents/tools/minimal/inspectTools.js +15 -0
- package/ts_build/src/agents/tools/minimal/inspectTools.js.map +1 -0
- package/ts_build/src/agents/tools/skills.d.ts +33 -0
- package/ts_build/src/agents/tools/skills.js +62 -0
- package/ts_build/src/agents/tools/skills.js.map +1 -0
- package/ts_build/src/chat/modules/AgentModule.d.ts +2 -0
- package/ts_build/src/chat/modules/AgentModule.js +77 -2
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +1 -1
- package/ts_build/src/cli.js +4 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +2 -0
- package/ts_build/src/clients/anthropic.js +35 -0
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/contextLimits.js +8 -0
- package/ts_build/src/clients/contextLimits.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +1 -0
- package/ts_build/src/clients/index.js +6 -0
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/meta.d.ts +4 -0
- package/ts_build/src/clients/meta.js +15 -0
- package/ts_build/src/clients/meta.js.map +1 -0
- package/ts_build/src/clients/openai.d.ts +1 -0
- package/ts_build/src/clients/openai.js +25 -5
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/anthropic.d.ts +4 -0
- package/ts_build/src/clients/pricing/anthropic.js +12 -0
- package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
- package/ts_build/src/clients/pricing/fireworks.d.ts +2 -5
- package/ts_build/src/clients/pricing/fireworks.js +13 -11
- package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
- package/ts_build/src/clients/pricing/index.d.ts +1 -0
- package/ts_build/src/clients/pricing/index.js +5 -2
- package/ts_build/src/clients/pricing/index.js.map +1 -1
- package/ts_build/src/clients/pricing/meta.d.ts +6 -0
- package/ts_build/src/clients/pricing/meta.js +8 -0
- package/ts_build/src/clients/pricing/meta.js.map +1 -0
- package/ts_build/src/clients/pricing/openai.d.ts +3 -0
- package/ts_build/src/clients/pricing/openai.js +15 -0
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/xai.d.ts +1 -0
- package/ts_build/src/clients/pricing/xai.js +3 -0
- package/ts_build/src/clients/pricing/xai.js.map +1 -1
- package/ts_build/src/clients/types.d.ts +6 -1
- package/ts_build/src/clients/xai.js +1 -1
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/commands/agent.js +16 -0
- package/ts_build/src/commands/agent.js.map +1 -1
- package/ts_build/src/commands/behaviors.d.ts +2 -0
- package/ts_build/src/commands/behaviors.js +43 -0
- package/ts_build/src/commands/behaviors.js.map +1 -0
- package/ts_build/src/commands/services.js +4 -2
- package/ts_build/src/commands/services.js.map +1 -1
- package/ts_build/src/commands/skills.d.ts +2 -0
- package/ts_build/src/commands/skills.js +61 -0
- package/ts_build/src/commands/skills.js.map +1 -0
- package/ts_build/src/index.d.ts +2 -0
- package/ts_build/src/index.js +5 -1
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/plugins/PluginBase.d.ts +0 -2
- package/ts_build/src/plugins/PluginBase.js.map +1 -1
- package/ts_build/src/plugins/SkillsPlugin.d.ts +1 -6
- package/ts_build/src/plugins/SkillsPlugin.js +6 -135
- package/ts_build/src/plugins/SkillsPlugin.js.map +1 -1
- package/ts_build/src/plugins/language.js.map +1 -1
- package/ts_build/src/plugins/plugins.d.ts +4 -1
- package/ts_build/src/plugins/plugins.js +17 -11
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/plugins/types.d.ts +3 -2
- package/ts_build/src/processors/MinimalToolsMessageProcessor.d.ts +7 -0
- package/ts_build/src/processors/MinimalToolsMessageProcessor.js +61 -0
- package/ts_build/src/processors/MinimalToolsMessageProcessor.js.map +1 -0
- package/ts_build/src/processors/TokenCompressor.js +5 -2
- package/ts_build/src/processors/TokenCompressor.js.map +1 -1
- package/ts_build/src/processors/index.d.ts +2 -1
- package/ts_build/src/processors/index.js +3 -1
- package/ts_build/src/processors/index.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.d.ts +2 -0
- package/ts_build/src/services/AgentSyncFs.js +30 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/AgentSyncKnowhowWeb.js +1 -1
- package/ts_build/src/services/BehaviorsService.d.ts +46 -0
- package/ts_build/src/services/BehaviorsService.js +358 -0
- package/ts_build/src/services/BehaviorsService.js.map +1 -0
- package/ts_build/src/services/KnowhowClient.d.ts +9 -1
- package/ts_build/src/services/KnowhowClient.js +20 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/LazyToolsService.js.map +1 -1
- package/ts_build/src/services/MinimalToolsService.d.ts +14 -0
- package/ts_build/src/services/MinimalToolsService.js +80 -0
- package/ts_build/src/services/MinimalToolsService.js.map +1 -0
- package/ts_build/src/services/SkillsService.d.ts +53 -0
- package/ts_build/src/services/SkillsService.js +478 -0
- package/ts_build/src/services/SkillsService.js.map +1 -0
- package/ts_build/src/services/Tools.d.ts +2 -0
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/services/index.d.ts +4 -0
- package/ts_build/src/services/index.js +9 -2
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/modules/index.d.ts +4 -2
- package/ts_build/src/services/modules/types.d.ts +2 -0
- package/ts_build/src/types.d.ts +8 -0
- package/ts_build/src/worker.js +5 -0
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/tests/processors/TokenCompressor.test.js +27 -0
- package/ts_build/tests/processors/TokenCompressor.test.js.map +1 -1
- package/ts_build/tests/unit/services/MinimalToolsService.test.d.ts +1 -0
- package/ts_build/tests/unit/services/MinimalToolsService.test.js +320 -0
- package/ts_build/tests/unit/services/MinimalToolsService.test.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyvm/knowhow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.130",
|
|
4
4
|
"description": "ai cli with plugins and agents",
|
|
5
5
|
"main": "ts_build/src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"lint:deps": "depcheck --config=.depcheckrc",
|
|
23
23
|
"lint:all": "npm run lint && npm run lint:deps",
|
|
24
24
|
"check:model-pricing": "ts-node scripts/check-model-pricing.ts",
|
|
25
|
+
"debug:cache-miss": "ts-node scripts/cache-miss-debug.ts",
|
|
25
26
|
"version:nightly": "bash scripts/publish.sh version:nightly",
|
|
26
27
|
"version:dev": "bash scripts/publish.sh version:dev",
|
|
27
28
|
"publish:nightly": "bash scripts/publish.sh nightly",
|
|
@@ -34,7 +35,6 @@
|
|
|
34
35
|
"license": "MIT",
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@babel/preset-typescript": "^7.23.3",
|
|
37
|
-
"typescript": "^5.0.0",
|
|
38
38
|
"@types/diff": "^5.2.1",
|
|
39
39
|
"@types/express": "^4.17.13",
|
|
40
40
|
"@types/jest": "^29.5.13",
|
|
@@ -46,10 +46,11 @@
|
|
|
46
46
|
"ts-jest": "^29.1.1",
|
|
47
47
|
"ts-node": "^10.7.0",
|
|
48
48
|
"tslint": "^6.1.3",
|
|
49
|
-
"tslint-config-prettier": "^1.18.0"
|
|
49
|
+
"tslint-config-prettier": "^1.18.0",
|
|
50
|
+
"typescript": "^5.0.0"
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
|
-
"@anthropic-ai/sdk": "^0.
|
|
53
|
+
"@anthropic-ai/sdk": "^0.111.0",
|
|
53
54
|
"@google/genai": "^0.14.1",
|
|
54
55
|
"@inquirer/editor": "^4.2.18",
|
|
55
56
|
"@modelcontextprotocol/sdk": "^1.13.3",
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
#!/usr/bin/env npx ts-node
|
|
2
|
+
/**
|
|
3
|
+
* cache-miss-debug.ts
|
|
4
|
+
*
|
|
5
|
+
* Debug helper for analyzing usage.json files written by AgentSyncFs
|
|
6
|
+
* (`.knowhow/processes/agents/<taskId>/usage.json`).
|
|
7
|
+
*
|
|
8
|
+
* Each entry in usage.json corresponds to one AI completion call and looks
|
|
9
|
+
* like:
|
|
10
|
+
* {
|
|
11
|
+
* timestamp, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens,
|
|
12
|
+
* totalInputTokens, totalOutputTokens, totalCacheReadTokens, totalCacheWriteTokens,
|
|
13
|
+
* messages: [...] // exact message chain sent to the model for that call
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* This script helps answer: "did a particular call miss the prompt cache?"
|
|
17
|
+
* and "what changed in the message chain between call N and call N+1 that
|
|
18
|
+
* broke the cache prefix?"
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* npx ts-node scripts/cache-miss-debug.ts <path-to-usage.json> [options]
|
|
22
|
+
*
|
|
23
|
+
* Options:
|
|
24
|
+
* --summary Print a per-call table of token/cache stats + hit/miss verdict (default if no other option given)
|
|
25
|
+
* --diff <a> <b> Print a diff of the `messages` array between call index a and call index b (0-based)
|
|
26
|
+
* --dump <n> Dump the full `messages` array for call index n as JSON
|
|
27
|
+
* --dump-message <n> <m> Dump a single message (index m) from call n's messages array
|
|
28
|
+
* --json Output the summary as JSON instead of a table
|
|
29
|
+
*
|
|
30
|
+
* Examples:
|
|
31
|
+
* npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json
|
|
32
|
+
* npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json --diff 1 2
|
|
33
|
+
* npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json --dump 2
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import * as fs from "fs";
|
|
37
|
+
import * as path from "path";
|
|
38
|
+
import { diffJson } from "diff";
|
|
39
|
+
|
|
40
|
+
interface UsageEntry {
|
|
41
|
+
timestamp?: string;
|
|
42
|
+
inputTokens: number;
|
|
43
|
+
outputTokens: number;
|
|
44
|
+
cacheReadTokens: number;
|
|
45
|
+
cacheWriteTokens: number;
|
|
46
|
+
totalInputTokens?: number;
|
|
47
|
+
totalOutputTokens?: number;
|
|
48
|
+
totalCacheReadTokens?: number;
|
|
49
|
+
totalCacheWriteTokens?: number;
|
|
50
|
+
messages?: any[];
|
|
51
|
+
[key: string]: any;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface Verdict {
|
|
55
|
+
index: number;
|
|
56
|
+
timestamp: string;
|
|
57
|
+
inputTokens: number;
|
|
58
|
+
cacheReadTokens: number;
|
|
59
|
+
cacheWriteTokens: number;
|
|
60
|
+
cumulativeWritesBefore: number;
|
|
61
|
+
status: "cold-start" | "hit" | "partial-hit" | "miss";
|
|
62
|
+
note: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function loadUsage(filePath: string): UsageEntry[] {
|
|
66
|
+
const resolved = path.resolve(filePath);
|
|
67
|
+
if (!fs.existsSync(resolved)) {
|
|
68
|
+
throw new Error(`File not found: ${resolved}`);
|
|
69
|
+
}
|
|
70
|
+
const raw = fs.readFileSync(resolved, "utf8");
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
if (!Array.isArray(parsed)) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Expected usage.json to contain a JSON array of call entries, got: ${typeof parsed}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return parsed as UsageEntry[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Compute a hit/miss verdict for each call by comparing cacheReadTokens
|
|
82
|
+
* against the cumulative cacheWriteTokens available from all prior calls.
|
|
83
|
+
*
|
|
84
|
+
* - cold-start: first call, no cache could exist yet (cacheReadTokens ~ 0 expected)
|
|
85
|
+
* - hit: cacheReadTokens roughly matches (or exceeds a large portion of) prior cumulative writes
|
|
86
|
+
* - partial-hit: cacheReadTokens is nonzero but noticeably less than what was available
|
|
87
|
+
* - miss: cacheReadTokens is 0 (or near 0) despite prior writes being available
|
|
88
|
+
*/
|
|
89
|
+
function computeVerdicts(entries: UsageEntry[]): Verdict[] {
|
|
90
|
+
const verdicts: Verdict[] = [];
|
|
91
|
+
let cumulativeWrites = 0;
|
|
92
|
+
|
|
93
|
+
entries.forEach((entry, index) => {
|
|
94
|
+
const cacheReadTokens = entry.cacheReadTokens ?? 0;
|
|
95
|
+
const cacheWriteTokens = entry.cacheWriteTokens ?? 0;
|
|
96
|
+
const cumulativeWritesBefore = cumulativeWrites;
|
|
97
|
+
|
|
98
|
+
let status: Verdict["status"];
|
|
99
|
+
let note = "";
|
|
100
|
+
|
|
101
|
+
if (index === 0) {
|
|
102
|
+
status = "cold-start";
|
|
103
|
+
note = "First call — no prior cache expected.";
|
|
104
|
+
} else if (cumulativeWritesBefore === 0) {
|
|
105
|
+
status = cacheReadTokens > 0 ? "hit" : "cold-start";
|
|
106
|
+
note = "No prior writes recorded to compare against.";
|
|
107
|
+
} else if (cacheReadTokens === 0) {
|
|
108
|
+
status = "miss";
|
|
109
|
+
note = `Expected up to ${cumulativeWritesBefore} cached tokens available, got 0 reads — full cache miss.`;
|
|
110
|
+
} else if (cacheReadTokens < cumulativeWritesBefore * 0.5) {
|
|
111
|
+
status = "partial-hit";
|
|
112
|
+
note = `Read ${cacheReadTokens} of up to ${cumulativeWritesBefore} available cached tokens — partial miss, prefix likely diverged partway through.`;
|
|
113
|
+
} else {
|
|
114
|
+
status = "hit";
|
|
115
|
+
note = `Read ${cacheReadTokens} tokens, consistent with prior cumulative writes (${cumulativeWritesBefore}).`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
verdicts.push({
|
|
119
|
+
index,
|
|
120
|
+
timestamp: entry.timestamp || "unknown",
|
|
121
|
+
inputTokens: entry.inputTokens ?? 0,
|
|
122
|
+
cacheReadTokens,
|
|
123
|
+
cacheWriteTokens,
|
|
124
|
+
cumulativeWritesBefore,
|
|
125
|
+
status,
|
|
126
|
+
note,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
cumulativeWrites += cacheWriteTokens;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return verdicts;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
function fmtNum(n: number): string {
|
|
137
|
+
return n.toLocaleString();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function statusEmoji(status: Verdict["status"]): string {
|
|
141
|
+
switch (status) {
|
|
142
|
+
case "cold-start":
|
|
143
|
+
return "🧊";
|
|
144
|
+
case "hit":
|
|
145
|
+
return "✅";
|
|
146
|
+
case "partial-hit":
|
|
147
|
+
return "🟡";
|
|
148
|
+
case "miss":
|
|
149
|
+
return "🚨";
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function printSummaryTable(entries: UsageEntry[], verdicts: Verdict[]) {
|
|
154
|
+
const header = [
|
|
155
|
+
"#",
|
|
156
|
+
"Time",
|
|
157
|
+
"Input",
|
|
158
|
+
"Output",
|
|
159
|
+
"CacheRead",
|
|
160
|
+
"CacheWrite",
|
|
161
|
+
"Status",
|
|
162
|
+
];
|
|
163
|
+
const rows = verdicts.map((v, i) => [
|
|
164
|
+
String(v.index),
|
|
165
|
+
v.timestamp,
|
|
166
|
+
fmtNum(v.inputTokens),
|
|
167
|
+
fmtNum(entries[i].outputTokens ?? 0),
|
|
168
|
+
fmtNum(v.cacheReadTokens),
|
|
169
|
+
fmtNum(v.cacheWriteTokens),
|
|
170
|
+
`${statusEmoji(v.status)} ${v.status}`,
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
const widths = header.map((h, i) =>
|
|
174
|
+
Math.max(h.length, ...rows.map((r) => r[i].length))
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const printRow = (cols: string[]) =>
|
|
178
|
+
console.log(
|
|
179
|
+
"| " + cols.map((c, i) => c.padEnd(widths[i])).join(" | ") + " |"
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
printRow(header);
|
|
183
|
+
printRow(widths.map((w) => "-".repeat(w)));
|
|
184
|
+
rows.forEach(printRow);
|
|
185
|
+
|
|
186
|
+
console.log("");
|
|
187
|
+
const misses = verdicts.filter((v) => v.status === "miss");
|
|
188
|
+
const partials = verdicts.filter((v) => v.status === "partial-hit");
|
|
189
|
+
if (misses.length === 0 && partials.length === 0) {
|
|
190
|
+
console.log("✅ No cache misses detected — caching looks healthy.");
|
|
191
|
+
} else {
|
|
192
|
+
console.log(
|
|
193
|
+
`🚨 Found ${misses.length} full miss(es) and ${partials.length} partial-hit(s):`
|
|
194
|
+
);
|
|
195
|
+
[...misses, ...partials].forEach((v) => {
|
|
196
|
+
console.log(` - Call #${v.index} (${v.timestamp}): ${v.note}`);
|
|
197
|
+
});
|
|
198
|
+
console.log(
|
|
199
|
+
"\nTip: use --diff <a> <b> to compare the message chains between two calls, e.g.:"
|
|
200
|
+
);
|
|
201
|
+
[...misses, ...partials].forEach((v) => {
|
|
202
|
+
if (v.index > 0) {
|
|
203
|
+
console.log(
|
|
204
|
+
` npx ts-node scripts/cache-miss-debug.ts <file> --diff ${
|
|
205
|
+
v.index - 1
|
|
206
|
+
} ${v.index}`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function printJsonSummary(verdicts: Verdict[]) {
|
|
214
|
+
console.log(JSON.stringify(verdicts, null, 2));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Diff the messages array of two calls. Uses a structural JSON diff so
|
|
219
|
+
* additions/removals/reordering of individual messages (and fields within
|
|
220
|
+
* them, like tool_call ids or content) are all visible.
|
|
221
|
+
*/
|
|
222
|
+
function printMessagesDiff(entries: UsageEntry[], aIdx: number, bIdx: number) {
|
|
223
|
+
if (!entries[aIdx] || !entries[bIdx]) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Invalid indices: file has ${entries.length} entries (valid range 0-${
|
|
226
|
+
entries.length - 1
|
|
227
|
+
})`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const a = entries[aIdx].messages ?? [];
|
|
232
|
+
const b = entries[bIdx].messages ?? [];
|
|
233
|
+
|
|
234
|
+
console.log(
|
|
235
|
+
`\nDiffing messages: call #${aIdx} (${entries[aIdx].timestamp}) -> call #${bIdx} (${entries[bIdx].timestamp})`
|
|
236
|
+
);
|
|
237
|
+
console.log(` call #${aIdx}: ${a.length} messages`);
|
|
238
|
+
console.log(` call #${bIdx}: ${b.length} messages\n`);
|
|
239
|
+
|
|
240
|
+
const diff = diffJson(a, b);
|
|
241
|
+
diff.forEach((part) => {
|
|
242
|
+
const color = part.added ? "\x1b[32m" : part.removed ? "\x1b[31m" : "";
|
|
243
|
+
const reset = part.added || part.removed ? "\x1b[0m" : "";
|
|
244
|
+
const prefix = part.added ? "+ " : part.removed ? "- " : " ";
|
|
245
|
+
const lines = part.value.split("\n");
|
|
246
|
+
lines.forEach((line, i) => {
|
|
247
|
+
if (line === "" && i === lines.length - 1) return; // trailing empty line from split
|
|
248
|
+
console.log(`${color}${prefix}${line}${reset}`);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function dumpMessages(entries: UsageEntry[], idx: number) {
|
|
254
|
+
if (!entries[idx]) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Invalid index ${idx}: file has ${entries.length} entries (valid range 0-${
|
|
257
|
+
entries.length - 1
|
|
258
|
+
})`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
console.log(JSON.stringify(entries[idx].messages ?? [], null, 2));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function dumpMessage(entries: UsageEntry[], callIdx: number, msgIdx: number) {
|
|
265
|
+
if (!entries[callIdx]) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`Invalid call index ${callIdx}: file has ${entries.length} entries`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
const messages = entries[callIdx].messages ?? [];
|
|
271
|
+
if (!messages[msgIdx]) {
|
|
272
|
+
throw new Error(
|
|
273
|
+
`Invalid message index ${msgIdx}: call #${callIdx} has ${messages.length} messages`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
console.log(JSON.stringify(messages[msgIdx], null, 2));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
function printUsage() {
|
|
281
|
+
console.log(`
|
|
282
|
+
cache-miss-debug.ts — inspect usage.json files for prompt-cache issues
|
|
283
|
+
|
|
284
|
+
Usage:
|
|
285
|
+
npx ts-node scripts/cache-miss-debug.ts <path-to-usage.json> [options]
|
|
286
|
+
|
|
287
|
+
Options:
|
|
288
|
+
--summary Print a per-call table of token/cache stats + hit/miss verdict (default)
|
|
289
|
+
--json Output the summary as JSON instead of a table
|
|
290
|
+
--diff <a> <b> Diff the messages array between call index a and call index b (0-based)
|
|
291
|
+
--dump <n> Dump the full messages array for call index n as JSON
|
|
292
|
+
--dump-message <n> <m> Dump a single message (index m) from call n's messages array
|
|
293
|
+
|
|
294
|
+
Examples:
|
|
295
|
+
npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json
|
|
296
|
+
npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json --diff 1 2
|
|
297
|
+
npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json --dump 2
|
|
298
|
+
npx ts-node scripts/cache-miss-debug.ts .knowhow/processes/agents/123-foo/usage.json --dump-message 2 0
|
|
299
|
+
`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function main() {
|
|
303
|
+
const args = process.argv.slice(2);
|
|
304
|
+
const filePath = args.find((a) => !a.startsWith("--"));
|
|
305
|
+
|
|
306
|
+
if (!filePath || args.includes("--help") || args.includes("-h")) {
|
|
307
|
+
printUsage();
|
|
308
|
+
process.exit(filePath ? 0 : 1);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const entries = loadUsage(filePath);
|
|
312
|
+
|
|
313
|
+
if (entries.length === 0) {
|
|
314
|
+
console.log("usage.json contains no entries.");
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const diffFlagIdx = args.indexOf("--diff");
|
|
319
|
+
const dumpFlagIdx = args.indexOf("--dump");
|
|
320
|
+
const dumpMessageFlagIdx = args.indexOf("--dump-message");
|
|
321
|
+
|
|
322
|
+
if (diffFlagIdx !== -1) {
|
|
323
|
+
const a = parseInt(args[diffFlagIdx + 1], 10);
|
|
324
|
+
const b = parseInt(args[diffFlagIdx + 2], 10);
|
|
325
|
+
if (Number.isNaN(a) || Number.isNaN(b)) {
|
|
326
|
+
console.error("--diff requires two numeric indices, e.g. --diff 1 2");
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
printMessagesDiff(entries, a, b);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (dumpMessageFlagIdx !== -1) {
|
|
334
|
+
const callIdx = parseInt(args[dumpMessageFlagIdx + 1], 10);
|
|
335
|
+
const msgIdx = parseInt(args[dumpMessageFlagIdx + 2], 10);
|
|
336
|
+
if (Number.isNaN(callIdx) || Number.isNaN(msgIdx)) {
|
|
337
|
+
console.error(
|
|
338
|
+
"--dump-message requires two numeric indices, e.g. --dump-message 2 0"
|
|
339
|
+
);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
dumpMessage(entries, callIdx, msgIdx);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (dumpFlagIdx !== -1) {
|
|
347
|
+
const idx = parseInt(args[dumpFlagIdx + 1], 10);
|
|
348
|
+
if (Number.isNaN(idx)) {
|
|
349
|
+
console.error("--dump requires a numeric index, e.g. --dump 2");
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
dumpMessages(entries, idx);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Default: summary
|
|
357
|
+
const verdicts = computeVerdicts(entries);
|
|
358
|
+
if (args.includes("--json")) {
|
|
359
|
+
printJsonSummary(verdicts);
|
|
360
|
+
} else {
|
|
361
|
+
printSummaryTable(entries, verdicts);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
main();
|