@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
@@ -1,4 +1,5 @@
1
1
  import { deadlineStopPendingSinceLastTurn } from "../../../turn-deadline.js";
2
+ import { subscribeProviderAccountEvents } from "../anthropic-subscription/account-events.js";
2
3
  import { isHumanInputMonitorNotification } from "../monitor-state-event.js";
3
4
  import { createGoalCacheWarmScheduleData, estimateCacheWarmMetrics, GOAL_CACHE_WARMUP_ENTRY_TYPE, GOAL_MONITOR_CONTINUATION_FALLBACK_DELAY_MS, resolveGoalBackstopMaxSecondsForCache, resolveGoalMonitorContinuationDelayMs, resolveGoalPromptCacheLifetime, } from "./cache-warm.js";
4
5
  import { subscribeGoalChannelState } from "./channel-state-subscriptions.js";
@@ -9,7 +10,7 @@ import { lastAssistantMessage } from "./last-assistant-message.js";
9
10
  import { admitAndQueueGoalContinuation, buildCurrentGoalContinuationSignature, isLastTurnStuckOnContextOverflow, lastAssistantText, } from "./lifecycle-helpers.js";
10
11
  import { findParkedGoalWait } from "./parked-wait.js";
11
12
  import { buildContinuationPrompt, buildGoalStallNotice, buildTruncationRecoveryPrompt } from "./prompt.js";
12
- import { exhaustedLadderResumeDelayMs, PROVIDER_RETRY_BASE_MS, providerRetryAt, providerRetryDelayMs, } from "./provider-retry.js";
13
+ import { exhaustedLadderResumeDelayMs, GOAL_LIMIT_WAIT_WAKE_LIMIT, isLimitRefusal, limitWaitFloorMs, PROVIDER_RETRY_BASE_MS, providerRetryAt, providerRetryDelayMs, } from "./provider-retry.js";
13
14
  import { isStaleExtensionContextError } from "./stale-context.js";
14
15
  import { resetContinuationStreak } from "./store.js";
15
16
  import { goalStoreRef } from "./store-ref.js";
@@ -22,6 +23,8 @@ export const GOAL_CONTINUATION_SCHEDULED_EVENT = "goal_continuation_scheduled";
22
23
  export const GOAL_CONTINUATION_RESUMED_EVENT = "goal_continuation_resumed";
23
24
  export const GOAL_CONTINUATION_TIMER_STATE_EVENT = "goal_continuation_timer_state";
24
25
  export const GOAL_MONITOR_STALL_EVENT = "goal_monitor_continuation_stall";
26
+ /** A limit outlasted GOAL_LIMIT_WAIT_WAKE_LIMIT re-checks: the goal waits for a sign-in, a block reset or input. */
27
+ const GOAL_LIMIT_WAIT_PARKED_EVENT = "goal_limit_wait_parked";
25
28
  /** The one notice a provider-limit park shows: when the goal resumes, in local time and as the exact instant. */
26
29
  function providerPauseNotice(retryAt) {
27
30
  const local = new Date(retryAt).toLocaleString("en-US", {
@@ -68,12 +71,51 @@ export class MonitorAwareGoalContinuation {
68
71
  #pendingProviderRecovery;
69
72
  #providerRetryAttempts = 0;
70
73
  #providerRetryDelayMs;
74
+ /** The armed provider retry waits out a usage or rate limit, so its wake spends no continuation budget (senpi#4085). */
75
+ #limitWait = false;
76
+ #limitWakes = 0;
77
+ #limitParked = false;
71
78
  constructor(pi, isContinuationPending = () => false, markContinuationPending = () => { }, waitTicker) {
72
79
  this.#pi = pi;
73
80
  this.#isContinuationPending = isContinuationPending;
74
81
  this.#markContinuationPending = markContinuationPending;
75
82
  this.#waitTicker = waitTicker;
76
83
  this.#subscribeToChannelState();
84
+ // A login or an explicit block reset can end the wait early: re-check the provider now (senpi#4085).
85
+ this.#channelStateUnsubscribers.push(subscribeProviderAccountEvents((event) => {
86
+ if (event.type === "accounts_changed" && event.remedy !== undefined)
87
+ this.#wakeLimitWait();
88
+ }));
89
+ }
90
+ #wakeLimitWait() {
91
+ const goal = this.#goal;
92
+ const waiting = this.#scheduledContinuationKind === "providerRetry" || this.#limitParked;
93
+ if (!this.#limitWait || !waiting || goal === null)
94
+ return;
95
+ this.#limitWakes = 0;
96
+ this.#limitParked = false;
97
+ this.#cancelTimer();
98
+ this.#providerRetryDelayMs = 0;
99
+ this.#schedule(goal, "providerRetry");
100
+ }
101
+ /**
102
+ * Arms the next re-check of a limit wait, never sooner than the provider backoff. A limit that outlasts
103
+ * GOAL_LIMIT_WAIT_WAKE_LIMIT re-checks parks the goal, still active, until a sign-in, a block reset or input.
104
+ */
105
+ #armLimitWait(goal, delayMs) {
106
+ this.#limitWait = true;
107
+ this.#limitWakes += 1;
108
+ if (this.#limitWakes > GOAL_LIMIT_WAIT_WAKE_LIMIT) {
109
+ this.#limitParked = true;
110
+ this.#publish(GOAL_LIMIT_WAIT_PARKED_EVENT, { goalId: goal.id, wakes: GOAL_LIMIT_WAIT_WAKE_LIMIT });
111
+ if (this.#ctxHasUI()) {
112
+ this.#ctx?.ui.notify(`Goal paused: the provider kept refusing for a usage limit through ${GOAL_LIMIT_WAIT_WAKE_LIMIT} re-checks. It resumes after a sign-in, an account block reset, or your next message.`, "warning");
113
+ }
114
+ return false;
115
+ }
116
+ this.#providerRetryDelayMs = Math.max(delayMs, limitWaitFloorMs(this.#providerRetryAttempts));
117
+ this.#schedule(goal, "providerRetry");
118
+ return true;
77
119
  }
78
120
  start(ctx) {
79
121
  this.#cancelTimer();
@@ -91,6 +133,9 @@ export class MonitorAwareGoalContinuation {
91
133
  if (options.goal?.id !== this.#goal?.id)
92
134
  this.#resetContinuationState();
93
135
  this.#providerRetryAttempts = 0;
136
+ this.#limitWait = false;
137
+ this.#limitWakes = 0;
138
+ this.#limitParked = false;
94
139
  this.#ctx = options.ctx;
95
140
  this.#goal = options.goal;
96
141
  this.#lastAgentEndMessages = options.messages;
@@ -214,9 +259,18 @@ export class MonitorAwareGoalContinuation {
214
259
  return undefined;
215
260
  }
216
261
  this.#providerRetryAttempts += 1;
262
+ this.#limitWait = isLimitRefusal(pendingProvider.messages);
217
263
  const now = Date.now();
218
264
  const retryAt = providerRetryAt(pendingProvider.messages, now);
219
265
  const delayMs = providerRetryDelayMs(this.#providerRetryAttempts, retryAt, now);
266
+ if (this.#limitWait) {
267
+ this.#cancelTimer();
268
+ const armed = this.#armLimitWait(pendingProvider.goal, delayMs);
269
+ if (armed && retryAt !== undefined && this.#ctxHasUI(pendingProvider.ctx)) {
270
+ pendingProvider.ctx.ui.notify(providerPauseNotice(retryAt), "info");
271
+ }
272
+ return pendingProvider.goal;
273
+ }
220
274
  if (delayMs > 0) {
221
275
  this.#cancelTimer();
222
276
  this.#providerRetryDelayMs = delayMs;
@@ -230,17 +284,16 @@ export class MonitorAwareGoalContinuation {
230
284
  }
231
285
  /**
232
286
  * senpi#3709: every fallback rung failed, so a continuation now would hit the same limits. Park on
233
- * the soonest reset the run named and resume once; with no known reset, stay parked until input.
287
+ * the soonest reset the run named; with no known reset, re-check on the provider backoff (senpi#4085).
234
288
  */
235
289
  #parkUntilLadderReset(resumeAt) {
236
290
  const goal = this.#goal;
237
291
  if (goal?.status !== "active")
238
292
  return undefined;
239
293
  this.#cancelTimer();
240
- if (resumeAt === undefined)
241
- return goal;
242
- this.#providerRetryDelayMs = exhaustedLadderResumeDelayMs(resumeAt, Date.now());
243
- this.#schedule(goal, "providerRetry");
294
+ this.#providerRetryAttempts += 1;
295
+ // No failure named a reset: re-check on the provider backoff (15 s doubling to 30 min) rather than wait for input.
296
+ this.#armLimitWait(goal, resumeAt === undefined ? 0 : exhaustedLadderResumeDelayMs(resumeAt, Date.now()));
244
297
  return goal;
245
298
  }
246
299
  syncGoal(goal) {
@@ -521,7 +574,10 @@ export class MonitorAwareGoalContinuation {
521
574
  this.#resetCacheWarmIteration();
522
575
  }
523
576
  async #admitAndQueue(ctx, goal, path, messages) {
524
- const input = this.#buildVerdictInput(ctx, goal, path, messages);
577
+ const input = {
578
+ ...this.#buildVerdictInput(ctx, goal, path, messages),
579
+ ...(path === "providerRecovery" && this.#limitWait ? { limitWait: true } : {}),
580
+ };
525
581
  const verdict = evaluateGoalContinuation({ goal, ...input });
526
582
  const admittedGoal = await admitAndQueueGoalContinuation(this.#pi, ctx, goal, {
527
583
  input,
@@ -747,6 +803,9 @@ export class MonitorAwareGoalContinuation {
747
803
  this.#pendingSystemRecovery = undefined;
748
804
  this.#pendingProviderRecovery = undefined;
749
805
  this.#providerRetryAttempts = 0;
806
+ this.#limitWait = false;
807
+ this.#limitWakes = 0;
808
+ this.#limitParked = false;
750
809
  this.#consecutiveLengthRecoveries.clear();
751
810
  this.#recentNormalizedOutputHashes = [];
752
811
  this.#recentToolEffects = [];
@@ -318,8 +318,28 @@ function sanitizeContinuationState(goal) {
318
318
  delete next.lastContinuationSignature;
319
319
  if (!isNonNegativeSafeInteger(next.continuationStoppedAt))
320
320
  delete next.continuationStoppedAt;
321
+ if (!isValidContinuationStoppedDetail(next.continuationStoppedDetail)) {
322
+ delete next.continuationStoppedDetail;
323
+ }
324
+ // The detail only means something with its claim; never keep a stale payload past the claim.
325
+ if (next.continuationStoppedAt === undefined)
326
+ delete next.continuationStoppedDetail;
321
327
  return next;
322
328
  }
329
+ /** A stop-publication payload is usable only with a known deny reason and non-negative counters. */
330
+ function isValidContinuationStoppedDetail(value) {
331
+ if (!isRecord(value))
332
+ return false;
333
+ return ((value.reason === "cap" ||
334
+ value.reason === "stale" ||
335
+ value.reason === "repetition" ||
336
+ value.reason === "length-exhausted" ||
337
+ value.reason === "no-progress" ||
338
+ value.reason === "context-overflow") &&
339
+ isNonNegativeSafeInteger(value.consecutiveContinuations) &&
340
+ isNonNegativeSafeInteger(value.unattendedContinuations) &&
341
+ isNonNegativeSafeInteger(value.noProgressContinuations));
342
+ }
323
343
  function isMissingFile(error) {
324
344
  return isFileSystemError(error, "ENOENT");
325
345
  }
@@ -7,6 +7,15 @@ export declare const PROVIDER_RETRY_WAKE_CAP_MS: number;
7
7
  * pool guidance falls back to the earliest future ISO instant in the error only.
8
8
  */
9
9
  export declare function providerRetryAt(messages: readonly AgentMessage[], now: number): number | undefined;
10
+ /** Re-checks of a limit that never clears before the goal parks until a sign-in, a block reset or input (senpi#4085). */
11
+ export declare const GOAL_LIMIT_WAIT_WAKE_LIMIT = 48;
12
+ /** The shortest wait before the `attempt`-th limit re-check: the provider backoff, 15 s doubling to 30 min. */
13
+ export declare function limitWaitFloorMs(attempt: number): number;
14
+ /**
15
+ * The turn ended on a provider refusing for a usage or rate limit, not on an outage (senpi#4085). An OpenAI-compatible
16
+ * adapter reports a bare 429 as a status-only diagnostic (`unknown` + 429); that status is still a limit.
17
+ */
18
+ export declare function isLimitRefusal(messages: readonly AgentMessage[]): boolean;
10
19
  /**
11
20
  * How long to wait before the `attempt`-th consecutive provider-recovery
12
21
  * continuation (senpi#3264). A stated unblock time is the wake; otherwise the
@@ -31,6 +31,22 @@ export function providerRetryAt(messages, now) {
31
31
  .filter((at) => Number.isFinite(at) && at > now);
32
32
  return instants.length === 0 ? undefined : Math.min(...instants);
33
33
  }
34
+ /** Re-checks of a limit that never clears before the goal parks until a sign-in, a block reset or input (senpi#4085). */
35
+ export const GOAL_LIMIT_WAIT_WAKE_LIMIT = 48;
36
+ /** The shortest wait before the `attempt`-th limit re-check: the provider backoff, 15 s doubling to 30 min. */
37
+ export function limitWaitFloorMs(attempt) {
38
+ return Math.min(PROVIDER_RETRY_CAP_MS, PROVIDER_RETRY_BASE_MS * 2 ** (Math.max(1, attempt) - 1));
39
+ }
40
+ /**
41
+ * The turn ended on a provider refusing for a usage or rate limit, not on an outage (senpi#4085). An OpenAI-compatible
42
+ * adapter reports a bare 429 as a status-only diagnostic (`unknown` + 429); that status is still a limit.
43
+ */
44
+ export function isLimitRefusal(messages) {
45
+ const diagnostic = lastAssistantMessage(messages)?.providerDiagnostic;
46
+ return (diagnostic?.category === "rate_limit" ||
47
+ diagnostic?.category === "quota" ||
48
+ (diagnostic?.category === "unknown" && diagnostic.httpStatus === 429));
49
+ }
34
50
  /**
35
51
  * How long to wait before the `attempt`-th consecutive provider-recovery
36
52
  * continuation (senpi#3264). A stated unblock time is the wake; otherwise the
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionContext } from "../../types.ts";
2
2
  import type { MonitorAwareGoalContinuation } from "./monitor-continuation.ts";
3
+ import type { TrailingGoalContinuations } from "./session-start-continuation.ts";
3
4
  import type { Goal } from "./types.ts";
4
5
  export type ReloadReengagementOutcome = "skipped-inactive" | "backstop-rearmed" | "suppressed-flood" | "continuation-queued";
5
6
  /**
@@ -24,7 +25,7 @@ export declare function reengageGoalAfterReload(options: {
24
25
  readonly ctx: ExtensionContext;
25
26
  readonly goal: Goal;
26
27
  readonly monitor: MonitorAwareGoalContinuation;
27
- readonly countTrailingContinuations: () => number;
28
- readonly queueContinuation: () => Promise<void>;
28
+ readonly readTrailingContinuations: () => TrailingGoalContinuations;
29
+ readonly queueContinuation: (limitWait: boolean) => Promise<void>;
29
30
  }): Promise<ReloadReengagementOutcome>;
30
31
  //# sourceMappingURL=reload-reengagement.d.ts.map
@@ -30,12 +30,12 @@ export async function reengageGoalAfterReload(options) {
30
30
  monitor.rearmMonitorBackstop(goal);
31
31
  return "backstop-rearmed";
32
32
  }
33
- const trailingContinuations = options.countTrailingContinuations();
34
- if (trailingContinuations >= GOAL_CONTINUATION_CAP) {
35
- ctx.ui.notify(`Goal auto-continuation suppressed for this resumed session (${trailingContinuations} historical continuations). Send a message to resume.`, "info");
33
+ const trailing = options.readTrailingContinuations();
34
+ if (trailing.counted >= GOAL_CONTINUATION_CAP) {
35
+ ctx.ui.notify(`Goal auto-continuation suppressed for this resumed session (${trailing.counted} historical continuations). Send a message to resume.`, "info");
36
36
  return "suppressed-flood";
37
37
  }
38
- await options.queueContinuation();
38
+ await options.queueContinuation(trailing.waitingOutLimit);
39
39
  return "continuation-queued";
40
40
  }
41
41
  //# sourceMappingURL=reload-reengagement.js.map
@@ -0,0 +1,28 @@
1
+ import type { SessionEntry } from "../../../session-manager.ts";
2
+ import type { ExtensionAPI, ExtensionContext } from "../../types.ts";
3
+ import type { Goal } from "./types.ts";
4
+ export type TrailingGoalContinuations = {
5
+ /** Continuations whose turn ran: the unattended loop the load-time flood guard bounds. */
6
+ readonly counted: number;
7
+ /** The newest continuation was refused for a usage or rate limit, so the goal was waiting that limit out. */
8
+ readonly waitingOutLimit: boolean;
9
+ };
10
+ export type SessionStartContinuationOptions = {
11
+ readonly continuationPending: boolean;
12
+ readonly markContinuationPending: () => void;
13
+ /** The session opened in the middle of a limit wait, so this wake re-checks that limit (senpi#4085). */
14
+ readonly limitWait?: boolean;
15
+ };
16
+ /**
17
+ * Reads the goal continuations queued since the most recent real user message in the branch (mirrors the
18
+ * todo-gate / todo-bridge backward branch read). A real user message is the only reset: the assistant turns in
19
+ * between are exactly the unattended loop the load-time admission guard exists to stop.
20
+ *
21
+ * A continuation the provider refused for a usage or rate limit ran no model turn, so it is not part of that
22
+ * loop (senpi#4085). A goal that waited a limit out for more wakes than the cap is therefore not a flood, and it
23
+ * resumes after a restart or a config reload.
24
+ */
25
+ export declare function readTrailingGoalContinuations(entries: readonly SessionEntry[]): TrailingGoalContinuations;
26
+ /** Routes startup and resume continuations through the same verdict and delivery accounting as agent-end paths. */
27
+ export declare function queueGoalContinuation(pi: ExtensionAPI, ctx: ExtensionContext, goal: Goal, options: SessionStartContinuationOptions): Promise<Goal | null>;
28
+ //# sourceMappingURL=session-start-continuation.d.ts.map
@@ -0,0 +1,75 @@
1
+ import { GOAL_CONTINUATION_MESSAGE_TYPE } from "../../../messages.js";
2
+ import { admitAndQueueGoalContinuation, buildCurrentGoalContinuationSignature, isLastTurnStuckOnContextOverflow, lastAssistantText, } from "./lifecycle-helpers.js";
3
+ import { buildContinuationPrompt } from "./prompt.js";
4
+ import { isLimitRefusal } from "./provider-retry.js";
5
+ /**
6
+ * Reads the goal continuations queued since the most recent real user message in the branch (mirrors the
7
+ * todo-gate / todo-bridge backward branch read). A real user message is the only reset: the assistant turns in
8
+ * between are exactly the unattended loop the load-time admission guard exists to stop.
9
+ *
10
+ * A continuation the provider refused for a usage or rate limit ran no model turn, so it is not part of that
11
+ * loop (senpi#4085). A goal that waited a limit out for more wakes than the cap is therefore not a flood, and it
12
+ * resumes after a restart or a config reload.
13
+ */
14
+ export function readTrailingGoalContinuations(entries) {
15
+ let counted = 0;
16
+ let waitingOutLimit;
17
+ let turn = [];
18
+ for (let index = entries.length - 1; index >= 0; index--) {
19
+ const entry = entries[index];
20
+ if (entry?.type === "message") {
21
+ if (entry.message.role === "user")
22
+ break;
23
+ if (entry.message.role === "assistant")
24
+ turn.push(entry.message);
25
+ continue;
26
+ }
27
+ if (entry?.type !== "custom_message" || entry.customType !== GOAL_CONTINUATION_MESSAGE_TYPE)
28
+ continue;
29
+ const refused = wasRefusedForLimit(turn);
30
+ waitingOutLimit ??= refused;
31
+ if (!refused)
32
+ counted += 1;
33
+ turn = [];
34
+ }
35
+ return { counted, waitingOutLimit: waitingOutLimit ?? false };
36
+ }
37
+ function wasRefusedForLimit(turn) {
38
+ return (turn.length > 0 &&
39
+ turn.every((message) => message.stopReason === "error") &&
40
+ turn.some((message) => isLimitRefusal([message])));
41
+ }
42
+ /** Routes startup and resume continuations through the same verdict and delivery accounting as agent-end paths. */
43
+ export async function queueGoalContinuation(pi, ctx, goal, options) {
44
+ const lastAssistant = lastAssistantFromEntries(ctx.sessionManager.getBranch());
45
+ const signature = buildCurrentGoalContinuationSignature(ctx, goal, lastAssistant === undefined ? "" : lastAssistantText([lastAssistant]));
46
+ return admitAndQueueGoalContinuation(pi, ctx, goal, {
47
+ input: {
48
+ isIdle: ctx.isIdle(),
49
+ hasPendingMessages: ctx.hasPendingMessages(),
50
+ path: "sessionStart",
51
+ lastStopReason: undefined,
52
+ lastTurnWasMalformedToolUse: false,
53
+ consecutiveContinuations: goal.consecutiveContinuations ?? 0,
54
+ lastContinuationSignature: goal.lastContinuationSignature,
55
+ currentSignature: signature,
56
+ consecutiveLengthRecoveries: 0,
57
+ recentNormalizedOutputHashes: [],
58
+ toollessContinuationStreak: 0,
59
+ continuationPending: options.continuationPending,
60
+ lastTurnStuckOnContextOverflow: isLastTurnStuckOnContextOverflow(ctx, lastAssistant),
61
+ ...(options.limitWait === true ? { limitWait: true } : {}),
62
+ },
63
+ content: () => buildContinuationPrompt(goal, { modelId: ctx.model?.id }),
64
+ markContinuationPending: options.markContinuationPending,
65
+ });
66
+ }
67
+ function lastAssistantFromEntries(entries) {
68
+ for (let index = entries.length - 1; index >= 0; index--) {
69
+ const entry = entries[index];
70
+ if (entry?.type === "message" && entry.message.role === "assistant")
71
+ return entry.message;
72
+ }
73
+ return undefined;
74
+ }
75
+ //# sourceMappingURL=session-start-continuation.js.map
@@ -1,5 +1,5 @@
1
1
  import { goalFilePath } from "./persistence.ts";
2
- import type { Goal, GoalAccountingMode, GoalStoreRef, GoalUpdate, GoalUpdateSource, TokenUsageSnapshot } from "./types.ts";
2
+ import type { Goal, GoalAccountingMode, GoalContinuationStoppedDetail, GoalStoreRef, GoalUpdate, GoalUpdateSource, TokenUsageSnapshot } from "./types.ts";
3
3
  export { goalFilePath };
4
4
  export declare function goalHistoryFilePath(ref: GoalStoreRef): string;
5
5
  export declare function objectiveFullTextFileName(ref: GoalStoreRef): string;
@@ -15,13 +15,14 @@ export declare function clearGoal(ref: GoalStoreRef): Promise<boolean>;
15
15
  export declare function accountGoalUsage(ref: GoalStoreRef, usage: TokenUsageSnapshot, elapsedSeconds: number, mode?: GoalAccountingMode, expectedGoalId?: string): Promise<Goal | null>;
16
16
  export declare function recordContinuationDelivered(ref: GoalStoreRef, signature: string, expectedGoalId?: string, options?: {
17
17
  countUnattended?: boolean;
18
+ countStreak?: boolean;
18
19
  }): Promise<Goal | null>;
19
20
  export declare function resetContinuationStreak(ref: GoalStoreRef, options?: {
20
21
  unattended?: boolean;
21
22
  progress?: boolean;
22
23
  }): Promise<Goal | null>;
23
24
  /** Claims one denial under the same lock as input/reset, so evaluation retries cannot append another stop. */
24
- export declare function recordGoalContinuationStopped(ref: GoalStoreRef, expected: Goal, at: number, blockedReason: string | undefined): Promise<{
25
+ export declare function recordGoalContinuationStopped(ref: GoalStoreRef, expected: Goal, at: number, blockedReason: string | undefined, detail: GoalContinuationStoppedDetail): Promise<{
25
26
  goal: Goal | null;
26
27
  recorded: boolean;
27
28
  }>;
@@ -111,6 +111,7 @@ export async function updateGoal(ref, update, source = "model") {
111
111
  next.noProgressContinuations = 0;
112
112
  delete next.lastContinuationSignature;
113
113
  delete next.continuationStoppedAt;
114
+ delete next.continuationStoppedDetail;
114
115
  }
115
116
  if (source === "user" && requestedStatus === "active") {
116
117
  next.consecutiveContinuations = 0;
@@ -118,6 +119,7 @@ export async function updateGoal(ref, update, source = "model") {
118
119
  next.noProgressContinuations = 0;
119
120
  delete next.lastContinuationSignature;
120
121
  delete next.continuationStoppedAt;
122
+ delete next.continuationStoppedDetail;
121
123
  next.lastStartedAt ??= nowSeconds();
122
124
  }
123
125
  if (tokenBudget === undefined)
@@ -175,12 +177,13 @@ export async function recordContinuationDelivered(ref, signature, expectedGoalId
175
177
  const counted = options.countUnattended === false ? 0 : 1;
176
178
  const next = {
177
179
  ...goal,
178
- consecutiveContinuations: (goal.consecutiveContinuations ?? 0) + 1,
180
+ consecutiveContinuations: (goal.consecutiveContinuations ?? 0) + (options.countStreak === false ? 0 : 1),
179
181
  unattendedContinuations: (goal.unattendedContinuations ?? 0) + counted,
180
182
  noProgressContinuations: (goal.noProgressContinuations ?? 0) + counted,
181
183
  lastContinuationSignature: signature,
182
184
  };
183
185
  delete next.continuationStoppedAt;
186
+ delete next.continuationStoppedDetail;
184
187
  if (next.status === "active")
185
188
  next.lastStartedAt ??= nowSeconds();
186
189
  await held.write(next);
@@ -199,6 +202,7 @@ export async function resetContinuationStreak(ref, options = {}) {
199
202
  next.noProgressContinuations = 0;
200
203
  delete next.lastContinuationSignature;
201
204
  delete next.continuationStoppedAt;
205
+ delete next.continuationStoppedDetail;
202
206
  if (options.unattended === true && next.status === "active")
203
207
  next.lastStartedAt ??= nowSeconds();
204
208
  await held.write(next);
@@ -206,7 +210,7 @@ export async function resetContinuationStreak(ref, options = {}) {
206
210
  });
207
211
  }
208
212
  /** Claims one denial under the same lock as input/reset, so evaluation retries cannot append another stop. */
209
- export async function recordGoalContinuationStopped(ref, expected, at, blockedReason) {
213
+ export async function recordGoalContinuationStopped(ref, expected, at, blockedReason, detail) {
210
214
  return withGoalFileLock(ref, async (held) => {
211
215
  const current = await readGoalFile(ref);
212
216
  if (current?.id !== expected.id ||
@@ -230,6 +234,9 @@ export async function recordGoalContinuationStopped(ref, expected, at, blockedRe
230
234
  }
231
235
  delete next.lastStartedAt;
232
236
  next.continuationStoppedAt = at;
237
+ // The claim alone cannot rebuild the session entries; keep the denied payload with it so a later
238
+ // session start can re-append exactly the entries a crash swallowed (senpi#3015).
239
+ next.continuationStoppedDetail = detail;
233
240
  await held.write(next);
234
241
  return { goal: next, recorded: true };
235
242
  });
@@ -8,6 +8,13 @@ export type GoalStoreRef = {
8
8
  };
9
9
  export type GoalAccountingMode = "active" | "activeOrBlocked" | "activeOrComplete";
10
10
  export type GoalUpdateSource = "model" | "user";
11
+ /** Deny reason + counters captured with a stop claim, so a later session start can re-append the stop entries if the process died between the claim and the append (senpi#3015). */
12
+ export type GoalContinuationStoppedDetail = {
13
+ reason: "cap" | "stale" | "repetition" | "length-exhausted" | "no-progress" | "context-overflow";
14
+ consecutiveContinuations: number;
15
+ unattendedContinuations: number;
16
+ noProgressContinuations: number;
17
+ };
11
18
  export type Goal = {
12
19
  id: string;
13
20
  threadId: string;
@@ -22,6 +29,8 @@ export type Goal = {
22
29
  lastContinuationSignature?: string;
23
30
  /** Durable claim for a stopped continuation; cleared by input, resume, or a delivered continuation. */
24
31
  continuationStoppedAt?: number;
32
+ /** Stop-publication payload captured with the claim; cleared together with it. */
33
+ continuationStoppedDetail?: GoalContinuationStoppedDetail;
25
34
  createdAt: number;
26
35
  updatedAt: number;
27
36
  lastStartedAt?: number;
@@ -5,6 +5,7 @@ import { readGoal } from "../goal/store.js";
5
5
  import { goalStoreRef } from "../goal/store-ref.js";
6
6
  import { buildHandoffBrief } from "./brief.js";
7
7
  import { estimateHandoff, hintText } from "./estimate.js";
8
+ import { createReplacementRelay } from "./replacement-relay.js";
8
9
  export const HANDOFF_HINT_ENTRY_TYPE = "handoff-hint";
9
10
  export const HANDOFF_BRIEF_MESSAGE_TYPE = "handoff-brief";
10
11
  export const HANDOFF_HINT_EVENT = "handoff_hint";
@@ -51,6 +52,7 @@ const readActiveGoal = (ctx) => readGoal(goalStoreRef(ctx.sessionManager, ctx.cw
51
52
  export function createHandoffHintExtension(dependencies = {}) {
52
53
  const now = dependencies.now ?? Date.now;
53
54
  const goalOf = dependencies.readGoal ?? readActiveGoal;
55
+ const acceptances = createReplacementRelay();
54
56
  return (pi) => {
55
57
  let ctx;
56
58
  let timer;
@@ -168,22 +170,21 @@ export function createHandoffHintExtension(dependencies = {}) {
168
170
  previousSessionFile: current.sessionManager.getSessionFile(),
169
171
  contextTokens: usage?.tokens ?? estimate?.contextTokens ?? 0,
170
172
  });
171
- const accepted = estimate === undefined ? undefined : hintEvent("accepted", current, estimate);
172
173
  const sessionId = current.sessionManager.getSessionId();
174
+ const previousSessionFile = current.sessionManager.getSessionFile();
175
+ // The replacement's session_start emits it through the new generation's pi (senpi#4051).
176
+ if (estimate !== undefined)
177
+ acceptances.hold(previousSessionFile, hintEvent("accepted", current, estimate));
173
178
  hide(current);
174
- const result = await current.newSession({
175
- parentSession: current.sessionManager.getSessionFile(),
179
+ const switching = current.newSession({
180
+ parentSession: previousSessionFile,
176
181
  withSession: async (next) => {
177
182
  await next.sendMessage({ customType: HANDOFF_BRIEF_MESSAGE_TYPE, content: brief, display: true }, { triggerTurn: false });
178
183
  },
179
184
  });
180
- if (result.cancelled) {
181
- if (estimate !== undefined && current.sessionManager.getSessionId() === sessionId)
182
- render(current, estimate);
183
- return;
184
- }
185
- if (accepted !== undefined)
186
- pi.events?.emit(HANDOFF_HINT_EVENT, accepted);
185
+ const result = await switching.finally(() => acceptances.release(previousSessionFile));
186
+ if (result.cancelled && estimate !== undefined && current.sessionManager.getSessionId() === sessionId)
187
+ render(current, estimate);
187
188
  }
188
189
  pi.registerCommand(HANDOFF_COMMAND, {
189
190
  description: "Start a fresh session seeded with a short handoff brief (`/handoff keep` hides the hint)",
@@ -207,8 +208,11 @@ export function createHandoffHintExtension(dependencies = {}) {
207
208
  await startFresh(current);
208
209
  },
209
210
  });
210
- pi.on("session_start", (_event, next) => {
211
+ pi.on("session_start", (event, next) => {
211
212
  ctx = next;
213
+ const accepted = event.reason === "new" ? acceptances.take(event.previousSessionFile) : undefined;
214
+ if (accepted !== undefined)
215
+ pi.events?.emit(HANDOFF_HINT_EVENT, accepted);
212
216
  disarm();
213
217
  visible = undefined;
214
218
  shownThisCrossing = false;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Carries a value from a session to the session that replaces it (senpi#4051).
3
+ *
4
+ * `newSession()` retires the old extension generation before the replacement starts, so a captured `pi` cannot emit
5
+ * after the switch, and emitting before it would report a switch that `session_before_switch` may still cancel. The
6
+ * builtin factory outlives both generations in one process, so the value waits here, keyed by the session being
7
+ * replaced, until the replacement's `session_start` takes it through its own live `pi`. The holder releases its key
8
+ * once `newSession()` settles, so a cancelled or failed switch delivers nothing.
9
+ */
10
+ export interface ReplacementRelay<T> {
11
+ hold(previousSessionFile: string | undefined, value: T): void;
12
+ take(previousSessionFile: string | undefined): T | undefined;
13
+ release(previousSessionFile: string | undefined): void;
14
+ }
15
+ export declare function createReplacementRelay<T>(): ReplacementRelay<T>;
16
+ //# sourceMappingURL=replacement-relay.d.ts.map
@@ -0,0 +1,17 @@
1
+ export function createReplacementRelay() {
2
+ const pending = new Map();
3
+ return {
4
+ hold: (previousSessionFile, value) => {
5
+ pending.set(previousSessionFile, value);
6
+ },
7
+ take: (previousSessionFile) => {
8
+ const value = pending.get(previousSessionFile);
9
+ pending.delete(previousSessionFile);
10
+ return value;
11
+ },
12
+ release: (previousSessionFile) => {
13
+ pending.delete(previousSessionFile);
14
+ },
15
+ };
16
+ }
17
+ //# sourceMappingURL=replacement-relay.js.map
@@ -1,6 +1,7 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { Type } from "typebox";
4
+ import { resolvePromptSurface } from "../../../dynamic-prompt/build.js";
4
5
  import { defineTool } from "../../types.js";
5
6
  import { injectHtmlRenderBootstrap } from "./bootstrap.js";
6
7
  import { inlineLocalImages } from "./images.js";
@@ -36,6 +37,13 @@ function assertWithinInputLimit(html) {
36
37
  throw new Error(`The page is ${html.length} characters; show_html_page accepts at most ${MAX_HTML_CHARACTERS} characters.${advice}`);
37
38
  }
38
39
  const clampHeight = (height) => height === undefined ? undefined : Math.min(2000, Math.max(80, Math.round(height)));
40
+ /**
41
+ * Whether this session's host shows the page. The OmO desktop opens every session it renders with
42
+ * `open_session.promptSurface: "app"` and publishes a completed call from its details; every other
43
+ * surface (terminal, chat bridge, or none named) only gets the file. The result states which happened,
44
+ * because the model trusts it in later turns too (senpi#6273).
45
+ */
46
+ const hostRendersPage = (ctx) => (ctx.getSystemPromptOptions?.().surface ?? resolvePromptSurface(process.env)) === "app";
39
47
  export const showHtmlPageTool = defineTool({
40
48
  name: "show_html_page",
41
49
  label: "Show HTML Page",
@@ -46,8 +54,8 @@ export const showHtmlPageTool = defineTool({
46
54
  "Local images written as absolute file paths " +
47
55
  "are inlined automatically after a magic-byte check (a renamed non-image is refused); pass screenshots " +
48
56
  "that way, never as base64 data: URIs, since the page is limited to 512000 characters before images are " +
49
- "inlined. In the desktop thread this renders inline above your " +
50
- "reply; elsewhere it is written to a file you can open in the desktop.",
57
+ "inlined. In the OmO desktop thread the page renders inline above your reply; elsewhere it is only " +
58
+ "saved to a file and not displayed. The result says which happened.",
51
59
  exposure: "search",
52
60
  allowLazyActivation: true,
53
61
  searchKeywords: ["html", "chart", "table", "diagram", "visualize", "render page", "mockup"],
@@ -71,7 +79,9 @@ export const showHtmlPageTool = defineTool({
71
79
  content: [
72
80
  {
73
81
  type: "text",
74
- text: `Wrote the page to ${path}. Open it in the desktop to see it rendered with your theme. ` +
82
+ text: (hostRendersPage(ctx)
83
+ ? `The page was shown to the reader inline above your reply. A copy is saved at ${path}. `
84
+ : `Wrote the page to ${path}. It was not displayed in this interface. `) +
75
85
  (missing === undefined
76
86
  ? "Local images were inlined."
77
87
  : `Some local images could not be read and were left as written: ${missing.join(", ")}.`),
@@ -12,7 +12,7 @@ How to get the image right the first time, and how to fix it fast when it is not
12
12
  When image generation tooling is present in your tool set, pick the surface that actually exists right now:
13
13
 
14
14
  - If a native `image_generation` server tool is available, use it. The provider runs generation server-side and returns the image in the response stream.
15
- - Otherwise, call the `generate_image` tool. It sends your prompt and optional reference images to the configured OpenAI-compatible endpoint and saves the result as a png, jpeg, or webp file. The model, quality, size, background, and format controls below apply to `generate_image`, not to the native server tool.
15
+ - Otherwise, call `generate_image`. It is eval-only, like `bash`: when an `eval` tool exists it is not in your direct tool list, so run it inside an eval cell as `tool.generate_image({ prompt: "..." })` (js) or `tool.generate_image(prompt="...")` (py); `tool_schema("generate_image")` lists every parameter. Without an eval tool it is an ordinary direct tool. It sends your prompt and optional reference images to the configured OpenAI-compatible endpoint and saves the result as a png, jpeg, or webp file. The model, quality, size, background, and format controls below apply to `generate_image`, not to the native server tool.
16
16
 
17
17
  Check your current tool set before choosing. Tool state can change mid-session (model switch, credential change), so trust the tools you can see over what this page said at startup. If both surfaces ever appear, prefer the native server tool. When the request is clear, generate directly instead of asking for confirmation; ask only when a required reference image is missing.
18
18
 
@@ -22,20 +22,10 @@ export const generateImageTool = defineTool({
22
22
  label: "Generate Image",
23
23
  description: "Generate or edit an image with OpenAI gpt-image-2.6 (Sunburst by default, the most capable; Flare when speed matters more than quality; an endpoint without 2.6 falls back to 2.5) and save it as a png, jpeg, or webp file, optionally with a transparent background. Pass reference_image_paths to edit or reference existing images. Generate directly when the request is clear instead of asking for confirmation. Returns the saved file paths.",
24
24
  promptSnippet: "Generate or edit images from prompts and optional reference images, saving them as png/jpeg/webp files.",
25
- // Deferred: the schema costs ~1K prompt tokens on every request and the bundled imagegen skill
26
- // names the tool, so a by-name call activates it on first use instead of shipping it resident.
27
- exposure: "search",
28
- searchGroup: "imagegen",
29
- searchKeywords: [
30
- "generate image",
31
- "image generation",
32
- "edit image",
33
- "create a picture",
34
- "illustration",
35
- "mockup",
36
- "gpt-image",
37
- "transparent png",
38
- ],
25
+ // Eval-only, like bash (senpi#6269): whenever an eval tool is registered the model never sees
26
+ // this ~1K-token schema and calls `tool.generate_image({ prompt: "..." })` inside a cell instead;
27
+ // without an eval tool it is an ordinary direct tool. The bundled imagegen skill explains the call.
28
+ exposure: "eval",
39
29
  parameters: Params,
40
30
  async execute(toolCallId, params, signal, _onUpdate, ctx) {
41
31
  const size = params.size ?? "auto";