@poolzin/pool-bot 2026.1.33 → 2026.1.34
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/dist/agents/pi-embedded-helpers/errors.js +0 -2
- package/dist/agents/session-tool-result-guard.js +2 -2
- package/dist/agents/session-transcript-repair.js +1 -20
- package/dist/build-info.json +3 -3
- package/dist/entry.js +0 -0
- package/dist/gateway/hooks/index.js +0 -2
- package/extensions/googlechat/node_modules/.bin/poolbot +0 -0
- package/extensions/line/node_modules/.bin/poolbot +0 -0
- package/extensions/matrix/node_modules/.bin/markdown-it +0 -0
- package/extensions/matrix/node_modules/.bin/poolbot +0 -0
- package/extensions/memory-core/node_modules/.bin/poolbot +0 -0
- package/extensions/memory-lancedb/node_modules/.bin/openai +0 -0
- package/extensions/msteams/node_modules/.bin/poolbot +0 -0
- package/extensions/nostr/node_modules/.bin/poolbot +0 -0
- package/extensions/twitch/node_modules/.bin/poolbot +0 -0
- package/extensions/zalo/node_modules/.bin/poolbot +0 -0
- package/extensions/zalouser/node_modules/.bin/poolbot +0 -0
- package/git-hooks/pre-commit +0 -0
- package/package.json +69 -79
- package/skills/nano-banana-pro/scripts/generate_image.py +0 -0
- package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +0 -0
- package/skills/tmux/scripts/find-sessions.sh +0 -0
- package/skills/tmux/scripts/wait-for-text.sh +0 -0
- package/dist/gateway/hooks/tool-usage-capture.js +0 -253
- package/dist/gateway/hooks/tool-usage-storage.js +0 -144
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { makeMissingToolResult
|
|
1
|
+
import { makeMissingToolResult } from "./session-transcript-repair.js";
|
|
2
2
|
import { emitSessionTranscriptUpdate } from "../sessions/transcript-events.js";
|
|
3
3
|
function extractAssistantToolCalls(msg) {
|
|
4
4
|
const content = msg.content;
|
|
@@ -14,7 +14,7 @@ function extractAssistantToolCalls(msg) {
|
|
|
14
14
|
if (rec.type === "toolCall" || rec.type === "toolUse" || rec.type === "functionCall") {
|
|
15
15
|
toolCalls.push({
|
|
16
16
|
id: rec.id,
|
|
17
|
-
name:
|
|
17
|
+
name: typeof rec.name === "string" ? rec.name : undefined,
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
import { logWarn } from "../logger.js";
|
|
2
|
-
/**
|
|
3
|
-
* Maximum allowed tool name length per Anthropic API spec.
|
|
4
|
-
* Names exceeding this will cause "tool_use.name > 200 chars" validation errors.
|
|
5
|
-
*/
|
|
6
|
-
export const MAX_TOOL_NAME_LENGTH = 64;
|
|
7
|
-
/**
|
|
8
|
-
* Truncate tool names that exceed the API limit.
|
|
9
|
-
* This prevents "tool_use.name > 200 chars" validation errors when
|
|
10
|
-
* the model hallucinates overly long tool names.
|
|
11
|
-
*/
|
|
12
|
-
export function sanitizeToolName(name) {
|
|
13
|
-
if (!name)
|
|
14
|
-
return name;
|
|
15
|
-
if (name.length <= MAX_TOOL_NAME_LENGTH)
|
|
16
|
-
return name;
|
|
17
|
-
logWarn(`[transcript-repair] truncating tool name from ${name.length} to ${MAX_TOOL_NAME_LENGTH} chars: ${name.slice(0, 50)}...`);
|
|
18
|
-
return name.slice(0, MAX_TOOL_NAME_LENGTH);
|
|
19
|
-
}
|
|
20
1
|
function extractToolCallsFromAssistant(msg) {
|
|
21
2
|
const content = msg.content;
|
|
22
3
|
if (!Array.isArray(content))
|
|
@@ -31,7 +12,7 @@ function extractToolCallsFromAssistant(msg) {
|
|
|
31
12
|
if (rec.type === "toolCall" || rec.type === "toolUse" || rec.type === "functionCall") {
|
|
32
13
|
toolCalls.push({
|
|
33
14
|
id: rec.id,
|
|
34
|
-
name:
|
|
15
|
+
name: typeof rec.name === "string" ? rec.name : undefined,
|
|
35
16
|
});
|
|
36
17
|
}
|
|
37
18
|
}
|
package/dist/build-info.json
CHANGED
package/dist/entry.js
CHANGED
|
File without changes
|
|
@@ -24,8 +24,6 @@ defaultOnSessionStart, defaultOnSessionEnd, registerDefaultHooks,
|
|
|
24
24
|
clearAllHooks, } from "./lifecycle-hooks.js";
|
|
25
25
|
// Integration with agent events
|
|
26
26
|
export { startLifecycleHooksIntegration, getIntegrationStats, isSessionTracked, getTrackedSessionInfo, clearTrackedSessions, removeTrackedSession, } from "./lifecycle-hooks-integration.js";
|
|
27
|
-
// Tool usage capture (Day 2 - ACTIVE, feature-flagged)
|
|
28
|
-
export { toolUsageCaptureHook, loadSessionToolUsage, saveSessionToolUsage, } from "./tool-usage-capture.js";
|
|
29
27
|
// Progressive disclosure (Day 3 - ACTIVE, feature-flagged, OPT-IN)
|
|
30
28
|
// Feature flag: PROGRESSIVE_DISCLOSURE_FLAGS.ENABLED = false (OFF by default)
|
|
31
29
|
// Safe: Returns error "feature disabled" if called without enabling flag
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/git-hooks/pre-commit
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poolzin/pool-bot",
|
|
3
|
-
"version": "2026.1.
|
|
3
|
+
"version": "2026.1.34",
|
|
4
4
|
"description": "🎱 Pool Bot - AI assistant with PLCODE integrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -76,81 +76,12 @@
|
|
|
76
76
|
"dist/pairing/**",
|
|
77
77
|
"dist/whatsapp/**"
|
|
78
78
|
],
|
|
79
|
-
"scripts": {
|
|
80
|
-
"dev": "node scripts/run-node.mjs",
|
|
81
|
-
"postinstall": "node scripts/postinstall.js",
|
|
82
|
-
"prepack": "pnpm build && pnpm ui:build",
|
|
83
|
-
"docs:list": "node scripts/docs-list.js",
|
|
84
|
-
"docs:bin": "node scripts/build-docs-list.mjs",
|
|
85
|
-
"docs:dev": "cd docs && mint dev",
|
|
86
|
-
"docs:build": "cd docs && pnpm dlx --reporter append-only mint broken-links",
|
|
87
|
-
"build": "tsc -p tsconfig.json && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/write-build-info.ts",
|
|
88
|
-
"plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts",
|
|
89
|
-
"release:check": "node --import tsx scripts/release-check.ts",
|
|
90
|
-
"ui:install": "node scripts/ui.js install",
|
|
91
|
-
"ui:dev": "node scripts/ui.js dev",
|
|
92
|
-
"ui:build": "node scripts/ui.js build",
|
|
93
|
-
"start": "node scripts/run-node.mjs",
|
|
94
|
-
"poolbot": "node scripts/run-node.mjs",
|
|
95
|
-
"gateway:watch": "node scripts/watch-node.mjs gateway --force",
|
|
96
|
-
"gateway:dev": "CLAWDBOT_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway",
|
|
97
|
-
"gateway:dev:reset": "CLAWDBOT_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway --reset",
|
|
98
|
-
"tui": "node scripts/run-node.mjs tui",
|
|
99
|
-
"tui:dev": "CLAWDBOT_PROFILE=dev node scripts/run-node.mjs tui",
|
|
100
|
-
"poolbot:rpc": "node scripts/run-node.mjs agent --mode rpc --json",
|
|
101
|
-
"ios:gen": "cd apps/ios && xcodegen generate",
|
|
102
|
-
"ios:open": "cd apps/ios && xcodegen generate && open Moltbot.xcodeproj",
|
|
103
|
-
"ios:build": "bash -lc 'cd apps/ios && xcodegen generate && xcodebuild -project Moltbot.xcodeproj -scheme Moltbot -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build'",
|
|
104
|
-
"ios:run": "bash -lc 'cd apps/ios && xcodegen generate && xcodebuild -project Moltbot.xcodeproj -scheme Moltbot -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build && xcrun simctl boot \"${IOS_SIM:-iPhone 17}\" || true && xcrun simctl launch booted com.poolbot.ios'",
|
|
105
|
-
"android:assemble": "cd apps/android && ./gradlew :app:assembleDebug",
|
|
106
|
-
"android:install": "cd apps/android && ./gradlew :app:installDebug",
|
|
107
|
-
"android:run": "cd apps/android && ./gradlew :app:installDebug && adb shell am start -n com.poolbot.android/.MainActivity",
|
|
108
|
-
"android:test": "cd apps/android && ./gradlew :app:testDebugUnitTest",
|
|
109
|
-
"mac:restart": "bash scripts/restart-mac.sh",
|
|
110
|
-
"mac:package": "bash scripts/package-mac-app.sh",
|
|
111
|
-
"mac:open": "open dist/Moltbot.app",
|
|
112
|
-
"lint": "oxlint --type-aware src test",
|
|
113
|
-
"lint:swift": "swiftlint lint --config .swiftlint.yml && (cd apps/ios && swiftlint lint --config .swiftlint.yml)",
|
|
114
|
-
"lint:all": "pnpm lint && pnpm lint:swift",
|
|
115
|
-
"lint:fix": "pnpm format:fix && oxlint --type-aware --fix src test",
|
|
116
|
-
"format": "oxfmt --check src test",
|
|
117
|
-
"format:swift": "swiftformat --lint --config .swiftformat apps/macos/Sources apps/ios/Sources apps/shared/ClawdbotKit/Sources",
|
|
118
|
-
"format:all": "pnpm format && pnpm format:swift",
|
|
119
|
-
"format:fix": "oxfmt --write src test",
|
|
120
|
-
"test": "node scripts/test-parallel.mjs",
|
|
121
|
-
"test:watch": "vitest",
|
|
122
|
-
"test:ui": "pnpm --dir ui test",
|
|
123
|
-
"test:force": "node --import tsx scripts/test-force.ts",
|
|
124
|
-
"test:coverage": "vitest run --coverage",
|
|
125
|
-
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
126
|
-
"test:live": "CLAWDBOT_LIVE_TEST=1 vitest run --config vitest.live.config.ts",
|
|
127
|
-
"test:docker:onboard": "bash scripts/e2e/onboard-docker.sh",
|
|
128
|
-
"test:docker:gateway-network": "bash scripts/e2e/gateway-network-docker.sh",
|
|
129
|
-
"test:docker:live-models": "bash scripts/test-live-models-docker.sh",
|
|
130
|
-
"test:docker:live-gateway": "bash scripts/test-live-gateway-models-docker.sh",
|
|
131
|
-
"test:docker:qr": "bash scripts/e2e/qr-import-docker.sh",
|
|
132
|
-
"test:docker:doctor-switch": "bash scripts/e2e/doctor-install-switch-docker.sh",
|
|
133
|
-
"test:docker:plugins": "bash scripts/e2e/plugins-docker.sh",
|
|
134
|
-
"test:docker:cleanup": "bash scripts/test-cleanup-docker.sh",
|
|
135
|
-
"test:docker:all": "pnpm test:docker:live-models && pnpm test:docker:live-gateway && pnpm test:docker:onboard && pnpm test:docker:gateway-network && pnpm test:docker:qr && pnpm test:docker:doctor-switch && pnpm test:docker:plugins && pnpm test:docker:cleanup",
|
|
136
|
-
"test:all": "pnpm lint && pnpm build && pnpm test && pnpm test:e2e && pnpm test:live && pnpm test:docker:all",
|
|
137
|
-
"test:install:e2e": "bash scripts/test-install-sh-e2e-docker.sh",
|
|
138
|
-
"test:install:smoke": "bash scripts/test-install-sh-docker.sh",
|
|
139
|
-
"test:install:e2e:openai": "CLAWDBOT_E2E_MODELS=openai bash scripts/test-install-sh-e2e-docker.sh",
|
|
140
|
-
"test:install:e2e:anthropic": "CLAWDBOT_E2E_MODELS=anthropic bash scripts/test-install-sh-e2e-docker.sh",
|
|
141
|
-
"protocol:gen": "node --import tsx scripts/protocol-gen.ts",
|
|
142
|
-
"protocol:gen:swift": "node --import tsx scripts/protocol-gen-swift.ts",
|
|
143
|
-
"protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift",
|
|
144
|
-
"canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh",
|
|
145
|
-
"check:loc": "node --import tsx scripts/check-ts-max-loc.ts --max 500"
|
|
146
|
-
},
|
|
147
79
|
"keywords": [],
|
|
148
80
|
"author": "João Vitor Cunha <jvsantos.cunha@gmail.com>",
|
|
149
81
|
"license": "MIT",
|
|
150
82
|
"engines": {
|
|
151
83
|
"node": ">=22.12.0"
|
|
152
84
|
},
|
|
153
|
-
"packageManager": "pnpm@10.23.0",
|
|
154
85
|
"dependencies": {
|
|
155
86
|
"@agentclientprotocol/sdk": "0.13.1",
|
|
156
87
|
"@aws-sdk/client-bedrock": "^3.975.0",
|
|
@@ -242,14 +173,6 @@
|
|
|
242
173
|
"overrides": {
|
|
243
174
|
"tar": "7.5.4"
|
|
244
175
|
},
|
|
245
|
-
"pnpm": {
|
|
246
|
-
"minimumReleaseAge": 2880,
|
|
247
|
-
"overrides": {
|
|
248
|
-
"@sinclair/typebox": "0.34.47",
|
|
249
|
-
"hono": "4.11.4",
|
|
250
|
-
"tar": "7.5.4"
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
176
|
"vitest": {
|
|
254
177
|
"coverage": {
|
|
255
178
|
"provider": "v8",
|
|
@@ -281,5 +204,72 @@
|
|
|
281
204
|
"apps/macos/.build/**",
|
|
282
205
|
"dist/Moltbot.app/**"
|
|
283
206
|
]
|
|
207
|
+
},
|
|
208
|
+
"scripts": {
|
|
209
|
+
"dev": "node scripts/run-node.mjs",
|
|
210
|
+
"postinstall": "node scripts/postinstall.js",
|
|
211
|
+
"docs:list": "node scripts/docs-list.js",
|
|
212
|
+
"docs:bin": "node scripts/build-docs-list.mjs",
|
|
213
|
+
"docs:dev": "cd docs && mint dev",
|
|
214
|
+
"docs:build": "cd docs && pnpm dlx --reporter append-only mint broken-links",
|
|
215
|
+
"build": "tsc -p tsconfig.json && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/write-build-info.ts",
|
|
216
|
+
"plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts",
|
|
217
|
+
"release:check": "node --import tsx scripts/release-check.ts",
|
|
218
|
+
"ui:install": "node scripts/ui.js install",
|
|
219
|
+
"ui:dev": "node scripts/ui.js dev",
|
|
220
|
+
"ui:build": "node scripts/ui.js build",
|
|
221
|
+
"start": "node scripts/run-node.mjs",
|
|
222
|
+
"poolbot": "node scripts/run-node.mjs",
|
|
223
|
+
"gateway:watch": "node scripts/watch-node.mjs gateway --force",
|
|
224
|
+
"gateway:dev": "CLAWDBOT_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway",
|
|
225
|
+
"gateway:dev:reset": "CLAWDBOT_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway --reset",
|
|
226
|
+
"tui": "node scripts/run-node.mjs tui",
|
|
227
|
+
"tui:dev": "CLAWDBOT_PROFILE=dev node scripts/run-node.mjs tui",
|
|
228
|
+
"poolbot:rpc": "node scripts/run-node.mjs agent --mode rpc --json",
|
|
229
|
+
"ios:gen": "cd apps/ios && xcodegen generate",
|
|
230
|
+
"ios:open": "cd apps/ios && xcodegen generate && open Moltbot.xcodeproj",
|
|
231
|
+
"ios:build": "bash -lc 'cd apps/ios && xcodegen generate && xcodebuild -project Moltbot.xcodeproj -scheme Moltbot -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build'",
|
|
232
|
+
"ios:run": "bash -lc 'cd apps/ios && xcodegen generate && xcodebuild -project Moltbot.xcodeproj -scheme Moltbot -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build && xcrun simctl boot \"${IOS_SIM:-iPhone 17}\" || true && xcrun simctl launch booted com.poolbot.ios'",
|
|
233
|
+
"android:assemble": "cd apps/android && ./gradlew :app:assembleDebug",
|
|
234
|
+
"android:install": "cd apps/android && ./gradlew :app:installDebug",
|
|
235
|
+
"android:run": "cd apps/android && ./gradlew :app:installDebug && adb shell am start -n com.poolbot.android/.MainActivity",
|
|
236
|
+
"android:test": "cd apps/android && ./gradlew :app:testDebugUnitTest",
|
|
237
|
+
"mac:restart": "bash scripts/restart-mac.sh",
|
|
238
|
+
"mac:package": "bash scripts/package-mac-app.sh",
|
|
239
|
+
"mac:open": "open dist/Moltbot.app",
|
|
240
|
+
"lint": "oxlint --type-aware src test",
|
|
241
|
+
"lint:swift": "swiftlint lint --config .swiftlint.yml && (cd apps/ios && swiftlint lint --config .swiftlint.yml)",
|
|
242
|
+
"lint:all": "pnpm lint && pnpm lint:swift",
|
|
243
|
+
"lint:fix": "pnpm format:fix && oxlint --type-aware --fix src test",
|
|
244
|
+
"format": "oxfmt --check src test",
|
|
245
|
+
"format:swift": "swiftformat --lint --config .swiftformat apps/macos/Sources apps/ios/Sources apps/shared/ClawdbotKit/Sources",
|
|
246
|
+
"format:all": "pnpm format && pnpm format:swift",
|
|
247
|
+
"format:fix": "oxfmt --write src test",
|
|
248
|
+
"test": "node scripts/test-parallel.mjs",
|
|
249
|
+
"test:watch": "vitest",
|
|
250
|
+
"test:ui": "pnpm --dir ui test",
|
|
251
|
+
"test:force": "node --import tsx scripts/test-force.ts",
|
|
252
|
+
"test:coverage": "vitest run --coverage",
|
|
253
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
254
|
+
"test:live": "CLAWDBOT_LIVE_TEST=1 vitest run --config vitest.live.config.ts",
|
|
255
|
+
"test:docker:onboard": "bash scripts/e2e/onboard-docker.sh",
|
|
256
|
+
"test:docker:gateway-network": "bash scripts/e2e/gateway-network-docker.sh",
|
|
257
|
+
"test:docker:live-models": "bash scripts/test-live-models-docker.sh",
|
|
258
|
+
"test:docker:live-gateway": "bash scripts/test-live-gateway-models-docker.sh",
|
|
259
|
+
"test:docker:qr": "bash scripts/e2e/qr-import-docker.sh",
|
|
260
|
+
"test:docker:doctor-switch": "bash scripts/e2e/doctor-install-switch-docker.sh",
|
|
261
|
+
"test:docker:plugins": "bash scripts/e2e/plugins-docker.sh",
|
|
262
|
+
"test:docker:cleanup": "bash scripts/test-cleanup-docker.sh",
|
|
263
|
+
"test:docker:all": "pnpm test:docker:live-models && pnpm test:docker:live-gateway && pnpm test:docker:onboard && pnpm test:docker:gateway-network && pnpm test:docker:qr && pnpm test:docker:doctor-switch && pnpm test:docker:plugins && pnpm test:docker:cleanup",
|
|
264
|
+
"test:all": "pnpm lint && pnpm build && pnpm test && pnpm test:e2e && pnpm test:live && pnpm test:docker:all",
|
|
265
|
+
"test:install:e2e": "bash scripts/test-install-sh-e2e-docker.sh",
|
|
266
|
+
"test:install:smoke": "bash scripts/test-install-sh-docker.sh",
|
|
267
|
+
"test:install:e2e:openai": "CLAWDBOT_E2E_MODELS=openai bash scripts/test-install-sh-e2e-docker.sh",
|
|
268
|
+
"test:install:e2e:anthropic": "CLAWDBOT_E2E_MODELS=anthropic bash scripts/test-install-sh-e2e-docker.sh",
|
|
269
|
+
"protocol:gen": "node --import tsx scripts/protocol-gen.ts",
|
|
270
|
+
"protocol:gen:swift": "node --import tsx scripts/protocol-gen-swift.ts",
|
|
271
|
+
"protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift",
|
|
272
|
+
"canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh",
|
|
273
|
+
"check:loc": "node --import tsx scripts/check-ts-max-loc.ts --max 500"
|
|
284
274
|
}
|
|
285
|
-
}
|
|
275
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool Usage Capture Hook
|
|
3
|
-
*
|
|
4
|
-
* Captures tool usage automatically via lifecycle hooks.
|
|
5
|
-
* Safe: never throws, async operations, feature-flagged.
|
|
6
|
-
*
|
|
7
|
-
* Integration:
|
|
8
|
-
* - Registers as onToolUse hook
|
|
9
|
-
* - Captures all tool calls automatically
|
|
10
|
-
* - Stores compressed data in SessionEntry
|
|
11
|
-
*/
|
|
12
|
-
import { TOOL_USAGE_ENABLED, TOOL_USAGE_MAX_RECORDS, createToolUsageRecord, updateToolStats, addToHistory, } from "./tool-usage-storage.js";
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// Configuration
|
|
15
|
-
// ============================================================================
|
|
16
|
-
const TOOL_USAGE_BUFFER_FLUSH_INTERVAL = 30000; // 30 seconds
|
|
17
|
-
const TOOL_USAGE_BUFFER_MAX_SIZE = 50; // Flush after 50 tool calls
|
|
18
|
-
const toolUsageBuffer = new Map();
|
|
19
|
-
let bufferFlushScheduled = false;
|
|
20
|
-
// ============================================================================
|
|
21
|
-
// SessionEntry Extension (Type-safe, backward compatible)
|
|
22
|
-
// ============================================================================
|
|
23
|
-
/**
|
|
24
|
-
* Load tool usage from SessionEntry
|
|
25
|
-
* Safe: handles missing data, validates structure
|
|
26
|
-
*/
|
|
27
|
-
export async function loadSessionToolUsage(sessionKey) {
|
|
28
|
-
try {
|
|
29
|
-
// TODO: Integrate with actual SessionEntry in Phase 4
|
|
30
|
-
// For now, return in-memory placeholder
|
|
31
|
-
const existing = toolUsageBuffer.get(sessionKey);
|
|
32
|
-
if (existing && existing.length > 0) {
|
|
33
|
-
// Return computed stats from buffer
|
|
34
|
-
const tools = {};
|
|
35
|
-
for (const record of existing) {
|
|
36
|
-
if (!tools[record.toolName]) {
|
|
37
|
-
tools[record.toolName] = {
|
|
38
|
-
count: 0,
|
|
39
|
-
totalMs: 0,
|
|
40
|
-
avgMs: 0,
|
|
41
|
-
lastUsed: record.timestamp,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
tools[record.toolName] = updateToolStats(tools[record.toolName], record.executionTimeMs);
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
version: 2,
|
|
48
|
-
sessionKey,
|
|
49
|
-
sessionId: sessionKey, // Placeholder
|
|
50
|
-
tools,
|
|
51
|
-
history: existing,
|
|
52
|
-
lastCaptured: Date.now(),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
// Return empty usage
|
|
56
|
-
return {
|
|
57
|
-
version: 2,
|
|
58
|
-
sessionKey,
|
|
59
|
-
sessionId: sessionKey,
|
|
60
|
-
tools: {},
|
|
61
|
-
history: [],
|
|
62
|
-
lastCaptured: Date.now(),
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
console.error("[tool-usage-capture] Failed to load tool usage:", err);
|
|
67
|
-
// Return empty usage on error
|
|
68
|
-
return {
|
|
69
|
-
version: 2,
|
|
70
|
-
sessionKey,
|
|
71
|
-
sessionId: sessionKey,
|
|
72
|
-
tools: {},
|
|
73
|
-
history: [],
|
|
74
|
-
lastCaptured: Date.now(),
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Save tool usage to SessionEntry
|
|
80
|
-
* Safe: validates data, handles errors gracefully
|
|
81
|
-
*/
|
|
82
|
-
export async function saveSessionToolUsage(sessionKey, usage) {
|
|
83
|
-
try {
|
|
84
|
-
// TODO: Integrate with actual SessionEntry in Phase 4
|
|
85
|
-
// For now, just update in-memory buffer
|
|
86
|
-
const existing = toolUsageBuffer.get(sessionKey) || [];
|
|
87
|
-
const newRecords = usage.history.slice(existing.length);
|
|
88
|
-
if (newRecords.length > 0) {
|
|
89
|
-
toolUsageBuffer.set(sessionKey, [...existing, ...newRecords]);
|
|
90
|
-
}
|
|
91
|
-
if (TOOL_USAGE_ENABLED) {
|
|
92
|
-
console.log(`[tool-usage-capture] Saved ${newRecords.length} records for ${sessionKey}`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
catch (err) {
|
|
96
|
-
console.error("[tool-usage-capture] Failed to save tool usage:", err);
|
|
97
|
-
// Never throw - errors are logged only
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// ============================================================================
|
|
101
|
-
// Hook Implementation
|
|
102
|
-
// ============================================================================
|
|
103
|
-
/**
|
|
104
|
-
* Main hook: captures tool usage automatically
|
|
105
|
-
* Safe: validates all inputs, never throws, handles all edge cases
|
|
106
|
-
*/
|
|
107
|
-
export const toolUsageCaptureHook = async (ctx) => {
|
|
108
|
-
// Feature flag check (safe exit)
|
|
109
|
-
if (!TOOL_USAGE_ENABLED) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
// Validate inputs (safe defaults)
|
|
114
|
-
const toolName = ctx.toolName || "unknown_tool";
|
|
115
|
-
const executionTimeMs = ctx.executionTimeMs || 0;
|
|
116
|
-
const success = ctx.success !== undefined ? ctx.success : true;
|
|
117
|
-
// Create compressed record
|
|
118
|
-
const record = createToolUsageRecord(toolName, ctx.toolInput, ctx.toolOutput, executionTimeMs, success, ctx.error);
|
|
119
|
-
// Load existing usage
|
|
120
|
-
const usage = await loadSessionToolUsage(ctx.sessionKey);
|
|
121
|
-
// Update stats
|
|
122
|
-
if (!usage.tools[toolName]) {
|
|
123
|
-
usage.tools[toolName] = {
|
|
124
|
-
count: 0,
|
|
125
|
-
totalMs: 0,
|
|
126
|
-
avgMs: 0,
|
|
127
|
-
lastUsed: record.timestamp,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
usage.tools[toolName] = updateToolStats(usage.tools[toolName], executionTimeMs);
|
|
131
|
-
// Add to history (respects max limit)
|
|
132
|
-
usage.history = addToHistory(usage.history, record, TOOL_USAGE_MAX_RECORDS);
|
|
133
|
-
usage.lastCaptured = Date.now();
|
|
134
|
-
// Save (async, non-blocking)
|
|
135
|
-
await saveSessionToolUsage(ctx.sessionKey, usage).catch((err) => {
|
|
136
|
-
// Save errors are logged in saveSessionToolUsage
|
|
137
|
-
// This catch is just for extra safety
|
|
138
|
-
console.error("[tool-usage-capture] Unhandled save error:", err);
|
|
139
|
-
});
|
|
140
|
-
// Debug logging (can be disabled)
|
|
141
|
-
if (success && TOOL_USAGE_ENABLED) {
|
|
142
|
-
console.log(`[tool-usage-capture] ${toolName} (${executionTimeMs}ms) - ${ctx.sessionKey}`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
catch (err) {
|
|
146
|
-
// NEVER throw from hook - would break the tool call
|
|
147
|
-
console.error("[tool-usage-capture] Hook error:", err);
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
// ============================================================================
|
|
151
|
-
// Buffer Management (performance optimization)
|
|
152
|
-
// ============================================================================
|
|
153
|
-
/**
|
|
154
|
-
* Flush in-memory buffer to storage
|
|
155
|
-
* Safe: handles errors, never throws
|
|
156
|
-
*/
|
|
157
|
-
export async function flushToolUsageBuffer() {
|
|
158
|
-
try {
|
|
159
|
-
if (toolUsageBuffer.size === 0) {
|
|
160
|
-
return; // Nothing to flush
|
|
161
|
-
}
|
|
162
|
-
let flushed = 0;
|
|
163
|
-
for (const [sessionKey, records] of toolUsageBuffer.entries()) {
|
|
164
|
-
try {
|
|
165
|
-
// TODO: Actually persist to SessionEntry in Phase 4
|
|
166
|
-
// For now, buffer stays in-memory
|
|
167
|
-
flushed += records.length;
|
|
168
|
-
}
|
|
169
|
-
catch (err) {
|
|
170
|
-
console.error(`[tool-usage-capture] Failed to flush buffer for ${sessionKey}:`, err);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
if (flushed > 0 && TOOL_USAGE_ENABLED) {
|
|
174
|
-
console.log(`[tool-usage-capture] Flushed ${flushed} records from buffer`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
catch (err) {
|
|
178
|
-
console.error("[tool-usage-capture] Buffer flush error:", err);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Schedule buffer flush
|
|
183
|
-
* Safe: debounced, only schedules once
|
|
184
|
-
*/
|
|
185
|
-
function scheduleBufferFlush() {
|
|
186
|
-
if (bufferFlushScheduled) {
|
|
187
|
-
return; // Already scheduled
|
|
188
|
-
}
|
|
189
|
-
bufferFlushScheduled = true;
|
|
190
|
-
// Flush after 30 seconds
|
|
191
|
-
setTimeout(async () => {
|
|
192
|
-
await flushToolUsageBuffer();
|
|
193
|
-
bufferFlushScheduled = false;
|
|
194
|
-
}, TOOL_USAGE_BUFFER_FLUSH_INTERVAL);
|
|
195
|
-
}
|
|
196
|
-
// ============================================================================
|
|
197
|
-
// Query API (for future use)
|
|
198
|
-
// ============================================================================
|
|
199
|
-
/**
|
|
200
|
-
* Get tool usage statistics for a session
|
|
201
|
-
* Safe: returns empty object if no data
|
|
202
|
-
*/
|
|
203
|
-
export async function getSessionToolUsage(sessionKey) {
|
|
204
|
-
try {
|
|
205
|
-
const usage = await loadSessionToolUsage(sessionKey);
|
|
206
|
-
return usage;
|
|
207
|
-
}
|
|
208
|
-
catch (err) {
|
|
209
|
-
console.error(`[tool-usage-capture] Failed to get usage for ${sessionKey}:`, err);
|
|
210
|
-
return undefined;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Get recent tool usage (last N records)
|
|
215
|
-
* Safe: returns empty array if no data
|
|
216
|
-
*/
|
|
217
|
-
export async function getRecentToolUsage(sessionKey, count = 10) {
|
|
218
|
-
try {
|
|
219
|
-
const usage = await loadSessionToolUsage(sessionKey);
|
|
220
|
-
return usage.history.slice(-count);
|
|
221
|
-
}
|
|
222
|
-
catch (err) {
|
|
223
|
-
console.error(`[tool-usage-capture] Failed to get recent usage for ${sessionKey}:`, err);
|
|
224
|
-
return [];
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
// ============================================================================
|
|
228
|
-
// Cleanup
|
|
229
|
-
// ============================================================================
|
|
230
|
-
/**
|
|
231
|
-
* Clear tool usage buffer (for testing)
|
|
232
|
-
* Safe: never throws
|
|
233
|
-
*/
|
|
234
|
-
export function clearToolUsageBuffer() {
|
|
235
|
-
try {
|
|
236
|
-
toolUsageBuffer.clear();
|
|
237
|
-
}
|
|
238
|
-
catch (err) {
|
|
239
|
-
console.error("[tool-usage-capture] Buffer clear error:", err);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Clear tool usage for a specific session
|
|
244
|
-
* Safe: never throws
|
|
245
|
-
*/
|
|
246
|
-
export function clearSessionToolUsage(sessionKey) {
|
|
247
|
-
try {
|
|
248
|
-
toolUsageBuffer.delete(sessionKey);
|
|
249
|
-
}
|
|
250
|
-
catch (err) {
|
|
251
|
-
console.error(`[tool-usage-capture] Failed to clear usage for ${sessionKey}:`, err);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool Usage Capture - Storage Layer
|
|
3
|
-
*
|
|
4
|
-
* Safe, efficient storage for tool usage data.
|
|
5
|
-
* Phase 2a: Type definitions and interfaces (NO breaking changes)
|
|
6
|
-
*
|
|
7
|
-
* Safety measures:
|
|
8
|
-
* - All fields optional (backward compatible)
|
|
9
|
-
* - Version field for future migrations
|
|
10
|
-
* - No modifications to existing SessionEntry fields
|
|
11
|
-
*/
|
|
12
|
-
// ============================================================================
|
|
13
|
-
// Constants
|
|
14
|
-
// ============================================================================
|
|
15
|
-
export const TOOL_USAGE_MAX_RECORDS = 100; // Keep only last 100 records
|
|
16
|
-
export const TOOL_USAGE_MAX_SUMMARY_LEN = 100; // Max 100 chars per summary
|
|
17
|
-
export const TOOL_USAGE_ENABLED = true; // Feature flag (master switch)
|
|
18
|
-
// ============================================================================
|
|
19
|
-
// Compression Functions (safe, no side effects)
|
|
20
|
-
// ============================================================================
|
|
21
|
-
/**
|
|
22
|
-
* Compress a tool input/output value to a short summary
|
|
23
|
-
* Safe: never throws, handles all types gracefully
|
|
24
|
-
*/
|
|
25
|
-
export function compressToolValue(value, maxLen = TOOL_USAGE_MAX_SUMMARY_LEN) {
|
|
26
|
-
try {
|
|
27
|
-
// Handle primitives
|
|
28
|
-
if (value === null)
|
|
29
|
-
return "null";
|
|
30
|
-
if (value === undefined)
|
|
31
|
-
return "undefined";
|
|
32
|
-
if (typeof value === "string") {
|
|
33
|
-
return value.length > maxLen ? value.slice(0, maxLen) + "..." : value;
|
|
34
|
-
}
|
|
35
|
-
if (typeof value === "number")
|
|
36
|
-
return String(value);
|
|
37
|
-
if (typeof value === "boolean")
|
|
38
|
-
return String(value);
|
|
39
|
-
// Handle objects and arrays
|
|
40
|
-
if (typeof value === "object") {
|
|
41
|
-
const json = JSON.stringify(value);
|
|
42
|
-
return json.length > maxLen ? json.slice(0, maxLen) + "..." : json;
|
|
43
|
-
}
|
|
44
|
-
// Fallback
|
|
45
|
-
return String(value);
|
|
46
|
-
}
|
|
47
|
-
catch (err) {
|
|
48
|
-
// If compression fails, return safe fallback
|
|
49
|
-
return "[compression_error]";
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Create a tool usage record from hook context
|
|
54
|
-
* Safe: validates all inputs, never throws
|
|
55
|
-
*/
|
|
56
|
-
export function createToolUsageRecord(toolName, toolInput, toolOutput, executionTimeMs, success, error) {
|
|
57
|
-
// Validate inputs (safe defaults)
|
|
58
|
-
const safeToolName = typeof toolName === "string" && toolName.length > 0 ? toolName : "unknown_tool";
|
|
59
|
-
const safeInput = typeof toolInput === "object" && toolInput !== null ? toolInput : {};
|
|
60
|
-
const safeTime = typeof executionTimeMs === "number" && executionTimeMs >= 0 ? executionTimeMs : 0;
|
|
61
|
-
const safeSuccess = Boolean(success);
|
|
62
|
-
return {
|
|
63
|
-
toolName: safeToolName,
|
|
64
|
-
timestamp: Date.now(),
|
|
65
|
-
executionTimeMs: safeTime,
|
|
66
|
-
success: safeSuccess,
|
|
67
|
-
inputSummary: compressToolValue(safeInput),
|
|
68
|
-
outputSummary: compressToolValue(toolOutput),
|
|
69
|
-
error: error ? String(error).slice(0, 200) : undefined,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
// ============================================================================
|
|
73
|
-
// Statistics Update (pure function, no side effects)
|
|
74
|
-
// ============================================================================
|
|
75
|
-
/**
|
|
76
|
-
* Update tool statistics with a new record
|
|
77
|
-
* Safe: never mutates input, returns new object
|
|
78
|
-
*/
|
|
79
|
-
export function updateToolStats(existingStats, executionTimeMs) {
|
|
80
|
-
const count = (existingStats?.count ?? 0) + 1;
|
|
81
|
-
const totalMs = (existingStats?.totalMs ?? 0) + executionTimeMs;
|
|
82
|
-
const avgMs = count > 0 ? Math.round((totalMs / count) * 100) / 100 : 0;
|
|
83
|
-
return {
|
|
84
|
-
count,
|
|
85
|
-
totalMs,
|
|
86
|
-
avgMs,
|
|
87
|
-
lastUsed: Date.now(),
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
// ============================================================================
|
|
91
|
-
// History Management (pure functions)
|
|
92
|
-
// ============================================================================
|
|
93
|
-
/**
|
|
94
|
-
* Add record to history, respecting max limit
|
|
95
|
-
* Safe: never exceeds max records, returns new array
|
|
96
|
-
*/
|
|
97
|
-
export function addToHistory(history, record, maxRecords = TOOL_USAGE_MAX_RECORDS) {
|
|
98
|
-
const newHistory = [...history, record];
|
|
99
|
-
// Keep only last N records
|
|
100
|
-
if (newHistory.length > maxRecords) {
|
|
101
|
-
return newHistory.slice(-maxRecords);
|
|
102
|
-
}
|
|
103
|
-
return newHistory;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Get recent tool usage (last N records)
|
|
107
|
-
* Safe: never mutates input, returns slice
|
|
108
|
-
*/
|
|
109
|
-
export function getRecentToolUsage(history, count = 10) {
|
|
110
|
-
return history.slice(-count);
|
|
111
|
-
}
|
|
112
|
-
// ============================================================================
|
|
113
|
-
// Query Functions (for future use)
|
|
114
|
-
// ============================================================================
|
|
115
|
-
/**
|
|
116
|
-
* Get statistics for a specific tool
|
|
117
|
-
* Safe: returns undefined if tool not found
|
|
118
|
-
*/
|
|
119
|
-
export function getToolStats(usage, toolName) {
|
|
120
|
-
return usage?.tools[toolName];
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Get all tool names sorted by usage frequency
|
|
124
|
-
* Safe: returns empty array if no data
|
|
125
|
-
*/
|
|
126
|
-
export function getToolsByFrequency(usage) {
|
|
127
|
-
if (!usage?.tools)
|
|
128
|
-
return [];
|
|
129
|
-
return Object.entries(usage.tools)
|
|
130
|
-
.map(([toolName, stats]) => ({ toolName, count: stats.count }))
|
|
131
|
-
.sort((a, b) => b.count - a.count);
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Get slowest tools (by average execution time)
|
|
135
|
-
* Safe: returns empty array if no data
|
|
136
|
-
*/
|
|
137
|
-
export function getSlowestTools(usage, limit = 5) {
|
|
138
|
-
if (!usage?.tools)
|
|
139
|
-
return [];
|
|
140
|
-
return Object.entries(usage.tools)
|
|
141
|
-
.map(([toolName, stats]) => ({ toolName, avgMs: stats.avgMs }))
|
|
142
|
-
.sort((a, b) => b.avgMs - a.avgMs)
|
|
143
|
-
.slice(0, limit);
|
|
144
|
-
}
|