@tyvm/knowhow 0.0.56 → 0.0.59

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 (190) hide show
  1. package/package.json +3 -3
  2. package/src/agents/base/base.ts +87 -43
  3. package/src/agents/tools/execCommand.ts +17 -14
  4. package/src/agents/tools/googleSearch.ts +1 -0
  5. package/src/agents/tools/index.ts +1 -0
  6. package/src/agents/tools/lazy/definitions.ts +63 -0
  7. package/src/agents/tools/lazy/disableTools.ts +16 -0
  8. package/src/agents/tools/lazy/enableTools.ts +16 -0
  9. package/src/agents/tools/lazy/index.ts +3 -0
  10. package/src/agents/tools/lazy/listAvailableTools.ts +14 -0
  11. package/src/agents/tools/list.ts +2 -0
  12. package/src/agents/tools/mcp/connectMcpServer.ts +40 -0
  13. package/src/agents/tools/mcp/definitions.ts +67 -0
  14. package/src/agents/tools/mcp/disconnectMcpServer.ts +40 -0
  15. package/src/agents/tools/mcp/index.ts +3 -0
  16. package/src/agents/tools/mcp/listAvailableMcpServers.ts +28 -0
  17. package/src/agents/tools/writeFile.ts +4 -1
  18. package/src/chat/CliChatService.ts +8 -3
  19. package/src/chat/modules/AgentModule.ts +74 -296
  20. package/src/cli.ts +33 -10
  21. package/src/plugins/GitPlugin.ts +30 -24
  22. package/src/plugins/language.ts +95 -18
  23. package/src/processors/ToolResponseCache.ts +98 -79
  24. package/src/processors/tools/grepToolResponse.ts +99 -0
  25. package/src/processors/tools/index.ts +21 -0
  26. package/src/processors/tools/jqToolResponse.ts +124 -0
  27. package/src/processors/tools/listStoredToolResponses.ts +83 -0
  28. package/src/processors/tools/tailToolResponse.ts +75 -0
  29. package/src/services/AgentService.ts +1 -1
  30. package/src/services/AgentSynchronization.ts +291 -0
  31. package/src/services/DockerService.ts +37 -1
  32. package/src/services/EventService.ts +8 -2
  33. package/src/services/KnowhowClient.ts +141 -1
  34. package/src/services/LazyToolsService.ts +146 -0
  35. package/src/services/Mcp.ts +171 -4
  36. package/src/services/SessionManager.ts +287 -0
  37. package/src/services/TaskRegistry.ts +108 -0
  38. package/src/services/Tools.ts +2 -0
  39. package/src/services/index.ts +7 -0
  40. package/src/services/script-execution/ScriptExecutor.ts +7 -5
  41. package/src/types.ts +1 -0
  42. package/src/utils/InputQueueManager.ts +91 -57
  43. package/src/utils/errors.ts +0 -0
  44. package/src/utils/index.ts +11 -0
  45. package/src/worker.ts +12 -0
  46. package/tests/compressor/bigstring.test.ts +100 -0
  47. package/tests/compressor/bigstring.txt +1 -0
  48. package/tests/plugins/language/languagePlugin-content-triggers.test.ts +13 -5
  49. package/tests/plugins/language/languagePlugin-integration.test.ts +22 -7
  50. package/tests/plugins/language/languagePlugin.test.ts +11 -4
  51. package/tests/processors/ToolResponseCache.test.ts +128 -0
  52. package/tests/unit/InputQueueManager.test.ts +174 -0
  53. package/ts_build/package.json +3 -3
  54. package/ts_build/src/agents/base/base.d.ts +10 -0
  55. package/ts_build/src/agents/base/base.js +66 -34
  56. package/ts_build/src/agents/base/base.js.map +1 -1
  57. package/ts_build/src/agents/tools/execCommand.js +1 -9
  58. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  59. package/ts_build/src/agents/tools/github/index.d.ts +1 -1
  60. package/ts_build/src/agents/tools/googleSearch.d.ts +1 -0
  61. package/ts_build/src/agents/tools/googleSearch.js +1 -0
  62. package/ts_build/src/agents/tools/googleSearch.js.map +1 -1
  63. package/ts_build/src/agents/tools/index.d.ts +1 -0
  64. package/ts_build/src/agents/tools/index.js +1 -0
  65. package/ts_build/src/agents/tools/index.js.map +1 -1
  66. package/ts_build/src/agents/tools/lazy/definitions.d.ts +5 -0
  67. package/ts_build/src/agents/tools/lazy/definitions.js +58 -0
  68. package/ts_build/src/agents/tools/lazy/definitions.js.map +1 -0
  69. package/ts_build/src/agents/tools/lazy/disableTools.d.ts +9 -0
  70. package/ts_build/src/agents/tools/lazy/disableTools.js +15 -0
  71. package/ts_build/src/agents/tools/lazy/disableTools.js.map +1 -0
  72. package/ts_build/src/agents/tools/lazy/enableTools.d.ts +9 -0
  73. package/ts_build/src/agents/tools/lazy/enableTools.js +15 -0
  74. package/ts_build/src/agents/tools/lazy/enableTools.js.map +1 -0
  75. package/ts_build/src/agents/tools/lazy/index.d.ts +3 -0
  76. package/ts_build/src/agents/tools/lazy/index.js +20 -0
  77. package/ts_build/src/agents/tools/lazy/index.js.map +1 -0
  78. package/ts_build/src/agents/tools/lazy/listAvailableTools.d.ts +11 -0
  79. package/ts_build/src/agents/tools/lazy/listAvailableTools.js +15 -0
  80. package/ts_build/src/agents/tools/lazy/listAvailableTools.js.map +1 -0
  81. package/ts_build/src/agents/tools/list.js +2 -0
  82. package/ts_build/src/agents/tools/list.js.map +1 -1
  83. package/ts_build/src/agents/tools/mcp/connectMcpServer.d.ts +5 -0
  84. package/ts_build/src/agents/tools/mcp/connectMcpServer.js +31 -0
  85. package/ts_build/src/agents/tools/mcp/connectMcpServer.js.map +1 -0
  86. package/ts_build/src/agents/tools/mcp/definitions.d.ts +2 -0
  87. package/ts_build/src/agents/tools/mcp/definitions.js +62 -0
  88. package/ts_build/src/agents/tools/mcp/definitions.js.map +1 -0
  89. package/ts_build/src/agents/tools/mcp/disconnectMcpServer.d.ts +5 -0
  90. package/ts_build/src/agents/tools/mcp/disconnectMcpServer.js +31 -0
  91. package/ts_build/src/agents/tools/mcp/disconnectMcpServer.js.map +1 -0
  92. package/ts_build/src/agents/tools/mcp/index.d.ts +3 -0
  93. package/ts_build/src/agents/tools/mcp/index.js +10 -0
  94. package/ts_build/src/agents/tools/mcp/index.js.map +1 -0
  95. package/ts_build/src/agents/tools/mcp/listAvailableMcpServers.d.ts +14 -0
  96. package/ts_build/src/agents/tools/mcp/listAvailableMcpServers.js +23 -0
  97. package/ts_build/src/agents/tools/mcp/listAvailableMcpServers.js.map +1 -0
  98. package/ts_build/src/agents/tools/writeFile.js +4 -1
  99. package/ts_build/src/agents/tools/writeFile.js.map +1 -1
  100. package/ts_build/src/chat/CliChatService.js +3 -1
  101. package/ts_build/src/chat/CliChatService.js.map +1 -1
  102. package/ts_build/src/chat/modules/AgentModule.d.ts +4 -3
  103. package/ts_build/src/chat/modules/AgentModule.js +71 -265
  104. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  105. package/ts_build/src/cli.d.ts +1 -1
  106. package/ts_build/src/cli.js +17 -4
  107. package/ts_build/src/cli.js.map +1 -1
  108. package/ts_build/src/plugins/GitPlugin.d.ts +1 -0
  109. package/ts_build/src/plugins/GitPlugin.js +26 -19
  110. package/ts_build/src/plugins/GitPlugin.js.map +1 -1
  111. package/ts_build/src/plugins/language.d.ts +3 -0
  112. package/ts_build/src/plugins/language.js +55 -13
  113. package/ts_build/src/plugins/language.js.map +1 -1
  114. package/ts_build/src/processors/ToolResponseCache.d.ts +7 -4
  115. package/ts_build/src/processors/ToolResponseCache.js +47 -88
  116. package/ts_build/src/processors/ToolResponseCache.js.map +1 -1
  117. package/ts_build/src/processors/tools/grepToolResponse.d.ts +10 -0
  118. package/ts_build/src/processors/tools/grepToolResponse.js +71 -0
  119. package/ts_build/src/processors/tools/grepToolResponse.js.map +1 -0
  120. package/ts_build/src/processors/tools/index.d.ts +4 -0
  121. package/ts_build/src/processors/tools/index.js +16 -0
  122. package/ts_build/src/processors/tools/index.js.map +1 -0
  123. package/ts_build/src/processors/tools/jqToolResponse.d.ts +3 -0
  124. package/ts_build/src/processors/tools/jqToolResponse.js +115 -0
  125. package/ts_build/src/processors/tools/jqToolResponse.js.map +1 -0
  126. package/ts_build/src/processors/tools/listStoredToolResponses.d.ts +21 -0
  127. package/ts_build/src/processors/tools/listStoredToolResponses.js +51 -0
  128. package/ts_build/src/processors/tools/listStoredToolResponses.js.map +1 -0
  129. package/ts_build/src/processors/tools/tailToolResponse.d.ts +6 -0
  130. package/ts_build/src/processors/tools/tailToolResponse.js +55 -0
  131. package/ts_build/src/processors/tools/tailToolResponse.js.map +1 -0
  132. package/ts_build/src/services/AgentService.d.ts +1 -1
  133. package/ts_build/src/services/AgentSynchronization.d.ts +27 -0
  134. package/ts_build/src/services/AgentSynchronization.js +168 -0
  135. package/ts_build/src/services/AgentSynchronization.js.map +1 -0
  136. package/ts_build/src/services/DockerService.d.ts +2 -0
  137. package/ts_build/src/services/DockerService.js +21 -1
  138. package/ts_build/src/services/DockerService.js.map +1 -1
  139. package/ts_build/src/services/EventService.d.ts +5 -0
  140. package/ts_build/src/services/EventService.js +7 -2
  141. package/ts_build/src/services/EventService.js.map +1 -1
  142. package/ts_build/src/services/KnowhowClient.d.ts +41 -1
  143. package/ts_build/src/services/KnowhowClient.js +42 -0
  144. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  145. package/ts_build/src/services/LazyToolsService.d.ts +29 -0
  146. package/ts_build/src/services/LazyToolsService.js +96 -0
  147. package/ts_build/src/services/LazyToolsService.js.map +1 -0
  148. package/ts_build/src/services/Mcp.d.ts +18 -1
  149. package/ts_build/src/services/Mcp.js +119 -4
  150. package/ts_build/src/services/Mcp.js.map +1 -1
  151. package/ts_build/src/services/SessionManager.d.ts +15 -0
  152. package/ts_build/src/services/SessionManager.js +220 -0
  153. package/ts_build/src/services/SessionManager.js.map +1 -0
  154. package/ts_build/src/services/TaskRegistry.d.ts +15 -0
  155. package/ts_build/src/services/TaskRegistry.js +58 -0
  156. package/ts_build/src/services/TaskRegistry.js.map +1 -0
  157. package/ts_build/src/services/Tools.d.ts +2 -0
  158. package/ts_build/src/services/Tools.js.map +1 -1
  159. package/ts_build/src/services/index.d.ts +4 -0
  160. package/ts_build/src/services/index.js +4 -0
  161. package/ts_build/src/services/index.js.map +1 -1
  162. package/ts_build/src/services/script-execution/ScriptExecutor.js +7 -5
  163. package/ts_build/src/services/script-execution/ScriptExecutor.js.map +1 -1
  164. package/ts_build/src/types.d.ts +1 -0
  165. package/ts_build/src/types.js.map +1 -1
  166. package/ts_build/src/utils/InputQueueManager.d.ts +9 -2
  167. package/ts_build/src/utils/InputQueueManager.js +54 -40
  168. package/ts_build/src/utils/InputQueueManager.js.map +1 -1
  169. package/ts_build/src/utils/errors.d.ts +0 -0
  170. package/ts_build/src/utils/errors.js +1 -0
  171. package/ts_build/src/utils/errors.js.map +1 -0
  172. package/ts_build/src/utils/index.d.ts +1 -0
  173. package/ts_build/src/utils/index.js +5 -1
  174. package/ts_build/src/utils/index.js.map +1 -1
  175. package/ts_build/src/worker.js +8 -0
  176. package/ts_build/src/worker.js.map +1 -1
  177. package/ts_build/tests/compressor/bigstring.test.d.ts +1 -0
  178. package/ts_build/tests/compressor/bigstring.test.js +66 -0
  179. package/ts_build/tests/compressor/bigstring.test.js.map +1 -0
  180. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +6 -5
  181. package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
  182. package/ts_build/tests/plugins/language/languagePlugin-integration.test.js +9 -7
  183. package/ts_build/tests/plugins/language/languagePlugin-integration.test.js.map +1 -1
  184. package/ts_build/tests/plugins/language/languagePlugin.test.js +7 -4
  185. package/ts_build/tests/plugins/language/languagePlugin.test.js.map +1 -1
  186. package/ts_build/tests/processors/ToolResponseCache.test.js +107 -0
  187. package/ts_build/tests/processors/ToolResponseCache.test.js.map +1 -1
  188. package/ts_build/tests/unit/InputQueueManager.test.d.ts +1 -0
  189. package/ts_build/tests/unit/InputQueueManager.test.js +104 -0
  190. package/ts_build/tests/unit/InputQueueManager.test.js.map +1 -0
@@ -1,6 +1,7 @@
1
1
  import readline from "node:readline";
2
2
 
3
- export const askHistory: string[] = [];
3
+ // Callback type for notifying when a new history entry is added
4
+ type OnNewHistoryEntry = (entry: string) => void;
4
5
 
5
6
  type AskOptions = {
6
7
  question: string;
@@ -13,14 +14,31 @@ export class InputQueueManager {
13
14
  private stack: AskOptions[] = [];
14
15
  private rl: readline.Interface | null = null;
15
16
 
16
- // We keep one live buffer shared across stacked questions
17
+ // We keep one "live" buffer shared across stacked questions
17
18
  // (so typing is preserved when questions change)
18
19
  private currentLine = "";
19
20
 
20
- // History navigation state (custom: global askHistory + per-question history)
21
+ // Paste detection - buffer lines during paste operations
22
+ private pasteBuffer: string[] = [];
23
+ private pasteTimeout: NodeJS.Timeout | null = null;
24
+ private readonly PASTE_DELAY_MS = 10; // Time to wait for more paste lines
25
+
26
+ // History navigation state - uses only the history passed to ask()
21
27
  private historyIndex = -1;
22
28
  private savedLineBeforeHistory = "";
23
29
 
30
+ // Callback to notify caller when a new entry should be added to history
31
+ // This allows CliChatService to update inputHistory immediately
32
+ private onNewEntry?: OnNewHistoryEntry;
33
+
34
+ /**
35
+ * Set a callback to be notified when user enters a new history entry.
36
+ * This allows the caller to update their history source immediately.
37
+ */
38
+ setOnNewEntry(callback: OnNewHistoryEntry | undefined): void {
39
+ this.onNewEntry = callback;
40
+ }
41
+
24
42
  private ensureRl(): readline.Interface {
25
43
  if (this.rl) return this.rl;
26
44
 
@@ -28,7 +46,8 @@ export class InputQueueManager {
28
46
  input: process.stdin,
29
47
  output: process.stdout,
30
48
  terminal: true,
31
- historySize: 500,
49
+ // Disable readline's internal history - we manage history ourselves
50
+ historySize: 0,
32
51
 
33
52
  /**
34
53
  * Use readline's built-in completion system so Tab does NOT insert a literal tab.
@@ -54,35 +73,37 @@ export class InputQueueManager {
54
73
  },
55
74
  });
56
75
 
57
- // When user presses Enter, resolve ONLY the top question
76
+ // When user presses Enter, buffer the line for paste detection
58
77
  this.rl.on("line", (line) => {
59
78
  const current = this.peek();
60
79
  if (!current) return;
61
80
 
62
- // IMPORTANT: do not allow embedded newlines in history / answers
63
- const answer = this.sanitizeHistoryEntry(line);
64
-
65
- // Pop & resolve current question
66
- const resolved = this.stack.pop();
67
- resolved?.resolve(answer);
68
-
69
- // Add to global history
70
- if (answer && !askHistory.includes(answer)) {
71
- askHistory.push(answer);
81
+ // Detect paste operation: if we receive a line while already processing lines,
82
+ // it's likely part of a paste. Buffer it and wait for more.
83
+ if (this.pasteTimeout) {
84
+ // Already in paste mode, add to buffer
85
+ this.pasteBuffer.push(line);
86
+ clearTimeout(this.pasteTimeout);
87
+ this.pasteTimeout = setTimeout(() => this.flushPasteBuffer(), this.PASTE_DELAY_MS);
88
+ return;
72
89
  }
73
90
 
74
- // Reset preserved buffer + history nav state for the next question
75
- this.currentLine = "";
76
- this.historyIndex = -1;
77
- this.savedLineBeforeHistory = "";
91
+ // Start paste detection mode - buffer this line and wait to see if more come
92
+ this.pasteBuffer.push(line);
93
+ this.pasteTimeout = setTimeout(() => this.flushPasteBuffer(), this.PASTE_DELAY_MS);
94
+ });
78
95
 
79
- // Update prompt for next stacked question (if any)
80
- this.renderTopOrClose();
96
+ this.rl.on("close", () => {
97
+ // Flush any remaining paste buffer on close
98
+ if (this.pasteTimeout) {
99
+ clearTimeout(this.pasteTimeout);
100
+ this.flushPasteBuffer();
101
+ }
81
102
  });
82
103
 
83
104
  // Handle Ctrl+C (readline SIGINT)
84
105
  this.rl.on("SIGINT", () => {
85
- // If theres an active question, cancel it (like Esc)
106
+ // If there's an active question, cancel it (like Esc)
86
107
  if (this.stack.length > 0) {
87
108
  const cancelled = this.stack.pop();
88
109
  cancelled?.resolve("");
@@ -117,7 +138,7 @@ export class InputQueueManager {
117
138
  // If RL is closed or nothing to ask, ignore
118
139
  if (!this.rl || this.stack.length === 0) return;
119
140
 
120
- // Keep our buffer in sync with readlines live line
141
+ // Keep our buffer in sync with readline's live line
121
142
  this.syncFromReadline();
122
143
 
123
144
  // Any "real typing" should exit history mode
@@ -136,35 +157,20 @@ export class InputQueueManager {
136
157
  this.savedLineBeforeHistory = "";
137
158
  }
138
159
 
139
- if (key?.name === "escape") {
140
- // Cancel only the current (top) question
141
- const cancelled = this.stack.pop();
142
- cancelled?.resolve("");
143
-
144
- this.currentLine = "";
145
- this.historyIndex = -1;
146
- this.savedLineBeforeHistory = "";
147
-
148
- // clear the current input in readline and redraw
149
- this.replaceLine("");
150
- this.renderTopOrClose();
151
- return;
152
- }
153
-
154
- // Custom Up/Down history: global askHistory + per-question history
160
+ // Custom Up/Down history navigation using only passed-in history
155
161
  if (key?.name === "up") {
156
- const fullHistory = this.getFullHistory();
157
- if (fullHistory.length === 0) return;
162
+ const history = this.getHistory();
163
+ if (history.length === 0) return;
158
164
 
159
165
  if (this.historyIndex === -1) {
160
166
  // entering history mode: remember current typed text
161
167
  this.savedLineBeforeHistory = this.currentLine;
162
168
  }
163
169
 
164
- if (this.historyIndex < fullHistory.length - 1) {
170
+ if (this.historyIndex < history.length - 1) {
165
171
  this.historyIndex++;
166
172
  const next =
167
- fullHistory[fullHistory.length - 1 - this.historyIndex] ?? "";
173
+ history[history.length - 1 - this.historyIndex] ?? "";
168
174
  this.replaceLine(next);
169
175
  this.currentLine = next;
170
176
  }
@@ -172,13 +178,13 @@ export class InputQueueManager {
172
178
  }
173
179
 
174
180
  if (key?.name === "down") {
175
- const fullHistory = this.getFullHistory();
176
- if (fullHistory.length === 0) return;
181
+ const history = this.getHistory();
182
+ if (history.length === 0) return;
177
183
 
178
184
  if (this.historyIndex > 0) {
179
185
  this.historyIndex--;
180
186
  const next =
181
- fullHistory[fullHistory.length - 1 - this.historyIndex] ?? "";
187
+ history[history.length - 1 - this.historyIndex] ?? "";
182
188
  this.replaceLine(next);
183
189
  this.currentLine = next;
184
190
  return;
@@ -201,6 +207,34 @@ export class InputQueueManager {
201
207
  return this.rl;
202
208
  }
203
209
 
210
+ private flushPasteBuffer(): void {
211
+ if (this.pasteBuffer.length === 0) return;
212
+
213
+ const answer = this.pasteBuffer.join("\n");
214
+ this.pasteBuffer = [];
215
+ this.pasteTimeout = null;
216
+
217
+ const current = this.peek();
218
+ if (!current) return;
219
+
220
+ // Pop & resolve current question with the combined paste content (or single line)
221
+ const resolved = this.stack.pop();
222
+ resolved?.resolve(answer);
223
+
224
+ // Notify caller about new entry
225
+ if (answer && this.onNewEntry) {
226
+ this.onNewEntry(answer);
227
+ }
228
+
229
+ // Reset preserved buffer + history nav state for the next question
230
+ this.currentLine = "";
231
+ this.historyIndex = -1;
232
+ this.savedLineBeforeHistory = "";
233
+
234
+ // Update prompt for next stacked question (if any)
235
+ this.renderTopOrClose();
236
+ }
237
+
204
238
  async ask(question: string, options: string[] = [], history: string[] = []) {
205
239
  return new Promise<string>((resolve) => {
206
240
  this.stack.push({ question, options, history, resolve });
@@ -235,21 +269,21 @@ export class InputQueueManager {
235
269
  return value.replace(/[\r\n]+/g, " ").trim();
236
270
  }
237
271
 
238
- private getFullHistory(): string[] {
272
+ /**
273
+ * Get history for navigation - simply uses the history passed to ask()
274
+ * Single source of truth: the caller (CliChatService) manages all history
275
+ */
276
+ private getHistory(): string[] {
239
277
  const current = this.peek();
240
- const local = current?.history ?? [];
278
+ const history = current?.history ?? [];
241
279
 
242
- // De-dup while preserving order preference (older -> newer)
243
- const merged = [...askHistory, ...local];
244
- const seen = new Set<string>();
280
+ // Sanitize entries
245
281
  const out: string[] = [];
246
-
247
- for (const item of merged) {
282
+ for (const item of history) {
248
283
  const clean = this.sanitizeHistoryEntry(item);
249
- if (!clean) continue;
250
- if (seen.has(clean)) continue;
251
- seen.add(clean);
252
- out.push(clean);
284
+ if (clean) {
285
+ out.push(clean);
286
+ }
253
287
  }
254
288
 
255
289
  return out;
File without changes
@@ -29,6 +29,17 @@ export const ask = async (
29
29
  return inputQueue.ask(question, options, history);
30
30
  };
31
31
 
32
+ /**
33
+ * Set a callback to be notified when user enters a new history entry.
34
+ * This allows the caller to update their history source immediately,
35
+ * ensuring the next ask() call has the updated history.
36
+ */
37
+ export const setOnNewHistoryEntry = (
38
+ callback: ((entry: string) => void) | undefined
39
+ ): void => {
40
+ inputQueue.setOnNewEntry(callback);
41
+ };
42
+
32
43
  export const Marked = marked;
33
44
 
34
45
  export function dotp(x, y) {
package/src/worker.ts CHANGED
@@ -86,6 +86,18 @@ export async function worker(options?: {
86
86
  }) {
87
87
  const config = await getConfig();
88
88
 
89
+ // Check if we're already running inside a Docker container
90
+ const isInsideDocker = process.env.KNOWHOW_DOCKER === "true";
91
+
92
+ if (isInsideDocker) {
93
+ console.log("🐳 Already running inside Docker container, skipping sandbox mode");
94
+ // Force sandbox mode off when inside Docker to prevent nested containers
95
+ if (options) {
96
+ options.sandbox = false;
97
+ options.noSandbox = true;
98
+ }
99
+ }
100
+
89
101
  // Determine sandbox mode with priority: command line flags > config > default (false)
90
102
  let shouldUseSandbox = false;
91
103
  let sandboxSource = "";
@@ -0,0 +1,100 @@
1
+ import { readFile } from "../../src/utils";
2
+ import { TokenCompressor } from "../../src/processors/TokenCompressor";
3
+ import { services } from "../../src/services";
4
+
5
+ describe("TokenCompressor - Large File Test", () => {
6
+ let tokenCompressor: TokenCompressor;
7
+ const bigstringPath = "tests/compressor/bigstring.txt";
8
+
9
+ beforeAll(() => {
10
+ const { Tools } = services();
11
+ tokenCompressor = new TokenCompressor(Tools);
12
+ });
13
+
14
+ afterEach(() => {
15
+ tokenCompressor.clearStorage();
16
+ });
17
+
18
+ test("should compress large file contents and allow retrieval via chunks", async () => {
19
+ // Load the large file
20
+ const fileBuffer = await readFile(bigstringPath);
21
+ const fileContents = fileBuffer.toString();
22
+
23
+ console.log(`Original file size: ${fileContents.length} characters`);
24
+ console.log(`Estimated tokens: ${Math.ceil(fileContents.length / 4)}`);
25
+
26
+ // Compress the content
27
+ const compressed = tokenCompressor.compressContent(
28
+ fileContents,
29
+ bigstringPath
30
+ );
31
+
32
+ console.log(`Compressed result: ${compressed}`);
33
+
34
+ // Verify that compression occurred
35
+ expect(compressed).toContain("[COMPRESSED_STRING");
36
+ expect(compressed).toContain("Key:");
37
+ expect(compressed).toContain("chunks]");
38
+ expect(compressed.length).toBeLessThan(fileContents.length);
39
+
40
+ // Extract the key from the compressed string
41
+ const keyMatch = compressed.match(/Key: (compressed_[a-z0-9_]+)/);
42
+ expect(keyMatch).not.toBeNull();
43
+ const firstKey = keyMatch![1];
44
+
45
+ // Retrieve the first chunk
46
+ const firstChunk = tokenCompressor.retrieveString(firstKey);
47
+ expect(firstChunk).toBeTruthy();
48
+ expect(firstChunk.length).toBeGreaterThan(0);
49
+
50
+ // Verify the first chunk contains the beginning of the original content
51
+ expect(fileContents.startsWith(firstChunk.split("[NEXT_CHUNK_KEY:")[0]));
52
+
53
+ // Follow the chain to retrieve all chunks
54
+ const currentChunk = firstChunk;
55
+ let reconstructed = "";
56
+ let chunkCount = 0;
57
+ const maxChunks = 100; // Safety limit
58
+
59
+ while (currentChunk && chunkCount < maxChunks) {
60
+ chunkCount++;
61
+
62
+ const nextKeyMatch = currentChunk.match(/\[NEXT_CHUNK_KEY: ([^\]]+)\]/);
63
+ if (nextKeyMatch) {
64
+ // Remove the NEXT_CHUNK_KEY marker and add content
65
+ const nextKey = nextKeyMatch[1];
66
+ const retrieved = await tokenCompressor.retrieveString(nextKey);
67
+ console.log(`Retrieved chunk ${chunkCount} with key: ${nextKey}, length: ${retrieved.length}`);
68
+ } else {
69
+ // Last chunk
70
+ reconstructed += currentChunk;
71
+ break;
72
+ }
73
+ }
74
+
75
+ console.log(`Retrieved ${chunkCount} chunks`);
76
+ console.log(`Reconstructed size: ${reconstructed.length} characters`);
77
+
78
+ // Verify the reconstructed content matches the original
79
+ // expect(reconstructed).toBe(fileContents);
80
+ expect(chunkCount).toBeGreaterThan(1); // Should have multiple chunks for a large file
81
+ });
82
+
83
+ test("should handle compression threshold correctly", async () => {
84
+ const fileBuffer = await readFile(bigstringPath);
85
+ const fileContents = fileBuffer.toString();
86
+
87
+ // Test that it compresses when above threshold
88
+ const estimatedTokens = Math.ceil(fileContents.length / 4);
89
+ expect(estimatedTokens).toBeGreaterThan(4000); // Default threshold
90
+
91
+ const compressed = tokenCompressor.compressContent(fileContents);
92
+
93
+ // Should be compressed
94
+ expect(compressed).toContain("[COMPRESSED_STRING");
95
+
96
+ // The compression should result in a much smaller representation
97
+ const compressionRatio = compressed.length / fileContents.length;
98
+ expect(compressionRatio).toBeLessThan(0.01); // Less than 1% of original size
99
+ });
100
+ });