@tyvm/knowhow 0.0.146 → 0.0.148

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 (271) hide show
  1. package/autodoc/cli-reference.md +36 -7
  2. package/bin/knowhow.js +8 -6
  3. package/package.json +1 -1
  4. package/src/agents/base/base.ts +51 -23
  5. package/src/agents/historyRollback.ts +36 -0
  6. package/src/agents/tools/execCommand.ts +102 -320
  7. package/src/agents/tools/list.ts +2 -2
  8. package/src/agents/tools/startAgentTask.ts +91 -34
  9. package/src/ai.ts +4 -1
  10. package/src/auth/browserLogin.ts +9 -3
  11. package/src/auth/keyAuth.ts +115 -0
  12. package/src/auth/keyManager.ts +166 -0
  13. package/src/chat/CliChatService.ts +23 -2
  14. package/src/chat/modules/AgentModule.ts +114 -33
  15. package/src/chat/modules/AskModule.ts +2 -0
  16. package/src/chat/modules/BaseChatModule.ts +0 -1
  17. package/src/chat/modules/CustomCommandsModule.ts +2 -0
  18. package/src/chat/modules/InternalChatModule.ts +7 -0
  19. package/src/chat/modules/RemoteSyncModule.ts +4 -0
  20. package/src/chat/modules/RendererModule.ts +2 -0
  21. package/src/chat/modules/SearchModule.ts +2 -0
  22. package/src/chat/modules/SessionsModule.ts +103 -34
  23. package/src/chat/modules/SetupModule.ts +2 -0
  24. package/src/chat/modules/ShellCommandModule.ts +2 -0
  25. package/src/chat/modules/SystemModule.ts +2 -0
  26. package/src/chat/modules/ToolCommandModule.ts +102 -0
  27. package/src/chat/modules/index.ts +1 -0
  28. package/src/chat/types.ts +3 -1
  29. package/src/cli.ts +10 -3
  30. package/src/clients/http.ts +67 -7
  31. package/src/clients/index.ts +4 -0
  32. package/src/commands/agent.ts +1 -38
  33. package/src/commands/agents.ts +455 -60
  34. package/src/commands/misc.ts +3 -2
  35. package/src/commands/processes.ts +138 -0
  36. package/src/commands/services.ts +23 -1
  37. package/src/index.ts +1 -0
  38. package/src/login.ts +89 -28
  39. package/src/processes/ProcessManager.ts +95 -0
  40. package/src/processes/managedProcessShim.ts +115 -0
  41. package/src/services/AgentService.ts +2 -0
  42. package/src/services/AgentSyncFs.ts +77 -1
  43. package/src/services/AgentSyncKnowhowWeb.ts +22 -4
  44. package/src/services/BehaviorsService.ts +2 -0
  45. package/src/services/DockerService.ts +2 -0
  46. package/src/services/EmbeddingService.ts +2 -0
  47. package/src/services/EmbeddingsService.ts +3 -0
  48. package/src/services/ExtensionsService.ts +25 -0
  49. package/src/services/KnowhowClient.ts +68 -7
  50. package/src/services/LazyToolsService.ts +24 -20
  51. package/src/services/Mcp.ts +2 -0
  52. package/src/services/McpServer.ts +2 -0
  53. package/src/services/MessageProcessor.ts +2 -0
  54. package/src/services/MinimalToolsService.ts +2 -0
  55. package/src/services/SessionManager.ts +5 -0
  56. package/src/services/SkillsService.ts +2 -0
  57. package/src/services/SyncedAgentWatcher.ts +2 -0
  58. package/src/services/SyncerService.ts +23 -8
  59. package/src/services/TaskRegistry.ts +2 -0
  60. package/src/services/Tools.ts +12 -0
  61. package/src/services/index.ts +6 -0
  62. package/src/services/modules/index.ts +10 -1
  63. package/src/services/modules/types.ts +4 -0
  64. package/src/types.ts +2 -0
  65. package/src/util/Trace.ts +1 -1
  66. package/src/utils/http.ts +18 -2
  67. package/tests/unit/agents/historyRollback.test.ts +53 -0
  68. package/tests/unit/auth/keyAuth.test.ts +102 -0
  69. package/tests/unit/auth/keyManager.test.ts +67 -0
  70. package/tests/unit/chat/ToolCommandModule.test.ts +71 -0
  71. package/tests/unit/chat/historyCommandArgs.test.ts +37 -0
  72. package/tests/unit/commands/agents.test.ts +111 -0
  73. package/tests/unit/login.test.ts +119 -0
  74. package/tests/unit/services/AgentSyncKnowhowWeb.test.ts +38 -0
  75. package/tests/unit/services/LazyToolsService.test.ts +53 -0
  76. package/tests/unit/services/SessionManager.test.ts +43 -0
  77. package/tests/unit/services/SyncerService.test.ts +45 -0
  78. package/tests/unit/util/Trace.test.ts +119 -0
  79. package/tests/unit/utils/http.test.ts +43 -0
  80. package/ts_build/package.json +1 -1
  81. package/ts_build/src/agents/base/base.d.ts +2 -1
  82. package/ts_build/src/agents/base/base.js +47 -14
  83. package/ts_build/src/agents/base/base.js.map +1 -1
  84. package/ts_build/src/agents/historyRollback.d.ts +2 -0
  85. package/ts_build/src/agents/historyRollback.js +22 -0
  86. package/ts_build/src/agents/historyRollback.js.map +1 -0
  87. package/ts_build/src/agents/tools/execCommand.d.ts +4 -1
  88. package/ts_build/src/agents/tools/execCommand.js +100 -223
  89. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  90. package/ts_build/src/agents/tools/list.js +2 -2
  91. package/ts_build/src/agents/tools/list.js.map +1 -1
  92. package/ts_build/src/agents/tools/startAgentTask.d.ts +16 -1
  93. package/ts_build/src/agents/tools/startAgentTask.js +71 -28
  94. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  95. package/ts_build/src/ai.js +2 -1
  96. package/ts_build/src/ai.js.map +1 -1
  97. package/ts_build/src/auth/browserLogin.d.ts +1 -0
  98. package/ts_build/src/auth/browserLogin.js +5 -2
  99. package/ts_build/src/auth/browserLogin.js.map +1 -1
  100. package/ts_build/src/auth/keyAuth.d.ts +5 -0
  101. package/ts_build/src/auth/keyAuth.js +79 -0
  102. package/ts_build/src/auth/keyAuth.js.map +1 -0
  103. package/ts_build/src/auth/keyManager.d.ts +14 -0
  104. package/ts_build/src/auth/keyManager.js +145 -0
  105. package/ts_build/src/auth/keyManager.js.map +1 -0
  106. package/ts_build/src/chat/CliChatService.d.ts +6 -0
  107. package/ts_build/src/chat/CliChatService.js +17 -2
  108. package/ts_build/src/chat/CliChatService.js.map +1 -1
  109. package/ts_build/src/chat/modules/AgentModule.d.ts +13 -1
  110. package/ts_build/src/chat/modules/AgentModule.js +83 -24
  111. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  112. package/ts_build/src/chat/modules/AskModule.js +12 -2
  113. package/ts_build/src/chat/modules/AskModule.js.map +1 -1
  114. package/ts_build/src/chat/modules/BaseChatModule.js.map +1 -1
  115. package/ts_build/src/chat/modules/CustomCommandsModule.js +12 -2
  116. package/ts_build/src/chat/modules/CustomCommandsModule.js.map +1 -1
  117. package/ts_build/src/chat/modules/InternalChatModule.d.ts +1 -0
  118. package/ts_build/src/chat/modules/InternalChatModule.js +21 -2
  119. package/ts_build/src/chat/modules/InternalChatModule.js.map +1 -1
  120. package/ts_build/src/chat/modules/RemoteSyncModule.js +19 -2
  121. package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
  122. package/ts_build/src/chat/modules/RendererModule.js +17 -2
  123. package/ts_build/src/chat/modules/RendererModule.js.map +1 -1
  124. package/ts_build/src/chat/modules/SearchModule.js +12 -2
  125. package/ts_build/src/chat/modules/SearchModule.js.map +1 -1
  126. package/ts_build/src/chat/modules/SessionsModule.d.ts +6 -0
  127. package/ts_build/src/chat/modules/SessionsModule.js +113 -18
  128. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
  129. package/ts_build/src/chat/modules/SetupModule.js +17 -2
  130. package/ts_build/src/chat/modules/SetupModule.js.map +1 -1
  131. package/ts_build/src/chat/modules/ShellCommandModule.js +12 -2
  132. package/ts_build/src/chat/modules/ShellCommandModule.js.map +1 -1
  133. package/ts_build/src/chat/modules/SystemModule.js +12 -2
  134. package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
  135. package/ts_build/src/chat/modules/ToolCommandModule.d.ts +10 -0
  136. package/ts_build/src/chat/modules/ToolCommandModule.js +86 -0
  137. package/ts_build/src/chat/modules/ToolCommandModule.js.map +1 -0
  138. package/ts_build/src/chat/modules/index.d.ts +1 -0
  139. package/ts_build/src/chat/modules/index.js +3 -1
  140. package/ts_build/src/chat/modules/index.js.map +1 -1
  141. package/ts_build/src/chat/types.d.ts +3 -1
  142. package/ts_build/src/cli.js +7 -3
  143. package/ts_build/src/cli.js.map +1 -1
  144. package/ts_build/src/clients/http.d.ts +4 -0
  145. package/ts_build/src/clients/http.js +68 -6
  146. package/ts_build/src/clients/http.js.map +1 -1
  147. package/ts_build/src/clients/index.js +23 -4
  148. package/ts_build/src/clients/index.js.map +1 -1
  149. package/ts_build/src/commands/agent.js +1 -21
  150. package/ts_build/src/commands/agent.js.map +1 -1
  151. package/ts_build/src/commands/agents.d.ts +2 -1
  152. package/ts_build/src/commands/agents.js +216 -23
  153. package/ts_build/src/commands/agents.js.map +1 -1
  154. package/ts_build/src/commands/misc.js +3 -2
  155. package/ts_build/src/commands/misc.js.map +1 -1
  156. package/ts_build/src/commands/processes.d.ts +2 -0
  157. package/ts_build/src/commands/processes.js +213 -0
  158. package/ts_build/src/commands/processes.js.map +1 -0
  159. package/ts_build/src/commands/services.js +11 -1
  160. package/ts_build/src/commands/services.js.map +1 -1
  161. package/ts_build/src/index.d.ts +1 -0
  162. package/ts_build/src/index.js +1 -0
  163. package/ts_build/src/index.js.map +1 -1
  164. package/ts_build/src/login.d.ts +1 -1
  165. package/ts_build/src/login.js +53 -15
  166. package/ts_build/src/login.js.map +1 -1
  167. package/ts_build/src/processes/ProcessManager.d.ts +51 -0
  168. package/ts_build/src/processes/ProcessManager.js +160 -0
  169. package/ts_build/src/processes/ProcessManager.js.map +1 -0
  170. package/ts_build/src/processes/managedProcessShim.d.ts +1 -0
  171. package/ts_build/src/processes/managedProcessShim.js +161 -0
  172. package/ts_build/src/processes/managedProcessShim.js.map +1 -0
  173. package/ts_build/src/services/AgentService.js +18 -2
  174. package/ts_build/src/services/AgentService.js.map +1 -1
  175. package/ts_build/src/services/AgentSyncFs.d.ts +3 -0
  176. package/ts_build/src/services/AgentSyncFs.js +68 -16
  177. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  178. package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +2 -0
  179. package/ts_build/src/services/AgentSyncKnowhowWeb.js +28 -6
  180. package/ts_build/src/services/AgentSyncKnowhowWeb.js.map +1 -1
  181. package/ts_build/src/services/BehaviorsService.js +12 -2
  182. package/ts_build/src/services/BehaviorsService.js.map +1 -1
  183. package/ts_build/src/services/DockerService.js +20 -8
  184. package/ts_build/src/services/DockerService.js.map +1 -1
  185. package/ts_build/src/services/EmbeddingService.js +16 -2
  186. package/ts_build/src/services/EmbeddingService.js.map +1 -1
  187. package/ts_build/src/services/EmbeddingsService.js +12 -2
  188. package/ts_build/src/services/EmbeddingsService.js.map +1 -1
  189. package/ts_build/src/services/ExtensionsService.d.ts +10 -0
  190. package/ts_build/src/services/ExtensionsService.js +17 -0
  191. package/ts_build/src/services/ExtensionsService.js.map +1 -0
  192. package/ts_build/src/services/KnowhowClient.d.ts +7 -2
  193. package/ts_build/src/services/KnowhowClient.js +108 -10
  194. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  195. package/ts_build/src/services/LazyToolsService.d.ts +2 -0
  196. package/ts_build/src/services/LazyToolsService.js +26 -9
  197. package/ts_build/src/services/LazyToolsService.js.map +1 -1
  198. package/ts_build/src/services/Mcp.js +12 -2
  199. package/ts_build/src/services/Mcp.js.map +1 -1
  200. package/ts_build/src/services/McpServer.js +17 -2
  201. package/ts_build/src/services/McpServer.js.map +1 -1
  202. package/ts_build/src/services/MessageProcessor.js +16 -2
  203. package/ts_build/src/services/MessageProcessor.js.map +1 -1
  204. package/ts_build/src/services/MinimalToolsService.js +16 -2
  205. package/ts_build/src/services/MinimalToolsService.js.map +1 -1
  206. package/ts_build/src/services/SessionManager.js +19 -2
  207. package/ts_build/src/services/SessionManager.js.map +1 -1
  208. package/ts_build/src/services/SkillsService.js +16 -2
  209. package/ts_build/src/services/SkillsService.js.map +1 -1
  210. package/ts_build/src/services/SyncedAgentWatcher.js +16 -2
  211. package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -1
  212. package/ts_build/src/services/SyncerService.d.ts +1 -0
  213. package/ts_build/src/services/SyncerService.js +27 -5
  214. package/ts_build/src/services/SyncerService.js.map +1 -1
  215. package/ts_build/src/services/TaskRegistry.js +12 -2
  216. package/ts_build/src/services/TaskRegistry.js.map +1 -1
  217. package/ts_build/src/services/Tools.d.ts +1 -0
  218. package/ts_build/src/services/Tools.js +19 -2
  219. package/ts_build/src/services/Tools.js.map +1 -1
  220. package/ts_build/src/services/index.d.ts +5 -0
  221. package/ts_build/src/services/index.js +7 -2
  222. package/ts_build/src/services/index.js.map +1 -1
  223. package/ts_build/src/services/modules/index.d.ts +4 -1
  224. package/ts_build/src/services/modules/index.js +6 -1
  225. package/ts_build/src/services/modules/index.js.map +1 -1
  226. package/ts_build/src/services/modules/types.d.ts +3 -0
  227. package/ts_build/src/types.d.ts +1 -0
  228. package/ts_build/src/types.js.map +1 -1
  229. package/ts_build/src/util/Trace.d.ts +1 -3
  230. package/ts_build/src/utils/http.d.ts +6 -2
  231. package/ts_build/src/utils/http.js +8 -2
  232. package/ts_build/src/utils/http.js.map +1 -1
  233. package/ts_build/tests/unit/agents/historyRollback.test.d.ts +1 -0
  234. package/ts_build/tests/unit/agents/historyRollback.test.js +42 -0
  235. package/ts_build/tests/unit/agents/historyRollback.test.js.map +1 -0
  236. package/ts_build/tests/unit/auth/keyAuth.test.d.ts +1 -0
  237. package/ts_build/tests/unit/auth/keyAuth.test.js +77 -0
  238. package/ts_build/tests/unit/auth/keyAuth.test.js.map +1 -0
  239. package/ts_build/tests/unit/auth/keyManager.test.d.ts +1 -0
  240. package/ts_build/tests/unit/auth/keyManager.test.js +52 -0
  241. package/ts_build/tests/unit/auth/keyManager.test.js.map +1 -0
  242. package/ts_build/tests/unit/chat/ToolCommandModule.test.d.ts +1 -0
  243. package/ts_build/tests/unit/chat/ToolCommandModule.test.js +61 -0
  244. package/ts_build/tests/unit/chat/ToolCommandModule.test.js.map +1 -0
  245. package/ts_build/tests/unit/chat/historyCommandArgs.test.d.ts +1 -0
  246. package/ts_build/tests/unit/chat/historyCommandArgs.test.js +29 -0
  247. package/ts_build/tests/unit/chat/historyCommandArgs.test.js.map +1 -0
  248. package/ts_build/tests/unit/commands/agents.test.d.ts +1 -0
  249. package/ts_build/tests/unit/commands/agents.test.js +119 -0
  250. package/ts_build/tests/unit/commands/agents.test.js.map +1 -0
  251. package/ts_build/tests/unit/login.test.d.ts +1 -0
  252. package/ts_build/tests/unit/login.test.js +97 -0
  253. package/ts_build/tests/unit/login.test.js.map +1 -0
  254. package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.d.ts +1 -0
  255. package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.js +32 -0
  256. package/ts_build/tests/unit/services/AgentSyncKnowhowWeb.test.js.map +1 -0
  257. package/ts_build/tests/unit/services/LazyToolsService.test.d.ts +1 -0
  258. package/ts_build/tests/unit/services/LazyToolsService.test.js +47 -0
  259. package/ts_build/tests/unit/services/LazyToolsService.test.js.map +1 -0
  260. package/ts_build/tests/unit/services/SessionManager.test.d.ts +1 -0
  261. package/ts_build/tests/unit/services/SessionManager.test.js +72 -0
  262. package/ts_build/tests/unit/services/SessionManager.test.js.map +1 -0
  263. package/ts_build/tests/unit/services/SyncerService.test.d.ts +1 -0
  264. package/ts_build/tests/unit/services/SyncerService.test.js +40 -0
  265. package/ts_build/tests/unit/services/SyncerService.test.js.map +1 -0
  266. package/ts_build/tests/unit/util/Trace.test.d.ts +1 -0
  267. package/ts_build/tests/unit/util/Trace.test.js +107 -0
  268. package/ts_build/tests/unit/util/Trace.test.js.map +1 -0
  269. package/ts_build/tests/unit/utils/http.test.d.ts +1 -0
  270. package/ts_build/tests/unit/utils/http.test.js +69 -0
  271. package/ts_build/tests/unit/utils/http.test.js.map +1 -0
@@ -5,11 +5,13 @@
5
5
  * Command semantics:
6
6
  * /attach <taskId> - Attach to a RUNNING session (in-memory, filesystem, or web).
7
7
  * If the task is completed, the user is told to use /resume instead.
8
- * /resume <taskId> - Resume a COMPLETED/saved session with optional additional context.
8
+ * /resume <taskId> [--rollback N] - Continue the same saved task.
9
+ * /fork <taskId> [--rollback N] - Start a new task from saved history.
9
10
  * /sessions - List sessions that can be attached to or resumed.
10
11
  * /logs [N] - Show the last N messages from the currently attached agent.
11
12
  */
12
13
  import { BaseChatModule } from "./BaseChatModule";
14
+ import { TraceAll } from "../../util/Trace";
13
15
  import { ChatCommand, ChatMode, ChatContext } from "../types";
14
16
  import { AgentModule } from "./AgentModule";
15
17
  import {
@@ -24,6 +26,33 @@ import { Marked } from "../../utils/index";
24
26
  import * as fs from "fs";
25
27
  import * as path from "path";
26
28
 
29
+ export function parseHistoryCommandArgs(args: string[]): {
30
+ id?: string;
31
+ rollback: number;
32
+ showAll: boolean;
33
+ } {
34
+ let id: string | undefined;
35
+ let rollback = 0;
36
+ let showAll = false;
37
+ for (let i = 0; i < args.length; i++) {
38
+ if (args[i] === "--all") {
39
+ showAll = true;
40
+ } else if (args[i] === "--rollback") {
41
+ const value = args[++i];
42
+ rollback = Number(value);
43
+ if (!Number.isInteger(rollback) || rollback < 0) {
44
+ throw new Error("--rollback must be a non-negative integer");
45
+ }
46
+ } else if (!id) {
47
+ id = args[i];
48
+ } else {
49
+ throw new Error(`Unexpected argument: ${args[i]}`);
50
+ }
51
+ }
52
+ return { id, rollback, showAll };
53
+ }
54
+
55
+ @TraceAll()
27
56
  export class SessionsModule extends BaseChatModule {
28
57
  name = "sessions";
29
58
  description = "Session and attachment management";
@@ -49,6 +78,12 @@ export class SessionsModule extends BaseChatModule {
49
78
  "Resume a completed/saved session with optional additional context",
50
79
  handler: this.handleResumeCommand.bind(this),
51
80
  },
81
+ {
82
+ name: "fork",
83
+ description:
84
+ "Fork a saved session into a new task; use --rollback N to discard recent agent interactions",
85
+ handler: this.handleForkCommand.bind(this),
86
+ },
52
87
  {
53
88
  name: "sessions",
54
89
  description:
@@ -305,10 +340,17 @@ export class SessionsModule extends BaseChatModule {
305
340
 
306
341
  async handleResumeCommand(args: string[]): Promise<void> {
307
342
  const sessionManager = this.agentModule.getSessionManager();
343
+ let parsed;
344
+ try {
345
+ parsed = parseHistoryCommandArgs(args);
346
+ } catch (error: any) {
347
+ console.error(`Usage: /resume <taskId> [--rollback N] [--all]\n${error.message}`);
348
+ return;
349
+ }
308
350
 
309
- if (args.length === 0 || args[0] === "--all") {
351
+ if (!parsed.id) {
310
352
  // Interactive: show saved sessions for selection
311
- const showAll = args[0] === "--all";
353
+ const showAll = parsed.showAll;
312
354
  const allSessions = sessionManager.listAvailableSessions();
313
355
  const savedSessions = showAll ? allSessions : allSessions.slice(0, 10);
314
356
  if (savedSessions.length === 0) {
@@ -328,18 +370,32 @@ export class SessionsModule extends BaseChatModule {
328
370
  );
329
371
 
330
372
  if (selectedId) {
331
- await this.resumeById(selectedId);
373
+ await this.resumeById(selectedId, parsed.rollback);
332
374
  }
333
375
  return;
334
376
  }
335
377
 
336
- await this.resumeById(args[0]);
378
+ await this.resumeById(parsed.id, parsed.rollback);
379
+ }
380
+
381
+ async handleForkCommand(args: string[]): Promise<void> {
382
+ let parsed;
383
+ try {
384
+ parsed = parseHistoryCommandArgs(args);
385
+ } catch (error: any) {
386
+ console.error(`Usage: /fork <taskId> [--rollback N]\n${error.message}`);
387
+ return;
388
+ }
389
+ if (!parsed.id) {
390
+ console.error("Usage: /fork <taskId> [--rollback N]");
391
+ return;
392
+ }
393
+ await this.resumeById(parsed.id, parsed.rollback, true);
337
394
  }
338
395
 
339
- private async resumeById(id: string): Promise<void> {
396
+ private async resumeById(id: string, rollback = 0, fork = false): Promise<void> {
340
397
  const sessionManager = this.agentModule.getSessionManager();
341
398
 
342
- // Check saved sessions first
343
399
  try {
344
400
  const session = sessionManager.loadSession(id);
345
401
  if (session) {
@@ -349,34 +405,46 @@ export class SessionsModule extends BaseChatModule {
349
405
  console.log(` Status : ${session.status}`);
350
406
 
351
407
  const additionalContext = await this.chatService?.getInput(
352
- "Add any additional context for resuming this session (or press Enter to skip): "
353
- );
354
- await this.agentModule.resumeSession(
355
- id,
356
- additionalContext?.trim() || undefined
408
+ `Add any additional context for ${fork ? "forking" : "resuming"} this session (or press Enter to skip): `
357
409
  );
410
+ if (fork) {
411
+ const result = await this.agentModule.resumeFromMessages({
412
+ agentName: session.agentName,
413
+ threads: session.threads,
414
+ input: additionalContext?.trim() || session.initialInput,
415
+ interactive: true,
416
+ rollback,
417
+ fork: true,
418
+ model: session.model,
419
+ provider: session.provider,
420
+ reasoningEffort: session.reasoningEffort,
421
+ summarizeReasoning: session.summarizeReasoning,
422
+ enabledTools: session.enabledTools,
423
+ });
424
+ console.log(`🌿 Forked ${id} into new task ${result.taskId}`);
425
+ } else {
426
+ await this.agentModule.resumeSession(
427
+ id,
428
+ additionalContext?.trim() || undefined,
429
+ rollback
430
+ );
431
+ }
358
432
  return;
359
433
  }
360
- } catch {
434
+ } catch (e: any) {
435
+ if (e?.message?.includes("rollback")) throw e;
361
436
  // not found as a saved session
362
437
  }
363
438
 
364
- // Check filesystem agent (may have metadata with threads)
365
439
  const fsAgentPath = path.join(".knowhow", "processes", "agents", id);
366
440
  if (fs.existsSync(fsAgentPath)) {
367
- // Try to load threads from metadata.json and resume
368
441
  const metadataPath = path.join(fsAgentPath, "metadata.json");
369
442
  if (fs.existsSync(metadataPath)) {
370
443
  try {
371
- const raw = fs.readFileSync(metadataPath, "utf-8");
372
- const metadata = JSON.parse(raw);
444
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
373
445
  const threads: any[] = metadata.threads || [];
374
446
  const agentName = metadata.agentName || "Developer";
375
-
376
- // Try to get initialInput from the saved session file (more complete)
377
- // since metadata.json doesn't always store it
378
- const savedSession = sessionManager.loadSession(id);
379
- const initialInput = savedSession?.initialInput || metadata.initialInput || metadata.prompt || "";
447
+ const initialInput = metadata.initialInput || metadata.prompt || "";
380
448
 
381
449
  console.log(`\n📋 Found task in filesystem: ${id}`);
382
450
  console.log(` Agent : ${agentName}`);
@@ -384,30 +452,33 @@ export class SessionsModule extends BaseChatModule {
384
452
  console.log(` Status : ${metadata.status || "unknown"}`);
385
453
 
386
454
  const additionalContext = await this.chatService?.getInput(
387
- "Add any additional context for resuming this session (or press Enter to skip): "
455
+ `Add any additional context for ${fork ? "forking" : "resuming"} this session (or press Enter to skip): `
388
456
  );
389
-
390
- // Normalize threads: if flat Message[] (old buggy format), wrap in array
391
457
  const normalizedThreads = threads.length > 0 && !Array.isArray(threads[0])
392
458
  ? [threads]
393
459
  : threads;
394
-
395
- await this.agentModule.resumeFromMessages({
460
+ const result = await this.agentModule.resumeFromMessages({
396
461
  agentName,
397
- taskId: id,
462
+ remoteTaskId: fork
463
+ ? undefined
464
+ : metadata.remoteTaskId || metadata.knowhowTaskId,
465
+ taskId: fork ? undefined : id,
398
466
  threads: normalizedThreads,
399
- input: additionalContext?.trim() || initialInput || "",
467
+ input: additionalContext?.trim() || initialInput,
400
468
  interactive: true,
401
- // Restore the exact model/provider/reasoning the run used so
402
- // resuming doesn't silently drop back to agent defaults.
469
+ rollback,
470
+ fork,
403
471
  model: metadata.model,
404
472
  provider: metadata.provider,
405
473
  reasoningEffort: metadata.reasoningEffort,
406
474
  summarizeReasoning: metadata.summarizeReasoning,
475
+ enabledTools: metadata.enabledTools,
407
476
  });
477
+ if (fork) console.log(`🌿 Forked ${id} into new task ${result.taskId}`);
408
478
  return;
409
479
  } catch (e: any) {
410
480
  console.error(`⚠️ Failed to load metadata for task ${id}: ${e.message}`);
481
+ return;
411
482
  }
412
483
  }
413
484
  console.log(
@@ -417,9 +488,7 @@ export class SessionsModule extends BaseChatModule {
417
488
  return;
418
489
  }
419
490
 
420
- console.log(
421
- `Session "${id}" not found. Use /sessions to list available sessions.`
422
- );
491
+ console.log(`Session "${id}" not found. Use /sessions to list available sessions.`);
423
492
  }
424
493
 
425
494
  // ─────────────────────────────────────────────────────────────────────────────
@@ -1,4 +1,5 @@
1
1
  import { services } from "../../services";
2
+ import { TraceAll } from "../../util/Trace";
2
3
  import { login } from "../../login";
3
4
  import { ask } from "../../utils";
4
5
  import { CliChatService } from "../CliChatService";
@@ -6,6 +7,7 @@ import { ChatCommand, ChatMode } from "../types";
6
7
  import { AgentModule } from "./AgentModule";
7
8
  import { BaseChatModule } from "./BaseChatModule";
8
9
 
10
+ @TraceAll()
9
11
  export class SetupModule extends BaseChatModule {
10
12
  name = "setup";
11
13
  description = "Setup commands";
@@ -1,4 +1,5 @@
1
1
  import { BaseChatModule } from "./BaseChatModule";
2
+ import { TraceAll } from "../../util/Trace";
2
3
  import { ChatCommand, ChatContext, CommandResult } from "../types";
3
4
  import { execSync } from "child_process";
4
5
 
@@ -7,6 +8,7 @@ import { execSync } from "child_process";
7
8
  * /! - Execute command and display output in console (interactive if needed)
8
9
  * /!! - Execute command and send output to the AI agent
9
10
  */
11
+ @TraceAll()
10
12
  export class ShellCommandModule extends BaseChatModule {
11
13
  name = "shell-command";
12
14
  description = "Execute shell commands with /! and /!!";
@@ -1,10 +1,12 @@
1
1
  import { BaseChatModule } from "./BaseChatModule";
2
+ import { TraceAll } from "../../util/Trace";
2
3
  import { CliChatService } from "../CliChatService";
3
4
  import { ChatCommand, ChatMode, ChatContext } from "../types";
4
5
  import { ask } from "../../utils";
5
6
  import { services } from "../../services";
6
7
  import { Models } from "../../types";
7
8
 
9
+ @TraceAll()
8
10
  export class SystemModule extends BaseChatModule {
9
11
  name = "system";
10
12
  description = "System commands for model, provider, debug, and clear";
@@ -0,0 +1,102 @@
1
+ import { ToolCall } from "../../clients/types";
2
+ import { services } from "../../services";
3
+ import { BaseChatModule } from "./BaseChatModule";
4
+ import { ChatCommand, ChatContext, CommandResult } from "../types";
5
+
6
+ /** Execute registered tools directly from interactive chat. */
7
+ export class ToolCommandModule extends BaseChatModule {
8
+ name = "tool-command";
9
+ description = "Execute a tool with /tool";
10
+
11
+ public getCommands(): ChatCommand[] {
12
+ return [
13
+ {
14
+ name: "tool",
15
+ description: "Execute a tool call from JSON",
16
+ handler: (args: string[]) => this.handleToolCommand(args),
17
+ },
18
+ ];
19
+ }
20
+
21
+ private async handleToolCommand(args: string[]): Promise<CommandResult> {
22
+ const input = args.join(" ").trim();
23
+ if (!input) {
24
+ console.log('Usage: /tool {"name":"toolName","arguments":{"key":"value"}}');
25
+ return { handled: true };
26
+ }
27
+
28
+ let toolCall: ToolCall;
29
+ try {
30
+ toolCall = this.parseToolCall(input);
31
+ } catch (error: any) {
32
+ console.error(`Invalid tool call JSON: ${error.message}`);
33
+ return { handled: true };
34
+ }
35
+
36
+ const tools = this.chatService.getTools() || services().Tools;
37
+ if (!tools) {
38
+ console.error("Tool execution failed: no tool service is available");
39
+ return { handled: true };
40
+ }
41
+
42
+ try {
43
+ const context = this.chatService.getContext();
44
+ const result = await tools.callTool(
45
+ toolCall,
46
+ tools.getFunctionNames(),
47
+ {
48
+ caller: context.selectedAgent,
49
+ taskId: context.activeAgentTaskId,
50
+ }
51
+ );
52
+
53
+ const toolError = result.toolMessages?.find(
54
+ (message: any) => message.name === "error"
55
+ );
56
+ if (toolError) {
57
+ console.error(`Tool execution failed: ${toolError.content}`);
58
+ } else {
59
+ const output = result.functionResp;
60
+ console.log(
61
+ typeof output === "object"
62
+ ? JSON.stringify(output, null, 2)
63
+ : String(output)
64
+ );
65
+ }
66
+ } catch (error: any) {
67
+ console.error(`Tool execution failed: ${error.message || String(error)}`);
68
+ }
69
+
70
+ return { handled: true };
71
+ }
72
+
73
+ private parseToolCall(input: string): ToolCall {
74
+ const parsed = JSON.parse(input);
75
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
76
+ throw new Error("expected a JSON object");
77
+ }
78
+
79
+ const fn = parsed.function || parsed;
80
+ if (!fn || typeof fn.name !== "string" || !fn.name.trim()) {
81
+ throw new Error('expected a non-empty "name" (or "function.name")');
82
+ }
83
+
84
+ const args = fn.arguments === undefined ? {} : fn.arguments;
85
+ if (typeof args !== "string" && (args === null || typeof args !== "object")) {
86
+ throw new Error('"arguments" must be a JSON object, array, or JSON string');
87
+ }
88
+
89
+ return {
90
+ id: typeof parsed.id === "string" ? parsed.id : `chat_tool_${Date.now()}`,
91
+ type: "function",
92
+ function: {
93
+ name: fn.name,
94
+ arguments: typeof args === "string" ? args : JSON.stringify(args),
95
+ },
96
+ };
97
+ }
98
+
99
+ async handleInput(input: string, context: ChatContext): Promise<boolean> {
100
+ return false;
101
+ }
102
+ }
@@ -4,4 +4,5 @@ export { AgentModule } from './AgentModule';
4
4
  export { VoiceModule } from './VoiceModule';
5
5
  export { InternalChatModule } from './InternalChatModule';
6
6
  export { ShellCommandModule } from './ShellCommandModule';
7
+ export { ToolCommandModule } from './ToolCommandModule';
7
8
  export { RendererModule } from './RendererModule';
package/src/chat/types.ts CHANGED
@@ -45,7 +45,7 @@ export interface CommandResult {
45
45
  export interface ChatCommand {
46
46
  name: string;
47
47
  description: string;
48
- handler: (args: string[]) => Promise<void | CommandResult>;
48
+ handler: (args: string[], chatService?: ChatService) => Promise<void | CommandResult>;
49
49
  modes?: string[]; // Commands can specify which modes they're available in
50
50
  }
51
51
 
@@ -104,6 +104,7 @@ export interface TaskInfo {
104
104
  provider?: string;
105
105
  reasoningEffort?: ReasoningEffort;
106
106
  summarizeReasoning?: boolean;
107
+ enabledTools?: string[];
107
108
  }
108
109
 
109
110
  export interface ChatSession {
@@ -126,6 +127,7 @@ export interface ChatSession {
126
127
  provider?: string;
127
128
  reasoningEffort?: ReasoningEffort;
128
129
  summarizeReasoning?: boolean;
130
+ enabledTools?: string[];
129
131
  }
130
132
 
131
133
  export interface ChatHistory {
package/src/cli.ts CHANGED
@@ -62,6 +62,7 @@ import { addSkillsCommand } from "./commands/skills";
62
62
  import { addAgentsCommand } from "./commands/agents";
63
63
  import { addHashCommand } from "./commands/hash";
64
64
  import { addSandboxCommand } from "./commands/sandbox";
65
+ import { addProcessesCommand } from "./commands/processes";
65
66
 
66
67
  // Handle unhandled promise rejections gracefully — particularly from MCP SDK
67
68
  // which fires errors via event emitters that can bypass Promise.allSettled.
@@ -128,18 +129,24 @@ async function main() {
128
129
  addReplayCommand(program);
129
130
  addBehaviorsCommand(program);
130
131
  addSkillsCommand(program);
131
- addAgentsCommand(program);
132
+ addAgentsCommand(program, getChatService);
132
133
  addHashCommand(program);
133
134
  addSandboxCommand(program);
135
+ addProcessesCommand(program);
134
136
 
135
137
  // Load global modules early (before parse) so they can register CLI subcommands.
136
138
  // We pass only the Program in context — no services are spun up at this stage.
137
139
  // Each module's command action is responsible for calling setupServices() as needed.
138
140
  try {
139
141
  const globalConfig = await getGlobalConfig();
142
+ const globalModules = [...new Set(globalConfig.modules || [])];
143
+ const localModules = [...new Set(config.modules || [])];
144
+ // Local modules take precedence: if a module appears in both global and
145
+ // local configs, use the local version so it resolves against local
146
+ // node_modules first. This prevents duplicate command registration errors.
140
147
  const allModulePaths = [
141
- ...(globalConfig.modules || []),
142
- ...(config.modules || []),
148
+ ...globalModules.filter((m) => !localModules.includes(m)),
149
+ ...localModules,
143
150
  ];
144
151
  if (allModulePaths.length) {
145
152
  const earlyModulesService = new ModulesService();
@@ -1,4 +1,4 @@
1
- import http from "../utils/http";
1
+ import http, { HTTP_UNAUTHORIZED_HANDLER, RefreshableHeaders } from "../utils/http";
2
2
  import {
3
3
  GenericClient,
4
4
  CompletionOptions,
@@ -17,16 +17,72 @@ export interface HttpClientOptions {
17
17
  extra_body?: Record<string, any>;
18
18
  }
19
19
 
20
+ /**
21
+ * Optional callback that, when set, is called whenever a 401 is received
22
+ * (reactive refresh) or whenever `needsRefresh()` returns true before a
23
+ * request (proactive refresh). Override this in a subclass or set it
24
+ * directly on an instance:
25
+ *
26
+ * client.refreshToken = async () => { const newJwt = await ...; client.setJwt(newJwt); };
27
+ */
28
+ export type RefreshTokenFn = () => Promise<void>;
29
+
20
30
  export class HttpClient implements GenericClient {
21
31
  /** Timeout in milliseconds for HTTP requests. Default: 30000 (30s). Use 0 to disable. */
22
32
  private timeout: number;
23
- private headers: Record<string, string>;
33
+ private headers: RefreshableHeaders;
24
34
  private extra_body: Record<string, any>;
25
35
  /** Optional pricing table: model id → per-million-token prices */
26
36
  private pricingMap: Record<string, ModelPricing> = {};
27
37
 
38
+ /**
39
+ * Override this to provide automatic token refresh. Called reactively on
40
+ * 401 responses (via HTTP_UNAUTHORIZED_HANDLER) and proactively before
41
+ * each request when `needsRefresh()` returns true.
42
+ *
43
+ * The implementation should obtain a new token and call `this.setJwt()`
44
+ * (or `this.setKey()`) before returning.
45
+ */
46
+ refreshToken: RefreshTokenFn | undefined = undefined;
47
+
48
+ /**
49
+ * Override this to provide proactive expiry detection. When it returns
50
+ * `true`, `refreshToken()` is called before the next outgoing request.
51
+ * The default implementation decodes the JWT `exp` claim (< 5 min window).
52
+ */
53
+ needsRefresh(): boolean {
54
+ const authHeader = (this.headers as Record<string, string>)["Authorization"];
55
+ if (!authHeader) return false;
56
+ const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader;
57
+ try {
58
+ const parts = token.split(".");
59
+ if (parts.length !== 3) return false;
60
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
61
+ return typeof payload.exp === "number" && payload.exp * 1000 - Date.now() < 5 * 60 * 1000;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Call before each outgoing request to proactively refresh an expiring
69
+ * token. No-ops when no `refreshToken` hook is set.
70
+ */
71
+ private async refreshIfNeeded(): Promise<void> {
72
+ if (this.refreshToken && this.needsRefresh()) {
73
+ await this.refreshToken();
74
+ }
75
+ }
76
+
28
77
  constructor(private baseUrl: string, options: HttpClientOptions = {}) {
29
78
  this.headers = options.headers ?? {};
79
+ // Wire the reactive 401 handler so the low-level http util can retry once
80
+ // after a token refresh without callers needing to do anything extra.
81
+ Object.defineProperty(this.headers, HTTP_UNAUTHORIZED_HANDLER, {
82
+ enumerable: false,
83
+ configurable: true,
84
+ get: () => this.refreshToken ? () => this.refreshToken!() : undefined,
85
+ });
30
86
  this.timeout = options.timeout ?? 30000;
31
87
  this.extra_body = options.extra_body ?? {};
32
88
  }
@@ -69,10 +125,8 @@ export class HttpClient implements GenericClient {
69
125
  }
70
126
 
71
127
  setJwt(jwt: string) {
72
- this.headers = {
73
- ...this.headers,
74
- Authorization: `Bearer ${jwt}`,
75
- };
128
+ // Mutate in-place to preserve the HTTP_UNAUTHORIZED_HANDLER symbol property.
129
+ this.headers.Authorization = `Bearer ${jwt}`;
76
130
  }
77
131
 
78
132
  setBaseUrl(baseUrl: string) {
@@ -163,7 +217,8 @@ export class HttpClient implements GenericClient {
163
217
  if (options.timeout !== undefined) this.timeout = options.timeout;
164
218
  if (options.extra_body !== undefined) this.extra_body = options.extra_body;
165
219
  if (options.headers) {
166
- this.headers = { ...this.headers, ...options.headers };
220
+ // Mutate in-place to preserve the HTTP_UNAUTHORIZED_HANDLER symbol property.
221
+ Object.assign(this.headers, options.headers);
167
222
  }
168
223
  }
169
224
 
@@ -183,6 +238,7 @@ export class HttpClient implements GenericClient {
183
238
  async createChatCompletion(
184
239
  options: CompletionOptions
185
240
  ): Promise<CompletionResponse> {
241
+ await this.refreshIfNeeded();
186
242
  return this.withRetry(async () => {
187
243
  const body = {
188
244
  ...options,
@@ -232,6 +288,7 @@ export class HttpClient implements GenericClient {
232
288
  async *createChatCompletionStream(
233
289
  options: CompletionOptions
234
290
  ): AsyncGenerator<StreamChunk> {
291
+ await this.refreshIfNeeded();
235
292
  const body = {
236
293
  ...options,
237
294
  model: options.model,
@@ -316,6 +373,7 @@ export class HttpClient implements GenericClient {
316
373
  options: CompletionOptions,
317
374
  store = false
318
375
  ): Promise<CompletionResponse> {
376
+ await this.refreshIfNeeded();
319
377
  return this.withRetry(async () => {
320
378
  // Extract system messages as instructions
321
379
  const systemMessages = options.messages.filter((m) => m.role === "system");
@@ -484,6 +542,7 @@ export class HttpClient implements GenericClient {
484
542
  }
485
543
 
486
544
  async createEmbedding(options: EmbeddingOptions): Promise<EmbeddingResponse> {
545
+ await this.refreshIfNeeded();
487
546
  return this.withRetry(async () => {
488
547
  const response = await http.post(
489
548
  `${this.baseUrl}/v1/embeddings`,
@@ -511,6 +570,7 @@ export class HttpClient implements GenericClient {
511
570
  }
512
571
 
513
572
  async getModels(type = "all") {
573
+ await this.refreshIfNeeded();
514
574
  return this.withRetry(async () => {
515
575
  const response = await http.get(`${this.baseUrl}/v1/models?type=${type}`, {
516
576
  headers: this.headers as Record<string, string>,
@@ -59,6 +59,7 @@ import { GenericLlamaClient } from "./llama";
59
59
  import { GenericFireworksClient } from "./fireworks";
60
60
  import { GenericMetaClient } from "./meta";
61
61
  import { GenericQwenClient } from "./qwen";
62
+ import { TraceAll } from "../util/Trace";
62
63
  export {
63
64
  OpenAiTextPricing,
64
65
  AnthropicTextPricing,
@@ -139,6 +140,7 @@ const DEFAULT_PROVIDERS: ModelProvider[] = [
139
140
  { provider: "qwen", envKey: "QWEN_CLOUD_API_KEY" },
140
141
  ];
141
142
 
143
+ @TraceAll()
142
144
  export class AIClient {
143
145
  clients: Record<string, GenericClient> = {};
144
146
 
@@ -526,12 +528,14 @@ export class AIClient {
526
528
  }
527
529
 
528
530
  providerHasModel(provider: string, model: string): boolean {
531
+ if (!provider) return false;
529
532
  const models = this.clientModels[provider];
530
533
  if (!models) return false;
531
534
  return models.includes(model);
532
535
  }
533
536
 
534
537
  findModel(modelPrefix: string) {
538
+ if (!modelPrefix) return undefined;
535
539
  for (const provider of Object.keys(this.clientModels)) {
536
540
  const models = this.clientModels[provider] as string[];
537
541
  const foundModel = models.find((m) => m.startsWith(modelPrefix));
@@ -55,7 +55,7 @@ export function addAgentCommand(program: Command, getChatService: () => any): vo
55
55
  "AI provider (openai, anthropic, google, xai)"
56
56
  )
57
57
  .option("--model <model>", "Specific model for the provider")
58
- .option("--agent-name <name>", "Which agent to use", "Patcher")
58
+ .option("--agent-name <name>", "Which agent to use (default: Patcher)")
59
59
  .option(
60
60
  "--reasoning-effort <effort>",
61
61
  `Reasoning effort: ${REASONING_EFFORTS.join(", ")}. Individual models support different subsets.`
@@ -94,10 +94,6 @@ export function addAgentCommand(program: Command, getChatService: () => any): vo
94
94
  )
95
95
  .option("--prompt-file <path>", "Custom prompt template file with {text}")
96
96
  .option("--input <text>", "Task input (fallback to stdin if not provided)")
97
- .option(
98
- "--resume",
99
- "Resume a previously started task using the --task-id (local FS or remote)"
100
- )
101
97
  .option(
102
98
  "--renderer <name>",
103
99
  "Renderer to use: basic, compact, fancy, or a path/package (default: from config or basic)"
@@ -124,39 +120,6 @@ export function addAgentCommand(program: Command, getChatService: () => any): vo
124
120
 
125
121
  const agentModule = new AgentModule();
126
122
 
127
- if (options.resume) {
128
- const threads = await agentModule.loadThreadsForTask(
129
- options.taskId,
130
- options.messageId
131
- );
132
- // Restore the exact model/provider/reasoning the original run used
133
- // (falls back to agent defaults when not persisted / remote-only).
134
- const savedSettings = await agentModule.loadTaskSettings(
135
- options.taskId,
136
- options.messageId
137
- );
138
- const resumeInput =
139
- options.input || "Please continue from where you left off.";
140
-
141
- await agentModule.initialize(chatService);
142
- const { taskCompleted: resumed } =
143
- await agentModule.resumeFromMessages({
144
- agentName:
145
- options.agentName || savedSettings.agentName || "Patcher",
146
- input: resumeInput,
147
- threads,
148
- messageId: options.messageId,
149
- taskId: options.taskId,
150
- // CLI-provided model/provider still win over persisted ones.
151
- model: options.model || savedSettings.model,
152
- provider: options.provider || savedSettings.provider,
153
- reasoningEffort: savedSettings.reasoningEffort,
154
- summarizeReasoning: savedSettings.summarizeReasoning,
155
- });
156
- await resumed;
157
- return;
158
- }
159
-
160
123
  let input = options.input;
161
124
 
162
125
  if (!input && !options.promptFile) {