@claudetools/tools 0.8.2 → 0.8.4
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/cli.js +41 -0
- package/dist/context/deduplication.d.ts +72 -0
- package/dist/context/deduplication.js +77 -0
- package/dist/context/deduplication.test.d.ts +6 -0
- package/dist/context/deduplication.test.js +84 -0
- package/dist/context/emergency-eviction.d.ts +73 -0
- package/dist/context/emergency-eviction.example.d.ts +13 -0
- package/dist/context/emergency-eviction.example.js +94 -0
- package/dist/context/emergency-eviction.js +226 -0
- package/dist/context/eviction-engine.d.ts +76 -0
- package/dist/context/eviction-engine.example.d.ts +7 -0
- package/dist/context/eviction-engine.example.js +144 -0
- package/dist/context/eviction-engine.js +176 -0
- package/dist/context/example-usage.d.ts +1 -0
- package/dist/context/example-usage.js +128 -0
- package/dist/context/exchange-summariser.d.ts +80 -0
- package/dist/context/exchange-summariser.js +261 -0
- package/dist/context/health-monitor.d.ts +97 -0
- package/dist/context/health-monitor.example.d.ts +1 -0
- package/dist/context/health-monitor.example.js +164 -0
- package/dist/context/health-monitor.js +210 -0
- package/dist/context/importance-scorer.d.ts +94 -0
- package/dist/context/importance-scorer.example.d.ts +1 -0
- package/dist/context/importance-scorer.example.js +140 -0
- package/dist/context/importance-scorer.js +187 -0
- package/dist/context/index.d.ts +9 -0
- package/dist/context/index.js +16 -0
- package/dist/context/session-helper.d.ts +10 -0
- package/dist/context/session-helper.js +51 -0
- package/dist/context/session-store.d.ts +94 -0
- package/dist/context/session-store.js +286 -0
- package/dist/context/usage-estimator.d.ts +131 -0
- package/dist/context/usage-estimator.js +260 -0
- package/dist/context/usage-estimator.test.d.ts +1 -0
- package/dist/context/usage-estimator.test.js +208 -0
- package/dist/context-cli.d.ts +16 -0
- package/dist/context-cli.js +309 -0
- package/dist/evaluation/build-dataset.d.ts +1 -0
- package/dist/evaluation/build-dataset.js +135 -0
- package/dist/evaluation/threshold-eval.d.ts +63 -0
- package/dist/evaluation/threshold-eval.js +250 -0
- package/dist/handlers/codedna-handlers.d.ts +2 -2
- package/dist/handlers/tool-handlers.js +126 -165
- package/dist/helpers/api-client.d.ts +5 -1
- package/dist/helpers/api-client.js +3 -1
- package/dist/helpers/compact-formatter.d.ts +51 -0
- package/dist/helpers/compact-formatter.js +130 -0
- package/dist/helpers/engagement-tracker.d.ts +10 -0
- package/dist/helpers/engagement-tracker.js +61 -0
- package/dist/helpers/error-tracking.js +1 -1
- package/dist/helpers/session-validation.d.ts +76 -0
- package/dist/helpers/session-validation.js +221 -0
- package/dist/helpers/usage-analytics.js +1 -1
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/post-tool-use-hook-cli.d.ts +2 -0
- package/dist/hooks/post-tool-use-hook-cli.js +34 -0
- package/dist/hooks/post-tool-use.d.ts +67 -0
- package/dist/hooks/post-tool-use.js +234 -0
- package/dist/hooks/stop-hook-cli.d.ts +2 -0
- package/dist/hooks/stop-hook-cli.js +34 -0
- package/dist/hooks/stop.d.ts +64 -0
- package/dist/hooks/stop.js +192 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +4 -0
- package/dist/resources.js +3 -0
- package/dist/setup.js +206 -2
- package/dist/templates/claude-md.d.ts +1 -1
- package/dist/templates/claude-md.js +23 -35
- package/dist/templates/worker-prompt.js +35 -202
- package/dist/tools.js +26 -20
- package/package.json +6 -2
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Post-Tool-Use Hook: Automatic Context Management
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// This hook runs after each tool execution (Read, Edit, Write, Bash, etc.)
|
|
5
|
+
// Updates session state with tool output size and triggers eviction if needed
|
|
6
|
+
//
|
|
7
|
+
// Trigger Points:
|
|
8
|
+
// - estimated_fill > 0.60 → Standard eviction cycle
|
|
9
|
+
// - estimated_fill > 0.70 → Emergency eviction
|
|
10
|
+
//
|
|
11
|
+
// =============================================================================
|
|
12
|
+
import { mcpLogger } from '../logger.js';
|
|
13
|
+
import { getSessionId } from '../helpers/engagement-tracker.js';
|
|
14
|
+
import { estimateTokens } from '../context/usage-estimator.js';
|
|
15
|
+
const sessions = new Map();
|
|
16
|
+
// Default context limit for Claude Code (Sonnet 4.5)
|
|
17
|
+
const DEFAULT_CONTEXT_LIMIT = 200000;
|
|
18
|
+
// Eviction thresholds
|
|
19
|
+
const STANDARD_EVICTION_THRESHOLD = 0.60; // 60%
|
|
20
|
+
const EMERGENCY_EVICTION_THRESHOLD = 0.70; // 70%
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Session Store
|
|
23
|
+
// =============================================================================
|
|
24
|
+
class SessionStore {
|
|
25
|
+
/**
|
|
26
|
+
* Get or create session data
|
|
27
|
+
*/
|
|
28
|
+
getSession(sessionId) {
|
|
29
|
+
let session = sessions.get(sessionId);
|
|
30
|
+
if (!session) {
|
|
31
|
+
session = {
|
|
32
|
+
session_id: sessionId,
|
|
33
|
+
tool_executions: 0,
|
|
34
|
+
estimated_tokens: 0,
|
|
35
|
+
context_limit: DEFAULT_CONTEXT_LIMIT,
|
|
36
|
+
estimated_fill: 0,
|
|
37
|
+
last_updated: new Date(),
|
|
38
|
+
tool_output_tokens: 0,
|
|
39
|
+
};
|
|
40
|
+
sessions.set(sessionId, session);
|
|
41
|
+
}
|
|
42
|
+
return session;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Update session with tool output tokens
|
|
46
|
+
*/
|
|
47
|
+
updateSession(sessionId, outputTokens) {
|
|
48
|
+
const session = this.getSession(sessionId);
|
|
49
|
+
// Increment tool execution count
|
|
50
|
+
session.tool_executions += 1;
|
|
51
|
+
// Add output tokens to total
|
|
52
|
+
session.tool_output_tokens += outputTokens;
|
|
53
|
+
session.estimated_tokens += outputTokens;
|
|
54
|
+
// Update estimated fill
|
|
55
|
+
session.estimated_fill = session.estimated_tokens / session.context_limit;
|
|
56
|
+
// Update timestamp
|
|
57
|
+
session.last_updated = new Date();
|
|
58
|
+
sessions.set(sessionId, session);
|
|
59
|
+
return session;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Clear session (for cleanup)
|
|
63
|
+
*/
|
|
64
|
+
clearSession(sessionId) {
|
|
65
|
+
sessions.delete(sessionId);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get all sessions (for debugging)
|
|
69
|
+
*/
|
|
70
|
+
getAllSessions() {
|
|
71
|
+
return Array.from(sessions.values());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const sessionStore = new SessionStore();
|
|
75
|
+
// =============================================================================
|
|
76
|
+
// Eviction Trigger Logic
|
|
77
|
+
// =============================================================================
|
|
78
|
+
class EvictionTriggerEngine {
|
|
79
|
+
/**
|
|
80
|
+
* Check if eviction should be triggered based on fill percentage
|
|
81
|
+
*/
|
|
82
|
+
checkEvictionTrigger(session) {
|
|
83
|
+
const fill = session.estimated_fill;
|
|
84
|
+
if (fill > EMERGENCY_EVICTION_THRESHOLD) {
|
|
85
|
+
return {
|
|
86
|
+
triggered: true,
|
|
87
|
+
level: 'emergency',
|
|
88
|
+
estimated_fill: fill,
|
|
89
|
+
message: `EMERGENCY: Context fill at ${(fill * 100).toFixed(1)}% - triggering emergency eviction`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (fill > STANDARD_EVICTION_THRESHOLD) {
|
|
93
|
+
return {
|
|
94
|
+
triggered: true,
|
|
95
|
+
level: 'standard',
|
|
96
|
+
estimated_fill: fill,
|
|
97
|
+
message: `Context fill at ${(fill * 100).toFixed(1)}% - triggering standard eviction`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
triggered: false,
|
|
102
|
+
level: 'none',
|
|
103
|
+
estimated_fill: fill,
|
|
104
|
+
message: `Context fill at ${(fill * 100).toFixed(1)}% - no eviction needed`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Run standard eviction cycle
|
|
109
|
+
* NOTE: This is a placeholder - actual implementation would call into
|
|
110
|
+
* the context rotation/eviction system when it's built
|
|
111
|
+
*/
|
|
112
|
+
async runStandardEviction(session) {
|
|
113
|
+
mcpLogger.info('EVICTION', `Running standard eviction for session ${session.session_id}`);
|
|
114
|
+
mcpLogger.debug('EVICTION', `Fill: ${(session.estimated_fill * 100).toFixed(1)}%`);
|
|
115
|
+
// TODO: Integrate with actual eviction engine
|
|
116
|
+
// This would call into packages/tools/src/context/rotation-engine.ts (when built)
|
|
117
|
+
// For now, just log the trigger
|
|
118
|
+
console.warn(`\n⚠️ [ClaudeTools] Context Rotation Triggered`);
|
|
119
|
+
console.warn(` Fill: ${(session.estimated_fill * 100).toFixed(1)}%`);
|
|
120
|
+
console.warn(` Action: Standard eviction cycle\n`);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Run emergency eviction cycle
|
|
124
|
+
* More aggressive eviction for critical situations
|
|
125
|
+
*/
|
|
126
|
+
async runEmergencyEviction(session) {
|
|
127
|
+
mcpLogger.info('EVICTION', `Running EMERGENCY eviction for session ${session.session_id}`);
|
|
128
|
+
mcpLogger.debug('EVICTION', `Fill: ${(session.estimated_fill * 100).toFixed(1)}%`);
|
|
129
|
+
// TODO: Integrate with actual eviction engine
|
|
130
|
+
// Emergency mode would evict more aggressively
|
|
131
|
+
console.error(`\n🚨 [ClaudeTools] EMERGENCY Context Rotation`);
|
|
132
|
+
console.error(` Fill: ${(session.estimated_fill * 100).toFixed(1)}%`);
|
|
133
|
+
console.error(` Action: Emergency eviction - aggressive cleanup\n`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const evictionEngine = new EvictionTriggerEngine();
|
|
137
|
+
// =============================================================================
|
|
138
|
+
// Post-Tool-Use Hook Handler
|
|
139
|
+
// =============================================================================
|
|
140
|
+
/**
|
|
141
|
+
* Handle PostToolUse hook event
|
|
142
|
+
* Updates session state with tool output and triggers eviction if needed
|
|
143
|
+
*/
|
|
144
|
+
export async function handlePostToolUseHook(input) {
|
|
145
|
+
try {
|
|
146
|
+
// Get session ID (from input or fallback to tracker)
|
|
147
|
+
const sessionId = input.session_id || getSessionId();
|
|
148
|
+
// CRITICAL: Prevent infinite loops
|
|
149
|
+
if (input.post_tool_use_active) {
|
|
150
|
+
mcpLogger.debug('HOOK', 'PostToolUse hook already active - skipping');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// Only track tools that generate significant output
|
|
154
|
+
// Read, Edit, Write, Bash, etc. - not memory tools
|
|
155
|
+
if (!shouldTrackTool(input.tool_name)) {
|
|
156
|
+
mcpLogger.debug('HOOK', `Skipping tracking for tool: ${input.tool_name}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// Estimate tokens from tool output
|
|
160
|
+
const outputTokens = input.tool_output ? estimateTokens(input.tool_output) : 0;
|
|
161
|
+
if (outputTokens === 0) {
|
|
162
|
+
mcpLogger.debug('HOOK', `No output tokens for ${input.tool_name} - skipping`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// Update session state
|
|
166
|
+
const session = sessionStore.updateSession(sessionId, outputTokens);
|
|
167
|
+
// Log tool execution
|
|
168
|
+
mcpLogger.info('HOOK', `PostToolUse: ${input.tool_name} (+${outputTokens} tokens)`);
|
|
169
|
+
mcpLogger.debug('HOOK', `Session: ${sessionId}`);
|
|
170
|
+
mcpLogger.debug('HOOK', `Total estimated: ${session.estimated_tokens} tokens`);
|
|
171
|
+
mcpLogger.debug('HOOK', `Fill: ${(session.estimated_fill * 100).toFixed(1)}%`);
|
|
172
|
+
// Check eviction trigger
|
|
173
|
+
const trigger = evictionEngine.checkEvictionTrigger(session);
|
|
174
|
+
if (trigger.triggered) {
|
|
175
|
+
mcpLogger.info('HOOK', trigger.message);
|
|
176
|
+
if (trigger.level === 'emergency') {
|
|
177
|
+
await evictionEngine.runEmergencyEviction(session);
|
|
178
|
+
}
|
|
179
|
+
else if (trigger.level === 'standard') {
|
|
180
|
+
await evictionEngine.runStandardEviction(session);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
mcpLogger.debug('HOOK', trigger.message);
|
|
185
|
+
}
|
|
186
|
+
// Output verbose status if significant
|
|
187
|
+
if (outputTokens > 1000 || trigger.triggered) {
|
|
188
|
+
outputToolOutputSummary(input.tool_name, outputTokens, session, trigger);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
// Don't fail the tool execution on hook errors
|
|
193
|
+
mcpLogger.error('HOOK', `Error in PostToolUse hook: ${error}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Determine if a tool should be tracked for context usage
|
|
198
|
+
* Only track tools that generate significant output (Read, Bash, etc.)
|
|
199
|
+
* Skip memory tools (they're tracked separately)
|
|
200
|
+
*/
|
|
201
|
+
function shouldTrackTool(toolName) {
|
|
202
|
+
// Tools that generate significant output
|
|
203
|
+
const trackedTools = [
|
|
204
|
+
'Read',
|
|
205
|
+
'Bash',
|
|
206
|
+
'Grep',
|
|
207
|
+
'Glob',
|
|
208
|
+
'WebFetch',
|
|
209
|
+
'WebSearch',
|
|
210
|
+
// Add other tools as needed
|
|
211
|
+
];
|
|
212
|
+
return trackedTools.includes(toolName);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Output tool execution summary
|
|
216
|
+
* Shown when tool generates significant output or triggers eviction
|
|
217
|
+
*/
|
|
218
|
+
function outputToolOutputSummary(toolName, outputTokens, session, trigger) {
|
|
219
|
+
const fillPercentage = (session.estimated_fill * 100).toFixed(1);
|
|
220
|
+
const statusEmoji = trigger.triggered ? '⚠️' : '✓';
|
|
221
|
+
console.log(`\n${statusEmoji} Tool Output Tracking:`);
|
|
222
|
+
console.log(` Tool: ${toolName}`);
|
|
223
|
+
console.log(` Output: +${outputTokens.toLocaleString()} tokens`);
|
|
224
|
+
console.log(` Total: ${session.estimated_tokens.toLocaleString()}/${session.context_limit.toLocaleString()} tokens (${fillPercentage}%)`);
|
|
225
|
+
console.log(` Executions: ${session.tool_executions}`);
|
|
226
|
+
if (trigger.triggered) {
|
|
227
|
+
console.log(` Eviction: ${trigger.level.toUpperCase()} triggered`);
|
|
228
|
+
}
|
|
229
|
+
console.log('');
|
|
230
|
+
}
|
|
231
|
+
// =============================================================================
|
|
232
|
+
// Exports
|
|
233
|
+
// =============================================================================
|
|
234
|
+
export { sessionStore, evictionEngine };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Stop Hook CLI Entry Point
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// Called by Claude Code when Stop event occurs
|
|
6
|
+
// Reads hook input from stdin, processes it, and outputs to stdout/stderr
|
|
7
|
+
import { handleStopHook } from './stop.js';
|
|
8
|
+
async function main() {
|
|
9
|
+
try {
|
|
10
|
+
// Read JSON input from stdin
|
|
11
|
+
let inputData = '';
|
|
12
|
+
// Set up stdin reading
|
|
13
|
+
process.stdin.setEncoding('utf8');
|
|
14
|
+
for await (const chunk of process.stdin) {
|
|
15
|
+
inputData += chunk;
|
|
16
|
+
}
|
|
17
|
+
// Parse hook input
|
|
18
|
+
const hookInput = JSON.parse(inputData);
|
|
19
|
+
// Validate hook event
|
|
20
|
+
if (hookInput.hook_event_name !== 'Stop') {
|
|
21
|
+
console.error(`Error: Expected Stop event, got ${hookInput.hook_event_name}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
// Process the hook
|
|
25
|
+
await handleStopHook(hookInput);
|
|
26
|
+
// Success - exit code 0
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error('Error processing Stop hook:', error);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
main();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { getSessionStats } from '../helpers/session-validation.js';
|
|
2
|
+
interface StopHookInput {
|
|
3
|
+
session_id?: string;
|
|
4
|
+
hook_event_name: 'Stop';
|
|
5
|
+
stop_hook_reason: 'end_turn' | 'max_turns' | 'error';
|
|
6
|
+
stop_hook_active?: boolean;
|
|
7
|
+
last_assistant_message?: string;
|
|
8
|
+
}
|
|
9
|
+
interface ContextHealth {
|
|
10
|
+
estimated_fill: number;
|
|
11
|
+
warning_level: 'healthy' | 'caution' | 'critical';
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
interface SessionData {
|
|
15
|
+
session_id: string;
|
|
16
|
+
exchanges: number;
|
|
17
|
+
estimated_tokens: number;
|
|
18
|
+
context_limit: number;
|
|
19
|
+
estimated_fill: number;
|
|
20
|
+
last_updated: Date;
|
|
21
|
+
engagement_score: number;
|
|
22
|
+
}
|
|
23
|
+
declare class UsageEstimator {
|
|
24
|
+
/**
|
|
25
|
+
* Estimate tokens used in current session
|
|
26
|
+
* Uses session statistics to calculate rough estimate
|
|
27
|
+
*/
|
|
28
|
+
getEstimatedTokens(sessionStats: ReturnType<typeof getSessionStats>): number;
|
|
29
|
+
/**
|
|
30
|
+
* Calculate estimated fill percentage
|
|
31
|
+
*/
|
|
32
|
+
getEstimatedFill(contextLimit: number, estimatedTokens: number): number;
|
|
33
|
+
/**
|
|
34
|
+
* Determine health status based on fill percentage
|
|
35
|
+
*/
|
|
36
|
+
getHealthStatus(fill: number): ContextHealth;
|
|
37
|
+
}
|
|
38
|
+
declare const usageEstimator: UsageEstimator;
|
|
39
|
+
declare class SessionStore {
|
|
40
|
+
/**
|
|
41
|
+
* Get or create session data
|
|
42
|
+
*/
|
|
43
|
+
getSession(sessionId: string): SessionData;
|
|
44
|
+
/**
|
|
45
|
+
* Update session with latest stats
|
|
46
|
+
*/
|
|
47
|
+
updateSession(sessionId: string, stats: ReturnType<typeof getSessionStats>): SessionData;
|
|
48
|
+
/**
|
|
49
|
+
* Get all sessions (for debugging)
|
|
50
|
+
*/
|
|
51
|
+
getAllSessions(): SessionData[];
|
|
52
|
+
/**
|
|
53
|
+
* Clear session (for cleanup)
|
|
54
|
+
*/
|
|
55
|
+
clearSession(sessionId: string): void;
|
|
56
|
+
}
|
|
57
|
+
declare const sessionStore: SessionStore;
|
|
58
|
+
/**
|
|
59
|
+
* Handle Stop hook event
|
|
60
|
+
* Updates session state and logs context health warnings
|
|
61
|
+
*/
|
|
62
|
+
export declare function handleStopHook(input: StopHookInput): Promise<void>;
|
|
63
|
+
export { sessionStore, usageEstimator };
|
|
64
|
+
export type { StopHookInput, SessionData, ContextHealth };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Stop Hook: Usage Dashboard and Context Health Monitoring
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// This hook runs when the main agent finishes responding (Stop event)
|
|
5
|
+
// Updates session state with latest exchange data and warns if context fill > 60%
|
|
6
|
+
import { mcpLogger } from '../logger.js';
|
|
7
|
+
import { getSessionId } from '../helpers/engagement-tracker.js';
|
|
8
|
+
import { getSessionStats } from '../helpers/session-validation.js';
|
|
9
|
+
const sessions = new Map();
|
|
10
|
+
// Default context limit for Claude Code (Sonnet 4.5)
|
|
11
|
+
const DEFAULT_CONTEXT_LIMIT = 200000;
|
|
12
|
+
// Token estimation heuristic (rough estimate)
|
|
13
|
+
// Average tokens per exchange based on typical usage patterns
|
|
14
|
+
const AVG_TOKENS_PER_EXCHANGE = 2000; // Conservative estimate
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Usage Estimator
|
|
17
|
+
// =============================================================================
|
|
18
|
+
class UsageEstimator {
|
|
19
|
+
/**
|
|
20
|
+
* Estimate tokens used in current session
|
|
21
|
+
* Uses session statistics to calculate rough estimate
|
|
22
|
+
*/
|
|
23
|
+
getEstimatedTokens(sessionStats) {
|
|
24
|
+
// Base estimation from tool calls
|
|
25
|
+
const toolCallTokens = sessionStats.totalCalls * 200; // ~200 tokens per tool call
|
|
26
|
+
// Add engagement-based estimation
|
|
27
|
+
const engagementTokens = sessionStats.engagement.score * 100; // More engagement = more context
|
|
28
|
+
// Add base conversation overhead
|
|
29
|
+
const baseOverhead = 5000; // Initial prompt + system context
|
|
30
|
+
return baseOverhead + toolCallTokens + engagementTokens;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Calculate estimated fill percentage
|
|
34
|
+
*/
|
|
35
|
+
getEstimatedFill(contextLimit, estimatedTokens) {
|
|
36
|
+
return estimatedTokens / contextLimit;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Determine health status based on fill percentage
|
|
40
|
+
*/
|
|
41
|
+
getHealthStatus(fill) {
|
|
42
|
+
if (fill < 0.6) {
|
|
43
|
+
return {
|
|
44
|
+
estimated_fill: fill,
|
|
45
|
+
warning_level: 'healthy',
|
|
46
|
+
message: 'Context usage is healthy',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
else if (fill < 0.8) {
|
|
50
|
+
return {
|
|
51
|
+
estimated_fill: fill,
|
|
52
|
+
warning_level: 'caution',
|
|
53
|
+
message: 'Context fill approaching limits - consider eviction strategies',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
return {
|
|
58
|
+
estimated_fill: fill,
|
|
59
|
+
warning_level: 'critical',
|
|
60
|
+
message: 'CRITICAL: Context fill very high - eviction strongly recommended',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const usageEstimator = new UsageEstimator();
|
|
66
|
+
// =============================================================================
|
|
67
|
+
// Session Store
|
|
68
|
+
// =============================================================================
|
|
69
|
+
class SessionStore {
|
|
70
|
+
/**
|
|
71
|
+
* Get or create session data
|
|
72
|
+
*/
|
|
73
|
+
getSession(sessionId) {
|
|
74
|
+
let session = sessions.get(sessionId);
|
|
75
|
+
if (!session) {
|
|
76
|
+
session = {
|
|
77
|
+
session_id: sessionId,
|
|
78
|
+
exchanges: 0,
|
|
79
|
+
estimated_tokens: 0,
|
|
80
|
+
context_limit: DEFAULT_CONTEXT_LIMIT,
|
|
81
|
+
estimated_fill: 0,
|
|
82
|
+
last_updated: new Date(),
|
|
83
|
+
engagement_score: 0,
|
|
84
|
+
};
|
|
85
|
+
sessions.set(sessionId, session);
|
|
86
|
+
}
|
|
87
|
+
return session;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Update session with latest stats
|
|
91
|
+
*/
|
|
92
|
+
updateSession(sessionId, stats) {
|
|
93
|
+
const session = this.getSession(sessionId);
|
|
94
|
+
// Update exchange count (each Stop = 1 exchange)
|
|
95
|
+
session.exchanges += 1;
|
|
96
|
+
// Update estimated tokens
|
|
97
|
+
session.estimated_tokens = usageEstimator.getEstimatedTokens(stats);
|
|
98
|
+
// Update estimated fill
|
|
99
|
+
session.estimated_fill = usageEstimator.getEstimatedFill(session.context_limit, session.estimated_tokens);
|
|
100
|
+
// Update engagement score
|
|
101
|
+
session.engagement_score = stats.engagement.score;
|
|
102
|
+
// Update timestamp
|
|
103
|
+
session.last_updated = new Date();
|
|
104
|
+
sessions.set(sessionId, session);
|
|
105
|
+
return session;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get all sessions (for debugging)
|
|
109
|
+
*/
|
|
110
|
+
getAllSessions() {
|
|
111
|
+
return Array.from(sessions.values());
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Clear session (for cleanup)
|
|
115
|
+
*/
|
|
116
|
+
clearSession(sessionId) {
|
|
117
|
+
sessions.delete(sessionId);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const sessionStore = new SessionStore();
|
|
121
|
+
// =============================================================================
|
|
122
|
+
// Stop Hook Handler
|
|
123
|
+
// =============================================================================
|
|
124
|
+
/**
|
|
125
|
+
* Handle Stop hook event
|
|
126
|
+
* Updates session state and logs context health warnings
|
|
127
|
+
*/
|
|
128
|
+
export async function handleStopHook(input) {
|
|
129
|
+
try {
|
|
130
|
+
// Get session ID (from input or fallback to tracker)
|
|
131
|
+
const sessionId = input.session_id || getSessionId();
|
|
132
|
+
// CRITICAL: Prevent infinite loops
|
|
133
|
+
if (input.stop_hook_active) {
|
|
134
|
+
mcpLogger.debug('HOOK', 'Stop hook already active - allowing stop');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Get current session statistics
|
|
138
|
+
const stats = getSessionStats();
|
|
139
|
+
// Update session state
|
|
140
|
+
const session = sessionStore.updateSession(sessionId, stats);
|
|
141
|
+
// Get health status
|
|
142
|
+
const health = usageEstimator.getHealthStatus(session.estimated_fill);
|
|
143
|
+
// Log session statistics
|
|
144
|
+
mcpLogger.info('HOOK', `Stop event processed - Session: ${sessionId}`);
|
|
145
|
+
mcpLogger.debug('HOOK', `Exchanges: ${session.exchanges}`);
|
|
146
|
+
mcpLogger.debug('HOOK', `Estimated tokens: ${session.estimated_tokens}`);
|
|
147
|
+
mcpLogger.debug('HOOK', `Engagement score: ${session.engagement_score}/100`);
|
|
148
|
+
// Warn if approaching limits
|
|
149
|
+
if (health.warning_level === 'caution') {
|
|
150
|
+
console.warn(`\n⚠️ [ClaudeTools] Context fill at ${(health.estimated_fill * 100).toFixed(1)}%`);
|
|
151
|
+
console.warn(` ${health.message}\n`);
|
|
152
|
+
}
|
|
153
|
+
else if (health.warning_level === 'critical') {
|
|
154
|
+
console.error(`\n🚨 [ClaudeTools] CRITICAL: Context fill at ${(health.estimated_fill * 100).toFixed(1)}%`);
|
|
155
|
+
console.error(` ${health.message}\n`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
mcpLogger.debug('HOOK', `Context health: ${health.warning_level} (${(health.estimated_fill * 100).toFixed(1)}%)`);
|
|
159
|
+
}
|
|
160
|
+
// Output context health status (for verbose mode)
|
|
161
|
+
outputContextHealth(session, health);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
// Don't fail the stop event on errors
|
|
165
|
+
mcpLogger.error('HOOK', `Error in Stop hook: ${error}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Output context health status
|
|
170
|
+
* Shown in verbose mode via stdout
|
|
171
|
+
*/
|
|
172
|
+
function outputContextHealth(session, health) {
|
|
173
|
+
const fillPercentage = (health.estimated_fill * 100).toFixed(1);
|
|
174
|
+
const statusEmoji = health.warning_level === 'healthy'
|
|
175
|
+
? '✓'
|
|
176
|
+
: health.warning_level === 'caution'
|
|
177
|
+
? '⚠'
|
|
178
|
+
: '🚨';
|
|
179
|
+
console.log(`\n${statusEmoji} Context Health Status:`);
|
|
180
|
+
console.log(` Fill: ${fillPercentage}% (${session.estimated_tokens.toLocaleString()}/${session.context_limit.toLocaleString()} tokens)`);
|
|
181
|
+
console.log(` Exchanges: ${session.exchanges}`);
|
|
182
|
+
console.log(` Engagement: ${session.engagement_score}/100`);
|
|
183
|
+
console.log(` Status: ${health.warning_level.toUpperCase()}`);
|
|
184
|
+
if (health.warning_level !== 'healthy') {
|
|
185
|
+
console.log(` Message: ${health.message}`);
|
|
186
|
+
}
|
|
187
|
+
console.log('');
|
|
188
|
+
}
|
|
189
|
+
// =============================================================================
|
|
190
|
+
// Exports
|
|
191
|
+
// =============================================================================
|
|
192
|
+
export { sessionStore, usageEstimator };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export type { ExpertWorker } from './helpers/workers.js';
|
|
2
2
|
export type { Task, TaskContext, DispatchableTask } from './helpers/tasks.js';
|
|
3
|
+
export type { DeduplicationTracker } from './context/index.js';
|
|
3
4
|
export { EXPERT_WORKERS, matchTaskToWorker } from './helpers/workers.js';
|
|
4
5
|
export { parseJsonArray, getDispatchableTasks, getExecutionContext, resolveTaskDependencies, createTask, listTasks, getTask, claimTask, releaseTask, updateTaskStatus, addTaskContext, getTaskSummary, heartbeatTask } from './helpers/tasks.js';
|
|
5
6
|
export { injectContext } from './helpers/api-client.js';
|
|
7
|
+
export { recordToolCall, getToolCallWarnings, getSessionStats, clearSessionState, recordContextReference, calculateEngagementScore, getEngagementStats } from './helpers/session-validation.js';
|
|
8
|
+
export { createDeduplicationTracker, InMemoryDeduplicationTracker } from './context/index.js';
|
|
6
9
|
export declare function startServer(): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,8 @@ import { registerPromptHandlers } from './prompts.js';
|
|
|
15
15
|
export { EXPERT_WORKERS, matchTaskToWorker } from './helpers/workers.js';
|
|
16
16
|
export { parseJsonArray, getDispatchableTasks, getExecutionContext, resolveTaskDependencies, createTask, listTasks, getTask, claimTask, releaseTask, updateTaskStatus, addTaskContext, getTaskSummary, heartbeatTask } from './helpers/tasks.js';
|
|
17
17
|
export { injectContext } from './helpers/api-client.js';
|
|
18
|
+
export { recordToolCall, getToolCallWarnings, getSessionStats, clearSessionState, recordContextReference, calculateEngagementScore, getEngagementStats } from './helpers/session-validation.js';
|
|
19
|
+
export { createDeduplicationTracker, InMemoryDeduplicationTracker } from './context/index.js';
|
|
18
20
|
// =============================================================================
|
|
19
21
|
// Server Initialization
|
|
20
22
|
// =============================================================================
|
package/dist/logger.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type LogCategory = 'TOOL' | 'API' | 'MEMORY' | 'SEARCH' | 'INJECT' | 'EXTRACT' | 'STORE' | 'QUERY' | 'IMPACT' | 'PATTERN' | 'CONFIG' | 'REGISTRATION' | 'ERROR';
|
|
1
|
+
export type LogCategory = 'TOOL' | 'API' | 'MEMORY' | 'SEARCH' | 'INJECT' | 'EXTRACT' | 'STORE' | 'QUERY' | 'IMPACT' | 'PATTERN' | 'CONFIG' | 'REGISTRATION' | 'HOOK' | 'ERROR' | 'SESSION_HELPER' | 'EVICTION' | 'CONTEXT';
|
|
2
2
|
declare class MCPLogger {
|
|
3
3
|
private logFile;
|
|
4
4
|
private logDir;
|
package/dist/logger.js
CHANGED
package/dist/resources.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcon
|
|
|
5
5
|
import { getSummary, getEntities, getContext } from './helpers/api-client.js';
|
|
6
6
|
import { formatContextForClaude } from './helpers/formatter.js';
|
|
7
7
|
import { getDefaultProjectId } from './helpers/config.js';
|
|
8
|
+
import { recordContextReference } from './helpers/session-validation.js';
|
|
8
9
|
export function registerResourceHandlers(server) {
|
|
9
10
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
10
11
|
resources: [
|
|
@@ -59,6 +60,8 @@ export function registerResourceHandlers(server) {
|
|
|
59
60
|
}
|
|
60
61
|
if (uri === 'memory://context') {
|
|
61
62
|
const context = await getContext(projectId);
|
|
63
|
+
// Track context reference for engagement scoring (+20 points)
|
|
64
|
+
recordContextReference(projectId);
|
|
62
65
|
return {
|
|
63
66
|
contents: [
|
|
64
67
|
{
|