@botbotgo/runtime 1.0.0

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 (313) hide show
  1. package/.github/workflows/ci.yml +46 -0
  2. package/.github/workflows/release.yml +79 -0
  3. package/README.md +71 -0
  4. package/config/examples/memory.yaml +39 -0
  5. package/config/examples/model.yaml +21 -0
  6. package/config/examples/runtime.yaml +44 -0
  7. package/config/examples/skills.yaml +8 -0
  8. package/config/examples/tool.yaml +16 -0
  9. package/dist/config/index.d.ts +7 -0
  10. package/dist/config/index.d.ts.map +1 -0
  11. package/dist/config/index.js +4 -0
  12. package/dist/config/index.js.map +1 -0
  13. package/dist/config/loader.d.ts +41 -0
  14. package/dist/config/loader.d.ts.map +1 -0
  15. package/dist/config/loader.js +140 -0
  16. package/dist/config/loader.js.map +1 -0
  17. package/dist/config/resolveRuntimeConfig.d.ts +83 -0
  18. package/dist/config/resolveRuntimeConfig.d.ts.map +1 -0
  19. package/dist/config/resolveRuntimeConfig.js +85 -0
  20. package/dist/config/resolveRuntimeConfig.js.map +1 -0
  21. package/dist/config/resources.d.ts +112 -0
  22. package/dist/config/resources.d.ts.map +1 -0
  23. package/dist/config/resources.js +20 -0
  24. package/dist/config/resources.js.map +1 -0
  25. package/dist/config/runtimeConfigLoader.d.ts +28 -0
  26. package/dist/config/runtimeConfigLoader.d.ts.map +1 -0
  27. package/dist/config/runtimeConfigLoader.js +38 -0
  28. package/dist/config/runtimeConfigLoader.js.map +1 -0
  29. package/dist/index.d.ts +9 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +5 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/runtime/agentMiddleware.d.ts +3 -0
  34. package/dist/runtime/agentMiddleware.d.ts.map +1 -0
  35. package/dist/runtime/agentMiddleware.js +2 -0
  36. package/dist/runtime/agentMiddleware.js.map +1 -0
  37. package/dist/runtime/bootstrap/runtimeFactory.d.ts +7 -0
  38. package/dist/runtime/bootstrap/runtimeFactory.d.ts.map +1 -0
  39. package/dist/runtime/bootstrap/runtimeFactory.js +139 -0
  40. package/dist/runtime/bootstrap/runtimeFactory.js.map +1 -0
  41. package/dist/runtime/bootstrap/runtimeModuleInitializer.d.ts +5 -0
  42. package/dist/runtime/bootstrap/runtimeModuleInitializer.d.ts.map +1 -0
  43. package/dist/runtime/bootstrap/runtimeModuleInitializer.js +32 -0
  44. package/dist/runtime/bootstrap/runtimeModuleInitializer.js.map +1 -0
  45. package/dist/runtime/context/agentContext.d.ts +5 -0
  46. package/dist/runtime/context/agentContext.d.ts.map +1 -0
  47. package/dist/runtime/context/agentContext.js +19 -0
  48. package/dist/runtime/context/agentContext.js.map +1 -0
  49. package/dist/runtime/context/globalContext.d.ts +19 -0
  50. package/dist/runtime/context/globalContext.d.ts.map +1 -0
  51. package/dist/runtime/context/globalContext.js +47 -0
  52. package/dist/runtime/context/globalContext.js.map +1 -0
  53. package/dist/runtime/events/eventPublisher.d.ts +15 -0
  54. package/dist/runtime/events/eventPublisher.d.ts.map +1 -0
  55. package/dist/runtime/events/eventPublisher.js +23 -0
  56. package/dist/runtime/events/eventPublisher.js.map +1 -0
  57. package/dist/runtime/execution/agentRunExecutor.d.ts +38 -0
  58. package/dist/runtime/execution/agentRunExecutor.d.ts.map +1 -0
  59. package/dist/runtime/execution/agentRunExecutor.js +378 -0
  60. package/dist/runtime/execution/agentRunExecutor.js.map +1 -0
  61. package/dist/runtime/execution/agentRunExecutor.types.d.ts +37 -0
  62. package/dist/runtime/execution/agentRunExecutor.types.d.ts.map +1 -0
  63. package/dist/runtime/execution/agentRunExecutor.types.js +2 -0
  64. package/dist/runtime/execution/agentRunExecutor.types.js.map +1 -0
  65. package/dist/runtime/execution/agentRunProgress.d.ts +8 -0
  66. package/dist/runtime/execution/agentRunProgress.d.ts.map +1 -0
  67. package/dist/runtime/execution/agentRunProgress.js +46 -0
  68. package/dist/runtime/execution/agentRunProgress.js.map +1 -0
  69. package/dist/runtime/execution/agentRunState.d.ts +44 -0
  70. package/dist/runtime/execution/agentRunState.d.ts.map +1 -0
  71. package/dist/runtime/execution/agentRunState.js +17 -0
  72. package/dist/runtime/execution/agentRunState.js.map +1 -0
  73. package/dist/runtime/execution/policies/runCompletionPolicy.d.ts +5 -0
  74. package/dist/runtime/execution/policies/runCompletionPolicy.d.ts.map +1 -0
  75. package/dist/runtime/execution/policies/runCompletionPolicy.js +30 -0
  76. package/dist/runtime/execution/policies/runCompletionPolicy.js.map +1 -0
  77. package/dist/runtime/execution/policies/runErrorPolicy.d.ts +7 -0
  78. package/dist/runtime/execution/policies/runErrorPolicy.d.ts.map +1 -0
  79. package/dist/runtime/execution/policies/runErrorPolicy.js +40 -0
  80. package/dist/runtime/execution/policies/runErrorPolicy.js.map +1 -0
  81. package/dist/runtime/execution/policies/runPromptBuilder.d.ts +10 -0
  82. package/dist/runtime/execution/policies/runPromptBuilder.d.ts.map +1 -0
  83. package/dist/runtime/execution/policies/runPromptBuilder.js +34 -0
  84. package/dist/runtime/execution/policies/runPromptBuilder.js.map +1 -0
  85. package/dist/runtime/execution/policies/runSummaryFormatter.d.ts +6 -0
  86. package/dist/runtime/execution/policies/runSummaryFormatter.d.ts.map +1 -0
  87. package/dist/runtime/execution/policies/runSummaryFormatter.js +47 -0
  88. package/dist/runtime/execution/policies/runSummaryFormatter.js.map +1 -0
  89. package/dist/runtime/execution/policies/runtimeDelay.d.ts +4 -0
  90. package/dist/runtime/execution/policies/runtimeDelay.d.ts.map +1 -0
  91. package/dist/runtime/execution/policies/runtimeDelay.js +6 -0
  92. package/dist/runtime/execution/policies/runtimeDelay.js.map +1 -0
  93. package/dist/runtime/execution/policies/toolConfigReader.d.ts +8 -0
  94. package/dist/runtime/execution/policies/toolConfigReader.d.ts.map +1 -0
  95. package/dist/runtime/execution/policies/toolConfigReader.js +91 -0
  96. package/dist/runtime/execution/policies/toolConfigReader.js.map +1 -0
  97. package/dist/runtime/human-loop/consoleHumanLoop.d.ts +21 -0
  98. package/dist/runtime/human-loop/consoleHumanLoop.d.ts.map +1 -0
  99. package/dist/runtime/human-loop/consoleHumanLoop.js +102 -0
  100. package/dist/runtime/human-loop/consoleHumanLoop.js.map +1 -0
  101. package/dist/runtime/human-loop/humanLoop.d.ts +44 -0
  102. package/dist/runtime/human-loop/humanLoop.d.ts.map +1 -0
  103. package/dist/runtime/human-loop/humanLoop.js +36 -0
  104. package/dist/runtime/human-loop/humanLoop.js.map +1 -0
  105. package/dist/runtime/index.d.ts +10 -0
  106. package/dist/runtime/index.d.ts.map +1 -0
  107. package/dist/runtime/index.js +6 -0
  108. package/dist/runtime/index.js.map +1 -0
  109. package/dist/runtime/middleware/agentToolMiddleware.d.ts +30 -0
  110. package/dist/runtime/middleware/agentToolMiddleware.d.ts.map +1 -0
  111. package/dist/runtime/middleware/agentToolMiddleware.js +259 -0
  112. package/dist/runtime/middleware/agentToolMiddleware.js.map +1 -0
  113. package/dist/runtime/middleware/commandPolicy.d.ts +8 -0
  114. package/dist/runtime/middleware/commandPolicy.d.ts.map +1 -0
  115. package/dist/runtime/middleware/commandPolicy.js +55 -0
  116. package/dist/runtime/middleware/commandPolicy.js.map +1 -0
  117. package/dist/runtime/middleware/frameworkPrompt.d.ts +4 -0
  118. package/dist/runtime/middleware/frameworkPrompt.d.ts.map +1 -0
  119. package/dist/runtime/middleware/frameworkPrompt.js +44 -0
  120. package/dist/runtime/middleware/frameworkPrompt.js.map +1 -0
  121. package/dist/runtime/middleware/index.d.ts +4 -0
  122. package/dist/runtime/middleware/index.d.ts.map +1 -0
  123. package/dist/runtime/middleware/index.js +3 -0
  124. package/dist/runtime/middleware/index.js.map +1 -0
  125. package/dist/runtime/middleware/skillPathMap.d.ts +3 -0
  126. package/dist/runtime/middleware/skillPathMap.d.ts.map +1 -0
  127. package/dist/runtime/middleware/skillPathMap.js +44 -0
  128. package/dist/runtime/middleware/skillPathMap.js.map +1 -0
  129. package/dist/runtime/middleware/toolArgsNormalizer.d.ts +21 -0
  130. package/dist/runtime/middleware/toolArgsNormalizer.d.ts.map +1 -0
  131. package/dist/runtime/middleware/toolArgsNormalizer.js +227 -0
  132. package/dist/runtime/middleware/toolArgsNormalizer.js.map +1 -0
  133. package/dist/runtime/middleware/toolCallGuard.d.ts +8 -0
  134. package/dist/runtime/middleware/toolCallGuard.d.ts.map +1 -0
  135. package/dist/runtime/middleware/toolCallGuard.js +60 -0
  136. package/dist/runtime/middleware/toolCallGuard.js.map +1 -0
  137. package/dist/runtime/middleware/toolFsHandlers.d.ts +12 -0
  138. package/dist/runtime/middleware/toolFsHandlers.d.ts.map +1 -0
  139. package/dist/runtime/middleware/toolFsHandlers.js +79 -0
  140. package/dist/runtime/middleware/toolFsHandlers.js.map +1 -0
  141. package/dist/runtime/middleware/toolRequestParser.d.ts +14 -0
  142. package/dist/runtime/middleware/toolRequestParser.d.ts.map +1 -0
  143. package/dist/runtime/middleware/toolRequestParser.js +72 -0
  144. package/dist/runtime/middleware/toolRequestParser.js.map +1 -0
  145. package/dist/runtime/middleware/types.d.ts +87 -0
  146. package/dist/runtime/middleware/types.d.ts.map +1 -0
  147. package/dist/runtime/middleware/types.js +3 -0
  148. package/dist/runtime/middleware/types.js.map +1 -0
  149. package/dist/runtime/providers/backendResolver.d.ts +26 -0
  150. package/dist/runtime/providers/backendResolver.d.ts.map +1 -0
  151. package/dist/runtime/providers/backendResolver.js +73 -0
  152. package/dist/runtime/providers/backendResolver.js.map +1 -0
  153. package/dist/runtime/providers/index.d.ts +14 -0
  154. package/dist/runtime/providers/index.d.ts.map +1 -0
  155. package/dist/runtime/providers/index.js +10 -0
  156. package/dist/runtime/providers/index.js.map +1 -0
  157. package/dist/runtime/providers/langchainRuntime.d.ts +11 -0
  158. package/dist/runtime/providers/langchainRuntime.d.ts.map +1 -0
  159. package/dist/runtime/providers/langchainRuntime.js +45 -0
  160. package/dist/runtime/providers/langchainRuntime.js.map +1 -0
  161. package/dist/runtime/providers/localShellBackend.d.ts +10 -0
  162. package/dist/runtime/providers/localShellBackend.d.ts.map +1 -0
  163. package/dist/runtime/providers/localShellBackend.js +23 -0
  164. package/dist/runtime/providers/localShellBackend.js.map +1 -0
  165. package/dist/runtime/providers/openaiDeepAgent.d.ts +9 -0
  166. package/dist/runtime/providers/openaiDeepAgent.d.ts.map +1 -0
  167. package/dist/runtime/providers/openaiDeepAgent.js +7 -0
  168. package/dist/runtime/providers/openaiDeepAgent.js.map +1 -0
  169. package/dist/runtime/providers/openaiLocalAgent.d.ts +17 -0
  170. package/dist/runtime/providers/openaiLocalAgent.d.ts.map +1 -0
  171. package/dist/runtime/providers/openaiLocalAgent.js +26 -0
  172. package/dist/runtime/providers/openaiLocalAgent.js.map +1 -0
  173. package/dist/runtime/providers/openaiLocalAgentFactory.d.ts +36 -0
  174. package/dist/runtime/providers/openaiLocalAgentFactory.d.ts.map +1 -0
  175. package/dist/runtime/providers/openaiLocalAgentFactory.js +139 -0
  176. package/dist/runtime/providers/openaiLocalAgentFactory.js.map +1 -0
  177. package/dist/runtime/providers/shared/agentProbe.d.ts +4 -0
  178. package/dist/runtime/providers/shared/agentProbe.d.ts.map +1 -0
  179. package/dist/runtime/providers/shared/agentProbe.js +17 -0
  180. package/dist/runtime/providers/shared/agentProbe.js.map +1 -0
  181. package/dist/runtime/providers/shared/moduleFallback.d.ts +2 -0
  182. package/dist/runtime/providers/shared/moduleFallback.d.ts.map +1 -0
  183. package/dist/runtime/providers/shared/moduleFallback.js +34 -0
  184. package/dist/runtime/providers/shared/moduleFallback.js.map +1 -0
  185. package/dist/runtime/providers/shared/resourceCloser.d.ts +6 -0
  186. package/dist/runtime/providers/shared/resourceCloser.d.ts.map +1 -0
  187. package/dist/runtime/providers/shared/resourceCloser.js +30 -0
  188. package/dist/runtime/providers/shared/resourceCloser.js.map +1 -0
  189. package/dist/runtime/runtimeService.d.ts +109 -0
  190. package/dist/runtime/runtimeService.d.ts.map +1 -0
  191. package/dist/runtime/runtimeService.js +90 -0
  192. package/dist/runtime/runtimeService.js.map +1 -0
  193. package/dist/runtime/stream/agentMessages.d.ts +40 -0
  194. package/dist/runtime/stream/agentMessages.d.ts.map +1 -0
  195. package/dist/runtime/stream/agentMessages.js +44 -0
  196. package/dist/runtime/stream/agentMessages.js.map +1 -0
  197. package/dist/runtime/stream/agentStream.d.ts +31 -0
  198. package/dist/runtime/stream/agentStream.d.ts.map +1 -0
  199. package/dist/runtime/stream/agentStream.js +106 -0
  200. package/dist/runtime/stream/agentStream.js.map +1 -0
  201. package/dist/runtime/stream/messageSummary.d.ts +20 -0
  202. package/dist/runtime/stream/messageSummary.d.ts.map +1 -0
  203. package/dist/runtime/stream/messageSummary.js +150 -0
  204. package/dist/runtime/stream/messageSummary.js.map +1 -0
  205. package/dist/runtime/stream/runArtifacts.d.ts +9 -0
  206. package/dist/runtime/stream/runArtifacts.d.ts.map +1 -0
  207. package/dist/runtime/stream/runArtifacts.js +48 -0
  208. package/dist/runtime/stream/runArtifacts.js.map +1 -0
  209. package/dist/state/index.d.ts +7 -0
  210. package/dist/state/index.d.ts.map +1 -0
  211. package/dist/state/index.js +4 -0
  212. package/dist/state/index.js.map +1 -0
  213. package/dist/state/runState.d.ts +36 -0
  214. package/dist/state/runState.d.ts.map +1 -0
  215. package/dist/state/runState.js +51 -0
  216. package/dist/state/runState.js.map +1 -0
  217. package/dist/state/todos.d.ts +19 -0
  218. package/dist/state/todos.d.ts.map +1 -0
  219. package/dist/state/todos.js +45 -0
  220. package/dist/state/todos.js.map +1 -0
  221. package/dist/state/workspaceRunSession.d.ts +32 -0
  222. package/dist/state/workspaceRunSession.d.ts.map +1 -0
  223. package/dist/state/workspaceRunSession.js +67 -0
  224. package/dist/state/workspaceRunSession.js.map +1 -0
  225. package/dist/state/workspaceState.d.ts +94 -0
  226. package/dist/state/workspaceState.d.ts.map +1 -0
  227. package/dist/state/workspaceState.js +119 -0
  228. package/dist/state/workspaceState.js.map +1 -0
  229. package/dist/types.d.ts +2 -0
  230. package/dist/types.d.ts.map +1 -0
  231. package/dist/types.js +2 -0
  232. package/dist/types.js.map +1 -0
  233. package/example/.tsbuildinfo +1 -0
  234. package/example/build/.tsbuildinfo +1 -0
  235. package/example/config/memory.yaml +35 -0
  236. package/example/config/model.yaml +14 -0
  237. package/example/config/runtime.yaml +46 -0
  238. package/example/config/tool.yaml +24 -0
  239. package/example/index.ts +159 -0
  240. package/example/package-lock.json +1396 -0
  241. package/example/package.json +26 -0
  242. package/example/serve-output.mjs +52 -0
  243. package/example/tsconfig.json +19 -0
  244. package/package.json +73 -0
  245. package/scripts/resolve-deps.js +40 -0
  246. package/src/config/index.ts +29 -0
  247. package/src/config/loader.ts +174 -0
  248. package/src/config/resolveRuntimeConfig.ts +160 -0
  249. package/src/config/resources.ts +152 -0
  250. package/src/config/runtimeConfigLoader.ts +68 -0
  251. package/src/external-modules.d.ts +20 -0
  252. package/src/index.ts +36 -0
  253. package/src/runtime/agentMiddleware.ts +6 -0
  254. package/src/runtime/bootstrap/runtimeFactory.ts +212 -0
  255. package/src/runtime/bootstrap/runtimeModuleInitializer.ts +34 -0
  256. package/src/runtime/context/agentContext.ts +24 -0
  257. package/src/runtime/context/globalContext.ts +70 -0
  258. package/src/runtime/events/eventPublisher.ts +45 -0
  259. package/src/runtime/execution/agentRunExecutor.ts +499 -0
  260. package/src/runtime/execution/agentRunExecutor.types.ts +39 -0
  261. package/src/runtime/execution/agentRunProgress.ts +67 -0
  262. package/src/runtime/execution/agentRunState.ts +66 -0
  263. package/src/runtime/execution/policies/runCompletionPolicy.ts +30 -0
  264. package/src/runtime/execution/policies/runErrorPolicy.ts +50 -0
  265. package/src/runtime/execution/policies/runPromptBuilder.ts +39 -0
  266. package/src/runtime/execution/policies/runSummaryFormatter.ts +57 -0
  267. package/src/runtime/execution/policies/runtimeDelay.ts +5 -0
  268. package/src/runtime/execution/policies/toolConfigReader.ts +107 -0
  269. package/src/runtime/human-loop/consoleHumanLoop.ts +113 -0
  270. package/src/runtime/human-loop/humanLoop.ts +90 -0
  271. package/src/runtime/index.ts +36 -0
  272. package/src/runtime/middleware/agentToolMiddleware.ts +329 -0
  273. package/src/runtime/middleware/commandPolicy.ts +65 -0
  274. package/src/runtime/middleware/frameworkPrompt.ts +51 -0
  275. package/src/runtime/middleware/index.ts +7 -0
  276. package/src/runtime/middleware/skillPathMap.ts +49 -0
  277. package/src/runtime/middleware/toolArgsNormalizer.ts +277 -0
  278. package/src/runtime/middleware/toolCallGuard.ts +73 -0
  279. package/src/runtime/middleware/toolFsHandlers.ts +94 -0
  280. package/src/runtime/middleware/toolRequestParser.ts +84 -0
  281. package/src/runtime/middleware/types.ts +91 -0
  282. package/src/runtime/providers/backendResolver.ts +127 -0
  283. package/src/runtime/providers/index.ts +19 -0
  284. package/src/runtime/providers/langchainRuntime.ts +67 -0
  285. package/src/runtime/providers/localShellBackend.ts +54 -0
  286. package/src/runtime/providers/openaiDeepAgent.ts +17 -0
  287. package/src/runtime/providers/openaiLocalAgent.ts +35 -0
  288. package/src/runtime/providers/openaiLocalAgentFactory.ts +185 -0
  289. package/src/runtime/providers/shared/agentProbe.ts +24 -0
  290. package/src/runtime/providers/shared/moduleFallback.ts +41 -0
  291. package/src/runtime/providers/shared/resourceCloser.ts +30 -0
  292. package/src/runtime/runtimeService.ts +205 -0
  293. package/src/runtime/stream/agentMessages.ts +72 -0
  294. package/src/runtime/stream/agentStream.ts +168 -0
  295. package/src/runtime/stream/messageSummary.ts +164 -0
  296. package/src/runtime/stream/runArtifacts.ts +72 -0
  297. package/src/state/index.ts +19 -0
  298. package/src/state/runState.ts +84 -0
  299. package/src/state/todos.ts +59 -0
  300. package/src/state/workspaceRunSession.ts +96 -0
  301. package/src/state/workspaceState.ts +199 -0
  302. package/src/types/easynet-agent-common.d.ts +62 -0
  303. package/src/types.ts +1 -0
  304. package/test/integration/runtime-live.integration.test.ts +30 -0
  305. package/test/unit/config/loader.test.ts +429 -0
  306. package/test/unit/runtime/agentContext.test.ts +23 -0
  307. package/test/unit/runtime/agentRuntime.test.ts +99 -0
  308. package/test/unit/runtime/consoleHumanLoop.test.ts +52 -0
  309. package/test/unit/runtime/events.test.ts +11 -0
  310. package/test/unit/runtime/globalContext.test.ts +34 -0
  311. package/test/unit/runtime/humanLoop.test.ts +104 -0
  312. package/test/unit/runtime/toolArgsNormalizer.test.ts +136 -0
  313. package/tsconfig.json +18 -0
@@ -0,0 +1,277 @@
1
+ import { existsSync } from "node:fs";
2
+ import { basename, dirname, relative, resolve } from "node:path";
3
+ import { TOOL_PATH_ARG_KEYS, type ToolArgs } from "./types.js";
4
+
5
+ export class ToolArgsNormalizer {
6
+ public static normalizeToolArgs(
7
+ rootDir: string,
8
+ skillRoots: string[],
9
+ name: string,
10
+ args: ToolArgs,
11
+ skillPathMap?: Record<string, string>,
12
+ ): ToolArgs {
13
+ let nextArgs: ToolArgs = this.expandPlaceholders(rootDir, skillRoots, args, skillPathMap);
14
+ nextArgs = this.normalizePathArgs(rootDir, nextArgs);
15
+
16
+ if (name === "execute" && typeof nextArgs.command === "string") {
17
+ nextArgs = {
18
+ ...nextArgs,
19
+ command: this.normalizeExecuteCommand(rootDir, skillRoots, nextArgs.command, skillPathMap),
20
+ };
21
+ }
22
+ if (this.usesFilePathAlias(name, args)) {
23
+ nextArgs = { ...nextArgs, file_path: args.path };
24
+ }
25
+ if (name === "write_todos") {
26
+ nextArgs = this.normalizeWriteTodosArgs(args, nextArgs);
27
+ }
28
+
29
+ return nextArgs;
30
+ }
31
+
32
+ private static usesFilePathAlias(name: string, args: ToolArgs): boolean {
33
+ return (name === "read_file" || name === "write_file" || name === "edit_file") &&
34
+ typeof args.path === "string" &&
35
+ !("file_path" in args);
36
+ }
37
+
38
+ private static normalizeExecuteCommand(
39
+ rootDir: string,
40
+ skillRoots: string[],
41
+ command: string,
42
+ skillPathMap?: Record<string, string>,
43
+ ): string {
44
+ const trimmed = this.expandStringPlaceholders(rootDir, skillRoots, command, skillPathMap).trim();
45
+ const normalized = /^\.\/(Users|home|usr|opt)\//.test(trimmed)
46
+ ? `/${trimmed.slice(2)}`
47
+ : trimmed;
48
+ if (skillRoots.length === 0) {
49
+ return normalized;
50
+ }
51
+
52
+ const parts = this.tokenizeCommand(normalized);
53
+ if (parts.length < 2) {
54
+ return normalized;
55
+ }
56
+
57
+ let changed = false;
58
+ const rewritten = parts.map((part, index) => {
59
+ if (index === 0) return part;
60
+ const nextPart = this.resolveSkillRelativeCommandArg(rootDir, skillRoots, part);
61
+ if (nextPart !== part) {
62
+ changed = true;
63
+ }
64
+ return nextPart;
65
+ });
66
+
67
+ return changed ? rewritten.map((part) => this.quoteCommandPart(part)).join(" ") : normalized;
68
+ }
69
+
70
+ private static normalizePathArgs(rootDir: string, args: ToolArgs): ToolArgs {
71
+ let nextArgs: ToolArgs = { ...args };
72
+ for (const key of TOOL_PATH_ARG_KEYS) {
73
+ const value = nextArgs[key];
74
+ if (!this.shouldNormalizePath(value)) {
75
+ continue;
76
+ }
77
+ if (this.isResolvableAbsolutePath(rootDir, value)) {
78
+ continue;
79
+ }
80
+ const stripped = value.replace(/^\/+/, "");
81
+ if (!stripped.startsWith("usr/") && !stripped.startsWith("etc/") && !stripped.startsWith("var/")) {
82
+ nextArgs = { ...nextArgs, [key]: stripped };
83
+ }
84
+ }
85
+ return nextArgs;
86
+ }
87
+
88
+ private static expandPlaceholders(
89
+ rootDir: string,
90
+ skillRoots: string[],
91
+ args: ToolArgs,
92
+ skillPathMap?: Record<string, string>,
93
+ ): ToolArgs {
94
+ let nextArgs: ToolArgs = { ...args };
95
+ for (const [key, value] of Object.entries(nextArgs)) {
96
+ if (typeof value === "string") {
97
+ nextArgs = {
98
+ ...nextArgs,
99
+ [key]: this.expandStringPlaceholders(rootDir, skillRoots, value, skillPathMap),
100
+ };
101
+ }
102
+ }
103
+ return nextArgs;
104
+ }
105
+
106
+ private static expandStringPlaceholders(
107
+ rootDir: string,
108
+ skillRoots: string[],
109
+ value: string,
110
+ skillPathMap?: Record<string, string>,
111
+ ): string {
112
+ let nextValue = value.replaceAll("${WORKSPACE}", rootDir.replace(/\\/g, "/"));
113
+ nextValue = nextValue.replace(/\$\{SKILL_PATH(?::([^}]+))?\}/g, (_match, skillName: string | undefined) => {
114
+ const resolved = this.resolveSkillPathPlaceholder(skillRoots, skillName, skillPathMap);
115
+ return resolved ?? _match;
116
+ });
117
+ return nextValue;
118
+ }
119
+
120
+ private static resolveSkillPathPlaceholder(
121
+ skillRoots: string[],
122
+ skillName: string | undefined,
123
+ skillPathMap?: Record<string, string>,
124
+ ): string | null {
125
+ if (skillName && skillPathMap?.[skillName]) {
126
+ return skillPathMap[skillName]!;
127
+ }
128
+ const normalizedRoots = skillRoots.map((root) => root.replace(/\\/g, "/"));
129
+ if (skillName) {
130
+ const matches = normalizedRoots.filter((root) => basename(root) === skillName);
131
+ return matches.length === 1 ? matches[0]! : null;
132
+ }
133
+ return normalizedRoots.length === 1 ? normalizedRoots[0]! : null;
134
+ }
135
+
136
+ private static shouldNormalizePath(value: unknown): value is string {
137
+ return typeof value === "string" && value.startsWith("/") && !value.startsWith("//");
138
+ }
139
+
140
+ private static isResolvableAbsolutePath(rootDir: string, value: string): boolean {
141
+ return value === rootDir ||
142
+ value.startsWith(`${rootDir}/`) ||
143
+ existsSync(value) ||
144
+ existsSync(dirname(value));
145
+ }
146
+
147
+ private static tokenizeCommand(command: string): string[] {
148
+ const parts: string[] = [];
149
+ let current = "";
150
+ let quote: "'" | "\"" | null = null;
151
+ let escaped = false;
152
+
153
+ for (const char of command) {
154
+ if (escaped) {
155
+ current += char;
156
+ escaped = false;
157
+ continue;
158
+ }
159
+ if (char === "\\") {
160
+ escaped = true;
161
+ continue;
162
+ }
163
+ if (quote) {
164
+ if (char === quote) {
165
+ quote = null;
166
+ } else {
167
+ current += char;
168
+ }
169
+ continue;
170
+ }
171
+ if (char === "'" || char === "\"") {
172
+ quote = char;
173
+ continue;
174
+ }
175
+ if (/\s/.test(char)) {
176
+ if (current) {
177
+ parts.push(current);
178
+ current = "";
179
+ }
180
+ continue;
181
+ }
182
+ current += char;
183
+ }
184
+
185
+ if (current) {
186
+ parts.push(current);
187
+ }
188
+ return parts;
189
+ }
190
+
191
+ private static resolveSkillRelativeCommandArg(rootDir: string, skillRoots: string[], value: string): string {
192
+ if (!this.looksLikeRelativePath(value) || value.startsWith("./") || value.startsWith("../")) {
193
+ return value;
194
+ }
195
+ if (existsSync(resolve(rootDir, value))) {
196
+ return value;
197
+ }
198
+
199
+ const matches = skillRoots
200
+ .map((skillRoot) => resolve(skillRoot, value))
201
+ .filter((candidate) => existsSync(candidate));
202
+ if (matches.length !== 1) {
203
+ return value;
204
+ }
205
+
206
+ const rel = relative(rootDir, matches[0]!).replace(/\\/g, "/");
207
+ return rel && !rel.startsWith("..") ? rel : matches[0]!.replace(/\\/g, "/");
208
+ }
209
+
210
+ private static looksLikeRelativePath(value: string): boolean {
211
+ return value.includes("/") && !value.startsWith("/") && !value.startsWith("~") && !value.startsWith("-");
212
+ }
213
+
214
+ private static quoteCommandPart(value: string): string {
215
+ return /^[A-Za-z0-9_./:@=-]+$/.test(value)
216
+ ? value
217
+ : `'${value.replace(/'/g, `'\\''`)}'`;
218
+ }
219
+
220
+ private static normalizeWriteTodosArgs(args: ToolArgs, nextArgs: ToolArgs): ToolArgs {
221
+ if (!Array.isArray(args.todos)) {
222
+ const text = this.extractTodoText(args);
223
+ if (!text) {
224
+ return nextArgs;
225
+ }
226
+ return {
227
+ ...nextArgs,
228
+ todos: [{
229
+ content: text,
230
+ status: args.status === "in_progress" || args.status === "completed" ? args.status : "pending",
231
+ }],
232
+ };
233
+ }
234
+
235
+ return {
236
+ ...nextArgs,
237
+ todos: args.todos.map((todo) => this.normalizeTodo(todo)),
238
+ };
239
+ }
240
+
241
+ private static extractTodoText(args: ToolArgs): string | null {
242
+ if (typeof args.description === "string") {
243
+ return args.description;
244
+ }
245
+ if (typeof args.content === "string") {
246
+ return args.content;
247
+ }
248
+ return null;
249
+ }
250
+
251
+ private static normalizeTodo(todo: unknown): { content: string; status: "pending" | "in_progress" | "completed" } {
252
+ if (typeof todo === "string") {
253
+ return { content: todo, status: "pending" };
254
+ }
255
+ if (!todo || typeof todo !== "object") {
256
+ return { content: JSON.stringify(todo), status: "pending" };
257
+ }
258
+
259
+ const candidate = todo as { content?: unknown; description?: unknown; status?: unknown };
260
+ const content = typeof candidate.content === "string"
261
+ ? candidate.content
262
+ : typeof candidate.description === "string"
263
+ ? candidate.description
264
+ : JSON.stringify(todo);
265
+ const status = candidate.status === "in_progress" || candidate.status === "completed"
266
+ ? candidate.status
267
+ : "pending";
268
+ return { content, status };
269
+ }
270
+
271
+ public static resolveScriptSourcePath(rootDir: string, value: string): string {
272
+ const normalized = value.replace(/\\/g, "/");
273
+ return normalized.startsWith("/")
274
+ ? normalized
275
+ : resolve(rootDir, normalized).replace(/\\/g, "/");
276
+ }
277
+ }
@@ -0,0 +1,73 @@
1
+ import { CommandPolicy } from "./commandPolicy.js";
2
+ import { ToolArgsNormalizer } from "./toolArgsNormalizer.js";
3
+ import { TOOL_PATH_ARG_KEYS, type ToolArgs, type ToolMessageConstructorLike, type ToolMessageLike } from "./types.js";
4
+
5
+ export class ToolCallGuard {
6
+ public static maybeBlock(
7
+ name: string,
8
+ args: ToolArgs,
9
+ toolCallId: string | undefined,
10
+ ToolMessage: ToolMessageConstructorLike,
11
+ rootDir: string,
12
+ allowedToolNames?: string[],
13
+ allowedPathPrefixes: string[] = [],
14
+ ): ToolMessageLike | null {
15
+ if (allowedToolNames?.length && !this.isToolAllowed(name, allowedToolNames)) {
16
+ return new ToolMessage({
17
+ content: `Error: Tool "${name}" is not allowed by tool config allowlist.`,
18
+ tool_call_id: toolCallId ?? name,
19
+ });
20
+ }
21
+ if ((name === "read_file" || name === "edit_file") && this.isScriptSourcePath(args, rootDir)) {
22
+ return new ToolMessage({
23
+ content: "Error: Do not inspect script source files unless the user explicitly asks for script code.",
24
+ tool_call_id: toolCallId ?? name,
25
+ });
26
+ }
27
+ if (this.hasAbsolutePathArgOutsideWorkspace(args, rootDir, allowedPathPrefixes)) {
28
+ return new ToolMessage({
29
+ content: "Error: Do not use absolute filesystem paths outside workspace root. Use workspace-relative paths only.",
30
+ tool_call_id: toolCallId ?? name,
31
+ });
32
+ }
33
+ if (name === "execute" && typeof args.command === "string") {
34
+ return CommandPolicy.maybeBlockExecuteCommand(args.command, toolCallId, ToolMessage, rootDir, allowedPathPrefixes);
35
+ }
36
+ return null;
37
+ }
38
+
39
+ private static isToolAllowed(name: string, allowedToolNames: string[]): boolean {
40
+ if (allowedToolNames.includes(name)) return true;
41
+ return allowedToolNames.some((allowed) => {
42
+ const normalized = allowed.trim();
43
+ return Boolean(normalized && !normalized.includes(".") && name.endsWith(`.${normalized}`));
44
+ });
45
+ }
46
+
47
+ private static hasAbsolutePathArgOutsideWorkspace(
48
+ args: ToolArgs,
49
+ rootDir: string,
50
+ allowedPathPrefixes: string[],
51
+ ): boolean {
52
+ const normalizedRoot = rootDir.replace(/\\/g, "/");
53
+ const normalizedAllowed = allowedPathPrefixes.map((entry) => entry.replace(/\\/g, "/"));
54
+ return TOOL_PATH_ARG_KEYS.some((key) => {
55
+ const value = args[key];
56
+ if (typeof value !== "string") return false;
57
+ const path = value.replace(/\\/g, "/");
58
+ return path.startsWith("/") &&
59
+ !path.startsWith("//") &&
60
+ path !== normalizedRoot &&
61
+ !path.startsWith(`${normalizedRoot}/`) &&
62
+ !normalizedAllowed.some((prefix) => path === prefix || path.startsWith(`${prefix}/`));
63
+ });
64
+ }
65
+
66
+ private static isScriptSourcePath(args: ToolArgs, rootDir: string): boolean {
67
+ return TOOL_PATH_ARG_KEYS.some((key) => {
68
+ const value = args[key];
69
+ return typeof value === "string" &&
70
+ ToolArgsNormalizer.resolveScriptSourcePath(rootDir, value).includes("/scripts/");
71
+ });
72
+ }
73
+ }
@@ -0,0 +1,94 @@
1
+ import { lstat, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname, resolve } from "node:path";
3
+ import { MAX_LS_ENTRIES, type ToolArgs } from "./types.js";
4
+
5
+ export class ToolFsHandlers {
6
+ public static async expandEditFileContent(rootDir: string, args: ToolArgs): Promise<ToolArgs> {
7
+ if (
8
+ typeof args.content !== "string" ||
9
+ !(args.file_path ?? args.path) ||
10
+ "old_string" in args
11
+ ) {
12
+ return args;
13
+ }
14
+
15
+ const relPath = String(args.file_path ?? args.path);
16
+ const absPath = resolve(rootDir, relPath);
17
+ const oldContent = await readFile(absPath, "utf8").catch(() => "");
18
+ return { ...args, file_path: relPath, old_string: oldContent, new_string: args.content };
19
+ }
20
+
21
+ public static async handleWriteFile(
22
+ rootDir: string,
23
+ args: ToolArgs,
24
+ logger: (message: string) => void,
25
+ ): Promise<{ content: string } | null> {
26
+ const targetPath = args.file_path ?? args.path;
27
+ if (typeof targetPath !== "string") {
28
+ return null;
29
+ }
30
+
31
+ const absPath = targetPath.startsWith("/") ? targetPath : resolve(rootDir, targetPath);
32
+ if (absPath !== rootDir && !absPath.startsWith(`${rootDir}/`)) {
33
+ return null;
34
+ }
35
+
36
+ const content = String(args.content);
37
+ if (/\.json$/i.test(absPath)) {
38
+ try {
39
+ JSON.parse(content);
40
+ } catch (error) {
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ return { content: `Error: invalid JSON for '${targetPath}': ${message}` };
43
+ }
44
+ }
45
+
46
+ await mkdir(dirname(absPath), { recursive: true });
47
+ await writeFile(absPath, content, "utf8");
48
+ logger(`[tool] write_file: ${targetPath} (${content.length} chars)`);
49
+ logger("[tool] write_file done in 0ms");
50
+ return { content: `Successfully wrote to '${targetPath}'` };
51
+ }
52
+
53
+ public static async handleListFiles(rootDir: string, args: ToolArgs): Promise<{ content: string } | null> {
54
+ const requestedPath = typeof args.path === "string" && args.path.trim() ? args.path.trim() : ".";
55
+ const absPath = requestedPath.startsWith("/") ? requestedPath : resolve(rootDir, requestedPath);
56
+ if (absPath !== rootDir && !absPath.startsWith(`${rootDir}/`)) {
57
+ return { content: `Error: path '${requestedPath}' is outside workspace root` };
58
+ }
59
+
60
+ const info = await lstat(absPath).catch(() => null);
61
+ if (!info) {
62
+ return { content: `Error: path '${requestedPath}' does not exist` };
63
+ }
64
+ if (!info.isDirectory()) {
65
+ const size = typeof info.size === "number" ? `${info.size} bytes` : "unknown size";
66
+ return { content: `${requestedPath === "." ? "." : requestedPath}\n[file] ${size}` };
67
+ }
68
+
69
+ const entries = await readdir(absPath, { withFileTypes: true }).catch(() => null);
70
+ if (!entries) {
71
+ return { content: `Error: unable to list '${requestedPath}'` };
72
+ }
73
+
74
+ const lines = entries
75
+ .sort((a, b) => this.compareEntries(a.isDirectory(), b.isDirectory(), a.name, b.name))
76
+ .slice(0, MAX_LS_ENTRIES)
77
+ .map((entry) => `${entry.name}${entry.isDirectory() ? "/" : entry.isSymbolicLink() ? "@" : ""}`);
78
+ const truncated = entries.length > MAX_LS_ENTRIES
79
+ ? `\n... truncated ${entries.length - MAX_LS_ENTRIES} more entries`
80
+ : "";
81
+ return {
82
+ content: `${requestedPath === "." ? "." : requestedPath}\n${lines.join("\n")}${truncated}`,
83
+ };
84
+ }
85
+
86
+ private static compareEntries(aIsDirectory: boolean, bIsDirectory: boolean, aName: string, bName: string): number {
87
+ const aRank = aIsDirectory ? 0 : 1;
88
+ const bRank = bIsDirectory ? 0 : 1;
89
+ if (aRank !== bRank) {
90
+ return aRank - bRank;
91
+ }
92
+ return aName.localeCompare(bName);
93
+ }
94
+ }
@@ -0,0 +1,84 @@
1
+ import type {
2
+ MiddlewareRequest,
3
+ ToolArgs,
4
+ ToolCallAfterHookContext,
5
+ ToolCallHookContext,
6
+ } from "./types.js";
7
+
8
+ export class ToolRequestParser {
9
+ public static toMiddlewareRequest(request: unknown): MiddlewareRequest {
10
+ if (!request || typeof request !== "object") {
11
+ throw new Error("Invalid tool call request");
12
+ }
13
+ const candidate = request as Partial<MiddlewareRequest>;
14
+ if (!candidate.toolCall || typeof candidate.toolCall.name !== "string") {
15
+ throw new Error("Invalid tool call request");
16
+ }
17
+
18
+ return {
19
+ ...candidate,
20
+ threadId: this.extractThreadId(candidate),
21
+ toolCall: {
22
+ id: candidate.toolCall.id,
23
+ name: candidate.toolCall.name,
24
+ args: candidate.toolCall.args,
25
+ },
26
+ };
27
+ }
28
+
29
+ public static toToolArgs(args: unknown): ToolArgs {
30
+ if (!args || typeof args !== "object") {
31
+ return {};
32
+ }
33
+ return args as ToolArgs;
34
+ }
35
+
36
+ public static toHookRequest(request: MiddlewareRequest): ToolCallHookContext["request"] {
37
+ return {
38
+ toolCall: {
39
+ id: request.toolCall.id,
40
+ name: request.toolCall.name,
41
+ args: request.toolCall.args,
42
+ },
43
+ };
44
+ }
45
+
46
+ public static toAfterHookRequest(request: MiddlewareRequest): ToolCallAfterHookContext["request"] {
47
+ return {
48
+ toolCall: {
49
+ id: request.toolCall.id,
50
+ name: request.toolCall.name,
51
+ args: request.toolCall.args,
52
+ },
53
+ };
54
+ }
55
+
56
+ public static toModelRequest(request: unknown): Record<string, unknown> & { systemPrompt?: string; threadId?: string } {
57
+ if (!request || typeof request !== "object") {
58
+ throw new Error("Invalid model request");
59
+ }
60
+
61
+ const candidate = request as Record<string, unknown>;
62
+ return {
63
+ ...candidate,
64
+ systemPrompt: typeof candidate.systemPrompt === "string" ? candidate.systemPrompt : undefined,
65
+ threadId: this.extractThreadId(candidate),
66
+ };
67
+ }
68
+
69
+ private static extractThreadId(candidate: Record<string, unknown>): string | undefined {
70
+ const direct = this.readThreadId(candidate.configurable);
71
+ if (direct) return direct;
72
+ const config = candidate.config;
73
+ if (config && typeof config === "object") {
74
+ return this.readThreadId((config as { configurable?: unknown }).configurable);
75
+ }
76
+ return undefined;
77
+ }
78
+
79
+ private static readThreadId(value: unknown): string | undefined {
80
+ if (!value || typeof value !== "object") return undefined;
81
+ const threadId = (value as { thread_id?: unknown }).thread_id;
82
+ return typeof threadId === "string" && threadId.trim().length > 0 ? threadId : undefined;
83
+ }
84
+ }
@@ -0,0 +1,91 @@
1
+ import type {
2
+ HumanLoopService,
3
+ HumanLoopToolPolicy,
4
+ } from "../human-loop/humanLoop.js";
5
+
6
+ export const TOOL_PATH_ARG_KEYS = ["file_path", "path"] as const;
7
+ export const MAX_LS_ENTRIES = 200;
8
+
9
+ export type ToolArgs = Record<string, unknown>;
10
+ export type AgentMiddlewareLike = unknown;
11
+ export type ToolMessageLike = unknown;
12
+
13
+ export interface RequestToolCall {
14
+ id?: string;
15
+ name: string;
16
+ args?: unknown;
17
+ }
18
+
19
+ export interface MiddlewareRequest extends Record<string, unknown> {
20
+ toolCall: RequestToolCall;
21
+ threadId?: string;
22
+ }
23
+
24
+ export interface CreateMiddlewareLike {
25
+ (definition: {
26
+ name: string;
27
+ wrapToolCall: (request: unknown, handler: (request: unknown) => unknown) => unknown;
28
+ wrapModelCall?: (request: unknown, handler: (request: unknown) => unknown) => unknown;
29
+ }): AgentMiddlewareLike;
30
+ }
31
+
32
+ export interface ToolMessageConstructorLike {
33
+ new (fields: { content: string; tool_call_id: string }): ToolMessageLike;
34
+ }
35
+
36
+ export interface ToolCallHookContext {
37
+ threadId?: string;
38
+ name: string;
39
+ args: ToolArgs;
40
+ nextArgs: ToolArgs;
41
+ request: {
42
+ toolCall: {
43
+ id?: string;
44
+ name: string;
45
+ args: unknown;
46
+ };
47
+ };
48
+ rootDir: string;
49
+ }
50
+
51
+ export interface ToolCallAfterHookContext {
52
+ threadId?: string;
53
+ name: string;
54
+ args: ToolArgs;
55
+ result: unknown;
56
+ request: {
57
+ toolCall: {
58
+ id?: string;
59
+ name: string;
60
+ args: unknown;
61
+ };
62
+ };
63
+ rootDir: string;
64
+ }
65
+
66
+ export interface CreateAgentToolMiddlewareOptions {
67
+ rootDir: string;
68
+ skillRoots?: string[];
69
+ skillPathMap?: Record<string, string>;
70
+ createMiddleware: CreateMiddlewareLike;
71
+ ToolMessage: ToolMessageConstructorLike;
72
+ events?: {
73
+ emit(event: {
74
+ name: string;
75
+ from: string;
76
+ to: string;
77
+ payload?: Record<string, unknown>;
78
+ }): void;
79
+ };
80
+ executeTimeoutMs?: number;
81
+ blockTaskTool?: boolean;
82
+ allowedToolNames?: string[];
83
+ logger?: (message: string) => void;
84
+ persistTodos?: (todos: unknown[]) => Promise<void> | void;
85
+ humanLoop?: HumanLoopService;
86
+ humanLoopToolPolicy?: HumanLoopToolPolicy;
87
+ beforeToolCall?: (
88
+ context: ToolCallHookContext
89
+ ) => Promise<{ args?: ToolArgs; result?: ToolMessageLike } | void> | { args?: ToolArgs; result?: ToolMessageLike } | void;
90
+ afterToolCall?: (context: ToolCallAfterHookContext) => Promise<void> | void;
91
+ }