@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.
- package/dist/agent.d.ts +42 -1
- package/dist/agent.js +116 -17
- package/dist/anchors/manager.js +3 -2
- package/dist/context/delegated-result-store.d.ts +67 -0
- package/dist/context/delegated-result-store.js +99 -0
- package/dist/context/delegation-types.d.ts +82 -0
- package/dist/context/delegation-types.js +18 -0
- package/dist/context/file-tracker.d.ts +59 -1
- package/dist/context/file-tracker.js +96 -1
- package/dist/context/file-tracking-hook.js +9 -4
- package/dist/context/index.d.ts +7 -1
- package/dist/context/index.js +4 -0
- package/dist/context/manager.js +12 -32
- package/dist/context/tool-result-delegator.d.ts +63 -0
- package/dist/context/tool-result-delegator.js +314 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +9 -3
- package/dist/memory/loader.js +2 -1
- package/dist/memory/types.d.ts +1 -1
- package/dist/providers/claude.d.ts +1 -5
- package/dist/providers/claude.js +6 -29
- package/dist/providers/gemini-native.d.ts +1 -1
- package/dist/providers/gemini-native.js +10 -24
- package/dist/providers/mock.d.ts +1 -1
- package/dist/providers/mock.js +3 -24
- package/dist/providers/openai-compatible.d.ts +1 -5
- package/dist/providers/openai-compatible.js +14 -28
- package/dist/providers/types.d.ts +5 -0
- package/dist/rate-limit/provider-wrapper.d.ts +1 -1
- package/dist/rate-limit/provider-wrapper.js +3 -27
- package/dist/tools/builtin/index.d.ts +2 -0
- package/dist/tools/builtin/index.js +2 -0
- package/dist/tools/builtin/recall-result.d.ts +29 -0
- package/dist/tools/builtin/recall-result.js +48 -0
- package/dist/tools/builtin/task.js +1 -0
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +2 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/tokenizer.d.ts +19 -0
- package/dist/utils/tokenizer.js +93 -0
- 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
|
-
*
|
|
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
|
-
|
|
226
|
+
needsHooksManager = true;
|
|
189
227
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
this.hooksManager = new HooksManager({ 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
|
-
*
|
|
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.
|
|
804
|
-
|
|
842
|
+
const hints = this.fileTracker.formatRestorationHintsWithContent({
|
|
843
|
+
maxInlineTokens: this.fileRestorationConfig?.maxInlineTokens ?? 4000,
|
|
844
|
+
});
|
|
845
|
+
if (hints.length === 0) {
|
|
805
846
|
return;
|
|
806
847
|
}
|
|
807
|
-
//
|
|
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
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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 &&
|
|
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,
|
|
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,
|
|
2376
|
+
return withRetryGenerator(() => this.provider.chat(messages, chatOptions), {
|
|
2278
2377
|
maxAttempts,
|
|
2279
2378
|
baseDelayMs,
|
|
2280
2379
|
maxDelayMs,
|
package/dist/anchors/manager.js
CHANGED
|
@@ -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
|
|
18
|
+
* Default token estimator using tiktoken (cl100k_base encoding)
|
|
18
19
|
*/
|
|
19
20
|
function defaultEstimateTokens(content) {
|
|
20
|
-
return
|
|
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;
|