@akira-tl/forgerelay 0.8.8 → 0.8.9
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 +12 -0
- package/README.md +10 -18
- package/capabilities/host-integration/GUIDE.md +2 -2
- package/dist/activity/{audit-store.js → history/audit-store.js} +129 -21
- package/dist/activity/history/bash-output-store.js +250 -0
- package/dist/activity/{host-turn-store.js → history/host-turn-store.js} +4 -1
- package/dist/activity/{query-service.js → history/query-service.js} +13 -7
- package/dist/activity/{lifecycle.js → runtime/lifecycle.js} +1 -1
- package/dist/activity/{mcp-query-tools.js → runtime/mcp-query-tools.js} +3 -3
- package/dist/activity/storage/history.js +12 -0
- package/dist/activity/storage/paths.js +56 -0
- package/dist/activity/storage/segmented-log.js +149 -0
- package/dist/cli/setup-support.js +116 -0
- package/dist/cli.js +18 -126
- package/dist/lsp/code-intelligence.js +1 -1
- package/dist/lsp/test-support/server-fixture.js +9 -9
- package/dist/mcp/filesystem/filesystem-tools.js +245 -0
- package/dist/mcp/hooks/command-runner.js +79 -0
- package/dist/{hook-cli.js → mcp/hooks/hook-cli.js} +1 -1
- package/dist/{hooks.js → mcp/hooks/hooks.js} +3 -80
- package/dist/{oauth-provider.js → mcp/oauth/oauth-provider.js} +1 -1
- package/dist/{oauth-store.js → mcp/oauth/oauth-store.js} +5 -4
- package/dist/{operations → mcp/operations}/native-bulk-mutations.js +2 -2
- package/dist/mcp/panel/app.js +143 -0
- package/dist/{mcp-app-template.js → mcp/panel/mcp-app-template.js} +2 -38
- package/dist/mcp/process/runtime.js +257 -0
- package/dist/mcp/process/tools.js +346 -0
- package/dist/mcp/server/core/activity-support.js +70 -0
- package/dist/{capabilities.js → mcp/server/core/capabilities.js} +5 -3
- package/dist/{capability-registry.js → mcp/server/core/capability-registry.js} +2 -2
- package/dist/mcp/server/core/capability-support.js +90 -0
- package/dist/mcp/server/core/schemas.js +216 -0
- package/dist/mcp/server/core/tool-support.js +207 -0
- package/dist/mcp/server/core/version.js +9 -0
- package/dist/mcp/server/operations/runtime/filesystem-tools.js +400 -0
- package/dist/mcp/server/operations/runtime/operation-runtime.js +523 -0
- package/dist/mcp/server/transport/http-server.js +301 -0
- package/dist/mcp/server/transport/http-support.js +43 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +222 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +187 -0
- package/dist/mcp/server/workspace/runtime/workspace-open.js +552 -0
- package/dist/mcp/server/workspace/runtime/workspace-tools.js +601 -0
- package/dist/{config.js → runtime/config/config.js} +3 -3
- package/dist/{user-config.js → runtime/config/user-config.js} +14 -54
- package/dist/{db → runtime/state/db}/client.js +1 -1
- package/dist/{db → runtime/state/db}/migrations.js +34 -3
- package/dist/runtime/state/lock/file-lock.js +130 -0
- package/dist/runtime/testing/server-fixture.js +190 -0
- package/dist/server.js +116 -4202
- package/dist/subagents/providers/adapters/claude.js +86 -52
- package/dist/subagents/providers/adapters/codex.js +103 -39
- package/dist/subagents/providers/adapters/opencode.js +114 -84
- package/dist/subagents/providers/adapters/pi.js +59 -8
- package/dist/subagents/providers/availability.js +15 -30
- package/dist/subagents/providers/runtime/external-command.js +56 -0
- package/dist/subagents/sessions/capability.js +1 -1
- package/dist/subagents/sessions/execution.js +1 -1
- package/dist/subagents/sessions/mcp/activity-audit-test-support.js +30 -0
- package/dist/subagents/sessions/store.js +1 -1
- package/dist/ui/.vite/manifest.json +342 -410
- package/dist/ui/activity-panel-app.html +2 -4
- package/dist/ui/assets/activity-panel-app-7Xe0alhm.css +1 -0
- package/dist/ui/assets/activity-panel-app-Sl1Jro8v.js +115 -0
- package/dist/ui/assets/angular-html-B_yn91wW.js +1 -0
- package/dist/ui/assets/{angular-ts-Bxh4-O1-.js → angular-ts-DSgtloKz.js} +1 -1
- package/dist/ui/assets/{apl-BmYHKQ6m.js → apl-NatWtF8X.js} +1 -1
- package/dist/ui/assets/{astro-C51vqDKq.js → astro-BYFEVBuN.js} +1 -1
- package/dist/ui/assets/{blade-CdhVsmwq.js → blade-DoO7yvMR.js} +1 -1
- package/dist/ui/assets/c-Rl2hMCcg.js +1 -0
- package/dist/ui/assets/{cobol-Cf6azoN_.js → cobol-ByC5mWiB.js} +1 -1
- package/dist/ui/assets/{coffee-vVdGzlmt.js → coffee-Dez77Qov.js} +1 -1
- package/dist/ui/assets/cpp-DtaAQT9h.js +1 -0
- package/dist/ui/assets/{crystal-5UZy6KIX.js → crystal-Bgmbf8ec.js} +1 -1
- package/dist/ui/assets/css-C1tjBtVN.js +1 -0
- package/dist/ui/assets/{edge-CESgo4ia.js → edge-DeH6u8Wg.js} +1 -1
- package/dist/ui/assets/{elixir-9FKfw4cJ.js → elixir-_H3KnmWT.js} +1 -1
- package/dist/ui/assets/{elm-Dv7A0aLa.js → elm-CsCe3U28.js} +1 -1
- package/dist/ui/assets/{erb-DHFmDvM0.js → erb-CbM-L2g8.js} +1 -1
- package/dist/ui/assets/{git-rebase-utbU6CzV.js → git-rebase-D0s5pDh9.js} +1 -1
- package/dist/ui/assets/{glimmer-js-t2kiCkyB.js → glimmer-js-BfOToq_A.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-F0Jv5okV.js → glimmer-ts-CCUOkL2K.js} +1 -1
- package/dist/ui/assets/glsl-DH4ec9dE.js +1 -0
- package/dist/ui/assets/graphql-DilAMBoI.js +1 -0
- package/dist/ui/assets/{hack-8yjdbaSA.js → hack-BDkNDZlF.js} +1 -1
- package/dist/ui/assets/haml-D_QBp6K7.js +1 -0
- package/dist/ui/assets/{handlebars-BBFEg4Lo.js → handlebars-DePUrTpC.js} +1 -1
- package/dist/ui/assets/heavy-payload-DOwZpO6n.js +290 -0
- package/dist/ui/assets/html-By4dNnM4.js +1 -0
- package/dist/ui/assets/{html-derivative-DJAQcZyc.js → html-derivative-DDw9Ef4Q.js} +1 -1
- package/dist/ui/assets/{http-Dg5LSYRK.js → http-B0QxWc9M.js} +1 -1
- package/dist/ui/assets/{hurl-Y5uYzMZR.js → hurl-vMQ4lda1.js} +1 -1
- package/dist/ui/assets/java-DYRe2lWG.js +1 -0
- package/dist/ui/assets/javascript-CxyGqwcL.js +1 -0
- package/dist/ui/assets/{jinja-Bt30h8Ik.js → jinja-JUwV-fZt.js} +1 -1
- package/dist/ui/assets/{jison-D5uZ1Nyo.js → jison-7ZqCEvS8.js} +1 -1
- package/dist/ui/assets/json-dbltgiiA.js +1 -0
- package/dist/ui/assets/jsx-oDopT7YQ.js +1 -0
- package/dist/ui/assets/{julia-76hRaaGz.js → julia-CkKiNSav.js} +1 -1
- package/dist/ui/assets/{just-CDE7W0vW.js → just-DuqCAKJ3.js} +1 -1
- package/dist/ui/assets/{latex-CVO2Byab.js → latex-sudcRDSn.js} +1 -1
- package/dist/ui/assets/{liquid-CA-AS3EX.js → liquid-Di-08Js7.js} +1 -1
- package/dist/ui/assets/lua-YlqdMoV0.js +1 -0
- package/dist/ui/assets/{marko-_BWrPfny.js → marko-CJRNyJDb.js} +1 -1
- package/dist/ui/assets/{mdc-PyJv85eS.js → mdc-t12CzZHs.js} +1 -1
- package/dist/ui/assets/{nginx-DAWVw8yu.js → nginx-57-b4XwT.js} +1 -1
- package/dist/ui/assets/{nim-CKE1dzpU.js → nim-BzrswYQa.js} +1 -1
- package/dist/ui/assets/{perl-BBhWva19.js → perl-D9JR_iIh.js} +1 -1
- package/dist/ui/assets/{php-ChIeaAoF.js → php-Py2gFHil.js} +1 -1
- package/dist/ui/assets/{pug-CMgI5afi.js → pug-PzLLy0h1.js} +1 -1
- package/dist/ui/assets/{qml-CjxTkcFH.js → qml-By8fwgV4.js} +1 -1
- package/dist/ui/assets/r-CcezTbLq.js +1 -0
- package/dist/ui/assets/{razor-i4DbHr8p.js → razor-dG-vdhL_.js} +1 -1
- package/dist/ui/assets/regexp-BlsQ-N_8.js +1 -0
- package/dist/ui/assets/{rst-oy_97Kl4.js → rst-C2awDCat.js} +1 -1
- package/dist/ui/assets/{ruby-C5zYZIyl.js → ruby-zA2W_49d.js} +1 -1
- package/dist/ui/assets/{sas-BnMEh2S3.js → sas-D49bhyaN.js} +1 -1
- package/dist/ui/assets/scss-BQb-cp01.js +1 -0
- package/dist/ui/assets/shellscript-Dga4H_Ni.js +1 -0
- package/dist/ui/assets/{shellsession-BpE5o7_3.js → shellsession-n3-5CqcF.js} +1 -1
- package/dist/ui/assets/{soy-COesmhTY.js → soy-XYBOf_BA.js} +1 -1
- package/dist/ui/assets/sql-ET4u5Cpl.js +1 -0
- package/dist/ui/assets/{stata-CGQEuSZq.js → stata-CLHcJHO_.js} +1 -1
- package/dist/ui/assets/{surrealql-Bz4JbU-9.js → surrealql-BdjQ60T4.js} +1 -1
- package/dist/ui/assets/{svelte-1jM2ejXj.js → svelte-B2RPtKJe.js} +1 -1
- package/dist/ui/assets/{templ-C-VwELcp.js → templ-CrByPW_W.js} +1 -1
- package/dist/ui/assets/{tex-t1rUiQCm.js → tex-DZBkg363.js} +1 -1
- package/dist/ui/assets/{ts-tags-Bewoa6pS.js → ts-tags-DJR3Gphb.js} +1 -1
- package/dist/ui/assets/tsx-CajzROyy.js +1 -0
- package/dist/ui/assets/{twig-DVg-vELl.js → twig-BZjk5Zda.js} +1 -1
- package/dist/ui/assets/typescript-B-EWToLj.js +1 -0
- package/dist/ui/assets/{vue-wPQ7UlMy.js → vue-D_rHCCr8.js} +1 -1
- package/dist/ui/assets/{vue-html-BHtJ8geX.js → vue-html-CC4WlvVt.js} +1 -1
- package/dist/ui/assets/{vue-vine-CNSGAWwc.js → vue-vine-BgNMVgpk.js} +1 -1
- package/dist/ui/assets/xml-BI86-WbH.js +1 -0
- package/dist/ui/assets/{xsl-3_vJ27aE.js → xsl-DXeLZV4J.js} +1 -1
- package/dist/ui/assets/yaml-60_PFlWa.js +1 -0
- package/dist/workspaces/bootstrap.js +72 -0
- package/dist/workspaces/context.js +302 -0
- package/dist/workspaces/conversation-test-support.js +86 -0
- package/dist/{git-worktrees.js → workspaces/git/git-worktrees.js} +1 -1
- package/dist/workspaces/inventory.js +131 -0
- package/dist/workspaces/paths.js +55 -0
- package/dist/{remote-auth.js → workspaces/relay/auth/remote-auth.js} +2 -2
- package/dist/workspaces/relay/result-support.js +111 -0
- package/dist/workspaces/relay/tests/test-support.js +203 -0
- package/dist/{remote-mcp-connection-pool.js → workspaces/relay/transport/remote-mcp-connection-pool.js} +1 -1
- package/dist/workspaces/relay/types.js +1 -0
- package/dist/{remote-workspace-relay.js → workspaces/relay/workspace-relay.js} +23 -172
- package/dist/workspaces/resources/skills.js +285 -0
- package/dist/{review-checkpoints.js → workspaces/review/review-checkpoints.js} +2 -2
- package/dist/workspaces/sessions.js +457 -0
- package/dist/{workspace-store.js → workspaces/state/workspace-store.js} +2 -2
- package/dist/workspaces.js +68 -995
- package/docs/chatgpt-coding-workflow.md +3 -11
- package/docs/configuration.md +12 -25
- package/docs/gotchas.md +0 -30
- package/docs/roadmap.md +6 -10
- package/docs/security.md +1 -5
- package/docs/setup.md +0 -11
- package/package.json +6 -8
- package/scripts/ci/architecture.mjs +109 -0
- package/scripts/ci/verify.mjs +5 -0
- package/scripts/debug/accept/bootstrap.mjs +326 -0
- package/scripts/debug/accept/harness.mjs +76 -0
- package/scripts/debug/accept/support.mjs +432 -0
- package/scripts/debug/accept.mjs +43 -806
- package/scripts/debug/relay-accept/support.mjs +135 -0
- package/scripts/debug/relay-accept.mjs +26 -115
- package/scripts/debug/traffic/support.mjs +167 -0
- package/scripts/debug/traffic/traffic-audit.mjs +4 -161
- package/scripts/{lsp-interop-support.test.mjs → lsp/interop-support.test.mjs} +1 -1
- package/scripts/{lsp-interop.mjs → lsp/interop.mjs} +3 -3
- package/scripts/wiki/sync.mjs +21 -10
- package/dist/activity/bash-output-store.js +0 -127
- package/dist/pi-tools.js +0 -110
- package/dist/skills.js +0 -127
- package/dist/ui/assets/activity-panel-app-7AckaxIR.js +0 -2
- package/dist/ui/assets/angular-html-BkgDQl-G.js +0 -1
- package/dist/ui/assets/c-IBzBpxAO.js +0 -1
- package/dist/ui/assets/chunk-EyZ2wyi3.js +0 -1
- package/dist/ui/assets/cpp-CVHeO-Jk.js +0 -1
- package/dist/ui/assets/css-DEfus9J5.js +0 -1
- package/dist/ui/assets/glsl-_ooCjaeB.js +0 -1
- package/dist/ui/assets/graphql-B9LqP-cY.js +0 -1
- package/dist/ui/assets/haml-BU5EoZBn.js +0 -1
- package/dist/ui/assets/heavy-payload-Bol1_mcs.js +0 -4
- package/dist/ui/assets/html-D-C6YZk4.js +0 -1
- package/dist/ui/assets/java-ppHvycj5.js +0 -1
- package/dist/ui/assets/javascript-DDTjKLlQ.js +0 -1
- package/dist/ui/assets/json-Cw91r8h1.js +0 -1
- package/dist/ui/assets/jsx-04X2FT_1.js +0 -1
- package/dist/ui/assets/lua-CiyBp52Y.js +0 -1
- package/dist/ui/assets/r-DfFga4si.js +0 -1
- package/dist/ui/assets/regexp-iC9_nbSI.js +0 -1
- package/dist/ui/assets/review-payload-BqJp0c5e.js +0 -1
- package/dist/ui/assets/scrollbar-CvE-I-jG.js +0 -287
- package/dist/ui/assets/scss-BfZXsAWQ.js +0 -1
- package/dist/ui/assets/shellscript-BC2srNaB.js +0 -1
- package/dist/ui/assets/sql-Ccv_yeUm.js +0 -1
- package/dist/ui/assets/tsx-CTXzrBkN.js +0 -1
- package/dist/ui/assets/typescript--ElHoFkw.js +0 -1
- package/dist/ui/assets/workspace-app-BLW7p6IZ.js +0 -114
- package/dist/ui/assets/workspace-app-BjNqR0en.js +0 -1
- package/dist/ui/assets/workspace-app-CmaYU4DW.js +0 -3
- package/dist/ui/assets/workspace-app-D2bJ5fjt.css +0 -1
- package/dist/ui/assets/workspace-lifecycle-app-BVbI2Ilf.js +0 -1
- package/dist/ui/assets/xml-JnmX6vyS.js +0 -1
- package/dist/ui/assets/yaml-OAcZkP-w.js +0 -1
- package/dist/ui/workspace-app.html +0 -18
- package/dist/ui/workspace-lifecycle-app.html +0 -18
- package/docs/agent-profile-schema.md +0 -114
- package/docs/artifact-exchange.md +0 -65
- package/docs/versioning.md +0 -205
- /package/capabilities/{managed-worktrees → workspace/managed-worktrees}/GUIDE.md +0 -0
- /package/capabilities/{workspace-tasks → workspace/workspace-tasks}/GUIDE.md +0 -0
- /package/dist/activity/{process-output-audit.js → runtime/process-output-audit.js} +0 -0
- /package/dist/{artifact-error.js → mcp/artifacts/artifact-error.js} +0 -0
- /package/dist/{artifact-tools.js → mcp/artifacts/artifact-tools.js} +0 -0
- /package/dist/{incoming-artifacts.js → mcp/artifacts/incoming-artifacts.js} +0 -0
- /package/dist/{advertised-files.js → mcp/filesystem/advertised-files.js} +0 -0
- /package/dist/{apply-patch.js → mcp/filesystem/apply-patch.js} +0 -0
- /package/dist/{file-mutations.js → mcp/filesystem/file-mutations.js} +0 -0
- /package/dist/{roots.js → mcp/filesystem/roots.js} +0 -0
- /package/dist/{oauth → mcp/oauth}/public-url.js +0 -0
- /package/dist/{oauth → mcp/oauth}/router.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/executor.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/scheduler.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/types.js +0 -0
- /package/dist/{operations → mcp/operations}/bulk-mutation.js +0 -0
- /package/dist/{operations → mcp/operations}/bulk-read.js +0 -0
- /package/dist/{operations → mcp/operations}/core-operation-executor.js +0 -0
- /package/dist/{process-platform.js → mcp/process/process-platform.js} +0 -0
- /package/dist/{process-sessions.js → mcp/process/process-sessions.js} +0 -0
- /package/dist/{request-meta.js → mcp/request-meta.js} +0 -0
- /package/dist/{mcp-sessions.js → mcp/server/transport/mcp-sessions.js} +0 -0
- /package/dist/{server-shutdown.js → mcp/server/transport/server-shutdown.js} +0 -0
- /package/dist/{logger.js → runtime/logging/logger.js} +0 -0
- /package/dist/{db → runtime/state/db}/schema.js +0 -0
- /package/dist/{composite-activity.js → workspaces/composite/composite-activity.js} +0 -0
- /package/dist/{composite-workspaces.js → workspaces/composite/composite-workspaces.js} +0 -0
- /package/dist/{git.js → workspaces/git/git.js} +0 -0
- /package/dist/{workspace-presentation.js → workspaces/presentation/workspace-presentation.js} +0 -0
- /package/dist/{remote-transport.js → workspaces/relay/transport/remote-transport.js} +0 -0
- /package/dist/{workspace-task-reminders.js → workspaces/tasks/workspace-task-reminders.js} +0 -0
- /package/dist/{workspace-tasks.js → workspaces/tasks/workspace-tasks.js} +0 -0
- /package/scripts/{lsp-interop-support.mjs → lsp/interop-support.mjs} +0 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
3
|
+
import { getOAuthProtectedResourceMetadataUrl } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
4
|
+
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
5
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
6
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import { checkResourceAllowed, resourceUrlFromServerUrl } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
|
|
8
|
+
import express from "express";
|
|
9
|
+
import { ActivityAuditStore } from "../../../activity/history/audit-store.js";
|
|
10
|
+
import { BashOutputStore } from "../../../activity/history/bash-output-store.js";
|
|
11
|
+
import { HostTurnStore } from "../../../activity/history/host-turn-store.js";
|
|
12
|
+
import { ActivityLifecycle } from "../../../activity/runtime/lifecycle.js";
|
|
13
|
+
import { ActivityQueryService } from "../../../activity/history/query-service.js";
|
|
14
|
+
import { CodeIntelligenceManager } from "../../../lsp/runtime/manager.js";
|
|
15
|
+
import { createOpenAIIncomingArtifactAdapter } from "../../artifacts/incoming-artifacts.js";
|
|
16
|
+
import { logEvent, requestPath, transportSessionIdPrefix } from "../../../runtime/logging/logger.js";
|
|
17
|
+
import { SingleUserOAuthProvider } from "../../oauth/oauth-provider.js";
|
|
18
|
+
import { createForgeRelayAuthRouter } from "../../oauth/router.js";
|
|
19
|
+
import { publicEndpointUrl } from "../../oauth/public-url.js";
|
|
20
|
+
import { McpTransportRegistry } from "./mcp-sessions.js";
|
|
21
|
+
import { ProcessManager } from "../../process/process-sessions.js";
|
|
22
|
+
import { createReviewCheckpointManager } from "../../../workspaces/review/review-checkpoints.js";
|
|
23
|
+
import { RemoteWorkspaceRelay } from "../../../workspaces/relay/workspace-relay.js";
|
|
24
|
+
import { CompositeWorkspaceRegistry } from "../../../workspaces/composite/composite-workspaces.js";
|
|
25
|
+
import { createWorkspaceStore } from "../../../workspaces/state/workspace-store.js";
|
|
26
|
+
import { WorkspaceTaskReminderTracker } from "../../../workspaces/tasks/workspace-task-reminders.js";
|
|
27
|
+
import { WorkspaceTaskStore } from "../../../workspaces/tasks/workspace-tasks.js";
|
|
28
|
+
import { WorkspaceRegistry } from "../../../workspaces.js";
|
|
29
|
+
import { getSubagentProviderAvailabilitySnapshot } from "../../../subagents/providers/availability.js";
|
|
30
|
+
import { activityPanelAssetDirectory, setActivityPanelAssetHeaders } from "../../panel/app.js";
|
|
31
|
+
import { mcpRequestDebugFields, requestLogFields, sendJsonRpcError } from "./http-support.js";
|
|
32
|
+
const MCP_TRANSPORT_IDLE_TIMEOUT_MS = 24 * 60 * 60 * 1_000;
|
|
33
|
+
const MAX_MCP_TRANSPORT_SESSIONS = 64;
|
|
34
|
+
const MCP_TRANSPORT_CLEANUP_INTERVAL_MS = 5 * 60 * 1_000;
|
|
35
|
+
export function createHttpServer(config, options, createMcpServer) {
|
|
36
|
+
const incomingArtifactAdapters = options.incomingArtifactAdapters
|
|
37
|
+
?? [createOpenAIIncomingArtifactAdapter()];
|
|
38
|
+
const allowedHosts = config.allowedHosts.includes("*")
|
|
39
|
+
? undefined
|
|
40
|
+
: Array.from(new Set([config.host, ...config.allowedHosts]));
|
|
41
|
+
const app = createMcpExpressApp({
|
|
42
|
+
host: config.host,
|
|
43
|
+
...(allowedHosts ? { allowedHosts } : {}),
|
|
44
|
+
});
|
|
45
|
+
const transports = new McpTransportRegistry({
|
|
46
|
+
maxTransports: MAX_MCP_TRANSPORT_SESSIONS,
|
|
47
|
+
});
|
|
48
|
+
const mcpUrl = publicEndpointUrl(config.publicBaseUrl, "mcp");
|
|
49
|
+
const resourceServerUrl = resourceUrlFromServerUrl(mcpUrl);
|
|
50
|
+
const oauthProvider = new SingleUserOAuthProvider(config.oauth, mcpUrl, config.stateDir);
|
|
51
|
+
const bearerAuth = requireBearerAuth({
|
|
52
|
+
verifier: oauthProvider,
|
|
53
|
+
requiredScopes: [config.oauth.scopes[0] ?? "forgerelay"],
|
|
54
|
+
resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(resourceServerUrl),
|
|
55
|
+
});
|
|
56
|
+
const workspaceStore = createWorkspaceStore(config.stateDir);
|
|
57
|
+
const workspaces = new WorkspaceRegistry(config, workspaceStore);
|
|
58
|
+
const sharedRemoteWorkspaces = new RemoteWorkspaceRelay(config.configDir, config.stateDir);
|
|
59
|
+
const sharedCompositeWorkspaces = new CompositeWorkspaceRegistry(config.stateDir);
|
|
60
|
+
const sharedWorkspaceTasks = new WorkspaceTaskStore(config.stateDir);
|
|
61
|
+
const sharedTaskReminders = new WorkspaceTaskReminderTracker(config.taskReminderInterval, sharedWorkspaceTasks);
|
|
62
|
+
const activityAuditStore = new ActivityAuditStore(config.stateDir);
|
|
63
|
+
const bashOutputStore = new BashOutputStore(config.stateDir);
|
|
64
|
+
const hostTurnStore = new HostTurnStore(config.stateDir);
|
|
65
|
+
const activityQueries = new ActivityQueryService(hostTurnStore, activityAuditStore, bashOutputStore);
|
|
66
|
+
const activityLifecycle = new ActivityLifecycle(activityAuditStore, {
|
|
67
|
+
turnIdForConversation: (conversationScopeId, workspaceId) => activityQueries.currentTurnId(conversationScopeId, workspaceId),
|
|
68
|
+
});
|
|
69
|
+
const reviewCheckpoints = createReviewCheckpointManager();
|
|
70
|
+
const processSessions = new ProcessManager({ outputAudit: bashOutputStore });
|
|
71
|
+
const codeIntelligence = new CodeIntelligenceManager(config);
|
|
72
|
+
const subagentProviders = config.subagents
|
|
73
|
+
? getSubagentProviderAvailabilitySnapshot()
|
|
74
|
+
: [];
|
|
75
|
+
const logTransportCloseResults = (reason, results) => {
|
|
76
|
+
let closedCount = 0;
|
|
77
|
+
for (const result of results) {
|
|
78
|
+
if (result.error) {
|
|
79
|
+
logEvent(config.logging, "warn", "mcp_transport_session_close_failed", {
|
|
80
|
+
reason,
|
|
81
|
+
transportSessionIdPrefix: transportSessionIdPrefix(result.transportSessionId),
|
|
82
|
+
error: result.error instanceof Error
|
|
83
|
+
? result.error.message
|
|
84
|
+
: String(result.error),
|
|
85
|
+
});
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
closedCount += 1;
|
|
89
|
+
if (reason !== "server_shutdown") {
|
|
90
|
+
logEvent(config.logging, "debug", "mcp_transport_session_closed", {
|
|
91
|
+
reason,
|
|
92
|
+
transportSessionIdPrefix: transportSessionIdPrefix(result.transportSessionId),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (reason === "server_shutdown" && closedCount > 0) {
|
|
97
|
+
logEvent(config.logging, "debug", "mcp_transport_sessions_closed", {
|
|
98
|
+
reason,
|
|
99
|
+
count: closedCount,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const logRuntimeResources = () => {
|
|
104
|
+
const memory = process.memoryUsage();
|
|
105
|
+
const processStats = processSessions.stats();
|
|
106
|
+
const codeStats = codeIntelligence.stats();
|
|
107
|
+
logEvent(config.logging, "debug", "runtime_resources", {
|
|
108
|
+
rssBytes: memory.rss,
|
|
109
|
+
heapUsedBytes: memory.heapUsed,
|
|
110
|
+
heapTotalBytes: memory.heapTotal,
|
|
111
|
+
externalBytes: memory.external,
|
|
112
|
+
arrayBuffersBytes: memory.arrayBuffers,
|
|
113
|
+
mcpTransports: transports.size,
|
|
114
|
+
processesTotal: processStats.total,
|
|
115
|
+
processesRunning: processStats.running,
|
|
116
|
+
processesCompleted: processStats.completed,
|
|
117
|
+
cachedWorkspaces: workspaces.cachedWorkspaceCount,
|
|
118
|
+
reviewStates: reviewCheckpoints.stateCount,
|
|
119
|
+
languageServices: codeStats.servicesTotal,
|
|
120
|
+
languageServicesActive: codeStats.servicesActive,
|
|
121
|
+
languageProcessesRunning: codeStats.processesRunning,
|
|
122
|
+
languageOperationsInFlight: codeStats.operationsInFlight,
|
|
123
|
+
languageRequestsActive: codeStats.semanticRequestsActive,
|
|
124
|
+
languageRequestsQueued: codeStats.semanticRequestsQueued,
|
|
125
|
+
languageOpenDocuments: codeStats.openDocuments,
|
|
126
|
+
languageDiagnosticSnapshots: codeStats.diagnosticSnapshots,
|
|
127
|
+
languageDiagnosticsRetained: codeStats.diagnosticsRetained,
|
|
128
|
+
languageStderrBytes: codeStats.stderrBytes,
|
|
129
|
+
languagePendingCreations: codeStats.pendingCreations,
|
|
130
|
+
languageCrashCooldowns: codeStats.crashCooldowns,
|
|
131
|
+
languageInvalidatedServices: codeStats.invalidatedServices,
|
|
132
|
+
languageRetiredWorkspaceRoots: codeStats.retiredWorkspaceRoots,
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
const transportCleanupTimer = setInterval(() => {
|
|
136
|
+
void transports
|
|
137
|
+
.closeIdle(MCP_TRANSPORT_IDLE_TIMEOUT_MS)
|
|
138
|
+
.then((results) => logTransportCloseResults("idle_timeout", results))
|
|
139
|
+
.finally(logRuntimeResources);
|
|
140
|
+
}, MCP_TRANSPORT_CLEANUP_INTERVAL_MS);
|
|
141
|
+
transportCleanupTimer.unref();
|
|
142
|
+
logRuntimeResources();
|
|
143
|
+
if (config.logging.trustProxy) {
|
|
144
|
+
app.set("trust proxy", 1);
|
|
145
|
+
}
|
|
146
|
+
app.use((req, res, next) => {
|
|
147
|
+
const requestId = randomUUID();
|
|
148
|
+
const startedAt = performance.now();
|
|
149
|
+
res.locals.requestId = requestId;
|
|
150
|
+
res.on("finish", () => {
|
|
151
|
+
const path = requestPath(req);
|
|
152
|
+
if (!config.logging.requests)
|
|
153
|
+
return;
|
|
154
|
+
if (!config.logging.assets && path.startsWith("/mcp-app-assets"))
|
|
155
|
+
return;
|
|
156
|
+
logEvent(config.logging, "info", "http_request", {
|
|
157
|
+
requestId,
|
|
158
|
+
method: req.method,
|
|
159
|
+
path,
|
|
160
|
+
status: res.statusCode,
|
|
161
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
162
|
+
...requestLogFields(req),
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
next();
|
|
166
|
+
});
|
|
167
|
+
app.use(createForgeRelayAuthRouter({
|
|
168
|
+
provider: oauthProvider,
|
|
169
|
+
cliAuthenticationProvider: oauthProvider,
|
|
170
|
+
instanceId: config.instanceId,
|
|
171
|
+
issuerUrl: new URL(config.publicBaseUrl),
|
|
172
|
+
resourceServerUrl,
|
|
173
|
+
scopesSupported: config.oauth.scopes,
|
|
174
|
+
resourceName: "ForgeRelay",
|
|
175
|
+
}));
|
|
176
|
+
app.options("/mcp-app-assets/{*asset}", (_req, res) => {
|
|
177
|
+
setActivityPanelAssetHeaders(res);
|
|
178
|
+
res.sendStatus(204);
|
|
179
|
+
});
|
|
180
|
+
app.use("/mcp-app-assets", express.static(activityPanelAssetDirectory(), {
|
|
181
|
+
immutable: true,
|
|
182
|
+
maxAge: "1y",
|
|
183
|
+
fallthrough: false,
|
|
184
|
+
setHeaders: setActivityPanelAssetHeaders,
|
|
185
|
+
}));
|
|
186
|
+
app.get("/healthz", (_req, res) => {
|
|
187
|
+
res.json({ ok: true, name: "forgerelay" });
|
|
188
|
+
});
|
|
189
|
+
app.all("/mcp", async (req, res) => {
|
|
190
|
+
const requestId = res.locals.requestId;
|
|
191
|
+
const transportSessionId = req.header("mcp-session-id");
|
|
192
|
+
const initializeRequest = req.method === "POST" && isInitializeRequest(req.body);
|
|
193
|
+
await new Promise((resolve, reject) => {
|
|
194
|
+
bearerAuth(req, res, (error) => {
|
|
195
|
+
if (error)
|
|
196
|
+
reject(error);
|
|
197
|
+
else
|
|
198
|
+
resolve();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
if (res.headersSent)
|
|
202
|
+
return;
|
|
203
|
+
if (!req.auth?.resource || !checkResourceAllowed({ requestedResource: req.auth.resource, configuredResource: resourceServerUrl })) {
|
|
204
|
+
logEvent(config.logging, "warn", "auth_denied", {
|
|
205
|
+
requestId,
|
|
206
|
+
method: req.method,
|
|
207
|
+
path: requestPath(req),
|
|
208
|
+
reason: "invalid_oauth_resource",
|
|
209
|
+
...requestLogFields(req),
|
|
210
|
+
});
|
|
211
|
+
sendJsonRpcError(res, 401, -32001, "Unauthorized");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
logEvent(config.logging, "debug", "mcp_request", {
|
|
215
|
+
requestId,
|
|
216
|
+
httpMethod: req.method,
|
|
217
|
+
transportSessionIdPresent: Boolean(transportSessionId),
|
|
218
|
+
transportSessionIdPrefix: transportSessionIdPrefix(transportSessionId),
|
|
219
|
+
isInitialize: initializeRequest,
|
|
220
|
+
...mcpRequestDebugFields(req.body),
|
|
221
|
+
});
|
|
222
|
+
try {
|
|
223
|
+
let transport;
|
|
224
|
+
if (transportSessionId) {
|
|
225
|
+
transport = transports.get(transportSessionId);
|
|
226
|
+
if (!transport) {
|
|
227
|
+
sendJsonRpcError(res, 404, -32000, "Unknown MCP transport session");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else if (initializeRequest) {
|
|
232
|
+
transport = new StreamableHTTPServerTransport({
|
|
233
|
+
sessionIdGenerator: () => randomUUID(),
|
|
234
|
+
onsessioninitialized: (newTransportSessionId) => {
|
|
235
|
+
if (transport) {
|
|
236
|
+
void transports
|
|
237
|
+
.register(newTransportSessionId, transport)
|
|
238
|
+
.then((results) => logTransportCloseResults("capacity_limit", results));
|
|
239
|
+
}
|
|
240
|
+
logEvent(config.logging, "debug", "mcp_transport_session_created", {
|
|
241
|
+
requestId,
|
|
242
|
+
transportSessionIdPrefix: transportSessionIdPrefix(newTransportSessionId),
|
|
243
|
+
...requestLogFields(req),
|
|
244
|
+
});
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
transport.onclose = () => {
|
|
248
|
+
const closedTransportSessionId = transport?.sessionId;
|
|
249
|
+
if (closedTransportSessionId && transports.remove(closedTransportSessionId)) {
|
|
250
|
+
logEvent(config.logging, "debug", "mcp_transport_session_closed", {
|
|
251
|
+
reason: "transport_close",
|
|
252
|
+
transportSessionIdPrefix: transportSessionIdPrefix(closedTransportSessionId),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const server = createMcpServer(config, workspaces, reviewCheckpoints, processSessions, subagentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries, {
|
|
257
|
+
taskReminders: sharedTaskReminders,
|
|
258
|
+
remoteWorkspaces: sharedRemoteWorkspaces,
|
|
259
|
+
compositeWorkspaces: sharedCompositeWorkspaces,
|
|
260
|
+
});
|
|
261
|
+
await server.connect(transport);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
sendJsonRpcError(res, 400, -32000, "No valid MCP transport session");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
await transport.handleRequest(req, res, req.body);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
logEvent(config.logging, "error", "mcp_request_error", {
|
|
271
|
+
requestId,
|
|
272
|
+
error: error instanceof Error ? error.message : String(error),
|
|
273
|
+
});
|
|
274
|
+
if (!res.headersSent) {
|
|
275
|
+
sendJsonRpcError(res, 500, -32603, "Internal server error");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
let closePromise;
|
|
280
|
+
return {
|
|
281
|
+
app,
|
|
282
|
+
config,
|
|
283
|
+
subagentProviders,
|
|
284
|
+
close: () => {
|
|
285
|
+
closePromise ??= (async () => {
|
|
286
|
+
clearInterval(transportCleanupTimer);
|
|
287
|
+
const results = await transports.closeAll();
|
|
288
|
+
logTransportCloseResults("server_shutdown", results);
|
|
289
|
+
await sharedRemoteWorkspaces.shutdown();
|
|
290
|
+
processSessions.shutdown();
|
|
291
|
+
await codeIntelligence.shutdown();
|
|
292
|
+
oauthProvider.close();
|
|
293
|
+
hostTurnStore.close();
|
|
294
|
+
bashOutputStore.close();
|
|
295
|
+
activityAuditStore.close();
|
|
296
|
+
workspaceStore.close?.();
|
|
297
|
+
})();
|
|
298
|
+
return closePromise;
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { requestIp } from "../../../runtime/logging/logger.js";
|
|
2
|
+
export function sendJsonRpcError(res, status, code, message) {
|
|
3
|
+
res.status(status).json({
|
|
4
|
+
jsonrpc: "2.0",
|
|
5
|
+
error: { code, message },
|
|
6
|
+
id: null,
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export function requestLogFields(req) {
|
|
10
|
+
return {
|
|
11
|
+
ip: requestIp(req),
|
|
12
|
+
host: req.header("host"),
|
|
13
|
+
userAgent: req.header("user-agent"),
|
|
14
|
+
origin: req.header("origin"),
|
|
15
|
+
referer: req.header("referer"),
|
|
16
|
+
contentLength: req.header("content-length"),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function mcpRequestDebugFields(body) {
|
|
20
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
21
|
+
return {};
|
|
22
|
+
const request = body;
|
|
23
|
+
const rpcMethod = typeof request.method === "string" ? request.method : undefined;
|
|
24
|
+
const params = request.params && typeof request.params === "object" && !Array.isArray(request.params)
|
|
25
|
+
? request.params
|
|
26
|
+
: undefined;
|
|
27
|
+
const rpcMeta = params?._meta && typeof params._meta === "object" && !Array.isArray(params._meta)
|
|
28
|
+
? params._meta
|
|
29
|
+
: undefined;
|
|
30
|
+
const rpcMetaKeys = rpcMeta ? Object.keys(rpcMeta).sort() : [];
|
|
31
|
+
let rpcTarget;
|
|
32
|
+
if (rpcMethod === "resources/read" && typeof params?.uri === "string") {
|
|
33
|
+
rpcTarget = params.uri;
|
|
34
|
+
}
|
|
35
|
+
else if (rpcMethod === "tools/call" && typeof params?.name === "string") {
|
|
36
|
+
rpcTarget = params.name;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
rpcMethod,
|
|
40
|
+
rpcTarget,
|
|
41
|
+
...(rpcMetaKeys.length > 0 ? { rpcMetaKeys } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { buildCapabilityFingerprint } from "../../core/capabilities.js";
|
|
2
|
+
import { isArtifactDownloadSupportedPlatform } from "../../../artifacts/artifact-tools.js";
|
|
3
|
+
import { attachHookReports } from "../../../hooks/hooks.js";
|
|
4
|
+
import { formatPathForPrompt } from "../../../../workspaces/resources/skills.js";
|
|
5
|
+
import { compactWorkspacePresentation } from "../../../../workspaces/presentation/workspace-presentation.js";
|
|
6
|
+
import { formatAgentsPath } from "../../../../workspaces.js";
|
|
7
|
+
import { formatAvailableSubagentProfile, summarizeSubagentProfile } from "../../../../subagents/profiles.js";
|
|
8
|
+
import { formatUnavailableSubagentProvider } from "../../../../subagents/providers/availability.js";
|
|
9
|
+
import { capabilityContextFor } from "../../core/capability-support.js";
|
|
10
|
+
import { redactSkillDiagnosticPaths } from "../../core/schemas.js";
|
|
11
|
+
import { logToolCall, workspaceLogContext } from "../../core/tool-support.js";
|
|
12
|
+
export async function presentLocalWorkspaceOpen(options, input, contextData) {
|
|
13
|
+
const { config, forgerelayVersion: FORGERELAY_VERSION, workspaces, workspaceTasks, reviewCheckpoints, capabilityRegistry, subagentProviders, hooks, rememberWorkspacePanelState, } = options;
|
|
14
|
+
const { path, workspaceId, mode, baseRef, newWorktree, newWorkspace, context } = input;
|
|
15
|
+
const { conversationScopeId, protectedWorkspaceIds, startedAt, sessionId } = contextData;
|
|
16
|
+
const { workspace, agentsFiles, availableAgentsFiles, hookReports, workspaceReused, includeBootstrapContext, bootstrapContextComponents, contextFingerprint, } = await workspaces.openWorkspace({ path, workspaceId, mode, baseRef, newWorktree, newWorkspace, context }, {
|
|
17
|
+
conversationScopeId,
|
|
18
|
+
protectedWorkspaceIds,
|
|
19
|
+
});
|
|
20
|
+
workspaceTasks.initializeWorkspace(workspace.id);
|
|
21
|
+
const knownWorktrees = await workspaces.listKnownWorktrees(workspace);
|
|
22
|
+
const staleWorkspaces = await workspaces.listStaleWorkspaces(workspace);
|
|
23
|
+
const capabilityFingerprint = buildCapabilityFingerprint(config, FORGERELAY_VERSION, {
|
|
24
|
+
artifactDownloadSupported: isArtifactDownloadSupportedPlatform(),
|
|
25
|
+
});
|
|
26
|
+
if (config.widgets === "changes") {
|
|
27
|
+
await reviewCheckpoints.initializeWorkspace({
|
|
28
|
+
workspaceId: workspace.id,
|
|
29
|
+
root: workspace.root,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const cardSkills = workspace.skills
|
|
33
|
+
.filter((skill) => !skill.disableModelInvocation)
|
|
34
|
+
.map((skill) => ({
|
|
35
|
+
name: skill.name,
|
|
36
|
+
description: skill.description,
|
|
37
|
+
}));
|
|
38
|
+
const capabilityGuides = workspace.capabilityGuides.map((guide) => ({
|
|
39
|
+
name: guide.name,
|
|
40
|
+
description: guide.description,
|
|
41
|
+
whenToRead: guide.whenToRead,
|
|
42
|
+
path: formatPathForPrompt(guide.filePath),
|
|
43
|
+
}));
|
|
44
|
+
const capabilityCatalog = capabilityRegistry.catalog(capabilityContextFor(workspace));
|
|
45
|
+
const cardAgentProviders = config.subagents ? subagentProviders : [];
|
|
46
|
+
const cardAgents = workspace.agentProfiles.map((profile) => {
|
|
47
|
+
const summary = summarizeSubagentProfile(profile);
|
|
48
|
+
const availability = cardAgentProviders.find((provider) => provider.name === summary.provider);
|
|
49
|
+
return {
|
|
50
|
+
...summary,
|
|
51
|
+
providerAvailable: availability?.available,
|
|
52
|
+
providerUnavailableReason: availability?.reason,
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
const cardAgentsFiles = agentsFiles.map((file) => ({
|
|
56
|
+
path: formatAgentsPath(file.path, workspace.root),
|
|
57
|
+
content: file.content,
|
|
58
|
+
}));
|
|
59
|
+
const cardAvailableAgentsFiles = availableAgentsFiles.map((file) => ({
|
|
60
|
+
path: formatAgentsPath(file.path, workspace.root),
|
|
61
|
+
}));
|
|
62
|
+
const bootstrapComponents = new Set(bootstrapContextComponents);
|
|
63
|
+
const visibleSkills = bootstrapComponents.has("skills") ? cardSkills : [];
|
|
64
|
+
const visibleSkillDiagnostics = bootstrapComponents.has("skillDiagnostics")
|
|
65
|
+
? redactSkillDiagnosticPaths(workspace.skillDiagnostics)
|
|
66
|
+
: [];
|
|
67
|
+
const visibleCapabilityGuides = bootstrapComponents.has("capabilityGuides") ? capabilityGuides : [];
|
|
68
|
+
const visibleAgentProviders = bootstrapComponents.has("agentProfiles") ? cardAgentProviders : [];
|
|
69
|
+
const visibleAgents = bootstrapComponents.has("agentProfiles") ? cardAgents : [];
|
|
70
|
+
const loadedAgentsFiles = bootstrapComponents.has("agentsFiles") ? cardAgentsFiles : [];
|
|
71
|
+
const availableAgentsFileOutputs = bootstrapComponents.has("availableAgentsFiles")
|
|
72
|
+
? cardAvailableAgentsFiles
|
|
73
|
+
: [];
|
|
74
|
+
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 = "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.";
|
|
76
|
+
const cardInstruction = config.skillsEnabled
|
|
77
|
+
? `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
|
+
: `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
|
+
const instruction = workspaceReused
|
|
80
|
+
? includeBootstrapContext
|
|
81
|
+
? [
|
|
82
|
+
`Workspace already exists as ${workspace.id} for this directory.`,
|
|
83
|
+
"Reuse this workspaceId for subsequent tool calls.",
|
|
84
|
+
`Project bootstrap context components included in this response: ${bootstrapContextComponents.join(", ")}. Components not listed are unchanged and are not repeated.`,
|
|
85
|
+
workspaceContextInstruction,
|
|
86
|
+
workspaceManagementInstruction,
|
|
87
|
+
].join("\n\n")
|
|
88
|
+
: [
|
|
89
|
+
`Workspace already open as ${workspace.id}.`,
|
|
90
|
+
"Reuse this workspaceId for subsequent tool calls. This is the same directory previously opened in this conversation.",
|
|
91
|
+
"Continue following the project instructions, nested instruction files, skills, capability guides, agent profiles, and diagnostics previously provided for this workspace. They remain active and are not repeated here.",
|
|
92
|
+
workspaceContextInstruction,
|
|
93
|
+
workspaceManagementInstruction,
|
|
94
|
+
].join("\n\n")
|
|
95
|
+
: workspace.mode === "worktree"
|
|
96
|
+
? "Use this workspaceId for subsequent tool calls. Follow the project instructions, nested instruction files, skills, agent profiles, and diagnostics returned for this isolated worktree."
|
|
97
|
+
: cardInstruction;
|
|
98
|
+
const resultContent = [
|
|
99
|
+
{
|
|
100
|
+
type: "text",
|
|
101
|
+
text: [
|
|
102
|
+
workspaceReused
|
|
103
|
+
? `Workspace already open as ${workspace.id}.`
|
|
104
|
+
: workspace.mode === "worktree"
|
|
105
|
+
? `Opened isolated worktree workspace ${workspace.id}.`
|
|
106
|
+
: `Opened workspace ${workspace.id}.`,
|
|
107
|
+
`Root: ${workspace.root}`,
|
|
108
|
+
`Mode: ${workspace.mode}`,
|
|
109
|
+
loadedAgentsFiles.length > 0
|
|
110
|
+
? `Loaded project instructions: ${loadedAgentsFiles.map((file) => file.path).join(", ")}`
|
|
111
|
+
: undefined,
|
|
112
|
+
availableAgentsFileOutputs.length > 0
|
|
113
|
+
? `Available nested instructions: ${availableAgentsFileOutputs.map((file) => file.path).join(", ")}`
|
|
114
|
+
: undefined,
|
|
115
|
+
visibleSkills.length > 0
|
|
116
|
+
? `Available skills: ${visibleSkills.map((skill) => skill.name).join(", ")}`
|
|
117
|
+
: undefined,
|
|
118
|
+
capabilityCatalog.length > 0
|
|
119
|
+
? `Optional capabilities: ${capabilityCatalog.map((entry) => entry.name).join(", ")}`
|
|
120
|
+
: undefined,
|
|
121
|
+
visibleCapabilityGuides.length > 0
|
|
122
|
+
? `Capability guides: ${visibleCapabilityGuides.map((guide) => guide.name).join(", ")}`
|
|
123
|
+
: undefined,
|
|
124
|
+
visibleAgentProviders.some((provider) => provider.available)
|
|
125
|
+
? `Available subagent providers: ${visibleAgentProviders.filter((provider) => provider.available).map((provider) => provider.name).join(", ")}`
|
|
126
|
+
: undefined,
|
|
127
|
+
visibleAgentProviders.some((provider) => !provider.available)
|
|
128
|
+
? `Unavailable subagent providers: ${visibleAgentProviders.filter((provider) => !provider.available).map(formatUnavailableSubagentProvider).join(", ")}`
|
|
129
|
+
: undefined,
|
|
130
|
+
visibleAgents.length > 0
|
|
131
|
+
? `Available subagent profiles: ${visibleAgents.map(formatAvailableSubagentProfile).join(", ")}`
|
|
132
|
+
: undefined,
|
|
133
|
+
knownWorktrees.length > 0
|
|
134
|
+
? `Known worktrees: ${knownWorktrees.map((worktree) => `${worktree.path} [${worktree.workspaceId}]${worktree.branch ? ` branch=${worktree.branch}` : ""}${worktree.targetBranch ? ` target=${worktree.targetBranch}` : ""}${worktree.current ? " (current)" : ""}`).join(", ")}`
|
|
135
|
+
: undefined,
|
|
136
|
+
staleWorkspaces.length > 0
|
|
137
|
+
? `This Workspace has been idle for more than 2 days: ${staleWorkspaces.map((stale) => `${stale.workspaceId} last-used=${stale.lastUsedAt}`).join(", ")}. It remains available to resume or explicitly close; do not clean it up automatically.`
|
|
138
|
+
: undefined,
|
|
139
|
+
`ForgeRelay ${capabilityFingerprint.version} capabilities: ${capabilityFingerprint.capabilities.join(", ")}`,
|
|
140
|
+
instruction,
|
|
141
|
+
].filter(Boolean).join("\n"),
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
logToolCall(config, {
|
|
145
|
+
tool: "open_workspace",
|
|
146
|
+
...workspaceLogContext(workspace, sessionId),
|
|
147
|
+
path: workspace.root,
|
|
148
|
+
success: true,
|
|
149
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
150
|
+
});
|
|
151
|
+
const workspaceCard = {
|
|
152
|
+
workspaceId: workspace.id,
|
|
153
|
+
kind: "workspace",
|
|
154
|
+
root: workspace.root,
|
|
155
|
+
path: workspace.root,
|
|
156
|
+
mode: workspace.mode,
|
|
157
|
+
workspaceReused,
|
|
158
|
+
includeBootstrapContext,
|
|
159
|
+
sourceRoot: workspace.sourceRoot,
|
|
160
|
+
worktree: workspace.worktree,
|
|
161
|
+
worktrees: knownWorktrees,
|
|
162
|
+
staleWorkspaces,
|
|
163
|
+
capabilityFingerprint,
|
|
164
|
+
contextFingerprint,
|
|
165
|
+
capabilityCatalog,
|
|
166
|
+
agentsFiles: cardAgentsFiles,
|
|
167
|
+
availableAgentsFiles: cardAvailableAgentsFiles,
|
|
168
|
+
skills: cardSkills,
|
|
169
|
+
agentProviders: cardAgentProviders,
|
|
170
|
+
agents: cardAgents,
|
|
171
|
+
instruction: cardInstruction,
|
|
172
|
+
summary: {
|
|
173
|
+
mode: workspace.mode,
|
|
174
|
+
agentsFiles: cardAgentsFiles.length,
|
|
175
|
+
availableAgentsFiles: cardAvailableAgentsFiles.length,
|
|
176
|
+
skills: cardSkills.length,
|
|
177
|
+
capabilities: capabilityCatalog.length,
|
|
178
|
+
agentProviders: cardAgentProviders.length,
|
|
179
|
+
agents: cardAgents.length,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
const response = hooks.decorateResult(workspace.id, attachHookReports({
|
|
183
|
+
content: resultContent,
|
|
184
|
+
_meta: {
|
|
185
|
+
tool: "open_workspace",
|
|
186
|
+
card: includeBootstrapContext
|
|
187
|
+
? workspaceCard
|
|
188
|
+
: compactWorkspacePresentation(workspaceCard),
|
|
189
|
+
},
|
|
190
|
+
structuredContent: {
|
|
191
|
+
action: "open",
|
|
192
|
+
workspaceId: workspace.id,
|
|
193
|
+
kind: "workspace",
|
|
194
|
+
root: workspace.root,
|
|
195
|
+
mode: workspace.mode,
|
|
196
|
+
sourceRoot: workspace.sourceRoot,
|
|
197
|
+
worktree: workspace.worktree,
|
|
198
|
+
worktrees: knownWorktrees,
|
|
199
|
+
staleWorkspaces,
|
|
200
|
+
capabilityFingerprint,
|
|
201
|
+
contextFingerprint,
|
|
202
|
+
capabilityCatalog,
|
|
203
|
+
...(bootstrapComponents.has("capabilityGuides")
|
|
204
|
+
? { capabilityGuides: visibleCapabilityGuides }
|
|
205
|
+
: {}),
|
|
206
|
+
...(bootstrapComponents.has("agentsFiles") ? { agentsFiles: loadedAgentsFiles } : {}),
|
|
207
|
+
...(bootstrapComponents.has("availableAgentsFiles")
|
|
208
|
+
? { availableAgentsFiles: availableAgentsFileOutputs }
|
|
209
|
+
: {}),
|
|
210
|
+
...(bootstrapComponents.has("skills") ? { skills: visibleSkills } : {}),
|
|
211
|
+
...(bootstrapComponents.has("agentProfiles")
|
|
212
|
+
? { agentProviders: visibleAgentProviders, agents: visibleAgents }
|
|
213
|
+
: {}),
|
|
214
|
+
...(bootstrapComponents.has("skillDiagnostics")
|
|
215
|
+
? { skillDiagnostics: visibleSkillDiagnostics }
|
|
216
|
+
: {}),
|
|
217
|
+
instruction,
|
|
218
|
+
},
|
|
219
|
+
}, hookReports));
|
|
220
|
+
rememberWorkspacePanelState(workspace.id, response);
|
|
221
|
+
return response;
|
|
222
|
+
}
|