@librechat/agents 3.1.77-dev.1 → 3.1.78-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/common/enum.cjs +54 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +148 -4
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +291 -0
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -0
- package/dist/cjs/llm/openai/index.cjs +317 -1
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +90 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/anthropicToolCache.cjs +102 -0
- package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -0
- package/dist/cjs/messages/prune.cjs +27 -0
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/recency.cjs +99 -0
- package/dist/cjs/messages/recency.cjs.map +1 -0
- package/dist/cjs/run.cjs +30 -0
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +100 -6
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +635 -23
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/local/CompileCheckTool.cjs +227 -0
- package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -0
- package/dist/cjs/tools/local/FileCheckpointer.cjs +90 -0
- package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -0
- package/dist/cjs/tools/local/LocalCodingTools.cjs +1098 -0
- package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -0
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +1042 -0
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -0
- package/dist/cjs/tools/local/LocalExecutionTools.cjs +122 -0
- package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -0
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +453 -0
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -0
- package/dist/cjs/tools/local/attachments.cjs +183 -0
- package/dist/cjs/tools/local/attachments.cjs.map +1 -0
- package/dist/cjs/tools/local/bashAst.cjs +129 -0
- package/dist/cjs/tools/local/bashAst.cjs.map +1 -0
- package/dist/cjs/tools/local/editStrategies.cjs +188 -0
- package/dist/cjs/tools/local/editStrategies.cjs.map +1 -0
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +141 -0
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -0
- package/dist/cjs/tools/local/syntaxCheck.cjs +182 -0
- package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -0
- package/dist/cjs/tools/local/textEncoding.cjs +30 -0
- package/dist/cjs/tools/local/textEncoding.cjs.map +1 -0
- package/dist/cjs/tools/local/workspaceFS.cjs +51 -0
- package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +53 -1
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +149 -5
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +289 -0
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -0
- package/dist/esm/llm/openai/index.mjs +318 -2
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +17 -2
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/anthropicToolCache.mjs +99 -0
- package/dist/esm/messages/anthropicToolCache.mjs.map +1 -0
- package/dist/esm/messages/prune.mjs +26 -1
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/recency.mjs +97 -0
- package/dist/esm/messages/recency.mjs.map +1 -0
- package/dist/esm/run.mjs +30 -0
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +100 -6
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +635 -23
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/local/CompileCheckTool.mjs +223 -0
- package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -0
- package/dist/esm/tools/local/FileCheckpointer.mjs +87 -0
- package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -0
- package/dist/esm/tools/local/LocalCodingTools.mjs +1075 -0
- package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -0
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +1022 -0
- package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -0
- package/dist/esm/tools/local/LocalExecutionTools.mjs +117 -0
- package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -0
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +448 -0
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -0
- package/dist/esm/tools/local/attachments.mjs +180 -0
- package/dist/esm/tools/local/attachments.mjs.map +1 -0
- package/dist/esm/tools/local/bashAst.mjs +126 -0
- package/dist/esm/tools/local/bashAst.mjs.map +1 -0
- package/dist/esm/tools/local/editStrategies.mjs +185 -0
- package/dist/esm/tools/local/editStrategies.mjs.map +1 -0
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +137 -0
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -0
- package/dist/esm/tools/local/syntaxCheck.mjs +179 -0
- package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -0
- package/dist/esm/tools/local/textEncoding.mjs +27 -0
- package/dist/esm/tools/local/textEncoding.mjs.map +1 -0
- package/dist/esm/tools/local/workspaceFS.mjs +49 -0
- package/dist/esm/tools/local/workspaceFS.mjs.map +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +39 -1
- package/dist/types/graphs/Graph.d.ts +34 -0
- package/dist/types/hooks/createWorkspacePolicyHook.d.ts +95 -0
- package/dist/types/hooks/index.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/llm/openai/index.d.ts +17 -0
- package/dist/types/messages/anthropicToolCache.d.ts +51 -0
- package/dist/types/messages/index.d.ts +2 -0
- package/dist/types/messages/prune.d.ts +11 -0
- package/dist/types/messages/recency.d.ts +64 -0
- package/dist/types/run.d.ts +21 -0
- package/dist/types/tools/ToolNode.d.ts +145 -2
- package/dist/types/tools/local/CompileCheckTool.d.ts +31 -0
- package/dist/types/tools/local/FileCheckpointer.d.ts +39 -0
- package/dist/types/tools/local/LocalCodingTools.d.ts +57 -0
- package/dist/types/tools/local/LocalExecutionEngine.d.ts +149 -0
- package/dist/types/tools/local/LocalExecutionTools.d.ts +9 -0
- package/dist/types/tools/local/LocalProgrammaticToolCalling.d.ts +21 -0
- package/dist/types/tools/local/attachments.d.ts +84 -0
- package/dist/types/tools/local/bashAst.d.ts +11 -0
- package/dist/types/tools/local/editStrategies.d.ts +28 -0
- package/dist/types/tools/local/index.d.ts +12 -0
- package/dist/types/tools/local/resolveLocalExecutionTools.d.ts +38 -0
- package/dist/types/tools/local/syntaxCheck.d.ts +42 -0
- package/dist/types/tools/local/textEncoding.d.ts +21 -0
- package/dist/types/tools/local/workspaceFS.d.ts +49 -0
- package/dist/types/types/hitl.d.ts +56 -27
- package/dist/types/types/run.d.ts +8 -1
- package/dist/types/types/summarize.d.ts +30 -0
- package/dist/types/types/tools.d.ts +341 -6
- package/package.json +21 -2
- package/src/common/enum.ts +54 -0
- package/src/graphs/Graph.ts +164 -6
- package/src/hooks/__tests__/compactHooks.test.ts +38 -2
- package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +393 -0
- package/src/hooks/createWorkspacePolicyHook.ts +355 -0
- package/src/hooks/index.ts +6 -0
- package/src/index.ts +1 -0
- package/src/llm/openai/deepseek.test.ts +479 -0
- package/src/llm/openai/index.ts +484 -1
- package/src/messages/__tests__/anthropicToolCache.test.ts +125 -0
- package/src/messages/__tests__/recency.test.ts +267 -0
- package/src/messages/anthropicToolCache.ts +116 -0
- package/src/messages/index.ts +2 -0
- package/src/messages/prune.ts +27 -1
- package/src/messages/recency.ts +155 -0
- package/src/run.ts +31 -0
- package/src/scripts/compare_pi_vs_ours.ts +840 -0
- package/src/scripts/local_engine.ts +166 -0
- package/src/scripts/local_engine_checkpointer.ts +205 -0
- package/src/scripts/local_engine_compile.ts +263 -0
- package/src/scripts/local_engine_hooks.ts +226 -0
- package/src/scripts/local_engine_image.ts +201 -0
- package/src/scripts/local_engine_ptc.ts +151 -0
- package/src/scripts/local_engine_workspace.ts +258 -0
- package/src/scripts/summarization-recency.ts +462 -0
- package/src/specs/prune.test.ts +39 -0
- package/src/summarization/__tests__/node.test.ts +499 -3
- package/src/summarization/node.ts +124 -7
- package/src/tools/ToolNode.ts +769 -20
- package/src/tools/__tests__/LocalExecutionTools.test.ts +2647 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +175 -0
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +114 -0
- package/src/tools/__tests__/ToolNode.session.test.ts +84 -0
- package/src/tools/__tests__/directToolHITLResumeScope.test.ts +467 -0
- package/src/tools/__tests__/directToolHooks.test.ts +411 -0
- package/src/tools/__tests__/localToolNames.test.ts +73 -0
- package/src/tools/__tests__/workspaceSeam.test.ts +134 -0
- package/src/tools/local/CompileCheckTool.ts +278 -0
- package/src/tools/local/FileCheckpointer.ts +93 -0
- package/src/tools/local/LocalCodingTools.ts +1342 -0
- package/src/tools/local/LocalExecutionEngine.ts +1329 -0
- package/src/tools/local/LocalExecutionTools.ts +167 -0
- package/src/tools/local/LocalProgrammaticToolCalling.ts +594 -0
- package/src/tools/local/__tests__/FileCheckpointer.test.ts +120 -0
- package/src/tools/local/__tests__/editStrategies.test.ts +134 -0
- package/src/tools/local/attachments.ts +251 -0
- package/src/tools/local/bashAst.ts +151 -0
- package/src/tools/local/editStrategies.ts +188 -0
- package/src/tools/local/index.ts +12 -0
- package/src/tools/local/resolveLocalExecutionTools.ts +208 -0
- package/src/tools/local/syntaxCheck.ts +243 -0
- package/src/tools/local/textEncoding.ts +37 -0
- package/src/tools/local/workspaceFS.ts +89 -0
- package/src/types/hitl.ts +56 -27
- package/src/types/run.ts +12 -1
- package/src/types/summarize.ts +31 -0
- package/src/types/tools.ts +359 -7
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { CODE_EXECUTION_TOOLS, Constants } from '../../common/enum.mjs';
|
|
2
|
+
import { createLocalBashExecutionTool, createLocalCodeExecutionTool } from './LocalExecutionTools.mjs';
|
|
3
|
+
import { createLocalCodingToolBundle, createLocalCodingTools, createLocalCodingToolDefinitions } from './LocalCodingTools.mjs';
|
|
4
|
+
import { createLocalBashProgrammaticToolCallingTool, createLocalProgrammaticToolCallingTool } from './LocalProgrammaticToolCalling.mjs';
|
|
5
|
+
|
|
6
|
+
function shouldUseLocalExecution(config) {
|
|
7
|
+
return config?.engine === 'local';
|
|
8
|
+
}
|
|
9
|
+
function shouldIncludeCodingTools(config) {
|
|
10
|
+
return (shouldUseLocalExecution(config) &&
|
|
11
|
+
config?.local?.includeCodingTools !== false);
|
|
12
|
+
}
|
|
13
|
+
function createLocalExecutionTool(name, config) {
|
|
14
|
+
switch (name) {
|
|
15
|
+
case Constants.EXECUTE_CODE:
|
|
16
|
+
return createLocalCodeExecutionTool(config);
|
|
17
|
+
case Constants.BASH_TOOL:
|
|
18
|
+
return createLocalBashExecutionTool({ config });
|
|
19
|
+
case Constants.PROGRAMMATIC_TOOL_CALLING:
|
|
20
|
+
return createLocalProgrammaticToolCallingTool(config);
|
|
21
|
+
case Constants.BASH_PROGRAMMATIC_TOOL_CALLING:
|
|
22
|
+
return createLocalBashProgrammaticToolCallingTool(config);
|
|
23
|
+
default:
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function mergeToolsByName(baseTools, localTools) {
|
|
28
|
+
const orderedTools = [];
|
|
29
|
+
const indexByName = new Map();
|
|
30
|
+
for (const tool of baseTools ?? []) {
|
|
31
|
+
if ('name' in tool && typeof tool.name === 'string') {
|
|
32
|
+
indexByName.set(tool.name, orderedTools.length);
|
|
33
|
+
}
|
|
34
|
+
orderedTools.push(tool);
|
|
35
|
+
}
|
|
36
|
+
for (const tool of localTools) {
|
|
37
|
+
const existingIndex = indexByName.get(tool.name);
|
|
38
|
+
if (existingIndex == null) {
|
|
39
|
+
indexByName.set(tool.name, orderedTools.length);
|
|
40
|
+
orderedTools.push(tool);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
orderedTools[existingIndex] = tool;
|
|
44
|
+
}
|
|
45
|
+
return orderedTools;
|
|
46
|
+
}
|
|
47
|
+
function resolveLocalToolsForBinding(args) {
|
|
48
|
+
if (!shouldUseLocalExecution(args.toolExecution)) {
|
|
49
|
+
return args.tools;
|
|
50
|
+
}
|
|
51
|
+
const localConfig = args.toolExecution?.local ?? {};
|
|
52
|
+
if (shouldIncludeCodingTools(args.toolExecution)) {
|
|
53
|
+
return mergeToolsByName(args.tools, createLocalCodingTools(localConfig));
|
|
54
|
+
}
|
|
55
|
+
const replacements = (args.tools ?? [])
|
|
56
|
+
.filter((existingTool) => 'name' in existingTool &&
|
|
57
|
+
typeof existingTool.name === 'string' &&
|
|
58
|
+
CODE_EXECUTION_TOOLS.has(existingTool.name))
|
|
59
|
+
.map((existingTool) => createLocalExecutionTool(existingTool.name, localConfig))
|
|
60
|
+
.filter((localTool) => localTool != null);
|
|
61
|
+
return replacements.length === 0
|
|
62
|
+
? args.tools
|
|
63
|
+
: mergeToolsByName(args.tools, replacements);
|
|
64
|
+
}
|
|
65
|
+
function resolveLocalToolRegistry(args) {
|
|
66
|
+
if (!shouldIncludeCodingTools(args.toolExecution)) {
|
|
67
|
+
return args.toolRegistry;
|
|
68
|
+
}
|
|
69
|
+
const registry = new Map(args.toolRegistry ?? []);
|
|
70
|
+
for (const definition of createLocalCodingToolDefinitions()) {
|
|
71
|
+
registry.set(definition.name, definition);
|
|
72
|
+
}
|
|
73
|
+
return registry;
|
|
74
|
+
}
|
|
75
|
+
function resolveLocalExecutionTools(args) {
|
|
76
|
+
const directToolNames = new Set();
|
|
77
|
+
if (!shouldUseLocalExecution(args.toolExecution)) {
|
|
78
|
+
return {
|
|
79
|
+
toolMap: args.toolMap,
|
|
80
|
+
directToolNames,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const localConfig = args.toolExecution?.local ?? {};
|
|
84
|
+
const toolMap = new Map(args.toolMap);
|
|
85
|
+
let fileCheckpointer;
|
|
86
|
+
if (shouldIncludeCodingTools(args.toolExecution)) {
|
|
87
|
+
// Use the bundle factory when fileCheckpointing is on so we can
|
|
88
|
+
// surface the checkpointer back to the caller — without this, the
|
|
89
|
+
// execution-path tools each captured into a checkpointer that was
|
|
90
|
+
// immediately discarded, making the public `fileCheckpointing`
|
|
91
|
+
// config flag a silent no-op outside of direct
|
|
92
|
+
// `createLocalCodingToolBundle()` use.
|
|
93
|
+
if (localConfig.fileCheckpointing === true || args.fileCheckpointer != null) {
|
|
94
|
+
const bundle = createLocalCodingToolBundle(localConfig, {
|
|
95
|
+
checkpointer: args.fileCheckpointer,
|
|
96
|
+
});
|
|
97
|
+
fileCheckpointer = bundle.checkpointer;
|
|
98
|
+
for (const localTool of bundle.tools) {
|
|
99
|
+
toolMap.set(localTool.name, localTool);
|
|
100
|
+
directToolNames.add(localTool.name);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
for (const localTool of createLocalCodingTools(localConfig)) {
|
|
105
|
+
toolMap.set(localTool.name, localTool);
|
|
106
|
+
directToolNames.add(localTool.name);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// When the coding-tool bundle was already installed above, it
|
|
111
|
+
// already created `bash_tool` / `execute_code` / programmatic-tool
|
|
112
|
+
// variants. Skip re-creating them here — the audit-of-audit (manual
|
|
113
|
+
// finding #4) flagged that the original loop overwrote those bundle
|
|
114
|
+
// instances with fresh ones via `createLocalExecutionTool`, wasting
|
|
115
|
+
// work and (more importantly) replacing tools the bundle had
|
|
116
|
+
// already wired up with shared state. The CODE_EXECUTION_TOOLS
|
|
117
|
+
// loop is now only relevant when the host pre-bound a tool with
|
|
118
|
+
// one of these names (the `toolMap.has(name)` branch) and coding
|
|
119
|
+
// tools are off.
|
|
120
|
+
const includeCodingTools = shouldIncludeCodingTools(args.toolExecution);
|
|
121
|
+
for (const name of CODE_EXECUTION_TOOLS) {
|
|
122
|
+
if (includeCodingTools)
|
|
123
|
+
continue;
|
|
124
|
+
if (!toolMap.has(name))
|
|
125
|
+
continue;
|
|
126
|
+
const localTool = createLocalExecutionTool(name, localConfig);
|
|
127
|
+
if (localTool == null) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
toolMap.set(name, localTool);
|
|
131
|
+
directToolNames.add(name);
|
|
132
|
+
}
|
|
133
|
+
return { toolMap, directToolNames, fileCheckpointer };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { resolveLocalExecutionTools, resolveLocalToolRegistry, resolveLocalToolsForBinding };
|
|
137
|
+
//# sourceMappingURL=resolveLocalExecutionTools.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveLocalExecutionTools.mjs","sources":["../../../../src/tools/local/resolveLocalExecutionTools.ts"],"sourcesContent":["import { Constants, CODE_EXECUTION_TOOLS } from '@/common';\nimport {\n createLocalBashExecutionTool,\n createLocalCodeExecutionTool,\n} from './LocalExecutionTools';\nimport {\n createLocalCodingToolBundle,\n createLocalCodingToolDefinitions,\n createLocalCodingTools,\n} from './LocalCodingTools';\nimport {\n createLocalBashProgrammaticToolCallingTool,\n createLocalProgrammaticToolCallingTool,\n} from './LocalProgrammaticToolCalling';\nimport type * as t from '@/types';\n\ntype ResolveLocalToolsResult = {\n toolMap: t.ToolMap;\n directToolNames: Set<string>;\n /**\n * Set when `local.fileCheckpointing === true` AND the auto-bind\n * coding suite is in use. ToolNode stashes this on the node and\n * exposes it via `getFileCheckpointer()` so the host can call\n * `rewind()` after a failed batch. Manual review (finding E)\n * flagged that the config flag was previously a no-op in the\n * Run/ToolNode auto-bind path — only direct\n * `createLocalCodingToolBundle()` callers could access the\n * checkpointer.\n */\n fileCheckpointer?: t.LocalFileCheckpointer;\n};\n\nfunction shouldUseLocalExecution(config?: t.ToolExecutionConfig): boolean {\n return config?.engine === 'local';\n}\n\nfunction shouldIncludeCodingTools(config?: t.ToolExecutionConfig): boolean {\n return (\n shouldUseLocalExecution(config) &&\n config?.local?.includeCodingTools !== false\n );\n}\n\nfunction createLocalExecutionTool(\n name: string,\n config: t.LocalExecutionConfig\n): t.GenericTool | undefined {\n switch (name) {\n case Constants.EXECUTE_CODE:\n return createLocalCodeExecutionTool(config);\n case Constants.BASH_TOOL:\n return createLocalBashExecutionTool({ config });\n case Constants.PROGRAMMATIC_TOOL_CALLING:\n return createLocalProgrammaticToolCallingTool(config);\n case Constants.BASH_PROGRAMMATIC_TOOL_CALLING:\n return createLocalBashProgrammaticToolCallingTool(config);\n default:\n return undefined;\n }\n}\n\nfunction mergeToolsByName(\n baseTools: t.GraphTools | undefined,\n localTools: t.GenericTool[]\n): t.GraphTools {\n const orderedTools: t.GenericTool[] = [];\n const indexByName = new Map<string, number>();\n\n for (const tool of (baseTools as t.GenericTool[] | undefined) ?? []) {\n if ('name' in tool && typeof tool.name === 'string') {\n indexByName.set(tool.name, orderedTools.length);\n }\n orderedTools.push(tool);\n }\n\n for (const tool of localTools) {\n const existingIndex = indexByName.get(tool.name);\n if (existingIndex == null) {\n indexByName.set(tool.name, orderedTools.length);\n orderedTools.push(tool);\n continue;\n }\n orderedTools[existingIndex] = tool;\n }\n\n return orderedTools;\n}\n\nexport function resolveLocalToolsForBinding(args: {\n tools?: t.GraphTools;\n toolExecution?: t.ToolExecutionConfig;\n}): t.GraphTools | undefined {\n if (!shouldUseLocalExecution(args.toolExecution)) {\n return args.tools;\n }\n\n const localConfig = args.toolExecution?.local ?? {};\n if (shouldIncludeCodingTools(args.toolExecution)) {\n return mergeToolsByName(args.tools, createLocalCodingTools(localConfig));\n }\n\n const replacements = ((args.tools as t.GenericTool[] | undefined) ?? [])\n .filter(\n (existingTool): existingTool is t.GenericTool & { name: string } =>\n 'name' in existingTool &&\n typeof existingTool.name === 'string' &&\n CODE_EXECUTION_TOOLS.has(existingTool.name)\n )\n .map((existingTool) =>\n createLocalExecutionTool(existingTool.name, localConfig)\n )\n .filter((localTool): localTool is t.GenericTool => localTool != null);\n\n return replacements.length === 0\n ? args.tools\n : mergeToolsByName(args.tools, replacements);\n}\n\nexport function resolveLocalToolRegistry(args: {\n toolRegistry?: t.LCToolRegistry;\n toolExecution?: t.ToolExecutionConfig;\n}): t.LCToolRegistry | undefined {\n if (!shouldIncludeCodingTools(args.toolExecution)) {\n return args.toolRegistry;\n }\n\n const registry = new Map(args.toolRegistry ?? []);\n for (const definition of createLocalCodingToolDefinitions()) {\n registry.set(definition.name, definition);\n }\n return registry;\n}\n\nexport function resolveLocalExecutionTools(args: {\n toolMap: t.ToolMap;\n toolExecution?: t.ToolExecutionConfig;\n /**\n * Caller-provided checkpointer that overrides the bundle's\n * auto-created one. The Graph layer threads a single per-Run\n * instance so every ToolNode it compiles shares one snapshot\n * store — without that, a multi-agent graph would each get a\n * private checkpointer and `Run.rewindFiles()` couldn't reach\n * any of them.\n */\n fileCheckpointer?: t.LocalFileCheckpointer;\n}): ResolveLocalToolsResult {\n const directToolNames = new Set<string>();\n if (!shouldUseLocalExecution(args.toolExecution)) {\n return {\n toolMap: args.toolMap,\n directToolNames,\n };\n }\n\n const localConfig = args.toolExecution?.local ?? {};\n const toolMap = new Map(args.toolMap);\n let fileCheckpointer: t.LocalFileCheckpointer | undefined;\n\n if (shouldIncludeCodingTools(args.toolExecution)) {\n // Use the bundle factory when fileCheckpointing is on so we can\n // surface the checkpointer back to the caller — without this, the\n // execution-path tools each captured into a checkpointer that was\n // immediately discarded, making the public `fileCheckpointing`\n // config flag a silent no-op outside of direct\n // `createLocalCodingToolBundle()` use.\n if (localConfig.fileCheckpointing === true || args.fileCheckpointer != null) {\n const bundle = createLocalCodingToolBundle(localConfig, {\n checkpointer: args.fileCheckpointer,\n });\n fileCheckpointer = bundle.checkpointer;\n for (const localTool of bundle.tools) {\n toolMap.set(localTool.name, localTool);\n directToolNames.add(localTool.name);\n }\n } else {\n for (const localTool of createLocalCodingTools(localConfig)) {\n toolMap.set(localTool.name, localTool);\n directToolNames.add(localTool.name);\n }\n }\n }\n\n // When the coding-tool bundle was already installed above, it\n // already created `bash_tool` / `execute_code` / programmatic-tool\n // variants. Skip re-creating them here — the audit-of-audit (manual\n // finding #4) flagged that the original loop overwrote those bundle\n // instances with fresh ones via `createLocalExecutionTool`, wasting\n // work and (more importantly) replacing tools the bundle had\n // already wired up with shared state. The CODE_EXECUTION_TOOLS\n // loop is now only relevant when the host pre-bound a tool with\n // one of these names (the `toolMap.has(name)` branch) and coding\n // tools are off.\n const includeCodingTools = shouldIncludeCodingTools(args.toolExecution);\n for (const name of CODE_EXECUTION_TOOLS) {\n if (includeCodingTools) continue;\n if (!toolMap.has(name)) continue;\n\n const localTool = createLocalExecutionTool(name, localConfig);\n if (localTool == null) {\n continue;\n }\n\n toolMap.set(name, localTool);\n directToolNames.add(name);\n }\n\n return { toolMap, directToolNames, fileCheckpointer };\n}\n"],"names":[],"mappings":";;;;;AAgCA,SAAS,uBAAuB,CAAC,MAA8B,EAAA;AAC7D,IAAA,OAAO,MAAM,EAAE,MAAM,KAAK,OAAO;AACnC;AAEA,SAAS,wBAAwB,CAAC,MAA8B,EAAA;AAC9D,IAAA,QACE,uBAAuB,CAAC,MAAM,CAAC;AAC/B,QAAA,MAAM,EAAE,KAAK,EAAE,kBAAkB,KAAK,KAAK;AAE/C;AAEA,SAAS,wBAAwB,CAC/B,IAAY,EACZ,MAA8B,EAAA;IAE9B,QAAQ,IAAI;QACZ,KAAK,SAAS,CAAC,YAAY;AACzB,YAAA,OAAO,4BAA4B,CAAC,MAAM,CAAC;QAC7C,KAAK,SAAS,CAAC,SAAS;AACtB,YAAA,OAAO,4BAA4B,CAAC,EAAE,MAAM,EAAE,CAAC;QACjD,KAAK,SAAS,CAAC,yBAAyB;AACtC,YAAA,OAAO,sCAAsC,CAAC,MAAM,CAAC;QACvD,KAAK,SAAS,CAAC,8BAA8B;AAC3C,YAAA,OAAO,0CAA0C,CAAC,MAAM,CAAC;AAC3D,QAAA;AACE,YAAA,OAAO,SAAS;;AAEpB;AAEA,SAAS,gBAAgB,CACvB,SAAmC,EACnC,UAA2B,EAAA;IAE3B,MAAM,YAAY,GAAoB,EAAE;AACxC,IAAA,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB;AAE7C,IAAA,KAAK,MAAM,IAAI,IAAK,SAAyC,IAAI,EAAE,EAAE;QACnE,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE;YACnD,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC;QACjD;AACA,QAAA,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;IACzB;AAEA,IAAA,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE;QAC7B,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;AAChD,QAAA,IAAI,aAAa,IAAI,IAAI,EAAE;YACzB,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC;AAC/C,YAAA,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;YACvB;QACF;AACA,QAAA,YAAY,CAAC,aAAa,CAAC,GAAG,IAAI;IACpC;AAEA,IAAA,OAAO,YAAY;AACrB;AAEM,SAAU,2BAA2B,CAAC,IAG3C,EAAA;IACC,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE;QAChD,OAAO,IAAI,CAAC,KAAK;IACnB;IAEA,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,EAAE,KAAK,IAAI,EAAE;AACnD,IAAA,IAAI,wBAAwB,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE;QAChD,OAAO,gBAAgB,CAAC,IAAI,CAAC,KAAK,EAAE,sBAAsB,CAAC,WAAW,CAAC,CAAC;IAC1E;IAEA,MAAM,YAAY,GAAG,CAAE,IAAI,CAAC,KAAqC,IAAI,EAAE;SACpE,MAAM,CACL,CAAC,YAAY,KACX,MAAM,IAAI,YAAY;AACtB,QAAA,OAAO,YAAY,CAAC,IAAI,KAAK,QAAQ;AACrC,QAAA,oBAAoB,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC;AAE9C,SAAA,GAAG,CAAC,CAAC,YAAY,KAChB,wBAAwB,CAAC,YAAY,CAAC,IAAI,EAAE,WAAW,CAAC;SAEzD,MAAM,CAAC,CAAC,SAAS,KAAiC,SAAS,IAAI,IAAI,CAAC;AAEvE,IAAA,OAAO,YAAY,CAAC,MAAM,KAAK;UAC3B,IAAI,CAAC;UACL,gBAAgB,CAAC,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC;AAChD;AAEM,SAAU,wBAAwB,CAAC,IAGxC,EAAA;IACC,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE;QACjD,OAAO,IAAI,CAAC,YAAY;IAC1B;IAEA,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;AACjD,IAAA,KAAK,MAAM,UAAU,IAAI,gCAAgC,EAAE,EAAE;QAC3D,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC;IAC3C;AACA,IAAA,OAAO,QAAQ;AACjB;AAEM,SAAU,0BAA0B,CAAC,IAY1C,EAAA;AACC,IAAA,MAAM,eAAe,GAAG,IAAI,GAAG,EAAU;IACzC,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE;QAChD,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,eAAe;SAChB;IACH;IAEA,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,EAAE,KAAK,IAAI,EAAE;IACnD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC;AACrC,IAAA,IAAI,gBAAqD;AAEzD,IAAA,IAAI,wBAAwB,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE;;;;;;;AAOhD,QAAA,IAAI,WAAW,CAAC,iBAAiB,KAAK,IAAI,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,EAAE;AAC3E,YAAA,MAAM,MAAM,GAAG,2BAA2B,CAAC,WAAW,EAAE;gBACtD,YAAY,EAAE,IAAI,CAAC,gBAAgB;AACpC,aAAA,CAAC;AACF,YAAA,gBAAgB,GAAG,MAAM,CAAC,YAAY;AACtC,YAAA,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,KAAK,EAAE;gBACpC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC;AACtC,gBAAA,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC;YACrC;QACF;aAAO;YACL,KAAK,MAAM,SAAS,IAAI,sBAAsB,CAAC,WAAW,CAAC,EAAE;gBAC3D,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC;AACtC,gBAAA,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC;YACrC;QACF;IACF;;;;;;;;;;;IAYA,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,IAAI,CAAC,aAAa,CAAC;AACvE,IAAA,KAAK,MAAM,IAAI,IAAI,oBAAoB,EAAE;AACvC,QAAA,IAAI,kBAAkB;YAAE;AACxB,QAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE;QAExB,MAAM,SAAS,GAAG,wBAAwB,CAAC,IAAI,EAAE,WAAW,CAAC;AAC7D,QAAA,IAAI,SAAS,IAAI,IAAI,EAAE;YACrB;QACF;AAEA,QAAA,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC;AAC5B,QAAA,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;IAC3B;AAEA,IAAA,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE;AACvD;;;;"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { extname } from 'path';
|
|
2
|
+
import { spawnLocalProcess, getWorkspaceFS, getSpawn } from './LocalExecutionEngine.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Per-file syntax check used by `edit_file` / `write_file` to surface
|
|
6
|
+
* obvious errors immediately after the write — strictly cheaper than
|
|
7
|
+
* full LSP integration and catches the bulk of "you broke the file"
|
|
8
|
+
* regressions a vision-less agent loop would otherwise miss until
|
|
9
|
+
* the next call.
|
|
10
|
+
*
|
|
11
|
+
* Each checker is a tiny shell-out (or in-process function) keyed on
|
|
12
|
+
* file extension. Failures are returned as a single short message;
|
|
13
|
+
* the wiring layer decides whether to append it to the tool result
|
|
14
|
+
* advisorily (`auto`) or to throw and force the model to react
|
|
15
|
+
* (`strict`).
|
|
16
|
+
*
|
|
17
|
+
* We deliberately do NOT cover TypeScript here because per-file `tsc`
|
|
18
|
+
* is slow and per-file syntax (without type info) misses most TS
|
|
19
|
+
* errors anyway. Use the project-level `compile_check` tool for that.
|
|
20
|
+
*/
|
|
21
|
+
// Per-backend × per-env cache. Codex P2 #40 — keying by spawn
|
|
22
|
+
// backend alone misses env-driven availability changes (e.g. PATH
|
|
23
|
+
// loses node between Runs that share the same backend). Same fix
|
|
24
|
+
// shape as the ripgrep cache (Codex P1 #34).
|
|
25
|
+
let probeCacheByBackend = new WeakMap();
|
|
26
|
+
function envCacheKey(env) {
|
|
27
|
+
if (env == null)
|
|
28
|
+
return '';
|
|
29
|
+
const sorted = {};
|
|
30
|
+
for (const k of Object.keys(env).sort()) {
|
|
31
|
+
sorted[k] = env[k];
|
|
32
|
+
}
|
|
33
|
+
return JSON.stringify(sorted);
|
|
34
|
+
}
|
|
35
|
+
function cacheFor(config) {
|
|
36
|
+
const backend = getSpawn(config);
|
|
37
|
+
let envMap = probeCacheByBackend.get(backend);
|
|
38
|
+
if (envMap == null) {
|
|
39
|
+
envMap = new Map();
|
|
40
|
+
probeCacheByBackend.set(backend, envMap);
|
|
41
|
+
}
|
|
42
|
+
const envKey = envCacheKey(config.env);
|
|
43
|
+
let entry = envMap.get(envKey);
|
|
44
|
+
if (entry == null) {
|
|
45
|
+
entry = {};
|
|
46
|
+
envMap.set(envKey, entry);
|
|
47
|
+
}
|
|
48
|
+
return entry;
|
|
49
|
+
}
|
|
50
|
+
async function probe(command, args, cached, config) {
|
|
51
|
+
const entry = cacheFor(config);
|
|
52
|
+
let probePromise = entry[cached];
|
|
53
|
+
if (probePromise == null) {
|
|
54
|
+
probePromise = spawnLocalProcess(command, args, { ...config, timeoutMs: 5000, sandbox: { enabled: false } }, { internal: true })
|
|
55
|
+
.then((result) => result != null && result.exitCode === 0)
|
|
56
|
+
.catch(() => false);
|
|
57
|
+
entry[cached] = probePromise;
|
|
58
|
+
}
|
|
59
|
+
return probePromise;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Test-only reset hook. Clears the per-backend probe cache so tests
|
|
63
|
+
* can swap in mocked spawn backends and reprobe deterministically.
|
|
64
|
+
*
|
|
65
|
+
* @internal Not part of the public SDK surface.
|
|
66
|
+
*/
|
|
67
|
+
function _resetSyntaxCheckProbeCacheForTests() {
|
|
68
|
+
probeCacheByBackend = new WeakMap();
|
|
69
|
+
}
|
|
70
|
+
const jsCheck = async (path, config) => {
|
|
71
|
+
if (!(await probe('node', ['--version'], 'hasNode', config))) {
|
|
72
|
+
return { ok: true };
|
|
73
|
+
}
|
|
74
|
+
const result = await spawnLocalProcess('node', ['--check', path], { ...config, timeoutMs: 5000, sandbox: { enabled: false } }, { internal: true });
|
|
75
|
+
if (result.exitCode === 0)
|
|
76
|
+
return { ok: true };
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
checker: 'node --check',
|
|
80
|
+
output: result.stderr.trim() || result.stdout.trim() || 'syntax error',
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
const pythonCheck = async (path, config) => {
|
|
84
|
+
if (!(await probe('python3', ['--version'], 'hasPython', config))) {
|
|
85
|
+
return { ok: true };
|
|
86
|
+
}
|
|
87
|
+
const program = 'import py_compile, sys\n' +
|
|
88
|
+
'try:\n' +
|
|
89
|
+
' py_compile.compile(sys.argv[1], doraise=True)\n' +
|
|
90
|
+
'except py_compile.PyCompileError as e:\n' +
|
|
91
|
+
' print(e.msg.strip(), file=sys.stderr)\n' +
|
|
92
|
+
' sys.exit(1)\n';
|
|
93
|
+
const result = await spawnLocalProcess('python3', ['-c', program, path], { ...config, timeoutMs: 5000, sandbox: { enabled: false } }, { internal: true });
|
|
94
|
+
if (result.exitCode === 0)
|
|
95
|
+
return { ok: true };
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
checker: 'py_compile',
|
|
99
|
+
output: result.stderr.trim() || result.stdout.trim() || 'syntax error',
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
const jsonCheck = async (path, config) => {
|
|
103
|
+
// Route through the configured WorkspaceFS so a Run with a custom
|
|
104
|
+
// `local.exec.fs` (in-memory or remote engine) validates the same
|
|
105
|
+
// file the write_file/edit_file path actually wrote — pre-fix this
|
|
106
|
+
// read went to the host fs and either silently passed (no host
|
|
107
|
+
// file → catch returns undefined → ok: true) or read a different
|
|
108
|
+
// file with the same absolute path. Codex P1 #24.
|
|
109
|
+
const fs = getWorkspaceFS(config);
|
|
110
|
+
const raw = await fs.readFile(path, 'utf8').catch(() => undefined);
|
|
111
|
+
if (raw == null)
|
|
112
|
+
return { ok: true };
|
|
113
|
+
try {
|
|
114
|
+
JSON.parse(raw);
|
|
115
|
+
return { ok: true };
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
checker: 'JSON.parse',
|
|
121
|
+
output: err.message,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const bashCheck = async (path, config) => {
|
|
126
|
+
if (!(await probe('bash', ['--version'], 'hasBash', config))) {
|
|
127
|
+
return { ok: true };
|
|
128
|
+
}
|
|
129
|
+
const result = await spawnLocalProcess('bash', ['-n', path], { ...config, timeoutMs: 5000, sandbox: { enabled: false } }, { internal: true });
|
|
130
|
+
if (result.exitCode === 0)
|
|
131
|
+
return { ok: true };
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
checker: 'bash -n',
|
|
135
|
+
output: result.stderr.trim() || result.stdout.trim() || 'syntax error',
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
const CHECKERS_BY_EXT = {
|
|
139
|
+
'.js': jsCheck,
|
|
140
|
+
'.mjs': jsCheck,
|
|
141
|
+
'.cjs': jsCheck,
|
|
142
|
+
'.jsx': jsCheck,
|
|
143
|
+
'.py': pythonCheck,
|
|
144
|
+
'.pyw': pythonCheck,
|
|
145
|
+
'.json': jsonCheck,
|
|
146
|
+
'.sh': bashCheck,
|
|
147
|
+
'.bash': bashCheck,
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Run the post-edit syntax check for `absolutePath`. Returns
|
|
151
|
+
* `null` when no checker matches the extension (most files), or a
|
|
152
|
+
* `SyntaxCheckOutcome`.
|
|
153
|
+
*
|
|
154
|
+
* Truncates `output` to `maxOutputChars` (default 4096) so a
|
|
155
|
+
* 10MB-of-errors transpiler dump can't blow the model context.
|
|
156
|
+
*/
|
|
157
|
+
async function runPostEditSyntaxCheck(absolutePath, config) {
|
|
158
|
+
const ext = extname(absolutePath).toLowerCase();
|
|
159
|
+
const checker = CHECKERS_BY_EXT[ext];
|
|
160
|
+
if (checker == null)
|
|
161
|
+
return null;
|
|
162
|
+
try {
|
|
163
|
+
const result = await checker(absolutePath, config);
|
|
164
|
+
if (!result.ok) {
|
|
165
|
+
return {
|
|
166
|
+
ok: false,
|
|
167
|
+
checker: result.checker,
|
|
168
|
+
output: result.output.slice(0, 4096),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export { _resetSyntaxCheckProbeCacheForTests, runPostEditSyntaxCheck };
|
|
179
|
+
//# sourceMappingURL=syntaxCheck.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syntaxCheck.mjs","sources":["../../../../src/tools/local/syntaxCheck.ts"],"sourcesContent":["/**\n * Per-file syntax check used by `edit_file` / `write_file` to surface\n * obvious errors immediately after the write — strictly cheaper than\n * full LSP integration and catches the bulk of \"you broke the file\"\n * regressions a vision-less agent loop would otherwise miss until\n * the next call.\n *\n * Each checker is a tiny shell-out (or in-process function) keyed on\n * file extension. Failures are returned as a single short message;\n * the wiring layer decides whether to append it to the tool result\n * advisorily (`auto`) or to throw and force the model to react\n * (`strict`).\n *\n * We deliberately do NOT cover TypeScript here because per-file `tsc`\n * is slow and per-file syntax (without type info) misses most TS\n * errors anyway. Use the project-level `compile_check` tool for that.\n */\n\nimport { extname } from 'path';\nimport type * as t from '@/types';\nimport {\n getSpawn,\n getWorkspaceFS,\n spawnLocalProcess,\n} from './LocalExecutionEngine';\n\nexport type SyntaxCheckOutcome =\n | { ok: true }\n | { ok: false; checker: string; output: string };\n\nexport type SyntaxChecker = (\n path: string,\n config: t.LocalExecutionConfig\n) => Promise<SyntaxCheckOutcome>;\n\n/**\n * Per-backend availability cache for the post-edit syntax-check probe\n * tools (node, python3, bash). Keyed on the *effective spawn backend*\n * — see `getSpawn(config)` in LocalExecutionEngine — so a Run that\n * probes node over Node's child_process can't poison a subsequent Run\n * whose `local.exec.spawn` routes elsewhere (a remote sandbox might\n * have python but not node, etc.).\n *\n * Mirrors the same fix that landed for the ripgrep cache in\n * `LocalCodingTools.ts` after the first round of Codex review.\n * WeakMap keying lets disposed backends GC their entry; the test\n * reset hook re-creates the map.\n */\ntype ProbeKind = 'hasNode' | 'hasPython' | 'hasBash';\ntype ProbeCache = Partial<Record<ProbeKind, Promise<boolean>>>;\n\n// Per-backend × per-env cache. Codex P2 #40 — keying by spawn\n// backend alone misses env-driven availability changes (e.g. PATH\n// loses node between Runs that share the same backend). Same fix\n// shape as the ripgrep cache (Codex P1 #34).\nlet probeCacheByBackend = new WeakMap<\n t.LocalSpawn,\n Map<string, ProbeCache>\n>();\n\nfunction envCacheKey(env: NodeJS.ProcessEnv | undefined): string {\n if (env == null) return '';\n const sorted: Record<string, string | undefined> = {};\n for (const k of Object.keys(env).sort()) {\n sorted[k] = env[k];\n }\n return JSON.stringify(sorted);\n}\n\nfunction cacheFor(\n config: t.LocalExecutionConfig\n): ProbeCache {\n const backend = getSpawn(config);\n let envMap = probeCacheByBackend.get(backend);\n if (envMap == null) {\n envMap = new Map();\n probeCacheByBackend.set(backend, envMap);\n }\n const envKey = envCacheKey(config.env);\n let entry = envMap.get(envKey);\n if (entry == null) {\n entry = {};\n envMap.set(envKey, entry);\n }\n return entry;\n}\n\nasync function probe(\n command: string,\n args: string[],\n cached: ProbeKind,\n config: t.LocalExecutionConfig\n): Promise<boolean> {\n const entry = cacheFor(config);\n let probePromise = entry[cached];\n if (probePromise == null) {\n probePromise = spawnLocalProcess(\n command,\n args,\n { ...config, timeoutMs: 5000, sandbox: { enabled: false } },\n { internal: true }\n )\n .then((result) => result != null && result.exitCode === 0)\n .catch(() => false);\n entry[cached] = probePromise;\n }\n return probePromise;\n}\n\n/**\n * Test-only reset hook. Clears the per-backend probe cache so tests\n * can swap in mocked spawn backends and reprobe deterministically.\n *\n * @internal Not part of the public SDK surface.\n */\nexport function _resetSyntaxCheckProbeCacheForTests(): void {\n probeCacheByBackend = new WeakMap();\n}\n\nconst jsCheck: SyntaxChecker = async (path, config) => {\n if (!(await probe('node', ['--version'], 'hasNode', config))) {\n return { ok: true };\n }\n const result = await spawnLocalProcess(\n 'node',\n ['--check', path],\n { ...config, timeoutMs: 5000, sandbox: { enabled: false } },\n { internal: true }\n );\n if (result.exitCode === 0) return { ok: true };\n return {\n ok: false,\n checker: 'node --check',\n output: result.stderr.trim() || result.stdout.trim() || 'syntax error',\n };\n};\n\nconst pythonCheck: SyntaxChecker = async (path, config) => {\n if (!(await probe('python3', ['--version'], 'hasPython', config))) {\n return { ok: true };\n }\n const program =\n 'import py_compile, sys\\n' +\n 'try:\\n' +\n ' py_compile.compile(sys.argv[1], doraise=True)\\n' +\n 'except py_compile.PyCompileError as e:\\n' +\n ' print(e.msg.strip(), file=sys.stderr)\\n' +\n ' sys.exit(1)\\n';\n const result = await spawnLocalProcess(\n 'python3',\n ['-c', program, path],\n { ...config, timeoutMs: 5000, sandbox: { enabled: false } },\n { internal: true }\n );\n if (result.exitCode === 0) return { ok: true };\n return {\n ok: false,\n checker: 'py_compile',\n output: result.stderr.trim() || result.stdout.trim() || 'syntax error',\n };\n};\n\nconst jsonCheck: SyntaxChecker = async (path, config) => {\n // Route through the configured WorkspaceFS so a Run with a custom\n // `local.exec.fs` (in-memory or remote engine) validates the same\n // file the write_file/edit_file path actually wrote — pre-fix this\n // read went to the host fs and either silently passed (no host\n // file → catch returns undefined → ok: true) or read a different\n // file with the same absolute path. Codex P1 #24.\n const fs = getWorkspaceFS(config);\n const raw = await fs.readFile(path, 'utf8').catch(() => undefined);\n if (raw == null) return { ok: true };\n try {\n JSON.parse(raw);\n return { ok: true };\n } catch (err) {\n return {\n ok: false,\n checker: 'JSON.parse',\n output: (err as Error).message,\n };\n }\n};\n\nconst bashCheck: SyntaxChecker = async (path, config) => {\n if (!(await probe('bash', ['--version'], 'hasBash', config))) {\n return { ok: true };\n }\n const result = await spawnLocalProcess(\n 'bash',\n ['-n', path],\n { ...config, timeoutMs: 5000, sandbox: { enabled: false } },\n { internal: true }\n );\n if (result.exitCode === 0) return { ok: true };\n return {\n ok: false,\n checker: 'bash -n',\n output: result.stderr.trim() || result.stdout.trim() || 'syntax error',\n };\n};\n\nconst CHECKERS_BY_EXT: Record<string, SyntaxChecker> = {\n '.js': jsCheck,\n '.mjs': jsCheck,\n '.cjs': jsCheck,\n '.jsx': jsCheck,\n '.py': pythonCheck,\n '.pyw': pythonCheck,\n '.json': jsonCheck,\n '.sh': bashCheck,\n '.bash': bashCheck,\n};\n\n/**\n * Run the post-edit syntax check for `absolutePath`. Returns\n * `null` when no checker matches the extension (most files), or a\n * `SyntaxCheckOutcome`.\n *\n * Truncates `output` to `maxOutputChars` (default 4096) so a\n * 10MB-of-errors transpiler dump can't blow the model context.\n */\nexport async function runPostEditSyntaxCheck(\n absolutePath: string,\n config: t.LocalExecutionConfig\n): Promise<SyntaxCheckOutcome | null> {\n const ext = extname(absolutePath).toLowerCase();\n const checker = (CHECKERS_BY_EXT as Record<string, SyntaxChecker | undefined>)[ext];\n if (checker == null) return null;\n try {\n const result = await checker(absolutePath, config);\n if (!result.ok) {\n return {\n ok: false,\n checker: result.checker,\n output: result.output.slice(0, 4096),\n };\n }\n return result;\n } catch {\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BOM and line-ending preservation helpers for the local engine's
|
|
3
|
+
* file-mutating tools. We never *introduce* a BOM or change line
|
|
4
|
+
* endings — only preserve what was already on disk so a Windows-
|
|
5
|
+
* checked-in source file stays CRLF and a UTF-8-with-BOM JSON file
|
|
6
|
+
* keeps its BOM after an edit.
|
|
7
|
+
*
|
|
8
|
+
* Inspired by opencode's `Bom` helper. Trimmed to the cases that
|
|
9
|
+
* actually matter for editing source code (UTF-8 BOM only;
|
|
10
|
+
* UTF-16/UTF-32 are out of scope).
|
|
11
|
+
*/
|
|
12
|
+
const UTF8_BOM = '';
|
|
13
|
+
function decodeFile(raw) {
|
|
14
|
+
const hasBom = raw.startsWith(UTF8_BOM);
|
|
15
|
+
const stripped = hasBom ? raw.slice(1) : raw;
|
|
16
|
+
const newline = stripped.includes('\r\n') ? '\r\n' : '\n';
|
|
17
|
+
// Internally we always work in LF; encode() restores CRLF on write.
|
|
18
|
+
const lf = newline === '\r\n' ? stripped.replace(/\r\n/g, '\n') : stripped;
|
|
19
|
+
return { text: lf, hasBom, newline };
|
|
20
|
+
}
|
|
21
|
+
function encodeFile(text, encoding) {
|
|
22
|
+
const out = encoding.newline === '\r\n' ? text.replace(/\n/g, '\r\n') : text;
|
|
23
|
+
return encoding.hasBom ? `${UTF8_BOM}${out}` : out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { decodeFile, encodeFile };
|
|
27
|
+
//# sourceMappingURL=textEncoding.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textEncoding.mjs","sources":["../../../../src/tools/local/textEncoding.ts"],"sourcesContent":["/**\n * BOM and line-ending preservation helpers for the local engine's\n * file-mutating tools. We never *introduce* a BOM or change line\n * endings — only preserve what was already on disk so a Windows-\n * checked-in source file stays CRLF and a UTF-8-with-BOM JSON file\n * keeps its BOM after an edit.\n *\n * Inspired by opencode's `Bom` helper. Trimmed to the cases that\n * actually matter for editing source code (UTF-8 BOM only;\n * UTF-16/UTF-32 are out of scope).\n */\n\nconst UTF8_BOM = '';\n\nexport interface EncodedFile {\n /** File contents with BOM stripped. */\n text: string;\n /** Whether the on-disk file started with a UTF-8 BOM. */\n hasBom: boolean;\n /** Detected newline style. CRLF wins if any CRLF is present. */\n newline: '\\n' | '\\r\\n';\n}\n\nexport function decodeFile(raw: string): EncodedFile {\n const hasBom = raw.startsWith(UTF8_BOM);\n const stripped = hasBom ? raw.slice(1) : raw;\n const newline = stripped.includes('\\r\\n') ? '\\r\\n' : '\\n';\n // Internally we always work in LF; encode() restores CRLF on write.\n const lf = newline === '\\r\\n' ? stripped.replace(/\\r\\n/g, '\\n') : stripped;\n return { text: lf, hasBom, newline };\n}\n\nexport function encodeFile(text: string, encoding: EncodedFile): string {\n const out =\n encoding.newline === '\\r\\n' ? text.replace(/\\n/g, '\\r\\n') : text;\n return encoding.hasBom ? `${UTF8_BOM}${out}` : out;\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;AAUG;AAEH,MAAM,QAAQ,GAAG,GAAG;AAWd,SAAU,UAAU,CAAC,GAAW,EAAA;IACpC,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC;AACvC,IAAA,MAAM,QAAQ,GAAG,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG;AAC5C,IAAA,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI;;IAEzD,MAAM,EAAE,GAAG,OAAO,KAAK,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,QAAQ;IAC1E,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;AACtC;AAEM,SAAU,UAAU,CAAC,IAAY,EAAE,QAAqB,EAAA;IAC5D,MAAM,GAAG,GACP,QAAQ,CAAC,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI;AAClE,IAAA,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAA,EAAG,QAAQ,CAAA,EAAG,GAAG,CAAA,CAAE,GAAG,GAAG;AACpD;;;;"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { open, unlink, realpath, mkdir, readdir, stat, writeFile, readFile } from 'fs/promises';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Engine-agnostic filesystem seam for the local-coding tool suite.
|
|
5
|
+
*
|
|
6
|
+
* The current "local" engine maps every operation to Node's
|
|
7
|
+
* `fs/promises` against the host machine. A future engine — e.g. a
|
|
8
|
+
* stateful remote sandbox (e2b / Modal / Daytona / ssh-jail) —
|
|
9
|
+
* supplies its own `WorkspaceFS` implementation and reuses every
|
|
10
|
+
* tool factory unchanged. Same fuzzy-match `edit_file`, same
|
|
11
|
+
* checkpointer, same syntax-check, same image attachments — only
|
|
12
|
+
* the underlying I/O changes.
|
|
13
|
+
*
|
|
14
|
+
* Path semantics belong to the implementation. The local engine
|
|
15
|
+
* interprets paths as host filesystem paths; a remote engine would
|
|
16
|
+
* interpret them as remote-namespace paths. Tool factories don't
|
|
17
|
+
* inspect the strings beyond passing them through.
|
|
18
|
+
*
|
|
19
|
+
* Keep this surface minimal. Add a method only when an existing
|
|
20
|
+
* tool genuinely needs it; resist the temptation to mirror all of
|
|
21
|
+
* `fs/promises`.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Default `WorkspaceFS` backed by Node's `fs/promises` module.
|
|
25
|
+
* Returned by `getWorkspaceFS(config)` when the host hasn't supplied
|
|
26
|
+
* an override on `local.exec.fs`.
|
|
27
|
+
*/
|
|
28
|
+
const nodeWorkspaceFS = {
|
|
29
|
+
// The runtime impl ignores the encoding-vs-buffer distinction; the
|
|
30
|
+
// overload signatures above are what callers see.
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
readFile: ((path, encoding) => encoding != null
|
|
33
|
+
? readFile(path, encoding)
|
|
34
|
+
: readFile(path)),
|
|
35
|
+
writeFile: (path, content, options) => writeFile(path, content, options ?? 'utf8'),
|
|
36
|
+
stat: (path) => stat(path),
|
|
37
|
+
readdir: ((path, options) => options?.withFileTypes === true
|
|
38
|
+
? readdir(path, { withFileTypes: true })
|
|
39
|
+
: readdir(path)),
|
|
40
|
+
mkdir: async (path, options) => {
|
|
41
|
+
await mkdir(path, options);
|
|
42
|
+
},
|
|
43
|
+
realpath: (path) => realpath(path),
|
|
44
|
+
unlink: (path) => unlink(path),
|
|
45
|
+
open: (path, flags) => open(path, flags),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export { nodeWorkspaceFS };
|
|
49
|
+
//# sourceMappingURL=workspaceFS.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceFS.mjs","sources":["../../../../src/tools/local/workspaceFS.ts"],"sourcesContent":["/**\n * Engine-agnostic filesystem seam for the local-coding tool suite.\n *\n * The current \"local\" engine maps every operation to Node's\n * `fs/promises` against the host machine. A future engine — e.g. a\n * stateful remote sandbox (e2b / Modal / Daytona / ssh-jail) —\n * supplies its own `WorkspaceFS` implementation and reuses every\n * tool factory unchanged. Same fuzzy-match `edit_file`, same\n * checkpointer, same syntax-check, same image attachments — only\n * the underlying I/O changes.\n *\n * Path semantics belong to the implementation. The local engine\n * interprets paths as host filesystem paths; a remote engine would\n * interpret them as remote-namespace paths. Tool factories don't\n * inspect the strings beyond passing them through.\n *\n * Keep this surface minimal. Add a method only when an existing\n * tool genuinely needs it; resist the temptation to mirror all of\n * `fs/promises`.\n */\n\nimport {\n mkdir as fsMkdir,\n open as fsOpen,\n readdir as fsReaddir,\n readFile as fsReadFile,\n realpath as fsRealpath,\n stat as fsStat,\n unlink as fsUnlink,\n writeFile as fsWriteFile,\n} from 'fs/promises';\nimport type { MakeDirectoryOptions, Stats, WriteFileOptions } from 'fs';\nimport type { FileHandle } from 'fs/promises';\n\nexport type ReaddirEntry = {\n name: string;\n isFile(): boolean;\n isDirectory(): boolean;\n isSymbolicLink(): boolean;\n};\n\nexport interface WorkspaceFS {\n readFile(path: string, encoding: 'utf8'): Promise<string>;\n readFile(path: string): Promise<Buffer>;\n writeFile(\n path: string,\n content: string | Buffer,\n options?: WriteFileOptions\n ): Promise<void>;\n stat(path: string): Promise<Stats>;\n readdir(\n path: string,\n options: { withFileTypes: true }\n ): Promise<ReaddirEntry[]>;\n readdir(path: string): Promise<string[]>;\n mkdir(path: string, options?: MakeDirectoryOptions): Promise<void>;\n realpath(path: string): Promise<string>;\n unlink(path: string): Promise<void>;\n /** Open a file for low-level read access (used by binary detection). */\n open(path: string, flags: 'r'): Promise<FileHandle>;\n}\n\n/**\n * Default `WorkspaceFS` backed by Node's `fs/promises` module.\n * Returned by `getWorkspaceFS(config)` when the host hasn't supplied\n * an override on `local.exec.fs`.\n */\nexport const nodeWorkspaceFS: WorkspaceFS = {\n // The runtime impl ignores the encoding-vs-buffer distinction; the\n // overload signatures above are what callers see.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readFile: ((path: string, encoding?: 'utf8') =>\n encoding != null\n ? fsReadFile(path, encoding)\n : fsReadFile(path)) as WorkspaceFS['readFile'],\n writeFile: (path, content, options) =>\n fsWriteFile(path, content, options ?? 'utf8'),\n stat: (path) => fsStat(path),\n readdir: ((path: string, options?: { withFileTypes: true }) =>\n options?.withFileTypes === true\n ? fsReaddir(path, { withFileTypes: true })\n : fsReaddir(path)) as WorkspaceFS['readdir'],\n mkdir: async (path, options) => {\n await fsMkdir(path, options);\n },\n realpath: (path) => fsRealpath(path),\n unlink: (path) => fsUnlink(path),\n open: (path, flags) => fsOpen(path, flags),\n};\n"],"names":["fsReadFile","fsWriteFile","fsStat","fsReaddir","fsMkdir","fsRealpath","fsUnlink","fsOpen"],"mappings":";;AAAA;;;;;;;;;;;;;;;;;;;AAmBG;AA2CH;;;;AAIG;AACI,MAAM,eAAe,GAAgB;;;;IAI1C,QAAQ,GAAG,CAAC,IAAY,EAAE,QAAiB,KACzC,QAAQ,IAAI;AACV,UAAEA,QAAU,CAAC,IAAI,EAAE,QAAQ;AAC3B,UAAEA,QAAU,CAAC,IAAI,CAAC,CAA4B;AAClD,IAAA,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,KAChCC,SAAW,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,IAAI,MAAM,CAAC;IAC/C,IAAI,EAAE,CAAC,IAAI,KAAKC,IAAM,CAAC,IAAI,CAAC;AAC5B,IAAA,OAAO,GAAG,CAAC,IAAY,EAAE,OAAiC,KACxD,OAAO,EAAE,aAAa,KAAK;UACvBC,OAAS,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;AACzC,UAAEA,OAAS,CAAC,IAAI,CAAC,CAA2B;AAChD,IAAA,KAAK,EAAE,OAAO,IAAI,EAAE,OAAO,KAAI;AAC7B,QAAA,MAAMC,KAAO,CAAC,IAAI,EAAE,OAAO,CAAC;IAC9B,CAAC;IACD,QAAQ,EAAE,CAAC,IAAI,KAAKC,QAAU,CAAC,IAAI,CAAC;IACpC,MAAM,EAAE,CAAC,IAAI,KAAKC,MAAQ,CAAC,IAAI,CAAC;AAChC,IAAA,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,KAAKC,IAAM,CAAC,IAAI,EAAE,KAAK,CAAC;;;;;"}
|
|
@@ -3,6 +3,7 @@ import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
|
3
3
|
import { HumanMessage } from '@langchain/core/messages';
|
|
4
4
|
import { GraphEvents, Callback } from '../../common/enum.mjs';
|
|
5
5
|
import { executeHooks } from '../../hooks/executeHooks.mjs';
|
|
6
|
+
import '../../hooks/createWorkspacePolicyHook.mjs';
|
|
6
7
|
|
|
7
8
|
const DEFAULT_MAX_TURNS = 25;
|
|
8
9
|
const RECURSION_MULTIPLIER = 3;
|