@compilr-dev/sdk 0.7.32 → 0.9.0

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.js CHANGED
@@ -2,6 +2,7 @@
2
2
  * CompilrAgent — high-level wrapper around @compilr-dev/agents Agent
3
3
  */
4
4
  import { Agent, ContextManager, createSuggestTool, } from '@compilr-dev/agents';
5
+ import { createCompressorHook } from './compressors/index.js';
5
6
  import { resolveProvider } from './provider.js';
6
7
  import { resolvePreset } from './presets/index.js';
7
8
  import { assembleTools, deduplicateTools } from './tools.js';
@@ -241,6 +242,13 @@ class CompilrAgentImpl {
241
242
  : [];
242
243
  mergedHooks.beforeLLM = [...existingHooks, ...capabilityHooks];
243
244
  }
245
+ // Add output compressor as the first afterTool hook (runs before delegation)
246
+ const existingAfterTool = config?.hooks?.afterTool
247
+ ? Array.isArray(config.hooks.afterTool)
248
+ ? config.hooks.afterTool
249
+ : [config.hooks.afterTool]
250
+ : [];
251
+ mergedHooks.afterTool = [createCompressorHook(), ...existingAfterTool];
244
252
  // Build observation mask config — SDK defaults include platform tools
245
253
  const observationMask = config?.context?.observationMask !== false
246
254
  ? {
@@ -314,7 +322,16 @@ class CompilrAgentImpl {
314
322
  : false
315
323
  : undefined,
316
324
  hooks: mergedHooks,
317
- pins: {},
325
+ enableFileTracking: config?.enableFileTracking ?? true,
326
+ pins: config?.pins
327
+ ? {
328
+ maxAnchors: config.pins.maxAnchors ?? 50,
329
+ maxTokens: config.pins.maxTokens ?? 4000,
330
+ includeDefaults: config.pins.includeDefaults ?? true,
331
+ }
332
+ : {},
333
+ onIterationLimitReached: config?.onIterationLimitReached,
334
+ iterationLimitBehavior: config?.iterationLimitBehavior,
318
335
  permissions: {
319
336
  defaultLevel: permissionsConfig.defaultLevel,
320
337
  onPermissionRequest: permissionsConfig.onPermissionRequest,
@@ -334,6 +351,8 @@ class CompilrAgentImpl {
334
351
  this.totalUsage.outputTokens += event.tokens.outputTokens;
335
352
  this.totalUsage.totalTokens += event.tokens.inputTokens + event.tokens.outputTokens;
336
353
  }
354
+ // Forward to user-provided onEvent callback
355
+ config?.onEvent?.(event);
337
356
  // Forward ALL events to external listener (if set via run/stream)
338
357
  this.externalEventListener?.(event);
339
358
  },
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Bash Output Compressors — command-specific output compression
3
+ *
4
+ * Each compressor understands the structure of a CLI tool's output
5
+ * and strips noise (progress bars, boilerplate, redundant info)
6
+ * while preserving signal (errors, changed files, test failures).
7
+ */
8
+ /**
9
+ * Compress bash command output based on the command that was run.
10
+ * Returns null if no compressor matches (output passes through unchanged).
11
+ */
12
+ export declare function compressBashOutput(command: string, stdout: string): string | null;
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Bash Output Compressors — command-specific output compression
3
+ *
4
+ * Each compressor understands the structure of a CLI tool's output
5
+ * and strips noise (progress bars, boilerplate, redundant info)
6
+ * while preserving signal (errors, changed files, test failures).
7
+ */
8
+ // ─── Router ─────────────────────────────────────────────────────────────────
9
+ /**
10
+ * Compress bash command output based on the command that was run.
11
+ * Returns null if no compressor matches (output passes through unchanged).
12
+ */
13
+ export function compressBashOutput(command, stdout) {
14
+ const cmd = command.trim();
15
+ // git commands
16
+ if (cmd.match(/^git\s+status/))
17
+ return compressGitStatus(stdout);
18
+ if (cmd.match(/^git\s+log/))
19
+ return compressGitLog(stdout);
20
+ if (cmd.match(/^git\s+diff/))
21
+ return compressGitDiff(stdout);
22
+ if (cmd.match(/^git\s+(add|commit|push|pull|fetch|merge|rebase|checkout|switch|branch)/))
23
+ return compressGitAction(stdout);
24
+ // npm/yarn/pnpm
25
+ if (cmd.match(/^(npm|yarn|pnpm)\s+install/))
26
+ return compressNpmInstall(stdout);
27
+ if (cmd.match(/^(npm|yarn|pnpm)\s+(test|run\s+test)/))
28
+ return compressTestOutput(stdout);
29
+ if (cmd.match(/^(npm|yarn|pnpm)\s+run\s+(lint|eslint)/))
30
+ return compressLintOutput(stdout);
31
+ if (cmd.match(/^(npm|yarn|pnpm)\s+run\s+(build|tsc)/))
32
+ return compressBuildOutput(stdout);
33
+ // Direct test runners
34
+ if (cmd.match(/^(jest|vitest|mocha|pytest|cargo\s+test|go\s+test)/))
35
+ return compressTestOutput(stdout);
36
+ // Direct linters
37
+ if (cmd.match(/^(eslint|tsc|ruff|cargo\s+clippy|golangci-lint)/))
38
+ return compressLintOutput(stdout);
39
+ // ls / find / tree
40
+ if (cmd.match(/^(ls|find|tree|fd)\b/))
41
+ return compressFileList(stdout);
42
+ // curl / wget (HTTP responses)
43
+ if (cmd.match(/^(curl|wget)\b/))
44
+ return compressCurlOutput(stdout);
45
+ return null; // No compressor matched
46
+ }
47
+ // ─── Git Status ─────────────────────────────────────────────────────────────
48
+ function compressGitStatus(output) {
49
+ const lines = output.split('\n').filter((l) => l.trim());
50
+ if (lines.length <= 20)
51
+ return output;
52
+ const sections = [];
53
+ let current = null;
54
+ for (const line of lines) {
55
+ if (line.startsWith('On branch') ||
56
+ line.startsWith('Your branch') ||
57
+ line.startsWith('HEAD detached')) {
58
+ sections.push({ header: line, files: [] });
59
+ }
60
+ else if (line.match(/^(Changes|Untracked|Unmerged)/)) {
61
+ current = { header: line, files: [] };
62
+ sections.push(current);
63
+ }
64
+ else if (current &&
65
+ (line.startsWith('\t') || line.match(/^\s+(modified|new file|deleted|renamed)/))) {
66
+ current.files.push(line.trim());
67
+ }
68
+ }
69
+ // Rebuild with capped file lists
70
+ const maxFilesPerSection = 15;
71
+ const result = [];
72
+ for (const section of sections) {
73
+ result.push(section.header);
74
+ const shown = section.files.slice(0, maxFilesPerSection);
75
+ for (const f of shown)
76
+ result.push(` ${f}`);
77
+ if (section.files.length > maxFilesPerSection) {
78
+ result.push(` ... +${String(section.files.length - maxFilesPerSection)} more files`);
79
+ }
80
+ }
81
+ return result.join('\n');
82
+ }
83
+ // ─── Git Log ────────────────────────────────────────────────────────────────
84
+ function compressGitLog(output) {
85
+ const lines = output.split('\n');
86
+ if (lines.length <= 60)
87
+ return output;
88
+ // Keep first 40 lines (recent commits), summarize the rest
89
+ const kept = lines.slice(0, 40);
90
+ const remaining = lines.slice(40).filter((l) => l.match(/^commit\s/));
91
+ if (remaining.length > 0) {
92
+ kept.push(`\n... +${String(remaining.length)} older commits omitted`);
93
+ }
94
+ return kept.join('\n');
95
+ }
96
+ // ─── Git Diff ───────────────────────────────────────────────────────────────
97
+ function compressGitDiff(output) {
98
+ const lines = output.split('\n');
99
+ if (lines.length <= 100)
100
+ return output;
101
+ // Keep diff headers and first N lines of each file, collapse large hunks
102
+ const result = [];
103
+ let currentFile = '';
104
+ let hunkLines = 0;
105
+ const maxHunkLines = 30;
106
+ for (const line of lines) {
107
+ if (line.startsWith('diff --git')) {
108
+ currentFile = line;
109
+ hunkLines = 0;
110
+ result.push(line);
111
+ }
112
+ else if (line.startsWith('---') || line.startsWith('+++') || line.startsWith('@@')) {
113
+ hunkLines = 0;
114
+ result.push(line);
115
+ }
116
+ else if (line.startsWith('+') || line.startsWith('-') || line.startsWith(' ')) {
117
+ hunkLines++;
118
+ if (hunkLines <= maxHunkLines) {
119
+ result.push(line);
120
+ }
121
+ else if (hunkLines === maxHunkLines + 1) {
122
+ result.push(`... (hunk truncated, ${currentFile.split(' b/')[1] ?? 'file'} continues)`);
123
+ }
124
+ }
125
+ else {
126
+ result.push(line);
127
+ }
128
+ }
129
+ return result.join('\n');
130
+ }
131
+ // ─── Git Action (add, commit, push, pull, etc.) ─────────────────────────────
132
+ function compressGitAction(output) {
133
+ const lines = output.split('\n').filter((l) => l.trim());
134
+ if (lines.length <= 15)
135
+ return output;
136
+ // Keep summary lines, strip verbose file lists
137
+ const important = lines.filter((l) => l.match(/^(\[|To |From |Already|Everything|Branch|Updating|Fast-forward|CONFLICT|error:|fatal:|warning:|\s*\d+ file|create mode|delete mode)/) ||
138
+ l.includes('->') ||
139
+ l.includes('insertions') ||
140
+ l.includes('deletions'));
141
+ if (important.length > 0 && important.length < lines.length) {
142
+ const omitted = lines.length - important.length;
143
+ important.push(`(${String(omitted)} lines of detail omitted)`);
144
+ return important.join('\n');
145
+ }
146
+ return output;
147
+ }
148
+ // ─── npm install ────────────────────────────────────────────────────────────
149
+ function compressNpmInstall(output) {
150
+ const lines = output.split('\n');
151
+ if (lines.length <= 10)
152
+ return output;
153
+ // Keep: added/removed/changed summary, warnings, errors
154
+ // Strip: progress bars, individual package resolutions, timing
155
+ const kept = lines.filter((l) => {
156
+ const t = l.trim();
157
+ if (!t)
158
+ return false;
159
+ if (t.startsWith('npm warn') || t.startsWith('npm error') || t.startsWith('npm ERR'))
160
+ return true;
161
+ if (t.match(/^added \d|^removed \d|^changed \d|^up to date/))
162
+ return true;
163
+ if (t.includes('vulnerabilit'))
164
+ return true;
165
+ if (t.startsWith('found '))
166
+ return true;
167
+ return false;
168
+ });
169
+ if (kept.length === 0)
170
+ return output; // Couldn't parse — return original
171
+ return kept.join('\n');
172
+ }
173
+ // ─── Test Output ────────────────────────────────────────────────────────────
174
+ function compressTestOutput(output) {
175
+ const lines = output.split('\n');
176
+ if (lines.length <= 30)
177
+ return output;
178
+ const result = [];
179
+ let inFailure = false;
180
+ let failureLines = 0;
181
+ const maxFailureLines = 20;
182
+ for (const line of lines) {
183
+ const t = line.trim();
184
+ // Always keep: summary lines, failures, errors
185
+ if (t.match(/^(Tests?|Test Suites?|FAIL|PASS|ERROR|✓|✗|✘|×|●|Ran \d|passed|failed|\d+ passing|\d+ failing|Test Files|Duration)/i)) {
186
+ result.push(line);
187
+ continue;
188
+ }
189
+ // Keep failure context (limited)
190
+ if (t.match(/^(FAIL|✗|✘|×|Error:|AssertionError|Expected|Received|at\s)/i) || inFailure) {
191
+ if (t.match(/^(FAIL|✗|✘|×)/i)) {
192
+ inFailure = true;
193
+ failureLines = 0;
194
+ }
195
+ failureLines++;
196
+ if (failureLines <= maxFailureLines) {
197
+ result.push(line);
198
+ }
199
+ if (t === '' || t.match(/^(PASS|✓)/)) {
200
+ inFailure = false;
201
+ }
202
+ continue;
203
+ }
204
+ // Strip: individual passing test names, progress, timing of each test
205
+ // (these are the bulk of test output — passing tests don't need names)
206
+ }
207
+ if (result.length < lines.length) {
208
+ const omitted = lines.length - result.length;
209
+ result.push(`(${String(omitted)} lines of passing test detail omitted)`);
210
+ }
211
+ return result.join('\n');
212
+ }
213
+ // ─── Lint Output ────────────────────────────────────────────────────────────
214
+ function compressLintOutput(output) {
215
+ const lines = output.split('\n');
216
+ if (lines.length <= 30)
217
+ return output;
218
+ // Group errors by rule, cap per rule
219
+ const errors = [];
220
+ const warnings = [];
221
+ const summaryLines = [];
222
+ const ruleCount = new Map();
223
+ const maxPerRule = 5;
224
+ for (const line of lines) {
225
+ const t = line.trim();
226
+ // Summary lines
227
+ if (t.match(/^✖|^\d+ problem|^\d+ error|^\d+ warning|^error:|^warning:/i)) {
228
+ summaryLines.push(line);
229
+ continue;
230
+ }
231
+ // ESLint-style: "file:line:col error message rule"
232
+ const ruleMatch = t.match(/\s+(error|warning)\s+.+\s+(\S+)$/);
233
+ if (ruleMatch) {
234
+ const rule = ruleMatch[2];
235
+ const count = (ruleCount.get(rule) ?? 0) + 1;
236
+ ruleCount.set(rule, count);
237
+ if (count <= maxPerRule) {
238
+ if (ruleMatch[1] === 'error')
239
+ errors.push(line);
240
+ else
241
+ warnings.push(line);
242
+ }
243
+ continue;
244
+ }
245
+ // TypeScript-style: "file(line,col): error TS..."
246
+ if (t.match(/error TS\d+/)) {
247
+ errors.push(line);
248
+ continue;
249
+ }
250
+ }
251
+ const result = [...errors, ...warnings.slice(0, 20)];
252
+ // Show rules that were capped
253
+ for (const [rule, count] of ruleCount) {
254
+ if (count > maxPerRule) {
255
+ result.push(` ... +${String(count - maxPerRule)} more ${rule}`);
256
+ }
257
+ }
258
+ result.push(...summaryLines);
259
+ if (result.length < lines.length) {
260
+ return result.join('\n');
261
+ }
262
+ return output;
263
+ }
264
+ // ─── Build Output ───────────────────────────────────────────────────────────
265
+ function compressBuildOutput(output) {
266
+ const lines = output.split('\n');
267
+ if (lines.length <= 20)
268
+ return output;
269
+ // Keep errors and summary, strip progress/compilation messages
270
+ const kept = lines.filter((l) => {
271
+ const t = l.trim();
272
+ if (!t)
273
+ return false;
274
+ if (t.match(/^(error|Error|ERROR|warning|Warning|WARN)/))
275
+ return true;
276
+ if (t.match(/^(✓|✗|✘|Done|Built|Compiled|Successfully|Failed|FAIL)/))
277
+ return true;
278
+ if (t.match(/error TS\d+/))
279
+ return true;
280
+ if (t.includes('bundle') && t.includes('kB'))
281
+ return true; // Bundle size info
282
+ return false;
283
+ });
284
+ if (kept.length > 0 && kept.length < lines.length * 0.7) {
285
+ const omitted = lines.length - kept.length;
286
+ kept.push(`(${String(omitted)} lines of build output omitted)`);
287
+ return kept.join('\n');
288
+ }
289
+ return output;
290
+ }
291
+ // ─── File List (ls, find, tree) ─────────────────────────────────────────────
292
+ function compressFileList(output) {
293
+ const lines = output.split('\n').filter((l) => l.trim());
294
+ if (lines.length <= 50)
295
+ return output;
296
+ // Group by top-level directory, cap entries
297
+ const groups = new Map();
298
+ for (const line of lines) {
299
+ const parts = line.replace(/^\.\//, '').split('/');
300
+ const dir = parts.length > 1 ? parts[0] : '.';
301
+ const arr = groups.get(dir) ?? [];
302
+ arr.push(line);
303
+ groups.set(dir, arr);
304
+ }
305
+ const maxPerDir = 10;
306
+ const result = [];
307
+ for (const [dir, files] of groups) {
308
+ if (dir !== '.')
309
+ result.push(`${dir}/`);
310
+ const shown = files.slice(0, maxPerDir);
311
+ for (const f of shown)
312
+ result.push(f);
313
+ if (files.length > maxPerDir) {
314
+ result.push(` ... +${String(files.length - maxPerDir)} more in ${dir}/`);
315
+ }
316
+ }
317
+ if (result.length < lines.length) {
318
+ result.push(`\n(${String(lines.length)} total entries, showing ${String(result.length)} lines)`);
319
+ }
320
+ return result.join('\n');
321
+ }
322
+ // ─── curl / wget ────────────────────────────────────────────────────────────
323
+ function compressCurlOutput(output) {
324
+ const lines = output.split('\n');
325
+ if (lines.length <= 30)
326
+ return output;
327
+ // Strip progress bars (curl -# output), keep headers and body
328
+ const kept = lines.filter((l) => {
329
+ const t = l.trim();
330
+ // Strip progress indicators
331
+ if (t.match(/^[#\s]*\d+(\.\d+)?%/) || t.match(/^\s*\d+\s+\d+\s+\d+\s+\d+/))
332
+ return false;
333
+ // Strip curl stats
334
+ if (t.match(/^\s*(Dload|Upload|Total|Spent|Left|Speed)/))
335
+ return false;
336
+ return true;
337
+ });
338
+ if (kept.length < lines.length) {
339
+ return kept.join('\n');
340
+ }
341
+ return output;
342
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Tool Output Compressors — Format-aware output compression
3
+ *
4
+ * Reduces token usage by 60-90% on common tool outputs.
5
+ * Understands output structure (git, npm, test runners, etc.)
6
+ * and strips noise while preserving signal.
7
+ *
8
+ * Runs as an AfterToolHook — transparent to agents.
9
+ * Inspired by rtk-ai/rtk.
10
+ */
11
+ import type { AfterToolHook } from '@compilr-dev/agents';
12
+ export interface CompressorConfig {
13
+ /** Minimum output length to trigger compression (default: 500) */
14
+ minLength?: number;
15
+ /** Enable/disable specific compressors */
16
+ disabled?: string[];
17
+ }
18
+ /**
19
+ * Create an AfterToolHook that compresses tool outputs.
20
+ *
21
+ * Usage:
22
+ * ```typescript
23
+ * const agent = createCompilrAgent({
24
+ * hooks: {
25
+ * afterTool: [createCompressorHook()],
26
+ * },
27
+ * });
28
+ * ```
29
+ */
30
+ export declare function createCompressorHook(config?: CompressorConfig): AfterToolHook;
31
+ export { compressBashOutput } from './bash.js';
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Tool Output Compressors — Format-aware output compression
3
+ *
4
+ * Reduces token usage by 60-90% on common tool outputs.
5
+ * Understands output structure (git, npm, test runners, etc.)
6
+ * and strips noise while preserving signal.
7
+ *
8
+ * Runs as an AfterToolHook — transparent to agents.
9
+ * Inspired by rtk-ai/rtk.
10
+ */
11
+ import { compressBashOutput } from './bash.js';
12
+ // ─── Hook Factory ───────────────────────────────────────────────────────────
13
+ /**
14
+ * Create an AfterToolHook that compresses tool outputs.
15
+ *
16
+ * Usage:
17
+ * ```typescript
18
+ * const agent = createCompilrAgent({
19
+ * hooks: {
20
+ * afterTool: [createCompressorHook()],
21
+ * },
22
+ * });
23
+ * ```
24
+ */
25
+ export function createCompressorHook(config) {
26
+ const minLength = config?.minLength ?? 500;
27
+ return (ctx) => {
28
+ if (!ctx.result.success)
29
+ return undefined;
30
+ const result = ctx.result.result;
31
+ if (!result || typeof result !== 'object')
32
+ return undefined;
33
+ const toolName = ctx.toolName;
34
+ // ── Bash tool ────────────────────────────────────────────────────
35
+ if (toolName === 'bash') {
36
+ const bashResult = result;
37
+ const stdout = bashResult.stdout;
38
+ if (!stdout || stdout.length < minLength)
39
+ return undefined;
40
+ // Get the command from tool input
41
+ const command = ctx.input.command ?? '';
42
+ const compressed = compressBashOutput(command, stdout);
43
+ if (compressed !== null && compressed.length < stdout.length) {
44
+ return {
45
+ result: {
46
+ ...ctx.result,
47
+ result: { ...bashResult, stdout: compressed },
48
+ },
49
+ };
50
+ }
51
+ }
52
+ // ── Grep tool ────────────────────────────────────────────────────
53
+ if (toolName === 'grep') {
54
+ const grepResult = result;
55
+ const content = grepResult.content;
56
+ if (content && typeof content === 'string' && content.length >= minLength) {
57
+ const compressed = compressGrepOutput(content);
58
+ if (compressed.length < content.length) {
59
+ return {
60
+ result: {
61
+ ...ctx.result,
62
+ result: { ...grepResult, content: compressed },
63
+ },
64
+ };
65
+ }
66
+ }
67
+ }
68
+ // ── Read file tool ───────────────────────────────────────────────
69
+ if (toolName === 'read_file') {
70
+ const fileResult = result;
71
+ const content = fileResult.content;
72
+ if (content && typeof content === 'string' && content.length >= minLength) {
73
+ const compressed = compressFileContent(content);
74
+ if (compressed.length < content.length) {
75
+ return {
76
+ result: {
77
+ ...ctx.result,
78
+ result: { ...fileResult, content: compressed },
79
+ },
80
+ };
81
+ }
82
+ }
83
+ }
84
+ return undefined;
85
+ };
86
+ }
87
+ // ─── Grep Compressor ────────────────────────────────────────────────────────
88
+ /**
89
+ * Compress grep output: deduplicate, cap matches per file.
90
+ */
91
+ function compressGrepOutput(content) {
92
+ const lines = content.split('\n');
93
+ if (lines.length <= 50)
94
+ return content;
95
+ // Group by file
96
+ const groups = new Map();
97
+ for (const line of lines) {
98
+ const colonIdx = line.indexOf(':');
99
+ if (colonIdx > 0) {
100
+ const file = line.substring(0, colonIdx);
101
+ const rest = line.substring(colonIdx + 1);
102
+ const arr = groups.get(file) ?? [];
103
+ arr.push(rest);
104
+ groups.set(file, arr);
105
+ }
106
+ }
107
+ // Cap at 10 matches per file
108
+ const maxPerFile = 10;
109
+ const compressed = [];
110
+ for (const [file, matches] of groups) {
111
+ const shown = matches.slice(0, maxPerFile);
112
+ for (const m of shown) {
113
+ compressed.push(`${file}:${m}`);
114
+ }
115
+ if (matches.length > maxPerFile) {
116
+ compressed.push(` ... +${String(matches.length - maxPerFile)} more matches in ${file}`);
117
+ }
118
+ }
119
+ return compressed.join('\n');
120
+ }
121
+ // ─── File Content Compressor ────────────────────────────────────────────────
122
+ /**
123
+ * Compress file content: collapse consecutive blank lines, large comment blocks.
124
+ */
125
+ function compressFileContent(content) {
126
+ const lines = content.split('\n');
127
+ if (lines.length <= 100)
128
+ return content;
129
+ const result = [];
130
+ let consecutiveBlanks = 0;
131
+ let inBlockComment = false;
132
+ let blockCommentLines = 0;
133
+ for (const line of lines) {
134
+ const trimmed = line.trim();
135
+ // Collapse consecutive blank lines
136
+ if (trimmed === '') {
137
+ consecutiveBlanks++;
138
+ if (consecutiveBlanks <= 1)
139
+ result.push(line);
140
+ continue;
141
+ }
142
+ consecutiveBlanks = 0;
143
+ // Collapse large block comments (keep first and last line)
144
+ if (trimmed.startsWith('/*') && !trimmed.endsWith('*/')) {
145
+ inBlockComment = true;
146
+ blockCommentLines = 0;
147
+ result.push(line);
148
+ continue;
149
+ }
150
+ if (inBlockComment) {
151
+ blockCommentLines++;
152
+ if (trimmed.endsWith('*/') || trimmed === '*/') {
153
+ if (blockCommentLines > 5) {
154
+ result.push(` // ... ${String(blockCommentLines - 1)} comment lines omitted`);
155
+ }
156
+ result.push(line);
157
+ inBlockComment = false;
158
+ }
159
+ else if (blockCommentLines <= 3) {
160
+ result.push(line);
161
+ }
162
+ continue;
163
+ }
164
+ result.push(line);
165
+ }
166
+ return result.join('\n');
167
+ }
168
+ // ─── Re-exports ─────────────────────────────────────────────────────────────
169
+ export { compressBashOutput } from './bash.js';
package/dist/config.d.ts CHANGED
@@ -245,6 +245,45 @@ export interface CompilrAgentConfig {
245
245
  action: string;
246
246
  reason?: string;
247
247
  }) => void;
248
+ /**
249
+ * Event handler for monitoring agent execution.
250
+ * Called alongside the SDK's internal usage tracking — both run on every event.
251
+ */
252
+ onEvent?: (event: AgentEvent) => void;
253
+ /**
254
+ * Enable file access tracking for context restoration hints.
255
+ * When enabled, the agent tracks which files were read, referenced, and modified.
256
+ * After context compaction, hints are injected to help the LLM understand
257
+ * what files it previously accessed.
258
+ * Default: true when contextManager is created (i.e., almost always).
259
+ */
260
+ enableFileTracking?: boolean;
261
+ /**
262
+ * Pin/anchor configuration. Controls the AnchorManager for critical information
263
+ * that survives context compaction.
264
+ */
265
+ pins?: {
266
+ /** Maximum number of pins. Default: 50 */
267
+ maxAnchors?: number;
268
+ /** Maximum total tokens for pins. Default: 4000 */
269
+ maxTokens?: number;
270
+ /** Include built-in safety pins. Default: true */
271
+ includeDefaults?: boolean;
272
+ };
273
+ /**
274
+ * Callback invoked when the agent reaches its iteration limit.
275
+ * Return a positive number to extend by that many iterations, or false to stop.
276
+ */
277
+ onIterationLimitReached?: (context: {
278
+ iteration: number;
279
+ maxIterations: number;
280
+ toolCallCount: number;
281
+ }) => Promise<number | false>;
282
+ /**
283
+ * Behavior when max iterations is reached and no onIterationLimitReached callback
284
+ * is provided. Default: 'error'.
285
+ */
286
+ iterationLimitBehavior?: 'error' | 'summarize' | 'continue';
248
287
  /**
249
288
  * Dynamic capability loading configuration.
250
289
  * When enabled, tools are loaded on-demand for token efficiency.
package/dist/index.d.ts CHANGED
@@ -79,3 +79,5 @@ export { readFileTool, writeFileTool, createBashTool, bashTool, bashOutputTool,
79
79
  export { gitStatusTool, gitDiffTool, gitLogTool, gitCommitTool, gitBranchTool, gitStashTool, gitBlameTool, gitFileHistoryTool, detectProjectTool, findProjectRootTool, runTestsTool, runLintTool, runBuildTool, runFormatTool, findDefinitionTool, findReferencesTool, findTodosTool, checkOutdatedTool, findVulnerabilitiesTool, analyzeTestCoverageTool, getFileStructureTool, getComplexityTool, allCodingTools, unifiedTools, } from '@compilr-dev/agents-coding';
80
80
  export { createLogger, createSilentLogger } from '@compilr-dev/logger';
81
81
  export type { Logger, LoggerOptions, LogLevel } from '@compilr-dev/logger';
82
+ export { createCompressorHook, compressBashOutput } from './compressors/index.js';
83
+ export type { CompressorConfig } from './compressors/index.js';
package/dist/index.js CHANGED
@@ -225,3 +225,7 @@ allCodingTools, unifiedTools, } from '@compilr-dev/agents-coding';
225
225
  // Logger (re-export for consumers)
226
226
  // =============================================================================
227
227
  export { createLogger, createSilentLogger } from '@compilr-dev/logger';
228
+ // =============================================================================
229
+ // Tool Output Compressors
230
+ // =============================================================================
231
+ export { createCompressorHook, compressBashOutput } from './compressors/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/sdk",
3
- "version": "0.7.32",
3
+ "version": "0.9.0",
4
4
  "description": "Universal agent runtime for building AI-powered applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",