@akira-tl/forgerelay 0.5.2 → 0.5.5
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 +31 -0
- package/capabilities/batch-execution/GUIDE.md +18 -0
- package/dist/activity/audit-store.js +33 -1
- package/dist/activity/host-turn-store.js +46 -0
- package/dist/activity/lifecycle.js +7 -1
- package/dist/activity/mcp-query-tools.js +129 -0
- package/dist/activity/query-service.js +289 -0
- package/dist/capabilities.js +9 -0
- package/dist/capability-registry.js +28 -0
- package/dist/db/migrations.js +33 -0
- package/dist/db/schema.js +8 -0
- package/dist/file-mutations.js +33 -2
- package/dist/lsp/test-support/server-fixture.js +9 -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 +843 -495
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -17,7 +17,10 @@ import * as z from "zod/v4";
|
|
|
17
17
|
import { applyPatch } from "./apply-patch.js";
|
|
18
18
|
import { ActivityAuditStore } from "./activity/audit-store.js";
|
|
19
19
|
import { BashOutputStore } from "./activity/bash-output-store.js";
|
|
20
|
+
import { HostTurnStore } from "./activity/host-turn-store.js";
|
|
21
|
+
import { registerActivityQueryTools } from "./activity/mcp-query-tools.js";
|
|
20
22
|
import { ActivityLifecycle, } from "./activity/lifecycle.js";
|
|
23
|
+
import { ActivityQueryService } from "./activity/query-service.js";
|
|
21
24
|
import { buildCapabilityFingerprint } from "./capabilities.js";
|
|
22
25
|
import { CapabilityError, createCapabilityRegistry, } from "./capability-registry.js";
|
|
23
26
|
import { deletePath, renamePath } from "./file-mutations.js";
|
|
@@ -33,6 +36,10 @@ import { createOpenAIIncomingArtifactAdapter, IncomingArtifactAdapterRegistry, }
|
|
|
33
36
|
import { logEvent, requestIp, requestPath, commandPreview, transportSessionIdPrefix, workspaceLogLabel, } from "./logger.js";
|
|
34
37
|
import { editFileTool, readFileTool, writeFileTool, } from "./pi-tools.js";
|
|
35
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";
|
|
36
43
|
import { McpTransportRegistry, } from "./mcp-sessions.js";
|
|
37
44
|
import { ProcessManager, resolveProcessId, } from "./process-sessions.js";
|
|
38
45
|
import { createReviewCheckpointManager } from "./review-checkpoints.js";
|
|
@@ -168,6 +175,7 @@ const capabilityCatalogOutputSchema = z.object({
|
|
|
168
175
|
description: z.string(),
|
|
169
176
|
available: z.boolean(),
|
|
170
177
|
unavailableReason: z.string().optional(),
|
|
178
|
+
batchPolicy: z.enum(["parallel", "serial", "unsupported"]),
|
|
171
179
|
guide: capabilityCatalogGuideOutputSchema,
|
|
172
180
|
});
|
|
173
181
|
const capabilityErrorOutputSchema = z.object({
|
|
@@ -721,6 +729,53 @@ async function reviewWorkspaceChanges(reviewCheckpoints, workspace) {
|
|
|
721
729
|
function toolResultIsError(result) {
|
|
722
730
|
return typeof result === "object" && result !== null && result.isError === true;
|
|
723
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
|
+
}
|
|
724
779
|
function workspaceActivitySnapshot(workspace) {
|
|
725
780
|
return {
|
|
726
781
|
id: workspace.id,
|
|
@@ -778,7 +833,13 @@ function processActivityOutcome(result) {
|
|
|
778
833
|
}
|
|
779
834
|
return { type: "succeeded" };
|
|
780
835
|
}
|
|
781
|
-
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 = {}) {
|
|
782
843
|
return lifecycle.run({
|
|
783
844
|
tool,
|
|
784
845
|
workspace: workspaceActivitySnapshot(workspace),
|
|
@@ -786,12 +847,13 @@ function runActivityTool(lifecycle, workspace, requestMeta, tool, request, opera
|
|
|
786
847
|
request,
|
|
787
848
|
operation,
|
|
788
849
|
outcome,
|
|
850
|
+
...relation,
|
|
789
851
|
});
|
|
790
852
|
}
|
|
791
|
-
function runActivityToolWithHooks(lifecycle, hooks, workspace, requestMeta, request, hookOptions) {
|
|
792
|
-
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);
|
|
793
855
|
}
|
|
794
|
-
function registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore) {
|
|
856
|
+
function registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, shellRun) {
|
|
795
857
|
if (config.toolMode === "codex") {
|
|
796
858
|
registerAppTool(server, "exec_command", {
|
|
797
859
|
title: "Execute command",
|
|
@@ -834,70 +896,22 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
834
896
|
outputSchema: processOutputSchema(),
|
|
835
897
|
...toolWidgetDescriptorMeta(config, "shell"),
|
|
836
898
|
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
837
|
-
}, async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens }, extra) => {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
command: cmd,
|
|
854
|
-
cwd,
|
|
855
|
-
workspaceRoot: workspace.root,
|
|
856
|
-
tty,
|
|
857
|
-
columns,
|
|
858
|
-
rows,
|
|
859
|
-
yieldTimeMs,
|
|
860
|
-
timeoutMs,
|
|
861
|
-
maxOutputTokens,
|
|
862
|
-
codexCi: true,
|
|
863
|
-
signal: extra.signal,
|
|
864
|
-
audit: activityContext,
|
|
865
|
-
});
|
|
866
|
-
undeliveredProcessId = snapshot.running ? snapshot.processId : undefined;
|
|
867
|
-
logToolCall(config, {
|
|
868
|
-
tool: "exec_command",
|
|
869
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
870
|
-
workingDirectory: workingDirectory ?? ".",
|
|
871
|
-
command: cmd,
|
|
872
|
-
commandLength: cmd.length,
|
|
873
|
-
exitCode: snapshot.exitCode,
|
|
874
|
-
running: snapshot.running,
|
|
875
|
-
processId: snapshot.processId,
|
|
876
|
-
success: snapshot.running || snapshot.exitCode === 0,
|
|
877
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
878
|
-
});
|
|
879
|
-
return processToolResponse("exec_command", workspaceId, snapshot, {
|
|
880
|
-
command: cmd,
|
|
881
|
-
workingDirectory: workingDirectory ?? ".",
|
|
882
|
-
running: snapshot.running,
|
|
883
|
-
exitCode: snapshot.exitCode,
|
|
884
|
-
wallTimeMs: snapshot.wallTimeMs,
|
|
885
|
-
});
|
|
886
|
-
},
|
|
887
|
-
});
|
|
888
|
-
extra.signal.throwIfAborted();
|
|
889
|
-
return result;
|
|
890
|
-
}
|
|
891
|
-
catch (error) {
|
|
892
|
-
if (undeliveredProcessId !== undefined) {
|
|
893
|
-
processSessions.discardUndelivered(workspaceId, undeliveredProcessId);
|
|
894
|
-
}
|
|
895
|
-
throw error;
|
|
896
|
-
}
|
|
897
|
-
}, processActivityOutcome);
|
|
898
|
-
markReturnedOutput(bashOutputStore, activityResult);
|
|
899
|
-
return activityResult;
|
|
900
|
-
});
|
|
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
|
+
}));
|
|
901
915
|
}
|
|
902
916
|
if (config.toolMode !== "codex")
|
|
903
917
|
return;
|
|
@@ -992,14 +1006,33 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
992
1006
|
});
|
|
993
1007
|
});
|
|
994
1008
|
}
|
|
995
|
-
export function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore) {
|
|
1009
|
+
export function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries) {
|
|
996
1010
|
const toolDescriptions = buildToolDescriptions(config);
|
|
997
1011
|
const hooks = new HookRunner(config.hooks, config.logging, process.env, (workspaceId, result) => attachCompletedProcessNotices(processSessions, workspaceId, result, (snapshot) => recordBashCompletion(activityLifecycle, bashOutputStore, snapshot.outputId)));
|
|
998
1012
|
const incomingArtifactRegistry = new IncomingArtifactAdapterRegistry(incomingArtifactAdapters);
|
|
999
1013
|
const artifactDownloadAvailable = config.artifactsEnabled && isArtifactDownloadSupportedPlatform();
|
|
1000
1014
|
const reviewChangesAvailable = config.widgets === "changes";
|
|
1015
|
+
let batchExecutor;
|
|
1016
|
+
const batchExecuteAvailable = config.toolMode !== "codex";
|
|
1001
1017
|
const capabilityRegistry = createCapabilityRegistry({
|
|
1002
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
|
+
},
|
|
1003
1036
|
codeIntelligence: {
|
|
1004
1037
|
available: true,
|
|
1005
1038
|
run: async (input, context, options) => {
|
|
@@ -1071,6 +1104,510 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1071
1104
|
},
|
|
1072
1105
|
},
|
|
1073
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
|
+
});
|
|
1074
1611
|
const server = new McpServer({
|
|
1075
1612
|
name: "forgerelay",
|
|
1076
1613
|
title: "ForgeRelay",
|
|
@@ -1465,6 +2002,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1465
2002
|
},
|
|
1466
2003
|
}, hookReports));
|
|
1467
2004
|
});
|
|
2005
|
+
registerActivityQueryTools(server, activityQueries);
|
|
1468
2006
|
registerAppTool(server, toolNames.capability, {
|
|
1469
2007
|
title: "Use optional capability",
|
|
1470
2008
|
description: "Describe or run one optional ForgeRelay capability advertised by open_workspace. Use describe when the capability contract is unfamiliar, then read its advertised guide if needed. Run dispatches only explicitly registered capabilities; it cannot invoke arbitrary shell commands, URLs, or methods.",
|
|
@@ -1502,60 +2040,82 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1502
2040
|
openWorldHint: true,
|
|
1503
2041
|
},
|
|
1504
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
|
+
}
|
|
1505
2098
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1506
|
-
let changedPaths = [];
|
|
1507
2099
|
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, extra._meta, { workspaceId, name, action, arguments: capabilityArguments, file }, {
|
|
1508
2100
|
signal: extra.signal,
|
|
1509
2101
|
tool: toolNames.capability,
|
|
1510
2102
|
invocation: workspaceHookInvocation(workspace),
|
|
1511
2103
|
payload: { name, action },
|
|
1512
2104
|
isFailure: toolResultIsError,
|
|
1513
|
-
changedPaths: () => changedPaths,
|
|
1514
2105
|
operation: async () => {
|
|
1515
2106
|
const startedAt = performance.now();
|
|
1516
2107
|
try {
|
|
1517
|
-
|
|
1518
|
-
const capability = capabilityRegistry.describe(name, capabilityContextFor(workspace));
|
|
1519
|
-
const result = {
|
|
1520
|
-
content: [textBlock([
|
|
1521
|
-
`${capability.name}: ${capability.description}`,
|
|
1522
|
-
`Available: ${capability.available}`,
|
|
1523
|
-
`Guide: ${capability.guide.path}`,
|
|
1524
|
-
capability.guide.readBeforeFirstUse
|
|
1525
|
-
? "Read the guide before first use when this contract is unfamiliar."
|
|
1526
|
-
: undefined,
|
|
1527
|
-
].filter(Boolean).join("\n"))],
|
|
1528
|
-
structuredContent: { name, action, capability },
|
|
1529
|
-
};
|
|
1530
|
-
logToolCall(config, {
|
|
1531
|
-
tool: toolNames.capability,
|
|
1532
|
-
...workspaceLogContext(workspace),
|
|
1533
|
-
capability: name,
|
|
1534
|
-
action,
|
|
1535
|
-
success: true,
|
|
1536
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1537
|
-
});
|
|
1538
|
-
return result;
|
|
1539
|
-
}
|
|
1540
|
-
const execution = await capabilityRegistry.run(name, capabilityArguments ?? {}, capabilityContextFor(workspace), { nativeFile: file, signal: extra.signal });
|
|
1541
|
-
changedPaths = execution.changedPaths ?? [];
|
|
2108
|
+
const capability = capabilityRegistry.describe(name, capabilityContextFor(workspace));
|
|
1542
2109
|
const result = {
|
|
1543
|
-
content: [textBlock(
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
files: execution.card.files,
|
|
1553
|
-
payload: execution.card.payload ?? {},
|
|
1554
|
-
},
|
|
1555
|
-
},
|
|
1556
|
-
}
|
|
1557
|
-
: {}),
|
|
1558
|
-
structuredContent: { name, action, result: execution.value },
|
|
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 },
|
|
1559
2119
|
};
|
|
1560
2120
|
logToolCall(config, {
|
|
1561
2121
|
tool: toolNames.capability,
|
|
@@ -1704,9 +2264,16 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1704
2264
|
.describe("Workspace identifier returned by open_workspace."),
|
|
1705
2265
|
path: z
|
|
1706
2266
|
.string()
|
|
2267
|
+
.optional()
|
|
1707
2268
|
.describe(config.skillsEnabled
|
|
1708
|
-
? "
|
|
1709
|
-
: "
|
|
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."),
|
|
1710
2277
|
offset: z
|
|
1711
2278
|
.number()
|
|
1712
2279
|
.int()
|
|
@@ -1722,79 +2289,81 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1722
2289
|
},
|
|
1723
2290
|
outputSchema: resultOutputSchema({
|
|
1724
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(),
|
|
1725
2299
|
}),
|
|
1726
2300
|
...toolWidgetDescriptorMeta(config, "read"),
|
|
1727
2301
|
annotations: { readOnlyHint: true },
|
|
1728
|
-
}, 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
|
+
}
|
|
1729
2313
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
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
|
-
structuredContent: {
|
|
1785
|
-
result: contentText(content),
|
|
1786
|
-
...(discoveredInstructions.length > 0
|
|
1787
|
-
? {
|
|
1788
|
-
agentsFiles: discoveredInstructions.map((file) => ({
|
|
1789
|
-
path: formatAgentsPath(file.path, workspace.root),
|
|
1790
|
-
content: file.content,
|
|
1791
|
-
})),
|
|
1792
|
-
}
|
|
1793
|
-
: {}),
|
|
1794
|
-
},
|
|
1795
|
-
};
|
|
1796
|
-
},
|
|
1797
|
-
});
|
|
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;
|
|
1798
2367
|
});
|
|
1799
2368
|
if (config.toolMode !== "codex") {
|
|
1800
2369
|
registerAppTool(server, toolNames.write, {
|
|
@@ -1812,66 +2381,11 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1812
2381
|
outputSchema: resultOutputSchema(),
|
|
1813
2382
|
...toolWidgetDescriptorMeta(config, "write"),
|
|
1814
2383
|
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
1815
|
-
}, async ({ workspaceId, ...input }, extra) => {
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1821
|
-
payload: { path: input.path },
|
|
1822
|
-
isFailure: toolResultIsError,
|
|
1823
|
-
changedPaths: (result) => toolResultIsError(result) ? [] : [input.path],
|
|
1824
|
-
operation: async () => {
|
|
1825
|
-
const startedAt = performance.now();
|
|
1826
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [input.path]);
|
|
1827
|
-
const response = await writeFileTool(input, {
|
|
1828
|
-
cwd: workspace.root,
|
|
1829
|
-
root: workspace.root,
|
|
1830
|
-
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1831
|
-
});
|
|
1832
|
-
if (response.isError) {
|
|
1833
|
-
logFailedToolResponse(config, {
|
|
1834
|
-
tool: toolNames.write,
|
|
1835
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1836
|
-
path: input.path,
|
|
1837
|
-
}, response.content, startedAt);
|
|
1838
|
-
return response;
|
|
1839
|
-
}
|
|
1840
|
-
const patch = newFilePatch(input.path, input.content);
|
|
1841
|
-
const stats = countDiffStats(patch);
|
|
1842
|
-
const summary = {
|
|
1843
|
-
...stats,
|
|
1844
|
-
lines: contentLineCount(input.content),
|
|
1845
|
-
characters: input.content.length,
|
|
1846
|
-
};
|
|
1847
|
-
logToolCall(config, {
|
|
1848
|
-
tool: toolNames.write,
|
|
1849
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1850
|
-
path: input.path,
|
|
1851
|
-
success: true,
|
|
1852
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1853
|
-
});
|
|
1854
|
-
return {
|
|
1855
|
-
...response,
|
|
1856
|
-
_meta: {
|
|
1857
|
-
tool: toolNames.write,
|
|
1858
|
-
card: {
|
|
1859
|
-
workspaceId,
|
|
1860
|
-
path: input.path,
|
|
1861
|
-
summary,
|
|
1862
|
-
payload: {
|
|
1863
|
-
content: response.content,
|
|
1864
|
-
patch,
|
|
1865
|
-
},
|
|
1866
|
-
},
|
|
1867
|
-
},
|
|
1868
|
-
structuredContent: {
|
|
1869
|
-
result: contentText(response.content),
|
|
1870
|
-
},
|
|
1871
|
-
};
|
|
1872
|
-
},
|
|
1873
|
-
});
|
|
1874
|
-
});
|
|
2384
|
+
}, async ({ workspaceId, ...input }, extra) => coreOperations.write({ workspaceId, ...input }, {
|
|
2385
|
+
requestMeta: extra._meta,
|
|
2386
|
+
signal: extra.signal,
|
|
2387
|
+
sessionId: extra.sessionId,
|
|
2388
|
+
}));
|
|
1875
2389
|
registerAppTool(server, toolNames.edit, {
|
|
1876
2390
|
title: "Edit file",
|
|
1877
2391
|
description: toolDescriptions.edit,
|
|
@@ -1881,7 +2395,14 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1881
2395
|
.describe("Workspace identifier returned by open_workspace."),
|
|
1882
2396
|
path: z
|
|
1883
2397
|
.string()
|
|
1884
|
-
.
|
|
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."),
|
|
1885
2406
|
edits: z
|
|
1886
2407
|
.array(z.object({
|
|
1887
2408
|
oldText: z
|
|
@@ -1892,69 +2413,34 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1892
2413
|
.min(1),
|
|
1893
2414
|
},
|
|
1894
2415
|
outputSchema: resultOutputSchema({
|
|
1895
|
-
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(),
|
|
1896
2426
|
}),
|
|
1897
2427
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
1898
2428
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
1899
|
-
}, async ({ workspaceId,
|
|
1900
|
-
|
|
1901
|
-
|
|
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,
|
|
1902
2442
|
signal: extra.signal,
|
|
1903
|
-
|
|
1904
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1905
|
-
payload: { path: input.path, editCount: input.edits.length },
|
|
1906
|
-
isFailure: toolResultIsError,
|
|
1907
|
-
changedPaths: (result) => toolResultIsError(result) ? [] : [input.path],
|
|
1908
|
-
operation: async () => {
|
|
1909
|
-
const startedAt = performance.now();
|
|
1910
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [input.path]);
|
|
1911
|
-
const response = await editFileTool(input, {
|
|
1912
|
-
cwd: workspace.root,
|
|
1913
|
-
root: workspace.root,
|
|
1914
|
-
fileRoots: workspaces.fileToolRoots(workspace),
|
|
1915
|
-
});
|
|
1916
|
-
if (response.isError) {
|
|
1917
|
-
logFailedToolResponse(config, {
|
|
1918
|
-
tool: toolNames.edit,
|
|
1919
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1920
|
-
path: input.path,
|
|
1921
|
-
}, response.content, startedAt);
|
|
1922
|
-
return response;
|
|
1923
|
-
}
|
|
1924
|
-
const stats = countDiffStats(response.details?.patch ?? response.details?.diff);
|
|
1925
|
-
const summary = {
|
|
1926
|
-
...stats,
|
|
1927
|
-
editCount: input.edits.length,
|
|
1928
|
-
};
|
|
1929
|
-
const editResultText = `Edited ${input.path} (+${stats.additions} -${stats.removals}).`;
|
|
1930
|
-
const editContent = [textBlock(editResultText)];
|
|
1931
|
-
logToolCall(config, {
|
|
1932
|
-
tool: toolNames.edit,
|
|
1933
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1934
|
-
path: input.path,
|
|
1935
|
-
success: true,
|
|
1936
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
1937
|
-
});
|
|
1938
|
-
return {
|
|
1939
|
-
content: editContent,
|
|
1940
|
-
_meta: {
|
|
1941
|
-
tool: toolNames.edit,
|
|
1942
|
-
card: {
|
|
1943
|
-
workspaceId,
|
|
1944
|
-
path: input.path,
|
|
1945
|
-
summary,
|
|
1946
|
-
payload: {
|
|
1947
|
-
diff: response.details?.diff,
|
|
1948
|
-
patch: response.details?.patch,
|
|
1949
|
-
},
|
|
1950
|
-
},
|
|
1951
|
-
},
|
|
1952
|
-
structuredContent: {
|
|
1953
|
-
status: "applied",
|
|
1954
|
-
result: contentText(editContent),
|
|
1955
|
-
},
|
|
1956
|
-
};
|
|
1957
|
-
},
|
|
2443
|
+
sessionId: extra.sessionId,
|
|
1958
2444
|
});
|
|
1959
2445
|
});
|
|
1960
2446
|
}
|
|
@@ -1973,135 +2459,56 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1973
2459
|
}),
|
|
1974
2460
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
1975
2461
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
1976
|
-
}, async ({ workspaceId, path, newPath }, extra) => {
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
invocation: workspaceHookInvocation(workspace),
|
|
1982
|
-
payload: { path, newPath, paths: [path, newPath] },
|
|
1983
|
-
changedPaths: () => [path, newPath],
|
|
1984
|
-
operation: async () => {
|
|
1985
|
-
const startedAt = performance.now();
|
|
1986
|
-
try {
|
|
1987
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [path, newPath]);
|
|
1988
|
-
await renamePath({ path, newPath }, {
|
|
1989
|
-
cwd: workspace.root,
|
|
1990
|
-
allowedRoots: workspaces.fileToolRoots(workspace),
|
|
1991
|
-
});
|
|
1992
|
-
const result = `Renamed ${path} to ${newPath}.`;
|
|
1993
|
-
const content = [textBlock(result)];
|
|
1994
|
-
logToolCall(config, {
|
|
1995
|
-
tool: toolNames.rename,
|
|
1996
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
1997
|
-
path: `${path} -> ${newPath}`,
|
|
1998
|
-
success: true,
|
|
1999
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2000
|
-
});
|
|
2001
|
-
return {
|
|
2002
|
-
content,
|
|
2003
|
-
_meta: {
|
|
2004
|
-
tool: toolNames.rename,
|
|
2005
|
-
card: {
|
|
2006
|
-
workspaceId,
|
|
2007
|
-
path: newPath,
|
|
2008
|
-
summary: { previousPath: path },
|
|
2009
|
-
payload: { content },
|
|
2010
|
-
},
|
|
2011
|
-
},
|
|
2012
|
-
structuredContent: {
|
|
2013
|
-
result,
|
|
2014
|
-
status: "renamed",
|
|
2015
|
-
path,
|
|
2016
|
-
newPath,
|
|
2017
|
-
},
|
|
2018
|
-
};
|
|
2019
|
-
}
|
|
2020
|
-
catch (error) {
|
|
2021
|
-
logToolCall(config, {
|
|
2022
|
-
tool: toolNames.rename,
|
|
2023
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
2024
|
-
path: `${path} -> ${newPath}`,
|
|
2025
|
-
success: false,
|
|
2026
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2027
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2028
|
-
});
|
|
2029
|
-
throw error;
|
|
2030
|
-
}
|
|
2031
|
-
},
|
|
2032
|
-
});
|
|
2033
|
-
});
|
|
2462
|
+
}, async ({ workspaceId, path, newPath }, extra) => coreOperations.rename({ workspaceId, path, newPath }, {
|
|
2463
|
+
requestMeta: extra._meta,
|
|
2464
|
+
signal: extra.signal,
|
|
2465
|
+
sessionId: extra.sessionId,
|
|
2466
|
+
}));
|
|
2034
2467
|
registerAppTool(server, toolNames.delete, {
|
|
2035
2468
|
title: "Delete path",
|
|
2036
2469
|
description: toolDescriptions.delete,
|
|
2037
2470
|
inputSchema: {
|
|
2038
2471
|
workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
|
|
2039
|
-
path: z.string().describe("
|
|
2040
|
-
|
|
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."),
|
|
2041
2480
|
},
|
|
2042
2481
|
outputSchema: resultOutputSchema({
|
|
2043
|
-
status: z.
|
|
2044
|
-
path: z.string(),
|
|
2045
|
-
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(),
|
|
2046
2494
|
}),
|
|
2047
2495
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
2048
2496
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
2049
|
-
}, async ({ workspaceId, path, recursive }, extra) => {
|
|
2050
|
-
|
|
2051
|
-
|
|
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,
|
|
2052
2510
|
signal: extra.signal,
|
|
2053
|
-
|
|
2054
|
-
invocation: workspaceHookInvocation(workspace),
|
|
2055
|
-
payload: { path, recursive: recursive ?? false },
|
|
2056
|
-
changedPaths: () => [path],
|
|
2057
|
-
operation: async () => {
|
|
2058
|
-
const startedAt = performance.now();
|
|
2059
|
-
try {
|
|
2060
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [path]);
|
|
2061
|
-
const deleted = await deletePath({ path, recursive }, {
|
|
2062
|
-
cwd: workspace.root,
|
|
2063
|
-
allowedRoots: workspaces.fileToolRoots(workspace),
|
|
2064
|
-
});
|
|
2065
|
-
const result = `Deleted ${path}${deleted.recursive ? " recursively" : ""}.`;
|
|
2066
|
-
const content = [textBlock(result)];
|
|
2067
|
-
logToolCall(config, {
|
|
2068
|
-
tool: toolNames.delete,
|
|
2069
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
2070
|
-
path,
|
|
2071
|
-
success: true,
|
|
2072
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2073
|
-
});
|
|
2074
|
-
return {
|
|
2075
|
-
content,
|
|
2076
|
-
_meta: {
|
|
2077
|
-
tool: toolNames.delete,
|
|
2078
|
-
card: {
|
|
2079
|
-
workspaceId,
|
|
2080
|
-
path,
|
|
2081
|
-
summary: { recursive: deleted.recursive },
|
|
2082
|
-
payload: { content },
|
|
2083
|
-
},
|
|
2084
|
-
},
|
|
2085
|
-
structuredContent: {
|
|
2086
|
-
result,
|
|
2087
|
-
status: "deleted",
|
|
2088
|
-
path,
|
|
2089
|
-
recursive: deleted.recursive,
|
|
2090
|
-
},
|
|
2091
|
-
};
|
|
2092
|
-
}
|
|
2093
|
-
catch (error) {
|
|
2094
|
-
logToolCall(config, {
|
|
2095
|
-
tool: toolNames.delete,
|
|
2096
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
2097
|
-
path,
|
|
2098
|
-
success: false,
|
|
2099
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2100
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2101
|
-
});
|
|
2102
|
-
throw error;
|
|
2103
|
-
}
|
|
2104
|
-
},
|
|
2511
|
+
sessionId: extra.sessionId,
|
|
2105
2512
|
});
|
|
2106
2513
|
});
|
|
2107
2514
|
if (config.toolMode === "codex") {
|
|
@@ -2268,11 +2675,10 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2268
2675
|
if (processId !== undefined || outputId !== undefined || input !== undefined || interrupt !== undefined) {
|
|
2269
2676
|
throw new Error("bash action=run does not accept processId, outputId, input, or interrupt.");
|
|
2270
2677
|
}
|
|
2271
|
-
|
|
2272
|
-
const activityResult = await runActivityTool(activityLifecycle, workspace, extra._meta, toolNames.shell, {
|
|
2678
|
+
return coreOperations.shellRun({
|
|
2273
2679
|
workspaceId,
|
|
2274
|
-
action,
|
|
2275
2680
|
command,
|
|
2681
|
+
surface: "bash",
|
|
2276
2682
|
tty,
|
|
2277
2683
|
columns,
|
|
2278
2684
|
rows,
|
|
@@ -2280,74 +2686,11 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2280
2686
|
yieldTimeMs,
|
|
2281
2687
|
timeoutMs,
|
|
2282
2688
|
maxOutputTokens,
|
|
2283
|
-
},
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
invocation: workspaceHookInvocation(workspace),
|
|
2289
|
-
payload: {
|
|
2290
|
-
action,
|
|
2291
|
-
command,
|
|
2292
|
-
workingDirectory: workingDirectory ?? ".",
|
|
2293
|
-
},
|
|
2294
|
-
isFailure: toolResultIsError,
|
|
2295
|
-
operation: async () => {
|
|
2296
|
-
const startedAt = performance.now();
|
|
2297
|
-
const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
|
|
2298
|
-
await assertWorkspaceInstructionsLoadedBeforeSideEffect(workspaces, workspace, [cwd]);
|
|
2299
|
-
const snapshot = await processSessions.start({
|
|
2300
|
-
workspaceId,
|
|
2301
|
-
command,
|
|
2302
|
-
cwd,
|
|
2303
|
-
workspaceRoot: workspace.root,
|
|
2304
|
-
tty,
|
|
2305
|
-
columns,
|
|
2306
|
-
rows,
|
|
2307
|
-
yieldTimeMs,
|
|
2308
|
-
timeoutMs,
|
|
2309
|
-
maxOutputTokens,
|
|
2310
|
-
signal: extra.signal,
|
|
2311
|
-
audit: activityContext,
|
|
2312
|
-
});
|
|
2313
|
-
undeliveredProcessId = snapshot.running ? snapshot.processId : undefined;
|
|
2314
|
-
logToolCall(config, {
|
|
2315
|
-
tool: toolNames.shell,
|
|
2316
|
-
...workspaceLogContext(workspace, extra.sessionId),
|
|
2317
|
-
workingDirectory: workingDirectory ?? ".",
|
|
2318
|
-
command,
|
|
2319
|
-
commandLength: command.length,
|
|
2320
|
-
exitCode: snapshot.exitCode,
|
|
2321
|
-
running: snapshot.running,
|
|
2322
|
-
processId: snapshot.processId,
|
|
2323
|
-
success: snapshot.running || (snapshot.exitCode === 0 && !snapshot.signal),
|
|
2324
|
-
durationMs: Math.round(performance.now() - startedAt),
|
|
2325
|
-
});
|
|
2326
|
-
const response = processToolResponse(toolNames.shell, workspaceId, snapshot, {
|
|
2327
|
-
action,
|
|
2328
|
-
command,
|
|
2329
|
-
workingDirectory: workingDirectory ?? ".",
|
|
2330
|
-
running: snapshot.running,
|
|
2331
|
-
exitCode: snapshot.exitCode,
|
|
2332
|
-
wallTimeMs: snapshot.wallTimeMs,
|
|
2333
|
-
});
|
|
2334
|
-
return !snapshot.running && (snapshot.signal || snapshot.exitCode !== 0)
|
|
2335
|
-
? { ...response, isError: true }
|
|
2336
|
-
: response;
|
|
2337
|
-
},
|
|
2338
|
-
});
|
|
2339
|
-
extra.signal.throwIfAborted();
|
|
2340
|
-
return result;
|
|
2341
|
-
}
|
|
2342
|
-
catch (error) {
|
|
2343
|
-
if (undeliveredProcessId !== undefined) {
|
|
2344
|
-
processSessions.discardUndelivered(workspaceId, undeliveredProcessId);
|
|
2345
|
-
}
|
|
2346
|
-
throw error;
|
|
2347
|
-
}
|
|
2348
|
-
}, processActivityOutcome);
|
|
2349
|
-
markReturnedOutput(bashOutputStore, activityResult);
|
|
2350
|
-
return activityResult;
|
|
2689
|
+
}, {
|
|
2690
|
+
requestMeta: extra._meta,
|
|
2691
|
+
signal: extra.signal,
|
|
2692
|
+
sessionId: extra.sessionId,
|
|
2693
|
+
});
|
|
2351
2694
|
}
|
|
2352
2695
|
if (action === "output") {
|
|
2353
2696
|
if (!outputId)
|
|
@@ -2426,7 +2769,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2426
2769
|
});
|
|
2427
2770
|
});
|
|
2428
2771
|
}
|
|
2429
|
-
registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore);
|
|
2772
|
+
registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, (input, context) => coreOperations.shellRun(input, context));
|
|
2430
2773
|
return server;
|
|
2431
2774
|
}
|
|
2432
2775
|
export function createServer(config = loadConfig(), options = {}) {
|
|
@@ -2453,8 +2796,12 @@ export function createServer(config = loadConfig(), options = {}) {
|
|
|
2453
2796
|
const workspaceStore = createWorkspaceStore(config.stateDir);
|
|
2454
2797
|
const workspaces = new WorkspaceRegistry(config, workspaceStore);
|
|
2455
2798
|
const activityAuditStore = new ActivityAuditStore(config.stateDir);
|
|
2456
|
-
const activityLifecycle = new ActivityLifecycle(activityAuditStore);
|
|
2457
2799
|
const bashOutputStore = new BashOutputStore(config.stateDir);
|
|
2800
|
+
const hostTurnStore = new HostTurnStore(config.stateDir);
|
|
2801
|
+
const activityQueries = new ActivityQueryService(hostTurnStore, activityAuditStore, bashOutputStore);
|
|
2802
|
+
const activityLifecycle = new ActivityLifecycle(activityAuditStore, {
|
|
2803
|
+
turnIdForConversation: (conversationScopeId) => activityQueries.currentTurnId(conversationScopeId),
|
|
2804
|
+
});
|
|
2458
2805
|
const reviewCheckpoints = createReviewCheckpointManager();
|
|
2459
2806
|
const processSessions = new ProcessManager({ outputAudit: bashOutputStore });
|
|
2460
2807
|
const codeIntelligence = new CodeIntelligenceManager(config);
|
|
@@ -2641,7 +2988,7 @@ export function createServer(config = loadConfig(), options = {}) {
|
|
|
2641
2988
|
});
|
|
2642
2989
|
}
|
|
2643
2990
|
};
|
|
2644
|
-
const server = createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore);
|
|
2991
|
+
const server = createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries);
|
|
2645
2992
|
await server.connect(transport);
|
|
2646
2993
|
}
|
|
2647
2994
|
else {
|
|
@@ -2673,6 +3020,7 @@ export function createServer(config = loadConfig(), options = {}) {
|
|
|
2673
3020
|
processSessions.shutdown();
|
|
2674
3021
|
await codeIntelligence.shutdown();
|
|
2675
3022
|
oauthProvider.close();
|
|
3023
|
+
hostTurnStore.close();
|
|
2676
3024
|
bashOutputStore.close();
|
|
2677
3025
|
activityAuditStore.close();
|
|
2678
3026
|
workspaceStore.close?.();
|