@code-yeongyu/senpi 2026.10.25-2 → 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 (199) hide show
  1. package/CHANGELOG.md +20 -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-6O4SEW2W.js +5 -0
  5. package/dist/bundle/chunks/{chunk-Y5OTHMXO.js → chunk-7CNOCJCN.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-JRXKLIEQ.js → chunk-7W7OJDIY.js} +4 -4
  7. package/dist/bundle/chunks/chunk-AX72VNRS.js +27 -0
  8. package/dist/bundle/chunks/{chunk-LYGAV6KU.js → chunk-C5UFJ7B3.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-G2UETS2R.js → chunk-CNL6QSUB.js} +4 -4
  10. package/dist/bundle/chunks/{chunk-2EFVQWXZ.js → chunk-FFQOEC76.js} +3 -3
  11. package/dist/bundle/chunks/chunk-H3HNMY2W.js +13 -0
  12. package/dist/bundle/chunks/{chunk-W2BKZBYJ.js → chunk-HVJI5T75.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-YN7ELQ43.js → chunk-JA2R2ZSE.js} +1 -1
  14. package/dist/bundle/chunks/chunk-JPGT64RV.js +2 -0
  15. package/dist/bundle/chunks/chunk-KXB5WIS5.js +4 -0
  16. package/dist/bundle/chunks/chunk-L73UYAH5.js +3 -0
  17. package/dist/bundle/chunks/chunk-LTU6UFEX.js +2 -0
  18. package/dist/bundle/chunks/{chunk-KUE4AQN6.js → chunk-MJ5NNWX4.js} +1 -1
  19. package/dist/bundle/chunks/{chunk-WFXUCVIR.js → chunk-O3Q2ET6W.js} +2 -5
  20. package/dist/bundle/chunks/{chunk-MXZPAUFB.js → chunk-ODTGC6YU.js} +1 -1
  21. package/dist/bundle/chunks/chunk-P3M2PF6K.js +27 -0
  22. package/dist/bundle/chunks/{chunk-VNN2V74Y.js → chunk-SWJXRC2I.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-RXKNOEQQ.js → chunk-UQNRW3VU.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-U67ML6CQ.js → chunk-WD6WBYC5.js} +1 -1
  25. package/dist/bundle/chunks/chunk-WJOT3DFL.js +2 -0
  26. package/dist/bundle/chunks/{chunk-XBUSVKWC.js → chunk-WLHNEUGD.js} +1 -1
  27. package/dist/bundle/chunks/chunk-YI7SPFDN.js +5 -0
  28. package/dist/bundle/chunks/{chunk-J2U5VDG7.js → chunk-YRF4I43T.js} +28 -28
  29. package/dist/bundle/chunks/chunk-Z3UYNSVR.js +3 -0
  30. package/dist/bundle/chunks/cli-main-HEKFFB5S.js +3 -0
  31. package/dist/bundle/chunks/{client-KWTLTKVV.js → client-WTYEICLH.js} +3 -3
  32. package/dist/bundle/chunks/dispatch-NYAULNVW.js +9 -0
  33. package/dist/bundle/chunks/{help-fast-path-ZHZGXAUG.js → help-fast-path-3Z26DQSV.js} +1 -1
  34. package/dist/bundle/chunks/host-command-Z3WPHJOW.js +2 -0
  35. package/dist/bundle/chunks/host-lifecycle-ONQIMWMX.js +3 -0
  36. package/dist/bundle/chunks/host-lifecycle.js +7 -5
  37. package/dist/bundle/chunks/host-session-command-J65ENSV5.js +20 -0
  38. package/dist/bundle/chunks/{host-socket-request-AS2OCRLP.js → host-socket-request-3CWE4265.js} +1 -1
  39. package/dist/bundle/chunks/interactive-mode-DEPQC6TX.js +2 -0
  40. package/dist/bundle/chunks/mcp-presenter-bootstrap-XUSCQVRC.js +2 -0
  41. package/dist/bundle/chunks/{multi-session-host-VTTY5MFT.js → multi-session-host-YI2WZOET.js} +12 -9
  42. package/dist/bundle/chunks/{package-manager-cli-DZFOC3MF.js → package-manager-cli-GHZPJMRF.js} +1 -1
  43. package/dist/bundle/chunks/{rotation-stream-U7YQMC7H.js → rotation-stream-ZPVCFOVD.js} +1 -1
  44. package/dist/bundle/chunks/rpc-mode-ORHBGBBV.js +2 -0
  45. package/dist/bundle/chunks/{rpc-monitor-source-B5NQ4ZVH.js → rpc-monitor-source-FFEQS744.js} +1 -1
  46. package/dist/bundle/chunks/{schedule-command-SLISTG4V.js → schedule-command-YWG6D6ZF.js} +1 -1
  47. package/dist/bundle/chunks/session-control-endpoint-36HAQSQO.js +3 -0
  48. package/dist/bundle/chunks/{session-picker-465FVXQG.js → session-picker-25H5KNYP.js} +1 -1
  49. package/dist/bundle/chunks/session-worker.js +278 -264
  50. package/dist/bundle/chunks/{stdio-IEC635SY.js → stdio-YXDKKKQZ.js} +1 -1
  51. package/dist/bundle/chunks/{streamableHttp-KJI5KE4K.js → streamableHttp-DKIBUWVY.js} +1 -1
  52. package/dist/bundle/chunks/{types-LLNV23LW.js → types-BJUEQ4II.js} +1 -1
  53. package/dist/bundle/chunks/zod-compat-RJDXCU7S.js +2 -0
  54. package/dist/bundle/cli.js +2 -2
  55. package/dist/bundle/host-supervisor.js +7 -5
  56. package/dist/bundle/index.js +1 -1
  57. package/dist/bundle/rpc-entry.js +1 -1
  58. package/dist/bundle/runtime-manifest.json +1 -1
  59. package/dist/cli/args.d.ts +2 -0
  60. package/dist/cli/args.js +9 -0
  61. package/dist/cli/host-command-args.d.ts +1 -0
  62. package/dist/cli/host-command-args.js +7 -1
  63. package/dist/cli/host-command.js +3 -1
  64. package/dist/core/agent-session.js +22 -7
  65. package/dist/core/extensions/builtin/goal/index.js +1 -0
  66. package/dist/core/extensions/builtin/goal/lifecycle-facts.d.ts +3 -0
  67. package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -1
  68. package/dist/core/extensions/builtin/mcp/catalog.js +2 -0
  69. package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -0
  70. package/dist/core/extensions/builtin/mcp/connection.js +46 -1
  71. package/dist/core/extensions/builtin/mcp/elicitation.d.ts +6 -1
  72. package/dist/core/extensions/builtin/mcp/elicitation.js +56 -2
  73. package/dist/core/extensions/builtin/mcp/expose/call.js +6 -2
  74. package/dist/core/extensions/builtin/mcp/expose/session.d.ts +4 -1
  75. package/dist/core/extensions/builtin/mcp/expose/session.js +7 -0
  76. package/dist/core/extensions/builtin/mcp/health.js +5 -0
  77. package/dist/core/extensions/builtin/mcp/host-registry.js +7 -1
  78. package/dist/core/extensions/builtin/mcp/index.js +2 -0
  79. package/dist/core/extensions/builtin/mcp/modern/client.d.ts +5 -0
  80. package/dist/core/extensions/builtin/mcp/modern/client.js +15 -0
  81. package/dist/core/extensions/builtin/mcp/modern/consent-queue.d.ts +2 -0
  82. package/dist/core/extensions/builtin/mcp/modern/consent-queue.js +59 -0
  83. package/dist/core/extensions/builtin/mcp/modern/diagnostics.d.ts +4 -0
  84. package/dist/core/extensions/builtin/mcp/modern/diagnostics.js +39 -0
  85. package/dist/core/extensions/builtin/mcp/modern/dispatch.js +21 -11
  86. package/dist/core/extensions/builtin/mcp/modern/elicitation.d.ts +14 -0
  87. package/dist/core/extensions/builtin/mcp/modern/elicitation.js +145 -0
  88. package/dist/core/extensions/builtin/mcp/modern/errors.d.ts +21 -0
  89. package/dist/core/extensions/builtin/mcp/modern/errors.js +23 -0
  90. package/dist/core/extensions/builtin/mcp/modern/form-schema.d.ts +20 -0
  91. package/dist/core/extensions/builtin/mcp/modern/form-schema.js +153 -0
  92. package/dist/core/extensions/builtin/mcp/modern/http-fetch.d.ts +3 -1
  93. package/dist/core/extensions/builtin/mcp/modern/http-fetch.js +8 -2
  94. package/dist/core/extensions/builtin/mcp/modern/inputs.d.ts +11 -0
  95. package/dist/core/extensions/builtin/mcp/modern/inputs.js +65 -0
  96. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +11 -0
  97. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +109 -0
  98. package/dist/core/extensions/builtin/mcp/modern/request.d.ts +20 -0
  99. package/dist/core/extensions/builtin/mcp/modern/request.js +292 -0
  100. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.d.ts +1 -1
  101. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +54 -6
  102. package/dist/core/extensions/builtin/mcp/modern/transport.js +80 -14
  103. package/dist/core/extensions/builtin/mcp/modern/wire.js +11 -7
  104. package/dist/core/extensions/builtin/mcp/prompts.d.ts +5 -1
  105. package/dist/core/extensions/builtin/mcp/prompts.js +6 -1
  106. package/dist/core/extensions/builtin/mcp/request-origin.d.ts +47 -0
  107. package/dist/core/extensions/builtin/mcp/request-origin.js +59 -0
  108. package/dist/core/extensions/builtin/mcp/resources.d.ts +4 -1
  109. package/dist/core/extensions/builtin/mcp/resources.js +19 -4
  110. package/dist/core/extensions/builtin/mcp/sdk.lazy.d.ts +3 -1
  111. package/dist/core/extensions/builtin/mcp/sdk.lazy.js +3 -1
  112. package/dist/core/extensions/builtin/mcp/service-register.d.ts +4 -1
  113. package/dist/core/extensions/builtin/mcp/service-register.js +3 -0
  114. package/dist/core/extensions/builtin/mcp/service-types.d.ts +2 -1
  115. package/dist/core/extensions/builtin/mcp/service.d.ts +1 -0
  116. package/dist/core/extensions/builtin/mcp/service.js +79 -4
  117. package/dist/core/extensions/builtin/mcp/shared-connection.js +36 -2
  118. package/dist/core/extensions/builtin/mcp/shared-lease.d.ts +1 -0
  119. package/dist/core/extensions/builtin/mcp/shared-lease.js +9 -0
  120. package/dist/core/extensions/builtin/mcp/transport-sdk.d.ts +3 -2
  121. package/dist/core/extensions/builtin/mcp/transport-sdk.js +56 -25
  122. package/dist/core/extensions/builtin/mcp/wrap.d.ts +7 -0
  123. package/dist/core/extensions/builtin/mcp/wrap.js +6 -0
  124. package/dist/core/extensions/runner.js +3 -2
  125. package/dist/core/extensions/types.d.ts +2 -0
  126. package/dist/core/lifecycle-ledger.d.ts +1 -0
  127. package/dist/core/lifecycle-ledger.js +7 -2
  128. package/dist/core/lifecycle-record.js +3 -0
  129. package/dist/core/lifecycle-session-binding.d.ts +11 -1
  130. package/dist/core/lifecycle-session-binding.js +29 -2
  131. package/dist/core/lifecycle-stop.d.ts +41 -0
  132. package/dist/core/lifecycle-stop.js +92 -0
  133. package/dist/core/mcp-form-types.d.ts +89 -0
  134. package/dist/core/mcp-form-types.js +23 -0
  135. package/dist/core/mcp-mrtr-presentations.d.ts +15 -0
  136. package/dist/core/mcp-mrtr-presentations.js +17 -0
  137. package/dist/core/session-lifecycle-types.d.ts +15 -0
  138. package/dist/core/session-lifecycle.d.ts +17 -1
  139. package/dist/core/session-lifecycle.js +40 -2
  140. package/dist/main.js +5 -1
  141. package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
  142. package/dist/modes/interactive/components/extension-selector.js +4 -0
  143. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  144. package/dist/modes/interactive/interactive-mode.js +34 -8
  145. package/dist/modes/interactive/mcp-local-presenter.d.ts +5 -0
  146. package/dist/modes/interactive/mcp-local-presenter.js +109 -0
  147. package/dist/modes/rpc/connection-handler.d.ts +1 -0
  148. package/dist/modes/rpc/connection-handler.js +7 -1
  149. package/dist/modes/rpc/host-capabilities.d.ts +1 -0
  150. package/dist/modes/rpc/host-capabilities.js +2 -1
  151. package/dist/modes/rpc/host-ensure-start.js +13 -2
  152. package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
  153. package/dist/modes/rpc/host-ensure.js +1 -0
  154. package/dist/modes/rpc/host-handoff.d.ts +2 -0
  155. package/dist/modes/rpc/host-handover-wire.d.ts +2 -0
  156. package/dist/modes/rpc/host-handover-wire.js +1 -0
  157. package/dist/modes/rpc/host-lifecycle-launch.d.ts +1 -0
  158. package/dist/modes/rpc/host-lifecycle-launch.js +43 -2
  159. package/dist/modes/rpc/host-runner.d.ts +2 -0
  160. package/dist/modes/rpc/host-runner.js +8 -6
  161. package/dist/modes/rpc/host-successor.js +11 -1
  162. package/dist/modes/rpc/mcp-form-dialog.d.ts +45 -0
  163. package/dist/modes/rpc/mcp-form-dialog.js +159 -0
  164. package/dist/modes/rpc/mcp-form-worker.d.ts +14 -0
  165. package/dist/modes/rpc/mcp-form-worker.js +167 -0
  166. package/dist/modes/rpc/mcp-presenter-authority.d.ts +53 -0
  167. package/dist/modes/rpc/mcp-presenter-authority.js +309 -0
  168. package/dist/modes/rpc/mcp-presenter-bootstrap.d.ts +8 -0
  169. package/dist/modes/rpc/mcp-presenter-bootstrap.js +111 -0
  170. package/dist/modes/rpc/mcp-presenter-control.d.ts +24 -0
  171. package/dist/modes/rpc/mcp-presenter-control.js +330 -0
  172. package/dist/modes/rpc/multi-session-host.d.ts +3 -0
  173. package/dist/modes/rpc/multi-session-host.js +36 -1
  174. package/dist/modes/rpc/session-binding.js +6 -1
  175. package/dist/modes/rpc/session-event-writer.d.ts +14 -0
  176. package/dist/modes/rpc/session-event-writer.js +128 -1
  177. package/dist/modes/rpc/session-router-routing.js +1 -0
  178. package/dist/modes/rpc/session-worker-client.d.ts +3 -0
  179. package/dist/modes/rpc/session-worker-client.js +89 -0
  180. package/dist/modes/rpc/session-worker-protocol.d.ts +41 -0
  181. package/dist/modes/rpc/session-worker.js +9 -0
  182. package/docs/extensions.md +5 -0
  183. package/docs/mcp.md +20 -3
  184. package/docs/rpc.md +111 -0
  185. package/package.json +6 -6
  186. package/dist/bundle/chunks/chunk-B6TPQDBW.js +0 -5
  187. package/dist/bundle/chunks/chunk-CDRFFHLL.js +0 -4
  188. package/dist/bundle/chunks/chunk-HPCS4HM4.js +0 -27
  189. package/dist/bundle/chunks/chunk-KJZAY5YH.js +0 -27
  190. package/dist/bundle/chunks/chunk-OI4NMCKP.js +0 -3
  191. package/dist/bundle/chunks/chunk-SCVRKIFK.js +0 -2
  192. package/dist/bundle/chunks/cli-main-SWKFMTZQ.js +0 -3
  193. package/dist/bundle/chunks/dispatch-DEP4PEDZ.js +0 -10
  194. package/dist/bundle/chunks/host-command-BC6UWP33.js +0 -2
  195. package/dist/bundle/chunks/host-lifecycle-TN5NEGNF.js +0 -3
  196. package/dist/bundle/chunks/host-session-command-MGVBLBRV.js +0 -20
  197. package/dist/bundle/chunks/interactive-mode-SCSROHD5.js +0 -2
  198. package/dist/bundle/chunks/rpc-mode-TOXYD4XA.js +0 -2
  199. package/dist/bundle/chunks/session-control-endpoint-HFFMBALG.js +0 -3
@@ -1,6 +1,7 @@
1
1
  import { ConfigReferenceError } from "@earendil-works/pi-ai/utils/config-reference";
2
2
  import { getAgentDir } from "../../../../config.js";
3
3
  import { redactResolvedReferenceData } from "../../../config-reference-redaction.js";
4
+ import { getMcpMrtrPresentation } from "../../../mcp-mrtr-presentations.js";
4
5
  import { getToolSearchService, getToolSearchServiceForExtension, resetToolSearchServiceForTests, ToolSearchService, } from "../tool-search/service.js";
5
6
  import { mcpCredentialIdentity } from "./auth/catalog-identity.js";
6
7
  import { resolveAuthMode } from "./auth/context.js";
@@ -13,6 +14,8 @@ import { HostMcpRegistry } from "./host-registry.js";
13
14
  import { refreshMcpInstructionsForSession } from "./instructions.js";
14
15
  import { createMcpLogger } from "./log.js";
15
16
  import { prepareProjectSkillServer } from "./project-skill-policy.js";
17
+ import { createMcpRequestOrigin } from "./request-origin.js";
18
+ import { McpMrtrError } from "./modern/errors.js";
16
19
  import { reconnectMcpNow } from "./reconnect.js";
17
20
  import { createMcpSessionConnection, disposeEntryConnection } from "./service-connection.js";
18
21
  import { getMcpServiceExposureStatus } from "./service-exposure.js";
@@ -181,8 +184,12 @@ export class McpService {
181
184
  };
182
185
  const previous = this.#bindings.get(pi);
183
186
  // A session-owned service serves exactly one session: a new attach takes over its binding.
184
- if (!this.#servesManySessions)
187
+ previous?.lifetime.abort();
188
+ if (!this.#servesManySessions) {
189
+ for (const binding of this.#bindings.values())
190
+ binding.lifetime.abort();
185
191
  this.#bindings.clear();
192
+ }
186
193
  let fallbackToolSearch;
187
194
  let holdsProcessFallback = false;
188
195
  const sessionToolSearch = getToolSearchServiceForExtension(pi);
@@ -205,6 +212,8 @@ export class McpService {
205
212
  }
206
213
  }
207
214
  const binding = {
215
+ lifetime: new AbortController(),
216
+ presentation: owner.context.ui === undefined ? undefined : getMcpMrtrPresentation(owner.context.ui),
208
217
  pi,
209
218
  fallbackToolSearch,
210
219
  holdsProcessFallback,
@@ -224,8 +233,10 @@ export class McpService {
224
233
  #liveBindings() {
225
234
  for (const [pi, binding] of this.#bindings) {
226
235
  // A disposed session retires its own tool-search service; its binding goes with it.
227
- if (getToolSearchServiceForExtension(binding.pi)?.isDisposed === true)
236
+ if (getToolSearchServiceForExtension(binding.pi)?.isDisposed === true) {
237
+ binding.lifetime.abort();
228
238
  this.#bindings.delete(pi);
239
+ }
229
240
  }
230
241
  return [...this.#bindings.values()];
231
242
  }
@@ -240,6 +251,7 @@ export class McpService {
240
251
  */
241
252
  async releaseSession(pi, disposeReason) {
242
253
  const released = this.#bindings.get(pi);
254
+ released?.lifetime.abort();
243
255
  this.#bindings.delete(pi);
244
256
  if (disposeReason !== undefined)
245
257
  this.#releasedSessions.add(pi);
@@ -386,7 +398,7 @@ export class McpService {
386
398
  ? await prepareProjectSkillServer(name, decl, {
387
399
  context: binding.context,
388
400
  options: binding.options,
389
- ui: () => this.getMcpElicitationUi(),
401
+ ui: this.#elicitationProviderFor(binding),
390
402
  notice: (identity, names) => {
391
403
  // An untrusted project keeps the skill's references literal (senpi#2345); keep that fix in
392
404
  // the one notice instead of a generic withheld line (senpi#3796).
@@ -527,6 +539,8 @@ export class McpService {
527
539
  this.#disposed = true;
528
540
  this.#disposeCount += 1;
529
541
  this.#lastDisposeReason = reason;
542
+ for (const binding of this.#bindings.values())
543
+ binding.lifetime.abort();
530
544
  this.#bindings.clear();
531
545
  this.#sessionContext = null;
532
546
  this.#config = null;
@@ -711,7 +725,7 @@ export class McpService {
711
725
  session: options,
712
726
  elicitationEnabled: declarer.context.mode !== "print" && declarer.context.mode !== "json",
713
727
  cwd: declarer.context.cwd,
714
- ui: () => this.getMcpElicitationUi(),
728
+ ui: this.#elicitationProviderFor(declarer),
715
729
  artifacts: this.#outputArtifacts,
716
730
  shouldReconnect: (current) => !this.#disposed &&
717
731
  this.#entryForName(name) === current &&
@@ -813,6 +827,8 @@ export class McpService {
813
827
  onRegistered: (entry, identity) => binding.registeredIdentities.set(entry.key, identity),
814
828
  contextRequired: binding.context.mode !== undefined,
815
829
  sessionManager: binding.context.sessionManager,
830
+ requestOrigin: (entry) => this.#requestOrigin(binding, entry),
831
+ bindPresentation: (ui) => this.bindMcpMrtrPresentation(binding.pi, ui),
816
832
  // A new attach of this session replaces its binding (and with it its config); a peer's attach does not.
817
833
  isCurrent: (entry) => !this.#disposed &&
818
834
  this.#bindings.get(binding.pi) === binding &&
@@ -955,9 +971,68 @@ export class McpService {
955
971
  setMcpElicitationUiProvider(provider) {
956
972
  this.#elicitationUiProvider = provider;
957
973
  }
974
+ bindMcpMrtrPresentation(pi, ui) {
975
+ const binding = this.#bindings.get(pi);
976
+ const presentation = getMcpMrtrPresentation(ui);
977
+ if (binding && presentation)
978
+ binding.presentation = presentation;
979
+ }
980
+ #requestOrigin(binding, entry) {
981
+ const source = binding.config.servers[entry.name]?.source;
982
+ return createMcpRequestOrigin({
983
+ binding,
984
+ connection: entry.connection,
985
+ signal: AbortSignal.any([binding.lifetime.signal, entry.connection.requestLifetimeSignal]),
986
+ presentation: binding.presentation,
987
+ privateFormsEnforced: binding.presentation?.privateForms !== undefined,
988
+ privateForm: binding.presentation?.privateForms?.capture(),
989
+ serverLabel: entry.name,
990
+ serverSource: source === "project" || source === "claude"
991
+ ? "project"
992
+ : source === "skill" || source === "extension"
993
+ ? "plugin"
994
+ : "user",
995
+ isCurrent: () => !this.#disposed &&
996
+ !binding.lifetime.signal.aborted &&
997
+ this.#bindings.get(binding.pi) === binding &&
998
+ [...this.#connections.values()].includes(entry) &&
999
+ declares(binding.config, entry.name, entry.configHash) &&
1000
+ entry.isCurrent?.() !== false &&
1001
+ entry.credentialsCurrent?.() !== false,
1002
+ resolveAuthority: async (signal) => {
1003
+ if (await entry.generationChanged?.())
1004
+ throw new McpMrtrError("MCP_MRTR_CONTEXT_CHANGED");
1005
+ // Same observed-revocation boundary as ordinary MCP requests. Never prompts for a new grant.
1006
+ return await entry.connection.projectPolicy?.resolve(signal, false);
1007
+ },
1008
+ });
1009
+ }
958
1010
  getMcpElicitationUi() {
959
1011
  return this.#elicitationUiProvider?.();
960
1012
  }
1013
+ #elicitationProviderFor(owner) {
1014
+ // Connections outlive their first declarer's extension context. Capture its host-owned
1015
+ // references while it is live; readiness must never read a released session's ctx.ui.
1016
+ const binding = [...this.#bindings.values()].find((candidate) => candidate === owner);
1017
+ const ui = owner.context.ui;
1018
+ const privateBinding = () => {
1019
+ // Keep observing presentation replacements on the binding, not just its initial grant.
1020
+ const presentation = binding
1021
+ ? binding.presentation
1022
+ : ui === undefined
1023
+ ? undefined
1024
+ : getMcpMrtrPresentation(ui);
1025
+ return presentation?.privateForms;
1026
+ };
1027
+ return Object.assign(() => this.getMcpElicitationUi(), {
1028
+ enforced: () => privateBinding() !== undefined,
1029
+ subscribe: (listener) => privateBinding()?.subscribe?.(listener) ?? (() => { }),
1030
+ eligible: () => {
1031
+ const binding = privateBinding();
1032
+ return binding === undefined || binding.capture()?.ready() === true;
1033
+ },
1034
+ });
1035
+ }
961
1036
  isMcpPromptCommandRegistered(name) {
962
1037
  return this.#promptCommandNames.has(name);
963
1038
  }
@@ -9,6 +9,8 @@ import { safeInterval, safeTimer } from "./wrap.js";
9
9
  /** Owns one SDK handler set, reconnect loop, cache writer and aggregate lifetime. */
10
10
  export class SharedMcpConnection {
11
11
  #calls;
12
+ #readinessListeners;
13
+ #readinessSubscriptions;
12
14
  #options;
13
15
  #onDispose;
14
16
  #idleTimeoutMin;
@@ -22,6 +24,8 @@ export class SharedMcpConnection {
22
24
  constructor(options, onDispose) {
23
25
  this.leases = new Set();
24
26
  this.#calls = new Map();
27
+ this.#readinessListeners = new Set();
28
+ this.#readinessSubscriptions = new Map();
25
29
  this.#cacheGeneration = -1;
26
30
  this.#disposed = false;
27
31
  this.#inFlight = 0;
@@ -64,13 +68,30 @@ export class SharedMcpConnection {
64
68
  },
65
69
  },
66
70
  logger,
67
- elicitationUiProvider: () => {
71
+ elicitationUiProvider: Object.assign(() => {
68
72
  // SDK server requests carry no originating tool-call id.
69
73
  const owners = [...this.#calls.keys()];
70
74
  return owners.length === 1 && this.leases.has(owners[0])
71
75
  ? owners[0].options.elicitationUiProvider?.()
72
76
  : undefined;
73
- },
77
+ }, {
78
+ enforced: () => [...this.leases].some((lease) => lease.options.elicitationUiProvider?.enforced?.()),
79
+ subscribe: (listener) => {
80
+ this.#readinessListeners.add(listener);
81
+ return () => {
82
+ this.#readinessListeners.delete(listener);
83
+ };
84
+ },
85
+ eligible: () => {
86
+ const leases = [...this.leases];
87
+ const eligible = (lease) => lease.options.elicitationEnabled !== false &&
88
+ lease.options.elicitationUiProvider?.eligible?.() !== false;
89
+ // Any ready owner permits negotiation; per-operation authority still selects the holder.
90
+ if (leases.some((lease) => lease.options.elicitationUiProvider?.enforced?.()))
91
+ return leases.some(eligible);
92
+ return leases.length > 0 && leases.every(eligible);
93
+ },
94
+ }),
74
95
  });
75
96
  this.connection.onToolsChanged(() => {
76
97
  this.#catalog = undefined;
@@ -112,6 +133,14 @@ export class SharedMcpConnection {
112
133
  this.#clearIdle();
113
134
  const lease = new SharedMcpLease(this, options, owner, key);
114
135
  this.leases.add(lease);
136
+ const unsubscribe = options.elicitationUiProvider?.subscribe?.(() => {
137
+ for (const listener of this.#readinessListeners)
138
+ listener();
139
+ });
140
+ if (unsubscribe)
141
+ this.#readinessSubscriptions.set(lease, unsubscribe);
142
+ for (const listener of this.#readinessListeners)
143
+ listener();
115
144
  this.#idleTimeoutMin = Math.max(...[...this.leases].map((item) => item.options.config.idleTimeoutMin));
116
145
  this.#refreshKeepAlive();
117
146
  return lease;
@@ -154,6 +183,11 @@ export class SharedMcpConnection {
154
183
  // Capture the last attached policy before removing the final owner.
155
184
  this.#idleTimeoutMin = Math.max(...[...this.leases].map((item) => item.options.config.idleTimeoutMin));
156
185
  this.leases.delete(lease);
186
+ this.#readinessSubscriptions.get(lease)?.();
187
+ this.#readinessSubscriptions.delete(lease);
188
+ if (this.leases.size > 0)
189
+ for (const listener of this.#readinessListeners)
190
+ listener();
157
191
  this.#refreshKeepAlive();
158
192
  this.#armIdle();
159
193
  }
@@ -9,6 +9,7 @@ export declare class SharedMcpLease extends ServerConnection {
9
9
  readonly key: string;
10
10
  readonly options: ServerConnectionOptions;
11
11
  readonly shared: SharedMcpConnection;
12
+ get requestLifetimeSignal(): AbortSignal;
12
13
  constructor(shared: SharedMcpConnection, options: ServerConnectionOptions, owner: object, key: string);
13
14
  get state(): import("./connection-types.ts").ServerConnectionState;
14
15
  get generation(): number;
@@ -1,5 +1,6 @@
1
1
  import { ServerConnection } from "./connection.js";
2
2
  import { ConnectError } from "./errors.js";
3
+ import { registerMcpClientAlias } from "./request-origin.js";
3
4
  const requestMethods = new Set([
4
5
  "request",
5
6
  "listTools",
@@ -19,6 +20,10 @@ export class SharedMcpLease extends ServerConnection {
19
20
  #unsubscribeTools;
20
21
  #stateSubscriptions = new Set();
21
22
  #released = false;
23
+ #requestLifetime = new AbortController();
24
+ get requestLifetimeSignal() {
25
+ return this.#requestLifetime.signal;
26
+ }
22
27
  #localGeneration = 0;
23
28
  #client;
24
29
  #physicalClient;
@@ -63,6 +68,7 @@ export class SharedMcpLease extends ServerConnection {
63
68
  return typeof value === "function" ? value.bind(target) : value;
64
69
  },
65
70
  });
71
+ registerMcpClientAlias(this.#client, physical);
66
72
  }
67
73
  if (this.#client === undefined)
68
74
  throw new ConnectError("MCP client unavailable", { phase: "client" });
@@ -79,6 +85,8 @@ export class SharedMcpLease extends ServerConnection {
79
85
  }
80
86
  async bumpGeneration() {
81
87
  this.#assertAttached();
88
+ this.#requestLifetime.abort();
89
+ this.#requestLifetime = new AbortController();
82
90
  this.#localGeneration++;
83
91
  this.#refreshCatalog = true;
84
92
  // Re-list only this owner's catalog; never interrupt another owner's call.
@@ -128,6 +136,7 @@ export class SharedMcpLease extends ServerConnection {
128
136
  if (this.#released)
129
137
  return;
130
138
  this.#released = true;
139
+ this.#requestLifetime.abort();
131
140
  this.#unsubscribeTools();
132
141
  for (const off of this.#stateSubscriptions)
133
142
  off();
@@ -11,8 +11,9 @@
11
11
  import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
12
12
  import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
13
13
  import type { McpOAuthProvider } from "./auth/oauth-provider.ts";
14
- import { type McpElicitationUiProvider } from "./elicitation.ts";
14
+ import type { McpElicitationUiProvider } from "./elicitation.ts";
15
15
  import type { McpLogger } from "./log.ts";
16
+ import type { ModernProtocolState } from "./modern/request.ts";
16
17
  import { type McpAsyncErrorSink } from "./wrap.ts";
17
18
  export type McpStdioTransportSpec = {
18
19
  readonly projectPolicy?: boolean;
@@ -48,5 +49,5 @@ export interface MaterializeMcpTransportOptions {
48
49
  /** Invoked once the stdio child has started, so the caller can capture its pid. */
49
50
  readonly onStart: () => void;
50
51
  }
51
- export declare function materializeMcpTransport(options: MaterializeMcpTransportOptions): Promise<McpMaterializedTransport>;
52
+ export declare function materializeMcpTransport(options: MaterializeMcpTransportOptions, protocol?: ModernProtocolState): Promise<McpMaterializedTransport>;
52
53
  //# sourceMappingURL=transport-sdk.d.ts.map
@@ -1,14 +1,15 @@
1
- import { configureMcpElicitation, mcpClientCapabilities } from "./elicitation.js";
1
+ import { modernCredentialSnapshot } from "./modern/http-fetch.js";
2
+ import { createMcpRequestClient } from "./modern/client.js";
2
3
  import { ProjectStdioTransport } from "./project-stdio-transport.js";
3
- import { loadMcpSdkClient, loadMcpSdkStdioTransport, loadMcpSdkStreamableHttpTransport } from "./sdk.lazy.js";
4
+ import { loadMcpSdkStdioTransport, loadMcpSdkStreamableHttpTransport } from "./sdk.lazy.js";
4
5
  import { safeOn } from "./wrap.js";
5
- export async function materializeMcpTransport(options) {
6
- const client = await buildMcpClient(options.elicitationUiProvider, options.elicitationEnabled);
6
+ export async function materializeMcpTransport(options, protocol = {}) {
7
+ const client = await createMcpRequestClient(protocol, options.elicitationUiProvider, () => modernCredentialSnapshot(options.spec.authProvider?.tokens()), options.elicitationEnabled);
7
8
  if (options.spec.kind === "stdio")
8
- return materializeStdio(options.spec, options, client);
9
+ return materializeStdio(options.spec, options, client, protocol);
9
10
  return materializeHttp(options.spec, client);
10
11
  }
11
- async function materializeStdio(spec, options, client) {
12
+ async function materializeStdio(spec, options, client, protocol) {
12
13
  const { getDefaultEnvironment, StdioClientTransport } = await loadMcpSdkStdioTransport();
13
14
  const create = (current) => new StdioClientTransport({
14
15
  args: current.args,
@@ -25,7 +26,7 @@ async function materializeStdio(spec, options, client) {
25
26
  ? new ProjectStdioTransport(spec.resolveBeforeStart, create)
26
27
  : create(spec);
27
28
  trackStdioStart(transport, options.onStart);
28
- pipeStderr(transport, options.logger, options.sink);
29
+ pipeStderr(transport, options.logger, options.sink, protocol);
29
30
  dropNullNextCursor(transport);
30
31
  return {
31
32
  client,
@@ -48,15 +49,6 @@ async function materializeHttp(spec, client) {
48
49
  transport,
49
50
  };
50
51
  }
51
- async function buildMcpClient(elicitationUiProvider, elicitationEnabled = true) {
52
- // Elicitation capability is declared EMPTY on purpose (form mode only;
53
- // Spring-AI servers reject richer shapes) and the create-handler is wired
54
- // before any connect so mid-call requests never race registration.
55
- const { Client: McpClient } = await loadMcpSdkClient();
56
- const client = new McpClient({ name: "senpi-mcp-client", version: "0.0.0" }, { capabilities: mcpClientCapabilities(elicitationEnabled) });
57
- await configureMcpElicitation(client, elicitationUiProvider, undefined, elicitationEnabled);
58
- return client;
59
- }
60
52
  // OMP pattern: hand stdio OAuth servers the current access token via env.
61
53
  function oauthAccessTokenEnv(authProvider) {
62
54
  const accessToken = authProvider?.tokens()?.access_token;
@@ -90,26 +82,65 @@ function trackStdioStart(transport, onStart) {
90
82
  onStart();
91
83
  };
92
84
  }
93
- function pipeStderr(transport, logger, sink) {
85
+ function pipeStderr(transport, logger, sink, protocol) {
94
86
  let pending = "";
87
+ let undecided = "";
88
+ let stderrEnded = false;
95
89
  const stderr = transport.stderr;
96
90
  if (stderr === undefined || stderr === null)
97
91
  return;
98
- safeOn(stderr, "data", "transport.stderr.data", (chunk) => {
99
- if (!Buffer.isBuffer(chunk) && typeof chunk !== "string")
100
- return;
101
- pending += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
92
+ const logLine = (line) => {
93
+ // Arbitrary server text may echo private messages, schemas, or answers. Keep diagnostic
94
+ // events and allowlisted system error codes, never untrusted payload text under enforcement.
95
+ const code = protocol.privateForms
96
+ ? line.match(/\b(?:EACCES|ENOENT|EPIPE|ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOSPC|ENOMEM|EMFILE)\b/)?.[0]
97
+ : undefined;
98
+ logger.stderr(protocol.privateForms ? `[private MCP stderr redacted]${code ? ` ${code}` : ""}` : line);
99
+ };
100
+ const append = (text) => {
101
+ pending += text;
102
102
  const lines = pending.split(/\r?\n/);
103
103
  pending = lines.pop() ?? "";
104
- for (const line of lines) {
104
+ for (const line of lines)
105
105
  if (line.length > 0)
106
- logger.stderr(line);
106
+ logLine(line);
107
+ };
108
+ protocol.resolveStdioDiagnostics = (modern) => {
109
+ if (!modern || protocol.privateForms) {
110
+ append(undecided);
111
+ if (stderrEnded && pending.length > 0) {
112
+ logLine(pending);
113
+ pending = "";
114
+ }
115
+ }
116
+ undecided = "";
117
+ if (modern && !protocol.privateForms)
118
+ pending = "";
119
+ };
120
+ safeOn(stderr, "data", "transport.stderr.data", (chunk) => {
121
+ if (protocol.version !== undefined && !protocol.privateForms) {
122
+ pending = "";
123
+ undecided = "";
124
+ return;
125
+ }
126
+ if (!Buffer.isBuffer(chunk) && typeof chunk !== "string")
127
+ return;
128
+ const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
129
+ if (!protocol.legacy && !protocol.privateForms) {
130
+ // Only legacy-positive negotiation may release startup diagnostics.
131
+ undecided += text.slice(0, Math.max(0, 512 * 1024 - undecided.length));
132
+ return;
107
133
  }
134
+ append(text);
108
135
  }, sink);
109
136
  safeOn(stderr, "end", "transport.stderr.end", () => {
110
- if (pending.length > 0)
111
- logger.stderr(pending);
137
+ stderrEnded = true;
138
+ if ((protocol.privateForms || (protocol.legacy && protocol.version === undefined)) && pending.length > 0)
139
+ logLine(pending);
112
140
  pending = "";
141
+ // A crashed probe is classified after EOF; only that decision may release or discard its buffer.
142
+ if (protocol.legacy || protocol.version !== undefined)
143
+ undecided = "";
113
144
  }, sink);
114
145
  }
115
146
  async function closeClientAndTransport(transport, client) {
@@ -3,6 +3,13 @@ export type { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  export type { CallToolResult, ListToolsResult } from "@modelcontextprotocol/sdk/types.js";
4
4
  import type { EventEmitter } from "node:events";
5
5
  import type { EventBus } from "../../../event-bus.ts";
6
+ export interface McpClock {
7
+ now(): number;
8
+ schedule(callback: () => void, ms: number): ReturnType<typeof setTimeout>;
9
+ cancel(timer: ReturnType<typeof setTimeout>): void;
10
+ }
11
+ /** The modern operation uses one injectable monotonic clock and scheduler. */
12
+ export declare const mcpElapsedClock: McpClock;
6
13
  type MaybePromise<T> = T | Promise<T>;
7
14
  export interface McpErrorLogger {
8
15
  error(message: string, data?: unknown): void;
@@ -1,4 +1,10 @@
1
1
  import { redactMcpLogText } from "./log.js";
2
+ /** The modern operation uses one injectable monotonic clock and scheduler. */
3
+ export const mcpElapsedClock = {
4
+ now: () => performance.now(),
5
+ schedule: setTimeout,
6
+ cancel: clearTimeout,
7
+ };
2
8
  export function wrapAsync(scope, fn, sink) {
3
9
  return async (...args) => {
4
10
  try {
@@ -6,6 +6,7 @@ import { getAgentDir } from "../../config.js";
6
6
  import { theme } from "../../modes/interactive/theme/theme.js";
7
7
  import { stripAnsi } from "../../utils/ansi.js";
8
8
  import { createEventBus, EXTENSION_RPC_EVENT_CHANNEL } from "../event-bus.js";
9
+ import { inheritMcpMrtrPresentation } from "../mcp-mrtr-presentations.js";
9
10
  import { bindQuestionInput } from "../question-input.js";
10
11
  import { DEFAULT_EXTENSION_EVENT_HANDLER_TIMEOUT_MS, DEFAULT_EXTENSION_EVENT_HANDLER_WARN_MS, DEFAULT_SESSION_SHUTDOWN_HANDLER_TIMEOUT_MS, DEFAULT_SESSION_SHUTDOWN_HANDLER_WARN_MS, DEFAULT_SESSION_SHUTDOWN_QUIT_BUDGET_MS, SettingsManager, } from "../settings-manager.js";
11
12
  import { goalFilePath } from "./builtin/goal/persistence.js";
@@ -589,7 +590,7 @@ export class ExtensionRunner {
589
590
  }
590
591
  wrapUIPromptContext(ui) {
591
592
  const questionFn = ui.question;
592
- return {
593
+ return inheritMcpMrtrPresentation(ui, {
593
594
  ...ui,
594
595
  select: (title, options, opts) => this.withUIPrompt("select", title, () => ui.select(title, options, opts)),
595
596
  confirm: (title, message, opts) => this.withUIPrompt("confirm", title, () => ui.confirm(title, message, opts)),
@@ -601,7 +602,7 @@ export class ExtensionRunner {
601
602
  question: (request, opts) => this.withUIPrompt("question", request.questions[0]?.header, () => questionFn(request, opts), request.requestId),
602
603
  }
603
604
  : {}),
604
- };
605
+ });
605
606
  }
606
607
  withUIPrompt(kind, title, run, requestId = crypto.randomUUID()) {
607
608
  const outerPrompt = this.uiPromptDepth++ === 0;
@@ -59,6 +59,8 @@ export type CompactionReason = "manual" | "threshold" | "overflow" | "pre_prompt
59
59
  export type CompactionRejectionCause = "cancelled-by-extension" | "external-owner" | "would-overflow" | "circuit-breaker" | "per-turn-cap" | "stale-revision";
60
60
  /** Options for extension UI dialogs. */
61
61
  export interface ExtensionUIDialogOptions {
62
+ /** Private local dialog: emits no generic blocked event or pending marker. Not an RPC field. */
63
+ ephemeral?: boolean;
62
64
  /** AbortSignal to programmatically dismiss the dialog. */
63
65
  signal?: AbortSignal;
64
66
  /** Timeout in milliseconds. Dialog auto-dismisses with live countdown display. */
@@ -39,6 +39,7 @@ export declare class LifecycleLedger {
39
39
  readonly waits: LifecycleWaits;
40
40
  readonly holds: readonly LifecycleHold[];
41
41
  readonly failure?: SessionLifecycle["failure"];
42
+ readonly lastStop?: SessionLifecycle["lastStop"];
42
43
  }): void;
43
44
  checkpoint(): void;
44
45
  /** Publishes a revision or epoch that no record carries yet, once. */
@@ -80,6 +80,9 @@ export class LifecycleLedger {
80
80
  startRun(origin) {
81
81
  this.lastTurnEnd = undefined;
82
82
  this.openEpoch(origin);
83
+ // The previous run's stop never describes this one (senpi#3864).
84
+ const { lastStop: _lastStop, ...current } = this.current;
85
+ this.current = current;
83
86
  this.runId = crypto.randomUUID();
84
87
  this.runActive = true;
85
88
  this.admissionWriteFailed = false;
@@ -134,7 +137,7 @@ export class LifecycleLedger {
134
137
  if (input.holds.length > 0 || input.state === "failed")
135
138
  this.request = undefined;
136
139
  const terminal = input.state === "done" || input.state === "failed";
137
- const { completionId: _id, completion: _completion, failure: _failure, settleDecision: _decision, ...base } = this.current;
140
+ const { completionId: _id, completion: _completion, failure: _failure, settleDecision: _decision, lastStop: _lastStop, ...base } = this.current;
138
141
  const now = new Date().toISOString();
139
142
  const standingRequest = this.request?.epoch === base.epoch &&
140
143
  this.request.revision === base.humanInputRevision &&
@@ -163,6 +166,7 @@ export class LifecycleLedger {
163
166
  }
164
167
  : {}),
165
168
  ...(input.state === "failed" && input.failure ? { failure: input.failure } : {}),
169
+ ...(input.lastStop ? { lastStop: input.lastStop } : {}),
166
170
  ...(input.state === "done" && standingRequest
167
171
  ? {
168
172
  settleDecision: this.current.settleDecision ?? { decisionId: crypto.randomUUID() },
@@ -238,7 +242,8 @@ function equivalent(previous, next) {
238
242
  previous.selfSettleEligible === next.selfSettleEligible &&
239
243
  JSON.stringify(previous.waitingOn) === JSON.stringify(next.waitingOn) &&
240
244
  JSON.stringify(previous.holds) === JSON.stringify(next.holds) &&
241
- JSON.stringify(previous.failure) === JSON.stringify(next.failure));
245
+ JSON.stringify(previous.failure) === JSON.stringify(next.failure) &&
246
+ JSON.stringify(previous.lastStop) === JSON.stringify(next.lastStop));
242
247
  }
243
248
  function wireSnapshot(record) {
244
249
  const { sessionId: _sessionId, interrupted: _interrupted, completedGoalId: _goal, completedRunId: _completedRun, lastTurnEnd: _lastTurnEnd, attentionSeq: _attentionSeq, synthetic: _synthetic, epochSealed: _sealed, settleRequest: _request, runId: _runId, runActive: _runActive, ...snapshot } = record;
@@ -1,5 +1,6 @@
1
1
  import { readHumanWait } from "./lifecycle-attention.js";
2
2
  import { readLifecycleState } from "./lifecycle-children.js";
3
+ import { readLastStop } from "./lifecycle-stop.js";
3
4
  import { isRecord } from "./work-custody-record.js";
4
5
  /** Restore the wire snapshot and its private high-water marks from owned JSONL. */
5
6
  export function readLifecycleRecord(value) {
@@ -33,6 +34,7 @@ export function readLifecycleRecord(value) {
33
34
  const completion = readCompletion(value.completion);
34
35
  const failure = readFailure(value.failure);
35
36
  const lastTurnEnd = readTurnEnd(value.lastTurnEnd);
37
+ const lastStop = readLastStop(value.lastStop, value.runId);
36
38
  return {
37
39
  sessionId: value.sessionId,
38
40
  state,
@@ -63,6 +65,7 @@ export function readLifecycleRecord(value) {
63
65
  ...(completion ? { completion } : {}),
64
66
  ...(failure ? { failure } : {}),
65
67
  ...(lastTurnEnd ? { lastTurnEnd } : {}),
68
+ ...(lastStop ? { lastStop } : {}),
66
69
  ...(isRecord(value.settleDecision) && typeof value.settleDecision.decisionId === "string"
67
70
  ? { settleDecision: { decisionId: value.settleDecision.decisionId } }
68
71
  : {}),
@@ -5,6 +5,7 @@ import type { ExtensionRunner } from "./extensions/runner.ts";
5
5
  import type { AgentEndEvent } from "./extensions/types.ts";
6
6
  import { SessionLifecycleController } from "./session-lifecycle.ts";
7
7
  import type { SessionLifecycleEvent } from "./session-lifecycle-types.ts";
8
+ import type { TurnDeadlineKind } from "./turn-deadline.ts";
8
9
  interface LifecycleBindingHost {
9
10
  append(type: string, data: unknown): string;
10
11
  emit(event: SessionLifecycleEvent): void;
@@ -20,6 +21,11 @@ export declare class LifecycleSessionBinding {
20
21
  private readonly unsubscribeAdmission;
21
22
  private readonly unsubscribeGoal;
22
23
  private lastEnd;
24
+ /** The run each emitted agent_end belonged to, read when it was emitted, not when its queue reaches it. */
25
+ private readonly boundEnds;
26
+ private lastEndRunId;
27
+ /** Captured once inside the bound end's own extension-processing window, never re-scanned by a later run. */
28
+ private lastEndGuard;
23
29
  private refreshGeneration;
24
30
  private restored;
25
31
  private nextRunOrigin;
@@ -32,9 +38,13 @@ export declare class LifecycleSessionBinding {
32
38
  prepareRun(): void;
33
39
  get isRestored(): boolean;
34
40
  startRun(): void;
41
+ deadlineStopped(kind: TurnDeadlineKind, atMs: number): void;
42
+ deadlineStopWriteFailed(): void;
43
+ bindTurnEnd(end: object): void;
35
44
  consume(message: AgentMessage): string | undefined;
36
45
  consumedEntry(message: AgentMessage): string | undefined;
37
- turnEnded(event: AgentEndEvent): Promise<void>;
46
+ /** `event` is the extension agent_end, never the session-level stamp; `end` is the core event bound at emission. */
47
+ turnEnded(event: AgentEndEvent, end: object, guardStartLeafId?: string | null): Promise<void>;
38
48
  refreshFacts(changedGoal?: Goal | null): Promise<void>;
39
49
  idle(): Promise<void>;
40
50
  restore(): Promise<void>;