@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
@@ -0,0 +1,195 @@
1
+ /**
2
+ * `read_project_file` / `write_project_file`: read and edit the session workspace's instruction files over RPC, so a
3
+ * client with no filesystem access to the machine (a remote desktop) edits Rules the way a local one does.
4
+ *
5
+ * Reading runs nothing, so trust does not gate it; writing is refused while the project is untrusted, like
6
+ * `set_project_setting`. Every write names the version it replaces (`ifEtag`, `null` to create), is refused as a
7
+ * conflict carrying the current etag when the file moved on, and lands by temp file + rename so a reader never sees a
8
+ * half-written file. Writes to one file are serialized inside the engine.
9
+ */
10
+ import { createHash, randomUUID } from "node:crypto";
11
+ import { constants } from "node:fs";
12
+ import { chmod, open, realpath, rename, rm, writeFile } from "node:fs/promises";
13
+ import { basename, dirname, join } from "node:path";
14
+ import { createMissingFolders, NO_FOLLOW, PROJECT_FILE_MAX_BYTES, parseProjectFilePath, resolveProjectFile, } from "./project-file-path.js";
15
+ import { RPC_ERROR_INVALID_ARGUMENT, RPC_ERROR_PROJECT_FILE_CONFLICT, RPC_ERROR_PROJECT_FILE_NOT_ALLOWED, RPC_ERROR_PROJECT_FILE_TOO_LARGE, RPC_ERROR_PROJECT_FILE_WRITE_FAILED, RPC_ERROR_PROJECT_NOT_TRUSTED, } from "./rpc-error-types.js";
16
+ const writeChains = new Map();
17
+ function etagOf(bytes) {
18
+ return `sha256-${createHash("sha256").update(bytes).digest("hex")}`;
19
+ }
20
+ function notAllowed(path) {
21
+ return {
22
+ ok: false,
23
+ message: `${path} is not an instruction file inside this workspace (AGENTS.md, CLAUDE.md or .omo/rules/**).`,
24
+ errorCode: RPC_ERROR_PROJECT_FILE_NOT_ALLOWED,
25
+ errorData: { path },
26
+ };
27
+ }
28
+ function tooLarge(path) {
29
+ return {
30
+ ok: false,
31
+ message: `${path} is larger than ${PROJECT_FILE_MAX_BYTES} bytes.`,
32
+ errorCode: RPC_ERROR_PROJECT_FILE_TOO_LARGE,
33
+ errorData: { limitBytes: PROJECT_FILE_MAX_BYTES },
34
+ };
35
+ }
36
+ function parsePath(command, path) {
37
+ if (typeof path !== "string") {
38
+ return { ok: false, message: `${command} requires a string 'path'.`, errorCode: RPC_ERROR_INVALID_ARGUMENT };
39
+ }
40
+ return parseProjectFilePath(path) ?? notAllowed(path);
41
+ }
42
+ /**
43
+ * Reads the stored bytes without following a link and without blocking on a FIFO swapped in after resolution; the
44
+ * size is checked on the open handle before anything is read.
45
+ */
46
+ async function readStored(absolute) {
47
+ let handle;
48
+ try {
49
+ handle = await open(absolute, constants.O_RDONLY | NO_FOLLOW | (constants.O_NONBLOCK ?? 0));
50
+ }
51
+ catch (error) {
52
+ const code = error instanceof Error && "code" in error ? error.code : undefined;
53
+ if (code === "ENOENT")
54
+ return { kind: "missing" };
55
+ if (code === "ELOOP")
56
+ return { kind: "refused" };
57
+ throw error;
58
+ }
59
+ try {
60
+ const stat = await handle.stat();
61
+ if (!stat.isFile())
62
+ return { kind: "refused" };
63
+ if (stat.size > PROJECT_FILE_MAX_BYTES)
64
+ return { kind: "too_large" };
65
+ return { kind: "file", bytes: await handle.readFile(), mode: stat.mode & 0o777 };
66
+ }
67
+ finally {
68
+ await handle.close();
69
+ }
70
+ }
71
+ async function readFileState(session, file) {
72
+ const cwd = session.sessionManager.getCwd();
73
+ const resolved = await resolveProjectFile(cwd, file);
74
+ if (!resolved.ok)
75
+ return notAllowed(file.relative);
76
+ const read = resolved.exists ? await readStored(resolved.absolute) : { kind: "missing" };
77
+ if (read.kind === "refused")
78
+ return notAllowed(file.relative);
79
+ if (read.kind === "too_large")
80
+ return tooLarge(file.relative);
81
+ const stored = read.kind === "file" ? read.bytes : undefined;
82
+ return {
83
+ data: {
84
+ cwd,
85
+ path: file.relative,
86
+ exists: stored !== undefined,
87
+ content: stored === undefined ? null : stored.toString("utf8"),
88
+ etag: stored === undefined ? null : etagOf(stored),
89
+ projectTrusted: session.settingsManager.isProjectTrusted(),
90
+ },
91
+ mode: read.kind === "file" ? read.mode : undefined,
92
+ absolute: resolved.absolute,
93
+ missingFolders: resolved.missingFolders,
94
+ };
95
+ }
96
+ export async function readProjectFile(session, path) {
97
+ const file = parsePath("read_project_file", path);
98
+ if ("ok" in file)
99
+ return file;
100
+ const state = await readFileState(session, file);
101
+ if ("ok" in state)
102
+ return state;
103
+ return { ok: true, data: state.data };
104
+ }
105
+ /** Thrown when the parent folder no longer resolves to itself, i.e. a folder on the way was swapped for a link. */
106
+ class ParentMoved extends Error {
107
+ }
108
+ async function assertParentInPlace(folder) {
109
+ if ((await realpath(folder)) !== folder)
110
+ throw new ParentMoved(folder);
111
+ }
112
+ /**
113
+ * Temp file + rename in the resolved folder, keeping an existing file's permissions. The folder is re-resolved before
114
+ * the temp file is written and again before the rename, so a folder swapped for a link after resolution is refused
115
+ * instead of followed (the remaining window between that check and the syscall needs a local writer racing it).
116
+ */
117
+ async function replaceAtomically(absolute, content, mode) {
118
+ const folder = dirname(absolute);
119
+ const temp = join(folder, `.${basename(absolute)}.${randomUUID()}.tmp`);
120
+ await assertParentInPlace(folder);
121
+ try {
122
+ await writeFile(temp, content, { flag: "wx", mode: mode ?? 0o644 });
123
+ if (mode !== undefined)
124
+ await chmod(temp, mode);
125
+ await assertParentInPlace(folder);
126
+ await rename(temp, absolute);
127
+ }
128
+ catch (error) {
129
+ await rm(temp, { force: true });
130
+ throw error;
131
+ }
132
+ }
133
+ async function writeChecked(session, file, content, ifEtag) {
134
+ const state = await readFileState(session, file);
135
+ if ("ok" in state)
136
+ return state;
137
+ if (state.data.etag !== ifEtag) {
138
+ return {
139
+ ok: false,
140
+ message: `${file.relative} changed since that version was read; read it again before writing.`,
141
+ errorCode: RPC_ERROR_PROJECT_FILE_CONFLICT,
142
+ errorData: { currentEtag: state.data.etag, exists: state.data.exists },
143
+ };
144
+ }
145
+ try {
146
+ if (!(await createMissingFolders(state.missingFolders)))
147
+ return notAllowed(file.relative);
148
+ await replaceAtomically(state.absolute, content, state.mode);
149
+ }
150
+ catch (error) {
151
+ if (error instanceof ParentMoved)
152
+ return notAllowed(file.relative);
153
+ return {
154
+ ok: false,
155
+ message: `Writing ${file.relative} failed: ${error instanceof Error ? error.message : String(error)}`,
156
+ errorCode: RPC_ERROR_PROJECT_FILE_WRITE_FAILED,
157
+ };
158
+ }
159
+ return readProjectFile(session, file.relative);
160
+ }
161
+ export async function writeProjectFile(session, path, content, ifEtag) {
162
+ const file = parsePath("write_project_file", path);
163
+ if ("ok" in file)
164
+ return file;
165
+ if (typeof content !== "string" || (ifEtag !== null && typeof ifEtag !== "string")) {
166
+ return {
167
+ ok: false,
168
+ message: "write_project_file requires a string 'content' and 'ifEtag' (the etag read, or null to create).",
169
+ errorCode: RPC_ERROR_INVALID_ARGUMENT,
170
+ };
171
+ }
172
+ if (!session.settingsManager.isProjectTrusted()) {
173
+ return {
174
+ ok: false,
175
+ message: "Project is not trusted; refusing to write project files. Trust it with set_project_trust first.",
176
+ errorCode: RPC_ERROR_PROJECT_NOT_TRUSTED,
177
+ };
178
+ }
179
+ if (Buffer.byteLength(content, "utf8") > PROJECT_FILE_MAX_BYTES)
180
+ return tooLarge(file.relative);
181
+ const key = join(session.sessionManager.getCwd(), file.relative);
182
+ const previous = writeChains.get(key) ?? Promise.resolve();
183
+ const run = () => writeChecked(session, file, content, ifEtag);
184
+ // A write queued behind a failed one still runs, and its caller never receives the earlier caller's error.
185
+ const next = previous.then(run, run);
186
+ writeChains.set(key, next);
187
+ try {
188
+ return await next;
189
+ }
190
+ finally {
191
+ if (writeChains.get(key) === next)
192
+ writeChains.delete(key);
193
+ }
194
+ }
195
+ //# sourceMappingURL=project-file-commands.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Which workspace files `read_project_file` / `write_project_file` may touch, and where they resolve on disk.
3
+ *
4
+ * The allowlist is the instruction files the engine itself reads: `AGENTS.md` / `CLAUDE.md` at the workspace root or
5
+ * in any folder below it, and anything under `.omo/rules/`. A path is relative, `/`-separated and canonical (no empty,
6
+ * `.` or `..` segment), and never passes through `.git` or `node_modules`. Resolution walks the path one segment at a
7
+ * time with `lstat` from the real workspace root and refuses any symlink or non-folder on the way, so neither a
8
+ * traversal nor a link can reach a file outside the workspace.
9
+ */
10
+ /** Under the host's 1,000,000-character message limit, so an oversized write reaches this typed refusal. */
11
+ export declare const PROJECT_FILE_MAX_BYTES: number;
12
+ /** `O_NOFOLLOW` where the platform has it, so a link swapped in after resolution still fails the open. */
13
+ export declare const NO_FOLLOW: number;
14
+ export interface ProjectFilePath {
15
+ readonly relative: string;
16
+ readonly segments: readonly string[];
17
+ }
18
+ export declare function parseProjectFilePath(input: string): ProjectFilePath | undefined;
19
+ export type ResolvedProjectFile = {
20
+ readonly ok: true;
21
+ readonly absolute: string;
22
+ readonly exists: boolean;
23
+ readonly missingFolders: readonly string[];
24
+ } | {
25
+ readonly ok: false;
26
+ };
27
+ /** Resolves `file` inside the real workspace root; `missingFolders` are the parent folders a write must create. */
28
+ export declare function resolveProjectFile(cwd: string, file: ProjectFilePath): Promise<ResolvedProjectFile>;
29
+ /**
30
+ * Creates the missing parent folders one at a time; a non-recursive `mkdir` fails on anything that appeared in the
31
+ * meantime, and each new folder is re-checked as a real folder before the next one is made inside it.
32
+ */
33
+ export declare function createMissingFolders(folders: readonly string[]): Promise<boolean>;
34
+ //# sourceMappingURL=project-file-path.d.ts.map
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Which workspace files `read_project_file` / `write_project_file` may touch, and where they resolve on disk.
3
+ *
4
+ * The allowlist is the instruction files the engine itself reads: `AGENTS.md` / `CLAUDE.md` at the workspace root or
5
+ * in any folder below it, and anything under `.omo/rules/`. A path is relative, `/`-separated and canonical (no empty,
6
+ * `.` or `..` segment), and never passes through `.git` or `node_modules`. Resolution walks the path one segment at a
7
+ * time with `lstat` from the real workspace root and refuses any symlink or non-folder on the way, so neither a
8
+ * traversal nor a link can reach a file outside the workspace.
9
+ */
10
+ import { constants } from "node:fs";
11
+ import { lstat, mkdir, realpath } from "node:fs/promises";
12
+ import { join } from "node:path";
13
+ /** Under the host's 1,000,000-character message limit, so an oversized write reaches this typed refusal. */
14
+ export const PROJECT_FILE_MAX_BYTES = 512 * 1024;
15
+ const INSTRUCTION_FILE_NAMES = new Set(["AGENTS.md", "CLAUDE.md"]);
16
+ const EXCLUDED_SEGMENTS = new Set([".git", "node_modules"]);
17
+ const FORBIDDEN_CHARACTERS = /[\\:\0]/;
18
+ /** `O_NOFOLLOW` where the platform has it, so a link swapped in after resolution still fails the open. */
19
+ export const NO_FOLLOW = constants.O_NOFOLLOW ?? 0;
20
+ export function parseProjectFilePath(input) {
21
+ if (input.length === 0 || input.startsWith("/") || FORBIDDEN_CHARACTERS.test(input))
22
+ return undefined;
23
+ const segments = input.split("/");
24
+ for (const segment of segments) {
25
+ if (segment === "" || segment === "." || segment === ".." || EXCLUDED_SEGMENTS.has(segment))
26
+ return undefined;
27
+ }
28
+ const name = segments[segments.length - 1] ?? "";
29
+ const isRule = segments.length >= 3 && segments[0] === ".omo" && segments[1] === "rules";
30
+ if (!isRule && !INSTRUCTION_FILE_NAMES.has(name))
31
+ return undefined;
32
+ return { relative: segments.join("/"), segments };
33
+ }
34
+ function isMissing(error) {
35
+ return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
36
+ }
37
+ async function lstatOrMissing(path) {
38
+ try {
39
+ return await lstat(path);
40
+ }
41
+ catch (error) {
42
+ if (isMissing(error))
43
+ return undefined;
44
+ throw error;
45
+ }
46
+ }
47
+ /** Resolves `file` inside the real workspace root; `missingFolders` are the parent folders a write must create. */
48
+ export async function resolveProjectFile(cwd, file) {
49
+ let current = await realpath(cwd);
50
+ const folders = file.segments.slice(0, -1);
51
+ const missingFolders = [];
52
+ for (const folder of folders) {
53
+ current = join(current, folder);
54
+ if (missingFolders.length > 0) {
55
+ missingFolders.push(current);
56
+ continue;
57
+ }
58
+ const stat = await lstatOrMissing(current);
59
+ if (stat === undefined)
60
+ missingFolders.push(current);
61
+ else if (stat.isSymbolicLink() || !stat.isDirectory())
62
+ return { ok: false };
63
+ }
64
+ const absolute = join(current, file.segments[file.segments.length - 1] ?? "");
65
+ if (missingFolders.length > 0)
66
+ return { ok: true, absolute, exists: false, missingFolders };
67
+ const stat = await lstatOrMissing(absolute);
68
+ if (stat === undefined)
69
+ return { ok: true, absolute, exists: false, missingFolders };
70
+ if (stat.isSymbolicLink() || !stat.isFile())
71
+ return { ok: false };
72
+ return { ok: true, absolute, exists: true, missingFolders };
73
+ }
74
+ /**
75
+ * Creates the missing parent folders one at a time; a non-recursive `mkdir` fails on anything that appeared in the
76
+ * meantime, and each new folder is re-checked as a real folder before the next one is made inside it.
77
+ */
78
+ export async function createMissingFolders(folders) {
79
+ for (const folder of folders) {
80
+ try {
81
+ await mkdir(folder);
82
+ }
83
+ catch (error) {
84
+ if (!(error instanceof Error && "code" in error && error.code === "EEXIST"))
85
+ throw error;
86
+ }
87
+ const stat = await lstat(folder);
88
+ if (stat.isSymbolicLink() || !stat.isDirectory())
89
+ return false;
90
+ }
91
+ return true;
92
+ }
93
+ //# sourceMappingURL=project-file-path.js.map
@@ -36,6 +36,14 @@ export declare const RPC_ERROR_PROJECT_SETTINGS_WRITE_FAILED = "project_settings
36
36
  export declare const RPC_ERROR_PROJECT_NOT_TRUSTED = "project_not_trusted";
37
37
  /** `set_project_setting` while the project settings file does not parse; fix the file first. */
38
38
  export declare const RPC_ERROR_PROJECT_SETTINGS_UNREADABLE = "project_settings_unreadable";
39
+ /** `read_project_file`/`write_project_file` on a path outside the instruction-file allowlist or the workspace. */
40
+ export declare const RPC_ERROR_PROJECT_FILE_NOT_ALLOWED = "project_file_not_allowed";
41
+ /** `write_project_file` whose `ifEtag` is not the file's current version; `errorData.currentEtag` names it. */
42
+ export declare const RPC_ERROR_PROJECT_FILE_CONFLICT = "project_file_conflict";
43
+ /** `read_project_file`/`write_project_file` over the project file size limit. */
44
+ export declare const RPC_ERROR_PROJECT_FILE_TOO_LARGE = "project_file_too_large";
45
+ /** `write_project_file` whose write failed (permissions, disk). */
46
+ export declare const RPC_ERROR_PROJECT_FILE_WRITE_FAILED = "project_file_write_failed";
39
47
  /** `open_session.context` broke a documented cap (key count, key syntax, value or total bytes). */
40
48
  export declare const RPC_ERROR_INVALID_SESSION_CONTEXT = "invalid_session_context";
41
49
  /** `open_session.env` is malformed or names a variable that is not caller-passable on this host (senpi#4381). */
@@ -123,5 +131,5 @@ export declare const RPC_ERROR_RELEASE_FAILED = "release_failed";
123
131
  export declare const RPC_ERROR_HOST_DRAINING = "host_draining";
124
132
  /** The supervisor has closed admission; a launch-owning caller may ensure a successor. */
125
133
  export declare const RPC_ERROR_HOST_STOPPING = "host_stopping";
126
- export type RpcErrorCode = typeof RPC_ERROR_SESSION_IDENTITY_MISMATCH | typeof RPC_ERROR_SESSION_BUSY_LIFECYCLE | typeof RPC_ERROR_ATTACHED_ELSEWHERE | typeof RPC_ERROR_OWNER_CHANGED | typeof RPC_ERROR_SESSION_NOT_HELD | typeof RPC_ERROR_HOST_STOPPING | typeof RPC_ERROR_UNKNOWN_SESSION | typeof RPC_ERROR_SESSION_NOT_ATTACHED | typeof RPC_ERROR_SESSION_CLOSING | typeof RPC_ERROR_SESSION_PATH_IN_USE | typeof RPC_ERROR_SESSION_HELD | typeof RPC_ERROR_SESSION_RESERVATION_LIMIT | typeof RPC_ERROR_MISSING_SESSION_ID | typeof RPC_ERROR_MULTI_SESSION_DISABLED | typeof RPC_ERROR_INVALID_PATH | typeof RPC_ERROR_OPEN_FAILED | typeof RPC_ERROR_MEDIA_NOT_FOUND | typeof RPC_ERROR_PROJECT_TRUST_LOCKED | typeof RPC_ERROR_PROJECT_TRUST_EXTENSION_DECIDED | typeof RPC_ERROR_PROJECT_SETTINGS_WRITE_FAILED | typeof RPC_ERROR_PROJECT_NOT_TRUSTED | typeof RPC_ERROR_PROJECT_SETTINGS_UNREADABLE | typeof RPC_ERROR_INVALID_SESSION_CONTEXT | typeof RPC_ERROR_INVALID_SUBSCRIPTION | typeof RPC_ERROR_UNKNOWN_SUBSCRIPTION | typeof RPC_ERROR_SUBSCRIPTION_LIMIT | typeof RPC_ERROR_UNSUPPORTED_TRANSPORT | typeof RPC_ERROR_INVALID_SESSION_KIND | typeof RPC_ERROR_INVALID_LAUNCH_PROFILE | typeof RPC_ERROR_INVALID_SESSION_ID | typeof RPC_ERROR_SESSION_ID_IN_USE | typeof RPC_ERROR_HOST_MEMORY_PRESSURE | typeof RPC_ERROR_WARM_FAILED | typeof RPC_ERROR_STREAMING | typeof RPC_ERROR_ENTRY_NOT_FOUND | typeof RPC_ERROR_MODEL_NOT_FOUND | typeof RPC_ERROR_EFFECT_ID_CONFLICT | typeof RPC_ERROR_TRANSCRIPT_UNREADABLE | typeof RPC_ERROR_PROVIDER_UNAVAILABLE | typeof RPC_ERROR_PROVIDER_CREDENTIALS_MISSING | typeof RPC_ERROR_PROVIDER_CREDENTIALS_EXPIRED | typeof RPC_ERROR_NO_MODEL_SELECTED | typeof RPC_ERROR_TURN_CONFLICT | typeof RPC_ERROR_CONTEXT_BUDGET | typeof RPC_ERROR_EVAL_CELL_NOT_FOUND | typeof RPC_ERROR_INVALID_EVAL_CELL_REQUEST | typeof RPC_ERROR_EVAL_CELLS_UNAVAILABLE | typeof RPC_ERROR_NOT_ASSISTANT | typeof RPC_ERROR_NOT_USER | typeof RPC_ERROR_EMPTY_TEXT | typeof RPC_ERROR_UNKNOWN_COMMAND | typeof RPC_ERROR_STALE_LEAF | typeof RPC_ERROR_TURN_ACTIVE | typeof RPC_ERROR_SESSION_BUSY | typeof RPC_ERROR_ATTACHED | typeof RPC_ERROR_INVALID_RELEASE_REASON | typeof RPC_ERROR_RELEASE_UNSUPPORTED | typeof RPC_ERROR_RELEASE_FAILED | typeof RPC_ERROR_HOST_DRAINING | typeof RPC_ERROR_RESPONSE_UNWRITABLE;
134
+ export type RpcErrorCode = typeof RPC_ERROR_SESSION_IDENTITY_MISMATCH | typeof RPC_ERROR_SESSION_BUSY_LIFECYCLE | typeof RPC_ERROR_ATTACHED_ELSEWHERE | typeof RPC_ERROR_OWNER_CHANGED | typeof RPC_ERROR_SESSION_NOT_HELD | typeof RPC_ERROR_HOST_STOPPING | typeof RPC_ERROR_UNKNOWN_SESSION | typeof RPC_ERROR_SESSION_NOT_ATTACHED | typeof RPC_ERROR_SESSION_CLOSING | typeof RPC_ERROR_SESSION_PATH_IN_USE | typeof RPC_ERROR_SESSION_HELD | typeof RPC_ERROR_SESSION_RESERVATION_LIMIT | typeof RPC_ERROR_MISSING_SESSION_ID | typeof RPC_ERROR_MULTI_SESSION_DISABLED | typeof RPC_ERROR_INVALID_PATH | typeof RPC_ERROR_OPEN_FAILED | typeof RPC_ERROR_MEDIA_NOT_FOUND | typeof RPC_ERROR_PROJECT_TRUST_LOCKED | typeof RPC_ERROR_PROJECT_TRUST_EXTENSION_DECIDED | typeof RPC_ERROR_PROJECT_SETTINGS_WRITE_FAILED | typeof RPC_ERROR_PROJECT_NOT_TRUSTED | typeof RPC_ERROR_PROJECT_SETTINGS_UNREADABLE | typeof RPC_ERROR_PROJECT_FILE_NOT_ALLOWED | typeof RPC_ERROR_PROJECT_FILE_CONFLICT | typeof RPC_ERROR_PROJECT_FILE_TOO_LARGE | typeof RPC_ERROR_PROJECT_FILE_WRITE_FAILED | typeof RPC_ERROR_INVALID_SESSION_CONTEXT | typeof RPC_ERROR_INVALID_SUBSCRIPTION | typeof RPC_ERROR_UNKNOWN_SUBSCRIPTION | typeof RPC_ERROR_SUBSCRIPTION_LIMIT | typeof RPC_ERROR_UNSUPPORTED_TRANSPORT | typeof RPC_ERROR_INVALID_SESSION_KIND | typeof RPC_ERROR_INVALID_LAUNCH_PROFILE | typeof RPC_ERROR_INVALID_SESSION_ID | typeof RPC_ERROR_SESSION_ID_IN_USE | typeof RPC_ERROR_HOST_MEMORY_PRESSURE | typeof RPC_ERROR_WARM_FAILED | typeof RPC_ERROR_STREAMING | typeof RPC_ERROR_ENTRY_NOT_FOUND | typeof RPC_ERROR_MODEL_NOT_FOUND | typeof RPC_ERROR_EFFECT_ID_CONFLICT | typeof RPC_ERROR_TRANSCRIPT_UNREADABLE | typeof RPC_ERROR_PROVIDER_UNAVAILABLE | typeof RPC_ERROR_PROVIDER_CREDENTIALS_MISSING | typeof RPC_ERROR_PROVIDER_CREDENTIALS_EXPIRED | typeof RPC_ERROR_NO_MODEL_SELECTED | typeof RPC_ERROR_TURN_CONFLICT | typeof RPC_ERROR_CONTEXT_BUDGET | typeof RPC_ERROR_EVAL_CELL_NOT_FOUND | typeof RPC_ERROR_INVALID_EVAL_CELL_REQUEST | typeof RPC_ERROR_EVAL_CELLS_UNAVAILABLE | typeof RPC_ERROR_NOT_ASSISTANT | typeof RPC_ERROR_NOT_USER | typeof RPC_ERROR_EMPTY_TEXT | typeof RPC_ERROR_UNKNOWN_COMMAND | typeof RPC_ERROR_STALE_LEAF | typeof RPC_ERROR_TURN_ACTIVE | typeof RPC_ERROR_SESSION_BUSY | typeof RPC_ERROR_ATTACHED | typeof RPC_ERROR_INVALID_RELEASE_REASON | typeof RPC_ERROR_RELEASE_UNSUPPORTED | typeof RPC_ERROR_RELEASE_FAILED | typeof RPC_ERROR_HOST_DRAINING | typeof RPC_ERROR_RESPONSE_UNWRITABLE;
127
135
  //# sourceMappingURL=rpc-error-types.d.ts.map
@@ -36,6 +36,14 @@ export const RPC_ERROR_PROJECT_SETTINGS_WRITE_FAILED = "project_settings_write_f
36
36
  export const RPC_ERROR_PROJECT_NOT_TRUSTED = "project_not_trusted";
37
37
  /** `set_project_setting` while the project settings file does not parse; fix the file first. */
38
38
  export const RPC_ERROR_PROJECT_SETTINGS_UNREADABLE = "project_settings_unreadable";
39
+ /** `read_project_file`/`write_project_file` on a path outside the instruction-file allowlist or the workspace. */
40
+ export const RPC_ERROR_PROJECT_FILE_NOT_ALLOWED = "project_file_not_allowed";
41
+ /** `write_project_file` whose `ifEtag` is not the file's current version; `errorData.currentEtag` names it. */
42
+ export const RPC_ERROR_PROJECT_FILE_CONFLICT = "project_file_conflict";
43
+ /** `read_project_file`/`write_project_file` over the project file size limit. */
44
+ export const RPC_ERROR_PROJECT_FILE_TOO_LARGE = "project_file_too_large";
45
+ /** `write_project_file` whose write failed (permissions, disk). */
46
+ export const RPC_ERROR_PROJECT_FILE_WRITE_FAILED = "project_file_write_failed";
39
47
  /** `open_session.context` broke a documented cap (key count, key syntax, value or total bytes). */
40
48
  export const RPC_ERROR_INVALID_SESSION_CONTEXT = "invalid_session_context";
41
49
  /** `open_session.env` is malformed or names a variable that is not caller-passable on this host (senpi#4381). */
@@ -255,7 +255,20 @@ type RpcCommandBody = RpcAttachmentCommand | (RpcSessionCommand & {
255
255
  context?: Record<string, string>;
256
256
  };
257
257
  /** Compact callers may identify their own admission on any correlated session request. */
258
- export type RpcCommand = RpcCommandBody & {
258
+ export type RpcCommand = (RpcCommandBody | {
259
+ id?: string;
260
+ /** Build one invisible session for later claim; never an attachment. Requires `park_claim`. */
261
+ type: "park_session";
262
+ cwd: string;
263
+ kind?: SessionKind;
264
+ context?: Record<string, string>;
265
+ } | (Omit<Extract<RpcCommandBody, {
266
+ type: "open_session";
267
+ }>, "type" | "cwd"> & {
268
+ /** Consume one ready park. A miss returns `claimed: false` and never builds a session. */
269
+ type: "claim_session";
270
+ cwd: string;
271
+ })) & {
259
272
  readonly admissionId?: string;
260
273
  };
261
274
  /** Stdin records: session/host commands plus extension-UI replies and progress, and host-request answers. */
@@ -19,6 +19,27 @@ export interface RpcSessionPresence {
19
19
  readonly held: number;
20
20
  }
21
21
  export type RpcHostCommandResponse = {
22
+ id?: string;
23
+ type: "response";
24
+ command: "park_session";
25
+ success: true;
26
+ data: {
27
+ state: "parked" | "already_parked" | "full";
28
+ };
29
+ } | {
30
+ id?: string;
31
+ type: "response";
32
+ command: "claim_session";
33
+ success: true;
34
+ data: {
35
+ claimed: false;
36
+ } | {
37
+ sessionId: string;
38
+ state: RpcSessionState;
39
+ attached?: boolean;
40
+ presence?: RpcSessionPresence;
41
+ };
42
+ } | {
22
43
  id?: string;
23
44
  type: "response";
24
45
  command: "list_hands";
@@ -9,6 +9,7 @@ import type { SessionControlAdmission } from "../../core/extensions/types.ts";
9
9
  import type { PermissionModeReply } from "../../core/permission-mode.ts";
10
10
  import type { ApiKeyVerification } from "../../core/verify-api-key.ts";
11
11
  import type { ClientMessageAdmission } from "./client-admission-record.ts";
12
+ import type { RpcProjectFile } from "./project-file-commands.ts";
12
13
  import type { RpcProjectSettings, RpcProjectTrust } from "./project-workspace-commands.ts";
13
14
  import type { RpcAbortQuiesce, RpcEffectAnswer, RpcQueuedInputChange } from "./rpc-effect-answer-types.ts";
14
15
  import type { EditAssistantMessageResult, EditUserMessageResult, NavigateTreeResult } from "./rpc-result-types.ts";
@@ -219,6 +220,12 @@ export type RpcMutationResponse = {
219
220
  command: "set_project_setting";
220
221
  success: true;
221
222
  data: RpcProjectSettings;
223
+ } | {
224
+ id?: string;
225
+ type: "response";
226
+ command: "write_project_file";
227
+ success: true;
228
+ data: RpcProjectFile;
222
229
  } | {
223
230
  id?: string;
224
231
  type: "response";
@@ -9,6 +9,7 @@ import type { ReleaseReceipt } from "./compact-event-leases.ts";
9
9
  import type { EventPageAnswer, RecoveryPageAnswer } from "./compact-recovery-requests.ts";
10
10
  import type { ThinkingLevelSource } from "../../core/session-thinking-memory.ts";
11
11
  import type { EffectStatus } from "./effect-record.ts";
12
+ import type { RpcProjectFile } from "./project-file-commands.ts";
12
13
  import type { RpcProjectSettings, RpcProjectTrust } from "./project-workspace-commands.ts";
13
14
  import type { RpcSlashCommand } from "./rpc-command-surface.ts";
14
15
  import type { RpcEvalCell, RpcEvalCellSnapshot } from "./rpc-result-types.ts";
@@ -209,6 +210,12 @@ export type RpcReadResponse = {
209
210
  command: "get_project_settings";
210
211
  success: true;
211
212
  data: RpcProjectSettings;
213
+ } | {
214
+ id?: string;
215
+ type: "response";
216
+ command: "read_project_file";
217
+ success: true;
218
+ data: RpcProjectFile;
212
219
  } | {
213
220
  id?: string;
214
221
  type: "response";
@@ -488,6 +488,16 @@ export type RpcSessionCommand = {
488
488
  type: "set_project_setting";
489
489
  key: string;
490
490
  value: unknown;
491
+ } | {
492
+ id?: string;
493
+ type: "read_project_file";
494
+ path: string;
495
+ } | {
496
+ id?: string;
497
+ type: "write_project_file";
498
+ path: string;
499
+ content: string;
500
+ ifEtag: string | null;
491
501
  } | {
492
502
  id?: string;
493
503
  type: "extension_request";
@@ -74,6 +74,8 @@ export interface RpcProviderAccount {
74
74
  model: string;
75
75
  outcome: "ok" | "failed";
76
76
  reason?: string;
77
+ message?: string;
78
+ status?: number;
77
79
  at: number;
78
80
  };
79
81
  /** `needs_sign_in`: the account must sign in again before it is used. */
@@ -8,6 +8,8 @@ export const ATTACH_GATE_EXEMPT = new Set([
8
8
  "unsubscribe_sessions",
9
9
  "get_subscription_limits",
10
10
  "warm",
11
+ "park_session",
12
+ "claim_session",
11
13
  "detach_connection",
12
14
  "open_session",
13
15
  "attach_session",
@@ -25,6 +27,7 @@ export const ATTACH_GATE_EXEMPT = new Set([
25
27
  "get_loaded_surfaces",
26
28
  "get_project_trust",
27
29
  "get_project_settings",
30
+ "read_project_file",
28
31
  "get_available_models",
29
32
  "get_available_thinking_levels",
30
33
  "get_fast_mode",
@@ -65,6 +68,9 @@ export function attachmentGate(host, command) {
65
68
  return undefined;
66
69
  if (host.sessionsByConnection.get(owner)?.has(command.sessionId))
67
70
  return undefined;
71
+ // senpi#6305: the forwarder's peer lane delivers an agent's message without ever attaching; only its admit passes.
72
+ if (command.type === "admit_external_message" && host.connectionRoles.grantedByForwarder(owner, "peer_agent"))
73
+ return undefined;
68
74
  if (process.env.SENPI_RPC_REQUIRE_ATTACH === "1")
69
75
  return error(command.id, command.type, "session_not_attached", {
70
76
  reason: "this connection is not attached to that session",
@@ -4,6 +4,7 @@ import type { RpcSessionEntry } from "./session-registry.ts";
4
4
  /** A session-owned adapter around the classic command and extension-UI wiring. */
5
5
  export interface RpcSessionBinding {
6
6
  handle(command: object): Promise<void>;
7
+ setClientCapabilities?(capabilities: readonly string[]): void;
7
8
  cancelPendingExtensionUiRequests?(): void;
8
9
  /** A connection of the shared host left this session (`close_session`, a dropped or detached connection). */
9
10
  connectionDetached?(connectionId: string): void;
@@ -40,6 +40,8 @@ export async function createRpcSessionBinding(sessionId, entry, writer, requestC
40
40
  return carrier && record && firstOrdinal !== undefined ? { record, carrier, ordinal, firstOrdinal } : undefined;
41
41
  };
42
42
  const enqueueRecord = (record) => {
43
+ if (entry.speculative)
44
+ return;
43
45
  toolSpans.observe(record);
44
46
  const canonical = pairing?.take(record);
45
47
  const refused = pairing?.refused(record) === true;
@@ -103,6 +105,8 @@ export async function createRpcSessionBinding(sessionId, entry, writer, requestC
103
105
  writeInScope(record);
104
106
  };
105
107
  const writeStructuredInScope = bindToProviderScope((record) => {
108
+ if (entry.speculative)
109
+ return;
106
110
  writer.structuredLanes.accept(sessionId, record);
107
111
  const control = pairing?.structured(record);
108
112
  if (control && lane)
@@ -151,7 +155,7 @@ export async function createRpcSessionBinding(sessionId, entry, writer, requestC
151
155
  rpcOwnersByConnection.clear();
152
156
  },
153
157
  publishLoadedSurfacesChanged: (previous, digest) => {
154
- if (entry.scope.state !== "closed")
158
+ if (!entry.speculative && entry.scope.state !== "closed")
155
159
  writer.publishLoadedSurfacesChanged(sessionId, previous, digest);
156
160
  },
157
161
  ...options,
@@ -171,6 +175,7 @@ export async function createRpcSessionBinding(sessionId, entry, writer, requestC
171
175
  });
172
176
  await handler.ready;
173
177
  return {
178
+ setClientCapabilities: (capabilities) => runWithProviderScope(entry.scope, () => handler.setClientCapabilities(capabilities)),
174
179
  handle: (command) => {
175
180
  if (writer.handleMcpForm(command))
176
181
  return Promise.resolve();
@@ -23,6 +23,7 @@ const observations = new Set([
23
23
  "get_loaded_surfaces",
24
24
  "get_project_trust",
25
25
  "get_project_settings",
26
+ "read_project_file",
26
27
  "get_media",
27
28
  "get_auth_providers",
28
29
  "get_provider_accounts",
@@ -7,6 +7,8 @@ export declare class SessionConnectionContext {
7
7
  private readonly delegationOf;
8
8
  constructor(ownerOf: (id: string) => ExtensionTransportOwner | undefined, delegationOf?: (id: string) => DelegationDescriptor | undefined);
9
9
  run<T>(id: string, task: () => T): T;
10
+ /** Host-owned background construction has no requesting transport or inherited delegation. */
11
+ outside<T>(task: () => T): T;
10
12
  /** The delegation the running line captured at its start, or undefined for a connection that never declared. */
11
13
  get delegation(): DelegationDescriptor | undefined;
12
14
  /**
@@ -10,6 +10,10 @@ export class SessionConnectionContext {
10
10
  const delegation = this.delegationOf(id);
11
11
  return this.storage.run({ id, owner: this.ownerOf(id), ...(delegation === undefined ? {} : { delegation }) }, task);
12
12
  }
13
+ /** Host-owned background construction has no requesting transport or inherited delegation. */
14
+ outside(task) {
15
+ return this.storage.exit(task);
16
+ }
13
17
  /** The delegation the running line captured at its start, or undefined for a connection that never declared. */
14
18
  get delegation() {
15
19
  return this.storage.getStore()?.delegation;
@@ -1,4 +1,6 @@
1
1
  import type { SessionPathReservations } from "./host-reservations.ts";
2
2
  import { type OpenRpcSession, type RpcSessionEntry, type RpcSessionLaunchProfile, type RpcSessionOpenOptions } from "./session-registry-types.ts";
3
3
  export declare function attachToOpenSession(entries: ReadonlyMap<string, RpcSessionEntry>, sessionPath: string, profile: RpcSessionLaunchProfile, options: RpcSessionOpenOptions | undefined, pathReservations: SessionPathReservations | undefined, now: number): Promise<OpenRpcSession>;
4
+ /** The live launch choices shared by attach-on-open and claiming an already-built session. */
5
+ export declare function applyAttachedProfile(entry: RpcSessionEntry, profile: RpcSessionLaunchProfile): void;
4
6
  //# sourceMappingURL=session-registry-attach.d.ts.map
@@ -102,6 +102,21 @@ export async function attachToOpenSession(entries, sessionPath, profile, options
102
102
  entry.retainOnDisconnect = true;
103
103
  if (!entry.durableSessionId)
104
104
  throw new RpcSessionRegistryError("session_path_in_use");
105
+ applyAttachedProfile(entry, profile);
106
+ entry.lastCommandAt = now;
107
+ if (wasParked) {
108
+ entry.lifecycleMutex = entry.lifecycleMutex.then(() => entry.runtime?.emitAttachmentEvent("session_resumed"));
109
+ await entry.lifecycleMutex;
110
+ }
111
+ return {
112
+ sessionId: handle,
113
+ durableSessionId: entry.durableSessionId,
114
+ sessionPath: entry.sessionPath,
115
+ attached: true,
116
+ };
117
+ }
118
+ /** The live launch choices shared by attach-on-open and claiming an already-built session. */
119
+ export function applyAttachedProfile(entry, profile) {
105
120
  // The surface, browser engine and permission preset follow the client: an attach that names one
106
121
  // moves the live session to it; an attach without one keeps what the session has.
107
122
  if (profile.promptSurface !== undefined && profile.promptSurface !== entry.profile.promptSurface) {
@@ -121,16 +136,5 @@ export async function attachToOpenSession(entries, sessionPath, profile, options
121
136
  entry.profile = frozenProfile(withClientPreset(entry.profile, namedPreset));
122
137
  entry.runtime?.setPermissionPreset(namedPreset);
123
138
  }
124
- entry.lastCommandAt = now;
125
- if (wasParked) {
126
- entry.lifecycleMutex = entry.lifecycleMutex.then(() => entry.runtime?.emitAttachmentEvent("session_resumed"));
127
- await entry.lifecycleMutex;
128
- }
129
- return {
130
- sessionId: handle,
131
- durableSessionId: entry.durableSessionId,
132
- sessionPath: entry.sessionPath,
133
- attached: true,
134
- };
135
139
  }
136
140
  //# sourceMappingURL=session-registry-attach.js.map
@@ -70,6 +70,8 @@ export async function settlePendingClaim(pending, sessionPath, graceMs) {
70
70
  /** Reconcile path and durable identity after runtime replacement. */
71
71
  export function syncRuntimeMetadata(entries, reservations, pathReservations) {
72
72
  for (const entry of entries) {
73
+ if (entry.speculative)
74
+ continue;
73
75
  const manager = entry.runtime?.session.sessionManager;
74
76
  if (!manager)
75
77
  continue;