@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
package/src/cli.ts CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node --no-node-snapshot
2
2
  import "source-map-support/register";
3
3
  import * as fs from "fs";
4
4
  import * as path from "path";
@@ -11,7 +11,7 @@ import { init } from "./config";
11
11
  import { download, purge } from ".";
12
12
  import { includedTools } from "./agents/tools/list";
13
13
  import * as allTools from "./agents/tools";
14
- import { services } from "./services";
14
+ import { LazyToolsService, services } from "./services";
15
15
  import { login } from "./login";
16
16
  import { worker } from "./worker";
17
17
  import {
@@ -35,8 +35,14 @@ import { SetupModule } from "./chat/modules/SetupModule";
35
35
  import { CliChatService } from "./chat/CliChatService";
36
36
 
37
37
  async function setupServices() {
38
- const { Tools, Agents, Mcp, Clients } = services();
39
- const { Researcher, Developer, Patcher, Setup } = agents();
38
+ const { Agents, Mcp, Clients } = services();
39
+ const Tools = new LazyToolsService();
40
+
41
+ const { Researcher, Developer, Patcher, Setup } = agents({
42
+ ...services(),
43
+ Tools,
44
+ });
45
+
40
46
  Agents.registerAgent(Researcher);
41
47
  Agents.registerAgent(Patcher);
42
48
  Agents.registerAgent(Developer);
@@ -45,6 +51,9 @@ async function setupServices() {
45
51
 
46
52
  Tools.defineTools(includedTools, allTools);
47
53
 
54
+ // Add Mcp service to tool context directly so MCP management tools can access it
55
+ Tools.addContext("Mcp", Mcp);
56
+
48
57
  await Promise.all([
49
58
  Mcp.connectToConfigured(Tools),
50
59
  Clients.registerConfiguredModels(),
@@ -81,9 +90,6 @@ async function main() {
81
90
  .description("AI CLI with plugins and agents")
82
91
  .version(version);
83
92
 
84
- // Initialize services for all commands
85
- await setupServices();
86
-
87
93
  program
88
94
  .command("init")
89
95
  .description("Initialize knowhow configuration")
@@ -103,6 +109,7 @@ async function main() {
103
109
  .command("generate")
104
110
  .description("Generate documentation")
105
111
  .action(async () => {
112
+ await setupServices();
106
113
  await generate();
107
114
  });
108
115
 
@@ -110,6 +117,7 @@ async function main() {
110
117
  .command("embed")
111
118
  .description("Create embeddings")
112
119
  .action(async () => {
120
+ await setupServices();
113
121
  await embed();
114
122
  });
115
123
 
@@ -139,6 +147,7 @@ async function main() {
139
147
  .command("chat")
140
148
  .description("Start new chat interface")
141
149
  .action(async () => {
150
+ await setupServices();
142
151
  await startChat();
143
152
  });
144
153
 
@@ -166,6 +175,7 @@ async function main() {
166
175
  .option("--input <text>", "Task input (fallback to stdin if not provided)")
167
176
  .action(async (options) => {
168
177
  try {
178
+ await setupServices();
169
179
  let input = options.input;
170
180
  if (!input) {
171
181
  input = await readStdin();
@@ -204,6 +214,7 @@ async function main() {
204
214
  .option("--prompt-file <path>", "Custom prompt template file")
205
215
  .action(async (options) => {
206
216
  try {
217
+ await setupServices();
207
218
  let input = options.input;
208
219
  if (!input) {
209
220
  input = await readStdin();
@@ -235,6 +246,7 @@ async function main() {
235
246
  .description("Ask the agent to configure knowhow")
236
247
  .action(async (options) => {
237
248
  try {
249
+ await setupServices();
238
250
  const setupModule = new SetupModule();
239
251
  await setupModule.initialize(chatService);
240
252
  await setupModule.handleSetupCommand([]);
@@ -258,6 +270,7 @@ async function main() {
258
270
  )
259
271
  .action(async (options) => {
260
272
  try {
273
+ await setupServices();
261
274
  let input = options.input;
262
275
  if (!input) {
263
276
  input = await readStdin();
@@ -292,13 +305,22 @@ async function main() {
292
305
 
293
306
  program
294
307
  .command("worker")
295
- .description("Start worker process and optionally register current directory")
308
+ .description(
309
+ "Start worker process and optionally register current directory"
310
+ )
296
311
  .option("--register", "Register current directory as a worker path")
297
- .option("--share", "Share this worker with your organization (allows other users to use it)")
312
+ .option(
313
+ "--share",
314
+ "Share this worker with your organization (allows other users to use it)"
315
+ )
298
316
  .option("--unshare", "Make this worker private (only you can use it)")
299
317
  .option("--sandbox", "Run worker in a Docker container for isolation")
300
- .option("--no-sandbox", "Run worker directly on host (disable sandbox mode)")
318
+ .option(
319
+ "--no-sandbox",
320
+ "Run worker directly on host (disable sandbox mode)"
321
+ )
301
322
  .action(async (options) => {
323
+ await setupServices();
302
324
  await worker(options);
303
325
  });
304
326
 
@@ -337,6 +359,7 @@ async function main() {
337
359
  }
338
360
 
339
361
  // Default action: start all workers
362
+ await setupServices();
340
363
  await startAllWorkers();
341
364
  } catch (error) {
342
365
  console.error("Error managing workers:", error);
@@ -19,6 +19,7 @@ export class GitPlugin extends PluginBase {
19
19
  private projectHasGit: boolean = false;
20
20
  private eventService: EventService;
21
21
  private currentTask: string | null = null;
22
+ static isListening = false;
22
23
 
23
24
  constructor(context: PluginContext = {}) {
24
25
  super(context);
@@ -96,9 +97,7 @@ Your modifications are automatically tracked separately and won't affect the use
96
97
  // since HEAD doesn't exist yet
97
98
  try {
98
99
  this.gitCommand("add -A");
99
- this.gitCommand(
100
- 'commit -m "Initial commit for agent tracking"'
101
- );
100
+ this.gitCommand('commit -m "Initial commit for agent tracking"');
102
101
  } catch (error) {
103
102
  // If there's nothing to commit, create an empty commit
104
103
  this.gitCommand(
@@ -150,26 +149,30 @@ Your modifications are automatically tracked separately and won't affect the use
150
149
 
151
150
  private setupEventListeners(): void {
152
151
  // Listen for file:post-edit events to auto-commit
153
- this.eventService.on("file:post-edit", async (data: any) => {
154
- if (this.isEnabled()) {
155
- await this.autoCommit(data);
156
- }
157
- });
152
+ if (!GitPlugin.isListening) {
153
+ this.eventService.on("file:post-edit", async (data: any) => {
154
+ if (this.isEnabled()) {
155
+ await this.autoCommit(data);
156
+ }
157
+ });
158
158
 
159
- // Listen for agent newTask events to create new branches
160
- this.eventService.on("agent:newTask", async (data: any) => {
161
- if (this.isEnabled()) {
162
- await this.ensureCleanState(data);
163
- await this.handleNewTask(data);
164
- }
165
- });
159
+ // Listen for agent newTask events to create new branches
160
+ this.eventService.on("agent:newTask", async (data: any) => {
161
+ if (this.isEnabled()) {
162
+ await this.ensureCleanState(data);
163
+ await this.handleNewTask(data);
164
+ }
165
+ });
166
166
 
167
- // Listen for task completion events to squash merge
168
- this.eventService.on("agent:taskComplete", async (data: any) => {
169
- if (this.isEnabled()) {
170
- await this.handleTaskComplete(data);
171
- }
172
- });
167
+ // Listen for task completion events to squash merge
168
+ this.eventService.on("agent:taskComplete", async (data: any) => {
169
+ if (this.isEnabled()) {
170
+ await this.handleTaskComplete(data);
171
+ }
172
+ });
173
+
174
+ GitPlugin.isListening = true;
175
+ }
173
176
  }
174
177
 
175
178
  /**
@@ -306,8 +309,8 @@ Your modifications are automatically tracked separately and won't affect the use
306
309
  this.ensureValidHead();
307
310
 
308
311
  // Commit the changes
309
- const escapedMessage = message.replace(/\n/g, '\\n');
310
- this.gitCommand(`commit -m "${escapedMessage}"`);
312
+ const escapedMessage = message.replace(/\n/g, "\\n");
313
+ this.gitCommand(`commit --allow-empty -m "${escapedMessage}"`);
311
314
  }
312
315
 
313
316
  async commitAll(message: string): Promise<void> {
@@ -356,11 +359,14 @@ Your modifications are automatically tracked separately and won't affect the use
356
359
 
357
360
  this.eventService.emit(
358
361
  "agent:msg",
359
- `GitPlugin::Commit: ${enhancedMessage} on branch: ${this.getCurrentBranch()}
362
+ `
363
+ <Workflow>
364
+ GitPlugin::Commit: ${enhancedMessage} on branch: ${this.getCurrentBranch()}
360
365
  You can access your change history via git --git-dir ${
361
366
  this.knowhowGitPath
362
367
  } log or other commands
363
368
  This can be used to revert changes, or compare against previous states during a task.
369
+ </Workflow>
364
370
  `
365
371
  );
366
372
  } catch (error) {
@@ -46,11 +46,25 @@ export class LanguagePlugin extends PluginBase implements Plugin {
46
46
  });
47
47
 
48
48
  // Register handlers for each event
49
- allEvents.forEach((eventType) => {
49
+ const fileEvents = Array.from(allEvents).filter((e: string) =>
50
+ e.startsWith("file")
51
+ );
52
+
53
+ const otherEvents = Array.from(allEvents).filter(
54
+ (e: string) => !e.startsWith("file")
55
+ );
56
+
57
+ fileEvents.forEach((eventType) => {
50
58
  this.eventService.on(eventType, async (eventData) => {
51
59
  await this.handleFileEvent(eventType, eventData);
52
60
  });
53
61
  });
62
+
63
+ otherEvents.forEach((eventType) => {
64
+ this.eventService.on(eventType, async (eventData) => {
65
+ await this.handleGenericEvent(eventType, eventData);
66
+ });
67
+ });
54
68
  } catch (error) {
55
69
  console.error("LANGUAGE PLUGIN: Error setting up event handlers:", error);
56
70
  }
@@ -143,7 +157,10 @@ export class LanguagePlugin extends PluginBase implements Plugin {
143
157
  const matches = patterns.some(
144
158
  (pattern) =>
145
159
  minimatch(filePath, pattern) ||
146
- fileContent.toString().toLowerCase().includes(pattern.toLowerCase())
160
+ fileContent
161
+ .toString()
162
+ .toLowerCase()
163
+ .includes(pattern.toLowerCase())
147
164
  );
148
165
  return matches;
149
166
  })
@@ -153,21 +170,12 @@ export class LanguagePlugin extends PluginBase implements Plugin {
153
170
  // Resolve sources for matching terms
154
171
  const resolvedSources = await this.resolveSources(matchingFileTerms);
155
172
 
156
- // Emit agent message event with resolved context
157
- this.eventService.emit(
158
- "agent:msg",
159
- JSON.stringify({
160
- type: "language_context_trigger",
161
- filePath,
162
- matchingTerms: matchingFileTerms,
163
- eventType,
164
- resolvedSources,
165
- contextMessage: `LANGUAGE PLUGIN: File event ${eventType} on ${filePath} triggered contextual expansions for terms: ${matchingFileTerms.join(
166
- ", "
167
- )}.
168
- Expanded context: ${JSON.stringify(resolvedSources)}
169
- These terms are directly related to the file operation so be sure to contextualize your response to this information.`,
170
- })
173
+ const message = `These terms are directly related to the file operation so be sure to contextualize your response to this information.`;
174
+ this.emitAgentMessage(
175
+ eventType,
176
+ matchingFileTerms,
177
+ resolvedSources,
178
+ message
171
179
  );
172
180
  }
173
181
  } catch (error) {
@@ -175,11 +183,72 @@ export class LanguagePlugin extends PluginBase implements Plugin {
175
183
  }
176
184
  }
177
185
 
186
+ private emitAgentMessage(
187
+ eventType: string,
188
+ matchingTerms: string[],
189
+ resolvedSources: any[],
190
+ context: string
191
+ ) {
192
+ this.eventService.emit(
193
+ "agent:msg",
194
+
195
+ `<Workflow>
196
+ ${JSON.stringify({
197
+ type: "language_context_trigger",
198
+ eventType,
199
+ matchingTerms,
200
+ resolvedSources,
201
+ contextMessage: `LANGUAGE PLUGIN: Agent event ${eventType} triggered contextual expansions for terms: ${matchingTerms.join(
202
+ ", "
203
+ )}.
204
+ Expanded context: ${JSON.stringify(resolvedSources)}
205
+ These terms are directly related to what the agent is discussing so be sure to contextualize your response to this information.
206
+ ${context}
207
+ `,
208
+ })}
209
+ </Workflow>
210
+ `
211
+ );
212
+ }
213
+
214
+ /**
215
+ * Handle agent message and say events and emit agent messages when patterns match
216
+ */
217
+ private async handleGenericEvent(eventType: string, eventData: any) {
218
+ try {
219
+ const languageConfig = await getLanguageConfig();
220
+ const userPrompt = JSON.stringify(eventData);
221
+ const isJsonObj = userPrompt.startsWith("{") && userPrompt.endsWith("}");
222
+
223
+ // Skip if this is probably a language_context_trigger to avoid loops
224
+ if (userPrompt.includes("language_context_trigger")) {
225
+ return;
226
+ }
227
+
228
+ const matchingTerms = await this.getMatchingTerms(userPrompt);
229
+
230
+ if (matchingTerms.length > 0) {
231
+ // Resolve sources for matching terms
232
+ const resolvedSources = await this.resolveSources(matchingTerms);
233
+
234
+ const message = `These terms are directly related to the agent's message so be sure to contextualize your response to this information.`;
235
+ this.emitAgentMessage(
236
+ eventType,
237
+ matchingTerms,
238
+ resolvedSources,
239
+ message
240
+ );
241
+ }
242
+ } catch (error) {
243
+ console.error("LANGUAGE PLUGIN: Error handling agent event:", error);
244
+ }
245
+ }
246
+
178
247
  async embed(userPrompt: string) {
179
248
  return [];
180
249
  }
181
250
 
182
- async call(userPrompt: string) {
251
+ async getMatchingTerms(userPrompt: string) {
183
252
  // Get language configuration
184
253
  const languageConfig = await getLanguageConfig();
185
254
  const terms = Object.keys(languageConfig);
@@ -194,6 +263,12 @@ export class LanguagePlugin extends PluginBase implements Plugin {
194
263
  : userPrompt.toLowerCase().includes(trimmedPattern.toLowerCase());
195
264
  })
196
265
  );
266
+ return matchingTerms;
267
+ }
268
+
269
+ async call(userPrompt: string) {
270
+ // Get language configuration
271
+ const matchingTerms = await this.getMatchingTerms(userPrompt);
197
272
 
198
273
  if (matchingTerms.length === 0) {
199
274
  return "LANGUAGE PLUGIN: No matching terms found";
@@ -206,6 +281,8 @@ export class LanguagePlugin extends PluginBase implements Plugin {
206
281
  return "LANGUAGE PLUGIN: No matching terms found";
207
282
  }
208
283
 
284
+ console.log("LANGUAGE PLUGIN: Matching terms found:", matchingTerms);
285
+
209
286
  // Return the file contents in a format that can be added to the prompt context
210
287
  return `LANGUAGE PLUGIN: The user mentioned these terms triggering contextual expansions ${matchingTerms} expanded to: ${JSON.stringify(
211
288
  contexts
@@ -1,8 +1,18 @@
1
1
  import { Message } from "../clients/types";
2
2
  import { MessageProcessorFunction } from "../services/MessageProcessor";
3
3
  import { ToolsService } from "../services";
4
- import { Tool } from "../clients";
5
- import * as jq from "node-jq";
4
+ import {
5
+ jqToolResponseDefinition,
6
+ executeJqQuery,
7
+ grepToolResponseDefinition,
8
+ executeGrep,
9
+ GrepOptions,
10
+ tailToolResponseDefinition,
11
+ executeTail,
12
+ TailOptions,
13
+ listStoredToolResponsesDefinition,
14
+ executeListStoredToolResponses,
15
+ } from "./tools";
6
16
 
7
17
  interface ToolResponseStorage {
8
18
  [toolCallId: string]: string;
@@ -21,7 +31,7 @@ interface ToolResponseMetadataStorage {
21
31
  export class ToolResponseCache {
22
32
  private storage: ToolResponseStorage = {};
23
33
  private metadataStorage: ToolResponseMetadataStorage = {};
24
- private toolName: string = jqToolResponseDefinition.function.name;
34
+ private toolNameMap: { [toolCallId: string]: string } = {};
25
35
 
26
36
  constructor(toolsService: ToolsService) {
27
37
  this.registerTool(toolsService);
@@ -68,8 +78,19 @@ export class ToolResponseCache {
68
78
  /**
69
79
  * Stores a tool response for later manipulation
70
80
  */
71
- public storeToolResponse(content: string, toolCallId: string): void {
72
- // Always store the original content for later JQ manipulation
81
+ public storeToolResponse(
82
+ content: string,
83
+ toolCallId: string,
84
+ toolName?: string
85
+ ): void {
86
+ // Only store if not already stored - prevents overwriting with compressed data
87
+ // The first time we see a tool response, it contains the original uncompressed content
88
+ // On subsequent passes (after compression), we don't want to overwrite with compressed data
89
+ if (this.storage[toolCallId]) {
90
+ return;
91
+ }
92
+
93
+ // Store the original content for later JQ/grep manipulation
73
94
  this.storage[toolCallId] = content;
74
95
 
75
96
  // Store metadata for reference
@@ -78,6 +99,10 @@ export class ToolResponseCache {
78
99
  originalLength: content.length,
79
100
  storedAt: Date.now(),
80
101
  };
102
+
103
+ if (toolName) {
104
+ this.toolNameMap[toolCallId] = toolName;
105
+ }
81
106
  }
82
107
 
83
108
  /**
@@ -94,7 +119,7 @@ export class ToolResponseCache {
94
119
  }
95
120
 
96
121
  // Store the tool response silently without modifying the message
97
- this.storeToolResponse(message.content, message.tool_call_id);
122
+ this.storeToolResponse(message.content, message.tool_call_id, message.name);
98
123
  }
99
124
 
100
125
  /**
@@ -104,7 +129,7 @@ export class ToolResponseCache {
104
129
  filterFn?: (msg: Message) => boolean
105
130
  ): MessageProcessorFunction {
106
131
  return async (originalMessages: Message[], modifiedMessages: Message[]) => {
107
- for (const message of modifiedMessages) {
132
+ for (const message of originalMessages) {
108
133
  if (filterFn && !filterFn(message)) {
109
134
  continue;
110
135
  }
@@ -121,56 +146,44 @@ export class ToolResponseCache {
121
146
  jqQuery: string
122
147
  ): Promise<string> {
123
148
  const data = this.storage[toolCallId];
149
+ const availableIds = Object.keys(this.storage);
150
+ return executeJqQuery(data, toolCallId, jqQuery, availableIds);
151
+ }
124
152
 
125
- if (!data) {
126
- const availableIds = Object.keys(this.storage);
127
- return `Error: No tool response found for toolCallId "${toolCallId}". Available IDs: ${availableIds.join(
128
- ", "
129
- )}`;
130
- }
153
+ /**
154
+ * Grep through tool response data to find matching lines
155
+ */
156
+ async grepToolResponse(
157
+ toolCallId: string,
158
+ pattern: string,
159
+ options?: GrepOptions
160
+ ): Promise<string> {
161
+ const data = this.storage[toolCallId];
162
+ const availableIds = Object.keys(this.storage);
163
+ return executeGrep(data, toolCallId, pattern, availableIds, options);
164
+ }
131
165
 
132
- try {
133
- // First parse the stored string as JSON, then handle nested JSON strings
134
- const jsonData = this.tryParseJson(data);
135
- if (!jsonData) {
136
- return `Error: Tool response data is not valid JSON for toolCallId "${toolCallId}"`;
137
- }
138
- const parsedData = this.parseNestedJsonStrings(jsonData);
139
-
140
- // Execute JQ query
141
- const result = await jq.run(jqQuery, parsedData, { input: "json" });
142
-
143
- // Handle the result based on its type
144
- if (typeof result === "string") {
145
- return result;
146
- } else if (typeof result === "number" || typeof result === "boolean") {
147
- return String(result);
148
- } else if (result === null) {
149
- return "null";
150
- } else {
151
- return JSON.stringify(result);
152
- }
153
- } catch (error: any) {
154
- // If JQ fails, try to provide helpful error message
155
- let errorMessage = `JQ Query Error: ${error.message}`;
156
-
157
- // Try to parse as JSON to see if it's valid
158
- const jsonObj = this.tryParseJson(data);
159
- if (!jsonObj) {
160
- errorMessage += `\nNote: The tool response data is not valid JSON. Raw data preview:\n${data.substring(
161
- 0,
162
- 300
163
- )}...`;
164
- } else {
165
- errorMessage += `\nData structure preview:\n${JSON.stringify(
166
- jsonObj,
167
- null,
168
- 2
169
- ).substring(0, 500)}...`;
170
- }
166
+ /**
167
+ * Get the last n lines from a tool response
168
+ */
169
+ async tailToolResponse(
170
+ toolCallId: string,
171
+ options?: TailOptions
172
+ ): Promise<string> {
173
+ const data = this.storage[toolCallId];
174
+ const availableIds = Object.keys(this.storage);
175
+ return executeTail(data, toolCallId, availableIds, options);
176
+ }
171
177
 
172
- return errorMessage;
173
- }
178
+ /**
179
+ * List all stored tool responses with metadata
180
+ */
181
+ async listStoredToolResponses(): Promise<string> {
182
+ return executeListStoredToolResponses(
183
+ this.storage,
184
+ this.metadataStorage,
185
+ this.toolNameMap
186
+ );
174
187
  }
175
188
 
176
189
  /**
@@ -206,36 +219,42 @@ export class ToolResponseCache {
206
219
  * Registers the jqToolResponse tool with the ToolsService
207
220
  */
208
221
  registerTool(toolsService: ToolsService): void {
209
- toolsService.addTools([jqToolResponseDefinition]);
222
+ toolsService.addTools([
223
+ jqToolResponseDefinition,
224
+ grepToolResponseDefinition,
225
+ tailToolResponseDefinition,
226
+ listStoredToolResponsesDefinition,
227
+ ]);
210
228
  toolsService.addFunctions({
211
- [this.toolName]: async (toolCallId: string, jqQuery: string) => {
229
+ [jqToolResponseDefinition.function.name]: async (
230
+ toolCallId: string,
231
+ jqQuery: string
232
+ ) => {
212
233
  return await this.queryToolResponse(toolCallId, jqQuery);
213
234
  },
235
+ [grepToolResponseDefinition.function.name]: async (
236
+ toolCallId: string,
237
+ pattern: string,
238
+ options?: any
239
+ ) => {
240
+ return await this.grepToolResponse(toolCallId, pattern, options);
241
+ },
242
+ [tailToolResponseDefinition.function.name]: async (
243
+ toolCallId: string,
244
+ options?: any
245
+ ) => {
246
+ return await this.tailToolResponse(toolCallId, options);
247
+ },
248
+ [listStoredToolResponsesDefinition.function.name]: async () => {
249
+ return await this.listStoredToolResponses();
250
+ },
214
251
  });
215
252
  }
216
253
  }
217
254
 
218
- export const jqToolResponseDefinition: Tool = {
219
- type: "function",
220
- function: {
221
- name: "jqToolResponse",
222
- description:
223
- "Execute a JQ query on a stored tool response to extract specific data. Use this when you need to extract specific information from any tool response that has been stored. Many MCP tool responses store data in nested structures like .content[0].text where the actual data array is located.",
224
- parameters: {
225
- type: "object",
226
- positional: true,
227
- properties: {
228
- toolCallId: {
229
- type: "string",
230
- description: "The toolCallId of the stored tool response",
231
- },
232
- jqQuery: {
233
- type: "string",
234
- description:
235
- "The JQ query to execute on the tool response data. Examples: '.content[0].text | map(.title)' (extract titles from MCP array), '.content[0].text | map(select(.createdAt > \"2025-01-01\"))' (filter MCP items by date) ",
236
- },
237
- },
238
- required: ["toolCallId", "jqQuery"],
239
- },
240
- },
255
+ export {
256
+ jqToolResponseDefinition,
257
+ grepToolResponseDefinition,
258
+ tailToolResponseDefinition,
259
+ listStoredToolResponsesDefinition,
241
260
  };