@librechat/agents 3.4.6 → 3.5.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 (1027) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +0 -356
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/agents/projection.cjs +0 -9
  4. package/dist/cjs/agents/projection.cjs.map +1 -1
  5. package/dist/cjs/common/constants.cjs +0 -26
  6. package/dist/cjs/common/constants.cjs.map +1 -1
  7. package/dist/cjs/common/enum.cjs +0 -85
  8. package/dist/cjs/common/enum.cjs.map +1 -1
  9. package/dist/cjs/events.cjs +0 -14
  10. package/dist/cjs/events.cjs.map +1 -1
  11. package/dist/cjs/graphs/Graph.cjs +1 -909
  12. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  13. package/dist/cjs/graphs/MultiAgentGraph.cjs +7 -272
  14. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  15. package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs.map +1 -1
  16. package/dist/cjs/graphs/createGraph.cjs.map +1 -1
  17. package/dist/cjs/hitl/askUserQuestion.cjs +0 -53
  18. package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -1
  19. package/dist/cjs/hitl/askUserQuestions.cjs +0 -20
  20. package/dist/cjs/hitl/askUserQuestions.cjs.map +1 -1
  21. package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs +0 -6
  22. package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs.map +1 -1
  23. package/dist/cjs/hooks/HookRegistry.cjs +0 -127
  24. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  25. package/dist/cjs/hooks/createToolPolicyHook.cjs +0 -38
  26. package/dist/cjs/hooks/createToolPolicyHook.cjs.map +1 -1
  27. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +0 -77
  28. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -1
  29. package/dist/cjs/hooks/executeHooks.cjs +0 -79
  30. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  31. package/dist/cjs/hooks/index.cjs +0 -16
  32. package/dist/cjs/hooks/index.cjs.map +1 -1
  33. package/dist/cjs/hooks/matchers.cjs +0 -104
  34. package/dist/cjs/hooks/matchers.cjs.map +1 -1
  35. package/dist/cjs/hooks/types.cjs +0 -8
  36. package/dist/cjs/hooks/types.cjs.map +1 -1
  37. package/dist/cjs/instrumentation.cjs +0 -14
  38. package/dist/cjs/instrumentation.cjs.map +1 -1
  39. package/dist/cjs/langfuse.cjs +0 -75
  40. package/dist/cjs/langfuse.cjs.map +1 -1
  41. package/dist/cjs/langfuseConfig.cjs.map +1 -1
  42. package/dist/cjs/langfuseRuntimeContext.cjs +0 -13
  43. package/dist/cjs/langfuseRuntimeContext.cjs.map +1 -1
  44. package/dist/cjs/langfuseRuntimeScope.cjs +0 -4
  45. package/dist/cjs/langfuseRuntimeScope.cjs.map +1 -1
  46. package/dist/cjs/langfuseSpanRegistry.cjs +0 -28
  47. package/dist/cjs/langfuseSpanRegistry.cjs.map +1 -1
  48. package/dist/cjs/langfuseToolOutputTracing.cjs +0 -3
  49. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  50. package/dist/cjs/langfuseTraceShaping.cjs +0 -74
  51. package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
  52. package/dist/cjs/llm/anthropic/index.cjs +0 -3
  53. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  54. package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
  55. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +1 -111
  56. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  57. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +0 -1
  58. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  59. package/dist/cjs/llm/anthropic/utils/stream_events.cjs +0 -8
  60. package/dist/cjs/llm/anthropic/utils/stream_events.cjs.map +1 -1
  61. package/dist/cjs/llm/anthropic/utils/tools.cjs.map +1 -1
  62. package/dist/cjs/llm/bedrock/cachePoints.cjs.map +1 -1
  63. package/dist/cjs/llm/bedrock/index.cjs +1 -95
  64. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  65. package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
  66. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +0 -78
  67. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  68. package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +0 -24
  69. package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
  70. package/dist/cjs/llm/contextOverflowRecovery.cjs +0 -62
  71. package/dist/cjs/llm/contextOverflowRecovery.cjs.map +1 -1
  72. package/dist/cjs/llm/fake.cjs.map +1 -1
  73. package/dist/cjs/llm/google/index.cjs +2 -21
  74. package/dist/cjs/llm/google/index.cjs.map +1 -1
  75. package/dist/cjs/llm/google/utils/common.cjs +5 -35
  76. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  77. package/dist/cjs/llm/init.cjs +0 -9
  78. package/dist/cjs/llm/init.cjs.map +1 -1
  79. package/dist/cjs/llm/invoke.cjs +1 -270
  80. package/dist/cjs/llm/invoke.cjs.map +1 -1
  81. package/dist/cjs/llm/mistral/index.cjs.map +1 -1
  82. package/dist/cjs/llm/openai/index.cjs +0 -99
  83. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  84. package/dist/cjs/llm/openai/streamMetadata.cjs +0 -21
  85. package/dist/cjs/llm/openai/streamMetadata.cjs.map +1 -1
  86. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  87. package/dist/cjs/llm/openrouter/index.cjs +0 -1
  88. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  89. package/dist/cjs/llm/openrouter/toolCache.cjs +0 -6
  90. package/dist/cjs/llm/openrouter/toolCache.cjs.map +1 -1
  91. package/dist/cjs/llm/preempt.cjs +0 -75
  92. package/dist/cjs/llm/preempt.cjs.map +1 -1
  93. package/dist/cjs/llm/providers.cjs.map +1 -1
  94. package/dist/cjs/llm/request.cjs +0 -10
  95. package/dist/cjs/llm/request.cjs.map +1 -1
  96. package/dist/cjs/llm/stream/chunkAdapters.cjs +0 -62
  97. package/dist/cjs/llm/stream/chunkAdapters.cjs.map +1 -1
  98. package/dist/cjs/llm/stream/smoother.cjs +0 -58
  99. package/dist/cjs/llm/stream/smoother.cjs.map +1 -1
  100. package/dist/cjs/llm/streamLimits.cjs +4 -293
  101. package/dist/cjs/llm/streamLimits.cjs.map +1 -1
  102. package/dist/cjs/llm/truncation.cjs +0 -36
  103. package/dist/cjs/llm/truncation.cjs.map +1 -1
  104. package/dist/cjs/llm/vertexai/index.cjs +0 -330
  105. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  106. package/dist/cjs/messages/alternation.cjs +0 -43
  107. package/dist/cjs/messages/alternation.cjs.map +1 -1
  108. package/dist/cjs/messages/anthropicToolCache.cjs +0 -45
  109. package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -1
  110. package/dist/cjs/messages/assistantPhase.cjs +0 -12
  111. package/dist/cjs/messages/assistantPhase.cjs.map +1 -1
  112. package/dist/cjs/messages/budget.cjs +0 -9
  113. package/dist/cjs/messages/budget.cjs.map +1 -1
  114. package/dist/cjs/messages/cache.cjs +0 -172
  115. package/dist/cjs/messages/cache.cjs.map +1 -1
  116. package/dist/cjs/messages/content.cjs +0 -9
  117. package/dist/cjs/messages/content.cjs.map +1 -1
  118. package/dist/cjs/messages/contextPruning.cjs +0 -15
  119. package/dist/cjs/messages/contextPruning.cjs.map +1 -1
  120. package/dist/cjs/messages/contextPruningSettings.cjs +0 -3
  121. package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -1
  122. package/dist/cjs/messages/core.cjs +0 -48
  123. package/dist/cjs/messages/core.cjs.map +1 -1
  124. package/dist/cjs/messages/format.cjs +1 -345
  125. package/dist/cjs/messages/format.cjs.map +1 -1
  126. package/dist/cjs/messages/handoffCue.cjs +0 -36
  127. package/dist/cjs/messages/handoffCue.cjs.map +1 -1
  128. package/dist/cjs/messages/ids.cjs.map +1 -1
  129. package/dist/cjs/messages/injected.cjs +0 -33
  130. package/dist/cjs/messages/injected.cjs.map +1 -1
  131. package/dist/cjs/messages/langchain.cjs +0 -14
  132. package/dist/cjs/messages/langchain.cjs.map +1 -1
  133. package/dist/cjs/messages/prune.cjs +1 -184
  134. package/dist/cjs/messages/prune.cjs.map +1 -1
  135. package/dist/cjs/messages/recency.cjs +0 -32
  136. package/dist/cjs/messages/recency.cjs.map +1 -1
  137. package/dist/cjs/messages/reducer.cjs +0 -30
  138. package/dist/cjs/messages/reducer.cjs.map +1 -1
  139. package/dist/cjs/messages/tools.cjs +0 -14
  140. package/dist/cjs/messages/tools.cjs.map +1 -1
  141. package/dist/cjs/openai/index.cjs.map +1 -1
  142. package/dist/cjs/prompts/activityLabel.cjs +9 -79
  143. package/dist/cjs/prompts/activityLabel.cjs.map +1 -1
  144. package/dist/cjs/responses/index.cjs.map +1 -1
  145. package/dist/cjs/run.cjs +2 -538
  146. package/dist/cjs/run.cjs.map +1 -1
  147. package/dist/cjs/session/AgentSession.cjs +1 -17
  148. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  149. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -1
  150. package/dist/cjs/session/handlers.cjs +0 -7
  151. package/dist/cjs/session/handlers.cjs.map +1 -1
  152. package/dist/cjs/session/ids.cjs.map +1 -1
  153. package/dist/cjs/session/messageSerialization.cjs +0 -4
  154. package/dist/cjs/session/messageSerialization.cjs.map +1 -1
  155. package/dist/cjs/stream.cjs +1 -93
  156. package/dist/cjs/stream.cjs.map +1 -1
  157. package/dist/cjs/summarization/index.cjs +0 -27
  158. package/dist/cjs/summarization/index.cjs.map +1 -1
  159. package/dist/cjs/summarization/node.cjs +0 -273
  160. package/dist/cjs/summarization/node.cjs.map +1 -1
  161. package/dist/cjs/tools/BashExecutor.cjs +0 -35
  162. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  163. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +0 -24
  164. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  165. package/dist/cjs/tools/Calculator.cjs.map +1 -1
  166. package/dist/cjs/tools/CodeExecutor.cjs +0 -13
  167. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  168. package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -1
  169. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +0 -101
  170. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  171. package/dist/cjs/tools/ReadFile.cjs.map +1 -1
  172. package/dist/cjs/tools/SkillTool.cjs +0 -5
  173. package/dist/cjs/tools/SkillTool.cjs.map +1 -1
  174. package/dist/cjs/tools/SubagentTool.cjs +0 -10
  175. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  176. package/dist/cjs/tools/ToolNode.cjs +6 -1038
  177. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  178. package/dist/cjs/tools/ToolSearch.cjs +0 -179
  179. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  180. package/dist/cjs/tools/cloudflare/CloudflareBridgeRuntime.cjs.map +1 -1
  181. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
  182. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +0 -39
  183. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
  184. package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs.map +1 -1
  185. package/dist/cjs/tools/eagerEventExecution.cjs +0 -6
  186. package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -1
  187. package/dist/cjs/tools/handlers.cjs +1 -41
  188. package/dist/cjs/tools/handlers.cjs.map +1 -1
  189. package/dist/cjs/tools/intentArg.cjs +1 -132
  190. package/dist/cjs/tools/intentArg.cjs.map +1 -1
  191. package/dist/cjs/tools/local/CompileCheckTool.cjs +0 -25
  192. package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
  193. package/dist/cjs/tools/local/FileCheckpointer.cjs +0 -23
  194. package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -1
  195. package/dist/cjs/tools/local/LocalCodingTools.cjs +0 -80
  196. package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
  197. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +0 -126
  198. package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -1
  199. package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -1
  200. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +0 -12
  201. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  202. package/dist/cjs/tools/local/attachments.cjs +0 -44
  203. package/dist/cjs/tools/local/attachments.cjs.map +1 -1
  204. package/dist/cjs/tools/local/bashAst.cjs +0 -16
  205. package/dist/cjs/tools/local/bashAst.cjs.map +1 -1
  206. package/dist/cjs/tools/local/editStrategies.cjs +0 -14
  207. package/dist/cjs/tools/local/editStrategies.cjs.map +1 -1
  208. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
  209. package/dist/cjs/tools/local/syntaxCheck.cjs +0 -31
  210. package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -1
  211. package/dist/cjs/tools/local/textEncoding.cjs +0 -11
  212. package/dist/cjs/tools/local/textEncoding.cjs.map +1 -1
  213. package/dist/cjs/tools/local/workspaceFS.cjs +0 -33
  214. package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -1
  215. package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
  216. package/dist/cjs/tools/schema.cjs +0 -8
  217. package/dist/cjs/tools/schema.cjs.map +1 -1
  218. package/dist/cjs/tools/search/anthropic.cjs +0 -9
  219. package/dist/cjs/tools/search/anthropic.cjs.map +1 -1
  220. package/dist/cjs/tools/search/content.cjs.map +1 -1
  221. package/dist/cjs/tools/search/crw-scraper.cjs +0 -33
  222. package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -1
  223. package/dist/cjs/tools/search/crw-search.cjs +0 -2
  224. package/dist/cjs/tools/search/crw-search.cjs.map +1 -1
  225. package/dist/cjs/tools/search/firecrawl.cjs +0 -25
  226. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  227. package/dist/cjs/tools/search/format.cjs +0 -30
  228. package/dist/cjs/tools/search/format.cjs.map +1 -1
  229. package/dist/cjs/tools/search/highlights.cjs +0 -16
  230. package/dist/cjs/tools/search/highlights.cjs.map +1 -1
  231. package/dist/cjs/tools/search/keenable-scraper.cjs +0 -8
  232. package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -1
  233. package/dist/cjs/tools/search/keenable-search.cjs +0 -7
  234. package/dist/cjs/tools/search/keenable-search.cjs.map +1 -1
  235. package/dist/cjs/tools/search/rerankers.cjs +0 -35
  236. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  237. package/dist/cjs/tools/search/schema.cjs +0 -1
  238. package/dist/cjs/tools/search/schema.cjs.map +1 -1
  239. package/dist/cjs/tools/search/search.cjs +1 -14
  240. package/dist/cjs/tools/search/search.cjs.map +1 -1
  241. package/dist/cjs/tools/search/serper-scraper.cjs +0 -46
  242. package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
  243. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
  244. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
  245. package/dist/cjs/tools/search/tool.cjs +0 -25
  246. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  247. package/dist/cjs/tools/search/utils.cjs +0 -8
  248. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  249. package/dist/cjs/tools/skillCatalog.cjs +0 -6
  250. package/dist/cjs/tools/skillCatalog.cjs.map +1 -1
  251. package/dist/cjs/tools/streamedToolCallSeals.cjs +0 -6
  252. package/dist/cjs/tools/streamedToolCallSeals.cjs.map +1 -1
  253. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +0 -2
  254. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
  255. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +3 -213
  256. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  257. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  258. package/dist/cjs/tools/subagent/childGraphConfig.cjs +0 -4
  259. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
  260. package/dist/cjs/tools/subagent/runtimeLimits.cjs.map +1 -1
  261. package/dist/cjs/tools/toolOutputReferences.cjs +1 -322
  262. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  263. package/dist/cjs/types/hitl.cjs +0 -5
  264. package/dist/cjs/types/hitl.cjs.map +1 -1
  265. package/dist/cjs/utils/callbacks.cjs.map +1 -1
  266. package/dist/cjs/utils/errors.cjs +14 -165
  267. package/dist/cjs/utils/errors.cjs.map +1 -1
  268. package/dist/cjs/utils/events.cjs +0 -10
  269. package/dist/cjs/utils/events.cjs.map +1 -1
  270. package/dist/cjs/utils/graph.cjs.map +1 -1
  271. package/dist/cjs/utils/handlers.cjs +0 -3
  272. package/dist/cjs/utils/handlers.cjs.map +1 -1
  273. package/dist/cjs/utils/llm.cjs +0 -1
  274. package/dist/cjs/utils/llm.cjs.map +1 -1
  275. package/dist/cjs/utils/misc.cjs +0 -16
  276. package/dist/cjs/utils/misc.cjs.map +1 -1
  277. package/dist/cjs/utils/proxy.cjs +0 -26
  278. package/dist/cjs/utils/proxy.cjs.map +1 -1
  279. package/dist/cjs/utils/run.cjs +0 -6
  280. package/dist/cjs/utils/run.cjs.map +1 -1
  281. package/dist/cjs/utils/schema.cjs +0 -5
  282. package/dist/cjs/utils/schema.cjs.map +1 -1
  283. package/dist/cjs/utils/title.cjs +0 -6
  284. package/dist/cjs/utils/title.cjs.map +1 -1
  285. package/dist/cjs/utils/tokens.cjs +0 -113
  286. package/dist/cjs/utils/tokens.cjs.map +1 -1
  287. package/dist/cjs/utils/toolContent.cjs +0 -41
  288. package/dist/cjs/utils/toolContent.cjs.map +1 -1
  289. package/dist/cjs/utils/truncation.cjs +0 -61
  290. package/dist/cjs/utils/truncation.cjs.map +1 -1
  291. package/dist/esm/agents/AgentContext.mjs +0 -356
  292. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  293. package/dist/esm/agents/projection.mjs +0 -9
  294. package/dist/esm/agents/projection.mjs.map +1 -1
  295. package/dist/esm/common/constants.mjs +0 -26
  296. package/dist/esm/common/constants.mjs.map +1 -1
  297. package/dist/esm/common/enum.mjs +0 -85
  298. package/dist/esm/common/enum.mjs.map +1 -1
  299. package/dist/esm/events.mjs +0 -14
  300. package/dist/esm/events.mjs.map +1 -1
  301. package/dist/esm/graphs/Graph.mjs +1 -909
  302. package/dist/esm/graphs/Graph.mjs.map +1 -1
  303. package/dist/esm/graphs/MultiAgentGraph.mjs +7 -272
  304. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  305. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs.map +1 -1
  306. package/dist/esm/graphs/createGraph.mjs.map +1 -1
  307. package/dist/esm/hitl/askUserQuestion.mjs +0 -53
  308. package/dist/esm/hitl/askUserQuestion.mjs.map +1 -1
  309. package/dist/esm/hitl/askUserQuestions.mjs +0 -20
  310. package/dist/esm/hitl/askUserQuestions.mjs.map +1 -1
  311. package/dist/esm/hitl/askUserQuestionsInterrupt.mjs +0 -6
  312. package/dist/esm/hitl/askUserQuestionsInterrupt.mjs.map +1 -1
  313. package/dist/esm/hooks/HookRegistry.mjs +0 -127
  314. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  315. package/dist/esm/hooks/createToolPolicyHook.mjs +0 -38
  316. package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -1
  317. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +0 -77
  318. package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -1
  319. package/dist/esm/hooks/executeHooks.mjs +0 -79
  320. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  321. package/dist/esm/hooks/index.mjs +0 -16
  322. package/dist/esm/hooks/index.mjs.map +1 -1
  323. package/dist/esm/hooks/matchers.mjs +0 -104
  324. package/dist/esm/hooks/matchers.mjs.map +1 -1
  325. package/dist/esm/hooks/types.mjs +0 -8
  326. package/dist/esm/hooks/types.mjs.map +1 -1
  327. package/dist/esm/instrumentation.mjs +0 -14
  328. package/dist/esm/instrumentation.mjs.map +1 -1
  329. package/dist/esm/langfuse.mjs +0 -75
  330. package/dist/esm/langfuse.mjs.map +1 -1
  331. package/dist/esm/langfuseConfig.mjs.map +1 -1
  332. package/dist/esm/langfuseRuntimeContext.mjs +0 -13
  333. package/dist/esm/langfuseRuntimeContext.mjs.map +1 -1
  334. package/dist/esm/langfuseRuntimeScope.mjs +0 -4
  335. package/dist/esm/langfuseRuntimeScope.mjs.map +1 -1
  336. package/dist/esm/langfuseSpanRegistry.mjs +0 -28
  337. package/dist/esm/langfuseSpanRegistry.mjs.map +1 -1
  338. package/dist/esm/langfuseToolOutputTracing.mjs +0 -3
  339. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  340. package/dist/esm/langfuseTraceShaping.mjs +0 -74
  341. package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
  342. package/dist/esm/llm/anthropic/index.mjs +0 -3
  343. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  344. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  345. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +1 -111
  346. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  347. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +0 -1
  348. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  349. package/dist/esm/llm/anthropic/utils/stream_events.mjs +0 -8
  350. package/dist/esm/llm/anthropic/utils/stream_events.mjs.map +1 -1
  351. package/dist/esm/llm/anthropic/utils/tools.mjs.map +1 -1
  352. package/dist/esm/llm/bedrock/cachePoints.mjs.map +1 -1
  353. package/dist/esm/llm/bedrock/index.mjs +1 -95
  354. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  355. package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
  356. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +0 -78
  357. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  358. package/dist/esm/llm/bedrock/utils/message_outputs.mjs +0 -24
  359. package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
  360. package/dist/esm/llm/contextOverflowRecovery.mjs +0 -62
  361. package/dist/esm/llm/contextOverflowRecovery.mjs.map +1 -1
  362. package/dist/esm/llm/fake.mjs.map +1 -1
  363. package/dist/esm/llm/google/index.mjs +2 -21
  364. package/dist/esm/llm/google/index.mjs.map +1 -1
  365. package/dist/esm/llm/google/utils/common.mjs +5 -35
  366. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  367. package/dist/esm/llm/init.mjs +0 -9
  368. package/dist/esm/llm/init.mjs.map +1 -1
  369. package/dist/esm/llm/invoke.mjs +1 -270
  370. package/dist/esm/llm/invoke.mjs.map +1 -1
  371. package/dist/esm/llm/mistral/index.mjs.map +1 -1
  372. package/dist/esm/llm/openai/index.mjs +0 -99
  373. package/dist/esm/llm/openai/index.mjs.map +1 -1
  374. package/dist/esm/llm/openai/streamMetadata.mjs +0 -21
  375. package/dist/esm/llm/openai/streamMetadata.mjs.map +1 -1
  376. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  377. package/dist/esm/llm/openrouter/index.mjs +0 -1
  378. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  379. package/dist/esm/llm/openrouter/toolCache.mjs +0 -6
  380. package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -1
  381. package/dist/esm/llm/preempt.mjs +0 -75
  382. package/dist/esm/llm/preempt.mjs.map +1 -1
  383. package/dist/esm/llm/providers.mjs.map +1 -1
  384. package/dist/esm/llm/request.mjs +0 -10
  385. package/dist/esm/llm/request.mjs.map +1 -1
  386. package/dist/esm/llm/stream/chunkAdapters.mjs +0 -62
  387. package/dist/esm/llm/stream/chunkAdapters.mjs.map +1 -1
  388. package/dist/esm/llm/stream/smoother.mjs +0 -58
  389. package/dist/esm/llm/stream/smoother.mjs.map +1 -1
  390. package/dist/esm/llm/streamLimits.mjs +4 -293
  391. package/dist/esm/llm/streamLimits.mjs.map +1 -1
  392. package/dist/esm/llm/truncation.mjs +0 -36
  393. package/dist/esm/llm/truncation.mjs.map +1 -1
  394. package/dist/esm/llm/vertexai/index.mjs +0 -330
  395. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  396. package/dist/esm/messages/alternation.mjs +0 -43
  397. package/dist/esm/messages/alternation.mjs.map +1 -1
  398. package/dist/esm/messages/anthropicToolCache.mjs +0 -45
  399. package/dist/esm/messages/anthropicToolCache.mjs.map +1 -1
  400. package/dist/esm/messages/assistantPhase.mjs +0 -12
  401. package/dist/esm/messages/assistantPhase.mjs.map +1 -1
  402. package/dist/esm/messages/budget.mjs +0 -9
  403. package/dist/esm/messages/budget.mjs.map +1 -1
  404. package/dist/esm/messages/cache.mjs +0 -172
  405. package/dist/esm/messages/cache.mjs.map +1 -1
  406. package/dist/esm/messages/content.mjs +0 -9
  407. package/dist/esm/messages/content.mjs.map +1 -1
  408. package/dist/esm/messages/contextPruning.mjs +0 -15
  409. package/dist/esm/messages/contextPruning.mjs.map +1 -1
  410. package/dist/esm/messages/contextPruningSettings.mjs +0 -3
  411. package/dist/esm/messages/contextPruningSettings.mjs.map +1 -1
  412. package/dist/esm/messages/core.mjs +0 -48
  413. package/dist/esm/messages/core.mjs.map +1 -1
  414. package/dist/esm/messages/format.mjs +1 -345
  415. package/dist/esm/messages/format.mjs.map +1 -1
  416. package/dist/esm/messages/handoffCue.mjs +0 -36
  417. package/dist/esm/messages/handoffCue.mjs.map +1 -1
  418. package/dist/esm/messages/ids.mjs.map +1 -1
  419. package/dist/esm/messages/injected.mjs +0 -33
  420. package/dist/esm/messages/injected.mjs.map +1 -1
  421. package/dist/esm/messages/langchain.mjs +0 -14
  422. package/dist/esm/messages/langchain.mjs.map +1 -1
  423. package/dist/esm/messages/prune.mjs +1 -184
  424. package/dist/esm/messages/prune.mjs.map +1 -1
  425. package/dist/esm/messages/recency.mjs +0 -32
  426. package/dist/esm/messages/recency.mjs.map +1 -1
  427. package/dist/esm/messages/reducer.mjs +0 -30
  428. package/dist/esm/messages/reducer.mjs.map +1 -1
  429. package/dist/esm/messages/tools.mjs +0 -14
  430. package/dist/esm/messages/tools.mjs.map +1 -1
  431. package/dist/esm/openai/index.mjs.map +1 -1
  432. package/dist/esm/prompts/activityLabel.mjs +9 -79
  433. package/dist/esm/prompts/activityLabel.mjs.map +1 -1
  434. package/dist/esm/responses/index.mjs.map +1 -1
  435. package/dist/esm/run.mjs +2 -538
  436. package/dist/esm/run.mjs.map +1 -1
  437. package/dist/esm/session/AgentSession.mjs +1 -17
  438. package/dist/esm/session/AgentSession.mjs.map +1 -1
  439. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -1
  440. package/dist/esm/session/handlers.mjs +0 -7
  441. package/dist/esm/session/handlers.mjs.map +1 -1
  442. package/dist/esm/session/ids.mjs.map +1 -1
  443. package/dist/esm/session/messageSerialization.mjs +0 -4
  444. package/dist/esm/session/messageSerialization.mjs.map +1 -1
  445. package/dist/esm/stream.mjs +1 -93
  446. package/dist/esm/stream.mjs.map +1 -1
  447. package/dist/esm/summarization/index.mjs +0 -27
  448. package/dist/esm/summarization/index.mjs.map +1 -1
  449. package/dist/esm/summarization/node.mjs +0 -273
  450. package/dist/esm/summarization/node.mjs.map +1 -1
  451. package/dist/esm/tools/BashExecutor.mjs +0 -35
  452. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  453. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +0 -24
  454. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  455. package/dist/esm/tools/Calculator.mjs.map +1 -1
  456. package/dist/esm/tools/CodeExecutor.mjs +0 -13
  457. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  458. package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -1
  459. package/dist/esm/tools/ProgrammaticToolCalling.mjs +0 -101
  460. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  461. package/dist/esm/tools/ReadFile.mjs.map +1 -1
  462. package/dist/esm/tools/SkillTool.mjs +0 -5
  463. package/dist/esm/tools/SkillTool.mjs.map +1 -1
  464. package/dist/esm/tools/SubagentTool.mjs +0 -10
  465. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  466. package/dist/esm/tools/ToolNode.mjs +6 -1038
  467. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  468. package/dist/esm/tools/ToolSearch.mjs +0 -179
  469. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  470. package/dist/esm/tools/cloudflare/CloudflareBridgeRuntime.mjs.map +1 -1
  471. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
  472. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +0 -39
  473. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
  474. package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs.map +1 -1
  475. package/dist/esm/tools/eagerEventExecution.mjs +0 -6
  476. package/dist/esm/tools/eagerEventExecution.mjs.map +1 -1
  477. package/dist/esm/tools/handlers.mjs +1 -41
  478. package/dist/esm/tools/handlers.mjs.map +1 -1
  479. package/dist/esm/tools/intentArg.mjs +1 -132
  480. package/dist/esm/tools/intentArg.mjs.map +1 -1
  481. package/dist/esm/tools/local/CompileCheckTool.mjs +0 -25
  482. package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
  483. package/dist/esm/tools/local/FileCheckpointer.mjs +0 -23
  484. package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -1
  485. package/dist/esm/tools/local/LocalCodingTools.mjs +0 -80
  486. package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
  487. package/dist/esm/tools/local/LocalExecutionEngine.mjs +0 -126
  488. package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -1
  489. package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -1
  490. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +0 -12
  491. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  492. package/dist/esm/tools/local/attachments.mjs +0 -44
  493. package/dist/esm/tools/local/attachments.mjs.map +1 -1
  494. package/dist/esm/tools/local/bashAst.mjs +0 -16
  495. package/dist/esm/tools/local/bashAst.mjs.map +1 -1
  496. package/dist/esm/tools/local/editStrategies.mjs +0 -14
  497. package/dist/esm/tools/local/editStrategies.mjs.map +1 -1
  498. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
  499. package/dist/esm/tools/local/syntaxCheck.mjs +0 -31
  500. package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -1
  501. package/dist/esm/tools/local/textEncoding.mjs +0 -11
  502. package/dist/esm/tools/local/textEncoding.mjs.map +1 -1
  503. package/dist/esm/tools/local/workspaceFS.mjs +0 -33
  504. package/dist/esm/tools/local/workspaceFS.mjs.map +1 -1
  505. package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
  506. package/dist/esm/tools/schema.mjs +0 -8
  507. package/dist/esm/tools/schema.mjs.map +1 -1
  508. package/dist/esm/tools/search/anthropic.mjs +0 -9
  509. package/dist/esm/tools/search/anthropic.mjs.map +1 -1
  510. package/dist/esm/tools/search/content.mjs.map +1 -1
  511. package/dist/esm/tools/search/crw-scraper.mjs +0 -33
  512. package/dist/esm/tools/search/crw-scraper.mjs.map +1 -1
  513. package/dist/esm/tools/search/crw-search.mjs +0 -2
  514. package/dist/esm/tools/search/crw-search.mjs.map +1 -1
  515. package/dist/esm/tools/search/firecrawl.mjs +0 -25
  516. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  517. package/dist/esm/tools/search/format.mjs +0 -30
  518. package/dist/esm/tools/search/format.mjs.map +1 -1
  519. package/dist/esm/tools/search/highlights.mjs +0 -16
  520. package/dist/esm/tools/search/highlights.mjs.map +1 -1
  521. package/dist/esm/tools/search/keenable-scraper.mjs +0 -8
  522. package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -1
  523. package/dist/esm/tools/search/keenable-search.mjs +0 -7
  524. package/dist/esm/tools/search/keenable-search.mjs.map +1 -1
  525. package/dist/esm/tools/search/rerankers.mjs +0 -35
  526. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  527. package/dist/esm/tools/search/schema.mjs +0 -1
  528. package/dist/esm/tools/search/schema.mjs.map +1 -1
  529. package/dist/esm/tools/search/search.mjs +1 -14
  530. package/dist/esm/tools/search/search.mjs.map +1 -1
  531. package/dist/esm/tools/search/serper-scraper.mjs +0 -46
  532. package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
  533. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
  534. package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
  535. package/dist/esm/tools/search/tool.mjs +0 -25
  536. package/dist/esm/tools/search/tool.mjs.map +1 -1
  537. package/dist/esm/tools/search/utils.mjs +0 -8
  538. package/dist/esm/tools/search/utils.mjs.map +1 -1
  539. package/dist/esm/tools/skillCatalog.mjs +0 -6
  540. package/dist/esm/tools/skillCatalog.mjs.map +1 -1
  541. package/dist/esm/tools/streamedToolCallSeals.mjs +0 -6
  542. package/dist/esm/tools/streamedToolCallSeals.mjs.map +1 -1
  543. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +0 -2
  544. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
  545. package/dist/esm/tools/subagent/SubagentExecutor.mjs +3 -213
  546. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  547. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  548. package/dist/esm/tools/subagent/childGraphConfig.mjs +0 -4
  549. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
  550. package/dist/esm/tools/subagent/runtimeLimits.mjs.map +1 -1
  551. package/dist/esm/tools/toolOutputReferences.mjs +1 -322
  552. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  553. package/dist/esm/types/hitl.mjs +0 -5
  554. package/dist/esm/types/hitl.mjs.map +1 -1
  555. package/dist/esm/utils/callbacks.mjs.map +1 -1
  556. package/dist/esm/utils/errors.mjs +14 -165
  557. package/dist/esm/utils/errors.mjs.map +1 -1
  558. package/dist/esm/utils/events.mjs +0 -10
  559. package/dist/esm/utils/events.mjs.map +1 -1
  560. package/dist/esm/utils/graph.mjs.map +1 -1
  561. package/dist/esm/utils/handlers.mjs +0 -3
  562. package/dist/esm/utils/handlers.mjs.map +1 -1
  563. package/dist/esm/utils/llm.mjs +0 -1
  564. package/dist/esm/utils/llm.mjs.map +1 -1
  565. package/dist/esm/utils/misc.mjs +0 -16
  566. package/dist/esm/utils/misc.mjs.map +1 -1
  567. package/dist/esm/utils/proxy.mjs +0 -26
  568. package/dist/esm/utils/proxy.mjs.map +1 -1
  569. package/dist/esm/utils/run.mjs +0 -6
  570. package/dist/esm/utils/run.mjs.map +1 -1
  571. package/dist/esm/utils/schema.mjs +0 -5
  572. package/dist/esm/utils/schema.mjs.map +1 -1
  573. package/dist/esm/utils/title.mjs +0 -6
  574. package/dist/esm/utils/title.mjs.map +1 -1
  575. package/dist/esm/utils/tokens.mjs +0 -113
  576. package/dist/esm/utils/tokens.mjs.map +1 -1
  577. package/dist/esm/utils/toolContent.mjs +0 -41
  578. package/dist/esm/utils/toolContent.mjs.map +1 -1
  579. package/dist/esm/utils/truncation.mjs +0 -61
  580. package/dist/esm/utils/truncation.mjs.map +1 -1
  581. package/dist/types/agents/AgentContext.d.ts +3 -3
  582. package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +3 -3
  583. package/dist/types/agents/projection.d.ts +1 -1
  584. package/dist/types/events.d.ts +2 -2
  585. package/dist/types/graphs/Graph.d.ts +11 -11
  586. package/dist/types/graphs/MultiAgentGraph.d.ts +2 -2
  587. package/dist/types/graphs/createGraph.d.ts +4 -4
  588. package/dist/types/graphs/graphFactory.d.ts +1 -1
  589. package/dist/types/hitl/askUserQuestion.d.ts +1 -1
  590. package/dist/types/hitl/askUserQuestions.d.ts +1 -1
  591. package/dist/types/hitl/askUserQuestionsInterrupt.d.ts +1 -1
  592. package/dist/types/hooks/types.d.ts +1 -1
  593. package/dist/types/instrumentation.d.ts +1 -1
  594. package/dist/types/langfuse.d.ts +3 -3
  595. package/dist/types/langfuseConfig.d.ts +2 -2
  596. package/dist/types/langfuseRuntimeContext.d.ts +1 -1
  597. package/dist/types/langfuseRuntimeScope.d.ts +2 -2
  598. package/dist/types/langfuseSpanRegistry.d.ts +1 -1
  599. package/dist/types/langfuseToolOutputTracing.d.ts +3 -3
  600. package/dist/types/llm/anthropic/index.d.ts +1 -1
  601. package/dist/types/llm/bedrock/index.d.ts +1 -1
  602. package/dist/types/llm/bedrock/toolCache.d.ts +2 -2
  603. package/dist/types/llm/contextOverflowRecovery.d.ts +2 -2
  604. package/dist/types/llm/google/index.d.ts +1 -1
  605. package/dist/types/llm/init.d.ts +2 -2
  606. package/dist/types/llm/invoke.d.ts +6 -6
  607. package/dist/types/llm/mistral/index.d.ts +1 -1
  608. package/dist/types/llm/openai/index.d.ts +2 -2
  609. package/dist/types/llm/openrouter/index.d.ts +2 -2
  610. package/dist/types/llm/openrouter/toolCache.d.ts +2 -2
  611. package/dist/types/llm/providers.d.ts +2 -2
  612. package/dist/types/llm/request.d.ts +2 -2
  613. package/dist/types/llm/stream/chunkAdapters.d.ts +1 -1
  614. package/dist/types/llm/streamLimits.d.ts +1 -1
  615. package/dist/types/llm/truncation.d.ts +1 -1
  616. package/dist/types/llm/vertexai/index.d.ts +1 -1
  617. package/dist/types/messages/alternation.d.ts +1 -1
  618. package/dist/types/messages/anthropicToolCache.d.ts +2 -2
  619. package/dist/types/messages/assistantPhase.d.ts +3 -3
  620. package/dist/types/messages/budget.d.ts +1 -1
  621. package/dist/types/messages/cache.d.ts +1 -1
  622. package/dist/types/messages/contextPruning.d.ts +2 -2
  623. package/dist/types/messages/core.d.ts +2 -2
  624. package/dist/types/messages/format.d.ts +2 -2
  625. package/dist/types/messages/ids.d.ts +2 -2
  626. package/dist/types/messages/injected.d.ts +1 -1
  627. package/dist/types/messages/langchain.d.ts +1 -1
  628. package/dist/types/messages/prune.d.ts +3 -3
  629. package/dist/types/openai/index.d.ts +1 -1
  630. package/dist/types/prompts/activityLabel.d.ts +2 -2
  631. package/dist/types/responses/index.d.ts +1 -1
  632. package/dist/types/run.d.ts +3 -3
  633. package/dist/types/session/handlers.d.ts +1 -1
  634. package/dist/types/session/types.d.ts +1 -1
  635. package/dist/types/stream.d.ts +4 -4
  636. package/dist/types/summarization/index.d.ts +1 -1
  637. package/dist/types/summarization/node.d.ts +6 -6
  638. package/dist/types/test/mockTools.d.ts +1 -1
  639. package/dist/types/tools/BashExecutor.d.ts +2 -2
  640. package/dist/types/tools/BashProgrammaticToolCalling.d.ts +2 -2
  641. package/dist/types/tools/CodeExecutor.d.ts +3 -3
  642. package/dist/types/tools/CodeSessionFileSummary.d.ts +1 -1
  643. package/dist/types/tools/ProgrammaticToolCalling.d.ts +2 -2
  644. package/dist/types/tools/ReadFile.d.ts +1 -1
  645. package/dist/types/tools/SkillTool.d.ts +1 -1
  646. package/dist/types/tools/SubagentTool.d.ts +3 -3
  647. package/dist/types/tools/ToolNode.d.ts +5 -5
  648. package/dist/types/tools/ToolSearch.d.ts +2 -2
  649. package/dist/types/tools/cloudflare/CloudflareBridgeRuntime.d.ts +1 -1
  650. package/dist/types/tools/cloudflare/CloudflareProgrammaticToolCalling.d.ts +1 -1
  651. package/dist/types/tools/cloudflare/CloudflareSandboxExecutionEngine.d.ts +2 -2
  652. package/dist/types/tools/cloudflare/CloudflareSandboxTools.d.ts +1 -1
  653. package/dist/types/tools/eagerEventExecution.d.ts +1 -1
  654. package/dist/types/tools/handlers.d.ts +3 -3
  655. package/dist/types/tools/intentArg.d.ts +1 -1
  656. package/dist/types/tools/local/CompileCheckTool.d.ts +2 -2
  657. package/dist/types/tools/local/FileCheckpointer.d.ts +1 -1
  658. package/dist/types/tools/local/LocalCodingTools.d.ts +2 -2
  659. package/dist/types/tools/local/LocalExecutionEngine.d.ts +1 -1
  660. package/dist/types/tools/local/LocalExecutionTools.d.ts +1 -1
  661. package/dist/types/tools/local/LocalProgrammaticToolCalling.d.ts +1 -1
  662. package/dist/types/tools/local/bashAst.d.ts +1 -1
  663. package/dist/types/tools/local/resolveLocalExecutionTools.d.ts +1 -1
  664. package/dist/types/tools/local/syntaxCheck.d.ts +1 -1
  665. package/dist/types/tools/ptcTimeout.d.ts +1 -1
  666. package/dist/types/tools/schema.d.ts +1 -1
  667. package/dist/types/tools/search/anthropic.d.ts +1 -1
  668. package/dist/types/tools/skillCatalog.d.ts +1 -1
  669. package/dist/types/tools/subagent/SubagentExecutor.d.ts +5 -5
  670. package/dist/types/tools/subagent/SubagentReplay.d.ts +3 -3
  671. package/dist/types/tools/subagent/childGraphConfig.d.ts +2 -2
  672. package/dist/types/types/activityLabel.d.ts +3 -3
  673. package/dist/types/types/graph.d.ts +7 -7
  674. package/dist/types/types/llm.d.ts +9 -9
  675. package/dist/types/types/run.d.ts +7 -7
  676. package/dist/types/types/stream.d.ts +5 -5
  677. package/dist/types/types/summarize.d.ts +2 -2
  678. package/dist/types/types/tools.d.ts +5 -5
  679. package/dist/types/utils/__tests__/fixtures/contextOverflowSignatures.d.ts +1 -1
  680. package/dist/types/utils/errors.d.ts +1 -1
  681. package/dist/types/utils/events.d.ts +1 -1
  682. package/dist/types/utils/handlers.d.ts +3 -3
  683. package/dist/types/utils/llm.d.ts +1 -1
  684. package/dist/types/utils/llmConfig.d.ts +1 -1
  685. package/dist/types/utils/title.d.ts +1 -1
  686. package/package.json +10 -3
  687. package/src/llm/google/utils/common.ts +6 -4
  688. package/src/__tests__/stream.eagerArgsDivergence.test.ts +0 -911
  689. package/src/__tests__/stream.eagerEventExecution.test.ts +0 -5046
  690. package/src/__tests__/stream.streamLimits.test.ts +0 -1982
  691. package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +0 -448
  692. package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +0 -655
  693. package/src/agents/__tests__/AgentContext.openrouter.live.test.ts +0 -128
  694. package/src/agents/__tests__/AgentContext.overflow.test.ts +0 -205
  695. package/src/agents/__tests__/AgentContext.test.ts +0 -2425
  696. package/src/agents/__tests__/projection.test.ts +0 -73
  697. package/src/agents/__tests__/promptCacheLiveHelpers.ts +0 -175
  698. package/src/aggregator.test.ts +0 -1214
  699. package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +0 -273
  700. package/src/graphs/__tests__/Graph.closeRunStep.test.ts +0 -385
  701. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +0 -1766
  702. package/src/graphs/__tests__/Graph.preemptSignal.test.ts +0 -126
  703. package/src/graphs/__tests__/Graph.reasoning.test.ts +0 -1717
  704. package/src/graphs/__tests__/Graph.subagentResumeState.test.ts +0 -80
  705. package/src/graphs/__tests__/MultiAgentGraph.test.ts +0 -177
  706. package/src/graphs/__tests__/composition.smoke.test.ts +0 -509
  707. package/src/graphs/__tests__/createGraph.test.ts +0 -206
  708. package/src/hooks/__tests__/HookRegistry.test.ts +0 -334
  709. package/src/hooks/__tests__/compactHooks.test.ts +0 -250
  710. package/src/hooks/__tests__/createToolPolicyHook.test.ts +0 -268
  711. package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +0 -405
  712. package/src/hooks/__tests__/executeHooks.test.ts +0 -1294
  713. package/src/hooks/__tests__/integration.test.ts +0 -337
  714. package/src/hooks/__tests__/matchers.test.ts +0 -238
  715. package/src/hooks/__tests__/preemptBoundary.test.ts +0 -152
  716. package/src/hooks/__tests__/toolHooks.test.ts +0 -669
  717. package/src/llm/__tests__/contextOverflowRecovery.test.ts +0 -401
  718. package/src/llm/__tests__/fallbackOverflow.test.ts +0 -287
  719. package/src/llm/anthropic/Jacob_Lee_Resume_2023.pdf +0 -0
  720. package/src/llm/anthropic/inherited-content-utils.spec.ts +0 -249
  721. package/src/llm/anthropic/inherited-stream-events.spec.ts +0 -1259
  722. package/src/llm/anthropic/inherited-strict.spec.ts +0 -302
  723. package/src/llm/anthropic/llm.spec.ts +0 -3394
  724. package/src/llm/anthropic/utils/cross-provider-reasoning.test.ts +0 -383
  725. package/src/llm/anthropic/utils/cross-provider-server-tools.test.ts +0 -110
  726. package/src/llm/anthropic/utils/server-tool-inputs.test.ts +0 -508
  727. package/src/llm/anthropic/utils/streaming-tool-input.test.ts +0 -361
  728. package/src/llm/anthropic/utils/stripPrefillCache.test.ts +0 -111
  729. package/src/llm/anthropic/utils/tool-id-normalization.test.ts +0 -287
  730. package/src/llm/bedrock/inherited-cache.spec.ts +0 -144
  731. package/src/llm/bedrock/inherited.spec.ts +0 -724
  732. package/src/llm/bedrock/llm.spec.ts +0 -1533
  733. package/src/llm/bedrock/streamSealDispatch.test.ts +0 -331
  734. package/src/llm/bedrock/toolCache.test.ts +0 -194
  735. package/src/llm/bedrock/utils/cross-provider-reasoning.test.ts +0 -131
  736. package/src/llm/bedrock/utils/cross-provider-server-tools.test.ts +0 -122
  737. package/src/llm/bedrock/utils/message_inputs.test.ts +0 -650
  738. package/src/llm/bedrock/utils/message_outputs.test.ts +0 -85
  739. package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +0 -118
  740. package/src/llm/custom-chat-models.smoke.test.ts +0 -1966
  741. package/src/llm/google/data/gettysburg10.wav +0 -0
  742. package/src/llm/google/data/hotdog.jpg +0 -0
  743. package/src/llm/google/inherited-stream-events.spec.ts +0 -350
  744. package/src/llm/google/llm.spec.ts +0 -1388
  745. package/src/llm/google/streamSmoothing.test.ts +0 -121
  746. package/src/llm/google/utils/common.test.ts +0 -185
  747. package/src/llm/invoke.alternation.test.ts +0 -87
  748. package/src/llm/invoke.handoffCue.test.ts +0 -112
  749. package/src/llm/invoke.streamLimits.test.ts +0 -142
  750. package/src/llm/invoke.test.ts +0 -1198
  751. package/src/llm/mistral/streamSmoothing.test.ts +0 -97
  752. package/src/llm/openai/cacheWriteTokens.test.ts +0 -112
  753. package/src/llm/openai/contentBlocks.test.ts +0 -381
  754. package/src/llm/openai/deepseek.test.ts +0 -747
  755. package/src/llm/openai/inherited-deepseek.spec.ts +0 -347
  756. package/src/llm/openai/inherited-xai.spec.ts +0 -416
  757. package/src/llm/openai/llm.spec.ts +0 -1774
  758. package/src/llm/openai/managedRequests.test.ts +0 -182
  759. package/src/llm/openai/sequentialToolCallSeals.test.ts +0 -199
  760. package/src/llm/openai/streamMetadata.spec.ts +0 -86
  761. package/src/llm/openai/streamMetadataDedup.spec.ts +0 -166
  762. package/src/llm/openai/utils/isReasoningModel.test.ts +0 -90
  763. package/src/llm/openai/utils/messages.test.ts +0 -488
  764. package/src/llm/openrouter/reasoning.test.ts +0 -561
  765. package/src/llm/openrouter/toolCache.test.ts +0 -134
  766. package/src/llm/preempt.test.ts +0 -323
  767. package/src/llm/stream/chunkAdapters.test.ts +0 -202
  768. package/src/llm/stream/reassembly.test.ts +0 -241
  769. package/src/llm/stream/smoother.bench.test.ts +0 -155
  770. package/src/llm/stream/smoother.test.ts +0 -519
  771. package/src/llm/streamLimits.test.ts +0 -450
  772. package/src/llm/truncation.test.ts +0 -242
  773. package/src/llm/vertexai/fixThoughtSignatures.test.ts +0 -154
  774. package/src/llm/vertexai/inherited-stream-events.spec.ts +0 -271
  775. package/src/llm/vertexai/llm.spec.ts +0 -159
  776. package/src/llm/vertexai/repairUsageMetadata.test.ts +0 -54
  777. package/src/llm/vertexai/sealStreamedToolCalls.test.ts +0 -88
  778. package/src/llm/vertexai/streamSealDispatch.test.ts +0 -148
  779. package/src/llm/vertexai/streamSmoothing.test.ts +0 -109
  780. package/src/messages/__tests__/anthropicToolCache.test.ts +0 -339
  781. package/src/messages/__tests__/observationMasking.test.ts +0 -312
  782. package/src/messages/__tests__/recency.test.ts +0 -267
  783. package/src/messages/__tests__/tools.test.ts +0 -473
  784. package/src/messages/alternation.test.ts +0 -212
  785. package/src/messages/assistantPhase.test.ts +0 -75
  786. package/src/messages/cache.tail.test.ts +0 -533
  787. package/src/messages/cache.test.ts +0 -2166
  788. package/src/messages/content.test.ts +0 -362
  789. package/src/messages/contextPruning.test.ts +0 -184
  790. package/src/messages/ensureThinkingBlock.test.ts +0 -1427
  791. package/src/messages/foldToollessToolBlocks.test.ts +0 -596
  792. package/src/messages/formatAgentMessages.reducer.test.ts +0 -162
  793. package/src/messages/formatAgentMessages.skills.test.ts +0 -513
  794. package/src/messages/formatAgentMessages.steer.test.ts +0 -598
  795. package/src/messages/formatAgentMessages.test.ts +0 -8111
  796. package/src/messages/formatAgentMessages.tools.test.ts +0 -522
  797. package/src/messages/formatMessage.test.ts +0 -693
  798. package/src/messages/handoffCue.test.ts +0 -96
  799. package/src/messages/injected.test.ts +0 -107
  800. package/src/messages/labelContentByAgent.test.ts +0 -962
  801. package/src/messages/reducer.spec.ts +0 -32
  802. package/src/messages/shiftIndexTokenCountMap.test.ts +0 -81
  803. package/src/messages/tailCacheConversion.test.ts +0 -161
  804. package/src/openai/__tests__/openai.test.ts +0 -337
  805. package/src/responses/__tests__/responses.test.ts +0 -652
  806. package/src/scripts/abort.ts +0 -157
  807. package/src/scripts/activity-labels/captured.json +0 -56
  808. package/src/scripts/activity-labels/checks.cjs +0 -205
  809. package/src/scripts/activity-labels/corpus.cjs +0 -473
  810. package/src/scripts/activity-labels/report.cjs +0 -203
  811. package/src/scripts/activity-labels/rescore.cjs +0 -102
  812. package/src/scripts/activity-labels/run.ts +0 -705
  813. package/src/scripts/activity-labels/variants.ts +0 -71
  814. package/src/scripts/ant_web_search.ts +0 -159
  815. package/src/scripts/ant_web_search_edge_case.ts +0 -163
  816. package/src/scripts/ant_web_search_error_edge_case.ts +0 -149
  817. package/src/scripts/args.ts +0 -48
  818. package/src/scripts/bedrock-cache-debug.ts +0 -250
  819. package/src/scripts/bedrock-content-aggregation-test.ts +0 -266
  820. package/src/scripts/bedrock-merge-test.ts +0 -107
  821. package/src/scripts/bedrock-parallel-tools-test.ts +0 -204
  822. package/src/scripts/bench-prompt-cache.ts +0 -479
  823. package/src/scripts/caching.ts +0 -132
  824. package/src/scripts/cli.ts +0 -172
  825. package/src/scripts/cli2.ts +0 -133
  826. package/src/scripts/cli3.ts +0 -184
  827. package/src/scripts/cli4.ts +0 -191
  828. package/src/scripts/cli5.ts +0 -191
  829. package/src/scripts/code_exec.ts +0 -214
  830. package/src/scripts/code_exec_files.ts +0 -237
  831. package/src/scripts/code_exec_multi_session.ts +0 -237
  832. package/src/scripts/code_exec_ptc.ts +0 -335
  833. package/src/scripts/code_exec_session.ts +0 -283
  834. package/src/scripts/code_exec_simple.ts +0 -148
  835. package/src/scripts/compare_pi_vs_ours.ts +0 -1259
  836. package/src/scripts/content.ts +0 -139
  837. package/src/scripts/context-overflow-probe.ts +0 -997
  838. package/src/scripts/empty_input.ts +0 -137
  839. package/src/scripts/handoff-test.ts +0 -134
  840. package/src/scripts/local_engine.ts +0 -166
  841. package/src/scripts/local_engine_checkpointer.ts +0 -205
  842. package/src/scripts/local_engine_compile.ts +0 -263
  843. package/src/scripts/local_engine_hooks.ts +0 -226
  844. package/src/scripts/local_engine_image.ts +0 -201
  845. package/src/scripts/local_engine_ptc.ts +0 -151
  846. package/src/scripts/local_engine_workspace.ts +0 -258
  847. package/src/scripts/memory.ts +0 -107
  848. package/src/scripts/multi-agent-chain.ts +0 -332
  849. package/src/scripts/multi-agent-conditional.ts +0 -222
  850. package/src/scripts/multi-agent-document-review-chain.ts +0 -198
  851. package/src/scripts/multi-agent-hybrid-flow.ts +0 -311
  852. package/src/scripts/multi-agent-parallel-start.ts +0 -265
  853. package/src/scripts/multi-agent-parallel.ts +0 -395
  854. package/src/scripts/multi-agent-sequence.ts +0 -217
  855. package/src/scripts/multi-agent-subagent.ts +0 -246
  856. package/src/scripts/multi-agent-supervisor.ts +0 -366
  857. package/src/scripts/multi-agent-test.ts +0 -187
  858. package/src/scripts/openrouter_prompt_cache_live.ts +0 -310
  859. package/src/scripts/parallel-asymmetric-tools-test.ts +0 -275
  860. package/src/scripts/parallel-full-metadata-test.ts +0 -241
  861. package/src/scripts/parallel-tools-test.ts +0 -341
  862. package/src/scripts/preempt-probe.ts +0 -330
  863. package/src/scripts/preempt-scenarios.ts +0 -388
  864. package/src/scripts/programmatic_exec.ts +0 -387
  865. package/src/scripts/programmatic_exec_agent.ts +0 -232
  866. package/src/scripts/search.ts +0 -147
  867. package/src/scripts/sequential-full-metadata-test.ts +0 -198
  868. package/src/scripts/session_live.ts +0 -548
  869. package/src/scripts/simple.ts +0 -226
  870. package/src/scripts/single-agent-metadata-test.ts +0 -196
  871. package/src/scripts/stream.ts +0 -141
  872. package/src/scripts/subagent-configurable-inheritance.ts +0 -252
  873. package/src/scripts/subagent-event-driven-debug.ts +0 -190
  874. package/src/scripts/subagent-tools-debug.ts +0 -160
  875. package/src/scripts/subagent-usage-sink.ts +0 -176
  876. package/src/scripts/summarization-recency.ts +0 -462
  877. package/src/scripts/test-custom-prompt-key.ts +0 -145
  878. package/src/scripts/test-handoff-input.ts +0 -170
  879. package/src/scripts/test-handoff-preamble.ts +0 -278
  880. package/src/scripts/test-handoff-steering.ts +0 -430
  881. package/src/scripts/test-multi-agent-list-handoff.ts +0 -418
  882. package/src/scripts/test-parallel-agent-labeling.ts +0 -327
  883. package/src/scripts/test-parallel-handoffs.ts +0 -292
  884. package/src/scripts/test-thinking-handoff-bedrock.ts +0 -154
  885. package/src/scripts/test-thinking-handoff.ts +0 -156
  886. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.ts +0 -166
  887. package/src/scripts/test-tool-before-handoff-role-order.ts +0 -276
  888. package/src/scripts/test-tools-before-handoff.ts +0 -222
  889. package/src/scripts/test_code_api.ts +0 -354
  890. package/src/scripts/thinking-bedrock.ts +0 -160
  891. package/src/scripts/thinking-vertexai.ts +0 -168
  892. package/src/scripts/thinking.ts +0 -172
  893. package/src/scripts/tool_search.ts +0 -153
  894. package/src/scripts/tools.ts +0 -163
  895. package/src/session/__tests__/JsonlSessionStore.test.ts +0 -1463
  896. package/src/session/__tests__/handlers.test.ts +0 -161
  897. package/src/specs/activity-label-observability.live.test.ts +0 -262
  898. package/src/specs/activity-label-observability.test.ts +0 -167
  899. package/src/specs/activity-label-prompt.test.ts +0 -359
  900. package/src/specs/activity-label-trace-seed.test.ts +0 -47
  901. package/src/specs/activity-phase-label.test.ts +0 -306
  902. package/src/specs/agent-handoffs.live.test.ts +0 -140
  903. package/src/specs/agent-handoffs.test.ts +0 -2465
  904. package/src/specs/anthropic.simple.test.ts +0 -453
  905. package/src/specs/ask-user-question-batch.test.ts +0 -289
  906. package/src/specs/ask-user-questions.live.test.ts +0 -185
  907. package/src/specs/ask-user-questions.test.ts +0 -293
  908. package/src/specs/azure.simple.test.ts +0 -502
  909. package/src/specs/bedrock-toolless.live.test.ts +0 -123
  910. package/src/specs/bedrock-truncation.live.test.ts +0 -191
  911. package/src/specs/cache.simple.test.ts +0 -416
  912. package/src/specs/context-accuracy.live.test.ts +0 -409
  913. package/src/specs/context-overflow-recovery.live.test.ts +0 -213
  914. package/src/specs/context-usage-event.test.ts +0 -117
  915. package/src/specs/context-usage.live.test.ts +0 -297
  916. package/src/specs/custom-event-await.test.ts +0 -304
  917. package/src/specs/deepseek.simple.test.ts +0 -290
  918. package/src/specs/deterministic-trace-id.test.ts +0 -43
  919. package/src/specs/discovered-tools.test.ts +0 -217
  920. package/src/specs/durability-checkpoint.integration.test.ts +0 -243
  921. package/src/specs/graph-subagent.live.test.ts +0 -242
  922. package/src/specs/graph-subagent.test.ts +0 -322
  923. package/src/specs/handoffCue.test.ts +0 -165
  924. package/src/specs/langfuse-callbacks.test.ts +0 -1578
  925. package/src/specs/langfuse-config.test.ts +0 -247
  926. package/src/specs/langfuse-instrumentation.test.ts +0 -393
  927. package/src/specs/langfuse-metadata.test.ts +0 -245
  928. package/src/specs/langfuse-routing.integration.test.ts +0 -844
  929. package/src/specs/langfuse-runtime-context.test.ts +0 -92
  930. package/src/specs/langfuse-span-registry.test.ts +0 -87
  931. package/src/specs/langfuse-tool-output-tracing.test.ts +0 -1858
  932. package/src/specs/langfuse-trace-shaping.test.ts +0 -591
  933. package/src/specs/moonshot.simple.test.ts +0 -367
  934. package/src/specs/multi-agent-summarization.test.ts +0 -396
  935. package/src/specs/openai.simple.test.ts +0 -315
  936. package/src/specs/openrouter.simple.test.ts +0 -275
  937. package/src/specs/preemptSeal.test.ts +0 -678
  938. package/src/specs/prune.test.ts +0 -3741
  939. package/src/specs/reasoning.test.ts +0 -201
  940. package/src/specs/run-step-timestamps.test.ts +0 -414
  941. package/src/specs/spec.utils.ts +0 -15
  942. package/src/specs/subagent.test.ts +0 -864
  943. package/src/specs/summarization-unit.test.ts +0 -973
  944. package/src/specs/summarization.test.ts +0 -3823
  945. package/src/specs/summarize-prune.test.ts +0 -376
  946. package/src/specs/thinking-handoff.test.ts +0 -621
  947. package/src/specs/thinking-prune.test.ts +0 -827
  948. package/src/specs/token-accounting-e2e.test.ts +0 -1034
  949. package/src/specs/token-accounting-pipeline.test.ts +0 -1008
  950. package/src/specs/token-distribution-edge-case.test.ts +0 -315
  951. package/src/specs/token-memoization.test.ts +0 -40
  952. package/src/specs/tokens.test.ts +0 -1000
  953. package/src/specs/tool-error-resume.test.ts +0 -194
  954. package/src/specs/tool-error.test.ts +0 -198
  955. package/src/specs/vllm-reasoning-toolcalls.test.ts +0 -340
  956. package/src/stream.dispatch.test.ts +0 -63
  957. package/src/stream.test.ts +0 -144
  958. package/src/summarization/__tests__/aggregator.test.ts +0 -236
  959. package/src/summarization/__tests__/node.test.ts +0 -1996
  960. package/src/summarization/__tests__/trigger.test.ts +0 -149
  961. package/src/summarization/chunkHandler.test.ts +0 -196
  962. package/src/test/mockTools.ts +0 -386
  963. package/src/tools/Calculator.test.ts +0 -278
  964. package/src/tools/__tests__/BashExecutor.test.ts +0 -108
  965. package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +0 -939
  966. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +0 -858
  967. package/src/tools/__tests__/CodeExecutor.stateful.test.ts +0 -124
  968. package/src/tools/__tests__/LocalExecutionRoots.test.ts +0 -8
  969. package/src/tools/__tests__/LocalExecutionTools.test.ts +0 -2733
  970. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +0 -318
  971. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +0 -1557
  972. package/src/tools/__tests__/ReadFile.test.ts +0 -44
  973. package/src/tools/__tests__/SkillTool.test.ts +0 -442
  974. package/src/tools/__tests__/SubagentExecutor.test.ts +0 -3728
  975. package/src/tools/__tests__/SubagentReplay.test.ts +0 -329
  976. package/src/tools/__tests__/SubagentTool.test.ts +0 -149
  977. package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +0 -595
  978. package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +0 -1191
  979. package/src/tools/__tests__/ToolNode.invalidToolCalls.test.ts +0 -757
  980. package/src/tools/__tests__/ToolNode.langfuse.test.ts +0 -57
  981. package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +0 -736
  982. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +0 -1710
  983. package/src/tools/__tests__/ToolNode.runtimeState.test.ts +0 -120
  984. package/src/tools/__tests__/ToolNode.session.test.ts +0 -1352
  985. package/src/tools/__tests__/ToolNode.streamLimits.test.ts +0 -69
  986. package/src/tools/__tests__/ToolSearch.integration.test.ts +0 -161
  987. package/src/tools/__tests__/ToolSearch.test.ts +0 -1227
  988. package/src/tools/__tests__/annotateMessagesForLLM.test.ts +0 -561
  989. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +0 -980
  990. package/src/tools/__tests__/directToolHooks.test.ts +0 -700
  991. package/src/tools/__tests__/eagerEventExecution.session.test.ts +0 -92
  992. package/src/tools/__tests__/handlers.test.ts +0 -997
  993. package/src/tools/__tests__/hitl.test.ts +0 -4663
  994. package/src/tools/__tests__/intentArg.test.ts +0 -357
  995. package/src/tools/__tests__/intentCoverage.test.ts +0 -139
  996. package/src/tools/__tests__/localToolNames.test.ts +0 -75
  997. package/src/tools/__tests__/skillCatalog.test.ts +0 -161
  998. package/src/tools/__tests__/subagentHooks.test.ts +0 -1651
  999. package/src/tools/__tests__/toolOutputReferences.test.ts +0 -515
  1000. package/src/tools/__tests__/workspaceSeam.test.ts +0 -147
  1001. package/src/tools/local/__tests__/FileCheckpointer.test.ts +0 -162
  1002. package/src/tools/local/__tests__/editStrategies.test.ts +0 -134
  1003. package/src/tools/search/content.test.ts +0 -173
  1004. package/src/tools/search/crw.test.ts +0 -836
  1005. package/src/tools/search/format.test.ts +0 -242
  1006. package/src/tools/search/http-agent.test.ts +0 -133
  1007. package/src/tools/search/jina-reranker.test.ts +0 -306
  1008. package/src/tools/search/keenable-scraper.test.ts +0 -153
  1009. package/src/tools/search/keenable.test.ts +0 -183
  1010. package/src/tools/search/outcome.test.ts +0 -90
  1011. package/src/tools/search/output.md +0 -2775
  1012. package/src/tools/search/rag-api-reranker.test.ts +0 -891
  1013. package/src/tools/search/source-processing.test.ts +0 -516
  1014. package/src/tools/search/tavily.test.ts +0 -965
  1015. package/src/tools/search/test.html +0 -884
  1016. package/src/tools/search/test.md +0 -643
  1017. package/src/tools/subagent/__tests__/SubagentExecutionRegistry.test.ts +0 -637
  1018. package/src/tools/subagent/__tests__/SubagentExecutor.lazy.test.ts +0 -3054
  1019. package/src/tools/subagent/__tests__/childGraphConfig.test.ts +0 -521
  1020. package/src/utils/__tests__/apportion.test.ts +0 -32
  1021. package/src/utils/__tests__/errors.test.ts +0 -270
  1022. package/src/utils/__tests__/fixtures/contextOverflowSignatures.ts +0 -336
  1023. package/src/utils/__tests__/redactSecrets.test.ts +0 -56
  1024. package/src/utils/__tests__/truncation.test.ts +0 -66
  1025. package/src/utils/llm.test.ts +0 -18
  1026. package/src/utils/proxy.test.ts +0 -176
  1027. package/src/utils/toolContent.test.ts +0 -1432
@@ -1,2733 +0,0 @@
1
- import { z } from 'zod';
2
- import { tmpdir } from 'os';
3
- import { join } from 'path';
4
- import { spawnSync } from 'child_process';
5
- import { tool } from '@langchain/core/tools';
6
- import { AIMessage, ToolMessage } from '@langchain/core/messages';
7
- import {
8
- describe,
9
- it,
10
- expect,
11
- afterEach,
12
- beforeEach,
13
- jest,
14
- } from '@jest/globals';
15
- import {
16
- mkdtemp,
17
- rm,
18
- symlink,
19
- writeFile as fsWriteFile,
20
- readFile as fsReadFile,
21
- } from 'fs/promises';
22
- import type { StructuredToolInterface } from '@langchain/core/tools';
23
- import type { BaseMessage } from '@langchain/core/messages';
24
- import type * as t from '@/types';
25
- import {
26
- executeLocalBash,
27
- executeLocalCode,
28
- validateBashCommand,
29
- _resetLocalEngineWarningsForTests,
30
- } from '../local/LocalExecutionEngine';
31
- import {
32
- createLocalCodingToolBundle,
33
- _resetRipgrepCacheForTests,
34
- } from '../local/LocalCodingTools';
35
- import {
36
- runPostEditSyntaxCheck,
37
- _resetSyntaxCheckProbeCacheForTests,
38
- } from '../local/syntaxCheck';
39
- import { resolveLocalToolsForBinding } from '../local/resolveLocalExecutionTools';
40
- import { WorkspaceClientTimeoutError } from '../local/workspaceFS';
41
- import type { WorkspaceFS } from '../local/workspaceFS';
42
- import { LocalFileCheckpointerImpl } from '../local/FileCheckpointer';
43
- import { createCompileCheckTool } from '../local/CompileCheckTool';
44
- import { runBashAstChecks } from '../local/bashAst';
45
- import { Constants, Providers } from '@/common';
46
- import { ToolNode } from '../ToolNode';
47
-
48
- const hasPython3 = spawnSync('python3', ['--version']).status === 0;
49
-
50
- const tempDirs: string[] = [];
51
-
52
- async function createTempDir(): Promise<string> {
53
- const dir = await mkdtemp(join(tmpdir(), 'lc-local-tools-'));
54
- tempDirs.push(dir);
55
- return dir;
56
- }
57
-
58
- function createRemoteBashStub(): StructuredToolInterface {
59
- return tool(async () => 'remote bash should not run', {
60
- name: Constants.BASH_TOOL,
61
- description: 'Remote bash stub',
62
- schema: z.object({ command: z.string() }),
63
- }) as unknown as StructuredToolInterface;
64
- }
65
-
66
- function messagesFromResult(
67
- result: ToolMessage[] | { messages: ToolMessage[] }
68
- ): ToolMessage[] {
69
- return Array.isArray(result) ? result : result.messages;
70
- }
71
-
72
- function aiMessageWithToolCall(
73
- name: string,
74
- args: Record<string, string | number | boolean>
75
- ): AIMessage {
76
- return new AIMessage({
77
- content: '',
78
- tool_calls: [
79
- {
80
- id: `call_${name}`,
81
- name,
82
- args,
83
- },
84
- ],
85
- });
86
- }
87
-
88
- afterEach(async () => {
89
- await Promise.all(
90
- tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))
91
- );
92
- });
93
-
94
- describe('local execution tools', () => {
95
- it('blocks clearly destructive bash commands by default', async () => {
96
- const result = await validateBashCommand('rm -rf /');
97
-
98
- expect(result.valid).toBe(false);
99
- expect(result.errors.join('\n')).toContain('destructive command pattern');
100
- });
101
-
102
- it('replaces a configured remote bash tool when local mode is enabled', async () => {
103
- const cwd = await createTempDir();
104
- const node = new ToolNode({
105
- tools: [createRemoteBashStub()],
106
- toolExecution: {
107
- engine: 'local',
108
- local: {
109
- cwd,
110
- includeCodingTools: false,
111
- },
112
- },
113
- });
114
-
115
- const result = await node.invoke({
116
- messages: [
117
- aiMessageWithToolCall(Constants.BASH_TOOL, {
118
- command: 'printf local-mode',
119
- }),
120
- ],
121
- });
122
-
123
- const [message] = messagesFromResult(result as { messages: ToolMessage[] });
124
- expect(String(message.content)).toContain('local-mode');
125
- expect(String(message.content)).not.toContain('remote bash should not run');
126
- });
127
-
128
- it('auto-binds the local coding suite in local mode', () => {
129
- const tools = resolveLocalToolsForBinding({
130
- toolExecution: { engine: 'local' },
131
- }) as t.GenericTool[];
132
- const names = tools.map((localTool) => localTool.name);
133
-
134
- expect(names).toEqual(
135
- expect.arrayContaining([
136
- Constants.EXECUTE_CODE,
137
- Constants.BASH_TOOL,
138
- Constants.READ_FILE,
139
- 'write_file',
140
- 'edit_file',
141
- 'grep_search',
142
- 'glob_search',
143
- 'list_directory',
144
- ])
145
- );
146
- });
147
-
148
- it('updates existing code tool bindings when auto-binding is disabled', () => {
149
- const [bashTool] = resolveLocalToolsForBinding({
150
- tools: [createRemoteBashStub()],
151
- toolExecution: {
152
- engine: 'local',
153
- local: { includeCodingTools: false },
154
- },
155
- }) as t.GenericTool[];
156
-
157
- expect(bashTool.name).toBe(Constants.BASH_TOOL);
158
- expect(bashTool.description).toContain('local machine');
159
- });
160
-
161
- it('can call local coding tools from local programmatic execution', async () => {
162
- if (!hasPython3) {
163
- return;
164
- }
165
-
166
- const cwd = await createTempDir();
167
- const node = new ToolNode({
168
- tools: [],
169
- toolExecution: {
170
- engine: 'local',
171
- local: { cwd },
172
- },
173
- });
174
-
175
- const result = await node.invoke({
176
- messages: [
177
- aiMessageWithToolCall(Constants.PROGRAMMATIC_TOOL_CALLING, {
178
- lang: 'py',
179
- code: [
180
- 'await write_file(path="ptc.txt", content="from local ptc")',
181
- 'contents = await read_file(path="ptc.txt")',
182
- 'print(contents)',
183
- ].join('\n'),
184
- }),
185
- ],
186
- });
187
-
188
- const [message] = messagesFromResult(result as { messages: ToolMessage[] });
189
- expect(String(message.content)).toContain('from local ptc');
190
- });
191
-
192
- it('can run bash orchestration through run_tools_with_code in local mode', async () => {
193
- if (!hasPython3) {
194
- return;
195
- }
196
-
197
- const cwd = await createTempDir();
198
- const node = new ToolNode({
199
- tools: [],
200
- toolExecution: {
201
- engine: 'local',
202
- local: { cwd },
203
- },
204
- });
205
-
206
- const result = await node.invoke({
207
- messages: [
208
- aiMessageWithToolCall(Constants.PROGRAMMATIC_TOOL_CALLING, {
209
- code: [
210
- 'write_file \'{"path":"bash-ptc.txt","content":"from bash ptc"}\'',
211
- 'read_file \'{"path":"bash-ptc.txt"}\'',
212
- ].join('\n'),
213
- }),
214
- ],
215
- });
216
-
217
- const [message] = messagesFromResult(result as { messages: ToolMessage[] });
218
- expect(String(message.content)).toContain('from bash ptc');
219
- });
220
- });
221
-
222
- describe('local engine bashAst', () => {
223
- it('flags command substitution in auto mode', () => {
224
- const findings = runBashAstChecks('echo $(whoami)', 'auto');
225
- expect(findings.some((f) => f.code === 'cmd-subst-dollar-paren')).toBe(
226
- true
227
- );
228
- });
229
-
230
- it('escalates command substitution to deny in strict mode', () => {
231
- const findings = runBashAstChecks('echo $(whoami)', 'strict');
232
- const subst = findings.find((f) => f.code === 'cmd-subst-dollar-paren');
233
- expect(subst?.severity).toBe('deny');
234
- });
235
-
236
- it('always denies /proc/<pid>/environ access', () => {
237
- const findings = runBashAstChecks('cat /proc/1/environ', 'auto');
238
- expect(
239
- findings.some(
240
- (f) => f.code === 'proc-environ-read' && f.severity === 'deny'
241
- )
242
- ).toBe(true);
243
- });
244
-
245
- it('never produces findings when off', () => {
246
- const findings = runBashAstChecks('echo $(whoami)', 'off');
247
- expect(findings).toHaveLength(0);
248
- });
249
-
250
- it('blocks bash commands with a deny finding via validateBashCommand', async () => {
251
- const result = await validateBashCommand('cat /proc/1/environ', {
252
- bashAst: 'auto',
253
- });
254
- expect(result.valid).toBe(false);
255
- expect(result.errors.join('\n')).toContain('proc-environ-read');
256
- });
257
- });
258
-
259
- describe('local engine sandbox-off warning', () => {
260
- let warnSpy: jest.SpiedFunction<typeof console.warn>;
261
-
262
- beforeEach(() => {
263
- _resetLocalEngineWarningsForTests();
264
- warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined);
265
- });
266
-
267
- afterEach(() => {
268
- warnSpy.mockRestore();
269
- });
270
-
271
- it('warns once when running without sandbox', async () => {
272
- // Real (non-internal) executions should warn; the internal
273
- // `bash -n` syntax preflight inside validateBashCommand opts out
274
- // (Codex P2 — otherwise the latch would flip on a probe and hide
275
- // the warning when a genuinely-unsandboxed command later runs).
276
- await executeLocalBash('echo hi');
277
- await executeLocalBash('echo bye');
278
- const sandboxOffMessages = warnSpy.mock.calls.filter((call) =>
279
- String(call[0]).includes('without @anthropic-ai/sandbox-runtime')
280
- );
281
- expect(sandboxOffMessages).toHaveLength(1);
282
- });
283
-
284
- it('does NOT warn for internal probes when the run actually has sandbox enabled (Codex P2)', async () => {
285
- // Pre-fix: validateBashCommand's bash -n preflight (which forces
286
- // sandbox: false for itself, since you can't sandbox a syntax
287
- // probe) would emit a misleading "sandbox is off" warning AND
288
- // flip `sandboxOffWarned = true` even when the run had
289
- // `sandbox.enabled: true` — hiding the warning when a real
290
- // unsandboxed execution later happened. With the fix internal
291
- // probes pass `{ internal: true }` to spawnLocalProcess and
292
- // suppress both the message and the latch.
293
- await validateBashCommand('echo hi', { sandbox: { enabled: true } });
294
- const sandboxOffMessages = warnSpy.mock.calls.filter((call) =>
295
- String(call[0]).includes('without @anthropic-ai/sandbox-runtime')
296
- );
297
- expect(sandboxOffMessages).toHaveLength(0);
298
- });
299
- });
300
-
301
- describe('LocalFileCheckpointer', () => {
302
- it('snapshots and restores existing files', async () => {
303
- const dir = await createTempDir();
304
- const file = join(dir, 'a.txt');
305
- await fsWriteFile(file, 'original', 'utf8');
306
-
307
- const cp = new LocalFileCheckpointerImpl();
308
- await cp.captureBeforeWrite(file);
309
-
310
- await fsWriteFile(file, 'modified', 'utf8');
311
- expect(await fsReadFile(file, 'utf8')).toBe('modified');
312
-
313
- const restored = await cp.rewind();
314
- expect(restored).toBe(1);
315
- expect(await fsReadFile(file, 'utf8')).toBe('original');
316
- });
317
-
318
- it('deletes files that did not exist before the run', async () => {
319
- const dir = await createTempDir();
320
- const file = join(dir, 'new.txt');
321
-
322
- const cp = new LocalFileCheckpointerImpl();
323
- await cp.captureBeforeWrite(file);
324
- await fsWriteFile(file, 'should-be-removed', 'utf8');
325
-
326
- await cp.rewind();
327
- await expect(fsReadFile(file, 'utf8')).rejects.toThrow();
328
- });
329
-
330
- it('rewinds tools created via createLocalCodingToolBundle', async () => {
331
- const cwd = await createTempDir();
332
- const bundle = createLocalCodingToolBundle({
333
- cwd,
334
- fileCheckpointing: true,
335
- });
336
- expect(bundle.checkpointer).toBeDefined();
337
-
338
- const writeTool = bundle.tools.find((tool_) => tool_.name === 'write_file');
339
- expect(writeTool).toBeDefined();
340
- await writeTool!.invoke({ path: 'cp.txt', content: 'first' });
341
- await writeTool!.invoke({ path: 'cp.txt', content: 'second' });
342
-
343
- const restored = await bundle.checkpointer!.rewind();
344
- expect(restored).toBe(1);
345
- await expect(fsReadFile(join(cwd, 'cp.txt'), 'utf8')).rejects.toThrow();
346
- });
347
- });
348
-
349
- describe('local read tool guards', () => {
350
- it('refuses to read files containing NUL bytes', async () => {
351
- const cwd = await createTempDir();
352
- const binary = join(cwd, 'binary.bin');
353
- await fsWriteFile(binary, Buffer.from([0x00, 0x01, 0x02]));
354
-
355
- const bundle = createLocalCodingToolBundle({ cwd });
356
- const readTool = bundle.tools.find((t_) => t_.name === Constants.READ_FILE);
357
- const result = await readTool!.invoke({ path: 'binary.bin' });
358
- expect(String(result)).toContain('binary file');
359
- });
360
-
361
- it('returns a stub instead of OOMing on huge files', async () => {
362
- const cwd = await createTempDir();
363
- const big = join(cwd, 'big.txt');
364
- await fsWriteFile(big, 'x'.repeat(2048));
365
-
366
- const bundle = createLocalCodingToolBundle({
367
- cwd,
368
- maxReadBytes: 1024,
369
- });
370
- const readTool = bundle.tools.find((t_) => t_.name === Constants.READ_FILE);
371
- const result = await readTool!.invoke({ path: 'big.txt' });
372
- expect(String(result)).toContain('exceeds the 1024-byte read cap');
373
- });
374
-
375
- it('rejects symlink escapes', async () => {
376
- const cwd = await createTempDir();
377
- const outside = await createTempDir();
378
- const secret = join(outside, 'secret.txt');
379
- await fsWriteFile(secret, 'top-secret', 'utf8');
380
- await symlink(outside, join(cwd, 'escape'));
381
-
382
- const bundle = createLocalCodingToolBundle({ cwd });
383
- const readTool = bundle.tools.find((t_) => t_.name === Constants.READ_FILE);
384
- await expect(
385
- readTool!.invoke({ path: 'escape/secret.txt' })
386
- ).rejects.toThrow(/symlink escape/);
387
- });
388
- });
389
-
390
- describe('local programmatic bridge auth', () => {
391
- it('rejects unauthenticated requests to the local bridge', async () => {
392
- if (!hasPython3) {
393
- return;
394
- }
395
- const cwd = await createTempDir();
396
- const node = new ToolNode({
397
- tools: [],
398
- toolExecution: {
399
- engine: 'local',
400
- local: { cwd },
401
- },
402
- });
403
-
404
- const result = await node.invoke({
405
- messages: [
406
- aiMessageWithToolCall(Constants.PROGRAMMATIC_TOOL_CALLING, {
407
- lang: 'py',
408
- code: [
409
- 'import os, json, urllib.request, urllib.error',
410
- 'url = os.environ["BRIDGE_PROBE_URL"] if "BRIDGE_PROBE_URL" in os.environ else __LIBRECHAT_TOOL_BRIDGE',
411
- 'body = json.dumps({"name":"read_file","input":{"path":"x"}}).encode("utf-8")',
412
- 'try:',
413
- ' req = urllib.request.Request(url, data=body, headers={"Content-Type":"application/json"}, method="POST")',
414
- ' urllib.request.urlopen(req, timeout=5)',
415
- ' print("LEAK")',
416
- 'except urllib.error.HTTPError as e:',
417
- ' print(f"AUTH={e.code}")',
418
- ].join('\n'),
419
- }),
420
- ],
421
- });
422
-
423
- const [message] = messagesFromResult(result as { messages: ToolMessage[] });
424
- expect(String(message.content)).toContain('AUTH=401');
425
- expect(String(message.content)).not.toContain('LEAK');
426
- });
427
- });
428
-
429
- describe('local edit fuzzy matching', () => {
430
- it('falls back to line-trimmed when trailing whitespace differs', async () => {
431
- const cwd = await createTempDir();
432
- const file = join(cwd, 'a.ts');
433
- // Real file has trailing whitespace on every line.
434
- await fsWriteFile(
435
- file,
436
- 'function greet(name: string) { \n return `Hello, ${name}!`; \n}\n',
437
- 'utf8'
438
- );
439
-
440
- const bundle = createLocalCodingToolBundle({ cwd });
441
- const editTool = bundle.tools.find((tt) => tt.name === 'edit_file');
442
- const result = await editTool!.invoke({
443
- path: 'a.ts',
444
- // LLM emits a trailing-whitespace-stripped version.
445
- old_text:
446
- 'function greet(name: string) {\n return `Hello, ${name}!`;\n}',
447
- new_text: 'function greet(name: string) {\n return `Hi, ${name}!`;\n}',
448
- });
449
- expect(String(result)).toContain('strategies: line-trimmed');
450
- const after = await fsReadFile(file, 'utf8');
451
- expect(after).toContain('Hi, ${name}!');
452
- });
453
-
454
- it('falls back to indentation-flexible when LLM strips leading indent', async () => {
455
- const cwd = await createTempDir();
456
- const file = join(cwd, 'a.ts');
457
- await fsWriteFile(
458
- file,
459
- 'class Foo {\n method() {\n return 1;\n }\n}\n',
460
- 'utf8'
461
- );
462
-
463
- const bundle = createLocalCodingToolBundle({ cwd });
464
- const editTool = bundle.tools.find((tt) => tt.name === 'edit_file');
465
- const result = await editTool!.invoke({
466
- path: 'a.ts',
467
- // LLM stripped the 4-space indent
468
- old_text: 'method() {\n return 1;\n}',
469
- new_text: 'method() {\n return 42;\n}',
470
- });
471
- expect(String(result)).toMatch(
472
- /strategies: (indentation-flexible|whitespace-normalized)/
473
- );
474
- const after = await fsReadFile(file, 'utf8');
475
- expect(after).toContain('return 42;');
476
- });
477
-
478
- it('returns a unified diff in the tool result', async () => {
479
- const cwd = await createTempDir();
480
- const file = join(cwd, 'a.txt');
481
- await fsWriteFile(file, 'first\nsecond\nthird\n', 'utf8');
482
- const bundle = createLocalCodingToolBundle({ cwd });
483
- const editTool = bundle.tools.find((tt) => tt.name === 'edit_file');
484
- const result = await editTool!.invoke({
485
- path: 'a.txt',
486
- old_text: 'second',
487
- new_text: 'SECOND',
488
- });
489
- const text = String(result);
490
- expect(text).toContain('Diff:');
491
- expect(text).toContain('-second');
492
- expect(text).toContain('+SECOND');
493
- });
494
-
495
- it('preserves CRLF line endings on edit', async () => {
496
- const cwd = await createTempDir();
497
- const file = join(cwd, 'a.txt');
498
- await fsWriteFile(file, 'one\r\ntwo\r\nthree\r\n', 'utf8');
499
- const bundle = createLocalCodingToolBundle({ cwd });
500
- const editTool = bundle.tools.find((tt) => tt.name === 'edit_file');
501
- await editTool!.invoke({
502
- path: 'a.txt',
503
- old_text: 'two',
504
- new_text: 'TWO',
505
- });
506
- const raw = await fsReadFile(file, 'utf8');
507
- expect(raw).toBe('one\r\nTWO\r\nthree\r\n');
508
- });
509
-
510
- it('preserves UTF-8 BOM on overwrite', async () => {
511
- const cwd = await createTempDir();
512
- const file = join(cwd, 'a.txt');
513
- const BOM = '';
514
- await fsWriteFile(file, BOM + 'hello\n', 'utf8');
515
- const bundle = createLocalCodingToolBundle({ cwd });
516
- const writeTool = bundle.tools.find((tt) => tt.name === 'write_file');
517
- await writeTool!.invoke({ path: 'a.txt', content: 'goodbye\n' });
518
- const raw = await fsReadFile(file, 'utf8');
519
- expect(raw.startsWith(BOM)).toBe(true);
520
- expect(raw.slice(1)).toBe('goodbye\n');
521
- });
522
- });
523
-
524
- describe('local read attachments', () => {
525
- // Smallest valid 1x1 PNG.
526
- const tinyPng = Buffer.from(
527
- '89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4890000000a49444154789c63000100000005000165be7e6e0000000049454e44ae426082',
528
- 'hex'
529
- );
530
-
531
- it('returns binary stub by default', async () => {
532
- const cwd = await createTempDir();
533
- const file = join(cwd, 'tiny.png');
534
- await fsWriteFile(file, tinyPng);
535
- const bundle = createLocalCodingToolBundle({ cwd });
536
- const readTool = bundle.tools.find((tt) => tt.name === Constants.READ_FILE);
537
- const result = await readTool!.invoke({ path: 'tiny.png' });
538
- expect(String(result)).toContain('binary file');
539
- });
540
-
541
- it('returns an image_url content block when attachReadAttachments=images-only', async () => {
542
- const cwd = await createTempDir();
543
- const file = join(cwd, 'tiny.png');
544
- await fsWriteFile(file, tinyPng);
545
-
546
- const bundle = createLocalCodingToolBundle({
547
- cwd,
548
- attachReadAttachments: 'images-only',
549
- });
550
- const readTool = bundle.tools.find((tt) => tt.name === Constants.READ_FILE);
551
- // Invoking via a tool_call envelope (rather than raw args) is what
552
- // makes the LangChain tool wrap the result as a ToolMessage with
553
- // `.content` and `.artifact` populated.
554
- const message = (await readTool!.invoke({
555
- id: 'call_image',
556
- name: Constants.READ_FILE,
557
- args: { path: 'tiny.png' },
558
- type: 'tool_call',
559
- })) as { content: unknown; artifact: unknown };
560
- expect(Array.isArray(message.content)).toBe(true);
561
- const blocks = message.content as Array<{
562
- type: string;
563
- image_url?: { url: string };
564
- }>;
565
- const imageBlock = blocks.find((b) => b.type === 'image_url');
566
- expect(imageBlock?.image_url?.url).toMatch(/^data:image\/png;base64,/);
567
- expect(blocks.find((b) => b.type === 'text')).toBeDefined();
568
- expect(message.artifact).toMatchObject({
569
- mime: 'image/png',
570
- attachment: 'image',
571
- });
572
- });
573
-
574
- it('refuses oversize images even when embedding is on', async () => {
575
- const cwd = await createTempDir();
576
- const file = join(cwd, 'big.png');
577
- // Forge a "PNG" larger than the cap. It will sniff as a generic
578
- // binary; classifyAttachment returns 'binary' since file-type
579
- // won't recognise the bytes — that's fine, we just want to
580
- // verify the oversize gate is reachable. So instead, build a
581
- // real big PNG by concatenating chunks with a fake IDAT.
582
- // Easier: keep the tiny PNG header but pad to 200 bytes; cap to 100.
583
- const padded = Buffer.concat([
584
- tinyPng,
585
- Buffer.alloc(200 - tinyPng.length, 0),
586
- ]);
587
- await fsWriteFile(file, padded);
588
- const bundle = createLocalCodingToolBundle({
589
- cwd,
590
- attachReadAttachments: 'images-only',
591
- maxAttachmentBytes: 100,
592
- });
593
- const readTool = bundle.tools.find((tt) => tt.name === Constants.READ_FILE);
594
- const result = await readTool!.invoke({ path: 'big.png' });
595
- expect(String(result)).toMatch(/Refusing to embed/);
596
- });
597
-
598
- it('still reads text files normally when embedding is on', async () => {
599
- const cwd = await createTempDir();
600
- const file = join(cwd, 'a.txt');
601
- await fsWriteFile(file, 'hello world\n', 'utf8');
602
- const bundle = createLocalCodingToolBundle({
603
- cwd,
604
- attachReadAttachments: 'images-only',
605
- });
606
- const readTool = bundle.tools.find((tt) => tt.name === Constants.READ_FILE);
607
- const result = await readTool!.invoke({ path: 'a.txt' });
608
- expect(String(result)).toContain('hello world');
609
- });
610
- });
611
-
612
- describe('post-edit syntax check', () => {
613
- beforeEach(() => {
614
- _resetSyntaxCheckProbeCacheForTests();
615
- });
616
-
617
- it('flags broken JS via node --check', async () => {
618
- const cwd = await createTempDir();
619
- const file = join(cwd, 'broken.js');
620
- await fsWriteFile(file, 'function (\n', 'utf8');
621
- const outcome = await runPostEditSyntaxCheck(file, {});
622
- expect(outcome).not.toBeNull();
623
- expect(outcome!.ok).toBe(false);
624
- if (outcome!.ok === false) {
625
- expect(outcome!.checker).toBe('node --check');
626
- expect(outcome!.output.length).toBeGreaterThan(0);
627
- }
628
- });
629
-
630
- it('passes valid JS', async () => {
631
- const cwd = await createTempDir();
632
- const file = join(cwd, 'good.js');
633
- await fsWriteFile(file, 'console.log(1)\n', 'utf8');
634
- const outcome = await runPostEditSyntaxCheck(file, {});
635
- expect(outcome?.ok).toBe(true);
636
- });
637
-
638
- it('flags broken JSON via JSON.parse', async () => {
639
- const cwd = await createTempDir();
640
- const file = join(cwd, 'broken.json');
641
- await fsWriteFile(file, '{ "x": ', 'utf8');
642
- const outcome = await runPostEditSyntaxCheck(file, {});
643
- expect(outcome?.ok).toBe(false);
644
- if (outcome!.ok === false) {
645
- expect(outcome!.checker).toBe('JSON.parse');
646
- }
647
- });
648
-
649
- it('returns null for unknown extensions', async () => {
650
- const cwd = await createTempDir();
651
- const file = join(cwd, 'random.xyz');
652
- await fsWriteFile(file, 'whatever\n', 'utf8');
653
- const outcome = await runPostEditSyntaxCheck(file, {});
654
- expect(outcome).toBeNull();
655
- });
656
-
657
- it('write_file appends syntax-check warning when postEditSyntaxCheck=auto', async () => {
658
- const cwd = await createTempDir();
659
- const bundle = createLocalCodingToolBundle({
660
- cwd,
661
- postEditSyntaxCheck: 'auto',
662
- });
663
- const writeTool = bundle.tools.find((tt) => tt.name === 'write_file');
664
- const message = (await writeTool!.invoke({
665
- id: 'call_w',
666
- name: 'write_file',
667
- args: { path: 'broken.js', content: 'function (\n' },
668
- type: 'tool_call',
669
- })) as { content: string; artifact: { syntax_error?: string } };
670
- expect(message.content).toContain('[syntax-check warning');
671
- expect(message.artifact.syntax_error).toBe('node --check');
672
- });
673
-
674
- it('write_file in strict mode throws on syntax error', async () => {
675
- const cwd = await createTempDir();
676
- const bundle = createLocalCodingToolBundle({
677
- cwd,
678
- postEditSyntaxCheck: 'strict',
679
- });
680
- const writeTool = bundle.tools.find((tt) => tt.name === 'write_file');
681
- await expect(
682
- writeTool!.invoke({
683
- id: 'call_w',
684
- name: 'write_file',
685
- args: { path: 'broken.js', content: 'function (\n' },
686
- type: 'tool_call',
687
- })
688
- ).rejects.toThrow(/syntax check failed/);
689
- });
690
- });
691
-
692
- describe('compile_check', () => {
693
- it('reports "no recognised project marker" when there are none', async () => {
694
- const cwd = await createTempDir();
695
- const checkTool = createCompileCheckTool({ cwd });
696
- const message = (await checkTool.invoke({
697
- id: 'call_c',
698
- name: 'compile_check',
699
- args: {},
700
- type: 'tool_call',
701
- })) as { content: string; artifact: { ran: boolean; kind: string } };
702
- expect(message.content).toContain('no recognised project marker');
703
- expect(message.artifact.ran).toBe(false);
704
- expect(message.artifact.kind).toBe('unknown');
705
- });
706
-
707
- it('honours an explicit command override and reports exit code', async () => {
708
- const cwd = await createTempDir();
709
- const checkTool = createCompileCheckTool({ cwd });
710
- const message = (await checkTool.invoke({
711
- id: 'call_c2',
712
- name: 'compile_check',
713
- args: { command: 'echo hello && false' },
714
- type: 'tool_call',
715
- })) as {
716
- content: string;
717
- artifact: { passed: boolean; exit_code: number | null };
718
- };
719
- expect(message.content).toContain('FAILED');
720
- expect(message.content).toContain('hello');
721
- expect(message.artifact.passed).toBe(false);
722
- expect(message.artifact.exit_code).not.toBe(0);
723
- });
724
- });
725
-
726
- describe('local search fallback', () => {
727
- beforeEach(() => {
728
- _resetRipgrepCacheForTests();
729
- });
730
-
731
- it('finds matches via the Node fallback when ripgrep is missing', async () => {
732
- const cwd = await createTempDir();
733
- await fsWriteFile(join(cwd, 'a.ts'), 'const needle = 42;\n', 'utf8');
734
- await fsWriteFile(join(cwd, 'b.ts'), 'const haystack = 1;\n', 'utf8');
735
-
736
- const bundle = createLocalCodingToolBundle({
737
- cwd,
738
- env: { PATH: '/nonexistent' },
739
- });
740
- const grepTool = bundle.tools.find((t_) => t_.name === 'grep_search');
741
- const result = await grepTool!.invoke({ pattern: 'needle' });
742
- expect(String(result)).toContain('a.ts');
743
- expect(String(result)).toContain('needle');
744
- });
745
- });
746
-
747
- describe('codex review fixes', () => {
748
- describe('executeLocalCode bash args (Codex P2 #1)', () => {
749
- it('passes input.args as positional shell parameters when lang is bash', async () => {
750
- const cwd = await createTempDir();
751
- const result = await executeLocalCode(
752
- {
753
- lang: 'bash',
754
- // Echo every positional arg space-separated. With the bug,
755
- // $@ is empty because args were dropped.
756
- code: 'echo "args:$@"',
757
- args: ['hello', 'world'],
758
- },
759
- { cwd }
760
- );
761
- expect(result.exitCode).toBe(0);
762
- expect(result.stdout.trim()).toBe('args:hello world');
763
- });
764
-
765
- it('still works when lang is bash and args is missing', async () => {
766
- const cwd = await createTempDir();
767
- const result = await executeLocalCode(
768
- { lang: 'bash', code: 'echo plain' },
769
- { cwd }
770
- );
771
- expect(result.exitCode).toBe(0);
772
- expect(result.stdout.trim()).toBe('plain');
773
- });
774
- });
775
-
776
- describe('ripgrep cache backend scope (Codex P2 #2)', () => {
777
- it('does not bleed an "rg available" verdict from one backend to another', async () => {
778
- // Backend A: pretends rg works (returns a fake spawn whose
779
- // process exits 0 on every call). The cache should record true
780
- // for THIS backend.
781
- const okBackend = jest.fn(
782
- (cmd: string, _args: string[], _opts: unknown) => {
783
- const ok = require('child_process').spawn('echo', [cmd]);
784
- return ok;
785
- }
786
- ) as unknown as t.LocalSpawn;
787
- // Backend B: pretends rg does not exist (returns a child that
788
- // exits 127, the "command not found" code).
789
- const missingBackend = jest.fn(
790
- (_cmd: string, _args: string[], _opts: unknown) => {
791
- const child = require('child_process').spawn('sh', [
792
- '-c',
793
- 'exit 127',
794
- ]);
795
- return child;
796
- }
797
- ) as unknown as t.LocalSpawn;
798
-
799
- _resetRipgrepCacheForTests();
800
-
801
- // Build two bundles with distinct backends.
802
- const cwdA = await createTempDir();
803
- const cwdB = await createTempDir();
804
- await fsWriteFile(join(cwdA, 'a.ts'), 'needle\n', 'utf8');
805
- await fsWriteFile(join(cwdB, 'b.ts'), 'needle\n', 'utf8');
806
-
807
- const bundleA = createLocalCodingToolBundle({
808
- cwd: cwdA,
809
- exec: { spawn: okBackend },
810
- });
811
- const bundleB = createLocalCodingToolBundle({
812
- cwd: cwdB,
813
- exec: { spawn: missingBackend },
814
- });
815
-
816
- // Run grep against A first — populates cache for A's backend.
817
- await bundleA.tools
818
- .find((t_) => t_.name === 'grep_search')!
819
- .invoke({
820
- pattern: 'needle',
821
- });
822
- // Run grep against B — must NOT see cached "true" from A's
823
- // backend. With the bug, B would try to spawn rg, fail, and
824
- // throw instead of falling back to the Node walker.
825
- const bResult = await bundleB.tools
826
- .find((t_) => t_.name === 'grep_search')!
827
- .invoke({ pattern: 'needle' });
828
- expect(String(bResult)).toContain('needle');
829
- });
830
- });
831
-
832
- describe('additionalRoots resolved against workspace root (Codex P2 #3)', () => {
833
- it('treats relative additionalRoots as siblings of root, not of process.cwd', async () => {
834
- const parent = await createTempDir();
835
- const fs = await import('fs/promises');
836
- await fs.mkdir(join(parent, 'app'), { recursive: true });
837
- await fs.mkdir(join(parent, 'shared'), { recursive: true });
838
- await fsWriteFile(join(parent, 'shared/lib.ts'), 'X\n', 'utf8');
839
-
840
- const bundle = createLocalCodingToolBundle({
841
- workspace: {
842
- root: join(parent, 'app'),
843
- additionalRoots: ['../shared'],
844
- },
845
- });
846
- const readTool = bundle.tools.find(
847
- (t_) => t_.name === Constants.READ_FILE
848
- );
849
- // Without the fix, '../shared/lib.ts' would resolve relative to
850
- // process.cwd (this test runner), miss the boundary check, and
851
- // throw "Path is outside the local workspace".
852
- const result = await readTool!.invoke({
853
- id: 'c',
854
- name: Constants.READ_FILE,
855
- args: { path: join(parent, 'shared/lib.ts') },
856
- type: 'tool_call',
857
- });
858
- expect(JSON.stringify(result)).toContain('X');
859
- });
860
- });
861
- });
862
-
863
- describe('codex review fixes (round 2)', () => {
864
- describe('streaming output cap (Codex P1)', () => {
865
- const {
866
- spawnLocalProcess,
867
- _resetLocalEngineWarningsForTests: _,
868
- } = require('../local/LocalExecutionEngine');
869
-
870
- it('hard-kills the child when total streamed bytes exceed maxSpawnedBytes', async () => {
871
- // Cap at 64 KiB. `yes` would otherwise run unbounded.
872
- const start = Date.now();
873
- const result = await spawnLocalProcess('yes', [], {
874
- timeoutMs: 30_000,
875
- maxSpawnedBytes: 64 * 1024,
876
- sandbox: { enabled: false },
877
- });
878
- const elapsed = Date.now() - start;
879
- // Killed promptly (much sooner than the 30s timeout).
880
- expect(elapsed).toBeLessThan(5000);
881
- // Process was killed by the overflow guard, not by timeout.
882
- expect(result.timedOut).toBe(false);
883
- expect(result.exitCode).not.toBe(0);
884
- // We DID see some output before the kill.
885
- expect(result.stdout.length).toBeGreaterThan(0);
886
- });
887
-
888
- it('spills overflow to a temp file (full output recoverable post-cap)', async () => {
889
- // Generate ~200 KiB of output with a 32 KiB inline cap → spill.
890
- const result = await spawnLocalProcess(
891
- 'bash',
892
- ['-c', 'head -c 200000 /dev/urandom | base64 | head -c 200000'],
893
- {
894
- timeoutMs: 10_000,
895
- maxOutputChars: 8_000, // inline cap = 16 KiB; ~200 KiB → overflow
896
- maxSpawnedBytes: 1024 * 1024, // 1 MiB hard cap
897
- sandbox: { enabled: false },
898
- }
899
- );
900
- expect(result.exitCode).toBe(0);
901
- expect(result.fullOutputPath).toBeTruthy();
902
- const fs = await import('fs/promises');
903
- const spilled = await fs.readFile(
904
- result.fullOutputPath as string,
905
- 'utf8'
906
- );
907
- // The spill file holds more bytes than the in-memory truncation.
908
- expect(spilled.length).toBeGreaterThan(result.stdout.length);
909
- });
910
-
911
- it('does not create a spill file for small outputs', async () => {
912
- const result = await spawnLocalProcess('bash', ['-c', 'echo small'], {
913
- timeoutMs: 5_000,
914
- sandbox: { enabled: false },
915
- });
916
- expect(result.fullOutputPath).toBeUndefined();
917
- expect(result.stdout.trim()).toBe('small');
918
- });
919
- });
920
-
921
- describe('bash_tool args (Codex P2)', () => {
922
- it('populates positional shell parameters from input.args', async () => {
923
- const cwd = await createTempDir();
924
- const bundle = createLocalCodingToolBundle({ cwd });
925
- const bashTool = bundle.tools.find(
926
- (tt) => tt.name === Constants.BASH_TOOL
927
- );
928
- const result = await bashTool!.invoke({
929
- id: 'b1',
930
- name: Constants.BASH_TOOL,
931
- args: {
932
- command: 'echo "first=$1 second=$2"',
933
- args: ['hello', 'world'],
934
- },
935
- type: 'tool_call',
936
- });
937
- const text = JSON.stringify(result);
938
- expect(text).toContain('first=hello second=world');
939
- });
940
-
941
- it('still works when args is missing', async () => {
942
- const cwd = await createTempDir();
943
- const bundle = createLocalCodingToolBundle({ cwd });
944
- const bashTool = bundle.tools.find(
945
- (tt) => tt.name === Constants.BASH_TOOL
946
- );
947
- const result = await bashTool!.invoke({
948
- id: 'b2',
949
- name: Constants.BASH_TOOL,
950
- args: { command: 'echo plain' },
951
- type: 'tool_call',
952
- });
953
- expect(JSON.stringify(result)).toContain('plain');
954
- });
955
- });
956
- });
957
-
958
- describe('codex review fixes (round 3)', () => {
959
- describe('validateBashCommand honours configured shell (Codex P1 #6)', () => {
960
- it('routes the -n preflight through `local.shell` when set', async () => {
961
- // Spawn calls go through the config'd backend; intercept and
962
- // assert which shell binary the syntax check picks.
963
- const calls: string[] = [];
964
- const intercept: t.LocalSpawn = ((
965
- command: string,
966
- args: string[],
967
- opts: import('child_process').SpawnOptions
968
- ) => {
969
- calls.push(command);
970
- // Fall through to a real spawn so the call resolves cleanly.
971
- const { spawn: realSpawn } =
972
- require('child_process') as typeof import('child_process');
973
- return realSpawn(command, args, opts);
974
- }) as unknown as t.LocalSpawn;
975
-
976
- const result = await validateBashCommand('echo ok', {
977
- shell: '/bin/sh',
978
- exec: { spawn: intercept },
979
- });
980
- expect(result.valid).toBe(true);
981
- // The very first call is the syntax-check spawn; assert it used
982
- // /bin/sh and not the DEFAULT_SHELL fallback.
983
- expect(calls[0]).toBe('/bin/sh');
984
- });
985
- });
986
-
987
- describe('syntax-check probe cache is backend-keyed (Codex P2 #7)', () => {
988
- it('does not bleed an "rg/node/python available" verdict from one backend to another', async () => {
989
- _resetSyntaxCheckProbeCacheForTests();
990
-
991
- // Backend A: probes succeed (real spawn).
992
- const realSpawn = (
993
- require('child_process') as typeof import('child_process')
994
- ).spawn;
995
- const okBackend: t.LocalSpawn = ((
996
- cmd: string,
997
- args: string[],
998
- opts: import('child_process').SpawnOptions
999
- ) => realSpawn(cmd, args, opts)) as unknown as t.LocalSpawn;
1000
- // Backend B: probes always fail with exit 127.
1001
- const missingBackend: t.LocalSpawn = ((
1002
- _cmd: string,
1003
- _args: string[],
1004
- opts: import('child_process').SpawnOptions
1005
- ) =>
1006
- realSpawn('sh', ['-c', 'exit 127'], opts)) as unknown as t.LocalSpawn;
1007
-
1008
- const cwdA = await createTempDir();
1009
- const cwdB = await createTempDir();
1010
- // Write a broken JS file we want syntax-checked.
1011
- await fsWriteFile(join(cwdA, 'a.js'), 'function (\n', 'utf8');
1012
- await fsWriteFile(join(cwdB, 'b.js'), 'function (\n', 'utf8');
1013
-
1014
- // Run on backend A — succeeds, populates A's probe cache for `node`.
1015
- const a = await runPostEditSyntaxCheck(join(cwdA, 'a.js'), {
1016
- cwd: cwdA,
1017
- exec: { spawn: okBackend },
1018
- });
1019
- expect(a?.ok).toBe(false);
1020
-
1021
- // Run on backend B — must NOT see A's cached "node available".
1022
- // With the bug, B would assume `node` works (skipping the probe),
1023
- // try to run `node --check`, get exit 127 from the missingBackend,
1024
- // and return ok=false with a misleading checker.
1025
- // With the fix: B's own probe runs, sees node is missing on this
1026
- // backend, and skips the syntax check (returns ok=true).
1027
- const b = await runPostEditSyntaxCheck(join(cwdB, 'b.js'), {
1028
- cwd: cwdB,
1029
- exec: { spawn: missingBackend },
1030
- });
1031
- expect(b?.ok).toBe(true);
1032
- });
1033
- });
1034
-
1035
- describe('grep passes pattern via -e (Codex P2 #8)', () => {
1036
- it('handles dash-prefixed patterns without rg interpreting them as flags', async () => {
1037
- const cwd = await createTempDir();
1038
- // File contains a literal "-foo" we want to find.
1039
- await fsWriteFile(
1040
- join(cwd, 'flags.txt'),
1041
- 'before\n-foo bar\nafter\n',
1042
- 'utf8'
1043
- );
1044
- const bundle = createLocalCodingToolBundle({ cwd });
1045
- const grepTool = bundle.tools.find((t_) => t_.name === 'grep_search');
1046
- const result = await grepTool!.invoke({
1047
- id: 'g1',
1048
- name: 'grep_search',
1049
- args: { pattern: '-foo' },
1050
- type: 'tool_call',
1051
- });
1052
- const text = JSON.stringify(result);
1053
- // Pre-fix, rg would parse "-foo" as a flag and bail out.
1054
- // Post-fix, "-foo" is matched and the line shows up.
1055
- expect(text).toContain('-foo bar');
1056
- });
1057
- });
1058
- });
1059
-
1060
- describe('codex review fixes (round 4)', () => {
1061
- describe('quoted destructive targets (Codex P1 #9)', () => {
1062
- it('blocks rm -rf "/" (target inside double quotes)', async () => {
1063
- const result = await validateBashCommand('rm -rf "/"');
1064
- expect(result.valid).toBe(false);
1065
- expect(result.errors.join('\n')).toContain('destructive command pattern');
1066
- });
1067
-
1068
- it('blocks rm -rf "$HOME" (env-quoted target)', async () => {
1069
- const result = await validateBashCommand('rm -rf "$HOME"');
1070
- expect(result.valid).toBe(false);
1071
- expect(result.errors.join('\n')).toContain('destructive command pattern');
1072
- });
1073
-
1074
- it('blocks rm -rf \'/\' (target inside single quotes)', async () => {
1075
- const result = await validateBashCommand('rm -rf \'/\'');
1076
- expect(result.valid).toBe(false);
1077
- expect(result.errors.join('\n')).toContain('destructive command pattern');
1078
- });
1079
-
1080
- it('blocks chmod -R 777 "/"', async () => {
1081
- const result = await validateBashCommand('chmod -R 777 "/"');
1082
- expect(result.valid).toBe(false);
1083
- expect(result.errors.join('\n')).toContain('destructive command pattern');
1084
- });
1085
-
1086
- it('still blocks unquoted forms (no regression)', async () => {
1087
- const result = await validateBashCommand('rm -rf /');
1088
- expect(result.valid).toBe(false);
1089
- });
1090
-
1091
- it('does not flag the print-only case echo "rm -rf /"', async () => {
1092
- // The destructive-target inside `echo "..."` is wrapped by the
1093
- // OUTER quotes only — there's no quote pair around the `/`
1094
- // itself — so the quoted-pattern pass should not match.
1095
- const result = await validateBashCommand('echo "rm -rf /"');
1096
- expect(result.valid).toBe(true);
1097
- });
1098
- });
1099
- });
1100
-
1101
- describe('codex review fixes (round 5)', () => {
1102
- describe('maxSpawnedBytes=0 disables the cap (Codex P2 #11)', () => {
1103
- const { spawnLocalProcess } = require('../local/LocalExecutionEngine');
1104
-
1105
- it('does not kill on first byte when maxSpawnedBytes is 0', async () => {
1106
- // Without the fix, `totalSpawnedBytes > 0` triggers on the first
1107
- // byte and the process tree gets killed before `echo` can finish.
1108
- const result = await spawnLocalProcess('bash', ['-c', 'echo hello'], {
1109
- timeoutMs: 5_000,
1110
- maxSpawnedBytes: 0,
1111
- sandbox: { enabled: false },
1112
- });
1113
- expect(result.exitCode).toBe(0);
1114
- expect(result.timedOut).toBe(false);
1115
- expect(result.stdout.trim()).toBe('hello');
1116
- });
1117
-
1118
- it('lets a moderately noisy command run to completion when cap is 0', async () => {
1119
- // Emit ~40 KiB. Default cap (50 MiB) would also let this through,
1120
- // but the explicit 0 must not flip into the kill path.
1121
- const result = await spawnLocalProcess(
1122
- 'bash',
1123
- ['-c', 'head -c 40000 /dev/urandom | base64 | head -c 40000'],
1124
- {
1125
- timeoutMs: 10_000,
1126
- maxOutputChars: 200_000,
1127
- maxSpawnedBytes: 0,
1128
- sandbox: { enabled: false },
1129
- }
1130
- );
1131
- expect(result.exitCode).toBe(0);
1132
- expect(result.timedOut).toBe(false);
1133
- expect(result.stdout.length).toBeGreaterThan(0);
1134
- });
1135
- });
1136
-
1137
- describe('spill path is ESM-safe (Codex P1 #12)', () => {
1138
- // The spill path used to do `require('fs')` inside an ESM-shipped
1139
- // module — fine in CJS test runs, would throw `ReferenceError` in
1140
- // any ESM consumer that triggered the overflow path. Pin the
1141
- // happy path here; the static `createWriteStream` import means a
1142
- // ReferenceError would surface as a test failure regardless of
1143
- // which build runs the test.
1144
-
1145
- const { spawnLocalProcess } = require('../local/LocalExecutionEngine');
1146
-
1147
- it('writes a spill file without a runtime require', async () => {
1148
- const result = await spawnLocalProcess(
1149
- 'bash',
1150
- ['-c', 'head -c 40000 /dev/urandom | base64 | head -c 40000'],
1151
- {
1152
- timeoutMs: 10_000,
1153
- // tiny inline cap → guaranteed overflow → ensureSpill() runs
1154
- maxOutputChars: 4_000,
1155
- maxSpawnedBytes: 1024 * 1024,
1156
- sandbox: { enabled: false },
1157
- }
1158
- );
1159
- expect(result.exitCode).toBe(0);
1160
- expect(result.fullOutputPath).toBeTruthy();
1161
- const fs = await import('fs/promises');
1162
- const spilled = await fs.readFile(
1163
- result.fullOutputPath as string,
1164
- 'utf8'
1165
- );
1166
- expect(spilled.length).toBeGreaterThan(result.stdout.length);
1167
- });
1168
- });
1169
-
1170
- describe('sandbox config: loopback bridge access (Codex P1 #14)', () => {
1171
- const {
1172
- buildSandboxRuntimeConfig,
1173
- } = require('../local/LocalExecutionEngine');
1174
-
1175
- it('seeds allowedDomains with loopback hosts so the bridge works under sandbox', () => {
1176
- const cfg = buildSandboxRuntimeConfig({}, '/tmp/ws', () => []);
1177
- expect(cfg.network.allowedDomains).toEqual(
1178
- expect.arrayContaining(['127.0.0.1', 'localhost', '::1'])
1179
- );
1180
- });
1181
-
1182
- it('keeps user-supplied allowedDomains and does not duplicate loopback', () => {
1183
- const cfg = buildSandboxRuntimeConfig(
1184
- {
1185
- sandbox: {
1186
- network: { allowedDomains: ['api.example.com', '127.0.0.1'] },
1187
- },
1188
- },
1189
- '/tmp/ws',
1190
- () => []
1191
- );
1192
- const occurrences = cfg.network.allowedDomains.filter(
1193
- (d: string) => d === '127.0.0.1'
1194
- ).length;
1195
- expect(occurrences).toBe(1);
1196
- expect(cfg.network.allowedDomains).toContain('api.example.com');
1197
- });
1198
-
1199
- it('respects deniedDomains overriding the loopback seed', () => {
1200
- const cfg = buildSandboxRuntimeConfig(
1201
- { sandbox: { network: { deniedDomains: ['127.0.0.1'] } } },
1202
- '/tmp/ws',
1203
- () => []
1204
- );
1205
- expect(cfg.network.allowedDomains).not.toContain('127.0.0.1');
1206
- // The other loopback aliases still get seeded — the host opted
1207
- // out of just `127.0.0.1`, not all loopback.
1208
- expect(cfg.network.allowedDomains).toEqual(
1209
- expect.arrayContaining(['localhost', '::1'])
1210
- );
1211
- });
1212
- });
1213
-
1214
- describe('sandbox allowWrite includes additionalRoots (Codex P2 #15)', () => {
1215
- const {
1216
- buildSandboxRuntimeConfig,
1217
- } = require('../local/LocalExecutionEngine');
1218
-
1219
- it('adds workspace.additionalRoots to allowWrite alongside cwd', () => {
1220
- const cfg = buildSandboxRuntimeConfig(
1221
- {
1222
- cwd: '/tmp/repo/app',
1223
- workspace: {
1224
- root: '/tmp/repo/app',
1225
- additionalRoots: ['/tmp/repo/shared'],
1226
- },
1227
- },
1228
- '/tmp/repo/app',
1229
- () => ['/tmp/runtime-default']
1230
- );
1231
- expect(cfg.filesystem.allowWrite).toEqual(
1232
- expect.arrayContaining([
1233
- '/tmp/repo/app',
1234
- '/tmp/repo/shared',
1235
- '/tmp/runtime-default',
1236
- ])
1237
- );
1238
- });
1239
-
1240
- it('resolves relative additionalRoots against the workspace root', () => {
1241
- const cfg = buildSandboxRuntimeConfig(
1242
- {
1243
- cwd: '/tmp/repo/app',
1244
- workspace: {
1245
- root: '/tmp/repo/app',
1246
- additionalRoots: ['../shared'],
1247
- },
1248
- },
1249
- '/tmp/repo/app',
1250
- () => []
1251
- );
1252
- // ../shared anchored to root: /tmp/repo/app -> /tmp/repo/shared.
1253
- expect(cfg.filesystem.allowWrite).toContain('/tmp/repo/shared');
1254
- });
1255
-
1256
- it('falls back to cwd-only when no additionalRoots are configured', () => {
1257
- const cfg = buildSandboxRuntimeConfig(
1258
- { cwd: '/tmp/ws' },
1259
- '/tmp/ws',
1260
- () => ['/tmp/runtime-default']
1261
- );
1262
- expect(cfg.filesystem.allowWrite).toEqual([
1263
- '/tmp/ws',
1264
- '/tmp/runtime-default',
1265
- ]);
1266
- });
1267
-
1268
- it('honours an explicit allowWrite override (no auto-seeding)', () => {
1269
- const cfg = buildSandboxRuntimeConfig(
1270
- {
1271
- cwd: '/tmp/ws',
1272
- workspace: {
1273
- root: '/tmp/ws',
1274
- additionalRoots: ['/tmp/extra'],
1275
- },
1276
- sandbox: { filesystem: { allowWrite: ['/explicit/path'] } },
1277
- },
1278
- '/tmp/ws',
1279
- () => ['/tmp/runtime-default']
1280
- );
1281
- expect(cfg.filesystem.allowWrite).toEqual(['/explicit/path']);
1282
- });
1283
- });
1284
-
1285
- describe('glob_search surfaces ripgrep failures (Codex P2 #13)', () => {
1286
- it('returns an explicit error (not "No files found.") when rg exits non-zero', async () => {
1287
- _resetRipgrepCacheForTests();
1288
- // Inject a spawn backend that pretends rg exists for the
1289
- // availability probe but fails the actual `rg --files` call
1290
- // with exit 2 + stderr — the failure mode the codex comment
1291
- // flagged. Pre-fix, glob_search dropped exitCode/stderr on
1292
- // the floor and returned "No files found." regardless.
1293
- const realSpawn = (
1294
- require('child_process') as typeof import('child_process')
1295
- ).spawn;
1296
- const fakeRgBackend: t.LocalSpawn = ((
1297
- cmd: string,
1298
- args: string[],
1299
- opts: import('child_process').SpawnOptions
1300
- ) => {
1301
- if (cmd === 'rg' && args[0] === '--version') {
1302
- return realSpawn('sh', ['-c', 'exit 0'], opts);
1303
- }
1304
- if (cmd === 'rg') {
1305
- return realSpawn(
1306
- 'sh',
1307
- ['-c', 'printf \'rg: bad glob target\\n\' >&2; exit 2'],
1308
- opts
1309
- );
1310
- }
1311
- return realSpawn(cmd, args, opts);
1312
- }) as unknown as t.LocalSpawn;
1313
-
1314
- const cwd = await createTempDir();
1315
- const bundle = createLocalCodingToolBundle({
1316
- cwd,
1317
- exec: { spawn: fakeRgBackend },
1318
- });
1319
- const globTool = bundle.tools.find(
1320
- (tt) => tt.name === Constants.GLOB_SEARCH
1321
- );
1322
- const result = await globTool!.invoke({
1323
- id: 'g1',
1324
- name: Constants.GLOB_SEARCH,
1325
- args: { pattern: '**/*' },
1326
- type: 'tool_call',
1327
- });
1328
- const text = JSON.stringify(result);
1329
- expect(text).not.toContain('No files found.');
1330
- expect(text).toContain('glob_search failed');
1331
- expect(text).toContain('bad glob target');
1332
- });
1333
- });
1334
-
1335
- describe('grep_search surfaces ripgrep failures (Codex P2 #23)', () => {
1336
- it('returns an explicit error (not "No matches found.") when rg exits non-zero', async () => {
1337
- _resetRipgrepCacheForTests();
1338
- // Same shape as the glob_search test above. Pre-fix the
1339
- // grep_search rg branch dropped exitCode and reported
1340
- // matches: 0 on a real rg error (codex flagged that
1341
- // glob_search had this fix but grep_search hadn't been
1342
- // updated to match).
1343
- const realSpawn = (
1344
- require('child_process') as typeof import('child_process')
1345
- ).spawn;
1346
- const fakeRgBackend: t.LocalSpawn = ((
1347
- cmd: string,
1348
- args: string[],
1349
- opts: import('child_process').SpawnOptions
1350
- ) => {
1351
- if (cmd === 'rg' && args[0] === '--version') {
1352
- return realSpawn('sh', ['-c', 'exit 0'], opts);
1353
- }
1354
- if (cmd === 'rg') {
1355
- return realSpawn(
1356
- 'sh',
1357
- ['-c', 'printf \'rg: io error reading dir\\n\' >&2; exit 2'],
1358
- opts
1359
- );
1360
- }
1361
- return realSpawn(cmd, args, opts);
1362
- }) as unknown as t.LocalSpawn;
1363
-
1364
- const cwd = await createTempDir();
1365
- const bundle = createLocalCodingToolBundle({
1366
- cwd,
1367
- exec: { spawn: fakeRgBackend },
1368
- });
1369
- const grepTool = bundle.tools.find(
1370
- (tt) => tt.name === Constants.GREP_SEARCH
1371
- );
1372
- const result = await grepTool!.invoke({
1373
- id: 'gr1',
1374
- name: Constants.GREP_SEARCH,
1375
- args: { pattern: 'needle' },
1376
- type: 'tool_call',
1377
- });
1378
- const text = JSON.stringify(result);
1379
- expect(text).not.toContain('No matches found.');
1380
- expect(text).toContain('grep_search failed');
1381
- expect(text).toContain('io error reading dir');
1382
- });
1383
- });
1384
- });
1385
-
1386
- describe('codex review fixes (round 6)', () => {
1387
- describe('destructive guard handles `--` end-of-options (Codex P1 #20)', () => {
1388
- it('blocks rm -rf -- "/" (-- between flags and quoted target)', async () => {
1389
- const result = await validateBashCommand('rm -rf -- "/"');
1390
- expect(result.valid).toBe(false);
1391
- expect(result.errors.join('\n')).toContain('destructive command pattern');
1392
- });
1393
-
1394
- it('blocks rm -rf -- / (-- between flags and bare target)', async () => {
1395
- const result = await validateBashCommand('rm -rf -- /');
1396
- expect(result.valid).toBe(false);
1397
- expect(result.errors.join('\n')).toContain('destructive command pattern');
1398
- });
1399
-
1400
- it('blocks chmod -R 777 -- "/"', async () => {
1401
- const result = await validateBashCommand('chmod -R 777 -- "/"');
1402
- expect(result.valid).toBe(false);
1403
- expect(result.errors.join('\n')).toContain('destructive command pattern');
1404
- });
1405
-
1406
- it('blocks rm -rf -- "$HOME"', async () => {
1407
- const result = await validateBashCommand('rm -rf -- "$HOME"');
1408
- expect(result.valid).toBe(false);
1409
- expect(result.errors.join('\n')).toContain('destructive command pattern');
1410
- });
1411
-
1412
- it('still allows benign `--` usage (no destructive target)', async () => {
1413
- // `find` uses `--` to separate options from filenames; benign.
1414
- const result = await validateBashCommand('find . -- -name "*.ts"');
1415
- expect(result.valid).toBe(true);
1416
- });
1417
- });
1418
-
1419
- describe('compile_check enforces validateBashCommand + readOnly (Codex P1 #21)', () => {
1420
- it('refuses a destructive command override (rm -rf "/")', async () => {
1421
- const cwd = await createTempDir();
1422
- const compile = createCompileCheckTool({ cwd });
1423
- const result = await compile.invoke({
1424
- id: 'cc1',
1425
- name: Constants.COMPILE_CHECK,
1426
- args: { command: 'rm -rf "/"' },
1427
- type: 'tool_call',
1428
- });
1429
- const text = JSON.stringify(result);
1430
- expect(text).toContain('compile_check refused to run');
1431
- expect(text).toContain('destructive command pattern');
1432
- });
1433
-
1434
- it('refuses a mutating command override under readOnly: true', async () => {
1435
- const cwd = await createTempDir();
1436
- const compile = createCompileCheckTool({ cwd, readOnly: true });
1437
- const result = await compile.invoke({
1438
- id: 'cc2',
1439
- name: Constants.COMPILE_CHECK,
1440
- // `touch` is in mutatingCommandPattern — fine outside readOnly,
1441
- // blocked under readOnly.
1442
- args: { command: 'touch /tmp/lc-cc-should-not-create' },
1443
- type: 'tool_call',
1444
- });
1445
- const text = JSON.stringify(result);
1446
- expect(text).toContain('compile_check refused to run');
1447
- expect(text).toMatch(/read-only|mutate/i);
1448
- });
1449
-
1450
- it('still allows benign override commands (echo)', async () => {
1451
- const cwd = await createTempDir();
1452
- const compile = createCompileCheckTool({ cwd });
1453
- const result = await compile.invoke({
1454
- id: 'cc3',
1455
- name: Constants.COMPILE_CHECK,
1456
- args: { command: 'echo hello' },
1457
- type: 'tool_call',
1458
- });
1459
- const text = JSON.stringify(result);
1460
- expect(text).not.toContain('refused to run');
1461
- });
1462
- });
1463
- });
1464
-
1465
- describe('comprehensive review (round 7) — manual finding C', () => {
1466
- describe('nested-shell destructive payload (manual #C)', () => {
1467
- it('blocks bash -lc "rm -rf $HOME"', async () => {
1468
- const result = await validateBashCommand('bash -lc "rm -rf $HOME"');
1469
- expect(result.valid).toBe(false);
1470
- expect(result.errors.join('\n')).toMatch(/destructive command pattern/);
1471
- });
1472
-
1473
- it('blocks sh -c "chmod -R 777 /"', async () => {
1474
- const result = await validateBashCommand('sh -c \'chmod -R 777 /\'');
1475
- expect(result.valid).toBe(false);
1476
- expect(result.errors.join('\n')).toMatch(/destructive command pattern/);
1477
- });
1478
-
1479
- it('blocks eval "rm -rf /"', async () => {
1480
- const result = await validateBashCommand('eval \'rm -rf /\'');
1481
- expect(result.valid).toBe(false);
1482
- expect(result.errors.join('\n')).toMatch(/destructive command pattern/);
1483
- });
1484
-
1485
- it('still allows benign nested shell (echo)', async () => {
1486
- const result = await validateBashCommand('bash -lc "echo hello"');
1487
- expect(result.valid).toBe(true);
1488
- });
1489
- });
1490
- });
1491
-
1492
- describe('comprehensive review (round 7) — manual finding D', () => {
1493
- describe('fallback grep DoS guardrails', () => {
1494
- it('rejects oversize patterns before compile', async () => {
1495
- const cwd = await createTempDir();
1496
- const bundle = createLocalCodingToolBundle({ cwd });
1497
- const grepTool = bundle.tools.find(
1498
- (tt) => tt.name === Constants.GREP_SEARCH
1499
- );
1500
- const result = await grepTool!.invoke({
1501
- id: 'g-long',
1502
- name: Constants.GREP_SEARCH,
1503
- // 2 KiB pattern — over the 1 KiB cap.
1504
- args: { pattern: 'a'.repeat(2048) },
1505
- type: 'tool_call',
1506
- });
1507
- const text = JSON.stringify(result);
1508
- // Either the rg path runs (and matches nothing on an empty
1509
- // dir) or — when rg is unavailable — the fallback rejects via
1510
- // FallbackGrepError. We only assert the fallback shape when
1511
- // it triggers.
1512
- if (text.includes('node-fallback')) {
1513
- expect(text).toContain('grep_search refused the pattern');
1514
- expect(text).toContain('exceeds');
1515
- }
1516
- });
1517
-
1518
- it('rejects nested-quantifier patterns (catastrophic backtracking)', async () => {
1519
- const cwd = await createTempDir();
1520
- const bundle = createLocalCodingToolBundle({ cwd });
1521
- const grepTool = bundle.tools.find(
1522
- (tt) => tt.name === Constants.GREP_SEARCH
1523
- );
1524
- const result = await grepTool!.invoke({
1525
- id: 'g-evil',
1526
- name: Constants.GREP_SEARCH,
1527
- args: { pattern: '(a+)+$' },
1528
- type: 'tool_call',
1529
- });
1530
- const text = JSON.stringify(result);
1531
- if (text.includes('node-fallback')) {
1532
- expect(text).toContain('catastrophic backtracking');
1533
- }
1534
- });
1535
- });
1536
- });
1537
-
1538
- describe('comprehensive review (round 7) — manual finding E', () => {
1539
- describe('fileCheckpointer exposed via ToolNode auto-bind path', () => {
1540
- it('Run/ToolNode-style bind makes the checkpointer reachable when fileCheckpointing is true', () => {
1541
- const node = new ToolNode({
1542
- tools: [],
1543
- toolExecution: {
1544
- engine: 'local',
1545
- local: { fileCheckpointing: true },
1546
- },
1547
- });
1548
- const cp = node.getFileCheckpointer();
1549
- expect(cp).toBeDefined();
1550
- expect(typeof cp?.captureBeforeWrite).toBe('function');
1551
- expect(typeof cp?.rewind).toBe('function');
1552
- });
1553
-
1554
- it('returns undefined when fileCheckpointing is not enabled', () => {
1555
- const node = new ToolNode({
1556
- tools: [],
1557
- toolExecution: { engine: 'local' },
1558
- });
1559
- expect(node.getFileCheckpointer()).toBeUndefined();
1560
- });
1561
- });
1562
-
1563
- describe('fileCheckpointer reachable through Run.getFileCheckpointer / Run.rewindFiles (audit-of-audit follow-up)', () => {
1564
- // The round-7 fix exposed `getFileCheckpointer()` on ToolNode but
1565
- // the normal `Run.create(...)` path constructs the ToolNode inline
1566
- // inside StandardGraph and dropped the reference, so the public
1567
- // `RunConfig.toolExecution.local.fileCheckpointing` flag was still
1568
- // a no-op for Run callers (only direct `new ToolNode(...)` users
1569
- // could reach it). Pin the round-trip: a Run constructed through
1570
- // the standard config path must surface the same checkpointer the
1571
- // graph wired into its ToolNode, and `Run.rewindFiles()` must
1572
- // restore captured paths.
1573
- it('exposes the checkpointer via Run.getFileCheckpointer + restores through Run.rewindFiles', async () => {
1574
- const { Run } = await import('@/run');
1575
- const fs = await import('fs/promises');
1576
- const cwd = await createTempDir();
1577
- const file = join(cwd, 'tracked.txt');
1578
- await fs.writeFile(file, 'before\n');
1579
-
1580
- const run = await Run.create<t.IState>({
1581
- runId: 'run-checkpoint-roundtrip',
1582
- graphConfig: {
1583
- type: 'standard',
1584
- llmConfig: { provider: Providers.OPENAI, model: 'gpt-4o' },
1585
- },
1586
- toolExecution: {
1587
- engine: 'local',
1588
- local: { cwd, fileCheckpointing: true },
1589
- },
1590
- });
1591
-
1592
- // Reachable straight off Run — used to be undefined here even
1593
- // when the config flag was true.
1594
- const cp = run.getFileCheckpointer();
1595
- expect(cp).toBeDefined();
1596
-
1597
- // Capture, mutate, rewind via Run.rewindFiles() (the API the
1598
- // public JSDoc on `LocalExecutionConfig.fileCheckpointing`
1599
- // promises).
1600
- await cp!.captureBeforeWrite(file);
1601
- await fs.writeFile(file, 'mutated\n');
1602
- const restored = await run.rewindFiles();
1603
- expect(restored).toBeGreaterThanOrEqual(1);
1604
- expect(await fs.readFile(file, 'utf8')).toBe('before\n');
1605
- });
1606
-
1607
- it('Run.rewindFiles returns 0 when fileCheckpointing is disabled', async () => {
1608
- const { Run } = await import('@/run');
1609
- const run = await Run.create<t.IState>({
1610
- runId: 'run-no-checkpoint',
1611
- graphConfig: {
1612
- type: 'standard',
1613
- llmConfig: { provider: Providers.OPENAI, model: 'gpt-4o' },
1614
- },
1615
- toolExecution: { engine: 'local' },
1616
- });
1617
- expect(run.getFileCheckpointer()).toBeUndefined();
1618
- expect(await run.rewindFiles()).toBe(0);
1619
- });
1620
-
1621
- it('checkpointer survives Graph.clearHeavyState so post-completion rewind works (Codex P1 #32)', async () => {
1622
- // The original round-7 wiring nulled `_fileCheckpointer` in
1623
- // clearHeavyState — but processStream calls clearHeavyState
1624
- // in its finally block, so the host could never reach
1625
- // rewindFiles AFTER the run completed (which is exactly when
1626
- // rollback is most often needed). Pin that calling
1627
- // clearHeavyState directly DOES NOT drop the checkpointer.
1628
- const { Run } = await import('@/run');
1629
- const fs = await import('fs/promises');
1630
- const cwd = await createTempDir();
1631
- const file = join(cwd, 'after-completion.txt');
1632
- await fs.writeFile(file, 'pre-run\n');
1633
-
1634
- const run = await Run.create<t.IState>({
1635
- runId: 'run-cp-survives-clear',
1636
- graphConfig: {
1637
- type: 'standard',
1638
- llmConfig: { provider: Providers.OPENAI, model: 'gpt-4o' },
1639
- },
1640
- toolExecution: {
1641
- engine: 'local',
1642
- local: { cwd, fileCheckpointing: true },
1643
- },
1644
- });
1645
- const cp = run.getFileCheckpointer();
1646
- expect(cp).toBeDefined();
1647
-
1648
- await cp!.captureBeforeWrite(file);
1649
- await fs.writeFile(file, 'mutated-by-tool\n');
1650
-
1651
- // Simulate end-of-run cleanup (what processStream's finally
1652
- // block does). Pre-fix this nulled the checkpointer.
1653
- run.Graph?.clearHeavyState();
1654
-
1655
- // Same checkpointer instance must still be reachable AFTER
1656
- // clearHeavyState — that's the whole point of the fix.
1657
- expect(run.getFileCheckpointer()).toBe(cp);
1658
-
1659
- // Host calls rewindFiles after processStream returned.
1660
- const restored = await run.rewindFiles();
1661
- expect(restored).toBeGreaterThanOrEqual(1);
1662
- expect(await fs.readFile(file, 'utf8')).toBe('pre-run\n');
1663
- });
1664
- });
1665
- });
1666
-
1667
- describe('comprehensive review (round 8) — Codex P1 #24 / P1 #25', () => {
1668
- describe('JSON post-edit syntax check uses WorkspaceFS (Codex P1 #24)', () => {
1669
- it('routes the JSON read through `local.exec.fs` instead of host fs', async () => {
1670
- const { runPostEditSyntaxCheck } = require('../local/syntaxCheck');
1671
-
1672
- const reads: string[] = [];
1673
- // Custom WorkspaceFS that returns valid JSON for the path the
1674
- // syntax checker asks about. If the checker bypassed our fs and
1675
- // hit the host filesystem instead, `reads` would stay empty
1676
- // AND the validator would silently pass (host file doesn't
1677
- // exist → catch returns undefined → `ok: true`). The "ok: true"
1678
- // would be a FALSE pass, exactly the failure mode codex flagged.
1679
- const fakeFs = {
1680
- readFile: async (p: string, _enc?: 'utf8'): Promise<string> => {
1681
- reads.push(p);
1682
- return '{"valid": true}';
1683
- },
1684
- // unused stubs to satisfy the WorkspaceFS shape — never called
1685
- // by the JSON checker
1686
- writeFile: async () => undefined,
1687
- stat: async () => {
1688
- throw new Error('not implemented');
1689
- },
1690
- readdir: async () => [],
1691
- mkdir: async () => undefined,
1692
- realpath: async (p: string) => p,
1693
- unlink: async () => undefined,
1694
- open: async () => {
1695
- throw new Error('not implemented');
1696
- },
1697
- };
1698
-
1699
- const ok = await runPostEditSyntaxCheck('/virtual/file.json', {
1700
- exec: { fs: fakeFs as unknown as never },
1701
- });
1702
- expect(ok?.ok).toBe(true);
1703
- expect(reads).toEqual(['/virtual/file.json']);
1704
- });
1705
-
1706
- it('flags invalid JSON returned by the WorkspaceFS', async () => {
1707
- const { runPostEditSyntaxCheck } = require('../local/syntaxCheck');
1708
- const fakeFs = {
1709
- readFile: async () => '{ invalid: json',
1710
- writeFile: async () => undefined,
1711
- stat: async () => {
1712
- throw new Error('not implemented');
1713
- },
1714
- readdir: async () => [],
1715
- mkdir: async () => undefined,
1716
- realpath: async (p: string) => p,
1717
- unlink: async () => undefined,
1718
- open: async () => {
1719
- throw new Error('not implemented');
1720
- },
1721
- };
1722
- const result = await runPostEditSyntaxCheck('/virtual/bad.json', {
1723
- exec: { fs: fakeFs as unknown as never },
1724
- });
1725
- expect(result?.ok).toBe(false);
1726
- expect(result?.checker).toBe('JSON.parse');
1727
- });
1728
- });
1729
-
1730
- describe('compile_check detect uses WorkspaceFS (Codex P1 #25)', () => {
1731
- it('routes project-marker probes through `local.exec.fs`', async () => {
1732
- // Custom FS that pretends `tsconfig.json` exists at the cwd. If
1733
- // detect bypasses our fs and uses host fs/promises, the host
1734
- // path won't have a tsconfig.json and detection falls through
1735
- // to "unknown".
1736
- const stats: string[] = [];
1737
- const fakeFs = {
1738
- readFile: async () => '',
1739
- writeFile: async () => undefined,
1740
- stat: async (p: string) => {
1741
- stats.push(p);
1742
- if (p.endsWith('tsconfig.json')) {
1743
- return {
1744
- isFile: () => true,
1745
- isDirectory: () => false,
1746
- size: 0,
1747
- };
1748
- }
1749
- throw new Error('ENOENT');
1750
- },
1751
- readdir: async () => [],
1752
- mkdir: async () => undefined,
1753
- realpath: async (p: string) => p,
1754
- unlink: async () => undefined,
1755
- open: async () => {
1756
- throw new Error('not implemented');
1757
- },
1758
- };
1759
-
1760
- const compile = createCompileCheckTool({
1761
- cwd: '/virtual/repo',
1762
- exec: { fs: fakeFs as unknown as never },
1763
- });
1764
- // Don't actually run anything — we only care that detect()
1765
- // saw the tsconfig and picked typescript. The validateBashCommand
1766
- // call inside the tool will still try to spawn, but we don't
1767
- // need to assert on its outcome; the artifact carries the
1768
- // detection result.
1769
- const result = await compile.invoke({
1770
- id: 'cc',
1771
- name: Constants.COMPILE_CHECK,
1772
- args: { command: 'echo skip-spawn' },
1773
- type: 'tool_call',
1774
- });
1775
- // Just confirm at least one stat was made through our fake fs
1776
- // (auto-detect path). Even with the explicit override we use
1777
- // here, the tool path doesn't run detect — but the cwd-init
1778
- // and validateBashCommand still go through the right fs.
1779
- // For the actual detect() invocation, drop the override:
1780
- void result;
1781
- const compile2 = createCompileCheckTool({
1782
- cwd: '/virtual/repo',
1783
- exec: { fs: fakeFs as unknown as never },
1784
- });
1785
- await compile2.invoke({
1786
- id: 'cc2',
1787
- name: Constants.COMPILE_CHECK,
1788
- args: {},
1789
- type: 'tool_call',
1790
- });
1791
- // The tsconfig probe and the package.json probe (if it gets
1792
- // there) happen BEFORE the spawn, so even if spawn fails the
1793
- // stats list captures what detect saw.
1794
- expect(stats.some((p) => p.endsWith('tsconfig.json'))).toBe(true);
1795
- });
1796
- });
1797
- });
1798
-
1799
- describe('comprehensive review (round 9) — Codex P1 (overflow-killed) + audit findings', () => {
1800
- describe('overflow-killed processes report as failures (Codex P1)', () => {
1801
- const { spawnLocalProcess } = require('../local/LocalExecutionEngine');
1802
-
1803
- it('reports overflowKilled=true and a non-null exit code when maxSpawnedBytes is exceeded', async () => {
1804
- // `yes` produces unbounded output. Cap at 16 KiB so the
1805
- // overflow guard fires within milliseconds. Pre-fix the close
1806
- // handler returned `exitCode: null` (signal-killed) and no
1807
- // overflow flag, so callers couldn't tell the run had been
1808
- // force-killed.
1809
- const result = await spawnLocalProcess('yes', [], {
1810
- timeoutMs: 30_000,
1811
- maxSpawnedBytes: 16 * 1024,
1812
- sandbox: { enabled: false },
1813
- });
1814
- expect(result.overflowKilled).toBe(true);
1815
- // SIGKILL'd processes report exitCode=null from Node; we
1816
- // synthesize 137 (128 + SIGKILL) so callers see a non-zero
1817
- // status.
1818
- expect(result.exitCode).not.toBeNull();
1819
- expect(result.exitCode).not.toBe(0);
1820
- expect(result.timedOut).toBe(false);
1821
- });
1822
-
1823
- it('formatLocalOutput surfaces the killed flag', async () => {
1824
- const cwd = await createTempDir();
1825
- const bundle = createLocalCodingToolBundle({
1826
- cwd,
1827
- maxSpawnedBytes: 16 * 1024,
1828
- timeoutMs: 30_000,
1829
- sandbox: { enabled: false },
1830
- });
1831
- const bashTool = bundle.tools.find(
1832
- (tt) => tt.name === Constants.BASH_TOOL
1833
- );
1834
- const result = await bashTool!.invoke({
1835
- id: 'b1',
1836
- name: Constants.BASH_TOOL,
1837
- args: { command: 'yes' },
1838
- type: 'tool_call',
1839
- });
1840
- const text = JSON.stringify(result);
1841
- expect(text).toContain('killed: true');
1842
- expect(text).toContain('local.maxSpawnedBytes');
1843
- });
1844
- });
1845
-
1846
- describe('signal-killed processes report as failures (Codex P2 — generalizes the overflow fix)', () => {
1847
- const { spawnLocalProcess } = require('../local/LocalExecutionEngine');
1848
-
1849
- it('synthesizes a non-zero exit code and surfaces the signal name on `kill -9 $$`', async () => {
1850
- // Script kills its own pgroup with SIGKILL. Pre-fix the close
1851
- // handler dropped the `signal` argument and kept exitCode=null,
1852
- // so this looked like a clean run.
1853
- const result = await spawnLocalProcess(
1854
- 'bash',
1855
- ['-c', 'echo started; kill -9 $$'],
1856
- { timeoutMs: 5_000, sandbox: { enabled: false } }
1857
- );
1858
- // Node may report SIGKILL on the script process or the wrapper;
1859
- // either way exitCode must end up non-null and non-zero.
1860
- expect(result.exitCode).not.toBeNull();
1861
- expect(result.exitCode).not.toBe(0);
1862
- // Signal field is present and matches one of the expected
1863
- // POSIX kill signals.
1864
- expect(result.signal).toMatch(/^SIG/);
1865
- });
1866
-
1867
- it('formatLocalOutput surfaces the signal kill', async () => {
1868
- const cwd = await createTempDir();
1869
- const bundle = createLocalCodingToolBundle({
1870
- cwd,
1871
- timeoutMs: 5_000,
1872
- sandbox: { enabled: false },
1873
- });
1874
- const bashTool = bundle.tools.find(
1875
- (tt) => tt.name === Constants.BASH_TOOL
1876
- );
1877
- const result = await bashTool!.invoke({
1878
- id: 'sig1',
1879
- name: Constants.BASH_TOOL,
1880
- args: { command: 'echo started; kill -9 $$' },
1881
- type: 'tool_call',
1882
- });
1883
- const text = JSON.stringify(result);
1884
- expect(text).toContain('killed: true');
1885
- expect(text).toMatch(/signal=SIG/);
1886
- });
1887
- });
1888
-
1889
- describe('fallback-grep nested-quantifier heuristic catches double-nested groups (audit #1)', () => {
1890
- it('rejects `((a+)+)` (the textbook ReDoS pattern)', async () => {
1891
- _resetRipgrepCacheForTests();
1892
- // Force the fallback path by injecting a backend that says rg
1893
- // is unavailable (the rg --version probe always fails). This
1894
- // way the fallback compileFallbackRegex actually runs.
1895
- const realSpawn = (
1896
- require('child_process') as typeof import('child_process')
1897
- ).spawn;
1898
- const noRgBackend: t.LocalSpawn = ((
1899
- cmd: string,
1900
- args: string[],
1901
- opts: import('child_process').SpawnOptions
1902
- ) => {
1903
- if (cmd === 'rg') {
1904
- return realSpawn('sh', ['-c', 'exit 127'], opts);
1905
- }
1906
- return realSpawn(cmd, args, opts);
1907
- }) as unknown as t.LocalSpawn;
1908
-
1909
- const cwd = await createTempDir();
1910
- const bundle = createLocalCodingToolBundle({
1911
- cwd,
1912
- exec: { spawn: noRgBackend },
1913
- });
1914
- const grepTool = bundle.tools.find(
1915
- (tt) => tt.name === Constants.GREP_SEARCH
1916
- );
1917
- const result = await grepTool!.invoke({
1918
- id: 'gr-evil',
1919
- name: Constants.GREP_SEARCH,
1920
- args: { pattern: '((a+)+)' },
1921
- type: 'tool_call',
1922
- });
1923
- const text = JSON.stringify(result);
1924
- expect(text).toContain('grep_search refused the pattern');
1925
- expect(text).toContain('catastrophic backtracking');
1926
- });
1927
- });
1928
-
1929
- describe('resolveLocalExecutionTools no longer overwrites bundle tools (audit #4)', () => {
1930
- it('CODE_EXECUTION_TOOLS loop does not re-create tools when coding-tools bundle ran first', () => {
1931
- // The bundle path creates bash_tool/execute_code/etc. with a
1932
- // stable identity. Pre-fix the CODE_EXECUTION_TOOLS loop
1933
- // overwrote those instances with fresh ones — wasted work, and
1934
- // the fresh tools wouldn't share the bundle's checkpointer.
1935
- // Pin via tool identity comparison.
1936
- const node1 = new ToolNode({
1937
- tools: [],
1938
- toolExecution: { engine: 'local' },
1939
- });
1940
- // Capture the bash_tool instance
1941
-
1942
- const m1 = (node1 as unknown as { toolMap: Map<string, unknown> })
1943
- .toolMap;
1944
- expect(m1.has(Constants.BASH_TOOL)).toBe(true);
1945
- // Run the resolver again (simulating a fresh ToolNode); the
1946
- // bash_tool instance from the bundle should still be the only
1947
- // one (no overwrite step). Identity comparison would be
1948
- // brittle; assert tool count for the bundle members instead.
1949
- const bundleNames = [
1950
- Constants.BASH_TOOL,
1951
- Constants.EXECUTE_CODE,
1952
- Constants.PROGRAMMATIC_TOOL_CALLING,
1953
- Constants.BASH_PROGRAMMATIC_TOOL_CALLING,
1954
- ];
1955
- for (const name of bundleNames) {
1956
- expect(m1.has(name)).toBe(true);
1957
- }
1958
- });
1959
- });
1960
- });
1961
-
1962
- describe('comprehensive review (round 10) — Codex P1 #28 / P2 #29', () => {
1963
- describe('SIGKILL escalation defeats SIGTERM-trapping processes (Codex P1 #28)', () => {
1964
- const { spawnLocalProcess } = require('../local/LocalExecutionEngine');
1965
-
1966
- it('escalates to SIGKILL when timeoutMs elapses and the child traps SIGTERM', async () => {
1967
- // Trap SIGTERM and loop forever. Pre-fix killProcessTree only
1968
- // sent SIGTERM, so the child kept running, `close` never
1969
- // fired, and the spawn promise hung past timeoutMs. Now SIGKILL
1970
- // escalation kicks in 2s after the SIGTERM and the child dies
1971
- // unconditionally.
1972
- const start = Date.now();
1973
- const result = await spawnLocalProcess(
1974
- 'bash',
1975
- ['-c', 'trap \'\' TERM; while true; do sleep 0.1; done'],
1976
- { timeoutMs: 1500, sandbox: { enabled: false } }
1977
- );
1978
- const elapsed = Date.now() - start;
1979
- // Sanity: the test has to actually have terminated. With the
1980
- // bug the promise hangs and Jest times out after 5s default.
1981
- // Generous upper bound: timeout (1.5s) + escalation (2s) +
1982
- // spawn overhead. Assert under 6s.
1983
- expect(elapsed).toBeLessThan(6000);
1984
- expect(result.timedOut).toBe(true);
1985
- // signal field is populated (SIGKILL after escalation, or
1986
- // possibly SIGTERM if the trap didn't take effect on a
1987
- // particular host).
1988
- expect(result.signal).toMatch(/^SIG/);
1989
- }, 10_000);
1990
- });
1991
-
1992
- describe('ripgrep cache also keys on env (Codex P1 #34)', () => {
1993
- it('does not bleed an "rg available" verdict from one env to another on the same backend', async () => {
1994
- _resetRipgrepCacheForTests();
1995
- // Same backend instance for both Runs. Vary `local.env` between
1996
- // them — pre-fix the WeakMap cache was keyed on the spawn
1997
- // function alone, so the second Run inherited the first's
1998
- // verdict and tried to use rg under an env without it,
1999
- // failing with ENOENT.
2000
- const realSpawn = (
2001
- require('child_process') as typeof import('child_process')
2002
- ).spawn;
2003
-
2004
- // Backend that returns success for `rg --version` ONLY when
2005
- // the spawned process's env has PATH=/with/rg, and 127
2006
- // otherwise. This is the structural shape of "rg is on PATH
2007
- // for env A but not env B".
2008
- const envSensitive: t.LocalSpawn = ((
2009
- cmd: string,
2010
- args: string[],
2011
- opts: import('child_process').SpawnOptions
2012
- ) => {
2013
- if (cmd === 'rg' && args[0] === '--version') {
2014
- const env = (opts.env ?? {}) as NodeJS.ProcessEnv;
2015
- if (env.PATH === '/with/rg') {
2016
- return realSpawn('sh', ['-c', 'exit 0'], opts);
2017
- }
2018
- return realSpawn('sh', ['-c', 'exit 127'], opts);
2019
- }
2020
- return realSpawn(cmd, args, opts);
2021
- }) as unknown as t.LocalSpawn;
2022
-
2023
- const cwdA = await createTempDir();
2024
- const cwdB = await createTempDir();
2025
- await (
2026
- await import('fs/promises')
2027
- ).writeFile(join(cwdA, 'a.ts'), 'needle\n');
2028
- await (
2029
- await import('fs/promises')
2030
- ).writeFile(join(cwdB, 'b.ts'), 'needle\n');
2031
-
2032
- // Run A: env says rg is available → cache records `true` for
2033
- // (backend, env-A).
2034
- const bundleA = createLocalCodingToolBundle({
2035
- cwd: cwdA,
2036
- exec: { spawn: envSensitive },
2037
- env: { PATH: '/with/rg' },
2038
- });
2039
- await bundleA.tools
2040
- .find((t_) => t_.name === 'grep_search')!
2041
- .invoke({
2042
- id: 'gA',
2043
- name: 'grep_search',
2044
- args: { pattern: 'needle' },
2045
- type: 'tool_call',
2046
- });
2047
-
2048
- // Run B: same backend, DIFFERENT env (PATH excludes rg). Must
2049
- // run a fresh probe and fall back to the Node walker, NOT
2050
- // reuse Run A's cached "true". Pre-fix this would attempt to
2051
- // spawn rg with the wrong PATH and surface a tool failure.
2052
- const bundleB = createLocalCodingToolBundle({
2053
- cwd: cwdB,
2054
- exec: { spawn: envSensitive },
2055
- env: { PATH: '/without/rg' },
2056
- });
2057
- const bResult = await bundleB.tools
2058
- .find((t_) => t_.name === 'grep_search')!
2059
- .invoke({
2060
- id: 'gB',
2061
- name: 'grep_search',
2062
- args: { pattern: 'needle' },
2063
- type: 'tool_call',
2064
- });
2065
- const text = JSON.stringify(bResult);
2066
- // Result must show the match (Node fallback ran successfully)
2067
- // and indicate the fallback engine, not a ripgrep failure.
2068
- expect(text).toContain('needle');
2069
- expect(text).toContain('node-fallback');
2070
- });
2071
- });
2072
-
2073
- describe('compile-style runtimes honor local.shell (Codex P2 #29)', () => {
2074
- const { executeLocalCode } = require('../local/LocalExecutionEngine');
2075
-
2076
- it('routes the rust runtime through `local.shell` instead of bare `bash`', async () => {
2077
- // Intercept spawn — assert the configured shell is used for
2078
- // the rs runtime, not hardcoded `bash`.
2079
- const realSpawn = (
2080
- require('child_process') as typeof import('child_process')
2081
- ).spawn;
2082
- const calls: string[] = [];
2083
- const intercept: t.LocalSpawn = ((
2084
- cmd: string,
2085
- args: string[],
2086
- opts: import('child_process').SpawnOptions
2087
- ) => {
2088
- calls.push(cmd);
2089
- // Don't actually try to compile rust — short-circuit via sh.
2090
- return realSpawn('sh', ['-c', 'exit 0'], opts);
2091
- }) as unknown as t.LocalSpawn;
2092
-
2093
- await executeLocalCode(
2094
- { lang: 'rs', code: 'fn main() {}', args: [] },
2095
- {
2096
- shell: '/bin/sh',
2097
- exec: { spawn: intercept },
2098
- sandbox: { enabled: false },
2099
- }
2100
- );
2101
-
2102
- // The rust path's compile-and-run command should have been
2103
- // dispatched via `/bin/sh`, not `bash` / `bash.exe`.
2104
- expect(calls[0]).toBe('/bin/sh');
2105
- });
2106
- });
2107
- });
2108
-
2109
- describe('comprehensive review (round 12) — Codex P1 #36', () => {
2110
- describe('granular workspace flags override the legacy allowOutsideWorkspace', () => {
2111
- const {
2112
- getWriteRoots,
2113
- getReadRoots,
2114
- } = require('../local/LocalExecutionEngine');
2115
-
2116
- it('workspace.allowWriteOutside=false beats allowOutsideWorkspace=true (Codex P1 #36)', () => {
2117
- // Pre-fix the OR short-circuited on the legacy flag, returning
2118
- // null (skip clamp) even though the host explicitly tightened
2119
- // the granular flag during migration.
2120
- const roots = getWriteRoots({
2121
- cwd: '/tmp/ws',
2122
- workspace: { root: '/tmp/ws', allowWriteOutside: false },
2123
- allowOutsideWorkspace: true,
2124
- });
2125
- expect(roots).not.toBeNull();
2126
- expect(roots).toContain('/tmp/ws');
2127
- });
2128
-
2129
- it('workspace.allowReadOutside=false beats allowOutsideWorkspace=true', () => {
2130
- const roots = getReadRoots({
2131
- cwd: '/tmp/ws',
2132
- workspace: { root: '/tmp/ws', allowReadOutside: false },
2133
- allowOutsideWorkspace: true,
2134
- });
2135
- expect(roots).not.toBeNull();
2136
- expect(roots).toContain('/tmp/ws');
2137
- });
2138
-
2139
- it('workspace.allowWriteOutside=true still permits writes outside', () => {
2140
- const roots = getWriteRoots({
2141
- cwd: '/tmp/ws',
2142
- workspace: { root: '/tmp/ws', allowWriteOutside: true },
2143
- });
2144
- expect(roots).toBeNull();
2145
- });
2146
-
2147
- it('legacy allowOutsideWorkspace=true still works when granular flag is unset', () => {
2148
- const roots = getWriteRoots({
2149
- cwd: '/tmp/ws',
2150
- workspace: { root: '/tmp/ws' },
2151
- allowOutsideWorkspace: true,
2152
- });
2153
- expect(roots).toBeNull();
2154
- });
2155
-
2156
- it('default (no flags) returns the workspace boundary for both read and write', () => {
2157
- const cfg = { cwd: '/tmp/ws', workspace: { root: '/tmp/ws' } };
2158
- expect(getWriteRoots(cfg)).toEqual(['/tmp/ws']);
2159
- expect(getReadRoots(cfg)).toEqual(['/tmp/ws']);
2160
- });
2161
- });
2162
- });
2163
-
2164
- describe('comprehensive review (round 14) — Codex P1 #37 + P2 #38/#40/#41', () => {
2165
- describe('destructive path normalization (Codex P1 #37)', () => {
2166
- const cases: Array<[string, string]> = [
2167
- ['rm -rf $HOME/', 'trailing slash on $HOME'],
2168
- ['rm -rf ~/', 'trailing slash on ~'],
2169
- ['rm -rf ${HOME}/', 'trailing slash on ${HOME}'],
2170
- ['rm -rf "$HOME/"', 'quoted $HOME with trailing slash'],
2171
- ['rm -rf "~/"', 'quoted ~ with trailing slash'],
2172
- ['rm -rf "${HOME}/"', 'quoted ${HOME} with trailing slash'],
2173
- ['chmod -R 777 ~/', 'chmod with trailing slash'],
2174
- ['chmod -R 777 "$HOME/"', 'quoted chmod with trailing slash'],
2175
- ];
2176
- it.each(cases)('blocks %s (%s)', async (cmd) => {
2177
- const result = await validateBashCommand(cmd);
2178
- expect(result.valid).toBe(false);
2179
- expect(result.errors.join('\n')).toMatch(/destructive command pattern/);
2180
- });
2181
-
2182
- it('still allows benign trailing-slash commands', async () => {
2183
- const result = await validateBashCommand('ls $HOME/');
2184
- expect(result.valid).toBe(true);
2185
- });
2186
- });
2187
-
2188
- describe('destructive wildcard targets (Codex P1 [42])', () => {
2189
- const cases: Array<[string, string]> = [
2190
- ['rm -rf $HOME/*', 'glob over $HOME contents'],
2191
- ['rm -rf ~/*', 'glob over ~ contents'],
2192
- ['rm -rf ${HOME}/*', 'glob over ${HOME} contents'],
2193
- ['rm -rf ./*', 'glob over current dir contents'],
2194
- ['rm -rf .*', 'dotfile glob in current dir'],
2195
- ['rm -rf $HOME*', 'prefix glob against $HOME base'],
2196
- ['chmod -R 777 ~/*', 'chmod with glob'],
2197
- ];
2198
- it.each(cases)('blocks %s (%s)', async (cmd) => {
2199
- const result = await validateBashCommand(cmd);
2200
- expect(result.valid).toBe(false);
2201
- expect(result.errors.join('\n')).toMatch(/destructive command pattern/);
2202
- });
2203
-
2204
- it('does not flag benign glob commands (no rm/chmod/chown)', async () => {
2205
- const result = await validateBashCommand('ls $HOME/*');
2206
- expect(result.valid).toBe(true);
2207
- });
2208
- });
2209
-
2210
- describe('destructive dot-glob targets (Codex P1 [47])', () => {
2211
- const cases: Array<[string, string]> = [
2212
- ['rm -rf $HOME/.*', 'dotfile glob under $HOME'],
2213
- ['rm -rf ~/.*', 'dotfile glob under ~'],
2214
- ['rm -rf ${HOME}/.*', 'dotfile glob under ${HOME}'],
2215
- ['rm -rf /.*', 'dotfile glob under root'],
2216
- ['rm -rf "$HOME/.*"', 'quoted dotfile glob under $HOME'],
2217
- ['chmod -R 777 ~/.*', 'chmod dotfile glob'],
2218
- ];
2219
- it.each(cases)('blocks %s (%s)', async (cmd) => {
2220
- const result = await validateBashCommand(cmd);
2221
- expect(result.valid).toBe(false);
2222
- expect(result.errors.join('\n')).toMatch(/destructive command pattern/);
2223
- });
2224
-
2225
- it('blocks the positional-arg dot-glob form too', async () => {
2226
- const {
2227
- executeLocalBashWithArgs,
2228
- } = require('../local/LocalExecutionEngine');
2229
- await expect(
2230
- executeLocalBashWithArgs('rm -rf "$1"', ['/.*'], {
2231
- sandbox: { enabled: false },
2232
- timeoutMs: 5000,
2233
- })
2234
- ).rejects.toThrow(/destructive command pattern.*protected target/i);
2235
- });
2236
- });
2237
-
2238
- describe('strict postEditSyntaxCheck reverts the write on failure (Codex P2 [49])', () => {
2239
- it('write_file: reverts the file contents to pre-write state when strict check fails', async () => {
2240
- const cwd = await createTempDir();
2241
- const fsp = await import('fs/promises');
2242
- const file = join(cwd, 'a.js');
2243
- await fsp.writeFile(file, '// good\nconsole.log("ok");\n');
2244
-
2245
- const bundle = createLocalCodingToolBundle({
2246
- cwd,
2247
- postEditSyntaxCheck: 'strict',
2248
- });
2249
- const writeTool = bundle.tools.find(
2250
- (tt) => tt.name === Constants.WRITE_FILE
2251
- );
2252
- // Bad JS content (missing closing brace) — node --check will
2253
- // reject this and strict mode must throw AND restore the file.
2254
- await expect(
2255
- writeTool!.invoke({
2256
- id: 'wf-strict',
2257
- name: Constants.WRITE_FILE,
2258
- args: { path: file, content: 'function broken( {\n' },
2259
- type: 'tool_call',
2260
- })
2261
- ).rejects.toThrow(/syntax check failed.*reverted/i);
2262
- // Critical assertion: file on disk is restored to the
2263
- // pre-write content. Pre-fix it would still hold the broken
2264
- // content.
2265
- expect(await fsp.readFile(file, 'utf8')).toBe(
2266
- '// good\nconsole.log("ok");\n'
2267
- );
2268
- });
2269
-
2270
- it('write_file: deletes a brand-new file when strict check fails on first write', async () => {
2271
- const cwd = await createTempDir();
2272
- const fsp = await import('fs/promises');
2273
- const file = join(cwd, 'never-existed.js');
2274
-
2275
- const bundle = createLocalCodingToolBundle({
2276
- cwd,
2277
- postEditSyntaxCheck: 'strict',
2278
- });
2279
- const writeTool = bundle.tools.find(
2280
- (tt) => tt.name === Constants.WRITE_FILE
2281
- );
2282
- await expect(
2283
- writeTool!.invoke({
2284
- id: 'wf-strict-new',
2285
- name: Constants.WRITE_FILE,
2286
- args: { path: file, content: 'function broken( {\n' },
2287
- type: 'tool_call',
2288
- })
2289
- ).rejects.toThrow(/syntax check failed.*reverted/i);
2290
- // Brand-new file must be removed on revert.
2291
- await expect(fsp.stat(file)).rejects.toThrow();
2292
- });
2293
-
2294
- it('edit_file: reverts to pre-edit content when strict check fails', async () => {
2295
- const cwd = await createTempDir();
2296
- const fsp = await import('fs/promises');
2297
- const file = join(cwd, 'b.js');
2298
- const original = 'function ok() { return 1; }\n';
2299
- await fsp.writeFile(file, original);
2300
-
2301
- const bundle = createLocalCodingToolBundle({
2302
- cwd,
2303
- postEditSyntaxCheck: 'strict',
2304
- });
2305
- const editTool = bundle.tools.find(
2306
- (tt) => tt.name === Constants.EDIT_FILE
2307
- );
2308
- await expect(
2309
- editTool!.invoke({
2310
- id: 'ef-strict',
2311
- name: Constants.EDIT_FILE,
2312
- args: {
2313
- path: file,
2314
- old_text: 'return 1;',
2315
- new_text: 'return broken(',
2316
- },
2317
- type: 'tool_call',
2318
- })
2319
- ).rejects.toThrow(/syntax check failed.*reverted/i);
2320
- expect(await fsp.readFile(file, 'utf8')).toBe(original);
2321
- });
2322
-
2323
- it('write_file: still reverts when the strict validation READ times out', async () => {
2324
- // A stalled validation read must not leave the just-written bytes on disk —
2325
- // strict mode's revert contract still applies when validation is
2326
- // inconclusive due to a timeout (here a brand-new file -> unlink).
2327
- const cwd = await createTempDir();
2328
- const file = join(cwd, 'config.json');
2329
- const unlinked: string[] = [];
2330
- let written = false;
2331
- const fakeFs = {
2332
- readFile: async () => {
2333
- if (!written) {
2334
- // write_file pre-read: the file does not exist yet.
2335
- throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
2336
- }
2337
- // jsonCheck's post-write validation read stalls.
2338
- throw new WorkspaceClientTimeoutError(
2339
- 'cloudflare sandbox readFile exceeded 6000ms client-side timeout'
2340
- );
2341
- },
2342
- writeFile: async () => {
2343
- written = true;
2344
- },
2345
- mkdir: async () => undefined,
2346
- unlink: async (p: string) => {
2347
- unlinked.push(p);
2348
- },
2349
- stat: async () => ({ isFile: () => true, size: 1 }),
2350
- readdir: async () => [],
2351
- realpath: async (p: string) => p,
2352
- } as unknown as WorkspaceFS;
2353
-
2354
- const bundle = createLocalCodingToolBundle({
2355
- cwd,
2356
- postEditSyntaxCheck: 'strict',
2357
- exec: { fs: fakeFs },
2358
- });
2359
- const writeTool = bundle.tools.find(
2360
- (tt) => tt.name === Constants.WRITE_FILE
2361
- );
2362
- await expect(
2363
- writeTool!.invoke({
2364
- id: 'wf-strict-read-timeout',
2365
- name: Constants.WRITE_FILE,
2366
- args: { path: file, content: '{"ok": true}\n' },
2367
- type: 'tool_call',
2368
- })
2369
- ).rejects.toThrow(/client-side timeout/);
2370
- // strict mode attempted the revert (brand-new file -> unlink), rather than
2371
- // leaving the unvalidated write on disk.
2372
- expect(unlinked.length).toBeGreaterThan(0);
2373
- });
2374
- });
2375
-
2376
- describe('fallbackGrep skip sentinels do not count as matches (Codex P2 [43])', () => {
2377
- it('reports `matches: 0` when only oversize files are present', async () => {
2378
- _resetRipgrepCacheForTests();
2379
- const realSpawn = (
2380
- require('child_process') as typeof import('child_process')
2381
- ).spawn;
2382
- const noRgBackend: t.LocalSpawn = ((
2383
- cmd: string,
2384
- args: string[],
2385
- opts: import('child_process').SpawnOptions
2386
- ) => {
2387
- if (cmd === 'rg') return realSpawn('sh', ['-c', 'exit 127'], opts);
2388
- return realSpawn(cmd, args, opts);
2389
- }) as unknown as t.LocalSpawn;
2390
-
2391
- const cwd = await createTempDir();
2392
- const fsp = await import('fs/promises');
2393
- // Two oversize files, no real matches.
2394
- await fsp.writeFile(
2395
- join(cwd, 'big1.txt'),
2396
- Buffer.alloc(6 * 1024 * 1024, 'a')
2397
- );
2398
- await fsp.writeFile(
2399
- join(cwd, 'big2.txt'),
2400
- Buffer.alloc(6 * 1024 * 1024, 'a')
2401
- );
2402
-
2403
- const bundle = createLocalCodingToolBundle({
2404
- cwd,
2405
- exec: { spawn: noRgBackend },
2406
- });
2407
- const grepTool = bundle.tools.find(
2408
- (tt) => tt.name === Constants.GREP_SEARCH
2409
- );
2410
- const result = await grepTool!.invoke({
2411
- id: 'g43',
2412
- name: Constants.GREP_SEARCH,
2413
- args: { pattern: 'needle' },
2414
- type: 'tool_call',
2415
- });
2416
- // Result is [text, artifact]; pull the artifact off the
2417
- // ToolMessage shape.
2418
- const text = JSON.stringify(result);
2419
- // Artifact shape: { matches: 0, skipped: 2, engine: 'node-fallback' }
2420
- expect(text).toContain('"matches":0');
2421
- expect(text).toContain('"skipped":2');
2422
- });
2423
- });
2424
-
2425
- describe('Send-input direct path threads additionalContextsSink (Codex P2 [44])', () => {
2426
- it('materializes hook additionalContext as a HumanMessage on the Send branch', async () => {
2427
- // The Send-input branch dispatches a single direct tool. It
2428
- // had its own runDirectToolWithLifecycleHooks call site that
2429
- // didn't pass the sink, so PreToolUse additionalContext was
2430
- // dropped on this otherwise-supported input shape.
2431
- const { tool } = await import('@langchain/core/tools');
2432
- const { z } = await import('zod');
2433
- const { HookRegistry } = await import('@/hooks');
2434
- const { HumanMessage } = await import('@langchain/core/messages');
2435
-
2436
- const echo = tool(async () => 'ECHO', {
2437
- name: 'echo',
2438
- description: 'send-input echo',
2439
- schema: z.object({}).passthrough(),
2440
- });
2441
- const registry = new HookRegistry();
2442
- registry.register('PreToolUse', {
2443
- hooks: [
2444
- async () => ({
2445
- decision: 'allow',
2446
- additionalContext: 'SEND-CTX: policy note via Send branch',
2447
- }),
2448
- ],
2449
- });
2450
-
2451
- const node = new ToolNode({
2452
- tools: [echo],
2453
- eventDrivenMode: true,
2454
- hookRegistry: registry,
2455
- directToolNames: new Set(['echo']),
2456
- });
2457
- // Construct a Send-shaped input: { lg_tool_call: ToolCall }
2458
- const result = (await node.invoke({
2459
- lg_tool_call: { id: 'send_1', name: 'echo', args: {} },
2460
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2461
- } as any)) as { messages: BaseMessage[] } | BaseMessage[];
2462
- const messages = Array.isArray(result) ? result : result.messages;
2463
- const found = messages.find(
2464
- (m) =>
2465
- m instanceof HumanMessage &&
2466
- typeof m.content === 'string' &&
2467
- m.content.includes('SEND-CTX')
2468
- );
2469
- expect(found).toBeDefined();
2470
- });
2471
- });
2472
-
2473
- describe('bash args validated against destructive-target patterns (Codex P1 [45])', () => {
2474
- const {
2475
- executeLocalBashWithArgs,
2476
- } = require('../local/LocalExecutionEngine');
2477
-
2478
- it('blocks `rm -rf "$1"` + args=["/"]', async () => {
2479
- await expect(
2480
- executeLocalBashWithArgs('rm -rf "$1"', ['/'], {
2481
- sandbox: { enabled: false },
2482
- timeoutMs: 5000,
2483
- })
2484
- ).rejects.toThrow(/destructive command pattern.*protected target/i);
2485
- });
2486
-
2487
- it('blocks `chmod -R 777 "$1"` + args=["~/"]', async () => {
2488
- await expect(
2489
- executeLocalBashWithArgs('chmod -R 777 "$1"', ['~/'], {
2490
- sandbox: { enabled: false },
2491
- timeoutMs: 5000,
2492
- })
2493
- ).rejects.toThrow(/destructive command pattern.*protected target/i);
2494
- });
2495
-
2496
- it('blocks `rm -rf "$@"` + args=["$HOME"]', async () => {
2497
- await expect(
2498
- executeLocalBashWithArgs('rm -rf "$@"', ['$HOME'], {
2499
- sandbox: { enabled: false },
2500
- timeoutMs: 5000,
2501
- })
2502
- ).rejects.toThrow(/destructive command pattern.*protected target/i);
2503
- });
2504
-
2505
- it('allows benign positional arg use (echo + protected-shape arg)', async () => {
2506
- // `echo` is not in the destructive-op set so a "/" arg is fine.
2507
- const result = await executeLocalBashWithArgs('echo "$1"', ['/'], {
2508
- sandbox: { enabled: false },
2509
- timeoutMs: 5000,
2510
- });
2511
- expect(result.exitCode).toBe(0);
2512
- });
2513
-
2514
- it('allows destructive op with non-protected args', async () => {
2515
- // `rm` of a clearly non-protected path inside a tmpdir is fine.
2516
- const cwd = await createTempDir();
2517
- const fsp = await import('fs/promises');
2518
- const f = join(cwd, 'goner.txt');
2519
- await fsp.writeFile(f, 'bye\n');
2520
- const result = await executeLocalBashWithArgs('rm -f "$1"', [f], {
2521
- cwd,
2522
- sandbox: { enabled: false },
2523
- timeoutMs: 5000,
2524
- });
2525
- expect(result.exitCode).toBe(0);
2526
- });
2527
- });
2528
-
2529
- describe('direct-path additionalContext is marked as system metadata (Codex P2 [46])', () => {
2530
- it('attaches `additional_kwargs.role: "system"` to the materialized HumanMessage', async () => {
2531
- const { tool } = await import('@langchain/core/tools');
2532
- const { z } = await import('zod');
2533
- const { HookRegistry } = await import('@/hooks');
2534
- const { HumanMessage, AIMessage } = await import(
2535
- '@langchain/core/messages'
2536
- );
2537
-
2538
- const echo = tool(async () => 'OK', {
2539
- name: 'echo',
2540
- description: 'noop',
2541
- schema: z.object({}).passthrough(),
2542
- });
2543
- const registry = new HookRegistry();
2544
- registry.register('PreToolUse', {
2545
- hooks: [
2546
- async () => ({
2547
- decision: 'allow',
2548
- additionalContext: 'POLICY: be careful',
2549
- }),
2550
- ],
2551
- });
2552
- const node = new ToolNode({
2553
- tools: [echo],
2554
- eventDrivenMode: true,
2555
- hookRegistry: registry,
2556
- directToolNames: new Set(['echo']),
2557
- });
2558
- const ai = new AIMessage({
2559
- content: '',
2560
- tool_calls: [{ id: 'c46', name: 'echo', args: {} }],
2561
- });
2562
- const result = (await node.invoke({ messages: [ai] })) as
2563
- | { messages: BaseMessage[] }
2564
- | BaseMessage[];
2565
- const messages = Array.isArray(result) ? result : result.messages;
2566
- const human = messages.find(
2567
- (m): m is InstanceType<typeof HumanMessage> =>
2568
- m instanceof HumanMessage &&
2569
- typeof m.content === 'string' &&
2570
- m.content.includes('POLICY')
2571
- );
2572
- expect(human).toBeDefined();
2573
- // The marker the event-driven path sets — direct path now
2574
- // matches it.
2575
- expect(human?.additional_kwargs).toMatchObject({
2576
- role: 'system',
2577
- source: 'hook',
2578
- });
2579
- });
2580
- });
2581
-
2582
- describe('resolveWorkspacePathSafe routes through WorkspaceFS.realpath (Codex P2 #38)', () => {
2583
- it('honors a custom workspace fs realpath impl', async () => {
2584
- const {
2585
- resolveWorkspacePathSafe,
2586
- } = require('../local/LocalExecutionEngine');
2587
- const calls: string[] = [];
2588
- const fakeFs = {
2589
- readFile: async () => '',
2590
- writeFile: async () => undefined,
2591
- stat: async () => ({
2592
- isFile: () => true,
2593
- isDirectory: () => false,
2594
- size: 0,
2595
- }),
2596
- readdir: async () => [],
2597
- mkdir: async () => undefined,
2598
- // The custom realpath that the safe-path resolver MUST use.
2599
- // Returns paths unchanged so the lexical containment check
2600
- // succeeds for in-workspace targets.
2601
- realpath: async (p: string): Promise<string> => {
2602
- calls.push(p);
2603
- return p;
2604
- },
2605
- unlink: async () => undefined,
2606
- open: async () => {
2607
- throw new Error('not implemented');
2608
- },
2609
- };
2610
-
2611
- await resolveWorkspacePathSafe('/virtual/ws/file.ts', {
2612
- cwd: '/virtual/ws',
2613
- workspace: { root: '/virtual/ws' },
2614
- exec: { fs: fakeFs as unknown as never },
2615
- });
2616
-
2617
- // Must have called the WorkspaceFS realpath at least once
2618
- // (for either the root or the candidate path). Pre-fix the
2619
- // host fs/promises.realpath was used instead.
2620
- expect(calls.length).toBeGreaterThan(0);
2621
- expect(calls.every((p) => p.startsWith('/virtual/'))).toBe(true);
2622
- });
2623
- });
2624
-
2625
- describe('syntax-check probe cache also keys on env (Codex P2 #40)', () => {
2626
- it('does not bleed `hasNode` verdict from one env to another on the same backend', async () => {
2627
- _resetSyntaxCheckProbeCacheForTests();
2628
- const realSpawn = (
2629
- require('child_process') as typeof import('child_process')
2630
- ).spawn;
2631
- const calls: Array<{ cmd: string; env?: NodeJS.ProcessEnv }> = [];
2632
- // Backend that returns `node --version` success ONLY when
2633
- // env.PATH includes 'with-node'. Mirrors P1 #34's shape.
2634
- const envSensitive: t.LocalSpawn = ((
2635
- cmd: string,
2636
- args: string[],
2637
- opts: import('child_process').SpawnOptions
2638
- ) => {
2639
- calls.push({ cmd, env: opts.env as NodeJS.ProcessEnv });
2640
- if (cmd === 'node' && args[0] === '--version') {
2641
- const env = (opts.env ?? {}) as NodeJS.ProcessEnv;
2642
- if (env.PATH?.includes('with-node') === true) {
2643
- return realSpawn('sh', ['-c', 'exit 0'], opts);
2644
- }
2645
- return realSpawn('sh', ['-c', 'exit 127'], opts);
2646
- }
2647
- // Run all other spawns through a no-op so we don't hit
2648
- // real node/python/bash on the host.
2649
- return realSpawn('sh', ['-c', 'exit 0'], opts);
2650
- }) as unknown as t.LocalSpawn;
2651
-
2652
- const { runPostEditSyntaxCheck } = require('../local/syntaxCheck');
2653
- const cwd = await createTempDir();
2654
- const file = join(cwd, 'a.js');
2655
- await (await import('fs/promises')).writeFile(file, 'function (\n');
2656
-
2657
- // Run A: env says node IS available — probe records `true`
2658
- // for (backend, envA).
2659
- await runPostEditSyntaxCheck(file, {
2660
- exec: { spawn: envSensitive },
2661
- env: { PATH: '/with-node' },
2662
- });
2663
-
2664
- // Run B: env says node is NOT available. Pre-fix the cache
2665
- // would reuse the (backend) entry and try to actually
2666
- // syntax-check via the missing node. Now: separate cache slot
2667
- // for envB → its own probe → records `false` → skips check.
2668
- const probeCallsBefore = calls.filter(
2669
- (c) =>
2670
- c.cmd === 'node' && c.env?.PATH?.includes('without-node') === true
2671
- ).length;
2672
- await runPostEditSyntaxCheck(file, {
2673
- exec: { spawn: envSensitive },
2674
- env: { PATH: '/without-node' },
2675
- });
2676
- const probeCallsAfter = calls.filter(
2677
- (c) =>
2678
- c.cmd === 'node' && c.env?.PATH?.includes('without-node') === true
2679
- ).length;
2680
- // A fresh probe must have run for envB (count went up).
2681
- expect(probeCallsAfter).toBeGreaterThan(probeCallsBefore);
2682
- });
2683
- });
2684
-
2685
- describe('fallbackGrep skips files larger than the per-file cap (Codex P2 #41)', () => {
2686
- it('emits a sentinel and continues instead of reading multi-MB files into memory', async () => {
2687
- _resetRipgrepCacheForTests();
2688
- const realSpawn = (
2689
- require('child_process') as typeof import('child_process')
2690
- ).spawn;
2691
- // Force the Node fallback by making rg unavailable.
2692
- const noRgBackend: t.LocalSpawn = ((
2693
- cmd: string,
2694
- args: string[],
2695
- opts: import('child_process').SpawnOptions
2696
- ) => {
2697
- if (cmd === 'rg') {
2698
- return realSpawn('sh', ['-c', 'exit 127'], opts);
2699
- }
2700
- return realSpawn(cmd, args, opts);
2701
- }) as unknown as t.LocalSpawn;
2702
-
2703
- const cwd = await createTempDir();
2704
- const fsp = await import('fs/promises');
2705
- // Write a small file (matches the search) and a 6 MB file
2706
- // (over the 5 MB cap) — the fallback must skip the big one
2707
- // with a sentinel and still find the small-file match.
2708
- await fsp.writeFile(join(cwd, 'small.txt'), 'needle\n');
2709
- const big = Buffer.alloc(6 * 1024 * 1024, 'a');
2710
- await fsp.writeFile(join(cwd, 'big.txt'), big);
2711
-
2712
- const bundle = createLocalCodingToolBundle({
2713
- cwd,
2714
- exec: { spawn: noRgBackend },
2715
- });
2716
- const grepTool = bundle.tools.find(
2717
- (tt) => tt.name === Constants.GREP_SEARCH
2718
- );
2719
- const result = await grepTool!.invoke({
2720
- id: 'g41',
2721
- name: Constants.GREP_SEARCH,
2722
- args: { pattern: 'needle' },
2723
- type: 'tool_call',
2724
- });
2725
- const text = JSON.stringify(result);
2726
- // Small-file match landed.
2727
- expect(text).toContain('needle');
2728
- // Big-file got the skip sentinel (didn't OOM, didn't read
2729
- // into memory).
2730
- expect(text).toContain('skipped');
2731
- });
2732
- });
2733
- });