@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
@@ -8,11 +8,14 @@ import { readLifecycleGoal } from "./lifecycle-goal.js";
8
8
  import { lifecycleHistory } from "./lifecycle-history.js";
9
9
  import { bindLifecycleHumanInput } from "./lifecycle-human-input.js";
10
10
  import { readLifecycleRuntimeFacts } from "./lifecycle-runtime-facts.js";
11
+ import { goalGuardAfter } from "./lifecycle-stop.js";
11
12
  import { SessionLifecycleController } from "./session-lifecycle.js";
12
13
  import { isRecord } from "./work-custody-record.js";
13
14
  /** Attaches durable lifecycle to the real session event/admission boundaries. */
14
15
  export class LifecycleSessionBinding {
15
16
  constructor(session, host) {
17
+ /** The run each emitted agent_end belonged to, read when it was emitted, not when its queue reaches it. */
18
+ this.boundEnds = new WeakMap();
16
19
  this.refreshGeneration = 0;
17
20
  this.restored = false;
18
21
  /** After dispose() nothing the controller decides is written or emitted: the session file is no longer this session's. */
@@ -102,10 +105,20 @@ export class LifecycleSessionBinding {
102
105
  }
103
106
  startRun() {
104
107
  this.lastEnd = undefined;
108
+ this.lastEndGuard = undefined;
105
109
  const origin = this.nextRunOrigin;
106
110
  this.nextRunOrigin = undefined;
107
111
  this.controller.startRun(origin);
108
112
  }
113
+ deadlineStopped(kind, atMs) {
114
+ this.controller.deadlineStopped(kind, atMs);
115
+ }
116
+ deadlineStopWriteFailed() {
117
+ this.controller.deadlineStopWriteFailed();
118
+ }
119
+ bindTurnEnd(end) {
120
+ this.boundEnds.set(end, this.controller.ledger.runId);
121
+ }
109
122
  consume(message) {
110
123
  return this.write(() => consumeLifecycleDelivery(this.session, this.controller, message));
111
124
  }
@@ -115,8 +128,14 @@ export class LifecycleSessionBinding {
115
128
  const ticketId = messageTicket(message);
116
129
  return ticketId ? this.session.sessionManager.reduceHistory(lifecycleDeliveryEntries).get(ticketId) : undefined;
117
130
  }
118
- async turnEnded(event) {
131
+ /** `event` is the extension agent_end, never the session-level stamp; `end` is the core event bound at emission. */
132
+ async turnEnded(event, end, guardStartLeafId) {
119
133
  this.lastEnd = event;
134
+ this.lastEndRunId = this.boundEnds.get(end);
135
+ this.lastEndGuard =
136
+ guardStartLeafId === undefined
137
+ ? undefined
138
+ : goalGuardAfter(this.session.sessionManager.getBranch(), guardStartLeafId);
120
139
  await this.refreshFacts();
121
140
  }
122
141
  async refreshFacts(changedGoal) {
@@ -166,7 +185,15 @@ export class LifecycleSessionBinding {
166
185
  deadlineStop: input.deadlineStop || saved.deadlineStop,
167
186
  providerRetryAtMs: input.providerRetryAtMs ?? saved.providerRetryAtMs,
168
187
  };
169
- this.controller.goalFacts(goal, input);
188
+ // Stop facts belong to the run that just ended in this process; a restored end carries its recorded stop.
189
+ const stop = this.lastEnd
190
+ ? {
191
+ runId: this.lastEndRunId,
192
+ guard: this.lastEndGuard,
193
+ todo: collected?.todoOutcome,
194
+ }
195
+ : undefined;
196
+ this.controller.goalFacts(goal, input, stop);
170
197
  }
171
198
  async idle() {
172
199
  await this.refreshFacts();
@@ -0,0 +1,41 @@
1
+ import type { TodoOwedOutcome } from "./extensions/builtin/goal/todo-owed-backstop.ts";
2
+ import type { TurnEndLivenessInput } from "./extensions/builtin/goal/turn-end-liveness.ts";
3
+ import type { LifecycleStop } from "./session-lifecycle-types.ts";
4
+ import type { TurnDeadlineKind } from "./turn-deadline.ts";
5
+ /** Stop facts the stopping run produced itself, read from that run's own end (senpi#3864). */
6
+ export interface RunStopFacts {
7
+ /** The run the turn end belonged to when its agent_end was emitted. */
8
+ readonly runId?: string;
9
+ readonly guard?: {
10
+ readonly reason: string;
11
+ readonly at: number;
12
+ };
13
+ readonly todo?: TodoOwedOutcome;
14
+ }
15
+ /** A deadline captured when it fired, before its durable write, for the run it stopped. */
16
+ export interface DeadlineStopCapture {
17
+ readonly runId: string;
18
+ readonly kind: TurnDeadlineKind;
19
+ readonly at: string;
20
+ readonly persisted?: false;
21
+ }
22
+ type StopDetail = Omit<LifecycleStop, "runId" | "epoch" | "at"> & {
23
+ readonly at?: string;
24
+ };
25
+ /**
26
+ * First match wins: the run's own deadline explains its system abort, a guard explains a refused continuation, then
27
+ * the classified abort, and `no_wake` only for a stall no hold or guard owns. Provider failures keep the failed path.
28
+ */
29
+ export declare function stopDetail(input: TurnEndLivenessInput | undefined, facts: RunStopFacts | undefined, deadline: DeadlineStopCapture | undefined, stalled: boolean): StopDetail | undefined;
30
+ export declare function sameStop(stop: LifecycleStop, detail: StopDetail): boolean;
31
+ /** The latest guard appended within one bound end's extension-processing window, including legacy entries. */
32
+ export declare function goalGuardAfter(branch: readonly {
33
+ readonly id: string;
34
+ readonly type: string;
35
+ readonly customType?: string;
36
+ readonly data?: unknown;
37
+ }[], guardStartLeafId: string | null): RunStopFacts["guard"];
38
+ /** Malformed optional detail is dropped; it never invalidates the record that carries it. */
39
+ export declare function readLastStop(value: unknown, runId: unknown): LifecycleStop | undefined;
40
+ export {};
41
+ //# sourceMappingURL=lifecycle-stop.d.ts.map
@@ -0,0 +1,92 @@
1
+ import { isRecord } from "./work-custody-record.js";
2
+ const ABORT_SOURCES = {
3
+ "user-abort": "user",
4
+ "system-abort": "system",
5
+ "provider-abort": "provider",
6
+ "unknown-abort": null,
7
+ };
8
+ /**
9
+ * First match wins: the run's own deadline explains its system abort, a guard explains a refused continuation, then
10
+ * the classified abort, and `no_wake` only for a stall no hold or guard owns. Provider failures keep the failed path.
11
+ */
12
+ export function stopDetail(input, facts, deadline, stalled) {
13
+ if (input?.endedBy === "provider-error" || input?.endedBy === "policy-rejection")
14
+ return undefined;
15
+ if (deadline)
16
+ return {
17
+ cause: "deadline",
18
+ deadline: deadline.kind,
19
+ at: deadline.at,
20
+ ...(deadline.persisted === false ? { persisted: false } : {}),
21
+ };
22
+ if (facts?.guard)
23
+ return { cause: "goal_guard", guard: facts.guard.reason, at: new Date(facts.guard.at).toISOString() };
24
+ if (facts?.todo === "capped" || facts?.todo === "cap")
25
+ return { cause: "todo_guard", todo: "cap" };
26
+ const abortSource = input ? ABORT_SOURCES[input.endedBy] : undefined;
27
+ if (abortSource !== undefined)
28
+ return { cause: "aborted", ...(abortSource === null ? {} : { abortSource }) };
29
+ if (stalled && !input?.deadlineStop && !input?.budgetSpent)
30
+ return { cause: "no_wake" };
31
+ return undefined;
32
+ }
33
+ export function sameStop(stop, detail) {
34
+ return (stop.cause === detail.cause &&
35
+ stop.deadline === detail.deadline &&
36
+ stop.guard === detail.guard &&
37
+ stop.todo === detail.todo &&
38
+ stop.abortSource === detail.abortSource &&
39
+ stop.persisted === detail.persisted);
40
+ }
41
+ /** The latest guard appended within one bound end's extension-processing window, including legacy entries. */
42
+ export function goalGuardAfter(branch, guardStartLeafId) {
43
+ const start = guardStartLeafId === null ? -1 : branch.findIndex((entry) => entry.id === guardStartLeafId);
44
+ if (guardStartLeafId !== null && start < 0)
45
+ return undefined;
46
+ for (let index = branch.length - 1; index > start; index--) {
47
+ const entry = branch[index];
48
+ if (entry?.type !== "custom" || entry.customType !== "goal-continuation-stopped" || !isRecord(entry.data))
49
+ continue;
50
+ const { reason, at } = entry.data;
51
+ return typeof reason === "string" && typeof at === "number" ? { reason, at } : undefined;
52
+ }
53
+ return undefined;
54
+ }
55
+ /** Malformed optional detail is dropped; it never invalidates the record that carries it. */
56
+ export function readLastStop(value, runId) {
57
+ if (!isRecord(value) ||
58
+ typeof value.runId !== "string" ||
59
+ value.runId !== runId ||
60
+ typeof value.at !== "string" ||
61
+ typeof value.epoch !== "number")
62
+ return undefined;
63
+ const base = { runId: value.runId, at: value.at, epoch: value.epoch };
64
+ switch (value.cause) {
65
+ case "deadline":
66
+ return value.deadline === "inactivity" || value.deadline === "max_runtime"
67
+ ? {
68
+ ...base,
69
+ cause: value.cause,
70
+ deadline: value.deadline,
71
+ ...(value.persisted === false ? { persisted: false } : {}),
72
+ }
73
+ : undefined;
74
+ case "goal_guard":
75
+ return { ...base, cause: value.cause, ...(typeof value.guard === "string" ? { guard: value.guard } : {}) };
76
+ case "todo_guard":
77
+ return { ...base, cause: value.cause, todo: "cap" };
78
+ case "aborted":
79
+ return {
80
+ ...base,
81
+ cause: value.cause,
82
+ ...(value.abortSource === "user" || value.abortSource === "system" || value.abortSource === "provider"
83
+ ? { abortSource: value.abortSource }
84
+ : {}),
85
+ };
86
+ case "no_wake":
87
+ return { ...base, cause: value.cause };
88
+ default:
89
+ return undefined;
90
+ }
91
+ }
92
+ //# sourceMappingURL=lifecycle-stop.js.map
@@ -0,0 +1,89 @@
1
+ import type { MrtrInputResponse } from "./extensions/builtin/mcp/modern/elicitation.ts";
2
+ import type { ValidatedMcpFormSchema } from "./extensions/builtin/mcp/modern/form-schema.ts";
3
+ export interface McpFormScope {
4
+ readonly hostInstance: string;
5
+ readonly durableSessionId: string;
6
+ readonly bindingEpoch: string;
7
+ readonly attachmentId: string;
8
+ readonly attachmentEpoch: string;
9
+ readonly connectionId: string;
10
+ readonly connectionEpoch: string;
11
+ readonly presenterPrincipalId: string;
12
+ readonly presenterGeneration: string;
13
+ readonly presentationBindingId: string;
14
+ readonly presenterKind: "desktop" | "gateway" | "local_tui";
15
+ readonly purpose: "mcp_form";
16
+ }
17
+ export interface McpFormEnvelope {
18
+ readonly sessionId: string;
19
+ readonly bindingEpoch: string;
20
+ readonly attachmentEpoch: string;
21
+ readonly grantId: string;
22
+ readonly dialogId: string;
23
+ readonly dialogToken: string;
24
+ readonly formRevision: string;
25
+ }
26
+ export interface McpFormRequest extends McpFormEnvelope {
27
+ readonly type: "mcp_form_request";
28
+ readonly server: {
29
+ readonly label: string;
30
+ readonly source: "user" | "project" | "plugin";
31
+ };
32
+ readonly message: string;
33
+ readonly schema: ValidatedMcpFormSchema;
34
+ readonly deadlineAtMs: number;
35
+ }
36
+ export interface McpFormResolved {
37
+ readonly type: "mcp_form_resolved";
38
+ readonly sessionId: string;
39
+ readonly grantId: string;
40
+ readonly dialogId: string;
41
+ readonly reason: "answered" | "declined" | "cancelled" | "processing_failed" | "operation_aborted";
42
+ }
43
+ export type McpFormOutcome = ({
44
+ readonly kind: "result";
45
+ readonly result: MrtrInputResponse;
46
+ } | {
47
+ readonly kind: "processing_failure" | "operation_aborted";
48
+ }) & ({
49
+ readonly source: "presenter_action";
50
+ readonly humanAdmission: true;
51
+ } | {
52
+ readonly source: "host_termination";
53
+ readonly humanAdmission: false;
54
+ });
55
+ export interface McpFormInput {
56
+ readonly operationId: string;
57
+ readonly inputId: string;
58
+ readonly round: number;
59
+ readonly server: McpFormRequest["server"];
60
+ readonly message: string;
61
+ readonly schema: ValidatedMcpFormSchema;
62
+ readonly operationSignal: AbortSignal;
63
+ /** Host reads a worker-owned atomic abort fence at the terminal latch, without an IPC round trip. */
64
+ readonly operationCurrent?: () => boolean;
65
+ readonly presentationSignal: AbortSignal;
66
+ /** Remaining UI budget, measured by the host's monotonic clock. */
67
+ readonly deadlineMs: number;
68
+ }
69
+ export interface McpFormPresentation {
70
+ readonly signal: AbortSignal;
71
+ readonly ready: () => boolean;
72
+ readonly present: (input: McpFormInput) => Promise<McpFormOutcome>;
73
+ /** Worker-backed presenters recheck host readiness before each network attempt. */
74
+ readonly synchronize?: () => Promise<void>;
75
+ }
76
+ export interface McpFormBinding {
77
+ readonly epoch: string;
78
+ readonly signal: AbortSignal;
79
+ readonly capture: () => McpFormPresentation | undefined;
80
+ readonly dispose: () => void;
81
+ readonly version?: () => string | undefined;
82
+ readonly subscribe?: (listener: () => void) => () => void;
83
+ }
84
+ export declare function bindMcpFormPresented(options: object, callback: (() => void) | undefined): void;
85
+ export declare function markMcpFormPresented(options: object | undefined): void;
86
+ /** An enforced binding is part of the host-issued presenter identity, not a UI capability declaration. */
87
+ export declare function bindPrivateMcpForms(ui: object, binding: McpFormBinding): void;
88
+ export declare function privateMcpForms(ui: object): McpFormBinding | undefined;
89
+ //# sourceMappingURL=mcp-form-types.d.ts.map
@@ -0,0 +1,23 @@
1
+ import { getMcpMrtrPresentation, registerMcpMrtrPresentation } from "./mcp-mrtr-presentations.js";
2
+ const presentedCallbacks = new WeakMap();
3
+ export function bindMcpFormPresented(options, callback) {
4
+ if (callback)
5
+ presentedCallbacks.set(options, callback);
6
+ else
7
+ presentedCallbacks.delete(options);
8
+ }
9
+ export function markMcpFormPresented(options) {
10
+ if (!options)
11
+ return;
12
+ const callback = presentedCallbacks.get(options);
13
+ presentedCallbacks.delete(options);
14
+ callback?.();
15
+ }
16
+ /** An enforced binding is part of the host-issued presenter identity, not a UI capability declaration. */
17
+ export function bindPrivateMcpForms(ui, binding) {
18
+ registerMcpMrtrPresentation(ui, binding.signal, undefined, binding);
19
+ }
20
+ export function privateMcpForms(ui) {
21
+ return getMcpMrtrPresentation(ui)?.privateForms;
22
+ }
23
+ //# sourceMappingURL=mcp-form-types.js.map
@@ -0,0 +1,15 @@
1
+ import type { ExtensionUIContext } from "./extensions/types.ts";
2
+ import type { McpFormBinding } from "./mcp-form-types.ts";
3
+ /** Local host references only. RPC capabilities and serialized UI objects cannot enter this registry. */
4
+ export interface McpMrtrPresentation {
5
+ readonly ui: Pick<ExtensionUIContext, "select" | "input">;
6
+ readonly signal: AbortSignal;
7
+ readonly admit?: () => void;
8
+ readonly presented?: () => void;
9
+ readonly privateForms?: McpFormBinding;
10
+ }
11
+ export declare function registerMcpMrtrPresentation(ui: ExtensionUIContext, signal: AbortSignal, admit?: () => void, privateForms?: McpFormBinding): ExtensionUIContext;
12
+ export declare function getMcpMrtrPresentation(ui: object): McpMrtrPresentation | undefined;
13
+ /** Preserve host identity across runner wrapping; private dialogs still use the raw host UI. */
14
+ export declare function inheritMcpMrtrPresentation(source: ExtensionUIContext, wrapper: ExtensionUIContext): ExtensionUIContext;
15
+ //# sourceMappingURL=mcp-mrtr-presentations.d.ts.map
@@ -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
@@ -10,8 +10,6 @@ export function createSessionControlActions(deps) {
10
10
  let retired = false;
11
11
  return {
12
12
  registerControlEndpoint: async (options) => {
13
- if (process.platform === "win32")
14
- return { status: "unsupported", reason: "unsupported_platform" };
15
13
  const host = deps.host();
16
14
  if (host === undefined)
17
15
  return { status: "unsupported", reason: "unsupported_mode" };
@@ -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
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;