@code-yeongyu/senpi 2026.9.27-4 → 2026.9.28

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 (271) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/bundle/chunks/{app-server-command-3GVJZFFH.js → app-server-command-TOPTGVQB.js} +1 -1
  3. package/dist/bundle/chunks/{azure-openai-responses-4SUOOZZ4.js → azure-openai-responses-FCZNBRWE.js} +1 -1
  4. package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
  5. package/dist/bundle/chunks/{chunk-PSCNHY7D.js → chunk-3GMFUEU7.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-HBG6SQU5.js → chunk-62XHQXCZ.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-SLL375O7.js → chunk-7DY2JPUX.js} +1 -1
  8. package/dist/bundle/chunks/chunk-7YUGCTEV.js +15 -0
  9. package/dist/bundle/chunks/{chunk-FWBSGMAE.js → chunk-A4UMPXQF.js} +1 -1
  10. package/dist/bundle/chunks/chunk-BBK6SSYT.js +2 -0
  11. package/dist/bundle/chunks/{chunk-C2425O2Q.js → chunk-CNQ4JB45.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-27IFXX3B.js → chunk-DRN4FQCE.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-22MGY5JQ.js → chunk-EHSZ7UCW.js} +1 -1
  14. package/dist/bundle/chunks/chunk-FHJYIU3U.js +5 -0
  15. package/dist/bundle/chunks/chunk-GJUCSTAG.js +19 -0
  16. package/dist/bundle/chunks/{chunk-VMKAYXHL.js → chunk-KIOTFUG6.js} +25 -20
  17. package/dist/bundle/chunks/{chunk-2BMD37YV.js → chunk-LUPPSEQW.js} +1 -1
  18. package/dist/bundle/chunks/chunk-OAIVTS5X.js +8 -0
  19. package/dist/bundle/chunks/{chunk-NRGMT74E.js → chunk-QOVUQZO4.js} +1 -1
  20. package/dist/bundle/chunks/chunk-SN4ZHPMN.js +135 -0
  21. package/dist/bundle/chunks/{chunk-IKXPBYSO.js → chunk-UJFLZ2M2.js} +1 -1
  22. package/dist/bundle/chunks/{chunk-4T4GFAGK.js → chunk-VFSXUT7K.js} +1 -1
  23. package/dist/bundle/chunks/{cli-main-66NFBKVH.js → cli-main-ARR54ET6.js} +1 -1
  24. package/dist/bundle/chunks/{google-generative-ai-ZTGKZTGU.js → google-generative-ai-LFCBHG3Y.js} +1 -1
  25. package/dist/bundle/chunks/{google-vertex-CCZ376YK.js → google-vertex-C474FXU7.js} +1 -1
  26. package/dist/bundle/chunks/{help-fast-path-NZ3I2UEN.js → help-fast-path-3U3YDEZ6.js} +1 -1
  27. package/dist/bundle/chunks/{host-command-RORPAC5X.js → host-command-6CJ7YM6W.js} +1 -1
  28. package/dist/bundle/chunks/{host-lifecycle-UDMJGLOR.js → host-lifecycle-RZIMOFDM.js} +1 -1
  29. package/dist/bundle/chunks/host-lifecycle.js +6 -6
  30. package/dist/bundle/chunks/interactive-host-runtime-A3GEIFTU.js +4 -0
  31. package/dist/bundle/chunks/{interactive-mode-LOAADKLH.js → interactive-mode-ITSFLGF6.js} +1 -1
  32. package/dist/bundle/chunks/{list-tips-S5CANO5W.js → list-tips-FPXDXN7R.js} +1 -1
  33. package/dist/bundle/chunks/multi-session-host-B4WMZE2G.js +21 -0
  34. package/dist/bundle/chunks/{openai-codex-responses-YSGZZDD5.js → openai-codex-responses-2LDIFL6Z.js} +1 -1
  35. package/dist/bundle/chunks/{openai-completions-KWHRWCBN.js → openai-completions-7KZLPKYP.js} +1 -1
  36. package/dist/bundle/chunks/{openai-images-6U5FCN5S.js → openai-images-I7DR7ERP.js} +1 -1
  37. package/dist/bundle/chunks/{openai-responses-7PWMBGYX.js → openai-responses-LCHS3AF3.js} +1 -1
  38. package/dist/bundle/chunks/{openrouter-images-QIKWQZUD.js → openrouter-images-IPNDRK6C.js} +1 -1
  39. package/dist/bundle/chunks/{package-manager-cli-5EEDA35I.js → package-manager-cli-DXHQ6RKA.js} +1 -1
  40. package/dist/bundle/chunks/{rotation-stream-KZG4OVR7.js → rotation-stream-ZVJEGA2G.js} +1 -1
  41. package/dist/bundle/chunks/{rpc-mode-HOEA5NGQ.js → rpc-mode-PFPNP65F.js} +1 -1
  42. package/dist/bundle/chunks/session-picker-KOPOV6Q4.js +2 -0
  43. package/dist/bundle/chunks/session-worker.js +311 -302
  44. package/dist/bundle/cli.js +1 -1
  45. package/dist/bundle/index.js +1 -1
  46. package/dist/bundle/rpc-entry.js +1 -1
  47. package/dist/cli/args.js +1 -1
  48. package/dist/cli/args.js.map +1 -1
  49. package/dist/cli/host-command.d.ts +8 -1
  50. package/dist/cli/host-command.d.ts.map +1 -1
  51. package/dist/cli/host-command.js +66 -14
  52. package/dist/cli/host-command.js.map +1 -1
  53. package/dist/core/agent-session.d.ts +18 -1
  54. package/dist/core/agent-session.d.ts.map +1 -1
  55. package/dist/core/agent-session.js +119 -3
  56. package/dist/core/agent-session.js.map +1 -1
  57. package/dist/core/retry-fallback/candidates.d.ts +26 -0
  58. package/dist/core/retry-fallback/candidates.d.ts.map +1 -0
  59. package/dist/core/retry-fallback/candidates.js +41 -0
  60. package/dist/core/retry-fallback/candidates.js.map +1 -0
  61. package/dist/core/retry-fallback/circuit-probes.d.ts +30 -0
  62. package/dist/core/retry-fallback/circuit-probes.d.ts.map +1 -0
  63. package/dist/core/retry-fallback/circuit-probes.js +60 -0
  64. package/dist/core/retry-fallback/circuit-probes.js.map +1 -0
  65. package/dist/core/retry-fallback/circuit.d.ts +101 -0
  66. package/dist/core/retry-fallback/circuit.d.ts.map +1 -0
  67. package/dist/core/retry-fallback/circuit.js +175 -0
  68. package/dist/core/retry-fallback/circuit.js.map +1 -0
  69. package/dist/core/retry-fallback/controller-types.d.ts +58 -0
  70. package/dist/core/retry-fallback/controller-types.d.ts.map +1 -0
  71. package/dist/core/retry-fallback/controller-types.js +2 -0
  72. package/dist/core/retry-fallback/controller-types.js.map +1 -0
  73. package/dist/core/retry-fallback/controller.d.ts +21 -49
  74. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  75. package/dist/core/retry-fallback/controller.js +88 -42
  76. package/dist/core/retry-fallback/controller.js.map +1 -1
  77. package/dist/core/sdk.d.ts.map +1 -1
  78. package/dist/core/sdk.js +25 -17
  79. package/dist/core/sdk.js.map +1 -1
  80. package/dist/core/session-failure-report.d.ts +33 -0
  81. package/dist/core/session-failure-report.d.ts.map +1 -0
  82. package/dist/core/session-failure-report.js +62 -0
  83. package/dist/core/session-failure-report.js.map +1 -0
  84. package/dist/core/settings-manager.d.ts +3 -0
  85. package/dist/core/settings-manager.d.ts.map +1 -1
  86. package/dist/core/settings-manager.js +4 -0
  87. package/dist/core/settings-manager.js.map +1 -1
  88. package/dist/core/slash-commands.d.ts.map +1 -1
  89. package/dist/core/slash-commands.js +1 -0
  90. package/dist/core/slash-commands.js.map +1 -1
  91. package/dist/index.d.ts +1 -1
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +1 -1
  94. package/dist/index.js.map +1 -1
  95. package/dist/modes/index.d.ts +1 -0
  96. package/dist/modes/index.d.ts.map +1 -1
  97. package/dist/modes/index.js +2 -0
  98. package/dist/modes/index.js.map +1 -1
  99. package/dist/modes/interactive/help-content.d.ts.map +1 -1
  100. package/dist/modes/interactive/help-content.js +3 -0
  101. package/dist/modes/interactive/help-content.js.map +1 -1
  102. package/dist/modes/interactive/interactive-host-attach.d.ts +10 -0
  103. package/dist/modes/interactive/interactive-host-attach.d.ts.map +1 -0
  104. package/dist/modes/interactive/interactive-host-attach.js +14 -0
  105. package/dist/modes/interactive/interactive-host-attach.js.map +1 -0
  106. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  107. package/dist/modes/interactive/interactive-host-runtime.js +3 -6
  108. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  109. package/dist/modes/interactive/interactive-mode.d.ts +7 -0
  110. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  111. package/dist/modes/interactive/interactive-mode.js +66 -1
  112. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  113. package/dist/modes/interactive/session-failure-info.d.ts +3 -0
  114. package/dist/modes/interactive/session-failure-info.d.ts.map +1 -0
  115. package/dist/modes/interactive/session-failure-info.js +21 -0
  116. package/dist/modes/interactive/session-failure-info.js.map +1 -0
  117. package/dist/modes/interactive/tips/catalog/model-tips.d.ts +7 -7
  118. package/dist/modes/interactive/tips/catalog/model-tips.d.ts.map +1 -1
  119. package/dist/modes/interactive/tips/catalog/model-tips.js +7 -1
  120. package/dist/modes/interactive/tips/catalog/model-tips.js.map +1 -1
  121. package/dist/modes/interactive/tips/catalog/session-tips.d.ts.map +1 -1
  122. package/dist/modes/interactive/tips/catalog/session-tips.js +1 -1
  123. package/dist/modes/interactive/tips/catalog/session-tips.js.map +1 -1
  124. package/dist/modes/rpc/host-attach-hold.d.ts +16 -0
  125. package/dist/modes/rpc/host-attach-hold.d.ts.map +1 -0
  126. package/dist/modes/rpc/host-attach-hold.js +9 -0
  127. package/dist/modes/rpc/host-attach-hold.js.map +1 -0
  128. package/dist/modes/rpc/host-cleanup-paths.d.ts +20 -0
  129. package/dist/modes/rpc/host-cleanup-paths.d.ts.map +1 -0
  130. package/dist/modes/rpc/host-cleanup-paths.js +13 -0
  131. package/dist/modes/rpc/host-cleanup-paths.js.map +1 -0
  132. package/dist/modes/rpc/host-client-occupancy.d.ts +24 -0
  133. package/dist/modes/rpc/host-client-occupancy.d.ts.map +1 -0
  134. package/dist/modes/rpc/host-client-occupancy.js +53 -0
  135. package/dist/modes/rpc/host-client-occupancy.js.map +1 -0
  136. package/dist/modes/rpc/host-daemon-paths.d.ts +61 -8
  137. package/dist/modes/rpc/host-daemon-paths.d.ts.map +1 -1
  138. package/dist/modes/rpc/host-daemon-paths.js +128 -11
  139. package/dist/modes/rpc/host-daemon-paths.js.map +1 -1
  140. package/dist/modes/rpc/host-daemon-registration.d.ts +11 -1
  141. package/dist/modes/rpc/host-daemon-registration.d.ts.map +1 -1
  142. package/dist/modes/rpc/host-daemon-registration.js +28 -15
  143. package/dist/modes/rpc/host-daemon-registration.js.map +1 -1
  144. package/dist/modes/rpc/host-endpoints.d.ts +12 -0
  145. package/dist/modes/rpc/host-endpoints.d.ts.map +1 -0
  146. package/dist/modes/rpc/host-endpoints.js +59 -0
  147. package/dist/modes/rpc/host-endpoints.js.map +1 -0
  148. package/dist/modes/rpc/host-ensure-lock.d.ts +11 -0
  149. package/dist/modes/rpc/host-ensure-lock.d.ts.map +1 -0
  150. package/dist/modes/rpc/host-ensure-lock.js +49 -0
  151. package/dist/modes/rpc/host-ensure-lock.js.map +1 -0
  152. package/dist/modes/rpc/host-ensure.d.ts +7 -0
  153. package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
  154. package/dist/modes/rpc/host-ensure.js +42 -65
  155. package/dist/modes/rpc/host-ensure.js.map +1 -1
  156. package/dist/modes/rpc/host-gc-evidence.d.ts +18 -0
  157. package/dist/modes/rpc/host-gc-evidence.d.ts.map +1 -0
  158. package/dist/modes/rpc/host-gc-evidence.js +128 -0
  159. package/dist/modes/rpc/host-gc-evidence.js.map +1 -0
  160. package/dist/modes/rpc/host-gc.d.ts +28 -0
  161. package/dist/modes/rpc/host-gc.d.ts.map +1 -0
  162. package/dist/modes/rpc/host-gc.js +110 -0
  163. package/dist/modes/rpc/host-gc.js.map +1 -0
  164. package/dist/modes/rpc/host-generations.d.ts +10 -2
  165. package/dist/modes/rpc/host-generations.d.ts.map +1 -1
  166. package/dist/modes/rpc/host-generations.js +12 -7
  167. package/dist/modes/rpc/host-generations.js.map +1 -1
  168. package/dist/modes/rpc/host-handoff.d.ts +6 -0
  169. package/dist/modes/rpc/host-handoff.d.ts.map +1 -1
  170. package/dist/modes/rpc/host-handoff.js +31 -4
  171. package/dist/modes/rpc/host-handoff.js.map +1 -1
  172. package/dist/modes/rpc/host-lifecycle-policy.d.ts +53 -0
  173. package/dist/modes/rpc/host-lifecycle-policy.d.ts.map +1 -0
  174. package/dist/modes/rpc/host-lifecycle-policy.js +64 -0
  175. package/dist/modes/rpc/host-lifecycle-policy.js.map +1 -0
  176. package/dist/modes/rpc/host-lifecycle.d.ts +1 -44
  177. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  178. package/dist/modes/rpc/host-lifecycle.js +23 -85
  179. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  180. package/dist/modes/rpc/host-observe-request.d.ts +17 -0
  181. package/dist/modes/rpc/host-observe-request.d.ts.map +1 -0
  182. package/dist/modes/rpc/host-observe-request.js +30 -0
  183. package/dist/modes/rpc/host-observe-request.js.map +1 -0
  184. package/dist/modes/rpc/host-probe.d.ts +12 -0
  185. package/dist/modes/rpc/host-probe.d.ts.map +1 -1
  186. package/dist/modes/rpc/host-probe.js +28 -1
  187. package/dist/modes/rpc/host-probe.js.map +1 -1
  188. package/dist/modes/rpc/host-protocol-info.d.ts +2 -0
  189. package/dist/modes/rpc/host-protocol-info.d.ts.map +1 -1
  190. package/dist/modes/rpc/host-protocol-info.js +1 -0
  191. package/dist/modes/rpc/host-protocol-info.js.map +1 -1
  192. package/dist/modes/rpc/host-readiness.d.ts +22 -0
  193. package/dist/modes/rpc/host-readiness.d.ts.map +1 -0
  194. package/dist/modes/rpc/host-readiness.js +44 -0
  195. package/dist/modes/rpc/host-readiness.js.map +1 -0
  196. package/dist/modes/rpc/host-runner.d.ts +13 -0
  197. package/dist/modes/rpc/host-runner.d.ts.map +1 -1
  198. package/dist/modes/rpc/host-runner.js +23 -4
  199. package/dist/modes/rpc/host-runner.js.map +1 -1
  200. package/dist/modes/rpc/host-status-all.d.ts +31 -0
  201. package/dist/modes/rpc/host-status-all.d.ts.map +1 -0
  202. package/dist/modes/rpc/host-status-all.js +78 -0
  203. package/dist/modes/rpc/host-status-all.js.map +1 -0
  204. package/dist/modes/rpc/host-status-rows.d.ts +35 -0
  205. package/dist/modes/rpc/host-status-rows.d.ts.map +1 -0
  206. package/dist/modes/rpc/host-status-rows.js +42 -0
  207. package/dist/modes/rpc/host-status-rows.js.map +1 -0
  208. package/dist/modes/rpc/host-status.d.ts +32 -1
  209. package/dist/modes/rpc/host-status.d.ts.map +1 -1
  210. package/dist/modes/rpc/host-status.js +33 -27
  211. package/dist/modes/rpc/host-status.js.map +1 -1
  212. package/dist/modes/rpc/host-successor.d.ts +3 -1
  213. package/dist/modes/rpc/host-successor.d.ts.map +1 -1
  214. package/dist/modes/rpc/host-successor.js +105 -42
  215. package/dist/modes/rpc/host-successor.js.map +1 -1
  216. package/dist/modes/rpc/host-watchdog.d.ts +6 -0
  217. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  218. package/dist/modes/rpc/host-watchdog.js +12 -13
  219. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  220. package/dist/modes/rpc/loop-lag-watchdog.d.ts +16 -0
  221. package/dist/modes/rpc/loop-lag-watchdog.d.ts.map +1 -1
  222. package/dist/modes/rpc/loop-lag-watchdog.js +34 -2
  223. package/dist/modes/rpc/loop-lag-watchdog.js.map +1 -1
  224. package/dist/modes/rpc/multi-session-host.d.ts +2 -1
  225. package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
  226. package/dist/modes/rpc/multi-session-host.js +33 -6
  227. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  228. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  229. package/dist/modes/rpc/rpc-mode.js +2 -2
  230. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  231. package/dist/modes/rpc/rpc-types.d.ts +16 -0
  232. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  233. package/dist/modes/rpc/rpc-types.js.map +1 -1
  234. package/dist/modes/rpc/session-command-router.d.ts +9 -1
  235. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  236. package/dist/modes/rpc/session-command-router.js +4 -1
  237. package/dist/modes/rpc/session-command-router.js.map +1 -1
  238. package/dist/modes/rpc/session-path-key.d.ts +3 -1
  239. package/dist/modes/rpc/session-path-key.d.ts.map +1 -1
  240. package/dist/modes/rpc/session-path-key.js +3 -1
  241. package/dist/modes/rpc/session-path-key.js.map +1 -1
  242. package/docs/rpc.md +186 -19
  243. package/docs/settings.md +25 -1
  244. package/docs/usage.md +5 -2
  245. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
  246. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  247. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  248. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  249. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  250. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts +6 -0
  251. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts.map +1 -1
  252. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js +36 -0
  253. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js.map +1 -1
  254. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +1 -0
  255. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  256. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +14 -1
  257. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  258. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  259. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  260. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  261. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  262. package/package.json +7 -7
  263. package/dist/bundle/chunks/chunk-3DEXCYNN.js +0 -135
  264. package/dist/bundle/chunks/chunk-5X4KSPK5.js +0 -12
  265. package/dist/bundle/chunks/chunk-KMD5ZONU.js +0 -5
  266. package/dist/bundle/chunks/chunk-PEHZYGYA.js +0 -8
  267. package/dist/bundle/chunks/chunk-PRC5XIIH.js +0 -2
  268. package/dist/bundle/chunks/chunk-QKY33652.js +0 -18
  269. package/dist/bundle/chunks/interactive-host-runtime-TIFT2SKR.js +0 -4
  270. package/dist/bundle/chunks/multi-session-host-GRHKB7Q2.js +0 -21
  271. package/dist/bundle/chunks/session-picker-FTFCQYHH.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"host-protocol-info.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-protocol-info.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The `get_protocol_info` answer as a CLIENT reads it: the shape, and the tolerant parse that\n * produces it.\n *\n * This is a BOUNDARY: everything past it is typed, and nothing past it re-validates. Every identity\n * field is optional because a host from an older release answers without them, and an absent field\n * stays absent rather than being defaulted - a guessed `0` generation or a guessed ordinal would be\n * a comparison a client could act on, which is precisely what must not happen (I2). The one field\n * that IS defaulted is `protocolVersion`, to `0`, because no client speaks protocol 0: a reply from\n * before the field existed therefore fails closed instead of matching.\n */\nimport type { EngineOrdinal } from \"../../core/engine-build-identity.ts\";\nimport type { RpcLaunchProfile } from \"./rpc-types.ts\";\n\n/**\n * A `get_protocol_info` answer as a CLIENT reads it. Every identity field is optional because a\n * host from an older release answers without them; absence is read as \"uncomparable\", never as zero.\n */\nexport interface HostProtocolInfo {\n\treadonly protocolVersion: number;\n\t/** Informational only. Nothing in this module compares it. */\n\treadonly serverVersion: string;\n\treadonly capabilities: readonly string[];\n\t/** Which host PROCESS answered. A handoff is complete exactly when this value changes. */\n\treadonly instanceId?: string;\n\t/** Which generation of the daemon that process is; `0` until the first handoff. */\n\treadonly generation?: number;\n\treadonly engineVersion?: string;\n\treadonly engineOrdinal?: EngineOrdinal;\n\treadonly launch_profile?: RpcLaunchProfile;\n}\n\n/** Parses the `data` of a `get_protocol_info` reply. Unknown or malformed identity fields are dropped, not guessed. */\nexport function parseHostProtocolInfo(data: unknown): HostProtocolInfo | undefined {\n\tif (!isRecord(data) || typeof data.serverVersion !== \"string\") return undefined;\n\tif (!Array.isArray(data.capabilities) || !data.capabilities.every((entry) => typeof entry === \"string\")) {\n\t\treturn undefined;\n\t}\n\tconst ordinal = parseOrdinal(data.engineOrdinal);\n\tconst launchProfile = parseLaunchProfile(data.launch_profile);\n\treturn {\n\t\t// A reply without a protocol version predates the field; 0 never matches, so it fails closed.\n\t\tprotocolVersion: typeof data.protocolVersion === \"number\" ? data.protocolVersion : 0,\n\t\tserverVersion: data.serverVersion,\n\t\tcapabilities: data.capabilities,\n\t\t...(typeof data.instanceId === \"string\" && { instanceId: data.instanceId }),\n\t\t...(typeof data.generation === \"number\" &&\n\t\t\tNumber.isSafeInteger(data.generation) && { generation: data.generation }),\n\t\t...(typeof data.engineVersion === \"string\" && { engineVersion: data.engineVersion }),\n\t\t...(ordinal && { engineOrdinal: ordinal }),\n\t\t...(launchProfile && { launch_profile: launchProfile }),\n\t};\n}\n\nfunction parseOrdinal(value: unknown): EngineOrdinal | undefined {\n\tif (!Array.isArray(value) || value.length !== 5 || !value.every((part) => typeof part === \"number\"))\n\t\treturn undefined;\n\treturn [value[0], value[1], value[2], value[3], value[4]];\n}\n\nfunction parseLaunchProfile(value: unknown): RpcLaunchProfile | undefined {\n\tif (!isRecord(value) || typeof value.profile_id !== \"string\" || !isRecord(value.core)) return undefined;\n\tconst { extensions, multi_session, session_runtime } = value.core;\n\tif (!Array.isArray(extensions) || !extensions.every((entry) => typeof entry === \"string\")) return undefined;\n\tif (typeof multi_session !== \"boolean\") return undefined;\n\tif (session_runtime !== \"in-process\" && session_runtime !== \"worker\") return undefined;\n\treturn { profile_id: value.profile_id, core: { extensions, multi_session, session_runtime } };\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
1
+ {"version":3,"file":"host-protocol-info.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-protocol-info.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The `get_protocol_info` answer as a CLIENT reads it: the shape, and the tolerant parse that\n * produces it.\n *\n * This is a BOUNDARY: everything past it is typed, and nothing past it re-validates. Every identity\n * field is optional because a host from an older release answers without them, and an absent field\n * stays absent rather than being defaulted - a guessed `0` generation or a guessed ordinal would be\n * a comparison a client could act on, which is precisely what must not happen (I2). The one field\n * that IS defaulted is `protocolVersion`, to `0`, because no client speaks protocol 0: a reply from\n * before the field existed therefore fails closed instead of matching.\n */\nimport type { EngineOrdinal } from \"../../core/engine-build-identity.ts\";\nimport type { RpcLaunchProfile } from \"./rpc-types.ts\";\n\n/**\n * A `get_protocol_info` answer as a CLIENT reads it. Every identity field is optional because a\n * host from an older release answers without them; absence is read as \"uncomparable\", never as zero.\n */\nexport interface HostProtocolInfo {\n\treadonly protocolVersion: number;\n\t/** Informational only. Nothing in this module compares it. */\n\treadonly serverVersion: string;\n\treadonly capabilities: readonly string[];\n\t/** Which host PROCESS answered. A handoff is complete exactly when this value changes. */\n\treadonly instanceId?: string;\n\t/** Which generation of the daemon that process is; `0` until the first handoff. */\n\treadonly generation?: number;\n\treadonly engineVersion?: string;\n\treadonly engineOrdinal?: EngineOrdinal;\n\treadonly launch_profile?: RpcLaunchProfile;\n\t/** Whether the answering host's memory sampler reads pressure right now; absent on classic and older hosts. */\n\treadonly memory_pressure?: boolean;\n}\n\n/** Parses the `data` of a `get_protocol_info` reply. Unknown or malformed identity fields are dropped, not guessed. */\nexport function parseHostProtocolInfo(data: unknown): HostProtocolInfo | undefined {\n\tif (!isRecord(data) || typeof data.serverVersion !== \"string\") return undefined;\n\tif (!Array.isArray(data.capabilities) || !data.capabilities.every((entry) => typeof entry === \"string\")) {\n\t\treturn undefined;\n\t}\n\tconst ordinal = parseOrdinal(data.engineOrdinal);\n\tconst launchProfile = parseLaunchProfile(data.launch_profile);\n\treturn {\n\t\t// A reply without a protocol version predates the field; 0 never matches, so it fails closed.\n\t\tprotocolVersion: typeof data.protocolVersion === \"number\" ? data.protocolVersion : 0,\n\t\tserverVersion: data.serverVersion,\n\t\tcapabilities: data.capabilities,\n\t\t...(typeof data.instanceId === \"string\" && { instanceId: data.instanceId }),\n\t\t...(typeof data.generation === \"number\" &&\n\t\t\tNumber.isSafeInteger(data.generation) && { generation: data.generation }),\n\t\t...(typeof data.engineVersion === \"string\" && { engineVersion: data.engineVersion }),\n\t\t...(ordinal && { engineOrdinal: ordinal }),\n\t\t...(launchProfile && { launch_profile: launchProfile }),\n\t\t...(typeof data.memory_pressure === \"boolean\" && { memory_pressure: data.memory_pressure }),\n\t};\n}\n\nfunction parseOrdinal(value: unknown): EngineOrdinal | undefined {\n\tif (!Array.isArray(value) || value.length !== 5 || !value.every((part) => typeof part === \"number\"))\n\t\treturn undefined;\n\treturn [value[0], value[1], value[2], value[3], value[4]];\n}\n\nfunction parseLaunchProfile(value: unknown): RpcLaunchProfile | undefined {\n\tif (!isRecord(value) || typeof value.profile_id !== \"string\" || !isRecord(value.core)) return undefined;\n\tconst { extensions, multi_session, session_runtime } = value.core;\n\tif (!Array.isArray(extensions) || !extensions.every((entry) => typeof entry === \"string\")) return undefined;\n\tif (typeof multi_session !== \"boolean\") return undefined;\n\tif (session_runtime !== \"in-process\" && session_runtime !== \"worker\") return undefined;\n\treturn { profile_id: value.profile_id, core: { extensions, multi_session, session_runtime } };\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The readiness gate of a host this process just spawned: poll `get_protocol_info` until the host
3
+ * answers compatibly, the budget runs out, or the spawned supervisor exits. A compatible answer
4
+ * comes with the attach hold of the connection it arrived on (see host-attach-hold.ts).
5
+ */
6
+ import type { HostAttachHold } from "./host-attach-hold.ts";
7
+ import type { HostProtocolInfo } from "./host-decision.ts";
8
+ export type ChildExit = {
9
+ readonly code: number | null;
10
+ readonly signal: NodeJS.Signals | null;
11
+ };
12
+ export type ProtocolPollResult = {
13
+ readonly ready: true;
14
+ readonly protocol: HostProtocolInfo;
15
+ readonly hold: HostAttachHold;
16
+ } | {
17
+ readonly ready: false;
18
+ readonly protocol?: HostProtocolInfo;
19
+ readonly exited?: ChildExit;
20
+ };
21
+ export declare function pollProtocolInfo(socket: string, timeoutMs: number, isCompatible: (protocol: HostProtocolInfo | undefined) => boolean, childExit?: Promise<ChildExit>): Promise<ProtocolPollResult>;
22
+ //# sourceMappingURL=host-readiness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-readiness.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-readiness.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,MAAM,MAAM,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;CAAE,CAAC;AAEjG,MAAM,MAAM,kBAAkB,GAC3B;IAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GAC5F;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC;AAIhG,wBAAsB,gBAAgB,CACrC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,CAAC,QAAQ,EAAE,gBAAgB,GAAG,SAAS,KAAK,OAAO,EACjE,SAAS,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CA4B7B"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The readiness gate of a host this process just spawned: poll `get_protocol_info` until the host
3
+ * answers compatibly, the budget runs out, or the spawned supervisor exits. A compatible answer
4
+ * comes with the attach hold of the connection it arrived on (see host-attach-hold.ts).
5
+ */
6
+ import { holdProtocolInfo } from "./host-probe.js";
7
+ const SPAWNED_HOST_PROBE_TIMEOUT_MS = 10_000;
8
+ export async function pollProtocolInfo(socket, timeoutMs, isCompatible, childExit) {
9
+ const deadline = Date.now() + timeoutMs;
10
+ let lastProtocol;
11
+ while (Date.now() <= deadline) {
12
+ const probe = holdProtocolInfo(socket, Math.min(SPAWNED_HOST_PROBE_TIMEOUT_MS, Math.max(1, deadline - Date.now())));
13
+ const raced = childExit ? await Promise.race([probe, childExit]) : await probe;
14
+ if (isChildExit(raced)) {
15
+ // A supervisor exit can be triggered by the Windows identity watchdog
16
+ // while a named-pipe client is still composing its protocol reply. Do
17
+ // not terminate the host based solely on that exit until this probe has
18
+ // had a chance to deliver an answer. A host that never answers still
19
+ // resolves through holdProtocolInfo's bounded timeout/close handling.
20
+ const held = await probe;
21
+ if (!held)
22
+ return { ready: false, protocol: lastProtocol, exited: raced };
23
+ lastProtocol = held.info;
24
+ if (isCompatible(held.info))
25
+ return { ready: true, protocol: held.info, hold: held.hold };
26
+ held.hold.release();
27
+ }
28
+ else if (raced) {
29
+ lastProtocol = raced.info;
30
+ if (isCompatible(raced.info))
31
+ return { ready: true, protocol: raced.info, hold: raced.hold };
32
+ raced.hold.release();
33
+ }
34
+ await delay(50);
35
+ }
36
+ return { ready: false, protocol: lastProtocol };
37
+ }
38
+ function isChildExit(value) {
39
+ return !!value && "code" in value && "signal" in value;
40
+ }
41
+ function delay(ms) {
42
+ return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
43
+ }
44
+ //# sourceMappingURL=host-readiness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-readiness.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-readiness.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAQnD,MAAM,6BAA6B,GAAG,MAAM,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,MAAc,EACd,SAAiB,EACjB,YAAiE,EACjE,SAA8B;IAE9B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,IAAI,YAA0C,CAAC;IAC/C,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,gBAAgB,CAC7B,MAAM,EACN,IAAI,CAAC,GAAG,CAAC,6BAA6B,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAC3E,CAAC;QACF,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC;QAC/E,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,sEAAsE;YACtE,sEAAsE;YACtE,wEAAwE;YACxE,qEAAqE;YACrE,sEAAsE;YACtE,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC;YACzB,IAAI,CAAC,IAAI;gBAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;YAC1E,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC;YACzB,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;YAC1F,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,CAAC;aAAM,IAAI,KAAK,EAAE,CAAC;YAClB,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC;YAC1B,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;YAC7F,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACtB,CAAC;QACD,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;AACjD,CAAC;AAED,SAAS,WAAW,CAAC,KAAkE;IACtF,OAAO,CAAC,CAAC,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,CAAC;AACxD,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACxB,OAAO,IAAI,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,UAAU,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC;AACpE,CAAC","sourcesContent":["/**\n * The readiness gate of a host this process just spawned: poll `get_protocol_info` until the host\n * answers compatibly, the budget runs out, or the spawned supervisor exits. A compatible answer\n * comes with the attach hold of the connection it arrived on (see host-attach-hold.ts).\n */\n\nimport type { HostAttachHold } from \"./host-attach-hold.ts\";\nimport type { HostProtocolInfo } from \"./host-decision.ts\";\nimport { holdProtocolInfo } from \"./host-probe.ts\";\n\nexport type ChildExit = { readonly code: number | null; readonly signal: NodeJS.Signals | null };\n\nexport type ProtocolPollResult =\n\t| { readonly ready: true; readonly protocol: HostProtocolInfo; readonly hold: HostAttachHold }\n\t| { readonly ready: false; readonly protocol?: HostProtocolInfo; readonly exited?: ChildExit };\n\nconst SPAWNED_HOST_PROBE_TIMEOUT_MS = 10_000;\n\nexport async function pollProtocolInfo(\n\tsocket: string,\n\ttimeoutMs: number,\n\tisCompatible: (protocol: HostProtocolInfo | undefined) => boolean,\n\tchildExit?: Promise<ChildExit>,\n): Promise<ProtocolPollResult> {\n\tconst deadline = Date.now() + timeoutMs;\n\tlet lastProtocol: HostProtocolInfo | undefined;\n\twhile (Date.now() <= deadline) {\n\t\tconst probe = holdProtocolInfo(\n\t\t\tsocket,\n\t\t\tMath.min(SPAWNED_HOST_PROBE_TIMEOUT_MS, Math.max(1, deadline - Date.now())),\n\t\t);\n\t\tconst raced = childExit ? await Promise.race([probe, childExit]) : await probe;\n\t\tif (isChildExit(raced)) {\n\t\t\t// A supervisor exit can be triggered by the Windows identity watchdog\n\t\t\t// while a named-pipe client is still composing its protocol reply. Do\n\t\t\t// not terminate the host based solely on that exit until this probe has\n\t\t\t// had a chance to deliver an answer. A host that never answers still\n\t\t\t// resolves through holdProtocolInfo's bounded timeout/close handling.\n\t\t\tconst held = await probe;\n\t\t\tif (!held) return { ready: false, protocol: lastProtocol, exited: raced };\n\t\t\tlastProtocol = held.info;\n\t\t\tif (isCompatible(held.info)) return { ready: true, protocol: held.info, hold: held.hold };\n\t\t\theld.hold.release();\n\t\t} else if (raced) {\n\t\t\tlastProtocol = raced.info;\n\t\t\tif (isCompatible(raced.info)) return { ready: true, protocol: raced.info, hold: raced.hold };\n\t\t\traced.hold.release();\n\t\t}\n\t\tawait delay(50);\n\t}\n\treturn { ready: false, protocol: lastProtocol };\n}\n\nfunction isChildExit(value: { readonly info: HostProtocolInfo } | ChildExit | undefined): value is ChildExit {\n\treturn !!value && \"code\" in value && \"signal\" in value;\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolveDelay) => setTimeout(resolveDelay, ms));\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { type ShardKind } from "./host-daemon-paths.ts";
1
2
  import { type HostDecisionPolicy } from "./host-decision.ts";
2
3
  import type { ResolvedHostLaunchSpec } from "./host-launch-spec.ts";
3
4
  export declare const HOST_EXIT_OK = 0;
@@ -19,6 +20,8 @@ export type HostRequest = {
19
20
  readonly action: "status";
20
21
  readonly target: HostTarget;
21
22
  readonly includeWorkers: boolean;
23
+ /** Every endpoint under `target.agentDir` (its socket is ignored), read without pruning. */
24
+ readonly all?: boolean;
22
25
  } | {
23
26
  readonly action: "stop";
24
27
  readonly target: HostTarget;
@@ -28,6 +31,16 @@ export type HostRequest = {
28
31
  readonly action: "handoff";
29
32
  readonly target: HostTarget;
30
33
  readonly spec: ResolvedHostLaunchSpec;
34
+ }
35
+ /** Removes endpoint state under `agentDir` only on the three-part evidence (`host-gc.ts`). */
36
+ | {
37
+ readonly action: "gc";
38
+ readonly agentDir: string;
39
+ } | {
40
+ readonly action: "shard_path";
41
+ readonly kind: ShardKind;
42
+ readonly owner: string;
43
+ readonly root: string;
31
44
  };
32
45
  /** One JSON line and the exit code it means. */
33
46
  export interface HostOutcome {
@@ -1 +1 @@
1
- {"version":3,"file":"host-runner.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-runner.ts"],"names":[],"mappings":"AAoBA,OAAO,EAIN,KAAK,kBAAkB,EAIvB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAKpE,eAAO,MAAM,YAAY,IAAI,CAAC;AAC9B,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAIpC,gGAAgG;AAChG,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,WAAW,GACpB;IACA,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;CACnC,GACD;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAC5F;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC1G;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAA;CAAE,CAAC;AAEtG,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAa/E"}
1
+ {"version":3,"file":"host-runner.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-runner.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAyB,KAAK,SAAS,EAAmC,MAAM,wBAAwB,CAAC;AAChH,OAAO,EAIN,KAAK,kBAAkB,EAIvB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAMpE,eAAO,MAAM,YAAY,IAAI,CAAC;AAC9B,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAIpC,gGAAgG;AAChG,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,WAAW,GACpB;IACA,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;CACnC,GACD;IACA,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,4FAA4F;IAC5F,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACtB,GACD;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC1G;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAA;CAAE;AACpG,8FAA8F;GAC5F;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9G,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAmB/E"}
@@ -10,19 +10,21 @@
10
10
  * 4 no host is better than this one an unreachable socket, or a stop that would end
11
11
  * one (fallback policy) somebody else's work
12
12
  *
13
- * The refusals are the point. One machine-wide daemon holds every client's sessions, so a client
13
+ * The refusals are the point. One daemon holds every client's sessions on its endpoint, so a client
14
14
  * that cannot get what it wants must SAY so and leave the running host alone (I1) - never stop it,
15
15
  * never bind a second host over its socket. `decideHostAction`, `ensureHost`, `stopHost` and
16
16
  * `handoffHost` already enforce that; this module only gives their outcomes a machine-readable shape.
17
17
  */
18
18
  import { engineBuildIdentity } from "../../core/engine-build-identity.js";
19
19
  import { daemonEnvKeys, daemonEnvOverrides, writeDaemonEnvKeys } from "./host-daemon-env.js";
20
- import { createHostDaemonPaths } from "./host-daemon-paths.js";
20
+ import { createHostDaemonPaths, shardKey, shardSocketPathForKey } from "./host-daemon-paths.js";
21
21
  import { decideHostAction, HOST_PROTOCOL_VERSION, HostEnsureRefusedError, REQUIRED_HOST_CAPABILITIES, } from "./host-decision.js";
22
22
  import { ensureHost } from "./host-ensure.js";
23
+ import { gcHostEndpoints } from "./host-gc.js";
23
24
  import { handoffHost } from "./host-handoff.js";
24
25
  import { probeProtocolInfo } from "./host-probe.js";
25
26
  import { hostSummary, readHostStatus, readSessionCounts } from "./host-status.js";
27
+ import { readAllHostStatus } from "./host-status-all.js";
26
28
  import { stopHost } from "./host-stop.js";
27
29
  export const HOST_EXIT_OK = 0;
28
30
  export const HOST_EXIT_ERROR = 1;
@@ -35,11 +37,17 @@ export async function runHostRequest(request) {
35
37
  case "ensure":
36
38
  return ensureOutcome(request.target, request.spec, request.policy);
37
39
  case "status":
38
- return statusOutcome(request.target, request.includeWorkers);
40
+ return request.all === true
41
+ ? statusAllOutcome(request.target, request.includeWorkers)
42
+ : statusOutcome(request.target, request.includeWorkers);
39
43
  case "stop":
40
44
  return stopOutcome(request.target, request.drain, request.force);
41
45
  case "handoff":
42
46
  return handoffOutcome(request.target, request.spec);
47
+ case "shard_path":
48
+ return shardPathOutcome(request.kind, request.owner, request.root);
49
+ case "gc":
50
+ return { exitCode: HOST_EXIT_OK, payload: { ...(await gcHostEndpoints(request.agentDir)) } };
43
51
  default:
44
52
  return assertNever(request);
45
53
  }
@@ -70,7 +78,7 @@ async function ensureOutcome(target, spec, policy) {
70
78
  throw error;
71
79
  return refusal("refuse", before, { reason: error.reason, socket: target.socket });
72
80
  }
73
- const host = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS);
81
+ const host = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS).finally(ensured.release);
74
82
  // Only a spawn grants an environment; a reuse inherited whatever the client that started it did.
75
83
  if (!ensured.reused)
76
84
  await recordEnvScope(target, spec);
@@ -96,6 +104,17 @@ async function statusOutcome(target, includeWorkers) {
96
104
  const status = await readHostStatus({ socket: target.socket, agentDir: target.agentDir, includeWorkers });
97
105
  return { exitCode: status.reachable ? HOST_EXIT_OK : HOST_EXIT_REFUSED, payload: { ...status } };
98
106
  }
107
+ /** Same exit vocabulary as one socket: 0 while anything answers, 3 when nothing does (or nothing is there). */
108
+ async function statusAllOutcome(target, includeWorkers) {
109
+ const endpoints = await readAllHostStatus({ agentDir: target.agentDir, includeWorkers });
110
+ const reachable = endpoints.some((endpoint) => endpoint.reachable);
111
+ return { exitCode: reachable ? HOST_EXIT_OK : HOST_EXIT_REFUSED, payload: { endpoints } };
112
+ }
113
+ /** The naming contract answered without contacting any host, for a client that mirrors it locally. */
114
+ function shardPathOutcome(kind, owner, root) {
115
+ const key = shardKey(kind, owner);
116
+ return { exitCode: HOST_EXIT_OK, payload: { kind, key, socket: shardSocketPathForKey(root, kind, key) } };
117
+ }
99
118
  /**
100
119
  * A hard stop ends every session the daemon holds, including the ones other clients are attached to,
101
120
  * so it is gated on the host's own count and refuses with those counts rather than a bare "busy".
@@ -1 +1 @@
1
- {"version":3,"file":"host-runner.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One host request, performed: ensure a daemon, report one, stop one, or hand one off.\n *\n * This is the whole behaviour behind `senpi host` - and behind the launchers that call the same\n * entry point in-process - separated from the command line that expresses it. Every request answers\n * with a JSON payload plus the exit code that payload means, so a CLI, a desktop and a task runner\n * report the same outcome with the same vocabulary:\n *\n * 0 the request happened 3 refused: a running host this client may not act on,\n * 4 no host is better than this one an unreachable socket, or a stop that would end\n * one (fallback policy) somebody else's work\n *\n * The refusals are the point. One machine-wide daemon holds every client's sessions, so a client\n * that cannot get what it wants must SAY so and leave the running host alone (I1) - never stop it,\n * never bind a second host over its socket. `decideHostAction`, `ensureHost`, `stopHost` and\n * `handoffHost` already enforce that; this module only gives their outcomes a machine-readable shape.\n */\nimport { engineBuildIdentity } from \"../../core/engine-build-identity.ts\";\nimport { daemonEnvKeys, daemonEnvOverrides, writeDaemonEnvKeys } from \"./host-daemon-env.ts\";\nimport { createHostDaemonPaths } from \"./host-daemon-paths.ts\";\nimport {\n\tdecideHostAction,\n\tHOST_PROTOCOL_VERSION,\n\ttype HostDecisionClient,\n\ttype HostDecisionPolicy,\n\tHostEnsureRefusedError,\n\ttype HostProtocolInfo,\n\tREQUIRED_HOST_CAPABILITIES,\n} from \"./host-decision.ts\";\nimport { type EnsuredHost, ensureHost } from \"./host-ensure.ts\";\nimport { type HandoffRefusal, handoffHost } from \"./host-handoff.ts\";\nimport type { ResolvedHostLaunchSpec } from \"./host-launch-spec.ts\";\nimport { probeProtocolInfo } from \"./host-probe.ts\";\nimport { hostSummary, readHostStatus, readSessionCounts } from \"./host-status.ts\";\nimport { stopHost } from \"./host-stop.ts\";\n\nexport const HOST_EXIT_OK = 0;\nexport const HOST_EXIT_ERROR = 1;\nexport const HOST_EXIT_USAGE = 2;\nexport const HOST_EXIT_REFUSED = 3;\nexport const HOST_EXIT_FALLBACK = 4;\n\nconst PROBE_TIMEOUT_MS = 10_000;\n\n/** Which daemon a request is about: the endpoint, and the agent directory holding its state. */\nexport interface HostTarget {\n\treadonly socket: string;\n\treadonly agentDir: string;\n}\n\nexport type HostRequest =\n\t| {\n\t\t\treadonly action: \"ensure\";\n\t\t\treadonly target: HostTarget;\n\t\t\treadonly spec: ResolvedHostLaunchSpec;\n\t\t\treadonly policy: HostDecisionPolicy;\n\t }\n\t| { readonly action: \"status\"; readonly target: HostTarget; readonly includeWorkers: boolean }\n\t| { readonly action: \"stop\"; readonly target: HostTarget; readonly drain: boolean; readonly force: boolean }\n\t| { readonly action: \"handoff\"; readonly target: HostTarget; readonly spec: ResolvedHostLaunchSpec };\n\n/** One JSON line and the exit code it means. */\nexport interface HostOutcome {\n\treadonly exitCode: number;\n\treadonly payload: Record<string, unknown>;\n}\n\nexport async function runHostRequest(request: HostRequest): Promise<HostOutcome> {\n\tswitch (request.action) {\n\t\tcase \"ensure\":\n\t\t\treturn ensureOutcome(request.target, request.spec, request.policy);\n\t\tcase \"status\":\n\t\t\treturn statusOutcome(request.target, request.includeWorkers);\n\t\tcase \"stop\":\n\t\t\treturn stopOutcome(request.target, request.drain, request.force);\n\t\tcase \"handoff\":\n\t\t\treturn handoffOutcome(request.target, request.spec);\n\t\tdefault:\n\t\t\treturn assertNever(request);\n\t}\n}\n\nasync function ensureOutcome(\n\ttarget: HostTarget,\n\tspec: ResolvedHostLaunchSpec,\n\tpolicy: HostDecisionPolicy,\n): Promise<HostOutcome> {\n\tconst before = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS);\n\tif (policy === \"fallback\") {\n\t\t// `fallback` is the caller saying it can live without a host. The decision is made HERE\n\t\t// because an ensure always produces a host or fails: it has no fallback answer of its own.\n\t\tconst decision = decideHostAction(decisionClient(), before, \"fallback\");\n\t\tif (decision.action === \"fallback\") {\n\t\t\treturn refusal(\"fallback\", before, { reason: decision.reason, socket: target.socket });\n\t\t}\n\t}\n\tlet ensured: EnsuredHost;\n\ttry {\n\t\tensured = await ensureHost({\n\t\t\tsocket: target.socket,\n\t\t\tagentDir: target.agentDir,\n\t\t\thostArgs: spec.hostArgs,\n\t\t\tenv: daemonEnvOverrides(process.env, spec.env),\n\t\t\tpolicy: spec.policy,\n\t\t\tupgrade: policy === \"upgrade\" ? \"if-engine-differs\" : \"never\",\n\t\t});\n\t} catch (error: unknown) {\n\t\tif (!(error instanceof HostEnsureRefusedError)) throw error;\n\t\treturn refusal(\"refuse\", before, { reason: error.reason, socket: target.socket });\n\t}\n\tconst host = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS);\n\t// Only a spawn grants an environment; a reuse inherited whatever the client that started it did.\n\tif (!ensured.reused) await recordEnvScope(target, spec);\n\treturn {\n\t\texitCode: HOST_EXIT_OK,\n\t\tpayload: {\n\t\t\taction: ensureAction(ensured.reused, before, host),\n\t\t\t...identityPayload(target.socket, ensured.pid, host),\n\t\t\treused: ensured.reused,\n\t\t},\n\t};\n}\n\n/**\n * What an ensure DID. A handoff and a fresh start both report `reused: false`, so the instance id is\n * what tells them apart: a handoff replaced the process behind a socket somebody was already serving.\n */\nfunction ensureAction(\n\treused: boolean,\n\tbefore: HostProtocolInfo | undefined,\n\tafter: HostProtocolInfo | undefined,\n): string {\n\tif (reused) return \"reuse\";\n\treturn before !== undefined && before.instanceId !== after?.instanceId ? \"handoff\" : \"start\";\n}\n\nasync function statusOutcome(target: HostTarget, includeWorkers: boolean): Promise<HostOutcome> {\n\tconst status = await readHostStatus({ socket: target.socket, agentDir: target.agentDir, includeWorkers });\n\treturn { exitCode: status.reachable ? HOST_EXIT_OK : HOST_EXIT_REFUSED, payload: { ...status } };\n}\n\n/**\n * A hard stop ends every session the daemon holds, including the ones other clients are attached to,\n * so it is gated on the host's own count and refuses with those counts rather than a bare \"busy\".\n * A DRAIN ends no work - the host stops accepting and leaves when it is empty - so it is never gated.\n */\nasync function stopOutcome(target: HostTarget, drain: boolean, force: boolean): Promise<HostOutcome> {\n\tconst host = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS);\n\tconst sessions = await readSessionCounts(target.socket, true);\n\tconst occupied = sessions.foreign_attached + sessions.foreign_retained > 0;\n\tif (!drain && !force && occupied) {\n\t\treturn refusal(\"refuse\", host, { reason: \"sessions_live\", socket: target.socket, sessions });\n\t}\n\tconst result = await stopHost({ socket: target.socket, agentDir: target.agentDir, drain, force });\n\tif (result.action === \"refuse\") {\n\t\treturn refusal(\"refuse\", host, { reason: result.reason, socket: target.socket, sessions });\n\t}\n\treturn {\n\t\texitCode: HOST_EXIT_OK,\n\t\tpayload: { action: result.action, socket: target.socket, pid: result.pid, sessions },\n\t};\n}\n\nasync function handoffOutcome(target: HostTarget, spec: ResolvedHostLaunchSpec): Promise<HostOutcome> {\n\tconst before = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS);\n\tconst result = await handoffHost({\n\t\tsocket: target.socket,\n\t\tagentDir: target.agentDir,\n\t\thostArgs: spec.hostArgs,\n\t\tenv: daemonEnvOverrides(process.env, spec.env),\n\t\tpolicy: spec.policy,\n\t});\n\tif (result.action === \"refuse\") {\n\t\treturn refusal(\"refuse\", before, {\n\t\t\treason: upgradeRefusal(result.reason),\n\t\t\tsocket: target.socket,\n\t\t\tdetail: result.reason,\n\t\t\tupgradeable: result.upgradeable,\n\t\t});\n\t}\n\tawait recordEnvScope(target, spec);\n\tconst host = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS);\n\treturn {\n\t\texitCode: HOST_EXIT_OK,\n\t\tpayload: { action: \"handoff\", ...identityPayload(target.socket, result.pid, host), reused: false },\n\t};\n}\n\n/**\n * A host that cannot drain and a platform that cannot hand off are one answer to the caller: this\n * build may not replace the running generation. The refusal that produced it stays in `detail`.\n */\nfunction upgradeRefusal(reason: HandoffRefusal): string {\n\treturn reason === \"handoff_unsupported\" ? \"upgrade_unsupported\" : reason;\n}\n\nfunction identityPayload(socket: string, pid: number, host: HostProtocolInfo | undefined): Record<string, unknown> {\n\treturn {\n\t\tsocket,\n\t\tpid,\n\t\tinstanceId: host?.instanceId ?? null,\n\t\tgeneration: host?.generation ?? null,\n\t\tengineVersion: host?.engineVersion ?? null,\n\t\tengineOrdinal: host?.engineOrdinal ?? null,\n\t\tcapabilities: host?.capabilities ?? [],\n\t\tlaunchProfileId: host?.launch_profile?.profile_id ?? null,\n\t\tupgradeable: decideHostAction(decisionClient(), host, \"upgrade\").upgradeable,\n\t};\n}\n\n/**\n * The two ways a request ends without acting, and the exit codes they mean: a `refuse` leaves a\n * running host alone (3), a `fallback` says no host is better than this one (4).\n */\nfunction refusal(\n\tkind: \"refuse\" | \"fallback\",\n\thost: HostProtocolInfo | undefined,\n\tbody: { readonly reason: string } & Record<string, unknown>,\n): HostOutcome {\n\treturn {\n\t\texitCode: kind === \"refuse\" ? HOST_EXIT_REFUSED : HOST_EXIT_FALLBACK,\n\t\tpayload: { action: kind, ...body, host: hostSummary(host) },\n\t};\n}\n\nasync function recordEnvScope(target: HostTarget, spec: ResolvedHostLaunchSpec): Promise<void> {\n\tconst paths = createHostDaemonPaths({ socket: target.socket, agentDir: target.agentDir });\n\tawait writeDaemonEnvKeys(paths, daemonEnvKeys(process.env, spec.env));\n}\n\n/**\n * This build as a client, WITHOUT a launch profile: the request's own `ensureHost` decides whether a\n * handoff may happen, from the profile it would launch. What is asked here is only what any client\n * can answer without one - is the running host usable, and could it be handed off from at all.\n */\nfunction decisionClient(): HostDecisionClient {\n\treturn {\n\t\tprotocolVersion: HOST_PROTOCOL_VERSION,\n\t\trequiredCapabilities: REQUIRED_HOST_CAPABILITIES,\n\t\tidentity: engineBuildIdentity(),\n\t\tstartedByUs: false,\n\t\tplatform: process.platform,\n\t};\n}\n\nfunction assertNever(value: never): never {\n\tthrow new Error(`unreachable host request: ${JSON.stringify(value)}`);\n}\n"]}
1
+ {"version":3,"file":"host-runner.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One host request, performed: ensure a daemon, report one, stop one, or hand one off.\n *\n * This is the whole behaviour behind `senpi host` - and behind the launchers that call the same\n * entry point in-process - separated from the command line that expresses it. Every request answers\n * with a JSON payload plus the exit code that payload means, so a CLI, a desktop and a task runner\n * report the same outcome with the same vocabulary:\n *\n * 0 the request happened 3 refused: a running host this client may not act on,\n * 4 no host is better than this one an unreachable socket, or a stop that would end\n * one (fallback policy) somebody else's work\n *\n * The refusals are the point. One daemon holds every client's sessions on its endpoint, so a client\n * that cannot get what it wants must SAY so and leave the running host alone (I1) - never stop it,\n * never bind a second host over its socket. `decideHostAction`, `ensureHost`, `stopHost` and\n * `handoffHost` already enforce that; this module only gives their outcomes a machine-readable shape.\n */\nimport { engineBuildIdentity } from \"../../core/engine-build-identity.ts\";\nimport { daemonEnvKeys, daemonEnvOverrides, writeDaemonEnvKeys } from \"./host-daemon-env.ts\";\nimport { createHostDaemonPaths, type ShardKind, shardKey, shardSocketPathForKey } from \"./host-daemon-paths.ts\";\nimport {\n\tdecideHostAction,\n\tHOST_PROTOCOL_VERSION,\n\ttype HostDecisionClient,\n\ttype HostDecisionPolicy,\n\tHostEnsureRefusedError,\n\ttype HostProtocolInfo,\n\tREQUIRED_HOST_CAPABILITIES,\n} from \"./host-decision.ts\";\nimport { type EnsuredHost, ensureHost } from \"./host-ensure.ts\";\nimport { gcHostEndpoints } from \"./host-gc.ts\";\nimport { type HandoffRefusal, handoffHost } from \"./host-handoff.ts\";\nimport type { ResolvedHostLaunchSpec } from \"./host-launch-spec.ts\";\nimport { probeProtocolInfo } from \"./host-probe.ts\";\nimport { hostSummary, readHostStatus, readSessionCounts } from \"./host-status.ts\";\nimport { readAllHostStatus } from \"./host-status-all.ts\";\nimport { stopHost } from \"./host-stop.ts\";\n\nexport const HOST_EXIT_OK = 0;\nexport const HOST_EXIT_ERROR = 1;\nexport const HOST_EXIT_USAGE = 2;\nexport const HOST_EXIT_REFUSED = 3;\nexport const HOST_EXIT_FALLBACK = 4;\n\nconst PROBE_TIMEOUT_MS = 10_000;\n\n/** Which daemon a request is about: the endpoint, and the agent directory holding its state. */\nexport interface HostTarget {\n\treadonly socket: string;\n\treadonly agentDir: string;\n}\n\nexport type HostRequest =\n\t| {\n\t\t\treadonly action: \"ensure\";\n\t\t\treadonly target: HostTarget;\n\t\t\treadonly spec: ResolvedHostLaunchSpec;\n\t\t\treadonly policy: HostDecisionPolicy;\n\t }\n\t| {\n\t\t\treadonly action: \"status\";\n\t\t\treadonly target: HostTarget;\n\t\t\treadonly includeWorkers: boolean;\n\t\t\t/** Every endpoint under `target.agentDir` (its socket is ignored), read without pruning. */\n\t\t\treadonly all?: boolean;\n\t }\n\t| { readonly action: \"stop\"; readonly target: HostTarget; readonly drain: boolean; readonly force: boolean }\n\t| { readonly action: \"handoff\"; readonly target: HostTarget; readonly spec: ResolvedHostLaunchSpec }\n\t/** Removes endpoint state under `agentDir` only on the three-part evidence (`host-gc.ts`). */\n\t| { readonly action: \"gc\"; readonly agentDir: string }\n\t| { readonly action: \"shard_path\"; readonly kind: ShardKind; readonly owner: string; readonly root: string };\n\n/** One JSON line and the exit code it means. */\nexport interface HostOutcome {\n\treadonly exitCode: number;\n\treadonly payload: Record<string, unknown>;\n}\n\nexport async function runHostRequest(request: HostRequest): Promise<HostOutcome> {\n\tswitch (request.action) {\n\t\tcase \"ensure\":\n\t\t\treturn ensureOutcome(request.target, request.spec, request.policy);\n\t\tcase \"status\":\n\t\t\treturn request.all === true\n\t\t\t\t? statusAllOutcome(request.target, request.includeWorkers)\n\t\t\t\t: statusOutcome(request.target, request.includeWorkers);\n\t\tcase \"stop\":\n\t\t\treturn stopOutcome(request.target, request.drain, request.force);\n\t\tcase \"handoff\":\n\t\t\treturn handoffOutcome(request.target, request.spec);\n\t\tcase \"shard_path\":\n\t\t\treturn shardPathOutcome(request.kind, request.owner, request.root);\n\t\tcase \"gc\":\n\t\t\treturn { exitCode: HOST_EXIT_OK, payload: { ...(await gcHostEndpoints(request.agentDir)) } };\n\t\tdefault:\n\t\t\treturn assertNever(request);\n\t}\n}\n\nasync function ensureOutcome(\n\ttarget: HostTarget,\n\tspec: ResolvedHostLaunchSpec,\n\tpolicy: HostDecisionPolicy,\n): Promise<HostOutcome> {\n\tconst before = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS);\n\tif (policy === \"fallback\") {\n\t\t// `fallback` is the caller saying it can live without a host. The decision is made HERE\n\t\t// because an ensure always produces a host or fails: it has no fallback answer of its own.\n\t\tconst decision = decideHostAction(decisionClient(), before, \"fallback\");\n\t\tif (decision.action === \"fallback\") {\n\t\t\treturn refusal(\"fallback\", before, { reason: decision.reason, socket: target.socket });\n\t\t}\n\t}\n\tlet ensured: EnsuredHost;\n\ttry {\n\t\tensured = await ensureHost({\n\t\t\tsocket: target.socket,\n\t\t\tagentDir: target.agentDir,\n\t\t\thostArgs: spec.hostArgs,\n\t\t\tenv: daemonEnvOverrides(process.env, spec.env),\n\t\t\tpolicy: spec.policy,\n\t\t\tupgrade: policy === \"upgrade\" ? \"if-engine-differs\" : \"never\",\n\t\t});\n\t} catch (error: unknown) {\n\t\tif (!(error instanceof HostEnsureRefusedError)) throw error;\n\t\treturn refusal(\"refuse\", before, { reason: error.reason, socket: target.socket });\n\t}\n\tconst host = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS).finally(ensured.release);\n\t// Only a spawn grants an environment; a reuse inherited whatever the client that started it did.\n\tif (!ensured.reused) await recordEnvScope(target, spec);\n\treturn {\n\t\texitCode: HOST_EXIT_OK,\n\t\tpayload: {\n\t\t\taction: ensureAction(ensured.reused, before, host),\n\t\t\t...identityPayload(target.socket, ensured.pid, host),\n\t\t\treused: ensured.reused,\n\t\t},\n\t};\n}\n\n/**\n * What an ensure DID. A handoff and a fresh start both report `reused: false`, so the instance id is\n * what tells them apart: a handoff replaced the process behind a socket somebody was already serving.\n */\nfunction ensureAction(\n\treused: boolean,\n\tbefore: HostProtocolInfo | undefined,\n\tafter: HostProtocolInfo | undefined,\n): string {\n\tif (reused) return \"reuse\";\n\treturn before !== undefined && before.instanceId !== after?.instanceId ? \"handoff\" : \"start\";\n}\n\nasync function statusOutcome(target: HostTarget, includeWorkers: boolean): Promise<HostOutcome> {\n\tconst status = await readHostStatus({ socket: target.socket, agentDir: target.agentDir, includeWorkers });\n\treturn { exitCode: status.reachable ? HOST_EXIT_OK : HOST_EXIT_REFUSED, payload: { ...status } };\n}\n\n/** Same exit vocabulary as one socket: 0 while anything answers, 3 when nothing does (or nothing is there). */\nasync function statusAllOutcome(target: HostTarget, includeWorkers: boolean): Promise<HostOutcome> {\n\tconst endpoints = await readAllHostStatus({ agentDir: target.agentDir, includeWorkers });\n\tconst reachable = endpoints.some((endpoint) => endpoint.reachable);\n\treturn { exitCode: reachable ? HOST_EXIT_OK : HOST_EXIT_REFUSED, payload: { endpoints } };\n}\n\n/** The naming contract answered without contacting any host, for a client that mirrors it locally. */\nfunction shardPathOutcome(kind: ShardKind, owner: string, root: string): HostOutcome {\n\tconst key = shardKey(kind, owner);\n\treturn { exitCode: HOST_EXIT_OK, payload: { kind, key, socket: shardSocketPathForKey(root, kind, key) } };\n}\n\n/**\n * A hard stop ends every session the daemon holds, including the ones other clients are attached to,\n * so it is gated on the host's own count and refuses with those counts rather than a bare \"busy\".\n * A DRAIN ends no work - the host stops accepting and leaves when it is empty - so it is never gated.\n */\nasync function stopOutcome(target: HostTarget, drain: boolean, force: boolean): Promise<HostOutcome> {\n\tconst host = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS);\n\tconst sessions = await readSessionCounts(target.socket, true);\n\tconst occupied = sessions.foreign_attached + sessions.foreign_retained > 0;\n\tif (!drain && !force && occupied) {\n\t\treturn refusal(\"refuse\", host, { reason: \"sessions_live\", socket: target.socket, sessions });\n\t}\n\tconst result = await stopHost({ socket: target.socket, agentDir: target.agentDir, drain, force });\n\tif (result.action === \"refuse\") {\n\t\treturn refusal(\"refuse\", host, { reason: result.reason, socket: target.socket, sessions });\n\t}\n\treturn {\n\t\texitCode: HOST_EXIT_OK,\n\t\tpayload: { action: result.action, socket: target.socket, pid: result.pid, sessions },\n\t};\n}\n\nasync function handoffOutcome(target: HostTarget, spec: ResolvedHostLaunchSpec): Promise<HostOutcome> {\n\tconst before = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS);\n\tconst result = await handoffHost({\n\t\tsocket: target.socket,\n\t\tagentDir: target.agentDir,\n\t\thostArgs: spec.hostArgs,\n\t\tenv: daemonEnvOverrides(process.env, spec.env),\n\t\tpolicy: spec.policy,\n\t});\n\tif (result.action === \"refuse\") {\n\t\treturn refusal(\"refuse\", before, {\n\t\t\treason: upgradeRefusal(result.reason),\n\t\t\tsocket: target.socket,\n\t\t\tdetail: result.reason,\n\t\t\tupgradeable: result.upgradeable,\n\t\t});\n\t}\n\tawait recordEnvScope(target, spec);\n\tconst host = await probeProtocolInfo(target.socket, PROBE_TIMEOUT_MS);\n\treturn {\n\t\texitCode: HOST_EXIT_OK,\n\t\tpayload: { action: \"handoff\", ...identityPayload(target.socket, result.pid, host), reused: false },\n\t};\n}\n\n/**\n * A host that cannot drain and a platform that cannot hand off are one answer to the caller: this\n * build may not replace the running generation. The refusal that produced it stays in `detail`.\n */\nfunction upgradeRefusal(reason: HandoffRefusal): string {\n\treturn reason === \"handoff_unsupported\" ? \"upgrade_unsupported\" : reason;\n}\n\nfunction identityPayload(socket: string, pid: number, host: HostProtocolInfo | undefined): Record<string, unknown> {\n\treturn {\n\t\tsocket,\n\t\tpid,\n\t\tinstanceId: host?.instanceId ?? null,\n\t\tgeneration: host?.generation ?? null,\n\t\tengineVersion: host?.engineVersion ?? null,\n\t\tengineOrdinal: host?.engineOrdinal ?? null,\n\t\tcapabilities: host?.capabilities ?? [],\n\t\tlaunchProfileId: host?.launch_profile?.profile_id ?? null,\n\t\tupgradeable: decideHostAction(decisionClient(), host, \"upgrade\").upgradeable,\n\t};\n}\n\n/**\n * The two ways a request ends without acting, and the exit codes they mean: a `refuse` leaves a\n * running host alone (3), a `fallback` says no host is better than this one (4).\n */\nfunction refusal(\n\tkind: \"refuse\" | \"fallback\",\n\thost: HostProtocolInfo | undefined,\n\tbody: { readonly reason: string } & Record<string, unknown>,\n): HostOutcome {\n\treturn {\n\t\texitCode: kind === \"refuse\" ? HOST_EXIT_REFUSED : HOST_EXIT_FALLBACK,\n\t\tpayload: { action: kind, ...body, host: hostSummary(host) },\n\t};\n}\n\nasync function recordEnvScope(target: HostTarget, spec: ResolvedHostLaunchSpec): Promise<void> {\n\tconst paths = createHostDaemonPaths({ socket: target.socket, agentDir: target.agentDir });\n\tawait writeDaemonEnvKeys(paths, daemonEnvKeys(process.env, spec.env));\n}\n\n/**\n * This build as a client, WITHOUT a launch profile: the request's own `ensureHost` decides whether a\n * handoff may happen, from the profile it would launch. What is asked here is only what any client\n * can answer without one - is the running host usable, and could it be handed off from at all.\n */\nfunction decisionClient(): HostDecisionClient {\n\treturn {\n\t\tprotocolVersion: HOST_PROTOCOL_VERSION,\n\t\trequiredCapabilities: REQUIRED_HOST_CAPABILITIES,\n\t\tidentity: engineBuildIdentity(),\n\t\tstartedByUs: false,\n\t\tplatform: process.platform,\n\t};\n}\n\nfunction assertNever(value: never): never {\n\tthrow new Error(`unreachable host request: ${JSON.stringify(value)}`);\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import { type HostEndpointEntry, type HostEndpointIdentitySource } from "./host-endpoints.ts";
2
+ import { type HostStatusReport } from "./host-status.ts";
3
+ export interface HostEndpointStatus extends Omit<HostStatusReport, "socket"> {
4
+ readonly socket: string | null;
5
+ readonly dir: string;
6
+ readonly identity: HostEndpointIdentitySource;
7
+ }
8
+ /**
9
+ * Endpoints read at once. Each read holds a socket and a few files open until its budget runs out, so
10
+ * an unbounded fan-out over hundreds of hung hosts hit EMFILE under a 256-descriptor hard limit and
11
+ * returned no rows at all; up to this many hung hosts still cost about one budget in total.
12
+ */
13
+ export declare const STATUS_ALL_MAX_IN_FLIGHT = 64;
14
+ interface StatusAllOptions {
15
+ readonly agentDir: string;
16
+ readonly includeWorkers: boolean;
17
+ /** Budget for each read of each endpoint's socket, default 10 s (`readHostStatus`). */
18
+ readonly timeoutMs?: number;
19
+ /** Replaces the per-endpoint read; tests observe how many run at once. */
20
+ readonly _test?: {
21
+ readonly readEndpoint?: (endpoint: HostEndpointEntry) => Promise<HostEndpointStatus>;
22
+ };
23
+ }
24
+ /**
25
+ * Endpoints are read concurrently, at most STATUS_ALL_MAX_IN_FLIGHT at a time, each under its own
26
+ * budget, so hung hosts cost about one budget per STATUS_ALL_MAX_IN_FLIGHT rather than one each; rows
27
+ * keep the enumeration order (sorted by directory name).
28
+ */
29
+ export declare function readAllHostStatus(options: StatusAllOptions): Promise<readonly HostEndpointStatus[]>;
30
+ export {};
31
+ //# sourceMappingURL=host-status-all.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-status-all.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-status-all.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,0BAA0B,EAAqB,MAAM,qBAAqB,CAAC;AAEjH,OAAO,EAAE,KAAK,gBAAgB,EAAkB,MAAM,kBAAkB,CAAC;AAGzE,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC;CAC9C;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C,UAAU,gBAAgB;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,uFAAuF;IACvF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAA;KAAE,CAAC;CAC1G;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAazG"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * `senpi host status --all`: one status row per endpoint an agent directory holds state for.
3
+ *
4
+ * Enumeration is the daemon directory (`listHostEndpoints`), and every row is the single-socket
5
+ * report read with `prune: false` - so this answer removes NOTHING. Dead generations stay visible as
6
+ * `alive: false` rows, an endpoint whose host exited stays listed through its `endpoint.json`, and
7
+ * the reclaiming of what ended is a separate, evidence-gated command rather than a side effect of
8
+ * looking. A directory that names no socket still gets a row (`socket: null`), built from the
9
+ * directory alone, because it is exactly what an operator asking "what is on this machine" needs to
10
+ * see.
11
+ */
12
+ import { readHostCrashRecords } from "./host-crash-record.js";
13
+ import { hostDaemonDirectoryPaths } from "./host-daemon-paths.js";
14
+ import { listHostEndpoints } from "./host-endpoints.js";
15
+ import { readGenerationRows } from "./host-generations.js";
16
+ import { readHostStatus } from "./host-status.js";
17
+ import { readClaimRows } from "./host-status-rows.js";
18
+ /**
19
+ * Endpoints read at once. Each read holds a socket and a few files open until its budget runs out, so
20
+ * an unbounded fan-out over hundreds of hung hosts hit EMFILE under a 256-descriptor hard limit and
21
+ * returned no rows at all; up to this many hung hosts still cost about one budget in total.
22
+ */
23
+ export const STATUS_ALL_MAX_IN_FLIGHT = 64;
24
+ /**
25
+ * Endpoints are read concurrently, at most STATUS_ALL_MAX_IN_FLIGHT at a time, each under its own
26
+ * budget, so hung hosts cost about one budget per STATUS_ALL_MAX_IN_FLIGHT rather than one each; rows
27
+ * keep the enumeration order (sorted by directory name).
28
+ */
29
+ export async function readAllHostStatus(options) {
30
+ const endpoints = await listHostEndpoints(options.agentDir);
31
+ const read = options._test?.readEndpoint ?? ((endpoint) => endpointStatus(endpoint, options));
32
+ const rows = new Array(endpoints.length);
33
+ let next = 0;
34
+ const worker = async () => {
35
+ for (let index = next++; index < endpoints.length; index = next++) {
36
+ const endpoint = endpoints[index];
37
+ if (endpoint !== undefined)
38
+ rows[index] = await read(endpoint);
39
+ }
40
+ };
41
+ await Promise.all(Array.from({ length: Math.min(STATUS_ALL_MAX_IN_FLIGHT, endpoints.length) }, worker));
42
+ return rows;
43
+ }
44
+ async function endpointStatus(endpoint, options) {
45
+ const located = { dir: endpoint.dir, identity: endpoint.identity };
46
+ if (endpoint.socket === null)
47
+ return { ...(await unaddressableStatus(endpoint.dir)), socket: null, ...located };
48
+ const report = await readHostStatus({ socket: endpoint.socket, agentDir: options.agentDir, includeWorkers: options.includeWorkers }, { prune: false, ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}) });
49
+ return { ...report, ...located };
50
+ }
51
+ async function unaddressableStatus(dir) {
52
+ const paths = hostDaemonDirectoryPaths(dir);
53
+ const generations = await readGenerationRows(paths, { includeDead: true });
54
+ const claims = await readClaimRows(paths, generations);
55
+ return {
56
+ reachable: false,
57
+ pid: null,
58
+ instanceId: null,
59
+ generation: null,
60
+ engineVersion: null,
61
+ capabilities: [],
62
+ launchProfile: null,
63
+ sessions: { total: 0, interactive: 0, worker: 0, retained: 0, foreign_attached: 0, foreign_retained: 0 },
64
+ zombies: null,
65
+ rss_mb: null,
66
+ host_rss_mb: null,
67
+ open_fds: null,
68
+ memory_pressure: null,
69
+ env_keys: [],
70
+ generations,
71
+ crashes: readHostCrashRecords(dir).length,
72
+ shard: null,
73
+ session_rows: [],
74
+ claims_live: claims.filter((claim) => claim.live).length,
75
+ claims: [],
76
+ };
77
+ }
78
+ //# sourceMappingURL=host-status-all.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-status-all.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-status-all.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `senpi host status --all`: one status row per endpoint an agent directory holds state for.\n *\n * Enumeration is the daemon directory (`listHostEndpoints`), and every row is the single-socket\n * report read with `prune: false` - so this answer removes NOTHING. Dead generations stay visible as\n * `alive: false` rows, an endpoint whose host exited stays listed through its `endpoint.json`, and\n * the reclaiming of what ended is a separate, evidence-gated command rather than a side effect of\n * looking. A directory that names no socket still gets a row (`socket: null`), built from the\n * directory alone, because it is exactly what an operator asking \"what is on this machine\" needs to\n * see.\n */\nimport { readHostCrashRecords } from \"./host-crash-record.ts\";\nimport { hostDaemonDirectoryPaths } from \"./host-daemon-paths.ts\";\nimport { type HostEndpointEntry, type HostEndpointIdentitySource, listHostEndpoints } from \"./host-endpoints.ts\";\nimport { readGenerationRows } from \"./host-generations.ts\";\nimport { type HostStatusReport, readHostStatus } from \"./host-status.ts\";\nimport { readClaimRows } from \"./host-status-rows.ts\";\n\nexport interface HostEndpointStatus extends Omit<HostStatusReport, \"socket\"> {\n\treadonly socket: string | null;\n\treadonly dir: string;\n\treadonly identity: HostEndpointIdentitySource;\n}\n\n/**\n * Endpoints read at once. Each read holds a socket and a few files open until its budget runs out, so\n * an unbounded fan-out over hundreds of hung hosts hit EMFILE under a 256-descriptor hard limit and\n * returned no rows at all; up to this many hung hosts still cost about one budget in total.\n */\nexport const STATUS_ALL_MAX_IN_FLIGHT = 64;\n\ninterface StatusAllOptions {\n\treadonly agentDir: string;\n\treadonly includeWorkers: boolean;\n\t/** Budget for each read of each endpoint's socket, default 10 s (`readHostStatus`). */\n\treadonly timeoutMs?: number;\n\t/** Replaces the per-endpoint read; tests observe how many run at once. */\n\treadonly _test?: { readonly readEndpoint?: (endpoint: HostEndpointEntry) => Promise<HostEndpointStatus> };\n}\n\n/**\n * Endpoints are read concurrently, at most STATUS_ALL_MAX_IN_FLIGHT at a time, each under its own\n * budget, so hung hosts cost about one budget per STATUS_ALL_MAX_IN_FLIGHT rather than one each; rows\n * keep the enumeration order (sorted by directory name).\n */\nexport async function readAllHostStatus(options: StatusAllOptions): Promise<readonly HostEndpointStatus[]> {\n\tconst endpoints = await listHostEndpoints(options.agentDir);\n\tconst read = options._test?.readEndpoint ?? ((endpoint: HostEndpointEntry) => endpointStatus(endpoint, options));\n\tconst rows: HostEndpointStatus[] = new Array(endpoints.length);\n\tlet next = 0;\n\tconst worker = async (): Promise<void> => {\n\t\tfor (let index = next++; index < endpoints.length; index = next++) {\n\t\t\tconst endpoint = endpoints[index];\n\t\t\tif (endpoint !== undefined) rows[index] = await read(endpoint);\n\t\t}\n\t};\n\tawait Promise.all(Array.from({ length: Math.min(STATUS_ALL_MAX_IN_FLIGHT, endpoints.length) }, worker));\n\treturn rows;\n}\n\nasync function endpointStatus(endpoint: HostEndpointEntry, options: StatusAllOptions): Promise<HostEndpointStatus> {\n\tconst located = { dir: endpoint.dir, identity: endpoint.identity };\n\tif (endpoint.socket === null) return { ...(await unaddressableStatus(endpoint.dir)), socket: null, ...located };\n\tconst report = await readHostStatus(\n\t\t{ socket: endpoint.socket, agentDir: options.agentDir, includeWorkers: options.includeWorkers },\n\t\t{ prune: false, ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}) },\n\t);\n\treturn { ...report, ...located };\n}\n\nasync function unaddressableStatus(dir: string): Promise<Omit<HostStatusReport, \"socket\">> {\n\tconst paths = hostDaemonDirectoryPaths(dir);\n\tconst generations = await readGenerationRows(paths, { includeDead: true });\n\tconst claims = await readClaimRows(paths, generations);\n\treturn {\n\t\treachable: false,\n\t\tpid: null,\n\t\tinstanceId: null,\n\t\tgeneration: null,\n\t\tengineVersion: null,\n\t\tcapabilities: [],\n\t\tlaunchProfile: null,\n\t\tsessions: { total: 0, interactive: 0, worker: 0, retained: 0, foreign_attached: 0, foreign_retained: 0 },\n\t\tzombies: null,\n\t\trss_mb: null,\n\t\thost_rss_mb: null,\n\t\topen_fds: null,\n\t\tmemory_pressure: null,\n\t\tenv_keys: [],\n\t\tgenerations,\n\t\tcrashes: readHostCrashRecords(dir).length,\n\t\tshard: null,\n\t\tsession_rows: [],\n\t\tclaims_live: claims.filter((claim) => claim.live).length,\n\t\tclaims: [],\n\t};\n}\n"]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The per-row detail of a status report: the sessions a host listed and the session-path claims its
3
+ * endpoint's `reservations/` directory holds.
4
+ *
5
+ * Both are parsed here from what the host and the directory SAY, at the boundary, so the report
6
+ * carries typed rows and never a raw reply. The listing is the same `list_sessions` reply the counts
7
+ * are made from; the claims are read from the directory every generation of the endpoint shares, so
8
+ * a path a draining predecessor still holds is visible even though the answering generation's own
9
+ * listing no longer names it.
10
+ */
11
+ import type { HostDaemonDirectory } from "./host-daemon-paths.ts";
12
+ import type { HostGenerationRow } from "./host-generations.ts";
13
+ /** One session the host listed, with the canonical path a client matches it by. */
14
+ export interface HostSessionRow {
15
+ readonly id: string;
16
+ readonly kind: string;
17
+ readonly session_path: string | null;
18
+ readonly attachments: number;
19
+ /** The labels published on a worker listing; `null` when the host published none. */
20
+ readonly context: Readonly<Record<string, string>> | null;
21
+ }
22
+ /** One session-path claim, whichever generation of the endpoint wrote it. */
23
+ export interface HostPathClaimRow {
24
+ readonly session_path: string;
25
+ readonly owner_pid: number;
26
+ readonly instance_id: string;
27
+ /** The owner generation's ordinal, `null` once its generation record is gone. */
28
+ readonly generation: number | null;
29
+ /** Whether the owner had a client attached when it last published; `null` for a pre-flag claim. */
30
+ readonly attached: boolean | null;
31
+ readonly live: boolean;
32
+ }
33
+ export declare function parseSessionRows(reply: unknown): readonly HostSessionRow[];
34
+ export declare function readClaimRows(paths: HostDaemonDirectory, generations: readonly HostGenerationRow[]): Promise<readonly HostPathClaimRow[]>;
35
+ //# sourceMappingURL=host-status-rows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-status-rows.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-status-rows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG/D,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC;CAC1D;AAED,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,mGAAmG;IACnG,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,cAAc,EAAE,CAc1E;AAED,wBAAsB,aAAa,CAClC,KAAK,EAAE,mBAAmB,EAC1B,WAAW,EAAE,SAAS,iBAAiB,EAAE,GACvC,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC,CActC"}
@@ -0,0 +1,42 @@
1
+ import { claimOwnerIsLive, readSessionPathClaims } from "./host-reservations.js";
2
+ export function parseSessionRows(reply) {
3
+ if (!isRecord(reply) || !Array.isArray(reply.sessions))
4
+ return [];
5
+ return reply.sessions.flatMap((entry) => {
6
+ if (!isRecord(entry))
7
+ return [];
8
+ return [
9
+ {
10
+ id: typeof entry.sessionId === "string" ? entry.sessionId : "",
11
+ kind: typeof entry.kind === "string" ? entry.kind : "interactive",
12
+ session_path: typeof entry.sessionPath === "string" ? entry.sessionPath : null,
13
+ attachments: typeof entry.attachments === "number" ? entry.attachments : 0,
14
+ context: stringRecord(entry.context),
15
+ },
16
+ ];
17
+ });
18
+ }
19
+ export async function readClaimRows(paths, generations) {
20
+ const rows = [];
21
+ for (const claim of await readSessionPathClaims(paths.reservationsDir)) {
22
+ const owner = claim.owner;
23
+ rows.push({
24
+ session_path: owner.sessionPath,
25
+ owner_pid: owner.pid,
26
+ instance_id: owner.instanceId,
27
+ generation: generations.find((row) => row.instanceId === owner.instanceId)?.generation ?? null,
28
+ attached: owner.attached ?? null,
29
+ live: await claimOwnerIsLive(owner),
30
+ });
31
+ }
32
+ return rows.sort((left, right) => left.session_path.localeCompare(right.session_path));
33
+ }
34
+ function stringRecord(value) {
35
+ if (!isRecord(value))
36
+ return null;
37
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
38
+ }
39
+ function isRecord(value) {
40
+ return typeof value === "object" && value !== null && !Array.isArray(value);
41
+ }
42
+ //# sourceMappingURL=host-status-rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-status-rows.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-status-rows.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAwBjF,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC9C,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAC;IAClE,OAAO,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAc,EAAE,EAAE;QAChD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChC,OAAO;YACN;gBACC,EAAE,EAAE,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;gBAC9D,IAAI,EAAE,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa;gBACjE,YAAY,EAAE,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI;gBAC9E,WAAW,EAAE,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;gBAC1E,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC;aACpC;SACD,CAAC;IACH,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,KAA0B,EAC1B,WAAyC;IAEzC,MAAM,IAAI,GAAuB,EAAE,CAAC;IACpC,KAAK,MAAM,KAAK,IAAI,MAAM,qBAAqB,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC;QACxE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC;YACT,YAAY,EAAE,KAAK,CAAC,WAAW;YAC/B,SAAS,EAAE,KAAK,CAAC,GAAG;YACpB,WAAW,EAAE,KAAK,CAAC,UAAU;YAC7B,UAAU,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU,CAAC,EAAE,UAAU,IAAI,IAAI;YAC9F,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,IAAI;YAChC,IAAI,EAAE,MAAM,gBAAgB,CAAC,KAAK,CAAC;SACnC,CAAC,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;AACxF,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IACnC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,MAAM,CAAC,WAAW,CACxB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAA6B,EAAE,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAChG,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC","sourcesContent":["/**\n * The per-row detail of a status report: the sessions a host listed and the session-path claims its\n * endpoint's `reservations/` directory holds.\n *\n * Both are parsed here from what the host and the directory SAY, at the boundary, so the report\n * carries typed rows and never a raw reply. The listing is the same `list_sessions` reply the counts\n * are made from; the claims are read from the directory every generation of the endpoint shares, so\n * a path a draining predecessor still holds is visible even though the answering generation's own\n * listing no longer names it.\n */\nimport type { HostDaemonDirectory } from \"./host-daemon-paths.ts\";\nimport type { HostGenerationRow } from \"./host-generations.ts\";\nimport { claimOwnerIsLive, readSessionPathClaims } from \"./host-reservations.ts\";\n\n/** One session the host listed, with the canonical path a client matches it by. */\nexport interface HostSessionRow {\n\treadonly id: string;\n\treadonly kind: string;\n\treadonly session_path: string | null;\n\treadonly attachments: number;\n\t/** The labels published on a worker listing; `null` when the host published none. */\n\treadonly context: Readonly<Record<string, string>> | null;\n}\n\n/** One session-path claim, whichever generation of the endpoint wrote it. */\nexport interface HostPathClaimRow {\n\treadonly session_path: string;\n\treadonly owner_pid: number;\n\treadonly instance_id: string;\n\t/** The owner generation's ordinal, `null` once its generation record is gone. */\n\treadonly generation: number | null;\n\t/** Whether the owner had a client attached when it last published; `null` for a pre-flag claim. */\n\treadonly attached: boolean | null;\n\treadonly live: boolean;\n}\n\nexport function parseSessionRows(reply: unknown): readonly HostSessionRow[] {\n\tif (!isRecord(reply) || !Array.isArray(reply.sessions)) return [];\n\treturn reply.sessions.flatMap((entry: unknown) => {\n\t\tif (!isRecord(entry)) return [];\n\t\treturn [\n\t\t\t{\n\t\t\t\tid: typeof entry.sessionId === \"string\" ? entry.sessionId : \"\",\n\t\t\t\tkind: typeof entry.kind === \"string\" ? entry.kind : \"interactive\",\n\t\t\t\tsession_path: typeof entry.sessionPath === \"string\" ? entry.sessionPath : null,\n\t\t\t\tattachments: typeof entry.attachments === \"number\" ? entry.attachments : 0,\n\t\t\t\tcontext: stringRecord(entry.context),\n\t\t\t},\n\t\t];\n\t});\n}\n\nexport async function readClaimRows(\n\tpaths: HostDaemonDirectory,\n\tgenerations: readonly HostGenerationRow[],\n): Promise<readonly HostPathClaimRow[]> {\n\tconst rows: HostPathClaimRow[] = [];\n\tfor (const claim of await readSessionPathClaims(paths.reservationsDir)) {\n\t\tconst owner = claim.owner;\n\t\trows.push({\n\t\t\tsession_path: owner.sessionPath,\n\t\t\towner_pid: owner.pid,\n\t\t\tinstance_id: owner.instanceId,\n\t\t\tgeneration: generations.find((row) => row.instanceId === owner.instanceId)?.generation ?? null,\n\t\t\tattached: owner.attached ?? null,\n\t\t\tlive: await claimOwnerIsLive(owner),\n\t\t});\n\t}\n\treturn rows.sort((left, right) => left.session_path.localeCompare(right.session_path));\n}\n\nfunction stringRecord(value: unknown): Readonly<Record<string, string>> | null {\n\tif (!isRecord(value)) return null;\n\treturn Object.fromEntries(\n\t\tObject.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\n\t);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
@@ -1,5 +1,7 @@
1
+ import { type ShardKind } from "./host-daemon-paths.ts";
1
2
  import type { HostProtocolInfo } from "./host-decision.ts";
2
3
  import { type HostGenerationRow } from "./host-generations.ts";
4
+ import { type HostPathClaimRow, type HostSessionRow } from "./host-status-rows.ts";
3
5
  import type { RpcLaunchProfile } from "./rpc-types.ts";
4
6
  /**
5
7
  * Sessions the host reports. `foreign_*` is the same count from the point of view of a client that
@@ -16,6 +18,7 @@ export interface HostSessionCounts {
16
18
  readonly foreign_retained: number;
17
19
  }
18
20
  export type { HostGenerationRow } from "./host-generations.ts";
21
+ export type { HostPathClaimRow, HostSessionRow } from "./host-status-rows.ts";
19
22
  export interface HostStatusReport {
20
23
  readonly reachable: boolean;
21
24
  readonly socket: string;
@@ -30,9 +33,27 @@ export interface HostStatusReport {
30
33
  readonly rss_mb: number | null;
31
34
  readonly host_rss_mb: number | null;
32
35
  readonly open_fds: number | null;
36
+ /**
37
+ * Whether the ANSWERING generation's memory sampler reads pressure right now (RSS above
38
+ * `SENPI_RPC_HOST_RSS_WARN_MB`); `null` when nothing answers or the host predates the field.
39
+ */
40
+ readonly memory_pressure: boolean | null;
33
41
  /** Environment NAMES the daemon was granted, never values. */
34
42
  readonly env_keys: readonly string[];
35
43
  readonly generations: readonly HostGenerationRow[];
44
+ /** Records in this endpoint's `crashes.jsonl`: supervised host children that DIED, not stopped. */
45
+ readonly crashes: number;
46
+ /** The owner-keyed shard the socket names (`<kind>-<16hex>.sock`), `null` for any other endpoint. */
47
+ readonly shard: {
48
+ readonly kind: ShardKind;
49
+ readonly key: string;
50
+ } | null;
51
+ /** Every listed session, only when workers were asked for; `[]` otherwise. */
52
+ readonly session_rows: readonly HostSessionRow[];
53
+ /** Session-path claims in `reservations/` whose owner process is still running. */
54
+ readonly claims_live: number;
55
+ /** Every claim in `reservations/`, whichever generation wrote it, only when workers were asked for. */
56
+ readonly claims: readonly HostPathClaimRow[];
36
57
  }
37
58
  export interface HostStatusOptions {
38
59
  readonly socket: string;
@@ -40,7 +61,17 @@ export interface HostStatusOptions {
40
61
  /** Ask the host to include `kind: "worker"` rows, exactly as `list_sessions` defines the flag. */
41
62
  readonly includeWorkers?: boolean;
42
63
  }
43
- export declare function readHostStatus(options: HostStatusOptions): Promise<HostStatusReport>;
64
+ /**
65
+ * How a status read treats the daemon directory. `prune` (the default, and the single-socket
66
+ * `status`) removes what ended before reporting; `prune: false` (`status --all`) reports dead
67
+ * generations as `alive: false` rows and removes nothing.
68
+ */
69
+ export interface HostStatusReadOptions {
70
+ readonly prune?: boolean;
71
+ /** Budget for each read of the socket, default 10 s. */
72
+ readonly timeoutMs?: number;
73
+ }
74
+ export declare function readHostStatus(options: HostStatusOptions, read?: HostStatusReadOptions): Promise<HostStatusReport>;
44
75
  /**
45
76
  * The occupancy a stop decision is made on. A host that does not answer holds nothing a client can
46
77
  * see, and an empty count is what lets a stop proceed against a socket nobody serves.