@akira-tl/forgerelay 0.5.4 → 0.5.6
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/CHANGELOG.md +24 -0
- package/capabilities/batch-execution/GUIDE.md +18 -0
- package/dist/activity/audit-store.js +15 -1
- package/dist/activity/lifecycle.js +2 -0
- package/dist/activity/query-service.js +45 -3
- package/dist/capabilities.js +9 -0
- package/dist/capability-registry.js +28 -0
- package/dist/db/migrations.js +23 -0
- package/dist/file-mutations.js +33 -2
- package/dist/mcp/server-instructions.js +3 -3
- package/dist/operations/batch/executor.js +216 -0
- package/dist/operations/batch/scheduler.js +108 -0
- package/dist/operations/batch/types.js +76 -0
- package/dist/operations/bulk-mutation.js +41 -0
- package/dist/operations/bulk-read.js +28 -0
- package/dist/operations/core-operation-executor.js +30 -0
- package/dist/operations/native-bulk-mutations.js +134 -0
- package/dist/pi-tools.js +36 -0
- package/dist/server.js +832 -493
- package/package.json +2 -2
- package/scripts/release/tag-local-ci.mjs +144 -0
package/dist/server.js
CHANGED
|
@@ -36,6 +36,10 @@ import { createOpenAIIncomingArtifactAdapter, IncomingArtifactAdapterRegistry, }
|
|
|
36
36
|
import { logEvent, requestIp, requestPath, commandPreview, transportSessionIdPrefix, workspaceLogLabel, } from "./logger.js";
|
|
37
37
|
import { editFileTool, readFileTool, writeFileTool, } from "./pi-tools.js";
|
|
38
38
|
import { SingleUserOAuthProvider } from "./oauth-provider.js";
|
|
39
|
+
import { BatchExecutor } from "./operations/batch/executor.js";
|
|
40
|
+
import { executeBulkRead } from "./operations/bulk-read.js";
|
|
41
|
+
import { NativeBulkMutationExecutor } from "./operations/native-bulk-mutations.js";
|
|
42
|
+
import { createCoreOperationExecutor, } from "./operations/core-operation-executor.js";
|
|
39
43
|
import { McpTransportRegistry, } from "./mcp-sessions.js";
|
|
40
44
|
import { ProcessManager, resolveProcessId, } from "./process-sessions.js";
|
|
41
45
|
import { createReviewCheckpointManager } from "./review-checkpoints.js";
|
|
@@ -171,6 +175,7 @@ const capabilityCatalogOutputSchema = z.object({
|
|
|
171
175
|
description: z.string(),
|
|
172
176
|
available: z.boolean(),
|
|
173
177
|
unavailableReason: z.string().optional(),
|
|
178
|
+
batchPolicy: z.enum(["parallel", "serial", "unsupported"]),
|
|
174
179
|
guide: capabilityCatalogGuideOutputSchema,
|
|
175
180
|
});
|
|
176
181
|
const capabilityErrorOutputSchema = z.object({
|
|
@@ -724,6 +729,53 @@ async function reviewWorkspaceChanges(reviewCheckpoints, workspace) {
|
|
|
724
729
|
function toolResultIsError(result) {
|
|
725
730
|
return typeof result === "object" && result !== null && result.isError === true;
|
|
726
731
|
}
|
|
732
|
+
function toolResultText(result) {
|
|
733
|
+
if (typeof result !== "object" || result === null)
|
|
734
|
+
return String(result ?? "");
|
|
735
|
+
const record = result;
|
|
736
|
+
if (Array.isArray(record.content)) {
|
|
737
|
+
const text = record.content
|
|
738
|
+
.map((entry) => {
|
|
739
|
+
if (typeof entry !== "object" || entry === null)
|
|
740
|
+
return "";
|
|
741
|
+
const value = entry.text;
|
|
742
|
+
return typeof value === "string" ? value : "";
|
|
743
|
+
})
|
|
744
|
+
.filter(Boolean)
|
|
745
|
+
.join("\n");
|
|
746
|
+
if (text)
|
|
747
|
+
return text;
|
|
748
|
+
}
|
|
749
|
+
if (typeof record.structuredContent === "object" && record.structuredContent !== null) {
|
|
750
|
+
const value = record.structuredContent.result;
|
|
751
|
+
if (typeof value === "string")
|
|
752
|
+
return value;
|
|
753
|
+
}
|
|
754
|
+
return "";
|
|
755
|
+
}
|
|
756
|
+
function toolResultContent(result) {
|
|
757
|
+
if (typeof result !== "object" || result === null)
|
|
758
|
+
return [];
|
|
759
|
+
const content = result.content;
|
|
760
|
+
return Array.isArray(content) ? content : [];
|
|
761
|
+
}
|
|
762
|
+
function toolResultAgentsFiles(result) {
|
|
763
|
+
if (typeof result !== "object" || result === null)
|
|
764
|
+
return [];
|
|
765
|
+
const structured = result.structuredContent;
|
|
766
|
+
if (typeof structured !== "object" || structured === null)
|
|
767
|
+
return [];
|
|
768
|
+
const agentsFiles = structured.agentsFiles;
|
|
769
|
+
if (!Array.isArray(agentsFiles))
|
|
770
|
+
return [];
|
|
771
|
+
return agentsFiles.flatMap((entry) => {
|
|
772
|
+
if (typeof entry !== "object" || entry === null)
|
|
773
|
+
return [];
|
|
774
|
+
const path = entry.path;
|
|
775
|
+
const content = entry.content;
|
|
776
|
+
return typeof path === "string" && typeof content === "string" ? [{ path, content }] : [];
|
|
777
|
+
});
|
|
778
|
+
}
|
|
727
779
|
function workspaceActivitySnapshot(workspace) {
|
|
728
780
|
return {
|
|
729
781
|
id: workspace.id,
|
|
@@ -781,7 +833,13 @@ function processActivityOutcome(result) {
|
|
|
781
833
|
}
|
|
782
834
|
return { type: "succeeded" };
|
|
783
835
|
}
|
|
784
|
-
function
|
|
836
|
+
function activityRelationFor(context) {
|
|
837
|
+
return {
|
|
838
|
+
...(context.parentActivityId ? { parentActivityId: context.parentActivityId } : {}),
|
|
839
|
+
...(context.turnId ? { turnId: context.turnId } : {}),
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function runActivityTool(lifecycle, workspace, requestMeta, tool, request, operation, outcome = standardActivityOutcome, relation = {}) {
|
|
785
843
|
return lifecycle.run({
|
|
786
844
|
tool,
|
|
787
845
|
workspace: workspaceActivitySnapshot(workspace),
|
|
@@ -789,12 +847,13 @@ function runActivityTool(lifecycle, workspace, requestMeta, tool, request, opera
|
|
|
789
847
|
request,
|
|
790
848
|
operation,
|
|
791
849
|
outcome,
|
|
850
|
+
...relation,
|
|
792
851
|
});
|
|
793
852
|
}
|
|
794
|
-
function runActivityToolWithHooks(lifecycle, hooks, workspace, requestMeta, request, hookOptions) {
|
|
795
|
-
return runActivityTool(lifecycle, workspace, requestMeta, hookOptions.tool, request, () => runToolWithHooks(hooks, hookOptions));
|
|
853
|
+
function runActivityToolWithHooks(lifecycle, hooks, workspace, requestMeta, request, hookOptions, relation = {}) {
|
|
854
|
+
return runActivityTool(lifecycle, workspace, requestMeta, hookOptions.tool, request, () => runToolWithHooks(hooks, hookOptions), standardActivityOutcome, relation);
|
|
796
855
|
}
|
|
797
|
-
function registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore) {
|
|
856
|
+
function registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, shellRun) {
|
|
798
857
|
if (config.toolMode === "codex") {
|
|
799
858
|
registerAppTool(server, "exec_command", {
|
|
800
859
|
title: "Execute command",
|
|
@@ -837,70 +896,22 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
837
896
|
outputSchema: processOutputSchema(),
|
|
838
897
|
...toolWidgetDescriptorMeta(config, "shell"),
|
|
839
898
|
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
840
|
-
}, async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens }, extra) => {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
command: cmd,
|
|
857
|
-
cwd,
|
|
858
|
-
workspaceRoot: workspace.root,
|
|
859
|
-
tty,
|
|
860
|
-
columns,
|
|
861
|
-
rows,
|
|
862
|
-
yieldTimeMs,
|
|
863
|
-
timeoutMs,
|
|
864
|
-
maxOutputTokens,
|
|
865
|
-
codexCi: true,
|
|
866
|
-
signal: extra.signal,
|
|
867
|
-
audit: activityContext,
|
|
868
|
-
});
|
|
869
|
-
undeliveredProcessId = snapshot.running ? snapshot.processId : undefined;
|
|
870
|
-
logToolCall(config, {
|
|
871
|
-
tool: "exec_command",
|
|
872
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
873
|
-
workingDirectory: workingDirectory ?? ".",
|
|
874
|
-
command: cmd,
|
|
875
|
-
commandLength: cmd.length,
|
|
876
|
-
exitCode: snapshot.exitCode,
|
|
877
|
-
running: snapshot.running,
|
|
878
|
-
processId: snapshot.processId,
|
|
879
|
-
success: snapshot.running || snapshot.exitCode === 0,
|
|
880
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
881
|
-
});
|
|
882
|
-
return processToolResponse("exec_command", workspaceId, snapshot, {
|
|
883
|
-
command: cmd,
|
|
884
|
-
workingDirectory: workingDirectory ?? ".",
|
|
885
|
-
running: snapshot.running,
|
|
886
|
-
exitCode: snapshot.exitCode,
|
|
887
|
-
wallTimeMs: snapshot.wallTimeMs,
|
|
888
|
-
});
|
|
889
|
-
},
|
|
890
|
-
});
|
|
891
|
-
extra.signal.throwIfAborted();
|
|
892
|
-
return result;
|
|
893
|
-
}
|
|
894
|
-
catch (error) {
|
|
895
|
-
if (undeliveredProcessId !== undefined) {
|
|
896
|
-
processSessions.discardUndelivered(workspaceId, undeliveredProcessId);
|
|
897
|
-
}
|
|
898
|
-
throw error;
|
|
899
|
-
}
|
|
900
|
-
}, processActivityOutcome);
|
|
901
|
-
markReturnedOutput(bashOutputStore, activityResult);
|
|
902
|
-
return activityResult;
|
|
903
|
-
});
|
|
899
|
+
}, async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens }, extra) => shellRun({
|
|
900
|
+
workspaceId,
|
|
901
|
+
command: cmd,
|
|
902
|
+
surface: "exec_command",
|
|
903
|
+
tty,
|
|
904
|
+
columns,
|
|
905
|
+
rows,
|
|
906
|
+
workingDirectory,
|
|
907
|
+
yieldTimeMs,
|
|
908
|
+
timeoutMs,
|
|
909
|
+
maxOutputTokens,
|
|
910
|
+
}, {
|
|
911
|
+
requestMeta: extra._meta,
|
|
912
|
+
signal: extra.signal,
|
|
913
|
+
sessionId: extra.sessionId,
|
|
914
|
+
}));
|
|
904
915
|
}
|
|
905
916
|
if (config.toolMode !== "codex")
|
|
906
917
|
return;
|
|
@@ -1001,8 +1012,27 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1001
1012
|
const incomingArtifactRegistry = new IncomingArtifactAdapterRegistry(incomingArtifactAdapters);
|
|
1002
1013
|
const artifactDownloadAvailable = config.artifactsEnabled && isArtifactDownloadSupportedPlatform();
|
|
1003
1014
|
const reviewChangesAvailable = config.widgets === "changes";
|
|
1015
|
+
let batchExecutor;
|
|
1016
|
+
const batchExecuteAvailable = config.toolMode !== "codex";
|
|
1004
1017
|
const capabilityRegistry = createCapabilityRegistry({
|
|
1005
1018
|
inspectHooks: (workspaceRoot) => checkHookConfiguration(workspaceRoot, config.hooks),
|
|
1019
|
+
batchExecute: {
|
|
1020
|
+
available: batchExecuteAvailable,
|
|
1021
|
+
unavailableReason: batchExecuteAvailable
|
|
1022
|
+
? undefined
|
|
1023
|
+
: "batch.execute is unavailable in Codex tool mode because v0.5.5 core batch tasks use the regular Read/Write/Edit/Bash operation surface.",
|
|
1024
|
+
run: async (input, context, options) => {
|
|
1025
|
+
if (!batchExecutor)
|
|
1026
|
+
throw new Error("Batch executor is not initialized.");
|
|
1027
|
+
return {
|
|
1028
|
+
value: await batchExecutor.run(context.workspaceId, input, {
|
|
1029
|
+
requestMeta: options.requestMeta,
|
|
1030
|
+
signal: options.signal,
|
|
1031
|
+
sessionId: options.sessionId,
|
|
1032
|
+
}),
|
|
1033
|
+
};
|
|
1034
|
+
},
|
|
1035
|
+
},
|
|
1006
1036
|
codeIntelligence: {
|
|
1007
1037
|
available: true,
|
|
1008
1038
|
run: async (input, context, options) => {
|
|
@@ -1074,6 +1104,510 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1074
1104
|
},
|
|
1075
1105
|
},
|
|
1076
1106
|
});
|
|
1107
|
+
const coreOperations = createCoreOperationExecutor({
|
|
1108
|
+
read: async (input, context) => {
|
|
1109
|
+
const { workspaceId, ...readInput } = input;
|
|
1110
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1111
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, context.requestMeta, input, {
|
|
1112
|
+
signal: context.signal,
|
|
1113
|
+
tool: toolNames.read,
|
|
1114
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1115
|
+
payload: { path: readInput.path, offset: readInput.offset, limit: readInput.limit },
|
|
1116
|
+
isFailure: toolResultIsError,
|
|
1117
|
+
operation: async () => {
|
|
1118
|
+
const startedAt = performance.now();
|
|
1119
|
+
const readPath = workspaces.resolveReadPath(workspace, readInput.path);
|
|
1120
|
+
const discoveredInstructions = (await workspaces.discoverPathInstructions(workspace, readPath.absolutePath)).filter((file) => file.path !== readPath.absolutePath);
|
|
1121
|
+
const response = await readFileTool({ ...readInput, path: readPath.absolutePath }, {
|
|
1122
|
+
cwd: workspace.root,
|
|
1123
|
+
root: workspace.root,
|
|
1124
|
+
readRoots: readPath.readRoots,
|
|
1125
|
+
});
|
|
1126
|
+
if (response.isError) {
|
|
1127
|
+
logFailedToolResponse(config, {
|
|
1128
|
+
tool: toolNames.read,
|
|
1129
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1130
|
+
path: readInput.path,
|
|
1131
|
+
}, response.content, startedAt);
|
|
1132
|
+
return response;
|
|
1133
|
+
}
|
|
1134
|
+
workspaces.markReadPathLoaded(workspace, readPath);
|
|
1135
|
+
const discoveredInstructionContent = discoveredInstructions.length > 0
|
|
1136
|
+
? textBlock(formatDiscoveredWorkspaceInstructions(discoveredInstructions, workspace.root))
|
|
1137
|
+
: undefined;
|
|
1138
|
+
const content = discoveredInstructionContent
|
|
1139
|
+
? [discoveredInstructionContent, ...response.content]
|
|
1140
|
+
: response.content;
|
|
1141
|
+
const summary = {
|
|
1142
|
+
...textSummary(response.content),
|
|
1143
|
+
offset: readInput.offset ?? 1,
|
|
1144
|
+
limited: readInput.limit !== undefined,
|
|
1145
|
+
};
|
|
1146
|
+
logToolCall(config, {
|
|
1147
|
+
tool: toolNames.read,
|
|
1148
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1149
|
+
path: readInput.path,
|
|
1150
|
+
success: true,
|
|
1151
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1152
|
+
});
|
|
1153
|
+
return {
|
|
1154
|
+
...response,
|
|
1155
|
+
content,
|
|
1156
|
+
_meta: {
|
|
1157
|
+
tool: toolNames.read,
|
|
1158
|
+
card: {
|
|
1159
|
+
workspaceId,
|
|
1160
|
+
path: readInput.path,
|
|
1161
|
+
summary,
|
|
1162
|
+
payload: { content: response.content },
|
|
1163
|
+
},
|
|
1164
|
+
},
|
|
1165
|
+
structuredContent: {
|
|
1166
|
+
result: contentText(content),
|
|
1167
|
+
...(discoveredInstructions.length > 0
|
|
1168
|
+
? {
|
|
1169
|
+
agentsFiles: discoveredInstructions.map((file) => ({
|
|
1170
|
+
path: formatAgentsPath(file.path, workspace.root),
|
|
1171
|
+
content: file.content,
|
|
1172
|
+
})),
|
|
1173
|
+
}
|
|
1174
|
+
: {}),
|
|
1175
|
+
},
|
|
1176
|
+
};
|
|
1177
|
+
},
|
|
1178
|
+
}, activityRelationFor(context));
|
|
1179
|
+
},
|
|
1180
|
+
write: async (input, context) => {
|
|
1181
|
+
const { workspaceId, ...writeInput } = input;
|
|
1182
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1183
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, context.requestMeta, input, {
|
|
1184
|
+
signal: context.signal,
|
|
1185
|
+
tool: toolNames.write,
|
|
1186
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1187
|
+
payload: { path: writeInput.path },
|
|
1188
|
+
isFailure: toolResultIsError,
|
|
1189
|
+
changedPaths: (result) => toolResultIsError(result) ? [] : [writeInput.path],
|
|
1190
|
+
operation: async () => {
|
|
1191
|
+
const startedAt = performance.now();
|
|
1192
|
+
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [writeInput.path]);
|
|
1193
|
+
const response = await writeFileTool(writeInput, {
|
|
1194
|
+
cwd: workspace.root,
|
|
1195
|
+
root: workspace.root,
|
|
1196
|
+
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1197
|
+
});
|
|
1198
|
+
if (response.isError) {
|
|
1199
|
+
logFailedToolResponse(config, {
|
|
1200
|
+
tool: toolNames.write,
|
|
1201
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1202
|
+
path: writeInput.path,
|
|
1203
|
+
}, response.content, startedAt);
|
|
1204
|
+
return response;
|
|
1205
|
+
}
|
|
1206
|
+
const patch = newFilePatch(writeInput.path, writeInput.content);
|
|
1207
|
+
const stats = countDiffStats(patch);
|
|
1208
|
+
const summary = {
|
|
1209
|
+
...stats,
|
|
1210
|
+
lines: contentLineCount(writeInput.content),
|
|
1211
|
+
characters: writeInput.content.length,
|
|
1212
|
+
};
|
|
1213
|
+
logToolCall(config, {
|
|
1214
|
+
tool: toolNames.write,
|
|
1215
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1216
|
+
path: writeInput.path,
|
|
1217
|
+
success: true,
|
|
1218
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1219
|
+
});
|
|
1220
|
+
return {
|
|
1221
|
+
...response,
|
|
1222
|
+
_meta: {
|
|
1223
|
+
tool: toolNames.write,
|
|
1224
|
+
card: {
|
|
1225
|
+
workspaceId,
|
|
1226
|
+
path: writeInput.path,
|
|
1227
|
+
summary,
|
|
1228
|
+
payload: {
|
|
1229
|
+
content: response.content,
|
|
1230
|
+
patch,
|
|
1231
|
+
},
|
|
1232
|
+
},
|
|
1233
|
+
},
|
|
1234
|
+
structuredContent: {
|
|
1235
|
+
result: contentText(response.content),
|
|
1236
|
+
},
|
|
1237
|
+
};
|
|
1238
|
+
},
|
|
1239
|
+
}, activityRelationFor(context));
|
|
1240
|
+
},
|
|
1241
|
+
edit: async (input, context) => {
|
|
1242
|
+
const { workspaceId, ...editInput } = input;
|
|
1243
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1244
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, context.requestMeta, input, {
|
|
1245
|
+
signal: context.signal,
|
|
1246
|
+
tool: toolNames.edit,
|
|
1247
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1248
|
+
payload: { path: editInput.path, editCount: editInput.edits.length },
|
|
1249
|
+
isFailure: toolResultIsError,
|
|
1250
|
+
changedPaths: (result) => toolResultIsError(result) ? [] : [editInput.path],
|
|
1251
|
+
operation: async () => {
|
|
1252
|
+
const startedAt = performance.now();
|
|
1253
|
+
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [editInput.path]);
|
|
1254
|
+
const response = await editFileTool(editInput, {
|
|
1255
|
+
cwd: workspace.root,
|
|
1256
|
+
root: workspace.root,
|
|
1257
|
+
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1258
|
+
});
|
|
1259
|
+
if (response.isError) {
|
|
1260
|
+
logFailedToolResponse(config, {
|
|
1261
|
+
tool: toolNames.edit,
|
|
1262
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1263
|
+
path: editInput.path,
|
|
1264
|
+
}, response.content, startedAt);
|
|
1265
|
+
return response;
|
|
1266
|
+
}
|
|
1267
|
+
const stats = countDiffStats(response.details?.patch ?? response.details?.diff);
|
|
1268
|
+
const summary = {
|
|
1269
|
+
...stats,
|
|
1270
|
+
editCount: editInput.edits.length,
|
|
1271
|
+
};
|
|
1272
|
+
const editResultText = `Edited ${editInput.path} (+${stats.additions} -${stats.removals}).`;
|
|
1273
|
+
const editContent = [textBlock(editResultText)];
|
|
1274
|
+
logToolCall(config, {
|
|
1275
|
+
tool: toolNames.edit,
|
|
1276
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1277
|
+
path: editInput.path,
|
|
1278
|
+
success: true,
|
|
1279
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1280
|
+
});
|
|
1281
|
+
return {
|
|
1282
|
+
content: editContent,
|
|
1283
|
+
_meta: {
|
|
1284
|
+
tool: toolNames.edit,
|
|
1285
|
+
card: {
|
|
1286
|
+
workspaceId,
|
|
1287
|
+
path: editInput.path,
|
|
1288
|
+
summary,
|
|
1289
|
+
payload: {
|
|
1290
|
+
diff: response.details?.diff,
|
|
1291
|
+
patch: response.details?.patch,
|
|
1292
|
+
},
|
|
1293
|
+
},
|
|
1294
|
+
},
|
|
1295
|
+
structuredContent: {
|
|
1296
|
+
status: "applied",
|
|
1297
|
+
result: contentText(editContent),
|
|
1298
|
+
},
|
|
1299
|
+
};
|
|
1300
|
+
},
|
|
1301
|
+
}, activityRelationFor(context));
|
|
1302
|
+
},
|
|
1303
|
+
rename: async (input, context) => {
|
|
1304
|
+
const { workspaceId, path, newPath } = input;
|
|
1305
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1306
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, context.requestMeta, input, {
|
|
1307
|
+
signal: context.signal,
|
|
1308
|
+
tool: toolNames.rename,
|
|
1309
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1310
|
+
payload: { path, newPath, paths: [path, newPath] },
|
|
1311
|
+
changedPaths: () => [path, newPath],
|
|
1312
|
+
operation: async () => {
|
|
1313
|
+
const startedAt = performance.now();
|
|
1314
|
+
try {
|
|
1315
|
+
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [path, newPath]);
|
|
1316
|
+
await renamePath({ path, newPath }, {
|
|
1317
|
+
cwd: workspace.root,
|
|
1318
|
+
allowedRoots: workspaces.fileToolRoots(workspace),
|
|
1319
|
+
});
|
|
1320
|
+
const result = `Renamed ${path} to ${newPath}.`;
|
|
1321
|
+
const content = [textBlock(result)];
|
|
1322
|
+
logToolCall(config, {
|
|
1323
|
+
tool: toolNames.rename,
|
|
1324
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1325
|
+
path: `${path} -> ${newPath}`,
|
|
1326
|
+
success: true,
|
|
1327
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1328
|
+
});
|
|
1329
|
+
return {
|
|
1330
|
+
content,
|
|
1331
|
+
_meta: {
|
|
1332
|
+
tool: toolNames.rename,
|
|
1333
|
+
card: {
|
|
1334
|
+
workspaceId,
|
|
1335
|
+
path: newPath,
|
|
1336
|
+
summary: { previousPath: path },
|
|
1337
|
+
payload: { content },
|
|
1338
|
+
},
|
|
1339
|
+
},
|
|
1340
|
+
structuredContent: {
|
|
1341
|
+
result,
|
|
1342
|
+
status: "renamed",
|
|
1343
|
+
path,
|
|
1344
|
+
newPath,
|
|
1345
|
+
},
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
catch (error) {
|
|
1349
|
+
logToolCall(config, {
|
|
1350
|
+
tool: toolNames.rename,
|
|
1351
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1352
|
+
path: `${path} -> ${newPath}`,
|
|
1353
|
+
success: false,
|
|
1354
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1355
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1356
|
+
});
|
|
1357
|
+
throw error;
|
|
1358
|
+
}
|
|
1359
|
+
},
|
|
1360
|
+
}, activityRelationFor(context));
|
|
1361
|
+
},
|
|
1362
|
+
delete: async (input, context) => {
|
|
1363
|
+
const { workspaceId, path, recursive } = input;
|
|
1364
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1365
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, context.requestMeta, input, {
|
|
1366
|
+
signal: context.signal,
|
|
1367
|
+
tool: toolNames.delete,
|
|
1368
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1369
|
+
payload: { path, recursive: recursive ?? false },
|
|
1370
|
+
changedPaths: () => [path],
|
|
1371
|
+
operation: async () => {
|
|
1372
|
+
const startedAt = performance.now();
|
|
1373
|
+
try {
|
|
1374
|
+
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [path]);
|
|
1375
|
+
const deleted = await deletePath({ path, recursive }, {
|
|
1376
|
+
cwd: workspace.root,
|
|
1377
|
+
allowedRoots: workspaces.fileToolRoots(workspace),
|
|
1378
|
+
});
|
|
1379
|
+
const result = `Deleted ${path}${deleted.recursive ? " recursively" : ""}.`;
|
|
1380
|
+
const content = [textBlock(result)];
|
|
1381
|
+
logToolCall(config, {
|
|
1382
|
+
tool: toolNames.delete,
|
|
1383
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1384
|
+
path,
|
|
1385
|
+
success: true,
|
|
1386
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1387
|
+
});
|
|
1388
|
+
return {
|
|
1389
|
+
content,
|
|
1390
|
+
_meta: {
|
|
1391
|
+
tool: toolNames.delete,
|
|
1392
|
+
card: {
|
|
1393
|
+
workspaceId,
|
|
1394
|
+
path,
|
|
1395
|
+
summary: { recursive: deleted.recursive },
|
|
1396
|
+
payload: { content },
|
|
1397
|
+
},
|
|
1398
|
+
},
|
|
1399
|
+
structuredContent: {
|
|
1400
|
+
result,
|
|
1401
|
+
status: "deleted",
|
|
1402
|
+
path,
|
|
1403
|
+
recursive: deleted.recursive,
|
|
1404
|
+
},
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
catch (error) {
|
|
1408
|
+
logToolCall(config, {
|
|
1409
|
+
tool: toolNames.delete,
|
|
1410
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1411
|
+
path,
|
|
1412
|
+
success: false,
|
|
1413
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1414
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1415
|
+
});
|
|
1416
|
+
throw error;
|
|
1417
|
+
}
|
|
1418
|
+
},
|
|
1419
|
+
}, activityRelationFor(context));
|
|
1420
|
+
},
|
|
1421
|
+
shellRun: async (input, context) => {
|
|
1422
|
+
const { workspaceId, command, surface, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens, } = input;
|
|
1423
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1424
|
+
const activityRequest = surface === "exec_command"
|
|
1425
|
+
? {
|
|
1426
|
+
workspaceId,
|
|
1427
|
+
cmd: command,
|
|
1428
|
+
tty,
|
|
1429
|
+
columns,
|
|
1430
|
+
rows,
|
|
1431
|
+
workingDirectory,
|
|
1432
|
+
yieldTimeMs,
|
|
1433
|
+
timeoutMs,
|
|
1434
|
+
maxOutputTokens,
|
|
1435
|
+
}
|
|
1436
|
+
: {
|
|
1437
|
+
workspaceId,
|
|
1438
|
+
action: "run",
|
|
1439
|
+
command,
|
|
1440
|
+
tty,
|
|
1441
|
+
columns,
|
|
1442
|
+
rows,
|
|
1443
|
+
workingDirectory,
|
|
1444
|
+
yieldTimeMs,
|
|
1445
|
+
timeoutMs,
|
|
1446
|
+
maxOutputTokens,
|
|
1447
|
+
};
|
|
1448
|
+
let undeliveredProcessId;
|
|
1449
|
+
const activityResult = await runActivityTool(activityLifecycle, workspace, context.requestMeta, surface, activityRequest, async (activityContext) => {
|
|
1450
|
+
try {
|
|
1451
|
+
const result = await runToolWithHooks(hooks, {
|
|
1452
|
+
signal: context.signal,
|
|
1453
|
+
tool: surface,
|
|
1454
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1455
|
+
payload: surface === "exec_command"
|
|
1456
|
+
? { command, workingDirectory: workingDirectory ?? "." }
|
|
1457
|
+
: { action: "run", command, workingDirectory: workingDirectory ?? "." },
|
|
1458
|
+
...(surface === "bash" ? { isFailure: toolResultIsError } : {}),
|
|
1459
|
+
operation: async () => {
|
|
1460
|
+
const startedAt = performance.now();
|
|
1461
|
+
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
1462
|
+
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [cwd]);
|
|
1463
|
+
const snapshot = await processSessions.start({
|
|
1464
|
+
workspaceId,
|
|
1465
|
+
command,
|
|
1466
|
+
cwd,
|
|
1467
|
+
workspaceRoot: workspace.root,
|
|
1468
|
+
tty,
|
|
1469
|
+
columns,
|
|
1470
|
+
rows,
|
|
1471
|
+
yieldTimeMs,
|
|
1472
|
+
timeoutMs,
|
|
1473
|
+
maxOutputTokens,
|
|
1474
|
+
...(surface === "exec_command" ? { codexCi: true } : {}),
|
|
1475
|
+
signal: context.signal,
|
|
1476
|
+
audit: activityContext,
|
|
1477
|
+
});
|
|
1478
|
+
undeliveredProcessId = snapshot.running ? snapshot.processId : undefined;
|
|
1479
|
+
logToolCall(config, {
|
|
1480
|
+
tool: surface,
|
|
1481
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1482
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1483
|
+
command,
|
|
1484
|
+
commandLength: command.length,
|
|
1485
|
+
exitCode: snapshot.exitCode,
|
|
1486
|
+
running: snapshot.running,
|
|
1487
|
+
processId: snapshot.processId,
|
|
1488
|
+
success: surface === "exec_command"
|
|
1489
|
+
? snapshot.running || snapshot.exitCode === 0
|
|
1490
|
+
: snapshot.running || (snapshot.exitCode === 0 && !snapshot.signal),
|
|
1491
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1492
|
+
});
|
|
1493
|
+
const response = processToolResponse(surface, workspaceId, snapshot, {
|
|
1494
|
+
...(surface === "bash" ? { action: "run" } : {}),
|
|
1495
|
+
command,
|
|
1496
|
+
workingDirectory: workingDirectory ?? ".",
|
|
1497
|
+
running: snapshot.running,
|
|
1498
|
+
exitCode: snapshot.exitCode,
|
|
1499
|
+
wallTimeMs: snapshot.wallTimeMs,
|
|
1500
|
+
});
|
|
1501
|
+
return surface === "bash" && !snapshot.running && (snapshot.signal || snapshot.exitCode !== 0)
|
|
1502
|
+
? { ...response, isError: true }
|
|
1503
|
+
: response;
|
|
1504
|
+
},
|
|
1505
|
+
});
|
|
1506
|
+
context.signal?.throwIfAborted();
|
|
1507
|
+
return result;
|
|
1508
|
+
}
|
|
1509
|
+
catch (error) {
|
|
1510
|
+
if (undeliveredProcessId !== undefined) {
|
|
1511
|
+
processSessions.discardUndelivered(workspaceId, undeliveredProcessId);
|
|
1512
|
+
}
|
|
1513
|
+
throw error;
|
|
1514
|
+
}
|
|
1515
|
+
}, processActivityOutcome, activityRelationFor(context));
|
|
1516
|
+
markReturnedOutput(bashOutputStore, activityResult);
|
|
1517
|
+
return activityResult;
|
|
1518
|
+
},
|
|
1519
|
+
capabilityRun: async (input, context) => {
|
|
1520
|
+
const { workspaceId, name, arguments: capabilityArguments, file } = input;
|
|
1521
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1522
|
+
let changedPaths = [];
|
|
1523
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, context.requestMeta, { workspaceId, name, action: "run", arguments: capabilityArguments, file }, {
|
|
1524
|
+
signal: context.signal,
|
|
1525
|
+
tool: toolNames.capability,
|
|
1526
|
+
invocation: workspaceHookInvocation(workspace),
|
|
1527
|
+
payload: { name, action: "run" },
|
|
1528
|
+
isFailure: toolResultIsError,
|
|
1529
|
+
changedPaths: () => changedPaths,
|
|
1530
|
+
operation: async () => {
|
|
1531
|
+
const startedAt = performance.now();
|
|
1532
|
+
try {
|
|
1533
|
+
const execution = await capabilityRegistry.run(name, capabilityArguments ?? {}, capabilityContextFor(workspace), {
|
|
1534
|
+
nativeFile: file,
|
|
1535
|
+
signal: context.signal,
|
|
1536
|
+
requestMeta: context.requestMeta,
|
|
1537
|
+
sessionId: context.sessionId,
|
|
1538
|
+
batch: context.batch,
|
|
1539
|
+
});
|
|
1540
|
+
changedPaths = execution.changedPaths ?? [];
|
|
1541
|
+
const result = {
|
|
1542
|
+
content: [textBlock(`Capability ${name} completed.\n${JSON.stringify(execution.value, null, 2)}`)],
|
|
1543
|
+
...(execution.card
|
|
1544
|
+
? {
|
|
1545
|
+
_meta: {
|
|
1546
|
+
tool: toolNames.capability,
|
|
1547
|
+
card: {
|
|
1548
|
+
workspaceId,
|
|
1549
|
+
capabilityName: name,
|
|
1550
|
+
summary: execution.card.summary ?? {},
|
|
1551
|
+
files: execution.card.files,
|
|
1552
|
+
payload: execution.card.payload ?? {},
|
|
1553
|
+
},
|
|
1554
|
+
},
|
|
1555
|
+
}
|
|
1556
|
+
: {}),
|
|
1557
|
+
structuredContent: { name, action: "run", result: execution.value },
|
|
1558
|
+
};
|
|
1559
|
+
logToolCall(config, {
|
|
1560
|
+
tool: toolNames.capability,
|
|
1561
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1562
|
+
capability: name,
|
|
1563
|
+
action: "run",
|
|
1564
|
+
success: true,
|
|
1565
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
1566
|
+
});
|
|
1567
|
+
return result;
|
|
1568
|
+
}
|
|
1569
|
+
catch (error) {
|
|
1570
|
+
const capabilityError = error instanceof CapabilityError
|
|
1571
|
+
? error
|
|
1572
|
+
: new CapabilityError("execution_failed", error instanceof Error ? error.message : String(error));
|
|
1573
|
+
const result = {
|
|
1574
|
+
content: [textBlock(`${capabilityError.code}: ${capabilityError.message}`)],
|
|
1575
|
+
structuredContent: {
|
|
1576
|
+
name,
|
|
1577
|
+
action: "run",
|
|
1578
|
+
error: { code: capabilityError.code, message: capabilityError.message },
|
|
1579
|
+
},
|
|
1580
|
+
isError: true,
|
|
1581
|
+
};
|
|
1582
|
+
logFailedToolResponse(config, {
|
|
1583
|
+
tool: toolNames.capability,
|
|
1584
|
+
...workspaceLogContext(workspace, context.sessionId),
|
|
1585
|
+
capability: name,
|
|
1586
|
+
action: "run",
|
|
1587
|
+
}, result.content, startedAt);
|
|
1588
|
+
return result;
|
|
1589
|
+
}
|
|
1590
|
+
},
|
|
1591
|
+
}, activityRelationFor(context));
|
|
1592
|
+
},
|
|
1593
|
+
});
|
|
1594
|
+
batchExecutor = new BatchExecutor({
|
|
1595
|
+
lifecycle: activityLifecycle,
|
|
1596
|
+
workspaces,
|
|
1597
|
+
coreOperations,
|
|
1598
|
+
resultIsError: toolResultIsError,
|
|
1599
|
+
capabilityBatchPolicy: (name) => capabilityRegistry.batchPolicy(name),
|
|
1600
|
+
shellSurface: "bash",
|
|
1601
|
+
});
|
|
1602
|
+
const nativeBulkMutations = new NativeBulkMutationExecutor({
|
|
1603
|
+
lifecycle: activityLifecycle,
|
|
1604
|
+
workspaces,
|
|
1605
|
+
coreOperations,
|
|
1606
|
+
preflightInstructions: (workspace, paths) => assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, paths),
|
|
1607
|
+
resultIsError: toolResultIsError,
|
|
1608
|
+
resultText: toolResultText,
|
|
1609
|
+
resultContent: toolResultContent,
|
|
1610
|
+
});
|
|
1077
1611
|
const server = new McpServer({
|
|
1078
1612
|
name: "forgerelay",
|
|
1079
1613
|
title: "ForgeRelay",
|
|
@@ -1506,60 +2040,82 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1506
2040
|
openWorldHint: true,
|
|
1507
2041
|
},
|
|
1508
2042
|
}, async ({ workspaceId, name, action, arguments: capabilityArguments, file }, extra) => {
|
|
2043
|
+
if (action === "run" && name === "batch.execute") {
|
|
2044
|
+
const workspace = workspaces.getWorkspace(workspaceId);
|
|
2045
|
+
const startedAt = performance.now();
|
|
2046
|
+
try {
|
|
2047
|
+
const execution = await capabilityRegistry.run(name, capabilityArguments ?? {}, capabilityContextFor(workspace), {
|
|
2048
|
+
nativeFile: file,
|
|
2049
|
+
signal: extra.signal,
|
|
2050
|
+
requestMeta: extra._meta,
|
|
2051
|
+
sessionId: extra.sessionId,
|
|
2052
|
+
});
|
|
2053
|
+
const result = {
|
|
2054
|
+
content: [textBlock(`Capability ${name} completed.\n${JSON.stringify(execution.value, null, 2)}`)],
|
|
2055
|
+
structuredContent: { name, action, result: execution.value },
|
|
2056
|
+
};
|
|
2057
|
+
logToolCall(config, {
|
|
2058
|
+
tool: toolNames.capability,
|
|
2059
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
2060
|
+
capability: name,
|
|
2061
|
+
action,
|
|
2062
|
+
success: true,
|
|
2063
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
2064
|
+
});
|
|
2065
|
+
return result;
|
|
2066
|
+
}
|
|
2067
|
+
catch (error) {
|
|
2068
|
+
if (extra.signal.aborted)
|
|
2069
|
+
throw error;
|
|
2070
|
+
const capabilityError = error instanceof CapabilityError
|
|
2071
|
+
? error
|
|
2072
|
+
: new CapabilityError("execution_failed", error instanceof Error ? error.message : String(error));
|
|
2073
|
+
const result = {
|
|
2074
|
+
content: [textBlock(`${capabilityError.code}: ${capabilityError.message}`)],
|
|
2075
|
+
structuredContent: {
|
|
2076
|
+
name,
|
|
2077
|
+
action,
|
|
2078
|
+
error: { code: capabilityError.code, message: capabilityError.message },
|
|
2079
|
+
},
|
|
2080
|
+
isError: true,
|
|
2081
|
+
};
|
|
2082
|
+
logFailedToolResponse(config, {
|
|
2083
|
+
tool: toolNames.capability,
|
|
2084
|
+
...workspaceLogContext(workspace, extra.sessionId),
|
|
2085
|
+
capability: name,
|
|
2086
|
+
action,
|
|
2087
|
+
}, result.content, startedAt);
|
|
2088
|
+
return result;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
if (action === "run") {
|
|
2092
|
+
return coreOperations.capabilityRun({ workspaceId, name, arguments: capabilityArguments, file }, {
|
|
2093
|
+
requestMeta: extra._meta,
|
|
2094
|
+
signal: extra.signal,
|
|
2095
|
+
sessionId: extra.sessionId,
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
1509
2098
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1510
|
-
let changedPaths = [];
|
|
1511
2099
|
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, extra._meta, { workspaceId, name, action, arguments: capabilityArguments, file }, {
|
|
1512
2100
|
signal: extra.signal,
|
|
1513
2101
|
tool: toolNames.capability,
|
|
1514
2102
|
invocation: workspaceHookInvocation(workspace),
|
|
1515
2103
|
payload: { name, action },
|
|
1516
2104
|
isFailure: toolResultIsError,
|
|
1517
|
-
changedPaths: () => changedPaths,
|
|
1518
2105
|
operation: async () => {
|
|
1519
2106
|
const startedAt = performance.now();
|
|
1520
2107
|
try {
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
structuredContent: { name, action, capability },
|
|
1533
|
-
};
|
|
1534
|
-
logToolCall(config, {
|
|
1535
|
-
tool: toolNames.capability,
|
|
1536
|
-
...workspaceLogContext(workspace),
|
|
1537
|
-
capability: name,
|
|
1538
|
-
action,
|
|
1539
|
-
success: true,
|
|
1540
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1541
|
-
});
|
|
1542
|
-
return result;
|
|
1543
|
-
}
|
|
1544
|
-
const execution = await capabilityRegistry.run(name, capabilityArguments ?? {}, capabilityContextFor(workspace), { nativeFile: file, signal: extra.signal });
|
|
1545
|
-
changedPaths = execution.changedPaths ?? [];
|
|
1546
|
-
const result = {
|
|
1547
|
-
content: [textBlock(`Capability ${name} completed.\n${JSON.stringify(execution.value, null, 2)}`)],
|
|
1548
|
-
...(execution.card
|
|
1549
|
-
? {
|
|
1550
|
-
_meta: {
|
|
1551
|
-
tool: toolNames.capability,
|
|
1552
|
-
card: {
|
|
1553
|
-
workspaceId,
|
|
1554
|
-
capabilityName: name,
|
|
1555
|
-
summary: execution.card.summary ?? {},
|
|
1556
|
-
files: execution.card.files,
|
|
1557
|
-
payload: execution.card.payload ?? {},
|
|
1558
|
-
},
|
|
1559
|
-
},
|
|
1560
|
-
}
|
|
1561
|
-
: {}),
|
|
1562
|
-
structuredContent: { name, action, result: execution.value },
|
|
2108
|
+
const capability = capabilityRegistry.describe(name, capabilityContextFor(workspace));
|
|
2109
|
+
const result = {
|
|
2110
|
+
content: [textBlock([
|
|
2111
|
+
`${capability.name}: ${capability.description}`,
|
|
2112
|
+
`Available: ${capability.available}`,
|
|
2113
|
+
`Guide: ${capability.guide.path}`,
|
|
2114
|
+
capability.guide.readBeforeFirstUse
|
|
2115
|
+
? "Read the guide before first use when this contract is unfamiliar."
|
|
2116
|
+
: undefined,
|
|
2117
|
+
].filter(Boolean).join("\n"))],
|
|
2118
|
+
structuredContent: { name, action, capability },
|
|
1563
2119
|
};
|
|
1564
2120
|
logToolCall(config, {
|
|
1565
2121
|
tool: toolNames.capability,
|
|
@@ -1708,9 +2264,16 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1708
2264
|
.describe("Workspace identifier returned by open_workspace."),
|
|
1709
2265
|
path: z
|
|
1710
2266
|
.string()
|
|
2267
|
+
.optional()
|
|
1711
2268
|
.describe(config.skillsEnabled
|
|
1712
|
-
? "
|
|
1713
|
-
: "
|
|
2269
|
+
? "One file path to read, relative to the workspace root or absolute inside the OS temp directory. May also be an advertised skill or capability-guide path from open_workspace, including a ~/... home-relative path. Use exactly one of path or paths."
|
|
2270
|
+
: "One file path to read, relative to the workspace root or absolute inside the OS temp directory. May also be an advertised capability-guide path from open_workspace. Use exactly one of path or paths."),
|
|
2271
|
+
paths: z
|
|
2272
|
+
.array(z.string())
|
|
2273
|
+
.min(1)
|
|
2274
|
+
.max(100)
|
|
2275
|
+
.optional()
|
|
2276
|
+
.describe("Multiple file paths to read in one call. Uses the same offset/limit for every file. Use exactly one of path or paths."),
|
|
1714
2277
|
offset: z
|
|
1715
2278
|
.number()
|
|
1716
2279
|
.int()
|
|
@@ -1726,79 +2289,81 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1726
2289
|
},
|
|
1727
2290
|
outputSchema: resultOutputSchema({
|
|
1728
2291
|
agentsFiles: z.array(workspaceAgentsFileOutputSchema).optional(),
|
|
2292
|
+
results: z.array(z.object({
|
|
2293
|
+
path: z.string(),
|
|
2294
|
+
status: z.enum(["done", "error"]),
|
|
2295
|
+
result: z.string(),
|
|
2296
|
+
})).optional(),
|
|
2297
|
+
files: z.number().int().nonnegative().optional(),
|
|
2298
|
+
failed: z.number().int().nonnegative().optional(),
|
|
1729
2299
|
}),
|
|
1730
2300
|
...toolWidgetDescriptorMeta(config, "read"),
|
|
1731
2301
|
annotations: { readOnlyHint: true },
|
|
1732
|
-
}, async ({ workspaceId,
|
|
2302
|
+
}, async ({ workspaceId, path, paths, offset, limit }, extra) => {
|
|
2303
|
+
if ((path === undefined) === (paths === undefined)) {
|
|
2304
|
+
throw new Error("read requires exactly one of path or paths.");
|
|
2305
|
+
}
|
|
2306
|
+
if (path !== undefined) {
|
|
2307
|
+
return coreOperations.read({ workspaceId, path, offset, limit }, {
|
|
2308
|
+
requestMeta: extra._meta,
|
|
2309
|
+
signal: extra.signal,
|
|
2310
|
+
sessionId: extra.sessionId,
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
1733
2313
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
},
|
|
1788
|
-
structuredContent: {
|
|
1789
|
-
result: contentText(content),
|
|
1790
|
-
...(discoveredInstructions.length > 0
|
|
1791
|
-
? {
|
|
1792
|
-
agentsFiles: discoveredInstructions.map((file) => ({
|
|
1793
|
-
path: formatAgentsPath(file.path, workspace.root),
|
|
1794
|
-
content: file.content,
|
|
1795
|
-
})),
|
|
1796
|
-
}
|
|
1797
|
-
: {}),
|
|
1798
|
-
},
|
|
1799
|
-
};
|
|
1800
|
-
},
|
|
1801
|
-
});
|
|
2314
|
+
let response;
|
|
2315
|
+
await runActivityTool(activityLifecycle, workspace, extra._meta, toolNames.read, { workspaceId, paths, offset, limit }, async (parentContext) => {
|
|
2316
|
+
const execution = await executeBulkRead({
|
|
2317
|
+
paths: paths,
|
|
2318
|
+
signal: extra.signal,
|
|
2319
|
+
run: (childPath) => coreOperations.read({ workspaceId, path: childPath, offset, limit }, {
|
|
2320
|
+
requestMeta: extra._meta,
|
|
2321
|
+
signal: extra.signal,
|
|
2322
|
+
sessionId: extra.sessionId,
|
|
2323
|
+
parentActivityId: parentContext.activityId,
|
|
2324
|
+
turnId: parentContext.turnId,
|
|
2325
|
+
}),
|
|
2326
|
+
isError: toolResultIsError,
|
|
2327
|
+
resultText: toolResultText,
|
|
2328
|
+
});
|
|
2329
|
+
const content = execution.children.flatMap((child) => [
|
|
2330
|
+
textBlock(`--- ${child.path} · ${child.status} ---`),
|
|
2331
|
+
...(child.response
|
|
2332
|
+
? toolResultContent(child.response)
|
|
2333
|
+
: [textBlock(child.result)]),
|
|
2334
|
+
]);
|
|
2335
|
+
const seenAgentPaths = new Set();
|
|
2336
|
+
const agentsFiles = execution.children.flatMap((child) => child.response ? toolResultAgentsFiles(child.response) : []).filter((file) => {
|
|
2337
|
+
if (seenAgentPaths.has(file.path))
|
|
2338
|
+
return false;
|
|
2339
|
+
seenAgentPaths.add(file.path);
|
|
2340
|
+
return true;
|
|
2341
|
+
});
|
|
2342
|
+
response = {
|
|
2343
|
+
content,
|
|
2344
|
+
structuredContent: {
|
|
2345
|
+
result: contentText(content),
|
|
2346
|
+
results: execution.children.map(({ path: childPath, status, result }) => ({
|
|
2347
|
+
path: childPath,
|
|
2348
|
+
status,
|
|
2349
|
+
result,
|
|
2350
|
+
})),
|
|
2351
|
+
files: execution.children.length,
|
|
2352
|
+
failed: execution.failed,
|
|
2353
|
+
...(agentsFiles.length > 0 ? { agentsFiles } : {}),
|
|
2354
|
+
},
|
|
2355
|
+
};
|
|
2356
|
+
return {
|
|
2357
|
+
childCount: execution.children.length,
|
|
2358
|
+
succeeded: execution.succeeded,
|
|
2359
|
+
failed: execution.failed,
|
|
2360
|
+
};
|
|
2361
|
+
}, (summary) => summary.failed > 0
|
|
2362
|
+
? { type: "failed", error: `${summary.failed} of ${summary.childCount} child Reads failed.` }
|
|
2363
|
+
: { type: "succeeded" });
|
|
2364
|
+
if (!response)
|
|
2365
|
+
throw new Error("Bulk Read completed without a response.");
|
|
2366
|
+
return response;
|
|
1802
2367
|
});
|
|
1803
2368
|
if (config.toolMode !== "codex") {
|
|
1804
2369
|
registerAppTool(server, toolNames.write, {
|
|
@@ -1816,66 +2381,11 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1816
2381
|
outputSchema: resultOutputSchema(),
|
|
1817
2382
|
...toolWidgetDescriptorMeta(config, "write"),
|
|
1818
2383
|
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
1819
|
-
}, async ({ workspaceId, ...input }, extra) => {
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1825
|
-
payload: { path: input.path },
|
|
1826
|
-
isFailure: toolResultIsError,
|
|
1827
|
-
changedPaths: (result) => toolResultIsError(result) ? [] : [input.path],
|
|
1828
|
-
operation: async () => {
|
|
1829
|
-
const startedAt = performance.now();
|
|
1830
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [input.path]);
|
|
1831
|
-
const response = await writeFileTool(input, {
|
|
1832
|
-
cwd: workspace.root,
|
|
1833
|
-
root: workspace.root,
|
|
1834
|
-
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1835
|
-
});
|
|
1836
|
-
if (response.isError) {
|
|
1837
|
-
logFailedToolResponse(config, {
|
|
1838
|
-
tool: toolNames.write,
|
|
1839
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1840
|
-
path: input.path,
|
|
1841
|
-
}, response.content, startedAt);
|
|
1842
|
-
return response;
|
|
1843
|
-
}
|
|
1844
|
-
const patch = newFilePatch(input.path, input.content);
|
|
1845
|
-
const stats = countDiffStats(patch);
|
|
1846
|
-
const summary = {
|
|
1847
|
-
...stats,
|
|
1848
|
-
lines: contentLineCount(input.content),
|
|
1849
|
-
characters: input.content.length,
|
|
1850
|
-
};
|
|
1851
|
-
logToolCall(config, {
|
|
1852
|
-
tool: toolNames.write,
|
|
1853
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1854
|
-
path: input.path,
|
|
1855
|
-
success: true,
|
|
1856
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1857
|
-
});
|
|
1858
|
-
return {
|
|
1859
|
-
...response,
|
|
1860
|
-
_meta: {
|
|
1861
|
-
tool: toolNames.write,
|
|
1862
|
-
card: {
|
|
1863
|
-
workspaceId,
|
|
1864
|
-
path: input.path,
|
|
1865
|
-
summary,
|
|
1866
|
-
payload: {
|
|
1867
|
-
content: response.content,
|
|
1868
|
-
patch,
|
|
1869
|
-
},
|
|
1870
|
-
},
|
|
1871
|
-
},
|
|
1872
|
-
structuredContent: {
|
|
1873
|
-
result: contentText(response.content),
|
|
1874
|
-
},
|
|
1875
|
-
};
|
|
1876
|
-
},
|
|
1877
|
-
});
|
|
1878
|
-
});
|
|
2384
|
+
}, async ({ workspaceId, ...input }, extra) => coreOperations.write({ workspaceId, ...input }, {
|
|
2385
|
+
requestMeta: extra._meta,
|
|
2386
|
+
signal: extra.signal,
|
|
2387
|
+
sessionId: extra.sessionId,
|
|
2388
|
+
}));
|
|
1879
2389
|
registerAppTool(server, toolNames.edit, {
|
|
1880
2390
|
title: "Edit file",
|
|
1881
2391
|
description: toolDescriptions.edit,
|
|
@@ -1885,7 +2395,14 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1885
2395
|
.describe("Workspace identifier returned by open_workspace."),
|
|
1886
2396
|
path: z
|
|
1887
2397
|
.string()
|
|
1888
|
-
.
|
|
2398
|
+
.optional()
|
|
2399
|
+
.describe("One file path to edit. Use exactly one of path or paths."),
|
|
2400
|
+
paths: z
|
|
2401
|
+
.array(z.string())
|
|
2402
|
+
.min(1)
|
|
2403
|
+
.max(100)
|
|
2404
|
+
.optional()
|
|
2405
|
+
.describe("Multiple file paths to edit with the same edits. Use exactly one of path or paths."),
|
|
1889
2406
|
edits: z
|
|
1890
2407
|
.array(z.object({
|
|
1891
2408
|
oldText: z
|
|
@@ -1896,69 +2413,34 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1896
2413
|
.min(1),
|
|
1897
2414
|
},
|
|
1898
2415
|
outputSchema: resultOutputSchema({
|
|
1899
|
-
status: z.
|
|
2416
|
+
status: z.enum(["applied", "partial"]),
|
|
2417
|
+
results: z.array(z.object({
|
|
2418
|
+
path: z.string(),
|
|
2419
|
+
status: z.enum(["done", "error", "unexecuted"]),
|
|
2420
|
+
result: z.string().optional(),
|
|
2421
|
+
})).optional(),
|
|
2422
|
+
files: z.number().int().nonnegative().optional(),
|
|
2423
|
+
completed: z.number().int().nonnegative().optional(),
|
|
2424
|
+
failed: z.number().int().nonnegative().optional(),
|
|
2425
|
+
unexecuted: z.number().int().nonnegative().optional(),
|
|
1900
2426
|
}),
|
|
1901
2427
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
1902
2428
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
1903
|
-
}, async ({ workspaceId,
|
|
1904
|
-
|
|
1905
|
-
|
|
2429
|
+
}, async ({ workspaceId, path, paths, edits }, extra) => {
|
|
2430
|
+
if ((path === undefined) === (paths === undefined)) {
|
|
2431
|
+
throw new Error("edit requires exactly one of path or paths.");
|
|
2432
|
+
}
|
|
2433
|
+
if (path !== undefined) {
|
|
2434
|
+
return coreOperations.edit({ workspaceId, path, edits }, {
|
|
2435
|
+
requestMeta: extra._meta,
|
|
2436
|
+
signal: extra.signal,
|
|
2437
|
+
sessionId: extra.sessionId,
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
return nativeBulkMutations.edit({ workspaceId, paths: paths, edits }, {
|
|
2441
|
+
requestMeta: extra._meta,
|
|
1906
2442
|
signal: extra.signal,
|
|
1907
|
-
|
|
1908
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1909
|
-
payload: { path: input.path, editCount: input.edits.length },
|
|
1910
|
-
isFailure: toolResultIsError,
|
|
1911
|
-
changedPaths: (result) => toolResultIsError(result) ? [] : [input.path],
|
|
1912
|
-
operation: async () => {
|
|
1913
|
-
const startedAt = performance.now();
|
|
1914
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [input.path]);
|
|
1915
|
-
const response = await editFileTool(input, {
|
|
1916
|
-
cwd: workspace.root,
|
|
1917
|
-
root: workspace.root,
|
|
1918
|
-
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1919
|
-
});
|
|
1920
|
-
if (response.isError) {
|
|
1921
|
-
logFailedToolResponse(config, {
|
|
1922
|
-
tool: toolNames.edit,
|
|
1923
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1924
|
-
path: input.path,
|
|
1925
|
-
}, response.content, startedAt);
|
|
1926
|
-
return response;
|
|
1927
|
-
}
|
|
1928
|
-
const stats = countDiffStats(response.details?.patch ?? response.details?.diff);
|
|
1929
|
-
const summary = {
|
|
1930
|
-
...stats,
|
|
1931
|
-
editCount: input.edits.length,
|
|
1932
|
-
};
|
|
1933
|
-
const editResultText = `Edited ${input.path} (+${stats.additions} -${stats.removals}).`;
|
|
1934
|
-
const editContent = [textBlock(editResultText)];
|
|
1935
|
-
logToolCall(config, {
|
|
1936
|
-
tool: toolNames.edit,
|
|
1937
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1938
|
-
path: input.path,
|
|
1939
|
-
success: true,
|
|
1940
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1941
|
-
});
|
|
1942
|
-
return {
|
|
1943
|
-
content: editContent,
|
|
1944
|
-
_meta: {
|
|
1945
|
-
tool: toolNames.edit,
|
|
1946
|
-
card: {
|
|
1947
|
-
workspaceId,
|
|
1948
|
-
path: input.path,
|
|
1949
|
-
summary,
|
|
1950
|
-
payload: {
|
|
1951
|
-
diff: response.details?.diff,
|
|
1952
|
-
patch: response.details?.patch,
|
|
1953
|
-
},
|
|
1954
|
-
},
|
|
1955
|
-
},
|
|
1956
|
-
structuredContent: {
|
|
1957
|
-
status: "applied",
|
|
1958
|
-
result: contentText(editContent),
|
|
1959
|
-
},
|
|
1960
|
-
};
|
|
1961
|
-
},
|
|
2443
|
+
sessionId: extra.sessionId,
|
|
1962
2444
|
});
|
|
1963
2445
|
});
|
|
1964
2446
|
}
|
|
@@ -1977,135 +2459,56 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1977
2459
|
}),
|
|
1978
2460
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
1979
2461
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
1980
|
-
}, async ({ workspaceId, path, newPath }, extra) => {
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1986
|
-
payload: { path, newPath, paths: [path, newPath] },
|
|
1987
|
-
changedPaths: () => [path, newPath],
|
|
1988
|
-
operation: async () => {
|
|
1989
|
-
const startedAt = performance.now();
|
|
1990
|
-
try {
|
|
1991
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [path, newPath]);
|
|
1992
|
-
await renamePath({ path, newPath }, {
|
|
1993
|
-
cwd: workspace.root,
|
|
1994
|
-
allowedRoots: workspaces.fileToolRoots(workspace),
|
|
1995
|
-
});
|
|
1996
|
-
const result = `Renamed ${path} to ${newPath}.`;
|
|
1997
|
-
const content = [textBlock(result)];
|
|
1998
|
-
logToolCall(config, {
|
|
1999
|
-
tool: toolNames.rename,
|
|
2000
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
2001
|
-
path: `${path} -> ${newPath}`,
|
|
2002
|
-
success: true,
|
|
2003
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2004
|
-
});
|
|
2005
|
-
return {
|
|
2006
|
-
content,
|
|
2007
|
-
_meta: {
|
|
2008
|
-
tool: toolNames.rename,
|
|
2009
|
-
card: {
|
|
2010
|
-
workspaceId,
|
|
2011
|
-
path: newPath,
|
|
2012
|
-
summary: { previousPath: path },
|
|
2013
|
-
payload: { content },
|
|
2014
|
-
},
|
|
2015
|
-
},
|
|
2016
|
-
structuredContent: {
|
|
2017
|
-
result,
|
|
2018
|
-
status: "renamed",
|
|
2019
|
-
path,
|
|
2020
|
-
newPath,
|
|
2021
|
-
},
|
|
2022
|
-
};
|
|
2023
|
-
}
|
|
2024
|
-
catch (error) {
|
|
2025
|
-
logToolCall(config, {
|
|
2026
|
-
tool: toolNames.rename,
|
|
2027
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
2028
|
-
path: `${path} -> ${newPath}`,
|
|
2029
|
-
success: false,
|
|
2030
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2031
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2032
|
-
});
|
|
2033
|
-
throw error;
|
|
2034
|
-
}
|
|
2035
|
-
},
|
|
2036
|
-
});
|
|
2037
|
-
});
|
|
2462
|
+
}, async ({ workspaceId, path, newPath }, extra) => coreOperations.rename({ workspaceId, path, newPath }, {
|
|
2463
|
+
requestMeta: extra._meta,
|
|
2464
|
+
signal: extra.signal,
|
|
2465
|
+
sessionId: extra.sessionId,
|
|
2466
|
+
}));
|
|
2038
2467
|
registerAppTool(server, toolNames.delete, {
|
|
2039
2468
|
title: "Delete path",
|
|
2040
2469
|
description: toolDescriptions.delete,
|
|
2041
2470
|
inputSchema: {
|
|
2042
2471
|
workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
|
|
2043
|
-
path: z.string().describe("
|
|
2044
|
-
|
|
2472
|
+
path: z.string().optional().describe("One file or directory path to delete. Use exactly one of path or paths."),
|
|
2473
|
+
paths: z
|
|
2474
|
+
.array(z.string())
|
|
2475
|
+
.min(1)
|
|
2476
|
+
.max(100)
|
|
2477
|
+
.optional()
|
|
2478
|
+
.describe("Multiple paths to delete in one call. Use exactly one of path or paths."),
|
|
2479
|
+
recursive: z.boolean().optional().describe("Delete non-empty directory trees. Defaults to false and applies to every bulk target."),
|
|
2045
2480
|
},
|
|
2046
2481
|
outputSchema: resultOutputSchema({
|
|
2047
|
-
status: z.
|
|
2048
|
-
path: z.string(),
|
|
2049
|
-
recursive: z.boolean(),
|
|
2482
|
+
status: z.enum(["deleted", "partial"]),
|
|
2483
|
+
path: z.string().optional(),
|
|
2484
|
+
recursive: z.boolean().optional(),
|
|
2485
|
+
results: z.array(z.object({
|
|
2486
|
+
path: z.string(),
|
|
2487
|
+
status: z.enum(["done", "error", "unexecuted"]),
|
|
2488
|
+
result: z.string().optional(),
|
|
2489
|
+
})).optional(),
|
|
2490
|
+
paths: z.number().int().nonnegative().optional(),
|
|
2491
|
+
completed: z.number().int().nonnegative().optional(),
|
|
2492
|
+
failed: z.number().int().nonnegative().optional(),
|
|
2493
|
+
unexecuted: z.number().int().nonnegative().optional(),
|
|
2050
2494
|
}),
|
|
2051
2495
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
2052
2496
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
2053
|
-
}, async ({ workspaceId, path, recursive }, extra) => {
|
|
2054
|
-
|
|
2055
|
-
|
|
2497
|
+
}, async ({ workspaceId, path, paths, recursive }, extra) => {
|
|
2498
|
+
if ((path === undefined) === (paths === undefined)) {
|
|
2499
|
+
throw new Error("delete requires exactly one of path or paths.");
|
|
2500
|
+
}
|
|
2501
|
+
if (path !== undefined) {
|
|
2502
|
+
return coreOperations.delete({ workspaceId, path, recursive }, {
|
|
2503
|
+
requestMeta: extra._meta,
|
|
2504
|
+
signal: extra.signal,
|
|
2505
|
+
sessionId: extra.sessionId,
|
|
2506
|
+
});
|
|
2507
|
+
}
|
|
2508
|
+
return nativeBulkMutations.delete({ workspaceId, paths: paths, recursive }, {
|
|
2509
|
+
requestMeta: extra._meta,
|
|
2056
2510
|
signal: extra.signal,
|
|
2057
|
-
|
|
2058
|
-
invocation: workspaceHookInvocation(workspace),
|
|
2059
|
-
payload: { path, recursive: recursive ?? false },
|
|
2060
|
-
changedPaths: () => [path],
|
|
2061
|
-
operation: async () => {
|
|
2062
|
-
const startedAt = performance.now();
|
|
2063
|
-
try {
|
|
2064
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [path]);
|
|
2065
|
-
const deleted = await deletePath({ path, recursive }, {
|
|
2066
|
-
cwd: workspace.root,
|
|
2067
|
-
allowedRoots: workspaces.fileToolRoots(workspace),
|
|
2068
|
-
});
|
|
2069
|
-
const result = `Deleted ${path}${deleted.recursive ? " recursively" : ""}.`;
|
|
2070
|
-
const content = [textBlock(result)];
|
|
2071
|
-
logToolCall(config, {
|
|
2072
|
-
tool: toolNames.delete,
|
|
2073
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
2074
|
-
path,
|
|
2075
|
-
success: true,
|
|
2076
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2077
|
-
});
|
|
2078
|
-
return {
|
|
2079
|
-
content,
|
|
2080
|
-
_meta: {
|
|
2081
|
-
tool: toolNames.delete,
|
|
2082
|
-
card: {
|
|
2083
|
-
workspaceId,
|
|
2084
|
-
path,
|
|
2085
|
-
summary: { recursive: deleted.recursive },
|
|
2086
|
-
payload: { content },
|
|
2087
|
-
},
|
|
2088
|
-
},
|
|
2089
|
-
structuredContent: {
|
|
2090
|
-
result,
|
|
2091
|
-
status: "deleted",
|
|
2092
|
-
path,
|
|
2093
|
-
recursive: deleted.recursive,
|
|
2094
|
-
},
|
|
2095
|
-
};
|
|
2096
|
-
}
|
|
2097
|
-
catch (error) {
|
|
2098
|
-
logToolCall(config, {
|
|
2099
|
-
tool: toolNames.delete,
|
|
2100
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
2101
|
-
path,
|
|
2102
|
-
success: false,
|
|
2103
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2104
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2105
|
-
});
|
|
2106
|
-
throw error;
|
|
2107
|
-
}
|
|
2108
|
-
},
|
|
2511
|
+
sessionId: extra.sessionId,
|
|
2109
2512
|
});
|
|
2110
2513
|
});
|
|
2111
2514
|
if (config.toolMode === "codex") {
|
|
@@ -2272,11 +2675,10 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2272
2675
|
if (processId !== undefined || outputId !== undefined || input !== undefined || interrupt !== undefined) {
|
|
2273
2676
|
throw new Error("bash action=run does not accept processId, outputId, input, or interrupt.");
|
|
2274
2677
|
}
|
|
2275
|
-
|
|
2276
|
-
const activityResult = await runActivityTool(activityLifecycle, workspace, extra._meta, toolNames.shell, {
|
|
2678
|
+
return coreOperations.shellRun({
|
|
2277
2679
|
workspaceId,
|
|
2278
|
-
action,
|
|
2279
2680
|
command,
|
|
2681
|
+
surface: "bash",
|
|
2280
2682
|
tty,
|
|
2281
2683
|
columns,
|
|
2282
2684
|
rows,
|
|
@@ -2284,74 +2686,11 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2284
2686
|
yieldTimeMs,
|
|
2285
2687
|
timeoutMs,
|
|
2286
2688
|
maxOutputTokens,
|
|
2287
|
-
},
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
invocation: workspaceHookInvocation(workspace),
|
|
2293
|
-
payload: {
|
|
2294
|
-
action,
|
|
2295
|
-
command,
|
|
2296
|
-
workingDirectory: workingDirectory ?? ".",
|
|
2297
|
-
},
|
|
2298
|
-
isFailure: toolResultIsError,
|
|
2299
|
-
operation: async () => {
|
|
2300
|
-
const startedAt = performance.now();
|
|
2301
|
-
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
2302
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [cwd]);
|
|
2303
|
-
const snapshot = await processSessions.start({
|
|
2304
|
-
workspaceId,
|
|
2305
|
-
command,
|
|
2306
|
-
cwd,
|
|
2307
|
-
workspaceRoot: workspace.root,
|
|
2308
|
-
tty,
|
|
2309
|
-
columns,
|
|
2310
|
-
rows,
|
|
2311
|
-
yieldTimeMs,
|
|
2312
|
-
timeoutMs,
|
|
2313
|
-
maxOutputTokens,
|
|
2314
|
-
signal: extra.signal,
|
|
2315
|
-
audit: activityContext,
|
|
2316
|
-
});
|
|
2317
|
-
undeliveredProcessId = snapshot.running ? snapshot.processId : undefined;
|
|
2318
|
-
logToolCall(config, {
|
|
2319
|
-
tool: toolNames.shell,
|
|
2320
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
2321
|
-
workingDirectory: workingDirectory ?? ".",
|
|
2322
|
-
command,
|
|
2323
|
-
commandLength: command.length,
|
|
2324
|
-
exitCode: snapshot.exitCode,
|
|
2325
|
-
running: snapshot.running,
|
|
2326
|
-
processId: snapshot.processId,
|
|
2327
|
-
success: snapshot.running || (snapshot.exitCode === 0 && !snapshot.signal),
|
|
2328
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2329
|
-
});
|
|
2330
|
-
const response = processToolResponse(toolNames.shell, workspaceId, snapshot, {
|
|
2331
|
-
action,
|
|
2332
|
-
command,
|
|
2333
|
-
workingDirectory: workingDirectory ?? ".",
|
|
2334
|
-
running: snapshot.running,
|
|
2335
|
-
exitCode: snapshot.exitCode,
|
|
2336
|
-
wallTimeMs: snapshot.wallTimeMs,
|
|
2337
|
-
});
|
|
2338
|
-
return !snapshot.running && (snapshot.signal || snapshot.exitCode !== 0)
|
|
2339
|
-
? { ...response, isError: true }
|
|
2340
|
-
: response;
|
|
2341
|
-
},
|
|
2342
|
-
});
|
|
2343
|
-
extra.signal.throwIfAborted();
|
|
2344
|
-
return result;
|
|
2345
|
-
}
|
|
2346
|
-
catch (error) {
|
|
2347
|
-
if (undeliveredProcessId !== undefined) {
|
|
2348
|
-
processSessions.discardUndelivered(workspaceId, undeliveredProcessId);
|
|
2349
|
-
}
|
|
2350
|
-
throw error;
|
|
2351
|
-
}
|
|
2352
|
-
}, processActivityOutcome);
|
|
2353
|
-
markReturnedOutput(bashOutputStore, activityResult);
|
|
2354
|
-
return activityResult;
|
|
2689
|
+
}, {
|
|
2690
|
+
requestMeta: extra._meta,
|
|
2691
|
+
signal: extra.signal,
|
|
2692
|
+
sessionId: extra.sessionId,
|
|
2693
|
+
});
|
|
2355
2694
|
}
|
|
2356
2695
|
if (action === "output") {
|
|
2357
2696
|
if (!outputId)
|
|
@@ -2430,7 +2769,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2430
2769
|
});
|
|
2431
2770
|
});
|
|
2432
2771
|
}
|
|
2433
|
-
registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore);
|
|
2772
|
+
registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, (input, context) => coreOperations.shellRun(input, context));
|
|
2434
2773
|
return server;
|
|
2435
2774
|
}
|
|
2436
2775
|
export function createServer(config = loadConfig(), options = {}) {
|