@librechat/agents 3.2.31 → 3.2.33

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 (582) hide show
  1. package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
  2. package/dist/cjs/agents/AgentContext.cjs +844 -1046
  3. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  4. package/dist/cjs/common/constants.cjs +13 -13
  5. package/dist/cjs/common/constants.cjs.map +1 -1
  6. package/dist/cjs/common/enum.cjs +233 -240
  7. package/dist/cjs/common/enum.cjs.map +1 -1
  8. package/dist/cjs/common/index.cjs +2 -0
  9. package/dist/cjs/events.cjs +121 -169
  10. package/dist/cjs/events.cjs.map +1 -1
  11. package/dist/cjs/graphs/Graph.cjs +1389 -1807
  12. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  13. package/dist/cjs/graphs/MultiAgentGraph.cjs +713 -945
  14. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  15. package/dist/cjs/graphs/index.cjs +2 -0
  16. package/dist/cjs/hitl/askUserQuestion.cjs +60 -62
  17. package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -1
  18. package/dist/cjs/hitl/index.cjs +1 -0
  19. package/dist/cjs/hooks/HookRegistry.cjs +176 -202
  20. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  21. package/dist/cjs/hooks/createToolPolicyHook.cjs +71 -101
  22. package/dist/cjs/hooks/createToolPolicyHook.cjs.map +1 -1
  23. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +170 -273
  24. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -1
  25. package/dist/cjs/hooks/executeHooks.cjs +227 -282
  26. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  27. package/dist/cjs/hooks/index.cjs +6 -0
  28. package/dist/cjs/hooks/matchers.cjs +196 -230
  29. package/dist/cjs/hooks/matchers.cjs.map +1 -1
  30. package/dist/cjs/hooks/types.cjs +24 -24
  31. package/dist/cjs/hooks/types.cjs.map +1 -1
  32. package/dist/cjs/instrumentation.cjs +110 -137
  33. package/dist/cjs/instrumentation.cjs.map +1 -1
  34. package/dist/cjs/langchain/google-common.cjs +0 -3
  35. package/dist/cjs/langchain/index.cjs +80 -43
  36. package/dist/cjs/langchain/language_models/chat_models.cjs +0 -3
  37. package/dist/cjs/langchain/messages/tool.cjs +0 -3
  38. package/dist/cjs/langchain/messages.cjs +35 -18
  39. package/dist/cjs/langchain/openai.cjs +0 -3
  40. package/dist/cjs/langchain/prompts.cjs +5 -8
  41. package/dist/cjs/langchain/runnables.cjs +11 -10
  42. package/dist/cjs/langchain/tools.cjs +14 -11
  43. package/dist/cjs/langchain/utils/env.cjs +5 -8
  44. package/dist/cjs/langfuse.cjs +60 -79
  45. package/dist/cjs/langfuse.cjs.map +1 -1
  46. package/dist/cjs/langfuseToolOutputTracing.cjs +267 -399
  47. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  48. package/dist/cjs/llm/anthropic/index.cjs +432 -562
  49. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  50. package/dist/cjs/llm/anthropic/types.cjs +23 -47
  51. package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
  52. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +441 -696
  53. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  54. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +171 -252
  55. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  56. package/dist/cjs/llm/anthropic/utils/output_parsers.cjs +2 -0
  57. package/dist/cjs/llm/anthropic/utils/tools.cjs +12 -26
  58. package/dist/cjs/llm/anthropic/utils/tools.cjs.map +1 -1
  59. package/dist/cjs/llm/bedrock/index.cjs +195 -240
  60. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  61. package/dist/cjs/llm/bedrock/toolCache.cjs +84 -106
  62. package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
  63. package/dist/cjs/llm/bedrock/utils/index.cjs +2 -0
  64. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +357 -620
  65. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  66. package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +105 -149
  67. package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
  68. package/dist/cjs/llm/fake.cjs +86 -96
  69. package/dist/cjs/llm/fake.cjs.map +1 -1
  70. package/dist/cjs/llm/google/index.cjs +183 -237
  71. package/dist/cjs/llm/google/index.cjs.map +1 -1
  72. package/dist/cjs/llm/google/utils/common.cjs +398 -674
  73. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  74. package/dist/cjs/llm/google/utils/zod_to_genai_parameters.cjs +2 -0
  75. package/dist/cjs/llm/init.cjs +44 -53
  76. package/dist/cjs/llm/init.cjs.map +1 -1
  77. package/dist/cjs/llm/invoke.cjs +142 -182
  78. package/dist/cjs/llm/invoke.cjs.map +1 -1
  79. package/dist/cjs/llm/openai/index.cjs +991 -1276
  80. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  81. package/dist/cjs/llm/openai/utils/index.cjs +189 -316
  82. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  83. package/dist/cjs/llm/openrouter/index.cjs +102 -153
  84. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  85. package/dist/cjs/llm/openrouter/toolCache.cjs +35 -44
  86. package/dist/cjs/llm/openrouter/toolCache.cjs.map +1 -1
  87. package/dist/cjs/llm/providers.cjs +29 -37
  88. package/dist/cjs/llm/providers.cjs.map +1 -1
  89. package/dist/cjs/llm/request.cjs +20 -33
  90. package/dist/cjs/llm/request.cjs.map +1 -1
  91. package/dist/cjs/llm/vertexai/index.cjs +427 -453
  92. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  93. package/dist/cjs/main.cjs +547 -528
  94. package/dist/cjs/messages/anthropicToolCache.cjs +68 -119
  95. package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -1
  96. package/dist/cjs/messages/cache.cjs +308 -401
  97. package/dist/cjs/messages/cache.cjs.map +1 -1
  98. package/dist/cjs/messages/content.cjs +36 -49
  99. package/dist/cjs/messages/content.cjs.map +1 -1
  100. package/dist/cjs/messages/contextPruning.cjs +112 -145
  101. package/dist/cjs/messages/contextPruning.cjs.map +1 -1
  102. package/dist/cjs/messages/contextPruningSettings.cjs +36 -46
  103. package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -1
  104. package/dist/cjs/messages/core.cjs +256 -397
  105. package/dist/cjs/messages/core.cjs.map +1 -1
  106. package/dist/cjs/messages/format.cjs +904 -1382
  107. package/dist/cjs/messages/format.cjs.map +1 -1
  108. package/dist/cjs/messages/ids.cjs +16 -20
  109. package/dist/cjs/messages/ids.cjs.map +1 -1
  110. package/dist/cjs/messages/index.cjs +12 -0
  111. package/dist/cjs/messages/langchain.cjs +18 -18
  112. package/dist/cjs/messages/langchain.cjs.map +1 -1
  113. package/dist/cjs/messages/prune.cjs +1054 -1517
  114. package/dist/cjs/messages/prune.cjs.map +1 -1
  115. package/dist/cjs/messages/recency.cjs +77 -95
  116. package/dist/cjs/messages/recency.cjs.map +1 -1
  117. package/dist/cjs/messages/reducer.cjs +63 -78
  118. package/dist/cjs/messages/reducer.cjs.map +1 -1
  119. package/dist/cjs/messages/tools.cjs +51 -79
  120. package/dist/cjs/messages/tools.cjs.map +1 -1
  121. package/dist/cjs/openai/index.cjs +171 -217
  122. package/dist/cjs/openai/index.cjs.map +1 -1
  123. package/dist/cjs/responses/index.cjs +302 -391
  124. package/dist/cjs/responses/index.cjs.map +1 -1
  125. package/dist/cjs/run.cjs +903 -1113
  126. package/dist/cjs/run.cjs.map +1 -1
  127. package/dist/cjs/session/AgentSession.cjs +805 -986
  128. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  129. package/dist/cjs/session/JsonlSessionStore.cjs +327 -410
  130. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -1
  131. package/dist/cjs/session/handlers.cjs +192 -208
  132. package/dist/cjs/session/handlers.cjs.map +1 -1
  133. package/dist/cjs/session/ids.cjs +9 -10
  134. package/dist/cjs/session/ids.cjs.map +1 -1
  135. package/dist/cjs/session/index.cjs +4 -0
  136. package/dist/cjs/session/messageSerialization.cjs +94 -156
  137. package/dist/cjs/session/messageSerialization.cjs.map +1 -1
  138. package/dist/cjs/splitStream.cjs +147 -206
  139. package/dist/cjs/splitStream.cjs.map +1 -1
  140. package/dist/cjs/stream.cjs +856 -1344
  141. package/dist/cjs/stream.cjs.map +1 -1
  142. package/dist/cjs/summarization/index.cjs +57 -101
  143. package/dist/cjs/summarization/index.cjs.map +1 -1
  144. package/dist/cjs/summarization/node.cjs +643 -796
  145. package/dist/cjs/summarization/node.cjs.map +1 -1
  146. package/dist/cjs/tools/BashExecutor.cjs +110 -136
  147. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  148. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +165 -245
  149. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  150. package/dist/cjs/tools/Calculator.cjs +36 -57
  151. package/dist/cjs/tools/Calculator.cjs.map +1 -1
  152. package/dist/cjs/tools/CodeExecutor.cjs +126 -168
  153. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  154. package/dist/cjs/tools/CodeSessionFileSummary.cjs +36 -46
  155. package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -1
  156. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +459 -649
  157. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  158. package/dist/cjs/tools/ReadFile.cjs +17 -20
  159. package/dist/cjs/tools/ReadFile.cjs.map +1 -1
  160. package/dist/cjs/tools/SkillTool.cjs +26 -27
  161. package/dist/cjs/tools/SkillTool.cjs.map +1 -1
  162. package/dist/cjs/tools/SubagentTool.cjs +59 -61
  163. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  164. package/dist/cjs/tools/ToolNode.cjs +2109 -2686
  165. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  166. package/dist/cjs/tools/ToolSearch.cjs +663 -825
  167. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  168. package/dist/cjs/tools/cloudflare/CloudflareBridgeRuntime.cjs +248 -340
  169. package/dist/cjs/tools/cloudflare/CloudflareBridgeRuntime.cjs.map +1 -1
  170. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +170 -197
  171. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
  172. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +425 -520
  173. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
  174. package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs +91 -124
  175. package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs.map +1 -1
  176. package/dist/cjs/tools/cloudflare/index.cjs +4 -0
  177. package/dist/cjs/tools/eagerEventExecution.cjs +75 -99
  178. package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -1
  179. package/dist/cjs/tools/handlers.cjs +200 -262
  180. package/dist/cjs/tools/handlers.cjs.map +1 -1
  181. package/dist/cjs/tools/local/CompileCheckTool.cjs +150 -212
  182. package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
  183. package/dist/cjs/tools/local/FileCheckpointer.cjs +77 -85
  184. package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -1
  185. package/dist/cjs/tools/local/LocalCodingTools.cjs +763 -1022
  186. package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
  187. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +666 -941
  188. package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -1
  189. package/dist/cjs/tools/local/LocalExecutionTools.cjs +49 -92
  190. package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -1
  191. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +286 -354
  192. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  193. package/dist/cjs/tools/local/attachments.cjs +108 -165
  194. package/dist/cjs/tools/local/attachments.cjs.map +1 -1
  195. package/dist/cjs/tools/local/bashAst.cjs +99 -113
  196. package/dist/cjs/tools/local/bashAst.cjs.map +1 -1
  197. package/dist/cjs/tools/local/editStrategies.cjs +126 -169
  198. package/dist/cjs/tools/local/editStrategies.cjs.map +1 -1
  199. package/dist/cjs/tools/local/index.cjs +12 -0
  200. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +136 -218
  201. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
  202. package/dist/cjs/tools/local/syntaxCheck.cjs +142 -161
  203. package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -1
  204. package/dist/cjs/tools/local/textEncoding.cjs +25 -23
  205. package/dist/cjs/tools/local/textEncoding.cjs.map +1 -1
  206. package/dist/cjs/tools/local/workspaceFS.cjs +38 -46
  207. package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -1
  208. package/dist/cjs/tools/ptcTimeout.cjs +27 -47
  209. package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
  210. package/dist/cjs/tools/schema.cjs +24 -23
  211. package/dist/cjs/tools/schema.cjs.map +1 -1
  212. package/dist/cjs/tools/search/anthropic.cjs +24 -33
  213. package/dist/cjs/tools/search/anthropic.cjs.map +1 -1
  214. package/dist/cjs/tools/search/content.cjs +95 -137
  215. package/dist/cjs/tools/search/content.cjs.map +1 -1
  216. package/dist/cjs/tools/search/firecrawl.cjs +141 -172
  217. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  218. package/dist/cjs/tools/search/format.cjs +128 -196
  219. package/dist/cjs/tools/search/format.cjs.map +1 -1
  220. package/dist/cjs/tools/search/highlights.cjs +165 -232
  221. package/dist/cjs/tools/search/highlights.cjs.map +1 -1
  222. package/dist/cjs/tools/search/index.cjs +2 -0
  223. package/dist/cjs/tools/search/rerankers.cjs +151 -174
  224. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  225. package/dist/cjs/tools/search/schema.cjs +40 -39
  226. package/dist/cjs/tools/search/schema.cjs.map +1 -1
  227. package/dist/cjs/tools/search/search.cjs +428 -530
  228. package/dist/cjs/tools/search/search.cjs.map +1 -1
  229. package/dist/cjs/tools/search/serper-scraper.cjs +106 -127
  230. package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
  231. package/dist/cjs/tools/search/tavily-scraper.cjs +129 -181
  232. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
  233. package/dist/cjs/tools/search/tavily-search.cjs +295 -359
  234. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
  235. package/dist/cjs/tools/search/tool.cjs +260 -299
  236. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  237. package/dist/cjs/tools/search/utils.cjs +74 -117
  238. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  239. package/dist/cjs/tools/skillCatalog.cjs +54 -72
  240. package/dist/cjs/tools/skillCatalog.cjs.map +1 -1
  241. package/dist/cjs/tools/streamedToolCallSeals.cjs +19 -36
  242. package/dist/cjs/tools/streamedToolCallSeals.cjs.map +1 -1
  243. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +612 -771
  244. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  245. package/dist/cjs/tools/subagent/index.cjs +1 -0
  246. package/dist/cjs/tools/toolOutputReferences.cjs +523 -630
  247. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  248. package/dist/cjs/utils/callbacks.cjs +11 -21
  249. package/dist/cjs/utils/callbacks.cjs.map +1 -1
  250. package/dist/cjs/utils/errors.cjs +70 -95
  251. package/dist/cjs/utils/errors.cjs.map +1 -1
  252. package/dist/cjs/utils/events.cjs +32 -42
  253. package/dist/cjs/utils/events.cjs.map +1 -1
  254. package/dist/cjs/utils/graph.cjs +8 -12
  255. package/dist/cjs/utils/graph.cjs.map +1 -1
  256. package/dist/cjs/utils/handlers.cjs +60 -82
  257. package/dist/cjs/utils/handlers.cjs.map +1 -1
  258. package/dist/cjs/utils/index.cjs +9 -0
  259. package/dist/cjs/utils/llm.cjs +19 -27
  260. package/dist/cjs/utils/llm.cjs.map +1 -1
  261. package/dist/cjs/utils/misc.cjs +30 -46
  262. package/dist/cjs/utils/misc.cjs.map +1 -1
  263. package/dist/cjs/utils/run.cjs +50 -66
  264. package/dist/cjs/utils/run.cjs.map +1 -1
  265. package/dist/cjs/utils/schema.cjs +11 -19
  266. package/dist/cjs/utils/schema.cjs.map +1 -1
  267. package/dist/cjs/utils/title.cjs +71 -106
  268. package/dist/cjs/utils/title.cjs.map +1 -1
  269. package/dist/cjs/utils/tokens.cjs +186 -283
  270. package/dist/cjs/utils/tokens.cjs.map +1 -1
  271. package/dist/cjs/utils/truncation.cjs +95 -114
  272. package/dist/cjs/utils/truncation.cjs.map +1 -1
  273. package/dist/esm/agents/AgentContext.mjs +844 -1044
  274. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  275. package/dist/esm/common/constants.mjs +13 -11
  276. package/dist/esm/common/constants.mjs.map +1 -1
  277. package/dist/esm/common/enum.mjs +221 -238
  278. package/dist/esm/common/enum.mjs.map +1 -1
  279. package/dist/esm/common/index.mjs +3 -0
  280. package/dist/esm/events.mjs +121 -167
  281. package/dist/esm/events.mjs.map +1 -1
  282. package/dist/esm/graphs/Graph.mjs +1388 -1804
  283. package/dist/esm/graphs/Graph.mjs.map +1 -1
  284. package/dist/esm/graphs/MultiAgentGraph.mjs +713 -943
  285. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  286. package/dist/esm/graphs/index.mjs +3 -0
  287. package/dist/esm/hitl/askUserQuestion.mjs +60 -60
  288. package/dist/esm/hitl/askUserQuestion.mjs.map +1 -1
  289. package/dist/esm/hitl/index.mjs +2 -0
  290. package/dist/esm/hooks/HookRegistry.mjs +176 -200
  291. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  292. package/dist/esm/hooks/createToolPolicyHook.mjs +71 -99
  293. package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -1
  294. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +170 -271
  295. package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -1
  296. package/dist/esm/hooks/executeHooks.mjs +227 -280
  297. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  298. package/dist/esm/hooks/index.mjs +7 -0
  299. package/dist/esm/hooks/matchers.mjs +196 -228
  300. package/dist/esm/hooks/matchers.mjs.map +1 -1
  301. package/dist/esm/hooks/types.mjs +24 -22
  302. package/dist/esm/hooks/types.mjs.map +1 -1
  303. package/dist/esm/instrumentation.mjs +109 -132
  304. package/dist/esm/instrumentation.mjs.map +1 -1
  305. package/dist/esm/langchain/google-common.mjs +1 -2
  306. package/dist/esm/langchain/index.mjs +5 -5
  307. package/dist/esm/langchain/language_models/chat_models.mjs +1 -2
  308. package/dist/esm/langchain/messages/tool.mjs +1 -2
  309. package/dist/esm/langchain/messages.mjs +2 -2
  310. package/dist/esm/langchain/openai.mjs +1 -2
  311. package/dist/esm/langchain/prompts.mjs +2 -2
  312. package/dist/esm/langchain/runnables.mjs +2 -2
  313. package/dist/esm/langchain/tools.mjs +2 -2
  314. package/dist/esm/langchain/utils/env.mjs +2 -2
  315. package/dist/esm/langfuse.mjs +60 -76
  316. package/dist/esm/langfuse.mjs.map +1 -1
  317. package/dist/esm/langfuseToolOutputTracing.mjs +267 -395
  318. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  319. package/dist/esm/llm/anthropic/index.mjs +432 -559
  320. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  321. package/dist/esm/llm/anthropic/types.mjs +23 -45
  322. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  323. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +439 -690
  324. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  325. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +171 -249
  326. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  327. package/dist/esm/llm/anthropic/utils/output_parsers.mjs +3 -0
  328. package/dist/esm/llm/anthropic/utils/tools.mjs +12 -24
  329. package/dist/esm/llm/anthropic/utils/tools.mjs.map +1 -1
  330. package/dist/esm/llm/bedrock/index.mjs +195 -238
  331. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  332. package/dist/esm/llm/bedrock/toolCache.mjs +84 -104
  333. package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
  334. package/dist/esm/llm/bedrock/utils/index.mjs +3 -0
  335. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +357 -618
  336. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  337. package/dist/esm/llm/bedrock/utils/message_outputs.mjs +105 -147
  338. package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
  339. package/dist/esm/llm/fake.mjs +86 -94
  340. package/dist/esm/llm/fake.mjs.map +1 -1
  341. package/dist/esm/llm/google/index.mjs +183 -235
  342. package/dist/esm/llm/google/index.mjs.map +1 -1
  343. package/dist/esm/llm/google/utils/common.mjs +397 -666
  344. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  345. package/dist/esm/llm/google/utils/zod_to_genai_parameters.mjs +3 -0
  346. package/dist/esm/llm/init.mjs +44 -51
  347. package/dist/esm/llm/init.mjs.map +1 -1
  348. package/dist/esm/llm/invoke.mjs +142 -180
  349. package/dist/esm/llm/invoke.mjs.map +1 -1
  350. package/dist/esm/llm/openai/index.mjs +991 -1271
  351. package/dist/esm/llm/openai/index.mjs.map +1 -1
  352. package/dist/esm/llm/openai/utils/index.mjs +188 -312
  353. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  354. package/dist/esm/llm/openrouter/index.mjs +102 -151
  355. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  356. package/dist/esm/llm/openrouter/toolCache.mjs +35 -42
  357. package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -1
  358. package/dist/esm/llm/providers.mjs +29 -34
  359. package/dist/esm/llm/providers.mjs.map +1 -1
  360. package/dist/esm/llm/request.mjs +20 -31
  361. package/dist/esm/llm/request.mjs.map +1 -1
  362. package/dist/esm/llm/vertexai/index.mjs +427 -449
  363. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  364. package/dist/esm/main.mjs +99 -87
  365. package/dist/esm/messages/anthropicToolCache.mjs +68 -117
  366. package/dist/esm/messages/anthropicToolCache.mjs.map +1 -1
  367. package/dist/esm/messages/cache.mjs +308 -399
  368. package/dist/esm/messages/cache.mjs.map +1 -1
  369. package/dist/esm/messages/content.mjs +36 -47
  370. package/dist/esm/messages/content.mjs.map +1 -1
  371. package/dist/esm/messages/contextPruning.mjs +112 -143
  372. package/dist/esm/messages/contextPruning.mjs.map +1 -1
  373. package/dist/esm/messages/contextPruningSettings.mjs +36 -44
  374. package/dist/esm/messages/contextPruningSettings.mjs.map +1 -1
  375. package/dist/esm/messages/core.mjs +254 -393
  376. package/dist/esm/messages/core.mjs.map +1 -1
  377. package/dist/esm/messages/format.mjs +902 -1378
  378. package/dist/esm/messages/format.mjs.map +1 -1
  379. package/dist/esm/messages/ids.mjs +16 -18
  380. package/dist/esm/messages/ids.mjs.map +1 -1
  381. package/dist/esm/messages/index.mjs +13 -0
  382. package/dist/esm/messages/langchain.mjs +18 -16
  383. package/dist/esm/messages/langchain.mjs.map +1 -1
  384. package/dist/esm/messages/prune.mjs +1053 -1514
  385. package/dist/esm/messages/prune.mjs.map +1 -1
  386. package/dist/esm/messages/recency.mjs +77 -93
  387. package/dist/esm/messages/recency.mjs.map +1 -1
  388. package/dist/esm/messages/reducer.mjs +63 -76
  389. package/dist/esm/messages/reducer.mjs.map +1 -1
  390. package/dist/esm/messages/tools.mjs +49 -75
  391. package/dist/esm/messages/tools.mjs.map +1 -1
  392. package/dist/esm/openai/index.mjs +170 -215
  393. package/dist/esm/openai/index.mjs.map +1 -1
  394. package/dist/esm/responses/index.mjs +301 -389
  395. package/dist/esm/responses/index.mjs.map +1 -1
  396. package/dist/esm/run.mjs +903 -1111
  397. package/dist/esm/run.mjs.map +1 -1
  398. package/dist/esm/session/AgentSession.mjs +806 -985
  399. package/dist/esm/session/AgentSession.mjs.map +1 -1
  400. package/dist/esm/session/JsonlSessionStore.mjs +326 -407
  401. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -1
  402. package/dist/esm/session/handlers.mjs +192 -206
  403. package/dist/esm/session/handlers.mjs.map +1 -1
  404. package/dist/esm/session/ids.mjs +9 -8
  405. package/dist/esm/session/ids.mjs.map +1 -1
  406. package/dist/esm/session/index.mjs +5 -0
  407. package/dist/esm/session/messageSerialization.mjs +94 -154
  408. package/dist/esm/session/messageSerialization.mjs.map +1 -1
  409. package/dist/esm/splitStream.mjs +147 -204
  410. package/dist/esm/splitStream.mjs.map +1 -1
  411. package/dist/esm/stream.mjs +854 -1341
  412. package/dist/esm/stream.mjs.map +1 -1
  413. package/dist/esm/summarization/index.mjs +57 -99
  414. package/dist/esm/summarization/index.mjs.map +1 -1
  415. package/dist/esm/summarization/node.mjs +640 -790
  416. package/dist/esm/summarization/node.mjs.map +1 -1
  417. package/dist/esm/tools/BashExecutor.mjs +103 -129
  418. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  419. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +162 -239
  420. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  421. package/dist/esm/tools/Calculator.mjs +34 -36
  422. package/dist/esm/tools/Calculator.mjs.map +1 -1
  423. package/dist/esm/tools/CodeExecutor.mjs +123 -164
  424. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  425. package/dist/esm/tools/CodeSessionFileSummary.mjs +36 -44
  426. package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -1
  427. package/dist/esm/tools/ProgrammaticToolCalling.mjs +454 -644
  428. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  429. package/dist/esm/tools/ReadFile.mjs +17 -18
  430. package/dist/esm/tools/ReadFile.mjs.map +1 -1
  431. package/dist/esm/tools/SkillTool.mjs +26 -25
  432. package/dist/esm/tools/SkillTool.mjs.map +1 -1
  433. package/dist/esm/tools/SubagentTool.mjs +59 -59
  434. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  435. package/dist/esm/tools/ToolNode.mjs +2107 -2684
  436. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  437. package/dist/esm/tools/ToolSearch.mjs +659 -804
  438. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  439. package/dist/esm/tools/cloudflare/CloudflareBridgeRuntime.mjs +248 -338
  440. package/dist/esm/tools/cloudflare/CloudflareBridgeRuntime.mjs.map +1 -1
  441. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +170 -195
  442. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
  443. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +424 -517
  444. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
  445. package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs +91 -122
  446. package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs.map +1 -1
  447. package/dist/esm/tools/cloudflare/index.mjs +5 -0
  448. package/dist/esm/tools/eagerEventExecution.mjs +75 -96
  449. package/dist/esm/tools/eagerEventExecution.mjs.map +1 -1
  450. package/dist/esm/tools/handlers.mjs +200 -260
  451. package/dist/esm/tools/handlers.mjs.map +1 -1
  452. package/dist/esm/tools/local/CompileCheckTool.mjs +150 -210
  453. package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
  454. package/dist/esm/tools/local/FileCheckpointer.mjs +77 -83
  455. package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -1
  456. package/dist/esm/tools/local/LocalCodingTools.mjs +760 -1017
  457. package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
  458. package/dist/esm/tools/local/LocalExecutionEngine.mjs +663 -936
  459. package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -1
  460. package/dist/esm/tools/local/LocalExecutionTools.mjs +49 -90
  461. package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -1
  462. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +283 -349
  463. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  464. package/dist/esm/tools/local/attachments.mjs +108 -163
  465. package/dist/esm/tools/local/attachments.mjs.map +1 -1
  466. package/dist/esm/tools/local/bashAst.mjs +99 -111
  467. package/dist/esm/tools/local/bashAst.mjs.map +1 -1
  468. package/dist/esm/tools/local/editStrategies.mjs +126 -167
  469. package/dist/esm/tools/local/editStrategies.mjs.map +1 -1
  470. package/dist/esm/tools/local/index.mjs +13 -0
  471. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +136 -216
  472. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
  473. package/dist/esm/tools/local/syntaxCheck.mjs +138 -155
  474. package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -1
  475. package/dist/esm/tools/local/textEncoding.mjs +25 -21
  476. package/dist/esm/tools/local/textEncoding.mjs.map +1 -1
  477. package/dist/esm/tools/local/workspaceFS.mjs +38 -44
  478. package/dist/esm/tools/local/workspaceFS.mjs.map +1 -1
  479. package/dist/esm/tools/ptcTimeout.mjs +27 -42
  480. package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
  481. package/dist/esm/tools/schema.mjs +24 -21
  482. package/dist/esm/tools/schema.mjs.map +1 -1
  483. package/dist/esm/tools/search/anthropic.mjs +24 -31
  484. package/dist/esm/tools/search/anthropic.mjs.map +1 -1
  485. package/dist/esm/tools/search/content.mjs +93 -116
  486. package/dist/esm/tools/search/content.mjs.map +1 -1
  487. package/dist/esm/tools/search/firecrawl.mjs +139 -169
  488. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  489. package/dist/esm/tools/search/format.mjs +128 -194
  490. package/dist/esm/tools/search/format.mjs.map +1 -1
  491. package/dist/esm/tools/search/highlights.mjs +165 -230
  492. package/dist/esm/tools/search/highlights.mjs.map +1 -1
  493. package/dist/esm/tools/search/index.mjs +3 -0
  494. package/dist/esm/tools/search/rerankers.mjs +149 -168
  495. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  496. package/dist/esm/tools/search/schema.mjs +39 -37
  497. package/dist/esm/tools/search/schema.mjs.map +1 -1
  498. package/dist/esm/tools/search/search.mjs +426 -528
  499. package/dist/esm/tools/search/search.mjs.map +1 -1
  500. package/dist/esm/tools/search/serper-scraper.mjs +104 -124
  501. package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
  502. package/dist/esm/tools/search/tavily-scraper.mjs +127 -178
  503. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
  504. package/dist/esm/tools/search/tavily-search.mjs +293 -357
  505. package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
  506. package/dist/esm/tools/search/tool.mjs +259 -297
  507. package/dist/esm/tools/search/tool.mjs.map +1 -1
  508. package/dist/esm/tools/search/utils.mjs +74 -115
  509. package/dist/esm/tools/search/utils.mjs.map +1 -1
  510. package/dist/esm/tools/skillCatalog.mjs +54 -70
  511. package/dist/esm/tools/skillCatalog.mjs.map +1 -1
  512. package/dist/esm/tools/streamedToolCallSeals.mjs +19 -31
  513. package/dist/esm/tools/streamedToolCallSeals.mjs.map +1 -1
  514. package/dist/esm/tools/subagent/SubagentExecutor.mjs +612 -768
  515. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  516. package/dist/esm/tools/subagent/index.mjs +2 -0
  517. package/dist/esm/tools/toolOutputReferences.mjs +523 -624
  518. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  519. package/dist/esm/utils/callbacks.mjs +11 -19
  520. package/dist/esm/utils/callbacks.mjs.map +1 -1
  521. package/dist/esm/utils/errors.mjs +70 -93
  522. package/dist/esm/utils/errors.mjs.map +1 -1
  523. package/dist/esm/utils/events.mjs +32 -40
  524. package/dist/esm/utils/events.mjs.map +1 -1
  525. package/dist/esm/utils/graph.mjs +8 -10
  526. package/dist/esm/utils/graph.mjs.map +1 -1
  527. package/dist/esm/utils/handlers.mjs +60 -80
  528. package/dist/esm/utils/handlers.mjs.map +1 -1
  529. package/dist/esm/utils/index.mjs +10 -0
  530. package/dist/esm/utils/llm.mjs +19 -25
  531. package/dist/esm/utils/llm.mjs.map +1 -1
  532. package/dist/esm/utils/misc.mjs +30 -44
  533. package/dist/esm/utils/misc.mjs.map +1 -1
  534. package/dist/esm/utils/run.mjs +50 -64
  535. package/dist/esm/utils/run.mjs.map +1 -1
  536. package/dist/esm/utils/schema.mjs +11 -17
  537. package/dist/esm/utils/schema.mjs.map +1 -1
  538. package/dist/esm/utils/title.mjs +71 -104
  539. package/dist/esm/utils/title.mjs.map +1 -1
  540. package/dist/esm/utils/tokens.mjs +186 -281
  541. package/dist/esm/utils/tokens.mjs.map +1 -1
  542. package/dist/esm/utils/truncation.mjs +95 -112
  543. package/dist/esm/utils/truncation.mjs.map +1 -1
  544. package/dist/types/messages/format.d.ts +5 -0
  545. package/dist/types/tools/search/tool.d.ts +17 -0
  546. package/dist/types/tools/search/types.d.ts +4 -0
  547. package/package.json +11 -17
  548. package/src/llm/anthropic/llm.spec.ts +36 -0
  549. package/src/llm/anthropic/utils/message_inputs.ts +45 -3
  550. package/src/llm/anthropic/utils/message_outputs.ts +6 -2
  551. package/src/llm/anthropic/utils/streaming-tool-input.test.ts +186 -0
  552. package/src/messages/cache.test.ts +122 -0
  553. package/src/messages/cache.ts +25 -1
  554. package/src/messages/format.ts +9 -0
  555. package/src/messages/formatAgentMessages.skills.test.ts +100 -0
  556. package/src/tools/search/highlights.ts +9 -1
  557. package/src/tools/search/search.ts +41 -3
  558. package/src/tools/search/source-processing.test.ts +373 -0
  559. package/src/tools/search/tool.ts +22 -2
  560. package/src/tools/search/types.ts +4 -0
  561. package/dist/cjs/langchain/google-common.cjs.map +0 -1
  562. package/dist/cjs/langchain/index.cjs.map +0 -1
  563. package/dist/cjs/langchain/language_models/chat_models.cjs.map +0 -1
  564. package/dist/cjs/langchain/messages/tool.cjs.map +0 -1
  565. package/dist/cjs/langchain/messages.cjs.map +0 -1
  566. package/dist/cjs/langchain/openai.cjs.map +0 -1
  567. package/dist/cjs/langchain/prompts.cjs.map +0 -1
  568. package/dist/cjs/langchain/runnables.cjs.map +0 -1
  569. package/dist/cjs/langchain/tools.cjs.map +0 -1
  570. package/dist/cjs/langchain/utils/env.cjs.map +0 -1
  571. package/dist/cjs/main.cjs.map +0 -1
  572. package/dist/esm/langchain/google-common.mjs.map +0 -1
  573. package/dist/esm/langchain/index.mjs.map +0 -1
  574. package/dist/esm/langchain/language_models/chat_models.mjs.map +0 -1
  575. package/dist/esm/langchain/messages/tool.mjs.map +0 -1
  576. package/dist/esm/langchain/messages.mjs.map +0 -1
  577. package/dist/esm/langchain/openai.mjs.map +0 -1
  578. package/dist/esm/langchain/prompts.mjs.map +0 -1
  579. package/dist/esm/langchain/runnables.mjs.map +0 -1
  580. package/dist/esm/langchain/tools.mjs.map +0 -1
  581. package/dist/esm/langchain/utils/env.mjs.map +0 -1
  582. package/dist/esm/main.mjs.map +0 -1
@@ -1,1034 +1,758 @@
1
- 'use strict';
2
-
3
- var os = require('os');
4
- var child_process = require('child_process');
5
- var fs = require('fs');
6
- var crypto = require('crypto');
7
- var path = require('path');
8
- var promises = require('fs/promises');
9
- var bashAst = require('./bashAst.cjs');
10
- var workspaceFS = require('./workspaceFS.cjs');
11
-
12
- const DEFAULT_TIMEOUT_MS = 60000;
13
- const DEFAULT_MAX_OUTPUT_CHARS = 200000;
1
+ const require_bashAst = require("./bashAst.cjs");
2
+ const require_workspaceFS = require("./workspaceFS.cjs");
3
+ let path = require("path");
4
+ let os = require("os");
5
+ let child_process = require("child_process");
6
+ let fs = require("fs");
7
+ let crypto = require("crypto");
8
+ let fs_promises = require("fs/promises");
9
+ //#region src/tools/local/LocalExecutionEngine.ts
10
+ const DEFAULT_TIMEOUT_MS = 6e4;
11
+ const DEFAULT_MAX_OUTPUT_CHARS = 2e5;
14
12
  /**
15
- * Hard cap on total stdout+stderr bytes a child process can stream
16
- * before we kill its process tree. Independent from `maxOutputChars`
17
- * (which only affects what the *model* sees) — this is the OOM
18
- * backstop. Configurable via `local.maxSpawnedBytes`.
19
- */
13
+ * Hard cap on total stdout+stderr bytes a child process can stream
14
+ * before we kill its process tree. Independent from `maxOutputChars`
15
+ * (which only affects what the *model* sees) — this is the OOM
16
+ * backstop. Configurable via `local.maxSpawnedBytes`.
17
+ */
20
18
  const DEFAULT_MAX_SPAWNED_BYTES = 50 * 1024 * 1024;
21
- const DEFAULT_LOCAL_SESSION_ID = 'local';
22
- const DEFAULT_SHELL = process.platform === 'win32' ? 'bash.exe' : 'bash';
23
- // `(?:--\s+)?` before each destructive-target alternation: GNU/BSD
24
- // utilities accept `--` as an end-of-options marker, so `rm -rf -- /`
25
- // is identical in effect to `rm -rf /` but pre-fix it slipped past
26
- // the guard because the regex required the path to follow option
27
- // flags directly. Codex P1 #20.
28
- // `DESTRUCTIVE_TARGET` is the canonical "protected location" pattern:
29
- // matches `/`, `~`, `$HOME`, `${HOME}`, `.`, each optionally followed
30
- // by a trailing-slash and/or wildcard glob suffix. The suffix matrix:
31
- // '' — `$HOME` (round 14)
32
- // '/' — `$HOME/` (round 14, Codex P1 [37])
33
- // '*' — `$HOME*` (round 15, Codex P1 [42])
34
- // '/*' — `$HOME/*` (round 15, Codex P1 [42])
35
- // '.*' — `$HOME.*` (round 17, Codex P1 [47])
36
- // '/.*' — `$HOME/.*` (round 17, Codex P1 [47]) — the
37
- // dot-glob form deletes all dotfiles under the protected
38
- // root, just as destructive as `/*` but the prior matrix
39
- // missed it.
40
- // Suffix expression: `(?:\/?\.?\*|\/)?` — one of:
41
- // `\/?\.?\*` → `*`, `.*`, `/*`, `/.*`
42
- // `\/` → `/`
43
- // (empty) → bare base
44
- const DESTRUCTIVE_TARGET = '(?:\\/|~|\\$\\{?HOME\\}?|\\.)(?:\\/?\\.?\\*|\\/)?';
19
+ const DEFAULT_LOCAL_SESSION_ID = "local";
20
+ const DEFAULT_SHELL = process.platform === "win32" ? "bash.exe" : "bash";
21
+ const DESTRUCTIVE_TARGET = "(?:\\/|~|\\$\\{?HOME\\}?|\\.)(?:\\/?\\.?\\*|\\/)?";
45
22
  const dangerousCommandPatterns = [
46
- new RegExp(`\\brm\\s+(?:-[^\\s]*[rf][^\\s]*\\s+|-[^\\s]*[r][^\\s]*\\s+-[^\\s]*[f][^\\s]*\\s+)(?:--\\s+)?${DESTRUCTIVE_TARGET}\\s*(?:$|[;&|])`),
47
- /\b(?:mkfs|mkswap|fdisk|parted|diskutil)\b/,
48
- /\bdd\s+[^;&|]*\bof=\/dev\//,
49
- new RegExp(`\\bchmod\\s+-R\\s+(?:777|a\\+w)\\s+(?:--\\s+)?${DESTRUCTIVE_TARGET}(?:$|\\s|[;&|])`),
50
- new RegExp(`\\bchown\\s+-R\\s+[^;&|]+\\s+(?:--\\s+)?${DESTRUCTIVE_TARGET}(?:$|\\s|[;&|])`),
51
- /:\s*\(\s*\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/,
23
+ new RegExp(`\\brm\\s+(?:-[^\\s]*[rf][^\\s]*\\s+|-[^\\s]*[r][^\\s]*\\s+-[^\\s]*[f][^\\s]*\\s+)(?:--\\s+)?${DESTRUCTIVE_TARGET}\\s*(?:$|[;&|])`),
24
+ /\b(?:mkfs|mkswap|fdisk|parted|diskutil)\b/,
25
+ /\bdd\s+[^;&|]*\bof=\/dev\//,
26
+ new RegExp(`\\bchmod\\s+-R\\s+(?:777|a\\+w)\\s+(?:--\\s+)?${DESTRUCTIVE_TARGET}(?:$|\\s|[;&|])`),
27
+ new RegExp(`\\bchown\\s+-R\\s+[^;&|]+\\s+(?:--\\s+)?${DESTRUCTIVE_TARGET}(?:$|\\s|[;&|])`),
28
+ /:\s*\(\s*\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/
52
29
  ];
53
30
  /**
54
- * Companion patterns that look for destructive targets *inside*
55
- * matching quote pairs. These are checked against the ORIGINAL
56
- * command (not the post-quote-strip `normalized` form), because
57
- * `stripQuotedContent` blanks the contents of quoted spans —
58
- * which would otherwise let `rm -rf "/"` and friends slip past
59
- * `dangerousCommandPatterns`.
60
- *
61
- * Kept as a separate list so we don't pay false-positive cost on
62
- * benign uses like `echo "rm -rf /"` (the print case): each pattern
63
- * here REQUIRES a quote *around the destructive path argument*, not
64
- * just a quote *somewhere* in the command. `echo "rm -rf /"` has
65
- * `/` outside of any quote-pair-around-the-path (the quotes wrap
66
- * the whole `rm -rf /` text), so it doesn't match here either.
67
- */
68
- // Quoted variant uses the same DESTRUCTIVE_TARGET (which accepts an
69
- // optional trailing slash) so `rm -rf "$HOME/"` and `rm -rf "~/"`
70
- // don't slip past. Codex P1 #37.
31
+ * Companion patterns that look for destructive targets *inside*
32
+ * matching quote pairs. These are checked against the ORIGINAL
33
+ * command (not the post-quote-strip `normalized` form), because
34
+ * `stripQuotedContent` blanks the contents of quoted spans —
35
+ * which would otherwise let `rm -rf "/"` and friends slip past
36
+ * `dangerousCommandPatterns`.
37
+ *
38
+ * Kept as a separate list so we don't pay false-positive cost on
39
+ * benign uses like `echo "rm -rf /"` (the print case): each pattern
40
+ * here REQUIRES a quote *around the destructive path argument*, not
41
+ * just a quote *somewhere* in the command. `echo "rm -rf /"` has
42
+ * `/` outside of any quote-pair-around-the-path (the quotes wrap
43
+ * the whole `rm -rf /` text), so it doesn't match here either.
44
+ */
71
45
  const quotedDestructivePatterns = [
72
- new RegExp(`\\brm\\s+(?:-[^\\s]*[rf][^\\s]*\\s+){1,3}(?:--\\s+)?["']${DESTRUCTIVE_TARGET}["']`),
73
- new RegExp(`\\bchmod\\s+-R\\s+(?:777|a\\+w)\\s+(?:--\\s+)?["']${DESTRUCTIVE_TARGET}["']`),
74
- new RegExp(`\\bchown\\s+-R\\s+[^;&|]+\\s+(?:--\\s+)?["']${DESTRUCTIVE_TARGET}["']`),
46
+ new RegExp(`\\brm\\s+(?:-[^\\s]*[rf][^\\s]*\\s+){1,3}(?:--\\s+)?["']${DESTRUCTIVE_TARGET}["']`),
47
+ new RegExp(`\\bchmod\\s+-R\\s+(?:777|a\\+w)\\s+(?:--\\s+)?["']${DESTRUCTIVE_TARGET}["']`),
48
+ new RegExp(`\\bchown\\s+-R\\s+[^;&|]+\\s+(?:--\\s+)?["']${DESTRUCTIVE_TARGET}["']`)
75
49
  ];
76
- /**
77
- * Catches destructive operations smuggled inside a nested shell or
78
- * `eval` call, e.g. `bash -lc "rm -rf $HOME"` — the outer command
79
- * looks benign (`bash -lc "..."`) and the destructive `rm` lives
80
- * inside the quoted payload that `stripQuotedContent` blanks out.
81
- * Comprehensive review (manual finding C) flagged this as a real
82
- * bypass of the otherwise-correct quote-strip-then-match approach.
83
- *
84
- * Run against the ORIGINAL command (quotes intact) so the inside of
85
- * the nested-shell payload is visible. Conservative: matches only
86
- * the same operation set as `dangerousCommandPatterns` (rm -rf,
87
- * chmod -R 777, chown -R) when they appear inside a `<shell> -[l]?c
88
- * "..."` or `eval "..."` payload.
89
- */
90
- const NESTED_SHELL_PREFIX = '(?:(?:ba|z|da|k)?sh|eval)\\s+(?:-l?c\\s+)?';
91
50
  const nestedShellDestructivePatterns = [
92
- new RegExp(NESTED_SHELL_PREFIX +
93
- '["\'][^"\']*\\brm\\s+-[^\\s"\']*[rf][^\\s"\']*\\s+(?:--\\s+)?(?:\\/|~|\\$\\{?HOME\\}?|\\.)'),
94
- new RegExp(NESTED_SHELL_PREFIX +
95
- '["\'][^"\']*\\bchmod\\s+-R\\s+(?:777|a\\+w)\\s+(?:--\\s+)?(?:\\/|~|\\$\\{?HOME\\}?|\\.)'),
96
- new RegExp(NESTED_SHELL_PREFIX +
97
- '["\'][^"\']*\\bchown\\s+-R\\s+[^;&|]+\\s+(?:--\\s+)?(?:\\/|~|\\$\\{?HOME\\}?|\\.)'),
51
+ /* @__PURE__ */ new RegExp("(?:(?:ba|z|da|k)?sh|eval)\\s+(?:-l?c\\s+)?[\"'][^\"']*\\brm\\s+-[^\\s\"']*[rf][^\\s\"']*\\s+(?:--\\s+)?(?:\\/|~|\\$\\{?HOME\\}?|\\.)"),
52
+ /* @__PURE__ */ new RegExp("(?:(?:ba|z|da|k)?sh|eval)\\s+(?:-l?c\\s+)?[\"'][^\"']*\\bchmod\\s+-R\\s+(?:777|a\\+w)\\s+(?:--\\s+)?(?:\\/|~|\\$\\{?HOME\\}?|\\.)"),
53
+ /* @__PURE__ */ new RegExp("(?:(?:ba|z|da|k)?sh|eval)\\s+(?:-l?c\\s+)?[\"'][^\"']*\\bchown\\s+-R\\s+[^;&|]+\\s+(?:--\\s+)?(?:\\/|~|\\$\\{?HOME\\}?|\\.)")
98
54
  ];
99
55
  const mutatingCommandPattern = /\b(?:rm|mv|cp|touch|mkdir|rmdir|ln|truncate|tee|sed\s+-i|perl\s+-pi|python(?:3)?\s+-c|node\s+-e|npm\s+(?:install|ci|update|publish)|pnpm\s+(?:install|update|publish)|yarn\s+(?:install|add|publish)|git\s+(?:add|commit|checkout|switch|reset|clean|rebase|merge|push|pull|stash|tag|branch)|chmod|chown)\b|(?:^|[^<])>\s*[^&]|\bcat\s+[^|;&]*>\s*/;
100
56
  /**
101
- * POSIX convention: `128 + signum` when a process is killed by a
102
- * signal. Maps the common signals; unknown ones default to 1 so the
103
- * caller still sees a non-zero (failed) exit. Only used when Node's
104
- * `close` event reports `exitCode === null` (true signal kill).
105
- */
57
+ * POSIX convention: `128 + signum` when a process is killed by a
58
+ * signal. Maps the common signals; unknown ones default to 1 so the
59
+ * caller still sees a non-zero (failed) exit. Only used when Node's
60
+ * `close` event reports `exitCode === null` (true signal kill).
61
+ */
106
62
  const SIGNAL_TO_EXIT_CODE = {
107
- SIGHUP: 129,
108
- SIGINT: 130,
109
- SIGQUIT: 131,
110
- SIGILL: 132,
111
- SIGTRAP: 133,
112
- SIGABRT: 134,
113
- SIGBUS: 135,
114
- SIGFPE: 136,
115
- SIGKILL: 137,
116
- SIGUSR1: 138,
117
- SIGSEGV: 139,
118
- SIGUSR2: 140,
119
- SIGPIPE: 141,
120
- SIGALRM: 142,
121
- SIGTERM: 143,
63
+ SIGHUP: 129,
64
+ SIGINT: 130,
65
+ SIGQUIT: 131,
66
+ SIGILL: 132,
67
+ SIGTRAP: 133,
68
+ SIGABRT: 134,
69
+ SIGBUS: 135,
70
+ SIGFPE: 136,
71
+ SIGKILL: 137,
72
+ SIGUSR1: 138,
73
+ SIGSEGV: 139,
74
+ SIGUSR2: 140,
75
+ SIGPIPE: 141,
76
+ SIGALRM: 142,
77
+ SIGTERM: 143
122
78
  };
123
79
  function exitCodeForSignal(signal) {
124
- if (signal == null)
125
- return 1;
126
- return SIGNAL_TO_EXIT_CODE[signal] ?? 1;
80
+ if (signal == null) return 1;
81
+ return SIGNAL_TO_EXIT_CODE[signal] ?? 1;
127
82
  }
128
83
  let sandboxConfigKey;
129
84
  let sandboxInitialized = false;
130
85
  let sandboxRuntimePromise;
131
86
  function isToolExecutionConfig(config) {
132
- return 'engine' in config || 'local' in config;
87
+ return "engine" in config || "local" in config;
133
88
  }
134
89
  function resolveLocalExecutionConfig(config) {
135
- if (config != null && isToolExecutionConfig(config)) {
136
- return config.local ?? {};
137
- }
138
- return config ?? {};
90
+ if (config != null && isToolExecutionConfig(config)) return config.local ?? {};
91
+ return config ?? {};
139
92
  }
140
93
  function getLocalCwd(config) {
141
- return path.resolve(config?.workspace?.root ?? config?.cwd ?? process.cwd());
94
+ return (0, path.resolve)(config?.workspace?.root ?? config?.cwd ?? process.cwd());
142
95
  }
143
96
  /**
144
- * Resolves the effective workspace boundary: a list of absolute roots
145
- * that file operations are allowed to touch. The first entry is always
146
- * the canonical root (`getLocalCwd`); subsequent entries come from
147
- * `workspace.additionalRoots` when provided.
148
- *
149
- * Returns plain absolute paths — callers symlink-resolve when they
150
- * need realpath equality (see `resolveWorkspacePathSafe`).
151
- */
97
+ * Resolves the effective workspace boundary: a list of absolute roots
98
+ * that file operations are allowed to touch. The first entry is always
99
+ * the canonical root (`getLocalCwd`); subsequent entries come from
100
+ * `workspace.additionalRoots` when provided.
101
+ *
102
+ * Returns plain absolute paths — callers symlink-resolve when they
103
+ * need realpath equality (see `resolveWorkspacePathSafe`).
104
+ */
152
105
  function getWorkspaceRoots(config) {
153
- const root = getLocalCwd(config);
154
- const extras = config?.workspace?.additionalRoots ?? [];
155
- if (extras.length === 0)
156
- return [root];
157
- const seen = new Set([root]);
158
- const out = [root];
159
- for (const extra of extras) {
160
- // Relative `additionalRoots` entries are anchored to the
161
- // workspace root (so monorepo configs like
162
- // `additionalRoots: ['../shared']` resolve to a sibling of
163
- // `root` rather than to `process.cwd()/../shared`, which would
164
- // mean something completely different on a server with a
165
- // different cwd).
166
- const abs = path.isAbsolute(extra) ? path.resolve(extra) : path.resolve(root, extra);
167
- if (!seen.has(abs)) {
168
- seen.add(abs);
169
- out.push(abs);
170
- }
171
- }
172
- return out;
106
+ const root = getLocalCwd(config);
107
+ const extras = config?.workspace?.additionalRoots ?? [];
108
+ if (extras.length === 0) return [root];
109
+ const seen = new Set([root]);
110
+ const out = [root];
111
+ for (const extra of extras) {
112
+ const abs = (0, path.isAbsolute)(extra) ? (0, path.resolve)(extra) : (0, path.resolve)(root, extra);
113
+ if (!seen.has(abs)) {
114
+ seen.add(abs);
115
+ out.push(abs);
116
+ }
117
+ }
118
+ return out;
173
119
  }
174
120
  /**
175
- * Pluggable spawn resolver. Honours `local.exec.spawn` first, falls
176
- * back to the legacy top-level `local.spawn`, then to Node's
177
- * `child_process.spawn`. Centralised so engine swapping is one knob.
178
- */
121
+ * Pluggable spawn resolver. Honours `local.exec.spawn` first, falls
122
+ * back to the legacy top-level `local.spawn`, then to Node's
123
+ * `child_process.spawn`. Centralised so engine swapping is one knob.
124
+ */
179
125
  function getSpawn(config) {
180
- return (config?.exec?.spawn ?? config?.spawn ?? child_process.spawn);
126
+ return config?.exec?.spawn ?? config?.spawn ?? child_process.spawn;
181
127
  }
182
128
  /**
183
- * Pluggable filesystem resolver. Honours `local.exec.fs`, falls back
184
- * to the Node-host implementation. A future remote engine supplies
185
- * its own implementation here and inherits every file-touching tool.
186
- */
129
+ * Pluggable filesystem resolver. Honours `local.exec.fs`, falls back
130
+ * to the Node-host implementation. A future remote engine supplies
131
+ * its own implementation here and inherits every file-touching tool.
132
+ */
187
133
  function getWorkspaceFS(config) {
188
- return config?.exec?.fs ?? workspaceFS.nodeWorkspaceFS;
134
+ return config?.exec?.fs ?? require_workspaceFS.nodeWorkspaceFS;
189
135
  }
190
136
  /**
191
- * Resolves the workspace boundary for *write* operations. Honours
192
- * `workspace.allowWriteOutside` (and the deprecated
193
- * `allowOutsideWorkspace`) by returning `null`, which the path-safety
194
- * helpers interpret as "skip the write clamp".
195
- */
137
+ * Resolves the workspace boundary for *write* operations. Honours
138
+ * `workspace.allowWriteOutside` (and the deprecated
139
+ * `allowOutsideWorkspace`) by returning `null`, which the path-safety
140
+ * helpers interpret as "skip the write clamp".
141
+ */
196
142
  function getWriteRoots(config = {}) {
197
- // Granular flag wins over the legacy one when explicitly set
198
- // (true OR false) otherwise a host tightening access during
199
- // migration (`allowOutsideWorkspace: true, workspace.
200
- // allowWriteOutside: false`) would still get the loose behavior
201
- // because the legacy flag short-circuited the OR. Codex P1 #36.
202
- const granular = config.workspace?.allowWriteOutside;
203
- if (granular === true)
204
- return null;
205
- if (granular === false)
206
- return getWorkspaceRoots(config);
207
- if (config.allowOutsideWorkspace === true)
208
- return null;
209
- return getWorkspaceRoots(config);
143
+ const granular = config.workspace?.allowWriteOutside;
144
+ if (granular === true) return null;
145
+ if (granular === false) return getWorkspaceRoots(config);
146
+ if (config.allowOutsideWorkspace === true) return null;
147
+ return getWorkspaceRoots(config);
210
148
  }
211
149
  /**
212
- * Resolves the workspace boundary for *read* operations. Honours
213
- * `workspace.allowReadOutside` (and the deprecated
214
- * `allowOutsideWorkspace`) by returning `null`.
215
- */
150
+ * Resolves the workspace boundary for *read* operations. Honours
151
+ * `workspace.allowReadOutside` (and the deprecated
152
+ * `allowOutsideWorkspace`) by returning `null`.
153
+ */
216
154
  function getReadRoots(config = {}) {
217
- // Same precedence as getWriteRoots: granular flag is authoritative
218
- // when set, legacy flag is the fallback. Codex P1 #36.
219
- const granular = config.workspace?.allowReadOutside;
220
- if (granular === true)
221
- return null;
222
- if (granular === false)
223
- return getWorkspaceRoots(config);
224
- if (config.allowOutsideWorkspace === true)
225
- return null;
226
- return getWorkspaceRoots(config);
155
+ const granular = config.workspace?.allowReadOutside;
156
+ if (granular === true) return null;
157
+ if (granular === false) return getWorkspaceRoots(config);
158
+ if (config.allowOutsideWorkspace === true) return null;
159
+ return getWorkspaceRoots(config);
227
160
  }
228
161
  function getLocalSessionId(config) {
229
- const cwd = getLocalCwd(config);
230
- const digest = crypto.createHash('sha1').update(cwd).digest('hex').slice(0, 12);
231
- return `${DEFAULT_LOCAL_SESSION_ID}:${digest}`;
162
+ const cwd = getLocalCwd(config);
163
+ return `${DEFAULT_LOCAL_SESSION_ID}:${(0, crypto.createHash)("sha1").update(cwd).digest("hex").slice(0, 12)}`;
232
164
  }
233
- const missingSandboxRuntimeMessage = [
234
- 'Local sandbox is enabled, but @anthropic-ai/sandbox-runtime is not installed.',
235
- 'Install it with `npm install @anthropic-ai/sandbox-runtime`, or disable local sandboxing with `local.sandbox.enabled: false`.',
236
- ].join(' ');
237
- const sandboxRuntimePackage = '@anthropic-ai/sandbox-runtime';
165
+ const missingSandboxRuntimeMessage = ["Local sandbox is enabled, but @anthropic-ai/sandbox-runtime is not installed.", "Install it with `npm install @anthropic-ai/sandbox-runtime`, or disable local sandboxing with `local.sandbox.enabled: false`."].join(" ");
166
+ const sandboxRuntimePackage = "@anthropic-ai/sandbox-runtime";
238
167
  /** Lazy-loads the ESM-only sandbox runtime only when sandboxing is enabled. */
239
168
  function loadSandboxRuntime() {
240
- sandboxRuntimePromise ??= import(sandboxRuntimePackage);
241
- return sandboxRuntimePromise;
169
+ sandboxRuntimePromise ??= import(sandboxRuntimePackage);
170
+ return sandboxRuntimePromise;
242
171
  }
243
172
  function shouldUseLocalSandbox(config) {
244
- return config.sandbox?.enabled === true;
173
+ return config.sandbox?.enabled === true;
245
174
  }
246
175
  let sandboxOffWarned = false;
247
176
  function maybeWarnSandboxOff(config) {
248
- if (sandboxOffWarned ||
249
- shouldUseLocalSandbox(config) ||
250
- config.exec?.sandboxed === true) {
251
- return;
252
- }
253
- sandboxOffWarned = true;
254
- // eslint-disable-next-line no-console
255
- console.warn('[@librechat/agents] Local execution engine is running without ' +
256
- '@anthropic-ai/sandbox-runtime wrapping. The agent has full access to ' +
257
- 'the host filesystem and network. Set toolExecution.local.sandbox.enabled ' +
258
- '= true to opt into process sandboxing.');
177
+ if (sandboxOffWarned || shouldUseLocalSandbox(config) || config.exec?.sandboxed === true) return;
178
+ sandboxOffWarned = true;
179
+ console.warn("[@librechat/agents] Local execution engine is running without @anthropic-ai/sandbox-runtime wrapping. The agent has full access to the host filesystem and network. Set toolExecution.local.sandbox.enabled = true to opt into process sandboxing.");
259
180
  }
260
181
  /**
261
- * Test-only reset hook for the sandbox-off warning latch.
262
- *
263
- * @internal Not part of the public SDK surface.
264
- */
182
+ * Test-only reset hook for the sandbox-off warning latch.
183
+ *
184
+ * @internal Not part of the public SDK surface.
185
+ */
265
186
  function _resetLocalEngineWarningsForTests() {
266
- sandboxOffWarned = false;
187
+ sandboxOffWarned = false;
267
188
  }
268
189
  function truncateLocalOutput(value, maxChars = DEFAULT_MAX_OUTPUT_CHARS) {
269
- if (value.length <= maxChars) {
270
- return value;
271
- }
272
- const head = Math.floor(maxChars * 0.6);
273
- const tail = maxChars - head;
274
- const omitted = value.length - maxChars;
275
- return `${value.slice(0, head)}\n\n[... ${omitted} characters truncated ...]\n\n${value.slice(value.length - tail)}`;
190
+ if (value.length <= maxChars) return value;
191
+ const head = Math.floor(maxChars * .6);
192
+ const tail = maxChars - head;
193
+ const omitted = value.length - maxChars;
194
+ return `${value.slice(0, head)}\n\n[... ${omitted} characters truncated ...]\n\n${value.slice(value.length - tail)}`;
276
195
  }
277
196
  function stripQuotedContent(command) {
278
- let output = '';
279
- let quote;
280
- let escaped = false;
281
- for (let i = 0; i < command.length; i++) {
282
- const char = command[i];
283
- if (escaped) {
284
- escaped = false;
285
- output += ' ';
286
- continue;
287
- }
288
- if (char === '\\') {
289
- escaped = true;
290
- output += ' ';
291
- continue;
292
- }
293
- if (quote != null) {
294
- if (char === quote) {
295
- quote = undefined;
296
- }
297
- output += ' ';
298
- continue;
299
- }
300
- if (char === '"' || char === '\'' || char === '`') {
301
- quote = char;
302
- output += ' ';
303
- continue;
304
- }
305
- if (char === '#') {
306
- while (i < command.length && command[i] !== '\n') {
307
- output += ' ';
308
- i++;
309
- }
310
- output += '\n';
311
- continue;
312
- }
313
- output += char;
314
- }
315
- return output;
197
+ let output = "";
198
+ let quote;
199
+ let escaped = false;
200
+ for (let i = 0; i < command.length; i++) {
201
+ const char = command[i];
202
+ if (escaped) {
203
+ escaped = false;
204
+ output += " ";
205
+ continue;
206
+ }
207
+ if (char === "\\") {
208
+ escaped = true;
209
+ output += " ";
210
+ continue;
211
+ }
212
+ if (quote != null) {
213
+ if (char === quote) quote = void 0;
214
+ output += " ";
215
+ continue;
216
+ }
217
+ if (char === "\"" || char === "'" || char === "`") {
218
+ quote = char;
219
+ output += " ";
220
+ continue;
221
+ }
222
+ if (char === "#") {
223
+ while (i < command.length && command[i] !== "\n") {
224
+ output += " ";
225
+ i++;
226
+ }
227
+ output += "\n";
228
+ continue;
229
+ }
230
+ output += char;
231
+ }
232
+ return output;
316
233
  }
317
234
  async function validateBashCommand(command, config = {}) {
318
- const errors = [];
319
- const warnings = [];
320
- const normalized = stripQuotedContent(command);
321
- if (command.trim() === '') {
322
- errors.push('Command is empty.');
323
- }
324
- if (command.includes('\0')) {
325
- errors.push('Command contains a NUL byte.');
326
- }
327
- if (config.allowDangerousCommands !== true) {
328
- let blocked = false;
329
- // Strip-then-match for the bare-form patterns (avoids false
330
- // positives where the destructive text is buried inside a
331
- // string the user is just printing).
332
- for (const pattern of dangerousCommandPatterns) {
333
- if (pattern.test(normalized)) {
334
- errors.push('Command matches a destructive command pattern.');
335
- blocked = true;
336
- break;
337
- }
338
- }
339
- // Original-form pass for patterns that REQUIRE matching quote
340
- // pairs around a destructive path. Without this, `rm -rf "/"`
341
- // and `chmod -R 777 "/"` slip past the strip-then-match pass
342
- // because their destructive target is inside quotes.
343
- if (!blocked) {
344
- for (const pattern of quotedDestructivePatterns) {
345
- if (pattern.test(command)) {
346
- errors.push('Command matches a destructive command pattern (quoted target).');
347
- blocked = true;
348
- break;
349
- }
350
- }
351
- }
352
- if (!blocked) {
353
- for (const pattern of nestedShellDestructivePatterns) {
354
- if (pattern.test(command)) {
355
- errors.push('Command matches a destructive command pattern (nested shell payload).');
356
- break;
357
- }
358
- }
359
- }
360
- }
361
- const bashAstMode = config.bashAst ?? 'off';
362
- if (bashAstMode !== 'off' && config.allowDangerousCommands !== true) {
363
- const findings = bashAst.runBashAstChecks(normalized, bashAstMode);
364
- const split = bashAst.bashAstFindingsToErrors(findings);
365
- errors.push(...split.errors);
366
- warnings.push(...split.warnings);
367
- }
368
- if (config.readOnly === true && mutatingCommandPattern.test(normalized)) {
369
- errors.push('Command appears to mutate files or repository state in read-only local mode.');
370
- }
371
- // Use the same shell the actual execution path will use. Hard-coding
372
- // DEFAULT_SHELL here would reject perfectly valid commands when the
373
- // host configures `local.shell` to a non-bash binary (or when the
374
- // runtime doesn't have bash installed at all but does have e.g. zsh).
375
- const syntaxShell = config.shell ?? DEFAULT_SHELL;
376
- const syntax = await spawnLocalProcess(syntaxShell, ['-n', '-c', command], {
377
- ...config,
378
- timeoutMs: Math.min(config.timeoutMs ?? DEFAULT_TIMEOUT_MS, 5000),
379
- sandbox: { enabled: false },
380
- }, { internal: true }).catch((error) => ({
381
- stdout: '',
382
- stderr: error.message,
383
- exitCode: 1,
384
- timedOut: false,
385
- }));
386
- if (syntax.exitCode !== 0) {
387
- errors.push(syntax.stderr.trim() === ''
388
- ? 'Command failed shell syntax validation.'
389
- : `Command failed shell syntax validation: ${syntax.stderr.trim()}`);
390
- }
391
- if (/\bsudo\b/.test(normalized)) {
392
- warnings.push('Command requests elevated privileges with sudo.');
393
- }
394
- return {
395
- valid: errors.length === 0,
396
- errors,
397
- warnings,
398
- };
235
+ const errors = [];
236
+ const warnings = [];
237
+ const normalized = stripQuotedContent(command);
238
+ if (command.trim() === "") errors.push("Command is empty.");
239
+ if (command.includes("\0")) errors.push("Command contains a NUL byte.");
240
+ if (config.allowDangerousCommands !== true) {
241
+ let blocked = false;
242
+ for (const pattern of dangerousCommandPatterns) if (pattern.test(normalized)) {
243
+ errors.push("Command matches a destructive command pattern.");
244
+ blocked = true;
245
+ break;
246
+ }
247
+ if (!blocked) {
248
+ for (const pattern of quotedDestructivePatterns) if (pattern.test(command)) {
249
+ errors.push("Command matches a destructive command pattern (quoted target).");
250
+ blocked = true;
251
+ break;
252
+ }
253
+ }
254
+ if (!blocked) {
255
+ for (const pattern of nestedShellDestructivePatterns) if (pattern.test(command)) {
256
+ errors.push("Command matches a destructive command pattern (nested shell payload).");
257
+ break;
258
+ }
259
+ }
260
+ }
261
+ const bashAstMode = config.bashAst ?? "off";
262
+ if (bashAstMode !== "off" && config.allowDangerousCommands !== true) {
263
+ const split = require_bashAst.bashAstFindingsToErrors(require_bashAst.runBashAstChecks(normalized, bashAstMode));
264
+ errors.push(...split.errors);
265
+ warnings.push(...split.warnings);
266
+ }
267
+ if (config.readOnly === true && mutatingCommandPattern.test(normalized)) errors.push("Command appears to mutate files or repository state in read-only local mode.");
268
+ const syntax = await spawnLocalProcess(config.shell ?? DEFAULT_SHELL, [
269
+ "-n",
270
+ "-c",
271
+ command
272
+ ], {
273
+ ...config,
274
+ timeoutMs: Math.min(config.timeoutMs ?? DEFAULT_TIMEOUT_MS, 5e3),
275
+ sandbox: { enabled: false }
276
+ }, { internal: true }).catch((error) => ({
277
+ stdout: "",
278
+ stderr: error.message,
279
+ exitCode: 1,
280
+ timedOut: false
281
+ }));
282
+ if (syntax.exitCode !== 0) errors.push(syntax.stderr.trim() === "" ? "Command failed shell syntax validation." : `Command failed shell syntax validation: ${syntax.stderr.trim()}`);
283
+ if (/\bsudo\b/.test(normalized)) warnings.push("Command requests elevated privileges with sudo.");
284
+ return {
285
+ valid: errors.length === 0,
286
+ errors,
287
+ warnings
288
+ };
399
289
  }
400
290
  async function ensureSandbox(config, cwd) {
401
- if (!shouldUseLocalSandbox(config)) {
402
- return undefined;
403
- }
404
- const runtime = await loadSandboxRuntime().catch((error) => {
405
- throw new Error(`${missingSandboxRuntimeMessage} Cause: ${error.message}`);
406
- });
407
- const runtimeConfig = buildSandboxRuntimeConfig(config, cwd, runtime.getDefaultWritePaths);
408
- const nextKey = JSON.stringify(runtimeConfig);
409
- if (sandboxInitialized && sandboxConfigKey === nextKey) {
410
- return runtime.SandboxManager;
411
- }
412
- const dependencyCheck = runtime.SandboxManager.checkDependencies();
413
- if (dependencyCheck.errors.length > 0) {
414
- if (config.sandbox?.failIfUnavailable === true) {
415
- throw new Error(`Local sandbox requested but unavailable: ${dependencyCheck.errors.join('; ')}`);
416
- }
417
- return undefined;
418
- }
419
- if (sandboxInitialized) {
420
- await runtime.SandboxManager.reset();
421
- }
422
- await runtime.SandboxManager.initialize(runtimeConfig);
423
- sandboxInitialized = true;
424
- sandboxConfigKey = nextKey;
425
- return runtime.SandboxManager;
291
+ if (!shouldUseLocalSandbox(config)) return;
292
+ const runtime = await loadSandboxRuntime().catch((error) => {
293
+ throw new Error(`${missingSandboxRuntimeMessage} Cause: ${error.message}`);
294
+ });
295
+ const runtimeConfig = buildSandboxRuntimeConfig(config, cwd, runtime.getDefaultWritePaths);
296
+ const nextKey = JSON.stringify(runtimeConfig);
297
+ if (sandboxInitialized && sandboxConfigKey === nextKey) return runtime.SandboxManager;
298
+ const dependencyCheck = runtime.SandboxManager.checkDependencies();
299
+ if (dependencyCheck.errors.length > 0) {
300
+ if (config.sandbox?.failIfUnavailable === true) throw new Error(`Local sandbox requested but unavailable: ${dependencyCheck.errors.join("; ")}`);
301
+ return;
302
+ }
303
+ if (sandboxInitialized) await runtime.SandboxManager.reset();
304
+ await runtime.SandboxManager.initialize(runtimeConfig);
305
+ sandboxInitialized = true;
306
+ sandboxConfigKey = nextKey;
307
+ return runtime.SandboxManager;
426
308
  }
427
309
  /**
428
- * Loopback addresses the in-process programmatic-tool bridge listens
429
- * on (`LocalProgrammaticToolCalling.ts` binds 127.0.0.1). Sandboxed
430
- * code launched by `run_tools_with_code` / `run_tools_with_bash` HTTPs
431
- * back to that address — without the entries below, the bridge is
432
- * silently blocked under sandbox.
433
- */
434
- const BRIDGE_LOOPBACK_HOSTS = ['127.0.0.1', 'localhost', '::1'];
310
+ * Loopback addresses the in-process programmatic-tool bridge listens
311
+ * on (`LocalProgrammaticToolCalling.ts` binds 127.0.0.1). Sandboxed
312
+ * code launched by `run_tools_with_code` / `run_tools_with_bash` HTTPs
313
+ * back to that address — without the entries below, the bridge is
314
+ * silently blocked under sandbox.
315
+ */
316
+ const BRIDGE_LOOPBACK_HOSTS = [
317
+ "127.0.0.1",
318
+ "localhost",
319
+ "::1"
320
+ ];
435
321
  function buildSandboxRuntimeConfig(config, cwd, getDefaultWritePaths) {
436
- const sandbox = config.sandbox;
437
- // Seed allowedDomains with loopback so the programmatic-tool bridge
438
- // works under sandbox. If the host explicitly denied a loopback
439
- // entry via `deniedDomains`, respect that and skip seeding it.
440
- const userAllowed = sandbox?.network?.allowedDomains ?? [];
441
- const denied = new Set(sandbox?.network?.deniedDomains ?? []);
442
- const seededLoopback = BRIDGE_LOOPBACK_HOSTS.filter((host) => !denied.has(host) && !userAllowed.includes(host));
443
- const allowedDomains = [...seededLoopback, ...userAllowed];
444
- // Mirror the file-tools workspace boundary: anything in
445
- // `additionalRoots` counts as in-workspace, so sandboxed shell/code
446
- // can write there too. Without this, file_tools can resolve a
447
- // sibling-root path but `bash`/`execute_code` is denied write
448
- // access confusing divergence flagged in Codex P2 #15.
449
- const workspaceWriteRoots = config.workspace?.additionalRoots != null
450
- ? getWorkspaceRoots(config)
451
- : [cwd];
452
- return {
453
- network: {
454
- allowedDomains,
455
- deniedDomains: sandbox?.network?.deniedDomains ?? [],
456
- ...(sandbox?.network?.allowUnixSockets != null && {
457
- allowUnixSockets: sandbox.network.allowUnixSockets,
458
- }),
459
- ...(sandbox?.network?.allowAllUnixSockets != null && {
460
- allowAllUnixSockets: sandbox.network.allowAllUnixSockets,
461
- }),
462
- ...(sandbox?.network?.allowLocalBinding != null && {
463
- allowLocalBinding: sandbox.network.allowLocalBinding,
464
- }),
465
- ...(sandbox?.network?.allowMachLookup != null && {
466
- allowMachLookup: sandbox.network.allowMachLookup,
467
- }),
468
- },
469
- filesystem: {
470
- denyRead: sandbox?.filesystem?.denyRead ?? [],
471
- allowRead: sandbox?.filesystem?.allowRead,
472
- allowWrite: sandbox?.filesystem?.allowWrite ?? [
473
- ...workspaceWriteRoots,
474
- ...getDefaultWritePaths(),
475
- ],
476
- denyWrite: sandbox?.filesystem?.denyWrite ?? [
477
- '.env',
478
- '.env.*',
479
- '.git/config',
480
- '.git/hooks/**',
481
- ],
482
- allowGitConfig: sandbox?.filesystem?.allowGitConfig,
483
- },
484
- };
485
- }
486
- const LOCAL_SPAWN_TIMEOUT_MS = Symbol('librechat.localSpawn.timeoutMs');
322
+ const sandbox = config.sandbox;
323
+ const userAllowed = sandbox?.network?.allowedDomains ?? [];
324
+ const denied = new Set(sandbox?.network?.deniedDomains ?? []);
325
+ const allowedDomains = [...BRIDGE_LOOPBACK_HOSTS.filter((host) => !denied.has(host) && !userAllowed.includes(host)), ...userAllowed];
326
+ const workspaceWriteRoots = config.workspace?.additionalRoots != null ? getWorkspaceRoots(config) : [cwd];
327
+ return {
328
+ network: {
329
+ allowedDomains,
330
+ deniedDomains: sandbox?.network?.deniedDomains ?? [],
331
+ ...sandbox?.network?.allowUnixSockets != null && { allowUnixSockets: sandbox.network.allowUnixSockets },
332
+ ...sandbox?.network?.allowAllUnixSockets != null && { allowAllUnixSockets: sandbox.network.allowAllUnixSockets },
333
+ ...sandbox?.network?.allowLocalBinding != null && { allowLocalBinding: sandbox.network.allowLocalBinding },
334
+ ...sandbox?.network?.allowMachLookup != null && { allowMachLookup: sandbox.network.allowMachLookup }
335
+ },
336
+ filesystem: {
337
+ denyRead: sandbox?.filesystem?.denyRead ?? [],
338
+ allowRead: sandbox?.filesystem?.allowRead,
339
+ allowWrite: sandbox?.filesystem?.allowWrite ?? [...workspaceWriteRoots, ...getDefaultWritePaths()],
340
+ denyWrite: sandbox?.filesystem?.denyWrite ?? [
341
+ ".env",
342
+ ".env.*",
343
+ ".git/config",
344
+ ".git/hooks/**"
345
+ ],
346
+ allowGitConfig: sandbox?.filesystem?.allowGitConfig
347
+ }
348
+ };
349
+ }
350
+ const LOCAL_SPAWN_TIMEOUT_MS = Symbol("librechat.localSpawn.timeoutMs");
487
351
  async function spawnLocalProcess(command, args, config = {}, options) {
488
- const cwd = getLocalCwd(config);
489
- const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
490
- const maxOutputChars = config.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
491
- // Streaming caps. Local tools execute arbitrary shell/code, so a noisy
492
- // command (`yes`, `cat /dev/urandom | base64`, a verbose build) could
493
- // accumulate gigabytes in memory before hitting the post-close cap.
494
- // We bound in-memory per-stream and spill the rest to disk; we also
495
- // hard-kill the child once total streamed bytes pass `maxSpawnedBytes`
496
- // so a process producing unbounded output gets stopped instead of
497
- // letting the host OOM.
498
- const inMemoryCapBytes = maxOutputChars * 2;
499
- const hardKillBytes = config.maxSpawnedBytes ?? DEFAULT_MAX_SPAWNED_BYTES;
500
- const sandboxManager = await ensureSandbox(config, cwd);
501
- // Internal probes (validateBashCommand syntax preflight,
502
- // isRipgrepAvailable, syntax-check probe cache priming) pass
503
- // `internal: true` so they don't emit a misleading "sandbox is
504
- // off" warning AND don't flip `sandboxOffWarned = true`. Without
505
- // this Codex P2 path: a run with `sandbox.enabled: true` would
506
- // see a false warning from the syntax preflight, and the latch
507
- // flip would suppress the warning in a *later* truly-unsandboxed
508
- // run — exactly the scenario operators need to see.
509
- if (sandboxManager == null && options?.internal !== true) {
510
- maybeWarnSandboxOff(config);
511
- }
512
- let spawnCommand = command;
513
- let spawnArgs = args;
514
- if (sandboxManager != null) {
515
- const rendered = [command, ...args.map(shellQuote)].join(' ');
516
- const sandboxed = await sandboxManager.wrapWithSandbox(rendered);
517
- spawnCommand = config.shell ?? DEFAULT_SHELL;
518
- spawnArgs = ['-lc', sandboxed];
519
- }
520
- const launcher = getSpawn(config);
521
- return new Promise((resolveResult, reject) => {
522
- const spawnOptions = {
523
- cwd,
524
- detached: process.platform !== 'win32',
525
- env: { ...process.env, ...(config.env ?? {}) },
526
- stdio: ['ignore', 'pipe', 'pipe'],
527
- };
528
- Object.defineProperty(spawnOptions, LOCAL_SPAWN_TIMEOUT_MS, {
529
- value: timeoutMs,
530
- });
531
- const child = launcher(spawnCommand, spawnArgs, spawnOptions);
532
- let stdout = '';
533
- let stderr = '';
534
- let totalSpawnedBytes = 0;
535
- let overflowKilled = false;
536
- let spillStream;
537
- let spillPath;
538
- let settled = false;
539
- let timedOut = false;
540
- let timeout;
541
- const ensureSpill = () => {
542
- if (spillStream != null)
543
- return;
544
- // Lazy-open the temp file the first time a stream's in-memory
545
- // buffer overflows. Seed it with everything we've buffered so
546
- // the file holds the FULL output (not just the post-cap tail).
547
- // Uses the static `createWriteStream` import — `require('fs')`
548
- // would throw `ReferenceError: require is not defined` in ESM
549
- // consumers (this package ships both `dist/cjs` and `dist/esm`).
550
- spillPath = path.resolve(os.tmpdir(), `lc-local-output-${crypto.randomUUID()}.txt`);
551
- spillStream = fs.createWriteStream(spillPath);
552
- spillStream.write('===== stdout =====\n');
553
- spillStream.write(stdout);
554
- spillStream.write('\n===== stderr =====\n');
555
- spillStream.write(stderr);
556
- spillStream.write('\n===== overflow stream begins here =====\n');
557
- };
558
- const handleChunk = (buf, kind) => {
559
- totalSpawnedBytes += buf.length;
560
- // hardKillBytes <= 0 means "no cap" per the public config contract
561
- // (see LocalExecutionConfig.maxSpawnedBytes). Skip the kill check
562
- // entirely in that case so a single byte doesn't terminate the run.
563
- if (hardKillBytes > 0 &&
564
- totalSpawnedBytes > hardKillBytes &&
565
- !overflowKilled) {
566
- overflowKilled = true;
567
- killProcessTree(child);
568
- return;
569
- }
570
- const current = kind === 'stdout' ? stdout : stderr;
571
- if (current.length < inMemoryCapBytes) {
572
- const text = buf.toString('utf8');
573
- if (kind === 'stdout')
574
- stdout += text;
575
- else
576
- stderr += text;
577
- if (current.length + text.length >= inMemoryCapBytes) {
578
- ensureSpill();
579
- }
580
- }
581
- else {
582
- ensureSpill();
583
- spillStream.write(`[${kind}] `);
584
- spillStream.write(buf);
585
- }
586
- };
587
- const finish = (result) => {
588
- if (settled) {
589
- return;
590
- }
591
- settled = true;
592
- if (timeout != null) {
593
- clearTimeout(timeout);
594
- }
595
- const finalize = () => {
596
- const truncated = {
597
- stdout: truncateLocalOutput(result.stdout, maxOutputChars),
598
- stderr: truncateLocalOutput(result.stderr, maxOutputChars),
599
- };
600
- resolveResult({
601
- ...result,
602
- ...truncated,
603
- ...(spillPath != null ? { fullOutputPath: spillPath } : {}),
604
- });
605
- };
606
- if (spillStream == null) {
607
- finalize();
608
- return;
609
- }
610
- // Wait for the temp file to flush before reporting the path.
611
- // Otherwise the model sees `full_output_path: …` for a file
612
- // that's still being written.
613
- spillStream.end(() => finalize());
614
- };
615
- const fail = (error) => {
616
- if (settled) {
617
- return;
618
- }
619
- settled = true;
620
- if (timeout != null) {
621
- clearTimeout(timeout);
622
- }
623
- if (spillStream != null) {
624
- spillStream.end();
625
- }
626
- reject(error);
627
- };
628
- if (timeoutMs > 0) {
629
- timeout = setTimeout(() => {
630
- timedOut = true;
631
- killProcessTree(child);
632
- }, timeoutMs);
633
- }
634
- child.stdout.on('data', (chunk) => {
635
- handleChunk(chunk, 'stdout');
636
- });
637
- child.stderr.on('data', (chunk) => {
638
- handleChunk(chunk, 'stderr');
639
- });
640
- child.on('error', fail);
641
- child.on('close', (exitCode, signal) => {
642
- // Synthesize a non-zero exit code whenever the process exited
643
- // by signal — Node reports `exitCode: null` in that case and
644
- // the formatter only prints non-null exit codes, so signal
645
- // kills (overflow guard, `kill -9 $$` from inside the script,
646
- // native crashes, OS OOM killer, …) would otherwise look like
647
- // successful runs (Codex P1 + Codex P2). Overflow path keeps
648
- // its 137 (SIGKILL) for compatibility; other signals map per
649
- // POSIX `128 + signum`.
650
- let finalExit = exitCode;
651
- if (finalExit == null) {
652
- if (overflowKilled) {
653
- finalExit = 137;
654
- }
655
- else if (signal != null) {
656
- finalExit = exitCodeForSignal(signal);
657
- }
658
- }
659
- finish({
660
- stdout,
661
- stderr,
662
- exitCode: finalExit,
663
- timedOut,
664
- ...(overflowKilled ? { overflowKilled: true } : {}),
665
- ...(signal != null ? { signal } : {}),
666
- });
667
- });
668
- });
352
+ const cwd = getLocalCwd(config);
353
+ const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
354
+ const maxOutputChars = config.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
355
+ const inMemoryCapBytes = maxOutputChars * 2;
356
+ const hardKillBytes = config.maxSpawnedBytes ?? DEFAULT_MAX_SPAWNED_BYTES;
357
+ const sandboxManager = await ensureSandbox(config, cwd);
358
+ if (sandboxManager == null && options?.internal !== true) maybeWarnSandboxOff(config);
359
+ let spawnCommand = command;
360
+ let spawnArgs = args;
361
+ if (sandboxManager != null) {
362
+ const rendered = [command, ...args.map(shellQuote)].join(" ");
363
+ const sandboxed = await sandboxManager.wrapWithSandbox(rendered);
364
+ spawnCommand = config.shell ?? DEFAULT_SHELL;
365
+ spawnArgs = ["-lc", sandboxed];
366
+ }
367
+ const launcher = getSpawn(config);
368
+ return new Promise((resolveResult, reject) => {
369
+ const spawnOptions = {
370
+ cwd,
371
+ detached: process.platform !== "win32",
372
+ env: {
373
+ ...process.env,
374
+ ...config.env ?? {}
375
+ },
376
+ stdio: [
377
+ "ignore",
378
+ "pipe",
379
+ "pipe"
380
+ ]
381
+ };
382
+ Object.defineProperty(spawnOptions, LOCAL_SPAWN_TIMEOUT_MS, { value: timeoutMs });
383
+ const child = launcher(spawnCommand, spawnArgs, spawnOptions);
384
+ let stdout = "";
385
+ let stderr = "";
386
+ let totalSpawnedBytes = 0;
387
+ let overflowKilled = false;
388
+ let spillStream;
389
+ let spillPath;
390
+ let settled = false;
391
+ let timedOut = false;
392
+ let timeout;
393
+ const ensureSpill = () => {
394
+ if (spillStream != null) return;
395
+ spillPath = (0, path.resolve)((0, os.tmpdir)(), `lc-local-output-${(0, crypto.randomUUID)()}.txt`);
396
+ spillStream = (0, fs.createWriteStream)(spillPath);
397
+ spillStream.write("===== stdout =====\n");
398
+ spillStream.write(stdout);
399
+ spillStream.write("\n===== stderr =====\n");
400
+ spillStream.write(stderr);
401
+ spillStream.write("\n===== overflow stream begins here =====\n");
402
+ };
403
+ const handleChunk = (buf, kind) => {
404
+ totalSpawnedBytes += buf.length;
405
+ if (hardKillBytes > 0 && totalSpawnedBytes > hardKillBytes && !overflowKilled) {
406
+ overflowKilled = true;
407
+ killProcessTree(child);
408
+ return;
409
+ }
410
+ const current = kind === "stdout" ? stdout : stderr;
411
+ if (current.length < inMemoryCapBytes) {
412
+ const text = buf.toString("utf8");
413
+ if (kind === "stdout") stdout += text;
414
+ else stderr += text;
415
+ if (current.length + text.length >= inMemoryCapBytes) ensureSpill();
416
+ } else {
417
+ ensureSpill();
418
+ spillStream.write(`[${kind}] `);
419
+ spillStream.write(buf);
420
+ }
421
+ };
422
+ const finish = (result) => {
423
+ if (settled) return;
424
+ settled = true;
425
+ if (timeout != null) clearTimeout(timeout);
426
+ const finalize = () => {
427
+ const truncated = {
428
+ stdout: truncateLocalOutput(result.stdout, maxOutputChars),
429
+ stderr: truncateLocalOutput(result.stderr, maxOutputChars)
430
+ };
431
+ resolveResult({
432
+ ...result,
433
+ ...truncated,
434
+ ...spillPath != null ? { fullOutputPath: spillPath } : {}
435
+ });
436
+ };
437
+ if (spillStream == null) {
438
+ finalize();
439
+ return;
440
+ }
441
+ spillStream.end(() => finalize());
442
+ };
443
+ const fail = (error) => {
444
+ if (settled) return;
445
+ settled = true;
446
+ if (timeout != null) clearTimeout(timeout);
447
+ if (spillStream != null) spillStream.end();
448
+ reject(error);
449
+ };
450
+ if (timeoutMs > 0) timeout = setTimeout(() => {
451
+ timedOut = true;
452
+ killProcessTree(child);
453
+ }, timeoutMs);
454
+ child.stdout.on("data", (chunk) => {
455
+ handleChunk(chunk, "stdout");
456
+ });
457
+ child.stderr.on("data", (chunk) => {
458
+ handleChunk(chunk, "stderr");
459
+ });
460
+ child.on("error", fail);
461
+ child.on("close", (exitCode, signal) => {
462
+ let finalExit = exitCode;
463
+ if (finalExit == null) {
464
+ if (overflowKilled) finalExit = 137;
465
+ else if (signal != null) finalExit = exitCodeForSignal(signal);
466
+ }
467
+ finish({
468
+ stdout,
469
+ stderr,
470
+ exitCode: finalExit,
471
+ timedOut,
472
+ ...overflowKilled ? { overflowKilled: true } : {},
473
+ ...signal != null ? { signal } : {}
474
+ });
475
+ });
476
+ });
669
477
  }
670
478
  async function executeLocalBash(command, config = {}) {
671
- const validation = await validateBashCommand(command, config);
672
- if (!validation.valid) {
673
- throw new Error(validation.errors.join('\n'));
674
- }
675
- const shell = config.shell ?? DEFAULT_SHELL;
676
- return spawnLocalProcess(shell, ['-lc', command], config);
479
+ const validation = await validateBashCommand(command, config);
480
+ if (!validation.valid) throw new Error(validation.errors.join("\n"));
481
+ return spawnLocalProcess(config.shell ?? DEFAULT_SHELL, ["-lc", command], config);
677
482
  }
678
483
  /**
679
- * Variant of `executeLocalBash` that exposes `args` as positional
680
- * shell parameters (`$1`, `$2`, …). Mirrors what the other runtimes
681
- * do in `getRuntimeCommand`. Uses the standard `bash -c <code> --
682
- * arg0 arg1 …` form: the `--` becomes `$0`, then `args[0]` is `$1`
683
- * and so on. Same AST validation as the no-args path.
684
- *
685
- * Used by both the `execute_code`/`lang:'bash'` path AND the
686
- * `bash_tool` factory so the schema's `args` contract works
687
- * identically in both surfaces.
688
- */
484
+ * Variant of `executeLocalBash` that exposes `args` as positional
485
+ * shell parameters (`$1`, `$2`, …). Mirrors what the other runtimes
486
+ * do in `getRuntimeCommand`. Uses the standard `bash -c <code> --
487
+ * arg0 arg1 …` form: the `--` becomes `$0`, then `args[0]` is `$1`
488
+ * and so on. Same AST validation as the no-args path.
489
+ *
490
+ * Used by both the `execute_code`/`lang:'bash'` path AND the
491
+ * `bash_tool` factory so the schema's `args` contract works
492
+ * identically in both surfaces.
493
+ */
689
494
  /**
690
- * Matches a single arg that, on its own, references a protected
691
- * location (`/`, `~`, `$HOME`, `${HOME}`, `.`, with optional trailing
692
- * slash, wildcard, or dot-glob suffix). Used to spot the
693
- * `command: 'rm -rf "$1"', args: ['/']` shape where the destructive
694
- * target is moved into a positional arg to evade the command regex.
695
- * Codex P1 [45], extended for dot-glob in Codex P1 [47] (mirrors the
696
- * `DESTRUCTIVE_TARGET` suffix matrix exactly).
697
- */
495
+ * Matches a single arg that, on its own, references a protected
496
+ * location (`/`, `~`, `$HOME`, `${HOME}`, `.`, with optional trailing
497
+ * slash, wildcard, or dot-glob suffix). Used to spot the
498
+ * `command: 'rm -rf "$1"', args: ['/']` shape where the destructive
499
+ * target is moved into a positional arg to evade the command regex.
500
+ * Codex P1 [45], extended for dot-glob in Codex P1 [47] (mirrors the
501
+ * `DESTRUCTIVE_TARGET` suffix matrix exactly).
502
+ */
698
503
  const PROTECTED_TARGET_ARG_RE = /^(?:\/|~|\$\{?HOME\}?|\.)(?:\/?\.?\*|\/)?$/;
699
504
  /**
700
- * Mutating-op recognizer for the args check. Conservative: only the
701
- * three operations the destructive-command guard already covers
702
- * directly (`rm -rf …`, `chmod -R …`, `chown -R …`). Other shell
703
- * builtins might mutate state (`mv`, `cp` over an existing file,
704
- * etc.) but the destructive guard doesn't try to catch those today,
705
- * so we don't widen here either.
706
- */
505
+ * Mutating-op recognizer for the args check. Conservative: only the
506
+ * three operations the destructive-command guard already covers
507
+ * directly (`rm -rf …`, `chmod -R …`, `chown -R …`). Other shell
508
+ * builtins might mutate state (`mv`, `cp` over an existing file,
509
+ * etc.) but the destructive guard doesn't try to catch those today,
510
+ * so we don't widen here either.
511
+ */
707
512
  const DESTRUCTIVE_OP_IN_COMMAND_RE = /\b(?:rm\s+-[^\s]*[rf]|chmod\s+-R|chown\s+-R)\b/;
708
513
  async function executeLocalBashWithArgs(command, args, config = {}) {
709
- const validation = await validateBashCommand(command, config);
710
- if (!validation.valid) {
711
- throw new Error(validation.errors.join('\n'));
712
- }
713
- // Per-arg protected-target check (Codex P1 [45]). The command
714
- // regex can't see `$1`/`$@` substitutions at runtime — `command:
715
- // 'rm -rf "$1"', args: ['/']` would expand to `rm -rf '/'` inside
716
- // bash but the validator only saw `rm -rf "$1"` (no destructive
717
- // target). Block when (a) the command contains a destructive op
718
- // AND (b) at least one arg matches the protected-target shape.
719
- // Skipped when allowDangerousCommands is true (host-opted-in).
720
- if (args.length > 0 &&
721
- config.allowDangerousCommands !== true &&
722
- DESTRUCTIVE_OP_IN_COMMAND_RE.test(command)) {
723
- const offending = args.find((a) => PROTECTED_TARGET_ARG_RE.test(a));
724
- if (offending !== undefined) {
725
- throw new Error(`Command matches a destructive command pattern (protected target "${offending}" passed via positional arg).`);
726
- }
727
- }
728
- const shell = config.shell ?? DEFAULT_SHELL;
729
- return spawnLocalProcess(shell, ['-lc', command, '--', ...args], config);
514
+ const validation = await validateBashCommand(command, config);
515
+ if (!validation.valid) throw new Error(validation.errors.join("\n"));
516
+ if (args.length > 0 && config.allowDangerousCommands !== true && DESTRUCTIVE_OP_IN_COMMAND_RE.test(command)) {
517
+ const offending = args.find((a) => PROTECTED_TARGET_ARG_RE.test(a));
518
+ if (offending !== void 0) throw new Error(`Command matches a destructive command pattern (protected target "${offending}" passed via positional arg).`);
519
+ }
520
+ return spawnLocalProcess(config.shell ?? DEFAULT_SHELL, [
521
+ "-lc",
522
+ command,
523
+ "--",
524
+ ...args
525
+ ], config);
730
526
  }
731
527
  async function executeLocalCode(input, config = {}) {
732
- if (input.lang === 'bash') {
733
- // Append `args` as positional parameters via the standard
734
- // `bash -c <code> -- <args...>` form so `$1`, `$2`, … inside
735
- // `code` resolve correctly. Honours the same args contract the
736
- // other runtimes (py, js, ) already support.
737
- if (input.args != null && input.args.length > 0) {
738
- return executeLocalBashWithArgs(input.code, input.args, config);
739
- }
740
- return executeLocalBash(input.code, config);
741
- }
742
- const tempDir = path.resolve(os.tmpdir(), `lc-local-${crypto.randomUUID()}`);
743
- await promises.mkdir(tempDir, { recursive: true });
744
- try {
745
- const runtime = getRuntimeCommand(input.lang, tempDir, input.code, input.args, config.shell);
746
- if (runtime.source != null) {
747
- await promises.writeFile(path.resolve(tempDir, runtime.fileName), runtime.source, 'utf8');
748
- }
749
- return await spawnLocalProcess(runtime.command, runtime.args, config);
750
- }
751
- finally {
752
- await promises.rm(tempDir, { recursive: true, force: true });
753
- }
754
- }
755
- function getRuntimeCommand(lang, tempDir, code, args = [],
756
- // Override for the shell used by compile-style runtimes (`rs`,
757
- // `c`, `cpp`, `java`, `d`, `f90`). Threads `local.shell` so a host
758
- // that doesn't have bash (or wants `/bin/sh` / zsh) can still
759
- // execute these languages — Codex P2 #29: the bare-bash hardcode
760
- // mirrored the same gap that Codex P1 #6 fixed for the syntax
761
- // preflight, but had been missed for these runtime invocations.
762
- shellOverride) {
763
- const fileFor = (name) => path.resolve(tempDir, name);
764
- const shell = shellOverride ?? configShell();
765
- switch (lang) {
766
- case 'py':
767
- return {
768
- command: 'python3',
769
- args: [fileFor('main.py'), ...args],
770
- fileName: 'main.py',
771
- source: code,
772
- };
773
- case 'js':
774
- return {
775
- command: 'node',
776
- args: [fileFor('main.js'), ...args],
777
- fileName: 'main.js',
778
- source: code,
779
- };
780
- case 'ts':
781
- return {
782
- command: 'npx',
783
- args: ['--no-install', 'tsx', fileFor('main.ts'), ...args],
784
- fileName: 'main.ts',
785
- source: code,
786
- };
787
- case 'php':
788
- return {
789
- command: 'php',
790
- args: [fileFor('main.php'), ...args],
791
- fileName: 'main.php',
792
- source: code,
793
- };
794
- case 'go':
795
- return {
796
- command: 'go',
797
- args: ['run', fileFor('main.go'), ...args],
798
- fileName: 'main.go',
799
- source: code,
800
- };
801
- case 'rs':
802
- return {
803
- command: shell,
804
- args: [
805
- '-lc',
806
- `rustc ${shellQuote(fileFor('main.rs'))} -o ${shellQuote(fileFor('main-rs'))} && ${shellQuote(fileFor('main-rs'))} ${args.map(shellQuote).join(' ')}`,
807
- ],
808
- fileName: 'main.rs',
809
- source: code,
810
- };
811
- case 'c':
812
- return {
813
- command: shell,
814
- args: [
815
- '-lc',
816
- `cc ${shellQuote(fileFor('main.c'))} -o ${shellQuote(fileFor('main-c'))} && ${shellQuote(fileFor('main-c'))} ${args.map(shellQuote).join(' ')}`,
817
- ],
818
- fileName: 'main.c',
819
- source: code,
820
- };
821
- case 'cpp':
822
- return {
823
- command: shell,
824
- args: [
825
- '-lc',
826
- `c++ ${shellQuote(fileFor('main.cpp'))} -o ${shellQuote(fileFor('main-cpp'))} && ${shellQuote(fileFor('main-cpp'))} ${args.map(shellQuote).join(' ')}`,
827
- ],
828
- fileName: 'main.cpp',
829
- source: code,
830
- };
831
- case 'java':
832
- return {
833
- command: shell,
834
- args: [
835
- '-lc',
836
- `javac ${shellQuote(fileFor('Main.java'))} && java -cp ${shellQuote(tempDir)} Main ${args.map(shellQuote).join(' ')}`,
837
- ],
838
- fileName: 'Main.java',
839
- source: code,
840
- };
841
- case 'r':
842
- return {
843
- command: 'Rscript',
844
- args: [fileFor('main.R'), ...args],
845
- fileName: 'main.R',
846
- source: code,
847
- };
848
- case 'd':
849
- return {
850
- command: shell,
851
- args: [
852
- '-lc',
853
- `dmd ${shellQuote(fileFor('main.d'))} -of=${shellQuote(fileFor('main-d'))} && ${shellQuote(fileFor('main-d'))} ${args.map(shellQuote).join(' ')}`,
854
- ],
855
- fileName: 'main.d',
856
- source: code,
857
- };
858
- case 'f90':
859
- return {
860
- command: shell,
861
- args: [
862
- '-lc',
863
- `gfortran ${shellQuote(fileFor('main.f90'))} -o ${shellQuote(fileFor('main-f90'))} && ${shellQuote(fileFor('main-f90'))} ${args.map(shellQuote).join(' ')}`,
864
- ],
865
- fileName: 'main.f90',
866
- source: code,
867
- };
868
- default:
869
- throw new Error(`Unsupported local runtime: ${lang}`);
870
- }
528
+ if (input.lang === "bash") {
529
+ if (input.args != null && input.args.length > 0) return executeLocalBashWithArgs(input.code, input.args, config);
530
+ return executeLocalBash(input.code, config);
531
+ }
532
+ const tempDir = (0, path.resolve)((0, os.tmpdir)(), `lc-local-${(0, crypto.randomUUID)()}`);
533
+ await (0, fs_promises.mkdir)(tempDir, { recursive: true });
534
+ try {
535
+ const runtime = getRuntimeCommand(input.lang, tempDir, input.code, input.args, config.shell);
536
+ if (runtime.source != null) await (0, fs_promises.writeFile)((0, path.resolve)(tempDir, runtime.fileName), runtime.source, "utf8");
537
+ return await spawnLocalProcess(runtime.command, runtime.args, config);
538
+ } finally {
539
+ await (0, fs_promises.rm)(tempDir, {
540
+ recursive: true,
541
+ force: true
542
+ });
543
+ }
544
+ }
545
+ function getRuntimeCommand(lang, tempDir, code, args = [], shellOverride) {
546
+ const fileFor = (name) => (0, path.resolve)(tempDir, name);
547
+ const shell = shellOverride ?? configShell();
548
+ switch (lang) {
549
+ case "py": return {
550
+ command: "python3",
551
+ args: [fileFor("main.py"), ...args],
552
+ fileName: "main.py",
553
+ source: code
554
+ };
555
+ case "js": return {
556
+ command: "node",
557
+ args: [fileFor("main.js"), ...args],
558
+ fileName: "main.js",
559
+ source: code
560
+ };
561
+ case "ts": return {
562
+ command: "npx",
563
+ args: [
564
+ "--no-install",
565
+ "tsx",
566
+ fileFor("main.ts"),
567
+ ...args
568
+ ],
569
+ fileName: "main.ts",
570
+ source: code
571
+ };
572
+ case "php": return {
573
+ command: "php",
574
+ args: [fileFor("main.php"), ...args],
575
+ fileName: "main.php",
576
+ source: code
577
+ };
578
+ case "go": return {
579
+ command: "go",
580
+ args: [
581
+ "run",
582
+ fileFor("main.go"),
583
+ ...args
584
+ ],
585
+ fileName: "main.go",
586
+ source: code
587
+ };
588
+ case "rs": return {
589
+ command: shell,
590
+ args: ["-lc", `rustc ${shellQuote(fileFor("main.rs"))} -o ${shellQuote(fileFor("main-rs"))} && ${shellQuote(fileFor("main-rs"))} ${args.map(shellQuote).join(" ")}`],
591
+ fileName: "main.rs",
592
+ source: code
593
+ };
594
+ case "c": return {
595
+ command: shell,
596
+ args: ["-lc", `cc ${shellQuote(fileFor("main.c"))} -o ${shellQuote(fileFor("main-c"))} && ${shellQuote(fileFor("main-c"))} ${args.map(shellQuote).join(" ")}`],
597
+ fileName: "main.c",
598
+ source: code
599
+ };
600
+ case "cpp": return {
601
+ command: shell,
602
+ args: ["-lc", `c++ ${shellQuote(fileFor("main.cpp"))} -o ${shellQuote(fileFor("main-cpp"))} && ${shellQuote(fileFor("main-cpp"))} ${args.map(shellQuote).join(" ")}`],
603
+ fileName: "main.cpp",
604
+ source: code
605
+ };
606
+ case "java": return {
607
+ command: shell,
608
+ args: ["-lc", `javac ${shellQuote(fileFor("Main.java"))} && java -cp ${shellQuote(tempDir)} Main ${args.map(shellQuote).join(" ")}`],
609
+ fileName: "Main.java",
610
+ source: code
611
+ };
612
+ case "r": return {
613
+ command: "Rscript",
614
+ args: [fileFor("main.R"), ...args],
615
+ fileName: "main.R",
616
+ source: code
617
+ };
618
+ case "d": return {
619
+ command: shell,
620
+ args: ["-lc", `dmd ${shellQuote(fileFor("main.d"))} -of=${shellQuote(fileFor("main-d"))} && ${shellQuote(fileFor("main-d"))} ${args.map(shellQuote).join(" ")}`],
621
+ fileName: "main.d",
622
+ source: code
623
+ };
624
+ case "f90": return {
625
+ command: shell,
626
+ args: ["-lc", `gfortran ${shellQuote(fileFor("main.f90"))} -o ${shellQuote(fileFor("main-f90"))} && ${shellQuote(fileFor("main-f90"))} ${args.map(shellQuote).join(" ")}`],
627
+ fileName: "main.f90",
628
+ source: code
629
+ };
630
+ default: throw new Error(`Unsupported local runtime: ${lang}`);
631
+ }
871
632
  }
872
633
  function configShell() {
873
- return process.platform === 'win32' ? 'bash.exe' : 'bash';
634
+ return process.platform === "win32" ? "bash.exe" : "bash";
874
635
  }
875
636
  /**
876
- * How long after SIGTERM we wait before escalating to SIGKILL. A
877
- * cooperative process gets a graceful chance to flush + clean up;
878
- * a process that ignores or traps SIGTERM (`trap '' TERM`) gets
879
- * killed unconditionally so timeoutMs / maxSpawnedBytes can't be
880
- * defeated by a hostile script. Codex P1 #28 — pre-fix the spawn
881
- * promise would never resolve in that case and the entire tool run
882
- * would hang past the advertised timeout.
883
- */
884
- const SIGKILL_ESCALATION_MS = 2000;
637
+ * How long after SIGTERM we wait before escalating to SIGKILL. A
638
+ * cooperative process gets a graceful chance to flush + clean up;
639
+ * a process that ignores or traps SIGTERM (`trap '' TERM`) gets
640
+ * killed unconditionally so timeoutMs / maxSpawnedBytes can't be
641
+ * defeated by a hostile script. Codex P1 #28 — pre-fix the spawn
642
+ * promise would never resolve in that case and the entire tool run
643
+ * would hang past the advertised timeout.
644
+ */
645
+ const SIGKILL_ESCALATION_MS = 2e3;
885
646
  function sigterm(child) {
886
- if (child.pid == null) {
887
- child.kill('SIGTERM');
888
- return;
889
- }
890
- try {
891
- if (process.platform === 'win32') {
892
- child.kill('SIGTERM');
893
- return;
894
- }
895
- process.kill(-child.pid, 'SIGTERM');
896
- }
897
- catch {
898
- child.kill('SIGTERM');
899
- }
647
+ if (child.pid == null) {
648
+ child.kill("SIGTERM");
649
+ return;
650
+ }
651
+ try {
652
+ if (process.platform === "win32") {
653
+ child.kill("SIGTERM");
654
+ return;
655
+ }
656
+ process.kill(-child.pid, "SIGTERM");
657
+ } catch {
658
+ child.kill("SIGTERM");
659
+ }
900
660
  }
901
661
  function sigkill(child) {
902
- if (child.exitCode != null || child.signalCode != null)
903
- return;
904
- if (child.pid == null) {
905
- child.kill('SIGKILL');
906
- return;
907
- }
908
- try {
909
- if (process.platform === 'win32') {
910
- child.kill('SIGKILL');
911
- return;
912
- }
913
- process.kill(-child.pid, 'SIGKILL');
914
- }
915
- catch {
916
- try {
917
- child.kill('SIGKILL');
918
- }
919
- catch {
920
- /* already dead */
921
- }
922
- }
662
+ if (child.exitCode != null || child.signalCode != null) return;
663
+ if (child.pid == null) {
664
+ child.kill("SIGKILL");
665
+ return;
666
+ }
667
+ try {
668
+ if (process.platform === "win32") {
669
+ child.kill("SIGKILL");
670
+ return;
671
+ }
672
+ process.kill(-child.pid, "SIGKILL");
673
+ } catch {
674
+ try {
675
+ child.kill("SIGKILL");
676
+ } catch {}
677
+ }
923
678
  }
924
679
  function killProcessTree(child) {
925
- sigterm(child);
926
- // Escalate to SIGKILL if the child is still alive after the grace
927
- // window. Use unref() so the timer doesn't keep the Node process
928
- // alive past the parent's natural exit.
929
- const escalation = setTimeout(() => sigkill(child), SIGKILL_ESCALATION_MS);
930
- escalation.unref();
931
- child.once('close', () => clearTimeout(escalation));
680
+ sigterm(child);
681
+ const escalation = setTimeout(() => sigkill(child), SIGKILL_ESCALATION_MS);
682
+ escalation.unref();
683
+ child.once("close", () => clearTimeout(escalation));
932
684
  }
933
685
  function shellQuote(value) {
934
- if (value === '') {
935
- return '\'\'';
936
- }
937
- if (/^[A-Za-z0-9_/:=.,@%+-]+$/.test(value)) {
938
- return value;
939
- }
940
- return `'${value.replace(/'/g, '\'\\\'\'')}'`;
941
- }
942
- function resolveWorkspacePath(filePath, config = {}, intent = 'write') {
943
- const cwd = getLocalCwd(config);
944
- const absolutePath = path.isAbsolute(filePath)
945
- ? path.resolve(filePath)
946
- : path.resolve(cwd, filePath);
947
- const roots = intent === 'write' ? getWriteRoots(config) : getReadRoots(config);
948
- if (roots == null)
949
- return absolutePath; // explicit allow-outside
950
- if (absolutePath === cwd || isInsideAnyRoot(absolutePath, roots)) {
951
- return absolutePath;
952
- }
953
- throw new Error(`Path is outside the local workspace: ${filePath}`);
686
+ if (value === "") return "''";
687
+ if (/^[A-Za-z0-9_/:=.,@%+-]+$/.test(value)) return value;
688
+ return `'${value.replace(/'/g, "'\\''")}'`;
689
+ }
690
+ function resolveWorkspacePath(filePath, config = {}, intent = "write") {
691
+ const cwd = getLocalCwd(config);
692
+ const absolutePath = (0, path.isAbsolute)(filePath) ? (0, path.resolve)(filePath) : (0, path.resolve)(cwd, filePath);
693
+ const roots = intent === "write" ? getWriteRoots(config) : getReadRoots(config);
694
+ if (roots == null) return absolutePath;
695
+ if (absolutePath === cwd || isInsideAnyRoot(absolutePath, roots)) return absolutePath;
696
+ throw new Error(`Path is outside the local workspace: ${filePath}`);
954
697
  }
955
698
  function isInsideAnyRoot(absolutePath, roots) {
956
- for (const root of roots) {
957
- if (absolutePath === root)
958
- return true;
959
- const rel = path.relative(root, absolutePath);
960
- if (!rel.startsWith('..') && !path.isAbsolute(rel))
961
- return true;
962
- }
963
- return false;
964
- }
965
- async function realpathOrSelf(absolutePath, realpathImpl = promises.realpath) {
966
- try {
967
- return await realpathImpl(absolutePath);
968
- }
969
- catch {
970
- return absolutePath;
971
- }
699
+ for (const root of roots) {
700
+ if (absolutePath === root) return true;
701
+ const rel = (0, path.relative)(root, absolutePath);
702
+ if (!rel.startsWith("..") && !(0, path.isAbsolute)(rel)) return true;
703
+ }
704
+ return false;
705
+ }
706
+ async function realpathOrSelf(absolutePath, realpathImpl = fs_promises.realpath) {
707
+ try {
708
+ return await realpathImpl(absolutePath);
709
+ } catch {
710
+ return absolutePath;
711
+ }
972
712
  }
973
713
  /**
974
- * Resolves the realpath of `absolutePath`, falling back to the nearest
975
- * existing ancestor when the target itself does not yet exist (so the
976
- * containment check still works for `write_file` to a brand-new path).
977
- *
978
- * Codex P2 #38: takes the realpath impl as a parameter so callers
979
- * can route through `WorkspaceFS.realpath` when a custom engine is
980
- * configured. Pre-fix, host `fs/promises.realpath` would fail on a
981
- * remote/in-memory FS path and silently fall back to lexical
982
- * containment, leaving the symlink-escape clamp ineffective on
983
- * non-default engines.
984
- */
985
- async function realpathOfPathOrAncestor(absolutePath, realpathImpl = promises.realpath) {
986
- let current = absolutePath;
987
- let suffix = '';
988
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
989
- while (true) {
990
- try {
991
- const real = await realpathImpl(current);
992
- return suffix === '' ? real : path.resolve(real, suffix);
993
- }
994
- catch {
995
- const parent = path.resolve(current, '..');
996
- if (parent === current) {
997
- return absolutePath;
998
- }
999
- const base = current.slice(parent.length + 1);
1000
- suffix = suffix === '' ? base : `${base}/${suffix}`;
1001
- current = parent;
1002
- }
1003
- }
714
+ * Resolves the realpath of `absolutePath`, falling back to the nearest
715
+ * existing ancestor when the target itself does not yet exist (so the
716
+ * containment check still works for `write_file` to a brand-new path).
717
+ *
718
+ * Codex P2 #38: takes the realpath impl as a parameter so callers
719
+ * can route through `WorkspaceFS.realpath` when a custom engine is
720
+ * configured. Pre-fix, host `fs/promises.realpath` would fail on a
721
+ * remote/in-memory FS path and silently fall back to lexical
722
+ * containment, leaving the symlink-escape clamp ineffective on
723
+ * non-default engines.
724
+ */
725
+ async function realpathOfPathOrAncestor(absolutePath, realpathImpl = fs_promises.realpath) {
726
+ let current = absolutePath;
727
+ let suffix = "";
728
+ while (true) try {
729
+ const real = await realpathImpl(current);
730
+ return suffix === "" ? real : (0, path.resolve)(real, suffix);
731
+ } catch {
732
+ const parent = (0, path.resolve)(current, "..");
733
+ if (parent === current) return absolutePath;
734
+ const base = current.slice(parent.length + 1);
735
+ suffix = suffix === "" ? base : `${base}/${suffix}`;
736
+ current = parent;
737
+ }
1004
738
  }
1005
739
  /**
1006
- * Resolves a workspace path AND follows any symlinks before checking
1007
- * containment, so a symlink inside the workspace pointing outside is
1008
- * rejected even though the lexical path looks safe. Handles paths that
1009
- * don't yet exist (e.g. write_file targets) by realpath-resolving the
1010
- * nearest existing ancestor and re-attaching the unresolved suffix.
1011
- */
1012
- async function resolveWorkspacePathSafe(filePath, config = {}, intent = 'write') {
1013
- const lexical = resolveWorkspacePath(filePath, config, intent);
1014
- const roots = intent === 'write' ? getWriteRoots(config) : getReadRoots(config);
1015
- if (roots == null) {
1016
- return lexical;
1017
- }
1018
- // Route realpath through the configured WorkspaceFS so a custom
1019
- // engine (in-memory, remote) gets the same symlink-escape clamp
1020
- // the host-fs path gets. Codex P2 #38: pre-fix the host realpath
1021
- // would fail on a non-default FS path and silently fall back to
1022
- // lexical containment, leaving the clamp ineffective.
1023
- const fsRealpath = (p) => getWorkspaceFS(config).realpath(p);
1024
- const realRoots = await Promise.all(roots.map((r) => realpathOrSelf(r, fsRealpath)));
1025
- const realPath = await realpathOfPathOrAncestor(lexical, fsRealpath);
1026
- if (isInsideAnyRoot(realPath, realRoots)) {
1027
- return lexical;
1028
- }
1029
- throw new Error(`Path is outside the local workspace (symlink escape): ${filePath}`);
1030
- }
1031
-
740
+ * Resolves a workspace path AND follows any symlinks before checking
741
+ * containment, so a symlink inside the workspace pointing outside is
742
+ * rejected even though the lexical path looks safe. Handles paths that
743
+ * don't yet exist (e.g. write_file targets) by realpath-resolving the
744
+ * nearest existing ancestor and re-attaching the unresolved suffix.
745
+ */
746
+ async function resolveWorkspacePathSafe(filePath, config = {}, intent = "write") {
747
+ const lexical = resolveWorkspacePath(filePath, config, intent);
748
+ const roots = intent === "write" ? getWriteRoots(config) : getReadRoots(config);
749
+ if (roots == null) return lexical;
750
+ const fsRealpath = (p) => getWorkspaceFS(config).realpath(p);
751
+ const realRoots = await Promise.all(roots.map((r) => realpathOrSelf(r, fsRealpath)));
752
+ if (isInsideAnyRoot(await realpathOfPathOrAncestor(lexical, fsRealpath), realRoots)) return lexical;
753
+ throw new Error(`Path is outside the local workspace (symlink escape): ${filePath}`);
754
+ }
755
+ //#endregion
1032
756
  exports.LOCAL_SPAWN_TIMEOUT_MS = LOCAL_SPAWN_TIMEOUT_MS;
1033
757
  exports._resetLocalEngineWarningsForTests = _resetLocalEngineWarningsForTests;
1034
758
  exports.buildSandboxRuntimeConfig = buildSandboxRuntimeConfig;
@@ -1049,4 +773,5 @@ exports.shellQuote = shellQuote;
1049
773
  exports.spawnLocalProcess = spawnLocalProcess;
1050
774
  exports.truncateLocalOutput = truncateLocalOutput;
1051
775
  exports.validateBashCommand = validateBashCommand;
1052
- //# sourceMappingURL=LocalExecutionEngine.cjs.map
776
+
777
+ //# sourceMappingURL=LocalExecutionEngine.cjs.map