@claudetools/tools 0.8.3 → 0.8.5

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.
Files changed (69) hide show
  1. package/dist/cli.js +41 -0
  2. package/dist/context/deduplication.d.ts +72 -0
  3. package/dist/context/deduplication.js +77 -0
  4. package/dist/context/deduplication.test.d.ts +6 -0
  5. package/dist/context/deduplication.test.js +84 -0
  6. package/dist/context/emergency-eviction.d.ts +73 -0
  7. package/dist/context/emergency-eviction.example.d.ts +13 -0
  8. package/dist/context/emergency-eviction.example.js +94 -0
  9. package/dist/context/emergency-eviction.js +226 -0
  10. package/dist/context/eviction-engine.d.ts +76 -0
  11. package/dist/context/eviction-engine.example.d.ts +7 -0
  12. package/dist/context/eviction-engine.example.js +144 -0
  13. package/dist/context/eviction-engine.js +176 -0
  14. package/dist/context/example-usage.d.ts +1 -0
  15. package/dist/context/example-usage.js +128 -0
  16. package/dist/context/exchange-summariser.d.ts +80 -0
  17. package/dist/context/exchange-summariser.js +261 -0
  18. package/dist/context/health-monitor.d.ts +97 -0
  19. package/dist/context/health-monitor.example.d.ts +1 -0
  20. package/dist/context/health-monitor.example.js +164 -0
  21. package/dist/context/health-monitor.js +210 -0
  22. package/dist/context/importance-scorer.d.ts +94 -0
  23. package/dist/context/importance-scorer.example.d.ts +1 -0
  24. package/dist/context/importance-scorer.example.js +140 -0
  25. package/dist/context/importance-scorer.js +187 -0
  26. package/dist/context/index.d.ts +9 -0
  27. package/dist/context/index.js +16 -0
  28. package/dist/context/session-helper.d.ts +10 -0
  29. package/dist/context/session-helper.js +51 -0
  30. package/dist/context/session-store.d.ts +94 -0
  31. package/dist/context/session-store.js +286 -0
  32. package/dist/context/usage-estimator.d.ts +131 -0
  33. package/dist/context/usage-estimator.js +260 -0
  34. package/dist/context/usage-estimator.test.d.ts +1 -0
  35. package/dist/context/usage-estimator.test.js +208 -0
  36. package/dist/context-cli.d.ts +16 -0
  37. package/dist/context-cli.js +309 -0
  38. package/dist/handlers/codedna-handlers.d.ts +1 -1
  39. package/dist/handlers/tool-handlers.js +215 -13
  40. package/dist/helpers/api-client.d.ts +5 -1
  41. package/dist/helpers/api-client.js +3 -1
  42. package/dist/helpers/circuit-breaker.d.ts +28 -0
  43. package/dist/helpers/circuit-breaker.js +97 -0
  44. package/dist/helpers/compact-formatter.d.ts +2 -0
  45. package/dist/helpers/compact-formatter.js +6 -0
  46. package/dist/helpers/error-tracking.js +1 -1
  47. package/dist/helpers/tasks-retry.d.ts +9 -0
  48. package/dist/helpers/tasks-retry.js +30 -0
  49. package/dist/helpers/tasks.d.ts +91 -5
  50. package/dist/helpers/tasks.js +261 -16
  51. package/dist/helpers/usage-analytics.js +1 -1
  52. package/dist/hooks/index.d.ts +4 -0
  53. package/dist/hooks/index.js +6 -0
  54. package/dist/hooks/post-tool-use-hook-cli.d.ts +2 -0
  55. package/dist/hooks/post-tool-use-hook-cli.js +34 -0
  56. package/dist/hooks/post-tool-use.d.ts +67 -0
  57. package/dist/hooks/post-tool-use.js +234 -0
  58. package/dist/hooks/stop-hook-cli.d.ts +2 -0
  59. package/dist/hooks/stop-hook-cli.js +34 -0
  60. package/dist/hooks/stop.d.ts +64 -0
  61. package/dist/hooks/stop.js +192 -0
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.js +2 -0
  64. package/dist/logger.d.ts +1 -1
  65. package/dist/logger.js +4 -0
  66. package/dist/setup.js +206 -2
  67. package/dist/tools.js +107 -2
  68. package/package.json +19 -18
  69. package/scripts/verify-prompt-compliance.sh +0 -0
@@ -3,6 +3,7 @@
3
3
  // =============================================================================
4
4
  import { apiRequest } from './api-client.js';
5
5
  import { matchTaskToWorker, buildWorkerPrompt } from './workers.js';
6
+ import { isCircuitOpen } from './circuit-breaker.js';
6
7
  export function parseJsonArray(value) {
7
8
  if (!value)
8
9
  return [];
@@ -51,11 +52,12 @@ export async function getTask(userId, projectId, taskId, full = false) {
51
52
  const endpoint = full ? 'full' : '';
52
53
  return apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}${endpoint ? `/${endpoint}` : ''}`);
53
54
  }
54
- export async function claimTask(userId, projectId, taskId, agentId, lockDurationMinutes = 30) {
55
- return apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}/claim`, 'POST', {
56
- agent_id: agentId,
57
- lock_duration_minutes: lockDurationMinutes,
58
- });
55
+ export async function claimTask(userId, projectId, taskId, agentId, lockDurationMinutes) {
56
+ const body = { agent_id: agentId };
57
+ if (lockDurationMinutes !== undefined) {
58
+ body.lock_duration_minutes = lockDurationMinutes;
59
+ }
60
+ return apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}/claim`, 'POST', body);
59
61
  }
60
62
  export async function releaseTask(userId, projectId, taskId, agentId, newStatus, workLog) {
61
63
  return apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}/release`, 'POST', {
@@ -90,11 +92,29 @@ export async function heartbeatTask(userId, projectId, taskId, agentId, extendMi
90
92
  // -----------------------------------------------------------------------------
91
93
  // Orchestration Functions
92
94
  // -----------------------------------------------------------------------------
93
- // Configuration: Default maximum parallel tasks
94
- export const DEFAULT_MAX_PARALLEL = 5;
95
+ // Effort weights for capacity planning
96
+ export const EFFORT_WEIGHTS = {
97
+ xs: 1,
98
+ s: 2,
99
+ m: 4,
100
+ l: 8,
101
+ xl: 16,
102
+ };
103
+ // Default lock duration by effort size (in minutes)
104
+ // Used when no explicit lockDurationMinutes is provided to claimTask
105
+ export const LOCK_DURATION_BY_EFFORT = {
106
+ xs: 15, // Extra small: 15 minutes
107
+ s: 30, // Small: 30 minutes (default)
108
+ m: 60, // Medium: 1 hour
109
+ l: 120, // Large: 2 hours
110
+ xl: 240, // Extra large: 4 hours
111
+ };
112
+ // Configuration: Default capacity budget in effort units
113
+ // 20 units allows: 20 xs tasks OR 1 xl + 1 m task OR 5 m tasks, etc.
114
+ export const DEFAULT_CAPACITY_BUDGET = 20;
95
115
  /**
96
- * Get count of currently active tasks
97
- * Returns both in_progress and claimed (locked) task counts
116
+ * Get effort-weighted load of currently active tasks
117
+ * Returns both in_progress and claimed (locked) task counts, plus effort-weighted load
98
118
  */
99
119
  export async function getActiveTaskCount(userId, projectId, epicId) {
100
120
  // Get all in_progress tasks
@@ -105,10 +125,15 @@ export async function getActiveTaskCount(userId, projectId, epicId) {
105
125
  });
106
126
  let inProgressCount = 0;
107
127
  let claimedCount = 0;
128
+ let effortLoad = 0;
108
129
  if (inProgressResult.success && inProgressResult.data.length > 0) {
109
130
  const now = new Date();
110
131
  for (const task of inProgressResult.data) {
111
132
  inProgressCount++;
133
+ // Calculate effort weight (default to 'm' = 4 units if not specified)
134
+ const effort = task.estimated_effort || 'm';
135
+ const weight = EFFORT_WEIGHTS[effort] || EFFORT_WEIGHTS.m;
136
+ effortLoad += weight;
112
137
  // Check if task is also claimed (has valid lock)
113
138
  if (task.assigned_to && task.lock_expires_at) {
114
139
  const lockExpires = new Date(task.lock_expires_at);
@@ -122,6 +147,7 @@ export async function getActiveTaskCount(userId, projectId, epicId) {
122
147
  inProgress: inProgressCount,
123
148
  claimed: claimedCount,
124
149
  total: inProgressCount,
150
+ effortLoad,
125
151
  };
126
152
  }
127
153
  /**
@@ -130,13 +156,13 @@ export async function getActiveTaskCount(userId, projectId, epicId) {
130
156
  * - Excludes already claimed tasks
131
157
  * - Resolves dependencies (only returns unblocked tasks)
132
158
  * - Matches each to appropriate expert worker
133
- * - Respects max parallel limit by considering currently active tasks
159
+ * - Respects effort-weighted capacity budget
134
160
  */
135
- export async function getDispatchableTasks(userId, projectId, epicId, maxParallel = DEFAULT_MAX_PARALLEL) {
136
- // Get current active task count
161
+ export async function getDispatchableTasks(userId, projectId, epicId, capacityBudget = DEFAULT_CAPACITY_BUDGET) {
162
+ // Get current active task effort load
137
163
  const activeCount = await getActiveTaskCount(userId, projectId, epicId);
138
- // Calculate remaining capacity
139
- const remainingCapacity = maxParallel - activeCount.total;
164
+ // Calculate remaining effort capacity
165
+ let remainingCapacity = capacityBudget - activeCount.effortLoad;
140
166
  if (remainingCapacity <= 0) {
141
167
  // No capacity available, return empty array
142
168
  return [];
@@ -178,6 +204,13 @@ export async function getDispatchableTasks(userId, projectId, epicId, maxParalle
178
204
  continue; // Still blocked
179
205
  }
180
206
  }
207
+ // Calculate task effort weight
208
+ const effort = task.estimated_effort || 'm';
209
+ const taskEffort = EFFORT_WEIGHTS[effort] || EFFORT_WEIGHTS.m;
210
+ // Check if this task fits within remaining capacity
211
+ if (taskEffort > remainingCapacity) {
212
+ continue; // Task too large for remaining capacity
213
+ }
181
214
  // Match to expert worker
182
215
  const worker = matchTaskToWorker({
183
216
  title: task.title,
@@ -185,6 +218,11 @@ export async function getDispatchableTasks(userId, projectId, epicId, maxParalle
185
218
  domain: task.domain || undefined,
186
219
  tags: parseJsonArray(task.tags),
187
220
  });
221
+ // Circuit breaker check: skip if worker circuit is open
222
+ if (isCircuitOpen(worker.id)) {
223
+ console.warn(`[Circuit Breaker] Skipping task ${task.id} - circuit OPEN for worker type: ${worker.id}`);
224
+ continue; // Skip this task
225
+ }
188
226
  // Get task context
189
227
  const fullTask = await getTask(userId, projectId, task.id, true);
190
228
  const taskData = fullTask.data;
@@ -195,13 +233,48 @@ export async function getDispatchableTasks(userId, projectId, epicId, maxParalle
195
233
  parentContext: taskData.parent,
196
234
  dependencies: [],
197
235
  });
198
- // Only dispatch tasks that fit within remaining capacity
199
- if (dispatchable.length >= remainingCapacity) {
236
+ // Reduce remaining capacity by the effort of this task
237
+ remainingCapacity -= taskEffort;
238
+ // Stop if no capacity remains
239
+ if (remainingCapacity <= 0) {
200
240
  break;
201
241
  }
202
242
  }
243
+ // Sort by priority: critical → high → medium → low
244
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
245
+ dispatchable.sort((a, b) => (priorityOrder[a.task.priority] ?? 2) - (priorityOrder[b.task.priority] ?? 2));
203
246
  return dispatchable;
204
247
  }
248
+ /**
249
+ * Check if task lock is about to expire and return warning
250
+ */
251
+ export function getLockExpiryWarning(lockExpiresAt) {
252
+ if (!lockExpiresAt) {
253
+ return { warning: false, minutes_remaining: 0 };
254
+ }
255
+ const now = new Date();
256
+ const expiryTime = new Date(lockExpiresAt);
257
+ const secondsRemaining = (expiryTime.getTime() - now.getTime()) / 1000;
258
+ const minutesRemaining = Math.floor(secondsRemaining / 60);
259
+ // Expired (lock time has passed)
260
+ if (secondsRemaining <= 0) {
261
+ return {
262
+ warning: true,
263
+ minutes_remaining: 0,
264
+ message: '🚨 Lock has EXPIRED. Task may be claimed by another agent.',
265
+ };
266
+ }
267
+ // Warn if lock expires within 5 minutes (but not yet expired)
268
+ if (minutesRemaining <= 5 && minutesRemaining >= 1) {
269
+ return {
270
+ warning: true,
271
+ minutes_remaining: minutesRemaining,
272
+ message: `⚠️ Lock expires in ${minutesRemaining} minute${minutesRemaining !== 1 ? 's' : ''}. Call task_heartbeat to extend.`,
273
+ };
274
+ }
275
+ // Less than 1 minute but not expired - no warning (edge case)
276
+ return { warning: false, minutes_remaining: minutesRemaining };
277
+ }
205
278
  /**
206
279
  * Get full execution context for a worker agent
207
280
  */
@@ -251,6 +324,8 @@ export async function getExecutionContext(userId, projectId, taskId) {
251
324
  status: s.status,
252
325
  })),
253
326
  });
327
+ // Check for lock expiry warning
328
+ const lockWarning = getLockExpiryWarning(task.lock_expires_at);
254
329
  return {
255
330
  task,
256
331
  worker,
@@ -258,6 +333,7 @@ export async function getExecutionContext(userId, projectId, taskId) {
258
333
  context: taskData.context,
259
334
  parentTask: taskData.parent,
260
335
  siblingTasks,
336
+ lockWarning,
261
337
  };
262
338
  }
263
339
  /**
@@ -303,6 +379,81 @@ export async function resolveTaskDependencies(userId, projectId, completedTaskId
303
379
  }
304
380
  return newlyUnblocked;
305
381
  }
382
+ /**
383
+ * Aggregate work_log context from all tasks in an epic
384
+ * Used by orchestrator to synthesize final results
385
+ */
386
+ export async function getEpicAggregate(userId, projectId, epicId, includePending = false) {
387
+ // Get the epic
388
+ const epicResult = await getTask(userId, projectId, epicId);
389
+ if (!epicResult.success) {
390
+ throw new Error(`Epic not found: ${epicId}`);
391
+ }
392
+ const epic = epicResult.data;
393
+ // Verify it's an epic
394
+ if (epic.type !== 'epic') {
395
+ throw new Error(`Task ${epicId} is not an epic (type: ${epic.type})`);
396
+ }
397
+ // Get all child tasks
398
+ const tasksResult = await listTasks(userId, projectId, {
399
+ parent_id: epicId,
400
+ limit: 100,
401
+ });
402
+ if (!tasksResult.success) {
403
+ return {
404
+ epic: {
405
+ id: epic.id,
406
+ title: epic.title,
407
+ description: epic.description,
408
+ status: epic.status,
409
+ },
410
+ tasks: [],
411
+ summary_stats: {
412
+ total: 0,
413
+ completed: 0,
414
+ in_progress: 0,
415
+ pending: 0,
416
+ },
417
+ };
418
+ }
419
+ const allTasks = tasksResult.data;
420
+ // Get work_log context for each task
421
+ const tasksWithLogs = await Promise.all(allTasks.map(async (task) => {
422
+ // Get full task data to access context
423
+ const fullTaskResult = await getTask(userId, projectId, task.id, true);
424
+ const fullTaskData = fullTaskResult.data;
425
+ // Find work_log context
426
+ const workLogContext = fullTaskData.context?.find(ctx => ctx.context_type === 'work_log');
427
+ return {
428
+ id: task.id,
429
+ title: task.title,
430
+ status: task.status,
431
+ work_log: workLogContext?.content || null,
432
+ completed_at: task.completed_at,
433
+ };
434
+ }));
435
+ // Filter based on includePending
436
+ const filteredTasks = includePending
437
+ ? tasksWithLogs
438
+ : tasksWithLogs.filter(t => t.status === 'done' || t.status === 'in_progress');
439
+ // Calculate stats
440
+ const stats = {
441
+ total: allTasks.length,
442
+ completed: allTasks.filter(t => t.status === 'done').length,
443
+ in_progress: allTasks.filter(t => t.status === 'in_progress').length,
444
+ pending: allTasks.filter(t => t.status === 'ready' || t.status === 'backlog').length,
445
+ };
446
+ return {
447
+ epic: {
448
+ id: epic.id,
449
+ title: epic.title,
450
+ description: epic.description,
451
+ status: epic.status,
452
+ },
453
+ tasks: filteredTasks,
454
+ summary_stats: stats,
455
+ };
456
+ }
306
457
  /**
307
458
  * Get epic status with progress tracking
308
459
  * Auto-completes epic if all child tasks are done
@@ -368,3 +519,97 @@ export async function getEpicStatus(userId, projectId, epicId) {
368
519
  autoCompleted,
369
520
  };
370
521
  }
522
+ export async function handoffTask(userId, projectId, taskId, agentId, newWorkerType, reason) {
523
+ return apiRequest(`/api/v1/tasks/${userId}/${projectId}/${taskId}/handoff`, 'POST', {
524
+ agent_id: agentId,
525
+ new_worker_type: newWorkerType,
526
+ reason,
527
+ });
528
+ }
529
+ /**
530
+ * Revise an existing epic by adding/removing/updating tasks
531
+ * Enables iterative planning without recreating the entire epic
532
+ */
533
+ export async function reviseEpic(userId, projectId, epicId, changes) {
534
+ // Verify epic exists and is not done
535
+ const epicResult = await getTask(userId, projectId, epicId);
536
+ if (!epicResult.success) {
537
+ throw new Error(`Epic not found: ${epicId}`);
538
+ }
539
+ const epic = epicResult.data;
540
+ if (epic.type !== 'epic') {
541
+ throw new Error(`Task ${epicId} is not an epic (type: ${epic.type})`);
542
+ }
543
+ if (epic.status === 'done') {
544
+ throw new Error(`Cannot revise completed epic: ${epicId}`);
545
+ }
546
+ const added = [];
547
+ const removed = [];
548
+ const updated = [];
549
+ // Add new tasks
550
+ if (changes.add_tasks && changes.add_tasks.length > 0) {
551
+ for (const taskDef of changes.add_tasks) {
552
+ const taskResult = await createTask(userId, projectId, {
553
+ type: 'task',
554
+ title: taskDef.title,
555
+ description: taskDef.description,
556
+ parent_id: epicId,
557
+ priority: epic.priority,
558
+ estimated_effort: taskDef.effort,
559
+ blocked_by: taskDef.blocked_by,
560
+ domain: taskDef.domain,
561
+ });
562
+ added.push(taskResult.data);
563
+ }
564
+ }
565
+ // Remove tasks (set to cancelled status)
566
+ if (changes.remove_task_ids && changes.remove_task_ids.length > 0) {
567
+ for (const taskId of changes.remove_task_ids) {
568
+ try {
569
+ await updateTaskStatus(userId, projectId, taskId, 'cancelled', 'system:epic-revision');
570
+ removed.push(taskId);
571
+ }
572
+ catch (error) {
573
+ // Log but continue - task may not exist
574
+ console.warn(`Failed to cancel task ${taskId}:`, error);
575
+ }
576
+ }
577
+ }
578
+ // Update existing tasks
579
+ // Note: The API doesn't support PATCH for tasks, so we can only update status
580
+ // For title/description/effort updates, we'll need to add context explaining the change
581
+ if (changes.update_tasks && changes.update_tasks.length > 0) {
582
+ for (const update of changes.update_tasks) {
583
+ try {
584
+ const taskResult = await getTask(userId, projectId, update.task_id);
585
+ if (!taskResult.success)
586
+ continue;
587
+ const task = taskResult.data;
588
+ // Add context about the updates since we can't modify fields directly
589
+ const updates = [];
590
+ if (update.title && update.title !== task.title) {
591
+ updates.push(`Title updated: "${task.title}" → "${update.title}"`);
592
+ }
593
+ if (update.description && update.description !== task.description) {
594
+ updates.push(`Description updated: "${update.description}"`);
595
+ }
596
+ if (update.effort && update.effort !== task.estimated_effort) {
597
+ updates.push(`Effort updated: ${task.estimated_effort || 'unset'} → ${update.effort}`);
598
+ }
599
+ if (updates.length > 0) {
600
+ await addTaskContext(userId, projectId, update.task_id, 'decision', updates.join('\n'), 'task_plan_revise', 'epic-revision');
601
+ updated.push(task);
602
+ }
603
+ }
604
+ catch (error) {
605
+ console.warn(`Failed to update task ${update.task_id}:`, error);
606
+ }
607
+ }
608
+ }
609
+ return {
610
+ epic,
611
+ added,
612
+ removed,
613
+ updated,
614
+ };
615
+ }
@@ -29,7 +29,7 @@ export class UsageAnalytics {
29
29
  const userId = DEFAULT_USER_ID;
30
30
  const projectId = event.projectId || resolveProjectId();
31
31
  // Store as fact in memory system
32
- await storeFact(projectId, 'CodeDNA', 'GENERATION_COMPLETED', event.generator || event.operation, JSON.stringify(usageEvent), userId);
32
+ await storeFact(projectId, 'CodeDNA', 'GENERATION_COMPLETED', event.generator || event.operation, JSON.stringify(usageEvent), { userId });
33
33
  console.log('[CodeDNA Analytics]', {
34
34
  operation: event.operation,
35
35
  generator: event.generator,
@@ -0,0 +1,4 @@
1
+ export { handleStopHook, sessionStore, usageEstimator } from './stop.js';
2
+ export type { StopHookInput, SessionData, ContextHealth } from './stop.js';
3
+ export { handlePostToolUseHook, sessionStore as postToolUseSessionStore, evictionEngine, } from './post-tool-use.js';
4
+ export type { PostToolUseInput, SessionData as PostToolUseSessionData, EvictionTrigger, } from './post-tool-use.js';
@@ -0,0 +1,6 @@
1
+ // =============================================================================
2
+ // Claude Code Hooks - Index
3
+ // =============================================================================
4
+ // Exports hook handlers for Claude Code integration
5
+ export { handleStopHook, sessionStore, usageEstimator } from './stop.js';
6
+ export { handlePostToolUseHook, sessionStore as postToolUseSessionStore, evictionEngine, } from './post-tool-use.js';
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // Post-Tool-Use Hook CLI Entry Point
4
+ // =============================================================================
5
+ // Called by Claude Code when PostToolUse event occurs
6
+ // Reads hook input from stdin, processes it, and outputs to stdout/stderr
7
+ import { handlePostToolUseHook } from './post-tool-use.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 !== 'PostToolUse') {
21
+ console.error(`Error: Expected PostToolUse event, got ${hookInput.hook_event_name}`);
22
+ process.exit(1);
23
+ }
24
+ // Process the hook
25
+ await handlePostToolUseHook(hookInput);
26
+ // Success - exit code 0
27
+ process.exit(0);
28
+ }
29
+ catch (error) {
30
+ console.error('Error processing PostToolUse hook:', error);
31
+ process.exit(1);
32
+ }
33
+ }
34
+ main();
@@ -0,0 +1,67 @@
1
+ interface PostToolUseInput {
2
+ session_id?: string;
3
+ hook_event_name: 'PostToolUse';
4
+ tool_name: string;
5
+ tool_input?: Record<string, unknown>;
6
+ tool_output?: string;
7
+ post_tool_use_active?: boolean;
8
+ }
9
+ interface EvictionTrigger {
10
+ triggered: boolean;
11
+ level: 'none' | 'standard' | 'emergency';
12
+ estimated_fill: number;
13
+ message: string;
14
+ }
15
+ interface SessionData {
16
+ session_id: string;
17
+ tool_executions: number;
18
+ estimated_tokens: number;
19
+ context_limit: number;
20
+ estimated_fill: number;
21
+ last_updated: Date;
22
+ tool_output_tokens: number;
23
+ }
24
+ declare class SessionStore {
25
+ /**
26
+ * Get or create session data
27
+ */
28
+ getSession(sessionId: string): SessionData;
29
+ /**
30
+ * Update session with tool output tokens
31
+ */
32
+ updateSession(sessionId: string, outputTokens: number): SessionData;
33
+ /**
34
+ * Clear session (for cleanup)
35
+ */
36
+ clearSession(sessionId: string): void;
37
+ /**
38
+ * Get all sessions (for debugging)
39
+ */
40
+ getAllSessions(): SessionData[];
41
+ }
42
+ declare const sessionStore: SessionStore;
43
+ declare class EvictionTriggerEngine {
44
+ /**
45
+ * Check if eviction should be triggered based on fill percentage
46
+ */
47
+ checkEvictionTrigger(session: SessionData): EvictionTrigger;
48
+ /**
49
+ * Run standard eviction cycle
50
+ * NOTE: This is a placeholder - actual implementation would call into
51
+ * the context rotation/eviction system when it's built
52
+ */
53
+ runStandardEviction(session: SessionData): Promise<void>;
54
+ /**
55
+ * Run emergency eviction cycle
56
+ * More aggressive eviction for critical situations
57
+ */
58
+ runEmergencyEviction(session: SessionData): Promise<void>;
59
+ }
60
+ declare const evictionEngine: EvictionTriggerEngine;
61
+ /**
62
+ * Handle PostToolUse hook event
63
+ * Updates session state with tool output and triggers eviction if needed
64
+ */
65
+ export declare function handlePostToolUseHook(input: PostToolUseInput): Promise<void>;
66
+ export { sessionStore, evictionEngine };
67
+ export type { PostToolUseInput, SessionData, EvictionTrigger };