@tyvm/knowhow 0.0.108 → 0.0.109-dev.86123ed

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 (236) hide show
  1. package/README.md +45 -0
  2. package/package.json +9 -4
  3. package/scripts/build-for-node.sh +10 -24
  4. package/scripts/publish.sh +86 -0
  5. package/src/agents/base/base.ts +10 -0
  6. package/src/agents/tools/execCommand.ts +49 -6
  7. package/src/agents/tools/index.ts +0 -1
  8. package/src/agents/tools/list.ts +2 -4
  9. package/src/chat/CliChatService.ts +11 -2
  10. package/src/chat/modules/AgentModule.ts +61 -31
  11. package/src/chat/modules/SessionsModule.ts +47 -3
  12. package/src/chat/modules/SystemModule.ts +2 -2
  13. package/src/chat/renderer/CompactRenderer.ts +20 -0
  14. package/src/chat/renderer/ConsoleRenderer.ts +19 -0
  15. package/src/chat/renderer/FancyRenderer.ts +19 -0
  16. package/src/chat/renderer/types.ts +11 -0
  17. package/src/cli.ts +91 -659
  18. package/src/clients/anthropic.ts +18 -17
  19. package/src/clients/index.ts +31 -11
  20. package/src/clients/openai.ts +8 -5
  21. package/src/clients/types.ts +48 -10
  22. package/src/clients/withRetry.ts +89 -0
  23. package/src/cloudWorker.ts +175 -113
  24. package/src/commands/agent.ts +246 -0
  25. package/src/commands/misc.ts +174 -0
  26. package/src/commands/modules.ts +217 -0
  27. package/src/commands/services.ts +77 -0
  28. package/src/commands/workers.ts +168 -0
  29. package/src/config.ts +38 -1
  30. package/src/fileSync.ts +70 -29
  31. package/src/hashes.ts +35 -13
  32. package/src/index.ts +18 -0
  33. package/src/logger.ts +197 -0
  34. package/src/plugins/embedding.ts +11 -6
  35. package/src/plugins/plugins.ts +0 -21
  36. package/src/plugins/vim.ts +5 -16
  37. package/src/processors/JsonCompressor.ts +6 -6
  38. package/src/services/EventService.ts +61 -1
  39. package/src/services/KnowhowClient.ts +34 -4
  40. package/src/services/MediaProcessorService.ts +3 -2
  41. package/src/services/modules/index.ts +95 -51
  42. package/src/services/modules/types.ts +6 -0
  43. package/src/tunnel.ts +216 -0
  44. package/src/types.ts +0 -1
  45. package/src/worker.ts +105 -312
  46. package/src/workers/auth/WsMiddleware.ts +99 -0
  47. package/src/workers/auth/authMiddleware.ts +104 -0
  48. package/src/workers/auth/types.ts +14 -2
  49. package/src/workers/tools/index.ts +2 -0
  50. package/src/workers/tools/reloadConfig.ts +84 -0
  51. package/tests/services/WorkerReloadConfig.test.ts +141 -0
  52. package/tests/unit/clients/AIClient.test.ts +446 -0
  53. package/tests/unit/clients/withRetry.test.ts +319 -0
  54. package/tests/unit/commands/github-credentials.test.ts +210 -0
  55. package/tests/unit/modules/moduleLoading.test.ts +39 -37
  56. package/tests/unit/plugins/pluginLoading.test.ts +0 -85
  57. package/ts_build/package.json +9 -4
  58. package/ts_build/src/agents/base/base.js +11 -0
  59. package/ts_build/src/agents/base/base.js.map +1 -1
  60. package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
  61. package/ts_build/src/agents/tools/execCommand.js +39 -5
  62. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  63. package/ts_build/src/agents/tools/index.d.ts +0 -1
  64. package/ts_build/src/agents/tools/index.js +0 -1
  65. package/ts_build/src/agents/tools/index.js.map +1 -1
  66. package/ts_build/src/agents/tools/list.js +2 -4
  67. package/ts_build/src/agents/tools/list.js.map +1 -1
  68. package/ts_build/src/chat/CliChatService.js +14 -2
  69. package/ts_build/src/chat/CliChatService.js.map +1 -1
  70. package/ts_build/src/chat/modules/AgentModule.d.ts +1 -1
  71. package/ts_build/src/chat/modules/AgentModule.js +43 -20
  72. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  73. package/ts_build/src/chat/modules/SessionsModule.js +37 -3
  74. package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
  75. package/ts_build/src/chat/modules/SystemModule.js +2 -2
  76. package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
  77. package/ts_build/src/chat/renderer/CompactRenderer.d.ts +4 -0
  78. package/ts_build/src/chat/renderer/CompactRenderer.js +16 -0
  79. package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -1
  80. package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +4 -0
  81. package/ts_build/src/chat/renderer/ConsoleRenderer.js +16 -0
  82. package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -1
  83. package/ts_build/src/chat/renderer/FancyRenderer.d.ts +4 -0
  84. package/ts_build/src/chat/renderer/FancyRenderer.js +16 -0
  85. package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -1
  86. package/ts_build/src/chat/renderer/types.d.ts +2 -0
  87. package/ts_build/src/cli.js +47 -519
  88. package/ts_build/src/cli.js.map +1 -1
  89. package/ts_build/src/clients/anthropic.d.ts +5 -5
  90. package/ts_build/src/clients/anthropic.js +18 -17
  91. package/ts_build/src/clients/anthropic.js.map +1 -1
  92. package/ts_build/src/clients/index.js +9 -10
  93. package/ts_build/src/clients/index.js.map +1 -1
  94. package/ts_build/src/clients/openai.js +4 -4
  95. package/ts_build/src/clients/openai.js.map +1 -1
  96. package/ts_build/src/clients/types.d.ts +15 -8
  97. package/ts_build/src/clients/withRetry.d.ts +2 -0
  98. package/ts_build/src/clients/withRetry.js +60 -0
  99. package/ts_build/src/clients/withRetry.js.map +1 -0
  100. package/ts_build/src/cloudWorker.d.ts +14 -0
  101. package/ts_build/src/cloudWorker.js +105 -66
  102. package/ts_build/src/cloudWorker.js.map +1 -1
  103. package/ts_build/src/commands/agent.d.ts +6 -0
  104. package/ts_build/src/commands/agent.js +229 -0
  105. package/ts_build/src/commands/agent.js.map +1 -0
  106. package/ts_build/src/commands/misc.d.ts +10 -0
  107. package/ts_build/src/commands/misc.js +197 -0
  108. package/ts_build/src/commands/misc.js.map +1 -0
  109. package/ts_build/src/commands/modules.d.ts +3 -0
  110. package/ts_build/src/commands/modules.js +207 -0
  111. package/ts_build/src/commands/modules.js.map +1 -0
  112. package/ts_build/src/commands/services.d.ts +5 -0
  113. package/ts_build/src/commands/services.js +87 -0
  114. package/ts_build/src/commands/services.js.map +1 -0
  115. package/ts_build/src/commands/workers.d.ts +6 -0
  116. package/ts_build/src/commands/workers.js +168 -0
  117. package/ts_build/src/commands/workers.js.map +1 -0
  118. package/ts_build/src/config.d.ts +1 -0
  119. package/ts_build/src/config.js +33 -1
  120. package/ts_build/src/config.js.map +1 -1
  121. package/ts_build/src/fileSync.d.ts +6 -0
  122. package/ts_build/src/fileSync.js +50 -23
  123. package/ts_build/src/fileSync.js.map +1 -1
  124. package/ts_build/src/hashes.d.ts +2 -2
  125. package/ts_build/src/hashes.js +35 -9
  126. package/ts_build/src/hashes.js.map +1 -1
  127. package/ts_build/src/index.d.ts +1 -0
  128. package/ts_build/src/index.js +17 -1
  129. package/ts_build/src/index.js.map +1 -1
  130. package/ts_build/src/logger.d.ts +21 -0
  131. package/ts_build/src/logger.js +106 -0
  132. package/ts_build/src/logger.js.map +1 -0
  133. package/ts_build/src/plugins/embedding.js +4 -3
  134. package/ts_build/src/plugins/embedding.js.map +1 -1
  135. package/ts_build/src/plugins/plugins.d.ts +0 -2
  136. package/ts_build/src/plugins/plugins.js +0 -11
  137. package/ts_build/src/plugins/plugins.js.map +1 -1
  138. package/ts_build/src/plugins/vim.js +3 -9
  139. package/ts_build/src/plugins/vim.js.map +1 -1
  140. package/ts_build/src/processors/JsonCompressor.js +4 -4
  141. package/ts_build/src/processors/JsonCompressor.js.map +1 -1
  142. package/ts_build/src/services/EventService.d.ts +6 -1
  143. package/ts_build/src/services/EventService.js +29 -0
  144. package/ts_build/src/services/EventService.js.map +1 -1
  145. package/ts_build/src/services/KnowhowClient.d.ts +13 -1
  146. package/ts_build/src/services/KnowhowClient.js +19 -2
  147. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  148. package/ts_build/src/services/MediaProcessorService.d.ts +2 -1
  149. package/ts_build/src/services/MediaProcessorService.js +2 -1
  150. package/ts_build/src/services/MediaProcessorService.js.map +1 -1
  151. package/ts_build/src/services/modules/index.d.ts +33 -0
  152. package/ts_build/src/services/modules/index.js +67 -47
  153. package/ts_build/src/services/modules/index.js.map +1 -1
  154. package/ts_build/src/services/modules/types.d.ts +6 -0
  155. package/ts_build/src/tunnel.d.ts +27 -0
  156. package/ts_build/src/tunnel.js +112 -0
  157. package/ts_build/src/tunnel.js.map +1 -0
  158. package/ts_build/src/types.d.ts +0 -1
  159. package/ts_build/src/types.js.map +1 -1
  160. package/ts_build/src/worker.d.ts +1 -4
  161. package/ts_build/src/worker.js +59 -227
  162. package/ts_build/src/worker.js.map +1 -1
  163. package/ts_build/src/workers/auth/WsMiddleware.d.ts +8 -0
  164. package/ts_build/src/workers/auth/WsMiddleware.js +65 -0
  165. package/ts_build/src/workers/auth/WsMiddleware.js.map +1 -0
  166. package/ts_build/src/workers/auth/authMiddleware.d.ts +3 -0
  167. package/ts_build/src/workers/auth/authMiddleware.js +60 -0
  168. package/ts_build/src/workers/auth/authMiddleware.js.map +1 -0
  169. package/ts_build/src/workers/auth/types.d.ts +8 -1
  170. package/ts_build/src/workers/tools/index.d.ts +2 -0
  171. package/ts_build/src/workers/tools/index.js +4 -1
  172. package/ts_build/src/workers/tools/index.js.map +1 -1
  173. package/ts_build/src/workers/tools/reloadConfig.d.ts +14 -0
  174. package/ts_build/src/workers/tools/reloadConfig.js +48 -0
  175. package/ts_build/src/workers/tools/reloadConfig.js.map +1 -0
  176. package/ts_build/tests/services/WorkerReloadConfig.test.d.ts +1 -0
  177. package/ts_build/tests/services/WorkerReloadConfig.test.js +86 -0
  178. package/ts_build/tests/services/WorkerReloadConfig.test.js.map +1 -0
  179. package/ts_build/tests/unit/clients/AIClient.test.d.ts +1 -0
  180. package/ts_build/tests/unit/clients/AIClient.test.js +339 -0
  181. package/ts_build/tests/unit/clients/AIClient.test.js.map +1 -0
  182. package/ts_build/tests/unit/clients/withRetry.test.d.ts +1 -0
  183. package/ts_build/tests/unit/clients/withRetry.test.js +225 -0
  184. package/ts_build/tests/unit/clients/withRetry.test.js.map +1 -0
  185. package/ts_build/tests/unit/commands/github-credentials.test.d.ts +1 -0
  186. package/ts_build/tests/unit/commands/github-credentials.test.js +145 -0
  187. package/ts_build/tests/unit/commands/github-credentials.test.js.map +1 -0
  188. package/ts_build/tests/unit/modules/moduleLoading.test.js +20 -26
  189. package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
  190. package/ts_build/tests/unit/plugins/pluginLoading.test.js +0 -65
  191. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
  192. package/src/agents/tools/executeScript/README.md +0 -94
  193. package/src/agents/tools/executeScript/definition.ts +0 -79
  194. package/src/agents/tools/executeScript/examples/dependency-injection-validation.ts +0 -272
  195. package/src/agents/tools/executeScript/examples/quick-test.ts +0 -74
  196. package/src/agents/tools/executeScript/examples/serialization-test.ts +0 -321
  197. package/src/agents/tools/executeScript/examples/test-runner.ts +0 -197
  198. package/src/agents/tools/executeScript/index.ts +0 -98
  199. package/src/services/script-execution/SandboxContext.ts +0 -282
  200. package/src/services/script-execution/ScriptExecutor.ts +0 -441
  201. package/src/services/script-execution/ScriptPolicy.ts +0 -194
  202. package/src/services/script-execution/ScriptTracer.ts +0 -249
  203. package/src/services/script-execution/types.ts +0 -134
  204. package/ts_build/src/agents/tools/executeScript/definition.d.ts +0 -2
  205. package/ts_build/src/agents/tools/executeScript/definition.js +0 -76
  206. package/ts_build/src/agents/tools/executeScript/definition.js.map +0 -1
  207. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.d.ts +0 -18
  208. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js +0 -192
  209. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js.map +0 -1
  210. package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +0 -3
  211. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +0 -64
  212. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +0 -1
  213. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +0 -15
  214. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +0 -266
  215. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +0 -1
  216. package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +0 -4
  217. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +0 -208
  218. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +0 -1
  219. package/ts_build/src/agents/tools/executeScript/index.d.ts +0 -28
  220. package/ts_build/src/agents/tools/executeScript/index.js +0 -72
  221. package/ts_build/src/agents/tools/executeScript/index.js.map +0 -1
  222. package/ts_build/src/services/script-execution/SandboxContext.d.ts +0 -34
  223. package/ts_build/src/services/script-execution/SandboxContext.js +0 -189
  224. package/ts_build/src/services/script-execution/SandboxContext.js.map +0 -1
  225. package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +0 -19
  226. package/ts_build/src/services/script-execution/ScriptExecutor.js +0 -269
  227. package/ts_build/src/services/script-execution/ScriptExecutor.js.map +0 -1
  228. package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +0 -28
  229. package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -115
  230. package/ts_build/src/services/script-execution/ScriptPolicy.js.map +0 -1
  231. package/ts_build/src/services/script-execution/ScriptTracer.d.ts +0 -19
  232. package/ts_build/src/services/script-execution/ScriptTracer.js +0 -186
  233. package/ts_build/src/services/script-execution/ScriptTracer.js.map +0 -1
  234. package/ts_build/src/services/script-execution/types.d.ts +0 -108
  235. package/ts_build/src/services/script-execution/types.js +0 -3
  236. package/ts_build/src/services/script-execution/types.js.map +0 -1
@@ -2,6 +2,17 @@
2
2
  * Auth message types for the worker WebSocket authentication protocol.
3
3
  */
4
4
 
5
+ // Client → Worker: request a challenge
6
+ export interface AuthGetChallengeMessage {
7
+ type: "auth:getChallenge";
8
+ }
9
+
10
+ // Worker → Client: sent when a non-auth message is received while locked
11
+ export interface AuthLockedMessage {
12
+ type: "auth:locked";
13
+ message: string;
14
+ }
15
+
5
16
  // Worker → Client: challenge
6
17
  export interface AuthChallengeMessage {
7
18
  type: "auth:challenge";
@@ -31,12 +42,13 @@ export interface AuthFailureMessage {
31
42
  type: "auth:failure";
32
43
  reason: "invalid_signature" | "expired" | "unknown_credential";
33
44
  }
34
-
35
45
  export type AuthMessage =
46
+ | AuthGetChallengeMessage
36
47
  | AuthChallengeMessage
37
48
  | AuthResponseMessage
38
49
  | AuthSuccessMessage
39
- | AuthFailureMessage;
50
+ | AuthFailureMessage
51
+ | AuthLockedMessage;
40
52
 
41
53
  // Passkey credential stored in config
42
54
  export interface PasskeyCredential {
@@ -2,6 +2,7 @@ export * from "./listAllowedPorts";
2
2
  export * from "./getChallenge";
3
3
  export * from "./unlock";
4
4
  export * from "./lock";
5
+ export * from "./reloadConfig";
5
6
 
6
7
  import {
7
8
  listAllowedPorts,
@@ -11,6 +12,7 @@ import {
11
12
  export { makeGetChallengeTool } from "./getChallenge";
12
13
  export { makeUnlockTool } from "./unlock";
13
14
  export { makeLockTool } from "./lock";
15
+ export { makeReloadConfigTool } from "./reloadConfig";
14
16
 
15
17
  export default {
16
18
  tools: { listAllowedPorts },
@@ -0,0 +1,84 @@
1
+ import { Tool } from "../../clients/types";
2
+ import { getConfig } from "../../config";
3
+ import { McpServerService } from "../../services/McpServer";
4
+ import { McpService } from "../../services/Mcp";
5
+ import { ToolsService } from "../../services/Tools";
6
+
7
+ export interface ReloadConfigResult {
8
+ success: boolean;
9
+ toolCount: number;
10
+ mcpCount: number;
11
+ message: string;
12
+ }
13
+
14
+ /**
15
+ * Factory that creates the reloadConfig tool with access to runtime services.
16
+ * The tool re-reads the config from disk, reconnects all MCPs, and rebuilds
17
+ * the active tool list — the same logic as the WebSocket reloadConfig handler.
18
+ *
19
+ * Typical usage after pulling updated config from the cloud worker API:
20
+ * 1. execCommand("knowhow cloudworker --pull <cloudWorkerId>")
21
+ * 2. reloadConfig()
22
+ */
23
+ export function makeReloadConfigTool(
24
+ Mcp: McpService,
25
+ Tools: ToolsService,
26
+ mcpServer: McpServerService,
27
+ setToolsToUse: (tools: ReturnType<typeof Tools.getToolsByNames>) => void
28
+ ) {
29
+ const reloadConfig = async (): Promise<ReloadConfigResult> => {
30
+ try {
31
+ // Re-read fresh config from disk
32
+ const freshConfig = await getConfig();
33
+
34
+ // Close all existing MCP connections
35
+ await Mcp.closeAll();
36
+
37
+ // Reconnect from fresh config and re-register tools
38
+ await Mcp.connectToConfigured(Tools);
39
+
40
+ // Rebuild the allowed tools list from fresh config
41
+ const allowedToolNames =
42
+ freshConfig.worker?.allowedTools ?? Tools.getToolNames();
43
+ const newToolsToUse = Tools.getToolsByNames(allowedToolNames);
44
+ setToolsToUse(newToolsToUse);
45
+
46
+ // Update the MCP server with the new tool list
47
+ mcpServer.withTools(newToolsToUse);
48
+
49
+ const mcpCount = freshConfig.mcps?.length ?? 0;
50
+
51
+ return {
52
+ success: true,
53
+ toolCount: newToolsToUse.length,
54
+ mcpCount,
55
+ message: `Config reloaded: ${newToolsToUse.length} tools active, ${mcpCount} MCP(s) configured`,
56
+ };
57
+ } catch (err) {
58
+ const message = err instanceof Error ? err.message : String(err);
59
+ return {
60
+ success: false,
61
+ toolCount: 0,
62
+ mcpCount: 0,
63
+ message: `Failed to reload config: ${message}`,
64
+ };
65
+ }
66
+ };
67
+
68
+ const reloadConfigDefinition: Tool = {
69
+ type: "function" as const,
70
+ function: {
71
+ name: "reloadConfig",
72
+ description:
73
+ "Reload the worker config from disk, reconnect all MCPs, and rebuild the active tool list. " +
74
+ "Call this after running `knowhow cloudworker --pull <id>` to apply updated MCPs without restarting the worker.",
75
+ parameters: {
76
+ type: "object",
77
+ properties: {},
78
+ required: [],
79
+ },
80
+ },
81
+ };
82
+
83
+ return { reloadConfig, reloadConfigDefinition };
84
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Tests for the worker reloadConfig message handler.
3
+ *
4
+ * We test the core logic in isolation — simulating the "reloadConfig" message
5
+ * arriving on the WebSocket and verifying that MCPs are torn down and
6
+ * re-connected, and that the tool list is rebuilt from fresh config.
7
+ */
8
+
9
+ import { McpService } from "../../src/services/Mcp";
10
+ import { ToolsService } from "../../src/services/Tools";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Helpers: build the same reload logic that lives in worker.ts so we can
14
+ // test it in isolation without spinning up a real WebSocket server.
15
+ // ---------------------------------------------------------------------------
16
+
17
+ async function simulateReloadConfig(
18
+ Mcp: McpService,
19
+ Tools: ToolsService,
20
+ mcpServer: { withTools: (tools: unknown[]) => void },
21
+ getConfig: () => Promise<{ worker?: { allowedTools?: string[] } }>,
22
+ toolsToUseRef: { value: unknown[] }
23
+ ) {
24
+ // This mirrors the handler in worker.ts
25
+ const freshConfig = await getConfig();
26
+ await Mcp.closeAll();
27
+ await Mcp.connectToConfigured(Tools);
28
+ const allowedToolNames =
29
+ freshConfig.worker?.allowedTools ?? Tools.getToolNames();
30
+ toolsToUseRef.value = Tools.getToolsByNames(allowedToolNames);
31
+ mcpServer.withTools(toolsToUseRef.value);
32
+ }
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Tests
36
+ // ---------------------------------------------------------------------------
37
+
38
+ describe("Worker reloadConfig handler", () => {
39
+ let Mcp: McpService;
40
+ let Tools: ToolsService;
41
+ let mcpServer: { withTools: jest.Mock };
42
+ let toolsToUseRef: { value: unknown[] };
43
+
44
+ beforeEach(() => {
45
+ Mcp = new McpService();
46
+ Tools = new ToolsService();
47
+ mcpServer = { withTools: jest.fn() };
48
+ toolsToUseRef = { value: [] };
49
+
50
+ // Spy on MCP methods
51
+ jest.spyOn(Mcp, "closeAll").mockResolvedValue(undefined);
52
+ jest.spyOn(Mcp, "connectToConfigured").mockResolvedValue(undefined);
53
+ });
54
+
55
+ afterEach(() => {
56
+ jest.restoreAllMocks();
57
+ });
58
+
59
+ it("should call closeAll() to tear down existing MCP connections", async () => {
60
+ const getConfig = jest
61
+ .fn()
62
+ .mockResolvedValue({ worker: { allowedTools: [] } });
63
+
64
+ await simulateReloadConfig(Mcp, Tools, mcpServer, getConfig, toolsToUseRef);
65
+
66
+ expect(Mcp.closeAll).toHaveBeenCalledTimes(1);
67
+ });
68
+
69
+ it("should call connectToConfigured() to reconnect MCPs from fresh config", async () => {
70
+ const getConfig = jest
71
+ .fn()
72
+ .mockResolvedValue({ worker: { allowedTools: [] } });
73
+
74
+ await simulateReloadConfig(Mcp, Tools, mcpServer, getConfig, toolsToUseRef);
75
+
76
+ expect(Mcp.connectToConfigured).toHaveBeenCalledWith(Tools);
77
+ });
78
+
79
+ it("should rebuild the tool list from allowedTools in fresh config", async () => {
80
+ // Spy on getToolsByNames so we can track what names were requested
81
+ const toolsByNamesSpy = jest
82
+ .spyOn(Tools, "getToolsByNames")
83
+ .mockReturnValue([{ function: { name: "execCommand" } }] as ReturnType<ToolsService["getToolsByNames"]>);
84
+
85
+ const getConfig = jest
86
+ .fn()
87
+ .mockResolvedValue({ worker: { allowedTools: ["execCommand"] } });
88
+
89
+ await simulateReloadConfig(Mcp, Tools, mcpServer, getConfig, toolsToUseRef);
90
+
91
+ expect(toolsByNamesSpy).toHaveBeenCalledWith(["execCommand"]);
92
+ expect(toolsToUseRef.value).toHaveLength(1);
93
+ });
94
+
95
+ it("should fall back to all tool names when allowedTools is not set", async () => {
96
+ const allNames = ["execCommand", "readFile", "writeFileChunk"];
97
+ jest.spyOn(Tools, "getToolNames").mockReturnValue(allNames);
98
+ const toolsByNamesSpy = jest
99
+ .spyOn(Tools, "getToolsByNames")
100
+ .mockReturnValue([] as ReturnType<ToolsService["getToolsByNames"]>);
101
+
102
+ // Config has no worker.allowedTools
103
+ const getConfig = jest.fn().mockResolvedValue({});
104
+
105
+ await simulateReloadConfig(Mcp, Tools, mcpServer, getConfig, toolsToUseRef);
106
+
107
+ expect(toolsByNamesSpy).toHaveBeenCalledWith(allNames);
108
+ });
109
+
110
+ it("should call mcpServer.withTools() with the rebuilt tool list", async () => {
111
+ const fakeTools = [{ function: { name: "readFile" } }] as ReturnType<ToolsService["getToolsByNames"]>;
112
+ jest.spyOn(Tools, "getToolsByNames").mockReturnValue(fakeTools);
113
+
114
+ const getConfig = jest
115
+ .fn()
116
+ .mockResolvedValue({ worker: { allowedTools: ["readFile"] } });
117
+
118
+ await simulateReloadConfig(Mcp, Tools, mcpServer, getConfig, toolsToUseRef);
119
+
120
+ expect(mcpServer.withTools).toHaveBeenCalledWith(fakeTools);
121
+ });
122
+
123
+ it("should re-read the config on every reload (not use stale config)", async () => {
124
+ const getConfig = jest
125
+ .fn()
126
+ .mockResolvedValueOnce({ worker: { allowedTools: ["execCommand"] } })
127
+ .mockResolvedValueOnce({ worker: { allowedTools: ["readFile", "writeFileChunk"] } });
128
+
129
+ jest.spyOn(Tools, "getToolsByNames").mockReturnValue([]);
130
+
131
+ // First reload
132
+ await simulateReloadConfig(Mcp, Tools, mcpServer, getConfig, toolsToUseRef);
133
+ // Second reload
134
+ await simulateReloadConfig(Mcp, Tools, mcpServer, getConfig, toolsToUseRef);
135
+
136
+ expect(getConfig).toHaveBeenCalledTimes(2);
137
+ // Each reload should tear down and reconnect
138
+ expect(Mcp.closeAll).toHaveBeenCalledTimes(2);
139
+ expect(Mcp.connectToConfigured).toHaveBeenCalledTimes(2);
140
+ });
141
+ });