@code-yeongyu/senpi 2026.11.10 → 2026.11.11

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 (320) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/dist/bundle/{SKILL-B4R7BVOT.md → SKILL-R7FEIQKG.md} +1 -1
  3. package/dist/bundle/chunks/{SKILL-AZ6ONO52.js → SKILL-5O2UKORG.js} +1 -1
  4. package/dist/bundle/chunks/{SKILL-B4R7BVOT.md → SKILL-R7FEIQKG.md} +1 -1
  5. package/dist/bundle/chunks/{args-77GYOPEO.js → args-HFYB6URH.js} +1 -1
  6. package/dist/bundle/chunks/{azure-openai-responses-YFZ734T3.js → azure-openai-responses-MNN2TINI.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-ZUQCYIRY.js → chunk-2ODGHBUD.js} +1 -1
  8. package/dist/bundle/chunks/chunk-3IYEGHBI.js +2 -0
  9. package/dist/bundle/chunks/{chunk-I5ASHSLZ.js → chunk-4JMGWVRH.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-YKYWL2NO.js → chunk-5F2XC6UC.js} +3 -3
  11. package/dist/bundle/chunks/{chunk-MSDBBOB2.js → chunk-5H43O4MA.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-R3OP7IA4.js → chunk-5PIZWSNG.js} +4 -4
  13. package/dist/bundle/chunks/{chunk-33MZJTQ3.js → chunk-B4WD5XHP.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-Q63YLZMN.js → chunk-BPEA5SWW.js} +2 -2
  15. package/dist/bundle/chunks/{chunk-MOVJQVMC.js → chunk-BUDJLVMF.js} +3 -3
  16. package/dist/bundle/chunks/{chunk-V5FSENW6.js → chunk-COKX65TE.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-2EJ4BGBM.js → chunk-DHORIIVQ.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-NH7SYIGN.js → chunk-DLPV4B3R.js} +1 -1
  19. package/dist/bundle/chunks/{chunk-RCHIMH7N.js → chunk-DQY26AX3.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-Y7Z7XPUF.js → chunk-DZQA5LVN.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-2CNSCMYC.js → chunk-E5O4SP5I.js} +1 -1
  22. package/dist/bundle/chunks/{chunk-3Q5ZENSD.js → chunk-E6ZBTSC6.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-GVPZPVVO.js → chunk-IBDAV2AL.js} +3 -3
  24. package/dist/bundle/chunks/chunk-IREUIMFE.js +11 -0
  25. package/dist/bundle/chunks/{chunk-KU7OCWJF.js → chunk-J4ADZ2OK.js} +1 -1
  26. package/dist/bundle/chunks/{chunk-T2SMQPRE.js → chunk-K3GAGIPX.js} +1 -1
  27. package/dist/bundle/chunks/{chunk-BELNOPAI.js → chunk-L3KVP7VJ.js} +3 -3
  28. package/dist/bundle/chunks/{chunk-IYSJFQUU.js → chunk-L6DF5G6K.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-7MN7YNO5.js → chunk-L7DGTA3L.js} +1 -1
  30. package/dist/bundle/chunks/chunk-LJFQYCFH.js +2 -0
  31. package/dist/bundle/chunks/{chunk-J2NGJKXL.js → chunk-LXINMDMX.js} +1 -1
  32. package/dist/bundle/chunks/{chunk-H33A33M4.js → chunk-M2VJVJMK.js} +1 -1
  33. package/dist/bundle/chunks/{chunk-2U6HXWXS.js → chunk-NNQ34ZDW.js} +1 -1
  34. package/dist/bundle/chunks/chunk-NQKCJQHB.js +2 -0
  35. package/dist/bundle/chunks/{chunk-EJB5E2JK.js → chunk-NRZAJUUO.js} +2 -2
  36. package/dist/bundle/chunks/{chunk-R2Z7CX77.js → chunk-OSNR3IRD.js} +1 -1
  37. package/dist/bundle/chunks/{chunk-O3FG6HML.js → chunk-PJI5GT7U.js} +1 -1
  38. package/dist/bundle/chunks/{chunk-FDNM2DAE.js → chunk-PUZDKFRT.js} +164 -164
  39. package/dist/bundle/chunks/{chunk-BPNSOZ2C.js → chunk-QUVSIW2R.js} +1 -1
  40. package/dist/bundle/chunks/{chunk-WXYMAUFX.js → chunk-QX7WD3IM.js} +1 -1
  41. package/dist/bundle/chunks/{chunk-Y4ONXQMR.js → chunk-RGHVPUUX.js} +1 -1
  42. package/dist/bundle/chunks/{chunk-RYPM5BZQ.js → chunk-S7NAJQB3.js} +1 -1
  43. package/dist/bundle/chunks/{chunk-2BH54YQR.js → chunk-SANWO37B.js} +1 -1
  44. package/dist/bundle/chunks/{chunk-YQZNARTX.js → chunk-SS5VTBAV.js} +1 -1
  45. package/dist/bundle/chunks/{chunk-LT353NSF.js → chunk-T4CUD7RF.js} +1 -1
  46. package/dist/bundle/chunks/{chunk-J7FW7MED.js → chunk-TCZORL3V.js} +1 -1
  47. package/dist/bundle/chunks/{chunk-KU72IKV5.js → chunk-VGSWOLIY.js} +1 -1
  48. package/dist/bundle/chunks/{chunk-DBQBJTMF.js → chunk-VUY4BZDI.js} +1 -1
  49. package/dist/bundle/chunks/chunk-XI6THO4F.js +2 -0
  50. package/dist/bundle/chunks/{chunk-FAFIGBUD.js → chunk-XUDKT4UH.js} +3 -3
  51. package/dist/bundle/chunks/{cli-main-BX5AFWJQ.js → cli-main-7II7GCIR.js} +1 -1
  52. package/dist/bundle/chunks/{credential-accounts-LVVPKFEY.js → credential-accounts-N3B5YJS7.js} +1 -1
  53. package/dist/bundle/chunks/{dispatch-EP35KKJ3.js → dispatch-YUYZQXRP.js} +1 -1
  54. package/dist/bundle/chunks/github-copilot.js +1 -1
  55. package/dist/bundle/chunks/{hand-entry-F6PWKNPJ.js → hand-entry-ONOLRMVB.js} +3 -3
  56. package/dist/bundle/chunks/{headless-host-HKGNJGYT.js → headless-host-53UMJMM6.js} +1 -1
  57. package/dist/bundle/chunks/{health-write-FBF74QJ7.js → health-write-JLSF5EVV.js} +1 -1
  58. package/dist/bundle/chunks/{help-fast-path-5SCPY3OH.js → help-fast-path-552QLMWT.js} +1 -1
  59. package/dist/bundle/chunks/{host-command-47QQKJEY.js → host-command-KWMJLPBS.js} +1 -1
  60. package/dist/bundle/chunks/{host-lifecycle-VSAH4M3R.js → host-lifecycle-4WQDCN3Y.js} +1 -1
  61. package/dist/bundle/chunks/host-lifecycle.js +5 -5
  62. package/dist/bundle/chunks/{host-session-command-SL4AN5WD.js → host-session-command-ZDYEKCUB.js} +1 -1
  63. package/dist/bundle/chunks/{host-socket-request-DMYXC56U.js → host-socket-request-QN4Q2P5E.js} +1 -1
  64. package/dist/bundle/chunks/{interactive-mode-JDMIHJMQ.js → interactive-mode-EPE4M32G.js} +1 -1
  65. package/dist/bundle/chunks/{login-health-EVXVLHB2.js → login-health-TOSQT5RG.js} +1 -1
  66. package/dist/bundle/chunks/{main-4RITUAQF.js → main-OM7BKVZG.js} +1 -1
  67. package/dist/bundle/chunks/{multi-session-host-SKDD4OWM.js → multi-session-host-DB6XMY53.js} +11 -8
  68. package/dist/bundle/chunks/openai-codex-responses-VRGOAFEG.js +10 -0
  69. package/dist/bundle/chunks/{openai-responses-NIVBWE2H.js → openai-responses-I7PXRWCM.js} +2 -2
  70. package/dist/bundle/chunks/{package-manager-cli-B5VA4KFO.js → package-manager-cli-GLU5Q2KV.js} +1 -1
  71. package/dist/bundle/chunks/{role-HQUN4XXK.js → role-AEOLJRTP.js} +1 -1
  72. package/dist/bundle/chunks/{rotation-stream-F3BEMXBE.js → rotation-stream-E3FJFHXJ.js} +1 -1
  73. package/dist/bundle/chunks/rpc-mode-SDJVBT36.js +2 -0
  74. package/dist/bundle/chunks/{rpc-monitor-source-C6VQ3BC4.js → rpc-monitor-source-PGTDSGWP.js} +1 -1
  75. package/dist/bundle/chunks/{run-attached-tui-R3RB5P7G.js → run-attached-tui-4AENRP7P.js} +1 -1
  76. package/dist/bundle/chunks/{schedule-command-FPEQCX33.js → schedule-command-5SQD7M5N.js} +1 -1
  77. package/dist/bundle/chunks/session-control-endpoint-R25A2QGS.js +3 -0
  78. package/dist/bundle/chunks/{session-picker-2FZSNFS7.js → session-picker-GZM5CVQL.js} +1 -1
  79. package/dist/bundle/chunks/{session-slot-CZGQQCOC.js → session-slot-RH7JLO3F.js} +1 -1
  80. package/dist/bundle/chunks/session-worker.js +335 -332
  81. package/dist/bundle/chunks/{state-store-S7QAESIF.js → state-store-IECVTYXC.js} +1 -1
  82. package/dist/bundle/cli.js +2 -2
  83. package/dist/bundle/host-supervisor.js +5 -5
  84. package/dist/bundle/index.js +1 -1
  85. package/dist/bundle/rpc-entry.js +1 -1
  86. package/dist/bundle/runtime-manifest.json +1 -1
  87. package/dist/cli.js +9 -3
  88. package/dist/core/agent-session-runtime.d.ts +2 -0
  89. package/dist/core/agent-session-runtime.js +5 -0
  90. package/dist/core/agent-session.d.ts +15 -3
  91. package/dist/core/agent-session.js +91 -18
  92. package/dist/core/assistant-error-redaction.d.ts +9 -0
  93. package/dist/core/assistant-error-redaction.js +27 -0
  94. package/dist/core/config-reference-redaction.js +90 -10
  95. package/dist/core/context-files.d.ts +10 -0
  96. package/dist/core/context-files.js +58 -12
  97. package/dist/core/credential-accounts.d.ts +2 -0
  98. package/dist/core/credential-accounts.js +10 -1
  99. package/dist/core/dynamic-prompt/intent-gate.js +2 -2
  100. package/dist/core/dynamic-prompt/style.js +2 -2
  101. package/dist/core/dynamic-prompt/verification.js +2 -2
  102. package/dist/core/egress/wire.d.ts +13 -0
  103. package/dist/core/extensions/builtin/anthropic-subscription/accounts.d.ts +3 -0
  104. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +3 -2
  105. package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-account-test.js +3 -1
  106. package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-connection-test.d.ts +3 -1
  107. package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-connection-test.js +47 -16
  108. package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-login-slots.d.ts +1 -1
  109. package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-login-slots.js +4 -2
  110. package/dist/core/extensions/builtin/anthropic-subscription/claude-settings-env.d.ts +20 -0
  111. package/dist/core/extensions/builtin/anthropic-subscription/claude-settings-env.js +39 -0
  112. package/dist/core/extensions/builtin/anthropic-subscription/context-window.d.ts +17 -0
  113. package/dist/core/extensions/builtin/anthropic-subscription/context-window.js +23 -0
  114. package/dist/core/extensions/builtin/anthropic-subscription/errors.d.ts +1 -0
  115. package/dist/core/extensions/builtin/anthropic-subscription/errors.js +14 -5
  116. package/dist/core/extensions/builtin/anthropic-subscription/oauth-login.js +3 -3
  117. package/dist/core/extensions/builtin/anthropic-subscription/prompt-cache-ttl.d.ts +10 -4
  118. package/dist/core/extensions/builtin/anthropic-subscription/prompt-cache-ttl.js +14 -5
  119. package/dist/core/extensions/builtin/anthropic-subscription/query-environment.d.ts +3 -2
  120. package/dist/core/extensions/builtin/anthropic-subscription/query-environment.js +5 -4
  121. package/dist/core/extensions/builtin/anthropic-subscription/sdk-boundary.lazy.d.ts +2 -1
  122. package/dist/core/extensions/builtin/anthropic-subscription/sdk-boundary.lazy.js +1 -0
  123. package/dist/core/extensions/builtin/anthropic-subscription/stream.js +11 -1
  124. package/dist/core/extensions/builtin/config-reload/change-groups.js +6 -2
  125. package/dist/core/extensions/builtin/goal/continuation.d.ts +6 -0
  126. package/dist/core/extensions/builtin/goal/continuation.js +3 -2
  127. package/dist/core/extensions/builtin/goal/index.js +14 -26
  128. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts +8 -6
  129. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +77 -39
  130. package/dist/core/extensions/builtin/goal/monitor-continuation.js +66 -7
  131. package/dist/core/extensions/builtin/goal/persistence.js +20 -0
  132. package/dist/core/extensions/builtin/goal/provider-retry.d.ts +9 -0
  133. package/dist/core/extensions/builtin/goal/provider-retry.js +16 -0
  134. package/dist/core/extensions/builtin/goal/reload-reengagement.d.ts +3 -2
  135. package/dist/core/extensions/builtin/goal/reload-reengagement.js +4 -4
  136. package/dist/core/extensions/builtin/goal/session-start-continuation.d.ts +28 -0
  137. package/dist/core/extensions/builtin/goal/session-start-continuation.js +75 -0
  138. package/dist/core/extensions/builtin/goal/store.d.ts +3 -2
  139. package/dist/core/extensions/builtin/goal/store.js +9 -2
  140. package/dist/core/extensions/builtin/goal/types.d.ts +9 -0
  141. package/dist/core/extensions/builtin/handoff-hint/index.js +15 -11
  142. package/dist/core/extensions/builtin/handoff-hint/replacement-relay.d.ts +16 -0
  143. package/dist/core/extensions/builtin/handoff-hint/replacement-relay.js +17 -0
  144. package/dist/core/extensions/builtin/html-render/tool.js +13 -3
  145. package/dist/core/extensions/builtin/imagegen/skill/SKILL.md +1 -1
  146. package/dist/core/extensions/builtin/imagegen/tool.js +4 -14
  147. package/dist/core/extensions/builtin/mcp/auth/catalog-identity.js +32 -8
  148. package/dist/core/extensions/builtin/mcp/auth/commands-auth.js +2 -1
  149. package/dist/core/extensions/builtin/mcp/auth/context.js +3 -0
  150. package/dist/core/extensions/builtin/mcp/auth/oauth-grant.d.ts +43 -0
  151. package/dist/core/extensions/builtin/mcp/auth/oauth-grant.js +196 -0
  152. package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.d.ts +7 -1
  153. package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.js +13 -0
  154. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +6 -17
  155. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +67 -88
  156. package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.d.ts +1 -1
  157. package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +25 -4
  158. package/dist/core/extensions/builtin/mcp/auth/oauth.js +10 -7
  159. package/dist/core/extensions/builtin/mcp/auth/registered-redirect.d.ts +19 -0
  160. package/dist/core/extensions/builtin/mcp/auth/registered-redirect.js +55 -0
  161. package/dist/core/extensions/builtin/mcp/auth/token-store.d.ts +23 -1
  162. package/dist/core/extensions/builtin/mcp/auth/token-store.js +3 -0
  163. package/dist/core/extensions/builtin/mcp/catalog-cache.js +7 -6
  164. package/dist/core/extensions/builtin/mcp/catalog.js +3 -2
  165. package/dist/core/extensions/builtin/mcp/invocation.js +6 -0
  166. package/dist/core/extensions/builtin/mcp/service-register.js +16 -2
  167. package/dist/core/extensions/builtin/mcp/service-tools-changed.js +61 -54
  168. package/dist/core/extensions/builtin/mcp/startup-race.d.ts +2 -0
  169. package/dist/core/extensions/builtin/mcp/startup-race.js +25 -7
  170. package/dist/core/extensions/builtin/permission-system/parsers.js +1 -0
  171. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js +3 -3
  172. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-2.js +9 -8
  173. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js +3 -3
  174. package/dist/core/extensions/builtin/prompt-preset/claude-haiku-5-5.js +3 -3
  175. package/dist/core/extensions/builtin/prompt-preset/claude-opus-5-5.js +3 -3
  176. package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js +4 -4
  177. package/dist/core/extensions/builtin/prompt-preset/claude-sonnet-5-5.js +3 -3
  178. package/dist/core/extensions/builtin/prompt-preset/deepseek-v4-1-flash.js +3 -3
  179. package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.js +3 -3
  180. package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +2 -2
  181. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js +3 -3
  182. package/dist/core/extensions/builtin/prompt-preset/grok-4.5.js +1 -1
  183. package/dist/core/extensions/builtin/prompt-preset/grok-4.6.js +4 -4
  184. package/dist/core/extensions/builtin/prompt-preset/grok-4.7.js +5 -5
  185. package/dist/core/extensions/builtin/prompt-preset/kimi-k3-1.js +4 -4
  186. package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js +7 -6
  187. package/dist/core/extensions/loader.js +24 -4
  188. package/dist/core/extensions/runner.d.ts +9 -1
  189. package/dist/core/extensions/runner.js +9 -4
  190. package/dist/core/extensions/types.d.ts +19 -3
  191. package/dist/core/hand/hook-advertise.d.ts +41 -0
  192. package/dist/core/hand/hook-advertise.js +98 -0
  193. package/dist/core/hand/hook-contract.d.ts +66 -0
  194. package/dist/core/hand/hook-contract.js +138 -0
  195. package/dist/core/open-timing.d.ts +2 -0
  196. package/dist/core/open-timing.js +7 -1
  197. package/dist/core/resource-loader.js +21 -3
  198. package/dist/core/retry-fallback/controller-types.d.ts +2 -0
  199. package/dist/core/retry-fallback/controller.js +9 -2
  200. package/dist/core/retry-fallback/cooldown.d.ts +7 -0
  201. package/dist/core/retry-fallback/cooldown.js +13 -1
  202. package/dist/core/retry-fallback/credential-recovery.d.ts +7 -0
  203. package/dist/core/retry-fallback/credential-recovery.js +24 -0
  204. package/dist/core/retry-fallback/log.d.ts +7 -1
  205. package/dist/core/retry-fallback/log.js +82 -5
  206. package/dist/core/retry-fallback/model-config-error.js +2 -1
  207. package/dist/core/retry-fallback/settings.d.ts +8 -5
  208. package/dist/core/retry-fallback/settings.js +9 -6
  209. package/dist/core/session-manager.d.ts +2 -0
  210. package/dist/core/session-manager.js +15 -1
  211. package/dist/core/settings-manager.d.ts +6 -0
  212. package/dist/core/settings-manager.js +8 -0
  213. package/dist/core/turn-origin.d.ts +18 -10
  214. package/dist/core/turn-origin.js +34 -30
  215. package/dist/core/ultrafast-lanes.d.ts +1 -0
  216. package/dist/core/ultrafast-lanes.js +4 -2
  217. package/dist/modes/interactive/components/footer.d.ts +23 -5
  218. package/dist/modes/interactive/components/footer.js +45 -16
  219. package/dist/modes/interactive/interactive-mode.js +9 -1
  220. package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
  221. package/dist/modes/interactive/session-control-rpc-commands.js +5 -0
  222. package/dist/modes/rpc/admit-external-message-command.d.ts +2 -0
  223. package/dist/modes/rpc/admit-external-message-command.js +5 -1
  224. package/dist/modes/rpc/compact-admission-validation.js +4 -1
  225. package/dist/modes/rpc/connection-authority.d.ts +2 -1
  226. package/dist/modes/rpc/connection-authority.js +11 -9
  227. package/dist/modes/rpc/connection-handler.d.ts +2 -0
  228. package/dist/modes/rpc/connection-handler.js +33 -7
  229. package/dist/modes/rpc/connection-roles.d.ts +7 -0
  230. package/dist/modes/rpc/connection-roles.js +15 -0
  231. package/dist/modes/rpc/custom-capability.d.ts +6 -0
  232. package/dist/modes/rpc/custom-capability.js +6 -0
  233. package/dist/modes/rpc/delegated-drive-origin.d.ts +6 -6
  234. package/dist/modes/rpc/delegated-drive-origin.js +7 -8
  235. package/dist/modes/rpc/delegation-declaration.d.ts +2 -0
  236. package/dist/modes/rpc/delegation-declaration.js +7 -3
  237. package/dist/modes/rpc/effect-commands.d.ts +33 -0
  238. package/dist/modes/rpc/host-capabilities.d.ts +1 -0
  239. package/dist/modes/rpc/host-capabilities.js +7 -1
  240. package/dist/modes/rpc/host-gc-evidence.d.ts +1 -1
  241. package/dist/modes/rpc/host-gc-evidence.js +9 -3
  242. package/dist/modes/rpc/host-gc.js +4 -1
  243. package/dist/modes/rpc/host-observe-request.d.ts +2 -0
  244. package/dist/modes/rpc/host-observe-request.js +6 -1
  245. package/dist/modes/rpc/host-park-ledger.d.ts +19 -0
  246. package/dist/modes/rpc/host-park-ledger.js +110 -0
  247. package/dist/modes/rpc/lifecycle-delivery.js +4 -1
  248. package/dist/modes/rpc/loopless-tool-handlers.d.ts +6 -0
  249. package/dist/modes/rpc/loopless-tool-handlers.js +14 -1
  250. package/dist/modes/rpc/media-placeholders.js +1 -0
  251. package/dist/modes/rpc/method-classes-schema-host.d.ts +42 -1
  252. package/dist/modes/rpc/method-classes-schema-host.js +24 -21
  253. package/dist/modes/rpc/method-classes-schema-session.d.ts +20 -0
  254. package/dist/modes/rpc/method-classes-schema-session.js +6 -0
  255. package/dist/modes/rpc/method-classes-schema.d.ts +62 -1
  256. package/dist/modes/rpc/method-classes-schema.js +5 -2
  257. package/dist/modes/rpc/method-classes.d.ts +4 -0
  258. package/dist/modes/rpc/method-classes.js +6 -0
  259. package/dist/modes/rpc/multi-session-host.js +1 -1
  260. package/dist/modes/rpc/project-file-commands.d.ts +32 -0
  261. package/dist/modes/rpc/project-file-commands.js +195 -0
  262. package/dist/modes/rpc/project-file-path.d.ts +34 -0
  263. package/dist/modes/rpc/project-file-path.js +93 -0
  264. package/dist/modes/rpc/rpc-error-types.d.ts +9 -1
  265. package/dist/modes/rpc/rpc-error-types.js +8 -0
  266. package/dist/modes/rpc/rpc-host-command-types.d.ts +14 -1
  267. package/dist/modes/rpc/rpc-host-response-types.d.ts +21 -0
  268. package/dist/modes/rpc/rpc-mutation-response-types.d.ts +7 -0
  269. package/dist/modes/rpc/rpc-read-response-types.d.ts +7 -0
  270. package/dist/modes/rpc/rpc-session-command-types.d.ts +10 -0
  271. package/dist/modes/rpc/rpc-session-state-types.d.ts +2 -0
  272. package/dist/modes/rpc/session-attachment-gate.js +6 -0
  273. package/dist/modes/rpc/session-binding.d.ts +1 -0
  274. package/dist/modes/rpc/session-binding.js +6 -1
  275. package/dist/modes/rpc/session-command-activity.js +1 -0
  276. package/dist/modes/rpc/session-connection-context.d.ts +2 -0
  277. package/dist/modes/rpc/session-connection-context.js +4 -0
  278. package/dist/modes/rpc/session-registry-attach.d.ts +2 -0
  279. package/dist/modes/rpc/session-registry-attach.js +15 -11
  280. package/dist/modes/rpc/session-registry-claims.js +2 -0
  281. package/dist/modes/rpc/session-registry-open.js +13 -7
  282. package/dist/modes/rpc/session-registry-opening.js +2 -1
  283. package/dist/modes/rpc/session-registry-types.d.ts +4 -0
  284. package/dist/modes/rpc/session-registry.d.ts +3 -0
  285. package/dist/modes/rpc/session-registry.js +27 -3
  286. package/dist/modes/rpc/session-router-close.js +1 -0
  287. package/dist/modes/rpc/session-router-context.d.ts +4 -2
  288. package/dist/modes/rpc/session-router-context.js +3 -0
  289. package/dist/modes/rpc/session-router-host-reads.js +1 -0
  290. package/dist/modes/rpc/session-router-occupancy.js +10 -1
  291. package/dist/modes/rpc/session-router-open-result.d.ts +12 -0
  292. package/dist/modes/rpc/session-router-open-result.js +63 -0
  293. package/dist/modes/rpc/session-router-open.d.ts +3 -0
  294. package/dist/modes/rpc/session-router-open.js +15 -63
  295. package/dist/modes/rpc/session-router-park.d.ts +15 -0
  296. package/dist/modes/rpc/session-router-park.js +176 -0
  297. package/dist/modes/rpc/session-router-routing.d.ts +1 -1
  298. package/dist/modes/rpc/session-router-routing.js +7 -0
  299. package/dist/modes/rpc/session-status-source.js +2 -2
  300. package/dist/private-fd-startup-fillers.d.ts +2 -2
  301. package/dist/private-fd-startup-fillers.js +34 -4
  302. package/dist/senpi +9 -3
  303. package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
  304. package/docs/engine-socket/fixtures/rpc-inbound-samples.json +44 -2
  305. package/docs/extensions.md +11 -1
  306. package/docs/rpc.md +106 -2
  307. package/docs/settings.md +5 -3
  308. package/docs/skills.md +1 -1
  309. package/docs/usage.md +1 -1
  310. package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
  311. package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
  312. package/package.json +6 -6
  313. package/dist/bundle/chunks/chunk-436DNN4Z.js +0 -2
  314. package/dist/bundle/chunks/chunk-DLAY7CIU.js +0 -2
  315. package/dist/bundle/chunks/chunk-SJI6UB23.js +0 -2
  316. package/dist/bundle/chunks/chunk-XH2MWTXA.js +0 -11
  317. package/dist/bundle/chunks/chunk-YK3ZZCHI.js +0 -2
  318. package/dist/bundle/chunks/openai-codex-responses-ABB2J43U.js +0 -10
  319. package/dist/bundle/chunks/rpc-mode-ZDUGW2IM.js +0 -2
  320. package/dist/bundle/chunks/session-control-endpoint-MEDV66PS.js +0 -3
@@ -2,8 +2,9 @@ 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 { credentialsMatchIssuer, validatedAuthorizationServerIssuer } from "./oauth-issuer.js";
6
- import { mergeTokensIntoStoredAuth, REFRESH_LEEWAY_MS, storedAuthToTokens, storedGrantIssuer, } from "./oauth-provider.js";
5
+ import { canonicalAuthorizationServerIssuer, credentialsMatchIssuer, validatedAuthorizationServerIssuer, } from "./oauth-issuer.js";
6
+ import { mergeTokensIntoStoredAuth, storedAuthToTokens, storedGrantIssuer, validGrantProof, withGrantAuthorization, } from "./oauth-grant.js";
7
+ const REFRESH_LEEWAY_MS = 5 * 60 * 1000;
7
8
  export function isTokenStale(record, now) {
8
9
  const token = record?.accessToken;
9
10
  if (token === undefined || token.length === 0)
@@ -82,6 +83,7 @@ export class McpRefreshManager {
82
83
  });
83
84
  }
84
85
  const resource = new URL(current.resource ?? this.#provider.serverUrl);
86
+ const proof = validGrantProof(current, this.#provider.grantContext);
85
87
  const { refreshAuthorization } = await loadMcpSdkAuth();
86
88
  const maxRetries = this.#options.maxRetries ?? 2;
87
89
  for (let attempt = 0;; attempt++) {
@@ -93,10 +95,29 @@ export class McpRefreshManager {
93
95
  resource,
94
96
  fetchFn: oauthFetch(this.#options.fetchFn),
95
97
  });
96
- this.#provider.store.writeUnlocked(mergeTokensIntoStoredAuth(current, tokens, this.#provider.serverUrl, authorizationServer));
97
- return { ...tokens, issuer: authorizationServer };
98
+ if (tokens.issuer !== undefined &&
99
+ canonicalAuthorizationServerIssuer(tokens.issuer) !==
100
+ canonicalAuthorizationServerIssuer(authorizationServer)) {
101
+ this.#provider.store.writeUnlocked(undefined);
102
+ throw new OAuthFlowError("needs_auth", "MCP refresh response names a different issuer.", {
103
+ serverName: this.#provider.serverName,
104
+ });
105
+ }
106
+ const next = mergeTokensIntoStoredAuth({ ...current, issuer: authorizationServer, resource: resource.href, refreshScope: proof?.refreshScope }, {
107
+ ...tokens,
108
+ issuer: authorizationServer,
109
+ scope: tokens.scope === undefined ? proof?.refreshScope : tokens.scope,
110
+ });
111
+ const committed = withGrantAuthorization(next, this.#provider.grantContext, current);
112
+ this.#provider.store.writeUnlocked(committed);
113
+ const result = storedAuthToTokens(committed);
114
+ if (result === undefined)
115
+ throw new OAuthFlowError("needs_auth", "MCP refresh returned no access token.");
116
+ return result;
98
117
  }
99
118
  catch (error) {
119
+ if (error instanceof OAuthFlowError)
120
+ throw error;
100
121
  if (isInvalidGrant(error)) {
101
122
  // Terminal: drop credentials so the next use forces a clean re-auth.
102
123
  this.#provider.store.writeUnlocked(undefined);
@@ -34,11 +34,11 @@ export async function finishAuthorization(provider, code, options = {}, iss) {
34
34
  let result;
35
35
  const { auth } = await loadMcpSdkAuth();
36
36
  try {
37
- result = await auth(provider, {
37
+ result = await provider.withFreshAuthorization(() => auth(provider, {
38
38
  serverUrl: provider.serverUrl,
39
39
  authorizationCode: code,
40
40
  fetchFn: oauthFetch(options.fetchFn),
41
- });
41
+ }));
42
42
  }
43
43
  catch (error) {
44
44
  if (isInvalidGrant(error) || isRejectedAuthorizationCode(error)) {
@@ -111,12 +111,15 @@ export async function clientCredentialsGrant(provider, options = {}) {
111
111
  },
112
112
  };
113
113
  const { fetchToken } = await loadMcpSdkAuth();
114
- const tokens = await fetchToken(credentialsProvider, info.authorizationServerUrl, {
115
- metadata: info.authorizationServerMetadata,
116
- resource: new URL(provider.serverUrl),
117
- fetchFn: oauthFetch(options.fetchFn),
114
+ await provider.validateResourceURL(provider.serverUrl, provider.serverUrl);
115
+ await provider.withFreshAuthorization(async () => {
116
+ const tokens = await fetchToken(credentialsProvider, info.authorizationServerUrl, {
117
+ metadata: info.authorizationServerMetadata,
118
+ resource: new URL(provider.serverUrl),
119
+ fetchFn: oauthFetch(options.fetchFn),
120
+ });
121
+ await provider.saveTokens({ ...tokens, issuer: validatedAuthorizationServerIssuer(info, provider.serverName) });
118
122
  });
119
- await provider.saveTokens({ ...tokens, issuer: validatedAuthorizationServerIssuer(info, provider.serverName) });
120
123
  }
121
124
  export async function logout(provider) {
122
125
  await provider.store.clear();
@@ -0,0 +1,19 @@
1
+ import type { McpClientRegistration, McpStoredAuth } from "./token-store.ts";
2
+ /** The provider options that decide which registration a flow uses. */
3
+ export interface RegistrationFlow {
4
+ readonly redirectUrl?: string | URL | undefined;
5
+ /** The flow sends an authorize request to `redirectUrl`. */
6
+ readonly requireRegisteredRedirect?: boolean | undefined;
7
+ }
8
+ /** The registration a flow uses: for an authorizing flow, the signed-in or staged client naming its redirect URI. */
9
+ export declare function registrationForFlow(record: McpStoredAuth | undefined, flow: RegistrationFlow): McpClientRegistration | undefined;
10
+ /** The stored grant as a flow may present it: a staged client has no grant, so the refresh token is withheld. */
11
+ export declare function grantForFlow(record: McpStoredAuth | undefined, flow: RegistrationFlow): McpStoredAuth | undefined;
12
+ /**
13
+ * Stores a registration. An authorizing flow stages a client that would replace the signed-in one; otherwise a
14
+ * replaced client's refresh token goes with it, because the SDK would present it under the new `client_id`.
15
+ */
16
+ export declare function withRegistration(current: McpStoredAuth | undefined, clientInfo: McpClientRegistration, flow: RegistrationFlow): McpStoredAuth;
17
+ /** After an authorizing flow's code exchange: its staged client replaces the signed-in one and that client's grant. */
18
+ export declare function withPromotedRegistration(current: McpStoredAuth | undefined, flow: RegistrationFlow): McpStoredAuth | undefined;
19
+ //# sourceMappingURL=registered-redirect.d.ts.map
@@ -0,0 +1,55 @@
1
+ // Matched against the redirect URIs the client sent when it registered: an authorization server may echo them
2
+ // rewritten. A registration saved before that record existed falls back to the echo; one with neither is kept.
3
+ function registrationNamesRedirect(registration, redirectUrl) {
4
+ const registered = registration.redirectUrisSent ?? registration.redirect_uris;
5
+ if (registered === undefined || redirectUrl === undefined)
6
+ return true;
7
+ return registered.some((uri) => String(uri) === String(redirectUrl));
8
+ }
9
+ /** The registration a flow uses: for an authorizing flow, the signed-in or staged client naming its redirect URI. */
10
+ export function registrationForFlow(record, flow) {
11
+ if (flow.requireRegisteredRedirect !== true)
12
+ return record?.clientInfo;
13
+ return [record?.clientInfo, record?.pendingClientInfo].find((registration) => registration !== undefined && registrationNamesRedirect(registration, flow.redirectUrl));
14
+ }
15
+ function usesStagedRegistration(record, flow) {
16
+ const registration = registrationForFlow(record, flow);
17
+ return registration !== undefined && registration === record?.pendingClientInfo;
18
+ }
19
+ /** The stored grant as a flow may present it: a staged client has no grant, so the refresh token is withheld. */
20
+ export function grantForFlow(record, flow) {
21
+ if (record === undefined || !usesStagedRegistration(record, flow))
22
+ return record;
23
+ const grant = { ...record };
24
+ delete grant.refreshToken;
25
+ return grant;
26
+ }
27
+ /**
28
+ * Stores a registration. An authorizing flow stages a client that would replace the signed-in one; otherwise a
29
+ * replaced client's refresh token goes with it, because the SDK would present it under the new `client_id`.
30
+ */
31
+ export function withRegistration(current, clientInfo, flow) {
32
+ const known = [current?.clientInfo, current?.pendingClientInfo].some((registration) => registration?.client_id === clientInfo.client_id);
33
+ // The SDK registers with `clientMetadata.redirect_uris`, which is exactly the flow's redirect URL.
34
+ const registration = known || flow.redirectUrl === undefined
35
+ ? clientInfo
36
+ : { ...clientInfo, redirectUrisSent: [String(flow.redirectUrl)] };
37
+ const replaced = current?.clientInfo?.client_id;
38
+ if (replaced === undefined || replaced === registration.client_id)
39
+ return { ...(current ?? {}), clientInfo: registration };
40
+ if (flow.requireRegisteredRedirect === true)
41
+ return { ...current, pendingClientInfo: registration };
42
+ const next = { ...current, clientInfo: registration };
43
+ delete next.refreshToken;
44
+ return next;
45
+ }
46
+ /** After an authorizing flow's code exchange: its staged client replaces the signed-in one and that client's grant. */
47
+ export function withPromotedRegistration(current, flow) {
48
+ if (current?.pendingClientInfo === undefined || !usesStagedRegistration(current, flow))
49
+ return current;
50
+ const next = { ...current, clientInfo: current.pendingClientInfo };
51
+ delete next.pendingClientInfo;
52
+ delete next.refreshToken;
53
+ return next;
54
+ }
55
+ //# sourceMappingURL=registered-redirect.js.map
@@ -1,9 +1,30 @@
1
1
  import type { OAuthDiscoveryState } from "@modelcontextprotocol/sdk/client/auth.js";
2
2
  import type { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js";
3
+ export type McpClientRegistration = OAuthClientInformationFull & {
4
+ redirectUrisSent?: string[];
5
+ };
6
+ export interface McpAuthorizationProof {
7
+ readonly credentialIdentity: string;
8
+ readonly previousCredentialIdentity?: string;
9
+ readonly issuer: string;
10
+ readonly resource: string;
11
+ readonly clientIdentity: string;
12
+ readonly scope: string;
13
+ readonly refreshScope?: string;
14
+ }
15
+ export interface McpAuthorization {
16
+ readonly version: 1;
17
+ readonly id: string;
18
+ readonly proof?: McpAuthorizationProof;
19
+ }
3
20
  export interface McpStoredAuth {
21
+ readonly scope?: string;
22
+ readonly refreshScope?: string;
23
+ readonly authorization?: McpAuthorization;
4
24
  accessToken?: string;
5
25
  refreshToken?: string;
6
- clientInfo?: OAuthClientInformationFull;
26
+ clientInfo?: McpClientRegistration;
27
+ pendingClientInfo?: McpClientRegistration;
7
28
  codeVerifier?: string;
8
29
  discoveryState?: OAuthDiscoveryState;
9
30
  resource?: string;
@@ -33,6 +54,7 @@ export declare class McpTokenStore<TRecord extends McpStoredAuth = McpStoredAuth
33
54
  readonly serverUrl: string;
34
55
  constructor(options: TokenStoreOptions);
35
56
  get rootDir(): string;
57
+ get agentDir(): string;
36
58
  get dir(): string;
37
59
  get tokensPath(): string;
38
60
  get lockPath(): string;
@@ -40,6 +40,9 @@ export class McpTokenStore {
40
40
  get rootDir() {
41
41
  return join(this.#agentDir, AUTH_ROOT);
42
42
  }
43
+ get agentDir() {
44
+ return this.#agentDir;
45
+ }
43
46
  get dir() {
44
47
  return join(this.rootDir, this.#hash);
45
48
  }
@@ -39,24 +39,25 @@ export function cachedCatalogNeedsRefresh(cached, now = Date.now()) {
39
39
  return now - cached.fetchedAt > CACHE_TTL_MS;
40
40
  }
41
41
  export async function collectServerCatalogForCache(connection, config, configHash, credentialIdentity) {
42
- const tools = await collectAllPages((cursor) => listMcpTools(connection.client, cursor === undefined ? {} : { cursor }, {
42
+ const client = connection.client;
43
+ const tools = await collectAllPages((cursor) => listMcpTools(client, cursor === undefined ? {} : { cursor }, {
43
44
  timeout: config.requestTimeoutMs,
44
45
  }));
45
- const resources = await collectOptionalPages((cursor) => connection.client.listResources(cursor === undefined ? {} : { cursor }, {
46
+ const resources = await collectOptionalPages((cursor) => client.listResources(cursor === undefined ? {} : { cursor }, {
46
47
  timeout: config.requestTimeoutMs,
47
48
  }));
48
- const prompts = await collectOptionalPages((cursor) => connection.client.listPrompts(cursor === undefined ? {} : { cursor }, {
49
+ const prompts = await collectOptionalPages((cursor) => client.listPrompts(cursor === undefined ? {} : { cursor }, {
49
50
  timeout: config.requestTimeoutMs,
50
51
  }));
51
- const resourceTemplates = await collectOptionalPages((cursor) => connection.client.listResourceTemplates(cursor === undefined ? {} : { cursor }, {
52
+ const resourceTemplates = await collectOptionalPages((cursor) => client.listResourceTemplates(cursor === undefined ? {} : { cursor }, {
52
53
  timeout: config.requestTimeoutMs,
53
54
  }));
54
55
  return {
55
56
  configHash,
56
- ...(isModernMcpClient(connection.client) ? { modern: true } : {}),
57
+ ...(isModernMcpClient(client) ? { modern: true } : {}),
57
58
  credentialIdentity,
58
59
  fetchedAt: Date.now(),
59
- instructions: connection.client.getInstructions(),
60
+ instructions: client.getInstructions(),
60
61
  prompts,
61
62
  resources,
62
63
  resourceTemplates,
@@ -3,7 +3,8 @@ import { isModernMcpClient } from "./modern/request.js";
3
3
  import { hasSupportedModernToolSchemas } from "./modern/tool-schema.js";
4
4
  import { collectAllPages } from "./expose/pagination.js";
5
5
  export async function collectToolCatalog(server, connection, config, options = {}) {
6
- const result = await collectAllPages((cursor) => listMcpTools(connection.client, cursor === undefined ? {} : { cursor }, { timeout: config.requestTimeoutMs }));
6
+ const client = connection.client;
7
+ const result = await collectAllPages((cursor) => listMcpTools(client, cursor === undefined ? {} : { cursor }, { timeout: config.requestTimeoutMs }));
7
8
  return result.items.map((tool) => ({
8
9
  requestOrigin: options.requestOrigin,
9
10
  annotations: tool.annotations,
@@ -17,7 +18,7 @@ export async function collectToolCatalog(server, connection, config, options = {
17
18
  requestTimeoutMs: config.requestTimeoutMs,
18
19
  schema: tool.inputSchema,
19
20
  outputSchema: tool.outputSchema,
20
- modern: isModernMcpClient(connection.client),
21
+ modern: isModernMcpClient(client),
21
22
  server,
22
23
  tool: tool.name,
23
24
  }));
@@ -61,6 +61,12 @@ export function createMcpInvocationResolver(scope) {
61
61
  try {
62
62
  await Promise.race([ready, interrupted]);
63
63
  }
64
+ catch (error) {
65
+ readySignal.throwIfAborted();
66
+ if (!scope.isCurrent())
67
+ return refuse("unavailable", "MCP session or server configuration was replaced.");
68
+ throw error;
69
+ }
64
70
  finally {
65
71
  clearTimeout(timeout);
66
72
  readySignal.removeEventListener("abort", abort);
@@ -3,7 +3,7 @@ import { computeMcpExposurePolicy } from "./expose/policy.js";
3
3
  import { registerDirectMcpTools } from "./expose/session.js";
4
4
  import { ensureMcpToolCallConnection } from "./health.js";
5
5
  import { createMcpInvocationResolver } from "./invocation.js";
6
- import { connectAndRefreshMcpCatalog } from "./startup-race.js";
6
+ import { connectAndRefreshMcpCatalog, prepareMcpAuthentication } from "./startup-race.js";
7
7
  export async function registerMcpServiceDirectTools(pi, config, entries, toolSearchService, options = {}) {
8
8
  return await registerDirectMcpTools(pi, config, [...entries].map((entry) => {
9
9
  const serverConfig = config.servers[entry.name]?.config;
@@ -18,7 +18,21 @@ export async function registerMcpServiceDirectTools(pi, config, entries, toolSea
18
18
  servesSession: options.servesSession,
19
19
  isCurrent: () => entry.isCurrent?.() !== false && options.isCurrent?.(entry) !== false,
20
20
  ready: async () => {
21
- await ensureMcpToolCallConnection(entry.connection, () => entry.authPlan?.refresh?.ensureFresh().then(() => undefined) ?? Promise.resolve());
21
+ if (!(await prepareMcpAuthentication(entry)))
22
+ return;
23
+ if (entry.isCurrent?.() === false || options.isCurrent?.(entry) === false)
24
+ return;
25
+ if (entry.credentialsCurrent?.() === false) {
26
+ await entry.onCredentialsChanged?.();
27
+ return;
28
+ }
29
+ await ensureMcpToolCallConnection(entry.connection);
30
+ if (entry.isCurrent?.() === false || options.isCurrent?.(entry) === false)
31
+ return;
32
+ if (entry.credentialsCurrent?.() === false) {
33
+ await entry.onCredentialsChanged?.();
34
+ return;
35
+ }
22
36
  await connectAndRefreshMcpCatalog(entry, serverConfig);
23
37
  await entry.startupCatalogClaim?.settled();
24
38
  if (entry.isCurrent?.() === false || options.isCurrent?.(entry) === false)
@@ -54,71 +54,78 @@ export async function refreshMcpToolsOnListChanged(entry, targets, config, conne
54
54
  // The startup connect that still owns registration registers its refreshed catalog itself.
55
55
  if (connectOnly && entry.startupCatalogClaim?.ownsRegistration() === true)
56
56
  return;
57
- const registeredCatalog = entry.cachedCatalog;
58
- if (entry.connection instanceof SharedMcpLease) {
59
- const refreshed = await entry.connection.catalog();
60
- if (!ownsCatalog())
61
- return;
62
- entry.cachedCatalog = refreshed;
63
- }
64
- const catalog = await collectToolCatalog(entry.name, entry.connection, server.config, {
65
- agentDir: entry.agentDir,
66
- outputGuard: config.settings.outputGuard,
67
- });
68
- if (!ownsCatalog())
69
- return;
70
- const newNames = mapMcpCatalogNames(catalog).map(({ name }) => name);
71
- // Before the first refresh, the registered names are the catalog the startup pass registered.
72
- const knownNames = entry.knownToolNames ??
73
- (registeredCatalog === undefined
74
- ? newNames
75
- : mapMcpCatalogNames(cachedToolsToCatalogEntries(entry.name, registeredCatalog, entry.connection, server.config.requestTimeoutMs, async () => { })).map(({ name }) => name));
76
- const diff = diffMcpToolNames(knownNames, newNames);
77
- const identity = mcpRegistrationIdentity(catalog, entry.cachedCatalog);
78
- const initial = targets();
79
- const stale = connectOnly ? initial.filter((target) => target.registeredIdentity() !== identity) : initial;
80
- if (stale.length > 0) {
81
- // Registration reads entry.cachedCatalog. A shared lease refreshed it above; nothing else
82
- // refreshes a non-shared connection's catalog after its startup connect (#2188).
83
- if (!(entry.connection instanceof SharedMcpLease)) {
84
- const refreshed = await collectServerCatalogForCache(entry.connection, server.config, entry.generationHash ?? entry.configHash, entry.credentialIdentity);
57
+ try {
58
+ const registeredCatalog = entry.cachedCatalog;
59
+ if (entry.connection instanceof SharedMcpLease) {
60
+ const refreshed = await entry.connection.catalog();
85
61
  if (!ownsCatalog())
86
62
  return;
87
63
  entry.cachedCatalog = refreshed;
88
- await writeMcpCachedServer(entry.agentDir, entry.name, refreshed, ownsCatalog);
89
64
  }
65
+ const catalog = await collectToolCatalog(entry.name, entry.connection, server.config, {
66
+ agentDir: entry.agentDir,
67
+ outputGuard: config.settings.outputGuard,
68
+ });
90
69
  if (!ownsCatalog())
91
70
  return;
92
- // Tombstone removed tools BEFORE re-registration so the subsequent
93
- // setActiveTools (which excludes them) leaves the tombstones inactive.
94
- const seen = new Set(initial.map((target) => target.pi));
95
- const failures = [];
96
- let pending = stale;
97
- while (pending.length > 0) {
98
- for (const target of pending) {
71
+ const newNames = mapMcpCatalogNames(catalog).map(({ name }) => name);
72
+ // Before the first refresh, the registered names are the catalog the startup pass registered.
73
+ const knownNames = entry.knownToolNames ??
74
+ (registeredCatalog === undefined
75
+ ? newNames
76
+ : mapMcpCatalogNames(cachedToolsToCatalogEntries(entry.name, registeredCatalog, entry.connection, server.config.requestTimeoutMs, async () => { })).map(({ name }) => name));
77
+ const diff = diffMcpToolNames(knownNames, newNames);
78
+ const identity = mcpRegistrationIdentity(catalog, entry.cachedCatalog);
79
+ const initial = targets();
80
+ const stale = connectOnly ? initial.filter((target) => target.registeredIdentity() !== identity) : initial;
81
+ if (stale.length > 0) {
82
+ // Registration reads entry.cachedCatalog. A shared lease refreshed it above; nothing else
83
+ // refreshes a non-shared connection's catalog after its startup connect (#2188).
84
+ if (!(entry.connection instanceof SharedMcpLease)) {
85
+ const refreshed = await collectServerCatalogForCache(entry.connection, server.config, entry.generationHash ?? entry.configHash, entry.credentialIdentity);
99
86
  if (!ownsCatalog())
100
87
  return;
101
- try {
102
- for (const removed of diff.removed)
103
- target.pi.registerTool(buildMcpTombstoneDefinition(removed, entry.name));
104
- await target.register();
105
- }
106
- catch (error) {
107
- failures.push(error);
88
+ entry.cachedCatalog = refreshed;
89
+ await writeMcpCachedServer(entry.agentDir, entry.name, refreshed, ownsCatalog);
90
+ }
91
+ if (!ownsCatalog())
92
+ return;
93
+ // Tombstone removed tools BEFORE re-registration so the subsequent
94
+ // setActiveTools (which excludes them) leaves the tombstones inactive.
95
+ const seen = new Set(initial.map((target) => target.pi));
96
+ const failures = [];
97
+ let pending = stale;
98
+ while (pending.length > 0) {
99
+ for (const target of pending) {
100
+ if (!ownsCatalog())
101
+ return;
102
+ try {
103
+ for (const removed of diff.removed)
104
+ target.pi.registerTool(buildMcpTombstoneDefinition(removed, entry.name));
105
+ await target.register();
106
+ }
107
+ catch (error) {
108
+ failures.push(error);
109
+ }
108
110
  }
111
+ // A session that attached during this refresh registered the catalog cached before it.
112
+ pending = targets().filter((target) => !seen.has(target.pi));
113
+ for (const target of pending)
114
+ seen.add(target.pi);
115
+ }
116
+ if (failures.length > 0) {
117
+ entry.knownToolNames = newNames;
118
+ entry.lastListChangedDelta = formatMcpListChangedDelta(diff);
119
+ throw new AggregateError(failures, `MCP ${entry.name} tool refresh failed in ${failures.length} of ${seen.size} session(s)`);
109
120
  }
110
- // A session that attached during this refresh registered the catalog cached before it.
111
- pending = targets().filter((target) => !seen.has(target.pi));
112
- for (const target of pending)
113
- seen.add(target.pi);
114
- }
115
- if (failures.length > 0) {
116
- entry.knownToolNames = newNames;
117
- entry.lastListChangedDelta = formatMcpListChangedDelta(diff);
118
- throw new AggregateError(failures, `MCP ${entry.name} tool refresh failed in ${failures.length} of ${seen.size} session(s)`);
119
121
  }
122
+ entry.knownToolNames = newNames;
123
+ entry.lastListChangedDelta = formatMcpListChangedDelta(diff);
124
+ }
125
+ catch (error) {
126
+ if (!ownsCatalog())
127
+ return;
128
+ throw error;
120
129
  }
121
- entry.knownToolNames = newNames;
122
- entry.lastListChangedDelta = formatMcpListChangedDelta(diff);
123
130
  }
124
131
  //# sourceMappingURL=service-tools-changed.js.map
@@ -43,6 +43,8 @@ export declare class McpDeferredAttach {
43
43
  }
44
44
  export declare function raceMcpStartupConnect(options: RaceMcpStartupConnectOptions): Promise<void>;
45
45
  export declare function connectAndRefreshMcpCatalog(entry: McpConnectionEntry, serverConfig: ResolvedMcpServer["config"]): Promise<void>;
46
+ /** Reconcile the grant before health recovery without attempting a transport connection. */
47
+ export declare function prepareMcpAuthentication(entry: McpConnectionEntry): Promise<boolean>;
46
48
  export declare function ignoreStartupNeedsAuth(entry: McpConnectionEntry, connect: Promise<void>): Promise<void>;
47
49
  export declare function waitForMcpStartupRace(connect: Promise<void>, deadlineMs?: number): Promise<McpStartupRaceResult>;
48
50
  export declare function shouldRaceMcpStartup(lifecycle: "lazy" | "eager" | "keep-alive"): boolean;
@@ -91,15 +91,16 @@ export function connectAndRefreshMcpCatalog(entry, serverConfig) {
91
91
  void refresh.then(finish, finish);
92
92
  return refresh;
93
93
  }
94
- async function refreshConnectedMcpCatalog(entry, serverConfig) {
95
- if (serverConfig === undefined || entry.isCurrent?.() === false)
96
- return;
94
+ /** Reconcile the grant before health recovery without attempting a transport connection. */
95
+ export async function prepareMcpAuthentication(entry) {
96
+ if (entry.isCurrent?.() === false)
97
+ return false;
97
98
  try {
98
99
  await entry.authPlan?.refresh?.ensureFresh();
99
100
  }
100
101
  catch (error) {
101
- if (entry.connection.state === "disabled")
102
- return;
102
+ if (entry.isCurrent?.() === false || entry.connection.state === "disabled")
103
+ return false;
103
104
  const authError = markMcpConnectionNeedsAuth(entry.connection, error);
104
105
  if (authError !== undefined) {
105
106
  entry.logger.warn(authError.message);
@@ -107,10 +108,17 @@ async function refreshConnectedMcpCatalog(entry, serverConfig) {
107
108
  }
108
109
  throw error;
109
110
  }
111
+ if (entry.isCurrent?.() === false)
112
+ return false;
110
113
  if (entry.credentialsCurrent?.() === false) {
111
114
  await entry.onCredentialsChanged?.();
112
- return;
115
+ return false;
113
116
  }
117
+ return true;
118
+ }
119
+ async function refreshConnectedMcpCatalog(entry, serverConfig) {
120
+ if (serverConfig === undefined || !(await prepareMcpAuthentication(entry)))
121
+ return;
114
122
  await connectMcpServer(entry.connection, entry.logger);
115
123
  if (entry.connection.state !== "connected" || entry.isCurrent?.() === false)
116
124
  return;
@@ -120,7 +128,15 @@ async function refreshConnectedMcpCatalog(entry, serverConfig) {
120
128
  entry.connection.state === "connected" &&
121
129
  entry.connection.generation === generation;
122
130
  if (entry.connection instanceof SharedMcpLease) {
123
- const catalog = await entry.connection.catalog();
131
+ let catalog;
132
+ try {
133
+ catalog = await entry.connection.catalog();
134
+ }
135
+ catch (error) {
136
+ if (!ownsCatalog())
137
+ return;
138
+ throw error;
139
+ }
124
140
  if (!ownsCatalog()) {
125
141
  if (entry.credentialsCurrent?.() === false)
126
142
  await entry.onCredentialsChanged?.();
@@ -150,6 +166,8 @@ async function refreshConnectedMcpCatalog(entry, serverConfig) {
150
166
  await ensureMcpResourceSubscriptions(entry.connection.client, catalog.resources ?? []);
151
167
  }
152
168
  catch (error) {
169
+ if (!ownsCatalog())
170
+ return;
153
171
  entry.logger.warn("Failed to refresh MCP catalog cache", {
154
172
  error: error instanceof Error ? error.message : String(error),
155
173
  });
@@ -203,6 +203,7 @@ export function toolOwnedPermissionRequests(tools, toolName, input, cwd) {
203
203
  permission: request.permission,
204
204
  patterns: [...request.patterns],
205
205
  always: [...request.always],
206
+ ...(request.autoApproveAsk === true ? { autoApproveAsk: true } : {}),
206
207
  }));
207
208
  }
208
209
  /** Registry for tool-specific permission parsers */
@@ -85,7 +85,7 @@ ${buildSearchLine(context)}
85
85
  Route by true intent, not surface form:
86
86
  - Information asks (explain, look into, investigate): read the code and report; no edits.
87
87
  - Judgment asks (what do you think, review) and open-ended changes (refactor, improve, clean up): assess and propose, then wait for confirmation.
88
- - Change asks (implement, add, fix this error): build, or diagnose and fix minimally.
88
+ - Change asks (implement, add, fix this error): build, or diagnose and fix at the cause.
89
89
 
90
90
  Derive intent from the latest user turn alone: a new direction drops the stale plan, and queued steering messages outrank earlier intent.
91
91
 
@@ -93,7 +93,7 @@ Derive intent from the latest user turn alone: a new direction drops the stale p
93
93
 
94
94
  The request sets the scope, and the scope is the deliverable: deliver all of it and only it. Make routine judgment calls yourself; ask only when different readings would lead to materially different work, and ask after doing everything that does not depend on the answer, through ask_user_question when it is available (waitForAnswer true when the next step depends on the answer). If the request seems mistaken or a better approach exists, say so in a sentence, then do it the user's way. If part of the task is blocked, finish every other part and say exactly what you left out and why.
95
95
 
96
- Smallest correct change wins: no refactors beside a focused fix, no helpers or abstractions for hypothetical needs, no defensive checks inside trusted code; validate only at system boundaries. A pre-existing bug or performance concern you notice is a follow-up for your summary, not a change in this diff. Scratch checks verify and get discarded; commit tests only where the task asks for them or the repository already keeps tests for that kind of change, sized like the neighboring test files. Prefer a surgical edit over rewriting a file when the result would be identical.
96
+ This codebase is yours: what it's for and how it's built are your call, and you'll keep building on it. Change it the way its owner would: build on the code that already does the job instead of adding a parallel copy, fit new behavior into the structure that is there, and leave no duplicate or dead code behind. A defect in the code you are changing is yours to fix now; one elsewhere goes in your summary as a follow-up. Add no helpers or abstractions for hypothetical needs and no defensive checks inside trusted code; validate only at system boundaries. Scratch checks verify and get discarded; commit tests only where the task asks for them or the repository already keeps tests for that kind of change, sized like the neighboring test files. Prefer a surgical edit over rewriting a file when the result would be identical.
97
97
 
98
98
  ## Working the Task
99
99
 
@@ -107,7 +107,7 @@ Scale the checks to the change, never the rigor: diagnostics on every changed fi
107
107
 
108
108
  ${buildTestDisciplineSection(context.sharedSections)}
109
109
 
110
- Before reporting progress, audit each claim against a tool result from this session; ${context.surface !== "terminal" ? `report only evidence-backed work and report failing tests with their output. ${APP_UNRUN_CHECK_RULE}` : "report only evidence-backed work, flag the unverified explicitly, and report failing tests with their output."} Fix only failures your change caused.
110
+ Before reporting progress, audit each claim against a tool result from this session; ${context.surface !== "terminal" ? `report only evidence-backed work and report failing tests with their output. ${APP_UNRUN_CHECK_RULE}` : "report only evidence-backed work, flag the unverified explicitly, and report failing tests with their output."} Fix failures your change caused and any that sit in the code you changed; note other pre-existing failures separately.
111
111
 
112
112
  ${context.toolSection}
113
113
 
@@ -16,10 +16,11 @@
16
16
  // retries after a busy tool from nearly all tries to four in ten. "Blind
17
17
  // retries" becomes the exact action it meant, repeating a failure that has not
18
18
  // been diagnosed, so it no longer collides with "wait and retry".
19
- // - Scope, structure ("Say who owns the code"): with no ownership statement 5.2
20
- // repeats existing code instead of reusing it. The user owns the code here, so
21
- // the guide's ownership sentence stays out (it invites unrequested changes);
22
- // the smallest-change rule says to build on the code that already does the job.
19
+ // - Scope, structure ("Say who owns the code"): the guide's ownership sentence
20
+ // opens the structure paragraph and replaces the smallest-change sentence
21
+ // (owner order). Without it, 5.2 acts as a guest: parallel code, duplicates
22
+ // left in place, and nearby bugs only mentioned. senpi#6358 carries the same
23
+ // sentence, with what the owner does, into every core.
23
24
  // - Working the Task, delegation ("Set expectations for subagents"): the 5.1
24
25
  // sentence was a standing invitation to delegate, which the guide says to
25
26
  // review first. It now states the cost before the invitation, what a brief
@@ -29,7 +30,7 @@
29
30
  // mannered-prose sentence. 5.2 reports run dense, so the rule asks for plain
30
31
  // words, mostly short one-idea sentences, one word per thing, and no names
31
32
  // made up during the work, for a reader switching in from something else.
32
- // Deliberately absent: the ownership sentence; "answer directly" or think-less
33
+ // Deliberately absent: "answer directly" or think-less
33
34
  // lines (the guide: no effect at medium effort and above, lower the effort
34
35
  // instead); a token budget; the "treat that answer as done" lines; hard
35
36
  // sentence-length limits; any instruction to write reasoning in the reply (a
@@ -67,7 +68,7 @@ ${buildSearchLine(context)}
67
68
  Route by true intent, not surface form:
68
69
  - Information asks (explain, look into, investigate): read the code and report; no edits.
69
70
  - Judgment asks (what do you think, review) and open-ended changes (refactor, improve, clean up): assess and propose, then wait for confirmation.
70
- - Change asks (implement, add, fix this error): build, or diagnose and fix minimally.
71
+ - Change asks (implement, add, fix this error): build, or diagnose and fix at the cause.
71
72
 
72
73
  Derive intent from the latest user turn alone: a new direction drops the stale plan, and queued steering messages outrank earlier intent.
73
74
 
@@ -77,7 +78,7 @@ The request sets the scope, and the scope is the deliverable: deliver all of it
77
78
 
78
79
  Errors, timeouts, locked files, empty results, and failing tools are ordinary obstacles, not blockers: diagnose them, then work through them with the access you have (wait and retry a call that is safe to repeat, fix the request, use another tool or source) without asking first. A file marked not to be touched, withheld access, or a safety guardrail is a deliberate blocker: leave it alone and look for another safe way to finish. If part of the task stays blocked, finish every other part and say exactly what you left out and why.
79
80
 
80
- Smallest correct change wins: build on the code that already does the job instead of adding a parallel copy, with no refactors beside a focused fix, no helpers or abstractions for hypothetical needs, and no defensive checks inside trusted code; validate only at system boundaries. A pre-existing bug or performance concern you notice is a follow-up for your summary, not a change in this diff. Scratch checks verify and get discarded; commit tests only where the task asks for them or the repository already keeps tests for that kind of change, sized like the neighboring test files. Prefer a surgical edit over rewriting a file when the result would be identical.
81
+ This codebase is yours: what it's for and how it's built are your call, and you'll keep building on it. Change it the way its owner would: build on the code that already does the job instead of adding a parallel copy, fit new behavior into the structure that is there, and leave no duplicate or dead code behind. A defect in the code you are changing is yours to fix now; one elsewhere goes in your summary as a follow-up. Add no helpers or abstractions for hypothetical needs and no defensive checks inside trusted code; validate only at system boundaries. Scratch checks verify and get discarded; commit tests only where the task asks for them or the repository already keeps tests for that kind of change, sized like the neighboring test files. Prefer a surgical edit over rewriting a file when the result would be identical.
81
82
 
82
83
  ## Working the Task
83
84
 
@@ -91,7 +92,7 @@ Scale the checks to the change, never the rigor: diagnostics on every changed fi
91
92
 
92
93
  ${buildTestDisciplineSection(context.sharedSections)}
93
94
 
94
- Before reporting progress, audit each claim against a tool result from this session; ${context.surface !== "terminal" ? `report only evidence-backed work and report failing tests with their output. ${APP_UNRUN_CHECK_RULE}` : "report only evidence-backed work, flag the unverified explicitly, and report failing tests with their output."} Fix only failures your change caused.
95
+ Before reporting progress, audit each claim against a tool result from this session; ${context.surface !== "terminal" ? `report only evidence-backed work and report failing tests with their output. ${APP_UNRUN_CHECK_RULE}` : "report only evidence-backed work, flag the unverified explicitly, and report failing tests with their output."} Fix failures your change caused and any that sit in the code you changed; note other pre-existing failures separately.
95
96
 
96
97
  ${context.toolSection}
97
98