@code-yeongyu/senpi 2026.10.25-1 → 2026.10.25-3

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 (226) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/bundle/chunks/{args-JXULI4EH.js → args-B444OQBK.js} +1 -1
  3. package/dist/bundle/chunks/{auth-4NUNUSTF.js → auth-C77UQA55.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-RHHBXIRN.js → chunk-4WZRVZNX.js} +1 -1
  5. package/dist/bundle/chunks/chunk-6O4SEW2W.js +5 -0
  6. package/dist/bundle/chunks/{chunk-Y5OTHMXO.js → chunk-7CNOCJCN.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-YDCRA5ES.js → chunk-7W7OJDIY.js} +4 -4
  8. package/dist/bundle/chunks/chunk-AX72VNRS.js +27 -0
  9. package/dist/bundle/chunks/{chunk-FWXMMMCV.js → chunk-C5UFJ7B3.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-7IRLFYNM.js → chunk-CNL6QSUB.js} +4 -4
  11. package/dist/bundle/chunks/{chunk-BTNYLNWZ.js → chunk-FFQOEC76.js} +5 -5
  12. package/dist/bundle/chunks/{chunk-VOBD6FQG.js → chunk-FHXR4ZAC.js} +1 -1
  13. package/dist/bundle/chunks/chunk-H3HNMY2W.js +13 -0
  14. package/dist/bundle/chunks/{chunk-BMYNMUVZ.js → chunk-HVJI5T75.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-EWOEVVQT.js → chunk-JA2R2ZSE.js} +1 -1
  16. package/dist/bundle/chunks/chunk-JPGT64RV.js +2 -0
  17. package/dist/bundle/chunks/chunk-KXB5WIS5.js +4 -0
  18. package/dist/bundle/chunks/chunk-L73UYAH5.js +3 -0
  19. package/dist/bundle/chunks/chunk-LTU6UFEX.js +2 -0
  20. package/dist/bundle/chunks/{chunk-EYBDBR7V.js → chunk-MJ5NNWX4.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-RVYSJBZ7.js → chunk-O3Q2ET6W.js} +2 -5
  22. package/dist/bundle/chunks/chunk-O5RYI3IW.js +2 -0
  23. package/dist/bundle/chunks/{chunk-SGDOZT6B.js → chunk-ODTGC6YU.js} +1 -1
  24. package/dist/bundle/chunks/chunk-P3M2PF6K.js +27 -0
  25. package/dist/bundle/chunks/chunk-SWJXRC2I.js +5 -0
  26. package/dist/bundle/chunks/{chunk-QH2HSCMX.js → chunk-TTU2RNVX.js} +3 -3
  27. package/dist/bundle/chunks/{chunk-PXKF2RHD.js → chunk-UFJ2K7Q7.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-7UUOGZKR.js → chunk-UQNRW3VU.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-U67ML6CQ.js → chunk-WD6WBYC5.js} +1 -1
  30. package/dist/bundle/chunks/chunk-WJOT3DFL.js +2 -0
  31. package/dist/bundle/chunks/{chunk-7WAJCESX.js → chunk-WLHNEUGD.js} +1 -1
  32. package/dist/bundle/chunks/chunk-YI7SPFDN.js +5 -0
  33. package/dist/bundle/chunks/{chunk-URBJS24R.js → chunk-YRF4I43T.js} +35 -35
  34. package/dist/bundle/chunks/chunk-Z3UYNSVR.js +3 -0
  35. package/dist/bundle/chunks/cli-main-HEKFFB5S.js +3 -0
  36. package/dist/bundle/chunks/{client-KWTLTKVV.js → client-WTYEICLH.js} +3 -3
  37. package/dist/bundle/chunks/dispatch-NYAULNVW.js +9 -0
  38. package/dist/bundle/chunks/{help-fast-path-ZHZGXAUG.js → help-fast-path-3Z26DQSV.js} +1 -1
  39. package/dist/bundle/chunks/host-command-Z3WPHJOW.js +2 -0
  40. package/dist/bundle/chunks/host-lifecycle-ONQIMWMX.js +3 -0
  41. package/dist/bundle/chunks/host-lifecycle.js +9 -7
  42. package/dist/bundle/chunks/host-session-command-J65ENSV5.js +20 -0
  43. package/dist/bundle/chunks/{host-socket-request-ZDJGSSZO.js → host-socket-request-3CWE4265.js} +1 -1
  44. package/dist/bundle/chunks/interactive-mode-DEPQC6TX.js +2 -0
  45. package/dist/bundle/chunks/mcp-presenter-bootstrap-XUSCQVRC.js +2 -0
  46. package/dist/bundle/chunks/{multi-session-host-M7HVUMRH.js → multi-session-host-YI2WZOET.js} +12 -9
  47. package/dist/bundle/chunks/{package-manager-cli-FSR5P6YL.js → package-manager-cli-GHZPJMRF.js} +1 -1
  48. package/dist/bundle/chunks/{role-M2MTVACJ.js → role-JVSBNKGE.js} +1 -1
  49. package/dist/bundle/chunks/{rotation-stream-U7YQMC7H.js → rotation-stream-ZPVCFOVD.js} +1 -1
  50. package/dist/bundle/chunks/rpc-mode-ORHBGBBV.js +2 -0
  51. package/dist/bundle/chunks/{rpc-monitor-source-OL5KU6NO.js → rpc-monitor-source-FFEQS744.js} +1 -1
  52. package/dist/bundle/chunks/{runtime-session-CLNODAYE.js → runtime-session-V5OO7ZRM.js} +1 -1
  53. package/dist/bundle/chunks/{schedule-command-XBW4EOG5.js → schedule-command-YWG6D6ZF.js} +1 -1
  54. package/dist/bundle/chunks/session-control-endpoint-36HAQSQO.js +3 -0
  55. package/dist/bundle/chunks/{session-picker-KQUFWDQC.js → session-picker-25H5KNYP.js} +1 -1
  56. package/dist/bundle/chunks/session-worker.js +285 -271
  57. package/dist/bundle/chunks/{stdio-IEC635SY.js → stdio-YXDKKKQZ.js} +1 -1
  58. package/dist/bundle/chunks/{streamableHttp-KJI5KE4K.js → streamableHttp-DKIBUWVY.js} +1 -1
  59. package/dist/bundle/chunks/{types-LLNV23LW.js → types-BJUEQ4II.js} +1 -1
  60. package/dist/bundle/chunks/zod-compat-RJDXCU7S.js +2 -0
  61. package/dist/bundle/cli.js +2 -2
  62. package/dist/bundle/host-supervisor.js +9 -7
  63. package/dist/bundle/index.js +1 -1
  64. package/dist/bundle/rpc-entry.js +1 -1
  65. package/dist/bundle/runtime-manifest.json +1 -1
  66. package/dist/cli/args.d.ts +2 -0
  67. package/dist/cli/args.js +9 -0
  68. package/dist/cli/host-command-args.d.ts +1 -0
  69. package/dist/cli/host-command-args.js +7 -1
  70. package/dist/cli/host-command.js +3 -1
  71. package/dist/core/agent-session.d.ts +1 -0
  72. package/dist/core/agent-session.js +50 -16
  73. package/dist/core/engine-turn-limit.d.ts +2 -0
  74. package/dist/core/engine-turn-limit.js +7 -1
  75. package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.js +5 -0
  76. package/dist/core/extensions/builtin/goal/index.js +1 -0
  77. package/dist/core/extensions/builtin/goal/lifecycle-facts.d.ts +3 -0
  78. package/dist/core/extensions/builtin/goal/stop-pause.js +2 -1
  79. package/dist/core/extensions/builtin/loop/session-hold.js +2 -1
  80. package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -1
  81. package/dist/core/extensions/builtin/mcp/catalog.js +2 -0
  82. package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -0
  83. package/dist/core/extensions/builtin/mcp/connection.js +46 -1
  84. package/dist/core/extensions/builtin/mcp/elicitation.d.ts +6 -1
  85. package/dist/core/extensions/builtin/mcp/elicitation.js +56 -2
  86. package/dist/core/extensions/builtin/mcp/expose/call.js +6 -2
  87. package/dist/core/extensions/builtin/mcp/expose/session.d.ts +4 -1
  88. package/dist/core/extensions/builtin/mcp/expose/session.js +7 -0
  89. package/dist/core/extensions/builtin/mcp/health.js +5 -0
  90. package/dist/core/extensions/builtin/mcp/host-registry.js +7 -1
  91. package/dist/core/extensions/builtin/mcp/index.js +2 -0
  92. package/dist/core/extensions/builtin/mcp/modern/client.d.ts +5 -0
  93. package/dist/core/extensions/builtin/mcp/modern/client.js +15 -0
  94. package/dist/core/extensions/builtin/mcp/modern/consent-queue.d.ts +2 -0
  95. package/dist/core/extensions/builtin/mcp/modern/consent-queue.js +59 -0
  96. package/dist/core/extensions/builtin/mcp/modern/diagnostics.d.ts +4 -0
  97. package/dist/core/extensions/builtin/mcp/modern/diagnostics.js +39 -0
  98. package/dist/core/extensions/builtin/mcp/modern/dispatch.js +21 -11
  99. package/dist/core/extensions/builtin/mcp/modern/elicitation.d.ts +14 -0
  100. package/dist/core/extensions/builtin/mcp/modern/elicitation.js +145 -0
  101. package/dist/core/extensions/builtin/mcp/modern/errors.d.ts +21 -0
  102. package/dist/core/extensions/builtin/mcp/modern/errors.js +23 -0
  103. package/dist/core/extensions/builtin/mcp/modern/form-schema.d.ts +20 -0
  104. package/dist/core/extensions/builtin/mcp/modern/form-schema.js +153 -0
  105. package/dist/core/extensions/builtin/mcp/modern/http-fetch.d.ts +3 -1
  106. package/dist/core/extensions/builtin/mcp/modern/http-fetch.js +8 -2
  107. package/dist/core/extensions/builtin/mcp/modern/inputs.d.ts +11 -0
  108. package/dist/core/extensions/builtin/mcp/modern/inputs.js +65 -0
  109. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +11 -0
  110. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +109 -0
  111. package/dist/core/extensions/builtin/mcp/modern/request.d.ts +20 -0
  112. package/dist/core/extensions/builtin/mcp/modern/request.js +292 -0
  113. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.d.ts +1 -1
  114. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +54 -6
  115. package/dist/core/extensions/builtin/mcp/modern/transport.js +80 -14
  116. package/dist/core/extensions/builtin/mcp/modern/wire.js +11 -7
  117. package/dist/core/extensions/builtin/mcp/prompts.d.ts +5 -1
  118. package/dist/core/extensions/builtin/mcp/prompts.js +6 -1
  119. package/dist/core/extensions/builtin/mcp/request-origin.d.ts +47 -0
  120. package/dist/core/extensions/builtin/mcp/request-origin.js +59 -0
  121. package/dist/core/extensions/builtin/mcp/resources.d.ts +4 -1
  122. package/dist/core/extensions/builtin/mcp/resources.js +19 -4
  123. package/dist/core/extensions/builtin/mcp/sdk.lazy.d.ts +3 -1
  124. package/dist/core/extensions/builtin/mcp/sdk.lazy.js +3 -1
  125. package/dist/core/extensions/builtin/mcp/service-register.d.ts +4 -1
  126. package/dist/core/extensions/builtin/mcp/service-register.js +3 -0
  127. package/dist/core/extensions/builtin/mcp/service-types.d.ts +2 -1
  128. package/dist/core/extensions/builtin/mcp/service.d.ts +1 -0
  129. package/dist/core/extensions/builtin/mcp/service.js +79 -4
  130. package/dist/core/extensions/builtin/mcp/shared-connection.js +36 -2
  131. package/dist/core/extensions/builtin/mcp/shared-lease.d.ts +1 -0
  132. package/dist/core/extensions/builtin/mcp/shared-lease.js +9 -0
  133. package/dist/core/extensions/builtin/mcp/transport-sdk.d.ts +3 -2
  134. package/dist/core/extensions/builtin/mcp/transport-sdk.js +56 -25
  135. package/dist/core/extensions/builtin/mcp/wrap.d.ts +7 -0
  136. package/dist/core/extensions/builtin/mcp/wrap.js +6 -0
  137. package/dist/core/extensions/runner.js +3 -2
  138. package/dist/core/extensions/types.d.ts +2 -0
  139. package/dist/core/lifecycle-ledger.d.ts +1 -0
  140. package/dist/core/lifecycle-ledger.js +7 -2
  141. package/dist/core/lifecycle-record.js +3 -0
  142. package/dist/core/lifecycle-runtime-facts.js +3 -0
  143. package/dist/core/lifecycle-session-binding.d.ts +11 -1
  144. package/dist/core/lifecycle-session-binding.js +35 -4
  145. package/dist/core/lifecycle-stop.d.ts +41 -0
  146. package/dist/core/lifecycle-stop.js +92 -0
  147. package/dist/core/mcp-form-types.d.ts +89 -0
  148. package/dist/core/mcp-form-types.js +23 -0
  149. package/dist/core/mcp-mrtr-presentations.d.ts +15 -0
  150. package/dist/core/mcp-mrtr-presentations.js +17 -0
  151. package/dist/core/run-resume-turn.d.ts +1 -0
  152. package/dist/core/run-resume-turn.js +4 -3
  153. package/dist/core/session-lifecycle-types.d.ts +15 -0
  154. package/dist/core/session-lifecycle.d.ts +17 -1
  155. package/dist/core/session-lifecycle.js +40 -2
  156. package/dist/core/stop-resume-directive.d.ts +1 -1
  157. package/dist/core/stop-resume-directive.js +10 -1
  158. package/dist/main.js +5 -1
  159. package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
  160. package/dist/modes/interactive/components/extension-selector.js +4 -0
  161. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  162. package/dist/modes/interactive/interactive-mode.js +35 -8
  163. package/dist/modes/interactive/mcp-local-presenter.d.ts +5 -0
  164. package/dist/modes/interactive/mcp-local-presenter.js +109 -0
  165. package/dist/modes/interactive/pause-footer.d.ts +5 -0
  166. package/dist/modes/interactive/pause-footer.js +2 -0
  167. package/dist/modes/rpc/connection-handler.d.ts +1 -0
  168. package/dist/modes/rpc/connection-handler.js +7 -1
  169. package/dist/modes/rpc/host-capabilities.d.ts +1 -0
  170. package/dist/modes/rpc/host-capabilities.js +2 -1
  171. package/dist/modes/rpc/host-ensure-start.js +13 -2
  172. package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
  173. package/dist/modes/rpc/host-ensure.js +1 -0
  174. package/dist/modes/rpc/host-handoff.d.ts +2 -0
  175. package/dist/modes/rpc/host-handover-wire.d.ts +2 -0
  176. package/dist/modes/rpc/host-handover-wire.js +1 -0
  177. package/dist/modes/rpc/host-lifecycle-launch.d.ts +1 -0
  178. package/dist/modes/rpc/host-lifecycle-launch.js +43 -2
  179. package/dist/modes/rpc/host-reservations.js +3 -2
  180. package/dist/modes/rpc/host-runner.d.ts +2 -0
  181. package/dist/modes/rpc/host-runner.js +8 -6
  182. package/dist/modes/rpc/host-stalled-evidence.js +2 -1
  183. package/dist/modes/rpc/host-successor.js +11 -1
  184. package/dist/modes/rpc/mcp-form-dialog.d.ts +45 -0
  185. package/dist/modes/rpc/mcp-form-dialog.js +159 -0
  186. package/dist/modes/rpc/mcp-form-worker.d.ts +14 -0
  187. package/dist/modes/rpc/mcp-form-worker.js +167 -0
  188. package/dist/modes/rpc/mcp-presenter-authority.d.ts +53 -0
  189. package/dist/modes/rpc/mcp-presenter-authority.js +309 -0
  190. package/dist/modes/rpc/mcp-presenter-bootstrap.d.ts +8 -0
  191. package/dist/modes/rpc/mcp-presenter-bootstrap.js +111 -0
  192. package/dist/modes/rpc/mcp-presenter-control.d.ts +24 -0
  193. package/dist/modes/rpc/mcp-presenter-control.js +330 -0
  194. package/dist/modes/rpc/multi-session-host.d.ts +3 -0
  195. package/dist/modes/rpc/multi-session-host.js +36 -1
  196. package/dist/modes/rpc/session-binding.js +6 -1
  197. package/dist/modes/rpc/session-event-writer.d.ts +14 -0
  198. package/dist/modes/rpc/session-event-writer.js +128 -1
  199. package/dist/modes/rpc/session-router-routing.js +1 -0
  200. package/dist/modes/rpc/session-worker-client.d.ts +3 -0
  201. package/dist/modes/rpc/session-worker-client.js +89 -0
  202. package/dist/modes/rpc/session-worker-protocol.d.ts +41 -0
  203. package/dist/modes/rpc/session-worker.js +9 -0
  204. package/dist/modes/rpc/tool-child/group-ownership.js +4 -2
  205. package/dist/utils/process-identity.d.ts +7 -0
  206. package/dist/utils/process-identity.js +32 -5
  207. package/docs/extensions.md +5 -0
  208. package/docs/mcp.md +20 -3
  209. package/docs/rpc.md +111 -0
  210. package/package.json +6 -6
  211. package/dist/bundle/chunks/chunk-3VOG6WFU.js +0 -27
  212. package/dist/bundle/chunks/chunk-7ASLQ243.js +0 -2
  213. package/dist/bundle/chunks/chunk-CDRFFHLL.js +0 -4
  214. package/dist/bundle/chunks/chunk-HPCS4HM4.js +0 -27
  215. package/dist/bundle/chunks/chunk-II22E3CV.js +0 -3
  216. package/dist/bundle/chunks/chunk-J5P2VL3Y.js +0 -5
  217. package/dist/bundle/chunks/chunk-SCVRKIFK.js +0 -2
  218. package/dist/bundle/chunks/chunk-XUFUKDKJ.js +0 -5
  219. package/dist/bundle/chunks/cli-main-TABBP55A.js +0 -3
  220. package/dist/bundle/chunks/dispatch-DEP4PEDZ.js +0 -10
  221. package/dist/bundle/chunks/host-command-OQR4RDTM.js +0 -2
  222. package/dist/bundle/chunks/host-lifecycle-U36U7JTE.js +0 -3
  223. package/dist/bundle/chunks/host-session-command-IGB27ZPJ.js +0 -20
  224. package/dist/bundle/chunks/interactive-mode-DGQNSXUA.js +0 -2
  225. package/dist/bundle/chunks/rpc-mode-SZAJNKKL.js +0 -2
  226. package/dist/bundle/chunks/session-control-endpoint-WEF3XTJR.js +0 -3
@@ -1,14 +1,15 @@
1
- import { configureMcpElicitation, mcpClientCapabilities } from "./elicitation.js";
1
+ import { modernCredentialSnapshot } from "./modern/http-fetch.js";
2
+ import { createMcpRequestClient } from "./modern/client.js";
2
3
  import { ProjectStdioTransport } from "./project-stdio-transport.js";
3
- import { loadMcpSdkClient, loadMcpSdkStdioTransport, loadMcpSdkStreamableHttpTransport } from "./sdk.lazy.js";
4
+ import { loadMcpSdkStdioTransport, loadMcpSdkStreamableHttpTransport } from "./sdk.lazy.js";
4
5
  import { safeOn } from "./wrap.js";
5
- export async function materializeMcpTransport(options) {
6
- const client = await buildMcpClient(options.elicitationUiProvider, options.elicitationEnabled);
6
+ export async function materializeMcpTransport(options, protocol = {}) {
7
+ const client = await createMcpRequestClient(protocol, options.elicitationUiProvider, () => modernCredentialSnapshot(options.spec.authProvider?.tokens()), options.elicitationEnabled);
7
8
  if (options.spec.kind === "stdio")
8
- return materializeStdio(options.spec, options, client);
9
+ return materializeStdio(options.spec, options, client, protocol);
9
10
  return materializeHttp(options.spec, client);
10
11
  }
11
- async function materializeStdio(spec, options, client) {
12
+ async function materializeStdio(spec, options, client, protocol) {
12
13
  const { getDefaultEnvironment, StdioClientTransport } = await loadMcpSdkStdioTransport();
13
14
  const create = (current) => new StdioClientTransport({
14
15
  args: current.args,
@@ -25,7 +26,7 @@ async function materializeStdio(spec, options, client) {
25
26
  ? new ProjectStdioTransport(spec.resolveBeforeStart, create)
26
27
  : create(spec);
27
28
  trackStdioStart(transport, options.onStart);
28
- pipeStderr(transport, options.logger, options.sink);
29
+ pipeStderr(transport, options.logger, options.sink, protocol);
29
30
  dropNullNextCursor(transport);
30
31
  return {
31
32
  client,
@@ -48,15 +49,6 @@ async function materializeHttp(spec, client) {
48
49
  transport,
49
50
  };
50
51
  }
51
- async function buildMcpClient(elicitationUiProvider, elicitationEnabled = true) {
52
- // Elicitation capability is declared EMPTY on purpose (form mode only;
53
- // Spring-AI servers reject richer shapes) and the create-handler is wired
54
- // before any connect so mid-call requests never race registration.
55
- const { Client: McpClient } = await loadMcpSdkClient();
56
- const client = new McpClient({ name: "senpi-mcp-client", version: "0.0.0" }, { capabilities: mcpClientCapabilities(elicitationEnabled) });
57
- await configureMcpElicitation(client, elicitationUiProvider, undefined, elicitationEnabled);
58
- return client;
59
- }
60
52
  // OMP pattern: hand stdio OAuth servers the current access token via env.
61
53
  function oauthAccessTokenEnv(authProvider) {
62
54
  const accessToken = authProvider?.tokens()?.access_token;
@@ -90,26 +82,65 @@ function trackStdioStart(transport, onStart) {
90
82
  onStart();
91
83
  };
92
84
  }
93
- function pipeStderr(transport, logger, sink) {
85
+ function pipeStderr(transport, logger, sink, protocol) {
94
86
  let pending = "";
87
+ let undecided = "";
88
+ let stderrEnded = false;
95
89
  const stderr = transport.stderr;
96
90
  if (stderr === undefined || stderr === null)
97
91
  return;
98
- safeOn(stderr, "data", "transport.stderr.data", (chunk) => {
99
- if (!Buffer.isBuffer(chunk) && typeof chunk !== "string")
100
- return;
101
- pending += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
92
+ const logLine = (line) => {
93
+ // Arbitrary server text may echo private messages, schemas, or answers. Keep diagnostic
94
+ // events and allowlisted system error codes, never untrusted payload text under enforcement.
95
+ const code = protocol.privateForms
96
+ ? line.match(/\b(?:EACCES|ENOENT|EPIPE|ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOSPC|ENOMEM|EMFILE)\b/)?.[0]
97
+ : undefined;
98
+ logger.stderr(protocol.privateForms ? `[private MCP stderr redacted]${code ? ` ${code}` : ""}` : line);
99
+ };
100
+ const append = (text) => {
101
+ pending += text;
102
102
  const lines = pending.split(/\r?\n/);
103
103
  pending = lines.pop() ?? "";
104
- for (const line of lines) {
104
+ for (const line of lines)
105
105
  if (line.length > 0)
106
- logger.stderr(line);
106
+ logLine(line);
107
+ };
108
+ protocol.resolveStdioDiagnostics = (modern) => {
109
+ if (!modern || protocol.privateForms) {
110
+ append(undecided);
111
+ if (stderrEnded && pending.length > 0) {
112
+ logLine(pending);
113
+ pending = "";
114
+ }
115
+ }
116
+ undecided = "";
117
+ if (modern && !protocol.privateForms)
118
+ pending = "";
119
+ };
120
+ safeOn(stderr, "data", "transport.stderr.data", (chunk) => {
121
+ if (protocol.version !== undefined && !protocol.privateForms) {
122
+ pending = "";
123
+ undecided = "";
124
+ return;
125
+ }
126
+ if (!Buffer.isBuffer(chunk) && typeof chunk !== "string")
127
+ return;
128
+ const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
129
+ if (!protocol.legacy && !protocol.privateForms) {
130
+ // Only legacy-positive negotiation may release startup diagnostics.
131
+ undecided += text.slice(0, Math.max(0, 512 * 1024 - undecided.length));
132
+ return;
107
133
  }
134
+ append(text);
108
135
  }, sink);
109
136
  safeOn(stderr, "end", "transport.stderr.end", () => {
110
- if (pending.length > 0)
111
- logger.stderr(pending);
137
+ stderrEnded = true;
138
+ if ((protocol.privateForms || (protocol.legacy && protocol.version === undefined)) && pending.length > 0)
139
+ logLine(pending);
112
140
  pending = "";
141
+ // A crashed probe is classified after EOF; only that decision may release or discard its buffer.
142
+ if (protocol.legacy || protocol.version !== undefined)
143
+ undecided = "";
113
144
  }, sink);
114
145
  }
115
146
  async function closeClientAndTransport(transport, client) {
@@ -3,6 +3,13 @@ export type { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  export type { CallToolResult, ListToolsResult } from "@modelcontextprotocol/sdk/types.js";
4
4
  import type { EventEmitter } from "node:events";
5
5
  import type { EventBus } from "../../../event-bus.ts";
6
+ export interface McpClock {
7
+ now(): number;
8
+ schedule(callback: () => void, ms: number): ReturnType<typeof setTimeout>;
9
+ cancel(timer: ReturnType<typeof setTimeout>): void;
10
+ }
11
+ /** The modern operation uses one injectable monotonic clock and scheduler. */
12
+ export declare const mcpElapsedClock: McpClock;
6
13
  type MaybePromise<T> = T | Promise<T>;
7
14
  export interface McpErrorLogger {
8
15
  error(message: string, data?: unknown): void;
@@ -1,4 +1,10 @@
1
1
  import { redactMcpLogText } from "./log.js";
2
+ /** The modern operation uses one injectable monotonic clock and scheduler. */
3
+ export const mcpElapsedClock = {
4
+ now: () => performance.now(),
5
+ schedule: setTimeout,
6
+ cancel: clearTimeout,
7
+ };
2
8
  export function wrapAsync(scope, fn, sink) {
3
9
  return async (...args) => {
4
10
  try {
@@ -6,6 +6,7 @@ import { getAgentDir } from "../../config.js";
6
6
  import { theme } from "../../modes/interactive/theme/theme.js";
7
7
  import { stripAnsi } from "../../utils/ansi.js";
8
8
  import { createEventBus, EXTENSION_RPC_EVENT_CHANNEL } from "../event-bus.js";
9
+ import { inheritMcpMrtrPresentation } from "../mcp-mrtr-presentations.js";
9
10
  import { bindQuestionInput } from "../question-input.js";
10
11
  import { DEFAULT_EXTENSION_EVENT_HANDLER_TIMEOUT_MS, DEFAULT_EXTENSION_EVENT_HANDLER_WARN_MS, DEFAULT_SESSION_SHUTDOWN_HANDLER_TIMEOUT_MS, DEFAULT_SESSION_SHUTDOWN_HANDLER_WARN_MS, DEFAULT_SESSION_SHUTDOWN_QUIT_BUDGET_MS, SettingsManager, } from "../settings-manager.js";
11
12
  import { goalFilePath } from "./builtin/goal/persistence.js";
@@ -589,7 +590,7 @@ export class ExtensionRunner {
589
590
  }
590
591
  wrapUIPromptContext(ui) {
591
592
  const questionFn = ui.question;
592
- return {
593
+ return inheritMcpMrtrPresentation(ui, {
593
594
  ...ui,
594
595
  select: (title, options, opts) => this.withUIPrompt("select", title, () => ui.select(title, options, opts)),
595
596
  confirm: (title, message, opts) => this.withUIPrompt("confirm", title, () => ui.confirm(title, message, opts)),
@@ -601,7 +602,7 @@ export class ExtensionRunner {
601
602
  question: (request, opts) => this.withUIPrompt("question", request.questions[0]?.header, () => questionFn(request, opts), request.requestId),
602
603
  }
603
604
  : {}),
604
- };
605
+ });
605
606
  }
606
607
  withUIPrompt(kind, title, run, requestId = crypto.randomUUID()) {
607
608
  const outerPrompt = this.uiPromptDepth++ === 0;
@@ -59,6 +59,8 @@ export type CompactionReason = "manual" | "threshold" | "overflow" | "pre_prompt
59
59
  export type CompactionRejectionCause = "cancelled-by-extension" | "external-owner" | "would-overflow" | "circuit-breaker" | "per-turn-cap" | "stale-revision";
60
60
  /** Options for extension UI dialogs. */
61
61
  export interface ExtensionUIDialogOptions {
62
+ /** Private local dialog: emits no generic blocked event or pending marker. Not an RPC field. */
63
+ ephemeral?: boolean;
62
64
  /** AbortSignal to programmatically dismiss the dialog. */
63
65
  signal?: AbortSignal;
64
66
  /** Timeout in milliseconds. Dialog auto-dismisses with live countdown display. */
@@ -39,6 +39,7 @@ export declare class LifecycleLedger {
39
39
  readonly waits: LifecycleWaits;
40
40
  readonly holds: readonly LifecycleHold[];
41
41
  readonly failure?: SessionLifecycle["failure"];
42
+ readonly lastStop?: SessionLifecycle["lastStop"];
42
43
  }): void;
43
44
  checkpoint(): void;
44
45
  /** Publishes a revision or epoch that no record carries yet, once. */
@@ -80,6 +80,9 @@ export class LifecycleLedger {
80
80
  startRun(origin) {
81
81
  this.lastTurnEnd = undefined;
82
82
  this.openEpoch(origin);
83
+ // The previous run's stop never describes this one (senpi#3864).
84
+ const { lastStop: _lastStop, ...current } = this.current;
85
+ this.current = current;
83
86
  this.runId = crypto.randomUUID();
84
87
  this.runActive = true;
85
88
  this.admissionWriteFailed = false;
@@ -134,7 +137,7 @@ export class LifecycleLedger {
134
137
  if (input.holds.length > 0 || input.state === "failed")
135
138
  this.request = undefined;
136
139
  const terminal = input.state === "done" || input.state === "failed";
137
- const { completionId: _id, completion: _completion, failure: _failure, settleDecision: _decision, ...base } = this.current;
140
+ const { completionId: _id, completion: _completion, failure: _failure, settleDecision: _decision, lastStop: _lastStop, ...base } = this.current;
138
141
  const now = new Date().toISOString();
139
142
  const standingRequest = this.request?.epoch === base.epoch &&
140
143
  this.request.revision === base.humanInputRevision &&
@@ -163,6 +166,7 @@ export class LifecycleLedger {
163
166
  }
164
167
  : {}),
165
168
  ...(input.state === "failed" && input.failure ? { failure: input.failure } : {}),
169
+ ...(input.lastStop ? { lastStop: input.lastStop } : {}),
166
170
  ...(input.state === "done" && standingRequest
167
171
  ? {
168
172
  settleDecision: this.current.settleDecision ?? { decisionId: crypto.randomUUID() },
@@ -238,7 +242,8 @@ function equivalent(previous, next) {
238
242
  previous.selfSettleEligible === next.selfSettleEligible &&
239
243
  JSON.stringify(previous.waitingOn) === JSON.stringify(next.waitingOn) &&
240
244
  JSON.stringify(previous.holds) === JSON.stringify(next.holds) &&
241
- JSON.stringify(previous.failure) === JSON.stringify(next.failure));
245
+ JSON.stringify(previous.failure) === JSON.stringify(next.failure) &&
246
+ JSON.stringify(previous.lastStop) === JSON.stringify(next.lastStop));
242
247
  }
243
248
  function wireSnapshot(record) {
244
249
  const { sessionId: _sessionId, interrupted: _interrupted, completedGoalId: _goal, completedRunId: _completedRun, lastTurnEnd: _lastTurnEnd, attentionSeq: _attentionSeq, synthetic: _synthetic, epochSealed: _sealed, settleRequest: _request, runId: _runId, runActive: _runActive, ...snapshot } = record;
@@ -1,5 +1,6 @@
1
1
  import { readHumanWait } from "./lifecycle-attention.js";
2
2
  import { readLifecycleState } from "./lifecycle-children.js";
3
+ import { readLastStop } from "./lifecycle-stop.js";
3
4
  import { isRecord } from "./work-custody-record.js";
4
5
  /** Restore the wire snapshot and its private high-water marks from owned JSONL. */
5
6
  export function readLifecycleRecord(value) {
@@ -33,6 +34,7 @@ export function readLifecycleRecord(value) {
33
34
  const completion = readCompletion(value.completion);
34
35
  const failure = readFailure(value.failure);
35
36
  const lastTurnEnd = readTurnEnd(value.lastTurnEnd);
37
+ const lastStop = readLastStop(value.lastStop, value.runId);
36
38
  return {
37
39
  sessionId: value.sessionId,
38
40
  state,
@@ -63,6 +65,7 @@ export function readLifecycleRecord(value) {
63
65
  ...(completion ? { completion } : {}),
64
66
  ...(failure ? { failure } : {}),
65
67
  ...(lastTurnEnd ? { lastTurnEnd } : {}),
68
+ ...(lastStop ? { lastStop } : {}),
66
69
  ...(isRecord(value.settleDecision) && typeof value.settleDecision.decisionId === "string"
67
70
  ? { settleDecision: { decisionId: value.settleDecision.decisionId } }
68
71
  : {}),
@@ -35,6 +35,9 @@ export function readLifecycleRuntimeFacts(session) {
35
35
  });
36
36
  }
37
37
  const holds = session.holds.flatMap((hold) => {
38
+ // The interruption gate is a human wait, not a user Stop. Preserve unattended and real Stop precedence.
39
+ if (hold.kind === "session_hold" && hold.key === "interrupted-run")
40
+ return [];
38
41
  switch (hold.kind) {
39
42
  case "unattended":
40
43
  case "session_hold":
@@ -5,6 +5,7 @@ import type { ExtensionRunner } from "./extensions/runner.ts";
5
5
  import type { AgentEndEvent } from "./extensions/types.ts";
6
6
  import { SessionLifecycleController } from "./session-lifecycle.ts";
7
7
  import type { SessionLifecycleEvent } from "./session-lifecycle-types.ts";
8
+ import type { TurnDeadlineKind } from "./turn-deadline.ts";
8
9
  interface LifecycleBindingHost {
9
10
  append(type: string, data: unknown): string;
10
11
  emit(event: SessionLifecycleEvent): void;
@@ -20,6 +21,11 @@ export declare class LifecycleSessionBinding {
20
21
  private readonly unsubscribeAdmission;
21
22
  private readonly unsubscribeGoal;
22
23
  private lastEnd;
24
+ /** The run each emitted agent_end belonged to, read when it was emitted, not when its queue reaches it. */
25
+ private readonly boundEnds;
26
+ private lastEndRunId;
27
+ /** Captured once inside the bound end's own extension-processing window, never re-scanned by a later run. */
28
+ private lastEndGuard;
23
29
  private refreshGeneration;
24
30
  private restored;
25
31
  private nextRunOrigin;
@@ -32,9 +38,13 @@ export declare class LifecycleSessionBinding {
32
38
  prepareRun(): void;
33
39
  get isRestored(): boolean;
34
40
  startRun(): void;
41
+ deadlineStopped(kind: TurnDeadlineKind, atMs: number): void;
42
+ deadlineStopWriteFailed(): void;
43
+ bindTurnEnd(end: object): void;
35
44
  consume(message: AgentMessage): string | undefined;
36
45
  consumedEntry(message: AgentMessage): string | undefined;
37
- turnEnded(event: AgentEndEvent): Promise<void>;
46
+ /** `event` is the extension agent_end, never the session-level stamp; `end` is the core event bound at emission. */
47
+ turnEnded(event: AgentEndEvent, end: object, guardStartLeafId?: string | null): Promise<void>;
38
48
  refreshFacts(changedGoal?: Goal | null): Promise<void>;
39
49
  idle(): Promise<void>;
40
50
  restore(): Promise<void>;
@@ -8,11 +8,14 @@ import { readLifecycleGoal } from "./lifecycle-goal.js";
8
8
  import { lifecycleHistory } from "./lifecycle-history.js";
9
9
  import { bindLifecycleHumanInput } from "./lifecycle-human-input.js";
10
10
  import { readLifecycleRuntimeFacts } from "./lifecycle-runtime-facts.js";
11
+ import { goalGuardAfter } from "./lifecycle-stop.js";
11
12
  import { SessionLifecycleController } from "./session-lifecycle.js";
12
13
  import { isRecord } from "./work-custody-record.js";
13
14
  /** Attaches durable lifecycle to the real session event/admission boundaries. */
14
15
  export class LifecycleSessionBinding {
15
16
  constructor(session, host) {
17
+ /** The run each emitted agent_end belonged to, read when it was emitted, not when its queue reaches it. */
18
+ this.boundEnds = new WeakMap();
16
19
  this.refreshGeneration = 0;
17
20
  this.restored = false;
18
21
  /** After dispose() nothing the controller decides is written or emitted: the session file is no longer this session's. */
@@ -44,8 +47,12 @@ export class LifecycleSessionBinding {
44
47
  });
45
48
  // A crashed receiver may have durable custody but no transcript entry yet. Recovery owns that
46
49
  // delivery already; register it before session_start can let the gateway enqueue a second carrier.
47
- const deliveryEntries = session.sessionManager.reduceHistory(lifecycleDeliveryEntries);
50
+ const deliveries = session.sessionManager.reduceHistory(lifecycleDeliveryEntries);
48
51
  for (const record of this.controller.obligations.active) {
52
+ // Discover consumed work before session_start can queue another automatic turn. The later
53
+ // recovery pass still writes interruption/tool records, but must not be the first to close admission.
54
+ if (record.ticketId && (record.state === "consumed" || deliveries.has(record.ticketId)))
55
+ this.controller.ledger.interrupted = true;
49
56
  if (record.state !== "queued" || !record.payload || !record.ticketId)
50
57
  continue;
51
58
  const ticketId = record.ticketId;
@@ -53,7 +60,7 @@ export class LifecycleSessionBinding {
53
60
  const deliveryId = deliveryIdOf(message);
54
61
  if (deliveryId === undefined)
55
62
  continue;
56
- if (deliveryEntries.has(ticketId))
63
+ if (deliveries.has(ticketId))
57
64
  session.externalAdmission.observePersisted(message);
58
65
  else
59
66
  session.externalAdmission.restoreQueued(deliveryId, () =>
@@ -98,10 +105,20 @@ export class LifecycleSessionBinding {
98
105
  }
99
106
  startRun() {
100
107
  this.lastEnd = undefined;
108
+ this.lastEndGuard = undefined;
101
109
  const origin = this.nextRunOrigin;
102
110
  this.nextRunOrigin = undefined;
103
111
  this.controller.startRun(origin);
104
112
  }
113
+ deadlineStopped(kind, atMs) {
114
+ this.controller.deadlineStopped(kind, atMs);
115
+ }
116
+ deadlineStopWriteFailed() {
117
+ this.controller.deadlineStopWriteFailed();
118
+ }
119
+ bindTurnEnd(end) {
120
+ this.boundEnds.set(end, this.controller.ledger.runId);
121
+ }
105
122
  consume(message) {
106
123
  return this.write(() => consumeLifecycleDelivery(this.session, this.controller, message));
107
124
  }
@@ -111,8 +128,14 @@ export class LifecycleSessionBinding {
111
128
  const ticketId = messageTicket(message);
112
129
  return ticketId ? this.session.sessionManager.reduceHistory(lifecycleDeliveryEntries).get(ticketId) : undefined;
113
130
  }
114
- async turnEnded(event) {
131
+ /** `event` is the extension agent_end, never the session-level stamp; `end` is the core event bound at emission. */
132
+ async turnEnded(event, end, guardStartLeafId) {
115
133
  this.lastEnd = event;
134
+ this.lastEndRunId = this.boundEnds.get(end);
135
+ this.lastEndGuard =
136
+ guardStartLeafId === undefined
137
+ ? undefined
138
+ : goalGuardAfter(this.session.sessionManager.getBranch(), guardStartLeafId);
116
139
  await this.refreshFacts();
117
140
  }
118
141
  async refreshFacts(changedGoal) {
@@ -162,7 +185,15 @@ export class LifecycleSessionBinding {
162
185
  deadlineStop: input.deadlineStop || saved.deadlineStop,
163
186
  providerRetryAtMs: input.providerRetryAtMs ?? saved.providerRetryAtMs,
164
187
  };
165
- this.controller.goalFacts(goal, input);
188
+ // Stop facts belong to the run that just ended in this process; a restored end carries its recorded stop.
189
+ const stop = this.lastEnd
190
+ ? {
191
+ runId: this.lastEndRunId,
192
+ guard: this.lastEndGuard,
193
+ todo: collected?.todoOutcome,
194
+ }
195
+ : undefined;
196
+ this.controller.goalFacts(goal, input, stop);
166
197
  }
167
198
  async idle() {
168
199
  await this.refreshFacts();
@@ -0,0 +1,41 @@
1
+ import type { TodoOwedOutcome } from "./extensions/builtin/goal/todo-owed-backstop.ts";
2
+ import type { TurnEndLivenessInput } from "./extensions/builtin/goal/turn-end-liveness.ts";
3
+ import type { LifecycleStop } from "./session-lifecycle-types.ts";
4
+ import type { TurnDeadlineKind } from "./turn-deadline.ts";
5
+ /** Stop facts the stopping run produced itself, read from that run's own end (senpi#3864). */
6
+ export interface RunStopFacts {
7
+ /** The run the turn end belonged to when its agent_end was emitted. */
8
+ readonly runId?: string;
9
+ readonly guard?: {
10
+ readonly reason: string;
11
+ readonly at: number;
12
+ };
13
+ readonly todo?: TodoOwedOutcome;
14
+ }
15
+ /** A deadline captured when it fired, before its durable write, for the run it stopped. */
16
+ export interface DeadlineStopCapture {
17
+ readonly runId: string;
18
+ readonly kind: TurnDeadlineKind;
19
+ readonly at: string;
20
+ readonly persisted?: false;
21
+ }
22
+ type StopDetail = Omit<LifecycleStop, "runId" | "epoch" | "at"> & {
23
+ readonly at?: string;
24
+ };
25
+ /**
26
+ * First match wins: the run's own deadline explains its system abort, a guard explains a refused continuation, then
27
+ * the classified abort, and `no_wake` only for a stall no hold or guard owns. Provider failures keep the failed path.
28
+ */
29
+ export declare function stopDetail(input: TurnEndLivenessInput | undefined, facts: RunStopFacts | undefined, deadline: DeadlineStopCapture | undefined, stalled: boolean): StopDetail | undefined;
30
+ export declare function sameStop(stop: LifecycleStop, detail: StopDetail): boolean;
31
+ /** The latest guard appended within one bound end's extension-processing window, including legacy entries. */
32
+ export declare function goalGuardAfter(branch: readonly {
33
+ readonly id: string;
34
+ readonly type: string;
35
+ readonly customType?: string;
36
+ readonly data?: unknown;
37
+ }[], guardStartLeafId: string | null): RunStopFacts["guard"];
38
+ /** Malformed optional detail is dropped; it never invalidates the record that carries it. */
39
+ export declare function readLastStop(value: unknown, runId: unknown): LifecycleStop | undefined;
40
+ export {};
41
+ //# sourceMappingURL=lifecycle-stop.d.ts.map
@@ -0,0 +1,92 @@
1
+ import { isRecord } from "./work-custody-record.js";
2
+ const ABORT_SOURCES = {
3
+ "user-abort": "user",
4
+ "system-abort": "system",
5
+ "provider-abort": "provider",
6
+ "unknown-abort": null,
7
+ };
8
+ /**
9
+ * First match wins: the run's own deadline explains its system abort, a guard explains a refused continuation, then
10
+ * the classified abort, and `no_wake` only for a stall no hold or guard owns. Provider failures keep the failed path.
11
+ */
12
+ export function stopDetail(input, facts, deadline, stalled) {
13
+ if (input?.endedBy === "provider-error" || input?.endedBy === "policy-rejection")
14
+ return undefined;
15
+ if (deadline)
16
+ return {
17
+ cause: "deadline",
18
+ deadline: deadline.kind,
19
+ at: deadline.at,
20
+ ...(deadline.persisted === false ? { persisted: false } : {}),
21
+ };
22
+ if (facts?.guard)
23
+ return { cause: "goal_guard", guard: facts.guard.reason, at: new Date(facts.guard.at).toISOString() };
24
+ if (facts?.todo === "capped" || facts?.todo === "cap")
25
+ return { cause: "todo_guard", todo: "cap" };
26
+ const abortSource = input ? ABORT_SOURCES[input.endedBy] : undefined;
27
+ if (abortSource !== undefined)
28
+ return { cause: "aborted", ...(abortSource === null ? {} : { abortSource }) };
29
+ if (stalled && !input?.deadlineStop && !input?.budgetSpent)
30
+ return { cause: "no_wake" };
31
+ return undefined;
32
+ }
33
+ export function sameStop(stop, detail) {
34
+ return (stop.cause === detail.cause &&
35
+ stop.deadline === detail.deadline &&
36
+ stop.guard === detail.guard &&
37
+ stop.todo === detail.todo &&
38
+ stop.abortSource === detail.abortSource &&
39
+ stop.persisted === detail.persisted);
40
+ }
41
+ /** The latest guard appended within one bound end's extension-processing window, including legacy entries. */
42
+ export function goalGuardAfter(branch, guardStartLeafId) {
43
+ const start = guardStartLeafId === null ? -1 : branch.findIndex((entry) => entry.id === guardStartLeafId);
44
+ if (guardStartLeafId !== null && start < 0)
45
+ return undefined;
46
+ for (let index = branch.length - 1; index > start; index--) {
47
+ const entry = branch[index];
48
+ if (entry?.type !== "custom" || entry.customType !== "goal-continuation-stopped" || !isRecord(entry.data))
49
+ continue;
50
+ const { reason, at } = entry.data;
51
+ return typeof reason === "string" && typeof at === "number" ? { reason, at } : undefined;
52
+ }
53
+ return undefined;
54
+ }
55
+ /** Malformed optional detail is dropped; it never invalidates the record that carries it. */
56
+ export function readLastStop(value, runId) {
57
+ if (!isRecord(value) ||
58
+ typeof value.runId !== "string" ||
59
+ value.runId !== runId ||
60
+ typeof value.at !== "string" ||
61
+ typeof value.epoch !== "number")
62
+ return undefined;
63
+ const base = { runId: value.runId, at: value.at, epoch: value.epoch };
64
+ switch (value.cause) {
65
+ case "deadline":
66
+ return value.deadline === "inactivity" || value.deadline === "max_runtime"
67
+ ? {
68
+ ...base,
69
+ cause: value.cause,
70
+ deadline: value.deadline,
71
+ ...(value.persisted === false ? { persisted: false } : {}),
72
+ }
73
+ : undefined;
74
+ case "goal_guard":
75
+ return { ...base, cause: value.cause, ...(typeof value.guard === "string" ? { guard: value.guard } : {}) };
76
+ case "todo_guard":
77
+ return { ...base, cause: value.cause, todo: "cap" };
78
+ case "aborted":
79
+ return {
80
+ ...base,
81
+ cause: value.cause,
82
+ ...(value.abortSource === "user" || value.abortSource === "system" || value.abortSource === "provider"
83
+ ? { abortSource: value.abortSource }
84
+ : {}),
85
+ };
86
+ case "no_wake":
87
+ return { ...base, cause: value.cause };
88
+ default:
89
+ return undefined;
90
+ }
91
+ }
92
+ //# sourceMappingURL=lifecycle-stop.js.map
@@ -0,0 +1,89 @@
1
+ import type { MrtrInputResponse } from "./extensions/builtin/mcp/modern/elicitation.ts";
2
+ import type { ValidatedMcpFormSchema } from "./extensions/builtin/mcp/modern/form-schema.ts";
3
+ export interface McpFormScope {
4
+ readonly hostInstance: string;
5
+ readonly durableSessionId: string;
6
+ readonly bindingEpoch: string;
7
+ readonly attachmentId: string;
8
+ readonly attachmentEpoch: string;
9
+ readonly connectionId: string;
10
+ readonly connectionEpoch: string;
11
+ readonly presenterPrincipalId: string;
12
+ readonly presenterGeneration: string;
13
+ readonly presentationBindingId: string;
14
+ readonly presenterKind: "desktop" | "gateway" | "local_tui";
15
+ readonly purpose: "mcp_form";
16
+ }
17
+ export interface McpFormEnvelope {
18
+ readonly sessionId: string;
19
+ readonly bindingEpoch: string;
20
+ readonly attachmentEpoch: string;
21
+ readonly grantId: string;
22
+ readonly dialogId: string;
23
+ readonly dialogToken: string;
24
+ readonly formRevision: string;
25
+ }
26
+ export interface McpFormRequest extends McpFormEnvelope {
27
+ readonly type: "mcp_form_request";
28
+ readonly server: {
29
+ readonly label: string;
30
+ readonly source: "user" | "project" | "plugin";
31
+ };
32
+ readonly message: string;
33
+ readonly schema: ValidatedMcpFormSchema;
34
+ readonly deadlineAtMs: number;
35
+ }
36
+ export interface McpFormResolved {
37
+ readonly type: "mcp_form_resolved";
38
+ readonly sessionId: string;
39
+ readonly grantId: string;
40
+ readonly dialogId: string;
41
+ readonly reason: "answered" | "declined" | "cancelled" | "processing_failed" | "operation_aborted";
42
+ }
43
+ export type McpFormOutcome = ({
44
+ readonly kind: "result";
45
+ readonly result: MrtrInputResponse;
46
+ } | {
47
+ readonly kind: "processing_failure" | "operation_aborted";
48
+ }) & ({
49
+ readonly source: "presenter_action";
50
+ readonly humanAdmission: true;
51
+ } | {
52
+ readonly source: "host_termination";
53
+ readonly humanAdmission: false;
54
+ });
55
+ export interface McpFormInput {
56
+ readonly operationId: string;
57
+ readonly inputId: string;
58
+ readonly round: number;
59
+ readonly server: McpFormRequest["server"];
60
+ readonly message: string;
61
+ readonly schema: ValidatedMcpFormSchema;
62
+ readonly operationSignal: AbortSignal;
63
+ /** Host reads a worker-owned atomic abort fence at the terminal latch, without an IPC round trip. */
64
+ readonly operationCurrent?: () => boolean;
65
+ readonly presentationSignal: AbortSignal;
66
+ /** Remaining UI budget, measured by the host's monotonic clock. */
67
+ readonly deadlineMs: number;
68
+ }
69
+ export interface McpFormPresentation {
70
+ readonly signal: AbortSignal;
71
+ readonly ready: () => boolean;
72
+ readonly present: (input: McpFormInput) => Promise<McpFormOutcome>;
73
+ /** Worker-backed presenters recheck host readiness before each network attempt. */
74
+ readonly synchronize?: () => Promise<void>;
75
+ }
76
+ export interface McpFormBinding {
77
+ readonly epoch: string;
78
+ readonly signal: AbortSignal;
79
+ readonly capture: () => McpFormPresentation | undefined;
80
+ readonly dispose: () => void;
81
+ readonly version?: () => string | undefined;
82
+ readonly subscribe?: (listener: () => void) => () => void;
83
+ }
84
+ export declare function bindMcpFormPresented(options: object, callback: (() => void) | undefined): void;
85
+ export declare function markMcpFormPresented(options: object | undefined): void;
86
+ /** An enforced binding is part of the host-issued presenter identity, not a UI capability declaration. */
87
+ export declare function bindPrivateMcpForms(ui: object, binding: McpFormBinding): void;
88
+ export declare function privateMcpForms(ui: object): McpFormBinding | undefined;
89
+ //# sourceMappingURL=mcp-form-types.d.ts.map
@@ -0,0 +1,23 @@
1
+ import { getMcpMrtrPresentation, registerMcpMrtrPresentation } from "./mcp-mrtr-presentations.js";
2
+ const presentedCallbacks = new WeakMap();
3
+ export function bindMcpFormPresented(options, callback) {
4
+ if (callback)
5
+ presentedCallbacks.set(options, callback);
6
+ else
7
+ presentedCallbacks.delete(options);
8
+ }
9
+ export function markMcpFormPresented(options) {
10
+ if (!options)
11
+ return;
12
+ const callback = presentedCallbacks.get(options);
13
+ presentedCallbacks.delete(options);
14
+ callback?.();
15
+ }
16
+ /** An enforced binding is part of the host-issued presenter identity, not a UI capability declaration. */
17
+ export function bindPrivateMcpForms(ui, binding) {
18
+ registerMcpMrtrPresentation(ui, binding.signal, undefined, binding);
19
+ }
20
+ export function privateMcpForms(ui) {
21
+ return getMcpMrtrPresentation(ui)?.privateForms;
22
+ }
23
+ //# sourceMappingURL=mcp-form-types.js.map
@@ -0,0 +1,15 @@
1
+ import type { ExtensionUIContext } from "./extensions/types.ts";
2
+ import type { McpFormBinding } from "./mcp-form-types.ts";
3
+ /** Local host references only. RPC capabilities and serialized UI objects cannot enter this registry. */
4
+ export interface McpMrtrPresentation {
5
+ readonly ui: Pick<ExtensionUIContext, "select" | "input">;
6
+ readonly signal: AbortSignal;
7
+ readonly admit?: () => void;
8
+ readonly presented?: () => void;
9
+ readonly privateForms?: McpFormBinding;
10
+ }
11
+ export declare function registerMcpMrtrPresentation(ui: ExtensionUIContext, signal: AbortSignal, admit?: () => void, privateForms?: McpFormBinding): ExtensionUIContext;
12
+ export declare function getMcpMrtrPresentation(ui: object): McpMrtrPresentation | undefined;
13
+ /** Preserve host identity across runner wrapping; private dialogs still use the raw host UI. */
14
+ export declare function inheritMcpMrtrPresentation(source: ExtensionUIContext, wrapper: ExtensionUIContext): ExtensionUIContext;
15
+ //# sourceMappingURL=mcp-mrtr-presentations.d.ts.map