@code-yeongyu/senpi 2026.10.25-2 → 2026.10.25-4

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 +37 -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-XBUSVKWC.js → chunk-2RB5EL4X.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-KUE4AQN6.js → chunk-3QCYBOKN.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-2EFVQWXZ.js → chunk-4MOZXOJX.js} +5 -5
  7. package/dist/bundle/chunks/{chunk-MXZPAUFB.js → chunk-5JJDR6XI.js} +1 -1
  8. package/dist/bundle/chunks/chunk-6VWIMHMH.js +2 -0
  9. package/dist/bundle/chunks/{chunk-Y5OTHMXO.js → chunk-7CNOCJCN.js} +1 -1
  10. package/dist/bundle/chunks/chunk-AX72VNRS.js +27 -0
  11. package/dist/bundle/chunks/{chunk-VNN2V74Y.js → chunk-BZ7726QO.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-J2U5VDG7.js → chunk-C2MQ4QXN.js} +30 -30
  13. package/dist/bundle/chunks/{chunk-JRXKLIEQ.js → chunk-DODH3N6U.js} +5 -5
  14. package/dist/bundle/chunks/{chunk-EHQXLHRB.js → chunk-E6D4RIT7.js} +2 -2
  15. package/dist/bundle/chunks/chunk-J37GRED2.js +5 -0
  16. package/dist/bundle/chunks/chunk-JMOUU75H.js +2 -0
  17. package/dist/bundle/chunks/chunk-JPGT64RV.js +2 -0
  18. package/dist/bundle/chunks/chunk-KKD4CYCI.js +3 -0
  19. package/dist/bundle/chunks/{chunk-YN7ELQ43.js → chunk-KNVNKDER.js} +1 -1
  20. package/dist/bundle/chunks/chunk-KXB5WIS5.js +4 -0
  21. package/dist/bundle/chunks/chunk-L73UYAH5.js +3 -0
  22. package/dist/bundle/chunks/{chunk-G2UETS2R.js → chunk-LOIF6PHI.js} +4 -4
  23. package/dist/bundle/chunks/chunk-LTU6UFEX.js +2 -0
  24. package/dist/bundle/chunks/chunk-N7IN46Q5.js +27 -0
  25. package/dist/bundle/chunks/{chunk-LYGAV6KU.js → chunk-PT6JJUN2.js} +1 -1
  26. package/dist/bundle/chunks/{chunk-WFXUCVIR.js → chunk-SBGIXMX3.js} +2 -5
  27. package/dist/bundle/chunks/{chunk-W2BKZBYJ.js → chunk-STMXIBXF.js} +2 -2
  28. package/dist/bundle/chunks/chunk-TBU6VYHN.js +13 -0
  29. package/dist/bundle/chunks/{chunk-RXKNOEQQ.js → chunk-V2UYXTRR.js} +1 -1
  30. package/dist/bundle/chunks/{chunk-U67ML6CQ.js → chunk-WD6WBYC5.js} +1 -1
  31. package/dist/bundle/chunks/chunk-WJOT3DFL.js +2 -0
  32. package/dist/bundle/chunks/chunk-XMJKZK5H.js +2 -0
  33. package/dist/bundle/chunks/chunk-YI7SPFDN.js +5 -0
  34. package/dist/bundle/chunks/cli-main-YYH3IBC6.js +3 -0
  35. package/dist/bundle/chunks/{client-KWTLTKVV.js → client-WTYEICLH.js} +3 -3
  36. package/dist/bundle/chunks/dispatch-MXG5YWLV.js +9 -0
  37. package/dist/bundle/chunks/{help-fast-path-ZHZGXAUG.js → help-fast-path-3Z26DQSV.js} +1 -1
  38. package/dist/bundle/chunks/host-command-TDLLHXVI.js +2 -0
  39. package/dist/bundle/chunks/host-lifecycle-3VHHIGDL.js +3 -0
  40. package/dist/bundle/chunks/host-lifecycle.js +8 -6
  41. package/dist/bundle/chunks/host-session-command-56XF6ACS.js +20 -0
  42. package/dist/bundle/chunks/{host-socket-request-AS2OCRLP.js → host-socket-request-QL2WEZTN.js} +1 -1
  43. package/dist/bundle/chunks/interactive-mode-OEPULLZK.js +2 -0
  44. package/dist/bundle/chunks/mcp-presenter-bootstrap-XUSCQVRC.js +2 -0
  45. package/dist/bundle/chunks/{multi-session-host-VTTY5MFT.js → multi-session-host-Z2CW4SDM.js} +12 -9
  46. package/dist/bundle/chunks/{package-manager-cli-DZFOC3MF.js → package-manager-cli-N55U6PEO.js} +1 -1
  47. package/dist/bundle/chunks/rotation-stream-CBGCBYHV.js +2 -0
  48. package/dist/bundle/chunks/rpc-mode-ETXQWEB2.js +2 -0
  49. package/dist/bundle/chunks/{rpc-monitor-source-B5NQ4ZVH.js → rpc-monitor-source-AE3C6EQV.js} +1 -1
  50. package/dist/bundle/chunks/{schedule-command-SLISTG4V.js → schedule-command-VS7CZC2J.js} +1 -1
  51. package/dist/bundle/chunks/session-control-endpoint-4LEIDIVI.js +3 -0
  52. package/dist/bundle/chunks/session-picker-E4KUJDLO.js +2 -0
  53. package/dist/bundle/chunks/session-worker.js +292 -278
  54. package/dist/bundle/chunks/{stdio-IEC635SY.js → stdio-YXDKKKQZ.js} +1 -1
  55. package/dist/bundle/chunks/{streamableHttp-KJI5KE4K.js → streamableHttp-DKIBUWVY.js} +1 -1
  56. package/dist/bundle/chunks/{types-LLNV23LW.js → types-BJUEQ4II.js} +1 -1
  57. package/dist/bundle/chunks/zod-compat-RJDXCU7S.js +2 -0
  58. package/dist/bundle/cli.js +2 -2
  59. package/dist/bundle/host-supervisor.js +8 -6
  60. package/dist/bundle/index.js +1 -1
  61. package/dist/bundle/rpc-entry.js +1 -1
  62. package/dist/bundle/runtime-manifest.json +1 -1
  63. package/dist/cli/args.d.ts +2 -0
  64. package/dist/cli/args.js +9 -0
  65. package/dist/cli/host-command-args.d.ts +1 -0
  66. package/dist/cli/host-command-args.js +7 -1
  67. package/dist/cli/host-command.js +3 -1
  68. package/dist/core/agent-session.js +22 -7
  69. package/dist/core/extensions/builtin/goal/index.js +1 -0
  70. package/dist/core/extensions/builtin/goal/lifecycle-facts.d.ts +3 -0
  71. package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -1
  72. package/dist/core/extensions/builtin/mcp/catalog.js +2 -0
  73. package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -0
  74. package/dist/core/extensions/builtin/mcp/connection.js +46 -1
  75. package/dist/core/extensions/builtin/mcp/elicitation.d.ts +6 -1
  76. package/dist/core/extensions/builtin/mcp/elicitation.js +54 -2
  77. package/dist/core/extensions/builtin/mcp/expose/call.js +6 -2
  78. package/dist/core/extensions/builtin/mcp/expose/session.d.ts +4 -1
  79. package/dist/core/extensions/builtin/mcp/expose/session.js +7 -0
  80. package/dist/core/extensions/builtin/mcp/health.js +5 -0
  81. package/dist/core/extensions/builtin/mcp/host-registry.js +7 -1
  82. package/dist/core/extensions/builtin/mcp/index.js +2 -0
  83. package/dist/core/extensions/builtin/mcp/modern/client.d.ts +5 -0
  84. package/dist/core/extensions/builtin/mcp/modern/client.js +15 -0
  85. package/dist/core/extensions/builtin/mcp/modern/consent-queue.d.ts +2 -0
  86. package/dist/core/extensions/builtin/mcp/modern/consent-queue.js +59 -0
  87. package/dist/core/extensions/builtin/mcp/modern/diagnostics.d.ts +4 -0
  88. package/dist/core/extensions/builtin/mcp/modern/diagnostics.js +39 -0
  89. package/dist/core/extensions/builtin/mcp/modern/dispatch.js +21 -11
  90. package/dist/core/extensions/builtin/mcp/modern/elicitation.d.ts +14 -0
  91. package/dist/core/extensions/builtin/mcp/modern/elicitation.js +145 -0
  92. package/dist/core/extensions/builtin/mcp/modern/errors.d.ts +21 -0
  93. package/dist/core/extensions/builtin/mcp/modern/errors.js +23 -0
  94. package/dist/core/extensions/builtin/mcp/modern/form-schema.d.ts +20 -0
  95. package/dist/core/extensions/builtin/mcp/modern/form-schema.js +153 -0
  96. package/dist/core/extensions/builtin/mcp/modern/http-fetch.d.ts +3 -1
  97. package/dist/core/extensions/builtin/mcp/modern/http-fetch.js +8 -2
  98. package/dist/core/extensions/builtin/mcp/modern/inputs.d.ts +11 -0
  99. package/dist/core/extensions/builtin/mcp/modern/inputs.js +65 -0
  100. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +11 -0
  101. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +109 -0
  102. package/dist/core/extensions/builtin/mcp/modern/request.d.ts +20 -0
  103. package/dist/core/extensions/builtin/mcp/modern/request.js +292 -0
  104. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.d.ts +1 -1
  105. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +54 -6
  106. package/dist/core/extensions/builtin/mcp/modern/transport.js +80 -14
  107. package/dist/core/extensions/builtin/mcp/modern/wire.js +11 -7
  108. package/dist/core/extensions/builtin/mcp/prompts.d.ts +5 -1
  109. package/dist/core/extensions/builtin/mcp/prompts.js +6 -1
  110. package/dist/core/extensions/builtin/mcp/request-origin.d.ts +47 -0
  111. package/dist/core/extensions/builtin/mcp/request-origin.js +59 -0
  112. package/dist/core/extensions/builtin/mcp/resources.d.ts +4 -1
  113. package/dist/core/extensions/builtin/mcp/resources.js +19 -4
  114. package/dist/core/extensions/builtin/mcp/sdk.lazy.d.ts +3 -1
  115. package/dist/core/extensions/builtin/mcp/sdk.lazy.js +3 -1
  116. package/dist/core/extensions/builtin/mcp/service-register.d.ts +4 -1
  117. package/dist/core/extensions/builtin/mcp/service-register.js +3 -0
  118. package/dist/core/extensions/builtin/mcp/service-types.d.ts +2 -1
  119. package/dist/core/extensions/builtin/mcp/service.d.ts +1 -0
  120. package/dist/core/extensions/builtin/mcp/service.js +79 -4
  121. package/dist/core/extensions/builtin/mcp/shared-connection.js +36 -2
  122. package/dist/core/extensions/builtin/mcp/shared-lease.d.ts +1 -0
  123. package/dist/core/extensions/builtin/mcp/shared-lease.js +9 -0
  124. package/dist/core/extensions/builtin/mcp/transport-sdk.d.ts +3 -2
  125. package/dist/core/extensions/builtin/mcp/transport-sdk.js +56 -25
  126. package/dist/core/extensions/builtin/mcp/wrap.d.ts +7 -0
  127. package/dist/core/extensions/builtin/mcp/wrap.js +6 -0
  128. package/dist/core/extensions/runner.js +3 -2
  129. package/dist/core/extensions/types.d.ts +2 -0
  130. package/dist/core/lifecycle-ledger.d.ts +1 -0
  131. package/dist/core/lifecycle-ledger.js +7 -2
  132. package/dist/core/lifecycle-record.js +3 -0
  133. package/dist/core/lifecycle-session-binding.d.ts +11 -1
  134. package/dist/core/lifecycle-session-binding.js +29 -2
  135. package/dist/core/lifecycle-stop.d.ts +41 -0
  136. package/dist/core/lifecycle-stop.js +92 -0
  137. package/dist/core/mcp-form-types.d.ts +89 -0
  138. package/dist/core/mcp-form-types.js +23 -0
  139. package/dist/core/mcp-mrtr-presentations.d.ts +15 -0
  140. package/dist/core/mcp-mrtr-presentations.js +17 -0
  141. package/dist/core/session-control-actions.js +0 -2
  142. package/dist/core/session-lifecycle-types.d.ts +15 -0
  143. package/dist/core/session-lifecycle.d.ts +17 -1
  144. package/dist/core/session-lifecycle.js +40 -2
  145. package/dist/main.js +5 -1
  146. package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
  147. package/dist/modes/interactive/components/extension-selector.js +4 -0
  148. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  149. package/dist/modes/interactive/interactive-mode.js +34 -8
  150. package/dist/modes/interactive/mcp-local-presenter.d.ts +5 -0
  151. package/dist/modes/interactive/mcp-local-presenter.js +109 -0
  152. package/dist/modes/interactive/session-control-attach.d.ts +20 -0
  153. package/dist/modes/interactive/session-control-attach.js +13 -0
  154. package/dist/modes/interactive/session-control-commands.js +16 -6
  155. package/dist/modes/interactive/session-control-registry.d.ts +2 -1
  156. package/dist/modes/interactive/session-control-registry.js +3 -2
  157. package/dist/modes/interactive/session-control-rpc-commands.d.ts +4 -2
  158. package/dist/modes/interactive/session-control-rpc-commands.js +114 -4
  159. package/dist/modes/interactive/session-control-server.js +9 -3
  160. package/dist/modes/rpc/connection-handler.d.ts +1 -0
  161. package/dist/modes/rpc/connection-handler.js +13 -41
  162. package/dist/modes/rpc/custom-capability.d.ts +6 -0
  163. package/dist/modes/rpc/custom-capability.js +6 -0
  164. package/dist/modes/rpc/get-entries-command.d.ts +13 -0
  165. package/dist/modes/rpc/get-entries-command.js +61 -0
  166. package/dist/modes/rpc/get-entries-page.d.ts +22 -0
  167. package/dist/modes/rpc/get-entries-page.js +41 -2
  168. package/dist/modes/rpc/host-capabilities.d.ts +1 -0
  169. package/dist/modes/rpc/host-capabilities.js +6 -2
  170. package/dist/modes/rpc/host-ensure-start.js +13 -2
  171. package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
  172. package/dist/modes/rpc/host-ensure.js +1 -0
  173. package/dist/modes/rpc/host-handoff.d.ts +2 -0
  174. package/dist/modes/rpc/host-handover-wire.d.ts +2 -0
  175. package/dist/modes/rpc/host-handover-wire.js +1 -0
  176. package/dist/modes/rpc/host-lifecycle-launch.d.ts +1 -0
  177. package/dist/modes/rpc/host-lifecycle-launch.js +43 -2
  178. package/dist/modes/rpc/host-observe-request.d.ts +2 -1
  179. package/dist/modes/rpc/host-observe-request.js +4 -1
  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-successor.js +11 -1
  183. package/dist/modes/rpc/mcp-form-dialog.d.ts +45 -0
  184. package/dist/modes/rpc/mcp-form-dialog.js +159 -0
  185. package/dist/modes/rpc/mcp-form-worker.d.ts +14 -0
  186. package/dist/modes/rpc/mcp-form-worker.js +167 -0
  187. package/dist/modes/rpc/mcp-presenter-authority.d.ts +53 -0
  188. package/dist/modes/rpc/mcp-presenter-authority.js +309 -0
  189. package/dist/modes/rpc/mcp-presenter-bootstrap.d.ts +8 -0
  190. package/dist/modes/rpc/mcp-presenter-bootstrap.js +111 -0
  191. package/dist/modes/rpc/mcp-presenter-control.d.ts +24 -0
  192. package/dist/modes/rpc/mcp-presenter-control.js +330 -0
  193. package/dist/modes/rpc/multi-session-host.d.ts +3 -0
  194. package/dist/modes/rpc/multi-session-host.js +36 -1
  195. package/dist/modes/rpc/rpc-read-response-types.d.ts +2 -0
  196. package/dist/modes/rpc/rpc-session-command-types.d.ts +8 -1
  197. package/dist/modes/rpc/session-binding.js +6 -1
  198. package/dist/modes/rpc/session-event-writer.d.ts +14 -0
  199. package/dist/modes/rpc/session-event-writer.js +128 -1
  200. package/dist/modes/rpc/session-router-routing.js +1 -0
  201. package/dist/modes/rpc/session-worker-client.d.ts +3 -0
  202. package/dist/modes/rpc/session-worker-client.js +89 -0
  203. package/dist/modes/rpc/session-worker-protocol.d.ts +41 -0
  204. package/dist/modes/rpc/session-worker.js +9 -0
  205. package/docs/extensions.md +5 -0
  206. package/docs/mcp.md +20 -3
  207. package/docs/rpc.md +151 -8
  208. package/package.json +6 -6
  209. package/dist/bundle/chunks/chunk-3LNSLJ7N.js +0 -2
  210. package/dist/bundle/chunks/chunk-B6TPQDBW.js +0 -5
  211. package/dist/bundle/chunks/chunk-CDRFFHLL.js +0 -4
  212. package/dist/bundle/chunks/chunk-HPCS4HM4.js +0 -27
  213. package/dist/bundle/chunks/chunk-KJZAY5YH.js +0 -27
  214. package/dist/bundle/chunks/chunk-MSQCTH6V.js +0 -2
  215. package/dist/bundle/chunks/chunk-OI4NMCKP.js +0 -3
  216. package/dist/bundle/chunks/chunk-SCVRKIFK.js +0 -2
  217. package/dist/bundle/chunks/cli-main-SWKFMTZQ.js +0 -3
  218. package/dist/bundle/chunks/dispatch-DEP4PEDZ.js +0 -10
  219. package/dist/bundle/chunks/host-command-BC6UWP33.js +0 -2
  220. package/dist/bundle/chunks/host-lifecycle-TN5NEGNF.js +0 -3
  221. package/dist/bundle/chunks/host-session-command-MGVBLBRV.js +0 -20
  222. package/dist/bundle/chunks/interactive-mode-SCSROHD5.js +0 -2
  223. package/dist/bundle/chunks/rotation-stream-U7YQMC7H.js +0 -2
  224. package/dist/bundle/chunks/rpc-mode-TOXYD4XA.js +0 -2
  225. package/dist/bundle/chunks/session-control-endpoint-HFFMBALG.js +0 -3
  226. package/dist/bundle/chunks/session-picker-465FVXQG.js +0 -2
@@ -26,11 +26,16 @@ export function parseHostArgs(args) {
26
26
  let root;
27
27
  let agentDir;
28
28
  let keyRoot;
29
+ let presenterBootstrapFd;
29
30
  const handover = {};
30
31
  for (let index = 0; index < rest.length; index++) {
31
32
  const flag = rest[index];
32
33
  const value = rest[index + 1];
33
- if (flag === "--rpc-key-root" && value !== undefined && !value.startsWith("--")) {
34
+ if (flag === "--mcp-presenter-fd" && value === "4" && (subcommand === "ensure" || subcommand === "handoff")) {
35
+ presenterBootstrapFd = 4;
36
+ index++;
37
+ }
38
+ else if (flag === "--rpc-key-root" && value !== undefined && !value.startsWith("--")) {
34
39
  keyRoot = resolve(value);
35
40
  index++;
36
41
  }
@@ -115,6 +120,7 @@ export function parseHostArgs(args) {
115
120
  if (typeof terms === "string")
116
121
  return terms;
117
122
  return {
123
+ ...(presenterBootstrapFd === undefined ? {} : { presenterBootstrapFd }),
118
124
  subcommand,
119
125
  ...(specPath !== undefined && { specPath }),
120
126
  policy,
@@ -28,6 +28,7 @@ import { DEFAULT_HOST_LAUNCH_SPEC, HostLaunchSpecError, loadHostLaunchSpec, } fr
28
28
  import { HOST_EXIT_ERROR, HOST_EXIT_USAGE, runHostRequest, } from "../modes/rpc/host-runner.js";
29
29
  import { parseHostArgs } from "./host-command-args.js";
30
30
  import { emit } from "./host-emit.js";
31
+ import { presenterBootstrapStream } from "../modes/rpc/mcp-presenter-bootstrap.js";
31
32
  export { parseHostArgs } from "./host-command-args.js";
32
33
  const USAGE = `usage: ${APP_NAME} host <ensure|status|stop|handoff|shard-path|gc> [options]
33
34
 
@@ -55,7 +56,8 @@ export async function runHostCommand(args) {
55
56
  }
56
57
  try {
57
58
  const request = await hostRequest(parsed);
58
- const outcome = await runHostRequest(request, parsed.keyRoot === undefined ? {} : { keyRoot: parsed.keyRoot });
59
+ const presenterBootstrap = parsed.presenterBootstrapFd === undefined ? undefined : presenterBootstrapStream(parsed.presenterBootstrapFd);
60
+ const outcome = await runHostRequest(request, { keyRoot: parsed.keyRoot, presenterBootstrap }).finally(() => presenterBootstrap?.destroy());
59
61
  if (parsed.subcommand === "shard-path" && !parsed.json)
60
62
  writeSync(1, `${String(outcome.payload.socket)}\n`);
61
63
  else
@@ -609,6 +609,9 @@ export class AgentSession {
609
609
  this._handleAgentEvent = (event, signal) => {
610
610
  if (event.type === "agent_start")
611
611
  this._lifecycleBinding.startRun();
612
+ // Bound at emission: the core can start the next run before the queue reaches this end (senpi#3864).
613
+ if (event.type === "agent_end")
614
+ this._lifecycleBinding.bindTurnEnd(event);
612
615
  if (event.type === "message_start") {
613
616
  try {
614
617
  const entryId = this._lifecycleBinding.consume(event.message);
@@ -3113,8 +3116,9 @@ export class AgentSession {
3113
3116
  else if (event.type === "agent_end") {
3114
3117
  const extensionEvent = this._abortProvenance.beginAgentEnd(event.messages, agentEndWillRetry, this._findLastAssistantInMessages(event.messages)?.stopReason === "aborted");
3115
3118
  try {
3119
+ const guardStartLeafId = this.sessionManager.getLeafId();
3116
3120
  await this._extensionRunner.emit(extensionEvent);
3117
- await this._lifecycleBinding.turnEnded(extensionEvent);
3121
+ await this._lifecycleBinding.turnEnded(extensionEvent, event, guardStartLeafId);
3118
3122
  }
3119
3123
  finally {
3120
3124
  this._abortProvenance.endAgentEnd(extensionEvent);
@@ -3538,12 +3542,23 @@ export class AgentSession {
3538
3542
  }
3539
3543
  /** Record a deadline stop durably so the goal engine holds off until admitted user input. */
3540
3544
  _recordDeadlineStop(deadline) {
3541
- this._emitEntryAppended(this.sessionManager.appendCustomEntry(TURN_DEADLINE_ENTRY_TYPE, {
3542
- kind: deadline.kind,
3543
- limitMs: deadline.limitMs,
3544
- elapsedMs: deadline.elapsedMs,
3545
- at: Date.now(),
3546
- }));
3545
+ const at = Date.now();
3546
+ // The lifecycle stop is captured before the write, so a refused write keeps it for this run (senpi#3864).
3547
+ this._lifecycleBinding.deadlineStopped(deadline.kind, at);
3548
+ let entryId;
3549
+ try {
3550
+ entryId = this.sessionManager.appendCustomEntry(TURN_DEADLINE_ENTRY_TYPE, {
3551
+ kind: deadline.kind,
3552
+ limitMs: deadline.limitMs,
3553
+ elapsedMs: deadline.elapsedMs,
3554
+ at,
3555
+ });
3556
+ }
3557
+ catch (error) {
3558
+ this._lifecycleBinding.deadlineStopWriteFailed();
3559
+ throw error;
3560
+ }
3561
+ this._emitEntryAppended(entryId);
3547
3562
  }
3548
3563
  /**
3549
3564
  * Get the names of currently active tools.
@@ -137,6 +137,7 @@ export default function goalExtension(pi) {
137
137
  const end = lastAgentEnd ?? restoredEnd;
138
138
  return {
139
139
  goal,
140
+ ...(lastTodoOutcome === undefined ? {} : { todoOutcome: lastTodoOutcome }),
140
141
  ...(end
141
142
  ? {
142
143
  input: collectTurnEndLivenessInput(ctx, end, {
@@ -1,9 +1,12 @@
1
1
  import type { AgentEndEvent } from "../../types.ts";
2
+ import type { TodoOwedOutcome } from "./todo-owed-backstop.ts";
2
3
  import type { TurnEndLivenessInput } from "./turn-end-liveness.ts";
3
4
  import type { Goal } from "./types.ts";
4
5
  export interface GoalLifecycleFacts {
5
6
  readonly goal: Goal | null;
6
7
  readonly input?: TurnEndLivenessInput;
8
+ /** What the todo backstop returned at the last turn end; a repeated `cap` emits no event. */
9
+ readonly todoOutcome?: TodoOwedOutcome;
7
10
  }
8
11
  type Collector = (restoredEnd?: AgentEndEvent, goal?: Goal | null) => Promise<GoalLifecycleFacts>;
9
12
  export declare function onGoalLifecycleChange(owner: object, listener: (goal: Goal | null) => void): () => void;
@@ -5,8 +5,10 @@ import type { ServerConnection } from "./connection.ts";
5
5
  import type { McpOutputArtifacts } from "./guard/output-guard.ts";
6
6
  import type { McpEnsureFreshAuth } from "./health.ts";
7
7
  import type { McpInvocationResolver } from "./invocation.ts";
8
+ import type { McpRequestOrigin } from "./request-origin.ts";
8
9
  type ListedTool = Awaited<ReturnType<Client["listTools"]>>["tools"][number];
9
10
  export interface McpToolCatalogEntry {
11
+ requestOrigin?: () => McpRequestOrigin | undefined;
10
12
  server: string;
11
13
  tool: string;
12
14
  schema: ListedTool["inputSchema"];
@@ -21,7 +23,7 @@ export interface McpToolCatalogEntry {
21
23
  outputGuard?: McpSettings["outputGuard"];
22
24
  invocation?: McpInvocationResolver;
23
25
  }
24
- type McpToolCatalogOptions = Pick<McpToolCatalogEntry, "agentDir" | "artifacts" | "ensureFresh" | "outputGuard" | "invocation">;
26
+ type McpToolCatalogOptions = Pick<McpToolCatalogEntry, "agentDir" | "artifacts" | "ensureFresh" | "outputGuard" | "invocation" | "requestOrigin">;
25
27
  export declare function collectToolCatalog(server: string, connection: ServerConnection, config: McpServerDeclaredConfig, options?: McpToolCatalogOptions): Promise<McpToolCatalogEntry[]>;
26
28
  /** What registering these tools with this catalog's resources and prompts exposes;
27
29
  * equal identities register identical definitions. */
@@ -2,6 +2,7 @@ import { collectAllPages } from "./expose/pagination.js";
2
2
  export async function collectToolCatalog(server, connection, config, options = {}) {
3
3
  const result = await collectAllPages((cursor) => connection.client.listTools(cursor === undefined ? {} : { cursor }, { timeout: config.requestTimeoutMs }));
4
4
  return result.items.map((tool) => ({
5
+ requestOrigin: options.requestOrigin,
5
6
  annotations: tool.annotations,
6
7
  agentDir: options.agentDir,
7
8
  artifacts: options.artifacts,
@@ -27,6 +28,7 @@ export function mcpRegistrationIdentity(tools, catalog) {
27
28
  }
28
29
  export function cachedToolsToCatalogEntries(server, catalog, connection, requestTimeoutMs, ensureConnected, options = {}) {
29
30
  return catalog.tools.map((tool) => ({
31
+ requestOrigin: options.requestOrigin,
30
32
  annotations: tool.annotations,
31
33
  agentDir: options.agentDir,
32
34
  artifacts: options.artifacts,
@@ -4,6 +4,7 @@ export type { ServerConnectionOptions, ServerConnectionState, ServerConnectionSt
4
4
  export declare class ServerConnection {
5
5
  #private;
6
6
  readonly serverName: string;
7
+ get requestLifetimeSignal(): AbortSignal;
7
8
  constructor(options: ServerConnectionOptions);
8
9
  get state(): ServerConnectionState;
9
10
  get declarationIdentity(): string | undefined;
@@ -2,6 +2,8 @@ import { dirname } from "node:path";
2
2
  import { resolveServerAuth } from "./auth/context.js";
3
3
  import { diagnoseCapturedMcpConnectFailure, diagnoseMcpConnectFailure } from "./diagnose.js";
4
4
  import { ConnectError } from "./errors.js";
5
+ import { legacyMcpFormEligible } from "./elicitation.js";
6
+ import { isModernMcpClient, legacyMcpFormsAdvertised } from "./modern/request.js";
5
7
  import { subscribeMcpServerLogging } from "./logging.js";
6
8
  import { isMcpNeedsAuthError } from "./needs-auth.js";
7
9
  import { subscribeMcpListChanged } from "./notifications.js";
@@ -18,12 +20,18 @@ export class ServerConnection {
18
20
  #resolveConfig;
19
21
  #projectPolicy;
20
22
  #policyAbort;
23
+ #requestLifetime = new AbortController();
24
+ get requestLifetimeSignal() {
25
+ return this.#requestLifetime.signal;
26
+ }
21
27
  #startedGrantNames = [];
22
28
  #state;
23
29
  #generation = 0;
24
30
  #connection;
25
31
  #pendingConnection;
26
32
  #pendingConnect;
33
+ #renegotiatingForms;
34
+ #stopFormReadiness;
27
35
  #lastError;
28
36
  #stateListeners = new Set();
29
37
  #toolsListeners = new Set();
@@ -87,9 +95,33 @@ export class ServerConnection {
87
95
  return this.#connection?.getRootPid() ?? null;
88
96
  }
89
97
  connect() {
98
+ return this.#renegotiatingForms ?? this.#connect();
99
+ }
100
+ #legacyFormsChanged(client) {
101
+ return (this.#elicitationUiProvider?.enforced?.() === true &&
102
+ !isModernMcpClient(client) &&
103
+ legacyMcpFormsAdvertised(client) !==
104
+ legacyMcpFormEligible(this.#elicitationUiProvider, this.#elicitationEnabled !== false));
105
+ }
106
+ #refreshLegacyForms() {
107
+ if (this.#state !== "connected" || this.#renegotiatingForms || !this.#legacyFormsChanged(this.client))
108
+ return;
109
+ // Publish the single flight before renewing: connect callers join it while the old transport closes.
110
+ this.#renegotiatingForms = Promise.resolve()
111
+ .then(() => this.renew())
112
+ .finally(() => {
113
+ this.#renegotiatingForms = undefined;
114
+ });
115
+ void this.#renegotiatingForms.catch((error) => {
116
+ this.markDegraded(error instanceof Error ? error : new Error(String(error)));
117
+ });
118
+ }
119
+ #connect() {
90
120
  if (this.#state === "disabled") {
91
121
  return Promise.reject(this.#connectError(`MCP server ${this.serverName} is disabled`, "connect"));
92
122
  }
123
+ if (this.#elicitationUiProvider?.enforced?.())
124
+ this.#stopFormReadiness ??= this.#elicitationUiProvider.subscribe?.(() => this.#refreshLegacyForms());
93
125
  if (this.#state === "connected")
94
126
  return Promise.resolve(this.client);
95
127
  if (this.#pendingConnect)
@@ -153,13 +185,17 @@ export class ServerConnection {
153
185
  throw this.#connectError(`MCP server ${this.serverName} is disabled`, "renew");
154
186
  }
155
187
  this.#policyAbort?.abort();
188
+ this.#requestLifetime.abort();
189
+ this.#requestLifetime = new AbortController();
156
190
  this.#generation++;
157
191
  this.#pendingConnect = undefined;
158
192
  this.#setState("idle");
159
193
  await this.#disposeOwnedConnections();
160
- return this.connect();
194
+ return this.#connect();
161
195
  }
162
196
  bumpGeneration() {
197
+ this.#requestLifetime.abort();
198
+ this.#requestLifetime = new AbortController();
163
199
  this.#policyAbort?.abort();
164
200
  this.#generation++;
165
201
  this.#pendingConnect = undefined;
@@ -169,6 +205,9 @@ export class ServerConnection {
169
205
  return this.#disposeOwnedConnections();
170
206
  }
171
207
  disable() {
208
+ this.#stopFormReadiness?.();
209
+ this.#stopFormReadiness = undefined;
210
+ this.#requestLifetime.abort();
172
211
  this.#policyAbort?.abort();
173
212
  this.#generation++;
174
213
  this.#pendingConnect = undefined;
@@ -177,6 +216,9 @@ export class ServerConnection {
177
216
  return this.#disposeOwnedConnections();
178
217
  }
179
218
  async dispose() {
219
+ this.#stopFormReadiness?.();
220
+ this.#stopFormReadiness = undefined;
221
+ this.#requestLifetime.abort();
180
222
  this.#policyAbort?.abort();
181
223
  this.#generation++;
182
224
  this.#pendingConnect = undefined;
@@ -320,6 +362,9 @@ export class ServerConnection {
320
362
  // Server logging notifications (todo 42): RFC-5424 → the per-server
321
363
  // logger, filtered by config.logLevel, burst-capped.
322
364
  subscribeMcpServerLogging(connection.client, { logLevel: this.#config.logLevel, logger: this.#logger });
365
+ // Readiness can change while initialize is in flight; never publish that stale negotiation as connected.
366
+ if (this.#legacyFormsChanged(connection.client))
367
+ return this.renew();
323
368
  this.#setState("connected");
324
369
  this.markToolsChanged("connect");
325
370
  return connection.client;
@@ -9,7 +9,11 @@ export declare function mcpClientCapabilities(elicitationEnabled?: boolean): {
9
9
  };
10
10
  export declare const MCP_ELICITATION_TIMEOUT_MS: number;
11
11
  type ElicitationUi = Pick<ExtensionUIContext, "input" | "select">;
12
- export type McpElicitationUiProvider = () => ElicitationUi | undefined;
12
+ export type McpElicitationUiProvider = (() => ElicitationUi | undefined) & {
13
+ readonly eligible?: () => boolean;
14
+ readonly enforced?: () => boolean;
15
+ readonly subscribe?: (listener: () => void) => () => void;
16
+ };
13
17
  export interface McpElicitationUiOwner {
14
18
  getMcpElicitationUi(): ElicitationUi | undefined;
15
19
  }
@@ -26,6 +30,7 @@ interface ElicitProperty {
26
30
  }
27
31
  /** Legacy classic-session seam. Session-owned services pass themselves directly. */
28
32
  export declare function setMcpElicitationUiProvider(provider: McpElicitationUiProvider | undefined): void;
33
+ export declare function legacyMcpFormEligible(provider: McpElicitationUiProvider | undefined, enabled: boolean): boolean;
29
34
  /**
30
35
  * Wire the elicitation/create handler onto a fresh client. Async because the
31
36
  * request schema comes from the lazily loaded SDK; the caller (`buildMcpClient`
@@ -9,6 +9,9 @@
9
9
  // The whole form is bounded by a timeout that resolves to cancel so a wedged
10
10
  // dialog can never wedge the tool-call pipeline. URL mode is deliberately not
11
11
  // implemented in v1.
12
+ import { privateMcpForms } from "../../../mcp-form-types.js";
13
+ import { legacyMcpOperation } from "./request-origin.js";
14
+ import { isRecord, validateMrtrForm } from "./modern/form-schema.js";
12
15
  import { createMcpLogger } from "./log.js";
13
16
  import { loadMcpSdkTypes } from "./sdk.lazy.js";
14
17
  import { safeTimer } from "./wrap.js";
@@ -27,6 +30,46 @@ async function rejectElicitation() {
27
30
  const { ErrorCode, McpError } = await loadMcpSdkTypes();
28
31
  throw new McpError(ErrorCode.InternalError, "Elicitation cannot be processed");
29
32
  }
33
+ export function legacyMcpFormEligible(provider, enabled) {
34
+ if (!enabled)
35
+ return false;
36
+ if (provider?.eligible)
37
+ return provider.eligible();
38
+ const ui = provider?.();
39
+ const binding = ui === undefined ? undefined : privateMcpForms(ui);
40
+ return binding === undefined || binding.capture()?.ready() === true;
41
+ }
42
+ async function rejectPrivateElicitation() {
43
+ const { ErrorCode, McpError } = await loadMcpSdkTypes();
44
+ throw new McpError(ErrorCode.InternalError, "Elicitation processing unavailable");
45
+ }
46
+ async function privateElicitation(client, params, signal, requestId, timeoutMs) {
47
+ const operation = legacyMcpOperation(client);
48
+ if (operation === "ambiguous")
49
+ return rejectPrivateElicitation();
50
+ const presenter = operation?.origin.privateForm;
51
+ if (!isRecord(params) ||
52
+ typeof params.message !== "string" ||
53
+ (params.mode !== undefined && params.mode !== "form") ||
54
+ !operation ||
55
+ !presenter?.ready() ||
56
+ !validateMrtrForm(params.requestedSchema))
57
+ return rejectPrivateElicitation();
58
+ const outcome = await presenter.present({
59
+ operationId: String(requestId),
60
+ inputId: String(requestId),
61
+ round: 0,
62
+ server: { label: operation.origin.serverLabel, source: operation.origin.serverSource ?? "user" },
63
+ message: params.message,
64
+ schema: params.requestedSchema,
65
+ operationSignal: AbortSignal.any([operation.signal, signal]),
66
+ presentationSignal: presenter.signal,
67
+ deadlineMs: timeoutMs,
68
+ });
69
+ if (outcome.kind !== "result" || operation.signal.aborted || !operation.origin.isCurrent())
70
+ return rejectPrivateElicitation();
71
+ return outcome.result;
72
+ }
30
73
  /**
31
74
  * Wire the elicitation/create handler onto a fresh client. Async because the
32
75
  * request schema comes from the lazily loaded SDK; the caller (`buildMcpClient`
@@ -40,18 +83,27 @@ export async function configureMcpElicitation(client, owner = legacyUiProvider,
40
83
  const { ErrorCode, McpError } = await loadMcpSdkTypes();
41
84
  client.fallbackRequestHandler = async (request) => {
42
85
  if (request.method === "elicitation/create")
43
- return rejectElicitation();
86
+ return typeof owner === "function" && owner.enforced?.() ? rejectPrivateElicitation() : rejectElicitation();
44
87
  throw new McpError(ErrorCode.MethodNotFound, "Method not found");
45
88
  };
46
89
  return;
47
90
  }
48
91
  const { ElicitRequestSchema } = await loadMcpSdkTypes();
49
- client.setRequestHandler(ElicitRequestSchema, async (request) => {
92
+ if (typeof owner === "function" && owner.enforced?.()) {
93
+ // Private validation prevents SDK error.data from echoing malformed form bodies.
94
+ client.setRequestHandler(ElicitRequestSchema.omit({ params: true }).passthrough(), (request, extra) => privateElicitation(client, request.params, extra.signal, extra.requestId, timeoutMs));
95
+ return;
96
+ }
97
+ client.setRequestHandler(ElicitRequestSchema, async (request, extra) => {
50
98
  const params = request.params;
51
99
  // URL mode is deliberately unsupported in v1 (form mode only).
52
100
  if (!("requestedSchema" in params))
53
101
  return rejectElicitation();
54
102
  const ui = typeof owner === "function" ? owner() : owner?.getMcpElicitationUi();
103
+ const enforced = (typeof owner === "function" && owner.enforced?.()) || (ui !== undefined && privateMcpForms(ui) !== undefined);
104
+ if (enforced) {
105
+ return privateElicitation(client, params, extra.signal, extra.requestId, timeoutMs);
106
+ }
55
107
  if (ui === undefined)
56
108
  return rejectElicitation();
57
109
  return await runElicitationForm(ui, params.message, params.requestedSchema, timeoutMs);
@@ -2,6 +2,8 @@ import { redactResolvedReferenceData, redactResolvedReferenceValues } from "../.
2
2
  import { ToolExecError } from "../errors.js";
3
3
  import { ensureMcpToolCallConnection, withMcpRetriableFailedSendRetry, withMcpSessionExpiryRetry } from "../health.js";
4
4
  import { runMcpConnectionLifecycleCall } from "../idle.js";
5
+ import { mcpRequestOptions } from "../request-origin.js";
6
+ import { McpMrtrError } from "../modern/errors.js";
5
7
  export async function callMcpTool(entry, args, signal, onUpdate, label, invocation) {
6
8
  try {
7
9
  return await runMcpConnectionLifecycleCall(entry.connection, () => withMcpSessionExpiryRetry(entry.connection, async () => {
@@ -29,7 +31,7 @@ export async function callMcpTool(entry, args, signal, onUpdate, label, invocati
29
31
  if (prepared !== undefined && !prepared.isCurrent())
30
32
  continue;
31
33
  const current = prepared?.entry ?? entry;
32
- const result = await current.connection.client.callTool({ name: current.tool, arguments: args }, undefined, {
34
+ const result = await current.connection.client.callTool({ name: current.tool, arguments: args }, undefined, mcpRequestOptions(current.requestOrigin?.(), {
33
35
  onprogress: (progress) => {
34
36
  onUpdate?.(redactResolvedReferenceData({
35
37
  content: [{ type: "text", text: formatProgress(label, progress) }],
@@ -38,13 +40,15 @@ export async function callMcpTool(entry, args, signal, onUpdate, label, invocati
38
40
  },
39
41
  signal,
40
42
  timeout: current.requestTimeoutMs,
41
- });
43
+ }));
42
44
  return { kind: "called", entry: current, result };
43
45
  }
44
46
  });
45
47
  }));
46
48
  }
47
49
  catch (error) {
50
+ if (error instanceof McpMrtrError)
51
+ throw error;
48
52
  const label = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
49
53
  throw new ToolExecError(redactResolvedReferenceValues(`ToolExecError: ${label}`), {
50
54
  cause: error,
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "../../../types.ts";
1
+ import type { ExtensionAPI, ExtensionUIContext } from "../../../types.ts";
2
2
  import type { ToolSearchService } from "../../tool-search/service.ts";
3
3
  import type { McpCachedServerCatalog } from "../catalog-cache.ts";
4
4
  import type { ResolvedMcpConfig } from "../config-schema.ts";
@@ -7,6 +7,7 @@ import type { McpOutputArtifacts } from "../guard/output-guard.ts";
7
7
  import type { McpInvocationResolver } from "../invocation.ts";
8
8
  import { type McpLogger } from "../log.ts";
9
9
  import type { McpPromptServer } from "../prompts.ts";
10
+ import type { McpRequestOrigin } from "../request-origin.ts";
10
11
  import { type McpResourceServer } from "../resources.ts";
11
12
  import type { McpCatalogRegistrationOptions } from "./register.ts";
12
13
  import { type McpTierBRegistration } from "./tier-b.ts";
@@ -15,6 +16,8 @@ export type McpSessionRegistration = McpTierBRegistration & {
15
16
  promptServers: McpPromptServer[];
16
17
  };
17
18
  export interface McpDirectRegistrationEntry {
19
+ readonly requestOrigin?: () => McpRequestOrigin | undefined;
20
+ readonly bindPresentation?: (ui: ExtensionUIContext) => void;
18
21
  readonly name: string;
19
22
  readonly connection: ServerConnection;
20
23
  readonly logger: McpLogger;
@@ -24,6 +24,7 @@ export async function registerDirectMcpTools(pi, config, entries, toolSearchServ
24
24
  artifacts: entry.artifacts,
25
25
  ensureFresh: entry.ensureFresh,
26
26
  invocation: entry.invocation,
27
+ requestOrigin: entry.requestOrigin,
27
28
  outputGuard: config.settings.outputGuard,
28
29
  })
29
30
  : []
@@ -32,6 +33,7 @@ export async function registerDirectMcpTools(pi, config, entries, toolSearchServ
32
33
  artifacts: entry.artifacts,
33
34
  ensureFresh: entry.ensureFresh,
34
35
  invocation: entry.invocation,
36
+ requestOrigin: entry.requestOrigin,
35
37
  outputGuard: config.settings.outputGuard,
36
38
  });
37
39
  const identity = mcpRegistrationIdentity(catalog, entry.cachedCatalog);
@@ -39,6 +41,9 @@ export async function registerDirectMcpTools(pi, config, entries, toolSearchServ
39
41
  const cachedPrompts = entry.cachedCatalog?.prompts ?? [];
40
42
  if (cachedPrompts.length > 0) {
41
43
  promptServers.push({
44
+ ensureFresh: entry.ensureFresh,
45
+ bindPresentation: entry.bindPresentation,
46
+ requestOrigin: entry.requestOrigin,
42
47
  connection: entry.connection,
43
48
  prompts: cachedPrompts,
44
49
  requestTimeoutMs: server.config.requestTimeoutMs,
@@ -48,6 +53,8 @@ export async function registerDirectMcpTools(pi, config, entries, toolSearchServ
48
53
  const cachedResources = entry.cachedCatalog?.resources ?? [];
49
54
  if (cachedResources.length > 0) {
50
55
  resourceServers.push({
56
+ ensureFresh: entry.ensureFresh,
57
+ requestOrigin: entry.requestOrigin,
51
58
  agentDir: entry.agentDir,
52
59
  artifacts: entry.artifacts,
53
60
  connection: entry.connection,
@@ -1,6 +1,7 @@
1
1
  import { AuthError, ConnectError, isMcpSessionExpiredError, isRetriableMcpError, SessionExpiredError, } from "./errors.js";
2
2
  import { isMcpNeedsAuthError } from "./needs-auth.js";
3
3
  import { reconnectMcpNow } from "./reconnect.js";
4
+ import { McpMrtrError } from "./modern/errors.js";
4
5
  export const MCP_PING_STALE_MS = 30_000;
5
6
  export const MCP_PING_TIMEOUT_MS = 2_000;
6
7
  const healthByConnection = new WeakMap();
@@ -46,6 +47,8 @@ export async function withMcpSessionExpiryRetry(connection, operation) {
46
47
  return await operation();
47
48
  }
48
49
  catch (error) {
50
+ if (error instanceof McpMrtrError)
51
+ throw error;
49
52
  if (!isMcpSessionExpiredError(error))
50
53
  throw error;
51
54
  connection.markDegraded(sessionExpiredError(connection, error, true));
@@ -67,6 +70,8 @@ export async function withMcpRetriableFailedSendRetry(connection, operation) {
67
70
  return await operation();
68
71
  }
69
72
  catch (error) {
73
+ if (error instanceof McpMrtrError)
74
+ throw error;
70
75
  if (!isRetriableFailedSendError(error))
71
76
  throw error;
72
77
  connection.markDegraded(failedSendError(connection, error, true));
@@ -1,3 +1,4 @@
1
+ import { legacyMcpFormEligible } from "./elicitation.js";
1
2
  import { observe } from "./env-grants-store.js";
2
3
  import { effectiveGrantNames } from "./project-skill-policy.js";
3
4
  import { SharedMcpConnection } from "./shared-connection.js";
@@ -17,7 +18,12 @@ export class HostMcpRegistry {
17
18
  #shared = new Map();
18
19
  #grantObservers = new Map();
19
20
  attachShared(key, owner, options) {
20
- const identity = sharedMcpKey(options, options.agentDir);
21
+ const eligible = legacyMcpFormEligible(options.elicitationUiProvider, options.elicitationEnabled !== false);
22
+ // Enforced pools observe live readiness; it must not remain frozen in a creation-time key.
23
+ const formPolicy = options.elicitationUiProvider?.enforced?.() === true
24
+ ? `private:${options.elicitationEnabled !== false}`
25
+ : `${eligible}:false`;
26
+ const identity = `${sharedMcpKey(options, options.agentDir)}\0legacy-form:${formPolicy}`;
21
27
  let shared = this.#shared.get(identity);
22
28
  if (shared === undefined) {
23
29
  shared = new SharedMcpConnection(options, () => this.#shared.delete(identity));
@@ -61,6 +61,7 @@ export function createMcpExtension(service, sessionOwned = true) {
61
61
  service.activateSkillMcpTools(targets, pi);
62
62
  };
63
63
  pi.on("input", async (event, ctx) => {
64
+ service.bindMcpMrtrPresentation(pi, ctx.ui);
64
65
  const match = /^\s*\/skill:([A-Za-z0-9._-]+)/.exec(event.text);
65
66
  if (match)
66
67
  revealSkill(match[1]);
@@ -125,6 +126,7 @@ export function createMcpExtension(service, sessionOwned = true) {
125
126
  // Elicitation (todo 41): point mid-call forms at this session's UI.
126
127
  service.setMcpElicitationUiProvider(() => (ctx.hasUI ? ctx.ui : undefined));
127
128
  await (attachPromise ?? attach({ type: "session_start", reason: "startup" }, ctx));
129
+ service.bindMcpMrtrPresentation(pi, ctx.ui);
128
130
  }
129
131
  const skills = preview ? [] : (event.systemPromptOptions.skills ?? []);
130
132
  if (skills.length > 0) {
@@ -0,0 +1,5 @@
1
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { type McpElicitationUiProvider } from "../elicitation.ts";
3
+ import { type ModernProtocolState } from "./request.ts";
4
+ export declare function createMcpRequestClient(protocol: ModernProtocolState, uiProvider: McpElicitationUiProvider | undefined, readCredential?: () => string | undefined, elicitationEnabled?: boolean): Promise<Client>;
5
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { configureMcpElicitation, legacyMcpFormEligible, mcpClientCapabilities, } from "../elicitation.js";
2
+ import { loadMcpSdkClient } from "../sdk.lazy.js";
3
+ import { mcpElapsedClock } from "../wrap.js";
4
+ import { installModernRequestAdapter } from "./request.js";
5
+ export async function createMcpRequestClient(protocol, uiProvider, readCredential = () => undefined, elicitationEnabled = true) {
6
+ const { Client } = await loadMcpSdkClient();
7
+ protocol.privateForms = uiProvider?.enforced?.() === true;
8
+ const legacyEligible = legacyMcpFormEligible(uiProvider, elicitationEnabled);
9
+ protocol.legacyFormEligible = legacyEligible;
10
+ const client = new Client({ name: "senpi-mcp-client", version: "0.0.0" }, { capabilities: mcpClientCapabilities(legacyEligible) });
11
+ await configureMcpElicitation(client, uiProvider, undefined, legacyEligible);
12
+ installModernRequestAdapter(client, protocol, mcpElapsedClock, readCredential);
13
+ return client;
14
+ }
15
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,2 @@
1
+ export declare function queuedForm<T>(binding: object, signal: AbortSignal, run: () => Promise<T>): Promise<T>;
2
+ //# sourceMappingURL=consent-queue.d.ts.map
@@ -0,0 +1,59 @@
1
+ import { McpMrtrError } from "./errors.js";
2
+ const queues = new WeakMap();
3
+ export function queuedForm(binding, signal, run) {
4
+ let queue = queues.get(binding);
5
+ if (!queue) {
6
+ queue = { busy: false, jobs: [] };
7
+ queues.set(binding, queue);
8
+ }
9
+ const current = queue;
10
+ const pump = () => {
11
+ if (current.busy)
12
+ return;
13
+ const next = current.jobs.shift();
14
+ if (!next) {
15
+ queues.delete(binding);
16
+ return;
17
+ }
18
+ next.signal.removeEventListener("abort", next.abort);
19
+ if (next.signal.aborted) {
20
+ next.reject(new McpMrtrError("MCP_MRTR_PROCESSING_UNAVAILABLE"));
21
+ pump();
22
+ return;
23
+ }
24
+ current.busy = true;
25
+ void next.run().finally(() => {
26
+ current.busy = false;
27
+ pump();
28
+ });
29
+ };
30
+ return new Promise((resolve, reject) => {
31
+ const job = {
32
+ signal,
33
+ run: async () => {
34
+ try {
35
+ resolve(await run());
36
+ }
37
+ catch (error) {
38
+ reject(error);
39
+ }
40
+ },
41
+ reject,
42
+ abort: () => { },
43
+ };
44
+ job.abort = () => {
45
+ const index = current.jobs.indexOf(job);
46
+ if (index >= 0) {
47
+ current.jobs.splice(index, 1);
48
+ signal.removeEventListener("abort", job.abort);
49
+ reject(new McpMrtrError("MCP_MRTR_PROCESSING_UNAVAILABLE"));
50
+ }
51
+ };
52
+ current.jobs.push(job);
53
+ signal.addEventListener("abort", job.abort, { once: true });
54
+ if (signal.aborted)
55
+ job.abort();
56
+ pump();
57
+ });
58
+ }
59
+ //# sourceMappingURL=consent-queue.js.map
@@ -0,0 +1,4 @@
1
+ import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
2
+ /** Control/diagnostic payloads are private from admission, not merely after consent starts. */
3
+ export declare function sanitizeModernMessage(message: JSONRPCMessage): JSONRPCMessage | undefined;
4
+ //# sourceMappingURL=diagnostics.d.ts.map