lynkr 8.0.0 → 9.0.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.
Files changed (128) hide show
  1. package/.lynkr/telemetry.db +0 -0
  2. package/.lynkr/telemetry.db-shm +0 -0
  3. package/.lynkr/telemetry.db-wal +0 -0
  4. package/README.md +196 -322
  5. package/lynkr-skill.tar.gz +0 -0
  6. package/package.json +4 -3
  7. package/src/api/openai-router.js +64 -13
  8. package/src/api/providers-handler.js +171 -3
  9. package/src/api/router.js +9 -2
  10. package/src/clients/circuit-breaker.js +10 -247
  11. package/src/clients/codex-process.js +342 -0
  12. package/src/clients/codex-utils.js +143 -0
  13. package/src/clients/databricks.js +210 -63
  14. package/src/clients/resilience.js +540 -0
  15. package/src/clients/retry.js +22 -167
  16. package/src/clients/standard-tools.js +23 -0
  17. package/src/config/index.js +77 -0
  18. package/src/context/compression.js +42 -9
  19. package/src/context/distill.js +492 -0
  20. package/src/orchestrator/index.js +48 -8
  21. package/src/routing/complexity-analyzer.js +258 -5
  22. package/src/routing/index.js +12 -2
  23. package/src/routing/latency-tracker.js +148 -0
  24. package/src/routing/model-tiers.js +2 -0
  25. package/src/routing/quality-scorer.js +113 -0
  26. package/src/routing/telemetry.js +464 -0
  27. package/src/server.js +13 -12
  28. package/src/tools/code-graph.js +538 -0
  29. package/src/tools/code-mode.js +304 -0
  30. package/src/tools/index.js +4 -0
  31. package/src/tools/lazy-loader.js +18 -0
  32. package/src/tools/mcp-remote.js +7 -0
  33. package/src/tools/smart-selection.js +11 -0
  34. package/src/tools/tinyfish.js +358 -0
  35. package/src/tools/truncate.js +1 -0
  36. package/src/utils/payload.js +206 -0
  37. package/src/utils/perf-timer.js +80 -0
  38. package/.github/FUNDING.yml +0 -15
  39. package/.github/workflows/README.md +0 -215
  40. package/.github/workflows/ci.yml +0 -69
  41. package/.github/workflows/index.yml +0 -62
  42. package/.github/workflows/web-tools-tests.yml +0 -56
  43. package/CITATIONS.bib +0 -6
  44. package/DEPLOYMENT.md +0 -1001
  45. package/LYNKR-TUI-PLAN.md +0 -984
  46. package/PERFORMANCE-REPORT.md +0 -866
  47. package/PLAN-per-client-model-routing.md +0 -252
  48. package/docs/42642f749da6234f41b6b425c3bb07c9.txt +0 -1
  49. package/docs/BingSiteAuth.xml +0 -4
  50. package/docs/docs-style.css +0 -478
  51. package/docs/docs.html +0 -198
  52. package/docs/google5be250e608e6da39.html +0 -1
  53. package/docs/index.html +0 -577
  54. package/docs/index.md +0 -584
  55. package/docs/robots.txt +0 -4
  56. package/docs/sitemap.xml +0 -44
  57. package/docs/style.css +0 -1223
  58. package/docs/toon-integration-spec.md +0 -130
  59. package/documentation/README.md +0 -101
  60. package/documentation/api.md +0 -806
  61. package/documentation/claude-code-cli.md +0 -679
  62. package/documentation/codex-cli.md +0 -397
  63. package/documentation/contributing.md +0 -571
  64. package/documentation/cursor-integration.md +0 -734
  65. package/documentation/docker.md +0 -874
  66. package/documentation/embeddings.md +0 -762
  67. package/documentation/faq.md +0 -713
  68. package/documentation/features.md +0 -403
  69. package/documentation/headroom.md +0 -519
  70. package/documentation/installation.md +0 -758
  71. package/documentation/memory-system.md +0 -476
  72. package/documentation/production.md +0 -636
  73. package/documentation/providers.md +0 -1009
  74. package/documentation/routing.md +0 -476
  75. package/documentation/testing.md +0 -629
  76. package/documentation/token-optimization.md +0 -325
  77. package/documentation/tools.md +0 -697
  78. package/documentation/troubleshooting.md +0 -969
  79. package/final-test.js +0 -33
  80. package/headroom-sidecar/config.py +0 -93
  81. package/headroom-sidecar/requirements.txt +0 -14
  82. package/headroom-sidecar/server.py +0 -451
  83. package/monitor-agents.sh +0 -31
  84. package/scripts/audit-log-reader.js +0 -399
  85. package/scripts/compact-dictionary.js +0 -204
  86. package/scripts/test-deduplication.js +0 -448
  87. package/src/db/database.sqlite +0 -0
  88. package/te +0 -11622
  89. package/test/README.md +0 -212
  90. package/test/azure-openai-config.test.js +0 -213
  91. package/test/azure-openai-error-resilience.test.js +0 -238
  92. package/test/azure-openai-format-conversion.test.js +0 -354
  93. package/test/azure-openai-integration.test.js +0 -287
  94. package/test/azure-openai-routing.test.js +0 -175
  95. package/test/azure-openai-streaming.test.js +0 -171
  96. package/test/bedrock-integration.test.js +0 -457
  97. package/test/comprehensive-test-suite.js +0 -928
  98. package/test/config-validation.test.js +0 -207
  99. package/test/cursor-integration.test.js +0 -484
  100. package/test/format-conversion.test.js +0 -578
  101. package/test/hybrid-routing-integration.test.js +0 -269
  102. package/test/hybrid-routing-performance.test.js +0 -428
  103. package/test/llamacpp-integration.test.js +0 -882
  104. package/test/lmstudio-integration.test.js +0 -347
  105. package/test/memory/extractor.test.js +0 -398
  106. package/test/memory/retriever.test.js +0 -613
  107. package/test/memory/retriever.test.js.bak +0 -585
  108. package/test/memory/search.test.js +0 -537
  109. package/test/memory/search.test.js.bak +0 -389
  110. package/test/memory/store.test.js +0 -344
  111. package/test/memory/store.test.js.bak +0 -312
  112. package/test/memory/surprise.test.js +0 -300
  113. package/test/memory-performance.test.js +0 -472
  114. package/test/openai-integration.test.js +0 -683
  115. package/test/openrouter-error-resilience.test.js +0 -418
  116. package/test/passthrough-mode.test.js +0 -385
  117. package/test/performance-benchmark.js +0 -351
  118. package/test/performance-tests.js +0 -528
  119. package/test/routing.test.js +0 -225
  120. package/test/toon-compression.test.js +0 -131
  121. package/test/web-tools.test.js +0 -329
  122. package/test-agents-simple.js +0 -43
  123. package/test-cli-connection.sh +0 -33
  124. package/test-learning-unit.js +0 -126
  125. package/test-learning.js +0 -112
  126. package/test-parallel-agents.sh +0 -124
  127. package/test-parallel-direct.js +0 -155
  128. package/test-subagents.sh +0 -117
@@ -1,200 +1,55 @@
1
- const logger = require("../logger");
2
-
3
1
  /**
4
- * Retry configuration for API calls
2
+ * Retry logic for API calls — backed by Cockatiel
3
+ *
4
+ * This module re-exports the Cockatiel-backed retry adapter from resilience.js
5
+ * while preserving all original exports for consumers.
5
6
  */
7
+ const { withCockatielRetry, DEFAULT_RETRY_CONFIG } = require("./resilience");
8
+
6
9
  const DEFAULT_CONFIG = {
7
- maxRetries: 3,
8
- initialDelay: 1000, // 1 second
9
- maxDelay: 30000, // 30 seconds
10
- backoffMultiplier: 2,
11
- jitterFactor: 0.1, // 10% jitter
12
- retryableStatuses: [429, 500, 502, 503, 504],
13
- retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'ENETUNREACH', 'ECONNREFUSED'],
10
+ maxRetries: DEFAULT_RETRY_CONFIG.maxRetries,
11
+ initialDelay: DEFAULT_RETRY_CONFIG.initialDelay,
12
+ maxDelay: DEFAULT_RETRY_CONFIG.maxDelay,
13
+ backoffMultiplier: DEFAULT_RETRY_CONFIG.backoffMultiplier,
14
+ jitterFactor: DEFAULT_RETRY_CONFIG.jitterFactor,
15
+ retryableStatuses: DEFAULT_RETRY_CONFIG.retryableStatuses,
16
+ retryableErrors: DEFAULT_RETRY_CONFIG.retryableErrors,
14
17
  };
15
18
 
16
19
  /**
17
- * Add jitter to prevent thundering herd
18
- */
19
- function addJitter(delay, jitterFactor) {
20
- const jitter = delay * jitterFactor * (Math.random() * 2 - 1);
21
- return Math.max(0, delay + jitter);
22
- }
23
-
24
- /**
25
- * Calculate delay with exponential backoff
20
+ * Calculate delay with exponential backoff (preserved for any direct callers)
26
21
  */
27
22
  function calculateDelay(attempt, config) {
28
23
  const baseDelay = config.initialDelay * Math.pow(config.backoffMultiplier, attempt);
29
24
  const cappedDelay = Math.min(baseDelay, config.maxDelay);
30
- return addJitter(cappedDelay, config.jitterFactor);
25
+ const jitter = cappedDelay * config.jitterFactor * (Math.random() * 2 - 1);
26
+ return Math.max(0, cappedDelay + jitter);
31
27
  }
32
28
 
33
29
  /**
34
- * Check if error/response is retryable
30
+ * Check if error/response is retryable (preserved for any direct callers)
35
31
  */
36
32
  function isRetryable(error, response, config) {
37
- // Check response status codes
38
33
  if (response && config.retryableStatuses.includes(response.status)) {
39
34
  return true;
40
35
  }
41
-
42
- // Check error codes
43
36
  if (error && error.code && config.retryableErrors.includes(error.code)) {
44
37
  return true;
45
38
  }
46
-
47
- // Check nested cause (Node undici wraps connection errors as TypeError)
48
39
  if (error && error.cause?.code && config.retryableErrors.includes(error.cause.code)) {
49
40
  return true;
50
41
  }
51
-
52
- // Check for network errors
53
- if (error && (error.name === 'FetchError' || error.name === 'AbortError')) {
42
+ if (error && (error.name === "FetchError" || error.name === "AbortError")) {
54
43
  return true;
55
44
  }
56
-
57
45
  return false;
58
46
  }
59
47
 
60
48
  /**
61
- * Detect if this is a cold start (longer than expected response time)
49
+ * Detect if this is a cold start
62
50
  */
63
51
  function detectColdStart(startTime, endTime, threshold = 5000) {
64
- const duration = endTime - startTime;
65
- return duration > threshold;
66
- }
67
-
68
- /**
69
- * Execute function with retry logic
70
- */
71
- async function withRetry(fn, options = {}) {
72
- const config = { ...DEFAULT_CONFIG, ...options };
73
- let lastError;
74
- let lastResponse;
75
-
76
- for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
77
- const startTime = Date.now();
78
-
79
- try {
80
- const result = await fn(attempt);
81
- const endTime = Date.now();
82
-
83
- // Detect cold starts for monitoring
84
- if (detectColdStart(startTime, endTime)) {
85
- logger.warn({
86
- attempt,
87
- duration: endTime - startTime,
88
- }, 'Potential cold start detected');
89
- }
90
-
91
- // Check if response indicates we should retry
92
- if (result && isRetryable(null, result, config) && attempt < config.maxRetries) {
93
- lastResponse = result;
94
-
95
- // Special handling for 429 (rate limiting)
96
- if (result.status === 429) {
97
- // Check for Retry-After header
98
- const retryAfter = result.headers?.get?.('retry-after');
99
- let delay;
100
-
101
- if (retryAfter) {
102
- // Retry-After can be in seconds or a date
103
- const retryAfterNum = parseInt(retryAfter, 10);
104
- if (!isNaN(retryAfterNum)) {
105
- delay = retryAfterNum * 1000; // Convert to ms
106
- } else {
107
- const retryAfterDate = new Date(retryAfter);
108
- delay = retryAfterDate.getTime() - Date.now();
109
- }
110
- } else {
111
- // Use exponential backoff with longer delays for rate limiting
112
- delay = calculateDelay(attempt, {
113
- ...config,
114
- initialDelay: 2000, // Start at 2s for rate limits
115
- maxDelay: 60000, // Up to 1 minute
116
- });
117
- }
118
-
119
- logger.warn({
120
- attempt,
121
- delay,
122
- retryAfter: retryAfter || 'not specified',
123
- }, 'Rate limited (429), retrying after delay');
124
-
125
- await sleep(delay);
126
- continue;
127
- }
128
-
129
- // Regular retry with exponential backoff
130
- const delay = calculateDelay(attempt, config);
131
- logger.warn({
132
- attempt,
133
- status: result.status,
134
- delay,
135
- }, 'Request failed, retrying with backoff');
136
-
137
- await sleep(delay);
138
- continue;
139
- }
140
-
141
- // Success or non-retryable error
142
- return result;
143
-
144
- } catch (error) {
145
- lastError = error;
146
- const endTime = Date.now();
147
-
148
- // Check if cold start
149
- if (detectColdStart(startTime, endTime)) {
150
- logger.warn({
151
- attempt,
152
- duration: endTime - startTime,
153
- error: error.message,
154
- }, 'Potential cold start with error detected');
155
- }
156
-
157
- // Check if we should retry
158
- if (isRetryable(error, null, config) && attempt < config.maxRetries) {
159
- const delay = calculateDelay(attempt, config);
160
- logger.warn({
161
- attempt,
162
- error: error.message,
163
- code: error.code,
164
- delay,
165
- }, 'Request error, retrying with backoff');
166
-
167
- await sleep(delay);
168
- continue;
169
- }
170
-
171
- // Not retryable or out of retries
172
- throw error;
173
- }
174
- }
175
-
176
- // Max retries exceeded
177
- if (lastError) {
178
- lastError.message = `Max retries (${config.maxRetries}) exceeded: ${lastError.message}`;
179
- throw lastError;
180
- }
181
-
182
- if (lastResponse) {
183
- logger.error({
184
- status: lastResponse.status,
185
- maxRetries: config.maxRetries,
186
- }, 'Max retries exceeded');
187
- return lastResponse;
188
- }
189
-
190
- throw new Error('Retry logic failed unexpectedly');
191
- }
192
-
193
- /**
194
- * Sleep helper
195
- */
196
- function sleep(ms) {
197
- return new Promise(resolve => setTimeout(resolve, ms));
52
+ return (endTime - startTime) > threshold;
198
53
  }
199
54
 
200
55
  /**
@@ -202,12 +57,12 @@ function sleep(ms) {
202
57
  */
203
58
  function createRetryWrapper(fn, defaultOptions = {}) {
204
59
  return async function (...args) {
205
- return withRetry(() => fn(...args), defaultOptions);
60
+ return withCockatielRetry(() => fn(...args), defaultOptions);
206
61
  };
207
62
  }
208
63
 
209
64
  module.exports = {
210
- withRetry,
65
+ withRetry: withCockatielRetry,
211
66
  createRetryWrapper,
212
67
  calculateDelay,
213
68
  isRetryable,
@@ -380,6 +380,29 @@ EXAMPLE: User says "explore this project" → Call Task with subagent_type="Expl
380
380
  required: ["url", "prompt"]
381
381
  }
382
382
  },
383
+ {
384
+ name: "WebAgent",
385
+ description: "Launches a browser agent to navigate a website and accomplish a goal. Use when you need to interact with dynamic web content (click buttons, fill forms, extract data from JS-rendered pages) beyond what a simple HTTP fetch can do. Returns structured JSON. Takes 10-60 seconds.",
386
+ input_schema: {
387
+ type: "object",
388
+ properties: {
389
+ url: {
390
+ type: "string",
391
+ description: "Target URL to navigate to"
392
+ },
393
+ goal: {
394
+ type: "string",
395
+ description: "What to accomplish on the page. Be specific about what data to extract or actions to take."
396
+ },
397
+ browser_profile: {
398
+ type: "string",
399
+ enum: ["lite", "stealth"],
400
+ description: "lite (default, faster) or stealth (for bot-protected sites)"
401
+ }
402
+ },
403
+ required: ["url", "goal"]
404
+ }
405
+ },
383
406
  {
384
407
  name: "NotebookEdit",
385
408
  description: "Completely replaces the contents of a specific cell in a Jupyter notebook (.ipynb file). Use for editing interactive documents that combine code, text, and visualizations.",
@@ -397,6 +397,14 @@ const webFetchBodyPreviewMax = Number.parseInt(process.env.WEB_FETCH_BODY_PREVIE
397
397
  const webSearchRetryEnabled = process.env.WEB_SEARCH_RETRY_ENABLED !== "false"; // default true
398
398
  const webSearchMaxRetries = Number.parseInt(process.env.WEB_SEARCH_MAX_RETRIES ?? "2", 10);
399
399
 
400
+ // TinyFish AI Browser Automation configuration
401
+ const tinyfishApiKey = process.env.TINYFISH_API_KEY?.trim() || null;
402
+ const tinyfishEndpoint = process.env.TINYFISH_ENDPOINT?.trim() || "https://agent.tinyfish.ai/v1/automation/run-sse";
403
+ const tinyfishBrowserProfile = process.env.TINYFISH_BROWSER_PROFILE?.trim() || "lite";
404
+ const tinyfishTimeoutMs = parseInt(process.env.TINYFISH_TIMEOUT_MS ?? "120000", 10);
405
+ const tinyfishProxyEnabled = process.env.TINYFISH_PROXY_ENABLED === "true";
406
+ const tinyfishProxyCountry = process.env.TINYFISH_PROXY_COUNTRY?.trim() || "US";
407
+
400
408
  const policyMaxSteps = Number.parseInt(process.env.POLICY_MAX_STEPS ?? "8", 10);
401
409
  const policyMaxToolCalls = Number.parseInt(process.env.POLICY_MAX_TOOL_CALLS ?? "12", 10);
402
410
  const policyToolLoopThreshold = Number.parseInt(process.env.POLICY_TOOL_LOOP_THRESHOLD ?? "10", 10);
@@ -610,6 +618,12 @@ var config = {
610
618
  endpoint: moonshotEndpoint,
611
619
  model: moonshotModel,
612
620
  },
621
+ codex: {
622
+ enabled: process.env.CODEX_ENABLED !== "false",
623
+ binaryPath: process.env.CODEX_BINARY_PATH?.trim() || null,
624
+ model: process.env.CODEX_MODEL?.trim() || "gpt-5.3-codex",
625
+ timeout: Number.parseInt(process.env.CODEX_TIMEOUT || "120000", 10) || 120000,
626
+ },
613
627
  hotReload: {
614
628
  enabled: hotReloadEnabled,
615
629
  debounceMs: Number.isNaN(hotReloadDebounceMs) ? 1000 : hotReloadDebounceMs,
@@ -660,6 +674,14 @@ var config = {
660
674
  retryEnabled: webSearchRetryEnabled,
661
675
  maxRetries: Number.isNaN(webSearchMaxRetries) ? 2 : webSearchMaxRetries,
662
676
  },
677
+ tinyfish: {
678
+ apiKey: tinyfishApiKey,
679
+ endpoint: tinyfishEndpoint,
680
+ browserProfile: tinyfishBrowserProfile,
681
+ timeoutMs: Number.isNaN(tinyfishTimeoutMs) ? 120000 : tinyfishTimeoutMs,
682
+ proxyEnabled: tinyfishProxyEnabled,
683
+ proxyCountry: tinyfishProxyCountry,
684
+ },
663
685
  policy: {
664
686
  maxStepsPerTurn: Number.isNaN(policyMaxSteps) ? 8 : policyMaxSteps,
665
687
  maxToolCallsPerTurn: Number.isNaN(policyMaxToolCalls) ? 12 : policyMaxToolCalls,
@@ -717,6 +739,10 @@ var config = {
717
739
  manifestPath: sandboxManifestPath,
718
740
  manifestDirs: sandboxManifestDirs,
719
741
  },
742
+ codeMode: {
743
+ enabled: process.env.CODE_MODE_ENABLED === 'true',
744
+ toolListCacheTtl: parseInt(process.env.CODE_MODE_CACHE_TTL, 10) || 60_000,
745
+ },
720
746
  },
721
747
  promptCache: {
722
748
  enabled: promptCacheEnabled,
@@ -902,6 +928,25 @@ var config = {
902
928
  COMPLEX: process.env.TIER_COMPLEX?.trim() || null,
903
929
  REASONING: process.env.TIER_REASONING?.trim() || null,
904
930
  },
931
+
932
+ // Graphify knowledge graph integration (structural analysis)
933
+ codeGraph: {
934
+ enabled: process.env.CODE_GRAPH_ENABLED === 'true',
935
+ command: process.env.CODE_GRAPH_COMMAND || 'graphify',
936
+ workspace: process.env.CODE_GRAPH_WORKSPACE || process.cwd(),
937
+ timeout: parseInt(process.env.CODE_GRAPH_TIMEOUT, 10) || 10000,
938
+ },
939
+
940
+ // Large payload optimization (skip cloning media blocks that get discarded)
941
+ largePayload: {
942
+ enabled: process.env.LARGE_PAYLOAD_OPTIMIZATION !== 'false',
943
+ threshold: parseInt(process.env.LARGE_PAYLOAD_THRESHOLD, 10) || 1_048_576,
944
+ },
945
+
946
+ // OpenClaw integration
947
+ openclaw: {
948
+ enabled: process.env.OPENCLAW_MODE === "true",
949
+ },
905
950
  };
906
951
 
907
952
  /**
@@ -938,15 +983,47 @@ function reloadConfig() {
938
983
  config.modelProvider.fallbackProvider = (process.env.FALLBACK_PROVIDER ?? "databricks").toLowerCase();
939
984
  config.modelProvider.suggestionModeModel = (process.env.SUGGESTION_MODE_MODEL ?? "default").trim();
940
985
 
986
+ // TinyFish config reload
987
+ config.tinyfish.apiKey = process.env.TINYFISH_API_KEY?.trim() || null;
988
+ config.tinyfish.browserProfile = process.env.TINYFISH_BROWSER_PROFILE?.trim() || "lite";
989
+
941
990
  config.toon.enabled = process.env.TOON_ENABLED === "true";
942
991
  const newToonMinBytes = Number.parseInt(process.env.TOON_MIN_BYTES ?? "4096", 10);
943
992
  config.toon.minBytes = Number.isNaN(newToonMinBytes) ? 4096 : newToonMinBytes;
944
993
  config.toon.failOpen = process.env.TOON_FAIL_OPEN !== "false";
945
994
  config.toon.logStats = process.env.TOON_LOG_STATS !== "false";
946
995
 
996
+ // Tier routing (critical for fixing model name issues without restart)
997
+ config.modelTiers.SIMPLE = process.env.TIER_SIMPLE?.trim() || null;
998
+ config.modelTiers.MEDIUM = process.env.TIER_MEDIUM?.trim() || null;
999
+ config.modelTiers.COMPLEX = process.env.TIER_COMPLEX?.trim() || null;
1000
+ config.modelTiers.REASONING = process.env.TIER_REASONING?.trim() || null;
1001
+ config.modelTiers.enabled = !!(config.modelTiers.SIMPLE && config.modelTiers.MEDIUM && config.modelTiers.COMPLEX && config.modelTiers.REASONING);
1002
+
1003
+ // Ollama model
1004
+ config.ollama.endpoint = process.env.OLLAMA_ENDPOINT ?? config.ollama.endpoint;
1005
+
1006
+ // OpenClaw
1007
+ config.openclaw.enabled = process.env.OPENCLAW_MODE === "true";
1008
+
1009
+ // Graphify
1010
+ config.codeGraph.enabled = process.env.CODE_GRAPH_ENABLED === 'true';
1011
+
1012
+ // Code Mode
1013
+ config.mcp.codeMode.enabled = process.env.CODE_MODE_ENABLED === 'true';
1014
+
947
1015
  // Log level
948
1016
  config.logger.level = process.env.LOG_LEVEL ?? "info";
949
1017
 
1018
+ // Reset circuit breakers so stale OPEN states don't persist
1019
+ try {
1020
+ const { getCircuitBreakerRegistry } = require('../clients/circuit-breaker');
1021
+ getCircuitBreakerRegistry().resetAll();
1022
+ console.log("[CONFIG] Circuit breakers reset");
1023
+ } catch (e) {
1024
+ // Ignore if not yet initialized
1025
+ }
1026
+
950
1027
  console.log("[CONFIG] Configuration reloaded from environment");
951
1028
  return config;
952
1029
  }
@@ -11,6 +11,7 @@
11
11
 
12
12
  const logger = require('../logger');
13
13
  const config = require('../config');
14
+ const distill = require('./distill');
14
15
 
15
16
  /**
16
17
  * Compress conversation history to fit within token budget
@@ -62,6 +63,18 @@ function compressHistory(messages, options = {}) {
62
63
  compressed = oldMessages.map(msg => compressMessage(msg));
63
64
  }
64
65
 
66
+ // Apply Distill dedup across all old messages to collapse repetitive tool results
67
+ if (compressed.length > 0) {
68
+ const dedupResult = distill.deduplicateHistory(compressed);
69
+ if (dedupResult.stats.deduplicated > 0) {
70
+ compressed = dedupResult.messages;
71
+ logger.debug({
72
+ checked: dedupResult.stats.checked,
73
+ deduplicated: dedupResult.stats.deduplicated,
74
+ }, '[Distill] History dedup applied to old messages');
75
+ }
76
+ }
77
+
65
78
  // Add recent messages (may compress tool results but keep content)
66
79
  const recentCompressed = recentMessages.map(msg => compressToolResults(msg));
67
80
 
@@ -248,12 +261,15 @@ function compressContentBlock(block) {
248
261
  * Compress tool result block
249
262
  *
250
263
  * Tool results can be very large (file contents, bash output).
251
- * Compress while preserving essential information.
264
+ * Uses Distill algorithms (normalization, delta, dedup) before
265
+ * falling back to head/tail truncation.
252
266
  *
253
267
  * @param {Object} block - tool_result block
268
+ * @param {Object} options - Options for compression
269
+ * @param {string} options.previousResult - Previous tool result for delta rendering
254
270
  * @returns {Object} Compressed tool_result
255
271
  */
256
- function compressToolResultBlock(block) {
272
+ function compressToolResultBlock(block, options = {}) {
257
273
  if (!block || block.type !== 'tool_result') return block;
258
274
 
259
275
  const compressed = {
@@ -261,18 +277,32 @@ function compressToolResultBlock(block) {
261
277
  tool_use_id: block.tool_use_id,
262
278
  };
263
279
 
264
- // Compress content
280
+ // Compress content using Distill when content is large enough to benefit
265
281
  if (typeof block.content === 'string') {
266
- compressed.content = compressText(block.content, 500);
282
+ if (block.content.length > 500) {
283
+ const result = distill.compressToolResult(block.content, {
284
+ previousResult: options.previousResult,
285
+ maxLength: 500,
286
+ });
287
+ compressed.content = result.text;
288
+ } else {
289
+ compressed.content = block.content;
290
+ }
267
291
  } else if (Array.isArray(block.content)) {
268
292
  compressed.content = block.content.map(item => {
269
293
  if (typeof item === 'string') {
270
- return compressText(item, 500);
294
+ if (item.length > 500) {
295
+ return distill.compressToolResult(item, { maxLength: 500 }).text;
296
+ }
297
+ return item;
271
298
  } else if (item.type === 'text') {
272
- return {
273
- type: 'text',
274
- text: compressText(item.text, 500)
275
- };
299
+ if (item.text && item.text.length > 500) {
300
+ return {
301
+ type: 'text',
302
+ text: distill.compressToolResult(item.text, { maxLength: 500 }).text,
303
+ };
304
+ }
305
+ return item;
276
306
  }
277
307
  return item;
278
308
  });
@@ -453,7 +483,10 @@ module.exports = {
453
483
  compressHistory,
454
484
  compressMessage,
455
485
  compressToolResults,
486
+ compressToolResultBlock,
456
487
  calculateCompressionStats,
457
488
  needsCompression,
458
489
  summarizeOldHistory,
490
+ // Distill re-exports for direct access
491
+ distill,
459
492
  };