@compilr-dev/agents 0.1.0 → 0.2.1
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 +168 -1
- package/dist/agent.js +268 -14
- package/dist/context/file-tracker.d.ts +156 -0
- package/dist/context/file-tracker.js +358 -0
- package/dist/context/file-tracking-hook.d.ts +29 -0
- package/dist/context/file-tracking-hook.js +103 -0
- package/dist/context/index.d.ts +5 -1
- package/dist/context/index.js +3 -0
- package/dist/context/manager.d.ts +69 -1
- package/dist/context/manager.js +304 -0
- package/dist/context/types.d.ts +95 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +7 -3
- package/dist/messages/index.d.ts +13 -0
- package/dist/messages/index.js +51 -0
- package/dist/permissions/manager.js +6 -1
- package/dist/providers/gemini.js +1 -3
- package/dist/providers/mock.js +8 -0
- package/dist/providers/openai-compatible.js +1 -3
- package/dist/skills/index.js +691 -0
- package/dist/tools/builtin/index.d.ts +6 -1
- package/dist/tools/builtin/index.js +7 -0
- package/dist/tools/builtin/suggest.d.ts +57 -0
- package/dist/tools/builtin/suggest.js +99 -0
- package/dist/tools/builtin/task.js +13 -8
- package/dist/tools/builtin/tool-names.d.ts +44 -0
- package/dist/tools/builtin/tool-names.js +51 -0
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +5 -1
- package/dist/tools/registry.d.ts +4 -0
- package/dist/tools/registry.js +9 -0
- package/package.json +4 -4
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Access Tracker
|
|
3
|
+
*
|
|
4
|
+
* Tracks file accesses during agent sessions to provide context restoration
|
|
5
|
+
* hints after compaction. Inspired by Claude Code's post-compaction file
|
|
6
|
+
* reference display.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Track read, referenced, and modified files
|
|
10
|
+
* - Deduplication (read supersedes reference)
|
|
11
|
+
* - Max entries with LRU eviction
|
|
12
|
+
* - Verbosity-aware formatting
|
|
13
|
+
*
|
|
14
|
+
* @see /workspace/project-docs/00-requirements/compilr-dev-agents/context-restoration-hints.md
|
|
15
|
+
*/
|
|
16
|
+
import type { VerbosityLevel } from './types.js';
|
|
17
|
+
/**
|
|
18
|
+
* Type of file access
|
|
19
|
+
*/
|
|
20
|
+
export type FileAccessType = 'read' | 'referenced' | 'modified';
|
|
21
|
+
/**
|
|
22
|
+
* Record of a file access
|
|
23
|
+
*/
|
|
24
|
+
export interface FileAccess {
|
|
25
|
+
/** Absolute file path */
|
|
26
|
+
path: string;
|
|
27
|
+
/** Type of access */
|
|
28
|
+
type: FileAccessType;
|
|
29
|
+
/** When accessed (timestamp) */
|
|
30
|
+
timestamp: number;
|
|
31
|
+
/** Number of lines (for 'read' type) */
|
|
32
|
+
lineCount?: number;
|
|
33
|
+
/** Optional summary of what was found/changed */
|
|
34
|
+
summary?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Options for FileAccessTracker constructor
|
|
38
|
+
*/
|
|
39
|
+
export interface FileAccessTrackerOptions {
|
|
40
|
+
/**
|
|
41
|
+
* Maximum number of entries to track
|
|
42
|
+
* @default 100
|
|
43
|
+
*/
|
|
44
|
+
maxEntries?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Deduplicate references (don't track same file twice as 'referenced')
|
|
47
|
+
* @default true
|
|
48
|
+
*/
|
|
49
|
+
deduplicateReferences?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Options for formatting restoration hints
|
|
53
|
+
*/
|
|
54
|
+
export interface FormatHintsOptions {
|
|
55
|
+
/** Include line counts for read files @default true */
|
|
56
|
+
includeLineCount?: boolean;
|
|
57
|
+
/** Include summaries @default false */
|
|
58
|
+
includeSummary?: boolean;
|
|
59
|
+
/** Include timestamps @default false */
|
|
60
|
+
includeTimestamp?: boolean;
|
|
61
|
+
/** Group by access type @default true */
|
|
62
|
+
groupByType?: boolean;
|
|
63
|
+
/** Maximum files to include @default 20 */
|
|
64
|
+
maxFiles?: number;
|
|
65
|
+
/** Verbosity level (adjusts format automatically) */
|
|
66
|
+
verbosityLevel?: VerbosityLevel;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Statistics about file accesses
|
|
70
|
+
*/
|
|
71
|
+
export interface FileAccessStats {
|
|
72
|
+
read: number;
|
|
73
|
+
referenced: number;
|
|
74
|
+
modified: number;
|
|
75
|
+
total: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Tracks file accesses during agent sessions for context restoration hints.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* const tracker = new FileAccessTracker();
|
|
83
|
+
*
|
|
84
|
+
* // Track file accesses
|
|
85
|
+
* tracker.trackRead('/path/to/file.ts', 597);
|
|
86
|
+
* tracker.trackReference('/path/to/other.ts');
|
|
87
|
+
* tracker.trackModification('/path/to/file.ts', 'Added function');
|
|
88
|
+
*
|
|
89
|
+
* // Get restoration hints after compaction
|
|
90
|
+
* const hints = tracker.formatRestorationHints();
|
|
91
|
+
* console.log(hints);
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare class FileAccessTracker {
|
|
95
|
+
private readonly accesses;
|
|
96
|
+
private readonly maxEntries;
|
|
97
|
+
private readonly deduplicateReferences;
|
|
98
|
+
constructor(options?: FileAccessTrackerOptions);
|
|
99
|
+
/**
|
|
100
|
+
* Track a file that was fully read
|
|
101
|
+
*/
|
|
102
|
+
trackRead(filePath: string, lineCount: number, summary?: string): void;
|
|
103
|
+
/**
|
|
104
|
+
* Track a file that was referenced (e.g., appeared in grep/glob results)
|
|
105
|
+
*/
|
|
106
|
+
trackReference(filePath: string): void;
|
|
107
|
+
/**
|
|
108
|
+
* Track a file that was modified (written or edited)
|
|
109
|
+
*/
|
|
110
|
+
trackModification(filePath: string, summary?: string): void;
|
|
111
|
+
/**
|
|
112
|
+
* Get all file accesses, optionally filtered
|
|
113
|
+
*/
|
|
114
|
+
getAccesses(options?: {
|
|
115
|
+
type?: FileAccessType;
|
|
116
|
+
since?: number;
|
|
117
|
+
}): FileAccess[];
|
|
118
|
+
/**
|
|
119
|
+
* Get most recent file accesses
|
|
120
|
+
*/
|
|
121
|
+
getRecentAccesses(limit?: number): FileAccess[];
|
|
122
|
+
/**
|
|
123
|
+
* Check if a file has been accessed
|
|
124
|
+
*/
|
|
125
|
+
hasAccessed(filePath: string): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Get access record for a specific file
|
|
128
|
+
*/
|
|
129
|
+
getAccess(filePath: string): FileAccess | undefined;
|
|
130
|
+
/**
|
|
131
|
+
* Get statistics about file accesses
|
|
132
|
+
*/
|
|
133
|
+
getStats(): FileAccessStats;
|
|
134
|
+
/**
|
|
135
|
+
* Format restoration hints for injection after compaction
|
|
136
|
+
*/
|
|
137
|
+
formatRestorationHints(options?: FormatHintsOptions): string;
|
|
138
|
+
/**
|
|
139
|
+
* Clear all tracked accesses
|
|
140
|
+
*/
|
|
141
|
+
clear(): void;
|
|
142
|
+
/**
|
|
143
|
+
* Get the number of tracked files
|
|
144
|
+
*/
|
|
145
|
+
get size(): number;
|
|
146
|
+
private normalizePath;
|
|
147
|
+
private enforceMaxEntries;
|
|
148
|
+
private getEffectiveMaxFiles;
|
|
149
|
+
private formatCompact;
|
|
150
|
+
private formatGrouped;
|
|
151
|
+
private formatFlat;
|
|
152
|
+
private formatAccessLine;
|
|
153
|
+
private groupByType;
|
|
154
|
+
private getDisplayName;
|
|
155
|
+
private getDisplayPath;
|
|
156
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Access Tracker
|
|
3
|
+
*
|
|
4
|
+
* Tracks file accesses during agent sessions to provide context restoration
|
|
5
|
+
* hints after compaction. Inspired by Claude Code's post-compaction file
|
|
6
|
+
* reference display.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Track read, referenced, and modified files
|
|
10
|
+
* - Deduplication (read supersedes reference)
|
|
11
|
+
* - Max entries with LRU eviction
|
|
12
|
+
* - Verbosity-aware formatting
|
|
13
|
+
*
|
|
14
|
+
* @see /workspace/project-docs/00-requirements/compilr-dev-agents/context-restoration-hints.md
|
|
15
|
+
*/
|
|
16
|
+
import path from 'path';
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// FileAccessTracker Class
|
|
19
|
+
// ============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Tracks file accesses during agent sessions for context restoration hints.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const tracker = new FileAccessTracker();
|
|
26
|
+
*
|
|
27
|
+
* // Track file accesses
|
|
28
|
+
* tracker.trackRead('/path/to/file.ts', 597);
|
|
29
|
+
* tracker.trackReference('/path/to/other.ts');
|
|
30
|
+
* tracker.trackModification('/path/to/file.ts', 'Added function');
|
|
31
|
+
*
|
|
32
|
+
* // Get restoration hints after compaction
|
|
33
|
+
* const hints = tracker.formatRestorationHints();
|
|
34
|
+
* console.log(hints);
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export class FileAccessTracker {
|
|
38
|
+
accesses = new Map();
|
|
39
|
+
maxEntries;
|
|
40
|
+
deduplicateReferences;
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this.maxEntries = options.maxEntries ?? 100;
|
|
43
|
+
this.deduplicateReferences = options.deduplicateReferences ?? true;
|
|
44
|
+
}
|
|
45
|
+
// ==========================================================================
|
|
46
|
+
// Track Methods
|
|
47
|
+
// ==========================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Track a file that was fully read
|
|
50
|
+
*/
|
|
51
|
+
trackRead(filePath, lineCount, summary) {
|
|
52
|
+
const normalizedPath = this.normalizePath(filePath);
|
|
53
|
+
// Read supersedes reference - remove any existing reference
|
|
54
|
+
const existing = this.accesses.get(normalizedPath);
|
|
55
|
+
if (existing && existing.type === 'referenced') {
|
|
56
|
+
this.accesses.delete(normalizedPath);
|
|
57
|
+
}
|
|
58
|
+
// Update or create read entry
|
|
59
|
+
this.accesses.set(normalizedPath, {
|
|
60
|
+
path: normalizedPath,
|
|
61
|
+
type: 'read',
|
|
62
|
+
timestamp: Date.now(),
|
|
63
|
+
lineCount,
|
|
64
|
+
summary,
|
|
65
|
+
});
|
|
66
|
+
this.enforceMaxEntries();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Track a file that was referenced (e.g., appeared in grep/glob results)
|
|
70
|
+
*/
|
|
71
|
+
trackReference(filePath) {
|
|
72
|
+
const normalizedPath = this.normalizePath(filePath);
|
|
73
|
+
// Don't downgrade read/modified to reference
|
|
74
|
+
const existing = this.accesses.get(normalizedPath);
|
|
75
|
+
if (existing && (existing.type === 'read' || existing.type === 'modified')) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Deduplicate references
|
|
79
|
+
if (this.deduplicateReferences && existing && existing.type === 'referenced') {
|
|
80
|
+
// Update timestamp only
|
|
81
|
+
existing.timestamp = Date.now();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.accesses.set(normalizedPath, {
|
|
85
|
+
path: normalizedPath,
|
|
86
|
+
type: 'referenced',
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
});
|
|
89
|
+
this.enforceMaxEntries();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Track a file that was modified (written or edited)
|
|
93
|
+
*/
|
|
94
|
+
trackModification(filePath, summary) {
|
|
95
|
+
const normalizedPath = this.normalizePath(filePath);
|
|
96
|
+
this.accesses.set(normalizedPath, {
|
|
97
|
+
path: normalizedPath,
|
|
98
|
+
type: 'modified',
|
|
99
|
+
timestamp: Date.now(),
|
|
100
|
+
summary,
|
|
101
|
+
});
|
|
102
|
+
this.enforceMaxEntries();
|
|
103
|
+
}
|
|
104
|
+
// ==========================================================================
|
|
105
|
+
// Query Methods
|
|
106
|
+
// ==========================================================================
|
|
107
|
+
/**
|
|
108
|
+
* Get all file accesses, optionally filtered
|
|
109
|
+
*/
|
|
110
|
+
getAccesses(options) {
|
|
111
|
+
let accesses = Array.from(this.accesses.values());
|
|
112
|
+
if (options?.type) {
|
|
113
|
+
accesses = accesses.filter((a) => a.type === options.type);
|
|
114
|
+
}
|
|
115
|
+
if (options?.since !== undefined) {
|
|
116
|
+
const since = options.since;
|
|
117
|
+
accesses = accesses.filter((a) => a.timestamp >= since);
|
|
118
|
+
}
|
|
119
|
+
// Sort by timestamp descending (most recent first)
|
|
120
|
+
return accesses.sort((a, b) => b.timestamp - a.timestamp);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get most recent file accesses
|
|
124
|
+
*/
|
|
125
|
+
getRecentAccesses(limit = 10) {
|
|
126
|
+
return this.getAccesses().slice(0, limit);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if a file has been accessed
|
|
130
|
+
*/
|
|
131
|
+
hasAccessed(filePath) {
|
|
132
|
+
return this.accesses.has(this.normalizePath(filePath));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get access record for a specific file
|
|
136
|
+
*/
|
|
137
|
+
getAccess(filePath) {
|
|
138
|
+
return this.accesses.get(this.normalizePath(filePath));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get statistics about file accesses
|
|
142
|
+
*/
|
|
143
|
+
getStats() {
|
|
144
|
+
const stats = { read: 0, referenced: 0, modified: 0, total: 0 };
|
|
145
|
+
for (const access of this.accesses.values()) {
|
|
146
|
+
stats[access.type]++;
|
|
147
|
+
stats.total++;
|
|
148
|
+
}
|
|
149
|
+
return stats;
|
|
150
|
+
}
|
|
151
|
+
// ==========================================================================
|
|
152
|
+
// Format Methods
|
|
153
|
+
// ==========================================================================
|
|
154
|
+
/**
|
|
155
|
+
* Format restoration hints for injection after compaction
|
|
156
|
+
*/
|
|
157
|
+
formatRestorationHints(options = {}) {
|
|
158
|
+
const { includeLineCount = true, includeSummary = false, includeTimestamp = false, groupByType = true, maxFiles = 20, verbosityLevel = 'normal', } = options;
|
|
159
|
+
// Adjust based on verbosity
|
|
160
|
+
const effectiveMaxFiles = this.getEffectiveMaxFiles(maxFiles, verbosityLevel);
|
|
161
|
+
if (this.accesses.size === 0) {
|
|
162
|
+
return '';
|
|
163
|
+
}
|
|
164
|
+
const allAccesses = this.getAccesses();
|
|
165
|
+
// Apply limit
|
|
166
|
+
const limitedAccesses = allAccesses.slice(0, effectiveMaxFiles);
|
|
167
|
+
const hasMore = allAccesses.length > effectiveMaxFiles;
|
|
168
|
+
// Use compact format for tight context
|
|
169
|
+
if (verbosityLevel === 'minimal' || verbosityLevel === 'abbreviated') {
|
|
170
|
+
return this.formatCompact(limitedAccesses, hasMore);
|
|
171
|
+
}
|
|
172
|
+
// Full format with grouping
|
|
173
|
+
if (groupByType) {
|
|
174
|
+
return this.formatGrouped(limitedAccesses, hasMore, {
|
|
175
|
+
includeLineCount,
|
|
176
|
+
includeSummary,
|
|
177
|
+
includeTimestamp,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
// Flat format (not grouped)
|
|
181
|
+
return this.formatFlat(limitedAccesses, hasMore, {
|
|
182
|
+
includeLineCount,
|
|
183
|
+
includeSummary,
|
|
184
|
+
includeTimestamp,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
// ==========================================================================
|
|
188
|
+
// Lifecycle Methods
|
|
189
|
+
// ==========================================================================
|
|
190
|
+
/**
|
|
191
|
+
* Clear all tracked accesses
|
|
192
|
+
*/
|
|
193
|
+
clear() {
|
|
194
|
+
this.accesses.clear();
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get the number of tracked files
|
|
198
|
+
*/
|
|
199
|
+
get size() {
|
|
200
|
+
return this.accesses.size;
|
|
201
|
+
}
|
|
202
|
+
// ==========================================================================
|
|
203
|
+
// Private Helpers
|
|
204
|
+
// ==========================================================================
|
|
205
|
+
normalizePath(filePath) {
|
|
206
|
+
// Resolve to absolute path
|
|
207
|
+
return path.resolve(filePath);
|
|
208
|
+
}
|
|
209
|
+
enforceMaxEntries() {
|
|
210
|
+
if (this.accesses.size <= this.maxEntries) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
// Evict oldest references first, then oldest reads
|
|
214
|
+
const sorted = this.getAccesses().reverse(); // Oldest first
|
|
215
|
+
// Separate by type for eviction priority
|
|
216
|
+
const references = sorted.filter((a) => a.type === 'referenced');
|
|
217
|
+
const reads = sorted.filter((a) => a.type === 'read');
|
|
218
|
+
const modified = sorted.filter((a) => a.type === 'modified');
|
|
219
|
+
// Build eviction order: references first, then reads, modified last
|
|
220
|
+
const evictionOrder = [...references, ...reads, ...modified];
|
|
221
|
+
// Evict until under limit
|
|
222
|
+
while (this.accesses.size > this.maxEntries && evictionOrder.length > 0) {
|
|
223
|
+
const toEvict = evictionOrder.shift();
|
|
224
|
+
if (toEvict) {
|
|
225
|
+
this.accesses.delete(toEvict.path);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
getEffectiveMaxFiles(maxFiles, verbosityLevel) {
|
|
230
|
+
switch (verbosityLevel) {
|
|
231
|
+
case 'minimal':
|
|
232
|
+
return Math.min(maxFiles, 3);
|
|
233
|
+
case 'abbreviated':
|
|
234
|
+
return Math.min(maxFiles, 5);
|
|
235
|
+
case 'normal':
|
|
236
|
+
return Math.min(maxFiles, 10);
|
|
237
|
+
case 'full':
|
|
238
|
+
default:
|
|
239
|
+
return maxFiles;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
formatCompact(accesses, hasMore) {
|
|
243
|
+
const byType = this.groupByType(accesses);
|
|
244
|
+
const parts = [];
|
|
245
|
+
if (byType.read.length > 0) {
|
|
246
|
+
const names = byType.read.map((a) => this.getDisplayName(a.path)).join(', ');
|
|
247
|
+
parts.push(`${names} (read)`);
|
|
248
|
+
}
|
|
249
|
+
if (byType.modified.length > 0) {
|
|
250
|
+
const names = byType.modified.map((a) => this.getDisplayName(a.path)).join(', ');
|
|
251
|
+
parts.push(`${names} (modified)`);
|
|
252
|
+
}
|
|
253
|
+
if (byType.referenced.length > 0) {
|
|
254
|
+
const count = byType.referenced.length;
|
|
255
|
+
if (count <= 2) {
|
|
256
|
+
const names = byType.referenced.map((a) => this.getDisplayName(a.path)).join(', ');
|
|
257
|
+
parts.push(`${names} (referenced)`);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
const first = this.getDisplayName(byType.referenced[0].path);
|
|
261
|
+
parts.push(`${first} +${String(count - 1)} more (referenced)`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const moreNote = hasMore ? ' +more' : '';
|
|
265
|
+
return `[Previously accessed: ${parts.join('; ')}${moreNote}]`;
|
|
266
|
+
}
|
|
267
|
+
formatGrouped(accesses, hasMore, opts) {
|
|
268
|
+
const byType = this.groupByType(accesses);
|
|
269
|
+
const lines = ['[Context compacted. Previously accessed files:]', ''];
|
|
270
|
+
if (byType.read.length > 0) {
|
|
271
|
+
const count = byType.read.length;
|
|
272
|
+
lines.push(`Read (${String(count)} file${count > 1 ? 's' : ''}):`);
|
|
273
|
+
for (const access of byType.read) {
|
|
274
|
+
lines.push(this.formatAccessLine(access, opts));
|
|
275
|
+
}
|
|
276
|
+
lines.push('');
|
|
277
|
+
}
|
|
278
|
+
if (byType.modified.length > 0) {
|
|
279
|
+
const count = byType.modified.length;
|
|
280
|
+
lines.push(`Modified (${String(count)} file${count > 1 ? 's' : ''}):`);
|
|
281
|
+
for (const access of byType.modified) {
|
|
282
|
+
lines.push(this.formatAccessLine(access, opts));
|
|
283
|
+
}
|
|
284
|
+
lines.push('');
|
|
285
|
+
}
|
|
286
|
+
if (byType.referenced.length > 0) {
|
|
287
|
+
const count = byType.referenced.length;
|
|
288
|
+
lines.push(`Referenced (${String(count)} file${count > 1 ? 's' : ''}):`);
|
|
289
|
+
for (const access of byType.referenced) {
|
|
290
|
+
lines.push(this.formatAccessLine(access, opts));
|
|
291
|
+
}
|
|
292
|
+
lines.push('');
|
|
293
|
+
}
|
|
294
|
+
if (hasMore) {
|
|
295
|
+
lines.push('(Additional files truncated)');
|
|
296
|
+
lines.push('');
|
|
297
|
+
}
|
|
298
|
+
lines.push('[Use read_file to re-access content if needed.]');
|
|
299
|
+
return lines.join('\n');
|
|
300
|
+
}
|
|
301
|
+
formatFlat(accesses, hasMore, opts) {
|
|
302
|
+
const lines = ['[Context compacted. Previously accessed files:]', ''];
|
|
303
|
+
for (const access of accesses) {
|
|
304
|
+
const typeLabel = access.type === 'read' ? 'R' : access.type === 'modified' ? 'M' : 'ref';
|
|
305
|
+
lines.push(` [${typeLabel}] ${this.formatAccessLine(access, opts).trim()}`);
|
|
306
|
+
}
|
|
307
|
+
if (hasMore) {
|
|
308
|
+
lines.push('');
|
|
309
|
+
lines.push('(Additional files truncated)');
|
|
310
|
+
}
|
|
311
|
+
lines.push('');
|
|
312
|
+
lines.push('[Use read_file to re-access content if needed.]');
|
|
313
|
+
return lines.join('\n');
|
|
314
|
+
}
|
|
315
|
+
formatAccessLine(access, opts) {
|
|
316
|
+
const parts = [` • ${this.getDisplayPath(access.path)}`];
|
|
317
|
+
if (opts.includeLineCount && access.lineCount !== undefined) {
|
|
318
|
+
parts.push(`(${String(access.lineCount)} lines)`);
|
|
319
|
+
}
|
|
320
|
+
if (opts.includeSummary && access.summary) {
|
|
321
|
+
parts.push(`- ${access.summary}`);
|
|
322
|
+
}
|
|
323
|
+
if (opts.includeTimestamp) {
|
|
324
|
+
const date = new Date(access.timestamp);
|
|
325
|
+
parts.push(`@ ${date.toISOString().slice(11, 19)}`);
|
|
326
|
+
}
|
|
327
|
+
return parts.join(' ');
|
|
328
|
+
}
|
|
329
|
+
groupByType(accesses) {
|
|
330
|
+
const result = {
|
|
331
|
+
read: [],
|
|
332
|
+
referenced: [],
|
|
333
|
+
modified: [],
|
|
334
|
+
};
|
|
335
|
+
for (const access of accesses) {
|
|
336
|
+
result[access.type].push(access);
|
|
337
|
+
}
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
getDisplayName(filePath) {
|
|
341
|
+
return path.basename(filePath);
|
|
342
|
+
}
|
|
343
|
+
getDisplayPath(filePath) {
|
|
344
|
+
// Try to make path shorter for display
|
|
345
|
+
const cwd = process.cwd();
|
|
346
|
+
if (filePath.startsWith(cwd)) {
|
|
347
|
+
return filePath.slice(cwd.length + 1); // Remove cwd + leading slash
|
|
348
|
+
}
|
|
349
|
+
// Truncate long absolute paths
|
|
350
|
+
if (filePath.length > 60) {
|
|
351
|
+
const parts = filePath.split(path.sep);
|
|
352
|
+
if (parts.length > 4) {
|
|
353
|
+
return `...${path.sep}${parts.slice(-3).join(path.sep)}`;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return filePath;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Tracking Hook
|
|
3
|
+
*
|
|
4
|
+
* An afterTool hook that automatically tracks file accesses based on tool calls.
|
|
5
|
+
* This approach is cleaner than modifying each tool directly.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const tracker = new FileAccessTracker();
|
|
10
|
+
* const agent = new Agent({
|
|
11
|
+
* hooks: {
|
|
12
|
+
* afterTool: [createFileTrackingHook(tracker)]
|
|
13
|
+
* }
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import type { AfterToolHook } from '../hooks/types.js';
|
|
18
|
+
import type { FileAccessTracker } from './file-tracker.js';
|
|
19
|
+
/**
|
|
20
|
+
* Create an afterTool hook that tracks file accesses
|
|
21
|
+
*
|
|
22
|
+
* @param tracker - FileAccessTracker instance to record accesses
|
|
23
|
+
* @returns An afterTool hook function
|
|
24
|
+
*/
|
|
25
|
+
export declare function createFileTrackingHook(tracker: FileAccessTracker): AfterToolHook;
|
|
26
|
+
/**
|
|
27
|
+
* Tool names that this hook tracks
|
|
28
|
+
*/
|
|
29
|
+
export declare const TRACKED_TOOLS: readonly ["read_file", "write_file", "edit", "grep", "glob"];
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Tracking Hook
|
|
3
|
+
*
|
|
4
|
+
* An afterTool hook that automatically tracks file accesses based on tool calls.
|
|
5
|
+
* This approach is cleaner than modifying each tool directly.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const tracker = new FileAccessTracker();
|
|
10
|
+
* const agent = new Agent({
|
|
11
|
+
* hooks: {
|
|
12
|
+
* afterTool: [createFileTrackingHook(tracker)]
|
|
13
|
+
* }
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import { TOOL_NAMES } from '../tools/builtin/tool-names.js';
|
|
18
|
+
/**
|
|
19
|
+
* Create an afterTool hook that tracks file accesses
|
|
20
|
+
*
|
|
21
|
+
* @param tracker - FileAccessTracker instance to record accesses
|
|
22
|
+
* @returns An afterTool hook function
|
|
23
|
+
*/
|
|
24
|
+
export function createFileTrackingHook(tracker) {
|
|
25
|
+
return (context) => {
|
|
26
|
+
const { toolName, input, result } = context;
|
|
27
|
+
// Only track successful operations
|
|
28
|
+
if (!result.success) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
switch (toolName) {
|
|
32
|
+
case TOOL_NAMES.READ_FILE: {
|
|
33
|
+
const readInput = input;
|
|
34
|
+
const readResult = result;
|
|
35
|
+
const lineCount = readResult.result?.lineCount ?? 0;
|
|
36
|
+
tracker.trackRead(readInput.file_path, lineCount);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case TOOL_NAMES.WRITE_FILE: {
|
|
40
|
+
const writeInput = input;
|
|
41
|
+
tracker.trackModification(writeInput.file_path, 'File written');
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case TOOL_NAMES.EDIT: {
|
|
45
|
+
const editInput = input;
|
|
46
|
+
tracker.trackModification(editInput.file_path, 'File edited');
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case TOOL_NAMES.GREP: {
|
|
50
|
+
const grepInput = input;
|
|
51
|
+
const grepResult = result;
|
|
52
|
+
// Track the search path if specified
|
|
53
|
+
if (grepInput.path) {
|
|
54
|
+
tracker.trackReference(grepInput.path);
|
|
55
|
+
}
|
|
56
|
+
// Track all files that matched
|
|
57
|
+
if (grepResult.result?.matches) {
|
|
58
|
+
const seenFiles = new Set();
|
|
59
|
+
for (const match of grepResult.result.matches) {
|
|
60
|
+
if (!seenFiles.has(match.file)) {
|
|
61
|
+
seenFiles.add(match.file);
|
|
62
|
+
tracker.trackReference(match.file);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (grepResult.result?.files) {
|
|
67
|
+
for (const file of grepResult.result.files) {
|
|
68
|
+
tracker.trackReference(file);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case TOOL_NAMES.GLOB: {
|
|
74
|
+
const globInput = input;
|
|
75
|
+
const globResult = result;
|
|
76
|
+
// Track the search path if specified
|
|
77
|
+
if (globInput.path) {
|
|
78
|
+
tracker.trackReference(globInput.path);
|
|
79
|
+
}
|
|
80
|
+
// Track all matched files
|
|
81
|
+
if (globResult.result?.files) {
|
|
82
|
+
for (const file of globResult.result.files) {
|
|
83
|
+
tracker.trackReference(file);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
// Note: bash tool could potentially be tracked for file operations,
|
|
89
|
+
// but it's complex to parse shell commands. Skip for now.
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Tool names that this hook tracks
|
|
96
|
+
*/
|
|
97
|
+
export const TRACKED_TOOLS = [
|
|
98
|
+
TOOL_NAMES.READ_FILE,
|
|
99
|
+
TOOL_NAMES.WRITE_FILE,
|
|
100
|
+
TOOL_NAMES.EDIT,
|
|
101
|
+
TOOL_NAMES.GREP,
|
|
102
|
+
TOOL_NAMES.GLOB,
|
|
103
|
+
];
|
package/dist/context/index.d.ts
CHANGED
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
* - Filtering (truncate large content)
|
|
7
7
|
* - Compaction (save to files, replace with references)
|
|
8
8
|
* - Summarization (compress history when near limit)
|
|
9
|
+
* - File access tracking (for context restoration hints)
|
|
9
10
|
*/
|
|
10
11
|
export { ContextManager, DEFAULT_CONTEXT_CONFIG } from './manager.js';
|
|
11
12
|
export type { ContextManagerOptions } from './manager.js';
|
|
12
|
-
export
|
|
13
|
+
export { FileAccessTracker } from './file-tracker.js';
|
|
14
|
+
export type { FileAccessType, FileAccess, FileAccessTrackerOptions, FormatHintsOptions, FileAccessStats, } from './file-tracker.js';
|
|
15
|
+
export { createFileTrackingHook, TRACKED_TOOLS } from './file-tracking-hook.js';
|
|
16
|
+
export type { ContextCategory, BudgetAllocation, CategoryBudgetInfo, PreflightResult, VerbosityLevel, VerbosityConfig, ContextConfig, FilteringConfig, CompactionConfig, SummarizationConfig, CompactionResult, SummarizationResult, FilteringResult, ContextEvent, ContextEventHandler, ContextStats, CategorizedMessages, SmartCompactOptions, SmartCompactionResult, } from './types.js';
|
package/dist/context/index.js
CHANGED
|
@@ -6,5 +6,8 @@
|
|
|
6
6
|
* - Filtering (truncate large content)
|
|
7
7
|
* - Compaction (save to files, replace with references)
|
|
8
8
|
* - Summarization (compress history when near limit)
|
|
9
|
+
* - File access tracking (for context restoration hints)
|
|
9
10
|
*/
|
|
10
11
|
export { ContextManager, DEFAULT_CONTEXT_CONFIG } from './manager.js';
|
|
12
|
+
export { FileAccessTracker } from './file-tracker.js';
|
|
13
|
+
export { createFileTrackingHook, TRACKED_TOOLS } from './file-tracking-hook.js';
|