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

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 (260) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/bundle/chunks/{anthropic-messages-WBASBTIW.js → anthropic-messages-ODSFJCXR.js} +3 -3
  3. package/dist/bundle/chunks/{args-B444OQBK.js → args-SWKJAYQD.js} +1 -1
  4. package/dist/bundle/chunks/azure-openai-responses-REGXS4XO.js +2 -0
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +3 -3
  6. package/dist/bundle/chunks/chunk-2XF6W6F6.js +410 -0
  7. package/dist/bundle/chunks/{chunk-WDPQ6CZJ.js → chunk-4QVORFQ2.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-6AOVUTUR.js → chunk-5W3DTM2J.js} +2 -2
  9. package/dist/bundle/chunks/{chunk-E6WYH3ZL.js → chunk-6GAMRAVM.js} +9 -7
  10. package/dist/bundle/chunks/{chunk-N7IN46Q5.js → chunk-7IZH2KQX.js} +9 -7
  11. package/dist/bundle/chunks/{chunk-7GZUFOM3.js → chunk-ARSENVDY.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-STMXIBXF.js → chunk-CNDPCYNT.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-XF3LJC3O.js → chunk-CYOHFREV.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-E6D4RIT7.js → chunk-DNDZPKIQ.js} +2 -2
  15. package/dist/bundle/chunks/{chunk-E4LBKOZG.js → chunk-EQNUL2WB.js} +4 -4
  16. package/dist/bundle/chunks/{chunk-2RB5EL4X.js → chunk-ESNSRUW4.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-4AOMNOGY.js → chunk-FQDARFHT.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-BWA4FVFW.js → chunk-GPPOPK76.js} +3 -3
  19. package/dist/bundle/chunks/{chunk-SBGIXMX3.js → chunk-GWGNVYPB.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-KKD4CYCI.js → chunk-H4WJ4LY3.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-V2UYXTRR.js → chunk-HWTD4QF3.js} +1 -1
  22. package/dist/bundle/chunks/chunk-I2WFLKZ2.js +5 -0
  23. package/dist/bundle/chunks/{chunk-WD6WBYC5.js → chunk-IHKHCWWG.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-T36LJ6MW.js → chunk-IY4G44MN.js} +1 -1
  25. package/dist/bundle/chunks/{chunk-BZ7726QO.js → chunk-J2YAYTB2.js} +1 -1
  26. package/dist/bundle/chunks/{chunk-UFJ2K7Q7.js → chunk-JH7MVRNJ.js} +1 -1
  27. package/dist/bundle/chunks/chunk-KBSFTXAT.js +2 -0
  28. package/dist/bundle/chunks/{chunk-OLCDIITS.js → chunk-KRO63T4Q.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-TTU2RNVX.js → chunk-KTDIIF2F.js} +1 -1
  30. package/dist/bundle/chunks/{chunk-KNVNKDER.js → chunk-L2O6K3SJ.js} +1 -1
  31. package/dist/bundle/chunks/{chunk-3BTN337S.js → chunk-NDNIMC5W.js} +1 -1
  32. package/dist/bundle/chunks/{chunk-ITGYN22E.js → chunk-ODRGPC5S.js} +1 -1
  33. package/dist/bundle/chunks/{chunk-4MOZXOJX.js → chunk-PDG4XWO2.js} +1 -1
  34. package/dist/bundle/chunks/{chunk-3QCYBOKN.js → chunk-PPECSSWX.js} +1 -1
  35. package/dist/bundle/chunks/{chunk-4P7WARQZ.js → chunk-PY4ZQHTN.js} +1 -1
  36. package/dist/bundle/chunks/{chunk-C2MQ4QXN.js → chunk-RPW3XQW3.js} +48 -42
  37. package/dist/bundle/chunks/{chunk-JMOUU75H.js → chunk-RQ5JTXUC.js} +1 -1
  38. package/dist/bundle/chunks/chunk-TQ67PB7U.js +13 -0
  39. package/dist/bundle/chunks/{chunk-5JJDR6XI.js → chunk-UJPG4ROT.js} +1 -1
  40. package/dist/bundle/chunks/{chunk-FHXR4ZAC.js → chunk-XM36IFP3.js} +1 -1
  41. package/dist/bundle/chunks/{chunk-PT6JJUN2.js → chunk-XMNPK472.js} +1 -1
  42. package/dist/bundle/chunks/{client-WTYEICLH.js → chunk-Y3TMJV3J.js} +3 -3
  43. package/dist/bundle/chunks/{chunk-GSSD3R5X.js → chunk-Z6BREXV3.js} +1 -1
  44. package/dist/bundle/chunks/{chunk-KXB5WIS5.js → chunk-Z6SAQ5O3.js} +1 -1
  45. package/dist/bundle/chunks/{chunk-LOIF6PHI.js → chunk-ZAUVY6ZC.js} +3 -3
  46. package/dist/bundle/chunks/{chunk-7CNOCJCN.js → chunk-ZP6JJDYV.js} +1 -1
  47. package/dist/bundle/chunks/cli-main-Q2GA53XM.js +3 -0
  48. package/dist/bundle/chunks/client-LOS6GSEW.js +2 -0
  49. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-K5H6BJP3.js → cloudflare-workers-ai-system-one-WDHT7BBV.js} +1 -1
  50. package/dist/bundle/chunks/cursor-agent.js +3 -3
  51. package/dist/bundle/chunks/devin-agent.js +3 -3
  52. package/dist/bundle/chunks/dispatch-5R3TYAYB.js +9 -0
  53. package/dist/bundle/chunks/google-generative-ai-FHYFXKLA.js +2 -0
  54. package/dist/bundle/chunks/google-vertex-5DWE5U5K.js +2 -0
  55. package/dist/bundle/chunks/{help-fast-path-3Z26DQSV.js → help-fast-path-FETLIF3W.js} +1 -1
  56. package/dist/bundle/chunks/host-command-XEB7SHT3.js +2 -0
  57. package/dist/bundle/chunks/{host-lifecycle-3VHHIGDL.js → host-lifecycle-HX3EGRRW.js} +1 -1
  58. package/dist/bundle/chunks/host-lifecycle.js +2 -2
  59. package/dist/bundle/chunks/{host-session-command-56XF6ACS.js → host-session-command-FFN52LXJ.js} +1 -1
  60. package/dist/bundle/chunks/{host-socket-request-QL2WEZTN.js → host-socket-request-K4LCUOUD.js} +1 -1
  61. package/dist/bundle/chunks/interactive-mode-5DEKY3YW.js +2 -0
  62. package/dist/bundle/chunks/{mcp-presenter-bootstrap-XUSCQVRC.js → mcp-presenter-bootstrap-FOMKF4TU.js} +1 -1
  63. package/dist/bundle/chunks/{mistral-conversations-DH44NOYV.js → mistral-conversations-I2PI7DDF.js} +1 -1
  64. package/dist/bundle/chunks/{multi-session-host-Z2CW4SDM.js → multi-session-host-OPR5VBFO.js} +6 -7
  65. package/dist/bundle/chunks/{openai-codex-responses-SERAFKAS.js → openai-codex-responses-WSEX6SH7.js} +1 -1
  66. package/dist/bundle/chunks/{openai-completions-BZB5O45A.js → openai-completions-YCBHTBSS.js} +1 -1
  67. package/dist/bundle/chunks/openai-responses-GXNSQF4A.js +3 -0
  68. package/dist/bundle/chunks/{package-manager-cli-N55U6PEO.js → package-manager-cli-W55E3LHP.js} +1 -1
  69. package/dist/bundle/chunks/{pi-messages-VXATX45B.js → pi-messages-O6GISIRW.js} +2 -2
  70. package/dist/bundle/chunks/{role-JVSBNKGE.js → role-SHIZQKK4.js} +1 -1
  71. package/dist/bundle/chunks/{rotation-stream-CBGCBYHV.js → rotation-stream-2PCZWNKF.js} +1 -1
  72. package/dist/bundle/chunks/rpc-mode-IDUJTSYS.js +2 -0
  73. package/dist/bundle/chunks/{rpc-monitor-source-AE3C6EQV.js → rpc-monitor-source-DRZXJHTL.js} +1 -1
  74. package/dist/bundle/chunks/{schedule-command-VS7CZC2J.js → schedule-command-B7RRSANU.js} +1 -1
  75. package/dist/bundle/chunks/session-control-endpoint-LMZH5JLH.js +3 -0
  76. package/dist/bundle/chunks/{session-picker-E4KUJDLO.js → session-picker-DTGBOXDR.js} +1 -1
  77. package/dist/bundle/chunks/session-worker.js +214 -205
  78. package/dist/bundle/chunks/{typesafe-system-one-P75WJ767.js → typesafe-system-one-XGYJJVSE.js} +1 -1
  79. package/dist/bundle/cli.js +2 -2
  80. package/dist/bundle/host-supervisor.js +2 -2
  81. package/dist/bundle/index.js +1 -1
  82. package/dist/bundle/rpc-entry.js +1 -1
  83. package/dist/bundle/runtime-manifest.json +1 -1
  84. package/dist/cli/host-command-args.d.ts +1 -0
  85. package/dist/cli/host-command-args.js +5 -0
  86. package/dist/cli/host-command.js +7 -1
  87. package/dist/cli.js +21 -3
  88. package/dist/core/agent-session.d.ts +11 -1
  89. package/dist/core/agent-session.js +19 -2
  90. package/dist/core/extensions/builtin/compaction/index.js +7 -1
  91. package/dist/core/extensions/builtin/herdr/index.js +9 -5
  92. package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.d.ts +6 -0
  93. package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.js +25 -0
  94. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +3 -3
  95. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +50 -19
  96. package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +4 -3
  97. package/dist/core/extensions/builtin/mcp/auth/oauth.js +19 -12
  98. package/dist/core/extensions/builtin/mcp/catalog-cache.d.ts +2 -1
  99. package/dist/core/extensions/builtin/mcp/catalog-cache.js +19 -6
  100. package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -2
  101. package/dist/core/extensions/builtin/mcp/catalog.js +20 -3
  102. package/dist/core/extensions/builtin/mcp/expose/call.d.ts +2 -1
  103. package/dist/core/extensions/builtin/mcp/expose/call.js +2 -1
  104. package/dist/core/extensions/builtin/mcp/expose/register.js +1 -1
  105. package/dist/core/extensions/builtin/mcp/expose/schema-compat.d.ts +2 -2
  106. package/dist/core/extensions/builtin/mcp/expose/schema-compat.js +11 -1
  107. package/dist/core/extensions/builtin/mcp/invocation.js +16 -2
  108. package/dist/core/extensions/builtin/mcp/modern/dispatch.js +1 -0
  109. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +4 -1
  110. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +33 -35
  111. package/dist/core/extensions/builtin/mcp/modern/request.d.ts +1 -1
  112. package/dist/core/extensions/builtin/mcp/modern/request.js +4 -1
  113. package/dist/core/extensions/builtin/mcp/modern/sdk-bridge.js +18 -3
  114. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +23 -0
  115. package/dist/core/extensions/builtin/mcp/modern/subscriptions.d.ts +14 -0
  116. package/dist/core/extensions/builtin/mcp/modern/subscriptions.js +226 -0
  117. package/dist/core/extensions/builtin/mcp/modern/tool-schema.d.ts +13 -0
  118. package/dist/core/extensions/builtin/mcp/modern/tool-schema.js +75 -0
  119. package/dist/core/extensions/builtin/mcp/modern/tools.d.ts +107 -0
  120. package/dist/core/extensions/builtin/mcp/modern/tools.js +53 -0
  121. package/dist/core/extensions/builtin/mcp/modern/transport.d.ts +2 -0
  122. package/dist/core/extensions/builtin/mcp/modern/transport.js +4 -2
  123. package/dist/core/extensions/builtin/mcp/resources.js +4 -0
  124. package/dist/core/extensions/builtin/mcp/tool-client.d.ts +13 -0
  125. package/dist/core/extensions/builtin/mcp/tool-client.js +17 -0
  126. package/dist/core/extensions/builtin/mcp/tool-contracts.d.ts +19 -0
  127. package/dist/core/extensions/builtin/mcp/tool-contracts.js +2 -0
  128. package/dist/core/extensions/builtin/mcp/wrap.js +2 -2
  129. package/dist/core/extensions/builtin/permission-system/bash-command-segments.d.ts +37 -0
  130. package/dist/core/extensions/builtin/permission-system/bash-command-segments.js +899 -0
  131. package/dist/core/extensions/builtin/permission-system/parsers.d.ts +2 -0
  132. package/dist/core/extensions/builtin/permission-system/parsers.js +44 -10
  133. package/dist/core/extensions/builtin/terminal/extension-state.js +2 -1
  134. package/dist/core/extensions/builtin/vertex-model-garden/auth.d.ts +9 -0
  135. package/dist/core/extensions/builtin/vertex-model-garden/auth.js +17 -1
  136. package/dist/core/extensions/builtin/websearch/websearch/providers/google.d.ts +6 -0
  137. package/dist/core/extensions/builtin/websearch/websearch/providers/google.js +6 -1
  138. package/dist/core/extensions/types.d.ts +2 -0
  139. package/dist/core/lifecycle-children.d.ts +3 -1
  140. package/dist/core/lifecycle-children.js +79 -15
  141. package/dist/core/lifecycle-record.js +1 -24
  142. package/dist/core/lifecycle-session-binding.js +2 -1
  143. package/dist/core/lifecycle-wait-items.d.ts +17 -0
  144. package/dist/core/lifecycle-wait-items.js +72 -0
  145. package/dist/core/model-runtime.js +6 -1
  146. package/dist/core/provider-api-key-auth.d.ts +6 -1
  147. package/dist/core/provider-api-key-auth.js +11 -4
  148. package/dist/core/provider-composer.d.ts +8 -1
  149. package/dist/core/provider-composer.js +7 -3
  150. package/dist/core/sdk.js +4 -1
  151. package/dist/core/session-activity.js +2 -0
  152. package/dist/core/session-lifecycle-types.d.ts +20 -4
  153. package/dist/core/session-lifecycle.js +35 -4
  154. package/dist/core/structured-session-lane.d.ts +33 -0
  155. package/dist/core/structured-session-lane.js +62 -0
  156. package/dist/core/work-custody-record.d.ts +2 -1
  157. package/dist/core/work-custody-record.js +2 -0
  158. package/dist/core/work-obligations.d.ts +1 -1
  159. package/dist/core/work-obligations.js +8 -2
  160. package/dist/modes/interactive/session-control-commands.js +21 -4
  161. package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
  162. package/dist/modes/interactive/session-control-rpc-commands.js +19 -0
  163. package/dist/modes/json-event.d.ts +79 -1
  164. package/dist/modes/json-event.js +79 -1
  165. package/dist/modes/rpc/compact-content-index.d.ts +85 -0
  166. package/dist/modes/rpc/compact-content-index.js +254 -0
  167. package/dist/modes/rpc/compact-content-store.d.ts +59 -0
  168. package/dist/modes/rpc/compact-content-store.js +134 -0
  169. package/dist/modes/rpc/compact-index-pages.d.ts +12 -0
  170. package/dist/modes/rpc/compact-index-pages.js +31 -0
  171. package/dist/modes/rpc/compact-session-state.d.ts +83 -0
  172. package/dist/modes/rpc/compact-session-state.js +197 -0
  173. package/dist/modes/rpc/compact-spool-dir.d.ts +8 -0
  174. package/dist/modes/rpc/compact-spool-dir.js +94 -0
  175. package/dist/modes/rpc/compact-spool-storage.d.ts +53 -0
  176. package/dist/modes/rpc/compact-spool-storage.js +162 -0
  177. package/dist/modes/rpc/compact-storage-io.d.ts +28 -0
  178. package/dist/modes/rpc/compact-storage-io.js +25 -0
  179. package/dist/modes/rpc/compact-transition.d.ts +41 -0
  180. package/dist/modes/rpc/compact-transition.js +12 -0
  181. package/dist/modes/rpc/connection-handler.d.ts +4 -0
  182. package/dist/modes/rpc/connection-handler.js +51 -13
  183. package/dist/modes/rpc/connection-question-bridge.d.ts +2 -1
  184. package/dist/modes/rpc/connection-question-bridge.js +6 -2
  185. package/dist/modes/rpc/extension-ui-response.d.ts +7 -2
  186. package/dist/modes/rpc/extension-ui-response.js +27 -7
  187. package/dist/modes/rpc/get-snapshot.d.ts +18 -0
  188. package/dist/modes/rpc/get-snapshot.js +43 -0
  189. package/dist/modes/rpc/host-capabilities.js +3 -0
  190. package/dist/modes/rpc/host-ensure-start.js +6 -3
  191. package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
  192. package/dist/modes/rpc/host-ensure.js +1 -0
  193. package/dist/modes/rpc/host-handoff.d.ts +2 -0
  194. package/dist/modes/rpc/host-handover-request.d.ts +1 -1
  195. package/dist/modes/rpc/host-handover-request.js +4 -3
  196. package/dist/modes/rpc/host-handover-wire.js +6 -0
  197. package/dist/modes/rpc/host-idle-handover.d.ts +2 -0
  198. package/dist/modes/rpc/host-idle-handover.js +3 -1
  199. package/dist/modes/rpc/host-observe-request.js +1 -0
  200. package/dist/modes/rpc/host-runner.d.ts +1 -0
  201. package/dist/modes/rpc/host-runner.js +9 -7
  202. package/dist/modes/rpc/host-successor.js +4 -0
  203. package/dist/modes/rpc/json-normal-value.d.ts +6 -0
  204. package/dist/modes/rpc/json-normal-value.js +100 -0
  205. package/dist/modes/rpc/mcp-presenter-bootstrap.js +5 -0
  206. package/dist/modes/rpc/media-placeholders.d.ts +9 -3
  207. package/dist/modes/rpc/media-placeholders.js +12 -4
  208. package/dist/modes/rpc/rpc-host-command-types.d.ts +2 -0
  209. package/dist/modes/rpc/rpc-question-types.d.ts +1 -0
  210. package/dist/modes/rpc/rpc-read-response-types.d.ts +7 -0
  211. package/dist/modes/rpc/rpc-session-command-types.d.ts +6 -0
  212. package/dist/modes/rpc/rpc-session-state-types.d.ts +3 -0
  213. package/dist/modes/rpc/rpc-session-state.js +2 -0
  214. package/dist/modes/rpc/rpc-types.d.ts +1 -0
  215. package/dist/modes/rpc/security-presentations.js +2 -1
  216. package/dist/modes/rpc/session-attachment-gate.js +1 -0
  217. package/dist/modes/rpc/session-binding.js +30 -17
  218. package/dist/modes/rpc/session-command-activity.js +1 -0
  219. package/dist/modes/rpc/session-event-fanout.d.ts +1 -1
  220. package/dist/modes/rpc/session-event-fanout.js +17 -9
  221. package/dist/modes/rpc/session-event-writer.d.ts +2 -0
  222. package/dist/modes/rpc/session-event-writer.js +24 -5
  223. package/dist/modes/rpc/session-extension-ui-requests.d.ts +2 -1
  224. package/dist/modes/rpc/session-extension-ui-requests.js +15 -10
  225. package/dist/modes/rpc/session-held.js +1 -0
  226. package/dist/modes/rpc/session-record-stream.d.ts +3 -3
  227. package/dist/modes/rpc/session-record-stream.js +3 -3
  228. package/dist/modes/rpc/session-status-stream.d.ts +2 -1
  229. package/dist/modes/rpc/session-status-stream.js +8 -2
  230. package/dist/modes/rpc/session-status-subscriptions.d.ts +2 -1
  231. package/dist/modes/rpc/session-status-subscriptions.js +4 -0
  232. package/dist/modes/rpc/session-structured-lanes.d.ts +12 -0
  233. package/dist/modes/rpc/session-structured-lanes.js +24 -0
  234. package/dist/modes/rpc/session-worker-client.js +6 -5
  235. package/dist/modes/rpc/session-worker.js +5 -2
  236. package/dist/modes/rpc/snapshot-types.d.ts +29 -0
  237. package/dist/modes/rpc/snapshot-types.js +2 -0
  238. package/dist/modes/rpc/structured-session-records.d.ts +12 -0
  239. package/dist/modes/rpc/structured-session-records.js +88 -0
  240. package/dist/modes/rpc/ui-answer-ledger.d.ts +13 -0
  241. package/dist/modes/rpc/ui-answer-ledger.js +34 -0
  242. package/dist/senpi +21 -3
  243. package/docs/extensions.md +7 -1
  244. package/docs/mcp.md +20 -0
  245. package/docs/rpc.md +61 -2
  246. package/package.json +7 -6
  247. package/dist/bundle/chunks/azure-openai-responses-7I45DI72.js +0 -2
  248. package/dist/bundle/chunks/chunk-DMI55M2N.js +0 -2
  249. package/dist/bundle/chunks/chunk-DODH3N6U.js +0 -410
  250. package/dist/bundle/chunks/chunk-J37GRED2.js +0 -5
  251. package/dist/bundle/chunks/chunk-TBU6VYHN.js +0 -13
  252. package/dist/bundle/chunks/cli-main-YYH3IBC6.js +0 -3
  253. package/dist/bundle/chunks/dispatch-MXG5YWLV.js +0 -9
  254. package/dist/bundle/chunks/google-generative-ai-UM5Y7T6B.js +0 -2
  255. package/dist/bundle/chunks/google-vertex-OKU2VQHL.js +0 -2
  256. package/dist/bundle/chunks/host-command-TDLLHXVI.js +0 -2
  257. package/dist/bundle/chunks/interactive-mode-OEPULLZK.js +0 -2
  258. package/dist/bundle/chunks/openai-responses-TNHQ6SJS.js +0 -3
  259. package/dist/bundle/chunks/rpc-mode-ETXQWEB2.js +0 -2
  260. package/dist/bundle/chunks/session-control-endpoint-4LEIDIVI.js +0 -3
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
- import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, AgentToolCall, AgentToolResult, ThinkingLevel } from "@earendil-works/pi-agent-core";
15
+ import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, AgentToolCall, AgentToolResult, InternalAgentChannel, ThinkingLevel } from "@earendil-works/pi-agent-core";
16
16
  import { type ThinkingSelection } from "@earendil-works/pi-ai";
17
17
  import type { Api, ImageContent, Model, TextContent } from "@earendil-works/pi-ai/compat";
18
18
  import { type ToolRuntimeKind } from "../cli/tool-runtime.ts";
@@ -60,6 +60,7 @@ import type { SessionRelocationHost } from "./session-relocation.ts";
60
60
  import { type SettingsManager, type SettingsSourceSelection } from "./settings-manager.ts";
61
61
  import { type SkillInvocationSyntax } from "./skill-invocation.ts";
62
62
  import { type SourceInfo } from "./source-info.ts";
63
+ import { type AgentSessionStructuredRecord } from "./structured-session-lane.ts";
63
64
  import { type ThinkingClampNotice } from "./thinking-levels.ts";
64
65
  import { type ToolBarrierSpec } from "./tool-barrier.ts";
65
66
  import { type SessionMessageUpdateEvent } from "./tool-call-display-name.ts";
@@ -372,6 +373,8 @@ export type AgentSessionEvent = WithParentToolCallId<Exclude<AgentEvent, {
372
373
  export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
373
374
  export interface AgentSessionConfig {
374
375
  agent: Agent;
376
+ /** @internal Construction channel for senpi's session layer only; absent for externally supplied Agents. */
377
+ internalChannel?: InternalAgentChannel;
375
378
  /** Explicit service tier for the initial model. */
376
379
  serviceTier?: ServiceTier;
377
380
  sessionManager: SessionManager;
@@ -650,6 +653,7 @@ export declare class AgentSession {
650
653
  private _unsubscribeSettingsSource?;
651
654
  private _unregisterMemoryReport;
652
655
  private _eventListeners;
656
+ private readonly _structuredLane;
653
657
  private _agentEventQueue;
654
658
  /**
655
659
  * Exact message objects whose message_end persistence is still queued.
@@ -1211,6 +1215,12 @@ export declare class AgentSession {
1211
1215
  * Multiple listeners can be added. Returns unsubscribe function for this listener.
1212
1216
  */
1213
1217
  subscribe(listener: AgentSessionEventListener): () => void;
1218
+ /**
1219
+ * The internal structured lane (senpi#4272): every session event in `_emit` order with its provider capture, plus
1220
+ * block controls at their position. Attach before a turn's first provider push; a stream already pushing keeps
1221
+ * its decision and yields no capture.
1222
+ */
1223
+ subscribeStructured(listener: (record: AgentSessionStructuredRecord) => void): () => void;
1214
1224
  /**
1215
1225
  * Temporarily disconnect from agent events.
1216
1226
  * User listeners are preserved and will receive events again after resubscribe().
@@ -139,6 +139,7 @@ import { formatSkillInvocationPrompt, parseSkillInvocationTokens, removeSkillInv
139
139
  import { BUILTIN_SLASH_COMMANDS } from "./slash-commands.js";
140
140
  import { BUILTIN_PATH_PREFIX, createSyntheticSourceInfo } from "./source-info.js";
141
141
  import { stopResumeDirective } from "./stop-resume-directive.js";
142
+ import { StructuredSessionLane } from "./structured-session-lane.js";
142
143
  import { clampThinkingSelection, getSupportedThinkingLevels, getThinkingClampNotice, supportsMax, supportsXhigh, } from "./thinking-levels.js";
143
144
  import { resetTimings, time } from "./timings.js";
144
145
  import { ToolBarrier, toolBarrierSpecError } from "./tool-barrier.js";
@@ -701,6 +702,11 @@ export class AgentSession {
701
702
  this._promptSurface = config.promptSurface;
702
703
  this._browserEngine = config.browserEngine;
703
704
  this.agent = config.agent;
705
+ this._structuredLane = new StructuredSessionLane(config.internalChannel, (deliver) => {
706
+ if (!this._unsubscribeAgent)
707
+ return;
708
+ this._agentEventQueue = this._agentEventQueue.then(deliver, deliver);
709
+ });
704
710
  this.sessionManager = config.sessionManager;
705
711
  this._sessionThinkingMemory = new SessionThinkingMemory(config.sessionManager);
706
712
  this._thinkingLevelSource = config.initialThinkingLevelSource ?? "default";
@@ -1701,7 +1707,7 @@ export class AgentSession {
1701
1707
  this.agent.state.messages = this.sessionManager.buildSessionContext().messages;
1702
1708
  this._emitEntryAppended(entry.id);
1703
1709
  }
1704
- _emit(event) {
1710
+ _emit(event, captureSource = event) {
1705
1711
  if (event.type === "session_paused" ||
1706
1712
  event.type === "session_resumed" ||
1707
1713
  event.type === "compaction_start" ||
@@ -1744,6 +1750,8 @@ export class AgentSession {
1744
1750
  for (const l of [...this._eventListeners]) {
1745
1751
  l(event);
1746
1752
  }
1753
+ if (this._structuredLane.active)
1754
+ this._structuredLane.emitEvent(event, captureSource);
1747
1755
  }
1748
1756
  async _emitSessionCompactFailed(event) {
1749
1757
  if (this._extensionRunner.hasHandlers("session_compact_failed")) {
@@ -2567,7 +2575,7 @@ export class AgentSession {
2567
2575
  : event.type === "message_update"
2568
2576
  ? withResolvedToolName(event, (name) => this.resolveToolCallName(name))
2569
2577
  : event;
2570
- this._emit(sessionEvent);
2578
+ this._emit(sessionEvent, event);
2571
2579
  if (event.type === "agent_end") {
2572
2580
  if (this._abortProvenance.takeLateUserJoin())
2573
2581
  await this._emitSessionAbort();
@@ -3234,6 +3242,14 @@ export class AgentSession {
3234
3242
  }
3235
3243
  };
3236
3244
  }
3245
+ /**
3246
+ * The internal structured lane (senpi#4272): every session event in `_emit` order with its provider capture, plus
3247
+ * block controls at their position. Attach before a turn's first provider push; a stream already pushing keeps
3248
+ * its decision and yields no capture.
3249
+ */
3250
+ subscribeStructured(listener) {
3251
+ return this._structuredLane.subscribe(listener);
3252
+ }
3237
3253
  /**
3238
3254
  * Temporarily disconnect from agent events.
3239
3255
  * User listeners are preserved and will receive events again after resubscribe().
@@ -3302,6 +3318,7 @@ export class AgentSession {
3302
3318
  this._unsubscribeWakeSources = undefined;
3303
3319
  this._lifecycleBinding.dispose();
3304
3320
  this._eventListeners = [];
3321
+ this._structuredLane.dispose();
3305
3322
  cleanupSessionResources(this.sessionId);
3306
3323
  // Nothing reads or writes this manager once its session is gone: it releases
3307
3324
  // its writer grant and its disposable blob directory here.
@@ -648,8 +648,14 @@ export default function compactionExtension(pi, remoteCompactionDependencies = {
648
648
  catch (error) {
649
649
  const message = error instanceof Error ? error.message : String(error);
650
650
  const failureKind = classifyRequiredCompactionFallbackFailure(error);
651
+ // A terminal provider failure (dead model, dead credential) has no cheaper
652
+ // recovery on any compaction kind: without this, a soft-threshold pre_prompt
653
+ // compaction cancelled instead, the session rejected it, and the pending
654
+ // prompt failed with RequiredCompactionError while shouldCompact still said
655
+ // compact (the "Compaction rejected: compaction generator failed" wedge).
651
656
  if ((requiresDeterministicCompactionFallback(event, ctx.getContextUsage()) ||
652
- failureKind === "unsafe-retained-content") &&
657
+ failureKind === "unsafe-retained-content" ||
658
+ failureKind === "summarization-provider-failure") &&
653
659
  failureKind !== undefined &&
654
660
  !event.signal.aborted) {
655
661
  const recovery = recoverRequiredCompaction(ctx, snapshot, failureKind, error);
@@ -2,6 +2,7 @@ import { closeSync, openSync, readdirSync, readFileSync, readSync } from "node:f
2
2
  import { createConnection } from "node:net";
3
3
  import { join, win32 } from "node:path";
4
4
  import { debuglog } from "node:util";
5
+ import { APP_COMMAND } from "../../../../config.js";
5
6
  import { isTerminalMonitorStateEvent, isWakeSourceStateEvent, TERMINAL_MONITOR_STATE_EVENT, WAKE_SOURCE_STATE_EVENT, } from "../monitor-state-event.js";
6
7
  import { HerdrClient } from "./herdr-client.js";
7
8
  import { initialHerdrState, isHerdrBlockedEvent, reduceHerdrState, selectHerdrReport } from "./herdr-state.js";
@@ -115,11 +116,14 @@ export function createHerdrExtension(deps) {
115
116
  void publish();
116
117
  }, 4000);
117
118
  poll.unref();
118
- await Promise.all([
119
- reportTitle(ctx),
120
- send("pane.report_agent_session", { agent: "pi", ...sessionRef(), session_start_source: _event.reason }),
121
- publish(),
122
- ]);
119
+ // Herdr accepts a resume command only after this reporter holds the pane.
120
+ await Promise.all([reportTitle(ctx), publish()]);
121
+ await send("pane.report_agent_session", {
122
+ agent: "pi",
123
+ ...sessionRef(),
124
+ resume_argv: [APP_COMMAND, "--session", bound.getSessionFile() ?? bound.getSessionId()],
125
+ session_start_source: _event.reason,
126
+ });
123
127
  });
124
128
  pi.on("session_info_changed", (_event, ctx) => {
125
129
  if (owns(ctx))
@@ -0,0 +1,6 @@
1
+ import type { OAuthServerInfo } from "@modelcontextprotocol/sdk/client/auth.js";
2
+ import type { McpStoredAuth } from "./token-store.ts";
3
+ export declare function canonicalAuthorizationServerIssuer(issuer: string): string;
4
+ export declare function validatedAuthorizationServerIssuer(info: OAuthServerInfo, serverName: string): string;
5
+ export declare function credentialsMatchIssuer(record: McpStoredAuth | undefined, issuer: string): boolean;
6
+ //# sourceMappingURL=oauth-issuer.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { OAuthFlowError } from "./oauth-errors.js";
2
+ // Credential stamps tolerate only the slash spelling of an origin-only URL, not URL normalization.
3
+ export function canonicalAuthorizationServerIssuer(issuer) {
4
+ return issuer.replace(/^(https?:\/\/[^/?#]+)\/$/, "$1");
5
+ }
6
+ // RFC 8414 section 3.3: identifiers from metadata are compared verbatim, not as normalized URLs.
7
+ export function validatedAuthorizationServerIssuer(info, serverName) {
8
+ // The SDK's legacy fallback discovers root metadata and spells the origin with a trailing slash.
9
+ // Only that configured-resource fallback is normalized; PRM-provided identifiers remain exact.
10
+ const origin = new URL(info.authorizationServerUrl).origin;
11
+ const issuer = !info.resourceMetadata?.authorization_servers?.length && info.authorizationServerUrl === `${origin}/`
12
+ ? canonicalAuthorizationServerIssuer(info.authorizationServerUrl)
13
+ : info.authorizationServerUrl;
14
+ if (info.authorizationServerMetadata !== undefined && info.authorizationServerMetadata.issuer !== issuer) {
15
+ throw new OAuthFlowError("needs_auth", `MCP server ${serverName} authorization server metadata issuer mismatch; restart authorization after correcting the server metadata.`, { serverName });
16
+ }
17
+ return issuer;
18
+ }
19
+ export function credentialsMatchIssuer(record, issuer) {
20
+ const grantIssuer = record?.issuer ?? record?.discoveryState?.authorizationServerUrl;
21
+ const clientIssuer = record?.clientInfo?.issuer ?? grantIssuer;
22
+ return [grantIssuer, clientIssuer].every((stamp) => stamp === undefined ||
23
+ canonicalAuthorizationServerIssuer(stamp) === canonicalAuthorizationServerIssuer(issuer));
24
+ }
25
+ //# sourceMappingURL=oauth-issuer.js.map
@@ -15,8 +15,6 @@ export interface McpOAuthProviderOptions {
15
15
  onRedirect?: (url: URL) => void | Promise<void>;
16
16
  }
17
17
  export declare function tokenExpiresAt(tokens: OAuthTokens, now?: number): number | undefined;
18
- /** Compares authorization server identifiers the way the SDK does: parsed, tolerating one trailing `/`. */
19
- export declare function sameAuthorizationServer(a: string, b: string): boolean;
20
18
  /**
21
19
  * The authorization server a stored grant belongs to (senpi#2940, GHSA-6qxp-vccf-f47h): its `issuer` stamp, or
22
20
  * for a record saved before the stamp existed, the authorization server its sign-in discovered. A record with
@@ -37,7 +35,9 @@ export declare class McpOAuthProvider implements OAuthClientProvider {
37
35
  get lastAuthorizationUrl(): URL | undefined;
38
36
  get redirectUrl(): string | URL | undefined;
39
37
  get clientMetadataUrl(): string | undefined;
40
- get clientMetadata(): OAuthClientMetadata;
38
+ get clientMetadata(): OAuthClientMetadata & {
39
+ application_type?: "native" | "web";
40
+ };
41
41
  state(): string;
42
42
  consumeState(candidate: string | undefined): boolean;
43
43
  clientInformation(): OAuthClientInformationMixed | undefined;
@@ -1,6 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { fingerprintSecret } from "../log.js";
3
3
  import { OAuthFlowError } from "./oauth-errors.js";
4
+ import { canonicalAuthorizationServerIssuer, credentialsMatchIssuer, validatedAuthorizationServerIssuer, } from "./oauth-issuer.js";
4
5
  // Buffer before absolute token expiry at which a refresh is considered due.
5
6
  export const REFRESH_LEEWAY_MS = 5 * 60 * 1000;
6
7
  export function tokenExpiresAt(tokens, now = Date.now()) {
@@ -8,20 +9,6 @@ export function tokenExpiresAt(tokens, now = Date.now()) {
8
9
  return undefined;
9
10
  return now + tokens.expires_in * 1000;
10
11
  }
11
- /** Compares authorization server identifiers the way the SDK does: parsed, tolerating one trailing `/`. */
12
- export function sameAuthorizationServer(a, b) {
13
- const normalize = (value) => {
14
- try {
15
- return new URL(value).href.replace(/\/$/, "");
16
- }
17
- catch (error) {
18
- if (error instanceof TypeError)
19
- return value.replace(/\/$/, "");
20
- throw error;
21
- }
22
- };
23
- return normalize(a) === normalize(b);
24
- }
25
12
  /**
26
13
  * The authorization server a stored grant belongs to (senpi#2940, GHSA-6qxp-vccf-f47h): its `issuer` stamp, or
27
14
  * for a record saved before the stamp existed, the authorization server its sign-in discovered. A record with
@@ -101,6 +88,14 @@ export class McpOAuthProvider {
101
88
  get clientMetadata() {
102
89
  const redirect = this.#options.redirectUrl;
103
90
  return {
91
+ ...(redirect === undefined
92
+ ? {}
93
+ : {
94
+ application_type: !["http:", "https:"].includes(new URL(redirect).protocol) ||
95
+ ["127.0.0.1", "localhost", "[::1]"].includes(new URL(redirect).hostname)
96
+ ? "native"
97
+ : "web",
98
+ }),
104
99
  redirect_uris: redirect === undefined ? [] : [String(redirect)],
105
100
  grant_types: ["authorization_code", "refresh_token"],
106
101
  response_types: ["code"],
@@ -127,20 +122,56 @@ export class McpOAuthProvider {
127
122
  return this.#store.read()?.clientInfo;
128
123
  }
129
124
  async saveClientInformation(info) {
130
- // The SDK stamps `info.issuer`; it is persisted with the rest so the binding survives a restart.
131
- await this.#store.update((current) => ({ ...(current ?? {}), clientInfo: info }));
125
+ const discovery = this.discoveryState();
126
+ const issuer = discovery === undefined
127
+ ? info.issuer && canonicalAuthorizationServerIssuer(info.issuer)
128
+ : validatedAuthorizationServerIssuer(discovery, this.serverName);
129
+ await this.#store.update((current) => ({ ...(current ?? {}), clientInfo: { ...info, issuer } }));
132
130
  }
133
131
  async saveDiscoveryState(state) {
134
- await this.#store.update((current) => ({ ...freezeGrantAttribution(current), discoveryState: state }));
132
+ const issuer = validatedAuthorizationServerIssuer(state, this.serverName);
133
+ const discoveryState = { ...state, authorizationServerUrl: issuer };
134
+ await this.#store.update((current) => {
135
+ const attributed = freezeGrantAttribution(current);
136
+ if (!credentialsMatchIssuer(attributed, issuer))
137
+ return { discoveryState };
138
+ const clientIssuer = attributed.clientInfo?.issuer ?? storedGrantIssuer(attributed);
139
+ return {
140
+ ...attributed,
141
+ // Unattributed legacy registrations cannot be sent to the server discovered now.
142
+ clientInfo: clientIssuer === undefined || attributed.clientInfo === undefined
143
+ ? undefined
144
+ : { ...attributed.clientInfo, issuer },
145
+ discoveryState,
146
+ };
147
+ });
135
148
  }
136
149
  discoveryState() {
137
- return this.#store.read()?.discoveryState;
150
+ const current = this.#store.read();
151
+ const state = current?.discoveryState;
152
+ if (state !== undefined) {
153
+ try {
154
+ const issuer = validatedAuthorizationServerIssuer(state, this.serverName);
155
+ if (!credentialsMatchIssuer(current, issuer))
156
+ return undefined;
157
+ }
158
+ catch (error) {
159
+ if (error instanceof OAuthFlowError)
160
+ return undefined;
161
+ throw error;
162
+ }
163
+ }
164
+ return state;
138
165
  }
139
166
  tokens() {
140
167
  return storedAuthToTokens(this.#store.read());
141
168
  }
142
169
  async saveTokens(tokens) {
143
- await this.#store.update((current) => mergeTokensIntoStoredAuth(current, tokens, this.serverUrl));
170
+ const discovery = this.discoveryState();
171
+ const issuer = discovery === undefined
172
+ ? tokens.issuer && canonicalAuthorizationServerIssuer(tokens.issuer)
173
+ : validatedAuthorizationServerIssuer(discovery, this.serverName);
174
+ await this.#store.update((current) => mergeTokensIntoStoredAuth(current, { ...tokens, issuer }, this.serverUrl));
144
175
  this.#logFingerprint("saveTokens", tokens.access_token);
145
176
  }
146
177
  async saveCodeVerifier(codeVerifier) {
@@ -2,7 +2,8 @@ import { loadMcpSdkAuth } from "../sdk.lazy.js";
2
2
  import { safeDelay } from "../wrap.js";
3
3
  import { isInvalidGrant, isTransientTokenError, OAuthFlowError } from "./oauth-errors.js";
4
4
  import { oauthFetch } from "./oauth-fetch.js";
5
- import { mergeTokensIntoStoredAuth, REFRESH_LEEWAY_MS, sameAuthorizationServer, storedAuthToTokens, storedGrantIssuer, } from "./oauth-provider.js";
5
+ import { credentialsMatchIssuer, validatedAuthorizationServerIssuer } from "./oauth-issuer.js";
6
+ import { mergeTokensIntoStoredAuth, REFRESH_LEEWAY_MS, storedAuthToTokens, storedGrantIssuer, } from "./oauth-provider.js";
6
7
  export function isTokenStale(record, now) {
7
8
  const token = record?.accessToken;
8
9
  if (token === undefined || token.length === 0)
@@ -67,8 +68,8 @@ export class McpRefreshManager {
67
68
  // elsewhere, or one that cannot be attributed at all, is a continuity break: drop it and require a fresh
68
69
  // sign-in rather than refreshing silently against whatever server discovery now names.
69
70
  const issuer = storedGrantIssuer(current);
70
- const authorizationServer = String(info.authorizationServerUrl);
71
- if (issuer === undefined || !sameAuthorizationServer(issuer, authorizationServer)) {
71
+ const authorizationServer = validatedAuthorizationServerIssuer(info, this.#provider.serverName);
72
+ if (issuer === undefined || !credentialsMatchIssuer(current, authorizationServer)) {
72
73
  this.#provider.store.writeUnlocked(undefined);
73
74
  throw new OAuthFlowError("needs_auth", issuer === undefined
74
75
  ? `MCP server ${this.#provider.serverName} has a saved sign-in with no record of the authorization server that issued it; credentials cleared, re-authentication required.`
@@ -1,6 +1,7 @@
1
1
  import { loadMcpSdkAuth } from "../sdk.lazy.js";
2
2
  import { isInvalidGrant, OAuthFlowError } from "./oauth-errors.js";
3
3
  import { oauthFetch } from "./oauth-fetch.js";
4
+ import { validatedAuthorizationServerIssuer } from "./oauth-issuer.js";
4
5
  import { assertS256Supported } from "./oauth-refresh.js";
5
6
  // Pre-flight: discover the authorization server and REFUSE (typed) if it does
6
7
  // not advertise PKCE S256. Runs before any authorize redirect.
@@ -20,7 +21,7 @@ export async function beginAuthorization(provider, options = {}) {
20
21
  }
21
22
  // Paste flow: validates single-use state itself, then exchanges the code.
22
23
  export async function completeAuthorization(provider, redirectInput, options = {}) {
23
- const { code, state, iss } = parseRedirect(redirectInput, provider.serverName);
24
+ const { code, state, iss } = parseRedirect(redirectInput, provider);
24
25
  if (!provider.consumeState(state)) {
25
26
  throw new OAuthFlowError("state_mismatch", `MCP server ${provider.serverName} authorization state did not match (possible CSRF or a stale/replayed link); restart with /mcp auth-start.`, { serverName: provider.serverName });
26
27
  }
@@ -56,15 +57,19 @@ export async function finishAuthorization(provider, code, options = {}, iss) {
56
57
  // RFC 9207: an authorization response that names another authorization server, or that omits `iss`
57
58
  // although this server promises to send it, may have been injected by a mix-up attack; never exchange its code.
58
59
  function assertAuthorizationResponseIssuer(provider, iss) {
59
- const metadata = provider.discoveryState()?.authorizationServerMetadata;
60
- if (metadata === undefined)
61
- return;
60
+ const discovery = provider.discoveryState();
61
+ if (discovery === undefined) {
62
+ throw new OAuthFlowError("needs_auth", `MCP server ${provider.serverName} has no trusted authorization discovery; restart with /mcp auth-start ${provider.serverName}.`, { serverName: provider.serverName });
63
+ }
64
+ const metadata = discovery.authorizationServerMetadata;
65
+ const issuer = validatedAuthorizationServerIssuer(discovery, provider.serverName);
62
66
  // The SDK keeps unknown metadata fields (loose schema) but does not type this RFC 9207 one.
63
- const promised = metadata
64
- .authorization_response_iss_parameter_supported === true;
67
+ const promised = metadata !== undefined &&
68
+ "authorization_response_iss_parameter_supported" in metadata &&
69
+ metadata.authorization_response_iss_parameter_supported === true;
65
70
  if (iss === undefined && !promised)
66
71
  return;
67
- if (iss !== undefined && iss === metadata.issuer)
72
+ if (iss !== undefined && iss === issuer)
68
73
  return;
69
74
  throw new OAuthFlowError("needs_auth", iss === undefined
70
75
  ? `MCP server ${provider.serverName} authorization response has no issuer although its authorization server promises one; restart with /mcp auth-start ${provider.serverName}.`
@@ -111,19 +116,19 @@ export async function clientCredentialsGrant(provider, options = {}) {
111
116
  resource: new URL(provider.serverUrl),
112
117
  fetchFn: oauthFetch(options.fetchFn),
113
118
  });
114
- await provider.saveTokens({ ...tokens, issuer: String(info.authorizationServerUrl) });
119
+ await provider.saveTokens({ ...tokens, issuer: validatedAuthorizationServerIssuer(info, provider.serverName) });
115
120
  }
116
121
  export async function logout(provider) {
117
122
  await provider.store.clear();
118
123
  }
119
124
  async function discover(provider, options) {
120
- if (options.discover !== undefined)
121
- return options.discover(provider.serverUrl);
122
125
  const cached = provider.discoveryState();
123
126
  if (cached !== undefined)
124
127
  return cached;
125
128
  const { discoverOAuthServerInfo } = await loadMcpSdkAuth();
126
- const info = await discoverOAuthServerInfo(provider.serverUrl, { fetchFn: oauthFetch(options.fetchFn) });
129
+ const info = options.discover !== undefined
130
+ ? await options.discover(provider.serverUrl)
131
+ : await discoverOAuthServerInfo(provider.serverUrl, { fetchFn: oauthFetch(options.fetchFn) });
127
132
  await provider.saveDiscoveryState(toDiscoveryState(info));
128
133
  return info;
129
134
  }
@@ -134,7 +139,8 @@ function toDiscoveryState(info) {
134
139
  resourceMetadata: info.resourceMetadata,
135
140
  };
136
141
  }
137
- function parseRedirect(input, serverName) {
142
+ function parseRedirect(input, provider) {
143
+ const serverName = provider.serverName;
138
144
  let url;
139
145
  try {
140
146
  url = new URL(input.trim());
@@ -142,6 +148,7 @@ function parseRedirect(input, serverName) {
142
148
  catch {
143
149
  throw new OAuthFlowError("needs_auth", `MCP server ${serverName} received a malformed redirect URL; paste the full http://127.0.0.1/... address from your browser.`, { serverName });
144
150
  }
151
+ assertAuthorizationResponseIssuer(provider, url.searchParams.get("iss") ?? undefined);
145
152
  const error = url.searchParams.get("error");
146
153
  if (error !== null) {
147
154
  throw new OAuthFlowError("needs_auth", `MCP server ${serverName} authorization failed: ${error}`, { serverName });
@@ -1,7 +1,7 @@
1
1
  import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import type { McpServerDeclaredConfig } from "./config-schema.ts";
3
3
  import type { ServerConnection } from "./connection.ts";
4
- type ListedTool = Awaited<ReturnType<Client["listTools"]>>["tools"][number];
4
+ import type { McpListedTool as ListedTool } from "./tool-contracts.ts";
5
5
  type ListedResource = Awaited<ReturnType<Client["listResources"]>>["resources"][number];
6
6
  type ListedResourceTemplate = Awaited<ReturnType<Client["listResourceTemplates"]>>["resourceTemplates"][number];
7
7
  type ListedPrompt = Awaited<ReturnType<Client["listPrompts"]>>["prompts"][number];
@@ -10,6 +10,7 @@ export interface McpCatalogCacheFile {
10
10
  readonly servers: Record<string, McpCachedServerCatalog>;
11
11
  }
12
12
  export interface McpCachedServerCatalog {
13
+ readonly modern?: boolean;
13
14
  readonly configHash: string;
14
15
  readonly credentialIdentity?: string;
15
16
  readonly fetchedAt: number;
@@ -6,6 +6,9 @@ import { redactResolvedReferenceData } from "../../../config-reference-redaction
6
6
  import { lockWithLossHandling } from "../../../file-lock.js";
7
7
  import { isEphemeralIdentity } from "./config-identity-key.js";
8
8
  import { collectAllPages } from "./expose/pagination.js";
9
+ import { listMcpTools } from "./tool-client.js";
10
+ import { isModernMcpClient } from "./modern/request.js";
11
+ import { hasSupportedModernToolSchemas } from "./modern/tool-schema.js";
9
12
  const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
10
13
  const EMPTY_CACHE = { version: 1, servers: {} };
11
14
  export function getMcpCatalogCachePath(agentDir = getAgentDir()) {
@@ -36,7 +39,7 @@ export function cachedCatalogNeedsRefresh(cached, now = Date.now()) {
36
39
  return now - cached.fetchedAt > CACHE_TTL_MS;
37
40
  }
38
41
  export async function collectServerCatalogForCache(connection, config, configHash, credentialIdentity) {
39
- const tools = await collectAllPages((cursor) => connection.client.listTools(cursor === undefined ? {} : { cursor }, {
42
+ const tools = await collectAllPages((cursor) => listMcpTools(connection.client, cursor === undefined ? {} : { cursor }, {
40
43
  timeout: config.requestTimeoutMs,
41
44
  }));
42
45
  const resources = await collectOptionalPages((cursor) => connection.client.listResources(cursor === undefined ? {} : { cursor }, {
@@ -50,6 +53,7 @@ export async function collectServerCatalogForCache(connection, config, configHas
50
53
  }));
51
54
  return {
52
55
  configHash,
56
+ ...(isModernMcpClient(connection.client) ? { modern: true } : {}),
53
57
  credentialIdentity,
54
58
  fetchedAt: Date.now(),
55
59
  instructions: connection.client.getInstructions(),
@@ -128,7 +132,7 @@ function normalizeCachedServer(value) {
128
132
  if (!isRecord(value) || typeof value.configHash !== "string" || typeof value.fetchedAt !== "number") {
129
133
  return undefined;
130
134
  }
131
- const tools = normalizeTools(value.tools);
135
+ const tools = normalizeTools(value.tools, value.modern === true);
132
136
  if (tools === undefined)
133
137
  return undefined;
134
138
  const resources = Array.isArray(value.resources) ? value.resources : [];
@@ -139,6 +143,7 @@ function normalizeCachedServer(value) {
139
143
  const instructions = typeof value.instructions === "string" ? value.instructions : undefined;
140
144
  return {
141
145
  configHash: value.configHash,
146
+ ...(value.modern === true ? { modern: true } : {}),
142
147
  credentialIdentity: typeof value.credentialIdentity === "string" ? value.credentialIdentity : undefined,
143
148
  fetchedAt: value.fetchedAt,
144
149
  instructions,
@@ -148,19 +153,27 @@ function normalizeCachedServer(value) {
148
153
  tools,
149
154
  };
150
155
  }
151
- function normalizeTools(value) {
156
+ function normalizeTools(value, modern) {
152
157
  if (!Array.isArray(value))
153
158
  return undefined;
154
159
  const tools = [];
155
160
  for (const item of value) {
156
- if (!isRecord(item) || typeof item.name !== "string" || !isRecord(item.inputSchema))
161
+ if (!isRecord(item) || typeof item.name !== "string")
157
162
  return undefined;
158
- tools.push({
163
+ if (!isRecord(item.inputSchema) && !(modern && typeof item.inputSchema === "boolean"))
164
+ return undefined;
165
+ const tool = {
166
+ ...(modern ? item : {}),
159
167
  annotations: isRecord(item.annotations) ? item.annotations : undefined,
160
168
  description: typeof item.description === "string" ? item.description : undefined,
161
169
  inputSchema: item.inputSchema,
170
+ ...(modern && (isRecord(item.outputSchema) || typeof item.outputSchema === "boolean")
171
+ ? { outputSchema: item.outputSchema }
172
+ : {}),
162
173
  name: item.name,
163
- });
174
+ };
175
+ if (!modern || hasSupportedModernToolSchemas(tool))
176
+ tools.push(tool);
164
177
  }
165
178
  return tools;
166
179
  }
@@ -1,4 +1,4 @@
1
- import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
1
+ import type { McpListedTool as ListedTool } from "./tool-contracts.ts";
2
2
  import type { McpCachedServerCatalog } from "./catalog-cache.ts";
3
3
  import type { McpServerDeclaredConfig, McpSettings } from "./config-schema.ts";
4
4
  import type { ServerConnection } from "./connection.ts";
@@ -6,12 +6,13 @@ import type { McpOutputArtifacts } from "./guard/output-guard.ts";
6
6
  import type { McpEnsureFreshAuth } from "./health.ts";
7
7
  import type { McpInvocationResolver } from "./invocation.ts";
8
8
  import type { McpRequestOrigin } from "./request-origin.ts";
9
- type ListedTool = Awaited<ReturnType<Client["listTools"]>>["tools"][number];
10
9
  export interface McpToolCatalogEntry {
11
10
  requestOrigin?: () => McpRequestOrigin | undefined;
12
11
  server: string;
13
12
  tool: string;
14
13
  schema: ListedTool["inputSchema"];
14
+ outputSchema?: ListedTool["outputSchema"];
15
+ modern?: boolean;
15
16
  description?: string;
16
17
  annotations?: ListedTool["annotations"];
17
18
  requestTimeoutMs: number;
@@ -1,6 +1,9 @@
1
+ import { listMcpTools } from "./tool-client.js";
2
+ import { isModernMcpClient } from "./modern/request.js";
3
+ import { hasSupportedModernToolSchemas } from "./modern/tool-schema.js";
1
4
  import { collectAllPages } from "./expose/pagination.js";
2
5
  export async function collectToolCatalog(server, connection, config, options = {}) {
3
- const result = await collectAllPages((cursor) => connection.client.listTools(cursor === undefined ? {} : { cursor }, { timeout: config.requestTimeoutMs }));
6
+ const result = await collectAllPages((cursor) => listMcpTools(connection.client, cursor === undefined ? {} : { cursor }, { timeout: config.requestTimeoutMs }));
4
7
  return result.items.map((tool) => ({
5
8
  requestOrigin: options.requestOrigin,
6
9
  annotations: tool.annotations,
@@ -13,6 +16,8 @@ export async function collectToolCatalog(server, connection, config, options = {
13
16
  outputGuard: options.outputGuard,
14
17
  requestTimeoutMs: config.requestTimeoutMs,
15
18
  schema: tool.inputSchema,
19
+ outputSchema: tool.outputSchema,
20
+ modern: isModernMcpClient(connection.client),
16
21
  server,
17
22
  tool: tool.name,
18
23
  }));
@@ -21,13 +26,23 @@ export async function collectToolCatalog(server, connection, config, options = {
21
26
  * equal identities register identical definitions. */
22
27
  export function mcpRegistrationIdentity(tools, catalog) {
23
28
  return JSON.stringify([
24
- tools.map((entry) => [entry.tool, entry.description, entry.schema, entry.annotations]),
29
+ tools.map((entry) => [
30
+ entry.tool,
31
+ entry.description,
32
+ entry.schema,
33
+ entry.outputSchema,
34
+ entry.modern,
35
+ entry.annotations,
36
+ ]),
25
37
  catalog?.resources ?? [],
26
38
  catalog?.prompts ?? [],
27
39
  ]);
28
40
  }
29
41
  export function cachedToolsToCatalogEntries(server, catalog, connection, requestTimeoutMs, ensureConnected, options = {}) {
30
- return catalog.tools.map((tool) => ({
42
+ const modern = connection.state === "connected" ? isModernMcpClient(connection.client) || undefined : catalog.modern;
43
+ return catalog.tools
44
+ .filter((tool) => !modern || hasSupportedModernToolSchemas(tool))
45
+ .map((tool) => ({
31
46
  requestOrigin: options.requestOrigin,
32
47
  annotations: tool.annotations,
33
48
  agentDir: options.agentDir,
@@ -40,6 +55,8 @@ export function cachedToolsToCatalogEntries(server, catalog, connection, request
40
55
  outputGuard: options.outputGuard,
41
56
  requestTimeoutMs,
42
57
  schema: tool.inputSchema,
58
+ outputSchema: tool.outputSchema,
59
+ modern,
43
60
  server,
44
61
  tool: tool.name,
45
62
  }));
@@ -1,10 +1,11 @@
1
1
  import type { McpToolCatalogEntry } from "../catalog.ts";
2
2
  import type { McpInvocation, McpInvocationError } from "../invocation.ts";
3
3
  import type { McpToolDefinition } from "./register.ts";
4
+ import type { McpToolResult } from "../tool-contracts.ts";
4
5
  export declare function callMcpTool(entry: McpToolCatalogEntry, args: Record<string, unknown>, signal: AbortSignal | undefined, onUpdate: Parameters<McpToolDefinition["execute"]>[3], label: string, invocation: McpInvocation | undefined): Promise<{
5
6
  kind: "called";
6
7
  entry: McpToolCatalogEntry;
7
- result: Awaited<ReturnType<McpToolCatalogEntry["connection"]["client"]["callTool"]>>;
8
+ result: McpToolResult;
8
9
  } | {
9
10
  kind: "refused";
10
11
  error: McpInvocationError;
@@ -4,6 +4,7 @@ import { ensureMcpToolCallConnection, withMcpRetriableFailedSendRetry, withMcpSe
4
4
  import { runMcpConnectionLifecycleCall } from "../idle.js";
5
5
  import { mcpRequestOptions } from "../request-origin.js";
6
6
  import { McpMrtrError } from "../modern/errors.js";
7
+ import { callMcpToolRequest } from "../tool-client.js";
7
8
  export async function callMcpTool(entry, args, signal, onUpdate, label, invocation) {
8
9
  try {
9
10
  return await runMcpConnectionLifecycleCall(entry.connection, () => withMcpSessionExpiryRetry(entry.connection, async () => {
@@ -31,7 +32,7 @@ export async function callMcpTool(entry, args, signal, onUpdate, label, invocati
31
32
  if (prepared !== undefined && !prepared.isCurrent())
32
33
  continue;
33
34
  const current = prepared?.entry ?? entry;
34
- const result = await current.connection.client.callTool({ name: current.tool, arguments: args }, undefined, mcpRequestOptions(current.requestOrigin?.(), {
35
+ const result = await callMcpToolRequest(current.connection.client, { name: current.tool, arguments: args }, current.outputSchema, mcpRequestOptions(current.requestOrigin?.(), {
35
36
  onprogress: (progress) => {
36
37
  onUpdate?.(redactResolvedReferenceData({
37
38
  content: [{ type: "text", text: formatProgress(label, progress) }],
@@ -18,7 +18,7 @@ export function buildMcpToolDefinitions(entries, warn) {
18
18
  return mapMcpCatalogNames(entries, warn).map(({ entry, name }) => createMcpToolDefinition(entry, name));
19
19
  }
20
20
  function createMcpToolDefinition(entry, name) {
21
- const converted = convertJsonSchemaToTypeBox(entry.schema);
21
+ const converted = convertJsonSchemaToTypeBox(entry.schema, entry.modern);
22
22
  const label = `${entry.server}/${entry.tool}`;
23
23
  registerDispatchIdentity(converted.schema, () => entry.invocation?.identity(entry));
24
24
  return {