@akira-tl/forgerelay 0.5.6 → 0.6.0
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 +43 -0
- package/README.md +7 -3
- package/capabilities/host-integration/GUIDE.md +15 -9
- package/dist/activity/host-turn-store.js +9 -7
- package/dist/activity/lifecycle.js +1 -1
- package/dist/activity/mcp-query-tools.js +110 -18
- package/dist/activity/query-service.js +4 -4
- package/dist/activity/ui/contract.js +2 -0
- package/dist/cli.js +47 -32
- package/dist/config.js +31 -3
- package/dist/db/migrations.js +30 -0
- package/dist/db/schema.js +2 -0
- package/dist/logger.js +6 -5
- package/dist/lsp/test-support/server-fixture.js +1 -1
- package/dist/mcp/server-instructions.js +7 -6
- package/dist/mcp-app-template.js +108 -12
- package/dist/oauth/public-url.js +14 -0
- package/dist/oauth/router.js +49 -0
- package/dist/process-sessions.js +2 -1
- package/dist/request-meta.js +4 -0
- package/dist/server.js +250 -84
- package/dist/skills.js +58 -1
- package/dist/ui/.vite/manifest.json +360 -312
- package/dist/ui/activity-panel-app.html +17 -0
- package/dist/ui/assets/activity-panel-app-CjZVvVNc.js +2 -0
- package/dist/ui/assets/angular-html-BkgDQl-G.js +1 -0
- package/dist/ui/assets/{angular-ts-BwPE8YDt.js → angular-ts-Bxh4-O1-.js} +1 -1
- package/dist/ui/assets/{apl-CGbONVvU.js → apl-BmYHKQ6m.js} +1 -1
- package/dist/ui/assets/{astro-DIAPy7cj.js → astro-C51vqDKq.js} +1 -1
- package/dist/ui/assets/{blade-Cgtd19Ky.js → blade-CdhVsmwq.js} +1 -1
- package/dist/ui/assets/c-IBzBpxAO.js +1 -0
- package/dist/ui/assets/chunk-EyZ2wyi3.js +1 -0
- package/dist/ui/assets/{cobol-G-7HLVKW.js → cobol-Cf6azoN_.js} +1 -1
- package/dist/ui/assets/{coffee-DQFyKxEB.js → coffee-vVdGzlmt.js} +1 -1
- package/dist/ui/assets/cpp-CVHeO-Jk.js +1 -0
- package/dist/ui/assets/{crystal-DuiWhEO3.js → crystal-5UZy6KIX.js} +1 -1
- package/dist/ui/assets/css-DEfus9J5.js +1 -0
- package/dist/ui/assets/{edge-COkalhFi.js → edge-CESgo4ia.js} +1 -1
- package/dist/ui/assets/{elixir-cdlzbYR1.js → elixir-9FKfw4cJ.js} +1 -1
- package/dist/ui/assets/{elm-ZwDYuYpb.js → elm-Dv7A0aLa.js} +1 -1
- package/dist/ui/assets/{erb-C8WG9TtC.js → erb-DHFmDvM0.js} +1 -1
- package/dist/ui/assets/{git-rebase-CMlDVJBT.js → git-rebase-utbU6CzV.js} +1 -1
- package/dist/ui/assets/{glimmer-js-DeLOQlol.js → glimmer-js-t2kiCkyB.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-DTEM04TK.js → glimmer-ts-F0Jv5okV.js} +1 -1
- package/dist/ui/assets/glsl-_ooCjaeB.js +1 -0
- package/dist/ui/assets/graphql-B9LqP-cY.js +1 -0
- package/dist/ui/assets/{hack-C14sh6Lh.js → hack-8yjdbaSA.js} +1 -1
- package/dist/ui/assets/haml-BU5EoZBn.js +1 -0
- package/dist/ui/assets/{handlebars-eS-J0O-M.js → handlebars-BBFEg4Lo.js} +1 -1
- package/dist/ui/assets/{heavy-payload-BKB0QDOI.js → heavy-payload-vGgBRvNX.js} +1 -1
- package/dist/ui/assets/html-D-C6YZk4.js +1 -0
- package/dist/ui/assets/{html-derivative-BlVZU2id.js → html-derivative-DJAQcZyc.js} +1 -1
- package/dist/ui/assets/{http-BmbZzA5x.js → http-Dg5LSYRK.js} +1 -1
- package/dist/ui/assets/{hurl-Dnd_YpOD.js → hurl-Y5uYzMZR.js} +1 -1
- package/dist/ui/assets/java-ppHvycj5.js +1 -0
- package/dist/ui/assets/javascript-DDTjKLlQ.js +1 -0
- package/dist/ui/assets/{jinja-DTvkZdHA.js → jinja-Bt30h8Ik.js} +1 -1
- package/dist/ui/assets/{jison-DUJG-tmh.js → jison-D5uZ1Nyo.js} +1 -1
- package/dist/ui/assets/json-Cw91r8h1.js +1 -0
- package/dist/ui/assets/jsx-04X2FT_1.js +1 -0
- package/dist/ui/assets/{julia-P5B4pHFa.js → julia-76hRaaGz.js} +1 -1
- package/dist/ui/assets/{just-DYaoh7C6.js → just-CDE7W0vW.js} +1 -1
- package/dist/ui/assets/{latex-CI5lnkWV.js → latex-CVO2Byab.js} +1 -1
- package/dist/ui/assets/{liquid-D3LnAs_z.js → liquid-CA-AS3EX.js} +1 -1
- package/dist/ui/assets/lua-CiyBp52Y.js +1 -0
- package/dist/ui/assets/{marko-CRDrjqfS.js → marko-_BWrPfny.js} +1 -1
- package/dist/ui/assets/{mdc-ZzwgmJVn.js → mdc-PyJv85eS.js} +1 -1
- package/dist/ui/assets/{nginx-BAvFXGSV.js → nginx-DAWVw8yu.js} +1 -1
- package/dist/ui/assets/{nim-BHIFawe9.js → nim-CKE1dzpU.js} +1 -1
- package/dist/ui/assets/{perl-C_f5AnLa.js → perl-BBhWva19.js} +1 -1
- package/dist/ui/assets/{php-DDqpegL5.js → php-ChIeaAoF.js} +1 -1
- package/dist/ui/assets/{pug-CSaAZXyd.js → pug-CMgI5afi.js} +1 -1
- package/dist/ui/assets/{qml-CNHvRs_z.js → qml-CjxTkcFH.js} +1 -1
- package/dist/ui/assets/r-DfFga4si.js +1 -0
- package/dist/ui/assets/{razor-BDfAPy7I.js → razor-i4DbHr8p.js} +1 -1
- package/dist/ui/assets/regexp-iC9_nbSI.js +1 -0
- package/dist/ui/assets/{review-payload-D7GIdYDj.js → review-payload-4erWKckt.js} +1 -1
- package/dist/ui/assets/{rst-BzPP2gle.js → rst-oy_97Kl4.js} +1 -1
- package/dist/ui/assets/{ruby-CQjzokzX.js → ruby-C5zYZIyl.js} +1 -1
- package/dist/ui/assets/{sas-Bp37KGr7.js → sas-BnMEh2S3.js} +1 -1
- package/dist/ui/assets/{scrollbar-HUMBdLLK.js → scrollbar-CaOPzUJd.js} +6 -6
- package/dist/ui/assets/scss-BfZXsAWQ.js +1 -0
- package/dist/ui/assets/shellscript-BC2srNaB.js +1 -0
- package/dist/ui/assets/{shellsession-CNc62UhY.js → shellsession-BpE5o7_3.js} +1 -1
- package/dist/ui/assets/{soy-CF44SpBo.js → soy-COesmhTY.js} +1 -1
- package/dist/ui/assets/sql-Ccv_yeUm.js +1 -0
- package/dist/ui/assets/{stata-DVeMA68W.js → stata-CGQEuSZq.js} +1 -1
- package/dist/ui/assets/{surrealql-52sF96Yv.js → surrealql-Bz4JbU-9.js} +1 -1
- package/dist/ui/assets/{svelte-ydwT7UbF.js → svelte-1jM2ejXj.js} +1 -1
- package/dist/ui/assets/{templ-BXcoVoGh.js → templ-C-VwELcp.js} +1 -1
- package/dist/ui/assets/{tex-COsYFp7O.js → tex-t1rUiQCm.js} +1 -1
- package/dist/ui/assets/{ts-tags-CF--LE1s.js → ts-tags-Bewoa6pS.js} +1 -1
- package/dist/ui/assets/tsx-CTXzrBkN.js +1 -0
- package/dist/ui/assets/{twig-YoM-DiI5.js → twig-DVg-vELl.js} +1 -1
- package/dist/ui/assets/typescript--ElHoFkw.js +1 -0
- package/dist/ui/assets/{vue-html-CPdGGk4T.js → vue-html-BHtJ8geX.js} +1 -1
- package/dist/ui/assets/{vue-vine-B5bjgk0a.js → vue-vine-CNSGAWwc.js} +1 -1
- package/dist/ui/assets/{vue-B5LDEy4l.js → vue-wPQ7UlMy.js} +1 -1
- package/dist/ui/assets/workspace-app-CcrHAUIn.css +1 -0
- package/dist/ui/assets/workspace-app-DJmkPYJC.js +1 -0
- package/dist/ui/assets/workspace-app-DkAiSl_0.js +111 -0
- package/dist/ui/assets/workspace-app-QyauBrJX.js +5 -0
- package/dist/ui/assets/workspace-lifecycle-app-BIXEo53I.js +1 -0
- package/dist/ui/assets/xml-JnmX6vyS.js +1 -0
- package/dist/ui/assets/{xsl-wls_CEK_.js → xsl-3_vJ27aE.js} +1 -1
- package/dist/ui/assets/yaml-OAcZkP-w.js +1 -0
- package/dist/ui/workspace-app.html +5 -2
- package/dist/ui/workspace-lifecycle-app.html +18 -0
- package/dist/workspaces.js +11 -0
- package/docs/configuration.md +36 -4
- package/docs/debugging.md +75 -17
- package/docs/gotchas.md +15 -9
- package/docs/setup.md +24 -10
- package/package.json +4 -3
- package/scripts/debug/accept.mjs +79 -27
- package/scripts/debug/host/run.sh +180 -0
- package/scripts/debug/host/serve.mjs +64 -0
- package/scripts/debug/runtime.mjs +57 -2
- package/scripts/debug/runtime.test.mjs +74 -0
- package/scripts/debug/serve.mjs +4 -5
- package/scripts/dev-server.mjs +32 -3
- package/scripts/release/release-gate.test.mjs +43 -0
- package/scripts/release/release-version.test.mjs +107 -0
- package/scripts/release-parity.mjs +70 -46
- package/scripts/release-proof.mjs +2 -2
- package/scripts/release-proof.test.mjs +18 -0
- package/scripts/release-version.mjs +17 -8
- package/dist/ui/assets/angular-html-BT2Z6jee.js +0 -1
- package/dist/ui/assets/c-acnvZ8tm.js +0 -1
- package/dist/ui/assets/cpp-UyrmHXan.js +0 -1
- package/dist/ui/assets/css-D_WCbY6m.js +0 -1
- package/dist/ui/assets/glsl-IJV4SB-M.js +0 -1
- package/dist/ui/assets/graphql-PM-WDnoB.js +0 -1
- package/dist/ui/assets/haml-B_5nS6Kx.js +0 -1
- package/dist/ui/assets/html-C3TPjrC5.js +0 -1
- package/dist/ui/assets/java-ne9HdIVn.js +0 -1
- package/dist/ui/assets/javascript-DpHT58rr.js +0 -1
- package/dist/ui/assets/json-DJmnBdxW.js +0 -1
- package/dist/ui/assets/jsx-BclJnZnN.js +0 -1
- package/dist/ui/assets/lua-dZC7ZQIY.js +0 -1
- package/dist/ui/assets/r-CAfoQND2.js +0 -1
- package/dist/ui/assets/regexp-Mug5jltQ.js +0 -1
- package/dist/ui/assets/scss-C_Z1DYnw.js +0 -1
- package/dist/ui/assets/shellscript-CLfJpn3I.js +0 -1
- package/dist/ui/assets/sql-CYnzfiQ1.js +0 -1
- package/dist/ui/assets/tsx-DLBt2-2f.js +0 -1
- package/dist/ui/assets/typescript-Ce9JGkNI.js +0 -1
- package/dist/ui/assets/workspace-app-Cm9EX5x4.js +0 -117
- package/dist/ui/assets/workspace-app-DHvE2ojW.css +0 -1
- package/dist/ui/assets/xml-Cun8xb0T.js +0 -1
- package/dist/ui/assets/yaml-BxuXn9OL.js +0 -1
- package/scripts/release/tag-local-ci.mjs +0 -144
package/dist/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import { resolve } from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
9
|
-
import {
|
|
9
|
+
import { getOAuthProtectedResourceMetadataUrl } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
10
10
|
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
11
11
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
12
12
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -36,6 +36,8 @@ import { createOpenAIIncomingArtifactAdapter, IncomingArtifactAdapterRegistry, }
|
|
|
36
36
|
import { logEvent, requestIp, requestPath, commandPreview, transportSessionIdPrefix, workspaceLogLabel, } from "./logger.js";
|
|
37
37
|
import { editFileTool, readFileTool, writeFileTool, } from "./pi-tools.js";
|
|
38
38
|
import { SingleUserOAuthProvider } from "./oauth-provider.js";
|
|
39
|
+
import { createForgeRelayAuthRouter } from "./oauth/router.js";
|
|
40
|
+
import { publicEndpointUrl } from "./oauth/public-url.js";
|
|
39
41
|
import { BatchExecutor } from "./operations/batch/executor.js";
|
|
40
42
|
import { executeBulkRead } from "./operations/bulk-read.js";
|
|
41
43
|
import { NativeBulkMutationExecutor } from "./operations/native-bulk-mutations.js";
|
|
@@ -43,8 +45,8 @@ import { createCoreOperationExecutor, } from "./operations/core-operation-execut
|
|
|
43
45
|
import { McpTransportRegistry, } from "./mcp-sessions.js";
|
|
44
46
|
import { ProcessManager, resolveProcessId, } from "./process-sessions.js";
|
|
45
47
|
import { createReviewCheckpointManager } from "./review-checkpoints.js";
|
|
46
|
-
import { openAiConversationScopeId } from "./request-meta.js";
|
|
47
|
-
import { readWorkspaceAppManifestEntry, resolveWorkspaceAppIdentity, WORKSPACE_APP_LEGACY_URI, WORKSPACE_APP_URI_TEMPLATE, } from "./mcp-app-template.js";
|
|
48
|
+
import { hostConversationScopeId, openAiConversationScopeId } from "./request-meta.js";
|
|
49
|
+
import { ACTIVITY_PANEL_APP_LEGACY_URI, ACTIVITY_PANEL_APP_URI_TEMPLATE, MCP_APP_RESOURCE_TEMPLATE_REVISION, readActivityPanelAppManifestEntry, readWorkspaceAppManifestEntry, readWorkspaceLifecycleAppManifestEntry, resolveActivityPanelAppIdentity, resolveWorkspaceAppIdentity, resolveWorkspaceLifecycleAppIdentity, WORKSPACE_APP_LEGACY_URI, WORKSPACE_APP_URI_TEMPLATE, WORKSPACE_LIFECYCLE_APP_LEGACY_URI, WORKSPACE_LIFECYCLE_APP_URI_TEMPLATE, } from "./mcp-app-template.js";
|
|
48
50
|
import { shutdownHttpServer } from "./server-shutdown.js";
|
|
49
51
|
import { formatPathForPrompt } from "./skills.js";
|
|
50
52
|
import { createWorkspaceStore } from "./workspace-store.js";
|
|
@@ -77,26 +79,22 @@ const SHELL_TOOL_ANNOTATIONS = {
|
|
|
77
79
|
openWorldHint: true,
|
|
78
80
|
};
|
|
79
81
|
function shouldAttachWidget(mode, kind) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
case "changes":
|
|
84
|
-
return kind === "workspace" || kind === "capability";
|
|
85
|
-
case "full":
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
82
|
+
if (mode === "off")
|
|
83
|
+
return false;
|
|
84
|
+
return kind === "activity";
|
|
88
85
|
}
|
|
89
86
|
function toolWidgetDescriptorMeta(config, kind) {
|
|
90
87
|
if (!shouldAttachWidget(config.widgets, kind))
|
|
91
88
|
return { _meta: {} };
|
|
92
|
-
const resourceUri =
|
|
89
|
+
const resourceUri = kind === "activity"
|
|
90
|
+
? currentActivityPanelAppIdentity(config).uri
|
|
91
|
+
: currentWorkspaceLifecycleAppIdentity(config).uri;
|
|
93
92
|
return {
|
|
94
93
|
_meta: {
|
|
95
94
|
ui: {
|
|
96
95
|
resourceUri,
|
|
97
96
|
visibility: ["model", "app"],
|
|
98
97
|
},
|
|
99
|
-
"openai/outputTemplate": resourceUri,
|
|
100
98
|
},
|
|
101
99
|
};
|
|
102
100
|
}
|
|
@@ -152,8 +150,40 @@ function resultOutputSchema(extra = {}) {
|
|
|
152
150
|
const workspaceSkillOutputSchema = z.object({
|
|
153
151
|
name: z.string(),
|
|
154
152
|
description: z.string(),
|
|
155
|
-
path: z.string(),
|
|
156
153
|
});
|
|
154
|
+
const workspaceSkillDiagnosticOutputSchema = z.object({
|
|
155
|
+
type: z.enum(["warning", "error", "collision"]),
|
|
156
|
+
message: z.string(),
|
|
157
|
+
collision: z.object({
|
|
158
|
+
resourceType: z.enum(["extension", "skill", "prompt", "theme"]),
|
|
159
|
+
name: z.string(),
|
|
160
|
+
}).optional(),
|
|
161
|
+
});
|
|
162
|
+
function redactSkillDiagnosticPaths(diagnostics) {
|
|
163
|
+
return diagnostics.map((diagnostic) => {
|
|
164
|
+
let message = diagnostic.message;
|
|
165
|
+
const hiddenPaths = [
|
|
166
|
+
diagnostic.path,
|
|
167
|
+
diagnostic.collision?.winnerPath,
|
|
168
|
+
diagnostic.collision?.loserPath,
|
|
169
|
+
].filter((path) => Boolean(path));
|
|
170
|
+
for (const path of hiddenPaths) {
|
|
171
|
+
message = message.split(path).join("<skill-path>");
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
type: diagnostic.type,
|
|
175
|
+
message,
|
|
176
|
+
...(diagnostic.collision
|
|
177
|
+
? {
|
|
178
|
+
collision: {
|
|
179
|
+
resourceType: diagnostic.collision.resourceType,
|
|
180
|
+
name: diagnostic.collision.name,
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
: {}),
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
}
|
|
157
187
|
const capabilityFingerprintOutputSchema = z.object({
|
|
158
188
|
version: z.string(),
|
|
159
189
|
toolMode: z.enum(["minimal", "full", "codex"]),
|
|
@@ -270,6 +300,10 @@ function mcpRequestDebugFields(body) {
|
|
|
270
300
|
const params = request.params && typeof request.params === "object" && !Array.isArray(request.params)
|
|
271
301
|
? request.params
|
|
272
302
|
: undefined;
|
|
303
|
+
const rpcMeta = params?._meta && typeof params._meta === "object" && !Array.isArray(params._meta)
|
|
304
|
+
? params._meta
|
|
305
|
+
: undefined;
|
|
306
|
+
const rpcMetaKeys = rpcMeta ? Object.keys(rpcMeta).sort() : [];
|
|
273
307
|
let rpcTarget;
|
|
274
308
|
if (rpcMethod === "resources/read" && typeof params?.uri === "string") {
|
|
275
309
|
rpcTarget = params.uri;
|
|
@@ -277,7 +311,11 @@ function mcpRequestDebugFields(body) {
|
|
|
277
311
|
else if (rpcMethod === "tools/call" && typeof params?.name === "string") {
|
|
278
312
|
rpcTarget = params.name;
|
|
279
313
|
}
|
|
280
|
-
return {
|
|
314
|
+
return {
|
|
315
|
+
rpcMethod,
|
|
316
|
+
rpcTarget,
|
|
317
|
+
...(rpcMetaKeys.length > 0 ? { rpcMetaKeys } : {}),
|
|
318
|
+
};
|
|
281
319
|
}
|
|
282
320
|
function logToolCall(config, fields) {
|
|
283
321
|
if (!config.logging.toolCalls)
|
|
@@ -368,24 +406,64 @@ function uiManifestUrl() {
|
|
|
368
406
|
function uiBuildDirectoryUrl() {
|
|
369
407
|
return new URL("../dist/ui/", import.meta.url);
|
|
370
408
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
409
|
+
const cachedWorkspaceAppIdentities = new Map();
|
|
410
|
+
const cachedWorkspaceLifecycleAppIdentities = new Map();
|
|
411
|
+
const cachedActivityPanelAppIdentities = new Map();
|
|
412
|
+
function appResourceContractRevision(config) {
|
|
413
|
+
return [
|
|
414
|
+
MCP_APP_RESOURCE_TEMPLATE_REVISION,
|
|
415
|
+
`publicBaseUrls=${JSON.stringify(config.publicBaseUrls)}`,
|
|
416
|
+
].join("\0");
|
|
417
|
+
}
|
|
418
|
+
function appIdentityOptions(config) {
|
|
419
|
+
return {
|
|
374
420
|
manifestUrl: uiManifestUrl(),
|
|
375
421
|
buildDirectoryUrl: uiBuildDirectoryUrl(),
|
|
376
422
|
fallbackRevision: FORGERELAY_VERSION,
|
|
377
|
-
|
|
378
|
-
|
|
423
|
+
resourceTemplateRevision: appResourceContractRevision(config),
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function currentWorkspaceAppIdentity(config) {
|
|
427
|
+
const key = appResourceContractRevision(config);
|
|
428
|
+
let identity = cachedWorkspaceAppIdentities.get(key);
|
|
429
|
+
if (!identity) {
|
|
430
|
+
identity = resolveWorkspaceAppIdentity(appIdentityOptions(config));
|
|
431
|
+
cachedWorkspaceAppIdentities.set(key, identity);
|
|
432
|
+
}
|
|
433
|
+
return identity;
|
|
434
|
+
}
|
|
435
|
+
function currentWorkspaceLifecycleAppIdentity(config) {
|
|
436
|
+
const key = appResourceContractRevision(config);
|
|
437
|
+
let identity = cachedWorkspaceLifecycleAppIdentities.get(key);
|
|
438
|
+
if (!identity) {
|
|
439
|
+
identity = resolveWorkspaceLifecycleAppIdentity(appIdentityOptions(config));
|
|
440
|
+
cachedWorkspaceLifecycleAppIdentities.set(key, identity);
|
|
441
|
+
}
|
|
442
|
+
return identity;
|
|
443
|
+
}
|
|
444
|
+
function currentActivityPanelAppIdentity(config) {
|
|
445
|
+
const key = appResourceContractRevision(config);
|
|
446
|
+
let identity = cachedActivityPanelAppIdentities.get(key);
|
|
447
|
+
if (!identity) {
|
|
448
|
+
identity = resolveActivityPanelAppIdentity(appIdentityOptions(config));
|
|
449
|
+
cachedActivityPanelAppIdentities.set(key, identity);
|
|
450
|
+
}
|
|
451
|
+
return identity;
|
|
379
452
|
}
|
|
380
453
|
function getWorkspaceAppManifestEntry() {
|
|
381
454
|
return readWorkspaceAppManifestEntry(uiManifestUrl());
|
|
382
455
|
}
|
|
456
|
+
function getWorkspaceLifecycleAppManifestEntry() {
|
|
457
|
+
return readWorkspaceLifecycleAppManifestEntry(uiManifestUrl());
|
|
458
|
+
}
|
|
459
|
+
function getActivityPanelAppManifestEntry() {
|
|
460
|
+
return readActivityPanelAppManifestEntry(uiManifestUrl());
|
|
461
|
+
}
|
|
383
462
|
function assetUrl(baseUrl, assetPath) {
|
|
384
463
|
return `${baseUrl}/${assetPath.replace(/^\/+/, "")}`;
|
|
385
464
|
}
|
|
386
|
-
function
|
|
465
|
+
function mcpAppHtml(config, entry, title, waitingMessage) {
|
|
387
466
|
const baseUrl = assetBaseUrl(config);
|
|
388
|
-
const entry = getWorkspaceAppManifestEntry();
|
|
389
467
|
const stylesheets = (entry.css ?? [])
|
|
390
468
|
.map((stylesheet) => ` <link rel="stylesheet" crossorigin href="${assetUrl(baseUrl, stylesheet)}" />`)
|
|
391
469
|
.join("\n");
|
|
@@ -394,25 +472,33 @@ function workspaceAppHtml(config) {
|
|
|
394
472
|
<head>
|
|
395
473
|
<meta charset="UTF-8" />
|
|
396
474
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
397
|
-
<title
|
|
475
|
+
<title>${title}</title>
|
|
398
476
|
<script type="module" crossorigin src="${assetUrl(baseUrl, entry.file)}"></script>
|
|
399
477
|
${stylesheets}
|
|
400
478
|
</head>
|
|
401
479
|
<body>
|
|
402
480
|
<main id="app" class="shell">
|
|
403
|
-
<section class="empty"
|
|
481
|
+
<section class="empty">${waitingMessage}</section>
|
|
404
482
|
</main>
|
|
405
483
|
</body>
|
|
406
484
|
</html>`;
|
|
407
485
|
}
|
|
486
|
+
function workspaceAppHtml(config) {
|
|
487
|
+
return mcpAppHtml(config, getWorkspaceAppManifestEntry(), "ForgeRelay Workspace", "Waiting for a workspace result.");
|
|
488
|
+
}
|
|
489
|
+
function workspaceLifecycleAppHtml(config) {
|
|
490
|
+
return mcpAppHtml(config, getWorkspaceLifecycleAppManifestEntry(), "ForgeRelay Workspace Lifecycle", "Waiting for a workspace result.");
|
|
491
|
+
}
|
|
492
|
+
function activityPanelAppHtml(config) {
|
|
493
|
+
return mcpAppHtml(config, getActivityPanelAppManifestEntry(), "ForgeRelay Activity Panel", "Waiting for Activity Panel state.");
|
|
494
|
+
}
|
|
408
495
|
function appDomain(config) {
|
|
409
496
|
return new URL(config.publicBaseUrl).origin;
|
|
410
497
|
}
|
|
411
498
|
function appCsp(config) {
|
|
412
|
-
const publicBaseUrl = config.publicBaseUrl.replace(/\/+$/, "");
|
|
413
499
|
return {
|
|
414
|
-
resourceDomains: [
|
|
415
|
-
connectDomains: [
|
|
500
|
+
resourceDomains: [...config.publicBaseUrls],
|
|
501
|
+
connectDomains: [...config.publicBaseUrls],
|
|
416
502
|
};
|
|
417
503
|
}
|
|
418
504
|
function uiBuildDirectory() {
|
|
@@ -424,59 +510,89 @@ function setAssetHeaders(res) {
|
|
|
424
510
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Range");
|
|
425
511
|
res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
|
|
426
512
|
}
|
|
427
|
-
async function
|
|
428
|
-
const entry = getWorkspaceAppManifestEntry();
|
|
513
|
+
async function assertMcpAppAssets(entry) {
|
|
429
514
|
const candidates = [entry.file, ...(entry.css ?? [])].map((assetPath) => new URL(`../dist/ui/${assetPath}`, import.meta.url));
|
|
430
|
-
for (const candidate of candidates)
|
|
515
|
+
for (const candidate of candidates)
|
|
431
516
|
await access(candidate);
|
|
432
|
-
}
|
|
433
517
|
}
|
|
434
|
-
function
|
|
518
|
+
function appResourceVariant(requestedUri, currentUri, legacyUri) {
|
|
435
519
|
if (requestedUri === currentUri)
|
|
436
520
|
return "current";
|
|
437
|
-
if (requestedUri ===
|
|
521
|
+
if (requestedUri === legacyUri)
|
|
438
522
|
return "legacy";
|
|
439
523
|
return "historical";
|
|
440
524
|
}
|
|
441
|
-
async function
|
|
442
|
-
const
|
|
443
|
-
const compatibility = workspaceAppCompatibilityKind(requestedUri, currentUri);
|
|
525
|
+
async function readMcpAppResource(config, options) {
|
|
526
|
+
const compatibility = appResourceVariant(options.requestedUri, options.currentUri, options.legacyUri);
|
|
444
527
|
try {
|
|
445
|
-
await
|
|
528
|
+
await assertMcpAppAssets(options.entry);
|
|
446
529
|
const result = {
|
|
447
|
-
contents: [
|
|
448
|
-
|
|
449
|
-
uri: requestedUri,
|
|
530
|
+
contents: [{
|
|
531
|
+
uri: options.requestedUri,
|
|
450
532
|
mimeType: RESOURCE_MIME_TYPE,
|
|
451
|
-
text:
|
|
533
|
+
text: options.html,
|
|
452
534
|
_meta: {
|
|
453
535
|
ui: {
|
|
454
536
|
domain: appDomain(config),
|
|
455
537
|
csp: appCsp(config),
|
|
456
538
|
},
|
|
539
|
+
// MCP Apps defines resource metadata under `_meta.ui`. Inspector 2.3.0
|
|
540
|
+
// reads the content-item CSP/domain from `_meta` directly, so mirror
|
|
541
|
+
// these values until that installed-host compatibility gap is gone.
|
|
542
|
+
domain: appDomain(config),
|
|
543
|
+
csp: appCsp(config),
|
|
457
544
|
},
|
|
458
|
-
},
|
|
459
|
-
],
|
|
545
|
+
}],
|
|
460
546
|
};
|
|
461
547
|
logEvent(config.logging, "debug", "mcp_app_template_read", {
|
|
462
|
-
requestedUri,
|
|
463
|
-
currentUri,
|
|
548
|
+
requestedUri: options.requestedUri,
|
|
549
|
+
currentUri: options.currentUri,
|
|
464
550
|
compatibility,
|
|
465
|
-
transportSessionIdPrefix: transportSessionIdPrefix(transportSessionId),
|
|
551
|
+
transportSessionIdPrefix: transportSessionIdPrefix(options.transportSessionId),
|
|
466
552
|
});
|
|
467
553
|
return result;
|
|
468
554
|
}
|
|
469
555
|
catch (error) {
|
|
470
556
|
logEvent(config.logging, "warn", "mcp_app_template_read_failed", {
|
|
471
|
-
requestedUri,
|
|
472
|
-
currentUri,
|
|
557
|
+
requestedUri: options.requestedUri,
|
|
558
|
+
currentUri: options.currentUri,
|
|
473
559
|
compatibility,
|
|
474
560
|
error: error instanceof Error ? error.message : String(error),
|
|
475
|
-
transportSessionIdPrefix: transportSessionIdPrefix(transportSessionId),
|
|
561
|
+
transportSessionIdPrefix: transportSessionIdPrefix(options.transportSessionId),
|
|
476
562
|
});
|
|
477
563
|
throw error;
|
|
478
564
|
}
|
|
479
565
|
}
|
|
566
|
+
function readWorkspaceAppResource(config, requestedUri, transportSessionId, currentUri = currentWorkspaceAppIdentity(config).uri, legacyUri = WORKSPACE_APP_LEGACY_URI) {
|
|
567
|
+
return readMcpAppResource(config, {
|
|
568
|
+
requestedUri,
|
|
569
|
+
currentUri,
|
|
570
|
+
legacyUri,
|
|
571
|
+
entry: getWorkspaceAppManifestEntry(),
|
|
572
|
+
html: workspaceAppHtml(config),
|
|
573
|
+
transportSessionId,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
function readWorkspaceLifecycleAppResource(config, requestedUri, transportSessionId) {
|
|
577
|
+
return readMcpAppResource(config, {
|
|
578
|
+
requestedUri,
|
|
579
|
+
currentUri: currentWorkspaceLifecycleAppIdentity(config).uri,
|
|
580
|
+
legacyUri: WORKSPACE_LIFECYCLE_APP_LEGACY_URI,
|
|
581
|
+
entry: getWorkspaceLifecycleAppManifestEntry(),
|
|
582
|
+
html: workspaceLifecycleAppHtml(config),
|
|
583
|
+
transportSessionId,
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
function readActivityPanelAppResource(config, requestedUri, transportSessionId) {
|
|
587
|
+
return readMcpAppResource(config, {
|
|
588
|
+
requestedUri,
|
|
589
|
+
currentUri: currentActivityPanelAppIdentity(config).uri,
|
|
590
|
+
legacyUri: ACTIVITY_PANEL_APP_LEGACY_URI,
|
|
591
|
+
entry: getActivityPanelAppManifestEntry(),
|
|
592
|
+
html: activityPanelAppHtml(config),
|
|
593
|
+
transportSessionId,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
480
596
|
const PROCESS_RESPONSE_OUTPUT_LINES = 10;
|
|
481
597
|
function compactProcessOutput(output) {
|
|
482
598
|
if (!output)
|
|
@@ -839,19 +955,19 @@ function activityRelationFor(context) {
|
|
|
839
955
|
...(context.turnId ? { turnId: context.turnId } : {}),
|
|
840
956
|
};
|
|
841
957
|
}
|
|
842
|
-
function runActivityTool(lifecycle, workspace,
|
|
958
|
+
function runActivityTool(lifecycle, workspace, conversationScopeId, tool, request, operation, outcome = standardActivityOutcome, relation = {}) {
|
|
843
959
|
return lifecycle.run({
|
|
844
960
|
tool,
|
|
845
961
|
workspace: workspaceActivitySnapshot(workspace),
|
|
846
|
-
conversationScopeId
|
|
962
|
+
conversationScopeId,
|
|
847
963
|
request,
|
|
848
964
|
operation,
|
|
849
965
|
outcome,
|
|
850
966
|
...relation,
|
|
851
967
|
});
|
|
852
968
|
}
|
|
853
|
-
function runActivityToolWithHooks(lifecycle, hooks, workspace,
|
|
854
|
-
return runActivityTool(lifecycle, workspace,
|
|
969
|
+
function runActivityToolWithHooks(lifecycle, hooks, workspace, conversationScopeId, request, hookOptions, relation = {}) {
|
|
970
|
+
return runActivityTool(lifecycle, workspace, conversationScopeId, hookOptions.tool, request, () => runToolWithHooks(hooks, hookOptions), standardActivityOutcome, relation);
|
|
855
971
|
}
|
|
856
972
|
function registerProcessTools(server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, shellRun) {
|
|
857
973
|
if (config.toolMode === "codex") {
|
|
@@ -1007,6 +1123,8 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
|
|
|
1007
1123
|
});
|
|
1008
1124
|
}
|
|
1009
1125
|
export function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries) {
|
|
1126
|
+
const connectionScopeId = `mcp-connection:${randomUUID()}`;
|
|
1127
|
+
const hostScopeIdFor = (requestMeta, transportSessionId) => hostConversationScopeId(requestMeta, transportSessionId, connectionScopeId);
|
|
1010
1128
|
const toolDescriptions = buildToolDescriptions(config);
|
|
1011
1129
|
const hooks = new HookRunner(config.hooks, config.logging, process.env, (workspaceId, result) => attachCompletedProcessNotices(processSessions, workspaceId, result, (snapshot) => recordBashCompletion(activityLifecycle, bashOutputStore, snapshot.outputId)));
|
|
1012
1130
|
const incomingArtifactRegistry = new IncomingArtifactAdapterRegistry(incomingArtifactAdapters);
|
|
@@ -1108,7 +1226,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1108
1226
|
read: async (input, context) => {
|
|
1109
1227
|
const { workspaceId, ...readInput } = input;
|
|
1110
1228
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1111
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, context.requestMeta, input, {
|
|
1229
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), input, {
|
|
1112
1230
|
signal: context.signal,
|
|
1113
1231
|
tool: toolNames.read,
|
|
1114
1232
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -1180,7 +1298,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1180
1298
|
write: async (input, context) => {
|
|
1181
1299
|
const { workspaceId, ...writeInput } = input;
|
|
1182
1300
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1183
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, context.requestMeta, input, {
|
|
1301
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), input, {
|
|
1184
1302
|
signal: context.signal,
|
|
1185
1303
|
tool: toolNames.write,
|
|
1186
1304
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -1241,7 +1359,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1241
1359
|
edit: async (input, context) => {
|
|
1242
1360
|
const { workspaceId, ...editInput } = input;
|
|
1243
1361
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1244
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, context.requestMeta, input, {
|
|
1362
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), input, {
|
|
1245
1363
|
signal: context.signal,
|
|
1246
1364
|
tool: toolNames.edit,
|
|
1247
1365
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -1303,7 +1421,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1303
1421
|
rename: async (input, context) => {
|
|
1304
1422
|
const { workspaceId, path, newPath } = input;
|
|
1305
1423
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1306
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, context.requestMeta, input, {
|
|
1424
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), input, {
|
|
1307
1425
|
signal: context.signal,
|
|
1308
1426
|
tool: toolNames.rename,
|
|
1309
1427
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -1362,7 +1480,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1362
1480
|
delete: async (input, context) => {
|
|
1363
1481
|
const { workspaceId, path, recursive } = input;
|
|
1364
1482
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1365
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, context.requestMeta, input, {
|
|
1483
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), input, {
|
|
1366
1484
|
signal: context.signal,
|
|
1367
1485
|
tool: toolNames.delete,
|
|
1368
1486
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -1446,7 +1564,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1446
1564
|
maxOutputTokens,
|
|
1447
1565
|
};
|
|
1448
1566
|
let undeliveredProcessId;
|
|
1449
|
-
const activityResult = await runActivityTool(activityLifecycle, workspace, context.requestMeta, surface, activityRequest, async (activityContext) => {
|
|
1567
|
+
const activityResult = await runActivityTool(activityLifecycle, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), surface, activityRequest, async (activityContext) => {
|
|
1450
1568
|
try {
|
|
1451
1569
|
const result = await runToolWithHooks(hooks, {
|
|
1452
1570
|
signal: context.signal,
|
|
@@ -1520,7 +1638,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1520
1638
|
const { workspaceId, name, arguments: capabilityArguments, file } = input;
|
|
1521
1639
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
1522
1640
|
let changedPaths = [];
|
|
1523
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, context.requestMeta, { workspaceId, name, action: "run", arguments: capabilityArguments, file }, {
|
|
1641
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), { workspaceId, name, action: "run", arguments: capabilityArguments, file }, {
|
|
1524
1642
|
signal: context.signal,
|
|
1525
1643
|
tool: toolNames.capability,
|
|
1526
1644
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -1616,9 +1734,18 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1616
1734
|
}, {
|
|
1617
1735
|
instructions: buildServerInstructions(config),
|
|
1618
1736
|
});
|
|
1619
|
-
const
|
|
1737
|
+
const workspacePanelStates = new Map();
|
|
1738
|
+
const rememberWorkspacePanelState = (workspaceId, response) => {
|
|
1739
|
+
if (typeof response._meta !== "object" || response._meta === null)
|
|
1740
|
+
return;
|
|
1741
|
+
const meta = response._meta;
|
|
1742
|
+
const card = meta.card;
|
|
1743
|
+
if (meta.tool !== toolNames.openWorkspace || typeof card !== "object" || card === null)
|
|
1744
|
+
return;
|
|
1745
|
+
workspacePanelStates.set(workspaceId, card);
|
|
1746
|
+
};
|
|
1620
1747
|
const workspaceAppResourceMetadata = {
|
|
1621
|
-
description: "
|
|
1748
|
+
description: "Historical ForgeRelay tool card UI.",
|
|
1622
1749
|
_meta: {
|
|
1623
1750
|
ui: {
|
|
1624
1751
|
domain: appDomain(config),
|
|
@@ -1626,12 +1753,23 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1626
1753
|
},
|
|
1627
1754
|
},
|
|
1628
1755
|
};
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1756
|
+
const workspaceLifecycleResourceMetadata = {
|
|
1757
|
+
description: "Historical ForgeRelay Workspace lifecycle UI compatibility resource.",
|
|
1758
|
+
_meta: workspaceAppResourceMetadata._meta,
|
|
1759
|
+
};
|
|
1760
|
+
const activityPanelResourceMetadata = {
|
|
1761
|
+
description: "ForgeRelay unified Workspace and Activity UI for one Host Turn.",
|
|
1762
|
+
_meta: workspaceAppResourceMetadata._meta,
|
|
1763
|
+
};
|
|
1764
|
+
const currentWorkspaceAppUri = currentWorkspaceAppIdentity(config).uri;
|
|
1765
|
+
registerAppResource(server, "ForgeRelay historical tool card", currentWorkspaceAppUri, workspaceAppResourceMetadata, async (uri, extra) => readWorkspaceAppResource(config, uri.toString(), extra.sessionId));
|
|
1766
|
+
registerAppResource(server, "ForgeRelay historical tool card legacy", WORKSPACE_APP_LEGACY_URI, workspaceAppResourceMetadata, async (uri, extra) => readWorkspaceAppResource(config, uri.toString(), extra.sessionId));
|
|
1767
|
+
server.registerResource("ForgeRelay historical tool card compatibility", new ResourceTemplate(WORKSPACE_APP_URI_TEMPLATE, { list: undefined }), { ...workspaceAppResourceMetadata, mimeType: RESOURCE_MIME_TYPE }, async (uri, _variables, extra) => readWorkspaceAppResource(config, uri.toString(), extra.sessionId));
|
|
1768
|
+
server.registerResource("ForgeRelay historical Workspace Lifecycle compatibility", new ResourceTemplate(WORKSPACE_LIFECYCLE_APP_URI_TEMPLATE, { list: undefined }), { ...workspaceLifecycleResourceMetadata, mimeType: RESOURCE_MIME_TYPE }, async (uri, _variables, extra) => readWorkspaceLifecycleAppResource(config, uri.toString(), extra.sessionId));
|
|
1769
|
+
const currentActivityPanelAppUri = currentActivityPanelAppIdentity(config).uri;
|
|
1770
|
+
registerAppResource(server, "ForgeRelay Activity Panel", currentActivityPanelAppUri, activityPanelResourceMetadata, async (uri, extra) => readActivityPanelAppResource(config, uri.toString(), extra.sessionId));
|
|
1771
|
+
registerAppResource(server, "ForgeRelay Activity Panel legacy", ACTIVITY_PANEL_APP_LEGACY_URI, activityPanelResourceMetadata, async (uri, extra) => readActivityPanelAppResource(config, uri.toString(), extra.sessionId));
|
|
1772
|
+
server.registerResource("ForgeRelay Activity Panel compatibility", new ResourceTemplate(ACTIVITY_PANEL_APP_URI_TEMPLATE, { list: undefined }), { ...activityPanelResourceMetadata, mimeType: RESOURCE_MIME_TYPE }, async (uri, _variables, extra) => readActivityPanelAppResource(config, uri.toString(), extra.sessionId));
|
|
1635
1773
|
registerAppTool(server, "open_workspace", {
|
|
1636
1774
|
title: "Open workspace",
|
|
1637
1775
|
description: "Open or resume a local coding workspace. Reuse the returned workspaceId for later calls. Default to checkout; use mode=\"worktree\" only when the user explicitly requests isolated or parallel Git work. Every call returns lightweight workspace metadata; bootstrap context is delivered automatically only when needed and can be explicitly suppressed or refreshed.",
|
|
@@ -1745,13 +1883,13 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1745
1883
|
skills: z.array(workspaceSkillOutputSchema).optional(),
|
|
1746
1884
|
agentProviders: z.array(workspaceLocalAgentProviderOutputSchema).optional(),
|
|
1747
1885
|
agents: z.array(workspaceLocalAgentOutputSchema).optional(),
|
|
1748
|
-
skillDiagnostics: z.array(
|
|
1886
|
+
skillDiagnostics: z.array(workspaceSkillDiagnosticOutputSchema).optional(),
|
|
1749
1887
|
workspaces: z.array(workspaceInventoryEntryOutputSchema).optional(),
|
|
1750
1888
|
summary: workspaceInventorySummaryOutputSchema.optional(),
|
|
1751
1889
|
page: workspaceInventoryPageOutputSchema.optional(),
|
|
1752
1890
|
instruction: z.string(),
|
|
1753
1891
|
},
|
|
1754
|
-
|
|
1892
|
+
_meta: {},
|
|
1755
1893
|
annotations: {
|
|
1756
1894
|
readOnlyHint: false,
|
|
1757
1895
|
destructiveHint: false,
|
|
@@ -1831,7 +1969,6 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1831
1969
|
.map((skill) => ({
|
|
1832
1970
|
name: skill.name,
|
|
1833
1971
|
description: skill.description,
|
|
1834
|
-
path: formatPathForPrompt(skill.filePath),
|
|
1835
1972
|
}));
|
|
1836
1973
|
const capabilityGuides = workspace.capabilityGuides.map((guide) => ({
|
|
1837
1974
|
name: guide.name,
|
|
@@ -1858,6 +1995,9 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1858
1995
|
path: formatAgentsPath(file.path, workspace.root),
|
|
1859
1996
|
}));
|
|
1860
1997
|
const visibleSkills = includeBootstrapContext ? cardSkills : [];
|
|
1998
|
+
const visibleSkillDiagnostics = includeBootstrapContext
|
|
1999
|
+
? redactSkillDiagnosticPaths(workspace.skillDiagnostics)
|
|
2000
|
+
: [];
|
|
1861
2001
|
const visibleCapabilityGuides = includeBootstrapContext ? capabilityGuides : [];
|
|
1862
2002
|
const visibleAgentProviders = includeBootstrapContext ? cardAgentProviders : [];
|
|
1863
2003
|
const visibleAgents = includeBootstrapContext ? cardAgents : [];
|
|
@@ -1866,7 +2006,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1866
2006
|
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.";
|
|
1867
2007
|
const workspaceManagementInstruction = "When you need to continue an earlier logical workspace or organize workspace state, use open_workspace(action=\"list\") to inspect candidates, then resume a selected workspaceId or ask the user before close_workspace cleanup.";
|
|
1868
2008
|
const cardInstruction = config.skillsEnabled
|
|
1869
|
-
? `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
|
|
2009
|
+
? `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}`
|
|
1870
2010
|
: `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}`;
|
|
1871
2011
|
const instruction = workspaceReused
|
|
1872
2012
|
? includeBootstrapContext
|
|
@@ -1940,7 +2080,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1940
2080
|
success: true,
|
|
1941
2081
|
durationMs: Math.round(performance.now() - startedAt),
|
|
1942
2082
|
});
|
|
1943
|
-
|
|
2083
|
+
const response = hooks.decorateResult(workspace.id, attachHookReports({
|
|
1944
2084
|
content: resultContent,
|
|
1945
2085
|
_meta: {
|
|
1946
2086
|
tool: "open_workspace",
|
|
@@ -1995,14 +2135,16 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
1995
2135
|
skills: visibleSkills,
|
|
1996
2136
|
agentProviders: visibleAgentProviders,
|
|
1997
2137
|
agents: visibleAgents,
|
|
1998
|
-
skillDiagnostics:
|
|
2138
|
+
skillDiagnostics: visibleSkillDiagnostics,
|
|
1999
2139
|
}
|
|
2000
2140
|
: {}),
|
|
2001
2141
|
instruction,
|
|
2002
2142
|
},
|
|
2003
2143
|
}, hookReports));
|
|
2144
|
+
rememberWorkspacePanelState(workspace.id, response);
|
|
2145
|
+
return response;
|
|
2004
2146
|
});
|
|
2005
|
-
registerActivityQueryTools(server, activityQueries);
|
|
2147
|
+
registerActivityQueryTools(server, activityQueries, connectionScopeId, toolWidgetDescriptorMeta(config, "activity")._meta, config.activityPanelExpanded, config.logging, (workspaceId) => workspacePanelStates.get(workspaceId));
|
|
2006
2148
|
registerAppTool(server, toolNames.capability, {
|
|
2007
2149
|
title: "Use optional capability",
|
|
2008
2150
|
description: "Describe or run one optional ForgeRelay capability advertised by open_workspace. Use describe when the capability contract is unfamiliar, then read its advertised guide if needed. Run dispatches only explicitly registered capabilities; it cannot invoke arbitrary shell commands, URLs, or methods.",
|
|
@@ -2096,7 +2238,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2096
2238
|
});
|
|
2097
2239
|
}
|
|
2098
2240
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
2099
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, extra._meta, { workspaceId, name, action, arguments: capabilityArguments, file }, {
|
|
2241
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(extra._meta, extra.sessionId), { workspaceId, name, action, arguments: capabilityArguments, file }, {
|
|
2100
2242
|
signal: extra.signal,
|
|
2101
2243
|
tool: toolNames.capability,
|
|
2102
2244
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -2177,7 +2319,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2177
2319
|
annotations: WRITE_TOOL_ANNOTATIONS,
|
|
2178
2320
|
}, async ({ workspaceId, commitMessage }, extra) => {
|
|
2179
2321
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
2180
|
-
|
|
2322
|
+
const response = await runToolWithHooks(hooks, {
|
|
2181
2323
|
signal: extra.signal,
|
|
2182
2324
|
tool: toolNames.closeWorkspace,
|
|
2183
2325
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -2225,6 +2367,21 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2225
2367
|
});
|
|
2226
2368
|
return attachHookReports({
|
|
2227
2369
|
content: [textBlock(result)],
|
|
2370
|
+
_meta: {
|
|
2371
|
+
tool: toolNames.closeWorkspace,
|
|
2372
|
+
card: {
|
|
2373
|
+
workspaceId,
|
|
2374
|
+
mode: "worktree",
|
|
2375
|
+
sourceRoot: closed.sourceRoot,
|
|
2376
|
+
branch: closed.branch,
|
|
2377
|
+
targetBranch: closed.targetBranch,
|
|
2378
|
+
commitSha: closed.commitSha,
|
|
2379
|
+
mergedSha: closed.mergedSha,
|
|
2380
|
+
committed: closed.committed,
|
|
2381
|
+
cleanupWarning: closed.cleanupWarning,
|
|
2382
|
+
payload: { content: [textBlock(result)] },
|
|
2383
|
+
},
|
|
2384
|
+
},
|
|
2228
2385
|
structuredContent: {
|
|
2229
2386
|
result,
|
|
2230
2387
|
workspaceId,
|
|
@@ -2250,10 +2407,20 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2250
2407
|
const result = `Closed checkout-backed workspace ${workspaceId}. Physical project files were not removed.`;
|
|
2251
2408
|
return {
|
|
2252
2409
|
content: [textBlock(result)],
|
|
2410
|
+
_meta: {
|
|
2411
|
+
tool: toolNames.closeWorkspace,
|
|
2412
|
+
card: {
|
|
2413
|
+
workspaceId,
|
|
2414
|
+
mode: "checkout",
|
|
2415
|
+
payload: { content: [textBlock(result)] },
|
|
2416
|
+
},
|
|
2417
|
+
},
|
|
2253
2418
|
structuredContent: { result, workspaceId, mode: "checkout" },
|
|
2254
2419
|
};
|
|
2255
2420
|
},
|
|
2256
2421
|
});
|
|
2422
|
+
workspacePanelStates.delete(workspaceId);
|
|
2423
|
+
return response;
|
|
2257
2424
|
});
|
|
2258
2425
|
registerAppTool(server, toolNames.read, {
|
|
2259
2426
|
title: "Read file",
|
|
@@ -2266,7 +2433,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2266
2433
|
.string()
|
|
2267
2434
|
.optional()
|
|
2268
2435
|
.describe(config.skillsEnabled
|
|
2269
|
-
? "One file path to read, relative to the workspace root or absolute inside the OS temp directory.
|
|
2436
|
+
? "One file path to read, relative to the workspace root or absolute inside the OS temp directory. Load an available skill with skills://<name>; after loading it, read files in that skill with skills://<name>/<relative-path>. Advertised capability-guide paths from open_workspace are also readable. Use exactly one of path or paths."
|
|
2270
2437
|
: "One file path to read, relative to the workspace root or absolute inside the OS temp directory. May also be an advertised capability-guide path from open_workspace. Use exactly one of path or paths."),
|
|
2271
2438
|
paths: z
|
|
2272
2439
|
.array(z.string())
|
|
@@ -2312,7 +2479,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2312
2479
|
}
|
|
2313
2480
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
2314
2481
|
let response;
|
|
2315
|
-
await runActivityTool(activityLifecycle, workspace, extra._meta, toolNames.read, { workspaceId, paths, offset, limit }, async (parentContext) => {
|
|
2482
|
+
await runActivityTool(activityLifecycle, workspace, hostScopeIdFor(extra._meta, extra.sessionId), toolNames.read, { workspaceId, paths, offset, limit }, async (parentContext) => {
|
|
2316
2483
|
const execution = await executeBulkRead({
|
|
2317
2484
|
paths: paths,
|
|
2318
2485
|
signal: extra.signal,
|
|
@@ -2536,7 +2703,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
2536
2703
|
annotations: EDIT_TOOL_ANNOTATIONS,
|
|
2537
2704
|
}, async ({ workspaceId, patch }, extra) => {
|
|
2538
2705
|
const workspace = workspaces.getWorkspace(workspaceId);
|
|
2539
|
-
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, extra._meta, { workspaceId, patch }, {
|
|
2706
|
+
return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(extra._meta, extra.sessionId), { workspaceId, patch }, {
|
|
2540
2707
|
signal: extra.signal,
|
|
2541
2708
|
tool: "apply_patch",
|
|
2542
2709
|
invocation: workspaceHookInvocation(workspace),
|
|
@@ -2785,7 +2952,7 @@ export function createServer(config = loadConfig(), options = {}) {
|
|
|
2785
2952
|
const transports = new McpTransportRegistry({
|
|
2786
2953
|
maxTransports: MAX_MCP_TRANSPORT_SESSIONS,
|
|
2787
2954
|
});
|
|
2788
|
-
const mcpUrl =
|
|
2955
|
+
const mcpUrl = publicEndpointUrl(config.publicBaseUrl, "mcp");
|
|
2789
2956
|
const resourceServerUrl = resourceUrlFromServerUrl(mcpUrl);
|
|
2790
2957
|
const oauthProvider = new SingleUserOAuthProvider(config.oauth, mcpUrl, config.stateDir);
|
|
2791
2958
|
const bearerAuth = requireBearerAuth({
|
|
@@ -2800,7 +2967,7 @@ export function createServer(config = loadConfig(), options = {}) {
|
|
|
2800
2967
|
const hostTurnStore = new HostTurnStore(config.stateDir);
|
|
2801
2968
|
const activityQueries = new ActivityQueryService(hostTurnStore, activityAuditStore, bashOutputStore);
|
|
2802
2969
|
const activityLifecycle = new ActivityLifecycle(activityAuditStore, {
|
|
2803
|
-
turnIdForConversation: (conversationScopeId) => activityQueries.currentTurnId(conversationScopeId),
|
|
2970
|
+
turnIdForConversation: (conversationScopeId, workspaceId) => activityQueries.currentTurnId(conversationScopeId, workspaceId),
|
|
2804
2971
|
});
|
|
2805
2972
|
const reviewCheckpoints = createReviewCheckpointManager();
|
|
2806
2973
|
const processSessions = new ProcessManager({ outputAudit: bashOutputStore });
|
|
@@ -2900,10 +3067,9 @@ export function createServer(config = loadConfig(), options = {}) {
|
|
|
2900
3067
|
});
|
|
2901
3068
|
next();
|
|
2902
3069
|
});
|
|
2903
|
-
app.use(
|
|
3070
|
+
app.use(createForgeRelayAuthRouter({
|
|
2904
3071
|
provider: oauthProvider,
|
|
2905
3072
|
issuerUrl: new URL(config.publicBaseUrl),
|
|
2906
|
-
baseUrl: new URL(config.publicBaseUrl),
|
|
2907
3073
|
resourceServerUrl,
|
|
2908
3074
|
scopesSupported: config.oauth.scopes,
|
|
2909
3075
|
resourceName: "ForgeRelay",
|