@tyvm/knowhow 0.0.83 → 0.0.85

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 (237) hide show
  1. package/package.json +4 -2
  2. package/src/agents/base/base.ts +72 -62
  3. package/src/agents/index.ts +30 -14
  4. package/src/agents/researcher/researcher.ts +1 -2
  5. package/src/agents/tools/aiClient.ts +48 -0
  6. package/src/agents/tools/list.ts +57 -0
  7. package/src/agents/tools/startAgentTask.ts +3 -1
  8. package/src/chat/CliChatService.ts +20 -4
  9. package/src/chat/modules/AgentModule.ts +399 -357
  10. package/src/chat/modules/CustomCommandsModule.ts +0 -1
  11. package/src/chat/modules/InternalChatModule.ts +18 -2
  12. package/src/chat/modules/RendererModule.ts +109 -0
  13. package/src/chat/modules/SessionsModule.ts +854 -0
  14. package/src/chat/modules/SetupModule.ts +6 -8
  15. package/src/chat/modules/index.ts +1 -0
  16. package/src/chat/renderer/CompactRenderer.ts +209 -0
  17. package/src/chat/renderer/ConsoleRenderer.ts +141 -0
  18. package/src/chat/renderer/FancyRenderer.ts +421 -0
  19. package/src/chat/renderer/index.ts +5 -0
  20. package/src/chat/renderer/loadRenderer.ts +314 -0
  21. package/src/chat/renderer/messagesToRenderEvents.ts +96 -0
  22. package/src/chat/renderer/types.ts +88 -0
  23. package/src/chat/types.ts +5 -0
  24. package/src/chat.ts +69 -5
  25. package/src/cli.ts +24 -5
  26. package/src/clients/index.ts +91 -0
  27. package/src/clients/pricing/google.ts +81 -2
  28. package/src/clients/pricing/openai.ts +68 -0
  29. package/src/config.ts +15 -0
  30. package/src/plugins/AgentsMdPlugin.ts +1 -1
  31. package/src/plugins/GitPlugin.ts +20 -20
  32. package/src/plugins/PluginBase.ts +11 -0
  33. package/src/plugins/SkillsPlugin.ts +150 -0
  34. package/src/plugins/asana.ts +4 -4
  35. package/src/plugins/embedding.ts +3 -5
  36. package/src/plugins/exec.ts +3 -3
  37. package/src/plugins/figma.ts +3 -7
  38. package/src/plugins/github.ts +18 -29
  39. package/src/plugins/jira.ts +2 -2
  40. package/src/plugins/language.ts +4 -4
  41. package/src/plugins/linear.ts +4 -4
  42. package/src/plugins/notion.ts +6 -8
  43. package/src/plugins/plugins.ts +29 -3
  44. package/src/plugins/url.ts +2 -2
  45. package/src/plugins/vim.ts +4 -3
  46. package/src/services/AgentService.ts +17 -0
  47. package/src/services/AgentSyncFs.ts +3 -0
  48. package/src/services/EventService.ts +168 -27
  49. package/src/services/KnowhowClient.ts +1 -0
  50. package/src/services/SessionManager.ts +51 -1
  51. package/src/services/SyncedAgentWatcher.ts +397 -0
  52. package/src/services/SyncerService.ts +147 -0
  53. package/src/services/index.ts +2 -0
  54. package/src/services/modules/index.ts +14 -3
  55. package/src/types.ts +103 -5
  56. package/src/worker.ts +80 -2
  57. package/src/workers/auth/PasskeySetup.ts +185 -0
  58. package/src/workers/auth/WorkerPasskeyAuth.ts +190 -0
  59. package/src/workers/auth/types.ts +58 -0
  60. package/src/workers/tools/getChallenge.ts +33 -0
  61. package/src/workers/tools/index.ts +8 -0
  62. package/src/workers/tools/lock.ts +31 -0
  63. package/src/workers/tools/unlock.ts +116 -0
  64. package/tests/clients/pricing.test.ts +144 -0
  65. package/tests/unit/modules/moduleLoading.test.ts +226 -0
  66. package/tests/unit/plugins/pluginLoading.test.ts +151 -0
  67. package/ts_build/package.json +4 -2
  68. package/ts_build/src/agents/base/base.d.ts +4 -3
  69. package/ts_build/src/agents/base/base.js +54 -30
  70. package/ts_build/src/agents/base/base.js.map +1 -1
  71. package/ts_build/src/agents/index.d.ts +3 -0
  72. package/ts_build/src/agents/index.js +21 -11
  73. package/ts_build/src/agents/index.js.map +1 -1
  74. package/ts_build/src/agents/researcher/researcher.js +1 -1
  75. package/ts_build/src/agents/researcher/researcher.js.map +1 -1
  76. package/ts_build/src/agents/tools/aiClient.d.ts +3 -0
  77. package/ts_build/src/agents/tools/aiClient.js +31 -1
  78. package/ts_build/src/agents/tools/aiClient.js.map +1 -1
  79. package/ts_build/src/agents/tools/list.js +48 -0
  80. package/ts_build/src/agents/tools/list.js.map +1 -1
  81. package/ts_build/src/agents/tools/startAgentTask.js +2 -1
  82. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  83. package/ts_build/src/chat/CliChatService.js +16 -5
  84. package/ts_build/src/chat/CliChatService.js.map +1 -1
  85. package/ts_build/src/chat/modules/AgentModule.d.ts +34 -17
  86. package/ts_build/src/chat/modules/AgentModule.js +248 -258
  87. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  88. package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -1
  89. package/ts_build/src/chat/modules/InternalChatModule.d.ts +3 -0
  90. package/ts_build/src/chat/modules/InternalChatModule.js +16 -1
  91. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  92. package/ts_build/src/chat/modules/RendererModule.d.ts +16 -0
  93. package/ts_build/src/chat/modules/RendererModule.js +76 -0
  94. package/ts_build/src/chat/modules/RendererModule.js.map +1 -0
  95. package/ts_build/src/chat/modules/SessionsModule.d.ts +33 -0
  96. package/ts_build/src/chat/modules/SessionsModule.js +582 -0
  97. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -0
  98. package/ts_build/src/chat/modules/SetupModule.d.ts +3 -3
  99. package/ts_build/src/chat/modules/SetupModule.js +4 -6
  100. package/ts_build/src/chat/modules/SetupModule.js.map +1 -1
  101. package/ts_build/src/chat/modules/index.d.ts +1 -0
  102. package/ts_build/src/chat/modules/index.js +3 -1
  103. package/ts_build/src/chat/modules/index.js.map +1 -1
  104. package/ts_build/src/chat/renderer/CompactRenderer.d.ts +23 -0
  105. package/ts_build/src/chat/renderer/CompactRenderer.js +167 -0
  106. package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -0
  107. package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +22 -0
  108. package/ts_build/src/chat/renderer/ConsoleRenderer.js +110 -0
  109. package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -0
  110. package/ts_build/src/chat/renderer/FancyRenderer.d.ts +23 -0
  111. package/ts_build/src/chat/renderer/FancyRenderer.js +328 -0
  112. package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -0
  113. package/ts_build/src/chat/renderer/index.d.ts +5 -0
  114. package/ts_build/src/chat/renderer/index.js +29 -0
  115. package/ts_build/src/chat/renderer/index.js.map +1 -0
  116. package/ts_build/src/chat/renderer/loadRenderer.d.ts +4 -0
  117. package/ts_build/src/chat/renderer/loadRenderer.js +246 -0
  118. package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -0
  119. package/ts_build/src/chat/renderer/messagesToRenderEvents.d.ts +15 -0
  120. package/ts_build/src/chat/renderer/messagesToRenderEvents.js +72 -0
  121. package/ts_build/src/chat/renderer/messagesToRenderEvents.js.map +1 -0
  122. package/ts_build/src/chat/renderer/types.d.ts +75 -0
  123. package/ts_build/src/chat/renderer/types.js +3 -0
  124. package/ts_build/src/chat/renderer/types.js.map +1 -0
  125. package/ts_build/src/chat/types.d.ts +5 -0
  126. package/ts_build/src/chat.js +46 -4
  127. package/ts_build/src/chat.js.map +1 -1
  128. package/ts_build/src/cli.js +18 -5
  129. package/ts_build/src/cli.js.map +1 -1
  130. package/ts_build/src/clients/gemini.d.ts +10 -10
  131. package/ts_build/src/clients/index.d.ts +10 -0
  132. package/ts_build/src/clients/index.js +58 -0
  133. package/ts_build/src/clients/index.js.map +1 -1
  134. package/ts_build/src/clients/pricing/google.d.ts +10 -10
  135. package/ts_build/src/clients/pricing/google.js +74 -2
  136. package/ts_build/src/clients/pricing/google.js.map +1 -1
  137. package/ts_build/src/clients/pricing/openai.js +65 -0
  138. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  139. package/ts_build/src/config.d.ts +1 -0
  140. package/ts_build/src/config.js +17 -1
  141. package/ts_build/src/config.js.map +1 -1
  142. package/ts_build/src/plugins/AgentsMdPlugin.js +1 -1
  143. package/ts_build/src/plugins/AgentsMdPlugin.js.map +1 -1
  144. package/ts_build/src/plugins/GitPlugin.js +20 -20
  145. package/ts_build/src/plugins/GitPlugin.js.map +1 -1
  146. package/ts_build/src/plugins/PluginBase.d.ts +1 -0
  147. package/ts_build/src/plugins/PluginBase.js +13 -0
  148. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  149. package/ts_build/src/plugins/SkillsPlugin.d.ts +13 -0
  150. package/ts_build/src/plugins/SkillsPlugin.js +149 -0
  151. package/ts_build/src/plugins/SkillsPlugin.js.map +1 -0
  152. package/ts_build/src/plugins/asana.js +4 -4
  153. package/ts_build/src/plugins/asana.js.map +1 -1
  154. package/ts_build/src/plugins/embedding.js +3 -3
  155. package/ts_build/src/plugins/embedding.js.map +1 -1
  156. package/ts_build/src/plugins/exec.js +3 -3
  157. package/ts_build/src/plugins/exec.js.map +1 -1
  158. package/ts_build/src/plugins/figma.js +3 -3
  159. package/ts_build/src/plugins/figma.js.map +1 -1
  160. package/ts_build/src/plugins/github.js +18 -18
  161. package/ts_build/src/plugins/github.js.map +1 -1
  162. package/ts_build/src/plugins/jira.js +2 -2
  163. package/ts_build/src/plugins/jira.js.map +1 -1
  164. package/ts_build/src/plugins/language.js +4 -4
  165. package/ts_build/src/plugins/language.js.map +1 -1
  166. package/ts_build/src/plugins/linear.js +4 -4
  167. package/ts_build/src/plugins/linear.js.map +1 -1
  168. package/ts_build/src/plugins/notion.js +6 -6
  169. package/ts_build/src/plugins/notion.js.map +1 -1
  170. package/ts_build/src/plugins/plugins.d.ts +3 -0
  171. package/ts_build/src/plugins/plugins.js +18 -3
  172. package/ts_build/src/plugins/plugins.js.map +1 -1
  173. package/ts_build/src/plugins/url.js +2 -2
  174. package/ts_build/src/plugins/url.js.map +1 -1
  175. package/ts_build/src/plugins/vim.js +2 -2
  176. package/ts_build/src/plugins/vim.js.map +1 -1
  177. package/ts_build/src/services/AgentService.d.ts +3 -0
  178. package/ts_build/src/services/AgentService.js +7 -0
  179. package/ts_build/src/services/AgentService.js.map +1 -1
  180. package/ts_build/src/services/AgentSyncFs.d.ts +1 -0
  181. package/ts_build/src/services/AgentSyncFs.js +2 -0
  182. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  183. package/ts_build/src/services/EventService.d.ts +25 -2
  184. package/ts_build/src/services/EventService.js +92 -14
  185. package/ts_build/src/services/EventService.js.map +1 -1
  186. package/ts_build/src/services/KnowhowClient.d.ts +1 -0
  187. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  188. package/ts_build/src/services/SessionManager.d.ts +6 -0
  189. package/ts_build/src/services/SessionManager.js +39 -1
  190. package/ts_build/src/services/SessionManager.js.map +1 -1
  191. package/ts_build/src/services/SyncedAgentWatcher.d.ts +101 -0
  192. package/ts_build/src/services/SyncedAgentWatcher.js +312 -0
  193. package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -0
  194. package/ts_build/src/services/SyncerService.d.ts +30 -0
  195. package/ts_build/src/services/SyncerService.js +72 -0
  196. package/ts_build/src/services/SyncerService.js.map +1 -0
  197. package/ts_build/src/services/index.d.ts +2 -0
  198. package/ts_build/src/services/index.js +2 -0
  199. package/ts_build/src/services/index.js.map +1 -1
  200. package/ts_build/src/services/modules/index.js +10 -2
  201. package/ts_build/src/services/modules/index.js.map +1 -1
  202. package/ts_build/src/types.d.ts +51 -2
  203. package/ts_build/src/types.js +73 -5
  204. package/ts_build/src/types.js.map +1 -1
  205. package/ts_build/src/worker.d.ts +2 -0
  206. package/ts_build/src/worker.js +59 -4
  207. package/ts_build/src/worker.js.map +1 -1
  208. package/ts_build/src/workers/auth/PasskeySetup.d.ts +10 -0
  209. package/ts_build/src/workers/auth/PasskeySetup.js +131 -0
  210. package/ts_build/src/workers/auth/PasskeySetup.js.map +1 -0
  211. package/ts_build/src/workers/auth/WorkerPasskeyAuth.d.ts +35 -0
  212. package/ts_build/src/workers/auth/WorkerPasskeyAuth.js +129 -0
  213. package/ts_build/src/workers/auth/WorkerPasskeyAuth.js.map +1 -0
  214. package/ts_build/src/workers/auth/types.d.ts +36 -0
  215. package/ts_build/src/workers/auth/types.js +3 -0
  216. package/ts_build/src/workers/auth/types.js.map +1 -0
  217. package/ts_build/src/workers/tools/getChallenge.d.ts +9 -0
  218. package/ts_build/src/workers/tools/getChallenge.js +27 -0
  219. package/ts_build/src/workers/tools/getChallenge.js.map +1 -0
  220. package/ts_build/src/workers/tools/index.d.ts +6 -0
  221. package/ts_build/src/workers/tools/index.js +10 -0
  222. package/ts_build/src/workers/tools/index.js.map +1 -1
  223. package/ts_build/src/workers/tools/lock.d.ts +9 -0
  224. package/ts_build/src/workers/tools/lock.js +27 -0
  225. package/ts_build/src/workers/tools/lock.js.map +1 -0
  226. package/ts_build/src/workers/tools/unlock.d.ts +18 -0
  227. package/ts_build/src/workers/tools/unlock.js +78 -0
  228. package/ts_build/src/workers/tools/unlock.js.map +1 -0
  229. package/ts_build/tests/clients/pricing.test.d.ts +1 -0
  230. package/ts_build/tests/clients/pricing.test.js +90 -0
  231. package/ts_build/tests/clients/pricing.test.js.map +1 -0
  232. package/ts_build/tests/unit/modules/moduleLoading.test.d.ts +1 -0
  233. package/ts_build/tests/unit/modules/moduleLoading.test.js +187 -0
  234. package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -0
  235. package/ts_build/tests/unit/plugins/pluginLoading.test.d.ts +1 -0
  236. package/ts_build/tests/unit/plugins/pluginLoading.test.js +123 -0
  237. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.83",
3
+ "version": "0.0.85",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -40,14 +40,15 @@
40
40
  "dependencies": {
41
41
  "@anthropic-ai/sdk": "^0.39.0",
42
42
  "@aws-sdk/client-s3": "^3.588.0",
43
- "@tyvm/knowhow-tunnel": "0.0.3",
44
43
  "@google/genai": "^0.14.1",
45
44
  "@inquirer/editor": "^4.2.18",
46
45
  "@linear/sdk": "^12.0.0",
47
46
  "@modelcontextprotocol/sdk": "^1.13.3",
48
47
  "@notionhq/client": "^2.2.14",
49
48
  "@octokit/rest": "^20.0.2",
49
+ "@simplewebauthn/server": "^13.3.0",
50
50
  "@types/react": "^19.1.8",
51
+ "@tyvm/knowhow-tunnel": "0.0.3",
51
52
  "asana": "^3.0.16",
52
53
  "axios": "^1.5.0",
53
54
  "cheerio": "^1.0.0",
@@ -61,6 +62,7 @@
61
62
  "ink": "^6.0.1",
62
63
  "isolated-vm": "^5.0.4",
63
64
  "jira-client": "^8.2.2",
65
+ "jiti": "^2.6.1",
64
66
  "marked": "^10.0.0",
65
67
  "marked-terminal": "^6.2.0",
66
68
  "minimatch": "^10.1.2",
@@ -1,4 +1,4 @@
1
- import { EventEmitter } from "events";
1
+ import { EventEmitter } from "events"; // kept for reference; agentEvents now uses EventService
2
2
  import {
3
3
  GenericClient,
4
4
  Message,
@@ -64,13 +64,15 @@ export abstract class BaseAgent implements IAgent {
64
64
  protected summaries = [] as string[];
65
65
  protected currentTaskId: string | null = null;
66
66
 
67
- public agentEvents = new EventEmitter();
67
+ public agentEvents = new EventService();
68
68
  public eventTypes = {
69
69
  newThread: "new_thread",
70
70
  threadUpdate: "thread_update",
71
71
  costUpdate: "cost_update",
72
+ agentLog: "agent:log",
72
73
  toolCall: "tool:pre_call",
73
74
  toolUsed: "tool:post_call",
75
+ agentStatus: "agent:status",
74
76
  notStarted: "not_started",
75
77
  inProgress: "in_progress",
76
78
  done: "done",
@@ -106,7 +108,8 @@ export abstract class BaseAgent implements IAgent {
106
108
  }
107
109
 
108
110
  // Subscribe to "agent:msg" events for dynamic context loading
109
- this.events.on(this.eventTypes.agentMsg, (eventData: any) => {
111
+ // Use setListener with a key so re-creating the agent doesn't double-subscribe
112
+ this.events.setListener({ key: `agent:msg:${this.constructor.name}`, event: this.eventTypes.agentMsg }, (eventData: any) => {
110
113
  if (
111
114
  this.status === this.eventTypes.inProgress ||
112
115
  this.status === this.eventTypes.pause
@@ -120,6 +123,16 @@ export abstract class BaseAgent implements IAgent {
120
123
  });
121
124
  }
122
125
 
126
+ protected log(message: string, level: "info" | "warn" | "error" = "info"): void {
127
+ this.agentEvents.emit(this.eventTypes.agentLog, {
128
+ agentName: this.name,
129
+ message,
130
+ level,
131
+ timestamp: Date.now(),
132
+ taskId: this.currentTaskId,
133
+ });
134
+ }
135
+
123
136
  setMaxTurns(maxTurns: number | null) {
124
137
  this.maxTurns = maxTurns;
125
138
  }
@@ -196,12 +209,12 @@ export abstract class BaseAgent implements IAgent {
196
209
  getClient() {
197
210
  if (!this.client) {
198
211
  if (this.provider) {
199
- console.log("Getting client for provider", this.provider);
212
+ this.log(`Getting client for provider ${this.provider}`);
200
213
  this.client = this.clientService.getClient(this.provider)?.client;
201
214
  }
202
215
 
203
216
  if (!this.client) {
204
- console.log("Getting client for model", this.modelName);
217
+ this.log(`Getting client for model ${this.modelName}`);
205
218
  this.client = this.clientService.getClient(
206
219
  undefined,
207
220
  this.modelName
@@ -246,17 +259,17 @@ export abstract class BaseAgent implements IAgent {
246
259
  private checkLimits(): boolean {
247
260
  // Check turn limit
248
261
  if (this.maxTurns !== null && this.turnCount >= this.maxTurns) {
249
- console.log(`Turn limit reached: ${this.turnCount}/${this.maxTurns}`);
262
+ this.log(`Turn limit reached: ${this.turnCount}/${this.maxTurns}`, "warn");
250
263
  return true;
251
264
  }
252
265
 
253
266
  // Check spend limit
254
267
  if (this.maxSpend !== null && this.totalCostUsd >= this.maxSpend) {
255
- console.log(
268
+ this.log(
256
269
  `Spend limit reached: $${this.totalCostUsd.toFixed(
257
270
  4
258
271
  )}/$${this.maxSpend.toFixed(4)}`
259
- );
272
+ , "warn");
260
273
  return true;
261
274
  }
262
275
 
@@ -264,8 +277,9 @@ export abstract class BaseAgent implements IAgent {
264
277
  if (this.maxRunTimeMs !== null && this.startTimeMs !== null) {
265
278
  const currentRunTimeMs = this.runTime();
266
279
  if (currentRunTimeMs >= this.maxRunTimeMs) {
267
- console.log(
268
- `Runtime limit reached: ${currentRunTimeMs}ms/${this.maxRunTimeMs}ms`
280
+ this.log(
281
+ `Runtime limit reached: ${currentRunTimeMs}ms/${this.maxRunTimeMs}ms`,
282
+ "warn"
269
283
  );
270
284
  return true;
271
285
  }
@@ -343,7 +357,6 @@ export abstract class BaseAgent implements IAgent {
343
357
  this.agentEvents.emit(this.eventTypes.agentSay, {
344
358
  message: message.content,
345
359
  });
346
- console.log("\n", "💬 " + message.content, "\n");
347
360
  }
348
361
  }
349
362
  }
@@ -385,7 +398,7 @@ export abstract class BaseAgent implements IAgent {
385
398
  });
386
399
  return true;
387
400
  } catch (e) {
388
- console.error(e);
401
+ this.log(String(e), "error");
389
402
  return false;
390
403
  }
391
404
  }
@@ -417,10 +430,8 @@ export abstract class BaseAgent implements IAgent {
417
430
  return false;
418
431
  }
419
432
 
420
- console.log(
421
- `Required tool: [${this.requiredToolNames}] not available, checking for finalAnswer`,
422
- this.getEnabledToolNames(),
423
- this.requiredToolNames
433
+ this.log(
434
+ `Required tool: [${this.requiredToolNames}] not available, checking for finalAnswer. Enabled: ${this.getEnabledToolNames().join(", ")}`
424
435
  );
425
436
 
426
437
  // Otherwise we're missing the required tool, lets use finalAnswer if we have it
@@ -430,9 +441,9 @@ export abstract class BaseAgent implements IAgent {
430
441
 
431
442
  // We have the final answer tool, but it wasn't required
432
443
  if (hasFinalAnswer && !requiredFinalAnswer) {
433
- console.warn(
444
+ this.log(
434
445
  "Required tool not available, setting finalAnswer as required tool"
435
- );
446
+ , "warn");
436
447
  this.requiredToolNames.push("finalAnswer");
437
448
  return false;
438
449
  }
@@ -445,22 +456,22 @@ export abstract class BaseAgent implements IAgent {
445
456
  }
446
457
 
447
458
  pause() {
448
- console.log("Pausing agent");
459
+ this.log("Pausing agent");
449
460
  this.agentEvents.emit(this.eventTypes.pause, this);
450
461
  this.status = this.eventTypes.pause;
451
462
  }
452
463
 
453
464
  unpause() {
454
- console.log("Unpausing agent");
465
+ this.log("Unpausing agent");
455
466
  this.agentEvents.emit(this.eventTypes.unpause, this);
456
467
  this.status = this.eventTypes.inProgress;
457
468
  }
458
469
 
459
470
  async unpaused() {
460
471
  return new Promise((resolve) => {
461
- console.log("Waiting for agent to unpause");
472
+ this.log("Waiting for agent to unpause");
462
473
  this.agentEvents.once(this.eventTypes.unpause, () => {
463
- console.log("Agent resumed");
474
+ this.log("Agent resumed");
464
475
  resolve(true);
465
476
  });
466
477
  this.agentEvents.once(this.eventTypes.done, () => {
@@ -470,7 +481,7 @@ export abstract class BaseAgent implements IAgent {
470
481
  }
471
482
 
472
483
  async kill() {
473
- console.log("Killing agent");
484
+ this.log("Killing agent");
474
485
  this.agentEvents.emit(this.eventTypes.kill, this);
475
486
  this.status = this.eventTypes.kill;
476
487
 
@@ -550,15 +561,15 @@ export abstract class BaseAgent implements IAgent {
550
561
  });
551
562
 
552
563
  if (response?.usd_cost === undefined) {
553
- console.warn(
554
- "Response cost is undefined",
555
- JSON.stringify(response, null, 2)
564
+ this.log(
565
+ `Response cost is undefined: ${JSON.stringify(response, null, 2)}`,
566
+ "warn"
556
567
  );
557
568
  const error = response as any;
558
569
  if ("response" in error && "data" in error.response) {
559
- console.warn(
560
- "Response data",
561
- JSON.stringify(error.response.data, null, 2)
570
+ this.log(
571
+ `Response data: ${JSON.stringify(error.response.data, null, 2)}`,
572
+ "warn"
562
573
  );
563
574
  }
564
575
  }
@@ -650,7 +661,7 @@ export abstract class BaseAgent implements IAgent {
650
661
  const error = `Required tool: ${JSON.stringify(
651
662
  this.requiredToolNames
652
663
  )} not available, options are ${this.getEnabledToolNames().join(", ")}`;
653
- console.error(error);
664
+ this.log(error, "error");
654
665
  this.status = this.eventTypes.done;
655
666
  this.agentEvents.emit(this.eventTypes.done, error);
656
667
  return error;
@@ -661,7 +672,7 @@ export abstract class BaseAgent implements IAgent {
661
672
  this.pendingUserMessages.length === 0 &&
662
673
  this.status === this.eventTypes.kill
663
674
  ) {
664
- console.log("Agent killed, stopping execution");
675
+ this.log("Agent killed, stopping execution");
665
676
  this.status = this.eventTypes.done;
666
677
  this.agentEvents.emit(this.eventTypes.done, firstMessage.content);
667
678
  return firstMessage.content;
@@ -672,11 +683,8 @@ export abstract class BaseAgent implements IAgent {
672
683
  messages.length > 30
673
684
  ) {
674
685
  const taskBreakdown = await this.getTaskBreakdown(messages);
675
- console.log(
676
- "Compressing messages",
677
- this.getMessagesLength(messages),
678
- "exceeds",
679
- compressThreshold
686
+ this.log(
687
+ `Compressing messages: ${this.getMessagesLength(messages)} exceeds ${compressThreshold}`
680
688
  );
681
689
  messages = await this.compressMessages(messages, startIndex, endIndex);
682
690
  this.startNewThread(messages);
@@ -722,9 +730,9 @@ export abstract class BaseAgent implements IAgent {
722
730
 
723
731
  if (isRetriable && retryCount < 3) {
724
732
  const delay = 1000 * Math.pow(2, retryCount);
725
- console.warn(
726
- `Agent request failed (attempt ${retryCount + 1}/3), retrying in ${delay}ms...`,
727
- e.message
733
+ this.log(
734
+ `Agent request failed (attempt ${retryCount + 1}/3), retrying in ${delay}ms: ${e.message}`,
735
+ "warn"
728
736
  );
729
737
  await new Promise((resolve) => setTimeout(resolve, delay));
730
738
  return this.call(userInput, _messages, retryCount + 1);
@@ -732,12 +740,12 @@ export abstract class BaseAgent implements IAgent {
732
740
 
733
741
  }
734
742
 
735
- console.error("Agent failed", e);
743
+ this.log(`Agent failed: ${e}`, "error");
736
744
 
737
745
  if ("response" in e && "data" in e.response) {
738
- console.error(
739
- "Error response data:",
740
- JSON.stringify(e.response.data, null, 2)
746
+ this.log(
747
+ `Error response data: ${JSON.stringify(e.response.data, null, 2)}`,
748
+ "error"
741
749
  );
742
750
  }
743
751
 
@@ -783,14 +791,26 @@ export abstract class BaseAgent implements IAgent {
783
791
 
784
792
  logStatus() {
785
793
  const statusMessage = this.getStatusMessage();
786
- console.log(
787
- `\n● ${this.name} status: ${statusMessage}`
788
- );
794
+ this.agentEvents.emit(this.eventTypes.agentStatus, {
795
+ agentName: this.name,
796
+ taskId: this.currentTaskId,
797
+ statusMessage,
798
+ details: {
799
+ totalCostUsd: this.getTotalCostUsd(),
800
+ elapsedMs: this.runTime(),
801
+ remainingTimeMs: this.maxRunTimeMs && this.startTimeMs
802
+ ? this.maxRunTimeMs - (Date.now() - this.startTimeMs)
803
+ : undefined,
804
+ remainingTurns: this.maxTurns ? this.maxTurns - this.turnCount : undefined,
805
+ remainingBudget: this.maxSpend ? this.maxSpend - this.totalCostUsd : undefined,
806
+ },
807
+ timestamp: Date.now(),
808
+ });
789
809
  }
790
810
 
791
811
  addPendingMessage(message: Message) {
792
812
  if (this.status === this.eventTypes.done) {
793
- console.warn("Agent is done, cannot take more messages");
813
+ this.log("Agent is done, cannot take more messages", "warn");
794
814
  } else {
795
815
  const pendingMessages = this.pendingUserMessages.map((m) => m.content);
796
816
  if (pendingMessages.includes(message.content)) {
@@ -839,7 +859,7 @@ export abstract class BaseAgent implements IAgent {
839
859
 
840
860
  this.adjustTotalCostUsd(response.usd_cost);
841
861
 
842
- console.log(response);
862
+ this.log(String(response));
843
863
 
844
864
  this.taskBreakdown = response.choices[0].message.content;
845
865
  return this.taskBreakdown;
@@ -850,13 +870,8 @@ export abstract class BaseAgent implements IAgent {
850
870
  startIndex: number,
851
871
  endIndex: number
852
872
  ) {
853
- console.log(
854
- "Compressing messages from",
855
- startIndex,
856
- "to",
857
- endIndex,
858
- "total messages:",
859
- messages.length
873
+ this.log(
874
+ `Compressing messages from ${startIndex} to ${endIndex}, total messages: ${messages.length}`
860
875
  );
861
876
  const toCompress = messages.slice(startIndex, endIndex);
862
877
  const toCompressPrompt = `We are compressing our conversation to save memory.
@@ -921,13 +936,8 @@ export abstract class BaseAgent implements IAgent {
921
936
  100
922
937
  ).toFixed(2);
923
938
 
924
- console.log(
925
- "Compressed messages from",
926
- oldLength,
927
- "to",
928
- newLength,
929
- compressionRatio + "%",
930
- "reduction in size"
939
+ this.log(
940
+ `Compressed messages from ${oldLength} to ${newLength}, ${compressionRatio}% reduction in size`
931
941
  );
932
942
 
933
943
  return newMessages;
@@ -16,21 +16,37 @@ export * from "./researcher/researcher";
16
16
  export * as tools from "./tools";
17
17
  export { includedTools } from "./tools/list";
18
18
 
19
- let singletons = {} as {
20
- Developer: DeveloperAgent;
21
- Patcher: PatchingAgent;
22
- Researcher: ResearcherAgent;
23
- Setup: SetupAgent;
19
+ export type AgentName = "Developer" | "Patcher" | "Researcher" | "Setup";
20
+
21
+ /**
22
+ * Registry of agent constructors (not instances).
23
+ * Use createAgent() to get a fresh instance per task, avoiding stale event listener issues.
24
+ */
25
+ export const agentConstructors: Record<AgentName, new (context: AgentContext) => any> = {
26
+ Developer: DeveloperAgent,
27
+ Patcher: PatchingAgent,
28
+ Researcher: ResearcherAgent,
29
+ Setup: SetupAgent,
24
30
  };
25
31
 
26
- export function agents(agentContext: AgentContext = services()) {
27
- if (Object.keys(singletons).length === 0) {
28
- singletons = {
29
- Developer: new DeveloperAgent(agentContext),
30
- Patcher: new PatchingAgent(agentContext),
31
- Researcher: new ResearcherAgent(agentContext),
32
- Setup: new SetupAgent(agentContext),
33
- };
32
+ /**
33
+ * Create a fresh agent instance by name.
34
+ * Always returns a new instance to avoid shared state / stale listeners between tasks.
35
+ */
36
+ export function createAgent(agentName: AgentName, agentContext: AgentContext = services()) {
37
+ const AgentClass = agentConstructors[agentName];
38
+ if (!AgentClass) {
39
+ throw new Error(`Agent "${agentName}" not found. Available agents: ${Object.keys(agentConstructors).join(", ")}`);
34
40
  }
35
- return singletons;
41
+ return new AgentClass(agentContext);
42
+ }
43
+
44
+ /** @deprecated Use createAgent() for per-task instances to avoid event listener leaks */
45
+ export function agents(agentContext: AgentContext = services()) {
46
+ return {
47
+ Developer: new DeveloperAgent(agentContext),
48
+ Patcher: new PatchingAgent(agentContext),
49
+ Researcher: new ResearcherAgent(agentContext),
50
+ Setup: new SetupAgent(agentContext),
51
+ };
36
52
  }
@@ -8,7 +8,7 @@ export class ResearcherAgent extends BaseAgent {
8
8
 
9
9
  constructor(context: AgentContext) {
10
10
  super(context);
11
- this.setModel(Models.google.Gemini_20_Flash);
11
+ this.setModel(Models.google.Gemini_3_Flash_Preview);
12
12
  this.setProvider("google");
13
13
  this.disableTool("patchFile");
14
14
  this.disableTool("writeFile");
@@ -106,4 +106,3 @@ export class ResearcherAgent extends BaseAgent {
106
106
  ] as Message[];
107
107
  }
108
108
  }
109
-
@@ -128,3 +128,51 @@ export async function listAllEmbeddingModels(): Promise<Record<string, string[]>
128
128
 
129
129
  return contextClients.listAllEmbeddingModels();
130
130
  }
131
+
132
+ export async function listAllImageModels(): Promise<Record<string, string[]>> {
133
+ // Get context from bound ToolsService
134
+ const toolService = (
135
+ this instanceof ToolsService ? this : services().Tools
136
+ ) as ToolsService;
137
+
138
+ const toolContext = toolService.getContext();
139
+ const { Clients: contextClients } = toolContext;
140
+
141
+ if (!contextClients) {
142
+ throw new Error("Clients not available in tool context");
143
+ }
144
+
145
+ return contextClients.listAllImageModels();
146
+ }
147
+
148
+ export async function listAllAudioModels(): Promise<Record<string, string[]>> {
149
+ // Get context from bound ToolsService
150
+ const toolService = (
151
+ this instanceof ToolsService ? this : services().Tools
152
+ ) as ToolsService;
153
+
154
+ const toolContext = toolService.getContext();
155
+ const { Clients: contextClients } = toolContext;
156
+
157
+ if (!contextClients) {
158
+ throw new Error("Clients not available in tool context");
159
+ }
160
+
161
+ return contextClients.listAllAudioModels();
162
+ }
163
+
164
+ export async function listAllVideoModels(): Promise<Record<string, string[]>> {
165
+ // Get context from bound ToolsService
166
+ const toolService = (
167
+ this instanceof ToolsService ? this : services().Tools
168
+ ) as ToolsService;
169
+
170
+ const toolContext = toolService.getContext();
171
+ const { Clients: contextClients } = toolContext;
172
+
173
+ if (!contextClients) {
174
+ throw new Error("Clients not available in tool context");
175
+ }
176
+
177
+ return contextClients.listAllVideoModels();
178
+ }
@@ -506,6 +506,63 @@ export const includedTools = [
506
506
  },
507
507
  },
508
508
 
509
+ {
510
+ type: "function",
511
+ function: {
512
+ name: "listAllImageModels",
513
+ description:
514
+ "List all available image generation models using the knowhow ai client. Use this to discover which providers and models support image generation.",
515
+ parameters: {
516
+ type: "object",
517
+ properties: {},
518
+ required: [],
519
+ },
520
+ returns: {
521
+ type: "object",
522
+ description:
523
+ "A dictionary of all available image generation models for each provider",
524
+ },
525
+ },
526
+ },
527
+
528
+ {
529
+ type: "function",
530
+ function: {
531
+ name: "listAllAudioModels",
532
+ description:
533
+ "List all available audio generation models (TTS/transcription) using the knowhow ai client. Use this to discover which providers and models support audio generation.",
534
+ parameters: {
535
+ type: "object",
536
+ properties: {},
537
+ required: [],
538
+ },
539
+ returns: {
540
+ type: "object",
541
+ description:
542
+ "A dictionary of all available audio generation models for each provider",
543
+ },
544
+ },
545
+ },
546
+
547
+ {
548
+ type: "function",
549
+ function: {
550
+ name: "listAllVideoModels",
551
+ description:
552
+ "List all available video generation models using the knowhow ai client. Use this to discover which providers and models support video generation.",
553
+ parameters: {
554
+ type: "object",
555
+ properties: {},
556
+ required: [],
557
+ },
558
+ returns: {
559
+ type: "object",
560
+ description:
561
+ "A dictionary of all available video generation models for each provider",
562
+ },
563
+ },
564
+ },
565
+
509
566
  {
510
567
  type: "function",
511
568
  function: {
@@ -32,7 +32,9 @@ function generateTaskId(prompt: string): string {
32
32
  .slice(0, 9);
33
33
  const wordPart = words.join("-") || "task";
34
34
  const epochSeconds = Math.floor(Date.now() / 1000);
35
- return `${epochSeconds}-${wordPart}`;
35
+ const fullId = `${epochSeconds}-${wordPart}`;
36
+ // Truncate to 80 chars to avoid ENAMETOOLONG filesystem errors
37
+ return fullId.slice(0, 80);
36
38
  }
37
39
 
38
40
  /**
@@ -317,13 +317,29 @@ export class CliChatService implements ChatService {
317
317
  console.log("Commands:", commandNames.join(", "));
318
318
 
319
319
  while (true) {
320
+ // Recompute available commands each iteration so mode changes are reflected in autocomplete
321
+ const currentCommandNames = this.getCommandsForActiveModes().map((cmd) => `/${cmd.name}`);
322
+
323
+ // Check active modes for a promptText first, then fall back to context.promptText, then default
324
+ const activeModeWithPrompt = this.modes
325
+ .filter((m) => m.active && m.promptText)
326
+ .slice(-1)[0]; // last active mode with a promptText wins
327
+
328
+ let modePrompt: string | undefined;
329
+ if (activeModeWithPrompt?.promptText) {
330
+ const p = activeModeWithPrompt.promptText;
331
+ modePrompt = typeof p === "function" ? p() : p;
332
+ }
333
+
320
334
  const promptText =
321
- this.context.agentMode && this.context.currentAgent
335
+ modePrompt ||
336
+ this.context.promptText ||
337
+ (this.context.agentMode && this.context.currentAgent
322
338
  ? `\nAsk knowhow ${this.context.currentAgent}: `
323
- : `\nAsk knowhow: `;
339
+ : `\nAsk knowhow: `);
324
340
  try {
325
341
  // Pass command names as autocomplete options
326
- const input = await this.getInput(promptText, commandNames);
342
+ const input = await this.getInput(promptText, currentCommandNames);
327
343
 
328
344
  if (input.trim() === "") {
329
345
  continue;
@@ -336,7 +352,7 @@ export class CliChatService implements ChatService {
336
352
  // Default chat behavior - this would be handled by a chat module
337
353
  const interaction = {
338
354
  input,
339
- output: `I didn't understand that command. Available commands: ${commandNames.join(
355
+ output: `I didn't understand that command. Available commands: ${currentCommandNames.join(
340
356
  ", "
341
357
  )}`,
342
358
  } as ChatInteraction;