@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
@@ -119,7 +119,9 @@ describe("LanguagePlugin - Content-Based Triggering", () => {
119
119
  // Verify that the content-based trigger was activated
120
120
  expect(mockEventService.emit).toHaveBeenCalledWith(
121
121
  "agent:msg",
122
- expect.stringContaining("language_context_trigger")
122
+ expect.stringMatching(
123
+ /<Workflow>[\s\S]*language_context_trigger[\s\S]*<\/Workflow>/
124
+ )
123
125
  );
124
126
 
125
127
  const emitCall = mockEventService.emit.mock.calls.find(
@@ -127,9 +129,11 @@ describe("LanguagePlugin - Content-Based Triggering", () => {
127
129
  );
128
130
  expect(emitCall).toBeDefined();
129
131
 
130
- const eventData = JSON.parse(emitCall![1]);
132
+ // Extract JSON from <Workflow> tags
133
+ const workflowContent = emitCall![1].match(/<Workflow>\s*(\{[\s\S]*?\})\s*<\/Workflow>/);
134
+ expect(workflowContent).toBeDefined();
135
+ const eventData = JSON.parse(workflowContent![1]);
131
136
  expect(eventData.type).toBe("language_context_trigger");
132
- expect(eventData.filePath).toBe("src/components/MyComponent.spec.ts");
133
137
  expect(eventData.matchingTerms).toContain("test(");
134
138
  expect(eventData.contextMessage).toContain("Jest testing best practices");
135
139
  });
@@ -275,7 +279,9 @@ describe("LanguagePlugin - Content-Based Triggering", () => {
275
279
  // Verify that case-insensitive matching worked
276
280
  expect(mockEventService.emit).toHaveBeenCalledWith(
277
281
  "agent:msg",
278
- expect.stringContaining("language_context_trigger")
282
+ expect.stringMatching(
283
+ /<Workflow>[\s\S]*language_context_trigger[\s\S]*<\/Workflow>/
284
+ )
279
285
  );
280
286
  });
281
287
 
@@ -326,7 +332,9 @@ describe("LanguagePlugin - Content-Based Triggering", () => {
326
332
  // Verify that edit events also trigger content analysis
327
333
  expect(mockEventService.emit).toHaveBeenCalledWith(
328
334
  "agent:msg",
329
- expect.stringContaining("language_context_trigger")
335
+ expect.stringMatching(
336
+ /<Workflow>[\s\S]*language_context_trigger[\s\S]*<\/Workflow>/
337
+ )
330
338
  );
331
339
  });
332
340
  });
@@ -146,7 +146,9 @@ test("should calculate sum correctly", () => {
146
146
  // Verify agent:msg was emitted
147
147
  expect(mockEventService.emit).toHaveBeenCalledWith(
148
148
  "agent:msg",
149
- expect.stringContaining("language_context_trigger")
149
+ expect.stringMatching(
150
+ /<Workflow>[\s\S]*language_context_trigger[\s\S]*<\/Workflow>/
151
+ )
150
152
  );
151
153
  });
152
154
 
@@ -198,7 +200,9 @@ describe('User service', () => {
198
200
  // Should trigger on 'test(' pattern
199
201
  expect(mockEventService.emit).toHaveBeenCalledWith(
200
202
  "agent:msg",
201
- expect.stringContaining("Testing context")
203
+ expect.stringMatching(
204
+ /<Workflow>[\s\S]*Testing context[\s\S]*<\/Workflow>/
205
+ )
202
206
  );
203
207
  });
204
208
 
@@ -267,7 +271,9 @@ test('should get user data', () => {
267
271
  // Should now trigger on the edited content
268
272
  expect(mockEventService.emit).toHaveBeenCalledWith(
269
273
  "agent:msg",
270
- expect.stringContaining("Testing context")
274
+ expect.stringMatching(
275
+ /<Workflow>[\s\S]*Testing context[\s\S]*<\/Workflow>/
276
+ )
271
277
  );
272
278
  });
273
279
 
@@ -336,7 +342,9 @@ test('should query database correctly', async () => {
336
342
  // Should emit agent:msg for the triggered terms
337
343
  expect(mockEventService.emit).toHaveBeenCalledWith(
338
344
  "agent:msg",
339
- expect.stringContaining("language_context_trigger")
345
+ expect.stringMatching(
346
+ /<Workflow>[\s\S]*language_context_trigger[\s\S]*<\/Workflow>/
347
+ )
340
348
  );
341
349
 
342
350
  // Check that the emitted message contains both contexts
@@ -345,7 +353,10 @@ test('should query database correctly', async () => {
345
353
  );
346
354
  expect(emitCalls.length).toBeGreaterThan(0);
347
355
 
348
- const eventData = JSON.parse(emitCalls[0][1]);
356
+ // Extract JSON from <Workflow> tags
357
+ const workflowContent = emitCalls[0][1].match(/<Workflow>\s*(\{[\s\S]*?\})\s*<\/Workflow>/);
358
+ expect(workflowContent).toBeDefined();
359
+ const eventData = JSON.parse(workflowContent[1]);
349
360
  expect(eventData.matchingTerms).toEqual(
350
361
  expect.arrayContaining(["test(", "database"])
351
362
  );
@@ -393,7 +404,9 @@ test('sum function works', () => {
393
404
 
394
405
  expect(mockEventService.emit).toHaveBeenCalledWith(
395
406
  "agent:msg",
396
- expect.stringContaining("Testing context")
407
+ expect.stringMatching(
408
+ /<Workflow>[\s\S]*Testing context[\s\S]*<\/Workflow>/
409
+ )
397
410
  );
398
411
 
399
412
  // Clean up
@@ -447,7 +460,9 @@ test('performance test', () => {
447
460
  // Should still trigger correctly
448
461
  expect(mockEventService.emit).toHaveBeenCalledWith(
449
462
  "agent:msg",
450
- expect.stringContaining("Testing context")
463
+ expect.stringMatching(
464
+ /<Workflow>[\s\S]*Testing context[\s\S]*<\/Workflow>/
465
+ )
451
466
  );
452
467
 
453
468
  // Should process reasonably quickly (less than 1 second)
@@ -199,9 +199,11 @@ describe("LanguagePlugin", () => {
199
199
  );
200
200
 
201
201
  expect(emitCall).toBeDefined();
202
- const eventData = JSON.parse(emitCall[1]);
202
+ // Extract JSON from <Workflow> tags
203
+ const workflowContent = emitCall[1].match(/<Workflow>\s*(\{[\s\S]*?\})\s*<\/Workflow>/);
204
+ expect(workflowContent).toBeDefined();
205
+ const eventData = JSON.parse(workflowContent[1]);
203
206
  expect(eventData.type).toBe("language_context_trigger");
204
- expect(eventData.filePath).toBe("src/components/Button.ts");
205
207
  expect(eventData.matchingTerms).toEqual(["**/*.ts"]);
206
208
  expect(eventData.eventType).toBe("file:post-edit");
207
209
  expect(eventData.resolvedSources).toBeDefined();
@@ -264,7 +266,10 @@ describe("LanguagePlugin", () => {
264
266
  const emitCall = mockEventService.emit.mock.calls.find(
265
267
  (call) => call[0] === "agent:msg"
266
268
  );
267
- const eventData = JSON.parse(emitCall![1]);
269
+ // Extract JSON from <Workflow> tags
270
+ const workflowContent = emitCall![1].match(/<Workflow>\s*(\{[\s\S]*?\})\s*<\/Workflow>/);
271
+ expect(workflowContent).toBeDefined();
272
+ const eventData = JSON.parse(workflowContent![1]);
268
273
  // With minimatch, *.ts doesn't match src/components/Button.ts (needs **/*.ts)
269
274
  // Only src/** pattern matches src/components/Button.ts
270
275
  expect(eventData.matchingTerms).toContain("src/**");
@@ -334,7 +339,9 @@ describe("LanguagePlugin", () => {
334
339
  );
335
340
  expect(mockEventService.emit).toHaveBeenCalledWith(
336
341
  "agent:msg",
337
- expect.stringContaining("language_context_trigger")
342
+ expect.stringMatching(
343
+ /<Workflow>[\s\S]*language_context_trigger[\s\S]*<\/Workflow>/
344
+ )
338
345
  );
339
346
  });
340
347
  });
@@ -2,6 +2,9 @@ import { Message } from "../../src/clients/types";
2
2
  import {
3
3
  ToolResponseCache,
4
4
  jqToolResponseDefinition,
5
+ grepToolResponseDefinition,
6
+ tailToolResponseDefinition,
7
+ listStoredToolResponsesDefinition,
5
8
  } from "../../src/processors/ToolResponseCache";
6
9
  import { ToolsService } from "../../src/services";
7
10
 
@@ -170,9 +173,15 @@ describe("ToolResponseCache", () => {
170
173
  expect(cache).toBeInstanceOf(ToolResponseCache);
171
174
  expect(mockToolsService.addTools).toHaveBeenCalledWith([
172
175
  jqToolResponseDefinition,
176
+ grepToolResponseDefinition,
177
+ tailToolResponseDefinition,
178
+ listStoredToolResponsesDefinition,
173
179
  ]);
174
180
  expect(mockToolsService.addFunctions).toHaveBeenCalledWith({
175
181
  jqToolResponse: expect.any(Function),
182
+ grepToolResponse: expect.any(Function),
183
+ tailToolResponse: expect.any(Function),
184
+ listStoredToolResponses: expect.any(Function),
176
185
  });
177
186
  });
178
187
 
@@ -549,9 +558,30 @@ describe("ToolResponseCache", () => {
549
558
  name: "jqToolResponse",
550
559
  }),
551
560
  },
561
+ {
562
+ type: "function",
563
+ function: expect.objectContaining({
564
+ name: "grepToolResponse",
565
+ }),
566
+ },
567
+ {
568
+ type: "function",
569
+ function: expect.objectContaining({
570
+ name: "tailToolResponse",
571
+ }),
572
+ },
573
+ {
574
+ type: "function",
575
+ function: expect.objectContaining({
576
+ name: "listStoredToolResponses",
577
+ }),
578
+ },
552
579
  ]);
553
580
  expect(mockToolsService.addFunctions).toHaveBeenCalledWith({
554
581
  jqToolResponse: expect.any(Function),
582
+ grepToolResponse: expect.any(Function),
583
+ tailToolResponse: expect.any(Function),
584
+ listStoredToolResponses: expect.any(Function),
555
585
  });
556
586
  });
557
587
 
@@ -581,6 +611,104 @@ describe("ToolResponseCache", () => {
581
611
  });
582
612
  });
583
613
 
614
+ describe("tailToolResponse", () => {
615
+ beforeEach(() => {
616
+ // Store test data with multiple lines
617
+ const multiLineContent = Array(50)
618
+ .fill(0)
619
+ .map((_, i) => `Line ${i + 1}: This is line number ${i + 1}`)
620
+ .join("\n");
621
+ cache.storeToolResponse(multiLineContent, "call_multiline");
622
+
623
+ // Store short content
624
+ cache.storeToolResponse("Line 1\nLine 2\nLine 3", "call_short");
625
+
626
+ // Store single line
627
+ cache.storeToolResponse("Single line content", "call_single");
628
+
629
+ // Store empty content
630
+ cache.storeToolResponse("", "call_empty");
631
+ });
632
+
633
+ it("should return last 10 lines by default", async () => {
634
+ const result = await cache.tailToolResponse("call_multiline");
635
+ const lines = result.split("\n");
636
+ expect(lines).toHaveLength(10);
637
+ expect(lines[0]).toContain("41: Line 41");
638
+ expect(lines[9]).toContain("50: Line 50");
639
+ });
640
+
641
+ it("should return last n lines when specified", async () => {
642
+ const result = await cache.tailToolResponse("call_multiline", {
643
+ lines: 5,
644
+ });
645
+ const lines = result.split("\n");
646
+ expect(lines).toHaveLength(5);
647
+ expect(lines[0]).toContain("46: Line 46");
648
+ expect(lines[4]).toContain("50: Line 50");
649
+ });
650
+
651
+ it("should return all lines if n is greater than total lines", async () => {
652
+ const result = await cache.tailToolResponse("call_short", { lines: 10 });
653
+ const lines = result.split("\n");
654
+ expect(lines).toHaveLength(3);
655
+ expect(lines[0]).toContain("1: Line 1");
656
+ expect(lines[1]).toContain("2: Line 2");
657
+ expect(lines[2]).toContain("3: Line 3");
658
+ });
659
+
660
+ it("should handle single line content", async () => {
661
+ const result = await cache.tailToolResponse("call_single", { lines: 5 });
662
+ expect(result).toBe("1: Single line content");
663
+ });
664
+
665
+ it("should handle empty content", async () => {
666
+ const result = await cache.tailToolResponse("call_empty", { lines: 10 });
667
+ expect(result).toBe("1: ");
668
+ });
669
+
670
+ it("should return last 1 line when lines is 1", async () => {
671
+ const result = await cache.tailToolResponse("call_multiline", {
672
+ lines: 1,
673
+ });
674
+ expect(result).toBe("50: Line 50: This is line number 50");
675
+ });
676
+
677
+ it("should return last 20 lines", async () => {
678
+ const result = await cache.tailToolResponse("call_multiline", {
679
+ lines: 20,
680
+ });
681
+ const lines = result.split("\n");
682
+ expect(lines).toHaveLength(20);
683
+ expect(lines[0]).toContain("31: Line 31");
684
+ expect(lines[19]).toContain("50: Line 50");
685
+ });
686
+
687
+ it("should format line numbers correctly", async () => {
688
+ const result = await cache.tailToolResponse("call_multiline", {
689
+ lines: 3,
690
+ });
691
+ const lines = result.split("\n");
692
+ expect(lines[0]).toMatch(/^48:/);
693
+ expect(lines[1]).toMatch(/^49:/);
694
+ expect(lines[2]).toMatch(/^50:/);
695
+ });
696
+
697
+ it("should return error for missing tool call ID", async () => {
698
+ const result = await cache.tailToolResponse("missing_id");
699
+ expect(result).toContain("Error: No tool response found");
700
+ expect(result).toContain("missing_id");
701
+ expect(result).toContain("Available IDs:");
702
+ });
703
+
704
+ it("should handle lines option of 0", async () => {
705
+ const result = await cache.tailToolResponse("call_multiline", {
706
+ lines: 0,
707
+ });
708
+ expect(result).toBe("");
709
+ });
710
+ });
711
+
584
712
  describe("Edge Cases and Error Handling", () => {
585
713
  it("should handle very large JSON objects", async () => {
586
714
  const largeObject = {
@@ -0,0 +1,174 @@
1
+ import { InputQueueManager } from "../../src/utils/InputQueueManager";
2
+
3
+ describe("InputQueueManager history ordering", () => {
4
+ test("should use only passed-in history for navigation", () => {
5
+ const manager = new InputQueueManager();
6
+
7
+ // Simulate history passed to ask() (oldest -> newest)
8
+ const history = ["old1", "old2", "old3"];
9
+
10
+ // Access the private getHistory method via reflection for testing
11
+ const getHistory = (manager as any).getHistory.bind(manager);
12
+
13
+ // Simulate having a current question with history
14
+ (manager as any).stack = [{
15
+ question: "test",
16
+ options: [],
17
+ history: history,
18
+ resolve: () => {}
19
+ }];
20
+
21
+ const result = getHistory();
22
+
23
+ // Should return history in oldest->newest order (same as passed in)
24
+ expect(result).toEqual(["old1", "old2", "old3"]);
25
+ });
26
+
27
+ test("should sanitize history entries", () => {
28
+ const manager = new InputQueueManager();
29
+
30
+ // History with entries that need sanitization
31
+ const history = ["normal", "has\nnewline", " spaces ", ""];
32
+
33
+ const getHistory = (manager as any).getHistory.bind(manager);
34
+
35
+ (manager as any).stack = [{
36
+ question: "test",
37
+ options: [],
38
+ history: history,
39
+ resolve: () => {}
40
+ }];
41
+
42
+ const result = getHistory();
43
+
44
+ // Should sanitize: remove newlines, trim whitespace, filter empty
45
+ expect(result).toEqual(["normal", "has newline", "spaces"]);
46
+ });
47
+
48
+ test("should return empty array when no history provided", () => {
49
+ const manager = new InputQueueManager();
50
+
51
+ const getHistory = (manager as any).getHistory.bind(manager);
52
+
53
+ // No history in stack
54
+ (manager as any).stack = [{
55
+ question: "test",
56
+ options: [],
57
+ history: undefined,
58
+ resolve: () => {}
59
+ }];
60
+
61
+ const result = getHistory();
62
+
63
+ expect(result).toEqual([]);
64
+ });
65
+
66
+ test("should return empty array when stack is empty", () => {
67
+ const manager = new InputQueueManager();
68
+
69
+ const getHistory = (manager as any).getHistory.bind(manager);
70
+
71
+ // Empty stack
72
+ (manager as any).stack = [];
73
+
74
+ const result = getHistory();
75
+
76
+ expect(result).toEqual([]);
77
+ });
78
+
79
+ test("Verify historyIndex math for first Up press", () => {
80
+ // History array: ["old1", "old2", "recent"]
81
+ // length = 3
82
+ // First Up: historyIndex becomes 0
83
+ // Index to access: length - 1 - historyIndex = 3 - 1 - 0 = 2
84
+ // history[2] = "recent" ✓
85
+
86
+ const history = ["old1", "old2", "recent"];
87
+ const historyIndex = 0; // After first Up press
88
+ const accessIndex = history.length - 1 - historyIndex;
89
+
90
+ expect(accessIndex).toBe(2);
91
+ expect(history[accessIndex]).toBe("recent");
92
+ });
93
+
94
+ test("Verify historyIndex math for multiple Up presses", () => {
95
+ // History array: ["old1", "old2", "recent"]
96
+ // length = 3
97
+ const history = ["old1", "old2", "recent"];
98
+
99
+ // First Up: historyIndex = 0
100
+ expect(history[history.length - 1 - 0]).toBe("recent");
101
+
102
+ // Second Up: historyIndex = 1
103
+ expect(history[history.length - 1 - 1]).toBe("old2");
104
+
105
+ // Third Up: historyIndex = 2
106
+ expect(history[history.length - 1 - 2]).toBe("old1");
107
+ });
108
+
109
+ test("onNewEntry callback is called when set", () => {
110
+ const manager = new InputQueueManager();
111
+ const entries: string[] = [];
112
+
113
+ manager.setOnNewEntry((entry) => {
114
+ entries.push(entry);
115
+ });
116
+
117
+ // Verify callback is stored (we can't easily test the actual Enter key behavior without mocking readline)
118
+ expect((manager as any).onNewEntry).toBeDefined();
119
+ });
120
+ });
121
+
122
+ describe("InputQueueManager with CliChatService integration pattern", () => {
123
+ test("new entries should appear in history for next ask() call", () => {
124
+ // This test verifies the pattern used by CliChatService:
125
+ // 1. ask() is called with current inputHistory
126
+ // 2. User types and presses Enter
127
+ // 3. onNewEntry callback adds entry to inputHistory
128
+ // 4. Next ask() call includes the new entry
129
+
130
+ const inputHistory: string[] = ["old1", "old2"];
131
+ const manager = new InputQueueManager();
132
+
133
+ // Set up callback (like CliChatService does)
134
+ manager.setOnNewEntry((entry) => {
135
+ if (!entry.startsWith("/") && entry.trim() !== "") {
136
+ inputHistory.push(entry);
137
+ }
138
+ });
139
+
140
+ // Simulate: first ask() call with current history
141
+ const getHistory = (manager as any).getHistory.bind(manager);
142
+ (manager as any).stack = [{
143
+ question: "test",
144
+ options: [],
145
+ history: [...inputHistory], // snapshot at time of ask()
146
+ resolve: () => {}
147
+ }];
148
+
149
+ let result = getHistory();
150
+ expect(result).toEqual(["old1", "old2"]);
151
+
152
+ // Simulate: user presses Enter with "new message"
153
+ // The callback fires (in real code this happens in the 'line' event)
154
+ (manager as any).onNewEntry("new message");
155
+
156
+ // inputHistory is now updated
157
+ expect(inputHistory).toEqual(["old1", "old2", "new message"]);
158
+
159
+ // Simulate: next ask() call with updated history
160
+ (manager as any).stack = [{
161
+ question: "test2",
162
+ options: [],
163
+ history: [...inputHistory], // new snapshot includes "new message"
164
+ resolve: () => {}
165
+ }];
166
+
167
+ result = getHistory();
168
+ expect(result).toEqual(["old1", "old2", "new message"]);
169
+
170
+ // Press Up: should get "new message" (the most recent)
171
+ const historyIndex = 0;
172
+ expect(result[result.length - 1 - historyIndex]).toBe("new message");
173
+ });
174
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.56",
3
+ "version": "0.0.59",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "test": "jest --testTimeout 300000",
11
11
  "test:debug": "node --inspect-brk ../../node_modules/jest/bin/jest.js --detectOpenHandles --forceExit --testTimeout 300000",
12
12
  "compile": "tsc",
13
- "start": "npm run compile && node ts_build/src/server/index.js",
13
+ "start": "npm run compile && node --no-node-snapshot ts_build/src/server/index.js",
14
14
  "dataset:diffs:generate": "ts-node src/dataset/diffs/generate.ts",
15
15
  "dataset:diffs:jsonl": "ts-node src/dataset/diffs/jsonl.ts",
16
16
  "prepublishOnly": "npm run compile",
@@ -62,7 +62,7 @@
62
62
  "jira-client": "^8.2.2",
63
63
  "marked": "^10.0.0",
64
64
  "marked-terminal": "^6.2.0",
65
- "minimatch": "^10.0.3",
65
+ "minimatch": "^10.1.2",
66
66
  "morgan": "^1.10.0",
67
67
  "node-fetch": "^3.2.3",
68
68
  "node-jq": "^6.0.1",
@@ -52,10 +52,17 @@ export declare abstract class BaseAgent implements IAgent {
52
52
  costUpdate: string;
53
53
  toolCall: string;
54
54
  toolUsed: string;
55
+ notStarted: string;
56
+ inProgress: string;
55
57
  done: string;
56
58
  pause: string;
57
59
  kill: string;
58
60
  unpause: string;
61
+ agentMsg: string;
62
+ userSay: string;
63
+ agentSay: string;
64
+ agentNewTask: string;
65
+ agentTaskComplete: string;
59
66
  };
60
67
  tools: ToolsService;
61
68
  events: EventService;
@@ -108,6 +115,9 @@ export declare abstract class BaseAgent implements IAgent {
108
115
  unpaused(): Promise<unknown>;
109
116
  kill(): Promise<void>;
110
117
  call(userInput: string, _messages?: Message[]): any;
118
+ getStatusMessage(): string;
119
+ logStatus(): void;
120
+ addPendingMessage(message: Message): void;
111
121
  addPendingUserMessage(message: Message): void;
112
122
  getMessagesLength(messages: Message[]): number;
113
123
  getTaskBreakdown(messages: Message[]): Promise<string>;