@tyvm/knowhow 0.0.144 → 0.0.146

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 (253) hide show
  1. package/bin/knowhow.js +22 -20
  2. package/package.json +1 -1
  3. package/scripts/migrate-usage-json.js +147 -0
  4. package/src/agents/base/base.ts +65 -10
  5. package/src/agents/tools/patch.ts +84 -2
  6. package/src/agents/tools/textSearch.ts +98 -3
  7. package/src/chat/CliChatService.ts +16 -0
  8. package/src/chat/modules/AgentModule.ts +159 -29
  9. package/src/chat/modules/SessionsModule.ts +13 -5
  10. package/src/chat/types.ts +15 -0
  11. package/src/chat.ts +11 -0
  12. package/src/cli.ts +2 -0
  13. package/src/clients/anthropic.ts +80 -1
  14. package/src/clients/contextLimits.ts +40 -0
  15. package/src/clients/fireworks.ts +3 -0
  16. package/src/clients/gemini.ts +67 -5
  17. package/src/clients/http.ts +85 -4
  18. package/src/clients/index.ts +4 -0
  19. package/src/clients/openai.ts +186 -35
  20. package/src/clients/pricing/fireworks.ts +50 -1
  21. package/src/clients/pricing/index.ts +1 -1
  22. package/src/clients/pricing/models.ts +3 -1
  23. package/src/clients/pricing/openai.ts +2 -2
  24. package/src/clients/pricing/qwen.ts +80 -0
  25. package/src/clients/qwen.ts +16 -0
  26. package/src/clients/types.ts +99 -8
  27. package/src/clients/xai.ts +96 -8
  28. package/src/commands/agent.ts +29 -1
  29. package/src/commands/services.ts +49 -3
  30. package/src/index.ts +9 -0
  31. package/src/plugins/plugins.ts +16 -0
  32. package/src/processors/Base64ImageDetector.ts +46 -7
  33. package/src/processors/GemmaToolProcessor.ts +163 -0
  34. package/src/processors/TokenCompressor.ts +40 -8
  35. package/src/processors/ToolResponseCache.ts +17 -8
  36. package/src/processors/index.ts +1 -0
  37. package/src/processors/tools/boundedToolResponse.ts +49 -0
  38. package/src/processors/tools/grepToolResponse.ts +164 -38
  39. package/src/processors/tools/index.ts +2 -0
  40. package/src/processors/tools/jqToolResponse.ts +40 -10
  41. package/src/processors/tools/listStoredToolResponses.ts +51 -10
  42. package/src/processors/tools/tailToolResponse.ts +85 -17
  43. package/src/services/AgentSyncFs.ts +37 -0
  44. package/src/services/LazyToolsService.ts +7 -0
  45. package/src/services/Mcp.ts +9 -2
  46. package/src/services/RuntimeReloadService.ts +31 -0
  47. package/src/services/SessionManager.ts +14 -0
  48. package/src/services/Tools.ts +14 -0
  49. package/src/services/TracingService.ts +116 -0
  50. package/src/services/index.ts +8 -0
  51. package/src/services/modules/computerUse.ts +216 -0
  52. package/src/services/modules/index.ts +90 -9
  53. package/src/services/modules/types.ts +31 -1
  54. package/src/types.ts +3 -0
  55. package/src/util/Trace.ts +236 -0
  56. package/tests/clients/contextLimits.test.ts +4 -0
  57. package/tests/clients/fireworks.test.ts +65 -0
  58. package/tests/clients/openai.images.test.ts +105 -0
  59. package/tests/clients/pricing.test.ts +6 -0
  60. package/tests/clients/reasoningEffort.test.ts +79 -0
  61. package/tests/compressor/toolResponseCache.test.ts +2 -1
  62. package/tests/compressor/usageBase64.json +159 -0
  63. package/tests/compressor/usageBase64.test.ts +175 -0
  64. package/tests/manual/clients/completions.test.ts +27 -10
  65. package/tests/patching/fixtures/transcript-mangles/agent-sync-fs-nested-catch.json +9 -0
  66. package/tests/patching/fixtures/transcript-mangles/automation-functions-crossed.json +9 -0
  67. package/tests/patching/fixtures/transcript-mangles/fruit-ninja-confirmed-retirement.json +8 -0
  68. package/tests/patching/patchFileOutput.test.ts +112 -1
  69. package/tests/patching/transcriptMangleRegression.test.ts +125 -0
  70. package/tests/processors/Base64ImageDetector.test.ts +36 -1
  71. package/tests/processors/TokenCompressor.test.ts +17 -3
  72. package/tests/processors/tools/boundedRetrievalTools.test.ts +68 -0
  73. package/tests/processors/tools/grepToolResponse.test.ts +88 -1
  74. package/tests/unit/plugins/pluginLoading.test.ts +31 -0
  75. package/tests/unit/services/RuntimeReloadService.test.ts +29 -0
  76. package/ts_build/package.json +1 -1
  77. package/ts_build/src/agents/base/base.d.ts +8 -1
  78. package/ts_build/src/agents/base/base.js +37 -3
  79. package/ts_build/src/agents/base/base.js.map +1 -1
  80. package/ts_build/src/agents/tools/patch.d.ts +1 -0
  81. package/ts_build/src/agents/tools/patch.js +35 -2
  82. package/ts_build/src/agents/tools/patch.js.map +1 -1
  83. package/ts_build/src/agents/tools/textSearch.js +56 -2
  84. package/ts_build/src/agents/tools/textSearch.js.map +1 -1
  85. package/ts_build/src/chat/CliChatService.js +15 -0
  86. package/ts_build/src/chat/CliChatService.js.map +1 -1
  87. package/ts_build/src/chat/modules/AgentModule.d.ts +14 -1
  88. package/ts_build/src/chat/modules/AgentModule.js +86 -24
  89. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  90. package/ts_build/src/chat/modules/SessionsModule.js +5 -6
  91. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
  92. package/ts_build/src/chat/types.d.ts +12 -0
  93. package/ts_build/src/chat.js +7 -0
  94. package/ts_build/src/chat.js.map +1 -1
  95. package/ts_build/src/cli.d.ts +2 -1
  96. package/ts_build/src/cli.js +1 -0
  97. package/ts_build/src/cli.js.map +1 -1
  98. package/ts_build/src/clients/anthropic.d.ts +1 -0
  99. package/ts_build/src/clients/anthropic.js +40 -1
  100. package/ts_build/src/clients/anthropic.js.map +1 -1
  101. package/ts_build/src/clients/contextLimits.js +25 -0
  102. package/ts_build/src/clients/contextLimits.js.map +1 -1
  103. package/ts_build/src/clients/fireworks.js +1 -0
  104. package/ts_build/src/clients/fireworks.js.map +1 -1
  105. package/ts_build/src/clients/gemini.js +56 -4
  106. package/ts_build/src/clients/gemini.js.map +1 -1
  107. package/ts_build/src/clients/http.d.ts +2 -0
  108. package/ts_build/src/clients/http.js +66 -9
  109. package/ts_build/src/clients/http.js.map +1 -1
  110. package/ts_build/src/clients/index.d.ts +1 -0
  111. package/ts_build/src/clients/index.js +4 -0
  112. package/ts_build/src/clients/index.js.map +1 -1
  113. package/ts_build/src/clients/openai.d.ts +6 -1
  114. package/ts_build/src/clients/openai.js +135 -35
  115. package/ts_build/src/clients/openai.js.map +1 -1
  116. package/ts_build/src/clients/pricing/fireworks.d.ts +4 -1
  117. package/ts_build/src/clients/pricing/fireworks.js +33 -1
  118. package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
  119. package/ts_build/src/clients/pricing/index.d.ts +1 -1
  120. package/ts_build/src/clients/pricing/index.js +6 -2
  121. package/ts_build/src/clients/pricing/index.js.map +1 -1
  122. package/ts_build/src/clients/pricing/models.d.ts +2 -1
  123. package/ts_build/src/clients/pricing/models.js +4 -1
  124. package/ts_build/src/clients/pricing/models.js.map +1 -1
  125. package/ts_build/src/clients/pricing/openai.js +2 -2
  126. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  127. package/ts_build/src/clients/pricing/qwen.d.ts +5 -0
  128. package/ts_build/src/clients/pricing/qwen.js +30 -0
  129. package/ts_build/src/clients/pricing/qwen.js.map +1 -0
  130. package/ts_build/src/clients/qwen.d.ts +4 -0
  131. package/ts_build/src/clients/qwen.js +15 -0
  132. package/ts_build/src/clients/qwen.js.map +1 -0
  133. package/ts_build/src/clients/types.d.ts +16 -1
  134. package/ts_build/src/clients/types.js +14 -0
  135. package/ts_build/src/clients/types.js.map +1 -1
  136. package/ts_build/src/clients/xai.d.ts +2 -0
  137. package/ts_build/src/clients/xai.js +79 -13
  138. package/ts_build/src/clients/xai.js.map +1 -1
  139. package/ts_build/src/commands/agent.js +15 -1
  140. package/ts_build/src/commands/agent.js.map +1 -1
  141. package/ts_build/src/commands/services.d.ts +2 -0
  142. package/ts_build/src/commands/services.js +45 -3
  143. package/ts_build/src/commands/services.js.map +1 -1
  144. package/ts_build/src/index.d.ts +2 -0
  145. package/ts_build/src/index.js +1 -0
  146. package/ts_build/src/index.js.map +1 -1
  147. package/ts_build/src/plugins/plugins.d.ts +1 -0
  148. package/ts_build/src/plugins/plugins.js +14 -0
  149. package/ts_build/src/plugins/plugins.js.map +1 -1
  150. package/ts_build/src/processors/Base64ImageDetector.js +22 -3
  151. package/ts_build/src/processors/Base64ImageDetector.js.map +1 -1
  152. package/ts_build/src/processors/GemmaToolProcessor.d.ts +14 -0
  153. package/ts_build/src/processors/GemmaToolProcessor.js +97 -0
  154. package/ts_build/src/processors/GemmaToolProcessor.js.map +1 -0
  155. package/ts_build/src/processors/TokenCompressor.js +15 -6
  156. package/ts_build/src/processors/TokenCompressor.js.map +1 -1
  157. package/ts_build/src/processors/ToolResponseCache.d.ts +3 -3
  158. package/ts_build/src/processors/ToolResponseCache.js +8 -8
  159. package/ts_build/src/processors/ToolResponseCache.js.map +1 -1
  160. package/ts_build/src/processors/index.d.ts +1 -0
  161. package/ts_build/src/processors/index.js +3 -1
  162. package/ts_build/src/processors/index.js.map +1 -1
  163. package/ts_build/src/processors/tools/boundedToolResponse.d.ts +7 -0
  164. package/ts_build/src/processors/tools/boundedToolResponse.js +27 -0
  165. package/ts_build/src/processors/tools/boundedToolResponse.js.map +1 -0
  166. package/ts_build/src/processors/tools/grepToolResponse.d.ts +4 -0
  167. package/ts_build/src/processors/tools/grepToolResponse.js +101 -25
  168. package/ts_build/src/processors/tools/grepToolResponse.js.map +1 -1
  169. package/ts_build/src/processors/tools/index.d.ts +2 -2
  170. package/ts_build/src/processors/tools/index.js.map +1 -1
  171. package/ts_build/src/processors/tools/jqToolResponse.d.ts +5 -1
  172. package/ts_build/src/processors/tools/jqToolResponse.js +26 -9
  173. package/ts_build/src/processors/tools/jqToolResponse.js.map +1 -1
  174. package/ts_build/src/processors/tools/listStoredToolResponses.d.ts +7 -1
  175. package/ts_build/src/processors/tools/listStoredToolResponses.js +39 -9
  176. package/ts_build/src/processors/tools/listStoredToolResponses.js.map +1 -1
  177. package/ts_build/src/processors/tools/tailToolResponse.d.ts +5 -0
  178. package/ts_build/src/processors/tools/tailToolResponse.js +56 -13
  179. package/ts_build/src/processors/tools/tailToolResponse.js.map +1 -1
  180. package/ts_build/src/services/AgentSyncFs.js +17 -0
  181. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  182. package/ts_build/src/services/LazyToolsService.d.ts +1 -0
  183. package/ts_build/src/services/LazyToolsService.js +5 -0
  184. package/ts_build/src/services/LazyToolsService.js.map +1 -1
  185. package/ts_build/src/services/Mcp.js +8 -2
  186. package/ts_build/src/services/Mcp.js.map +1 -1
  187. package/ts_build/src/services/RuntimeReloadService.d.ts +12 -0
  188. package/ts_build/src/services/RuntimeReloadService.js +26 -0
  189. package/ts_build/src/services/RuntimeReloadService.js.map +1 -0
  190. package/ts_build/src/services/SessionManager.js +12 -0
  191. package/ts_build/src/services/SessionManager.js.map +1 -1
  192. package/ts_build/src/services/Tools.d.ts +3 -0
  193. package/ts_build/src/services/Tools.js +5 -0
  194. package/ts_build/src/services/Tools.js.map +1 -1
  195. package/ts_build/src/services/TracingService.d.ts +16 -0
  196. package/ts_build/src/services/TracingService.js +35 -0
  197. package/ts_build/src/services/TracingService.js.map +1 -0
  198. package/ts_build/src/services/index.d.ts +6 -0
  199. package/ts_build/src/services/index.js +7 -1
  200. package/ts_build/src/services/index.js.map +1 -1
  201. package/ts_build/src/services/modules/computerUse.d.ts +163 -0
  202. package/ts_build/src/services/modules/computerUse.js +3 -0
  203. package/ts_build/src/services/modules/computerUse.js.map +1 -0
  204. package/ts_build/src/services/modules/index.d.ts +6 -0
  205. package/ts_build/src/services/modules/index.js +49 -6
  206. package/ts_build/src/services/modules/index.js.map +1 -1
  207. package/ts_build/src/services/modules/types.d.ts +7 -0
  208. package/ts_build/src/types.d.ts +3 -0
  209. package/ts_build/src/types.js.map +1 -1
  210. package/ts_build/src/util/Trace.d.ts +13 -0
  211. package/ts_build/src/util/Trace.js +107 -0
  212. package/ts_build/src/util/Trace.js.map +1 -0
  213. package/ts_build/src/workers/tools/index.d.ts +1 -1
  214. package/ts_build/tests/clients/contextLimits.test.js +2 -0
  215. package/ts_build/tests/clients/contextLimits.test.js.map +1 -1
  216. package/ts_build/tests/clients/fireworks.test.d.ts +1 -0
  217. package/ts_build/tests/clients/fireworks.test.js +65 -0
  218. package/ts_build/tests/clients/fireworks.test.js.map +1 -0
  219. package/ts_build/tests/clients/openai.images.test.d.ts +1 -0
  220. package/ts_build/tests/clients/openai.images.test.js +94 -0
  221. package/ts_build/tests/clients/openai.images.test.js.map +1 -0
  222. package/ts_build/tests/clients/pricing.test.js +4 -0
  223. package/ts_build/tests/clients/pricing.test.js.map +1 -1
  224. package/ts_build/tests/clients/reasoningEffort.test.d.ts +1 -0
  225. package/ts_build/tests/clients/reasoningEffort.test.js +68 -0
  226. package/ts_build/tests/clients/reasoningEffort.test.js.map +1 -0
  227. package/ts_build/tests/compressor/toolResponseCache.test.js +1 -1
  228. package/ts_build/tests/compressor/toolResponseCache.test.js.map +1 -1
  229. package/ts_build/tests/compressor/usageBase64.test.d.ts +1 -0
  230. package/ts_build/tests/compressor/usageBase64.test.js +109 -0
  231. package/ts_build/tests/compressor/usageBase64.test.js.map +1 -0
  232. package/ts_build/tests/manual/clients/completions.test.js +19 -6
  233. package/ts_build/tests/manual/clients/completions.test.js.map +1 -1
  234. package/ts_build/tests/patching/patchFileOutput.test.js +82 -0
  235. package/ts_build/tests/patching/patchFileOutput.test.js.map +1 -1
  236. package/ts_build/tests/patching/transcriptMangleRegression.test.d.ts +1 -0
  237. package/ts_build/tests/patching/transcriptMangleRegression.test.js +104 -0
  238. package/ts_build/tests/patching/transcriptMangleRegression.test.js.map +1 -0
  239. package/ts_build/tests/processors/Base64ImageDetector.test.js +24 -0
  240. package/ts_build/tests/processors/Base64ImageDetector.test.js.map +1 -1
  241. package/ts_build/tests/processors/TokenCompressor.test.js +11 -2
  242. package/ts_build/tests/processors/TokenCompressor.test.js.map +1 -1
  243. package/ts_build/tests/processors/tools/boundedRetrievalTools.test.d.ts +1 -0
  244. package/ts_build/tests/processors/tools/boundedRetrievalTools.test.js +57 -0
  245. package/ts_build/tests/processors/tools/boundedRetrievalTools.test.js.map +1 -0
  246. package/ts_build/tests/processors/tools/grepToolResponse.test.js +37 -0
  247. package/ts_build/tests/processors/tools/grepToolResponse.test.js.map +1 -1
  248. package/ts_build/tests/unit/plugins/pluginLoading.test.js +59 -0
  249. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
  250. package/ts_build/tests/unit/services/RuntimeReloadService.test.d.ts +1 -0
  251. package/ts_build/tests/unit/services/RuntimeReloadService.test.js +26 -0
  252. package/ts_build/tests/unit/services/RuntimeReloadService.test.js.map +1 -0
  253. package/x-home.png +0 -0
@@ -18,7 +18,12 @@ import { BaseChatModule } from "./BaseChatModule";
18
18
  import { services } from "../../services/index";
19
19
  import { BaseAgent } from "../../agents/index";
20
20
  import { ChatCommand, ChatMode, ChatContext, ChatService } from "../types";
21
- import { Message, CompletionOptions } from "../../clients/types";
21
+ import {
22
+ Message,
23
+ CompletionOptions,
24
+ REASONING_EFFORTS,
25
+ isReasoningEffort,
26
+ } from "../../clients/types";
22
27
  import { ChatInteraction } from "../../types";
23
28
  import { Marked } from "../../utils/index";
24
29
  import { TokenCompressor } from "../../processors/TokenCompressor";
@@ -27,6 +32,7 @@ import {
27
32
  CustomVariables,
28
33
  XmlToolCallProcessor,
29
34
  HarmonyToolProcessor,
35
+ GemmaToolProcessor,
30
36
  Base64ImageProcessor,
31
37
  MinimalToolsMessageProcessor,
32
38
  } from "../../processors/index";
@@ -172,21 +178,15 @@ export class AgentModule extends BaseChatModule {
172
178
  {
173
179
  name: "reasoning_effort",
174
180
  description:
175
- "Set the reasoning effort for the agent (none, low, medium, high). Use 'none' to disable thinking on models that support it.",
181
+ `Set the reasoning effort for the agent (${REASONING_EFFORTS.join(", ")}). Individual models support different subsets.`,
176
182
  handler: async (args: string[]): Promise<void> => {
177
- const validEfforts: CompletionOptions["reasoning_effort"][] = [
178
- "none",
179
- "low",
180
- "medium",
181
- "high",
182
- ];
183
- const effort = args[0] as CompletionOptions["reasoning_effort"];
184
- if (!effort || !validEfforts.includes(effort)) {
183
+ const effort = args[0];
184
+ if (!isReasoningEffort(effort)) {
185
185
  console.log(
186
186
  `Current reasoning effort: ${this.reasoningEffort ?? "(not set — model default)"}`
187
187
  );
188
188
  console.log(
189
- `Usage: /reasoning_effort <${validEfforts.join("|")}>`
189
+ `Usage: /reasoning_effort <${REASONING_EFFORTS.join("|")}>`
190
190
  );
191
191
  return;
192
192
  }
@@ -200,19 +200,13 @@ export class AgentModule extends BaseChatModule {
200
200
  name: "effort",
201
201
  description: "Alias for /reasoning_effort",
202
202
  handler: async (args: string[]): Promise<void> => {
203
- const validEfforts: CompletionOptions["reasoning_effort"][] = [
204
- "none",
205
- "low",
206
- "medium",
207
- "high",
208
- ];
209
- const effort = args[0] as CompletionOptions["reasoning_effort"];
210
- if (!effort || !validEfforts.includes(effort)) {
203
+ const effort = args[0];
204
+ if (!isReasoningEffort(effort)) {
211
205
  console.log(
212
206
  `Current reasoning effort: ${this.reasoningEffort ?? "(not set — model default)"}`
213
207
  );
214
208
  console.log(
215
- `Usage: /effort <${validEfforts.join("|")}>`
209
+ `Usage: /effort <${REASONING_EFFORTS.join("|")}>`
216
210
  );
217
211
  return;
218
212
  }
@@ -246,8 +240,15 @@ export class AgentModule extends BaseChatModule {
246
240
  description: "Detach from the currently attached agent",
247
241
  modes: ["agent:attached"],
248
242
  handler: async (_args: string[]): Promise<void> => {
243
+ const agent = this.attachedAgent;
249
244
  console.log("Detached from agent");
250
- this.detachFromAgent();
245
+ // Only detach if we are still attached to THIS agent. If the user
246
+ // killed this agent and started a new one while this agent was
247
+ // finishing a long tool call, detachFromAgent() would rip out the
248
+ // new agent's rendering and reset the mode.
249
+ if (agent && this.attachedAgent === agent) {
250
+ this.detachFromAgent();
251
+ }
251
252
  },
252
253
  },
253
254
  {
@@ -339,7 +340,34 @@ export class AgentModule extends BaseChatModule {
339
340
  }
340
341
  );
341
342
 
342
- await this.handleAgentCommand(["Patcher"]);
343
+ // Determine the default agent from context (set by chat.ts from config.chat.defaultAgent)
344
+ // falling back to "Patcher" if not configured.
345
+ const context = this.chatService?.getContext();
346
+ const defaultAgentName = context?.defaultAgent ?? "Patcher";
347
+ await this.handleAgentCommand([defaultAgentName]);
348
+
349
+ // Apply defaultModel and defaultProvider if configured
350
+ const defaultModel = context?.defaultModel;
351
+ const defaultProvider = context?.defaultProvider;
352
+ if (defaultModel || defaultProvider) {
353
+ const selectedAgent = context?.selectedAgent;
354
+ if (selectedAgent) {
355
+ if (defaultModel) {
356
+ selectedAgent.setModel(defaultModel);
357
+ selectedAgent.setModelPreferences([
358
+ { model: defaultModel, provider: defaultProvider as any },
359
+ ]);
360
+ }
361
+ if (defaultProvider) {
362
+ selectedAgent.setProvider(defaultProvider as any);
363
+ }
364
+ // Refresh context model/provider display
365
+ if (context) {
366
+ if (defaultModel) context.currentModel = defaultModel;
367
+ if (defaultProvider) context.currentProvider = defaultProvider;
368
+ }
369
+ }
370
+ }
343
371
  }
344
372
 
345
373
  async handleAgentCommand(args: string[]): Promise<void> {
@@ -682,6 +710,11 @@ export class AgentModule extends BaseChatModule {
682
710
  messageId: session.knowhowMessageId,
683
711
  chatHistory: [],
684
712
  run: false, // Don't run yet, we need to set up event listeners first
713
+ // Restore the exact model/provider/reasoning the original run used.
714
+ model: session.model,
715
+ provider: session.provider,
716
+ reasoningEffort: session.reasoningEffort,
717
+ summarizeReasoning: session.summarizeReasoning,
685
718
  });
686
719
 
687
720
  // After resume finishes, revert to normal chat (non-agent mode) so the
@@ -758,6 +791,10 @@ export class AgentModule extends BaseChatModule {
758
791
  run?: boolean; // whether to run immediately
759
792
  taskId?: string; // optional pre-generated taskId
760
793
  parentTaskId?: string; // taskId of the parent agent that spawned this task
794
+ // Reasoning settings to restore (e.g. on resume). When provided these take
795
+ // precedence over the module-level CLI defaults.
796
+ reasoningEffort?: CompletionOptions["reasoning_effort"];
797
+ summarizeReasoning?: boolean;
761
798
  }) {
762
799
  const { input, chatHistory = [], agentName } = options;
763
800
  const agentContext = services().Agents.getAgentContext();
@@ -841,8 +878,15 @@ export class AgentModule extends BaseChatModule {
841
878
 
842
879
  // Set up session update listener
843
880
  const threadUpdateHandler = async (threadState: any) => {
844
- this.updateSession(taskId, agent.getThreads());
845
881
  taskInfo.totalCost = agent.getTotalCostUsd();
882
+ // Capture the live run configuration so it's persisted for an exact
883
+ // resume (including mid-run model/provider fallbacks).
884
+ taskInfo.model = agent.getModel();
885
+ taskInfo.provider = agent.getProvider();
886
+ taskInfo.reasoningEffort = agent.getReasoningEffort?.();
887
+ taskInfo.summarizeReasoning = agent.getSummarizeReasoning?.();
888
+ // updateSession pulls the (mutated) taskInfo from the registry.
889
+ this.updateSession(taskId, agent.getThreads());
846
890
  };
847
891
  agent.agentEvents.on(agent.eventTypes.threadUpdate, threadUpdateHandler);
848
892
 
@@ -854,6 +898,13 @@ export class AgentModule extends BaseChatModule {
854
898
  // Initialize new task
855
899
  await agent.newTask(taskId);
856
900
 
901
+ // Record the parent task id on the agent so self-referential tools
902
+ // (e.g. replyToParent) can resolve who spawned this agent without
903
+ // relying solely on reading metadata.json off disk.
904
+ if (options.parentTaskId) {
905
+ agent.setParentTaskId(options.parentTaskId);
906
+ }
907
+
857
908
  if (options.model) {
858
909
  console.log("Setting model:", options.model);
859
910
  agent.setModel(options.model);
@@ -862,11 +913,28 @@ export class AgentModule extends BaseChatModule {
862
913
  ]);
863
914
  }
864
915
 
865
- // Apply module-level reasoning settings to the agent
866
- if (this.reasoningEffort !== undefined) {
867
- agent.setReasoningEffort(this.reasoningEffort);
916
+ // Restore the provider explicitly (e.g. on resume) so the agent talks to
917
+ // the same client the original run used, not the model's default provider.
918
+ if (options.provider) {
919
+ agent.setProvider(options.provider as any);
868
920
  }
869
- agent.setSummarizeReasoning(this.summarizeReasoning);
921
+
922
+ // Apply reasoning settings to the agent. Explicit per-task settings
923
+ // (e.g. restored from a resumed task's metadata) take precedence over
924
+ // the module-level CLI defaults so a /resume keeps the same reasoning
925
+ // effort the original run used.
926
+ const effectiveReasoningEffort =
927
+ options.reasoningEffort !== undefined
928
+ ? options.reasoningEffort
929
+ : this.reasoningEffort;
930
+ if (effectiveReasoningEffort !== undefined) {
931
+ agent.setReasoningEffort(effectiveReasoningEffort);
932
+ }
933
+ agent.setSummarizeReasoning(
934
+ options.summarizeReasoning !== undefined
935
+ ? options.summarizeReasoning
936
+ : this.summarizeReasoning
937
+ );
870
938
 
871
939
  // Set up message processors like in original startAgent
872
940
 
@@ -908,6 +976,7 @@ export class AgentModule extends BaseChatModule {
908
976
  agent.messageProcessor.setProcessors("post_call", [
909
977
  new XmlToolCallProcessor().createProcessor(),
910
978
  new HarmonyToolProcessor().createProcessor(),
979
+ new GemmaToolProcessor().createProcessor(),
911
980
  ]);
912
981
 
913
982
  agent.messageProcessor.setProcessors("pre_tools", [
@@ -1118,6 +1187,50 @@ export class AgentModule extends BaseChatModule {
1118
1187
  return threads;
1119
1188
  }
1120
1189
 
1190
+ /**
1191
+ * Load the persisted run settings (model / provider / reasoning) for a task
1192
+ * from local FS metadata, so a headless --resume can restore the exact
1193
+ * configuration the original run used instead of falling back to agent
1194
+ * defaults. Returns an empty object when no local metadata is found (e.g.
1195
+ * remote-only tasks).
1196
+ */
1197
+ public async loadTaskSettings(
1198
+ taskId: string,
1199
+ messageId?: string
1200
+ ): Promise<{
1201
+ model?: string;
1202
+ provider?: string;
1203
+ reasoningEffort?: CompletionOptions["reasoning_effort"];
1204
+ summarizeReasoning?: boolean;
1205
+ agentName?: string;
1206
+ }> {
1207
+ const localMetadataPath = path.join(
1208
+ ".knowhow",
1209
+ "processes",
1210
+ "agents",
1211
+ taskId,
1212
+ "metadata.json"
1213
+ );
1214
+
1215
+ if (!messageId && fs.existsSync(localMetadataPath)) {
1216
+ try {
1217
+ const raw = await fsPromises.readFile(localMetadataPath, "utf-8");
1218
+ const metadata = JSON.parse(raw);
1219
+ return {
1220
+ model: metadata.model,
1221
+ provider: metadata.provider,
1222
+ reasoningEffort: metadata.reasoningEffort,
1223
+ summarizeReasoning: metadata.summarizeReasoning,
1224
+ agentName: metadata.agentName,
1225
+ };
1226
+ } catch (e) {
1227
+ console.warn(`⚠️ Failed to parse local metadata for settings: ${e.message}`);
1228
+ }
1229
+ }
1230
+
1231
+ return {};
1232
+ }
1233
+
1121
1234
  /**
1122
1235
  * Resume an agent from a set of existing message threads
1123
1236
  * Used by the CLI --resume flag to continue crashed/failed tasks
@@ -1129,6 +1242,11 @@ export class AgentModule extends BaseChatModule {
1129
1242
  messageId?: string;
1130
1243
  taskId?: string;
1131
1244
  interactive?: boolean;
1245
+ // Restore the exact model/provider/reasoning the original run used.
1246
+ model?: string;
1247
+ provider?: string;
1248
+ reasoningEffort?: CompletionOptions["reasoning_effort"];
1249
+ summarizeReasoning?: boolean;
1132
1250
  }): Promise<{ taskCompleted: Promise<string> }> {
1133
1251
  const { agentName, input, threads, messageId, taskId } = options;
1134
1252
 
@@ -1189,6 +1307,11 @@ export class AgentModule extends BaseChatModule {
1189
1307
  // remote task identified by taskId.
1190
1308
  syncRemote: !messageId && !!taskId,
1191
1309
  run: false,
1310
+ // Restore the model/provider/reasoning captured from the task metadata.
1311
+ model: options.model,
1312
+ provider: options.provider,
1313
+ reasoningEffort: options.reasoningEffort,
1314
+ summarizeReasoning: options.summarizeReasoning,
1192
1315
  });
1193
1316
 
1194
1317
  // Interactive (chat REPL) resume: route through the attached chat loop so the
@@ -1345,8 +1468,15 @@ export class AgentModule extends BaseChatModule {
1345
1468
  }
1346
1469
 
1347
1470
  resolve("done");
1348
- // Exit agent:attached mode so the prompt resets back to the default
1349
- this.detachFromAgent();
1471
+ // Exit agent:attached mode so the prompt resets back to the default.
1472
+ // Only detach if we are still attached to THIS agent. If the user
1473
+ // killed this agent and started a new one (or /attached to a different
1474
+ // one) while this agent was finishing a long tool call, calling
1475
+ // detachFromAgent() unconditionally would rip out the new agent's
1476
+ // rendering listeners and reset the mode, making new output invisible.
1477
+ if (this.attachedAgent === agent) {
1478
+ this.detachFromAgent();
1479
+ }
1350
1480
  });
1351
1481
  });
1352
1482
 
@@ -214,7 +214,6 @@ export class SessionsModule extends BaseChatModule {
214
214
  // ── Case 1: in-memory running task ──────────────────────────────────────
215
215
  if (taskRegistry.has(id)) {
216
216
  const taskInfo = taskRegistry.get(id)!;
217
- const renderer = this.agentModule.getRenderer();
218
217
  const context = this.chatService?.getContext();
219
218
  const selectedAgent = taskInfo.agent;
220
219
 
@@ -222,13 +221,9 @@ export class SessionsModule extends BaseChatModule {
222
221
  context.selectedAgent = selectedAgent;
223
222
  context.agentMode = true;
224
223
  context.currentAgent = taskInfo.agentName;
225
- context.activeAgentTaskId = id;
226
224
  context.currentModel = selectedAgent.getModel();
227
225
  context.currentProvider = selectedAgent.getProvider();
228
226
  }
229
- this.agentModule.setActiveAgentTaskId(id);
230
- renderer.setActiveTaskId(id);
231
- if (this.chatService) this.chatService.setMode("agent:attached");
232
227
 
233
228
  console.log(`🔄 Attached to running task: ${id}`);
234
229
  console.log(` Agent : ${taskInfo.agentName}`);
@@ -237,6 +232,13 @@ export class SessionsModule extends BaseChatModule {
237
232
  console.log(
238
233
  ` Type /logs to see recent messages, or /detach to detach.`
239
234
  );
235
+
236
+ // Route through attachedAgentChatLoop so that:
237
+ // 1. wireAgentRendering is called (new output becomes visible)
238
+ // 2. attachedAgent is set (so /poke, /kill, /compact work)
239
+ // 3. a done listener is registered (mode resets when agent finishes)
240
+ // Do NOT pass initialInput — agent is already running, no need to call it again.
241
+ await this.agentModule.attachedAgentChatLoop(id, selectedAgent as any);
240
242
  return;
241
243
  }
242
244
 
@@ -396,6 +398,12 @@ export class SessionsModule extends BaseChatModule {
396
398
  threads: normalizedThreads,
397
399
  input: additionalContext?.trim() || initialInput || "",
398
400
  interactive: true,
401
+ // Restore the exact model/provider/reasoning the run used so
402
+ // resuming doesn't silently drop back to agent defaults.
403
+ model: metadata.model,
404
+ provider: metadata.provider,
405
+ reasoningEffort: metadata.reasoningEffort,
406
+ summarizeReasoning: metadata.summarizeReasoning,
399
407
  });
400
408
  return;
401
409
  } catch (e: any) {
package/src/chat/types.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { ChatInteraction, Config } from "../types";
5
5
  import { BaseAgent } from "../agents/base/base";
6
+ import { ReasoningEffort } from "../clients/types";
6
7
  import { ToolsService } from "../services";
7
8
  import { AgentRenderer } from "./renderer/types";
8
9
 
@@ -10,6 +11,9 @@ export interface ChatContext {
10
11
  debugMode?: boolean;
11
12
  agentMode?: boolean;
12
13
  currentAgent?: string;
14
+ defaultAgent?: string;
15
+ defaultModel?: string;
16
+ defaultProvider?: string;
13
17
  promptText?: string;
14
18
  searchMode?: boolean;
15
19
  voiceMode?: boolean;
@@ -94,6 +98,12 @@ export interface TaskInfo {
94
98
  endTime?: number;
95
99
  totalCost: number;
96
100
  sessionFile?: string;
101
+ // Model/provider/reasoning actually used for this run, so a resume can
102
+ // restore the exact configuration instead of falling back to agent defaults.
103
+ model?: string;
104
+ provider?: string;
105
+ reasoningEffort?: ReasoningEffort;
106
+ summarizeReasoning?: boolean;
97
107
  }
98
108
 
99
109
  export interface ChatSession {
@@ -111,6 +121,11 @@ export interface ChatSession {
111
121
  threads: any[][];
112
122
  currentThread: number;
113
123
  lastUpdated: number;
124
+ // Persisted run configuration for exact resume restoration.
125
+ model?: string;
126
+ provider?: string;
127
+ reasoningEffort?: ReasoningEffort;
128
+ summarizeReasoning?: boolean;
114
129
  }
115
130
 
116
131
  export interface ChatHistory {
package/src/chat.ts CHANGED
@@ -54,6 +54,17 @@ async function main(initialCommand?: string) {
54
54
  // Create chat service with plugins
55
55
  const chatService = new CliChatService(config.plugins?.enabled ?? []);
56
56
 
57
+ // ── Default Agent / Model / Provider ──────────────────────────────────────
58
+ // If chat config specifies defaults, inject them into the context so
59
+ // AgentModule can pick them up during initialize().
60
+ if (config.chat?.defaultAgent || config.chat?.defaultModel || config.chat?.defaultProvider) {
61
+ chatService.setContext({
62
+ defaultAgent: config.chat?.defaultAgent,
63
+ defaultModel: config.chat?.defaultModel,
64
+ defaultProvider: config.chat?.defaultProvider,
65
+ });
66
+ }
67
+
57
68
  // ── Renderer ──────────────────────────────────────────────────────────────
58
69
  // Load a custom renderer if configured, otherwise use the default
59
70
  // ConsoleRenderer (already the default inside AgentModule).
package/src/cli.ts CHANGED
@@ -157,6 +157,8 @@ async function main() {
157
157
  await program.parseAsync(process.argv);
158
158
  }
159
159
 
160
+ export { main };
161
+
160
162
  if (require.main === module) {
161
163
  main()
162
164
  .catch((e) => {
@@ -88,6 +88,25 @@ export class GenericAnthropicClient implements GenericClient {
88
88
  return undefined;
89
89
  }
90
90
 
91
+ /** Map provider-neutral effort names to Anthropic's output_config.effort. */
92
+ getEffortOutputConfig(
93
+ model: string,
94
+ reasoningEffort?: CompletionOptions["reasoning_effort"]
95
+ ): Anthropic.OutputConfig | undefined {
96
+ if (
97
+ reasoningEffort === undefined ||
98
+ reasoningEffort === "none" ||
99
+ (!alwaysOnModels.includes(model) && !optInModels.includes(model))
100
+ ) {
101
+ return undefined;
102
+ }
103
+
104
+ return {
105
+ // Anthropic uses `low` for the provider-neutral `minimal` level.
106
+ effort: reasoningEffort === "minimal" ? "low" : reasoningEffort,
107
+ };
108
+ }
109
+
91
110
  /**
92
111
  * Clean JSON Schema for Anthropic API compatibility.
93
112
  * Removes unsupported fields like additionalProperties, $ref, $defs, positional.
@@ -245,6 +264,18 @@ export class GenericAnthropicClient implements GenericClient {
245
264
 
246
265
  transformMessages(messages: Message[], longTtl = false): MessageParam[] {
247
266
  const toolCalls = messages.flatMap((msg) => msg.tool_calls || []);
267
+ // Map each tool_call id → the thinking blocks captured on the assistant
268
+ // message that produced it, so we can re-inject them before the tool_use.
269
+ const thinkingByCallId: { [id: string]: any[] } = {};
270
+ for (const m of messages) {
271
+ if (m.role !== "assistant" || !m.tool_calls?.length) continue;
272
+ const details = (m as any)._reasoning_details;
273
+ if (details?.provider === "anthropic" && Array.isArray(details.items)) {
274
+ // Attach to the FIRST tool_call of this assistant turn (thinking blocks
275
+ // must precede the first tool_use in the turn).
276
+ thinkingByCallId[m.tool_calls[0].id] = details.items;
277
+ }
278
+ }
248
279
  const claudeMessages: MessageParam[] = messages
249
280
  .filter((msg) => msg.role !== "system")
250
281
  .filter((msg) => msg.content || msg.role === "tool")
@@ -258,9 +289,14 @@ export class GenericAnthropicClient implements GenericClient {
258
289
  JSON.stringify(msg, null, 2)
259
290
  );
260
291
  } else {
292
+ // Re-inject any captured thinking / redacted_thinking blocks
293
+ // (verbatim, with signature/data) BEFORE the tool_use block, as
294
+ // Anthropic requires for reasoning continuity across tool turns.
295
+ const thinkingBlocks = thinkingByCallId[msg.tool_call_id] ?? [];
261
296
  toolMessages.push({
262
297
  role: "assistant",
263
298
  content: [
299
+ ...thinkingBlocks,
264
300
  {
265
301
  type: "tool_use",
266
302
  id: msg.tool_call_id,
@@ -406,6 +442,10 @@ export class GenericAnthropicClient implements GenericClient {
406
442
  options.reasoning_effort,
407
443
  options.reasoning_summary,
408
444
  );
445
+ const effortOutputConfig = this.getEffortOutputConfig(
446
+ options.model,
447
+ options.reasoning_effort
448
+ );
409
449
 
410
450
  const systemMessage = options.messages
411
451
  .filter((msg) => msg.role === "system")
@@ -430,6 +470,7 @@ export class GenericAnthropicClient implements GenericClient {
430
470
  : undefined,
431
471
  max_tokens: options.max_tokens || 8000,
432
472
  ...(thinkingParam && { thinking: thinkingParam }),
473
+ ...(effortOutputConfig && { output_config: effortOutputConfig }),
433
474
  ...(tools.length && {
434
475
  tool_choice: { type: "auto" },
435
476
  tools,
@@ -440,8 +481,42 @@ export class GenericAnthropicClient implements GenericClient {
440
481
  console.log("no content in Anthropic response", response);
441
482
  }
442
483
 
484
+ // Capture thinking / redacted_thinking blocks verbatim (with their
485
+ // signature / data) so they can be round-tripped unmodified on the next
486
+ // tool-use turn. Anthropic REQUIRES thinking blocks from the assistant
487
+ // message to be passed back, complete and unmodified, when returning tool
488
+ // results — otherwise reasoning continuity and prompt-cache hits are lost.
489
+ const thinkingBlocks = response.content.filter(
490
+ (c) => c.type === "thinking" || c.type === "redacted_thinking"
491
+ );
492
+ const reasoningDetails =
493
+ thinkingBlocks.length > 0
494
+ ? { provider: "anthropic", items: thinkingBlocks }
495
+ : undefined;
496
+ // Extract a human-readable summary from thinking blocks (when present).
497
+ const reasoningSummary = thinkingBlocks
498
+ .map((b) => ("thinking" in b ? (b as any).thinking : ""))
499
+ .filter(Boolean)
500
+ .join("\n") || undefined;
501
+
502
+ // Determine which resulting choice should carry the thinking blocks. They
503
+ // are re-injected before the first tool_use on the next turn, so prefer
504
+ // the first tool_use block; if there are no tool calls, fall back to the
505
+ // first (text) block. This keeps a single copy that transformMessages can
506
+ // reliably find via the assistant message's tool_calls.
507
+ const nonThinking = response.content.filter(
508
+ (c) => c.type !== "thinking" && c.type !== "redacted_thinking"
509
+ );
510
+ const firstToolUseIdx = nonThinking.findIndex((c) => c.type === "tool_use");
511
+ const carryIdx = firstToolUseIdx >= 0 ? firstToolUseIdx : 0;
512
+
443
513
  return {
444
- choices: response.content.map((c) => {
514
+ choices: nonThinking.map((c, idx) => {
515
+ // Only the FIRST resulting assistant message carries the thinking
516
+ // blocks (Anthropic requires a single thinking block sequence at the
517
+ // start of the assistant turn); attaching to every choice would
518
+ // duplicate them once the messages are re-transformed.
519
+ const carryReasoning = idx === carryIdx;
445
520
  if (c.type === "tool_use") {
446
521
  return {
447
522
  message: {
@@ -457,6 +532,8 @@ export class GenericAnthropicClient implements GenericClient {
457
532
  },
458
533
  },
459
534
  ],
535
+ ...(carryReasoning && reasoningDetails && { _reasoning_details: reasoningDetails }),
536
+ ...(carryReasoning && reasoningSummary && { reasoning_summary: reasoningSummary }),
460
537
  },
461
538
  };
462
539
  } else {
@@ -465,6 +542,8 @@ export class GenericAnthropicClient implements GenericClient {
465
542
  role: "assistant",
466
543
  content: "text" in c ? c.text : c.type,
467
544
  tool_calls: [],
545
+ ...(carryReasoning && reasoningDetails && { _reasoning_details: reasoningDetails }),
546
+ ...(carryReasoning && reasoningSummary && { reasoning_summary: reasoningSummary }),
468
547
  },
469
548
  };
470
549
  }
@@ -1,4 +1,5 @@
1
1
  import { Models, EmbeddingModels } from "../types";
2
+ import { FireworksModels } from "./pricing/fireworks";
2
3
 
3
4
  /**
4
5
  * Context window limits (in tokens) for all supported models.
@@ -207,6 +208,45 @@ export const ContextLimits: Record<string, number> = {
207
208
  [Models.xai.GrokImagineImage]: 0,
208
209
  [Models.xai.GrokImagineVideo]: 0,
209
210
  [Models.xai.Grok2Image1212]: 0,
211
+
212
+ // ─── Fireworks AI ──────────────────────────────────────────────────────────
213
+ // Moonshot AI (Kimi) — context windows per fireworks.ai/pricing
214
+ [FireworksModels.KimiK3]: 131_072,
215
+ [FireworksModels.KimiK2p7Code]: 131_072,
216
+ [FireworksModels.KimiK2_6]: 131_072,
217
+ [FireworksModels.KimiK2_5]: 131_072,
218
+
219
+ // MiniMax
220
+ [FireworksModels.MinimaxM3]: 1_000_000,
221
+ [FireworksModels.MinimaxM2_7]: 1_000_000,
222
+ [FireworksModels.MinimaxM2_5]: 1_000_000,
223
+
224
+ // Qwen
225
+ [FireworksModels.Qwen3p7Plus]: 131_072,
226
+ [FireworksModels.Qwen3_6Plus]: 131_072,
227
+ [FireworksModels.Qwen3p6Plus]: 131_072,
228
+ [FireworksModels.Qwen3Vl30bA3bThinking]: 32_768,
229
+ [FireworksModels.Qwen3Vl30bA3bInstruct]: 32_768,
230
+ [FireworksModels.Qwen3_8b]: 131_072,
231
+
232
+ // Z.ai (GLM)
233
+ [FireworksModels.Glm5p2]: 131_072,
234
+ [FireworksModels.Glm5p1]: 131_072,
235
+ [FireworksModels.Glm5]: 131_072,
236
+ [FireworksModels.Glm4_7]: 131_072,
237
+
238
+ // DeepSeek AI
239
+ [FireworksModels.DeepseekV4Pro]: 163_840,
240
+ [FireworksModels.DeepseekV4Flash]: 163_840,
241
+ [FireworksModels.DeepseekV3_2]: 163_840,
242
+ [FireworksModels.DeepseekV3_1]: 163_840,
243
+
244
+ // OpenAI OSS
245
+ [FireworksModels.GptOss120b]: 131_072,
246
+
247
+ // Fireworks embedding/reranker models — 40k context
248
+ [FireworksModels.Qwen3Embedding8b]: 40_960,
249
+ [FireworksModels.Qwen3Reranker8b]: 40_960,
210
250
  };
211
251
 
212
252
  /** Default fallback context window limit (tokens) used when a model is not found. */
@@ -51,6 +51,9 @@ export class GenericFireworksClient extends HttpClient {
51
51
  ...options,
52
52
  // Strip Anthropic-specific field not accepted by Fireworks
53
53
  long_ttl_cache: undefined,
54
+ // This is translated by the OpenAI/Anthropic clients, but is not part of
55
+ // Fireworks' OpenAI-compatible chat completion schema.
56
+ reasoning_summary: undefined,
54
57
  };
55
58
 
56
59
  if (sanitized.tools) {