@akira-tl/forgerelay 0.6.1 → 0.6.2
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 +17 -0
- package/README.md +38 -0
- package/dist/activity/mcp-query-tools.js +1 -0
- package/dist/activity/query-service.js +1 -0
- package/dist/composite-activity.js +155 -0
- package/dist/composite-workspaces.js +197 -0
- package/dist/remote-workspace-relay.js +16 -0
- package/dist/server.js +673 -144
- package/dist/ui/.vite/manifest.json +33 -33
- package/dist/ui/activity-panel-app.html +3 -3
- package/dist/ui/assets/{activity-panel-app-CjZVvVNc.js → activity-panel-app-E1ju2dqI.js} +1 -1
- package/dist/ui/assets/{heavy-payload-vGgBRvNX.js → heavy-payload-CeW-n9w5.js} +1 -1
- package/dist/ui/assets/{review-payload-4erWKckt.js → review-payload-B9CO298v.js} +1 -1
- package/dist/ui/assets/{scrollbar-CaOPzUJd.js → scrollbar-C2twAENW.js} +1 -1
- package/dist/ui/assets/workspace-app-BztEvZIC.js +5 -0
- package/dist/ui/assets/{workspace-app-DkAiSl_0.js → workspace-app-CwbJnb_w.js} +1 -1
- package/dist/ui/assets/workspace-app-YnUST8IP.css +1 -0
- package/dist/ui/assets/workspace-app-rKuhdae8.js +1 -0
- package/dist/ui/assets/workspace-lifecycle-app-CEfMdudP.js +1 -0
- package/dist/ui/workspace-app.html +4 -4
- package/dist/ui/workspace-lifecycle-app.html +4 -4
- package/docs/configuration.md +23 -0
- package/docs/debugging.md +7 -0
- package/package.json +1 -1
- package/scripts/debug/runtime.mjs +23 -1
- package/scripts/debug/runtime.test.mjs +14 -2
- package/scripts/debug/serve.mjs +4 -4
- package/dist/ui/assets/workspace-app-CcrHAUIn.css +0 -1
- package/dist/ui/assets/workspace-app-DJmkPYJC.js +0 -1
- package/dist/ui/assets/workspace-app-QyauBrJX.js +0 -5
- package/dist/ui/assets/workspace-lifecycle-app-BIXEo53I.js +0 -1
package/dist/server.js
CHANGED
|
@@ -45,6 +45,8 @@ import { createCoreOperationExecutor, } from "./operations/core-operation-execut
|
|
|
45
45
|
import { McpTransportRegistry, } from "./mcp-sessions.js";
|
|
46
46
|
import { ProcessManager, resolveProcessId, } from "./process-sessions.js";
|
|
47
47
|
import { createReviewCheckpointManager } from "./review-checkpoints.js";
|
|
48
|
+
import { CompositeActivityCoordinator } from "./composite-activity.js";
|
|
49
|
+
import { CompositeWorkspaceRegistry } from "./composite-workspaces.js";
|
|
48
50
|
import { RemoteWorkspaceRelay } from "./remote-workspace-relay.js";
|
|
49
51
|
import { hostConversationScopeId, openAiConversationScopeId } from "./request-meta.js";
|
|
50
52
|
import { ACTIVITY_PANEL_APP_LEGACY_URI, ACTIVITY_PANEL_APP_URI_TEMPLATE, MCP_APP_RESOURCE_TEMPLATE_REVISION, readActivityPanelAppManifestEntry, readWorkspaceAppManifestEntry, readWorkspaceLifecycleAppManifestEntry, resolveActivityPanelAppIdentity, resolveWorkspaceAppIdentity, resolveWorkspaceLifecycleAppIdentity, WORKSPACE_APP_LEGACY_URI, WORKSPACE_APP_URI_TEMPLATE, WORKSPACE_LIFECYCLE_APP_LEGACY_URI, WORKSPACE_LIFECYCLE_APP_URI_TEMPLATE, } from "./mcp-app-template.js";
|
|
@@ -876,6 +878,39 @@ function toolResultContent(result) {
|
|
|
876
878
|
const content = result.content;
|
|
877
879
|
return Array.isArray(content) ? content : [];
|
|
878
880
|
}
|
|
881
|
+
function remapCompositeToolResult(result, executionWorkspaceId, compositeWorkspaceId, member) {
|
|
882
|
+
if (typeof result !== "object" || result === null)
|
|
883
|
+
return result;
|
|
884
|
+
const record = result;
|
|
885
|
+
const remapped = replaceWorkspaceIdentity(record, executionWorkspaceId, compositeWorkspaceId);
|
|
886
|
+
const meta = typeof remapped._meta === "object" && remapped._meta !== null
|
|
887
|
+
? { ...remapped._meta }
|
|
888
|
+
: undefined;
|
|
889
|
+
if (meta) {
|
|
890
|
+
const card = typeof meta.card === "object" && meta.card !== null
|
|
891
|
+
? { ...meta.card, workspaceId: compositeWorkspaceId, member }
|
|
892
|
+
: undefined;
|
|
893
|
+
if (card)
|
|
894
|
+
meta.card = card;
|
|
895
|
+
remapped._meta = meta;
|
|
896
|
+
}
|
|
897
|
+
const structured = typeof remapped.structuredContent === "object" && remapped.structuredContent !== null
|
|
898
|
+
? { ...remapped.structuredContent, member }
|
|
899
|
+
: undefined;
|
|
900
|
+
if (structured)
|
|
901
|
+
remapped.structuredContent = structured;
|
|
902
|
+
return remapped;
|
|
903
|
+
}
|
|
904
|
+
function replaceWorkspaceIdentity(value, from, to) {
|
|
905
|
+
if (typeof value === "string")
|
|
906
|
+
return value.split(from).join(to);
|
|
907
|
+
if (Array.isArray(value))
|
|
908
|
+
return value.map((entry) => replaceWorkspaceIdentity(entry, from, to));
|
|
909
|
+
if (!value || typeof value !== "object")
|
|
910
|
+
return value;
|
|
911
|
+
return Object.fromEntries(Object.entries(value)
|
|
912
|
+
.map(([key, entry]) => [key, replaceWorkspaceIdentity(entry, from, to)]));
|
|
913
|
+
}
|
|
879
914
|
function toolResultAgentsFiles(result) {
|
|
880
915
|
if (typeof result !== "object" || result === null)
|
|
881
916
|
return [];
|
|
@@ -956,6 +991,14 @@ function activityRelationFor(context) {
|
|
|
956
991
|
...(context.turnId ? { turnId: context.turnId } : {}),
|
|
957
992
|
};
|
|
958
993
|
}
|
|
994
|
+
function activityRequestFor(input, context) {
|
|
995
|
+
if (!context.activityMember || !input || typeof input !== "object" || Array.isArray(input))
|
|
996
|
+
return input;
|
|
997
|
+
return {
|
|
998
|
+
...input,
|
|
999
|
+
member: context.activityMember,
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
959
1002
|
function runActivityTool(lifecycle, workspace, conversationScopeId, tool, request, operation, outcome = standardActivityOutcome, relation = {}) {
|
|
960
1003
|
return lifecycle.run({
|
|
961
1004
|
tool,
|
|
@@ -970,13 +1013,14 @@ function runActivityTool(lifecycle, workspace, conversationScopeId, tool, reques
|
|
|
970
1013
|
function runActivityToolWithHooks(lifecycle, hooks, workspace, conversationScopeId, request, hookOptions, relation = {}) {
|
|
971
1014
|
return runActivityTool(lifecycle, workspace, conversationScopeId, hookOptions.tool, request, () => runToolWithHooks(hooks, hookOptions), standardActivityOutcome, relation);
|
|
972
1015
|
}
|
|
973
|
-
function registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, shellRun) {
|
|
1016
|
+
function registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, shellRun, routing) {
|
|
974
1017
|
if (config.toolMode === "codex") {
|
|
975
1018
|
registerAppTool(server, "exec_command", {
|
|
976
1019
|
title: "Execute command",
|
|
977
1020
|
description: `Run a command inside an open workspace. Returns its result when it exits during the yield window, otherwise returns a processId for write_stdin. Use this for file inspection, tests, builds, package scripts, generators, formatters, and long-running processes. ${buildShellMutationPolicy()} Call open_workspace first and pass workspaceId.`,
|
|
978
1021
|
inputSchema: {
|
|
979
1022
|
workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
|
|
1023
|
+
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this process."),
|
|
980
1024
|
cmd: z.string().min(1).describe("Shell command to execute."),
|
|
981
1025
|
tty: z
|
|
982
1026
|
.boolean()
|
|
@@ -1013,22 +1057,34 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
1013
1057
|
outputSchema: processOutputSchema(),
|
|
1014
1058
|
...toolWidgetDescriptorMeta(config, "shell"),
|
|
1015
1059
|
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
1016
|
-
}, async ({ workspaceId, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens }, extra) =>
|
|
1017
|
-
workspaceId,
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1060
|
+
}, async ({ workspaceId, member, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens }, extra) => {
|
|
1061
|
+
const target = routing.resolve(workspaceId, member);
|
|
1062
|
+
const context = await routing.prepare(target, extra._meta, extra.signal, extra.sessionId);
|
|
1063
|
+
if (routing.isRemote(target.executionWorkspaceId)) {
|
|
1064
|
+
return routing.present(await routing.execCommandRemote(target.executionWorkspaceId, {
|
|
1065
|
+
cmd,
|
|
1066
|
+
...(tty !== undefined ? { tty } : {}),
|
|
1067
|
+
...(columns !== undefined ? { columns } : {}),
|
|
1068
|
+
...(rows !== undefined ? { rows } : {}),
|
|
1069
|
+
...(workingDirectory !== undefined ? { workingDirectory } : {}),
|
|
1070
|
+
...(yieldTimeMs !== undefined ? { yieldTimeMs } : {}),
|
|
1071
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
1072
|
+
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
1073
|
+
}, routing.hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
1074
|
+
}
|
|
1075
|
+
return routing.present(await shellRun({
|
|
1076
|
+
workspaceId: target.executionWorkspaceId,
|
|
1077
|
+
command: cmd,
|
|
1078
|
+
surface: "exec_command",
|
|
1079
|
+
tty,
|
|
1080
|
+
columns,
|
|
1081
|
+
rows,
|
|
1082
|
+
workingDirectory,
|
|
1083
|
+
yieldTimeMs,
|
|
1084
|
+
timeoutMs,
|
|
1085
|
+
maxOutputTokens,
|
|
1086
|
+
}, context), target);
|
|
1087
|
+
});
|
|
1032
1088
|
}
|
|
1033
1089
|
if (config.toolMode !== "codex")
|
|
1034
1090
|
return;
|
|
@@ -1037,6 +1093,7 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
1037
1093
|
description: "Poll or write characters to a running process returned by exec_command, or retrieve complete durable process output by outputId. Omit chars or pass an empty string to poll. Waiting never kills the process; pass \\u0003 to explicitly send Ctrl-C.",
|
|
1038
1094
|
inputSchema: {
|
|
1039
1095
|
workspaceId: z.string().describe("Workspace identifier used to start the process."),
|
|
1096
|
+
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns the process."),
|
|
1040
1097
|
processId: z.number().int().positive().optional().describe("Canonical process identifier returned by bash or exec_command."),
|
|
1041
1098
|
sessionId: z.number().int().positive().optional().describe("Deprecated alias for processId. Retained for compatibility."),
|
|
1042
1099
|
outputId: z.string().optional().describe("Stable output identifier returned by exec_command. When supplied, retrieve the complete durable output instead of controlling a process."),
|
|
@@ -1061,8 +1118,23 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
1061
1118
|
outputSchema: processOutputSchema(),
|
|
1062
1119
|
...toolWidgetDescriptorMeta(config, "shell"),
|
|
1063
1120
|
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
1064
|
-
}, async ({ workspaceId, processId, sessionId, outputId, chars, columns, rows, yieldTimeMs, maxOutputTokens }, extra) => {
|
|
1065
|
-
const
|
|
1121
|
+
}, async ({ workspaceId, member, processId, sessionId, outputId, chars, columns, rows, yieldTimeMs, maxOutputTokens }, extra) => {
|
|
1122
|
+
const target = routing.resolve(workspaceId, member);
|
|
1123
|
+
await routing.prepare(target, extra._meta, extra.signal, extra.sessionId);
|
|
1124
|
+
if (routing.isRemote(target.executionWorkspaceId)) {
|
|
1125
|
+
return routing.present(await routing.writeStdinRemote(target.executionWorkspaceId, {
|
|
1126
|
+
...(processId !== undefined ? { processId } : {}),
|
|
1127
|
+
...(sessionId !== undefined ? { sessionId } : {}),
|
|
1128
|
+
...(outputId !== undefined ? { outputId } : {}),
|
|
1129
|
+
...(chars !== undefined ? { chars } : {}),
|
|
1130
|
+
...(columns !== undefined ? { columns } : {}),
|
|
1131
|
+
...(rows !== undefined ? { rows } : {}),
|
|
1132
|
+
...(yieldTimeMs !== undefined ? { yieldTimeMs } : {}),
|
|
1133
|
+
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
1134
|
+
}, routing.hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
1135
|
+
}
|
|
1136
|
+
const executionWorkspaceId = target.executionWorkspaceId;
|
|
1137
|
+
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
1066
1138
|
if (outputId !== undefined) {
|
|
1067
1139
|
if (processId !== undefined || sessionId !== undefined || chars !== undefined || columns !== undefined ||
|
|
1068
1140
|
rows !== undefined || yieldTimeMs !== undefined || maxOutputTokens !== undefined) {
|
|
@@ -1073,7 +1145,7 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
1073
1145
|
tool: "write_stdin",
|
|
1074
1146
|
invocation: workspaceHookInvocation(workspace),
|
|
1075
1147
|
payload: { outputId },
|
|
1076
|
-
operation: async () => durableOutputResponse("write_stdin",
|
|
1148
|
+
operation: async () => durableOutputResponse("write_stdin", executionWorkspaceId, readWorkspaceBashOutput(bashOutputStore, executionWorkspaceId, outputId)),
|
|
1077
1149
|
});
|
|
1078
1150
|
}
|
|
1079
1151
|
const resolvedProcessId = resolveProcessId(processId, sessionId);
|
|
@@ -1090,7 +1162,7 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
1090
1162
|
operation: async () => {
|
|
1091
1163
|
const startedAt = performance.now();
|
|
1092
1164
|
const snapshot = await processSessions.write({
|
|
1093
|
-
workspaceId,
|
|
1165
|
+
workspaceId: executionWorkspaceId,
|
|
1094
1166
|
processId: resolvedProcessId,
|
|
1095
1167
|
chars,
|
|
1096
1168
|
columns,
|
|
@@ -1108,7 +1180,7 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
1108
1180
|
success: snapshot.running || snapshot.exitCode === 0,
|
|
1109
1181
|
durationMs: Math.round(performance.now() - startedAt),
|
|
1110
1182
|
});
|
|
1111
|
-
const response = processToolResponse("write_stdin",
|
|
1183
|
+
const response = processToolResponse("write_stdin", executionWorkspaceId, snapshot, {
|
|
1112
1184
|
processId: resolvedProcessId,
|
|
1113
1185
|
charactersWritten: chars?.length ?? 0,
|
|
1114
1186
|
running: snapshot.running,
|
|
@@ -1120,13 +1192,57 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
1120
1192
|
}
|
|
1121
1193
|
return response;
|
|
1122
1194
|
},
|
|
1123
|
-
});
|
|
1195
|
+
}).then((result) => routing.present(result, target));
|
|
1124
1196
|
});
|
|
1125
1197
|
}
|
|
1126
1198
|
export function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries) {
|
|
1127
1199
|
const connectionScopeId = `mcp-connection:${randomUUID()}`;
|
|
1128
1200
|
const remoteWorkspaces = new RemoteWorkspaceRelay(config.configDir, config.stateDir);
|
|
1201
|
+
const compositeWorkspaces = new CompositeWorkspaceRegistry(config.stateDir);
|
|
1202
|
+
const compositeActivity = new CompositeActivityCoordinator(compositeWorkspaces, activityQueries, remoteWorkspaces);
|
|
1203
|
+
const resolveExecutionTarget = (workspaceId, memberName) => {
|
|
1204
|
+
if (!compositeWorkspaces.has(workspaceId)) {
|
|
1205
|
+
if (memberName !== undefined) {
|
|
1206
|
+
throw new Error(`Workspace ${workspaceId} is not composite and does not accept member.`);
|
|
1207
|
+
}
|
|
1208
|
+
return { executionWorkspaceId: workspaceId };
|
|
1209
|
+
}
|
|
1210
|
+
if (!memberName) {
|
|
1211
|
+
throw new Error(`Composite Workspace ${workspaceId} requires member for this operation.`);
|
|
1212
|
+
}
|
|
1213
|
+
const member = compositeWorkspaces.member(workspaceId, memberName);
|
|
1214
|
+
try {
|
|
1215
|
+
if (!remoteWorkspaces.has(member.workspaceId))
|
|
1216
|
+
workspaces.getWorkspace(member.workspaceId);
|
|
1217
|
+
}
|
|
1218
|
+
catch (error) {
|
|
1219
|
+
throw new Error(`Composite Workspace ${workspaceId} member ${member.name} is unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
1220
|
+
}
|
|
1221
|
+
return {
|
|
1222
|
+
executionWorkspaceId: member.workspaceId,
|
|
1223
|
+
compositeWorkspaceId: workspaceId,
|
|
1224
|
+
memberName: member.name,
|
|
1225
|
+
};
|
|
1226
|
+
};
|
|
1227
|
+
const presentExecutionResult = (result, target) => {
|
|
1228
|
+
if (!target.compositeWorkspaceId || !target.memberName)
|
|
1229
|
+
return result;
|
|
1230
|
+
return remapCompositeToolResult(result, target.executionWorkspaceId, target.compositeWorkspaceId, target.memberName);
|
|
1231
|
+
};
|
|
1129
1232
|
const hostScopeIdFor = (requestMeta, transportSessionId) => hostConversationScopeId(requestMeta, transportSessionId, connectionScopeId);
|
|
1233
|
+
const prepareExecutionContext = async (target, requestMeta, signal, sessionId) => {
|
|
1234
|
+
const conversationScopeId = hostScopeIdFor(requestMeta, sessionId);
|
|
1235
|
+
const turnId = target.compositeWorkspaceId && target.memberName
|
|
1236
|
+
? await compositeActivity.prepareMember(target.compositeWorkspaceId, target.memberName, target.executionWorkspaceId, conversationScopeId)
|
|
1237
|
+
: undefined;
|
|
1238
|
+
return {
|
|
1239
|
+
requestMeta,
|
|
1240
|
+
signal,
|
|
1241
|
+
sessionId,
|
|
1242
|
+
...(turnId ? { turnId } : {}),
|
|
1243
|
+
...(target.memberName ? { activityMember: target.memberName } : {}),
|
|
1244
|
+
};
|
|
1245
|
+
};
|
|
1130
1246
|
const toolDescriptions = buildToolDescriptions(config);
|
|
1131
1247
|
const hooks = new HookRunner(config.hooks, config.logging, process.env, (workspaceId, result) => attachCompletedProcessNotices(processSessions, workspaceId, result, (snapshot) => recordBashCompletion(activityLifecycle, bashOutputStore, snapshot.outputId)));
|
|
1132
1248
|
const incomingArtifactRegistry = new IncomingArtifactAdapterRegistry(incomingArtifactAdapters);
|
|
@@ -1224,11 +1340,59 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1224
1340
|
},
|
|
1225
1341
|
},
|
|
1226
1342
|
});
|
|
1343
|
+
const loadCompositeMemberContext = async (compositeWorkspaceId, memberName, contextPolicy, conversationScopeId, protectedWorkspaceIds) => {
|
|
1344
|
+
const target = resolveExecutionTarget(compositeWorkspaceId, memberName);
|
|
1345
|
+
if (remoteWorkspaces.has(target.executionWorkspaceId)) {
|
|
1346
|
+
const resumed = await remoteWorkspaces.resumeWorkspace(target.executionWorkspaceId, contextPolicy, conversationScopeId);
|
|
1347
|
+
const presented = presentExecutionResult(resumed, target);
|
|
1348
|
+
return {
|
|
1349
|
+
member: memberName,
|
|
1350
|
+
...(presented.structuredContent ?? {}),
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
const opened = await workspaces.openWorkspace({ workspaceId: target.executionWorkspaceId, context: contextPolicy }, { conversationScopeId, protectedWorkspaceIds });
|
|
1354
|
+
const workspace = opened.workspace;
|
|
1355
|
+
const capabilityFingerprint = buildCapabilityFingerprint(config, FORGERELAY_VERSION, {
|
|
1356
|
+
artifactDownloadSupported: isArtifactDownloadSupportedPlatform(),
|
|
1357
|
+
});
|
|
1358
|
+
const capabilityCatalog = capabilityRegistry.catalog(capabilityContextFor(workspace));
|
|
1359
|
+
const agentsFiles = opened.agentsFiles.map((file) => ({
|
|
1360
|
+
path: formatAgentsPath(file.path, workspace.root),
|
|
1361
|
+
content: file.content,
|
|
1362
|
+
}));
|
|
1363
|
+
const availableAgentsFiles = opened.availableAgentsFiles.map((file) => ({
|
|
1364
|
+
path: formatAgentsPath(file.path, workspace.root),
|
|
1365
|
+
}));
|
|
1366
|
+
const skills = workspace.skills
|
|
1367
|
+
.filter((skill) => !skill.disableModelInvocation)
|
|
1368
|
+
.map((skill) => ({ name: skill.name, description: skill.description }));
|
|
1369
|
+
return {
|
|
1370
|
+
member: memberName,
|
|
1371
|
+
workspaceId: compositeWorkspaceId,
|
|
1372
|
+
root: workspace.root,
|
|
1373
|
+
mode: workspace.mode,
|
|
1374
|
+
contextFingerprint: opened.contextFingerprint,
|
|
1375
|
+
capabilityFingerprint,
|
|
1376
|
+
capabilityCatalog,
|
|
1377
|
+
includeBootstrapContext: opened.includeBootstrapContext,
|
|
1378
|
+
...(opened.includeBootstrapContext
|
|
1379
|
+
? {
|
|
1380
|
+
agentsFiles,
|
|
1381
|
+
availableAgentsFiles,
|
|
1382
|
+
skills,
|
|
1383
|
+
skillDiagnostics: redactSkillDiagnosticPaths(workspace.skillDiagnostics),
|
|
1384
|
+
}
|
|
1385
|
+
: {}),
|
|
1386
|
+
instruction: opened.includeBootstrapContext
|
|
1387
|
+
? `Bootstrap context for Composite member ${memberName}. Keep using Composite workspaceId ${compositeWorkspaceId} and pass member=${memberName} for work operations.`
|
|
1388
|
+
: `Composite member ${memberName} context was already delivered for this Host context; keep using Composite workspaceId ${compositeWorkspaceId} with member=${memberName}.`,
|
|
1389
|
+
};
|
|
1390
|
+
};
|
|
1227
1391
|
const coreOperations = createCoreOperationExecutor({
|
|
1228
1392
|
read: async (input, context) => {
|
|
1229
1393
|
const { workspaceId, ...readInput } = input;
|
|
1230
1394
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1231
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), input, {
|
|
1395
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
|
|
1232
1396
|
signal: context.signal,
|
|
1233
1397
|
tool: toolNames.read,
|
|
1234
1398
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -1300,7 +1464,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1300
1464
|
write: async (input, context) => {
|
|
1301
1465
|
const { workspaceId, ...writeInput } = input;
|
|
1302
1466
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1303
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), input, {
|
|
1467
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
|
|
1304
1468
|
signal: context.signal,
|
|
1305
1469
|
tool: toolNames.write,
|
|
1306
1470
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -1361,7 +1525,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1361
1525
|
edit: async (input, context) => {
|
|
1362
1526
|
const { workspaceId, ...editInput } = input;
|
|
1363
1527
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1364
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), input, {
|
|
1528
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
|
|
1365
1529
|
signal: context.signal,
|
|
1366
1530
|
tool: toolNames.edit,
|
|
1367
1531
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -1423,7 +1587,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1423
1587
|
rename: async (input, context) => {
|
|
1424
1588
|
const { workspaceId, path, newPath } = input;
|
|
1425
1589
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1426
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), input, {
|
|
1590
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
|
|
1427
1591
|
signal: context.signal,
|
|
1428
1592
|
tool: toolNames.rename,
|
|
1429
1593
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -1482,7 +1646,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1482
1646
|
delete: async (input, context) => {
|
|
1483
1647
|
const { workspaceId, path, recursive } = input;
|
|
1484
1648
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1485
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), input, {
|
|
1649
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
|
|
1486
1650
|
signal: context.signal,
|
|
1487
1651
|
tool: toolNames.delete,
|
|
1488
1652
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -1566,7 +1730,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1566
1730
|
maxOutputTokens,
|
|
1567
1731
|
};
|
|
1568
1732
|
let undeliveredProcessId;
|
|
1569
|
-
const activityResult = await runActivityTool(activityLifecycle, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), surface, activityRequest, async (activityContext) => {
|
|
1733
|
+
const activityResult = await runActivityTool(activityLifecycle, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), surface, activityRequestFor(activityRequest, context), async (activityContext) => {
|
|
1570
1734
|
try {
|
|
1571
1735
|
const result = await runToolWithHooks(hooks, {
|
|
1572
1736
|
signal: context.signal,
|
|
@@ -1640,7 +1804,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1640
1804
|
const { workspaceId, name, arguments: capabilityArguments, file } = input;
|
|
1641
1805
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1642
1806
|
let changedPaths = [];
|
|
1643
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), { workspaceId, name, action: "run", arguments: capabilityArguments, file }, {
|
|
1807
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor({ workspaceId, name, action: "run", arguments: capabilityArguments, file }, context), {
|
|
1644
1808
|
signal: context.signal,
|
|
1645
1809
|
tool: toolNames.capability,
|
|
1646
1810
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -1794,16 +1958,44 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1794
1958
|
server.registerResource("ForgeRelay Activity Panel compatibility", new ResourceTemplate(ACTIVITY_PANEL_APP_URI_TEMPLATE, { list: undefined }), { ...activityPanelResourceMetadata, mimeType: RESOURCE_MIME_TYPE }, async (uri, _variables, extra) => readActivityPanelAppResource(config, uri.toString(), extra.sessionId));
|
|
1795
1959
|
registerAppTool(server, "open_workspace", {
|
|
1796
1960
|
title: "Open workspace",
|
|
1797
|
-
description: "Open or resume a
|
|
1961
|
+
description: "Open or resume a ForgeRelay Workspace. Ordinary workspaces default to local execution; relay may name a registered remote ForgeRelay. Composite Workspaces use the same open lifecycle but have kind=\"composite\" and a name instead of a mounted root. Reuse the returned workspaceId for later calls. Bootstrap context is delivered automatically only when needed and can be suppressed or refreshed.",
|
|
1798
1962
|
inputSchema: {
|
|
1799
1963
|
action: z
|
|
1800
|
-
.enum(["open", "list"])
|
|
1964
|
+
.enum(["open", "list", "member"])
|
|
1965
|
+
.optional()
|
|
1966
|
+
.describe("Defaults to open. Use list to inspect logical workspaces. Use member to add/remove a named execution member on an existing Composite Workspace."),
|
|
1967
|
+
memberAction: z
|
|
1968
|
+
.enum(["add", "update", "remove"])
|
|
1969
|
+
.optional()
|
|
1970
|
+
.describe("Required with action=member."),
|
|
1971
|
+
member: z.object({
|
|
1972
|
+
name: z.string().describe("Stable member name such as code or compute. For update/remove this identifies the existing member."),
|
|
1973
|
+
newName: z.string().optional().describe("Optional replacement member name for memberAction=update."),
|
|
1974
|
+
purpose: z.string().optional().describe("Agent-facing purpose. Required when adding a member; optional replacement when updating."),
|
|
1975
|
+
workspaceId: z.string().optional().describe("Existing ordinary or relayed Workspace to mount. Mutually exclusive with path."),
|
|
1976
|
+
path: z.string().optional().describe("Workspace path to open internally and mount. Mutually exclusive with workspaceId."),
|
|
1977
|
+
relay: z.string().optional().describe("Optional registered remote ForgeRelay alias for a path-backed member."),
|
|
1978
|
+
mode: z.enum(["checkout", "worktree"]).optional(),
|
|
1979
|
+
baseRef: z.string().optional(),
|
|
1980
|
+
newWorktree: z.boolean().optional(),
|
|
1981
|
+
newWorkspace: z.boolean().optional(),
|
|
1982
|
+
}).optional().describe("Composite member definition used by action=member."),
|
|
1983
|
+
kind: z
|
|
1984
|
+
.enum(["workspace", "composite"])
|
|
1985
|
+
.optional()
|
|
1986
|
+
.describe("Workspace kind for action=open. Defaults to workspace. Use composite with name and no path to create or reopen a named Composite Workspace."),
|
|
1987
|
+
name: z
|
|
1988
|
+
.string()
|
|
1801
1989
|
.optional()
|
|
1802
|
-
.describe("
|
|
1990
|
+
.describe("Composite Workspace name. Used only with kind=\"composite\" when creating/opening by name."),
|
|
1991
|
+
memberName: z
|
|
1992
|
+
.string()
|
|
1993
|
+
.optional()
|
|
1994
|
+
.describe("For action=open on a Composite Workspace, load bootstrap context for this named member without making it an implicit current member."),
|
|
1803
1995
|
path: z
|
|
1804
1996
|
.string()
|
|
1805
1997
|
.optional()
|
|
1806
|
-
.describe("Project path to open. Required for action=open unless workspaceId is supplied. With mode=\"worktree\", this may also be a managed worktree path previously returned by ForgeRelay."),
|
|
1998
|
+
.describe("Project path to open for an ordinary Workspace. Required for action=open unless workspaceId is supplied or kind=\"composite\" with name is used. With mode=\"worktree\", this may also be a managed worktree path previously returned by ForgeRelay."),
|
|
1807
1999
|
relay: z
|
|
1808
2000
|
.string()
|
|
1809
2001
|
.optional()
|
|
@@ -1863,8 +2055,17 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1863
2055
|
.describe("For action=list, maximum records to return. Defaults to 50; maximum 100."),
|
|
1864
2056
|
},
|
|
1865
2057
|
outputSchema: {
|
|
1866
|
-
action: z.enum(["open", "list"]),
|
|
2058
|
+
action: z.enum(["open", "list", "member"]),
|
|
1867
2059
|
workspaceId: z.string().optional(),
|
|
2060
|
+
memberAction: z.enum(["add", "update", "remove"]).optional(),
|
|
2061
|
+
kind: z.enum(["workspace", "composite"]).optional(),
|
|
2062
|
+
name: z.string().optional(),
|
|
2063
|
+
members: z.array(z.object({
|
|
2064
|
+
name: z.string(),
|
|
2065
|
+
purpose: z.string(),
|
|
2066
|
+
workspaceId: z.string(),
|
|
2067
|
+
})).optional(),
|
|
2068
|
+
memberContext: z.unknown().optional(),
|
|
1868
2069
|
root: z.string().optional(),
|
|
1869
2070
|
mode: z.enum(["checkout", "worktree"]).optional(),
|
|
1870
2071
|
sourceRoot: z.string().optional(),
|
|
@@ -1911,6 +2112,18 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1911
2112
|
agents: z.array(workspaceLocalAgentOutputSchema).optional(),
|
|
1912
2113
|
skillDiagnostics: z.array(workspaceSkillDiagnosticOutputSchema).optional(),
|
|
1913
2114
|
workspaces: z.array(workspaceInventoryEntryOutputSchema).optional(),
|
|
2115
|
+
compositeWorkspaces: z.array(z.object({
|
|
2116
|
+
workspaceId: z.string(),
|
|
2117
|
+
kind: z.literal("composite"),
|
|
2118
|
+
name: z.string(),
|
|
2119
|
+
members: z.array(z.object({
|
|
2120
|
+
name: z.string(),
|
|
2121
|
+
purpose: z.string(),
|
|
2122
|
+
workspaceId: z.string(),
|
|
2123
|
+
})),
|
|
2124
|
+
createdAt: z.string(),
|
|
2125
|
+
lastUsedAt: z.string(),
|
|
2126
|
+
})).optional(),
|
|
1914
2127
|
summary: workspaceInventorySummaryOutputSchema.optional(),
|
|
1915
2128
|
page: workspaceInventoryPageOutputSchema.optional(),
|
|
1916
2129
|
instruction: z.string(),
|
|
@@ -1922,26 +2135,198 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1922
2135
|
idempotentHint: false,
|
|
1923
2136
|
openWorldHint: false,
|
|
1924
2137
|
},
|
|
1925
|
-
}, async ({ action = "open", path, relay, workspaceId, mode, baseRef, newWorktree, newWorkspace, context, root, status, state, staleOnly, offset, limit, }, { _meta, sessionId }) => {
|
|
2138
|
+
}, async ({ action = "open", memberAction, member, kind, name, memberName, path, relay, workspaceId, mode, baseRef, newWorktree, newWorkspace, context, root, status, state, staleOnly, offset, limit, }, { _meta, sessionId }) => {
|
|
1926
2139
|
const startedAt = performance.now();
|
|
1927
2140
|
const conversationScopeId = openAiConversationScopeId(_meta);
|
|
1928
2141
|
const protectedWorkspaceIds = processSessions.activeWorkspaceIds();
|
|
2142
|
+
if (action === "member") {
|
|
2143
|
+
if (!workspaceId || !compositeWorkspaces.has(workspaceId)) {
|
|
2144
|
+
throw new Error("open_workspace action=member requires an existing Composite Workspace workspaceId.");
|
|
2145
|
+
}
|
|
2146
|
+
if (!memberAction || !member) {
|
|
2147
|
+
throw new Error("open_workspace action=member requires memberAction and member.");
|
|
2148
|
+
}
|
|
2149
|
+
if (kind !== undefined || name !== undefined || memberName !== undefined || path !== undefined || relay !== undefined || mode !== undefined ||
|
|
2150
|
+
baseRef !== undefined || newWorktree !== undefined || newWorkspace !== undefined || context !== undefined ||
|
|
2151
|
+
root !== undefined || status !== undefined || state !== undefined || staleOnly !== undefined ||
|
|
2152
|
+
offset !== undefined || limit !== undefined) {
|
|
2153
|
+
throw new Error("open_workspace action=member accepts workspaceId, memberAction, and member only. Put any Workspace open definition inside member.");
|
|
2154
|
+
}
|
|
2155
|
+
const resolveMemberTargetWorkspaceId = async () => {
|
|
2156
|
+
const byWorkspaceId = typeof member.workspaceId === "string" && member.workspaceId.length > 0;
|
|
2157
|
+
const byPath = typeof member.path === "string" && member.path.length > 0;
|
|
2158
|
+
if (byWorkspaceId === byPath) {
|
|
2159
|
+
throw new Error("A Composite Workspace member target requires exactly one of member.workspaceId or member.path.");
|
|
2160
|
+
}
|
|
2161
|
+
if (byWorkspaceId) {
|
|
2162
|
+
const targetWorkspaceId = member.workspaceId;
|
|
2163
|
+
if (compositeWorkspaces.has(targetWorkspaceId)) {
|
|
2164
|
+
throw new Error("A Composite Workspace cannot be mounted as a Composite Workspace member.");
|
|
2165
|
+
}
|
|
2166
|
+
if (member.relay !== undefined || member.mode !== undefined || member.baseRef !== undefined ||
|
|
2167
|
+
member.newWorktree !== undefined || member.newWorkspace !== undefined) {
|
|
2168
|
+
throw new Error("member.workspaceId cannot be combined with relay/mode/baseRef/newWorktree/newWorkspace.");
|
|
2169
|
+
}
|
|
2170
|
+
if (!remoteWorkspaces.has(targetWorkspaceId))
|
|
2171
|
+
workspaces.getWorkspace(targetWorkspaceId);
|
|
2172
|
+
return targetWorkspaceId;
|
|
2173
|
+
}
|
|
2174
|
+
if (member.relay !== undefined) {
|
|
2175
|
+
const opened = await remoteWorkspaces.openWorkspace(member.relay, {
|
|
2176
|
+
path: member.path,
|
|
2177
|
+
...(member.mode ? { mode: member.mode } : {}),
|
|
2178
|
+
...(member.baseRef ? { baseRef: member.baseRef } : {}),
|
|
2179
|
+
...(member.newWorktree !== undefined ? { newWorktree: member.newWorktree } : {}),
|
|
2180
|
+
...(member.newWorkspace !== undefined ? { newWorkspace: member.newWorkspace } : {}),
|
|
2181
|
+
context: "none",
|
|
2182
|
+
});
|
|
2183
|
+
return opened.workspaceId;
|
|
2184
|
+
}
|
|
2185
|
+
const opened = await workspaces.openWorkspace({
|
|
2186
|
+
path: member.path,
|
|
2187
|
+
...(member.mode ? { mode: member.mode } : {}),
|
|
2188
|
+
...(member.baseRef ? { baseRef: member.baseRef } : {}),
|
|
2189
|
+
...(member.newWorktree !== undefined ? { newWorktree: member.newWorktree } : {}),
|
|
2190
|
+
...(member.newWorkspace !== undefined ? { newWorkspace: member.newWorkspace } : {}),
|
|
2191
|
+
context: "none",
|
|
2192
|
+
}, { protectedWorkspaceIds });
|
|
2193
|
+
return opened.workspace.id;
|
|
2194
|
+
};
|
|
2195
|
+
let composite;
|
|
2196
|
+
if (memberAction === "add") {
|
|
2197
|
+
if (member.newName !== undefined) {
|
|
2198
|
+
throw new Error("Adding a Composite Workspace member does not accept member.newName.");
|
|
2199
|
+
}
|
|
2200
|
+
const purpose = member.purpose?.trim();
|
|
2201
|
+
if (!purpose)
|
|
2202
|
+
throw new Error("Adding a Composite Workspace member requires member.purpose.");
|
|
2203
|
+
const targetWorkspaceId = await resolveMemberTargetWorkspaceId();
|
|
2204
|
+
composite = compositeWorkspaces.addMember(workspaceId, {
|
|
2205
|
+
name: member.name,
|
|
2206
|
+
purpose,
|
|
2207
|
+
workspaceId: targetWorkspaceId,
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
else if (memberAction === "update") {
|
|
2211
|
+
const targetFieldsPresent = member.workspaceId !== undefined || member.path !== undefined || member.relay !== undefined ||
|
|
2212
|
+
member.mode !== undefined || member.baseRef !== undefined || member.newWorktree !== undefined ||
|
|
2213
|
+
member.newWorkspace !== undefined;
|
|
2214
|
+
if (member.newName === undefined && member.purpose === undefined && !targetFieldsPresent) {
|
|
2215
|
+
throw new Error("Updating a Composite Workspace member requires newName, purpose, or a replacement Workspace target.");
|
|
2216
|
+
}
|
|
2217
|
+
const targetWorkspaceId = targetFieldsPresent
|
|
2218
|
+
? await resolveMemberTargetWorkspaceId()
|
|
2219
|
+
: undefined;
|
|
2220
|
+
composite = compositeWorkspaces.updateMember(workspaceId, member.name, {
|
|
2221
|
+
...(member.newName !== undefined ? { name: member.newName } : {}),
|
|
2222
|
+
...(member.purpose !== undefined ? { purpose: member.purpose } : {}),
|
|
2223
|
+
...(targetWorkspaceId !== undefined ? { workspaceId: targetWorkspaceId } : {}),
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
else {
|
|
2227
|
+
if (member.newName !== undefined || member.purpose !== undefined || member.workspaceId !== undefined || member.path !== undefined ||
|
|
2228
|
+
member.relay !== undefined || member.mode !== undefined || member.baseRef !== undefined ||
|
|
2229
|
+
member.newWorktree !== undefined || member.newWorkspace !== undefined) {
|
|
2230
|
+
throw new Error("Removing a Composite Workspace member accepts only member.name.");
|
|
2231
|
+
}
|
|
2232
|
+
composite = compositeWorkspaces.removeMember(workspaceId, member.name);
|
|
2233
|
+
}
|
|
2234
|
+
const memberActionVerb = memberAction === "add"
|
|
2235
|
+
? "Added"
|
|
2236
|
+
: memberAction === "update"
|
|
2237
|
+
? "Updated"
|
|
2238
|
+
: "Removed";
|
|
2239
|
+
const memberActionPreposition = memberAction === "remove" ? "from" : "in";
|
|
2240
|
+
const instruction = [
|
|
2241
|
+
`${memberActionVerb} member ${member.name} ${memberActionPreposition} Composite Workspace ${composite.name} (${composite.id}).`,
|
|
2242
|
+
composite.members.length > 0
|
|
2243
|
+
? `Members: ${composite.members.map((entry) => `${entry.name} — ${entry.purpose}`).join("; ")}.`
|
|
2244
|
+
: "This Composite Workspace currently has no members.",
|
|
2245
|
+
"Use the Composite workspaceId as the top-level handle. Work operations on it require an explicit member name; ForgeRelay never infers a member from tool type or purpose.",
|
|
2246
|
+
].join("\n");
|
|
2247
|
+
const response = {
|
|
2248
|
+
content: [textBlock(instruction)],
|
|
2249
|
+
_meta: {
|
|
2250
|
+
tool: "open_workspace",
|
|
2251
|
+
card: {
|
|
2252
|
+
workspaceId: composite.id,
|
|
2253
|
+
kind: "composite",
|
|
2254
|
+
name: composite.name,
|
|
2255
|
+
path: composite.name,
|
|
2256
|
+
members: composite.members,
|
|
2257
|
+
instruction,
|
|
2258
|
+
summary: { members: composite.members.length },
|
|
2259
|
+
},
|
|
2260
|
+
},
|
|
2261
|
+
structuredContent: {
|
|
2262
|
+
action: "member",
|
|
2263
|
+
workspaceId: composite.id,
|
|
2264
|
+
memberAction,
|
|
2265
|
+
kind: "composite",
|
|
2266
|
+
name: composite.name,
|
|
2267
|
+
members: composite.members,
|
|
2268
|
+
instruction,
|
|
2269
|
+
},
|
|
2270
|
+
};
|
|
2271
|
+
rememberWorkspacePanelState(composite.id, response);
|
|
2272
|
+
return response;
|
|
2273
|
+
}
|
|
1929
2274
|
if (action === "list") {
|
|
1930
|
-
if (path !== undefined || relay !== undefined || baseRef !== undefined || newWorktree !== undefined ||
|
|
2275
|
+
if (path !== undefined || relay !== undefined || name !== undefined || memberName !== undefined || baseRef !== undefined || newWorktree !== undefined ||
|
|
1931
2276
|
newWorkspace !== undefined || context !== undefined) {
|
|
1932
|
-
throw new Error("open_workspace action=list does not accept path, relay, baseRef, newWorktree, newWorkspace, or context. Use root/workspaceId/mode/status/state/staleOnly for inventory filters.");
|
|
2277
|
+
throw new Error("open_workspace action=list does not accept path, relay, name, memberName, baseRef, newWorktree, newWorkspace, or context. Use kind/root/workspaceId/mode/status/state/staleOnly for inventory filters.");
|
|
2278
|
+
}
|
|
2279
|
+
if (kind === "composite") {
|
|
2280
|
+
if (root !== undefined || mode !== undefined || status !== undefined || state !== undefined ||
|
|
2281
|
+
staleOnly !== undefined || offset !== undefined || limit !== undefined) {
|
|
2282
|
+
throw new Error("Composite Workspace inventory does not accept root/mode/status/state/staleOnly/offset/limit filters; use workspaceId when selecting one Composite Workspace.");
|
|
2283
|
+
}
|
|
2284
|
+
const composites = compositeWorkspaces.list()
|
|
2285
|
+
.filter((entry) => workspaceId === undefined || entry.id === workspaceId)
|
|
2286
|
+
.map((entry) => ({
|
|
2287
|
+
workspaceId: entry.id,
|
|
2288
|
+
kind: entry.kind,
|
|
2289
|
+
name: entry.name,
|
|
2290
|
+
members: entry.members,
|
|
2291
|
+
createdAt: entry.createdAt,
|
|
2292
|
+
lastUsedAt: entry.lastUsedAt,
|
|
2293
|
+
}));
|
|
2294
|
+
const instruction = "Resume a Composite Workspace with open_workspace(action=\"open\", workspaceId=...). Use close_workspace only when the user chooses to dissolve it.";
|
|
2295
|
+
const result = [
|
|
2296
|
+
`Composite Workspace inventory: ${composites.length} matching record${composites.length === 1 ? "" : "s"}.`,
|
|
2297
|
+
...composites.map((entry) => `${entry.name} [${entry.workspaceId}] members=${entry.members.length} last-used=${entry.lastUsedAt}`),
|
|
2298
|
+
instruction,
|
|
2299
|
+
].join("\n");
|
|
2300
|
+
return {
|
|
2301
|
+
content: [textBlock(result)],
|
|
2302
|
+
structuredContent: {
|
|
2303
|
+
action: "list",
|
|
2304
|
+
compositeWorkspaces: composites,
|
|
2305
|
+
instruction,
|
|
2306
|
+
},
|
|
2307
|
+
};
|
|
1933
2308
|
}
|
|
1934
2309
|
const inventory = await workspaces.listWorkspaces({ workspaceId, mode, root, status, state, staleOnly, offset, limit }, { conversationScopeId, protectedWorkspaceIds });
|
|
2310
|
+
const composites = kind === "workspace"
|
|
2311
|
+
? []
|
|
2312
|
+
: compositeWorkspaces.list().map((entry) => ({
|
|
2313
|
+
workspaceId: entry.id,
|
|
2314
|
+
kind: entry.kind,
|
|
2315
|
+
name: entry.name,
|
|
2316
|
+
members: entry.members,
|
|
2317
|
+
createdAt: entry.createdAt,
|
|
2318
|
+
lastUsedAt: entry.lastUsedAt,
|
|
2319
|
+
}));
|
|
1935
2320
|
const nextOffset = inventory.page.offset + inventory.page.limit;
|
|
1936
2321
|
const instruction = [
|
|
1937
2322
|
"Resume a selected workspaceId with open_workspace(action=\"open\", workspaceId=...).",
|
|
1938
|
-
"Use close_workspace only after the user chooses cleanup; never close inventory entries automatically.",
|
|
2323
|
+
"Use close_workspace only after the user chooses cleanup or Composite dissolution; never close inventory entries automatically.",
|
|
1939
2324
|
inventory.page.hasMore
|
|
1940
2325
|
? `More matching workspaces are available; continue with offset=${nextOffset}.`
|
|
1941
2326
|
: undefined,
|
|
1942
2327
|
].filter(Boolean).join(" ");
|
|
1943
2328
|
const result = [
|
|
1944
|
-
`Logical workspace inventory: ${inventory.summary.matching} matching
|
|
2329
|
+
`Logical workspace inventory: ${inventory.summary.matching} matching ordinary records; ${composites.length} Composite Workspace record${composites.length === 1 ? "" : "s"}.`,
|
|
1945
2330
|
`States: active=${inventory.summary.active}, stale=${inventory.summary.stale}, invalid=${inventory.summary.invalid}, closed=${inventory.summary.closed}.`,
|
|
1946
2331
|
...inventory.workspaces.map((entry) => [
|
|
1947
2332
|
entry.label,
|
|
@@ -1953,6 +2338,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1953
2338
|
`root=${entry.root}`,
|
|
1954
2339
|
`last-used=${entry.lastUsedAt}`,
|
|
1955
2340
|
].filter(Boolean).join(" ")),
|
|
2341
|
+
...composites.map((entry) => `${entry.name} [${entry.workspaceId}] kind=composite members=${entry.members.length}`),
|
|
1956
2342
|
instruction,
|
|
1957
2343
|
].join("\n");
|
|
1958
2344
|
logToolCall(config, {
|
|
@@ -1967,6 +2353,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1967
2353
|
structuredContent: {
|
|
1968
2354
|
action: "list",
|
|
1969
2355
|
...inventory,
|
|
2356
|
+
...(composites.length > 0 ? { compositeWorkspaces: composites } : {}),
|
|
1970
2357
|
instruction,
|
|
1971
2358
|
},
|
|
1972
2359
|
};
|
|
@@ -1975,6 +2362,70 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1975
2362
|
staleOnly !== undefined || offset !== undefined || limit !== undefined) {
|
|
1976
2363
|
throw new Error("open_workspace inventory filters root, status, state, staleOnly, offset, and limit are only valid with action=list.");
|
|
1977
2364
|
}
|
|
2365
|
+
const openingComposite = kind === "composite" ||
|
|
2366
|
+
(workspaceId !== undefined && compositeWorkspaces.has(workspaceId));
|
|
2367
|
+
if (openingComposite) {
|
|
2368
|
+
if (relay !== undefined || path !== undefined || mode !== undefined || baseRef !== undefined ||
|
|
2369
|
+
newWorktree !== undefined || newWorkspace !== undefined) {
|
|
2370
|
+
throw new Error("Composite Workspace open accepts name/workspaceId/context only; members are attached separately and keep their own Workspace definitions.");
|
|
2371
|
+
}
|
|
2372
|
+
if (workspaceId !== undefined && kind === "workspace") {
|
|
2373
|
+
throw new Error(`${workspaceId} is a Composite Workspace, not an ordinary Workspace.`);
|
|
2374
|
+
}
|
|
2375
|
+
const composite = workspaceId !== undefined
|
|
2376
|
+
? compositeWorkspaces.open(workspaceId)
|
|
2377
|
+
: compositeWorkspaces.create(name ?? "");
|
|
2378
|
+
const memberContext = memberName
|
|
2379
|
+
? await loadCompositeMemberContext(composite.id, memberName, context ?? "auto", conversationScopeId, protectedWorkspaceIds)
|
|
2380
|
+
: undefined;
|
|
2381
|
+
const instruction = [
|
|
2382
|
+
`This is Composite Workspace ${composite.name} (${composite.id}).`,
|
|
2383
|
+
"It has no mounted working directory of its own. Use the Composite workspaceId as the top-level Workspace handle and explicitly select one named member for member-scoped work operations.",
|
|
2384
|
+
composite.members.length > 0
|
|
2385
|
+
? `Members: ${composite.members.map((member) => `${member.name} — ${member.purpose}`).join("; ")}.`
|
|
2386
|
+
: "This Composite Workspace currently has no members.",
|
|
2387
|
+
"Member names and purposes are structural context and are always returned when this Composite Workspace is opened. context=auto/full/none controls only heavy member bootstrap context, not this Composite identity.",
|
|
2388
|
+
composite.members.length > 0
|
|
2389
|
+
? "Before first work on a member, reopen this Composite Workspace with memberName=<member> and context=auto to receive that member's project bootstrap without creating an implicit current member."
|
|
2390
|
+
: undefined,
|
|
2391
|
+
"Use close_workspace only when the user chooses to dissolve this Composite Workspace; dissolution does not close or clean up member Workspaces.",
|
|
2392
|
+
].join("\n\n");
|
|
2393
|
+
const response = {
|
|
2394
|
+
content: [textBlock(instruction)],
|
|
2395
|
+
_meta: {
|
|
2396
|
+
tool: "open_workspace",
|
|
2397
|
+
card: {
|
|
2398
|
+
workspaceId: composite.id,
|
|
2399
|
+
kind: "composite",
|
|
2400
|
+
name: composite.name,
|
|
2401
|
+
path: composite.name,
|
|
2402
|
+
members: composite.members,
|
|
2403
|
+
instruction,
|
|
2404
|
+
summary: { members: composite.members.length },
|
|
2405
|
+
},
|
|
2406
|
+
},
|
|
2407
|
+
structuredContent: {
|
|
2408
|
+
action: "open",
|
|
2409
|
+
workspaceId: composite.id,
|
|
2410
|
+
kind: "composite",
|
|
2411
|
+
name: composite.name,
|
|
2412
|
+
members: composite.members,
|
|
2413
|
+
...(memberContext ? { memberContext } : {}),
|
|
2414
|
+
instruction,
|
|
2415
|
+
},
|
|
2416
|
+
};
|
|
2417
|
+
logToolCall(config, {
|
|
2418
|
+
tool: "open_workspace",
|
|
2419
|
+
action: "composite",
|
|
2420
|
+
success: true,
|
|
2421
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
2422
|
+
});
|
|
2423
|
+
rememberWorkspacePanelState(composite.id, response);
|
|
2424
|
+
return response;
|
|
2425
|
+
}
|
|
2426
|
+
if (name !== undefined || memberName !== undefined) {
|
|
2427
|
+
throw new Error("open_workspace name/memberName are only valid for a Composite Workspace.");
|
|
2428
|
+
}
|
|
1978
2429
|
if (relay !== undefined) {
|
|
1979
2430
|
if (workspaceId !== undefined) {
|
|
1980
2431
|
throw new Error("Relayed open_workspace requires a path; resuming a relayed workspace is not available in this tracer bullet.");
|
|
@@ -2014,6 +2465,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2014
2465
|
tool: "open_workspace",
|
|
2015
2466
|
card: {
|
|
2016
2467
|
workspaceId: opened.workspaceId,
|
|
2468
|
+
kind: "workspace",
|
|
2017
2469
|
root: opened.root,
|
|
2018
2470
|
path: opened.root,
|
|
2019
2471
|
mode: opened.mode,
|
|
@@ -2025,6 +2477,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2025
2477
|
structuredContent: {
|
|
2026
2478
|
action: "open",
|
|
2027
2479
|
workspaceId: opened.workspaceId,
|
|
2480
|
+
kind: "workspace",
|
|
2028
2481
|
root: opened.root,
|
|
2029
2482
|
mode: opened.mode,
|
|
2030
2483
|
...(opened.sourceRoot ? { sourceRoot: opened.sourceRoot } : {}),
|
|
@@ -2202,6 +2655,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2202
2655
|
tool: "open_workspace",
|
|
2203
2656
|
card: {
|
|
2204
2657
|
workspaceId: workspace.id,
|
|
2658
|
+
kind: "workspace",
|
|
2205
2659
|
root: workspace.root,
|
|
2206
2660
|
path: workspace.root,
|
|
2207
2661
|
mode: workspace.mode,
|
|
@@ -2234,6 +2688,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2234
2688
|
structuredContent: {
|
|
2235
2689
|
action: "open",
|
|
2236
2690
|
workspaceId: workspace.id,
|
|
2691
|
+
kind: "workspace",
|
|
2237
2692
|
root: workspace.root,
|
|
2238
2693
|
mode: workspace.mode,
|
|
2239
2694
|
sourceRoot: workspace.sourceRoot,
|
|
@@ -2261,18 +2716,40 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2261
2716
|
return response;
|
|
2262
2717
|
});
|
|
2263
2718
|
registerActivityQueryTools(server, activityQueries, connectionScopeId, toolWidgetDescriptorMeta(config, "activity")._meta, config.activityPanelExpanded, config.logging, workspacePanelState, {
|
|
2264
|
-
panel: async (workspaceId, conversationScopeId) =>
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2719
|
+
panel: async (workspaceId, conversationScopeId) => {
|
|
2720
|
+
if (compositeWorkspaces.has(workspaceId)) {
|
|
2721
|
+
return compositeActivity.beginPanel(workspaceId, conversationScopeId);
|
|
2722
|
+
}
|
|
2723
|
+
return remoteWorkspaces.has(workspaceId)
|
|
2724
|
+
? remoteWorkspaces.activityPanel(workspaceId, conversationScopeId)
|
|
2725
|
+
: undefined;
|
|
2726
|
+
},
|
|
2727
|
+
snapshot: async (input, conversationScopeId) => {
|
|
2728
|
+
const compositeTurnId = input.turnId ?? (input.workspaceId && compositeWorkspaces.has(input.workspaceId)
|
|
2729
|
+
? compositeActivity.currentTurnId(conversationScopeId, input.workspaceId)
|
|
2730
|
+
: undefined);
|
|
2731
|
+
if (compositeTurnId) {
|
|
2732
|
+
const composite = await compositeActivity.snapshot(compositeTurnId, input.knownRevision);
|
|
2733
|
+
if (composite)
|
|
2734
|
+
return composite;
|
|
2735
|
+
}
|
|
2736
|
+
return remoteWorkspaces.activitySnapshot(input, conversationScopeId);
|
|
2737
|
+
},
|
|
2738
|
+
detail: async (turnId, activityId, conversationScopeId) => {
|
|
2739
|
+
const composite = await compositeActivity.detail(turnId, activityId);
|
|
2740
|
+
return composite ?? remoteWorkspaces.activityDetail(turnId, activityId, conversationScopeId);
|
|
2741
|
+
},
|
|
2742
|
+
output: async (turnId, outputId, conversationScopeId) => {
|
|
2743
|
+
const composite = await compositeActivity.output(turnId, outputId);
|
|
2744
|
+
return composite ?? remoteWorkspaces.activityOutput(turnId, outputId, conversationScopeId);
|
|
2745
|
+
},
|
|
2270
2746
|
});
|
|
2271
2747
|
registerAppTool(server, toolNames.capability, {
|
|
2272
2748
|
title: "Use optional capability",
|
|
2273
2749
|
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.",
|
|
2274
2750
|
inputSchema: {
|
|
2275
2751
|
workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
|
|
2752
|
+
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name whose capability surface is used."),
|
|
2276
2753
|
name: z
|
|
2277
2754
|
.string()
|
|
2278
2755
|
.regex(/^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/)
|
|
@@ -2304,17 +2781,20 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2304
2781
|
idempotentHint: false,
|
|
2305
2782
|
openWorldHint: true,
|
|
2306
2783
|
},
|
|
2307
|
-
}, async ({ workspaceId, name, action, arguments: capabilityArguments, file }, extra) => {
|
|
2308
|
-
|
|
2309
|
-
|
|
2784
|
+
}, async ({ workspaceId, member, name, action, arguments: capabilityArguments, file }, extra) => {
|
|
2785
|
+
const target = resolveExecutionTarget(workspaceId, member);
|
|
2786
|
+
const executionWorkspaceId = target.executionWorkspaceId;
|
|
2787
|
+
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
2788
|
+
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
2789
|
+
return presentExecutionResult(await remoteWorkspaces.capability(executionWorkspaceId, {
|
|
2310
2790
|
name,
|
|
2311
2791
|
action,
|
|
2312
2792
|
...(capabilityArguments !== undefined ? { arguments: capabilityArguments } : {}),
|
|
2313
2793
|
...(file !== undefined ? { file } : {}),
|
|
2314
|
-
}, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
2794
|
+
}, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
2315
2795
|
}
|
|
2316
2796
|
if (action === "run" && name === "batch.execute") {
|
|
2317
|
-
const workspace = workspaces.getWorkspace(
|
|
2797
|
+
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
2318
2798
|
const startedAt = performance.now();
|
|
2319
2799
|
try {
|
|
2320
2800
|
const execution = await capabilityRegistry.run(name, capabilityArguments ?? {}, capabilityContextFor(workspace), {
|
|
@@ -2335,7 +2815,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2335
2815
|
success: true,
|
|
2336
2816
|
durationMs: Math.round(performance.now() - startedAt),
|
|
2337
2817
|
});
|
|
2338
|
-
return result;
|
|
2818
|
+
return presentExecutionResult(result, target);
|
|
2339
2819
|
}
|
|
2340
2820
|
catch (error) {
|
|
2341
2821
|
if (extra.signal.aborted)
|
|
@@ -2358,18 +2838,14 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2358
2838
|
capability: name,
|
|
2359
2839
|
action,
|
|
2360
2840
|
}, result.content, startedAt);
|
|
2361
|
-
return result;
|
|
2841
|
+
return presentExecutionResult(result, target);
|
|
2362
2842
|
}
|
|
2363
2843
|
}
|
|
2364
2844
|
if (action === "run") {
|
|
2365
|
-
return coreOperations.capabilityRun({ workspaceId, name, arguments: capabilityArguments, file },
|
|
2366
|
-
requestMeta: extra._meta,
|
|
2367
|
-
signal: extra.signal,
|
|
2368
|
-
sessionId: extra.sessionId,
|
|
2369
|
-
});
|
|
2845
|
+
return presentExecutionResult(await coreOperations.capabilityRun({ workspaceId: executionWorkspaceId, name, arguments: capabilityArguments, file }, executionContext), target);
|
|
2370
2846
|
}
|
|
2371
|
-
const workspace = workspaces.getWorkspace(
|
|
2372
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(extra._meta, extra.sessionId), { workspaceId, name, action, arguments: capabilityArguments, file }, {
|
|
2847
|
+
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
2848
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(extra._meta, extra.sessionId), activityRequestFor({ workspaceId: executionWorkspaceId, name, action, arguments: capabilityArguments, file }, executionContext), {
|
|
2373
2849
|
signal: extra.signal,
|
|
2374
2850
|
tool: toolNames.capability,
|
|
2375
2851
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -2422,11 +2898,11 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2422
2898
|
return result;
|
|
2423
2899
|
}
|
|
2424
2900
|
},
|
|
2425
|
-
});
|
|
2901
|
+
}, activityRelationFor(executionContext)).then((result) => presentExecutionResult(result, target));
|
|
2426
2902
|
});
|
|
2427
2903
|
registerAppTool(server, toolNames.closeWorkspace, {
|
|
2428
2904
|
title: "Close workspace",
|
|
2429
|
-
description: "Close one workspace after the user chooses cleanup. Checkout-backed workspaces release only the logical handle. Managed-worktree-backed workspaces
|
|
2905
|
+
description: "Close one workspace after the user chooses cleanup. Composite Workspaces dissolve here: only the Composite identity and member links are removed; member Workspaces, files, processes, worktrees, and relay routes remain intact. Checkout-backed workspaces release only the logical handle. Managed-worktree-backed workspaces run the safe finalize lifecycle (hooks, commit, fast-forward integration, cleanup) and require commitMessage. Running processes block ordinary Workspace closure.",
|
|
2430
2906
|
inputSchema: {
|
|
2431
2907
|
workspaceId: z.string().describe("Workspace identifier to close."),
|
|
2432
2908
|
commitMessage: z
|
|
@@ -2437,7 +2913,15 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2437
2913
|
},
|
|
2438
2914
|
outputSchema: resultOutputSchema({
|
|
2439
2915
|
workspaceId: z.string(),
|
|
2440
|
-
|
|
2916
|
+
kind: z.enum(["workspace", "composite"]).optional(),
|
|
2917
|
+
mode: z.enum(["checkout", "worktree"]).optional(),
|
|
2918
|
+
name: z.string().optional(),
|
|
2919
|
+
members: z.array(z.object({
|
|
2920
|
+
name: z.string(),
|
|
2921
|
+
purpose: z.string(),
|
|
2922
|
+
workspaceId: z.string(),
|
|
2923
|
+
})).optional(),
|
|
2924
|
+
dissolved: z.boolean().optional(),
|
|
2441
2925
|
sourceRoot: z.string().optional(),
|
|
2442
2926
|
branch: z.string().optional(),
|
|
2443
2927
|
targetBranch: z.string().optional(),
|
|
@@ -2449,6 +2933,43 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2449
2933
|
_meta: {},
|
|
2450
2934
|
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
2451
2935
|
}, async ({ workspaceId, commitMessage }, extra) => {
|
|
2936
|
+
if (compositeWorkspaces.has(workspaceId)) {
|
|
2937
|
+
if (commitMessage !== undefined) {
|
|
2938
|
+
throw new Error("close_workspace commitMessage is not valid when dissolving a Composite Workspace.");
|
|
2939
|
+
}
|
|
2940
|
+
const composite = compositeWorkspaces.dissolve(workspaceId);
|
|
2941
|
+
compositeActivity.forgetComposite(workspaceId);
|
|
2942
|
+
workspacePanelStates.delete(workspaceId);
|
|
2943
|
+
const result = [
|
|
2944
|
+
`Dissolved Composite Workspace ${composite.name} (${workspaceId}).`,
|
|
2945
|
+
composite.members.length > 0
|
|
2946
|
+
? `Preserved member Workspaces: ${composite.members.map((member) => `${member.name} [${member.workspaceId}]`).join(", ")}.`
|
|
2947
|
+
: "The Composite Workspace had no members.",
|
|
2948
|
+
"Member Workspace handles, managed worktrees, processes, files, and Workspace Relay routes were not closed or cleaned up.",
|
|
2949
|
+
].join("\n");
|
|
2950
|
+
return {
|
|
2951
|
+
content: [textBlock(result)],
|
|
2952
|
+
_meta: {
|
|
2953
|
+
tool: toolNames.closeWorkspace,
|
|
2954
|
+
card: {
|
|
2955
|
+
workspaceId,
|
|
2956
|
+
kind: "composite",
|
|
2957
|
+
name: composite.name,
|
|
2958
|
+
members: composite.members,
|
|
2959
|
+
dissolved: true,
|
|
2960
|
+
payload: { content: [textBlock(result)] },
|
|
2961
|
+
},
|
|
2962
|
+
},
|
|
2963
|
+
structuredContent: {
|
|
2964
|
+
result,
|
|
2965
|
+
workspaceId,
|
|
2966
|
+
kind: "composite",
|
|
2967
|
+
name: composite.name,
|
|
2968
|
+
members: composite.members,
|
|
2969
|
+
dissolved: true,
|
|
2970
|
+
},
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2452
2973
|
if (remoteWorkspaces.has(workspaceId)) {
|
|
2453
2974
|
const response = await remoteWorkspaces.closeWorkspace(workspaceId, commitMessage, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
2454
2975
|
workspacePanelStates.delete(workspaceId);
|
|
@@ -2565,6 +3086,10 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2565
3086
|
workspaceId: z
|
|
2566
3087
|
.string()
|
|
2567
3088
|
.describe("Workspace identifier returned by open_workspace."),
|
|
3089
|
+
member: z
|
|
3090
|
+
.string()
|
|
3091
|
+
.optional()
|
|
3092
|
+
.describe("Required for a Composite Workspace; explicit member name that owns this operation."),
|
|
2568
3093
|
path: z
|
|
2569
3094
|
.string()
|
|
2570
3095
|
.optional()
|
|
@@ -2602,30 +3127,27 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2602
3127
|
}),
|
|
2603
3128
|
...toolWidgetDescriptorMeta(config, "read"),
|
|
2604
3129
|
annotations: { readOnlyHint: true },
|
|
2605
|
-
}, async ({ workspaceId, path, paths, offset, limit }, extra) => {
|
|
3130
|
+
}, async ({ workspaceId, member, path, paths, offset, limit }, extra) => {
|
|
2606
3131
|
if ((path === undefined) === (paths === undefined)) {
|
|
2607
3132
|
throw new Error("read requires exactly one of path or paths.");
|
|
2608
3133
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
3134
|
+
const target = resolveExecutionTarget(workspaceId, member);
|
|
3135
|
+
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3136
|
+
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3137
|
+
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3138
|
+
return presentExecutionResult(await remoteWorkspaces.read(executionWorkspaceId, { path, paths, offset, limit }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
2611
3139
|
}
|
|
2612
3140
|
if (path !== undefined) {
|
|
2613
|
-
return coreOperations.read({ workspaceId, path, offset, limit },
|
|
2614
|
-
requestMeta: extra._meta,
|
|
2615
|
-
signal: extra.signal,
|
|
2616
|
-
sessionId: extra.sessionId,
|
|
2617
|
-
});
|
|
3141
|
+
return presentExecutionResult(await coreOperations.read({ workspaceId: executionWorkspaceId, path, offset, limit }, executionContext), target);
|
|
2618
3142
|
}
|
|
2619
|
-
const workspace = workspaces.getWorkspace(
|
|
3143
|
+
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
2620
3144
|
let response;
|
|
2621
|
-
await runActivityTool(activityLifecycle, workspace, hostScopeIdFor(extra._meta, extra.sessionId), toolNames.read, { workspaceId, paths, offset, limit }, async (parentContext) => {
|
|
3145
|
+
await runActivityTool(activityLifecycle, workspace, hostScopeIdFor(extra._meta, extra.sessionId), toolNames.read, activityRequestFor({ workspaceId: executionWorkspaceId, paths, offset, limit }, executionContext), async (parentContext) => {
|
|
2622
3146
|
const execution = await executeBulkRead({
|
|
2623
3147
|
paths: paths,
|
|
2624
3148
|
signal: extra.signal,
|
|
2625
|
-
run: (childPath) => coreOperations.read({ workspaceId, path: childPath, offset, limit }, {
|
|
2626
|
-
|
|
2627
|
-
signal: extra.signal,
|
|
2628
|
-
sessionId: extra.sessionId,
|
|
3149
|
+
run: (childPath) => coreOperations.read({ workspaceId: executionWorkspaceId, path: childPath, offset, limit }, {
|
|
3150
|
+
...executionContext,
|
|
2629
3151
|
parentActivityId: parentContext.activityId,
|
|
2630
3152
|
turnId: parentContext.turnId,
|
|
2631
3153
|
}),
|
|
@@ -2666,10 +3188,10 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2666
3188
|
};
|
|
2667
3189
|
}, (summary) => summary.failed > 0
|
|
2668
3190
|
? { type: "failed", error: `${summary.failed} of ${summary.childCount} child Reads failed.` }
|
|
2669
|
-
: { type: "succeeded" });
|
|
3191
|
+
: { type: "succeeded" }, activityRelationFor(executionContext));
|
|
2670
3192
|
if (!response)
|
|
2671
3193
|
throw new Error("Bulk Read completed without a response.");
|
|
2672
|
-
return response;
|
|
3194
|
+
return presentExecutionResult(response, target);
|
|
2673
3195
|
});
|
|
2674
3196
|
if (config.toolMode !== "codex") {
|
|
2675
3197
|
registerAppTool(server, toolNames.write, {
|
|
@@ -2679,6 +3201,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2679
3201
|
workspaceId: z
|
|
2680
3202
|
.string()
|
|
2681
3203
|
.describe("Workspace identifier returned by open_workspace."),
|
|
3204
|
+
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this operation."),
|
|
2682
3205
|
path: z
|
|
2683
3206
|
.string()
|
|
2684
3207
|
.describe("File path to write, relative to the workspace root or absolute inside the OS temp directory."),
|
|
@@ -2687,15 +3210,14 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2687
3210
|
outputSchema: resultOutputSchema(),
|
|
2688
3211
|
...toolWidgetDescriptorMeta(config, "write"),
|
|
2689
3212
|
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
2690
|
-
}, async ({ workspaceId, ...input }, extra) => {
|
|
2691
|
-
|
|
2692
|
-
|
|
3213
|
+
}, async ({ workspaceId, member, ...input }, extra) => {
|
|
3214
|
+
const target = resolveExecutionTarget(workspaceId, member);
|
|
3215
|
+
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3216
|
+
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3217
|
+
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3218
|
+
return presentExecutionResult(await remoteWorkspaces.write(executionWorkspaceId, input, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
2693
3219
|
}
|
|
2694
|
-
return coreOperations.write({ workspaceId, ...input },
|
|
2695
|
-
requestMeta: extra._meta,
|
|
2696
|
-
signal: extra.signal,
|
|
2697
|
-
sessionId: extra.sessionId,
|
|
2698
|
-
});
|
|
3220
|
+
return presentExecutionResult(await coreOperations.write({ workspaceId: executionWorkspaceId, ...input }, executionContext), target);
|
|
2699
3221
|
});
|
|
2700
3222
|
registerAppTool(server, toolNames.edit, {
|
|
2701
3223
|
title: "Edit file",
|
|
@@ -2704,6 +3226,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2704
3226
|
workspaceId: z
|
|
2705
3227
|
.string()
|
|
2706
3228
|
.describe("Workspace identifier returned by open_workspace."),
|
|
3229
|
+
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this operation."),
|
|
2707
3230
|
path: z
|
|
2708
3231
|
.string()
|
|
2709
3232
|
.optional()
|
|
@@ -2737,25 +3260,20 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2737
3260
|
}),
|
|
2738
3261
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
2739
3262
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
2740
|
-
}, async ({ workspaceId, path, paths, edits }, extra) => {
|
|
3263
|
+
}, async ({ workspaceId, member, path, paths, edits }, extra) => {
|
|
2741
3264
|
if ((path === undefined) === (paths === undefined)) {
|
|
2742
3265
|
throw new Error("edit requires exactly one of path or paths.");
|
|
2743
3266
|
}
|
|
2744
|
-
|
|
2745
|
-
|
|
3267
|
+
const target = resolveExecutionTarget(workspaceId, member);
|
|
3268
|
+
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3269
|
+
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3270
|
+
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3271
|
+
return presentExecutionResult(await remoteWorkspaces.edit(executionWorkspaceId, { path, paths, edits }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
2746
3272
|
}
|
|
2747
3273
|
if (path !== undefined) {
|
|
2748
|
-
return coreOperations.edit({ workspaceId, path, edits },
|
|
2749
|
-
requestMeta: extra._meta,
|
|
2750
|
-
signal: extra.signal,
|
|
2751
|
-
sessionId: extra.sessionId,
|
|
2752
|
-
});
|
|
3274
|
+
return presentExecutionResult(await coreOperations.edit({ workspaceId: executionWorkspaceId, path, edits }, executionContext), target);
|
|
2753
3275
|
}
|
|
2754
|
-
return nativeBulkMutations.edit({ workspaceId, paths: paths, edits },
|
|
2755
|
-
requestMeta: extra._meta,
|
|
2756
|
-
signal: extra.signal,
|
|
2757
|
-
sessionId: extra.sessionId,
|
|
2758
|
-
});
|
|
3276
|
+
return presentExecutionResult(await nativeBulkMutations.edit({ workspaceId: executionWorkspaceId, paths: paths, edits }, executionContext), target);
|
|
2759
3277
|
});
|
|
2760
3278
|
}
|
|
2761
3279
|
registerAppTool(server, toolNames.rename, {
|
|
@@ -2763,6 +3281,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2763
3281
|
description: toolDescriptions.rename,
|
|
2764
3282
|
inputSchema: {
|
|
2765
3283
|
workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
|
|
3284
|
+
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this operation."),
|
|
2766
3285
|
path: z.string().describe("Source file or directory path relative to the workspace root, or absolute inside the OS temp directory."),
|
|
2767
3286
|
newPath: z.string().describe("Destination path relative to the workspace root, or absolute inside the OS temp directory. The destination must not already exist."),
|
|
2768
3287
|
},
|
|
@@ -2773,21 +3292,21 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2773
3292
|
}),
|
|
2774
3293
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
2775
3294
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
2776
|
-
}, async ({ workspaceId, path, newPath }, extra) => {
|
|
2777
|
-
|
|
2778
|
-
|
|
3295
|
+
}, async ({ workspaceId, member, path, newPath }, extra) => {
|
|
3296
|
+
const target = resolveExecutionTarget(workspaceId, member);
|
|
3297
|
+
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3298
|
+
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3299
|
+
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3300
|
+
return presentExecutionResult(await remoteWorkspaces.rename(executionWorkspaceId, { path, newPath }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
2779
3301
|
}
|
|
2780
|
-
return coreOperations.rename({ workspaceId, path, newPath },
|
|
2781
|
-
requestMeta: extra._meta,
|
|
2782
|
-
signal: extra.signal,
|
|
2783
|
-
sessionId: extra.sessionId,
|
|
2784
|
-
});
|
|
3302
|
+
return presentExecutionResult(await coreOperations.rename({ workspaceId: executionWorkspaceId, path, newPath }, executionContext), target);
|
|
2785
3303
|
});
|
|
2786
3304
|
registerAppTool(server, toolNames.delete, {
|
|
2787
3305
|
title: "Delete path",
|
|
2788
3306
|
description: toolDescriptions.delete,
|
|
2789
3307
|
inputSchema: {
|
|
2790
3308
|
workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
|
|
3309
|
+
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this operation."),
|
|
2791
3310
|
path: z.string().optional().describe("One file or directory path to delete. Use exactly one of path or paths."),
|
|
2792
3311
|
paths: z
|
|
2793
3312
|
.array(z.string())
|
|
@@ -2813,25 +3332,20 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2813
3332
|
}),
|
|
2814
3333
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
2815
3334
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
2816
|
-
}, async ({ workspaceId, path, paths, recursive }, extra) => {
|
|
3335
|
+
}, async ({ workspaceId, member, path, paths, recursive }, extra) => {
|
|
2817
3336
|
if ((path === undefined) === (paths === undefined)) {
|
|
2818
3337
|
throw new Error("delete requires exactly one of path or paths.");
|
|
2819
3338
|
}
|
|
2820
|
-
|
|
2821
|
-
|
|
3339
|
+
const target = resolveExecutionTarget(workspaceId, member);
|
|
3340
|
+
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3341
|
+
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3342
|
+
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3343
|
+
return presentExecutionResult(await remoteWorkspaces.delete(executionWorkspaceId, { path, paths, recursive }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
2822
3344
|
}
|
|
2823
3345
|
if (path !== undefined) {
|
|
2824
|
-
return coreOperations.delete({ workspaceId, path, recursive },
|
|
2825
|
-
requestMeta: extra._meta,
|
|
2826
|
-
signal: extra.signal,
|
|
2827
|
-
sessionId: extra.sessionId,
|
|
2828
|
-
});
|
|
3346
|
+
return presentExecutionResult(await coreOperations.delete({ workspaceId: executionWorkspaceId, path, recursive }, executionContext), target);
|
|
2829
3347
|
}
|
|
2830
|
-
return nativeBulkMutations.delete({ workspaceId, paths: paths, recursive },
|
|
2831
|
-
requestMeta: extra._meta,
|
|
2832
|
-
signal: extra.signal,
|
|
2833
|
-
sessionId: extra.sessionId,
|
|
2834
|
-
});
|
|
3348
|
+
return presentExecutionResult(await nativeBulkMutations.delete({ workspaceId: executionWorkspaceId, paths: paths, recursive }, executionContext), target);
|
|
2835
3349
|
});
|
|
2836
3350
|
if (config.toolMode === "codex") {
|
|
2837
3351
|
registerAppTool(server, "apply_patch", {
|
|
@@ -2841,6 +3355,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2841
3355
|
workspaceId: z
|
|
2842
3356
|
.string()
|
|
2843
3357
|
.describe("Workspace identifier returned by open_workspace."),
|
|
3358
|
+
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this patch."),
|
|
2844
3359
|
patch: z
|
|
2845
3360
|
.string()
|
|
2846
3361
|
.describe("Patch text enclosed by *** Begin Patch and *** End Patch markers."),
|
|
@@ -2856,9 +3371,15 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2856
3371
|
}),
|
|
2857
3372
|
...toolWidgetDescriptorMeta(config, "edit"),
|
|
2858
3373
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
2859
|
-
}, async ({ workspaceId, patch }, extra) => {
|
|
2860
|
-
const
|
|
2861
|
-
|
|
3374
|
+
}, async ({ workspaceId, member, patch }, extra) => {
|
|
3375
|
+
const target = resolveExecutionTarget(workspaceId, member);
|
|
3376
|
+
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3377
|
+
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3378
|
+
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3379
|
+
return presentExecutionResult(await remoteWorkspaces.applyPatch(executionWorkspaceId, { patch }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3380
|
+
}
|
|
3381
|
+
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
3382
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(extra._meta, extra.sessionId), activityRequestFor({ workspaceId: executionWorkspaceId, patch }, executionContext), {
|
|
2862
3383
|
signal: extra.signal,
|
|
2863
3384
|
tool: "apply_patch",
|
|
2864
3385
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -2886,7 +3407,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2886
3407
|
_meta: {
|
|
2887
3408
|
tool: "apply_patch",
|
|
2888
3409
|
card: {
|
|
2889
|
-
workspaceId,
|
|
3410
|
+
workspaceId: executionWorkspaceId,
|
|
2890
3411
|
path: displayPath,
|
|
2891
3412
|
summary: {
|
|
2892
3413
|
files: applied.files.length,
|
|
@@ -2905,7 +3426,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2905
3426
|
},
|
|
2906
3427
|
};
|
|
2907
3428
|
},
|
|
2908
|
-
});
|
|
3429
|
+
}, activityRelationFor(executionContext)).then((result) => presentExecutionResult(result, target));
|
|
2909
3430
|
});
|
|
2910
3431
|
}
|
|
2911
3432
|
if (config.toolMode !== "codex") {
|
|
@@ -2916,6 +3437,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2916
3437
|
workspaceId: z
|
|
2917
3438
|
.string()
|
|
2918
3439
|
.describe("Workspace identifier returned by open_workspace."),
|
|
3440
|
+
member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this process operation."),
|
|
2919
3441
|
action: z
|
|
2920
3442
|
.enum(["run", "process", "output"])
|
|
2921
3443
|
.optional()
|
|
@@ -2989,9 +3511,12 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2989
3511
|
outputSchema: processOutputSchema(),
|
|
2990
3512
|
...toolWidgetDescriptorMeta(config, "shell"),
|
|
2991
3513
|
annotations: SHELL_TOOL_ANNOTATIONS,
|
|
2992
|
-
}, async ({ workspaceId, action = "run", command, processId, outputId, input, interrupt, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens, }, extra) => {
|
|
2993
|
-
|
|
2994
|
-
|
|
3514
|
+
}, async ({ workspaceId, member, action = "run", command, processId, outputId, input, interrupt, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens, }, extra) => {
|
|
3515
|
+
const target = resolveExecutionTarget(workspaceId, member);
|
|
3516
|
+
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3517
|
+
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3518
|
+
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3519
|
+
return presentExecutionResult(await remoteWorkspaces.bash(executionWorkspaceId, {
|
|
2995
3520
|
action,
|
|
2996
3521
|
...(command !== undefined ? { command } : {}),
|
|
2997
3522
|
...(processId !== undefined ? { processId } : {}),
|
|
@@ -3005,17 +3530,17 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3005
3530
|
...(yieldTimeMs !== undefined ? { yieldTimeMs } : {}),
|
|
3006
3531
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
3007
3532
|
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
3008
|
-
}, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
3533
|
+
}, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3009
3534
|
}
|
|
3010
|
-
const workspace = workspaces.getWorkspace(
|
|
3535
|
+
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
3011
3536
|
if (action === "run") {
|
|
3012
3537
|
if (!command)
|
|
3013
3538
|
throw new Error("bash action=run requires command.");
|
|
3014
3539
|
if (processId !== undefined || outputId !== undefined || input !== undefined || interrupt !== undefined) {
|
|
3015
3540
|
throw new Error("bash action=run does not accept processId, outputId, input, or interrupt.");
|
|
3016
3541
|
}
|
|
3017
|
-
return coreOperations.shellRun({
|
|
3018
|
-
workspaceId,
|
|
3542
|
+
return presentExecutionResult(await coreOperations.shellRun({
|
|
3543
|
+
workspaceId: executionWorkspaceId,
|
|
3019
3544
|
command,
|
|
3020
3545
|
surface: "bash",
|
|
3021
3546
|
tty,
|
|
@@ -3025,11 +3550,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3025
3550
|
yieldTimeMs,
|
|
3026
3551
|
timeoutMs,
|
|
3027
3552
|
maxOutputTokens,
|
|
3028
|
-
},
|
|
3029
|
-
requestMeta: extra._meta,
|
|
3030
|
-
signal: extra.signal,
|
|
3031
|
-
sessionId: extra.sessionId,
|
|
3032
|
-
});
|
|
3553
|
+
}, executionContext), target);
|
|
3033
3554
|
}
|
|
3034
3555
|
if (action === "output") {
|
|
3035
3556
|
if (!outputId)
|
|
@@ -3044,8 +3565,8 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3044
3565
|
tool: toolNames.shell,
|
|
3045
3566
|
invocation: workspaceHookInvocation(workspace),
|
|
3046
3567
|
payload: { action, outputId },
|
|
3047
|
-
operation: async () => durableOutputResponse(toolNames.shell,
|
|
3048
|
-
});
|
|
3568
|
+
operation: async () => durableOutputResponse(toolNames.shell, executionWorkspaceId, readWorkspaceBashOutput(bashOutputStore, executionWorkspaceId, outputId)),
|
|
3569
|
+
}).then((result) => presentExecutionResult(result, target));
|
|
3049
3570
|
}
|
|
3050
3571
|
if (outputId !== undefined)
|
|
3051
3572
|
throw new Error("bash action=process does not accept outputId.");
|
|
@@ -3073,7 +3594,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3073
3594
|
operation: async () => {
|
|
3074
3595
|
const startedAt = performance.now();
|
|
3075
3596
|
const snapshot = await processSessions.write({
|
|
3076
|
-
workspaceId,
|
|
3597
|
+
workspaceId: executionWorkspaceId,
|
|
3077
3598
|
processId,
|
|
3078
3599
|
chars: interrupt ? "\u0003" : input,
|
|
3079
3600
|
columns,
|
|
@@ -3091,7 +3612,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3091
3612
|
success: snapshot.running || snapshot.exitCode === 0,
|
|
3092
3613
|
durationMs: Math.round(performance.now() - startedAt),
|
|
3093
3614
|
});
|
|
3094
|
-
const response = processToolResponse(toolNames.shell,
|
|
3615
|
+
const response = processToolResponse(toolNames.shell, executionWorkspaceId, snapshot, {
|
|
3095
3616
|
action,
|
|
3096
3617
|
processId,
|
|
3097
3618
|
inputLength: input?.length ?? 0,
|
|
@@ -3103,12 +3624,20 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3103
3624
|
if (!snapshot.running) {
|
|
3104
3625
|
recordBashCompletion(activityLifecycle, bashOutputStore, snapshot.outputId);
|
|
3105
3626
|
}
|
|
3106
|
-
return response;
|
|
3627
|
+
return presentExecutionResult(response, target);
|
|
3107
3628
|
},
|
|
3108
3629
|
});
|
|
3109
3630
|
});
|
|
3110
3631
|
}
|
|
3111
|
-
registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, (input, context) => coreOperations.shellRun(input, context)
|
|
3632
|
+
registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, (input, context) => coreOperations.shellRun(input, context), {
|
|
3633
|
+
resolve: resolveExecutionTarget,
|
|
3634
|
+
prepare: prepareExecutionContext,
|
|
3635
|
+
present: presentExecutionResult,
|
|
3636
|
+
isRemote: (workspaceId) => remoteWorkspaces.has(workspaceId),
|
|
3637
|
+
execCommandRemote: (workspaceId, input, conversationScopeId) => remoteWorkspaces.execCommand(workspaceId, input, conversationScopeId),
|
|
3638
|
+
writeStdinRemote: (workspaceId, input, conversationScopeId) => remoteWorkspaces.writeStdin(workspaceId, input, conversationScopeId),
|
|
3639
|
+
hostScopeIdFor,
|
|
3640
|
+
});
|
|
3112
3641
|
return server;
|
|
3113
3642
|
}
|
|
3114
3643
|
export function createServer(config = loadConfig(), options = {}) {
|