@compilr-dev/agents 0.3.11 → 0.3.13

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 (42) hide show
  1. package/dist/agent.d.ts +42 -1
  2. package/dist/agent.js +116 -17
  3. package/dist/anchors/manager.js +3 -2
  4. package/dist/context/delegated-result-store.d.ts +67 -0
  5. package/dist/context/delegated-result-store.js +99 -0
  6. package/dist/context/delegation-types.d.ts +82 -0
  7. package/dist/context/delegation-types.js +18 -0
  8. package/dist/context/file-tracker.d.ts +59 -1
  9. package/dist/context/file-tracker.js +96 -1
  10. package/dist/context/file-tracking-hook.js +9 -4
  11. package/dist/context/index.d.ts +7 -1
  12. package/dist/context/index.js +4 -0
  13. package/dist/context/manager.js +12 -32
  14. package/dist/context/tool-result-delegator.d.ts +63 -0
  15. package/dist/context/tool-result-delegator.js +314 -0
  16. package/dist/index.d.ts +5 -5
  17. package/dist/index.js +9 -3
  18. package/dist/memory/loader.js +2 -1
  19. package/dist/memory/types.d.ts +1 -1
  20. package/dist/providers/claude.d.ts +1 -5
  21. package/dist/providers/claude.js +6 -29
  22. package/dist/providers/gemini-native.d.ts +1 -1
  23. package/dist/providers/gemini-native.js +10 -24
  24. package/dist/providers/mock.d.ts +1 -1
  25. package/dist/providers/mock.js +3 -24
  26. package/dist/providers/openai-compatible.d.ts +1 -5
  27. package/dist/providers/openai-compatible.js +14 -28
  28. package/dist/providers/types.d.ts +5 -0
  29. package/dist/rate-limit/provider-wrapper.d.ts +1 -1
  30. package/dist/rate-limit/provider-wrapper.js +3 -27
  31. package/dist/tools/builtin/index.d.ts +2 -0
  32. package/dist/tools/builtin/index.js +2 -0
  33. package/dist/tools/builtin/recall-result.d.ts +29 -0
  34. package/dist/tools/builtin/recall-result.js +48 -0
  35. package/dist/tools/builtin/task.js +1 -0
  36. package/dist/tools/index.d.ts +2 -2
  37. package/dist/tools/index.js +2 -0
  38. package/dist/utils/index.d.ts +1 -0
  39. package/dist/utils/index.js +2 -0
  40. package/dist/utils/tokenizer.d.ts +19 -0
  41. package/dist/utils/tokenizer.js +93 -0
  42. package/package.json +3 -2
package/dist/agent.d.ts CHANGED
@@ -11,6 +11,7 @@ import type { ToolPermission, PermissionLevel, PermissionManagerOptions } from '
11
11
  import type { ProjectMemoryOptions, ProjectMemory } from './memory/types.js';
12
12
  import type { UsageTrackerOptions, UsageStats, BudgetStatus, TokenUsage } from './costs/types.js';
13
13
  import type { HooksConfig } from './hooks/types.js';
14
+ import type { DelegationConfig } from './context/delegation-types.js';
14
15
  import { PermissionManager } from './permissions/manager.js';
15
16
  import { ContextManager } from './context/manager.js';
16
17
  import { FileAccessTracker } from './context/file-tracker.js';
@@ -528,6 +529,28 @@ export interface AgentConfig {
528
529
  * ```
529
530
  */
530
531
  hooks?: HooksConfig;
532
+ /**
533
+ * Tool result delegation config. When set and enabled, large tool results
534
+ * are automatically summarized to conserve context tokens. Full results are
535
+ * stored in-memory for optional recall via `recall_full_result`.
536
+ *
537
+ * @example
538
+ * ```typescript
539
+ * const agent = new Agent({
540
+ * provider,
541
+ * delegation: {
542
+ * enabled: true,
543
+ * delegationThreshold: 8000, // tokens above which to delegate
544
+ * strategy: 'auto', // try LLM, fall back to extractive
545
+ * toolOverrides: {
546
+ * bash: { threshold: 12000 },
547
+ * grep: { threshold: 4000 },
548
+ * },
549
+ * },
550
+ * });
551
+ * ```
552
+ */
553
+ delegation?: DelegationConfig;
531
554
  /**
532
555
  * Enable file access tracking for context restoration hints.
533
556
  *
@@ -553,6 +576,19 @@ export interface AgentConfig {
553
576
  * ```
554
577
  */
555
578
  enableFileTracking?: boolean;
579
+ /**
580
+ * Options for file context restoration after compaction.
581
+ *
582
+ * Controls how file contents are re-injected into the context after
583
+ * compaction. Small files get their content inlined; large files get
584
+ * reference-only hints.
585
+ *
586
+ * Only applies when `enableFileTracking` is true.
587
+ */
588
+ fileRestoration?: {
589
+ /** Max total tokens for inline file content after compaction (default: 4000) */
590
+ maxInlineTokens?: number;
591
+ };
556
592
  }
557
593
  /**
558
594
  * Options for a single run
@@ -838,6 +874,10 @@ export declare class Agent {
838
874
  * File access tracker for context restoration hints
839
875
  */
840
876
  private readonly fileTracker?;
877
+ /**
878
+ * File restoration options for post-compaction content injection
879
+ */
880
+ private readonly fileRestorationConfig?;
841
881
  constructor(config: AgentConfig);
842
882
  /**
843
883
  * Create an agent with project memory loaded from files.
@@ -1255,7 +1295,8 @@ export declare class Agent {
1255
1295
  formatRestorationHints(): string;
1256
1296
  /**
1257
1297
  * Inject context restoration hints into messages after compaction/summarization.
1258
- * Modifies messages array in place if hints are available.
1298
+ * Uses content-aware format: small files get content inlined, large files get
1299
+ * reference-only notes. Each file is injected as a separate user message.
1259
1300
  *
1260
1301
  * @internal
1261
1302
  */
package/dist/agent.js CHANGED
@@ -9,6 +9,8 @@ import { DefaultToolRegistry } from './tools/registry.js';
9
9
  import { ContextManager } from './context/manager.js';
10
10
  import { FileAccessTracker } from './context/file-tracker.js';
11
11
  import { createFileTrackingHook } from './context/file-tracking-hook.js';
12
+ import { ToolResultDelegator, DELEGATION_SYSTEM_PROMPT } from './context/tool-result-delegator.js';
13
+ import { createRecallResultTool } from './tools/builtin/recall-result.js';
12
14
  import { AnchorManager } from './anchors/manager.js';
13
15
  import { GuardrailManager } from './guardrails/manager.js';
14
16
  import { MaxIterationsError, ToolLoopError, ProviderError } from './errors.js';
@@ -91,6 +93,10 @@ export class Agent {
91
93
  * File access tracker for context restoration hints
92
94
  */
93
95
  fileTracker;
96
+ /**
97
+ * File restoration options for post-compaction content injection
98
+ */
99
+ fileRestorationConfig;
94
100
  constructor(config) {
95
101
  this.provider = config.provider;
96
102
  this.systemPrompt = config.systemPrompt ?? '';
@@ -177,19 +183,51 @@ export class Agent {
177
183
  }
178
184
  });
179
185
  }
186
+ // Build hooks config (may be extended by file tracking and delegation)
187
+ const hooksConfig = config.hooks ? { ...config.hooks } : {};
188
+ let needsHooksManager = config.hooks !== undefined;
189
+ // Tool result delegation — auto-summarize large results
190
+ if (config.delegation?.enabled) {
191
+ const delegator = new ToolResultDelegator({
192
+ provider: config.provider,
193
+ config: config.delegation,
194
+ onEvent: (event) => {
195
+ this.onEvent?.({
196
+ type: 'custom',
197
+ name: event.type,
198
+ data: event,
199
+ });
200
+ },
201
+ });
202
+ hooksConfig.afterTool = hooksConfig.afterTool ?? [];
203
+ hooksConfig.afterTool.push(delegator.createHook());
204
+ needsHooksManager = true;
205
+ // Register recall_full_result tool
206
+ this.registerTool(createRecallResultTool({
207
+ store: delegator.getStore(),
208
+ onEvent: (event) => {
209
+ this.onEvent?.({
210
+ type: 'custom',
211
+ name: event.type,
212
+ data: event,
213
+ });
214
+ },
215
+ }));
216
+ // Append delegation instructions to system prompt
217
+ this.systemPrompt += DELEGATION_SYSTEM_PROMPT;
218
+ }
180
219
  // File tracking for context restoration hints
181
220
  if (config.enableFileTracking && config.contextManager) {
182
221
  this.fileTracker = new FileAccessTracker();
222
+ this.fileRestorationConfig = config.fileRestoration;
183
223
  const trackingHook = createFileTrackingHook(this.fileTracker);
184
- // Merge with existing hooks or create new hooks config
185
- const hooksConfig = config.hooks ?? {};
186
224
  hooksConfig.afterTool = hooksConfig.afterTool ?? [];
187
225
  hooksConfig.afterTool.push(trackingHook);
188
- this.hooksManager = new HooksManager({ hooks: hooksConfig });
226
+ needsHooksManager = true;
189
227
  }
190
- else if (config.hooks !== undefined) {
191
- // Hooks manager without file tracking
192
- this.hooksManager = new HooksManager({ hooks: config.hooks });
228
+ // Create hooks manager if any hooks were configured
229
+ if (needsHooksManager) {
230
+ this.hooksManager = new HooksManager({ hooks: hooksConfig });
193
231
  }
194
232
  // Retry configuration with defaults
195
233
  this.retryConfig = {
@@ -792,7 +830,8 @@ export class Agent {
792
830
  }
793
831
  /**
794
832
  * Inject context restoration hints into messages after compaction/summarization.
795
- * Modifies messages array in place if hints are available.
833
+ * Uses content-aware format: small files get content inlined, large files get
834
+ * reference-only notes. Each file is injected as a separate user message.
796
835
  *
797
836
  * @internal
798
837
  */
@@ -800,17 +839,22 @@ export class Agent {
800
839
  if (!this.fileTracker || this.fileTracker.size === 0) {
801
840
  return;
802
841
  }
803
- const hints = this.formatRestorationHints();
804
- if (!hints) {
842
+ const hints = this.fileTracker.formatRestorationHintsWithContent({
843
+ maxInlineTokens: this.fileRestorationConfig?.maxInlineTokens ?? 4000,
844
+ });
845
+ if (hints.length === 0) {
805
846
  return;
806
847
  }
807
- // Inject as a user message after the last system message
848
+ // Insert after system prompt, each as a separate user message
808
849
  const systemIndex = messages.findIndex((m) => m.role === 'system');
809
850
  const insertIndex = systemIndex >= 0 ? systemIndex + 1 : 0;
810
- messages.splice(insertIndex, 0, {
811
- role: 'user',
812
- content: hints,
813
- });
851
+ // Insert in reverse order so they end up in the correct order
852
+ for (let i = hints.length - 1; i >= 0; i--) {
853
+ messages.splice(insertIndex, 0, {
854
+ role: 'user',
855
+ content: hints[i].text,
856
+ });
857
+ }
814
858
  }
815
859
  // ==========================================================================
816
860
  // Context Compaction
@@ -1499,6 +1543,7 @@ export class Agent {
1499
1543
  this.contextManager.updateCategoryUsage('system', systemTokens);
1500
1544
  }
1501
1545
  // Check if we need to manage context before starting
1546
+ // Order: emergency (95%) → compaction (50%/20 turns) → warning (90%)
1502
1547
  if (this.contextManager.needsEmergencySummarization()) {
1503
1548
  emit({
1504
1549
  type: 'context_warning',
@@ -1517,6 +1562,27 @@ export class Agent {
1517
1562
  rounds: result.rounds,
1518
1563
  });
1519
1564
  }
1565
+ else if (this.contextManager.needsCompaction()) {
1566
+ // Proactive compaction (50% utilization or 20+ turns since last compaction)
1567
+ // compact() reads this.conversationHistory which has all previous runs' messages
1568
+ const compactResult = await this.compact({ useSmartCompaction: true });
1569
+ if (compactResult.success) {
1570
+ // Rebuild messages: compacted history + current userMsg (still in newMessages)
1571
+ messages = this.systemPrompt
1572
+ ? [
1573
+ { role: 'system', content: this.systemPrompt },
1574
+ ...this.conversationHistory,
1575
+ ...newMessages,
1576
+ ]
1577
+ : [...this.conversationHistory, ...newMessages];
1578
+ // Don't touch newMessages — it still has [userMsg] which finally needs to append
1579
+ emit({
1580
+ type: 'context_compacted',
1581
+ tokensBefore: compactResult.originalTokens,
1582
+ tokensAfter: compactResult.summaryTokens,
1583
+ });
1584
+ }
1585
+ }
1520
1586
  else if (this.contextManager.needsSummarization()) {
1521
1587
  emit({
1522
1588
  type: 'context_warning',
@@ -2044,6 +2110,36 @@ export class Agent {
2044
2110
  completedWithText: false,
2045
2111
  });
2046
2112
  }
2113
+ // Auto-compaction: check if context needs compaction after this iteration
2114
+ if (this.contextManager && this.autoContextManagement) {
2115
+ await this.contextManager.updateTokenCount(messages);
2116
+ if (this.contextManager.needsCompaction()) {
2117
+ // Flush current run's messages to conversationHistory so compact() can see them.
2118
+ // compact() reads this.conversationHistory, which normally only updates in finally.
2119
+ this.conversationHistory.push(...newMessages);
2120
+ newMessages.length = 0;
2121
+ const compactResult = await this.compact({ useSmartCompaction: true });
2122
+ if (compactResult.success) {
2123
+ // compact() already replaced this.conversationHistory with compacted version.
2124
+ // Rebuild local messages array from it.
2125
+ messages = this.systemPrompt
2126
+ ? [
2127
+ { role: 'system', content: this.systemPrompt },
2128
+ ...this.conversationHistory,
2129
+ ]
2130
+ : [...this.conversationHistory];
2131
+ // newMessages stays empty — subsequent iterations will push new messages into it,
2132
+ // and finally block will correctly append only post-compaction messages.
2133
+ emit({
2134
+ type: 'context_compacted',
2135
+ tokensBefore: compactResult.originalTokens,
2136
+ tokensAfter: compactResult.summaryTokens,
2137
+ });
2138
+ }
2139
+ // If compact() failed, messages are already flushed to conversationHistory.
2140
+ // newMessages is empty so finally won't double-push.
2141
+ }
2142
+ }
2047
2143
  emit({ type: 'iteration_end', iteration: iterations });
2048
2144
  // Check if we're about to hit the iteration limit
2049
2145
  // If callback is defined, ask if we should continue
@@ -2098,7 +2194,8 @@ export class Agent {
2098
2194
  throw new MaxIterationsError(maxIterations);
2099
2195
  }
2100
2196
  }
2101
- if (!aborted && !(iterations >= maxIterations && this.iterationLimitBehavior === 'summarize')) {
2197
+ if (!aborted &&
2198
+ !(iterations >= maxIterations && this.iterationLimitBehavior === 'summarize')) {
2102
2199
  emit({ type: 'done', response: finalResponse });
2103
2200
  }
2104
2201
  }
@@ -2268,13 +2365,15 @@ export class Agent {
2268
2365
  * @param signal - Optional abort signal
2269
2366
  */
2270
2367
  chatWithRetry(messages, options, emit, signal) {
2368
+ // Merge signal into chat options so providers can abort the request
2369
+ const chatOptions = signal ? { ...options, signal } : options;
2271
2370
  // If retry is disabled, return the raw provider stream
2272
2371
  if (!this.retryConfig.enabled) {
2273
- return this.provider.chat(messages, options);
2372
+ return this.provider.chat(messages, chatOptions);
2274
2373
  }
2275
2374
  const providerName = this.provider.name;
2276
2375
  const { maxAttempts, baseDelayMs, maxDelayMs } = this.retryConfig;
2277
- return withRetryGenerator(() => this.provider.chat(messages, options), {
2376
+ return withRetryGenerator(() => this.provider.chat(messages, chatOptions), {
2278
2377
  maxAttempts,
2279
2378
  baseDelayMs,
2280
2379
  maxDelayMs,
@@ -4,6 +4,7 @@
4
4
  import * as fs from 'node:fs';
5
5
  import * as path from 'node:path';
6
6
  import { generateId } from '../utils/index.js';
7
+ import { countTokens } from '../utils/tokenizer.js';
7
8
  import { getDefaultAnchors, isBuiltinAnchor } from './builtin.js';
8
9
  /**
9
10
  * Default options for AnchorManager
@@ -14,10 +15,10 @@ const DEFAULT_OPTIONS = {
14
15
  includeDefaults: true,
15
16
  };
16
17
  /**
17
- * Default token estimator (rough estimate: ~4 chars per token)
18
+ * Default token estimator using tiktoken (cl100k_base encoding)
18
19
  */
19
20
  function defaultEstimateTokens(content) {
20
- return Math.ceil(content.length / 4);
21
+ return countTokens(content);
21
22
  }
22
23
  /**
23
24
  * AnchorManager - Manages critical information that survives context compaction
@@ -0,0 +1,67 @@
1
+ /**
2
+ * In-memory store for delegated tool results.
3
+ *
4
+ * Stores full tool results that were replaced by summaries, allowing
5
+ * the agent to recall them via `recall_full_result`. Implements TTL
6
+ * expiration and LRU eviction.
7
+ */
8
+ import type { StoredResult } from './delegation-types.js';
9
+ /**
10
+ * Statistics about the delegation store.
11
+ */
12
+ export interface DelegatedResultStoreStats {
13
+ /** Number of currently stored results */
14
+ size: number;
15
+ /** Maximum capacity (LRU limit) */
16
+ maxSize: number;
17
+ /** Total results stored since creation */
18
+ totalStored: number;
19
+ /** Total results evicted (TTL or LRU) */
20
+ totalEvicted: number;
21
+ /** Total results successfully recalled */
22
+ totalRecalled: number;
23
+ }
24
+ /**
25
+ * In-memory store for delegated results with TTL expiration and LRU eviction.
26
+ */
27
+ export declare class DelegatedResultStore {
28
+ private readonly results;
29
+ private readonly maxSize;
30
+ private readonly defaultTTL;
31
+ private counter;
32
+ private totalStored;
33
+ private totalEvicted;
34
+ private totalRecalled;
35
+ constructor(options?: {
36
+ maxSize?: number;
37
+ defaultTTL?: number;
38
+ });
39
+ /**
40
+ * Generate a unique delegation ID.
41
+ */
42
+ generateId(): string;
43
+ /**
44
+ * Store a delegated result. Evicts oldest entries if at capacity.
45
+ */
46
+ add(result: StoredResult): void;
47
+ /**
48
+ * Get a stored result by ID. Returns undefined if not found or expired.
49
+ */
50
+ get(id: string): StoredResult | undefined;
51
+ /**
52
+ * Remove all expired entries.
53
+ */
54
+ cleanup(): void;
55
+ /**
56
+ * Clear all stored results.
57
+ */
58
+ clear(): void;
59
+ /**
60
+ * Get the default TTL for this store.
61
+ */
62
+ getDefaultTTL(): number;
63
+ /**
64
+ * Get store statistics.
65
+ */
66
+ getStats(): DelegatedResultStoreStats;
67
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * In-memory store for delegated tool results.
3
+ *
4
+ * Stores full tool results that were replaced by summaries, allowing
5
+ * the agent to recall them via `recall_full_result`. Implements TTL
6
+ * expiration and LRU eviction.
7
+ */
8
+ /**
9
+ * In-memory store for delegated results with TTL expiration and LRU eviction.
10
+ */
11
+ export class DelegatedResultStore {
12
+ results = new Map();
13
+ maxSize;
14
+ defaultTTL;
15
+ counter = 0;
16
+ totalStored = 0;
17
+ totalEvicted = 0;
18
+ totalRecalled = 0;
19
+ constructor(options) {
20
+ this.maxSize = options?.maxSize ?? 50;
21
+ this.defaultTTL = options?.defaultTTL ?? 600_000;
22
+ }
23
+ /**
24
+ * Generate a unique delegation ID.
25
+ */
26
+ generateId() {
27
+ return `dr_${String(Date.now())}_${String(this.counter++)}`;
28
+ }
29
+ /**
30
+ * Store a delegated result. Evicts oldest entries if at capacity.
31
+ */
32
+ add(result) {
33
+ // Evict expired entries first
34
+ this.cleanup();
35
+ // LRU eviction: remove oldest entries if at capacity
36
+ while (this.results.size >= this.maxSize) {
37
+ const oldestKey = this.results.keys().next().value;
38
+ this.results.delete(oldestKey);
39
+ this.totalEvicted++;
40
+ }
41
+ this.results.set(result.id, result);
42
+ this.totalStored++;
43
+ }
44
+ /**
45
+ * Get a stored result by ID. Returns undefined if not found or expired.
46
+ */
47
+ get(id) {
48
+ const result = this.results.get(id);
49
+ if (!result)
50
+ return undefined;
51
+ // Check TTL
52
+ if (Date.now() > result.expiresAt) {
53
+ this.results.delete(id);
54
+ this.totalEvicted++;
55
+ return undefined;
56
+ }
57
+ // Move to end for LRU (delete + re-insert)
58
+ this.results.delete(id);
59
+ this.results.set(id, result);
60
+ this.totalRecalled++;
61
+ return result;
62
+ }
63
+ /**
64
+ * Remove all expired entries.
65
+ */
66
+ cleanup() {
67
+ const now = Date.now();
68
+ for (const [id, result] of this.results) {
69
+ if (now > result.expiresAt) {
70
+ this.results.delete(id);
71
+ this.totalEvicted++;
72
+ }
73
+ }
74
+ }
75
+ /**
76
+ * Clear all stored results.
77
+ */
78
+ clear() {
79
+ this.results.clear();
80
+ }
81
+ /**
82
+ * Get the default TTL for this store.
83
+ */
84
+ getDefaultTTL() {
85
+ return this.defaultTTL;
86
+ }
87
+ /**
88
+ * Get store statistics.
89
+ */
90
+ getStats() {
91
+ return {
92
+ size: this.results.size,
93
+ maxSize: this.maxSize,
94
+ totalStored: this.totalStored,
95
+ totalEvicted: this.totalEvicted,
96
+ totalRecalled: this.totalRecalled,
97
+ };
98
+ }
99
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Types for tool result auto-delegation.
3
+ *
4
+ * When large tool results exceed a token threshold, they are automatically
5
+ * summarized and stored for optional recall. This conserves context tokens
6
+ * while preserving access to the full data.
7
+ */
8
+ /**
9
+ * Configuration for tool result delegation.
10
+ * Controls when and how large tool results are summarized.
11
+ */
12
+ export interface DelegationConfig {
13
+ /** Whether delegation is enabled. Default: false (opt-in) */
14
+ enabled: boolean;
15
+ /** Token count above which results are delegated. Default: 8000 */
16
+ delegationThreshold: number;
17
+ /** Maximum tokens for the summary. Default: 800 */
18
+ summaryMaxTokens: number;
19
+ /** Milliseconds before stored results expire. Default: 600_000 (10 min) */
20
+ resultTTL: number;
21
+ /** Maximum number of stored results (LRU eviction). Default: 50 */
22
+ maxStoredResults: number;
23
+ /** Summarization strategy. Default: 'auto' */
24
+ strategy: 'llm' | 'extractive' | 'auto';
25
+ /** Per-tool threshold/strategy overrides */
26
+ toolOverrides?: Record<string, {
27
+ enabled?: boolean;
28
+ threshold?: number;
29
+ strategy?: 'llm' | 'extractive' | 'auto';
30
+ }>;
31
+ }
32
+ /**
33
+ * A stored full result that was replaced by a summary.
34
+ */
35
+ export interface StoredResult {
36
+ /** Unique delegation ID, e.g., 'dr_1707900000_0' */
37
+ id: string;
38
+ /** Name of the tool that produced the result */
39
+ toolName: string;
40
+ /** Input parameters passed to the tool */
41
+ toolInput: Record<string, unknown>;
42
+ /** The full serialized result content */
43
+ fullContent: string;
44
+ /** Token count of the full content */
45
+ fullTokens: number;
46
+ /** The generated summary */
47
+ summary: string;
48
+ /** Token count of the summary */
49
+ summaryTokens: number;
50
+ /** Timestamp when stored */
51
+ storedAt: number;
52
+ /** Timestamp when this result expires */
53
+ expiresAt: number;
54
+ }
55
+ /**
56
+ * Events emitted during the delegation lifecycle.
57
+ */
58
+ export type DelegationEvent = {
59
+ type: 'delegation:started';
60
+ toolName: string;
61
+ originalTokens: number;
62
+ delegationId: string;
63
+ } | {
64
+ type: 'delegation:completed';
65
+ toolName: string;
66
+ originalTokens: number;
67
+ summaryTokens: number;
68
+ delegationId: string;
69
+ strategy: 'llm' | 'extractive';
70
+ } | {
71
+ type: 'delegation:failed';
72
+ toolName: string;
73
+ error: string;
74
+ } | {
75
+ type: 'delegation:recall';
76
+ delegationId: string;
77
+ found: boolean;
78
+ };
79
+ /**
80
+ * Default delegation configuration values.
81
+ */
82
+ export declare const DEFAULT_DELEGATION_CONFIG: DelegationConfig;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Types for tool result auto-delegation.
3
+ *
4
+ * When large tool results exceed a token threshold, they are automatically
5
+ * summarized and stored for optional recall. This conserves context tokens
6
+ * while preserving access to the full data.
7
+ */
8
+ /**
9
+ * Default delegation configuration values.
10
+ */
11
+ export const DEFAULT_DELEGATION_CONFIG = {
12
+ enabled: false,
13
+ delegationThreshold: 8000,
14
+ summaryMaxTokens: 800,
15
+ resultTTL: 600_000,
16
+ maxStoredResults: 50,
17
+ strategy: 'auto',
18
+ };
@@ -32,6 +32,10 @@ export interface FileAccess {
32
32
  lineCount?: number;
33
33
  /** Optional summary of what was found/changed */
34
34
  summary?: string;
35
+ /** Stored file content (only for small reads, used for post-compaction restoration) */
36
+ content?: string;
37
+ /** Token count of stored content */
38
+ tokenCount?: number;
35
39
  }
36
40
  /**
37
41
  * Options for FileAccessTracker constructor
@@ -47,6 +51,18 @@ export interface FileAccessTrackerOptions {
47
51
  * @default true
48
52
  */
49
53
  deduplicateReferences?: boolean;
54
+ /**
55
+ * Maximum line count for a file to have its content stored inline.
56
+ * Files with more lines than this threshold will only store path/lineCount.
57
+ * @default 200
58
+ */
59
+ inlineThreshold?: number;
60
+ /**
61
+ * Maximum number of files that can have stored content at once.
62
+ * When exceeded, oldest content entries are evicted (path still tracked).
63
+ * @default 10
64
+ */
65
+ maxContentEntries?: number;
50
66
  }
51
67
  /**
52
68
  * Options for formatting restoration hints
@@ -65,6 +81,19 @@ export interface FormatHintsOptions {
65
81
  /** Verbosity level (adjusts format automatically) */
66
82
  verbosityLevel?: VerbosityLevel;
67
83
  }
84
+ /**
85
+ * A single restoration hint message for post-compaction context injection
86
+ */
87
+ export interface RestorationHintMessage {
88
+ /** File path */
89
+ path: string;
90
+ /** Whether content is inlined or just referenced */
91
+ type: 'inline' | 'reference';
92
+ /** The formatted hint text */
93
+ text: string;
94
+ /** Estimated token count of this hint */
95
+ tokens: number;
96
+ }
68
97
  /**
69
98
  * Statistics about file accesses
70
99
  */
@@ -95,11 +124,13 @@ export declare class FileAccessTracker {
95
124
  private readonly accesses;
96
125
  private readonly maxEntries;
97
126
  private readonly deduplicateReferences;
127
+ private readonly inlineThreshold;
128
+ private readonly maxContentEntries;
98
129
  constructor(options?: FileAccessTrackerOptions);
99
130
  /**
100
131
  * Track a file that was fully read
101
132
  */
102
- trackRead(filePath: string, lineCount: number, summary?: string): void;
133
+ trackRead(filePath: string, lineCount: number, summary?: string, content?: string): void;
103
134
  /**
104
135
  * Track a file that was referenced (e.g., appeared in grep/glob results)
105
136
  */
@@ -135,6 +166,23 @@ export declare class FileAccessTracker {
135
166
  * Format restoration hints for injection after compaction
136
167
  */
137
168
  formatRestorationHints(options?: FormatHintsOptions): string;
169
+ /**
170
+ * Format restoration hints with inline file content (Claude Code style).
171
+ *
172
+ * Small files with stored content are inlined up to a token budget.
173
+ * Large files or files exceeding the budget get reference-only hints.
174
+ * Each file produces a separate hint message for individual injection.
175
+ *
176
+ * Priority order for inlining:
177
+ * 1. Modified files (most critical context)
178
+ * 2. Read files, most recent first
179
+ * 3. Once budget exhausted → remaining become reference-only
180
+ * 4. Referenced-only files → always reference-only
181
+ */
182
+ formatRestorationHintsWithContent(options?: {
183
+ /** Total token budget for all inline content (default: 4000) */
184
+ maxInlineTokens?: number;
185
+ }): RestorationHintMessage[];
138
186
  /**
139
187
  * Clear all tracked accesses
140
188
  */
@@ -143,6 +191,16 @@ export declare class FileAccessTracker {
143
191
  * Get the number of tracked files
144
192
  */
145
193
  get size(): number;
194
+ /**
195
+ * Rough token estimate (4 chars ≈ 1 token). Avoids heavy tiktoken dependency
196
+ * in the tracker — exact counts aren't critical for budget enforcement.
197
+ */
198
+ private estimateTokens;
199
+ /**
200
+ * Evict oldest stored content when maxContentEntries is exceeded.
201
+ * Only drops the `content`/`tokenCount` fields — the access entry itself is preserved.
202
+ */
203
+ private enforceMaxContentEntries;
146
204
  private normalizePath;
147
205
  private enforceMaxEntries;
148
206
  private getEffectiveMaxFiles;