@akira-tl/forgerelay 0.8.2 → 0.8.4
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 +16 -0
- package/capabilities/workspace-tasks/GUIDE.md +50 -0
- package/dist/capabilities.js +6 -0
- package/dist/capability-registry.js +96 -21
- package/dist/config.js +11 -0
- package/dist/remote-workspace-relay.js +15 -0
- package/dist/server.js +449 -34
- package/dist/subagents/sessions/capability.js +3 -0
- package/dist/workspace-task-reminders.js +42 -0
- package/dist/workspace-tasks.js +420 -0
- package/dist/workspaces.js +62 -30
- package/docs/chatgpt-coding-workflow.md +13 -3
- package/docs/configuration.md +46 -11
- package/package.json +2 -2
- package/scripts/debug/accept.mjs +298 -0
package/dist/server.js
CHANGED
|
@@ -21,7 +21,7 @@ import { HostTurnStore } from "./activity/host-turn-store.js";
|
|
|
21
21
|
import { registerActivityQueryTools } from "./activity/mcp-query-tools.js";
|
|
22
22
|
import { ActivityLifecycle, } from "./activity/lifecycle.js";
|
|
23
23
|
import { ActivityQueryService } from "./activity/query-service.js";
|
|
24
|
-
import { buildCapabilityFingerprint } from "./capabilities.js";
|
|
24
|
+
import { buildCapabilityFingerprint, loadCapabilityGuides } from "./capabilities.js";
|
|
25
25
|
import { CapabilityError, createCapabilityRegistry, } from "./capability-registry.js";
|
|
26
26
|
import { deletePath, renamePath } from "./file-mutations.js";
|
|
27
27
|
import { downloadIncomingArtifact, isArtifactDownloadSupportedPlatform, } from "./artifact-tools.js";
|
|
@@ -53,6 +53,8 @@ import { ACTIVITY_PANEL_APP_LEGACY_URI, ACTIVITY_PANEL_APP_URI_TEMPLATE, MCP_APP
|
|
|
53
53
|
import { shutdownHttpServer } from "./server-shutdown.js";
|
|
54
54
|
import { formatPathForPrompt } from "./skills.js";
|
|
55
55
|
import { createWorkspaceStore } from "./workspace-store.js";
|
|
56
|
+
import { WorkspaceTaskReminderTracker } from "./workspace-task-reminders.js";
|
|
57
|
+
import { WorkspaceTaskStore } from "./workspace-tasks.js";
|
|
56
58
|
import { formatAgentsPath, WorkspaceRegistry } from "./workspaces.js";
|
|
57
59
|
import { formatAvailableSubagentProfile, summarizeSubagentProfile } from "./subagents/profiles.js";
|
|
58
60
|
import { formatSubagentProviderAvailabilitySummary, formatUnavailableSubagentProvider, getSubagentProviderAvailabilitySnapshot, } from "./subagents/providers/availability.js";
|
|
@@ -258,6 +260,74 @@ const workspaceInventoryPageOutputSchema = z.object({
|
|
|
258
260
|
limit: z.number().int().positive(),
|
|
259
261
|
hasMore: z.boolean(),
|
|
260
262
|
});
|
|
263
|
+
const workspaceTaskInspectionSummaryOutputSchema = z.object({
|
|
264
|
+
level: z.literal("summary"),
|
|
265
|
+
version: z.literal(1),
|
|
266
|
+
revision: z.number().int().nonnegative(),
|
|
267
|
+
lists: z.array(z.object({
|
|
268
|
+
id: z.string(),
|
|
269
|
+
name: z.string(),
|
|
270
|
+
state: z.enum(["active", "archived"]),
|
|
271
|
+
revision: z.number().int().positive(),
|
|
272
|
+
taskCount: z.number().int().nonnegative(),
|
|
273
|
+
unfinishedTaskCount: z.number().int().nonnegative(),
|
|
274
|
+
})),
|
|
275
|
+
});
|
|
276
|
+
const workspaceInspectionMemberOutputSchema = z.object({
|
|
277
|
+
name: z.string(),
|
|
278
|
+
purpose: z.string(),
|
|
279
|
+
workspaceId: z.string(),
|
|
280
|
+
known: z.boolean(),
|
|
281
|
+
location: z.enum(["local", "relay"]).optional(),
|
|
282
|
+
state: z.enum(["active", "stale", "invalid", "closed"]).optional(),
|
|
283
|
+
status: z.string().optional(),
|
|
284
|
+
routeState: z.literal("known").optional(),
|
|
285
|
+
mode: z.enum(["checkout", "worktree"]).optional(),
|
|
286
|
+
rootValid: z.boolean().optional(),
|
|
287
|
+
});
|
|
288
|
+
const workspaceInspectionOutputSchema = z.union([
|
|
289
|
+
z.object({
|
|
290
|
+
workspaceId: z.string(),
|
|
291
|
+
kind: z.literal("workspace"),
|
|
292
|
+
location: z.literal("local"),
|
|
293
|
+
label: z.string(),
|
|
294
|
+
root: z.string(),
|
|
295
|
+
status: z.string(),
|
|
296
|
+
state: z.enum(["active", "stale", "invalid", "closed"]),
|
|
297
|
+
mode: z.enum(["checkout", "worktree"]),
|
|
298
|
+
sourceRoot: z.string().optional(),
|
|
299
|
+
branch: z.string().optional(),
|
|
300
|
+
targetBranch: z.string().optional(),
|
|
301
|
+
managed: z.boolean(),
|
|
302
|
+
createdAt: z.string(),
|
|
303
|
+
lastUsedAt: z.string(),
|
|
304
|
+
idleMs: z.number().nonnegative(),
|
|
305
|
+
rootValid: z.boolean(),
|
|
306
|
+
taskSummary: workspaceTaskInspectionSummaryOutputSchema.optional(),
|
|
307
|
+
}),
|
|
308
|
+
z.object({
|
|
309
|
+
workspaceId: z.string(),
|
|
310
|
+
kind: z.literal("workspace"),
|
|
311
|
+
location: z.literal("relay"),
|
|
312
|
+
root: z.string(),
|
|
313
|
+
routeState: z.literal("known"),
|
|
314
|
+
mode: z.enum(["checkout", "worktree"]),
|
|
315
|
+
sourceRoot: z.string().optional(),
|
|
316
|
+
relay: z.string(),
|
|
317
|
+
executionLocation: z.string(),
|
|
318
|
+
}),
|
|
319
|
+
z.object({
|
|
320
|
+
workspaceId: z.string(),
|
|
321
|
+
kind: z.literal("composite"),
|
|
322
|
+
name: z.string(),
|
|
323
|
+
status: z.enum(["active", "closed"]),
|
|
324
|
+
state: z.enum(["active", "closed"]),
|
|
325
|
+
createdAt: z.string(),
|
|
326
|
+
lastUsedAt: z.string(),
|
|
327
|
+
members: z.array(workspaceInspectionMemberOutputSchema),
|
|
328
|
+
taskSummary: workspaceTaskInspectionSummaryOutputSchema.optional(),
|
|
329
|
+
}),
|
|
330
|
+
]);
|
|
261
331
|
const reviewFileOutputSchema = z.object({
|
|
262
332
|
path: z.string(),
|
|
263
333
|
previousPath: z.string().optional(),
|
|
@@ -344,6 +414,17 @@ function logFailedToolResponse(config, fields, content, startedAt) {
|
|
|
344
414
|
function textBlock(text) {
|
|
345
415
|
return { type: "text", text };
|
|
346
416
|
}
|
|
417
|
+
function attachWorkspaceTaskReminder(result, reminder) {
|
|
418
|
+
if (!reminder || toolResultIsError(result) || typeof result !== "object" || result === null)
|
|
419
|
+
return result;
|
|
420
|
+
const content = result.content;
|
|
421
|
+
if (!Array.isArray(content))
|
|
422
|
+
return result;
|
|
423
|
+
return {
|
|
424
|
+
...result,
|
|
425
|
+
content: [...content, textBlock(reminder)],
|
|
426
|
+
};
|
|
427
|
+
}
|
|
347
428
|
function textSummary(content) {
|
|
348
429
|
const text = contentText(content);
|
|
349
430
|
return {
|
|
@@ -821,6 +902,7 @@ function workspaceHookInvocation(workspace) {
|
|
|
821
902
|
function capabilityContextFor(workspace) {
|
|
822
903
|
return {
|
|
823
904
|
workspaceId: workspace.id,
|
|
905
|
+
workspaceKind: "workspace",
|
|
824
906
|
workspaceRoot: workspace.root,
|
|
825
907
|
guides: workspace.capabilityGuides.map((guide) => ({
|
|
826
908
|
name: guide.name,
|
|
@@ -830,6 +912,66 @@ function capabilityContextFor(workspace) {
|
|
|
830
912
|
})),
|
|
831
913
|
};
|
|
832
914
|
}
|
|
915
|
+
function compositeCapabilityContext(workspaceId, guides) {
|
|
916
|
+
return {
|
|
917
|
+
workspaceId,
|
|
918
|
+
workspaceKind: "composite",
|
|
919
|
+
guides: guides.map((guide) => ({
|
|
920
|
+
name: guide.name,
|
|
921
|
+
description: guide.description,
|
|
922
|
+
whenToRead: guide.whenToRead,
|
|
923
|
+
path: formatPathForPrompt(guide.filePath),
|
|
924
|
+
})),
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function requireCapabilityWorkspaceRoot(context) {
|
|
928
|
+
if (!context.workspaceRoot) {
|
|
929
|
+
throw new CapabilityError("capability_unavailable", `Capability execution requires a filesystem-backed Workspace; ${context.workspaceId} is ${context.workspaceKind}.`);
|
|
930
|
+
}
|
|
931
|
+
return context.workspaceRoot;
|
|
932
|
+
}
|
|
933
|
+
function runWorkspaceTasksCapability(store, workspaceId, input) {
|
|
934
|
+
switch (input.operation) {
|
|
935
|
+
case "get":
|
|
936
|
+
if (input.level === "headers")
|
|
937
|
+
return store.readHeaders(workspaceId, input.listId);
|
|
938
|
+
if (input.level === "detail")
|
|
939
|
+
return store.readTaskDetail(workspaceId, input.listId, input.taskId);
|
|
940
|
+
return store.readSummary(workspaceId);
|
|
941
|
+
case "list.create":
|
|
942
|
+
store.createList(workspaceId, { name: input.name, position: input.position });
|
|
943
|
+
return store.readSummary(workspaceId);
|
|
944
|
+
case "list.update":
|
|
945
|
+
store.updateList(workspaceId, input.listId, {
|
|
946
|
+
name: input.name,
|
|
947
|
+
state: input.state,
|
|
948
|
+
position: input.position,
|
|
949
|
+
});
|
|
950
|
+
return store.readSummary(workspaceId);
|
|
951
|
+
case "list.delete":
|
|
952
|
+
store.deleteList(workspaceId, input.listId);
|
|
953
|
+
return store.readSummary(workspaceId);
|
|
954
|
+
case "task.create":
|
|
955
|
+
store.createTask(workspaceId, input.listId, {
|
|
956
|
+
subject: input.subject,
|
|
957
|
+
content: input.content,
|
|
958
|
+
status: input.status,
|
|
959
|
+
position: input.position,
|
|
960
|
+
});
|
|
961
|
+
return store.readHeaders(workspaceId, input.listId);
|
|
962
|
+
case "task.update":
|
|
963
|
+
store.updateTask(workspaceId, input.listId, input.taskId, {
|
|
964
|
+
subject: input.subject,
|
|
965
|
+
content: input.content,
|
|
966
|
+
status: input.status,
|
|
967
|
+
position: input.position,
|
|
968
|
+
});
|
|
969
|
+
return store.readHeaders(workspaceId, input.listId);
|
|
970
|
+
case "task.delete":
|
|
971
|
+
store.deleteTask(workspaceId, input.listId, input.taskId);
|
|
972
|
+
return store.readHeaders(workspaceId, input.listId);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
833
975
|
async function reviewWorkspaceChanges(reviewCheckpoints, workspace) {
|
|
834
976
|
return reviewCheckpoints.reviewChanges({
|
|
835
977
|
workspaceId: workspace.id,
|
|
@@ -1054,7 +1196,7 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
1054
1196
|
const target = routing.resolve(workspaceId, member);
|
|
1055
1197
|
const context = await routing.prepare(target, extra._meta, extra.signal, extra.sessionId);
|
|
1056
1198
|
if (routing.isRemote(target.executionWorkspaceId)) {
|
|
1057
|
-
return routing.
|
|
1199
|
+
return routing.presentSemantic(await routing.execCommandRemote(target.executionWorkspaceId, {
|
|
1058
1200
|
cmd,
|
|
1059
1201
|
...(tty !== undefined ? { tty } : {}),
|
|
1060
1202
|
...(columns !== undefined ? { columns } : {}),
|
|
@@ -1065,7 +1207,7 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
1065
1207
|
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
1066
1208
|
}, routing.hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
1067
1209
|
}
|
|
1068
|
-
return routing.
|
|
1210
|
+
return routing.presentSemantic(await shellRun({
|
|
1069
1211
|
workspaceId: target.executionWorkspaceId,
|
|
1070
1212
|
command: cmd,
|
|
1071
1213
|
surface: "exec_command",
|
|
@@ -1192,6 +1334,9 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1192
1334
|
const connectionScopeId = `mcp-connection:${randomUUID()}`;
|
|
1193
1335
|
const remoteWorkspaces = new RemoteWorkspaceRelay(config.configDir, config.stateDir);
|
|
1194
1336
|
const compositeWorkspaces = new CompositeWorkspaceRegistry(config.stateDir);
|
|
1337
|
+
const workspaceTasks = new WorkspaceTaskStore(config.stateDir);
|
|
1338
|
+
const taskReminders = new WorkspaceTaskReminderTracker(config.taskReminderInterval, workspaceTasks);
|
|
1339
|
+
const compositeTaskGuides = loadCapabilityGuides(config).filter((guide) => guide.name === "workspace-tasks");
|
|
1195
1340
|
const compositeActivity = new CompositeActivityCoordinator(compositeWorkspaces, activityQueries, remoteWorkspaces);
|
|
1196
1341
|
const resolveExecutionTarget = (workspaceId, memberName) => {
|
|
1197
1342
|
if (!compositeWorkspaces.has(workspaceId)) {
|
|
@@ -1224,6 +1369,25 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1224
1369
|
: remapCompositeToolResult(result, target.executionWorkspaceId, target.compositeWorkspaceId, target.memberName);
|
|
1225
1370
|
return subagentMcp.decorateResult(target.executionWorkspaceId, presented);
|
|
1226
1371
|
};
|
|
1372
|
+
const taskReminderWorkspaceIdFor = (target) => {
|
|
1373
|
+
if (target.compositeWorkspaceId)
|
|
1374
|
+
return target.compositeWorkspaceId;
|
|
1375
|
+
if (remoteWorkspaces.has(target.executionWorkspaceId))
|
|
1376
|
+
return undefined;
|
|
1377
|
+
try {
|
|
1378
|
+
return workspaces.getWorkspace(target.executionWorkspaceId).id;
|
|
1379
|
+
}
|
|
1380
|
+
catch {
|
|
1381
|
+
return undefined;
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
const presentSemanticWorkResult = (result, target) => {
|
|
1385
|
+
const presented = presentExecutionResult(result, target);
|
|
1386
|
+
if (toolResultIsError(presented))
|
|
1387
|
+
return presented;
|
|
1388
|
+
const reminderWorkspaceId = taskReminderWorkspaceIdFor(target);
|
|
1389
|
+
return attachWorkspaceTaskReminder(presented, reminderWorkspaceId ? taskReminders.recordWork(reminderWorkspaceId) : undefined);
|
|
1390
|
+
};
|
|
1227
1391
|
const hostScopeIdFor = (requestMeta, transportSessionId) => hostConversationScopeId(requestMeta, transportSessionId, connectionScopeId);
|
|
1228
1392
|
const prepareExecutionContext = async (target, requestMeta, signal, sessionId) => {
|
|
1229
1393
|
const conversationScopeId = hostScopeIdFor(requestMeta, sessionId);
|
|
@@ -1248,6 +1412,15 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1248
1412
|
const capabilityRegistry = createCapabilityRegistry({
|
|
1249
1413
|
inspectHooks: (workspaceRoot) => checkHookConfiguration(workspaceRoot, config.hooks),
|
|
1250
1414
|
...subagentMcp.registryDependencies,
|
|
1415
|
+
workspaceTasks: {
|
|
1416
|
+
available: true,
|
|
1417
|
+
run: async (input, context) => {
|
|
1418
|
+
const value = runWorkspaceTasksCapability(workspaceTasks, context.workspaceId, input);
|
|
1419
|
+
if (input.operation !== "get")
|
|
1420
|
+
taskReminders.reset(context.workspaceId);
|
|
1421
|
+
return { value };
|
|
1422
|
+
},
|
|
1423
|
+
},
|
|
1251
1424
|
batchExecute: {
|
|
1252
1425
|
available: batchExecuteAvailable,
|
|
1253
1426
|
unavailableReason: batchExecuteAvailable
|
|
@@ -1270,7 +1443,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1270
1443
|
run: async (input, context, options) => {
|
|
1271
1444
|
try {
|
|
1272
1445
|
return {
|
|
1273
|
-
value: await codeIntelligence.run(context
|
|
1446
|
+
value: await codeIntelligence.run(requireCapabilityWorkspaceRoot(context), input, { signal: options.signal }),
|
|
1274
1447
|
};
|
|
1275
1448
|
}
|
|
1276
1449
|
catch (error) {
|
|
@@ -1289,7 +1462,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1289
1462
|
run: async (context) => {
|
|
1290
1463
|
const review = await reviewWorkspaceChanges(reviewCheckpoints, {
|
|
1291
1464
|
id: context.workspaceId,
|
|
1292
|
-
root: context
|
|
1465
|
+
root: requireCapabilityWorkspaceRoot(context),
|
|
1293
1466
|
});
|
|
1294
1467
|
return {
|
|
1295
1468
|
value: {
|
|
@@ -1317,7 +1490,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1317
1490
|
const downloaded = await downloadIncomingArtifact({
|
|
1318
1491
|
registry: incomingArtifactRegistry,
|
|
1319
1492
|
workspaceId: context.workspaceId,
|
|
1320
|
-
workspaceRoot: context
|
|
1493
|
+
workspaceRoot: requireCapabilityWorkspaceRoot(context),
|
|
1321
1494
|
maxFileBytes: config.artifactMaxFileBytes,
|
|
1322
1495
|
file: input.file,
|
|
1323
1496
|
path: input.path,
|
|
@@ -1979,9 +2152,9 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1979
2152
|
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.",
|
|
1980
2153
|
inputSchema: {
|
|
1981
2154
|
action: z
|
|
1982
|
-
.enum(["open", "list", "member"])
|
|
2155
|
+
.enum(["open", "list", "inspect", "member"])
|
|
1983
2156
|
.optional()
|
|
1984
|
-
.describe("Defaults to open. Use list
|
|
2157
|
+
.describe("Defaults to open. Use list for lightweight inventory, inspect for bounded read-only metadata about one known Workspace without opening/resuming it, or member to change Composite membership."),
|
|
1985
2158
|
memberAction: z
|
|
1986
2159
|
.enum(["add", "update", "remove"])
|
|
1987
2160
|
.optional()
|
|
@@ -2021,7 +2194,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2021
2194
|
workspaceId: z
|
|
2022
2195
|
.string()
|
|
2023
2196
|
.optional()
|
|
2024
|
-
.describe("For action=open, an existing Workspace ID to resume or reuse. Historical duplicate IDs from earlier ForgeRelay versions may resolve to the canonical Workspace ID. For action=list, filters inventory
|
|
2197
|
+
.describe("For action=open, an existing Workspace ID to resume or reuse. Historical duplicate IDs from earlier ForgeRelay versions may resolve to the canonical Workspace ID. For action=list, filters inventory. For action=inspect, identifies the single Workspace to inspect without opening or binding it."),
|
|
2025
2198
|
mode: z
|
|
2026
2199
|
.enum(["checkout", "worktree"])
|
|
2027
2200
|
.optional()
|
|
@@ -2073,7 +2246,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2073
2246
|
.describe("For action=list, maximum records to return. Defaults to 50; maximum 100."),
|
|
2074
2247
|
},
|
|
2075
2248
|
outputSchema: {
|
|
2076
|
-
action: z.enum(["open", "list", "member"]),
|
|
2249
|
+
action: z.enum(["open", "list", "inspect", "member"]),
|
|
2077
2250
|
workspaceId: z.string().optional(),
|
|
2078
2251
|
memberAction: z.enum(["add", "update", "remove"]).optional(),
|
|
2079
2252
|
kind: z.enum(["workspace", "composite"]).optional(),
|
|
@@ -2148,6 +2321,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2148
2321
|
})).optional(),
|
|
2149
2322
|
summary: workspaceInventorySummaryOutputSchema.optional(),
|
|
2150
2323
|
page: workspaceInventoryPageOutputSchema.optional(),
|
|
2324
|
+
inspection: workspaceInspectionOutputSchema.optional(),
|
|
2151
2325
|
instruction: z.string(),
|
|
2152
2326
|
},
|
|
2153
2327
|
_meta: {},
|
|
@@ -2161,6 +2335,121 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2161
2335
|
const startedAt = performance.now();
|
|
2162
2336
|
const conversationScopeId = openAiConversationScopeId(_meta);
|
|
2163
2337
|
const protectedWorkspaceIds = processSessions.activeWorkspaceIds();
|
|
2338
|
+
const inspectTaskSummary = (targetWorkspaceId) => {
|
|
2339
|
+
try {
|
|
2340
|
+
const summary = workspaceTasks.inspectSummary(targetWorkspaceId);
|
|
2341
|
+
if (!summary)
|
|
2342
|
+
return undefined;
|
|
2343
|
+
const { fingerprint: _fingerprint, ...inspectionSummary } = summary;
|
|
2344
|
+
return inspectionSummary;
|
|
2345
|
+
}
|
|
2346
|
+
catch {
|
|
2347
|
+
return undefined;
|
|
2348
|
+
}
|
|
2349
|
+
};
|
|
2350
|
+
const inspectCompositeMember = async (entry) => {
|
|
2351
|
+
if (remoteWorkspaces.has(entry.workspaceId)) {
|
|
2352
|
+
const inspected = remoteWorkspaces.inspectWorkspace(entry.workspaceId);
|
|
2353
|
+
return {
|
|
2354
|
+
name: entry.name,
|
|
2355
|
+
purpose: entry.purpose,
|
|
2356
|
+
workspaceId: entry.workspaceId,
|
|
2357
|
+
known: true,
|
|
2358
|
+
location: inspected.location,
|
|
2359
|
+
routeState: inspected.routeState,
|
|
2360
|
+
mode: inspected.mode,
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
try {
|
|
2364
|
+
const inspected = await workspaces.inspectWorkspace(entry.workspaceId);
|
|
2365
|
+
return {
|
|
2366
|
+
name: entry.name,
|
|
2367
|
+
purpose: entry.purpose,
|
|
2368
|
+
workspaceId: entry.workspaceId,
|
|
2369
|
+
known: true,
|
|
2370
|
+
location: inspected.location,
|
|
2371
|
+
state: inspected.state,
|
|
2372
|
+
status: inspected.status,
|
|
2373
|
+
mode: inspected.mode,
|
|
2374
|
+
rootValid: inspected.rootValid,
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
catch {
|
|
2378
|
+
return {
|
|
2379
|
+
name: entry.name,
|
|
2380
|
+
purpose: entry.purpose,
|
|
2381
|
+
workspaceId: entry.workspaceId,
|
|
2382
|
+
known: false,
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
};
|
|
2386
|
+
if (action === "inspect") {
|
|
2387
|
+
if (!workspaceId) {
|
|
2388
|
+
throw new Error("open_workspace action=inspect requires workspaceId.");
|
|
2389
|
+
}
|
|
2390
|
+
if (memberAction !== undefined || member !== undefined || kind !== undefined || name !== undefined ||
|
|
2391
|
+
memberName !== undefined || path !== undefined || relay !== undefined || mode !== undefined ||
|
|
2392
|
+
baseRef !== undefined || newWorktree !== undefined || newWorkspace !== undefined || context !== undefined ||
|
|
2393
|
+
root !== undefined || status !== undefined || state !== undefined || staleOnly !== undefined ||
|
|
2394
|
+
offset !== undefined || limit !== undefined) {
|
|
2395
|
+
throw new Error("open_workspace action=inspect accepts only workspaceId. It never opens, resumes, binds, or mutates the inspected Workspace.");
|
|
2396
|
+
}
|
|
2397
|
+
let inspection;
|
|
2398
|
+
if (compositeWorkspaces.has(workspaceId)) {
|
|
2399
|
+
const composite = compositeWorkspaces.get(workspaceId);
|
|
2400
|
+
const members = await Promise.all(composite.members.map(inspectCompositeMember));
|
|
2401
|
+
const taskSummary = inspectTaskSummary(composite.id);
|
|
2402
|
+
inspection = {
|
|
2403
|
+
workspaceId: composite.id,
|
|
2404
|
+
kind: "composite",
|
|
2405
|
+
name: composite.name,
|
|
2406
|
+
status: composite.status,
|
|
2407
|
+
state: composite.status,
|
|
2408
|
+
createdAt: composite.createdAt,
|
|
2409
|
+
lastUsedAt: composite.lastUsedAt,
|
|
2410
|
+
members,
|
|
2411
|
+
...(taskSummary ? { taskSummary } : {}),
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
else if (remoteWorkspaces.has(workspaceId)) {
|
|
2415
|
+
inspection = remoteWorkspaces.inspectWorkspace(workspaceId);
|
|
2416
|
+
}
|
|
2417
|
+
else {
|
|
2418
|
+
const inspected = await workspaces.inspectWorkspace(workspaceId);
|
|
2419
|
+
const taskSummary = inspectTaskSummary(inspected.workspaceId);
|
|
2420
|
+
inspection = {
|
|
2421
|
+
...inspected,
|
|
2422
|
+
...(taskSummary ? { taskSummary } : {}),
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
const instruction = "This is a bounded read-only Workspace inspection. It does not open/resume the target, deliver bootstrap context, bind this conversation, or grant file/process/Git/Capability authority. Explicitly open the Workspace before modifying or executing against it.";
|
|
2426
|
+
const result = [
|
|
2427
|
+
`Inspected Workspace ${inspection.workspaceId} (${inspection.kind}).`,
|
|
2428
|
+
inspection.kind === "composite"
|
|
2429
|
+
? `State: ${inspection.state}; members=${inspection.members.length}.`
|
|
2430
|
+
: inspection.location === "relay"
|
|
2431
|
+
? `Route: ${inspection.routeState}; mode=${inspection.mode}; location=${inspection.location}. Remote lifecycle is not probed by inspection.`
|
|
2432
|
+
: `State: ${inspection.state}; mode=${inspection.mode}; location=${inspection.location}.`,
|
|
2433
|
+
"Task summary is included only when durable local Task state already exists; Task bodies are never returned.",
|
|
2434
|
+
instruction,
|
|
2435
|
+
].join("\n");
|
|
2436
|
+
logToolCall(config, {
|
|
2437
|
+
tool: "open_workspace",
|
|
2438
|
+
action: "inspect",
|
|
2439
|
+
success: true,
|
|
2440
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
2441
|
+
});
|
|
2442
|
+
return {
|
|
2443
|
+
content: [textBlock(result)],
|
|
2444
|
+
structuredContent: {
|
|
2445
|
+
action: "inspect",
|
|
2446
|
+
workspaceId: inspection.workspaceId,
|
|
2447
|
+
kind: inspection.kind,
|
|
2448
|
+
inspection,
|
|
2449
|
+
instruction,
|
|
2450
|
+
},
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
2164
2453
|
if (action === "member") {
|
|
2165
2454
|
if (!workspaceId || !compositeWorkspaces.has(workspaceId)) {
|
|
2166
2455
|
throw new Error("open_workspace action=member requires an existing Composite Workspace workspaceId.");
|
|
@@ -2398,6 +2687,15 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2398
2687
|
const composite = workspaceId !== undefined
|
|
2399
2688
|
? compositeWorkspaces.open(workspaceId)
|
|
2400
2689
|
: compositeWorkspaces.create(name ?? "");
|
|
2690
|
+
workspaceTasks.initializeWorkspace(composite.id);
|
|
2691
|
+
const compositeTaskContext = compositeCapabilityContext(composite.id, compositeTaskGuides);
|
|
2692
|
+
const compositeCapabilityCatalog = capabilityRegistry.catalog(compositeTaskContext);
|
|
2693
|
+
const compositeCapabilityGuides = compositeTaskGuides.map((guide) => ({
|
|
2694
|
+
name: guide.name,
|
|
2695
|
+
description: guide.description,
|
|
2696
|
+
whenToRead: guide.whenToRead,
|
|
2697
|
+
path: formatPathForPrompt(guide.filePath),
|
|
2698
|
+
}));
|
|
2401
2699
|
const memberContext = memberName
|
|
2402
2700
|
? await loadCompositeMemberContext(composite.id, memberName, context ?? "auto", conversationScopeId, protectedWorkspaceIds)
|
|
2403
2701
|
: undefined;
|
|
@@ -2408,6 +2706,9 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2408
2706
|
? `Members: ${composite.members.map((member) => `${member.name} — ${member.purpose}`).join("; ")}.`
|
|
2409
2707
|
: "This Composite Workspace currently has no members.",
|
|
2410
2708
|
"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.",
|
|
2709
|
+
compositeCapabilityCatalog.length > 0
|
|
2710
|
+
? `Composite-owned capabilities: ${compositeCapabilityCatalog.map((entry) => entry.name).join(", ")}. Use these without member because their state belongs to the Composite Workspace itself.`
|
|
2711
|
+
: undefined,
|
|
2411
2712
|
composite.members.length > 0
|
|
2412
2713
|
? "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."
|
|
2413
2714
|
: undefined,
|
|
@@ -2435,6 +2736,8 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2435
2736
|
status: composite.status,
|
|
2436
2737
|
state: composite.status,
|
|
2437
2738
|
members: composite.members,
|
|
2739
|
+
capabilityCatalog: compositeCapabilityCatalog,
|
|
2740
|
+
capabilityGuides: compositeCapabilityGuides,
|
|
2438
2741
|
...(memberContext ? { memberContext } : {}),
|
|
2439
2742
|
instruction,
|
|
2440
2743
|
},
|
|
@@ -2547,6 +2850,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2547
2850
|
conversationScopeId,
|
|
2548
2851
|
protectedWorkspaceIds,
|
|
2549
2852
|
});
|
|
2853
|
+
workspaceTasks.initializeWorkspace(workspace.id);
|
|
2550
2854
|
const knownWorktrees = await workspaces.listKnownWorktrees(workspace);
|
|
2551
2855
|
const staleWorkspaces = await workspaces.listStaleWorkspaces(workspace);
|
|
2552
2856
|
const capabilityFingerprint = buildCapabilityFingerprint(config, FORGERELAY_VERSION, {
|
|
@@ -2598,7 +2902,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2598
2902
|
const loadedAgentsFiles = includeBootstrapContext ? cardAgentsFiles : [];
|
|
2599
2903
|
const availableAgentsFileOutputs = includeBootstrapContext ? cardAvailableAgentsFiles : [];
|
|
2600
2904
|
const workspaceContextInstruction = "For later open_workspace calls, context=\"auto\" avoids repeating unchanged bootstrap context; use context=\"none\" when only the workspace handle/metadata is needed, or context=\"full\" to force a refresh.";
|
|
2601
|
-
const workspaceManagementInstruction = "
|
|
2905
|
+
const workspaceManagementInstruction = "Use open_workspace(action=\"list\") for lightweight Workspace inventory. Use action=\"inspect\" with one known workspaceId for bounded read-only metadata without opening/resuming it. Explicitly open a Workspace before executing or mutating against it, and ask the user before close_workspace cleanup.";
|
|
2602
2906
|
const cardInstruction = config.skillsEnabled
|
|
2603
2907
|
? `Use this workspaceId in all subsequent tool calls for this project. Follow loaded agentsFiles instructions. Read an availableAgentsFiles path before working under it. When a task matches an available skill, load it with read(path=\"skills://<name>\") before proceeding. When a task matches a capability guide, read its advertised path before proceeding. ${workspaceContextInstruction} ${workspaceManagementInstruction}`
|
|
2604
2908
|
: `Use this workspaceId in all subsequent tool calls for this project. Follow loaded agentsFiles instructions. Read an availableAgentsFiles path before working under it. When a task matches a capability guide, read its advertised path before proceeding. ${workspaceContextInstruction} ${workspaceManagementInstruction}`;
|
|
@@ -2808,16 +3112,87 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2808
3112
|
openWorldHint: true,
|
|
2809
3113
|
},
|
|
2810
3114
|
}, async ({ workspaceId, member, name, action, arguments: capabilityArguments, file }, extra) => {
|
|
3115
|
+
if (name === "workspace.tasks" && compositeWorkspaces.has(workspaceId)) {
|
|
3116
|
+
if (member !== undefined) {
|
|
3117
|
+
throw new Error(`workspace.tasks belongs to Composite Workspace ${workspaceId} itself and does not accept member.`);
|
|
3118
|
+
}
|
|
3119
|
+
if (!compositeWorkspaces.isActive(workspaceId)) {
|
|
3120
|
+
throw new Error(`Composite Workspace ${workspaceId} is closed. Reopen it with open_workspace before use.`);
|
|
3121
|
+
}
|
|
3122
|
+
const startedAt = performance.now();
|
|
3123
|
+
const context = compositeCapabilityContext(workspaceId, compositeTaskGuides);
|
|
3124
|
+
try {
|
|
3125
|
+
if (action === "run") {
|
|
3126
|
+
const execution = await capabilityRegistry.run(name, capabilityArguments ?? {}, context, {
|
|
3127
|
+
nativeFile: file,
|
|
3128
|
+
signal: extra.signal,
|
|
3129
|
+
requestMeta: extra._meta,
|
|
3130
|
+
sessionId: extra.sessionId,
|
|
3131
|
+
});
|
|
3132
|
+
const result = {
|
|
3133
|
+
content: [textBlock(`Capability ${name} completed.\n${JSON.stringify(execution.value, null, 2)}`)],
|
|
3134
|
+
structuredContent: { name, action, result: execution.value },
|
|
3135
|
+
};
|
|
3136
|
+
logToolCall(config, {
|
|
3137
|
+
tool: toolNames.capability,
|
|
3138
|
+
capability: name,
|
|
3139
|
+
action,
|
|
3140
|
+
success: true,
|
|
3141
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
3142
|
+
});
|
|
3143
|
+
return result;
|
|
3144
|
+
}
|
|
3145
|
+
const capability = capabilityRegistry.describe(name, context);
|
|
3146
|
+
const result = {
|
|
3147
|
+
content: [textBlock([
|
|
3148
|
+
`${capability.name}: ${capability.description}`,
|
|
3149
|
+
`Available: ${capability.available}`,
|
|
3150
|
+
`Guide: ${capability.guide.path}`,
|
|
3151
|
+
capability.guide.readBeforeFirstUse
|
|
3152
|
+
? "Read the guide before first use when this contract is unfamiliar."
|
|
3153
|
+
: undefined,
|
|
3154
|
+
].filter(Boolean).join("\n"))],
|
|
3155
|
+
structuredContent: { name, action, capability },
|
|
3156
|
+
};
|
|
3157
|
+
logToolCall(config, {
|
|
3158
|
+
tool: toolNames.capability,
|
|
3159
|
+
capability: name,
|
|
3160
|
+
action,
|
|
3161
|
+
success: true,
|
|
3162
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
3163
|
+
});
|
|
3164
|
+
return result;
|
|
3165
|
+
}
|
|
3166
|
+
catch (error) {
|
|
3167
|
+
if (extra.signal.aborted)
|
|
3168
|
+
throw error;
|
|
3169
|
+
const capabilityError = error instanceof CapabilityError
|
|
3170
|
+
? error
|
|
3171
|
+
: new CapabilityError("execution_failed", error instanceof Error ? error.message : String(error));
|
|
3172
|
+
return {
|
|
3173
|
+
content: [textBlock(`${capabilityError.code}: ${capabilityError.message}`)],
|
|
3174
|
+
structuredContent: {
|
|
3175
|
+
name,
|
|
3176
|
+
action,
|
|
3177
|
+
error: { code: capabilityError.code, message: capabilityError.message },
|
|
3178
|
+
},
|
|
3179
|
+
isError: true,
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
2811
3183
|
const target = resolveExecutionTarget(workspaceId, member);
|
|
2812
3184
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
2813
3185
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
2814
3186
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
2815
|
-
|
|
3187
|
+
const response = await remoteWorkspaces.capability(executionWorkspaceId, {
|
|
2816
3188
|
name,
|
|
2817
3189
|
action,
|
|
2818
3190
|
...(capabilityArguments !== undefined ? { arguments: capabilityArguments } : {}),
|
|
2819
3191
|
...(file !== undefined ? { file } : {}),
|
|
2820
|
-
}, hostScopeIdFor(extra._meta, extra.sessionId))
|
|
3192
|
+
}, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
3193
|
+
return action === "run" && name !== "workspace.tasks"
|
|
3194
|
+
? presentSemanticWorkResult(response, target)
|
|
3195
|
+
: presentExecutionResult(response, target);
|
|
2821
3196
|
}
|
|
2822
3197
|
if (action === "run" && name === "batch.execute") {
|
|
2823
3198
|
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
@@ -2841,7 +3216,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2841
3216
|
success: true,
|
|
2842
3217
|
durationMs: Math.round(performance.now() - startedAt),
|
|
2843
3218
|
});
|
|
2844
|
-
return
|
|
3219
|
+
return presentSemanticWorkResult(result, target);
|
|
2845
3220
|
}
|
|
2846
3221
|
catch (error) {
|
|
2847
3222
|
if (extra.signal.aborted)
|
|
@@ -2868,7 +3243,10 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2868
3243
|
}
|
|
2869
3244
|
}
|
|
2870
3245
|
if (action === "run") {
|
|
2871
|
-
|
|
3246
|
+
const response = await coreOperations.capabilityRun({ workspaceId: executionWorkspaceId, name, arguments: capabilityArguments, file }, executionContext);
|
|
3247
|
+
return name === "workspace.tasks"
|
|
3248
|
+
? presentExecutionResult(response, target)
|
|
3249
|
+
: presentSemanticWorkResult(response, target);
|
|
2872
3250
|
}
|
|
2873
3251
|
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
2874
3252
|
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(extra._meta, extra.sessionId), activityRequestFor({ workspaceId: executionWorkspaceId, name, action, arguments: capabilityArguments, file }, executionContext), {
|
|
@@ -2972,6 +3350,10 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2972
3350
|
const composite = action === "delete"
|
|
2973
3351
|
? compositeWorkspaces.dissolve(workspaceId)
|
|
2974
3352
|
: compositeWorkspaces.close(workspaceId);
|
|
3353
|
+
if (action === "delete") {
|
|
3354
|
+
workspaceTasks.deleteWorkspace(workspaceId);
|
|
3355
|
+
taskReminders.forget(workspaceId);
|
|
3356
|
+
}
|
|
2975
3357
|
compositeActivity.forgetComposite(workspaceId);
|
|
2976
3358
|
workspacePanelStates.delete(workspaceId);
|
|
2977
3359
|
const result = [
|
|
@@ -3038,6 +3420,8 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3038
3420
|
payload: { workspaceId: session.id, action: "delete", mode: session.mode },
|
|
3039
3421
|
operation: async () => {
|
|
3040
3422
|
workspaces.deleteWorkspace(session.id);
|
|
3423
|
+
workspaceTasks.deleteWorkspace(session.id);
|
|
3424
|
+
taskReminders.forget(session.id);
|
|
3041
3425
|
await reviewCheckpoints.releaseWorkspace(session.id);
|
|
3042
3426
|
const result = `Deleted ForgeRelay Workspace ${session.id}. Physical project files were not removed.`;
|
|
3043
3427
|
return {
|
|
@@ -3080,6 +3464,8 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3080
3464
|
payload: { workspaceId: session.id, action: "delete", mode: session.mode },
|
|
3081
3465
|
operation: async () => {
|
|
3082
3466
|
workspaces.deleteWorkspace(session.id);
|
|
3467
|
+
workspaceTasks.deleteWorkspace(session.id);
|
|
3468
|
+
taskReminders.forget(session.id);
|
|
3083
3469
|
await reviewCheckpoints.releaseWorkspace(session.id);
|
|
3084
3470
|
const result = `Deleted closed managed-worktree Workspace ${session.id}. Its already-removed worktree backing was not recreated.`;
|
|
3085
3471
|
return {
|
|
@@ -3142,6 +3528,8 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3142
3528
|
await Promise.all(physicalWorkspaceIds.map((id) => reviewCheckpoints.releaseWorkspace(id)));
|
|
3143
3529
|
if (action === "delete") {
|
|
3144
3530
|
workspaces.deleteWorkspace(workspace.id);
|
|
3531
|
+
workspaceTasks.deleteWorkspace(workspace.id);
|
|
3532
|
+
taskReminders.forget(workspace.id);
|
|
3145
3533
|
}
|
|
3146
3534
|
const result = [
|
|
3147
3535
|
action === "delete"
|
|
@@ -3237,7 +3625,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3237
3625
|
member: z
|
|
3238
3626
|
.string()
|
|
3239
3627
|
.optional()
|
|
3240
|
-
.describe("Required for
|
|
3628
|
+
.describe("Required for Composite member-scoped file reads. Omit only when reading an advertised Composite-owned capability guide."),
|
|
3241
3629
|
path: z
|
|
3242
3630
|
.string()
|
|
3243
3631
|
.optional()
|
|
@@ -3279,14 +3667,37 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3279
3667
|
if ((path === undefined) === (paths === undefined)) {
|
|
3280
3668
|
throw new Error("read requires exactly one of path or paths.");
|
|
3281
3669
|
}
|
|
3670
|
+
if (compositeWorkspaces.has(workspaceId) && member === undefined && path !== undefined) {
|
|
3671
|
+
const guide = compositeTaskGuides.find((candidate) => formatPathForPrompt(candidate.filePath) === path || candidate.filePath === path);
|
|
3672
|
+
if (guide) {
|
|
3673
|
+
if (!compositeWorkspaces.isActive(workspaceId)) {
|
|
3674
|
+
throw new Error(`Composite Workspace ${workspaceId} is closed. Reopen it with open_workspace before use.`);
|
|
3675
|
+
}
|
|
3676
|
+
const startedAt = performance.now();
|
|
3677
|
+
const raw = readFileSync(guide.filePath, "utf8");
|
|
3678
|
+
const start = (offset ?? 1) - 1;
|
|
3679
|
+
const end = limit === undefined ? undefined : start + limit;
|
|
3680
|
+
const result = raw.split("\n").slice(start, end).join("\n");
|
|
3681
|
+
logToolCall(config, {
|
|
3682
|
+
tool: toolNames.read,
|
|
3683
|
+
path: formatPathForPrompt(guide.filePath),
|
|
3684
|
+
success: true,
|
|
3685
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
3686
|
+
});
|
|
3687
|
+
return {
|
|
3688
|
+
content: [textBlock(result)],
|
|
3689
|
+
structuredContent: { result },
|
|
3690
|
+
};
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3282
3693
|
const target = resolveExecutionTarget(workspaceId, member);
|
|
3283
3694
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3284
3695
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3285
3696
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3286
|
-
return
|
|
3697
|
+
return presentSemanticWorkResult(await remoteWorkspaces.read(executionWorkspaceId, { path, paths, offset, limit }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3287
3698
|
}
|
|
3288
3699
|
if (path !== undefined) {
|
|
3289
|
-
return
|
|
3700
|
+
return presentSemanticWorkResult(await coreOperations.read({ workspaceId: executionWorkspaceId, path, offset, limit }, executionContext), target);
|
|
3290
3701
|
}
|
|
3291
3702
|
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
3292
3703
|
let response;
|
|
@@ -3339,7 +3750,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3339
3750
|
: { type: "succeeded" }, activityRelationFor(executionContext));
|
|
3340
3751
|
if (!response)
|
|
3341
3752
|
throw new Error("Bulk Read completed without a response.");
|
|
3342
|
-
return
|
|
3753
|
+
return presentSemanticWorkResult(response, target);
|
|
3343
3754
|
});
|
|
3344
3755
|
if (config.toolMode !== "codex") {
|
|
3345
3756
|
registerAppTool(server, toolNames.write, {
|
|
@@ -3363,9 +3774,9 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3363
3774
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3364
3775
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3365
3776
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3366
|
-
return
|
|
3777
|
+
return presentSemanticWorkResult(await remoteWorkspaces.write(executionWorkspaceId, input, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3367
3778
|
}
|
|
3368
|
-
return
|
|
3779
|
+
return presentSemanticWorkResult(await coreOperations.write({ workspaceId: executionWorkspaceId, ...input }, executionContext), target);
|
|
3369
3780
|
});
|
|
3370
3781
|
registerAppTool(server, toolNames.edit, {
|
|
3371
3782
|
title: "Edit file",
|
|
@@ -3416,12 +3827,12 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3416
3827
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3417
3828
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3418
3829
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3419
|
-
return
|
|
3830
|
+
return presentSemanticWorkResult(await remoteWorkspaces.edit(executionWorkspaceId, { path, paths, edits }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3420
3831
|
}
|
|
3421
3832
|
if (path !== undefined) {
|
|
3422
|
-
return
|
|
3833
|
+
return presentSemanticWorkResult(await coreOperations.edit({ workspaceId: executionWorkspaceId, path, edits }, executionContext), target);
|
|
3423
3834
|
}
|
|
3424
|
-
return
|
|
3835
|
+
return presentSemanticWorkResult(await nativeBulkMutations.edit({ workspaceId: executionWorkspaceId, paths: paths, edits }, executionContext), target);
|
|
3425
3836
|
});
|
|
3426
3837
|
}
|
|
3427
3838
|
registerAppTool(server, toolNames.rename, {
|
|
@@ -3445,9 +3856,9 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3445
3856
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3446
3857
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3447
3858
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3448
|
-
return
|
|
3859
|
+
return presentSemanticWorkResult(await remoteWorkspaces.rename(executionWorkspaceId, { path, newPath }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3449
3860
|
}
|
|
3450
|
-
return
|
|
3861
|
+
return presentSemanticWorkResult(await coreOperations.rename({ workspaceId: executionWorkspaceId, path, newPath }, executionContext), target);
|
|
3451
3862
|
});
|
|
3452
3863
|
registerAppTool(server, toolNames.delete, {
|
|
3453
3864
|
title: "Delete path",
|
|
@@ -3488,12 +3899,12 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3488
3899
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3489
3900
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3490
3901
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3491
|
-
return
|
|
3902
|
+
return presentSemanticWorkResult(await remoteWorkspaces.delete(executionWorkspaceId, { path, paths, recursive }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3492
3903
|
}
|
|
3493
3904
|
if (path !== undefined) {
|
|
3494
|
-
return
|
|
3905
|
+
return presentSemanticWorkResult(await coreOperations.delete({ workspaceId: executionWorkspaceId, path, recursive }, executionContext), target);
|
|
3495
3906
|
}
|
|
3496
|
-
return
|
|
3907
|
+
return presentSemanticWorkResult(await nativeBulkMutations.delete({ workspaceId: executionWorkspaceId, paths: paths, recursive }, executionContext), target);
|
|
3497
3908
|
});
|
|
3498
3909
|
if (config.toolMode === "codex") {
|
|
3499
3910
|
registerAppTool(server, "apply_patch", {
|
|
@@ -3524,7 +3935,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3524
3935
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3525
3936
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3526
3937
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3527
|
-
return
|
|
3938
|
+
return presentSemanticWorkResult(await remoteWorkspaces.applyPatch(executionWorkspaceId, { patch }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
3528
3939
|
}
|
|
3529
3940
|
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
3530
3941
|
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(extra._meta, extra.sessionId), activityRequestFor({ workspaceId: executionWorkspaceId, patch }, executionContext), {
|
|
@@ -3574,7 +3985,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3574
3985
|
},
|
|
3575
3986
|
};
|
|
3576
3987
|
},
|
|
3577
|
-
}, activityRelationFor(executionContext)).then((result) =>
|
|
3988
|
+
}, activityRelationFor(executionContext)).then((result) => presentSemanticWorkResult(result, target));
|
|
3578
3989
|
});
|
|
3579
3990
|
}
|
|
3580
3991
|
if (config.toolMode !== "codex") {
|
|
@@ -3664,7 +4075,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3664
4075
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
3665
4076
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
3666
4077
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
3667
|
-
|
|
4078
|
+
const response = await remoteWorkspaces.bash(executionWorkspaceId, {
|
|
3668
4079
|
action,
|
|
3669
4080
|
...(command !== undefined ? { command } : {}),
|
|
3670
4081
|
...(processId !== undefined ? { processId } : {}),
|
|
@@ -3678,7 +4089,10 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3678
4089
|
...(yieldTimeMs !== undefined ? { yieldTimeMs } : {}),
|
|
3679
4090
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
3680
4091
|
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
3681
|
-
}, hostScopeIdFor(extra._meta, extra.sessionId))
|
|
4092
|
+
}, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
4093
|
+
return action === "run"
|
|
4094
|
+
? presentSemanticWorkResult(response, target)
|
|
4095
|
+
: presentExecutionResult(response, target);
|
|
3682
4096
|
}
|
|
3683
4097
|
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
3684
4098
|
if (action === "run") {
|
|
@@ -3687,7 +4101,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3687
4101
|
if (processId !== undefined || outputId !== undefined || input !== undefined || interrupt !== undefined) {
|
|
3688
4102
|
throw new Error("bash action=run does not accept processId, outputId, input, or interrupt.");
|
|
3689
4103
|
}
|
|
3690
|
-
return
|
|
4104
|
+
return presentSemanticWorkResult(await coreOperations.shellRun({
|
|
3691
4105
|
workspaceId: executionWorkspaceId,
|
|
3692
4106
|
command,
|
|
3693
4107
|
surface: "bash",
|
|
@@ -3781,6 +4195,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
3781
4195
|
resolve: resolveExecutionTarget,
|
|
3782
4196
|
prepare: prepareExecutionContext,
|
|
3783
4197
|
present: presentExecutionResult,
|
|
4198
|
+
presentSemantic: presentSemanticWorkResult,
|
|
3784
4199
|
isRemote: (workspaceId) => remoteWorkspaces.has(workspaceId),
|
|
3785
4200
|
execCommandRemote: (workspaceId, input, conversationScopeId) => remoteWorkspaces.execCommand(workspaceId, input, conversationScopeId),
|
|
3786
4201
|
writeStdinRemote: (workspaceId, input, conversationScopeId) => remoteWorkspaces.writeStdin(workspaceId, input, conversationScopeId),
|