@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,143 @@
1
+ import { access } from "node:fs/promises";
2
+ import { fileURLToPath } from "node:url";
3
+ import { RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
4
+ import { logEvent, transportSessionIdPrefix } from "../../runtime/logging/logger.js";
5
+ import { MCP_APP_RESOURCE_TEMPLATE_REVISION, readActivityPanelAppManifestEntry, resolveActivityPanelAppIdentity, } from "./mcp-app-template.js";
6
+ const cachedIdentities = new Map();
7
+ /**
8
+ * Build the complete Host-facing Activity Panel resource contract. Keeping URI
9
+ * identity, CSP, bootstrap HTML, and tool metadata in one module prevents a UI
10
+ * revision from changing on one surface while another surface keeps stale Host
11
+ * cache metadata.
12
+ */
13
+ export function createActivityPanelApp(config, fallbackRevision) {
14
+ const identity = currentIdentity(config, fallbackRevision);
15
+ const domain = new URL(config.publicBaseUrl).origin;
16
+ const csp = activityPanelCsp(config);
17
+ return {
18
+ uri: identity.uri,
19
+ resourceMetadata: {
20
+ description: "ForgeRelay unified Workspace and Activity UI for one Host Turn.",
21
+ _meta: { ui: { domain, csp } },
22
+ },
23
+ toolMeta: config.widgets === "off"
24
+ ? { _meta: {} }
25
+ : {
26
+ _meta: {
27
+ ui: {
28
+ resourceUri: identity.uri,
29
+ visibility: ["model", "app"],
30
+ },
31
+ },
32
+ },
33
+ readResource: (requestedUri, transportSessionId) => readActivityPanelAppResource(config, fallbackRevision, requestedUri, transportSessionId),
34
+ };
35
+ }
36
+ export function activityPanelAssetDirectory() {
37
+ return fileURLToPath(new URL("../../../dist/ui", import.meta.url));
38
+ }
39
+ export function setActivityPanelAssetHeaders(res) {
40
+ res.setHeader("Access-Control-Allow-Origin", "*");
41
+ res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
42
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Range");
43
+ res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
44
+ }
45
+ function appResourceContractRevision(config) {
46
+ return [
47
+ MCP_APP_RESOURCE_TEMPLATE_REVISION,
48
+ `publicBaseUrls=${JSON.stringify(config.publicBaseUrls)}`,
49
+ ].join("\0");
50
+ }
51
+ function currentIdentity(config, fallbackRevision) {
52
+ const contractRevision = appResourceContractRevision(config);
53
+ const cacheKey = `${fallbackRevision}\0${contractRevision}`;
54
+ let identity = cachedIdentities.get(cacheKey);
55
+ if (!identity) {
56
+ identity = resolveActivityPanelAppIdentity({
57
+ manifestUrl: uiManifestUrl(),
58
+ buildDirectoryUrl: uiBuildDirectoryUrl(),
59
+ fallbackRevision,
60
+ resourceTemplateRevision: contractRevision,
61
+ });
62
+ cachedIdentities.set(cacheKey, identity);
63
+ }
64
+ return identity;
65
+ }
66
+ function activityPanelCsp(config) {
67
+ return {
68
+ resourceDomains: [...config.publicBaseUrls],
69
+ connectDomains: [...config.publicBaseUrls],
70
+ };
71
+ }
72
+ async function readActivityPanelAppResource(config, fallbackRevision, requestedUri, transportSessionId) {
73
+ const identity = currentIdentity(config, fallbackRevision);
74
+ const entry = readActivityPanelAppManifestEntry(uiManifestUrl());
75
+ const domain = new URL(config.publicBaseUrl).origin;
76
+ const csp = activityPanelCsp(config);
77
+ try {
78
+ await assertMcpAppAssets(entry);
79
+ const result = {
80
+ contents: [{
81
+ uri: requestedUri,
82
+ mimeType: RESOURCE_MIME_TYPE,
83
+ text: activityPanelAppHtml(config, entry),
84
+ _meta: {
85
+ ui: { domain, csp },
86
+ // MCP Apps defines these values under `_meta.ui`. Inspector 2.3.0
87
+ // still reads them directly from the content-item metadata, so mirror
88
+ // them until that installed-host compatibility gap is retired.
89
+ domain,
90
+ csp,
91
+ },
92
+ }],
93
+ };
94
+ logEvent(config.logging, "debug", "mcp_app_template_read", {
95
+ requestedUri,
96
+ currentUri: identity.uri,
97
+ transportSessionIdPrefix: transportSessionIdPrefix(transportSessionId),
98
+ });
99
+ return result;
100
+ }
101
+ catch (error) {
102
+ logEvent(config.logging, "warn", "mcp_app_template_read_failed", {
103
+ requestedUri,
104
+ currentUri: identity.uri,
105
+ error: error instanceof Error ? error.message : String(error),
106
+ transportSessionIdPrefix: transportSessionIdPrefix(transportSessionId),
107
+ });
108
+ throw error;
109
+ }
110
+ }
111
+ function activityPanelAppHtml(config, entry) {
112
+ const baseUrl = `${config.publicBaseUrl.replace(/\/+$/, "")}/mcp-app-assets`;
113
+ const assetUrl = (assetPath) => `${baseUrl}/${assetPath.replace(/^\/+/, "")}`;
114
+ const stylesheets = (entry.css ?? [])
115
+ .map((stylesheet) => ` <link rel="stylesheet" crossorigin href="${assetUrl(stylesheet)}" />`)
116
+ .join("\n");
117
+ return `<!doctype html>
118
+ <html lang="en">
119
+ <head>
120
+ <meta charset="UTF-8" />
121
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
122
+ <title>ForgeRelay Activity Panel</title>
123
+ <script type="module" crossorigin src="${assetUrl(entry.file)}"></script>
124
+ ${stylesheets}
125
+ </head>
126
+ <body>
127
+ <main id="app" class="shell">
128
+ <section class="empty">Waiting for Activity Panel state.</section>
129
+ </main>
130
+ </body>
131
+ </html>`;
132
+ }
133
+ async function assertMcpAppAssets(entry) {
134
+ const candidates = [entry.file, ...(entry.css ?? [])].map((assetPath) => new URL(`../../../dist/ui/${assetPath}`, import.meta.url));
135
+ for (const candidate of candidates)
136
+ await access(candidate);
137
+ }
138
+ function uiManifestUrl() {
139
+ return new URL("../../../dist/ui/.vite/manifest.json", import.meta.url);
140
+ }
141
+ function uiBuildDirectoryUrl() {
142
+ return new URL("../../../dist/ui/", import.meta.url);
143
+ }
@@ -1,26 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFileSync } from "node:fs";
3
- export const WORKSPACE_APP_MANIFEST_ENTRY = "workspace-app.html";
4
- export const WORKSPACE_LIFECYCLE_APP_MANIFEST_ENTRY = "workspace-lifecycle-app.html";
5
3
  export const ACTIVITY_PANEL_APP_MANIFEST_ENTRY = "activity-panel-app.html";
6
4
  // Host caches are keyed by the ui:// resource URI, so the revision must cover
7
5
  // both bundle assets and the HTML/resource contract that serves them. Bump this
8
- // when the MCP App HTML shell or resource-to-entry mapping changes.
9
- export const MCP_APP_RESOURCE_TEMPLATE_REVISION = "4";
10
- // Historical mixed-widget resource. Keep serving it so existing ChatGPT Web
11
- // conversations can still render cards created before the UI split.
12
- export const WORKSPACE_APP_LEGACY_URI = "ui://forgerelay/workspace-app.html";
13
- export const WORKSPACE_APP_URI_TEMPLATE = "ui://forgerelay/workspace-app-{revision}.html";
14
- export const WORKSPACE_LIFECYCLE_APP_LEGACY_URI = "ui://forgerelay/workspace-lifecycle-app.html";
15
- export const WORKSPACE_LIFECYCLE_APP_URI_TEMPLATE = "ui://forgerelay/workspace-lifecycle-app-{revision}.html";
16
- export const ACTIVITY_PANEL_APP_LEGACY_URI = "ui://forgerelay/activity-panel-app.html";
17
- export const ACTIVITY_PANEL_APP_URI_TEMPLATE = "ui://forgerelay/activity-panel-app-{revision}.html";
18
- export function workspaceAppUriForRevision(revision) {
19
- return `ui://forgerelay/workspace-app-${encodeURIComponent(revision)}.html`;
20
- }
21
- export function workspaceLifecycleAppUriForRevision(revision) {
22
- return `ui://forgerelay/workspace-lifecycle-app-${encodeURIComponent(revision)}.html`;
23
- }
6
+ // whenever either side changes so a Host never reuses stale UI bytes.
7
+ export const MCP_APP_RESOURCE_TEMPLATE_REVISION = "5";
24
8
  export function activityPanelAppUriForRevision(revision) {
25
9
  return `ui://forgerelay/activity-panel-app-${encodeURIComponent(revision)}.html`;
26
10
  }
@@ -64,12 +48,6 @@ export function readMcpAppManifestEntry(manifestUrl, manifestEntry) {
64
48
  ...(entry.isEntry !== undefined ? { isEntry: entry.isEntry } : {}),
65
49
  };
66
50
  }
67
- export function readWorkspaceAppManifestEntry(manifestUrl) {
68
- return readMcpAppManifestEntry(manifestUrl, WORKSPACE_APP_MANIFEST_ENTRY);
69
- }
70
- export function readWorkspaceLifecycleAppManifestEntry(manifestUrl) {
71
- return readMcpAppManifestEntry(manifestUrl, WORKSPACE_LIFECYCLE_APP_MANIFEST_ENTRY);
72
- }
73
51
  export function readActivityPanelAppManifestEntry(manifestUrl) {
74
52
  return readMcpAppManifestEntry(manifestUrl, ACTIVITY_PANEL_APP_MANIFEST_ENTRY);
75
53
  }
@@ -118,20 +96,6 @@ function resolveAppIdentity(options) {
118
96
  };
119
97
  }
120
98
  }
121
- export function resolveWorkspaceAppIdentity(options) {
122
- return resolveAppIdentity({
123
- ...options,
124
- manifestEntry: WORKSPACE_APP_MANIFEST_ENTRY,
125
- uriForRevision: workspaceAppUriForRevision,
126
- });
127
- }
128
- export function resolveWorkspaceLifecycleAppIdentity(options) {
129
- return resolveAppIdentity({
130
- ...options,
131
- manifestEntry: WORKSPACE_LIFECYCLE_APP_MANIFEST_ENTRY,
132
- uriForRevision: workspaceLifecycleAppUriForRevision,
133
- });
134
- }
135
99
  export function resolveActivityPanelAppIdentity(options) {
136
100
  return resolveAppIdentity({
137
101
  ...options,
@@ -11,23 +11,35 @@ const defaultProcessTreeRuntime = {
11
11
  return !result.error && result.status === 0;
12
12
  },
13
13
  };
14
- const LOGIN_SHELLS = new Set(["bash", "ksh", "zsh"]);
15
- const POSIX_SHELLS = new Set(["ash", "dash", "sh"]);
14
+ const POSIX_SHELLS = new Set(["ash", "dash", "ksh", "sh"]);
16
15
  export function resolveShellCommand(command, platform = process.platform, environment = process.env) {
17
16
  if (platform === "win32") {
18
17
  return {
19
18
  executable: environment.ComSpec ?? environment.COMSPEC ?? "cmd.exe",
20
- args: ["/d", "/s", "/c", command],
19
+ // Match Node's native `spawn(command, { shell: cmd.exe })` quoting.
20
+ // cmd.exe /S applies special quote stripping, so the whole command must
21
+ // be wrapped even when the executable inside it is already quoted.
22
+ args: ["/d", "/s", "/c", `"${command}"`],
21
23
  windowsVerbatimArguments: true,
22
24
  };
23
25
  }
24
- const configuredShell = environment.SHELL;
25
- const shellName = configuredShell ? basename(configuredShell) : "";
26
- if (configuredShell && LOGIN_SHELLS.has(shellName)) {
27
- return { executable: configuredShell, args: ["-lc", command] };
26
+ // Agent and Hook commands must not source the user's interactive/login shell
27
+ // configuration. ForgeRelay already inherits PATH and other environment from
28
+ // the process that launched the server; re-running zsh/bash as a login shell
29
+ // can inject prompts, banners, aliases, plugins, or other user-only behavior.
30
+ const configuredShell = environment.FORGERELAY_COMMAND_SHELL?.trim();
31
+ const executable = configuredShell || (platform === "linux" || platform === "darwin"
32
+ ? "/bin/bash"
33
+ : "/bin/sh");
34
+ const shellName = basename(executable);
35
+ if (shellName === "bash") {
36
+ return { executable, args: ["--noprofile", "--norc", "-c", command] };
28
37
  }
29
- if (configuredShell && POSIX_SHELLS.has(shellName)) {
30
- return { executable: configuredShell, args: ["-c", command] };
38
+ if (shellName === "zsh") {
39
+ return { executable, args: ["-f", "-c", command] };
40
+ }
41
+ if (POSIX_SHELLS.has(shellName)) {
42
+ return { executable, args: ["-c", command] };
31
43
  }
32
44
  return { executable: "/bin/sh", args: ["-c", command] };
33
45
  }
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { resolveShellCommand, terminateProcessTree } from "./process-platform.js";
3
3
  const DEFAULT_EXEC_YIELD_MS = 10_000;
4
4
  const DEFAULT_INTERACTIVE_YIELD_MS = 250;
5
- const DEFAULT_POLL_YIELD_MS = 5_000;
5
+ export const DEFAULT_POLL_YIELD_MS = 60_000;
6
6
  const MAX_START_YIELD_MS = 300_000;
7
7
  const MAX_COMMAND_YIELD_MS = 300_000;
8
8
  const MAX_POLL_YIELD_MS = 300_000;
@@ -437,7 +437,7 @@ export class ProcessManager {
437
437
  startPipe(processEntry, input) {
438
438
  const shell = resolveShellCommand(input.command);
439
439
  const detached = process.platform !== "win32";
440
- const child = spawn(input.command, {
440
+ const child = spawn(shell.executable, shell.args, {
441
441
  cwd: input.cwd,
442
442
  env: processEnvironment({
443
443
  workspaceId: input.workspaceId,
@@ -446,8 +446,8 @@ export class ProcessManager {
446
446
  }),
447
447
  stdio: "pipe",
448
448
  windowsHide: true,
449
+ windowsVerbatimArguments: shell.windowsVerbatimArguments,
449
450
  detached,
450
- shell: shell.executable,
451
451
  });
452
452
  processEntry.process = {
453
453
  write: (data) => child.stdin.write(data),
@@ -0,0 +1,257 @@
1
+ const PROCESS_RESPONSE_OUTPUT_LINES = 10;
2
+ export function attachCompletedProcessNotices(processSessions, workspaceId, result, onCompleted) {
3
+ if (result instanceof Error) {
4
+ const completed = processSessions.takeCompleted(workspaceId);
5
+ for (const snapshot of completed)
6
+ onCompleted?.(snapshot);
7
+ if (completed.length > 0) {
8
+ result.message = [
9
+ result.message,
10
+ ...completed.map((snapshot) => completedProcessResult(snapshot)),
11
+ ].join("\n\n");
12
+ }
13
+ return result;
14
+ }
15
+ if (typeof result !== "object" || result === null)
16
+ return result;
17
+ const content = result.content;
18
+ if (!Array.isArray(content))
19
+ return result;
20
+ const structured = result.structuredContent;
21
+ const currentProcessId = structured?.running === true
22
+ ? typeof structured.processId === "number"
23
+ ? structured.processId
24
+ : typeof structured.sessionId === "number"
25
+ ? structured.sessionId
26
+ : undefined
27
+ : undefined;
28
+ const completed = processSessions.takeCompleted(workspaceId, undefined, currentProcessId);
29
+ for (const snapshot of completed)
30
+ onCompleted?.(snapshot);
31
+ if (completed.length === 0)
32
+ return result;
33
+ return {
34
+ ...result,
35
+ content: [
36
+ ...content,
37
+ ...completed.map((snapshot) => textBlock(completedProcessResult(snapshot))),
38
+ ],
39
+ };
40
+ }
41
+ export function processToolResponse(tool, workspaceId, snapshot, summary) {
42
+ const compact = compactProcessOutput(snapshot.output);
43
+ const result = processResult(snapshot);
44
+ const content = [textBlock(result)];
45
+ const outputSummary = textSummary(compact.output ? [textBlock(compact.output)] : []);
46
+ return {
47
+ content,
48
+ _meta: {
49
+ tool,
50
+ card: {
51
+ workspaceId,
52
+ summary: { ...summary, ...outputSummary },
53
+ payload: { content },
54
+ },
55
+ },
56
+ structuredContent: {
57
+ result,
58
+ processId: snapshot.processId,
59
+ sessionId: snapshot.sessionId,
60
+ outputId: snapshot.outputId,
61
+ running: snapshot.running,
62
+ exitCode: snapshot.exitCode,
63
+ signal: snapshot.signal,
64
+ timedOut: snapshot.timedOut,
65
+ wallTimeMs: snapshot.wallTimeMs,
66
+ outputTruncated: snapshot.outputTruncated || compact.truncated,
67
+ },
68
+ };
69
+ }
70
+ export function durableOutputResponse(tool, workspaceId, record) {
71
+ const result = durableOutputResult(record);
72
+ const content = [textBlock(result)];
73
+ const finishedAt = record.finishedAt ? Date.parse(record.finishedAt) : Date.now();
74
+ const startedAt = Date.parse(record.startedAt);
75
+ return {
76
+ content,
77
+ _meta: {
78
+ tool,
79
+ card: {
80
+ workspaceId,
81
+ summary: textSummary(record.output ? [textBlock(record.output)] : []),
82
+ payload: { content },
83
+ },
84
+ },
85
+ structuredContent: {
86
+ result,
87
+ processId: record.processId,
88
+ sessionId: record.processId,
89
+ outputId: record.outputId,
90
+ running: record.status === "running",
91
+ exitCode: record.exitCode,
92
+ signal: record.signal,
93
+ timedOut: record.timedOut,
94
+ wallTimeMs: Math.max(0, Number.isFinite(finishedAt - startedAt) ? finishedAt - startedAt : 0),
95
+ outputTruncated: false,
96
+ },
97
+ };
98
+ }
99
+ export function readWorkspaceBashOutput(store, workspaceId, outputId) {
100
+ const record = store.read(outputId);
101
+ if (!record)
102
+ throw new Error(`Unknown Bash output: ${outputId}`);
103
+ if (record.workspaceId !== workspaceId) {
104
+ throw new Error(`Bash output ${outputId} does not belong to workspace ${workspaceId}.`);
105
+ }
106
+ return record;
107
+ }
108
+ export function markReturnedOutput(store, result) {
109
+ if (typeof result !== "object" || result === null)
110
+ return;
111
+ const structured = result.structuredContent;
112
+ if (typeof structured !== "object" || structured === null)
113
+ return;
114
+ const record = structured;
115
+ if (record.running === true && typeof record.outputId === "string") {
116
+ store.markReturned(record.outputId);
117
+ }
118
+ }
119
+ export function recordBashCompletion(lifecycle, store, outputId) {
120
+ if (!outputId)
121
+ return;
122
+ const completion = store.claimCompletion(outputId);
123
+ if (!completion)
124
+ return;
125
+ lifecycle.recordLinked({
126
+ sourceActivityId: completion.activityId,
127
+ tool: "bash_result",
128
+ request: {
129
+ processId: completion.processId,
130
+ outputId: completion.outputId,
131
+ },
132
+ result: {
133
+ processId: completion.processId,
134
+ outputId: completion.outputId,
135
+ exitCode: completion.exitCode,
136
+ signal: completion.signal,
137
+ timedOut: completion.timedOut,
138
+ },
139
+ outcome: completion.status === "failed"
140
+ ? { type: "failed", error: bashCompletionError(completion) }
141
+ : { type: "succeeded" },
142
+ });
143
+ }
144
+ export function processActivityOutcome(result) {
145
+ if (toolResultIsError(result))
146
+ return { type: "failed", error: activityFailureMessage(result) };
147
+ if (typeof result !== "object" || result === null)
148
+ return { type: "succeeded" };
149
+ const structured = result.structuredContent;
150
+ if (typeof structured !== "object" || structured === null)
151
+ return { type: "succeeded" };
152
+ const process = structured;
153
+ if (process.running === true)
154
+ return { type: "returned" };
155
+ if (process.timedOut === true ||
156
+ typeof process.signal === "string" ||
157
+ (typeof process.exitCode === "number" && process.exitCode !== 0)) {
158
+ return { type: "failed", error: activityFailureMessage(result) };
159
+ }
160
+ return { type: "succeeded" };
161
+ }
162
+ export function toolResultIsError(result) {
163
+ return typeof result === "object" && result !== null && result.isError === true;
164
+ }
165
+ function compactProcessOutput(output) {
166
+ if (!output)
167
+ return { output: "", truncated: false };
168
+ const trailingNewline = output.endsWith("\n");
169
+ const body = trailingNewline ? output.slice(0, -1) : output;
170
+ const lines = body.split("\n");
171
+ if (lines.length <= PROCESS_RESPONSE_OUTPUT_LINES)
172
+ return { output, truncated: false };
173
+ const compact = lines.slice(-PROCESS_RESPONSE_OUTPUT_LINES).join("\n");
174
+ return {
175
+ output: trailingNewline ? `${compact}\n` : compact,
176
+ truncated: true,
177
+ };
178
+ }
179
+ function processResult(snapshot) {
180
+ const status = snapshot.running
181
+ ? `Process running with process ID ${snapshot.processId}.`
182
+ : snapshot.timedOut
183
+ ? "Process timed out and was terminated."
184
+ : snapshot.signal
185
+ ? `Process exited after signal ${snapshot.signal}.`
186
+ : `Process exited with code ${snapshot.exitCode ?? "unknown"}.`;
187
+ const compact = compactProcessOutput(snapshot.output).output.replace(/\n$/, "");
188
+ return [compact, status, outputIdNotice(snapshot.outputId)].filter(Boolean).join("\n");
189
+ }
190
+ function completedProcessResult(snapshot) {
191
+ const status = snapshot.timedOut
192
+ ? `Background process ${snapshot.processId} timed out and was terminated.`
193
+ : snapshot.signal
194
+ ? `Background process ${snapshot.processId} exited after signal ${snapshot.signal}.`
195
+ : `Background process ${snapshot.processId} exited with code ${snapshot.exitCode ?? "unknown"}.`;
196
+ const command = `Command: ${snapshot.command}`;
197
+ const output = compactProcessOutput(snapshot.output).output.replace(/\n$/, "");
198
+ return [status, command, output, outputIdNotice(snapshot.outputId)].filter(Boolean).join("\n");
199
+ }
200
+ function outputIdNotice(outputId) {
201
+ return outputId ? `Full output ID: ${outputId}.` : "";
202
+ }
203
+ function durableOutputResult(record) {
204
+ const status = record.status === "running"
205
+ ? `Process ${record.processId} is still running.`
206
+ : record.timedOut
207
+ ? `Process ${record.processId} timed out and was terminated.`
208
+ : record.signal
209
+ ? `Process ${record.processId} exited after signal ${record.signal}.`
210
+ : `Process ${record.processId} exited with code ${record.exitCode ?? "unknown"}.`;
211
+ return [record.output.replace(/\n$/, ""), status, `Full output ID: ${record.outputId}.`]
212
+ .filter(Boolean)
213
+ .join("\n");
214
+ }
215
+ function bashCompletionError(record) {
216
+ if (record.error)
217
+ return record.error;
218
+ if (record.timedOut)
219
+ return `Background process ${record.processId} timed out.`;
220
+ if (record.signal)
221
+ return `Background process ${record.processId} exited after signal ${record.signal}.`;
222
+ return `Background process ${record.processId} exited with code ${record.exitCode ?? "unknown"}.`;
223
+ }
224
+ function activityFailureMessage(result) {
225
+ if (typeof result !== "object" || result === null)
226
+ return "Tool returned a failed result.";
227
+ const record = result;
228
+ if (Array.isArray(record.content)) {
229
+ const text = record.content
230
+ .map((entry) => {
231
+ if (typeof entry !== "object" || entry === null)
232
+ return "";
233
+ const value = entry.text;
234
+ return typeof value === "string" ? value : "";
235
+ })
236
+ .filter(Boolean)
237
+ .join("\n");
238
+ if (text)
239
+ return text;
240
+ }
241
+ if (typeof record.structuredContent === "object" && record.structuredContent !== null) {
242
+ const value = record.structuredContent.result;
243
+ if (typeof value === "string" && value)
244
+ return value;
245
+ }
246
+ return "Tool returned a failed result.";
247
+ }
248
+ function textBlock(text) {
249
+ return { type: "text", text };
250
+ }
251
+ function textSummary(content) {
252
+ const text = content.map((item) => item.text).join("\n");
253
+ return {
254
+ lines: text.length === 0 ? 0 : text.split("\n").length,
255
+ characters: text.length,
256
+ };
257
+ }