@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.
Files changed (246) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +10 -18
  3. package/capabilities/host-integration/GUIDE.md +2 -2
  4. package/dist/activity/{audit-store.js → history/audit-store.js} +129 -21
  5. package/dist/activity/history/bash-output-store.js +250 -0
  6. package/dist/activity/{host-turn-store.js → history/host-turn-store.js} +4 -1
  7. package/dist/activity/{query-service.js → history/query-service.js} +13 -7
  8. package/dist/activity/{lifecycle.js → runtime/lifecycle.js} +1 -1
  9. package/dist/activity/{mcp-query-tools.js → runtime/mcp-query-tools.js} +3 -3
  10. package/dist/activity/storage/history.js +12 -0
  11. package/dist/activity/storage/paths.js +56 -0
  12. package/dist/activity/storage/segmented-log.js +149 -0
  13. package/dist/cli/setup-support.js +116 -0
  14. package/dist/cli.js +18 -126
  15. package/dist/lsp/code-intelligence.js +1 -1
  16. package/dist/lsp/test-support/server-fixture.js +9 -9
  17. package/dist/mcp/filesystem/filesystem-tools.js +245 -0
  18. package/dist/mcp/hooks/command-runner.js +79 -0
  19. package/dist/{hook-cli.js → mcp/hooks/hook-cli.js} +1 -1
  20. package/dist/{hooks.js → mcp/hooks/hooks.js} +3 -80
  21. package/dist/{oauth-provider.js → mcp/oauth/oauth-provider.js} +1 -1
  22. package/dist/{oauth-store.js → mcp/oauth/oauth-store.js} +5 -4
  23. package/dist/{operations → mcp/operations}/native-bulk-mutations.js +2 -2
  24. package/dist/mcp/panel/app.js +143 -0
  25. package/dist/{mcp-app-template.js → mcp/panel/mcp-app-template.js} +2 -38
  26. package/dist/mcp/process/runtime.js +257 -0
  27. package/dist/mcp/process/tools.js +346 -0
  28. package/dist/mcp/server/core/activity-support.js +70 -0
  29. package/dist/{capabilities.js → mcp/server/core/capabilities.js} +5 -3
  30. package/dist/{capability-registry.js → mcp/server/core/capability-registry.js} +2 -2
  31. package/dist/mcp/server/core/capability-support.js +90 -0
  32. package/dist/mcp/server/core/schemas.js +216 -0
  33. package/dist/mcp/server/core/tool-support.js +207 -0
  34. package/dist/mcp/server/core/version.js +9 -0
  35. package/dist/mcp/server/operations/runtime/filesystem-tools.js +400 -0
  36. package/dist/mcp/server/operations/runtime/operation-runtime.js +523 -0
  37. package/dist/mcp/server/transport/http-server.js +301 -0
  38. package/dist/mcp/server/transport/http-support.js +43 -0
  39. package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +222 -0
  40. package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +187 -0
  41. package/dist/mcp/server/workspace/runtime/workspace-open.js +552 -0
  42. package/dist/mcp/server/workspace/runtime/workspace-tools.js +601 -0
  43. package/dist/{config.js → runtime/config/config.js} +3 -3
  44. package/dist/{user-config.js → runtime/config/user-config.js} +14 -54
  45. package/dist/{db → runtime/state/db}/client.js +1 -1
  46. package/dist/{db → runtime/state/db}/migrations.js +34 -3
  47. package/dist/runtime/state/lock/file-lock.js +130 -0
  48. package/dist/runtime/testing/server-fixture.js +190 -0
  49. package/dist/server.js +116 -4202
  50. package/dist/subagents/providers/adapters/claude.js +86 -52
  51. package/dist/subagents/providers/adapters/codex.js +103 -39
  52. package/dist/subagents/providers/adapters/opencode.js +114 -84
  53. package/dist/subagents/providers/adapters/pi.js +59 -8
  54. package/dist/subagents/providers/availability.js +15 -30
  55. package/dist/subagents/providers/runtime/external-command.js +56 -0
  56. package/dist/subagents/sessions/capability.js +1 -1
  57. package/dist/subagents/sessions/execution.js +1 -1
  58. package/dist/subagents/sessions/mcp/activity-audit-test-support.js +30 -0
  59. package/dist/subagents/sessions/store.js +1 -1
  60. package/dist/ui/.vite/manifest.json +342 -410
  61. package/dist/ui/activity-panel-app.html +2 -4
  62. package/dist/ui/assets/activity-panel-app-7Xe0alhm.css +1 -0
  63. package/dist/ui/assets/activity-panel-app-Sl1Jro8v.js +115 -0
  64. package/dist/ui/assets/angular-html-B_yn91wW.js +1 -0
  65. package/dist/ui/assets/{angular-ts-Bxh4-O1-.js → angular-ts-DSgtloKz.js} +1 -1
  66. package/dist/ui/assets/{apl-BmYHKQ6m.js → apl-NatWtF8X.js} +1 -1
  67. package/dist/ui/assets/{astro-C51vqDKq.js → astro-BYFEVBuN.js} +1 -1
  68. package/dist/ui/assets/{blade-CdhVsmwq.js → blade-DoO7yvMR.js} +1 -1
  69. package/dist/ui/assets/c-Rl2hMCcg.js +1 -0
  70. package/dist/ui/assets/{cobol-Cf6azoN_.js → cobol-ByC5mWiB.js} +1 -1
  71. package/dist/ui/assets/{coffee-vVdGzlmt.js → coffee-Dez77Qov.js} +1 -1
  72. package/dist/ui/assets/cpp-DtaAQT9h.js +1 -0
  73. package/dist/ui/assets/{crystal-5UZy6KIX.js → crystal-Bgmbf8ec.js} +1 -1
  74. package/dist/ui/assets/css-C1tjBtVN.js +1 -0
  75. package/dist/ui/assets/{edge-CESgo4ia.js → edge-DeH6u8Wg.js} +1 -1
  76. package/dist/ui/assets/{elixir-9FKfw4cJ.js → elixir-_H3KnmWT.js} +1 -1
  77. package/dist/ui/assets/{elm-Dv7A0aLa.js → elm-CsCe3U28.js} +1 -1
  78. package/dist/ui/assets/{erb-DHFmDvM0.js → erb-CbM-L2g8.js} +1 -1
  79. package/dist/ui/assets/{git-rebase-utbU6CzV.js → git-rebase-D0s5pDh9.js} +1 -1
  80. package/dist/ui/assets/{glimmer-js-t2kiCkyB.js → glimmer-js-BfOToq_A.js} +1 -1
  81. package/dist/ui/assets/{glimmer-ts-F0Jv5okV.js → glimmer-ts-CCUOkL2K.js} +1 -1
  82. package/dist/ui/assets/glsl-DH4ec9dE.js +1 -0
  83. package/dist/ui/assets/graphql-DilAMBoI.js +1 -0
  84. package/dist/ui/assets/{hack-8yjdbaSA.js → hack-BDkNDZlF.js} +1 -1
  85. package/dist/ui/assets/haml-D_QBp6K7.js +1 -0
  86. package/dist/ui/assets/{handlebars-BBFEg4Lo.js → handlebars-DePUrTpC.js} +1 -1
  87. package/dist/ui/assets/heavy-payload-DOwZpO6n.js +290 -0
  88. package/dist/ui/assets/html-By4dNnM4.js +1 -0
  89. package/dist/ui/assets/{html-derivative-DJAQcZyc.js → html-derivative-DDw9Ef4Q.js} +1 -1
  90. package/dist/ui/assets/{http-Dg5LSYRK.js → http-B0QxWc9M.js} +1 -1
  91. package/dist/ui/assets/{hurl-Y5uYzMZR.js → hurl-vMQ4lda1.js} +1 -1
  92. package/dist/ui/assets/java-DYRe2lWG.js +1 -0
  93. package/dist/ui/assets/javascript-CxyGqwcL.js +1 -0
  94. package/dist/ui/assets/{jinja-Bt30h8Ik.js → jinja-JUwV-fZt.js} +1 -1
  95. package/dist/ui/assets/{jison-D5uZ1Nyo.js → jison-7ZqCEvS8.js} +1 -1
  96. package/dist/ui/assets/json-dbltgiiA.js +1 -0
  97. package/dist/ui/assets/jsx-oDopT7YQ.js +1 -0
  98. package/dist/ui/assets/{julia-76hRaaGz.js → julia-CkKiNSav.js} +1 -1
  99. package/dist/ui/assets/{just-CDE7W0vW.js → just-DuqCAKJ3.js} +1 -1
  100. package/dist/ui/assets/{latex-CVO2Byab.js → latex-sudcRDSn.js} +1 -1
  101. package/dist/ui/assets/{liquid-CA-AS3EX.js → liquid-Di-08Js7.js} +1 -1
  102. package/dist/ui/assets/lua-YlqdMoV0.js +1 -0
  103. package/dist/ui/assets/{marko-_BWrPfny.js → marko-CJRNyJDb.js} +1 -1
  104. package/dist/ui/assets/{mdc-PyJv85eS.js → mdc-t12CzZHs.js} +1 -1
  105. package/dist/ui/assets/{nginx-DAWVw8yu.js → nginx-57-b4XwT.js} +1 -1
  106. package/dist/ui/assets/{nim-CKE1dzpU.js → nim-BzrswYQa.js} +1 -1
  107. package/dist/ui/assets/{perl-BBhWva19.js → perl-D9JR_iIh.js} +1 -1
  108. package/dist/ui/assets/{php-ChIeaAoF.js → php-Py2gFHil.js} +1 -1
  109. package/dist/ui/assets/{pug-CMgI5afi.js → pug-PzLLy0h1.js} +1 -1
  110. package/dist/ui/assets/{qml-CjxTkcFH.js → qml-By8fwgV4.js} +1 -1
  111. package/dist/ui/assets/r-CcezTbLq.js +1 -0
  112. package/dist/ui/assets/{razor-i4DbHr8p.js → razor-dG-vdhL_.js} +1 -1
  113. package/dist/ui/assets/regexp-BlsQ-N_8.js +1 -0
  114. package/dist/ui/assets/{rst-oy_97Kl4.js → rst-C2awDCat.js} +1 -1
  115. package/dist/ui/assets/{ruby-C5zYZIyl.js → ruby-zA2W_49d.js} +1 -1
  116. package/dist/ui/assets/{sas-BnMEh2S3.js → sas-D49bhyaN.js} +1 -1
  117. package/dist/ui/assets/scss-BQb-cp01.js +1 -0
  118. package/dist/ui/assets/shellscript-Dga4H_Ni.js +1 -0
  119. package/dist/ui/assets/{shellsession-BpE5o7_3.js → shellsession-n3-5CqcF.js} +1 -1
  120. package/dist/ui/assets/{soy-COesmhTY.js → soy-XYBOf_BA.js} +1 -1
  121. package/dist/ui/assets/sql-ET4u5Cpl.js +1 -0
  122. package/dist/ui/assets/{stata-CGQEuSZq.js → stata-CLHcJHO_.js} +1 -1
  123. package/dist/ui/assets/{surrealql-Bz4JbU-9.js → surrealql-BdjQ60T4.js} +1 -1
  124. package/dist/ui/assets/{svelte-1jM2ejXj.js → svelte-B2RPtKJe.js} +1 -1
  125. package/dist/ui/assets/{templ-C-VwELcp.js → templ-CrByPW_W.js} +1 -1
  126. package/dist/ui/assets/{tex-t1rUiQCm.js → tex-DZBkg363.js} +1 -1
  127. package/dist/ui/assets/{ts-tags-Bewoa6pS.js → ts-tags-DJR3Gphb.js} +1 -1
  128. package/dist/ui/assets/tsx-CajzROyy.js +1 -0
  129. package/dist/ui/assets/{twig-DVg-vELl.js → twig-BZjk5Zda.js} +1 -1
  130. package/dist/ui/assets/typescript-B-EWToLj.js +1 -0
  131. package/dist/ui/assets/{vue-wPQ7UlMy.js → vue-D_rHCCr8.js} +1 -1
  132. package/dist/ui/assets/{vue-html-BHtJ8geX.js → vue-html-CC4WlvVt.js} +1 -1
  133. package/dist/ui/assets/{vue-vine-CNSGAWwc.js → vue-vine-BgNMVgpk.js} +1 -1
  134. package/dist/ui/assets/xml-BI86-WbH.js +1 -0
  135. package/dist/ui/assets/{xsl-3_vJ27aE.js → xsl-DXeLZV4J.js} +1 -1
  136. package/dist/ui/assets/yaml-60_PFlWa.js +1 -0
  137. package/dist/workspaces/bootstrap.js +72 -0
  138. package/dist/workspaces/context.js +302 -0
  139. package/dist/workspaces/conversation-test-support.js +86 -0
  140. package/dist/{git-worktrees.js → workspaces/git/git-worktrees.js} +1 -1
  141. package/dist/workspaces/inventory.js +131 -0
  142. package/dist/workspaces/paths.js +55 -0
  143. package/dist/{remote-auth.js → workspaces/relay/auth/remote-auth.js} +2 -2
  144. package/dist/workspaces/relay/result-support.js +111 -0
  145. package/dist/workspaces/relay/tests/test-support.js +203 -0
  146. package/dist/{remote-mcp-connection-pool.js → workspaces/relay/transport/remote-mcp-connection-pool.js} +1 -1
  147. package/dist/workspaces/relay/types.js +1 -0
  148. package/dist/{remote-workspace-relay.js → workspaces/relay/workspace-relay.js} +23 -172
  149. package/dist/workspaces/resources/skills.js +285 -0
  150. package/dist/{review-checkpoints.js → workspaces/review/review-checkpoints.js} +2 -2
  151. package/dist/workspaces/sessions.js +457 -0
  152. package/dist/{workspace-store.js → workspaces/state/workspace-store.js} +2 -2
  153. package/dist/workspaces.js +68 -995
  154. package/docs/chatgpt-coding-workflow.md +3 -11
  155. package/docs/configuration.md +12 -25
  156. package/docs/gotchas.md +0 -30
  157. package/docs/roadmap.md +6 -10
  158. package/docs/security.md +1 -5
  159. package/docs/setup.md +0 -11
  160. package/package.json +6 -8
  161. package/scripts/ci/architecture.mjs +109 -0
  162. package/scripts/ci/verify.mjs +5 -0
  163. package/scripts/debug/accept/bootstrap.mjs +326 -0
  164. package/scripts/debug/accept/harness.mjs +76 -0
  165. package/scripts/debug/accept/support.mjs +432 -0
  166. package/scripts/debug/accept.mjs +43 -806
  167. package/scripts/debug/relay-accept/support.mjs +135 -0
  168. package/scripts/debug/relay-accept.mjs +26 -115
  169. package/scripts/debug/traffic/support.mjs +167 -0
  170. package/scripts/debug/traffic/traffic-audit.mjs +4 -161
  171. package/scripts/{lsp-interop-support.test.mjs → lsp/interop-support.test.mjs} +1 -1
  172. package/scripts/{lsp-interop.mjs → lsp/interop.mjs} +3 -3
  173. package/scripts/wiki/sync.mjs +21 -10
  174. package/dist/activity/bash-output-store.js +0 -127
  175. package/dist/pi-tools.js +0 -110
  176. package/dist/skills.js +0 -127
  177. package/dist/ui/assets/activity-panel-app-7AckaxIR.js +0 -2
  178. package/dist/ui/assets/angular-html-BkgDQl-G.js +0 -1
  179. package/dist/ui/assets/c-IBzBpxAO.js +0 -1
  180. package/dist/ui/assets/chunk-EyZ2wyi3.js +0 -1
  181. package/dist/ui/assets/cpp-CVHeO-Jk.js +0 -1
  182. package/dist/ui/assets/css-DEfus9J5.js +0 -1
  183. package/dist/ui/assets/glsl-_ooCjaeB.js +0 -1
  184. package/dist/ui/assets/graphql-B9LqP-cY.js +0 -1
  185. package/dist/ui/assets/haml-BU5EoZBn.js +0 -1
  186. package/dist/ui/assets/heavy-payload-Bol1_mcs.js +0 -4
  187. package/dist/ui/assets/html-D-C6YZk4.js +0 -1
  188. package/dist/ui/assets/java-ppHvycj5.js +0 -1
  189. package/dist/ui/assets/javascript-DDTjKLlQ.js +0 -1
  190. package/dist/ui/assets/json-Cw91r8h1.js +0 -1
  191. package/dist/ui/assets/jsx-04X2FT_1.js +0 -1
  192. package/dist/ui/assets/lua-CiyBp52Y.js +0 -1
  193. package/dist/ui/assets/r-DfFga4si.js +0 -1
  194. package/dist/ui/assets/regexp-iC9_nbSI.js +0 -1
  195. package/dist/ui/assets/review-payload-BqJp0c5e.js +0 -1
  196. package/dist/ui/assets/scrollbar-CvE-I-jG.js +0 -287
  197. package/dist/ui/assets/scss-BfZXsAWQ.js +0 -1
  198. package/dist/ui/assets/shellscript-BC2srNaB.js +0 -1
  199. package/dist/ui/assets/sql-Ccv_yeUm.js +0 -1
  200. package/dist/ui/assets/tsx-CTXzrBkN.js +0 -1
  201. package/dist/ui/assets/typescript--ElHoFkw.js +0 -1
  202. package/dist/ui/assets/workspace-app-BLW7p6IZ.js +0 -114
  203. package/dist/ui/assets/workspace-app-BjNqR0en.js +0 -1
  204. package/dist/ui/assets/workspace-app-CmaYU4DW.js +0 -3
  205. package/dist/ui/assets/workspace-app-D2bJ5fjt.css +0 -1
  206. package/dist/ui/assets/workspace-lifecycle-app-BVbI2Ilf.js +0 -1
  207. package/dist/ui/assets/xml-JnmX6vyS.js +0 -1
  208. package/dist/ui/assets/yaml-OAcZkP-w.js +0 -1
  209. package/dist/ui/workspace-app.html +0 -18
  210. package/dist/ui/workspace-lifecycle-app.html +0 -18
  211. package/docs/agent-profile-schema.md +0 -114
  212. package/docs/artifact-exchange.md +0 -65
  213. package/docs/versioning.md +0 -205
  214. /package/capabilities/{managed-worktrees → workspace/managed-worktrees}/GUIDE.md +0 -0
  215. /package/capabilities/{workspace-tasks → workspace/workspace-tasks}/GUIDE.md +0 -0
  216. /package/dist/activity/{process-output-audit.js → runtime/process-output-audit.js} +0 -0
  217. /package/dist/{artifact-error.js → mcp/artifacts/artifact-error.js} +0 -0
  218. /package/dist/{artifact-tools.js → mcp/artifacts/artifact-tools.js} +0 -0
  219. /package/dist/{incoming-artifacts.js → mcp/artifacts/incoming-artifacts.js} +0 -0
  220. /package/dist/{advertised-files.js → mcp/filesystem/advertised-files.js} +0 -0
  221. /package/dist/{apply-patch.js → mcp/filesystem/apply-patch.js} +0 -0
  222. /package/dist/{file-mutations.js → mcp/filesystem/file-mutations.js} +0 -0
  223. /package/dist/{roots.js → mcp/filesystem/roots.js} +0 -0
  224. /package/dist/{oauth → mcp/oauth}/public-url.js +0 -0
  225. /package/dist/{oauth → mcp/oauth}/router.js +0 -0
  226. /package/dist/{operations → mcp/operations}/batch/executor.js +0 -0
  227. /package/dist/{operations → mcp/operations}/batch/scheduler.js +0 -0
  228. /package/dist/{operations → mcp/operations}/batch/types.js +0 -0
  229. /package/dist/{operations → mcp/operations}/bulk-mutation.js +0 -0
  230. /package/dist/{operations → mcp/operations}/bulk-read.js +0 -0
  231. /package/dist/{operations → mcp/operations}/core-operation-executor.js +0 -0
  232. /package/dist/{process-platform.js → mcp/process/process-platform.js} +0 -0
  233. /package/dist/{process-sessions.js → mcp/process/process-sessions.js} +0 -0
  234. /package/dist/{request-meta.js → mcp/request-meta.js} +0 -0
  235. /package/dist/{mcp-sessions.js → mcp/server/transport/mcp-sessions.js} +0 -0
  236. /package/dist/{server-shutdown.js → mcp/server/transport/server-shutdown.js} +0 -0
  237. /package/dist/{logger.js → runtime/logging/logger.js} +0 -0
  238. /package/dist/{db → runtime/state/db}/schema.js +0 -0
  239. /package/dist/{composite-activity.js → workspaces/composite/composite-activity.js} +0 -0
  240. /package/dist/{composite-workspaces.js → workspaces/composite/composite-workspaces.js} +0 -0
  241. /package/dist/{git.js → workspaces/git/git.js} +0 -0
  242. /package/dist/{workspace-presentation.js → workspaces/presentation/workspace-presentation.js} +0 -0
  243. /package/dist/{remote-transport.js → workspaces/relay/transport/remote-transport.js} +0 -0
  244. /package/dist/{workspace-task-reminders.js → workspaces/tasks/workspace-task-reminders.js} +0 -0
  245. /package/dist/{workspace-tasks.js → workspaces/tasks/workspace-tasks.js} +0 -0
  246. /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
+ }