@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,6 +1,29 @@
1
1
  import { longContextAvailable } from "./long-context.js";
2
2
  /** Claude Code's default window when a request does not ask for the long context. */
3
3
  export const CLAUDE_CODE_DEFAULT_CONTEXT_WINDOW = 200_000;
4
+ /** The most of a request's output budget Claude Code holds back from its window (`zoo` in Claude Code 2.1.321). */
5
+ const CLAUDE_CODE_OUTPUT_RESERVE_CAP = 20_000;
6
+ /** The margin Claude Code keeps below its reserved window before it refuses a request (`Woo` in Claude Code 2.1.321). */
7
+ const CLAUDE_CODE_BLOCKING_BUFFER = 3_000;
8
+ /**
9
+ * The `CLAUDE_CODE_MAX_CONTEXT_TOKENS` that makes Claude Code accept a conversation up to `window`.
10
+ *
11
+ * With `DISABLE_COMPACT` set, Claude Code takes that variable as the model's window, then refuses a
12
+ * request on its own, with no API call, once the conversation passes the window minus its output reserve
13
+ * (the request's output budget, capped at 20k) minus a 3k buffer. Handing it the bare planned window put
14
+ * that limit 23k below the window senpi compacts against, so every turn between the two failed in
15
+ * milliseconds with "Prompt is too long", and so did the summarization request of the recovery
16
+ * compaction. senpi keeps its own output reserve below `window`, so the variable carries the window plus
17
+ * Claude Code's reserve and Claude Code's limit lands on `window` itself.
18
+ *
19
+ * `outputTokens` is the output budget Claude Code reserves for the request. Without an explicit
20
+ * `CLAUDE_CODE_MAX_OUTPUT_TOKENS` it is the model's own default, which is at least the cap for every
21
+ * Claude model, so the cap is the default here; a larger addition only lets a request reach the API's own
22
+ * window check, whose error senpi's overflow recovery already parses.
23
+ */
24
+ export function claudeCodeMaxContextTokens(window, outputTokens = CLAUDE_CODE_OUTPUT_RESERVE_CAP) {
25
+ return window + Math.min(outputTokens, CLAUDE_CODE_OUTPUT_RESERVE_CAP) + CLAUDE_CODE_BLOCKING_BUFFER;
26
+ }
4
27
  /**
5
28
  * Models whose 1M window is reachable only through Claude Code's `[1m]` opt-in, which sends the legacy
6
29
  * `context-1m-2025-08-07` beta that a subscription without extra usage refuses. These are the rows of
@@ -10,6 +10,7 @@ export type SdkErrorClassification = {
10
10
  /** Set on an auth failure that proves the login itself is gone, so the account needs a new sign-in (senpi#5350). */
11
11
  signInRequired?: "login_expired";
12
12
  };
13
+ export declare function errorText(error: unknown): string;
13
14
  export declare function isExtraUsageExhausted(error: unknown): boolean;
14
15
  export type SdkResultUsage = Extract<SDKMessage, {
15
16
  type: "result";
@@ -21,21 +21,30 @@ function record(value) {
21
21
  ? value
22
22
  : undefined;
23
23
  }
24
- function errorText(error) {
24
+ /** `String()` throws for a null-prototype object or a throwing `toString`; the classifiers must never throw. */
25
+ function safeString(value) {
26
+ try {
27
+ return String(value);
28
+ }
29
+ catch {
30
+ return Object.prototype.toString.call(value);
31
+ }
32
+ }
33
+ export function errorText(error) {
25
34
  if (typeof error === "string")
26
35
  return error;
27
- if (error instanceof Error)
28
- return error.message;
29
36
  const value = record(error);
30
37
  if (!value)
31
- return String(error);
38
+ return safeString(error);
32
39
  const message = value.message;
33
40
  if (typeof message === "string")
34
41
  return message;
42
+ if (error instanceof Error)
43
+ return safeString(message);
35
44
  const code = value.error;
36
45
  if (typeof code === "string")
37
46
  return code;
38
- return String(error);
47
+ return safeString(error);
39
48
  }
40
49
  export function isExtraUsageExhausted(error) {
41
50
  return EXTRA_USAGE_EXHAUSTED.test(errorText(error).toLowerCase());
@@ -306,17 +306,17 @@ export function createOAuthConfig(deps) {
306
306
  cancelled();
307
307
  callbacks.onProgress?.(check.outcome === "ok"
308
308
  ? `Connection test passed on ${model}`
309
- : `Connection test failed on ${model} (${check.reason ?? "unknown"}); the account was kept. Retry from the account list.`);
309
+ : `Connection test failed on ${model}: ${check.status === undefined ? "" : `HTTP ${check.status}: `}${check.message ?? check.reason ?? "No provider response was retained"}; the account was kept. Retry from the account list.`);
310
310
  // The slot stays written: the caller's commit merges onto it, so the account owns its dir throughout.
311
311
  let tested = slot;
312
312
  const saved = await credentials.modify(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, async (stored) => {
313
313
  const base = storedSubscription(stored) ?? withSlot(current, slot);
314
314
  tested = base.accounts?.find((candidate) => candidate.name === name) ?? slot;
315
- return withCheck(base, name, check);
315
+ return withCheck(base, name, check, dirId);
316
316
  });
317
317
  if (callbacks.signal !== undefined)
318
318
  recordClaudeCliLoginOutcome(callbacks.signal, { account: { name }, check });
319
- signedInCredential = withCheck(withSlot(storedSubscription(saved) ?? current, tested), name, check);
319
+ signedInCredential = withCheck(withSlot(storedSubscription(saved) ?? current, tested), name, check, dirId);
320
320
  }
321
321
  catch (error) {
322
322
  try {
@@ -11,11 +11,17 @@ type QueryEnvironment = {
11
11
  * Pin Claude Code's prompt-cache lifetime for a one-shot query and return it, so senpi's history breakpoint uses the
12
12
  * same lifetime (senpi#2982). The Messages API rejects a 1-hour breakpoint that follows a 5-minute one, and Claude Code
13
13
  * otherwise picks its lifetime per request: 1 hour on a subscription within its usage limits, 5 minutes on an API key
14
- * or after the limits. `FORCE_PROMPT_CACHING_5M` wins, as it does in Claude Code; then an explicit
15
- * `CLAUDE_CODE_PROMPT_CACHE_TTL`. The managed subscription lanes pin 1 hour,
14
+ * or after the limits. `FORCE_PROMPT_CACHING_5M` wins, as it does in Claude Code; then a lifetime from a loaded
15
+ * settings file. The managed subscription lanes pin 1 hour,
16
16
  * Claude Code's subscription default; so does the `claude-cli` lane, whose account is a verified first-party claude.ai login. The ambient lane can be an API key or a subscription login, so nothing is
17
- * pinned there and the caller adds no breakpoint.
17
+ * pinned there unless a lifetime is set explicitly — and a lifetime set in a loaded settings file counts as explicit
18
+ * (senpi#2988): Claude Code applies the `env` block of the settings files it loads, so `settingsEnv` carries the
19
+ * effective settings env for the sources this attempt loads. Precedence is settings env, then explicit env, then the
20
+ * lane default: Claude Code writes each settings source's env over the process env, and launch-env keys are only
21
+ * protected when the session is host-orchestrated (a desktop host, or `CLAUDE_CODE_ENVIRONMENT_KIND=byoc`); an SDK
22
+ * spawn is neither, so a settings value wins over the process env and over `options.env`, and a settings value
23
+ * wins over senpi's assumption about Claude Code's default.
18
24
  */
19
- export declare function pinOneShotPromptCacheTtl(options: QueryEnvironment, authLane: AnthropicSubscriptionAuthLane, environment: Readonly<Record<string, string | undefined>>): PromptCacheTtl | undefined;
25
+ export declare function pinOneShotPromptCacheTtl(options: QueryEnvironment, authLane: AnthropicSubscriptionAuthLane, environment: Readonly<Record<string, string | undefined>>, settingsEnv?: Readonly<Record<string, string | undefined>>): PromptCacheTtl | undefined;
20
26
  export {};
21
27
  //# sourceMappingURL=prompt-cache-ttl.d.ts.map
@@ -11,14 +11,23 @@ function parsePromptCacheTtl(value) {
11
11
  * Pin Claude Code's prompt-cache lifetime for a one-shot query and return it, so senpi's history breakpoint uses the
12
12
  * same lifetime (senpi#2982). The Messages API rejects a 1-hour breakpoint that follows a 5-minute one, and Claude Code
13
13
  * otherwise picks its lifetime per request: 1 hour on a subscription within its usage limits, 5 minutes on an API key
14
- * or after the limits. `FORCE_PROMPT_CACHING_5M` wins, as it does in Claude Code; then an explicit
15
- * `CLAUDE_CODE_PROMPT_CACHE_TTL`. The managed subscription lanes pin 1 hour,
14
+ * or after the limits. `FORCE_PROMPT_CACHING_5M` wins, as it does in Claude Code; then a lifetime from a loaded
15
+ * settings file. The managed subscription lanes pin 1 hour,
16
16
  * Claude Code's subscription default; so does the `claude-cli` lane, whose account is a verified first-party claude.ai login. The ambient lane can be an API key or a subscription login, so nothing is
17
- * pinned there and the caller adds no breakpoint.
17
+ * pinned there unless a lifetime is set explicitly — and a lifetime set in a loaded settings file counts as explicit
18
+ * (senpi#2988): Claude Code applies the `env` block of the settings files it loads, so `settingsEnv` carries the
19
+ * effective settings env for the sources this attempt loads. Precedence is settings env, then explicit env, then the
20
+ * lane default: Claude Code writes each settings source's env over the process env, and launch-env keys are only
21
+ * protected when the session is host-orchestrated (a desktop host, or `CLAUDE_CODE_ENVIRONMENT_KIND=byoc`); an SDK
22
+ * spawn is neither, so a settings value wins over the process env and over `options.env`, and a settings value
23
+ * wins over senpi's assumption about Claude Code's default.
18
24
  */
19
- export function pinOneShotPromptCacheTtl(options, authLane, environment) {
20
- const forced5m = isTruthy(options.env?.[FORCE_PROMPT_CACHING_5M_ENV]) || isTruthy(environment[FORCE_PROMPT_CACHING_5M_ENV]);
25
+ export function pinOneShotPromptCacheTtl(options, authLane, environment, settingsEnv) {
26
+ const forced5m = isTruthy(options.env?.[FORCE_PROMPT_CACHING_5M_ENV]) ||
27
+ isTruthy(environment[FORCE_PROMPT_CACHING_5M_ENV]) ||
28
+ isTruthy(settingsEnv?.[FORCE_PROMPT_CACHING_5M_ENV]);
21
29
  const ttl = (forced5m ? "5m" : undefined) ??
30
+ parsePromptCacheTtl(settingsEnv?.[PROMPT_CACHE_TTL_ENV]) ??
22
31
  parsePromptCacheTtl(options.env?.[PROMPT_CACHE_TTL_ENV]) ??
23
32
  parsePromptCacheTtl(environment[PROMPT_CACHE_TTL_ENV]) ??
24
33
  (authLane === "ambient" ? undefined : "1h");
@@ -5,8 +5,9 @@ import type { Api, Model } from "@earendil-works/pi-ai";
5
5
  *
6
6
  * - Compaction (senpi owner): Claude Code's per-turn token-budget reminder changes the prompt every turn and defeats
7
7
  * prompt-cache reuse; senpi's compaction carries its own budget reminder. With DISABLE_COMPACT set, Claude Code takes
8
- * CLAUDE_CODE_MAX_CONTEXT_TOKENS over every model rule of its own, so it plans the window senpi plans (senpi#3801),
9
- * and its native compaction stays off.
8
+ * CLAUDE_CODE_MAX_CONTEXT_TOKENS over every model rule of its own (senpi#3801) and its native compaction stays off.
9
+ * The variable carries the planned window plus the output reserve Claude Code holds back from it, so Claude Code
10
+ * refuses a request only past the window senpi plans, not 23k below the window senpi compacts against.
10
11
  * - Base URL: the registration baseUrl is the api-id sentinel. Any other value is the user's models.json baseUrl
11
12
  * (senpi#4765); Claude Code sends its requests to `ANTHROPIC_BASE_URL` and keeps the selected login as the credential.
12
13
  */
@@ -1,13 +1,14 @@
1
1
  import { CLAUDE_SDK_OAUTH_API_ID } from "./api-id.js";
2
- import { plannedContextWindow } from "./context-window.js";
2
+ import { claudeCodeMaxContextTokens, plannedContextWindow } from "./context-window.js";
3
3
  /**
4
4
  * Variables the lane layers over the Claude Code subprocess environment the auth lane builds; they win over the
5
5
  * host's, including the managed lanes' stripped `ANTHROPIC_BASE_URL`.
6
6
  *
7
7
  * - Compaction (senpi owner): Claude Code's per-turn token-budget reminder changes the prompt every turn and defeats
8
8
  * prompt-cache reuse; senpi's compaction carries its own budget reminder. With DISABLE_COMPACT set, Claude Code takes
9
- * CLAUDE_CODE_MAX_CONTEXT_TOKENS over every model rule of its own, so it plans the window senpi plans (senpi#3801),
10
- * and its native compaction stays off.
9
+ * CLAUDE_CODE_MAX_CONTEXT_TOKENS over every model rule of its own (senpi#3801) and its native compaction stays off.
10
+ * The variable carries the planned window plus the output reserve Claude Code holds back from it, so Claude Code
11
+ * refuses a request only past the window senpi plans, not 23k below the window senpi compacts against.
11
12
  * - Base URL: the registration baseUrl is the api-id sentinel. Any other value is the user's models.json baseUrl
12
13
  * (senpi#4765); Claude Code sends its requests to `ANTHROPIC_BASE_URL` and keeps the selected login as the credential.
13
14
  */
@@ -15,7 +16,7 @@ export function claudeCodeQueryEnvironment(model, senpiOwnsCompaction) {
15
16
  const environment = {};
16
17
  if (senpiOwnsCompaction) {
17
18
  environment.CLAUDE_CODE_TOTAL_TOKENS_REMINDER = "off";
18
- environment.CLAUDE_CODE_MAX_CONTEXT_TOKENS = String(plannedContextWindow(model));
19
+ environment.CLAUDE_CODE_MAX_CONTEXT_TOKENS = String(claudeCodeMaxContextTokens(plannedContextWindow(model)));
19
20
  environment.DISABLE_COMPACT = "1";
20
21
  }
21
22
  if (model.baseUrl !== CLAUDE_SDK_OAUTH_API_ID)
@@ -20,11 +20,12 @@
20
20
  * (`packages/ai/src/api/*.lazy.ts`); `test/startup-import-graph.test.ts` fails
21
21
  * if a static edge to the SDK reappears.
22
22
  */
23
- import type { createSdkMcpServer, getSessionMessages, query } from "@anthropic-ai/claude-agent-sdk";
23
+ import type { createSdkMcpServer, getSessionMessages, query, resolveSettings } from "@anthropic-ai/claude-agent-sdk";
24
24
  export type ClaudeAgentSdkModule = {
25
25
  query: typeof query;
26
26
  createSdkMcpServer: typeof createSdkMcpServer;
27
27
  getSessionMessages: typeof getSessionMessages;
28
+ resolveSettings: typeof resolveSettings;
28
29
  };
29
30
  /**
30
31
  * Loads the SDK once and caches it. Concurrent callers share the in-flight
@@ -13,6 +13,7 @@ export function loadClaudeAgentSdk() {
13
13
  query: module.query,
14
14
  createSdkMcpServer: module.createSdkMcpServer,
15
15
  getSessionMessages: module.getSessionMessages,
16
+ resolveSettings: module.resolveSettings,
16
17
  };
17
18
  return loaded;
18
19
  })
@@ -5,6 +5,7 @@ import { queryWithAuthLane } from "./auth-lane.js";
5
5
  import { markColdSeedOverflow } from "./cold-seed-budget.js";
6
6
  import { CLAUDE_CODE_DEFAULT_CONTEXT_WINDOW } from "./context-window.js";
7
7
  import { buildCustomToolServers } from "./custom-tools.js";
8
+ import { resolveQuerySettingsEnv } from "./claude-settings-env.js";
8
9
  import { sdkAssistantFailure, sdkResultFailure, sdkResultFailureUsage } from "./errors.js";
9
10
  import { defaultExecutableDeps, resolveClaudeCodeRun } from "./executable.js";
10
11
  import { estimateLongContextFallbackTokens } from "./long-context-budget.js";
@@ -79,6 +80,10 @@ export function streamAnthropicSubscription(model, context, options) {
79
80
  toolWatch.reconcileWithContext(sessionKey, context);
80
81
  const toolWatchNote = toolWatch.buildPromptNote(sessionKey, context, resolvedTools.customToolNameToSdk);
81
82
  const providerSettings = loadAnthropicSubscriptionProviderSettingsFromDisk(cwd);
83
+ // The effective `env` block of the settings files this stream's queries load (senpi#2988 item 4): a
84
+ // lifetime set there is what Claude Code will actually use, so the pin must see it. Resolved once per
85
+ // stream; the pin only receives it for attempts whose settingSources are non-empty.
86
+ const settingsEnv = await resolveQuerySettingsEnv(cwd, providerSettings);
82
87
  const senpiOwnsCompaction = resolveCompactionOwner(providerSettings) === "senpi";
83
88
  const toolLessRequest = options?.toolChoice === "none";
84
89
  const mcpServers = toolLessRequest ? undefined : await buildCustomToolServers(resolvedTools.customTools);
@@ -107,7 +112,12 @@ export function streamAnthropicSubscription(model, context, options) {
107
112
  queryOptions.mcpServers = mcpServers;
108
113
  // The one-shot prompt is built after this, for the lane this attempt resolved (senpi#2982).
109
114
  if (!useResidentSession) {
110
- oneShotCacheTtl = pinOneShotPromptCacheTtl(queryOptions, authLane ?? providerSettings.tokenInjection ?? "ambient", { ...process.env, ...options?.env });
115
+ oneShotCacheTtl = pinOneShotPromptCacheTtl(queryOptions, authLane ?? providerSettings.tokenInjection ?? "ambient", { ...process.env, ...options?.env },
116
+ // Only the settings files this attempt loads can set its lifetime: managed lanes load
117
+ // none, so nothing is passed there (senpi#2988).
118
+ queryOptions.settingSources !== undefined && queryOptions.settingSources.length > 0
119
+ ? settingsEnv
120
+ : undefined);
111
121
  }
112
122
  return queryOptions;
113
123
  };
@@ -7,8 +7,12 @@ export function groupChangedPaths(paths, targets, rearmedContainers) {
7
7
  if (!targetMatchesPath(activeTarget.target, path))
8
8
  continue;
9
9
  matched = true;
10
- // The project container only rearms discovery; newly discovered config files carry the change.
11
- if (activeTarget.target.id === "builtin-project-presence" && rearmedContainers.includes(resolve(path)))
10
+ // Discovery containers only rearm their watches; discovered files carry the change.
11
+ // An extensions directory may contain only runtime state (e.g. a park's goal/no-session).
12
+ if (rearmedContainers.includes(resolve(path)) &&
13
+ (activeTarget.target.id === "builtin-project-presence" ||
14
+ activeTarget.target.id === "builtin-global-extensions" ||
15
+ activeTarget.target.id === "builtin-project-extensions"))
12
16
  continue;
13
17
  const group = groups.get(activeTarget.registrationId) ?? [];
14
18
  if (!group.includes(path))
@@ -33,6 +33,12 @@ export type GoalContinuationInput = {
33
33
  readonly continuationPending: boolean;
34
34
  /** The last turn was rejected (or silently starved) by context size and recovery did not shrink it. */
35
35
  readonly lastTurnStuckOnContextOverflow: boolean;
36
+ /**
37
+ * The wake re-checks a provider that refused for a usage or rate limit, either on the armed provider retry or as
38
+ * the first wake of a session opened in the middle of that wait. Waiting out a limit is not a stall, so it spends
39
+ * neither the continuation cap nor the no-progress budget (senpi#4085).
40
+ */
41
+ readonly limitWait?: boolean;
36
42
  };
37
43
  export type GoalContinuationVerdict = {
38
44
  kind: "continue";
@@ -67,9 +67,10 @@ export function evaluateGoalContinuation(input) {
67
67
  return { kind: "deny", reason: "single-flight" };
68
68
  if (hasRepeatedNormalizedOutputHash(input.recentNormalizedOutputHashes))
69
69
  return { kind: "deny", reason: "repetition" };
70
- if (isNoProgressBudgetExhausted(input))
70
+ const limitWait = (input.path === "providerRecovery" || input.path === "sessionStart") && input.limitWait === true;
71
+ if (!limitWait && isNoProgressBudgetExhausted(input))
71
72
  return { kind: "deny", reason: "no-progress" };
72
- if (input.consecutiveContinuations >= GOAL_CONTINUATION_CAP) {
73
+ if (!limitWait && input.consecutiveContinuations >= GOAL_CONTINUATION_CAP) {
73
74
  return { kind: "deny", reason: "cap" };
74
75
  }
75
76
  if ((input.path === "immediate" || input.path === "userGrace") &&
@@ -1,5 +1,4 @@
1
1
  import { MANUAL_CONTINUE_CUSTOM_TYPE } from "../../../manual-continue.js";
2
- import { GOAL_CONTINUATION_MESSAGE_TYPE } from "../../../messages.js";
3
2
  import { buildDeadlineStopNotice, deadlineStopPendingSinceLastTurn, TURN_DEADLINE_ENTRY_TYPE, } from "../../../turn-deadline.js";
4
3
  import { noticeEntryRenderer } from "../../notice/index.js";
5
4
  import { continueGoalAfterAgentEnd } from "./agent-end-continuation.js";
@@ -11,10 +10,11 @@ import { GoalDirectInputLifecycle } from "./direct-input-lifecycle.js";
11
10
  import { GoalElapsedTicker } from "./elapsed-ticker.js";
12
11
  import { formatGoalForTool, goalStatusLabel } from "./format.js";
13
12
  import { goalLifecycleChanged, registerGoalLifecycleCollector } from "./lifecycle-facts.js";
14
- import { GOAL_STALE_STOP_NOTICE, isResumeOfStoppedGoal, queueGoalContinuation } from "./lifecycle-helpers.js";
13
+ import { GOAL_STALE_STOP_NOTICE, isResumeOfStoppedGoal, recoverMissingGoalContinuationStop, } from "./lifecycle-helpers.js";
15
14
  import { GOAL_CONTINUATION_SCHEDULED_EVENT, MonitorAwareGoalContinuation } from "./monitor-continuation.js";
16
15
  import { reengageGoalAfterReload } from "./reload-reengagement.js";
17
16
  import { registerGoalFireNowRequest } from "./rpc-wait-surface.js";
17
+ import { queueGoalContinuation, readTrailingGoalContinuations } from "./session-start-continuation.js";
18
18
  import { isStaleExtensionContextError } from "./stale-context.js";
19
19
  import { interruptedRunHeld, markStopPaused, markStopResumed, pausedByStop, stopHoldLive } from "./stop-pause.js";
20
20
  import { accountGoalUsage, migrateLegacyGoal, readGoal, updateGoal } from "./store.js";
@@ -156,6 +156,10 @@ export default function goalExtension(pi) {
156
156
  const ref = goalStoreRef(ctx);
157
157
  await migrateLegacyGoal(ref);
158
158
  let goal = await readGoal(goalStoreRef(ctx));
159
+ // senpi#3015: a crash between the stop claim and the session append leaves the claim
160
+ // without its entries; re-append the missing ones once on reopen.
161
+ if (goal)
162
+ await recoverMissingGoalContinuationStop(pi, ctx, goal);
159
163
  // A process that died mid-turn never ran the quit-time abort that stops an active goal (senpi#5753). Stop it the
160
164
  // same way here, so the resume prompt below asks before any continuation runs (senpi#5755). Opening that session
161
165
  // at launch (`--continue`, `--session`) is the same resume, so it asks too.
@@ -202,26 +206,26 @@ export default function goalExtension(pi) {
202
206
  ctx,
203
207
  goal,
204
208
  monitor: monitorContinuation,
205
- countTrailingContinuations: () => countTrailingGoalContinuationEntries(ctx.sessionManager.getBranch()),
206
- queueContinuation: () => queueGoalContinuationForCurrentSession(pi, ctx, goal),
209
+ readTrailingContinuations: () => readTrailingGoalContinuations(ctx.sessionManager.getBranch()),
210
+ queueContinuation: (limitWait) => queueGoalContinuationForCurrentSession(pi, ctx, goal, limitWait),
207
211
  });
208
212
  }
209
213
  else if (goal) {
210
214
  // Migration-lite admission: a resumed session carrying a trailing flood of
211
215
  // historical continuations must not reignite on load. Skip the auto-queue,
212
216
  // leave the goal active (no status rewrite), and tell the user how to resume.
213
- const trailingContinuations = countTrailingGoalContinuationEntries(ctx.sessionManager.getBranch());
214
- if (trailingContinuations >= GOAL_CONTINUATION_CAP) {
217
+ const trailing = readTrailingGoalContinuations(ctx.sessionManager.getBranch());
218
+ if (trailing.counted >= GOAL_CONTINUATION_CAP) {
215
219
  // The load-time flood suppression parks the goal without rewriting its
216
220
  // status, so the only resume signal left is the user's next message.
217
221
  // Arm the one-shot latch the direct-input lifecycle fires when that
218
222
  // message is accepted; without it the promise in the notice is a lie
219
223
  // and the resumed session sits idle until the goal is recreated.
220
224
  directInputLifecycle.armSuppressedLoadResume();
221
- ctx.ui.notify(`Goal auto-continuation suppressed for this resumed session (${trailingContinuations} historical continuations). Send a message to resume.`, "info");
225
+ ctx.ui.notify(`Goal auto-continuation suppressed for this resumed session (${trailing.counted} historical continuations). Send a message to resume.`, "info");
222
226
  }
223
227
  else {
224
- await queueGoalContinuationForCurrentSession(pi, ctx, goal);
228
+ await queueGoalContinuationForCurrentSession(pi, ctx, goal, trailing.waitingOutLimit);
225
229
  }
226
230
  }
227
231
  });
@@ -407,12 +411,13 @@ export default function goalExtension(pi) {
407
411
  await queueGoalContinuationForCurrentSession(pi, ctx, resumed);
408
412
  return true;
409
413
  }
410
- async function queueGoalContinuationForCurrentSession(extensionApi, ctx, goal) {
414
+ async function queueGoalContinuationForCurrentSession(extensionApi, ctx, goal, limitWait = false) {
411
415
  const continuedGoal = await queueGoalContinuation(extensionApi, ctx, goal, {
412
416
  continuationPending,
413
417
  markContinuationPending: () => {
414
418
  continuationPending = true;
415
419
  },
420
+ limitWait,
416
421
  });
417
422
  if (continuedGoal === null) {
418
423
  clearAgentGoalAccounting();
@@ -533,23 +538,6 @@ export default function goalExtension(pi) {
533
538
  return goal;
534
539
  }
535
540
  }
536
- /**
537
- * Counts goal-continuation entries queued since the most recent real user message
538
- * in the current branch (mirrors the todo-gate / todo-bridge backward branch read).
539
- * A real user message is the only reset: the assistant turns in between are exactly
540
- * the unattended loop this admission guard exists to stop.
541
- */
542
- function countTrailingGoalContinuationEntries(entries) {
543
- let count = 0;
544
- for (let index = entries.length - 1; index >= 0; index--) {
545
- const entry = entries[index];
546
- if (entry?.type === "message" && entry.message.role === "user")
547
- break;
548
- if (entry?.type === "custom_message" && entry.customType === GOAL_CONTINUATION_MESSAGE_TYPE)
549
- count += 1;
550
- }
551
- return count;
552
- }
553
541
  function goalStoreRef(ctx) {
554
542
  return buildGoalStoreRef(ctx.sessionManager, ctx.cwd);
555
543
  }
@@ -11,10 +11,6 @@ type GoalContinuationDeliveryOptions = {
11
11
  readonly content: (verdict: ContinuingGoalContinuationVerdict) => string;
12
12
  readonly markContinuationPending: () => void;
13
13
  };
14
- export type SessionStartContinuationOptions = {
15
- readonly continuationPending: boolean;
16
- readonly markContinuationPending: () => void;
17
- };
18
14
  /**
19
15
  * Mirrors codex `maybe_prompt_resume_paused_goal_after_resume`
20
16
  * (codex-rs/tui/src/app/thread_goal_actions.rs), which prompts on resume for every
@@ -24,13 +20,19 @@ export type SessionStartContinuationOptions = {
24
20
  export declare function isResumeOfStoppedGoal(ctx: ExtensionContext, sessionStartReason: string, goal: Goal | null): goal is Goal;
25
21
  /** Evaluates and delivers a continuation only after all guardrail checks admit it. */
26
22
  export declare function admitAndQueueGoalContinuation(pi: ExtensionAPI, ctx: ExtensionContext, goal: Goal, options: GoalContinuationDeliveryOptions): Promise<Goal | null>;
27
- /** Routes startup and resume continuations through the same verdict and delivery accounting as agent-end paths. */
28
- export declare function queueGoalContinuation(pi: ExtensionAPI, ctx: ExtensionContext, goal: Goal, options: SessionStartContinuationOptions): Promise<Goal | null>;
29
23
  export declare function buildCurrentGoalContinuationSignature(ctx: ExtensionContext, goal: Goal, lastAssistantText: string): string;
30
24
  export declare function lastAssistantText(messages: readonly AgentMessage[]): string;
31
25
  export declare function isLastTurnStuckOnContextOverflow(ctx: ExtensionContext, lastAssistant: Extract<AgentMessage, {
32
26
  role: "assistant";
33
27
  }> | undefined): boolean;
28
+ /**
29
+ * senpi#3015: the stop claim and the session entries are two separate writes, so a crash
30
+ * between them leaves the claim without its entries. On session start the claim still
31
+ * stands, so re-append the missing entries once from the payload the claim captured.
32
+ * A claim whose entries already landed is left alone; a claim recorded before the
33
+ * payload existed cannot be rebuilt and is left alone too.
34
+ */
35
+ export declare function recoverMissingGoalContinuationStop(pi: ExtensionAPI, ctx: ExtensionContext, goal: Goal): Promise<void>;
34
36
  export declare function queueHiddenGoalPrompt(pi: ExtensionAPI, content: string): void;
35
37
  export {};
36
38
  //# sourceMappingURL=lifecycle-helpers.d.ts.map
@@ -1,11 +1,11 @@
1
1
  import { isTurnStuckOnContextOverflow } from "../../../compaction/stuck-overflow.js";
2
- import { enginePauseSinceLastTurn } from "../../../engine-paused.js";
2
+ import { ENGINE_PAUSED_ENTRY_TYPE, enginePauseSinceLastTurn } from "../../../engine-paused.js";
3
3
  import { GOAL_CONTINUATION_MESSAGE_TYPE } from "../../../messages.js";
4
4
  import { createSessionLogger } from "../../../session-log.js";
5
+ import { isRecord } from "../../../work-custody-record.js";
5
6
  import { getLatestPhasesFromBranchEntries } from "../todotools/state.js";
6
7
  import { buildGoalContinuationSignature, evaluateGoalContinuation, hashAssistantText, } from "./continuation.js";
7
8
  import { CONTEXT_OVERFLOW_BLOCKED_REASON, CONTINUATION_CAP_BLOCKED_REASON, continuationCapRecoveryHint, LENGTH_EXHAUSTED_BLOCKED_REASON, NO_PROGRESS_BLOCKED_REASON, REPETITION_BLOCKED_REASON, } from "./continuation-recovery.js";
8
- import { buildContinuationPrompt } from "./prompt.js";
9
9
  import { recordContinuationDelivered, recordGoalContinuationStopped } from "./store.js";
10
10
  import { goalStoreRef } from "./store-ref.js";
11
11
  import { openTodoTaskContents } from "./todo-gate.js";
@@ -41,36 +41,15 @@ export async function admitAndQueueGoalContinuation(pi, ctx, goal, options) {
41
41
  if (options.input.currentSignature === undefined) {
42
42
  throw new Error("Cannot queue a goal continuation without a progress signature");
43
43
  }
44
- const recordedGoal = await recordContinuationDelivered(goalStoreRef(ctx.sessionManager, ctx.cwd), options.input.currentSignature, goal.id, { countUnattended: options.input.path !== "monitorDelayed" });
44
+ const recordedGoal = await recordContinuationDelivered(goalStoreRef(ctx.sessionManager, ctx.cwd), options.input.currentSignature, goal.id, options.input.limitWait === true
45
+ ? { countUnattended: false, countStreak: false }
46
+ : { countUnattended: options.input.path !== "monitorDelayed" });
45
47
  if (recordedGoal === null)
46
48
  return null;
47
49
  options.markContinuationPending();
48
50
  queueHiddenGoalPrompt(pi, options.content(verdict));
49
51
  return recordedGoal;
50
52
  }
51
- /** Routes startup and resume continuations through the same verdict and delivery accounting as agent-end paths. */
52
- export async function queueGoalContinuation(pi, ctx, goal, options) {
53
- const signature = buildCurrentGoalContinuationSignature(ctx, goal, lastAssistantTextFromEntries(ctx.sessionManager.getBranch()));
54
- return admitAndQueueGoalContinuation(pi, ctx, goal, {
55
- input: {
56
- isIdle: ctx.isIdle(),
57
- hasPendingMessages: ctx.hasPendingMessages(),
58
- path: "sessionStart",
59
- lastStopReason: undefined,
60
- lastTurnWasMalformedToolUse: false,
61
- consecutiveContinuations: goal.consecutiveContinuations ?? 0,
62
- lastContinuationSignature: goal.lastContinuationSignature,
63
- currentSignature: signature,
64
- consecutiveLengthRecoveries: 0,
65
- recentNormalizedOutputHashes: [],
66
- toollessContinuationStreak: 0,
67
- continuationPending: options.continuationPending,
68
- lastTurnStuckOnContextOverflow: isLastTurnStuckOnContextOverflow(ctx, lastAssistantFromEntries(ctx.sessionManager.getBranch())),
69
- },
70
- content: () => buildContinuationPrompt(goal, { modelId: ctx.model?.id }),
71
- markContinuationPending: options.markContinuationPending,
72
- });
73
- }
74
53
  export function buildCurrentGoalContinuationSignature(ctx, goal, lastAssistantText) {
75
54
  const entries = ctx.sessionManager.getBranch();
76
55
  const openTodos = openTodoTaskContents(entries).length;
@@ -85,18 +64,6 @@ export function lastAssistantText(messages) {
85
64
  }
86
65
  return "";
87
66
  }
88
- function lastAssistantTextFromEntries(entries) {
89
- const assistant = lastAssistantFromEntries(entries);
90
- return assistant === undefined ? "" : textContent(assistant);
91
- }
92
- function lastAssistantFromEntries(entries) {
93
- for (let index = entries.length - 1; index >= 0; index--) {
94
- const entry = entries[index];
95
- if (entry?.type === "message" && entry.message.role === "assistant")
96
- return entry.message;
97
- }
98
- return undefined;
99
- }
100
67
  export function isLastTurnStuckOnContextOverflow(ctx, lastAssistant) {
101
68
  return lastAssistant !== undefined && isTurnStuckOnContextOverflow(lastAssistant, ctx.model?.contextWindow ?? 0);
102
69
  }
@@ -112,7 +79,12 @@ async function handleDeniedContinuation(pi, ctx, goal, input, reason) {
112
79
  const logger = createSessionLogger(ctx.agentDir);
113
80
  const at = Date.now();
114
81
  const blockedReason = blockedReasonForContinuationGuard(reason);
115
- const stopped = await recordGoalContinuationStopped(goalStoreRef(ctx.sessionManager, ctx.cwd), goal, at, blockedReason);
82
+ const stopped = await recordGoalContinuationStopped(goalStoreRef(ctx.sessionManager, ctx.cwd), goal, at, blockedReason, {
83
+ reason,
84
+ consecutiveContinuations: input.consecutiveContinuations,
85
+ unattendedContinuations: goal.unattendedContinuations ?? 0,
86
+ noProgressContinuations: goal.noProgressContinuations ?? 0,
87
+ });
116
88
  if (!stopped.recorded)
117
89
  return stopped.goal;
118
90
  // Preserve the guard's existing side effects even if publishing the additive entries fails.
@@ -164,6 +136,72 @@ async function handleDeniedContinuation(pi, ctx, goal, input, reason) {
164
136
  }
165
137
  return stopped.goal;
166
138
  }
139
+ /**
140
+ * senpi#3015: the stop claim and the session entries are two separate writes, so a crash
141
+ * between them leaves the claim without its entries. On session start the claim still
142
+ * stands, so re-append the missing entries once from the payload the claim captured.
143
+ * A claim whose entries already landed is left alone; a claim recorded before the
144
+ * payload existed cannot be rebuilt and is left alone too.
145
+ */
146
+ export async function recoverMissingGoalContinuationStop(pi, ctx, goal) {
147
+ const at = goal.continuationStoppedAt;
148
+ const detail = goal.continuationStoppedDetail;
149
+ if (at === undefined || detail === undefined)
150
+ return;
151
+ const branch = ctx.sessionManager.getBranch();
152
+ const logger = createSessionLogger(ctx.agentDir);
153
+ if (!hasStopEntrySince(branch, goal.id, at)) {
154
+ try {
155
+ pi.appendEntry("goal-continuation-stopped", {
156
+ goalId: goal.id,
157
+ reason: detail.reason,
158
+ consecutiveContinuations: detail.consecutiveContinuations,
159
+ unattendedContinuations: detail.unattendedContinuations,
160
+ noProgressContinuations: detail.noProgressContinuations,
161
+ at,
162
+ });
163
+ }
164
+ catch (error) {
165
+ logger.warn("goal_continuation_record_write_failed", {
166
+ kind: "goal-continuation-stopped",
167
+ error: error instanceof Error ? error.message : String(error),
168
+ });
169
+ }
170
+ }
171
+ if (detail.reason !== "stale" && detail.reason !== "repetition")
172
+ return;
173
+ if (hasEnginePausedEntrySince(branch, at) || enginePauseSinceLastTurn(branch))
174
+ return;
175
+ try {
176
+ pi.appendEntry(ENGINE_PAUSED_ENTRY_TYPE, {
177
+ reason: detail.reason === "stale" ? "goal-stale" : "goal-repeat",
178
+ customType: GOAL_CONTINUATION_MESSAGE_TYPE,
179
+ count: detail.consecutiveContinuations,
180
+ at,
181
+ });
182
+ }
183
+ catch (error) {
184
+ logger.warn("goal_continuation_record_write_failed", {
185
+ kind: ENGINE_PAUSED_ENTRY_TYPE,
186
+ error: error instanceof Error ? error.message : String(error),
187
+ });
188
+ }
189
+ }
190
+ function hasStopEntrySince(branch, goalId, sinceAt) {
191
+ return branch.some((entry) => entry?.type === "custom" &&
192
+ entry.customType === "goal-continuation-stopped" &&
193
+ isRecord(entry.data) &&
194
+ entry.data.goalId === goalId &&
195
+ typeof entry.data.at === "number" &&
196
+ entry.data.at >= sinceAt);
197
+ }
198
+ function hasEnginePausedEntrySince(branch, sinceAt) {
199
+ return branch.some((entry) => entry?.type === "custom" &&
200
+ entry.customType === ENGINE_PAUSED_ENTRY_TYPE &&
201
+ isRecord(entry.data) &&
202
+ typeof entry.data.at === "number" &&
203
+ entry.data.at >= sinceAt);
204
+ }
167
205
  function blockedReasonForContinuationGuard(reason) {
168
206
  switch (reason) {
169
207
  case "cap":