@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
@@ -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,
@@ -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
+ }
@@ -0,0 +1,346 @@
1
+ import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
2
+ import * as z from "zod/v4";
3
+ import { runToolWithHooks } from "../hooks/hooks.js";
4
+ import { commandPreview, logEvent, workspaceLogLabel } from "../../runtime/logging/logger.js";
5
+ import { resolveProcessId } from "./process-sessions.js";
6
+ import { buildShellMutationPolicy, toolNames } from "../server-instructions.js";
7
+ import { durableOutputResponse, processToolResponse, readWorkspaceBashOutput, recordBashCompletion, toolResultIsError, } from "./runtime.js";
8
+ const SHELL_TOOL_ANNOTATIONS = {
9
+ readOnlyHint: false,
10
+ destructiveHint: true,
11
+ idempotentHint: false,
12
+ openWorldHint: true,
13
+ };
14
+ /**
15
+ * Register the complete process surface behind one seam. Bash and Codex-style
16
+ * process adapters intentionally share the same durable-output and completion
17
+ * runtime so tool-mode changes cannot fork process semantics.
18
+ */
19
+ export function registerProcessTools(options) {
20
+ if (options.config.toolMode === "codex")
21
+ registerCodexProcessTools(options);
22
+ else
23
+ registerBashTool(options);
24
+ }
25
+ function registerBashTool(options) {
26
+ const { server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, shellRun, routing, descriptions, } = options;
27
+ registerAppTool(server, toolNames.shell, {
28
+ title: "Bash",
29
+ description: descriptions.shell,
30
+ inputSchema: {
31
+ workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
32
+ member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this process operation."),
33
+ action: z.enum(["run", "process", "output"]).optional().describe("Defaults to run. Use process with a returned processId to poll/interact, or output with outputId to retrieve complete durable output."),
34
+ command: z.string().optional().describe(`${descriptions.shellCommand} Required for action=run.`),
35
+ processId: z.number().int().positive().optional().describe("Process identifier returned by a previous bash action=run call. Required for action=process."),
36
+ outputId: z.string().optional().describe("Stable output identifier returned by a Bash run. Required for action=output."),
37
+ input: z.string().optional().describe("Characters to write for action=process. Omit to poll/wait without input."),
38
+ interrupt: z.boolean().optional().describe("For action=process, send SIGINT to the process. Cannot be combined with input."),
39
+ tty: z.boolean().optional().describe("For action=run, allocate a pseudo-terminal for interactive commands. Defaults to false."),
40
+ columns: z.number().int().min(1).max(1_000).optional().describe("Initial PTY width for action=run, or resize width for action=process."),
41
+ rows: z.number().int().min(1).max(1_000).optional().describe("Initial PTY height for action=run, or resize height for action=process."),
42
+ workingDirectory: z.string().optional().describe("For action=run, working directory relative to the workspace root. Defaults to the workspace root."),
43
+ yieldTimeMs: z.number().int().min(0).max(300_000).optional().describe("Maximum feedback wait, not a minimum delay: if the process finishes sooner, the call returns immediately. For long-running commands or wait-only action=process calls, set a long window near the Host request deadline (60000ms when supported) instead of repeated short polling. For action=run, use 0 for immediate background handoff; otherwise defaults to 10000ms. For action=process, wait-only calls default to 5000ms and interaction to 250ms."),
44
+ timeoutMs: z.number().int().min(1).max(86_400_000).optional().describe("For action=run, total execution timeout from process start. On expiry ForgeRelay terminates the process. Omit for no ForgeRelay execution deadline."),
45
+ maxOutputTokens: z.number().int().positive().max(100_000).optional().describe("Approximate output token budget. Defaults to 10000."),
46
+ },
47
+ outputSchema: processOutputSchema(),
48
+ // The unified Activity Panel is mounted by activity_panel. Process tools
49
+ // remain headless and feed it through durable Activity state instead.
50
+ _meta: {},
51
+ annotations: SHELL_TOOL_ANNOTATIONS,
52
+ }, async ({ workspaceId, member, action = "run", command, processId, outputId, input, interrupt, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens, }, extra) => {
53
+ const target = routing.resolve(workspaceId, member);
54
+ const executionWorkspaceId = target.executionWorkspaceId;
55
+ const executionContext = await routing.prepare(target, extra._meta, extra.signal, extra.sessionId);
56
+ if (routing.isRemote(executionWorkspaceId)) {
57
+ const response = await routing.bashRemote(executionWorkspaceId, {
58
+ action,
59
+ ...(command !== undefined ? { command } : {}),
60
+ ...(processId !== undefined ? { processId } : {}),
61
+ ...(outputId !== undefined ? { outputId } : {}),
62
+ ...(input !== undefined ? { input } : {}),
63
+ ...(interrupt !== undefined ? { interrupt } : {}),
64
+ ...(tty !== undefined ? { tty } : {}),
65
+ ...(columns !== undefined ? { columns } : {}),
66
+ ...(rows !== undefined ? { rows } : {}),
67
+ ...(workingDirectory !== undefined ? { workingDirectory } : {}),
68
+ ...(yieldTimeMs !== undefined ? { yieldTimeMs } : {}),
69
+ ...(timeoutMs !== undefined ? { timeoutMs } : {}),
70
+ ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
71
+ }, routing.hostScopeIdFor(extra._meta, extra.sessionId));
72
+ return action === "run"
73
+ ? routing.presentSemantic(response, target)
74
+ : routing.present(response, target);
75
+ }
76
+ const workspace = workspaces.getWorkspace(executionWorkspaceId);
77
+ if (action === "run") {
78
+ if (!command)
79
+ throw new Error("bash action=run requires command.");
80
+ if (processId !== undefined || outputId !== undefined || input !== undefined || interrupt !== undefined) {
81
+ throw new Error("bash action=run does not accept processId, outputId, input, or interrupt.");
82
+ }
83
+ return routing.presentSemantic(await shellRun({
84
+ workspaceId: executionWorkspaceId,
85
+ command,
86
+ surface: "bash",
87
+ tty,
88
+ columns,
89
+ rows,
90
+ workingDirectory,
91
+ yieldTimeMs,
92
+ timeoutMs,
93
+ maxOutputTokens,
94
+ }, executionContext), target);
95
+ }
96
+ if (action === "output") {
97
+ if (!outputId)
98
+ throw new Error("bash action=output requires outputId.");
99
+ if (command !== undefined || processId !== undefined || input !== undefined || interrupt !== undefined ||
100
+ tty !== undefined || columns !== undefined || rows !== undefined || workingDirectory !== undefined ||
101
+ yieldTimeMs !== undefined || timeoutMs !== undefined || maxOutputTokens !== undefined) {
102
+ throw new Error("bash action=output accepts only workspaceId and outputId.");
103
+ }
104
+ return runToolWithHooks(hooks, {
105
+ signal: extra.signal,
106
+ tool: toolNames.shell,
107
+ invocation: workspaceHookInvocation(workspace),
108
+ payload: { action, outputId },
109
+ operation: async () => durableOutputResponse(toolNames.shell, executionWorkspaceId, readWorkspaceBashOutput(bashOutputStore, executionWorkspaceId, outputId)),
110
+ }).then((result) => routing.present(result, target));
111
+ }
112
+ if (outputId !== undefined)
113
+ throw new Error("bash action=process does not accept outputId.");
114
+ if (command !== undefined || workingDirectory !== undefined || tty !== undefined || timeoutMs !== undefined) {
115
+ throw new Error("bash action=process does not accept command, workingDirectory, tty, or timeoutMs.");
116
+ }
117
+ if (processId === undefined)
118
+ throw new Error("bash action=process requires processId.");
119
+ if (interrupt && input !== undefined) {
120
+ throw new Error("bash action=process cannot combine interrupt with input.");
121
+ }
122
+ return runToolWithHooks(hooks, {
123
+ signal: extra.signal,
124
+ tool: toolNames.shell,
125
+ invocation: workspaceHookInvocation(workspace),
126
+ payload: {
127
+ action,
128
+ processId,
129
+ inputLength: input?.length ?? 0,
130
+ interrupt: interrupt ?? false,
131
+ columns,
132
+ rows,
133
+ },
134
+ isFailure: toolResultIsError,
135
+ operation: async () => {
136
+ const startedAt = performance.now();
137
+ const snapshot = await processSessions.write({
138
+ workspaceId: executionWorkspaceId,
139
+ processId,
140
+ chars: interrupt ? "\u0003" : input,
141
+ columns,
142
+ rows,
143
+ yieldTimeMs,
144
+ maxOutputTokens,
145
+ signal: extra.signal,
146
+ });
147
+ logProcessToolCall(config, workspace, {
148
+ tool: toolNames.shell,
149
+ exitCode: snapshot.exitCode,
150
+ running: snapshot.running,
151
+ processId: snapshot.processId,
152
+ success: snapshot.running || snapshot.exitCode === 0,
153
+ durationMs: Math.round(performance.now() - startedAt),
154
+ });
155
+ const response = processToolResponse(toolNames.shell, executionWorkspaceId, snapshot, {
156
+ action,
157
+ processId,
158
+ inputLength: input?.length ?? 0,
159
+ interrupt: interrupt ?? false,
160
+ running: snapshot.running,
161
+ exitCode: snapshot.exitCode,
162
+ wallTimeMs: snapshot.wallTimeMs,
163
+ });
164
+ if (!snapshot.running) {
165
+ recordBashCompletion(activityLifecycle, bashOutputStore, snapshot.outputId);
166
+ }
167
+ return routing.present(response, target);
168
+ },
169
+ });
170
+ });
171
+ }
172
+ function registerCodexProcessTools(options) {
173
+ const { server, config, workspaces, processSessions, hooks, activityLifecycle, bashOutputStore, shellRun, routing, } = options;
174
+ registerAppTool(server, "exec_command", {
175
+ title: "Execute command",
176
+ description: `Run a command inside an open workspace. Returns its result when it exits during the yield window, otherwise returns a processId for write_stdin. Use this for file inspection, tests, builds, package scripts, generators, formatters, and long-running processes. ${buildShellMutationPolicy()} Call open_workspace first and pass workspaceId.`,
177
+ inputSchema: {
178
+ workspaceId: z.string().describe("Workspace identifier returned by open_workspace."),
179
+ member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns this process."),
180
+ cmd: z.string().min(1).describe("Shell command to execute."),
181
+ tty: z.boolean().optional().describe("Allocate a pseudo-terminal for interactive commands. Defaults to false."),
182
+ columns: z.number().int().min(1).max(1_000).optional().describe("Initial PTY width. Defaults to 80."),
183
+ rows: z.number().int().min(1).max(1_000).optional().describe("Initial PTY height. Defaults to 24."),
184
+ workingDirectory: z.string().optional().describe("Working directory relative to the workspace root. Defaults to the workspace root."),
185
+ yieldTimeMs: z.number().int().min(0).max(300_000).optional().describe("Feedback window before returning a processId. Use 0 for immediate background handoff. Defaults to 10000ms."),
186
+ timeoutMs: z.number().int().min(1).max(86_400_000).optional().describe("Total execution timeout from process start. On expiry ForgeRelay terminates the process. Omit for no ForgeRelay execution deadline."),
187
+ maxOutputTokens: z.number().int().positive().max(100_000).optional().describe("Approximate output token budget. Defaults to 10000."),
188
+ },
189
+ outputSchema: processOutputSchema(),
190
+ _meta: {},
191
+ annotations: SHELL_TOOL_ANNOTATIONS,
192
+ }, async ({ workspaceId, member, cmd, tty, columns, rows, workingDirectory, yieldTimeMs, timeoutMs, maxOutputTokens }, extra) => {
193
+ const target = routing.resolve(workspaceId, member);
194
+ const context = await routing.prepare(target, extra._meta, extra.signal, extra.sessionId);
195
+ if (routing.isRemote(target.executionWorkspaceId)) {
196
+ return routing.presentSemantic(await routing.execCommandRemote(target.executionWorkspaceId, {
197
+ cmd,
198
+ ...(tty !== undefined ? { tty } : {}),
199
+ ...(columns !== undefined ? { columns } : {}),
200
+ ...(rows !== undefined ? { rows } : {}),
201
+ ...(workingDirectory !== undefined ? { workingDirectory } : {}),
202
+ ...(yieldTimeMs !== undefined ? { yieldTimeMs } : {}),
203
+ ...(timeoutMs !== undefined ? { timeoutMs } : {}),
204
+ ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
205
+ }, routing.hostScopeIdFor(extra._meta, extra.sessionId)), target);
206
+ }
207
+ return routing.presentSemantic(await shellRun({
208
+ workspaceId: target.executionWorkspaceId,
209
+ command: cmd,
210
+ surface: "exec_command",
211
+ tty,
212
+ columns,
213
+ rows,
214
+ workingDirectory,
215
+ yieldTimeMs,
216
+ timeoutMs,
217
+ maxOutputTokens,
218
+ }, context), target);
219
+ });
220
+ registerAppTool(server, "write_stdin", {
221
+ title: "Write to process",
222
+ description: "Poll or write characters to a running process returned by exec_command, or retrieve complete durable process output by outputId. Omit chars or pass an empty string to poll. Waiting never kills the process; pass \\u0003 to explicitly send Ctrl-C.",
223
+ inputSchema: {
224
+ workspaceId: z.string().describe("Workspace identifier used to start the process."),
225
+ member: z.string().optional().describe("Required for a Composite Workspace; explicit member name that owns the process."),
226
+ processId: z.number().int().positive().optional().describe("Canonical process identifier returned by bash or exec_command."),
227
+ sessionId: z.number().int().positive().optional().describe("Deprecated alias for processId. Retained for compatibility."),
228
+ outputId: z.string().optional().describe("Stable output identifier returned by exec_command. When supplied, retrieve the complete durable output instead of controlling a process."),
229
+ chars: z.string().optional().describe("Characters to write. Omit or pass an empty string to poll."),
230
+ columns: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this width."),
231
+ rows: z.number().int().min(1).max(1_000).optional().describe("Resize a PTY to this height."),
232
+ yieldTimeMs: z.number().int().min(0).max(300_000).optional().describe("Milliseconds to keep waiting before returning again, max 300000. Polling defaults to 5000; interaction defaults to 250."),
233
+ maxOutputTokens: z.number().int().positive().max(100_000).optional().describe("Approximate output token budget. Defaults to 10000."),
234
+ },
235
+ outputSchema: processOutputSchema(),
236
+ _meta: {},
237
+ annotations: SHELL_TOOL_ANNOTATIONS,
238
+ }, async ({ workspaceId, member, processId, sessionId, outputId, chars, columns, rows, yieldTimeMs, maxOutputTokens }, extra) => {
239
+ const target = routing.resolve(workspaceId, member);
240
+ await routing.prepare(target, extra._meta, extra.signal, extra.sessionId);
241
+ if (routing.isRemote(target.executionWorkspaceId)) {
242
+ return routing.present(await routing.writeStdinRemote(target.executionWorkspaceId, {
243
+ ...(processId !== undefined ? { processId } : {}),
244
+ ...(sessionId !== undefined ? { sessionId } : {}),
245
+ ...(outputId !== undefined ? { outputId } : {}),
246
+ ...(chars !== undefined ? { chars } : {}),
247
+ ...(columns !== undefined ? { columns } : {}),
248
+ ...(rows !== undefined ? { rows } : {}),
249
+ ...(yieldTimeMs !== undefined ? { yieldTimeMs } : {}),
250
+ ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
251
+ }, routing.hostScopeIdFor(extra._meta, extra.sessionId)), target);
252
+ }
253
+ const executionWorkspaceId = target.executionWorkspaceId;
254
+ const workspace = workspaces.getWorkspace(executionWorkspaceId);
255
+ if (outputId !== undefined) {
256
+ if (processId !== undefined || sessionId !== undefined || chars !== undefined || columns !== undefined ||
257
+ rows !== undefined || yieldTimeMs !== undefined || maxOutputTokens !== undefined) {
258
+ throw new Error("write_stdin outputId lookup cannot be combined with process control fields.");
259
+ }
260
+ return runToolWithHooks(hooks, {
261
+ signal: extra.signal,
262
+ tool: "write_stdin",
263
+ invocation: workspaceHookInvocation(workspace),
264
+ payload: { outputId },
265
+ operation: async () => durableOutputResponse("write_stdin", executionWorkspaceId, readWorkspaceBashOutput(bashOutputStore, executionWorkspaceId, outputId)),
266
+ }).then((result) => routing.present(result, target));
267
+ }
268
+ const resolvedProcessId = resolveProcessId(processId, sessionId);
269
+ return runToolWithHooks(hooks, {
270
+ signal: extra.signal,
271
+ tool: "write_stdin",
272
+ invocation: workspaceHookInvocation(workspace),
273
+ payload: {
274
+ processId: resolvedProcessId,
275
+ charactersWritten: chars?.length ?? 0,
276
+ columns,
277
+ rows,
278
+ },
279
+ operation: async () => {
280
+ const startedAt = performance.now();
281
+ const snapshot = await processSessions.write({
282
+ workspaceId: executionWorkspaceId,
283
+ processId: resolvedProcessId,
284
+ chars,
285
+ columns,
286
+ rows,
287
+ yieldTimeMs,
288
+ maxOutputTokens,
289
+ signal: extra.signal,
290
+ });
291
+ logProcessToolCall(config, workspace, {
292
+ tool: "write_stdin",
293
+ exitCode: snapshot.exitCode,
294
+ running: snapshot.running,
295
+ processId: snapshot.processId,
296
+ success: snapshot.running || snapshot.exitCode === 0,
297
+ durationMs: Math.round(performance.now() - startedAt),
298
+ });
299
+ const response = processToolResponse("write_stdin", executionWorkspaceId, snapshot, {
300
+ processId: resolvedProcessId,
301
+ charactersWritten: chars?.length ?? 0,
302
+ running: snapshot.running,
303
+ exitCode: snapshot.exitCode,
304
+ wallTimeMs: snapshot.wallTimeMs,
305
+ });
306
+ if (!snapshot.running) {
307
+ recordBashCompletion(activityLifecycle, bashOutputStore, snapshot.outputId);
308
+ }
309
+ return response;
310
+ },
311
+ }).then((result) => routing.present(result, target));
312
+ });
313
+ }
314
+ function processOutputSchema() {
315
+ return {
316
+ result: z.string().describe("Model-readable result text for follow-up reasoning and plain MCP hosts."),
317
+ processId: z.number().int().positive().optional().describe("Canonical process handle for bash(action=\"process\") or the active command adapter."),
318
+ sessionId: z.number().int().positive().optional().describe("Deprecated alias of processId for compatibility."),
319
+ outputId: z.string().optional().describe("Stable local audit identifier for retrieving the complete original process output."),
320
+ running: z.boolean(),
321
+ exitCode: z.number().int().optional(),
322
+ signal: z.string().optional(),
323
+ timedOut: z.boolean(),
324
+ wallTimeMs: z.number().nonnegative(),
325
+ outputTruncated: z.boolean(),
326
+ };
327
+ }
328
+ function workspaceHookInvocation(workspace) {
329
+ return {
330
+ workspaceId: workspace.id,
331
+ workspaceRoot: workspace.root,
332
+ workspaceMode: workspace.mode,
333
+ sourceRoot: workspace.sourceRoot,
334
+ };
335
+ }
336
+ function logProcessToolCall(config, workspace, fields) {
337
+ if (!config.logging.toolCalls)
338
+ return;
339
+ const { command, ...safeFields } = fields;
340
+ logEvent(config.logging, fields.success ? "info" : "warn", "tool_call", {
341
+ ...safeFields,
342
+ workspaceId: workspace.id,
343
+ workspace: workspaceLogLabel(workspace.root, workspace.id),
344
+ commandPreview: config.logging.shellCommands && command ? commandPreview(command) : undefined,
345
+ });
346
+ }