@librechat/agents 3.2.32 → 3.2.34
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.
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/cjs/agents/AgentContext.cjs +844 -1046
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/constants.cjs +13 -13
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +233 -240
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/common/index.cjs +2 -0
- package/dist/cjs/events.cjs +121 -169
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +1389 -1807
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +713 -945
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/graphs/index.cjs +2 -0
- package/dist/cjs/hitl/askUserQuestion.cjs +60 -62
- package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -1
- package/dist/cjs/hitl/index.cjs +1 -0
- package/dist/cjs/hooks/HookRegistry.cjs +176 -202
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/createToolPolicyHook.cjs +71 -101
- package/dist/cjs/hooks/createToolPolicyHook.cjs.map +1 -1
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +170 -273
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +227 -282
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +6 -0
- package/dist/cjs/hooks/matchers.cjs +196 -230
- package/dist/cjs/hooks/matchers.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +24 -24
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +110 -137
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langchain/google-common.cjs +0 -3
- package/dist/cjs/langchain/index.cjs +80 -43
- package/dist/cjs/langchain/language_models/chat_models.cjs +0 -3
- package/dist/cjs/langchain/messages/tool.cjs +0 -3
- package/dist/cjs/langchain/messages.cjs +35 -18
- package/dist/cjs/langchain/openai.cjs +0 -3
- package/dist/cjs/langchain/prompts.cjs +5 -8
- package/dist/cjs/langchain/runnables.cjs +11 -10
- package/dist/cjs/langchain/tools.cjs +14 -11
- package/dist/cjs/langchain/utils/env.cjs +5 -8
- package/dist/cjs/langfuse.cjs +60 -79
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +267 -399
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +432 -562
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs +23 -47
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +441 -731
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +171 -256
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/output_parsers.cjs +2 -0
- package/dist/cjs/llm/anthropic/utils/tools.cjs +12 -26
- package/dist/cjs/llm/anthropic/utils/tools.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +214 -240
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/toolCache.cjs +84 -106
- package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/index.cjs +2 -0
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +357 -620
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +141 -149
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/fake.cjs +86 -96
- package/dist/cjs/llm/fake.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs +183 -237
- package/dist/cjs/llm/google/index.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +404 -674
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/zod_to_genai_parameters.cjs +2 -0
- package/dist/cjs/llm/init.cjs +44 -53
- package/dist/cjs/llm/init.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +142 -182
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +1035 -1273
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +189 -316
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +102 -153
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/toolCache.cjs +35 -44
- package/dist/cjs/llm/openrouter/toolCache.cjs.map +1 -1
- package/dist/cjs/llm/providers.cjs +29 -37
- package/dist/cjs/llm/providers.cjs.map +1 -1
- package/dist/cjs/llm/request.cjs +20 -33
- package/dist/cjs/llm/request.cjs.map +1 -1
- package/dist/cjs/llm/vertexai/index.cjs +446 -453
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +547 -528
- package/dist/cjs/messages/anthropicToolCache.cjs +68 -119
- package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +305 -418
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/content.cjs +36 -49
- package/dist/cjs/messages/content.cjs.map +1 -1
- package/dist/cjs/messages/contextPruning.cjs +112 -145
- package/dist/cjs/messages/contextPruning.cjs.map +1 -1
- package/dist/cjs/messages/contextPruningSettings.cjs +36 -46
- package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +256 -397
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +904 -1387
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/ids.cjs +16 -20
- package/dist/cjs/messages/ids.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +12 -0
- package/dist/cjs/messages/langchain.cjs +18 -18
- package/dist/cjs/messages/langchain.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +1054 -1517
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/recency.cjs +77 -95
- package/dist/cjs/messages/recency.cjs.map +1 -1
- package/dist/cjs/messages/reducer.cjs +63 -78
- package/dist/cjs/messages/reducer.cjs.map +1 -1
- package/dist/cjs/messages/tools.cjs +51 -79
- package/dist/cjs/messages/tools.cjs.map +1 -1
- package/dist/cjs/openai/index.cjs +171 -217
- package/dist/cjs/openai/index.cjs.map +1 -1
- package/dist/cjs/responses/index.cjs +302 -391
- package/dist/cjs/responses/index.cjs.map +1 -1
- package/dist/cjs/run.cjs +903 -1113
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +805 -986
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/session/JsonlSessionStore.cjs +327 -410
- package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -1
- package/dist/cjs/session/handlers.cjs +192 -208
- package/dist/cjs/session/handlers.cjs.map +1 -1
- package/dist/cjs/session/ids.cjs +9 -10
- package/dist/cjs/session/ids.cjs.map +1 -1
- package/dist/cjs/session/index.cjs +4 -0
- package/dist/cjs/session/messageSerialization.cjs +94 -156
- package/dist/cjs/session/messageSerialization.cjs.map +1 -1
- package/dist/cjs/splitStream.cjs +147 -206
- package/dist/cjs/splitStream.cjs.map +1 -1
- package/dist/cjs/stream.cjs +874 -1344
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/index.cjs +57 -101
- package/dist/cjs/summarization/index.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +643 -796
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +110 -136
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +165 -245
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/Calculator.cjs +36 -57
- package/dist/cjs/tools/Calculator.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +126 -168
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeSessionFileSummary.cjs +36 -46
- package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +459 -649
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ReadFile.cjs +17 -20
- package/dist/cjs/tools/ReadFile.cjs.map +1 -1
- package/dist/cjs/tools/SkillTool.cjs +26 -27
- package/dist/cjs/tools/SkillTool.cjs.map +1 -1
- package/dist/cjs/tools/SubagentTool.cjs +59 -61
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +2146 -2686
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +663 -825
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareBridgeRuntime.cjs +248 -340
- package/dist/cjs/tools/cloudflare/CloudflareBridgeRuntime.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +170 -197
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +425 -520
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs +91 -124
- package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/index.cjs +4 -0
- package/dist/cjs/tools/eagerEventExecution.cjs +75 -99
- package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +200 -262
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/tools/local/CompileCheckTool.cjs +150 -212
- package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
- package/dist/cjs/tools/local/FileCheckpointer.cjs +77 -85
- package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalCodingTools.cjs +763 -1022
- package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +666 -941
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalExecutionTools.cjs +49 -92
- package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +286 -354
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/local/attachments.cjs +108 -165
- package/dist/cjs/tools/local/attachments.cjs.map +1 -1
- package/dist/cjs/tools/local/bashAst.cjs +99 -113
- package/dist/cjs/tools/local/bashAst.cjs.map +1 -1
- package/dist/cjs/tools/local/editStrategies.cjs +126 -169
- package/dist/cjs/tools/local/editStrategies.cjs.map +1 -1
- package/dist/cjs/tools/local/index.cjs +12 -0
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +136 -218
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
- package/dist/cjs/tools/local/syntaxCheck.cjs +142 -161
- package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -1
- package/dist/cjs/tools/local/textEncoding.cjs +25 -23
- package/dist/cjs/tools/local/textEncoding.cjs.map +1 -1
- package/dist/cjs/tools/local/workspaceFS.cjs +38 -46
- package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -1
- package/dist/cjs/tools/ptcTimeout.cjs +27 -47
- package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
- package/dist/cjs/tools/schema.cjs +24 -23
- package/dist/cjs/tools/schema.cjs.map +1 -1
- package/dist/cjs/tools/search/anthropic.cjs +24 -33
- package/dist/cjs/tools/search/anthropic.cjs.map +1 -1
- package/dist/cjs/tools/search/content.cjs +95 -137
- package/dist/cjs/tools/search/content.cjs.map +1 -1
- package/dist/cjs/tools/search/firecrawl.cjs +141 -172
- package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
- package/dist/cjs/tools/search/format.cjs +128 -196
- package/dist/cjs/tools/search/format.cjs.map +1 -1
- package/dist/cjs/tools/search/highlights.cjs +165 -232
- package/dist/cjs/tools/search/highlights.cjs.map +1 -1
- package/dist/cjs/tools/search/index.cjs +2 -0
- package/dist/cjs/tools/search/rerankers.cjs +151 -174
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/schema.cjs +40 -39
- package/dist/cjs/tools/search/schema.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +428 -530
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/serper-scraper.cjs +106 -127
- package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs +129 -181
- package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-search.cjs +295 -359
- package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +260 -299
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/search/utils.cjs +74 -117
- package/dist/cjs/tools/search/utils.cjs.map +1 -1
- package/dist/cjs/tools/skillCatalog.cjs +54 -72
- package/dist/cjs/tools/skillCatalog.cjs.map +1 -1
- package/dist/cjs/tools/streamedToolCallSeals.cjs +46 -34
- package/dist/cjs/tools/streamedToolCallSeals.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +612 -771
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/index.cjs +1 -0
- package/dist/cjs/tools/toolOutputReferences.cjs +523 -630
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/callbacks.cjs +11 -21
- package/dist/cjs/utils/callbacks.cjs.map +1 -1
- package/dist/cjs/utils/errors.cjs +70 -95
- package/dist/cjs/utils/errors.cjs.map +1 -1
- package/dist/cjs/utils/events.cjs +32 -42
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/cjs/utils/graph.cjs +8 -12
- package/dist/cjs/utils/graph.cjs.map +1 -1
- package/dist/cjs/utils/handlers.cjs +60 -82
- package/dist/cjs/utils/handlers.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +9 -0
- package/dist/cjs/utils/llm.cjs +19 -27
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/misc.cjs +30 -46
- package/dist/cjs/utils/misc.cjs.map +1 -1
- package/dist/cjs/utils/run.cjs +50 -66
- package/dist/cjs/utils/run.cjs.map +1 -1
- package/dist/cjs/utils/schema.cjs +11 -19
- package/dist/cjs/utils/schema.cjs.map +1 -1
- package/dist/cjs/utils/title.cjs +71 -106
- package/dist/cjs/utils/title.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +186 -283
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/cjs/utils/truncation.cjs +95 -114
- package/dist/cjs/utils/truncation.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +844 -1044
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/constants.mjs +13 -11
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +221 -238
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/common/index.mjs +3 -0
- package/dist/esm/events.mjs +121 -167
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +1388 -1804
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +713 -943
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/graphs/index.mjs +3 -0
- package/dist/esm/hitl/askUserQuestion.mjs +60 -60
- package/dist/esm/hitl/askUserQuestion.mjs.map +1 -1
- package/dist/esm/hitl/index.mjs +2 -0
- package/dist/esm/hooks/HookRegistry.mjs +176 -200
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/createToolPolicyHook.mjs +71 -99
- package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -1
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +170 -271
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +227 -280
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +7 -0
- package/dist/esm/hooks/matchers.mjs +196 -228
- package/dist/esm/hooks/matchers.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +24 -22
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +109 -132
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langchain/google-common.mjs +1 -2
- package/dist/esm/langchain/index.mjs +5 -5
- package/dist/esm/langchain/language_models/chat_models.mjs +1 -2
- package/dist/esm/langchain/messages/tool.mjs +1 -2
- package/dist/esm/langchain/messages.mjs +2 -2
- package/dist/esm/langchain/openai.mjs +1 -2
- package/dist/esm/langchain/prompts.mjs +2 -2
- package/dist/esm/langchain/runnables.mjs +2 -2
- package/dist/esm/langchain/tools.mjs +2 -2
- package/dist/esm/langchain/utils/env.mjs +2 -2
- package/dist/esm/langfuse.mjs +60 -76
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +267 -395
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +432 -559
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs +23 -45
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +439 -725
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +171 -253
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/output_parsers.mjs +3 -0
- package/dist/esm/llm/anthropic/utils/tools.mjs +12 -24
- package/dist/esm/llm/anthropic/utils/tools.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +214 -238
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/toolCache.mjs +84 -104
- package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/index.mjs +3 -0
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +357 -618
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs +140 -147
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/fake.mjs +86 -94
- package/dist/esm/llm/fake.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +183 -235
- package/dist/esm/llm/google/index.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +403 -666
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/google/utils/zod_to_genai_parameters.mjs +3 -0
- package/dist/esm/llm/init.mjs +44 -51
- package/dist/esm/llm/init.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +142 -180
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +1035 -1268
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +188 -312
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +102 -151
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/toolCache.mjs +35 -42
- package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -1
- package/dist/esm/llm/providers.mjs +29 -34
- package/dist/esm/llm/providers.mjs.map +1 -1
- package/dist/esm/llm/request.mjs +20 -31
- package/dist/esm/llm/request.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs +446 -449
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +99 -87
- package/dist/esm/messages/anthropicToolCache.mjs +68 -117
- package/dist/esm/messages/anthropicToolCache.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +305 -416
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/content.mjs +36 -47
- package/dist/esm/messages/content.mjs.map +1 -1
- package/dist/esm/messages/contextPruning.mjs +112 -143
- package/dist/esm/messages/contextPruning.mjs.map +1 -1
- package/dist/esm/messages/contextPruningSettings.mjs +36 -44
- package/dist/esm/messages/contextPruningSettings.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +254 -393
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +902 -1383
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/ids.mjs +16 -18
- package/dist/esm/messages/ids.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +13 -0
- package/dist/esm/messages/langchain.mjs +18 -16
- package/dist/esm/messages/langchain.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +1053 -1514
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/recency.mjs +77 -93
- package/dist/esm/messages/recency.mjs.map +1 -1
- package/dist/esm/messages/reducer.mjs +63 -76
- package/dist/esm/messages/reducer.mjs.map +1 -1
- package/dist/esm/messages/tools.mjs +49 -75
- package/dist/esm/messages/tools.mjs.map +1 -1
- package/dist/esm/openai/index.mjs +170 -215
- package/dist/esm/openai/index.mjs.map +1 -1
- package/dist/esm/responses/index.mjs +301 -389
- package/dist/esm/responses/index.mjs.map +1 -1
- package/dist/esm/run.mjs +903 -1111
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +806 -985
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/session/JsonlSessionStore.mjs +326 -407
- package/dist/esm/session/JsonlSessionStore.mjs.map +1 -1
- package/dist/esm/session/handlers.mjs +192 -206
- package/dist/esm/session/handlers.mjs.map +1 -1
- package/dist/esm/session/ids.mjs +9 -8
- package/dist/esm/session/ids.mjs.map +1 -1
- package/dist/esm/session/index.mjs +5 -0
- package/dist/esm/session/messageSerialization.mjs +94 -154
- package/dist/esm/session/messageSerialization.mjs.map +1 -1
- package/dist/esm/splitStream.mjs +147 -204
- package/dist/esm/splitStream.mjs.map +1 -1
- package/dist/esm/stream.mjs +872 -1341
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/index.mjs +57 -99
- package/dist/esm/summarization/index.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +640 -790
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +103 -129
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +162 -239
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/Calculator.mjs +34 -36
- package/dist/esm/tools/Calculator.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +123 -164
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeSessionFileSummary.mjs +36 -44
- package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +454 -644
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ReadFile.mjs +17 -18
- package/dist/esm/tools/ReadFile.mjs.map +1 -1
- package/dist/esm/tools/SkillTool.mjs +26 -25
- package/dist/esm/tools/SkillTool.mjs.map +1 -1
- package/dist/esm/tools/SubagentTool.mjs +59 -59
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +2144 -2684
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +659 -804
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareBridgeRuntime.mjs +248 -338
- package/dist/esm/tools/cloudflare/CloudflareBridgeRuntime.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +170 -195
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +424 -517
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs +91 -122
- package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/index.mjs +5 -0
- package/dist/esm/tools/eagerEventExecution.mjs +75 -96
- package/dist/esm/tools/eagerEventExecution.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +200 -260
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/tools/local/CompileCheckTool.mjs +150 -210
- package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
- package/dist/esm/tools/local/FileCheckpointer.mjs +77 -83
- package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -1
- package/dist/esm/tools/local/LocalCodingTools.mjs +760 -1017
- package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +663 -936
- package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -1
- package/dist/esm/tools/local/LocalExecutionTools.mjs +49 -90
- package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -1
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +283 -349
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/local/attachments.mjs +108 -163
- package/dist/esm/tools/local/attachments.mjs.map +1 -1
- package/dist/esm/tools/local/bashAst.mjs +99 -111
- package/dist/esm/tools/local/bashAst.mjs.map +1 -1
- package/dist/esm/tools/local/editStrategies.mjs +126 -167
- package/dist/esm/tools/local/editStrategies.mjs.map +1 -1
- package/dist/esm/tools/local/index.mjs +13 -0
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +136 -216
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
- package/dist/esm/tools/local/syntaxCheck.mjs +138 -155
- package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -1
- package/dist/esm/tools/local/textEncoding.mjs +25 -21
- package/dist/esm/tools/local/textEncoding.mjs.map +1 -1
- package/dist/esm/tools/local/workspaceFS.mjs +38 -44
- package/dist/esm/tools/local/workspaceFS.mjs.map +1 -1
- package/dist/esm/tools/ptcTimeout.mjs +27 -42
- package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
- package/dist/esm/tools/schema.mjs +24 -21
- package/dist/esm/tools/schema.mjs.map +1 -1
- package/dist/esm/tools/search/anthropic.mjs +24 -31
- package/dist/esm/tools/search/anthropic.mjs.map +1 -1
- package/dist/esm/tools/search/content.mjs +93 -116
- package/dist/esm/tools/search/content.mjs.map +1 -1
- package/dist/esm/tools/search/firecrawl.mjs +139 -169
- package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
- package/dist/esm/tools/search/format.mjs +128 -194
- package/dist/esm/tools/search/format.mjs.map +1 -1
- package/dist/esm/tools/search/highlights.mjs +165 -230
- package/dist/esm/tools/search/highlights.mjs.map +1 -1
- package/dist/esm/tools/search/index.mjs +3 -0
- package/dist/esm/tools/search/rerankers.mjs +149 -168
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/schema.mjs +39 -37
- package/dist/esm/tools/search/schema.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +426 -528
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/serper-scraper.mjs +104 -124
- package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-scraper.mjs +127 -178
- package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-search.mjs +293 -357
- package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +259 -297
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/search/utils.mjs +74 -115
- package/dist/esm/tools/search/utils.mjs.map +1 -1
- package/dist/esm/tools/skillCatalog.mjs +54 -70
- package/dist/esm/tools/skillCatalog.mjs.map +1 -1
- package/dist/esm/tools/streamedToolCallSeals.mjs +42 -31
- package/dist/esm/tools/streamedToolCallSeals.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +612 -768
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/index.mjs +2 -0
- package/dist/esm/tools/toolOutputReferences.mjs +523 -624
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/callbacks.mjs +11 -19
- package/dist/esm/utils/callbacks.mjs.map +1 -1
- package/dist/esm/utils/errors.mjs +70 -93
- package/dist/esm/utils/errors.mjs.map +1 -1
- package/dist/esm/utils/events.mjs +32 -40
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/esm/utils/graph.mjs +8 -10
- package/dist/esm/utils/graph.mjs.map +1 -1
- package/dist/esm/utils/handlers.mjs +60 -80
- package/dist/esm/utils/handlers.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +10 -0
- package/dist/esm/utils/llm.mjs +19 -25
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/misc.mjs +30 -44
- package/dist/esm/utils/misc.mjs.map +1 -1
- package/dist/esm/utils/run.mjs +50 -64
- package/dist/esm/utils/run.mjs.map +1 -1
- package/dist/esm/utils/schema.mjs +11 -17
- package/dist/esm/utils/schema.mjs.map +1 -1
- package/dist/esm/utils/title.mjs +71 -104
- package/dist/esm/utils/title.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +186 -281
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/esm/utils/truncation.mjs +95 -112
- package/dist/esm/utils/truncation.mjs.map +1 -1
- package/dist/types/llm/bedrock/utils/index.d.ts +1 -1
- package/dist/types/llm/bedrock/utils/message_outputs.d.ts +9 -0
- package/dist/types/llm/vertexai/index.d.ts +10 -0
- package/dist/types/tools/ToolNode.d.ts +8 -0
- package/dist/types/tools/search/tool.d.ts +17 -0
- package/dist/types/tools/search/types.d.ts +4 -0
- package/dist/types/tools/streamedToolCallSeals.d.ts +5 -1
- package/dist/types/types/tools.d.ts +10 -0
- package/package.json +4 -10
- package/src/__tests__/stream.eagerEventExecution.test.ts +703 -0
- package/src/llm/bedrock/index.ts +40 -0
- package/src/llm/bedrock/streamSealDispatch.test.ts +158 -0
- package/src/llm/bedrock/utils/index.ts +1 -0
- package/src/llm/bedrock/utils/message_outputs.test.ts +85 -0
- package/src/llm/bedrock/utils/message_outputs.ts +43 -0
- package/src/llm/google/utils/common.test.ts +64 -0
- package/src/llm/google/utils/common.ts +18 -0
- package/src/llm/openai/index.ts +95 -1
- package/src/llm/openai/sequentialToolCallSeals.test.ts +199 -0
- package/src/llm/vertexai/index.ts +31 -0
- package/src/llm/vertexai/sealStreamedToolCalls.test.ts +88 -0
- package/src/llm/vertexai/streamSealDispatch.test.ts +148 -0
- package/src/stream.ts +40 -6
- package/src/tools/ToolNode.ts +85 -3
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +368 -0
- package/src/tools/search/highlights.ts +9 -1
- package/src/tools/search/search.ts +41 -3
- package/src/tools/search/source-processing.test.ts +373 -0
- package/src/tools/search/tool.ts +22 -2
- package/src/tools/search/types.ts +4 -0
- package/src/tools/streamedToolCallSeals.ts +37 -9
- package/src/types/tools.ts +10 -0
- package/dist/cjs/langchain/google-common.cjs.map +0 -1
- package/dist/cjs/langchain/index.cjs.map +0 -1
- package/dist/cjs/langchain/language_models/chat_models.cjs.map +0 -1
- package/dist/cjs/langchain/messages/tool.cjs.map +0 -1
- package/dist/cjs/langchain/messages.cjs.map +0 -1
- package/dist/cjs/langchain/openai.cjs.map +0 -1
- package/dist/cjs/langchain/prompts.cjs.map +0 -1
- package/dist/cjs/langchain/runnables.cjs.map +0 -1
- package/dist/cjs/langchain/tools.cjs.map +0 -1
- package/dist/cjs/langchain/utils/env.cjs.map +0 -1
- package/dist/cjs/main.cjs.map +0 -1
- package/dist/esm/langchain/google-common.mjs.map +0 -1
- package/dist/esm/langchain/index.mjs.map +0 -1
- package/dist/esm/langchain/language_models/chat_models.mjs.map +0 -1
- package/dist/esm/langchain/messages/tool.mjs.map +0 -1
- package/dist/esm/langchain/messages.mjs.map +0 -1
- package/dist/esm/langchain/openai.mjs.map +0 -1
- package/dist/esm/langchain/prompts.mjs.map +0 -1
- package/dist/esm/langchain/runnables.mjs.map +0 -1
- package/dist/esm/langchain/tools.mjs.map +0 -1
- package/dist/esm/langchain/utils/env.mjs.map +0 -1
- package/dist/esm/main.mjs.map +0 -1
|
@@ -1,1031 +1,758 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
const DEFAULT_TIMEOUT_MS =
|
|
11
|
-
const DEFAULT_MAX_OUTPUT_CHARS =
|
|
1
|
+
import { bashAstFindingsToErrors, runBashAstChecks } from "./bashAst.mjs";
|
|
2
|
+
import { nodeWorkspaceFS } from "./workspaceFS.mjs";
|
|
3
|
+
import { isAbsolute, relative, resolve } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
import { createWriteStream } from "fs";
|
|
7
|
+
import { createHash, randomUUID } from "crypto";
|
|
8
|
+
import { mkdir, realpath, rm, writeFile } from "fs/promises";
|
|
9
|
+
//#region src/tools/local/LocalExecutionEngine.ts
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = 6e4;
|
|
11
|
+
const DEFAULT_MAX_OUTPUT_CHARS = 2e5;
|
|
12
12
|
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
*/
|
|
18
18
|
const DEFAULT_MAX_SPAWNED_BYTES = 50 * 1024 * 1024;
|
|
19
|
-
const DEFAULT_LOCAL_SESSION_ID =
|
|
20
|
-
const DEFAULT_SHELL = process.platform ===
|
|
21
|
-
|
|
22
|
-
// utilities accept `--` as an end-of-options marker, so `rm -rf -- /`
|
|
23
|
-
// is identical in effect to `rm -rf /` but pre-fix it slipped past
|
|
24
|
-
// the guard because the regex required the path to follow option
|
|
25
|
-
// flags directly. Codex P1 #20.
|
|
26
|
-
// `DESTRUCTIVE_TARGET` is the canonical "protected location" pattern:
|
|
27
|
-
// matches `/`, `~`, `$HOME`, `${HOME}`, `.`, each optionally followed
|
|
28
|
-
// by a trailing-slash and/or wildcard glob suffix. The suffix matrix:
|
|
29
|
-
// '' — `$HOME` (round 14)
|
|
30
|
-
// '/' — `$HOME/` (round 14, Codex P1 [37])
|
|
31
|
-
// '*' — `$HOME*` (round 15, Codex P1 [42])
|
|
32
|
-
// '/*' — `$HOME/*` (round 15, Codex P1 [42])
|
|
33
|
-
// '.*' — `$HOME.*` (round 17, Codex P1 [47])
|
|
34
|
-
// '/.*' — `$HOME/.*` (round 17, Codex P1 [47]) — the
|
|
35
|
-
// dot-glob form deletes all dotfiles under the protected
|
|
36
|
-
// root, just as destructive as `/*` but the prior matrix
|
|
37
|
-
// missed it.
|
|
38
|
-
// Suffix expression: `(?:\/?\.?\*|\/)?` — one of:
|
|
39
|
-
// `\/?\.?\*` → `*`, `.*`, `/*`, `/.*`
|
|
40
|
-
// `\/` → `/`
|
|
41
|
-
// (empty) → bare base
|
|
42
|
-
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\\}?|\\.)(?:\\/?\\.?\\*|\\/)?";
|
|
43
22
|
const dangerousCommandPatterns = [
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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*:/
|
|
50
29
|
];
|
|
51
30
|
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Quoted variant uses the same DESTRUCTIVE_TARGET (which accepts an
|
|
67
|
-
// optional trailing slash) so `rm -rf "$HOME/"` and `rm -rf "~/"`
|
|
68
|
-
// 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
|
+
*/
|
|
69
45
|
const quotedDestructivePatterns = [
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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}["']`)
|
|
73
49
|
];
|
|
74
|
-
/**
|
|
75
|
-
* Catches destructive operations smuggled inside a nested shell or
|
|
76
|
-
* `eval` call, e.g. `bash -lc "rm -rf $HOME"` — the outer command
|
|
77
|
-
* looks benign (`bash -lc "..."`) and the destructive `rm` lives
|
|
78
|
-
* inside the quoted payload that `stripQuotedContent` blanks out.
|
|
79
|
-
* Comprehensive review (manual finding C) flagged this as a real
|
|
80
|
-
* bypass of the otherwise-correct quote-strip-then-match approach.
|
|
81
|
-
*
|
|
82
|
-
* Run against the ORIGINAL command (quotes intact) so the inside of
|
|
83
|
-
* the nested-shell payload is visible. Conservative: matches only
|
|
84
|
-
* the same operation set as `dangerousCommandPatterns` (rm -rf,
|
|
85
|
-
* chmod -R 777, chown -R) when they appear inside a `<shell> -[l]?c
|
|
86
|
-
* "..."` or `eval "..."` payload.
|
|
87
|
-
*/
|
|
88
|
-
const NESTED_SHELL_PREFIX = '(?:(?:ba|z|da|k)?sh|eval)\\s+(?:-l?c\\s+)?';
|
|
89
50
|
const nestedShellDestructivePatterns = [
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
'["\'][^"\']*\\bchmod\\s+-R\\s+(?:777|a\\+w)\\s+(?:--\\s+)?(?:\\/|~|\\$\\{?HOME\\}?|\\.)'),
|
|
94
|
-
new RegExp(NESTED_SHELL_PREFIX +
|
|
95
|
-
'["\'][^"\']*\\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\\}?|\\.)")
|
|
96
54
|
];
|
|
97
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*/;
|
|
98
56
|
/**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
*/
|
|
104
62
|
const SIGNAL_TO_EXIT_CODE = {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
120
78
|
};
|
|
121
79
|
function exitCodeForSignal(signal) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return SIGNAL_TO_EXIT_CODE[signal] ?? 1;
|
|
80
|
+
if (signal == null) return 1;
|
|
81
|
+
return SIGNAL_TO_EXIT_CODE[signal] ?? 1;
|
|
125
82
|
}
|
|
126
83
|
let sandboxConfigKey;
|
|
127
84
|
let sandboxInitialized = false;
|
|
128
85
|
let sandboxRuntimePromise;
|
|
129
86
|
function isToolExecutionConfig(config) {
|
|
130
|
-
|
|
87
|
+
return "engine" in config || "local" in config;
|
|
131
88
|
}
|
|
132
89
|
function resolveLocalExecutionConfig(config) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
return config ?? {};
|
|
90
|
+
if (config != null && isToolExecutionConfig(config)) return config.local ?? {};
|
|
91
|
+
return config ?? {};
|
|
137
92
|
}
|
|
138
93
|
function getLocalCwd(config) {
|
|
139
|
-
|
|
94
|
+
return resolve(config?.workspace?.root ?? config?.cwd ?? process.cwd());
|
|
140
95
|
}
|
|
141
96
|
/**
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
+
*/
|
|
150
105
|
function getWorkspaceRoots(config) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const abs = isAbsolute(extra) ? resolve(extra) : resolve(root, extra);
|
|
165
|
-
if (!seen.has(abs)) {
|
|
166
|
-
seen.add(abs);
|
|
167
|
-
out.push(abs);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
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 = isAbsolute(extra) ? resolve(extra) : resolve(root, extra);
|
|
113
|
+
if (!seen.has(abs)) {
|
|
114
|
+
seen.add(abs);
|
|
115
|
+
out.push(abs);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
171
119
|
}
|
|
172
120
|
/**
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
*/
|
|
177
125
|
function getSpawn(config) {
|
|
178
|
-
|
|
126
|
+
return config?.exec?.spawn ?? config?.spawn ?? spawn;
|
|
179
127
|
}
|
|
180
128
|
/**
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
+
*/
|
|
185
133
|
function getWorkspaceFS(config) {
|
|
186
|
-
|
|
134
|
+
return config?.exec?.fs ?? nodeWorkspaceFS;
|
|
187
135
|
}
|
|
188
136
|
/**
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
+
*/
|
|
194
142
|
function getWriteRoots(config = {}) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const granular = config.workspace?.allowWriteOutside;
|
|
201
|
-
if (granular === true)
|
|
202
|
-
return null;
|
|
203
|
-
if (granular === false)
|
|
204
|
-
return getWorkspaceRoots(config);
|
|
205
|
-
if (config.allowOutsideWorkspace === true)
|
|
206
|
-
return null;
|
|
207
|
-
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);
|
|
208
148
|
}
|
|
209
149
|
/**
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
150
|
+
* Resolves the workspace boundary for *read* operations. Honours
|
|
151
|
+
* `workspace.allowReadOutside` (and the deprecated
|
|
152
|
+
* `allowOutsideWorkspace`) by returning `null`.
|
|
153
|
+
*/
|
|
214
154
|
function getReadRoots(config = {}) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (granular === false)
|
|
221
|
-
return getWorkspaceRoots(config);
|
|
222
|
-
if (config.allowOutsideWorkspace === true)
|
|
223
|
-
return null;
|
|
224
|
-
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);
|
|
225
160
|
}
|
|
226
161
|
function getLocalSessionId(config) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return `${DEFAULT_LOCAL_SESSION_ID}:${digest}`;
|
|
162
|
+
const cwd = getLocalCwd(config);
|
|
163
|
+
return `${DEFAULT_LOCAL_SESSION_ID}:${createHash("sha1").update(cwd).digest("hex").slice(0, 12)}`;
|
|
230
164
|
}
|
|
231
|
-
const missingSandboxRuntimeMessage = [
|
|
232
|
-
|
|
233
|
-
'Install it with `npm install @anthropic-ai/sandbox-runtime`, or disable local sandboxing with `local.sandbox.enabled: false`.',
|
|
234
|
-
].join(' ');
|
|
235
|
-
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";
|
|
236
167
|
/** Lazy-loads the ESM-only sandbox runtime only when sandboxing is enabled. */
|
|
237
168
|
function loadSandboxRuntime() {
|
|
238
|
-
|
|
239
|
-
|
|
169
|
+
sandboxRuntimePromise ??= import(sandboxRuntimePackage);
|
|
170
|
+
return sandboxRuntimePromise;
|
|
240
171
|
}
|
|
241
172
|
function shouldUseLocalSandbox(config) {
|
|
242
|
-
|
|
173
|
+
return config.sandbox?.enabled === true;
|
|
243
174
|
}
|
|
244
175
|
let sandboxOffWarned = false;
|
|
245
176
|
function maybeWarnSandboxOff(config) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
sandboxOffWarned = true;
|
|
252
|
-
// eslint-disable-next-line no-console
|
|
253
|
-
console.warn('[@librechat/agents] Local execution engine is running without ' +
|
|
254
|
-
'@anthropic-ai/sandbox-runtime wrapping. The agent has full access to ' +
|
|
255
|
-
'the host filesystem and network. Set toolExecution.local.sandbox.enabled ' +
|
|
256
|
-
'= 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.");
|
|
257
180
|
}
|
|
258
181
|
/**
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
182
|
+
* Test-only reset hook for the sandbox-off warning latch.
|
|
183
|
+
*
|
|
184
|
+
* @internal Not part of the public SDK surface.
|
|
185
|
+
*/
|
|
263
186
|
function _resetLocalEngineWarningsForTests() {
|
|
264
|
-
|
|
187
|
+
sandboxOffWarned = false;
|
|
265
188
|
}
|
|
266
189
|
function truncateLocalOutput(value, maxChars = DEFAULT_MAX_OUTPUT_CHARS) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const omitted = value.length - maxChars;
|
|
273
|
-
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)}`;
|
|
274
195
|
}
|
|
275
196
|
function stripQuotedContent(command) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
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;
|
|
314
233
|
}
|
|
315
234
|
async function validateBashCommand(command, config = {}) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
// DEFAULT_SHELL here would reject perfectly valid commands when the
|
|
371
|
-
// host configures `local.shell` to a non-bash binary (or when the
|
|
372
|
-
// runtime doesn't have bash installed at all but does have e.g. zsh).
|
|
373
|
-
const syntaxShell = config.shell ?? DEFAULT_SHELL;
|
|
374
|
-
const syntax = await spawnLocalProcess(syntaxShell, ['-n', '-c', command], {
|
|
375
|
-
...config,
|
|
376
|
-
timeoutMs: Math.min(config.timeoutMs ?? DEFAULT_TIMEOUT_MS, 5000),
|
|
377
|
-
sandbox: { enabled: false },
|
|
378
|
-
}, { internal: true }).catch((error) => ({
|
|
379
|
-
stdout: '',
|
|
380
|
-
stderr: error.message,
|
|
381
|
-
exitCode: 1,
|
|
382
|
-
timedOut: false,
|
|
383
|
-
}));
|
|
384
|
-
if (syntax.exitCode !== 0) {
|
|
385
|
-
errors.push(syntax.stderr.trim() === ''
|
|
386
|
-
? 'Command failed shell syntax validation.'
|
|
387
|
-
: `Command failed shell syntax validation: ${syntax.stderr.trim()}`);
|
|
388
|
-
}
|
|
389
|
-
if (/\bsudo\b/.test(normalized)) {
|
|
390
|
-
warnings.push('Command requests elevated privileges with sudo.');
|
|
391
|
-
}
|
|
392
|
-
return {
|
|
393
|
-
valid: errors.length === 0,
|
|
394
|
-
errors,
|
|
395
|
-
warnings,
|
|
396
|
-
};
|
|
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 = bashAstFindingsToErrors(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
|
+
};
|
|
397
289
|
}
|
|
398
290
|
async function ensureSandbox(config, cwd) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
if (sandboxInitialized) {
|
|
418
|
-
await runtime.SandboxManager.reset();
|
|
419
|
-
}
|
|
420
|
-
await runtime.SandboxManager.initialize(runtimeConfig);
|
|
421
|
-
sandboxInitialized = true;
|
|
422
|
-
sandboxConfigKey = nextKey;
|
|
423
|
-
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;
|
|
424
308
|
}
|
|
425
309
|
/**
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const BRIDGE_LOOPBACK_HOSTS = [
|
|
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
|
+
];
|
|
433
321
|
function buildSandboxRuntimeConfig(config, cwd, getDefaultWritePaths) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
...(sandbox?.network?.allowMachLookup != null && {
|
|
464
|
-
allowMachLookup: sandbox.network.allowMachLookup,
|
|
465
|
-
}),
|
|
466
|
-
},
|
|
467
|
-
filesystem: {
|
|
468
|
-
denyRead: sandbox?.filesystem?.denyRead ?? [],
|
|
469
|
-
allowRead: sandbox?.filesystem?.allowRead,
|
|
470
|
-
allowWrite: sandbox?.filesystem?.allowWrite ?? [
|
|
471
|
-
...workspaceWriteRoots,
|
|
472
|
-
...getDefaultWritePaths(),
|
|
473
|
-
],
|
|
474
|
-
denyWrite: sandbox?.filesystem?.denyWrite ?? [
|
|
475
|
-
'.env',
|
|
476
|
-
'.env.*',
|
|
477
|
-
'.git/config',
|
|
478
|
-
'.git/hooks/**',
|
|
479
|
-
],
|
|
480
|
-
allowGitConfig: sandbox?.filesystem?.allowGitConfig,
|
|
481
|
-
},
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
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");
|
|
485
351
|
async function spawnLocalProcess(command, args, config = {}, options) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
spillStream.end(() => finalize());
|
|
612
|
-
};
|
|
613
|
-
const fail = (error) => {
|
|
614
|
-
if (settled) {
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
settled = true;
|
|
618
|
-
if (timeout != null) {
|
|
619
|
-
clearTimeout(timeout);
|
|
620
|
-
}
|
|
621
|
-
if (spillStream != null) {
|
|
622
|
-
spillStream.end();
|
|
623
|
-
}
|
|
624
|
-
reject(error);
|
|
625
|
-
};
|
|
626
|
-
if (timeoutMs > 0) {
|
|
627
|
-
timeout = setTimeout(() => {
|
|
628
|
-
timedOut = true;
|
|
629
|
-
killProcessTree(child);
|
|
630
|
-
}, timeoutMs);
|
|
631
|
-
}
|
|
632
|
-
child.stdout.on('data', (chunk) => {
|
|
633
|
-
handleChunk(chunk, 'stdout');
|
|
634
|
-
});
|
|
635
|
-
child.stderr.on('data', (chunk) => {
|
|
636
|
-
handleChunk(chunk, 'stderr');
|
|
637
|
-
});
|
|
638
|
-
child.on('error', fail);
|
|
639
|
-
child.on('close', (exitCode, signal) => {
|
|
640
|
-
// Synthesize a non-zero exit code whenever the process exited
|
|
641
|
-
// by signal — Node reports `exitCode: null` in that case and
|
|
642
|
-
// the formatter only prints non-null exit codes, so signal
|
|
643
|
-
// kills (overflow guard, `kill -9 $$` from inside the script,
|
|
644
|
-
// native crashes, OS OOM killer, …) would otherwise look like
|
|
645
|
-
// successful runs (Codex P1 + Codex P2). Overflow path keeps
|
|
646
|
-
// its 137 (SIGKILL) for compatibility; other signals map per
|
|
647
|
-
// POSIX `128 + signum`.
|
|
648
|
-
let finalExit = exitCode;
|
|
649
|
-
if (finalExit == null) {
|
|
650
|
-
if (overflowKilled) {
|
|
651
|
-
finalExit = 137;
|
|
652
|
-
}
|
|
653
|
-
else if (signal != null) {
|
|
654
|
-
finalExit = exitCodeForSignal(signal);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
finish({
|
|
658
|
-
stdout,
|
|
659
|
-
stderr,
|
|
660
|
-
exitCode: finalExit,
|
|
661
|
-
timedOut,
|
|
662
|
-
...(overflowKilled ? { overflowKilled: true } : {}),
|
|
663
|
-
...(signal != null ? { signal } : {}),
|
|
664
|
-
});
|
|
665
|
-
});
|
|
666
|
-
});
|
|
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 = resolve(tmpdir(), `lc-local-output-${randomUUID()}.txt`);
|
|
396
|
+
spillStream = 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
|
+
});
|
|
667
477
|
}
|
|
668
478
|
async function executeLocalBash(command, config = {}) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
}
|
|
673
|
-
const shell = config.shell ?? DEFAULT_SHELL;
|
|
674
|
-
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);
|
|
675
482
|
}
|
|
676
483
|
/**
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
+
*/
|
|
687
494
|
/**
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
+
*/
|
|
696
503
|
const PROTECTED_TARGET_ARG_RE = /^(?:\/|~|\$\{?HOME\}?|\.)(?:\/?\.?\*|\/)?$/;
|
|
697
504
|
/**
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
+
*/
|
|
705
512
|
const DESTRUCTIVE_OP_IN_COMMAND_RE = /\b(?:rm\s+-[^\s]*[rf]|chmod\s+-R|chown\s+-R)\b/;
|
|
706
513
|
async function executeLocalBashWithArgs(command, args, config = {}) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
config.allowDangerousCommands !== true &&
|
|
720
|
-
DESTRUCTIVE_OP_IN_COMMAND_RE.test(command)) {
|
|
721
|
-
const offending = args.find((a) => PROTECTED_TARGET_ARG_RE.test(a));
|
|
722
|
-
if (offending !== undefined) {
|
|
723
|
-
throw new Error(`Command matches a destructive command pattern (protected target "${offending}" passed via positional arg).`);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
const shell = config.shell ?? DEFAULT_SHELL;
|
|
727
|
-
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);
|
|
728
526
|
}
|
|
729
527
|
async function executeLocalCode(input, config = {}) {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
`javac ${shellQuote(fileFor('Main.java'))} && java -cp ${shellQuote(tempDir)} Main ${args.map(shellQuote).join(' ')}`,
|
|
835
|
-
],
|
|
836
|
-
fileName: 'Main.java',
|
|
837
|
-
source: code,
|
|
838
|
-
};
|
|
839
|
-
case 'r':
|
|
840
|
-
return {
|
|
841
|
-
command: 'Rscript',
|
|
842
|
-
args: [fileFor('main.R'), ...args],
|
|
843
|
-
fileName: 'main.R',
|
|
844
|
-
source: code,
|
|
845
|
-
};
|
|
846
|
-
case 'd':
|
|
847
|
-
return {
|
|
848
|
-
command: shell,
|
|
849
|
-
args: [
|
|
850
|
-
'-lc',
|
|
851
|
-
`dmd ${shellQuote(fileFor('main.d'))} -of=${shellQuote(fileFor('main-d'))} && ${shellQuote(fileFor('main-d'))} ${args.map(shellQuote).join(' ')}`,
|
|
852
|
-
],
|
|
853
|
-
fileName: 'main.d',
|
|
854
|
-
source: code,
|
|
855
|
-
};
|
|
856
|
-
case 'f90':
|
|
857
|
-
return {
|
|
858
|
-
command: shell,
|
|
859
|
-
args: [
|
|
860
|
-
'-lc',
|
|
861
|
-
`gfortran ${shellQuote(fileFor('main.f90'))} -o ${shellQuote(fileFor('main-f90'))} && ${shellQuote(fileFor('main-f90'))} ${args.map(shellQuote).join(' ')}`,
|
|
862
|
-
],
|
|
863
|
-
fileName: 'main.f90',
|
|
864
|
-
source: code,
|
|
865
|
-
};
|
|
866
|
-
default:
|
|
867
|
-
throw new Error(`Unsupported local runtime: ${lang}`);
|
|
868
|
-
}
|
|
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 = resolve(tmpdir(), `lc-local-${randomUUID()}`);
|
|
533
|
+
await 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 writeFile(resolve(tempDir, runtime.fileName), runtime.source, "utf8");
|
|
537
|
+
return await spawnLocalProcess(runtime.command, runtime.args, config);
|
|
538
|
+
} finally {
|
|
539
|
+
await rm(tempDir, {
|
|
540
|
+
recursive: true,
|
|
541
|
+
force: true
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function getRuntimeCommand(lang, tempDir, code, args = [], shellOverride) {
|
|
546
|
+
const fileFor = (name) => 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
|
+
}
|
|
869
632
|
}
|
|
870
633
|
function configShell() {
|
|
871
|
-
|
|
634
|
+
return process.platform === "win32" ? "bash.exe" : "bash";
|
|
872
635
|
}
|
|
873
636
|
/**
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
const SIGKILL_ESCALATION_MS =
|
|
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;
|
|
883
646
|
function sigterm(child) {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
}
|
|
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
|
+
}
|
|
898
660
|
}
|
|
899
661
|
function sigkill(child) {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
}
|
|
917
|
-
catch {
|
|
918
|
-
/* already dead */
|
|
919
|
-
}
|
|
920
|
-
}
|
|
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
|
+
}
|
|
921
678
|
}
|
|
922
679
|
function killProcessTree(child) {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
const escalation = setTimeout(() => sigkill(child), SIGKILL_ESCALATION_MS);
|
|
928
|
-
escalation.unref();
|
|
929
|
-
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));
|
|
930
684
|
}
|
|
931
685
|
function shellQuote(value) {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
? resolve(filePath)
|
|
944
|
-
: resolve(cwd, filePath);
|
|
945
|
-
const roots = intent === 'write' ? getWriteRoots(config) : getReadRoots(config);
|
|
946
|
-
if (roots == null)
|
|
947
|
-
return absolutePath; // explicit allow-outside
|
|
948
|
-
if (absolutePath === cwd || isInsideAnyRoot(absolutePath, roots)) {
|
|
949
|
-
return absolutePath;
|
|
950
|
-
}
|
|
951
|
-
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 = isAbsolute(filePath) ? resolve(filePath) : 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}`);
|
|
952
697
|
}
|
|
953
698
|
function isInsideAnyRoot(absolutePath, roots) {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
}
|
|
961
|
-
return false;
|
|
699
|
+
for (const root of roots) {
|
|
700
|
+
if (absolutePath === root) return true;
|
|
701
|
+
const rel = relative(root, absolutePath);
|
|
702
|
+
if (!rel.startsWith("..") && !isAbsolute(rel)) return true;
|
|
703
|
+
}
|
|
704
|
+
return false;
|
|
962
705
|
}
|
|
963
706
|
async function realpathOrSelf(absolutePath, realpathImpl = realpath) {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
}
|
|
707
|
+
try {
|
|
708
|
+
return await realpathImpl(absolutePath);
|
|
709
|
+
} catch {
|
|
710
|
+
return absolutePath;
|
|
711
|
+
}
|
|
970
712
|
}
|
|
971
713
|
/**
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
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
|
+
*/
|
|
983
725
|
async function realpathOfPathOrAncestor(absolutePath, realpathImpl = realpath) {
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
}
|
|
997
|
-
const base = current.slice(parent.length + 1);
|
|
998
|
-
suffix = suffix === '' ? base : `${base}/${suffix}`;
|
|
999
|
-
current = parent;
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
726
|
+
let current = absolutePath;
|
|
727
|
+
let suffix = "";
|
|
728
|
+
while (true) try {
|
|
729
|
+
const real = await realpathImpl(current);
|
|
730
|
+
return suffix === "" ? real : resolve(real, suffix);
|
|
731
|
+
} catch {
|
|
732
|
+
const parent = 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
|
+
}
|
|
1002
738
|
}
|
|
1003
739
|
/**
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
async function resolveWorkspacePathSafe(filePath, config = {}, intent =
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
// lexical containment, leaving the clamp ineffective.
|
|
1021
|
-
const fsRealpath = (p) => getWorkspaceFS(config).realpath(p);
|
|
1022
|
-
const realRoots = await Promise.all(roots.map((r) => realpathOrSelf(r, fsRealpath)));
|
|
1023
|
-
const realPath = await realpathOfPathOrAncestor(lexical, fsRealpath);
|
|
1024
|
-
if (isInsideAnyRoot(realPath, realRoots)) {
|
|
1025
|
-
return lexical;
|
|
1026
|
-
}
|
|
1027
|
-
throw new Error(`Path is outside the local workspace (symlink escape): ${filePath}`);
|
|
1028
|
-
}
|
|
1029
|
-
|
|
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
|
|
1030
756
|
export { LOCAL_SPAWN_TIMEOUT_MS, _resetLocalEngineWarningsForTests, buildSandboxRuntimeConfig, executeLocalBash, executeLocalBashWithArgs, executeLocalCode, getLocalCwd, getLocalSessionId, getReadRoots, getSpawn, getWorkspaceFS, getWorkspaceRoots, getWriteRoots, resolveLocalExecutionConfig, resolveWorkspacePath, resolveWorkspacePathSafe, shellQuote, spawnLocalProcess, truncateLocalOutput, validateBashCommand };
|
|
1031
|
-
|
|
757
|
+
|
|
758
|
+
//# sourceMappingURL=LocalExecutionEngine.mjs.map
|