@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
@@ -0,0 +1,17 @@
1
+ const presentations = new WeakMap();
2
+ export function registerMcpMrtrPresentation(ui, signal, admit, privateForms) {
3
+ presentations.set(ui, { ui, signal, admit, privateForms });
4
+ return ui;
5
+ }
6
+ export function getMcpMrtrPresentation(ui) {
7
+ const presentation = presentations.get(ui);
8
+ return presentation?.signal.aborted ? undefined : presentation;
9
+ }
10
+ /** Preserve host identity across runner wrapping; private dialogs still use the raw host UI. */
11
+ export function inheritMcpMrtrPresentation(source, wrapper) {
12
+ const presentation = getMcpMrtrPresentation(source);
13
+ if (presentation)
14
+ presentations.set(wrapper, presentation);
15
+ return wrapper;
16
+ }
17
+ //# sourceMappingURL=mcp-mrtr-presentations.js.map
@@ -2,6 +2,7 @@ import type { ImageContent } from "@earendil-works/pi-ai";
2
2
  import type { CustomMessage } from "./messages.ts";
3
3
  type Message = Pick<CustomMessage, "customType" | "content" | "display" | "details">;
4
4
  export interface RunResumePorts {
5
+ readonly resumeCause?: "stop" | "interrupted";
5
6
  readonly heldInputs: () => readonly {
6
7
  readonly inputId: string;
7
8
  readonly text: string;
@@ -1,4 +1,4 @@
1
- import { STOP_RESUME_CUSTOM_TYPE, stopResumeDirective } from "./stop-resume-directive.js";
1
+ import { stopResumeDirective } from "./stop-resume-directive.js";
2
2
  function partsOf(content) {
3
3
  return typeof content === "string" ? [{ type: "text", text: content }] : [...content];
4
4
  }
@@ -13,17 +13,18 @@ export async function startRunResumeTurn(ports) {
13
13
  const inputs = ports.heldInputs();
14
14
  const continues = ports.continueInputs();
15
15
  const wakes = ports.heldWakes();
16
+ const directive = stopResumeDirective(ports.resumeCause);
16
17
  const content = [
17
18
  ...inputs.flatMap((input) => [
18
19
  { type: "text", text: `The user wrote while the run was paused:\n${input.text}` },
19
20
  ...(input.images ?? []),
20
21
  ]),
21
22
  ...wakes.flatMap((wake) => partsOf(wake.message.content)),
22
- ...partsOf(stopResumeDirective().content),
23
+ ...partsOf(directive.content),
23
24
  ];
24
25
  const sendSettled = Promise.withResolvers();
25
26
  const started = ports.turnStarted(sendSettled.promise);
26
- const sending = ports.send({ customType: STOP_RESUME_CUSTOM_TYPE, content, display: inputs.length > 0 });
27
+ const sending = ports.send({ ...directive, content, display: inputs.length > 0 });
27
28
  sending.then(sendSettled.resolve, sendSettled.resolve);
28
29
  const turnId = await started;
29
30
  const delivered = (async () => {
@@ -22,6 +22,20 @@ export interface LifecycleHold {
22
22
  readonly kind: "session_hold" | "run_fence" | "inherited_hold" | "unattended";
23
23
  readonly since: string;
24
24
  }
25
+ /** Why the last run stopped, for that run only (`runId`); cleared when the next lifecycle run starts. */
26
+ export interface LifecycleStop {
27
+ readonly cause: "deadline" | "goal_guard" | "todo_guard" | "aborted" | "no_wake";
28
+ readonly runId: string;
29
+ readonly at: string;
30
+ readonly epoch: number;
31
+ readonly deadline?: "inactivity" | "max_runtime";
32
+ /** The goal guard reason exactly as `goal-continuation-stopped` recorded it. */
33
+ readonly guard?: string;
34
+ readonly todo?: "cap";
35
+ readonly abortSource?: "user" | "system" | "provider";
36
+ /** Present only when the deadline-stop entry write failed; distinct from the snapshot-level `persisted`. */
37
+ readonly persisted?: false;
38
+ }
25
39
  export interface SpawnedBy {
26
40
  readonly parentSessionId: string;
27
41
  readonly rootSessionId: string;
@@ -48,6 +62,7 @@ export interface SessionLifecycle {
48
62
  readonly taskId?: string;
49
63
  readonly waitingOn?: LifecycleWaits;
50
64
  readonly holds?: readonly LifecycleHold[];
65
+ readonly lastStop?: LifecycleStop;
51
66
  readonly completion?: {
52
67
  readonly kind: "goal" | "run" | "agent" | "reply";
53
68
  readonly at: string;
@@ -2,8 +2,10 @@ import { type TurnEndLivenessInput } from "./extensions/builtin/goal/turn-end-li
2
2
  import type { Goal } from "./extensions/builtin/goal/types.ts";
3
3
  import { LifecycleChildren } from "./lifecycle-children.ts";
4
4
  import { LifecycleLedger, type LifecyclePersistence } from "./lifecycle-ledger.ts";
5
+ import { type RunStopFacts } from "./lifecycle-stop.ts";
5
6
  import type { SessionActivitySnapshot } from "./session-activity.ts";
6
7
  import type { ChildLifecycleReport, HumanWait, LifecycleHold, SessionLifecycle } from "./session-lifecycle-types.ts";
8
+ import type { TurnDeadlineKind } from "./turn-deadline.ts";
7
9
  import type { WorkCustodyRecord } from "./work-custody-record.ts";
8
10
  import { SessionWorkObligations } from "./work-obligations.ts";
9
11
  export interface LifecycleRuntimeFacts {
@@ -33,6 +35,10 @@ export declare class SessionLifecycleController {
33
35
  private ownTask;
34
36
  private evaluating;
35
37
  private deferred;
38
+ private stopFacts;
39
+ private deadlineStop;
40
+ /** A run that ended before this process keeps exactly the stop its record carries; none is derived for it. */
41
+ private readonly restoredRunId;
36
42
  constructor(options: SessionLifecycleOptions);
37
43
  get snapshot(): SessionLifecycle;
38
44
  get lastTurnInput(): TurnEndLivenessInput | undefined;
@@ -45,12 +51,22 @@ export declare class SessionLifecycleController {
45
51
  admissionSettled(): void;
46
52
  startRun(origin?: "human" | "automatic"): void;
47
53
  idle(): void;
48
- goalFacts(goal: Goal | null, input?: TurnEndLivenessInput): void;
54
+ /** The turn deadline fired: captured before its durable write, for the run it stopped only. */
55
+ deadlineStopped(kind: TurnDeadlineKind, atMs: number): void;
56
+ /** The deadline-stop entry was refused; the captured stop says so, and the snapshot's `persisted` is untouched. */
57
+ deadlineStopWriteFailed(): void;
58
+ goalFacts(goal: Goal | null, input?: TurnEndLivenessInput, stop?: RunStopFacts): void;
49
59
  goalChanged(goal: Goal | null): void;
50
60
  reportChild(report: ChildLifecycleReport): void;
51
61
  requestSelfSettle(): import("./session-lifecycle-types.ts").SelfSettleResult;
52
62
  markRunCompleted(runId: string): void;
53
63
  recompute(): void;
54
64
  private evaluate;
65
+ /**
66
+ * Derived only from the current run's own end, bound when its agent_end was emitted: an end processed after a
67
+ * later run started sets nothing, and so does a fact refresh that carries no bound turn end, even when the goal
68
+ * collector still returns the previous run's end. An unchanged stop keeps its first `at`.
69
+ */
70
+ private lastStop;
55
71
  }
56
72
  //# sourceMappingURL=session-lifecycle.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import { effectiveWakes } from "./extensions/builtin/goal/turn-end-liveness.js";
2
2
  import { LifecycleChildren } from "./lifecycle-children.js";
3
3
  import { LifecycleLedger } from "./lifecycle-ledger.js";
4
+ import { sameStop, stopDetail } from "./lifecycle-stop.js";
4
5
  import { lifecycleState, lifecycleTerminal } from "./session-lifecycle-reducer.js";
5
6
  import { SessionWorkObligations } from "./work-obligations.js";
6
7
  /** One event-driven reducer per session, shared by every client surface. */
@@ -21,6 +22,7 @@ export class SessionLifecycleController {
21
22
  options.append(record);
22
23
  },
23
24
  });
25
+ this.restoredRunId = this.ledger.runId;
24
26
  if (this.ledger.lastTurnEnd)
25
27
  this.liveness = {
26
28
  ...this.ledger.lastTurnEnd,
@@ -94,6 +96,8 @@ export class SessionLifecycleController {
94
96
  this.runAdmitted = false;
95
97
  this.idleBoundaryPassed = false;
96
98
  this.liveness = undefined;
99
+ this.stopFacts = undefined;
100
+ this.deadlineStop = undefined;
97
101
  const interruptedRun = this.ledger.interrupted ? this.ledger.runId : undefined;
98
102
  this.ledger.interrupted = false;
99
103
  this.ledger.startRun(origin ?? (this.pendingHuman ? "human" : "automatic"));
@@ -115,8 +119,20 @@ export class SessionLifecycleController {
115
119
  if (seq > 0 && this.ledger.snapshot.seq === seq)
116
120
  this.ledger.checkpoint();
117
121
  }
118
- goalFacts(goal, input) {
122
+ /** The turn deadline fired: captured before its durable write, for the run it stopped only. */
123
+ deadlineStopped(kind, atMs) {
124
+ const runId = this.ledger.runId;
125
+ if (this.ledger.runActive && runId)
126
+ this.deadlineStop = { runId, kind, at: new Date(atMs).toISOString() };
127
+ }
128
+ /** The deadline-stop entry was refused; the captured stop says so, and the snapshot's `persisted` is untouched. */
129
+ deadlineStopWriteFailed() {
130
+ if (this.deadlineStop)
131
+ this.deadlineStop = { ...this.deadlineStop, persisted: false };
132
+ }
133
+ goalFacts(goal, input, stop) {
119
134
  this.liveness = input;
135
+ this.stopFacts = stop;
120
136
  if (input)
121
137
  this.ledger.lastTurnEnd = {
122
138
  endedBy: input.endedBy,
@@ -282,6 +298,7 @@ export class SessionLifecycleController {
282
298
  }
283
299
  : undefined;
284
300
  const waits = { children, background, human };
301
+ const lastStop = this.lastStop(input, synthetic.some((wait) => wait.kind === "stalled"));
285
302
  const state = lifecycleState({
286
303
  role: this.ledger.snapshot.role,
287
304
  terminal: this.ownTask?.terminal,
@@ -296,7 +313,28 @@ export class SessionLifecycleController {
296
313
  this.ownTask?.terminal === undefined &&
297
314
  (!this.idleBoundaryPassed || active.length > 0))
298
315
  return;
299
- this.ledger.update({ state, waits, holds: runtime.holds, failure });
316
+ this.ledger.update({ state, waits, holds: runtime.holds, failure, lastStop });
317
+ }
318
+ /**
319
+ * Derived only from the current run's own end, bound when its agent_end was emitted: an end processed after a
320
+ * later run started sets nothing, and so does a fact refresh that carries no bound turn end, even when the goal
321
+ * collector still returns the previous run's end. An unchanged stop keeps its first `at`.
322
+ */
323
+ lastStop(input, stalled) {
324
+ const existing = this.ledger.snapshot.lastStop;
325
+ const runId = this.ledger.runId;
326
+ if (!runId || runId === this.restoredRunId)
327
+ return existing;
328
+ if (this.stopFacts?.runId !== runId)
329
+ return existing;
330
+ const deadline = input && this.deadlineStop?.runId === runId ? this.deadlineStop : undefined;
331
+ const detail = stopDetail(input, this.stopFacts, deadline, stalled);
332
+ if (!detail)
333
+ return existing;
334
+ if (existing && sameStop(existing, detail))
335
+ return existing;
336
+ const { at, ...cause } = detail;
337
+ return { ...cause, runId, epoch: this.ledger.snapshot.epoch, at: at ?? new Date().toISOString() };
300
338
  }
301
339
  }
302
340
  //# sourceMappingURL=session-lifecycle.js.map
@@ -1,4 +1,4 @@
1
1
  import type { CustomMessage } from "./messages.ts";
2
2
  export declare const STOP_RESUME_CUSTOM_TYPE = "session-resumed";
3
- export declare function stopResumeDirective(): Pick<CustomMessage, "customType" | "content" | "display">;
3
+ export declare function stopResumeDirective(cause?: "stop" | "interrupted"): Pick<CustomMessage, "customType" | "content" | "display" | "details">;
4
4
  //# sourceMappingURL=stop-resume-directive.d.ts.map
@@ -2,7 +2,16 @@ export const STOP_RESUME_CUSTOM_TYPE = "session-resumed";
2
2
  const DIRECTIVE = "<system-reminder>The user stopped the previous turn and has now resumed. Tool calls that the Stop interrupted show " +
3
3
  "an aborted result; they were not re-run and their side effects may be partial. Check their effects before repeating " +
4
4
  "any of them, then continue the work.</system-reminder>";
5
- export function stopResumeDirective() {
5
+ export function stopResumeDirective(cause = "stop") {
6
+ if (cause === "interrupted")
7
+ return {
8
+ customType: STOP_RESUME_CUSTOM_TYPE,
9
+ content: "<system-reminder>The previous run was interrupted and has now resumed. Interrupted tool calls may have " +
10
+ "partial side effects and were not re-run. Check their effects before repeating any of them, then continue " +
11
+ "the work.</system-reminder>",
12
+ display: false,
13
+ details: { resumeCause: cause },
14
+ };
6
15
  return { customType: STOP_RESUME_CUSTOM_TYPE, content: DIRECTIVE, display: false };
7
16
  }
8
17
  //# sourceMappingURL=stop-resume-directive.js.map
package/dist/main.js CHANGED
@@ -1067,7 +1067,8 @@ export async function main(args, options) {
1067
1067
  if (appMode === "rpc" && parsed.multiSession) {
1068
1068
  if (options?.extensionFactories?.length)
1069
1069
  throw new Error("Shared RPC workers require file-backed extensions; inline factories cannot cross isolates");
1070
- const runtimeConfiguration = { parsed, cwd, agentDir, appMode, toolRuntime };
1070
+ const { presenterBootstrapFd, mcpPresenterEnforcement, ...sessionArgs } = parsed;
1071
+ const runtimeConfiguration = { parsed: sessionArgs, cwd, agentDir, appMode, toolRuntime };
1071
1072
  // Socket hosts (the machine-wide daemon) run every session IN this process:
1072
1073
  // createHostCore selects the worker registry only when a workerConfiguration
1073
1074
  // is passed, so withholding it is what selects the uncapped in-process registry.
@@ -1098,6 +1099,8 @@ export async function main(args, options) {
1098
1099
  }
1099
1100
  printTimings();
1100
1101
  await runMultiSessionHost({
1102
+ presenterBootstrapFd,
1103
+ mcpPresenterEnforcement,
1101
1104
  agentDir,
1102
1105
  createRuntime: createCliRuntimeFactory(runtimeConfiguration, {
1103
1106
  ...(hostModelRuntime === undefined ? {} : { modelRuntime: hostModelRuntime }),
@@ -1292,6 +1295,7 @@ export async function main(args, options) {
1292
1295
  // mode seam: interactive startup still loads the same module before first use.
1293
1296
  const { InteractiveMode } = await import("./modes/interactive/interactive-mode.js");
1294
1297
  const interactiveMode = new InteractiveMode(runtime, {
1298
+ mcpPresenterEnforcement: parsed.mcpPresenterEnforcement,
1295
1299
  migratedProviders,
1296
1300
  authNotices: interactiveAuthNotices(services.authStorage),
1297
1301
  legacyPiEditNotice: legacyPiEditStartupNotice(),
@@ -21,6 +21,7 @@ export declare class ExtensionSelectorComponent extends Container {
21
21
  private countdown;
22
22
  private onToggleToolsExpanded;
23
23
  private maxVisibleOptions;
24
+ private answerSettled;
24
25
  constructor(title: string, options: string[], onSelect: (option: string) => void, onCancel: () => void, opts?: ExtensionSelectorOptions);
25
26
  private updateList;
26
27
  handleInput(keyData: string): void;
@@ -12,6 +12,7 @@ export class ExtensionSelectorComponent extends Container {
12
12
  constructor(title, options, onSelect, onCancel, opts) {
13
13
  super();
14
14
  this.selectedIndex = 0;
15
+ this.answerSettled = false;
15
16
  this.options = options;
16
17
  this.onSelectCallback = onSelect;
17
18
  this.onCancelCallback = onCancel;
@@ -67,6 +68,8 @@ export class ExtensionSelectorComponent extends Container {
67
68
  }
68
69
  }
69
70
  handleInput(keyData) {
71
+ if (this.answerSettled)
72
+ return;
70
73
  const kb = getKeybindings();
71
74
  if (kb.matches(keyData, "app.tools.expand")) {
72
75
  this.onToggleToolsExpanded?.();
@@ -89,6 +92,7 @@ export class ExtensionSelectorComponent extends Container {
89
92
  }
90
93
  }
91
94
  dispose() {
95
+ this.answerSettled = true;
92
96
  this.countdown?.dispose();
93
97
  }
94
98
  }
@@ -56,6 +56,8 @@ interface InteractiveUserInput {
56
56
  * Options for InteractiveMode initialization.
57
57
  */
58
58
  export interface InteractiveModeOptions {
59
+ /** Opt in to host-issued MCP form authority; ordinary terminal forms remain the default. */
60
+ mcpPresenterEnforcement?: boolean;
59
61
  /** Renderer mode for the interactive terminal. */
60
62
  uiMode?: TuiMode;
61
63
  /** Providers that were migrated to auth.json (shows warning) */
@@ -327,6 +329,8 @@ export declare class InteractiveMode {
327
329
  */
328
330
  private unsubscribeSecurityNotices?;
329
331
  private detachMcpGrantPresentation?;
332
+ private mcpMrtrPresentationLifetime;
333
+ private mcpLocalPresenter?;
330
334
  private bindCurrentSessionExtensions;
331
335
  private applyFullscreenScrollbarSetting;
332
336
  private applyRuntimeSettings;
@@ -35,6 +35,9 @@ import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/htt
35
35
  import { KeybindingsManager } from "../../core/keybindings.js";
36
36
  import { isManualContinueSubmission } from "../../core/manual-continue.js";
37
37
  import { attachMcpGrantPresentation } from "../../core/mcp-grant-presentations.js";
38
+ import { registerMcpMrtrPresentation } from "../../core/mcp-mrtr-presentations.js";
39
+ import { markMcpFormPresented } from "../../core/mcp-form-types.js";
40
+ import { registerLocalMcpPresenter } from "./mcp-local-presenter.js";
38
41
  import { createCompactionSummaryMessage, createCustomMessage } from "../../core/messages.js";
39
42
  import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope, resolveModelScopeFromModels, resolveModelScopeWithDiagnostics, } from "../../core/model-resolver.js";
40
43
  import { CredentialSynchronizationError } from "../../core/model-runtime.js";
@@ -688,6 +691,7 @@ export class InteractiveMode {
688
691
  this.onRightClickPaste = () => {
689
692
  void this.handleRightClickPaste();
690
693
  };
694
+ this.mcpMrtrPresentationLifetime = new AbortController();
691
695
  /**
692
696
  * Gracefully shutdown the agent.
693
697
  * Stops the TUI before emitting shutdown events so extension UI cleanup cannot
@@ -2010,6 +2014,9 @@ export class InteractiveMode {
2010
2014
  }
2011
2015
  }
2012
2016
  async bindCurrentSessionExtensions() {
2017
+ this.mcpMrtrPresentationLifetime?.abort();
2018
+ this.mcpMrtrPresentationLifetime = new AbortController();
2019
+ this.mcpLocalPresenter = undefined;
2013
2020
  await this.sessionControlHost?.disposeActive();
2014
2021
  this.session.setControlEndpointHost?.(this.sessionControlHost);
2015
2022
  const uiContext = this.createExtensionUIContext();
@@ -2117,6 +2124,7 @@ export class InteractiveMode {
2117
2124
  this.showExtensionError(error);
2118
2125
  },
2119
2126
  });
2127
+ this.setExtensionStatus(PAUSE_STATUS_KEY, pauseFooterText(this.session));
2120
2128
  setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
2121
2129
  this.setupAutocompleteProvider();
2122
2130
  const extensionRunner = this.session.extensionRunner;
@@ -2317,7 +2325,12 @@ export class InteractiveMode {
2317
2325
  return false;
2318
2326
  };
2319
2327
  }
2320
- async withBlockedHostDialog(id, label, show) {
2328
+ async withBlockedHostDialog(id, label, show, opts) {
2329
+ if (opts?.ephemeral) {
2330
+ if (opts.signal?.aborted)
2331
+ return undefined;
2332
+ return await show();
2333
+ }
2321
2334
  this.session.emitExtensionEvent("herdr:blocked", { active: true, label, id });
2322
2335
  try {
2323
2336
  return await show();
@@ -2845,10 +2858,11 @@ export class InteractiveMode {
2845
2858
  };
2846
2859
  }
2847
2860
  createExtensionUIContext() {
2848
- return {
2849
- select: (title, options, opts) => this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionSelector(title, options, opts)),
2850
- confirm: (title, message, opts) => this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionConfirm(title, message, opts)),
2851
- input: (title, placeholder, opts) => this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionInput(title, placeholder, opts)),
2861
+ this.mcpMrtrPresentationLifetime ??= new AbortController();
2862
+ const context = {
2863
+ select: (title, options, opts) => this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionSelector(title, options, opts), opts),
2864
+ confirm: async (title, message, opts) => (await this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionConfirm(title, message, opts), opts)) ?? false,
2865
+ input: (title, placeholder, opts) => this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionInput(title, placeholder, opts), opts),
2852
2866
  // Async answers are delivered by the ask-user extension, not here: the
2853
2867
  // widget only resolves the question, so the model sees exactly one
2854
2868
  // framed user message no matter which surface answered.
@@ -2905,6 +2919,11 @@ export class InteractiveMode {
2905
2919
  getToolsExpanded: () => this.toolOutputExpanded,
2906
2920
  setToolsExpanded: (expanded) => this.setToolsExpanded(expanded),
2907
2921
  };
2922
+ const presenterSession = this.session;
2923
+ const privateForms = this.options?.mcpPresenterEnforcement === true
2924
+ ? (this.mcpLocalPresenter ??= registerLocalMcpPresenter(context, presenterSession.sessionId, this.mcpMrtrPresentationLifetime.signal, () => presenterSession.admitHumanAnswer()))
2925
+ : undefined;
2926
+ return registerMcpMrtrPresentation(context, this.mcpMrtrPresentationLifetime.signal, undefined, privateForms);
2908
2927
  }
2909
2928
  /**
2910
2929
  * Show a selector for extensions.
@@ -2921,7 +2940,8 @@ export class InteractiveMode {
2921
2940
  };
2922
2941
  opts?.signal?.addEventListener("abort", onAbort, { once: true });
2923
2942
  this.extensionSelector = new ExtensionSelectorComponent(title, options, (option) => {
2924
- this.session.admitHumanAnswer();
2943
+ if (!opts?.ephemeral || !this.options?.mcpPresenterEnforcement)
2944
+ this.session.admitHumanAnswer();
2925
2945
  opts?.signal?.removeEventListener("abort", onAbort);
2926
2946
  this.hideExtensionSelector();
2927
2947
  resolve(option);
@@ -2938,7 +2958,12 @@ export class InteractiveMode {
2938
2958
  this.editorContainer.clear();
2939
2959
  this.editorContainer.addChild(this.extensionSelector);
2940
2960
  this.ui.setFocus(this.extensionSelector);
2941
- this.ui.requestRender();
2961
+ if (opts?.ephemeral && this.options?.mcpPresenterEnforcement) {
2962
+ this.ui.renderNow();
2963
+ markMcpFormPresented(opts);
2964
+ }
2965
+ else
2966
+ this.ui.requestRender();
2942
2967
  });
2943
2968
  }
2944
2969
  /**
@@ -2978,7 +3003,8 @@ export class InteractiveMode {
2978
3003
  };
2979
3004
  opts?.signal?.addEventListener("abort", onAbort, { once: true });
2980
3005
  this.extensionInput = new ExtensionInputComponent(title, placeholder, (value) => {
2981
- this.session.admitHumanAnswer();
3006
+ if (!opts?.ephemeral || !this.options?.mcpPresenterEnforcement)
3007
+ this.session.admitHumanAnswer();
2982
3008
  opts?.signal?.removeEventListener("abort", onAbort);
2983
3009
  this.hideExtensionInput();
2984
3010
  resolve(value);
@@ -5504,6 +5530,7 @@ export class InteractiveMode {
5504
5530
  if (this.isShuttingDown)
5505
5531
  return;
5506
5532
  this.isShuttingDown = true;
5533
+ this.mcpMrtrPresentationLifetime?.abort();
5507
5534
  // Keep signal handlers registered until terminal cleanup has completed.
5508
5535
  // `signal-exit` checks the listener list during the same SIGTERM/SIGHUP
5509
5536
  // dispatch and re-sends the signal if only its own listeners remain.
@@ -0,0 +1,5 @@
1
+ import type { ExtensionUIContext } from "../../core/extensions/types.ts";
2
+ import type { McpFormBinding } from "../../core/mcp-form-types.ts";
3
+ /** The terminal itself is the principal; an RPC attachment never receives this handle. */
4
+ export declare function registerLocalMcpPresenter(ui: ExtensionUIContext, sessionId: string, lifetime: AbortSignal, admit: () => void): McpFormBinding;
5
+ //# sourceMappingURL=mcp-local-presenter.d.ts.map
@@ -0,0 +1,109 @@
1
+ import { collectMrtrForm } from "../../core/extensions/builtin/mcp/modern/elicitation.js";
2
+ import { queuedForm } from "../../core/extensions/builtin/mcp/modern/consent-queue.js";
3
+ import { McpPresenterAuthority } from "../rpc/mcp-presenter-authority.js";
4
+ /** The terminal itself is the principal; an RPC attachment never receives this handle. */
5
+ export function registerLocalMcpPresenter(ui, sessionId, lifetime, admit) {
6
+ const authority = new McpPresenterAuthority();
7
+ const binding = authority.bind(sessionId, sessionId, admit);
8
+ let activeInput;
9
+ const drafts = new Map();
10
+ const sender = authority.connect("local_tui", (frame) => {
11
+ if (frame.type !== "mcp_form_request") {
12
+ drafts.get(frame.dialogId)?.abort();
13
+ drafts.delete(frame.dialogId);
14
+ return true;
15
+ }
16
+ const active = activeInput;
17
+ if (!active)
18
+ return false;
19
+ const { input } = active;
20
+ const draft = new AbortController();
21
+ drafts.set(frame.dialogId, draft);
22
+ const signal = AbortSignal.any([input.operationSignal, input.presentationSignal, lifetime, draft.signal]);
23
+ const { sessionId, bindingEpoch, attachmentEpoch, grantId, dialogId, dialogToken, formRevision } = frame;
24
+ const envelope = { sessionId, bindingEpoch, attachmentEpoch, grantId, dialogId, dialogToken, formRevision };
25
+ void collectMrtrForm({
26
+ ui,
27
+ signal,
28
+ presented: () => authority.receive(sender, { ...envelope, type: "mcp_form_presented" }),
29
+ }, `${frame.server.label} (${frame.server.source})`, { message: frame.message, requestedSchema: frame.schema }, signal)
30
+ .then((result) => {
31
+ if (!signal.aborted)
32
+ authority.receive(sender, { ...envelope, type: "mcp_form_response", ...result });
33
+ }, () => active.fail())
34
+ .finally(() => drafts.delete(frame.dialogId));
35
+ return true;
36
+ });
37
+ authority.attach(sender, binding);
38
+ const owner = authority.enroll();
39
+ owner.acquire(owner.register(binding, sender, { kind: "local_tui" }));
40
+ const dispose = () => {
41
+ authority.dispose();
42
+ for (const draft of drafts.values())
43
+ draft.abort();
44
+ drafts.clear();
45
+ };
46
+ lifetime.addEventListener("abort", dispose, { once: true });
47
+ const privateForms = {
48
+ epoch: binding.epoch,
49
+ signal: binding.signal,
50
+ capture: () => {
51
+ const presentation = binding.capture();
52
+ if (!presentation)
53
+ return undefined;
54
+ return {
55
+ ...presentation,
56
+ present: async (input) => {
57
+ const deadline = performance.now() + input.deadlineMs;
58
+ const timeout = new AbortController();
59
+ const timer = setTimeout(() => timeout.abort(), Math.max(0, input.deadlineMs));
60
+ const signal = AbortSignal.any([
61
+ input.operationSignal,
62
+ input.presentationSignal,
63
+ presentation.signal,
64
+ lifetime,
65
+ timeout.signal,
66
+ ]);
67
+ try {
68
+ return await queuedForm(binding, signal, async () => {
69
+ const failure = new AbortController();
70
+ const current = {
71
+ input: {
72
+ ...input,
73
+ presentationSignal: AbortSignal.any([signal, failure.signal]),
74
+ deadlineMs: Math.max(0, deadline - performance.now()),
75
+ },
76
+ fail: () => failure.abort(),
77
+ };
78
+ activeInput = current;
79
+ try {
80
+ return await presentation.present(current.input);
81
+ }
82
+ finally {
83
+ if (activeInput === current)
84
+ activeInput = undefined;
85
+ }
86
+ });
87
+ }
88
+ catch {
89
+ return {
90
+ kind: input.operationSignal.aborted || binding.signal.aborted || input.operationCurrent?.() === false
91
+ ? "operation_aborted"
92
+ : "processing_failure",
93
+ source: "host_termination",
94
+ humanAdmission: false,
95
+ };
96
+ }
97
+ finally {
98
+ clearTimeout(timer);
99
+ }
100
+ },
101
+ };
102
+ },
103
+ dispose,
104
+ };
105
+ if (lifetime.aborted)
106
+ dispose();
107
+ return privateForms;
108
+ }
109
+ //# sourceMappingURL=mcp-local-presenter.js.map
@@ -3,6 +3,11 @@ interface PauseView {
3
3
  readonly paused: boolean;
4
4
  readonly pausedNoticeCount: number;
5
5
  readonly runPaused: boolean;
6
+ readonly pauseView?: {
7
+ readonly records: readonly {
8
+ readonly key: string;
9
+ }[];
10
+ };
6
11
  }
7
12
  export declare function pauseFooterText(view: PauseView): string | undefined;
8
13
  export declare function resumeCommandTarget(view: PauseView): "resume_session" | "session_picker";
@@ -3,6 +3,8 @@ export function pauseFooterText(view) {
3
3
  if (!view.paused && !view.runPaused)
4
4
  return undefined;
5
5
  const held = view.pausedNoticeCount === 1 ? "1 update held" : `${view.pausedNoticeCount} updates held`;
6
+ if (view.pauseView?.records.some((record) => record.key === "interrupted-run"))
7
+ return `Interrupted · ${view.pausedNoticeCount > 0 ? `${held} · ` : ""}type to continue, /resume to resume`;
6
8
  if (view.runPaused)
7
9
  return `Run paused · ${held} · type to continue, /resume to resume the run`;
8
10
  return `Paused · ${held} · type to continue, /resume to resume`;
@@ -34,6 +34,7 @@ export interface RpcConnectionOptions {
34
34
  hasCapability?: (connectionId: string, capability: string) => boolean;
35
35
  };
36
36
  bindSecurityPresentation?: (session: AgentSessionRuntime["session"], ui: ExtensionUIContext) => (() => void) | undefined;
37
+ bindMcpForms?: (session: AgentSessionRuntime["session"], ui: ExtensionUIContext) => (() => void) | undefined | Promise<(() => void) | undefined>;
37
38
  /** A worker's internal bridge is gated by its host's actual socket capabilities. */
38
39
  workerSecurityBridge?: boolean;
39
40
  grantDialogSettled?: (id: string) => void;
@@ -599,6 +599,8 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
599
599
  subscribeExtensionEvents();
600
600
  };
601
601
  const rebindSession = async (deferRefresh = false) => {
602
+ unbindMcpForms?.();
603
+ unbindMcpForms = undefined;
602
604
  unsubscribeSecurityNotices?.();
603
605
  unsubscribeLoadedSurfaces?.();
604
606
  unsubscribeExtensionEvents?.();
@@ -673,8 +675,10 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
673
675
  // reconstruct the session it is bound to. Installing here also drops the
674
676
  // previous session's subscription immediately instead of after the bind.
675
677
  installSessionSubscriptions();
678
+ const extensionUi = createExtensionUIContext();
679
+ unbindMcpForms = await options.bindMcpForms?.(session, extensionUi);
676
680
  const refresh = refreshLoadedSurfacesAfter(() => session.bindExtensions({
677
- uiContext: createExtensionUIContext(),
681
+ uiContext: extensionUi,
678
682
  mode: "rpc",
679
683
  commandContextActions: {
680
684
  waitForIdle: () => session.agent.waitForIdle(),
@@ -1752,6 +1756,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1752
1756
  ? subscriptionLimits.subscribe((limits) => eventOutput.enqueueEvent({ type: "subscription_limits", ...limits }))
1753
1757
  : () => { };
1754
1758
  const dispose = async (disposeOptions = {}) => {
1759
+ unbindMcpForms?.();
1755
1760
  grantPresentationAbort.abort();
1756
1761
  detachGrantPresentation?.();
1757
1762
  unsubscribeSecurityNotices?.();
@@ -1777,6 +1782,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1777
1782
  };
1778
1783
  // Perform the initial bind synchronously-scheduled so the handler is ready
1779
1784
  // as soon as the caller awaits `ready`.
1785
+ let unbindMcpForms;
1780
1786
  const ready = rebindSession();
1781
1787
  return {
1782
1788
  ready,
@@ -14,5 +14,6 @@ export declare function multiSessionHostCapabilities(options: {
14
14
  readonly warm: boolean;
15
15
  readonly toolRuntime: ToolRuntimeKind;
16
16
  readonly negotiated: readonly string[];
17
+ readonly mcpPresenters?: boolean;
17
18
  }): string[];
18
19
  //# sourceMappingURL=host-capabilities.d.ts.map
@@ -20,6 +20,7 @@ export function multiSessionHostCapabilities(options) {
20
20
  return [
21
21
  ...new Set([
22
22
  "multi_session",
23
+ ...(options.mcpPresenters ? ["mcp_form_presenter_v1"] : []),
23
24
  "session_lifecycle",
24
25
  "expected_host_instance",
25
26
  "attach_session",
@@ -80,7 +81,7 @@ export function multiSessionHostCapabilities(options) {
80
81
  ...(options.warm ? [WARM_CAPABILITY] : []),
81
82
  ...toolRuntimeCapabilities(options.toolRuntime),
82
83
  // The host alone says where tool work runs; a negotiated copy would make two answers.
83
- ...options.negotiated.filter((capability) => !isHostOwnedToolRuntimeCapability(capability)),
84
+ ...options.negotiated.filter((capability) => capability !== "mcp_form_presenter_v1" && !isHostOwnedToolRuntimeCapability(capability)),
84
85
  ]),
85
86
  ];
86
87
  }