@akira-tl/forgerelay 0.8.8 → 0.8.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +10 -18
  3. package/capabilities/code-intelligence/GUIDE.md +7 -3
  4. package/capabilities/host-integration/GUIDE.md +2 -2
  5. package/capabilities/subagents/GUIDE.md +9 -0
  6. package/capabilities/{workspace-tasks → workspace/workspace-tasks}/GUIDE.md +6 -0
  7. package/dist/activity/{audit-store.js → history/audit-store.js} +129 -21
  8. package/dist/activity/history/bash-output-store.js +250 -0
  9. package/dist/activity/{host-turn-store.js → history/host-turn-store.js} +4 -1
  10. package/dist/activity/{query-service.js → history/query-service.js} +13 -7
  11. package/dist/activity/{lifecycle.js → runtime/lifecycle.js} +1 -1
  12. package/dist/activity/{mcp-query-tools.js → runtime/mcp-query-tools.js} +3 -3
  13. package/dist/activity/storage/history.js +12 -0
  14. package/dist/activity/storage/paths.js +56 -0
  15. package/dist/activity/storage/segmented-log.js +149 -0
  16. package/dist/cli/init.js +180 -0
  17. package/dist/cli/setup-support.js +181 -0
  18. package/dist/cli.js +23 -216
  19. package/dist/lsp/code-intelligence.js +18 -4
  20. package/dist/lsp/runtime/diagnostic-snapshots.js +52 -1
  21. package/dist/lsp/runtime/managed-language-servers.js +172 -0
  22. package/dist/lsp/runtime/manager.js +37 -2
  23. package/dist/lsp/runtime/process-launch.js +3 -0
  24. package/dist/lsp/test-support/server-fixture.js +14 -11
  25. package/dist/mcp/filesystem/filesystem-tools.js +245 -0
  26. package/dist/mcp/hooks/command-runner.js +79 -0
  27. package/dist/{hook-cli.js → mcp/hooks/hook-cli.js} +1 -1
  28. package/dist/{hooks.js → mcp/hooks/hooks.js} +3 -80
  29. package/dist/{oauth-provider.js → mcp/oauth/oauth-provider.js} +1 -1
  30. package/dist/{oauth-store.js → mcp/oauth/oauth-store.js} +5 -4
  31. package/dist/{operations → mcp/operations}/native-bulk-mutations.js +2 -2
  32. package/dist/mcp/panel/app.js +143 -0
  33. package/dist/{mcp-app-template.js → mcp/panel/mcp-app-template.js} +2 -38
  34. package/dist/{process-platform.js → mcp/process/process-platform.js} +21 -9
  35. package/dist/{process-sessions.js → mcp/process/process-sessions.js} +3 -3
  36. package/dist/mcp/process/runtime.js +257 -0
  37. package/dist/mcp/process/tools.js +346 -0
  38. package/dist/mcp/server/core/activity-support.js +70 -0
  39. package/dist/{capabilities.js → mcp/server/core/capabilities.js} +5 -3
  40. package/dist/{capability-registry.js → mcp/server/core/capability-registry.js} +9 -4
  41. package/dist/mcp/server/core/capability-support.js +90 -0
  42. package/dist/mcp/server/core/schemas.js +216 -0
  43. package/dist/mcp/server/core/tool-support.js +210 -0
  44. package/dist/mcp/server/core/version.js +9 -0
  45. package/dist/mcp/server/operations/runtime/filesystem-tools.js +455 -0
  46. package/dist/mcp/server/operations/runtime/mutation-diagnostics.js +54 -0
  47. package/dist/mcp/server/operations/runtime/operation-runtime.js +524 -0
  48. package/dist/mcp/server/transport/http-server.js +301 -0
  49. package/dist/mcp/server/transport/http-support.js +43 -0
  50. package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +242 -0
  51. package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +187 -0
  52. package/dist/mcp/server/workspace/runtime/workspace-open.js +555 -0
  53. package/dist/mcp/server/workspace/runtime/workspace-tools.js +601 -0
  54. package/dist/mcp/server-instructions.js +1 -1
  55. package/dist/{config.js → runtime/config/config.js} +4 -3
  56. package/dist/{user-config.js → runtime/config/user-config.js} +14 -63
  57. package/dist/{db → runtime/state/db}/client.js +1 -1
  58. package/dist/{db → runtime/state/db}/migrations.js +34 -3
  59. package/dist/runtime/state/lock/file-lock.js +130 -0
  60. package/dist/runtime/testing/server-fixture.js +190 -0
  61. package/dist/server.js +133 -4189
  62. package/dist/subagents/providers/adapters/claude.js +86 -52
  63. package/dist/subagents/providers/adapters/codex.js +103 -39
  64. package/dist/subagents/providers/adapters/opencode.js +114 -84
  65. package/dist/subagents/providers/adapters/pi.js +59 -8
  66. package/dist/subagents/providers/availability.js +15 -30
  67. package/dist/subagents/providers/runtime/external-command.js +56 -0
  68. package/dist/subagents/sessions/capability.js +1 -1
  69. package/dist/subagents/sessions/execution.js +1 -1
  70. package/dist/subagents/sessions/mcp/activity-audit-test-support.js +30 -0
  71. package/dist/subagents/sessions/store.js +1 -1
  72. package/dist/ui/.vite/manifest.json +342 -410
  73. package/dist/ui/activity-panel-app.html +2 -4
  74. package/dist/ui/assets/activity-panel-app-7Xe0alhm.css +1 -0
  75. package/dist/ui/assets/activity-panel-app-Sl1Jro8v.js +115 -0
  76. package/dist/ui/assets/angular-html-B_yn91wW.js +1 -0
  77. package/dist/ui/assets/{angular-ts-Bxh4-O1-.js → angular-ts-DSgtloKz.js} +1 -1
  78. package/dist/ui/assets/{apl-BmYHKQ6m.js → apl-NatWtF8X.js} +1 -1
  79. package/dist/ui/assets/{astro-C51vqDKq.js → astro-BYFEVBuN.js} +1 -1
  80. package/dist/ui/assets/{blade-CdhVsmwq.js → blade-DoO7yvMR.js} +1 -1
  81. package/dist/ui/assets/c-Rl2hMCcg.js +1 -0
  82. package/dist/ui/assets/{cobol-Cf6azoN_.js → cobol-ByC5mWiB.js} +1 -1
  83. package/dist/ui/assets/{coffee-vVdGzlmt.js → coffee-Dez77Qov.js} +1 -1
  84. package/dist/ui/assets/cpp-DtaAQT9h.js +1 -0
  85. package/dist/ui/assets/{crystal-5UZy6KIX.js → crystal-Bgmbf8ec.js} +1 -1
  86. package/dist/ui/assets/css-C1tjBtVN.js +1 -0
  87. package/dist/ui/assets/{edge-CESgo4ia.js → edge-DeH6u8Wg.js} +1 -1
  88. package/dist/ui/assets/{elixir-9FKfw4cJ.js → elixir-_H3KnmWT.js} +1 -1
  89. package/dist/ui/assets/{elm-Dv7A0aLa.js → elm-CsCe3U28.js} +1 -1
  90. package/dist/ui/assets/{erb-DHFmDvM0.js → erb-CbM-L2g8.js} +1 -1
  91. package/dist/ui/assets/{git-rebase-utbU6CzV.js → git-rebase-D0s5pDh9.js} +1 -1
  92. package/dist/ui/assets/{glimmer-js-t2kiCkyB.js → glimmer-js-BfOToq_A.js} +1 -1
  93. package/dist/ui/assets/{glimmer-ts-F0Jv5okV.js → glimmer-ts-CCUOkL2K.js} +1 -1
  94. package/dist/ui/assets/glsl-DH4ec9dE.js +1 -0
  95. package/dist/ui/assets/graphql-DilAMBoI.js +1 -0
  96. package/dist/ui/assets/{hack-8yjdbaSA.js → hack-BDkNDZlF.js} +1 -1
  97. package/dist/ui/assets/haml-D_QBp6K7.js +1 -0
  98. package/dist/ui/assets/{handlebars-BBFEg4Lo.js → handlebars-DePUrTpC.js} +1 -1
  99. package/dist/ui/assets/heavy-payload-DOwZpO6n.js +290 -0
  100. package/dist/ui/assets/html-By4dNnM4.js +1 -0
  101. package/dist/ui/assets/{html-derivative-DJAQcZyc.js → html-derivative-DDw9Ef4Q.js} +1 -1
  102. package/dist/ui/assets/{http-Dg5LSYRK.js → http-B0QxWc9M.js} +1 -1
  103. package/dist/ui/assets/{hurl-Y5uYzMZR.js → hurl-vMQ4lda1.js} +1 -1
  104. package/dist/ui/assets/java-DYRe2lWG.js +1 -0
  105. package/dist/ui/assets/javascript-CxyGqwcL.js +1 -0
  106. package/dist/ui/assets/{jinja-Bt30h8Ik.js → jinja-JUwV-fZt.js} +1 -1
  107. package/dist/ui/assets/{jison-D5uZ1Nyo.js → jison-7ZqCEvS8.js} +1 -1
  108. package/dist/ui/assets/json-dbltgiiA.js +1 -0
  109. package/dist/ui/assets/jsx-oDopT7YQ.js +1 -0
  110. package/dist/ui/assets/{julia-76hRaaGz.js → julia-CkKiNSav.js} +1 -1
  111. package/dist/ui/assets/{just-CDE7W0vW.js → just-DuqCAKJ3.js} +1 -1
  112. package/dist/ui/assets/{latex-CVO2Byab.js → latex-sudcRDSn.js} +1 -1
  113. package/dist/ui/assets/{liquid-CA-AS3EX.js → liquid-Di-08Js7.js} +1 -1
  114. package/dist/ui/assets/lua-YlqdMoV0.js +1 -0
  115. package/dist/ui/assets/{marko-_BWrPfny.js → marko-CJRNyJDb.js} +1 -1
  116. package/dist/ui/assets/{mdc-PyJv85eS.js → mdc-t12CzZHs.js} +1 -1
  117. package/dist/ui/assets/{nginx-DAWVw8yu.js → nginx-57-b4XwT.js} +1 -1
  118. package/dist/ui/assets/{nim-CKE1dzpU.js → nim-BzrswYQa.js} +1 -1
  119. package/dist/ui/assets/{perl-BBhWva19.js → perl-D9JR_iIh.js} +1 -1
  120. package/dist/ui/assets/{php-ChIeaAoF.js → php-Py2gFHil.js} +1 -1
  121. package/dist/ui/assets/{pug-CMgI5afi.js → pug-PzLLy0h1.js} +1 -1
  122. package/dist/ui/assets/{qml-CjxTkcFH.js → qml-By8fwgV4.js} +1 -1
  123. package/dist/ui/assets/r-CcezTbLq.js +1 -0
  124. package/dist/ui/assets/{razor-i4DbHr8p.js → razor-dG-vdhL_.js} +1 -1
  125. package/dist/ui/assets/regexp-BlsQ-N_8.js +1 -0
  126. package/dist/ui/assets/{rst-oy_97Kl4.js → rst-C2awDCat.js} +1 -1
  127. package/dist/ui/assets/{ruby-C5zYZIyl.js → ruby-zA2W_49d.js} +1 -1
  128. package/dist/ui/assets/{sas-BnMEh2S3.js → sas-D49bhyaN.js} +1 -1
  129. package/dist/ui/assets/scss-BQb-cp01.js +1 -0
  130. package/dist/ui/assets/shellscript-Dga4H_Ni.js +1 -0
  131. package/dist/ui/assets/{shellsession-BpE5o7_3.js → shellsession-n3-5CqcF.js} +1 -1
  132. package/dist/ui/assets/{soy-COesmhTY.js → soy-XYBOf_BA.js} +1 -1
  133. package/dist/ui/assets/sql-ET4u5Cpl.js +1 -0
  134. package/dist/ui/assets/{stata-CGQEuSZq.js → stata-CLHcJHO_.js} +1 -1
  135. package/dist/ui/assets/{surrealql-Bz4JbU-9.js → surrealql-BdjQ60T4.js} +1 -1
  136. package/dist/ui/assets/{svelte-1jM2ejXj.js → svelte-B2RPtKJe.js} +1 -1
  137. package/dist/ui/assets/{templ-C-VwELcp.js → templ-CrByPW_W.js} +1 -1
  138. package/dist/ui/assets/{tex-t1rUiQCm.js → tex-DZBkg363.js} +1 -1
  139. package/dist/ui/assets/{ts-tags-Bewoa6pS.js → ts-tags-DJR3Gphb.js} +1 -1
  140. package/dist/ui/assets/tsx-CajzROyy.js +1 -0
  141. package/dist/ui/assets/{twig-DVg-vELl.js → twig-BZjk5Zda.js} +1 -1
  142. package/dist/ui/assets/typescript-B-EWToLj.js +1 -0
  143. package/dist/ui/assets/{vue-wPQ7UlMy.js → vue-D_rHCCr8.js} +1 -1
  144. package/dist/ui/assets/{vue-html-BHtJ8geX.js → vue-html-CC4WlvVt.js} +1 -1
  145. package/dist/ui/assets/{vue-vine-CNSGAWwc.js → vue-vine-BgNMVgpk.js} +1 -1
  146. package/dist/ui/assets/xml-BI86-WbH.js +1 -0
  147. package/dist/ui/assets/{xsl-3_vJ27aE.js → xsl-DXeLZV4J.js} +1 -1
  148. package/dist/ui/assets/yaml-60_PFlWa.js +1 -0
  149. package/dist/workspaces/bootstrap.js +72 -0
  150. package/dist/workspaces/context.js +337 -0
  151. package/dist/workspaces/conversation-test-support.js +86 -0
  152. package/dist/{git-worktrees.js → workspaces/git/git-worktrees.js} +1 -1
  153. package/dist/workspaces/inventory.js +131 -0
  154. package/dist/workspaces/paths.js +55 -0
  155. package/dist/{remote-auth.js → workspaces/relay/auth/remote-auth.js} +2 -2
  156. package/dist/workspaces/relay/result-support.js +111 -0
  157. package/dist/workspaces/relay/tests/test-support.js +203 -0
  158. package/dist/{remote-mcp-connection-pool.js → workspaces/relay/transport/remote-mcp-connection-pool.js} +1 -1
  159. package/dist/{remote-transport.js → workspaces/relay/transport/remote-transport.js} +30 -3
  160. package/dist/workspaces/relay/types.js +1 -0
  161. package/dist/{remote-workspace-relay.js → workspaces/relay/workspace-relay.js} +23 -172
  162. package/dist/workspaces/resources/resource-monitor.js +377 -0
  163. package/dist/workspaces/resources/skills.js +283 -0
  164. package/dist/{review-checkpoints.js → workspaces/review/review-checkpoints.js} +2 -2
  165. package/dist/workspaces/sessions.js +460 -0
  166. package/dist/{workspace-store.js → workspaces/state/workspace-store.js} +2 -2
  167. package/dist/workspaces.js +77 -993
  168. package/docs/chatgpt-coding-workflow.md +7 -14
  169. package/docs/configuration.md +26 -34
  170. package/docs/gotchas.md +0 -30
  171. package/docs/roadmap.md +6 -10
  172. package/docs/security.md +1 -5
  173. package/docs/setup.md +0 -11
  174. package/package.json +6 -9
  175. package/scripts/ci/architecture.mjs +109 -0
  176. package/scripts/ci/verify.mjs +5 -0
  177. package/scripts/debug/accept/bootstrap.mjs +326 -0
  178. package/scripts/debug/accept/harness.mjs +76 -0
  179. package/scripts/debug/accept/support.mjs +432 -0
  180. package/scripts/debug/accept.mjs +43 -806
  181. package/scripts/debug/relay-accept/support.mjs +135 -0
  182. package/scripts/debug/relay-accept.mjs +26 -115
  183. package/scripts/debug/traffic/support.mjs +167 -0
  184. package/scripts/debug/traffic/traffic-audit.mjs +4 -161
  185. package/scripts/{lsp-interop-support.test.mjs → lsp/interop-support.test.mjs} +1 -1
  186. package/scripts/{lsp-interop.mjs → lsp/interop.mjs} +3 -3
  187. package/scripts/release/publish.mjs +12 -9
  188. package/scripts/release/release-gate.test.mjs +3 -1
  189. package/scripts/release/release-version.test.mjs +13 -1
  190. package/scripts/release-parity.mjs +1 -1
  191. package/scripts/release-proof.mjs +15 -0
  192. package/scripts/release-proof.test.mjs +27 -1
  193. package/scripts/release-version.mjs +16 -1
  194. package/scripts/wiki/sync.mjs +21 -10
  195. package/dist/activity/bash-output-store.js +0 -127
  196. package/dist/pi-tools.js +0 -110
  197. package/dist/skills.js +0 -127
  198. package/dist/ui/assets/activity-panel-app-7AckaxIR.js +0 -2
  199. package/dist/ui/assets/angular-html-BkgDQl-G.js +0 -1
  200. package/dist/ui/assets/c-IBzBpxAO.js +0 -1
  201. package/dist/ui/assets/chunk-EyZ2wyi3.js +0 -1
  202. package/dist/ui/assets/cpp-CVHeO-Jk.js +0 -1
  203. package/dist/ui/assets/css-DEfus9J5.js +0 -1
  204. package/dist/ui/assets/glsl-_ooCjaeB.js +0 -1
  205. package/dist/ui/assets/graphql-B9LqP-cY.js +0 -1
  206. package/dist/ui/assets/haml-BU5EoZBn.js +0 -1
  207. package/dist/ui/assets/heavy-payload-Bol1_mcs.js +0 -4
  208. package/dist/ui/assets/html-D-C6YZk4.js +0 -1
  209. package/dist/ui/assets/java-ppHvycj5.js +0 -1
  210. package/dist/ui/assets/javascript-DDTjKLlQ.js +0 -1
  211. package/dist/ui/assets/json-Cw91r8h1.js +0 -1
  212. package/dist/ui/assets/jsx-04X2FT_1.js +0 -1
  213. package/dist/ui/assets/lua-CiyBp52Y.js +0 -1
  214. package/dist/ui/assets/r-DfFga4si.js +0 -1
  215. package/dist/ui/assets/regexp-iC9_nbSI.js +0 -1
  216. package/dist/ui/assets/review-payload-BqJp0c5e.js +0 -1
  217. package/dist/ui/assets/scrollbar-CvE-I-jG.js +0 -287
  218. package/dist/ui/assets/scss-BfZXsAWQ.js +0 -1
  219. package/dist/ui/assets/shellscript-BC2srNaB.js +0 -1
  220. package/dist/ui/assets/sql-Ccv_yeUm.js +0 -1
  221. package/dist/ui/assets/tsx-CTXzrBkN.js +0 -1
  222. package/dist/ui/assets/typescript--ElHoFkw.js +0 -1
  223. package/dist/ui/assets/workspace-app-BLW7p6IZ.js +0 -114
  224. package/dist/ui/assets/workspace-app-BjNqR0en.js +0 -1
  225. package/dist/ui/assets/workspace-app-CmaYU4DW.js +0 -3
  226. package/dist/ui/assets/workspace-app-D2bJ5fjt.css +0 -1
  227. package/dist/ui/assets/workspace-lifecycle-app-BVbI2Ilf.js +0 -1
  228. package/dist/ui/assets/xml-JnmX6vyS.js +0 -1
  229. package/dist/ui/assets/yaml-OAcZkP-w.js +0 -1
  230. package/dist/ui/workspace-app.html +0 -18
  231. package/dist/ui/workspace-lifecycle-app.html +0 -18
  232. package/docs/agent-profile-schema.md +0 -114
  233. package/docs/artifact-exchange.md +0 -65
  234. package/docs/versioning.md +0 -205
  235. package/skills/subagent-delegation/SKILL.md +0 -132
  236. /package/capabilities/{managed-worktrees → workspace/managed-worktrees}/GUIDE.md +0 -0
  237. /package/dist/activity/{process-output-audit.js → runtime/process-output-audit.js} +0 -0
  238. /package/dist/{artifact-error.js → mcp/artifacts/artifact-error.js} +0 -0
  239. /package/dist/{artifact-tools.js → mcp/artifacts/artifact-tools.js} +0 -0
  240. /package/dist/{incoming-artifacts.js → mcp/artifacts/incoming-artifacts.js} +0 -0
  241. /package/dist/{advertised-files.js → mcp/filesystem/advertised-files.js} +0 -0
  242. /package/dist/{apply-patch.js → mcp/filesystem/apply-patch.js} +0 -0
  243. /package/dist/{file-mutations.js → mcp/filesystem/file-mutations.js} +0 -0
  244. /package/dist/{roots.js → mcp/filesystem/roots.js} +0 -0
  245. /package/dist/{oauth → mcp/oauth}/public-url.js +0 -0
  246. /package/dist/{oauth → mcp/oauth}/router.js +0 -0
  247. /package/dist/{operations → mcp/operations}/batch/executor.js +0 -0
  248. /package/dist/{operations → mcp/operations}/batch/scheduler.js +0 -0
  249. /package/dist/{operations → mcp/operations}/batch/types.js +0 -0
  250. /package/dist/{operations → mcp/operations}/bulk-mutation.js +0 -0
  251. /package/dist/{operations → mcp/operations}/bulk-read.js +0 -0
  252. /package/dist/{operations → mcp/operations}/core-operation-executor.js +0 -0
  253. /package/dist/{request-meta.js → mcp/request-meta.js} +0 -0
  254. /package/dist/{mcp-sessions.js → mcp/server/transport/mcp-sessions.js} +0 -0
  255. /package/dist/{server-shutdown.js → mcp/server/transport/server-shutdown.js} +0 -0
  256. /package/dist/{logger.js → runtime/logging/logger.js} +0 -0
  257. /package/dist/{db → runtime/state/db}/schema.js +0 -0
  258. /package/dist/{composite-activity.js → workspaces/composite/composite-activity.js} +0 -0
  259. /package/dist/{composite-workspaces.js → workspaces/composite/composite-workspaces.js} +0 -0
  260. /package/dist/{git.js → workspaces/git/git.js} +0 -0
  261. /package/dist/{workspace-presentation.js → workspaces/presentation/workspace-presentation.js} +0 -0
  262. /package/dist/{workspace-task-reminders.js → workspaces/tasks/workspace-task-reminders.js} +0 -0
  263. /package/dist/{workspace-tasks.js → workspaces/tasks/workspace-tasks.js} +0 -0
  264. /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,242 @@
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 const workspaceTaskUsageInstruction = "Use workspace.tasks proactively for work that spans multiple steps or sessions: create or update Tasks to preserve useful next steps and current state, and read its summary when resuming relevant unfinished work. Do not query it mechanically on every open_workspace call.";
13
+ export async function presentLocalWorkspaceOpen(options, input, contextData) {
14
+ const { config, forgerelayVersion: FORGERELAY_VERSION, workspaces, workspaceTasks, reviewCheckpoints, capabilityRegistry, subagentProviders, hooks, rememberWorkspacePanelState, } = options;
15
+ const { path, workspaceId, mode, baseRef, newWorktree, newWorkspace, context } = input;
16
+ const { conversationScopeId, protectedWorkspaceIds, startedAt, sessionId } = contextData;
17
+ const { workspace, agentsFiles, availableAgentsFiles, hookReports, workspaceReused, bootstrapContextComponents, contextFingerprint, } = await workspaces.openWorkspace({ path, workspaceId, mode, baseRef, newWorktree, newWorkspace, context }, {
18
+ conversationScopeId,
19
+ protectedWorkspaceIds,
20
+ });
21
+ workspaceTasks.initializeWorkspace(workspace.id);
22
+ const requestedContext = context ?? "auto";
23
+ const resourceUpdate = workspaceReused && requestedContext === "auto"
24
+ ? workspaces.claimResourceUpdates(workspace.id, conversationScopeId)
25
+ : undefined;
26
+ if (requestedContext === "full" || (!workspaceReused && requestedContext !== "none")) {
27
+ workspaces.acknowledgeResourceUpdates(workspace.id, conversationScopeId);
28
+ }
29
+ const effectiveBootstrapContextComponents = resourceUpdate
30
+ ? bootstrapContextComponents.filter((component) => !resourceUpdate.coveredComponents.includes(component))
31
+ : bootstrapContextComponents;
32
+ const effectiveIncludeBootstrapContext = effectiveBootstrapContextComponents.length > 0 || Boolean(resourceUpdate);
33
+ const knownWorktrees = await workspaces.listKnownWorktrees(workspace);
34
+ const staleWorkspaces = await workspaces.listStaleWorkspaces(workspace);
35
+ const capabilityFingerprint = buildCapabilityFingerprint(config, FORGERELAY_VERSION, {
36
+ artifactDownloadSupported: isArtifactDownloadSupportedPlatform(),
37
+ });
38
+ if (config.widgets === "changes") {
39
+ await reviewCheckpoints.initializeWorkspace({
40
+ workspaceId: workspace.id,
41
+ root: workspace.root,
42
+ });
43
+ }
44
+ const cardSkills = workspace.skills
45
+ .filter((skill) => !skill.disableModelInvocation)
46
+ .map((skill) => ({
47
+ name: skill.name,
48
+ description: skill.description,
49
+ }));
50
+ const capabilityGuides = workspace.capabilityGuides.map((guide) => ({
51
+ name: guide.name,
52
+ description: guide.description,
53
+ whenToRead: guide.whenToRead,
54
+ path: formatPathForPrompt(guide.filePath),
55
+ }));
56
+ const capabilityCatalog = capabilityRegistry.catalog(capabilityContextFor(workspace));
57
+ const cardAgentProviders = config.subagents ? subagentProviders : [];
58
+ const cardAgents = workspace.agentProfiles.map((profile) => {
59
+ const summary = summarizeSubagentProfile(profile);
60
+ const availability = cardAgentProviders.find((provider) => provider.name === summary.provider);
61
+ return {
62
+ ...summary,
63
+ providerAvailable: availability?.available,
64
+ providerUnavailableReason: availability?.reason,
65
+ };
66
+ });
67
+ const cardAgentsFiles = agentsFiles.map((file) => ({
68
+ path: formatAgentsPath(file.path, workspace.root),
69
+ content: file.content,
70
+ }));
71
+ const cardAvailableAgentsFiles = availableAgentsFiles.map((file) => ({
72
+ path: formatAgentsPath(file.path, workspace.root),
73
+ }));
74
+ const bootstrapComponents = new Set(effectiveBootstrapContextComponents);
75
+ const visibleSkills = bootstrapComponents.has("skills") ? cardSkills : [];
76
+ const visibleSkillDiagnostics = bootstrapComponents.has("skillDiagnostics")
77
+ ? redactSkillDiagnosticPaths(workspace.skillDiagnostics)
78
+ : [];
79
+ const visibleCapabilityGuides = bootstrapComponents.has("capabilityGuides") ? capabilityGuides : [];
80
+ const visibleAgentProviders = bootstrapComponents.has("agentProfiles") ? cardAgentProviders : [];
81
+ const visibleAgents = bootstrapComponents.has("agentProfiles") ? cardAgents : [];
82
+ const loadedAgentsFiles = bootstrapComponents.has("agentsFiles") ? cardAgentsFiles : [];
83
+ const availableAgentsFileOutputs = bootstrapComponents.has("availableAgentsFiles")
84
+ ? cardAvailableAgentsFiles
85
+ : [];
86
+ const workspaceContextInstruction = "For later open_workspace calls, context=\"auto\" avoids repeating unchanged bootstrap context; use context=\"none\" when only the workspace handle/metadata is needed, or context=\"full\" to force a refresh.";
87
+ const workspaceManagementInstruction = [
88
+ "Use open_workspace(action=\"list\") for lightweight Workspace inventory. Use action=\"inspect\" with one known workspaceId for bounded read-only metadata without opening/resuming it. Explicitly open a Workspace before executing or mutating against it, and ask the user before close_workspace cleanup.",
89
+ capabilityCatalog.some((entry) => entry.name === "workspace.tasks")
90
+ ? workspaceTaskUsageInstruction
91
+ : undefined,
92
+ ].filter(Boolean).join(" ");
93
+ const cardInstruction = config.skillsEnabled
94
+ ? `Use this workspaceId in all subsequent tool calls for this project. Follow loaded agentsFiles instructions. Read an availableAgentsFiles path before working under it. When a task matches an available skill, load it with read(path=\"skills://<name>\") before proceeding. When a task matches a capability guide, read its advertised path before proceeding. ${workspaceContextInstruction} ${workspaceManagementInstruction}`
95
+ : `Use this workspaceId in all subsequent tool calls for this project. Follow loaded agentsFiles instructions. Read an availableAgentsFiles path before working under it. When a task matches a capability guide, read its advertised path before proceeding. ${workspaceContextInstruction} ${workspaceManagementInstruction}`;
96
+ const instruction = workspaceReused
97
+ ? effectiveIncludeBootstrapContext
98
+ ? [
99
+ `Workspace already exists as ${workspace.id} for this directory.`,
100
+ "Reuse this workspaceId for subsequent tool calls.",
101
+ effectiveBootstrapContextComponents.length > 0
102
+ ? `Project bootstrap context components included in this response: ${effectiveBootstrapContextComponents.join(", ")}. Components not listed are unchanged and are not repeated.`
103
+ : "Only Workspace context deltas are included in this response; unchanged bootstrap context is not repeated.",
104
+ workspaceContextInstruction,
105
+ workspaceManagementInstruction,
106
+ ].join("\n\n")
107
+ : [
108
+ `Workspace already open as ${workspace.id}.`,
109
+ "Reuse this workspaceId for subsequent tool calls. This is the same directory previously opened in this conversation.",
110
+ "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.",
111
+ workspaceContextInstruction,
112
+ workspaceManagementInstruction,
113
+ ].join("\n\n")
114
+ : workspace.mode === "worktree"
115
+ ? `Use this workspaceId for subsequent tool calls. Follow the project instructions, nested instruction files, skills, agent profiles, and diagnostics returned for this isolated worktree. ${workspaceManagementInstruction}`
116
+ : cardInstruction;
117
+ const resultContent = [
118
+ {
119
+ type: "text",
120
+ text: [
121
+ workspaceReused
122
+ ? `Workspace already open as ${workspace.id}.`
123
+ : workspace.mode === "worktree"
124
+ ? `Opened isolated worktree workspace ${workspace.id}.`
125
+ : `Opened workspace ${workspace.id}.`,
126
+ `Root: ${workspace.root}`,
127
+ `Mode: ${workspace.mode}`,
128
+ loadedAgentsFiles.length > 0
129
+ ? `Loaded project instructions: ${loadedAgentsFiles.map((file) => file.path).join(", ")}`
130
+ : undefined,
131
+ availableAgentsFileOutputs.length > 0
132
+ ? `Available nested instructions: ${availableAgentsFileOutputs.map((file) => file.path).join(", ")}`
133
+ : undefined,
134
+ visibleSkills.length > 0
135
+ ? `Available skills: ${visibleSkills.map((skill) => skill.name).join(", ")}`
136
+ : undefined,
137
+ capabilityCatalog.length > 0
138
+ ? `Optional capabilities: ${capabilityCatalog.map((entry) => entry.name).join(", ")}`
139
+ : undefined,
140
+ visibleCapabilityGuides.length > 0
141
+ ? `Capability guides: ${visibleCapabilityGuides.map((guide) => guide.name).join(", ")}`
142
+ : undefined,
143
+ visibleAgentProviders.some((provider) => provider.available)
144
+ ? `Available subagent providers: ${visibleAgentProviders.filter((provider) => provider.available).map((provider) => provider.name).join(", ")}`
145
+ : undefined,
146
+ visibleAgentProviders.some((provider) => !provider.available)
147
+ ? `Unavailable subagent providers: ${visibleAgentProviders.filter((provider) => !provider.available).map(formatUnavailableSubagentProvider).join(", ")}`
148
+ : undefined,
149
+ visibleAgents.length > 0
150
+ ? `Available subagent profiles: ${visibleAgents.map(formatAvailableSubagentProfile).join(", ")}`
151
+ : undefined,
152
+ knownWorktrees.length > 0
153
+ ? `Known worktrees: ${knownWorktrees.map((worktree) => `${worktree.path} [${worktree.workspaceId}]${worktree.branch ? ` branch=${worktree.branch}` : ""}${worktree.targetBranch ? ` target=${worktree.targetBranch}` : ""}${worktree.current ? " (current)" : ""}`).join(", ")}`
154
+ : undefined,
155
+ staleWorkspaces.length > 0
156
+ ? `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.`
157
+ : undefined,
158
+ `ForgeRelay ${capabilityFingerprint.version} capabilities: ${capabilityFingerprint.capabilities.join(", ")}`,
159
+ instruction,
160
+ resourceUpdate?.text,
161
+ ].filter(Boolean).join("\n"),
162
+ },
163
+ ];
164
+ logToolCall(config, {
165
+ tool: "open_workspace",
166
+ ...workspaceLogContext(workspace, sessionId),
167
+ path: workspace.root,
168
+ success: true,
169
+ durationMs: Math.round(performance.now() - startedAt),
170
+ });
171
+ const workspaceCard = {
172
+ workspaceId: workspace.id,
173
+ kind: "workspace",
174
+ root: workspace.root,
175
+ path: workspace.root,
176
+ mode: workspace.mode,
177
+ workspaceReused,
178
+ includeBootstrapContext: effectiveIncludeBootstrapContext,
179
+ sourceRoot: workspace.sourceRoot,
180
+ worktree: workspace.worktree,
181
+ worktrees: knownWorktrees,
182
+ staleWorkspaces,
183
+ capabilityFingerprint,
184
+ contextFingerprint,
185
+ capabilityCatalog,
186
+ agentsFiles: cardAgentsFiles,
187
+ availableAgentsFiles: cardAvailableAgentsFiles,
188
+ skills: cardSkills,
189
+ agentProviders: cardAgentProviders,
190
+ agents: cardAgents,
191
+ instruction: cardInstruction,
192
+ summary: {
193
+ mode: workspace.mode,
194
+ agentsFiles: cardAgentsFiles.length,
195
+ availableAgentsFiles: cardAvailableAgentsFiles.length,
196
+ skills: cardSkills.length,
197
+ capabilities: capabilityCatalog.length,
198
+ agentProviders: cardAgentProviders.length,
199
+ agents: cardAgents.length,
200
+ },
201
+ };
202
+ const response = hooks.decorateResult(workspace.id, attachHookReports({
203
+ content: resultContent,
204
+ _meta: {
205
+ tool: "open_workspace",
206
+ card: effectiveIncludeBootstrapContext
207
+ ? workspaceCard
208
+ : compactWorkspacePresentation(workspaceCard),
209
+ },
210
+ structuredContent: {
211
+ action: "open",
212
+ workspaceId: workspace.id,
213
+ kind: "workspace",
214
+ root: workspace.root,
215
+ mode: workspace.mode,
216
+ sourceRoot: workspace.sourceRoot,
217
+ worktree: workspace.worktree,
218
+ worktrees: knownWorktrees,
219
+ staleWorkspaces,
220
+ capabilityFingerprint,
221
+ contextFingerprint,
222
+ capabilityCatalog,
223
+ ...(bootstrapComponents.has("capabilityGuides")
224
+ ? { capabilityGuides: visibleCapabilityGuides }
225
+ : {}),
226
+ ...(bootstrapComponents.has("agentsFiles") ? { agentsFiles: loadedAgentsFiles } : {}),
227
+ ...(bootstrapComponents.has("availableAgentsFiles")
228
+ ? { availableAgentsFiles: availableAgentsFileOutputs }
229
+ : {}),
230
+ ...(bootstrapComponents.has("skills") ? { skills: visibleSkills } : {}),
231
+ ...(bootstrapComponents.has("agentProfiles")
232
+ ? { agentProviders: visibleAgentProviders, agents: visibleAgents }
233
+ : {}),
234
+ ...(bootstrapComponents.has("skillDiagnostics")
235
+ ? { skillDiagnostics: visibleSkillDiagnostics }
236
+ : {}),
237
+ instruction,
238
+ },
239
+ }, hookReports));
240
+ rememberWorkspacePanelState(workspace.id, response);
241
+ return response;
242
+ }