@akira-tl/forgerelay 0.8.9 → 0.8.10
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 +20 -0
- package/capabilities/code-intelligence/GUIDE.md +7 -3
- package/capabilities/subagents/GUIDE.md +9 -0
- package/capabilities/workspace/workspace-tasks/GUIDE.md +6 -0
- package/dist/cli/init.js +180 -0
- package/dist/cli/setup-support.js +65 -0
- package/dist/cli.js +9 -94
- package/dist/lsp/code-intelligence.js +17 -3
- package/dist/lsp/runtime/diagnostic-snapshots.js +52 -1
- package/dist/lsp/runtime/managed-language-servers.js +172 -0
- package/dist/lsp/runtime/manager.js +37 -2
- package/dist/lsp/runtime/process-launch.js +3 -0
- package/dist/lsp/test-support/server-fixture.js +5 -2
- package/dist/mcp/process/process-platform.js +21 -9
- package/dist/mcp/process/process-sessions.js +3 -3
- package/dist/mcp/process/tools.js +6 -6
- package/dist/mcp/server/core/capability-registry.js +7 -2
- package/dist/mcp/server/core/tool-support.js +8 -5
- package/dist/mcp/server/operations/runtime/filesystem-tools.js +74 -19
- package/dist/mcp/server/operations/runtime/mutation-diagnostics.js +54 -0
- package/dist/mcp/server/operations/runtime/operation-runtime.js +8 -7
- package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +28 -8
- package/dist/mcp/server/workspace/runtime/workspace-open.js +4 -1
- package/dist/mcp/server/workspace/runtime/workspace-tools.js +3 -3
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/runtime/config/config.js +1 -0
- package/dist/runtime/config/user-config.js +1 -10
- package/dist/server.js +36 -6
- package/dist/workspaces/context.js +35 -0
- package/dist/workspaces/relay/transport/remote-transport.js +30 -3
- package/dist/workspaces/resources/resource-monitor.js +377 -0
- package/dist/workspaces/resources/skills.js +13 -15
- package/dist/workspaces/sessions.js +3 -0
- package/dist/workspaces.js +11 -0
- package/docs/chatgpt-coding-workflow.md +4 -3
- package/docs/configuration.md +14 -9
- package/package.json +2 -3
- package/scripts/release/publish.mjs +12 -9
- package/scripts/release/release-gate.test.mjs +3 -1
- package/scripts/release/release-version.test.mjs +13 -1
- package/scripts/release-parity.mjs +1 -1
- package/scripts/release-proof.mjs +15 -0
- package/scripts/release-proof.test.mjs +27 -1
- package/scripts/release-version.mjs +16 -1
- package/skills/subagent-delegation/SKILL.md +0 -132
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
|
+
import { isAbsolute } from "node:path";
|
|
3
4
|
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
|
|
4
5
|
import * as z from "zod/v4";
|
|
5
6
|
import { applyPatch } from "../../../filesystem/apply-patch.js";
|
|
7
|
+
import { readFileTool } from "../../../filesystem/filesystem-tools.js";
|
|
6
8
|
import { toolNames } from "../../../server-instructions.js";
|
|
7
9
|
import { executeBulkRead } from "../../../operations/bulk-read.js";
|
|
8
10
|
import { formatPathForPrompt } from "../../../../workspaces/resources/skills.js";
|
|
@@ -10,6 +12,7 @@ import { activityRelationFor, activityRequestFor, runActivityTool, runActivityTo
|
|
|
10
12
|
import { workspaceHookInvocation } from "../../core/capability-support.js";
|
|
11
13
|
import { resultOutputSchema, workspaceAgentsFileOutputSchema } from "../../core/schemas.js";
|
|
12
14
|
import { contentText, logToolCall, textBlock, toolResultAgentsFiles, toolResultContent, toolResultIsError, toolResultText, workspaceLogContext, } from "../../core/tool-support.js";
|
|
15
|
+
import { appendAutomaticMutationDiagnostics } from "./mutation-diagnostics.js";
|
|
13
16
|
const WRITE_TOOL_ANNOTATIONS = {
|
|
14
17
|
readOnlyHint: false,
|
|
15
18
|
destructiveHint: true,
|
|
@@ -23,14 +26,15 @@ const EDIT_TOOL_ANNOTATIONS = {
|
|
|
23
26
|
openWorldHint: false,
|
|
24
27
|
};
|
|
25
28
|
export function registerFilesystemTools(options) {
|
|
26
|
-
const { server, config, workspaces, compositeWorkspaces, compositeTaskGuides, remoteWorkspaces, coreOperations, nativeBulkMutations, activityLifecycle, hooks, toolDescriptions, resolveExecutionTarget, prepareExecutionContext, presentSemanticWorkResult, hostScopeIdFor, } = options;
|
|
29
|
+
const { server, config, workspaces, compositeWorkspaces, compositeTaskGuides, remoteWorkspaces, coreOperations, nativeBulkMutations, activityLifecycle, codeIntelligence, hooks, toolDescriptions, resolveExecutionTarget, prepareExecutionContext, presentSemanticWorkResult, hostScopeIdFor, } = options;
|
|
27
30
|
registerAppTool(server, toolNames.read, {
|
|
28
31
|
title: "Read file",
|
|
29
32
|
description: toolDescriptions.read,
|
|
30
33
|
inputSchema: {
|
|
31
34
|
workspaceId: z
|
|
32
35
|
.string()
|
|
33
|
-
.
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Workspace identifier returned by open_workspace. Omit only for read-only inspection of absolute paths already inside configured allowedRoots."),
|
|
34
38
|
member: z
|
|
35
39
|
.string()
|
|
36
40
|
.optional()
|
|
@@ -76,6 +80,54 @@ export function registerFilesystemTools(options) {
|
|
|
76
80
|
if ((path === undefined) === (paths === undefined)) {
|
|
77
81
|
throw new Error("read requires exactly one of path or paths.");
|
|
78
82
|
}
|
|
83
|
+
if (workspaceId === undefined) {
|
|
84
|
+
if (member !== undefined)
|
|
85
|
+
throw new Error("read without workspaceId does not accept member.");
|
|
86
|
+
const requestedPaths = path !== undefined ? [path] : paths;
|
|
87
|
+
for (const requestedPath of requestedPaths) {
|
|
88
|
+
if (!isAbsolute(requestedPath)) {
|
|
89
|
+
throw new Error("read without workspaceId requires absolute paths inside configured allowedRoots.");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const startedAt = performance.now();
|
|
93
|
+
const children = await Promise.all(requestedPaths.map(async (requestedPath) => {
|
|
94
|
+
const response = await readFileTool({ path: requestedPath, offset, limit }, {
|
|
95
|
+
cwd: process.cwd(),
|
|
96
|
+
root: config.allowedRoots[0] ?? process.cwd(),
|
|
97
|
+
readRoots: config.allowedRoots,
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
path: requestedPath,
|
|
101
|
+
status: response.isError ? "error" : "done",
|
|
102
|
+
response,
|
|
103
|
+
result: contentText(response.content),
|
|
104
|
+
};
|
|
105
|
+
}));
|
|
106
|
+
const failed = children.filter((child) => child.status === "error").length;
|
|
107
|
+
const content = children.flatMap((child) => requestedPaths.length === 1
|
|
108
|
+
? child.response.content
|
|
109
|
+
: [textBlock(`--- ${child.path} · ${child.status} ---`), ...child.response.content]);
|
|
110
|
+
logToolCall(config, {
|
|
111
|
+
tool: toolNames.read,
|
|
112
|
+
path: requestedPaths.length === 1 ? requestedPaths[0] : `${requestedPaths.length} unscoped files`,
|
|
113
|
+
success: failed === 0,
|
|
114
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
content,
|
|
118
|
+
...(failed > 0 ? { isError: true } : {}),
|
|
119
|
+
structuredContent: {
|
|
120
|
+
result: contentText(content),
|
|
121
|
+
...(requestedPaths.length > 1
|
|
122
|
+
? {
|
|
123
|
+
results: children.map(({ path: childPath, status, result }) => ({ path: childPath, status, result })),
|
|
124
|
+
files: children.length,
|
|
125
|
+
failed,
|
|
126
|
+
}
|
|
127
|
+
: {}),
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
79
131
|
if (compositeWorkspaces.has(workspaceId) && member === undefined && path !== undefined) {
|
|
80
132
|
const guide = compositeTaskGuides.find((candidate) => formatPathForPrompt(candidate.filePath) === path || candidate.filePath === path);
|
|
81
133
|
if (guide) {
|
|
@@ -103,10 +155,10 @@ export function registerFilesystemTools(options) {
|
|
|
103
155
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
104
156
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
105
157
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
106
|
-
return presentSemanticWorkResult(await remoteWorkspaces.read(executionWorkspaceId, { path, paths, offset, limit }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
158
|
+
return presentSemanticWorkResult(await remoteWorkspaces.read(executionWorkspaceId, { path, paths, offset, limit }, hostScopeIdFor(extra._meta, extra.sessionId)), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
107
159
|
}
|
|
108
160
|
if (path !== undefined) {
|
|
109
|
-
return presentSemanticWorkResult(await coreOperations.read({ workspaceId: executionWorkspaceId, path, offset, limit }, executionContext), target);
|
|
161
|
+
return presentSemanticWorkResult(await coreOperations.read({ workspaceId: executionWorkspaceId, path, offset, limit }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
110
162
|
}
|
|
111
163
|
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
112
164
|
let response;
|
|
@@ -159,7 +211,7 @@ export function registerFilesystemTools(options) {
|
|
|
159
211
|
: { type: "succeeded" }, activityRelationFor(executionContext));
|
|
160
212
|
if (!response)
|
|
161
213
|
throw new Error("Bulk Read completed without a response.");
|
|
162
|
-
return presentSemanticWorkResult(response, target);
|
|
214
|
+
return presentSemanticWorkResult(response, target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
163
215
|
});
|
|
164
216
|
if (config.toolMode !== "codex") {
|
|
165
217
|
registerAppTool(server, toolNames.write, {
|
|
@@ -183,9 +235,9 @@ export function registerFilesystemTools(options) {
|
|
|
183
235
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
184
236
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
185
237
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
186
|
-
return presentSemanticWorkResult(await remoteWorkspaces.write(executionWorkspaceId, input, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
238
|
+
return presentSemanticWorkResult(await remoteWorkspaces.write(executionWorkspaceId, input, hostScopeIdFor(extra._meta, extra.sessionId)), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
187
239
|
}
|
|
188
|
-
return presentSemanticWorkResult(await coreOperations.write({ workspaceId: executionWorkspaceId, ...input }, executionContext), target);
|
|
240
|
+
return presentSemanticWorkResult(await coreOperations.write({ workspaceId: executionWorkspaceId, ...input }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
189
241
|
});
|
|
190
242
|
registerAppTool(server, toolNames.edit, {
|
|
191
243
|
title: "Edit file",
|
|
@@ -236,12 +288,12 @@ export function registerFilesystemTools(options) {
|
|
|
236
288
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
237
289
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
238
290
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
239
|
-
return presentSemanticWorkResult(await remoteWorkspaces.edit(executionWorkspaceId, { path, paths, edits }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
291
|
+
return presentSemanticWorkResult(await remoteWorkspaces.edit(executionWorkspaceId, { path, paths, edits }, hostScopeIdFor(extra._meta, extra.sessionId)), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
240
292
|
}
|
|
241
293
|
if (path !== undefined) {
|
|
242
|
-
return presentSemanticWorkResult(await coreOperations.edit({ workspaceId: executionWorkspaceId, path, edits }, executionContext), target);
|
|
294
|
+
return presentSemanticWorkResult(await coreOperations.edit({ workspaceId: executionWorkspaceId, path, edits }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
243
295
|
}
|
|
244
|
-
return presentSemanticWorkResult(await nativeBulkMutations.edit({ workspaceId: executionWorkspaceId, paths: paths, edits }, executionContext), target);
|
|
296
|
+
return presentSemanticWorkResult(await nativeBulkMutations.edit({ workspaceId: executionWorkspaceId, paths: paths, edits }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
245
297
|
});
|
|
246
298
|
}
|
|
247
299
|
registerAppTool(server, toolNames.rename, {
|
|
@@ -265,9 +317,9 @@ export function registerFilesystemTools(options) {
|
|
|
265
317
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
266
318
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
267
319
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
268
|
-
return presentSemanticWorkResult(await remoteWorkspaces.rename(executionWorkspaceId, { path, newPath }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
320
|
+
return presentSemanticWorkResult(await remoteWorkspaces.rename(executionWorkspaceId, { path, newPath }, hostScopeIdFor(extra._meta, extra.sessionId)), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
269
321
|
}
|
|
270
|
-
return presentSemanticWorkResult(await coreOperations.rename({ workspaceId: executionWorkspaceId, path, newPath }, executionContext), target);
|
|
322
|
+
return presentSemanticWorkResult(await coreOperations.rename({ workspaceId: executionWorkspaceId, path, newPath }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
271
323
|
});
|
|
272
324
|
registerAppTool(server, toolNames.delete, {
|
|
273
325
|
title: "Delete path",
|
|
@@ -308,12 +360,12 @@ export function registerFilesystemTools(options) {
|
|
|
308
360
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
309
361
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
310
362
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
311
|
-
return presentSemanticWorkResult(await remoteWorkspaces.delete(executionWorkspaceId, { path, paths, recursive }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
363
|
+
return presentSemanticWorkResult(await remoteWorkspaces.delete(executionWorkspaceId, { path, paths, recursive }, hostScopeIdFor(extra._meta, extra.sessionId)), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
312
364
|
}
|
|
313
365
|
if (path !== undefined) {
|
|
314
|
-
return presentSemanticWorkResult(await coreOperations.delete({ workspaceId: executionWorkspaceId, path, recursive }, executionContext), target);
|
|
366
|
+
return presentSemanticWorkResult(await coreOperations.delete({ workspaceId: executionWorkspaceId, path, recursive }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
315
367
|
}
|
|
316
|
-
return presentSemanticWorkResult(await nativeBulkMutations.delete({ workspaceId: executionWorkspaceId, paths: paths, recursive }, executionContext), target);
|
|
368
|
+
return presentSemanticWorkResult(await nativeBulkMutations.delete({ workspaceId: executionWorkspaceId, paths: paths, recursive }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
317
369
|
});
|
|
318
370
|
if (config.toolMode === "codex") {
|
|
319
371
|
registerAppTool(server, "apply_patch", {
|
|
@@ -344,7 +396,7 @@ export function registerFilesystemTools(options) {
|
|
|
344
396
|
const executionWorkspaceId = target.executionWorkspaceId;
|
|
345
397
|
const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
|
|
346
398
|
if (remoteWorkspaces.has(executionWorkspaceId)) {
|
|
347
|
-
return presentSemanticWorkResult(await remoteWorkspaces.applyPatch(executionWorkspaceId, { patch }, hostScopeIdFor(extra._meta, extra.sessionId)), target);
|
|
399
|
+
return presentSemanticWorkResult(await remoteWorkspaces.applyPatch(executionWorkspaceId, { patch }, hostScopeIdFor(extra._meta, extra.sessionId)), target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
348
400
|
}
|
|
349
401
|
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
350
402
|
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(extra._meta, extra.sessionId), activityRequestFor({ workspaceId: executionWorkspaceId, patch }, executionContext), {
|
|
@@ -363,6 +415,9 @@ export function registerFilesystemTools(options) {
|
|
|
363
415
|
const displayPath = applied.files.length === 1
|
|
364
416
|
? applied.files[0]?.path
|
|
365
417
|
: `${applied.files.length} files`;
|
|
418
|
+
const diagnosticPaths = applied.files
|
|
419
|
+
.filter((file) => file.operation !== "delete")
|
|
420
|
+
.map((file) => file.path);
|
|
366
421
|
logToolCall(config, {
|
|
367
422
|
tool: "apply_patch",
|
|
368
423
|
...workspaceLogContext(workspace, extra.sessionId),
|
|
@@ -370,7 +425,7 @@ export function registerFilesystemTools(options) {
|
|
|
370
425
|
success: true,
|
|
371
426
|
durationMs: Math.round(performance.now() - startedAt),
|
|
372
427
|
});
|
|
373
|
-
return {
|
|
428
|
+
return appendAutomaticMutationDiagnostics({
|
|
374
429
|
content,
|
|
375
430
|
_meta: {
|
|
376
431
|
tool: "apply_patch",
|
|
@@ -392,9 +447,9 @@ export function registerFilesystemTools(options) {
|
|
|
392
447
|
removals: applied.removals,
|
|
393
448
|
files: applied.files,
|
|
394
449
|
},
|
|
395
|
-
};
|
|
450
|
+
}, codeIntelligence, workspace.root, diagnosticPaths, extra.signal);
|
|
396
451
|
},
|
|
397
|
-
}, activityRelationFor(executionContext)).then((result) => presentSemanticWorkResult(result, target));
|
|
452
|
+
}, activityRelationFor(executionContext)).then((result) => presentSemanticWorkResult(result, target, hostScopeIdFor(extra._meta, extra.sessionId)));
|
|
398
453
|
});
|
|
399
454
|
}
|
|
400
455
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { CodeIntelligenceError } from "../../../../lsp/code-intelligence-error.js";
|
|
2
|
+
import { textBlock } from "../../core/tool-support.js";
|
|
3
|
+
const AUTO_DIAGNOSTIC_LIMIT = 20;
|
|
4
|
+
export async function appendAutomaticMutationDiagnostics(result, codeIntelligence, workspaceRoot, paths, signal) {
|
|
5
|
+
const summary = await automaticMutationDiagnostics(codeIntelligence, workspaceRoot, paths, signal);
|
|
6
|
+
if (!summary)
|
|
7
|
+
return result;
|
|
8
|
+
return {
|
|
9
|
+
...result,
|
|
10
|
+
content: [...result.content, textBlock(summary)],
|
|
11
|
+
structuredContent: {
|
|
12
|
+
...result.structuredContent,
|
|
13
|
+
result: `${result.structuredContent.result}\n\n${summary}`,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export async function automaticMutationDiagnostics(codeIntelligence, workspaceRoot, paths, signal) {
|
|
18
|
+
const sections = [];
|
|
19
|
+
for (const path of Array.from(new Set(paths))) {
|
|
20
|
+
try {
|
|
21
|
+
const result = await codeIntelligence.run(workspaceRoot, { operation: "diagnostics", path, limit: AUTO_DIAGNOSTIC_LIMIT }, { signal });
|
|
22
|
+
if (result.operation !== "diagnostics" || result.diagnostics.length === 0)
|
|
23
|
+
continue;
|
|
24
|
+
sections.push(formatDiagnostics(result));
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error instanceof CodeIntelligenceError) {
|
|
28
|
+
if (error.code === "code.language_service_unavailable" || error.code === "code.operation_unsupported")
|
|
29
|
+
continue;
|
|
30
|
+
sections.push(`Automatic diagnostics warning for ${path}: ${error.message}`);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
+
sections.push(`Automatic diagnostics warning for ${path}: ${message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (sections.length === 0)
|
|
38
|
+
return undefined;
|
|
39
|
+
return [
|
|
40
|
+
"Automatic Language Server validation after this file change:",
|
|
41
|
+
...sections,
|
|
42
|
+
].join("\n");
|
|
43
|
+
}
|
|
44
|
+
function formatDiagnostics(result) {
|
|
45
|
+
const lines = result.diagnostics.map((diagnostic) => {
|
|
46
|
+
const location = `${result.path}:${diagnostic.range.start.line}:${diagnostic.range.start.column}`;
|
|
47
|
+
const severity = diagnostic.severity ? `[${diagnostic.severity}] ` : "";
|
|
48
|
+
const code = diagnostic.code === undefined ? "" : ` (${diagnostic.code})`;
|
|
49
|
+
return `- ${location} ${severity}${diagnostic.message}${code}`;
|
|
50
|
+
});
|
|
51
|
+
if (result.truncated)
|
|
52
|
+
lines.push(`- … more diagnostics were truncated (showing ${result.returned}${result.total === undefined ? "" : ` of ${result.total}`}).`);
|
|
53
|
+
return [`${result.selectedServer} · ${result.path}`, ...lines].join("\n");
|
|
54
|
+
}
|
|
@@ -12,8 +12,9 @@ import { capabilityActivityAuditRequest, capabilityActivityAuditResult } from ".
|
|
|
12
12
|
import { activityRelationFor, activityRequestFor, runActivityTool, runActivityToolWithHooks, standardActivityOutcome, } from "../../core/activity-support.js";
|
|
13
13
|
import { capabilityContextFor, workspaceHookInvocation } from "../../core/capability-support.js";
|
|
14
14
|
import { assertWorkspaceInstructionsLoadedBeforeSideEffect, contentLineCount, contentText, countDiffStats, formatDiscoveredWorkspaceInstructions, logFailedToolResponse, logToolCall, newFilePatch, textBlock, textSummary, toolResultContent, toolResultIsError, toolResultText, workspaceLogContext, } from "../../core/tool-support.js";
|
|
15
|
+
import { appendAutomaticMutationDiagnostics } from "./mutation-diagnostics.js";
|
|
15
16
|
export function createOperationRuntime(options) {
|
|
16
|
-
const { config, workspaces, activityLifecycle, hooks, processSessions, bashOutputStore, capabilityRegistry, hostScopeIdFor } = options;
|
|
17
|
+
const { config, workspaces, activityLifecycle, hooks, processSessions, bashOutputStore, capabilityRegistry, codeIntelligence, hostScopeIdFor, } = options;
|
|
17
18
|
const coreOperations = createCoreOperationExecutor({
|
|
18
19
|
read: async (input, context) => {
|
|
19
20
|
const { workspaceId, ...readInput } = input;
|
|
@@ -127,7 +128,7 @@ export function createOperationRuntime(options) {
|
|
|
127
128
|
success: true,
|
|
128
129
|
durationMs: Math.round(performance.now() - startedAt),
|
|
129
130
|
});
|
|
130
|
-
return {
|
|
131
|
+
return appendAutomaticMutationDiagnostics({
|
|
131
132
|
...response,
|
|
132
133
|
_meta: {
|
|
133
134
|
tool: toolNames.write,
|
|
@@ -144,7 +145,7 @@ export function createOperationRuntime(options) {
|
|
|
144
145
|
structuredContent: {
|
|
145
146
|
result: contentText(response.content),
|
|
146
147
|
},
|
|
147
|
-
};
|
|
148
|
+
}, codeIntelligence, workspace.root, [writeInput.path], context.signal);
|
|
148
149
|
},
|
|
149
150
|
}, activityRelationFor(context));
|
|
150
151
|
},
|
|
@@ -188,7 +189,7 @@ export function createOperationRuntime(options) {
|
|
|
188
189
|
success: true,
|
|
189
190
|
durationMs: Math.round(performance.now() - startedAt),
|
|
190
191
|
});
|
|
191
|
-
return {
|
|
192
|
+
return appendAutomaticMutationDiagnostics({
|
|
192
193
|
content: editContent,
|
|
193
194
|
_meta: {
|
|
194
195
|
tool: toolNames.edit,
|
|
@@ -206,7 +207,7 @@ export function createOperationRuntime(options) {
|
|
|
206
207
|
status: "applied",
|
|
207
208
|
result: contentText(editContent),
|
|
208
209
|
},
|
|
209
|
-
};
|
|
210
|
+
}, codeIntelligence, workspace.root, [editInput.path], context.signal);
|
|
210
211
|
},
|
|
211
212
|
}, activityRelationFor(context));
|
|
212
213
|
},
|
|
@@ -236,7 +237,7 @@ export function createOperationRuntime(options) {
|
|
|
236
237
|
success: true,
|
|
237
238
|
durationMs: Math.round(performance.now() - startedAt),
|
|
238
239
|
});
|
|
239
|
-
return {
|
|
240
|
+
return appendAutomaticMutationDiagnostics({
|
|
240
241
|
content,
|
|
241
242
|
_meta: {
|
|
242
243
|
tool: toolNames.rename,
|
|
@@ -253,7 +254,7 @@ export function createOperationRuntime(options) {
|
|
|
253
254
|
path,
|
|
254
255
|
newPath,
|
|
255
256
|
},
|
|
256
|
-
};
|
|
257
|
+
}, codeIntelligence, workspace.root, [newPath], context.signal);
|
|
257
258
|
}
|
|
258
259
|
catch (error) {
|
|
259
260
|
logToolCall(config, {
|
|
@@ -9,15 +9,27 @@ import { formatUnavailableSubagentProvider } from "../../../../subagents/provide
|
|
|
9
9
|
import { capabilityContextFor } from "../../core/capability-support.js";
|
|
10
10
|
import { redactSkillDiagnosticPaths } from "../../core/schemas.js";
|
|
11
11
|
import { logToolCall, workspaceLogContext } from "../../core/tool-support.js";
|
|
12
|
+
export const workspaceTaskUsageInstruction = "Use workspace.tasks proactively for work that spans multiple steps or sessions: create or update Tasks to preserve useful next steps and current state, and read its summary when resuming relevant unfinished work. Do not query it mechanically on every open_workspace call.";
|
|
12
13
|
export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
13
14
|
const { config, forgerelayVersion: FORGERELAY_VERSION, workspaces, workspaceTasks, reviewCheckpoints, capabilityRegistry, subagentProviders, hooks, rememberWorkspacePanelState, } = options;
|
|
14
15
|
const { path, workspaceId, mode, baseRef, newWorktree, newWorkspace, context } = input;
|
|
15
16
|
const { conversationScopeId, protectedWorkspaceIds, startedAt, sessionId } = contextData;
|
|
16
|
-
const { workspace, agentsFiles, availableAgentsFiles, hookReports, workspaceReused,
|
|
17
|
+
const { workspace, agentsFiles, availableAgentsFiles, hookReports, workspaceReused, bootstrapContextComponents, contextFingerprint, } = await workspaces.openWorkspace({ path, workspaceId, mode, baseRef, newWorktree, newWorkspace, context }, {
|
|
17
18
|
conversationScopeId,
|
|
18
19
|
protectedWorkspaceIds,
|
|
19
20
|
});
|
|
20
21
|
workspaceTasks.initializeWorkspace(workspace.id);
|
|
22
|
+
const requestedContext = context ?? "auto";
|
|
23
|
+
const resourceUpdate = workspaceReused && requestedContext === "auto"
|
|
24
|
+
? workspaces.claimResourceUpdates(workspace.id, conversationScopeId)
|
|
25
|
+
: undefined;
|
|
26
|
+
if (requestedContext === "full" || (!workspaceReused && requestedContext !== "none")) {
|
|
27
|
+
workspaces.acknowledgeResourceUpdates(workspace.id, conversationScopeId);
|
|
28
|
+
}
|
|
29
|
+
const effectiveBootstrapContextComponents = resourceUpdate
|
|
30
|
+
? bootstrapContextComponents.filter((component) => !resourceUpdate.coveredComponents.includes(component))
|
|
31
|
+
: bootstrapContextComponents;
|
|
32
|
+
const effectiveIncludeBootstrapContext = effectiveBootstrapContextComponents.length > 0 || Boolean(resourceUpdate);
|
|
21
33
|
const knownWorktrees = await workspaces.listKnownWorktrees(workspace);
|
|
22
34
|
const staleWorkspaces = await workspaces.listStaleWorkspaces(workspace);
|
|
23
35
|
const capabilityFingerprint = buildCapabilityFingerprint(config, FORGERELAY_VERSION, {
|
|
@@ -59,7 +71,7 @@ export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
|
59
71
|
const cardAvailableAgentsFiles = availableAgentsFiles.map((file) => ({
|
|
60
72
|
path: formatAgentsPath(file.path, workspace.root),
|
|
61
73
|
}));
|
|
62
|
-
const bootstrapComponents = new Set(
|
|
74
|
+
const bootstrapComponents = new Set(effectiveBootstrapContextComponents);
|
|
63
75
|
const visibleSkills = bootstrapComponents.has("skills") ? cardSkills : [];
|
|
64
76
|
const visibleSkillDiagnostics = bootstrapComponents.has("skillDiagnostics")
|
|
65
77
|
? redactSkillDiagnosticPaths(workspace.skillDiagnostics)
|
|
@@ -72,16 +84,23 @@ export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
|
72
84
|
? cardAvailableAgentsFiles
|
|
73
85
|
: [];
|
|
74
86
|
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.";
|
|
75
|
-
const workspaceManagementInstruction =
|
|
87
|
+
const workspaceManagementInstruction = [
|
|
88
|
+
"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.",
|
|
89
|
+
capabilityCatalog.some((entry) => entry.name === "workspace.tasks")
|
|
90
|
+
? workspaceTaskUsageInstruction
|
|
91
|
+
: undefined,
|
|
92
|
+
].filter(Boolean).join(" ");
|
|
76
93
|
const cardInstruction = config.skillsEnabled
|
|
77
94
|
? `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}`
|
|
78
95
|
: `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}`;
|
|
79
96
|
const instruction = workspaceReused
|
|
80
|
-
?
|
|
97
|
+
? effectiveIncludeBootstrapContext
|
|
81
98
|
? [
|
|
82
99
|
`Workspace already exists as ${workspace.id} for this directory.`,
|
|
83
100
|
"Reuse this workspaceId for subsequent tool calls.",
|
|
84
|
-
|
|
101
|
+
effectiveBootstrapContextComponents.length > 0
|
|
102
|
+
? `Project bootstrap context components included in this response: ${effectiveBootstrapContextComponents.join(", ")}. Components not listed are unchanged and are not repeated.`
|
|
103
|
+
: "Only Workspace context deltas are included in this response; unchanged bootstrap context is not repeated.",
|
|
85
104
|
workspaceContextInstruction,
|
|
86
105
|
workspaceManagementInstruction,
|
|
87
106
|
].join("\n\n")
|
|
@@ -93,7 +112,7 @@ export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
|
93
112
|
workspaceManagementInstruction,
|
|
94
113
|
].join("\n\n")
|
|
95
114
|
: workspace.mode === "worktree"
|
|
96
|
-
?
|
|
115
|
+
? `Use this workspaceId for subsequent tool calls. Follow the project instructions, nested instruction files, skills, agent profiles, and diagnostics returned for this isolated worktree. ${workspaceManagementInstruction}`
|
|
97
116
|
: cardInstruction;
|
|
98
117
|
const resultContent = [
|
|
99
118
|
{
|
|
@@ -138,6 +157,7 @@ export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
|
138
157
|
: undefined,
|
|
139
158
|
`ForgeRelay ${capabilityFingerprint.version} capabilities: ${capabilityFingerprint.capabilities.join(", ")}`,
|
|
140
159
|
instruction,
|
|
160
|
+
resourceUpdate?.text,
|
|
141
161
|
].filter(Boolean).join("\n"),
|
|
142
162
|
},
|
|
143
163
|
];
|
|
@@ -155,7 +175,7 @@ export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
|
155
175
|
path: workspace.root,
|
|
156
176
|
mode: workspace.mode,
|
|
157
177
|
workspaceReused,
|
|
158
|
-
includeBootstrapContext,
|
|
178
|
+
includeBootstrapContext: effectiveIncludeBootstrapContext,
|
|
159
179
|
sourceRoot: workspace.sourceRoot,
|
|
160
180
|
worktree: workspace.worktree,
|
|
161
181
|
worktrees: knownWorktrees,
|
|
@@ -183,7 +203,7 @@ export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
|
183
203
|
content: resultContent,
|
|
184
204
|
_meta: {
|
|
185
205
|
tool: "open_workspace",
|
|
186
|
-
card:
|
|
206
|
+
card: effectiveIncludeBootstrapContext
|
|
187
207
|
? workspaceCard
|
|
188
208
|
: compactWorkspacePresentation(workspaceCard),
|
|
189
209
|
},
|
|
@@ -4,7 +4,7 @@ import { formatPathForPrompt } from "../../../../workspaces/resources/skills.js"
|
|
|
4
4
|
import { compositeCapabilityContext } from "../../core/capability-support.js";
|
|
5
5
|
import { logToolCall, textBlock } from "../../core/tool-support.js";
|
|
6
6
|
import { openWorkspaceToolDefinition } from "./workspace-open-schema.js";
|
|
7
|
-
import { presentLocalWorkspaceOpen } from "./workspace-open-presentation.js";
|
|
7
|
+
import { presentLocalWorkspaceOpen, workspaceTaskUsageInstruction, } from "./workspace-open-presentation.js";
|
|
8
8
|
export function registerOpenWorkspaceTool(options) {
|
|
9
9
|
const { server } = options;
|
|
10
10
|
registerAppTool(server, "open_workspace", openWorkspaceToolDefinition, (args, extra) => handleOpenWorkspace(options, args, extra));
|
|
@@ -405,6 +405,9 @@ async function handleOpenWorkspace(options, input, extra) {
|
|
|
405
405
|
compositeCapabilityCatalog.length > 0
|
|
406
406
|
? `Composite-owned capabilities: ${compositeCapabilityCatalog.map((entry) => entry.name).join(", ")}. Use these without member because their state belongs to the Composite Workspace itself.`
|
|
407
407
|
: undefined,
|
|
408
|
+
compositeCapabilityCatalog.some((entry) => entry.name === "workspace.tasks")
|
|
409
|
+
? workspaceTaskUsageInstruction
|
|
410
|
+
: undefined,
|
|
408
411
|
composite.members.length > 0
|
|
409
412
|
? "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."
|
|
410
413
|
: undefined,
|
|
@@ -170,7 +170,7 @@ export function registerWorkspaceAuxiliaryTools(options) {
|
|
|
170
170
|
...(file !== undefined ? { file } : {}),
|
|
171
171
|
}, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
172
172
|
return action === "run" && name !== "workspace.tasks"
|
|
173
|
-
? presentSemanticWorkResult(response, target)
|
|
173
|
+
? presentSemanticWorkResult(response, target, hostScopeIdFor(extra._meta, extra.sessionId))
|
|
174
174
|
: presentExecutionResult(response, target);
|
|
175
175
|
}
|
|
176
176
|
if (action === "run" && name === "batch.execute") {
|
|
@@ -195,7 +195,7 @@ export function registerWorkspaceAuxiliaryTools(options) {
|
|
|
195
195
|
success: true,
|
|
196
196
|
durationMs: Math.round(performance.now() - startedAt),
|
|
197
197
|
});
|
|
198
|
-
return presentSemanticWorkResult(result, target);
|
|
198
|
+
return presentSemanticWorkResult(result, target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
199
199
|
}
|
|
200
200
|
catch (error) {
|
|
201
201
|
if (extra.signal.aborted)
|
|
@@ -225,7 +225,7 @@ export function registerWorkspaceAuxiliaryTools(options) {
|
|
|
225
225
|
const response = await coreOperations.capabilityRun({ workspaceId: executionWorkspaceId, name, arguments: capabilityArguments, file }, executionContext);
|
|
226
226
|
return name === "workspace.tasks"
|
|
227
227
|
? presentExecutionResult(response, target)
|
|
228
|
-
: presentSemanticWorkResult(response, target);
|
|
228
|
+
: presentSemanticWorkResult(response, target, hostScopeIdFor(extra._meta, extra.sessionId));
|
|
229
229
|
}
|
|
230
230
|
const workspace = workspaces.getWorkspace(executionWorkspaceId);
|
|
231
231
|
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(extra._meta, extra.sessionId), activityRequestFor({ workspaceId: executionWorkspaceId, name, action, arguments: capabilityArguments, file }, executionContext), {
|
|
@@ -24,7 +24,7 @@ export function buildToolDescriptions(config) {
|
|
|
24
24
|
? ""
|
|
25
25
|
: " Use shell commands for search and directory inspection instead of dedicated MCP search tools.";
|
|
26
26
|
return {
|
|
27
|
-
read: `Read one file or multiple files
|
|
27
|
+
read: `Read one file or multiple files. Inside an open workspace, relative paths, Workspace instructions, advertised capability guides, and loaded advertised resources are available as usual.${skillCapability} For read-only inspection before opening a Workspace, omit workspaceId and use absolute paths already inside configured allowedRoots; unscoped reads never activate Workspace instructions, Skills, capabilities, or execution state. Use path for one target or paths for multiple targets; offset/limit apply to every target in a bulk read.`,
|
|
28
28
|
write: `Create or completely overwrite a file inside an open workspace or the OS temp directory. Workspace paths may be relative; OS temp paths may be absolute. Call ${toolNames.openWorkspace} first and pass workspaceId.`,
|
|
29
29
|
edit: `Edit one file or multiple files inside an open workspace or the OS temp directory by replacing exact text blocks. Use path for one target or paths for multiple targets; a bulk Edit applies the same edits to every file and preflights all targets before the first mutation. Each oldText must match a unique, non-overlapping region of the original file. Workspace paths may be relative; OS temp paths may be absolute. Call ${toolNames.openWorkspace} first and pass workspaceId.`,
|
|
30
30
|
rename: `Rename or move one file or directory inside an open workspace or the OS temp directory without overwriting an existing destination. Source and destination must both remain inside the permitted file roots. Call ${toolNames.openWorkspace} first and pass workspaceId.`,
|
|
@@ -261,6 +261,7 @@ export function loadConfig(env = process.env) {
|
|
|
261
261
|
? files.config.subagents === true
|
|
262
262
|
: parseBoolean(productEnv(env, "SUBAGENTS")),
|
|
263
263
|
languageServers: files.config.languageServers ?? {},
|
|
264
|
+
allowAgentLanguageServerInstall: files.config.allowAgentLanguageServerInstall === true,
|
|
264
265
|
agentDir: resolve(expandHomePath(productEnv(env, "AGENT_DIR") ?? files.config.agentDir ?? defaultAgentDir())),
|
|
265
266
|
systemInstructionsPath: parseSystemInstructionsPath(productEnv(env, "SYSTEM_INSTRUCTIONS_PATH") ?? files.config.systemInstructionsPath),
|
|
266
267
|
hooks: mergeHookConfigs(parseHookConfig(files.config.hooks), parseHookConfig(files.hooks), files.hookFiles),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import {
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
5
|
import { withFileLock } from "../state/lock/file-lock.js";
|
|
6
6
|
import { expandHomePath } from "../../mcp/filesystem/roots.js";
|
|
7
7
|
import { mergeHookConfigs, parseHookFile, } from "../../mcp/hooks/hooks.js";
|
|
@@ -135,15 +135,6 @@ export async function removeForgeRelayRemote(alias, env = process.env) {
|
|
|
135
135
|
return { ...auth, remotes };
|
|
136
136
|
}, env);
|
|
137
137
|
}
|
|
138
|
-
export function ensureForgeRelayDefaultSkills(env = process.env) {
|
|
139
|
-
const targetPath = join(forgerelaySkillsDir(env), "subagent-delegation", "SKILL.md");
|
|
140
|
-
if (existsSync(targetPath))
|
|
141
|
-
return [];
|
|
142
|
-
const sourcePath = new URL("../../../skills/subagent-delegation/SKILL.md", import.meta.url);
|
|
143
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
144
|
-
writeFileSync(targetPath, readFileSync(sourcePath, "utf8"), { mode: 0o644 });
|
|
145
|
-
return [targetPath];
|
|
146
|
-
}
|
|
147
138
|
export function resolveSubagentsFlag(config, env = process.env) {
|
|
148
139
|
const value = env.FORGERELAY_SUBAGENTS;
|
|
149
140
|
if (value === undefined)
|
package/dist/server.js
CHANGED
|
@@ -10,6 +10,7 @@ import { downloadIncomingArtifact, isArtifactDownloadSupportedPlatform } from ".
|
|
|
10
10
|
import { ArtifactError } from "./mcp/artifacts/artifact-error.js";
|
|
11
11
|
import { loadConfig } from "./runtime/config/config.js";
|
|
12
12
|
import { CodeIntelligenceError } from "./lsp/code-intelligence.js";
|
|
13
|
+
import { installManagedLanguageServers, installedManagedLanguageServers, supportedManagedLanguageServers, } from "./lsp/runtime/managed-language-servers.js";
|
|
13
14
|
import { HookRunner } from "./mcp/hooks/hooks.js";
|
|
14
15
|
import { checkHookConfiguration } from "./mcp/hooks/hook-cli.js";
|
|
15
16
|
import { buildServerInstructions, buildToolDescriptions, toolNames } from "./mcp/server-instructions.js";
|
|
@@ -38,7 +39,7 @@ import { createOperationRuntime } from "./mcp/server/operations/runtime/operatio
|
|
|
38
39
|
import { registerWorkspaceAuxiliaryTools } from "./mcp/server/workspace/runtime/workspace-tools.js";
|
|
39
40
|
import { registerOpenWorkspaceTool } from "./mcp/server/workspace/runtime/workspace-open.js";
|
|
40
41
|
import { redactSkillDiagnosticPaths } from "./mcp/server/core/schemas.js";
|
|
41
|
-
import { attachWorkspaceTaskReminder, remapCompositeToolResult, toolResultIsError } from "./mcp/server/core/tool-support.js";
|
|
42
|
+
import { attachWorkspaceContextUpdate, attachWorkspaceTaskReminder, remapCompositeToolResult, toolResultIsError } from "./mcp/server/core/tool-support.js";
|
|
42
43
|
export function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, subagentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries, options = {}) {
|
|
43
44
|
const connectionScopeId = `mcp-connection:${randomUUID()}`;
|
|
44
45
|
const activityPanelApp = createActivityPanelApp(config, FORGERELAY_VERSION);
|
|
@@ -95,10 +96,14 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
95
96
|
return undefined;
|
|
96
97
|
}
|
|
97
98
|
};
|
|
98
|
-
const presentSemanticWorkResult = (result, target) => {
|
|
99
|
-
|
|
99
|
+
const presentSemanticWorkResult = (result, target, conversationScopeId) => {
|
|
100
|
+
let presented = presentExecutionResult(result, target);
|
|
100
101
|
if (toolResultIsError(presented))
|
|
101
102
|
return presented;
|
|
103
|
+
if (!remoteWorkspaces.has(target.executionWorkspaceId)) {
|
|
104
|
+
const update = workspaces.claimResourceUpdates(target.executionWorkspaceId, conversationScopeId);
|
|
105
|
+
presented = attachWorkspaceContextUpdate(presented, update?.text);
|
|
106
|
+
}
|
|
102
107
|
const reminderWorkspaceId = taskReminderWorkspaceIdFor(target);
|
|
103
108
|
return attachWorkspaceTaskReminder(presented, reminderWorkspaceId ? taskReminders.recordWork(reminderWorkspaceId) : undefined);
|
|
104
109
|
};
|
|
@@ -154,10 +159,33 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
154
159
|
},
|
|
155
160
|
codeIntelligence: {
|
|
156
161
|
available: true,
|
|
157
|
-
run: async (input, context,
|
|
162
|
+
run: async (input, context, runOptions) => {
|
|
163
|
+
if (input.operation === "managed.status") {
|
|
164
|
+
return {
|
|
165
|
+
value: {
|
|
166
|
+
supported: supportedManagedLanguageServers(),
|
|
167
|
+
installed: installedManagedLanguageServers(config.configDir),
|
|
168
|
+
agentInstallAllowed: config.allowAgentLanguageServerInstall,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (input.operation === "managed.install") {
|
|
173
|
+
if (!config.allowAgentLanguageServerInstall) {
|
|
174
|
+
throw new CapabilityError("code.managed_install_disabled", "Agent-managed Language Server installation is disabled. Enable it explicitly with forgerelay init --force.");
|
|
175
|
+
}
|
|
176
|
+
const install = options.managedLanguageServerInstaller ?? installManagedLanguageServers;
|
|
177
|
+
const installed = await install(input.servers, config.configDir);
|
|
178
|
+
return {
|
|
179
|
+
value: {
|
|
180
|
+
...installed,
|
|
181
|
+
availableNow: installedManagedLanguageServers(config.configDir),
|
|
182
|
+
restartRequired: false,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
158
186
|
try {
|
|
159
187
|
return {
|
|
160
|
-
value: await codeIntelligence.run(requireCapabilityWorkspaceRoot(context), input, { signal:
|
|
188
|
+
value: await codeIntelligence.run(requireCapabilityWorkspaceRoot(context), input, { signal: runOptions.signal }),
|
|
161
189
|
};
|
|
162
190
|
}
|
|
163
191
|
catch (error) {
|
|
@@ -291,7 +319,8 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
291
319
|
};
|
|
292
320
|
};
|
|
293
321
|
const operationRuntime = createOperationRuntime({
|
|
294
|
-
config, workspaces, activityLifecycle, hooks, processSessions, bashOutputStore,
|
|
322
|
+
config, workspaces, activityLifecycle, hooks, processSessions, bashOutputStore,
|
|
323
|
+
capabilityRegistry, codeIntelligence, hostScopeIdFor,
|
|
295
324
|
});
|
|
296
325
|
batchExecutor = operationRuntime.batchExecutor;
|
|
297
326
|
const { coreOperations, nativeBulkMutations } = operationRuntime;
|
|
@@ -430,6 +459,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
430
459
|
coreOperations,
|
|
431
460
|
nativeBulkMutations,
|
|
432
461
|
activityLifecycle,
|
|
462
|
+
codeIntelligence,
|
|
433
463
|
hooks,
|
|
434
464
|
toolDescriptions,
|
|
435
465
|
resolveExecutionTarget,
|