@code-yeongyu/senpi 2026.8.26 → 2026.8.27

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 (231) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +3 -3
  3. package/dist/cli/args.d.ts +3 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +14 -1
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +10 -1
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/bash-executor.d.ts.map +1 -1
  11. package/dist/core/bash-executor.js +67 -29
  12. package/dist/core/bash-executor.js.map +1 -1
  13. package/dist/core/credential-pool/classify.d.ts +42 -0
  14. package/dist/core/credential-pool/classify.d.ts.map +1 -0
  15. package/dist/core/credential-pool/classify.js +75 -0
  16. package/dist/core/credential-pool/classify.js.map +1 -0
  17. package/dist/core/credential-pool/env-slots.d.ts +22 -0
  18. package/dist/core/credential-pool/env-slots.d.ts.map +1 -0
  19. package/dist/core/credential-pool/env-slots.js +37 -0
  20. package/dist/core/credential-pool/env-slots.js.map +1 -0
  21. package/dist/core/credential-pool/failover.d.ts +54 -0
  22. package/dist/core/credential-pool/failover.d.ts.map +1 -0
  23. package/dist/core/credential-pool/failover.js +106 -0
  24. package/dist/core/credential-pool/failover.js.map +1 -0
  25. package/dist/core/credential-pool/rotation-stream.d.ts +43 -0
  26. package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -0
  27. package/dist/core/credential-pool/rotation-stream.js +96 -0
  28. package/dist/core/credential-pool/rotation-stream.js.map +1 -0
  29. package/dist/core/credential-pool/state-store.d.ts +89 -0
  30. package/dist/core/credential-pool/state-store.d.ts.map +1 -0
  31. package/dist/core/credential-pool/state-store.js +166 -0
  32. package/dist/core/credential-pool/state-store.js.map +1 -0
  33. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  34. package/dist/core/extensions/builtin/compaction/index.js +67 -0
  35. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  36. package/dist/core/extensions/builtin/compaction/log.d.ts +1 -1
  37. package/dist/core/extensions/builtin/compaction/log.d.ts.map +1 -1
  38. package/dist/core/extensions/builtin/compaction/log.js +1 -0
  39. package/dist/core/extensions/builtin/compaction/log.js.map +1 -1
  40. package/dist/core/extensions/builtin/config-reload/index.js +14 -22
  41. package/dist/core/extensions/builtin/config-reload/index.js.map +1 -1
  42. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts +1 -0
  43. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/goal/continuation-recovery.js +2 -0
  45. package/dist/core/extensions/builtin/goal/continuation-recovery.js.map +1 -1
  46. package/dist/core/extensions/builtin/goal/continuation.d.ts +8 -1
  47. package/dist/core/extensions/builtin/goal/continuation.d.ts.map +1 -1
  48. package/dist/core/extensions/builtin/goal/continuation.js +15 -0
  49. package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
  50. package/dist/core/extensions/builtin/goal/direct-input-lifecycle.js +2 -2
  51. package/dist/core/extensions/builtin/goal/direct-input-lifecycle.js.map +1 -1
  52. package/dist/core/extensions/builtin/goal/format.d.ts +6 -1
  53. package/dist/core/extensions/builtin/goal/format.d.ts.map +1 -1
  54. package/dist/core/extensions/builtin/goal/format.js +6 -0
  55. package/dist/core/extensions/builtin/goal/format.js.map +1 -1
  56. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts.map +1 -1
  57. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +5 -2
  58. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js.map +1 -1
  59. package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
  60. package/dist/core/extensions/builtin/goal/monitor-continuation.js +31 -7
  61. package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
  62. package/dist/core/extensions/builtin/goal/persistence.js +2 -0
  63. package/dist/core/extensions/builtin/goal/persistence.js.map +1 -1
  64. package/dist/core/extensions/builtin/goal/renderers.d.ts +14 -0
  65. package/dist/core/extensions/builtin/goal/renderers.d.ts.map +1 -0
  66. package/dist/core/extensions/builtin/goal/renderers.js +153 -0
  67. package/dist/core/extensions/builtin/goal/renderers.js.map +1 -0
  68. package/dist/core/extensions/builtin/goal/store.d.ts +6 -2
  69. package/dist/core/extensions/builtin/goal/store.d.ts.map +1 -1
  70. package/dist/core/extensions/builtin/goal/store.js +8 -2
  71. package/dist/core/extensions/builtin/goal/store.js.map +1 -1
  72. package/dist/core/extensions/builtin/goal/tool-registration.d.ts.map +1 -1
  73. package/dist/core/extensions/builtin/goal/tool-registration.js +16 -6
  74. package/dist/core/extensions/builtin/goal/tool-registration.js.map +1 -1
  75. package/dist/core/extensions/builtin/goal/types.d.ts +1 -0
  76. package/dist/core/extensions/builtin/goal/types.d.ts.map +1 -1
  77. package/dist/core/extensions/builtin/goal/types.js.map +1 -1
  78. package/dist/core/extensions/builtin/todotools/index.js +1 -1
  79. package/dist/core/extensions/builtin/todotools/index.js.map +1 -1
  80. package/dist/core/extensions/builtin/todotools/native-todo-mirror.d.ts +1 -1
  81. package/dist/core/extensions/builtin/todotools/native-todo-mirror.d.ts.map +1 -1
  82. package/dist/core/extensions/builtin/todotools/native-todo-mirror.js +1 -1
  83. package/dist/core/extensions/builtin/todotools/native-todo-mirror.js.map +1 -1
  84. package/dist/core/hidden-stdout-log.d.ts +10 -0
  85. package/dist/core/hidden-stdout-log.d.ts.map +1 -1
  86. package/dist/core/hidden-stdout-log.js +36 -5
  87. package/dist/core/hidden-stdout-log.js.map +1 -1
  88. package/dist/core/model-config-schema.d.ts +85 -12
  89. package/dist/core/model-config-schema.d.ts.map +1 -1
  90. package/dist/core/model-config-schema.js +25 -0
  91. package/dist/core/model-config-schema.js.map +1 -1
  92. package/dist/core/model-runtime.d.ts +34 -1
  93. package/dist/core/model-runtime.d.ts.map +1 -1
  94. package/dist/core/model-runtime.js +98 -5
  95. package/dist/core/model-runtime.js.map +1 -1
  96. package/dist/core/tools/output-accumulator.d.ts +2 -0
  97. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  98. package/dist/core/tools/output-accumulator.js +32 -7
  99. package/dist/core/tools/output-accumulator.js.map +1 -1
  100. package/dist/index.d.ts +1 -1
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +1 -1
  103. package/dist/index.js.map +1 -1
  104. package/dist/main.d.ts.map +1 -1
  105. package/dist/main.js +26 -2
  106. package/dist/main.js.map +1 -1
  107. package/dist/modes/index.d.ts +1 -0
  108. package/dist/modes/index.d.ts.map +1 -1
  109. package/dist/modes/index.js +1 -0
  110. package/dist/modes/index.js.map +1 -1
  111. package/dist/modes/interactive/components/tool-execution-fallback.d.ts.map +1 -1
  112. package/dist/modes/interactive/components/tool-execution-fallback.js +82 -1
  113. package/dist/modes/interactive/components/tool-execution-fallback.js.map +1 -1
  114. package/dist/modes/interactive/interactive-host-runtime.d.ts +29 -0
  115. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -0
  116. package/dist/modes/interactive/interactive-host-runtime.js +244 -0
  117. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -0
  118. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  119. package/dist/modes/interactive/interactive-mode.js +46 -8
  120. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  121. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  122. package/dist/modes/rpc/connection-handler.js +7 -1
  123. package/dist/modes/rpc/connection-handler.js.map +1 -1
  124. package/dist/modes/rpc/host-ensure.d.ts +41 -0
  125. package/dist/modes/rpc/host-ensure.d.ts.map +1 -0
  126. package/dist/modes/rpc/host-ensure.js +245 -0
  127. package/dist/modes/rpc/host-ensure.js.map +1 -0
  128. package/dist/modes/rpc/host-lifecycle.d.ts +57 -0
  129. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -0
  130. package/dist/modes/rpc/host-lifecycle.js +491 -0
  131. package/dist/modes/rpc/host-lifecycle.js.map +1 -0
  132. package/dist/modes/rpc/host-watchdog.d.ts +33 -0
  133. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -0
  134. package/dist/modes/rpc/host-watchdog.js +142 -0
  135. package/dist/modes/rpc/host-watchdog.js.map +1 -0
  136. package/dist/modes/rpc/multi-session-host.d.ts +7 -1
  137. package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
  138. package/dist/modes/rpc/multi-session-host.js +206 -32
  139. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  140. package/dist/modes/rpc/rpc-client.d.ts +25 -0
  141. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  142. package/dist/modes/rpc/rpc-client.js +83 -10
  143. package/dist/modes/rpc/rpc-client.js.map +1 -1
  144. package/dist/modes/rpc/rpc-mode.d.ts +1 -1
  145. package/dist/modes/rpc/rpc-mode.js +1 -1
  146. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  147. package/dist/modes/rpc/rpc-types.d.ts +2 -1
  148. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  149. package/dist/modes/rpc/rpc-types.js.map +1 -1
  150. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  151. package/dist/modes/rpc/session-command-router.js +12 -2
  152. package/dist/modes/rpc/session-command-router.js.map +1 -1
  153. package/dist/modes/rpc/session-event-writer.d.ts +13 -2
  154. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  155. package/dist/modes/rpc/session-event-writer.js +65 -25
  156. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  157. package/docs/rpc.md +53 -1
  158. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +30 -0
  159. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  160. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +711 -1
  161. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +10 -10
  162. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-eval-result.ts +9 -3
  163. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +6 -1
  164. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  165. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.d.ts +1 -1
  166. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.d.ts.map +1 -1
  167. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.js +46 -14
  168. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.js.map +1 -1
  169. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +11 -0
  170. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  171. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +30 -16
  172. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  173. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.d.ts +16 -0
  174. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.d.ts.map +1 -0
  175. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.js +75 -0
  176. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.js.map +1 -0
  177. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts +45 -0
  178. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts.map +1 -0
  179. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js +81 -0
  180. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js.map +1 -0
  181. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.d.ts +31 -0
  182. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.d.ts.map +1 -0
  183. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.js +65 -0
  184. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.js.map +1 -0
  185. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts +12 -0
  186. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts.map +1 -1
  187. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js +37 -0
  188. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js.map +1 -1
  189. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts +6 -0
  190. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts.map +1 -1
  191. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js +32 -6
  192. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js.map +1 -1
  193. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts +6 -0
  194. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  195. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +6 -1
  196. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
  197. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +75 -0
  198. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  199. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +75 -0
  200. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  201. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  202. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  203. package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
  204. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  205. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  206. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  207. package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax-cn.json +1 -1
  208. package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax.json +1 -1
  209. package/node_modules/@earendil-works/pi-ai/dist/providers/data/mistral.json +1 -1
  210. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  211. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  212. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  213. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opengateway.json +1 -1
  214. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  215. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  216. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  217. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  218. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  219. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +4 -0
  220. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  221. package/node_modules/@earendil-works/pi-ai/package.json +5 -5
  222. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  223. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  224. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +5 -0
  225. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  226. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +90 -1
  227. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  228. package/node_modules/@earendil-works/pi-tui/dist/tui.js +1 -1
  229. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  230. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  231. package/package.json +7 -7
@@ -0,0 +1,491 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Lifecycle supervisor for the shared RPC socket host started by ensureHost().
4
+ *
5
+ * Process tree:
6
+ *
7
+ * ensureHost() ──detached──▶ host-lifecycle.ts (this supervisor, owns the pidfile)
8
+ * │ byte-proxies the public socket
9
+ * ▼
10
+ * cli-main --mode rpc --listen unix://<public>.internal
11
+ *
12
+ * The supervisor exists to enforce the host lifecycle policy without touching the
13
+ * RPC host itself:
14
+ *
15
+ * - cold start: `transient` (default) means the host lives for the current login
16
+ * session and idle-exits; `persistent` never idle-exits.
17
+ * - idle exit: after a continuous window with zero attached client connections
18
+ * and zero active agent turns, the supervisor tears the host down cleanly
19
+ * (child SIGTERM first so the host flushes pending output and removes its own
20
+ * socket, then pidfile/settings removal mirroring ensureHost's cleanupState).
21
+ *
22
+ * Observability without host changes: proxying the public socket yields the
23
+ * exact connection count, and the supervisor keeps one always-on observer
24
+ * connection to the internal socket. The multi-session host broadcasts every
25
+ * session lifecycle/agent event to every connection, so the observer sees
26
+ * `agent_start`/`agent_settled` for all sessions even when no client is
27
+ * attached. If the observer connection is ever unhealthy, activity is reported
28
+ * as unknown (non-idle), so a broken observer can only keep the host alive,
29
+ * never kill it mid-turn.
30
+ *
31
+ * Lifetime binding: the host is spawned with an extra inherited pipe on fd 3
32
+ * whose write end this supervisor holds and never writes to. The kernel closes
33
+ * that end whenever the supervisor dies - including SIGKILL, an OOM kill, or a
34
+ * crash, where no JS handler runs at all - so the host reads EOF and shuts down
35
+ * cleanly, removing the private internal directory. `stopChild()` remains the
36
+ * fast path for orderly shutdowns; the pipe is what makes an orphaned host
37
+ * impossible. `SENPI_RPC_HOST_WATCH_PPID` is passed alongside as a belt-and-
38
+ * braces fallback for platforms where the extra fd is not inherited.
39
+ */
40
+ import { spawn } from "node:child_process";
41
+ import { randomUUID } from "node:crypto";
42
+ import { realpathSync, writeSync } from "node:fs";
43
+ import { access, chmod, mkdir, readFile, rm, unlink } from "node:fs/promises";
44
+ import { createConnection, createServer } from "node:net";
45
+ import { tmpdir } from "node:os";
46
+ import { dirname, join, resolve } from "node:path";
47
+ import { fileURLToPath } from "node:url";
48
+ import { getAgentDir } from "../../config.js";
49
+ import { createHostDaemonPaths } from "./host-ensure.js";
50
+ import { HOST_SCRATCH_DIR_ENV, HOST_WATCH_FD_ENV, HOST_WATCH_PPID_ENV } from "./host-watchdog.js";
51
+ import { attachJsonlLineReader, MAX_RPC_LINE_CHARACTERS } from "./jsonl.js";
52
+ /** Environment override for the cold-start policy: `transient` or `persistent`. */
53
+ export const HOST_COLD_START_ENV = "SENPI_RPC_HOST_COLD_START";
54
+ /** Environment override for the idle-exit window in milliseconds. */
55
+ export const HOST_IDLE_EXIT_MS_ENV = "SENPI_RPC_HOST_IDLE_EXIT_MS";
56
+ /** Default idle-exit window: 15 minutes of continuous no-connection, no-turn idle. */
57
+ export const DEFAULT_HOST_IDLE_EXIT_MS = 15 * 60_000;
58
+ const CHILD_STOP_TIMEOUT_MS = 5_000;
59
+ /**
60
+ * Child stdio slot carrying the supervisor-lifetime pipe. The supervisor holds
61
+ * the write end open and never writes; the kernel closes it when the supervisor
62
+ * dies for ANY reason (SIGKILL, OOM kill, crash), so the host sees EOF on this
63
+ * fd and shuts itself down. Catchable-signal cleanup alone cannot do this.
64
+ */
65
+ const CHILD_WATCH_FD = 3;
66
+ /**
67
+ * The internal hop must stay short enough for sun_path (104 bytes on macOS)
68
+ * regardless of where the public socket lives, and private against other local
69
+ * users, so it gets its own 0700 directory under the OS temp directory.
70
+ */
71
+ async function createInternalSocketPath() {
72
+ const dir = join(tmpdir(), `senpi-rpc-host-internal-${randomUUID().slice(0, 8)}`);
73
+ await mkdir(dir, { recursive: false, mode: 0o700 });
74
+ return { socket: join(dir, "host.sock"), dir };
75
+ }
76
+ export function parseColdStart(value) {
77
+ return value === "transient" || value === "persistent" ? value : undefined;
78
+ }
79
+ export function parseIdleExitMs(value) {
80
+ if (value === undefined || !/^\d+$/.test(value.trim()))
81
+ return undefined;
82
+ const parsed = Number(value.trim());
83
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
84
+ }
85
+ /**
86
+ * Resolves the effective host policy. Precedence: environment overrides beat
87
+ * settings.json, which beats the documented defaults (transient, 15 minutes).
88
+ * Invalid values at either source fall through to the next source.
89
+ */
90
+ export function resolveHostPolicy(settings, env) {
91
+ const record = isRecord(settings) ? settings : {};
92
+ const coldStart = parseColdStart(env[HOST_COLD_START_ENV]) ?? parseColdStart(asOptionalString(record.coldStart)) ?? "transient";
93
+ const idleExitMs = parseIdleExitMs(env[HOST_IDLE_EXIT_MS_ENV]) ??
94
+ parseIdleExitMs(asOptionalString(record.idleExitMs)) ??
95
+ DEFAULT_HOST_IDLE_EXIT_MS;
96
+ return { coldStart, idleExitMs };
97
+ }
98
+ /**
99
+ * Pure idle-window decision core. `update()` must be called with the CURRENT
100
+ * activity state; the window only counts continuously idle time and any
101
+ * activity resets it, so a busy host can never cross the threshold.
102
+ */
103
+ export class IdleExitDecider {
104
+ constructor(idleExitMs, now = Date.now) {
105
+ this.idleExitMs = idleExitMs;
106
+ this.now = now;
107
+ }
108
+ update(activity) {
109
+ // Any attachment or active turn both holds the host open and resets the
110
+ // window, so only CONTINUOUS idle can ever cross the threshold.
111
+ if (activity.connections > 0 || activity.activeTurns > 0) {
112
+ this.idleSince = undefined;
113
+ return "active";
114
+ }
115
+ if (this.idleExitMs === Number.POSITIVE_INFINITY)
116
+ return "idle";
117
+ if (this.idleSince === undefined) {
118
+ this.idleSince = this.now();
119
+ return "idle";
120
+ }
121
+ return this.now() - this.idleSince >= this.idleExitMs ? "exit" : "idle";
122
+ }
123
+ }
124
+ /** `--socket <path>` selects the public socket; every other argument is forwarded to the host CLI. */
125
+ export function parseSupervisorArgs(argv) {
126
+ const hostArgs = [];
127
+ let socket;
128
+ let childCommand;
129
+ let childArgs;
130
+ let agentDir;
131
+ for (let index = 0; index < argv.length; index++) {
132
+ const arg = argv[index];
133
+ if (arg === "--socket" && index + 1 < argv.length) {
134
+ socket = argv[++index];
135
+ continue;
136
+ }
137
+ if (arg === "--child-command" && index + 1 < argv.length) {
138
+ childCommand = argv[++index];
139
+ continue;
140
+ }
141
+ if (arg === "--child-args" && index + 1 < argv.length) {
142
+ try {
143
+ const parsed = JSON.parse(argv[++index]);
144
+ if (Array.isArray(parsed) && parsed.every((value) => typeof value === "string"))
145
+ childArgs = parsed;
146
+ }
147
+ catch {
148
+ return undefined;
149
+ }
150
+ continue;
151
+ }
152
+ if (arg === "--agent-dir" && index + 1 < argv.length) {
153
+ agentDir = argv[++index];
154
+ continue;
155
+ }
156
+ hostArgs.push(arg);
157
+ }
158
+ return socket === undefined ? undefined : { socket, hostArgs, childCommand, childArgs, agentDir };
159
+ }
160
+ /** Resolves the committed CLI entry this supervisor wraps (source tree or built dist). */
161
+ export function resolveCliMainPath() {
162
+ const modulePath = fileURLToPath(import.meta.url);
163
+ const extension = modulePath.endsWith(".ts") ? ".ts" : ".js";
164
+ return resolve(dirname(modulePath), "..", "..", `cli-main${extension}`);
165
+ }
166
+ export async function runHostSupervisor(launch) {
167
+ const paths = createHostDaemonPaths(launch.agentDir ?? getAgentDir());
168
+ const policy = resolveHostPolicy(await readSettingsFile(paths.settingsFile), process.env);
169
+ const publicSocket = launch.socket;
170
+ const internal = await createInternalSocketPath();
171
+ const internalSocket = internal.socket;
172
+ const clientSockets = new Set();
173
+ const busySessions = new Map();
174
+ let observerHealthy = false;
175
+ let shuttingDown = false;
176
+ let shutdownPromise;
177
+ const child = spawn(launch.childCommand ?? process.execPath, launch.childCommand
178
+ ? [...(launch.childArgs ?? []), "--listen", `unix://${internalSocket}`]
179
+ : [
180
+ ...process.execArgv,
181
+ resolveCliMainPath(),
182
+ "--mode",
183
+ "rpc",
184
+ "--multi-session",
185
+ "--listen",
186
+ `unix://${internalSocket}`,
187
+ ...launch.hostArgs,
188
+ ], {
189
+ env: {
190
+ ...process.env,
191
+ [HOST_WATCH_FD_ENV]: String(CHILD_WATCH_FD),
192
+ [HOST_WATCH_PPID_ENV]: String(process.pid),
193
+ [HOST_SCRATCH_DIR_ENV]: internal.dir,
194
+ },
195
+ // Slot 3 is the lifetime pipe: "pipe" gives the child a read end it can
196
+ // wait on and keeps the write end owned by this process alone.
197
+ stdio: ["ignore", "ignore", "inherit", "pipe"],
198
+ });
199
+ // Nothing is ever written; the pipe exists purely so its EOF is a reliable
200
+ // death notification. Errors on it must not crash the supervisor.
201
+ child.stdio[CHILD_WATCH_FD]?.on("error", () => { });
202
+ child.once("exit", (code, signal) => {
203
+ if (!shuttingDown)
204
+ void shutdown(`rpc host process exited unexpectedly (${code ?? signal})`, 1);
205
+ });
206
+ const server = createServer((client) => {
207
+ if (shuttingDown) {
208
+ client.destroy();
209
+ return;
210
+ }
211
+ const internal = createConnection(internalSocket);
212
+ clientSockets.add(client);
213
+ const detach = () => {
214
+ clientSockets.delete(client);
215
+ internal.destroy();
216
+ client.destroy();
217
+ };
218
+ client.pipe(internal);
219
+ internal.pipe(client);
220
+ client.once("close", detach);
221
+ client.once("error", detach);
222
+ internal.once("close", detach);
223
+ internal.once("error", detach);
224
+ });
225
+ server.once("error", (cause) => {
226
+ if (!shuttingDown)
227
+ void shutdown(`public socket listener failed: ${errorMessage(cause)}`, 1);
228
+ });
229
+ const decider = new IdleExitDecider(policy.coldStart === "persistent" ? Number.POSITIVE_INFINITY : policy.idleExitMs);
230
+ const tickIntervalMs = Math.max(20, Math.min(1_000, policy.idleExitMs / 4));
231
+ const ticker = setInterval(() => {
232
+ if (decider.update(currentActivity()) === "exit")
233
+ void shutdown("idle", 0);
234
+ }, tickIntervalMs);
235
+ function currentActivity() {
236
+ return {
237
+ connections: clientSockets.size,
238
+ activeTurns: observerHealthy ? countBusySessions() : 1,
239
+ };
240
+ }
241
+ function countBusySessions() {
242
+ let busy = 0;
243
+ for (const count of busySessions.values())
244
+ if (count > 0)
245
+ busy++;
246
+ return busy;
247
+ }
248
+ function observeHostEvent(line) {
249
+ let event;
250
+ try {
251
+ event = JSON.parse(line);
252
+ }
253
+ catch {
254
+ return;
255
+ }
256
+ if (typeof event !== "object" || event === null)
257
+ return;
258
+ const { type, sessionId } = event;
259
+ if (typeof sessionId !== "string")
260
+ return;
261
+ if (type === "agent_start")
262
+ busySessions.set(sessionId, (busySessions.get(sessionId) ?? 0) + 1);
263
+ else if (type === "agent_settled")
264
+ busySessions.set(sessionId, Math.max(0, (busySessions.get(sessionId) ?? 1) - 1));
265
+ }
266
+ async function shutdown(reason, exitCode) {
267
+ // Single-flight: concurrent triggers (listener error, child exit, signals)
268
+ // must not process.exit mid-cleanup. Late callers park on this promise while
269
+ // the first shutdown finishes tearing down and exits.
270
+ shutdownPromise ??= performShutdown(reason, exitCode);
271
+ return shutdownPromise;
272
+ }
273
+ async function performShutdown(reason, exitCode) {
274
+ if (shuttingDown)
275
+ process.exit(exitCode);
276
+ shuttingDown = true;
277
+ clearInterval(ticker);
278
+ writeStderrLine(`senpi rpc host supervisor: ${reason} shutdown`);
279
+ for (const client of clientSockets)
280
+ client.destroy();
281
+ await closeServer(server);
282
+ // Unlink the private directory BEFORE the child stop, which can take seconds:
283
+ // an external SIGKILL landing during that wait (ensureHost escalates while
284
+ // replacing a host) would otherwise leave the directory behind. The child
285
+ // keeps serving through its already-open socket fd until it exits, and its
286
+ // own watchdog cleanup makes the removal idempotent.
287
+ await rm(internal.dir, { recursive: true, force: true });
288
+ await stopChild(child);
289
+ observer?.destroy();
290
+ await rm(publicSocket, { force: true });
291
+ // Mirror ensureHost's cleanupState: the pidfile and settings describe a
292
+ // live host only; the stderr log stays for diagnostics.
293
+ await rm(paths.pidFile, { force: true });
294
+ await rm(paths.settingsFile, { force: true });
295
+ process.exit(exitCode);
296
+ }
297
+ let observer;
298
+ // Registered before the startup handshake, not after it: the private internal
299
+ // directory already exists at this point, so a SIGTERM arriving during host
300
+ // startup must run the same cleanup instead of Node's default kill, which
301
+ // would leave that directory behind.
302
+ registerSupervisorSignals(shutdown);
303
+ try {
304
+ await waitForListener(internalSocket, 30_000);
305
+ observer = createConnection(internalSocket);
306
+ await waitForConnect(observer, 5_000);
307
+ observerHealthy = true;
308
+ attachJsonlLineReader(observer, observeHostEvent, { maxLineLength: MAX_RPC_LINE_CHARACTERS });
309
+ observer.once("close", () => {
310
+ observerHealthy = false;
311
+ });
312
+ observer.once("error", () => {
313
+ observerHealthy = false;
314
+ });
315
+ await prepareSocketPath(publicSocket);
316
+ await listen(server, publicSocket);
317
+ }
318
+ catch (cause) {
319
+ await shutdown(`startup failed: ${errorMessage(cause)}`, 1);
320
+ }
321
+ writeStderrLine(`senpi rpc host ready on unix://${publicSocket} (coldStart=${policy.coldStart}, idleExitMs=${policy.coldStart === "persistent" ? "never" : String(policy.idleExitMs)})`);
322
+ await new Promise(() => { });
323
+ }
324
+ /** External stop (ensureHost replacement, tests, QA) must clean up like idle exit. */
325
+ function registerSupervisorSignals(shutdown) {
326
+ for (const signal of process.platform === "win32" ? ["SIGTERM"] : ["SIGTERM", "SIGHUP"]) {
327
+ process.on(signal, () => {
328
+ void shutdown(`signal:${signal}`, signal === "SIGHUP" ? 129 : 143);
329
+ });
330
+ }
331
+ }
332
+ async function readSettingsFile(settingsFile) {
333
+ try {
334
+ return JSON.parse(await readFile(settingsFile, "utf8"));
335
+ }
336
+ catch {
337
+ return undefined;
338
+ }
339
+ }
340
+ async function stopChild(child) {
341
+ if (child.exitCode !== null || child.signalCode !== null)
342
+ return;
343
+ try {
344
+ child.kill("SIGTERM");
345
+ }
346
+ catch {
347
+ return;
348
+ }
349
+ if (await waitForChildExit(child, CHILD_STOP_TIMEOUT_MS))
350
+ return;
351
+ try {
352
+ child.kill("SIGKILL");
353
+ }
354
+ catch {
355
+ return;
356
+ }
357
+ await waitForChildExit(child, 2_000);
358
+ }
359
+ function waitForChildExit(child, timeoutMs) {
360
+ if (child.exitCode !== null || child.signalCode !== null)
361
+ return Promise.resolve(true);
362
+ return new Promise((resolve) => {
363
+ const timer = setTimeout(() => {
364
+ child.off("exit", onExit);
365
+ resolve(false);
366
+ }, timeoutMs);
367
+ const onExit = () => {
368
+ clearTimeout(timer);
369
+ resolve(true);
370
+ };
371
+ child.once("exit", onExit);
372
+ });
373
+ }
374
+ async function waitForListener(socketPath, timeoutMs) {
375
+ const deadline = Date.now() + timeoutMs;
376
+ while (Date.now() <= deadline) {
377
+ if (await canConnect(socketPath))
378
+ return;
379
+ await delay(50);
380
+ }
381
+ throw new Error(`${socketPath}: host did not start listening within ${timeoutMs}ms`);
382
+ }
383
+ function canConnect(socketPath) {
384
+ return new Promise((resolve) => {
385
+ const socket = createConnection(socketPath);
386
+ const settle = (value) => {
387
+ socket.destroy();
388
+ resolve(value);
389
+ };
390
+ socket.once("connect", () => settle(true));
391
+ socket.once("error", () => settle(false));
392
+ });
393
+ }
394
+ function waitForConnect(socket, timeoutMs) {
395
+ return new Promise((resolve, reject) => {
396
+ const timer = setTimeout(() => {
397
+ cleanup();
398
+ reject(new Error(`observer connection to internal host timed out after ${timeoutMs}ms`));
399
+ }, timeoutMs);
400
+ const onConnect = () => {
401
+ cleanup();
402
+ resolve();
403
+ };
404
+ const onError = (cause) => {
405
+ cleanup();
406
+ reject(cause);
407
+ };
408
+ const cleanup = () => {
409
+ clearTimeout(timer);
410
+ socket.off("connect", onConnect);
411
+ socket.off("error", onError);
412
+ };
413
+ socket.once("connect", onConnect);
414
+ socket.once("error", onError);
415
+ });
416
+ }
417
+ async function prepareSocketPath(socketPath) {
418
+ await mkdir(dirname(socketPath), { recursive: true, mode: 0o700 });
419
+ try {
420
+ await access(socketPath);
421
+ }
422
+ catch (cause) {
423
+ if (isNodeErrorCode(cause, "ENOENT"))
424
+ return;
425
+ throw cause;
426
+ }
427
+ if (await canConnect(socketPath))
428
+ throw new Error(`${socketPath}: address already in use by a live server.`);
429
+ await unlink(socketPath);
430
+ }
431
+ function listen(server, socketPath) {
432
+ return new Promise((resolve, reject) => {
433
+ server.once("error", reject);
434
+ server.listen(socketPath, async () => {
435
+ server.off("error", reject);
436
+ if (!socketPath.startsWith("\0"))
437
+ await chmod(socketPath, 0o600);
438
+ resolve();
439
+ });
440
+ });
441
+ }
442
+ function closeServer(server) {
443
+ return new Promise((resolve) => {
444
+ server.close(() => resolve());
445
+ });
446
+ }
447
+ function writeStderrLine(text) {
448
+ // A detached daemon exiting right after an async stderr.write to a file can
449
+ // lose the output entirely; write synchronously so diagnostics always land.
450
+ try {
451
+ writeSync(2, `${text}\n`);
452
+ }
453
+ catch {
454
+ /* fd 2 unavailable: nothing more we can do. */
455
+ }
456
+ }
457
+ function delay(ms) {
458
+ return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
459
+ }
460
+ function errorMessage(cause) {
461
+ return cause instanceof Error ? cause.message : String(cause);
462
+ }
463
+ function isNodeErrorCode(cause, code) {
464
+ return cause instanceof Error && "code" in cause && cause.code === code;
465
+ }
466
+ function isRecord(value) {
467
+ return typeof value === "object" && value !== null && !Array.isArray(value);
468
+ }
469
+ function asOptionalString(value) {
470
+ return typeof value === "string" ? value : typeof value === "number" ? String(value) : undefined;
471
+ }
472
+ function isEntryScript() {
473
+ const entry = process.argv[1];
474
+ if (!entry)
475
+ return false;
476
+ try {
477
+ return fileURLToPath(import.meta.url) === realpathSync(entry);
478
+ }
479
+ catch {
480
+ return false;
481
+ }
482
+ }
483
+ if (isEntryScript()) {
484
+ const launch = parseSupervisorArgs(process.argv.slice(2));
485
+ if (!launch) {
486
+ writeStderrLine("usage: host-lifecycle.ts --socket <path> [host cli args...]");
487
+ process.exit(2);
488
+ }
489
+ void runHostSupervisor(launch);
490
+ }
491
+ //# sourceMappingURL=host-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-lifecycle.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * Lifecycle supervisor for the shared RPC socket host started by ensureHost().\n *\n * Process tree:\n *\n * ensureHost() ──detached──▶ host-lifecycle.ts (this supervisor, owns the pidfile)\n * │ byte-proxies the public socket\n * ▼\n * cli-main --mode rpc --listen unix://<public>.internal\n *\n * The supervisor exists to enforce the host lifecycle policy without touching the\n * RPC host itself:\n *\n * - cold start: `transient` (default) means the host lives for the current login\n * session and idle-exits; `persistent` never idle-exits.\n * - idle exit: after a continuous window with zero attached client connections\n * and zero active agent turns, the supervisor tears the host down cleanly\n * (child SIGTERM first so the host flushes pending output and removes its own\n * socket, then pidfile/settings removal mirroring ensureHost's cleanupState).\n *\n * Observability without host changes: proxying the public socket yields the\n * exact connection count, and the supervisor keeps one always-on observer\n * connection to the internal socket. The multi-session host broadcasts every\n * session lifecycle/agent event to every connection, so the observer sees\n * `agent_start`/`agent_settled` for all sessions even when no client is\n * attached. If the observer connection is ever unhealthy, activity is reported\n * as unknown (non-idle), so a broken observer can only keep the host alive,\n * never kill it mid-turn.\n *\n * Lifetime binding: the host is spawned with an extra inherited pipe on fd 3\n * whose write end this supervisor holds and never writes to. The kernel closes\n * that end whenever the supervisor dies - including SIGKILL, an OOM kill, or a\n * crash, where no JS handler runs at all - so the host reads EOF and shuts down\n * cleanly, removing the private internal directory. `stopChild()` remains the\n * fast path for orderly shutdowns; the pipe is what makes an orphaned host\n * impossible. `SENPI_RPC_HOST_WATCH_PPID` is passed alongside as a belt-and-\n * braces fallback for platforms where the extra fd is not inherited.\n */\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { realpathSync, writeSync } from \"node:fs\";\nimport { access, chmod, mkdir, readFile, rm, unlink } from \"node:fs/promises\";\nimport { createConnection, createServer, type Server, type Socket } from \"node:net\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { createHostDaemonPaths } from \"./host-ensure.ts\";\nimport { HOST_SCRATCH_DIR_ENV, HOST_WATCH_FD_ENV, HOST_WATCH_PPID_ENV } from \"./host-watchdog.ts\";\nimport { attachJsonlLineReader, MAX_RPC_LINE_CHARACTERS } from \"./jsonl.ts\";\n\nexport type HostColdStart = \"transient\" | \"persistent\";\n\n/** Environment override for the cold-start policy: `transient` or `persistent`. */\nexport const HOST_COLD_START_ENV = \"SENPI_RPC_HOST_COLD_START\";\n/** Environment override for the idle-exit window in milliseconds. */\nexport const HOST_IDLE_EXIT_MS_ENV = \"SENPI_RPC_HOST_IDLE_EXIT_MS\";\n/** Default idle-exit window: 15 minutes of continuous no-connection, no-turn idle. */\nexport const DEFAULT_HOST_IDLE_EXIT_MS = 15 * 60_000;\n\n/** The policy fields ensureHost() records in rpc-host-daemon/settings.json. */\nexport interface HostLifecyclePolicyInput {\n\treadonly coldStart?: HostColdStart;\n\treadonly idleExitMs?: number;\n}\n\nexport interface HostLifecyclePolicy {\n\treadonly coldStart: HostColdStart;\n\treadonly idleExitMs: number;\n}\n\nconst CHILD_STOP_TIMEOUT_MS = 5_000;\n\n/**\n * Child stdio slot carrying the supervisor-lifetime pipe. The supervisor holds\n * the write end open and never writes; the kernel closes it when the supervisor\n * dies for ANY reason (SIGKILL, OOM kill, crash), so the host sees EOF on this\n * fd and shuts itself down. Catchable-signal cleanup alone cannot do this.\n */\nconst CHILD_WATCH_FD = 3;\n\n/**\n * The internal hop must stay short enough for sun_path (104 bytes on macOS)\n * regardless of where the public socket lives, and private against other local\n * users, so it gets its own 0700 directory under the OS temp directory.\n */\nasync function createInternalSocketPath(): Promise<{ socket: string; dir: string }> {\n\tconst dir = join(tmpdir(), `senpi-rpc-host-internal-${randomUUID().slice(0, 8)}`);\n\tawait mkdir(dir, { recursive: false, mode: 0o700 });\n\treturn { socket: join(dir, \"host.sock\"), dir };\n}\n\nexport function parseColdStart(value: string | undefined): HostColdStart | undefined {\n\treturn value === \"transient\" || value === \"persistent\" ? value : undefined;\n}\n\nexport function parseIdleExitMs(value: string | undefined): number | undefined {\n\tif (value === undefined || !/^\\d+$/.test(value.trim())) return undefined;\n\tconst parsed = Number(value.trim());\n\treturn Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n}\n\n/**\n * Resolves the effective host policy. Precedence: environment overrides beat\n * settings.json, which beats the documented defaults (transient, 15 minutes).\n * Invalid values at either source fall through to the next source.\n */\nexport function resolveHostPolicy(\n\tsettings: unknown,\n\tenv: Readonly<Record<string, string | undefined>>,\n): HostLifecyclePolicy {\n\tconst record = isRecord(settings) ? settings : {};\n\tconst coldStart =\n\t\tparseColdStart(env[HOST_COLD_START_ENV]) ?? parseColdStart(asOptionalString(record.coldStart)) ?? \"transient\";\n\tconst idleExitMs =\n\t\tparseIdleExitMs(env[HOST_IDLE_EXIT_MS_ENV]) ??\n\t\tparseIdleExitMs(asOptionalString(record.idleExitMs)) ??\n\t\tDEFAULT_HOST_IDLE_EXIT_MS;\n\treturn { coldStart, idleExitMs };\n}\n\nexport interface HostActivity {\n\treadonly connections: number;\n\treadonly activeTurns: number;\n}\n\nexport type IdleExitDecision = \"active\" | \"idle\" | \"exit\";\n\n/**\n * Pure idle-window decision core. `update()` must be called with the CURRENT\n * activity state; the window only counts continuously idle time and any\n * activity resets it, so a busy host can never cross the threshold.\n */\nexport class IdleExitDecider {\n\tprivate idleSince: number | undefined;\n\tprivate readonly now: () => number;\n\treadonly idleExitMs: number;\n\n\tconstructor(idleExitMs: number, now: () => number = Date.now) {\n\t\tthis.idleExitMs = idleExitMs;\n\t\tthis.now = now;\n\t}\n\n\tupdate(activity: HostActivity): IdleExitDecision {\n\t\t// Any attachment or active turn both holds the host open and resets the\n\t\t// window, so only CONTINUOUS idle can ever cross the threshold.\n\t\tif (activity.connections > 0 || activity.activeTurns > 0) {\n\t\t\tthis.idleSince = undefined;\n\t\t\treturn \"active\";\n\t\t}\n\t\tif (this.idleExitMs === Number.POSITIVE_INFINITY) return \"idle\";\n\t\tif (this.idleSince === undefined) {\n\t\t\tthis.idleSince = this.now();\n\t\t\treturn \"idle\";\n\t\t}\n\t\treturn this.now() - this.idleSince >= this.idleExitMs ? \"exit\" : \"idle\";\n\t}\n}\n\nexport interface SupervisorLaunch {\n\treadonly socket: string;\n\treadonly hostArgs: readonly string[];\n\t/** Optional runtime command used by rebranded/bundled callers. */\n\treadonly childCommand?: string;\n\treadonly childArgs?: readonly string[];\n\t/** Explicit ownership directory for callers whose environment is not yet branded. */\n\treadonly agentDir?: string;\n}\n\n/** `--socket <path>` selects the public socket; every other argument is forwarded to the host CLI. */\nexport function parseSupervisorArgs(argv: readonly string[]): SupervisorLaunch | undefined {\n\tconst hostArgs: string[] = [];\n\tlet socket: string | undefined;\n\tlet childCommand: string | undefined;\n\tlet childArgs: readonly string[] | undefined;\n\tlet agentDir: string | undefined;\n\tfor (let index = 0; index < argv.length; index++) {\n\t\tconst arg = argv[index];\n\t\tif (arg === \"--socket\" && index + 1 < argv.length) {\n\t\t\tsocket = argv[++index];\n\t\t\tcontinue;\n\t\t}\n\t\tif (arg === \"--child-command\" && index + 1 < argv.length) {\n\t\t\tchildCommand = argv[++index];\n\t\t\tcontinue;\n\t\t}\n\t\tif (arg === \"--child-args\" && index + 1 < argv.length) {\n\t\t\ttry {\n\t\t\t\tconst parsed: unknown = JSON.parse(argv[++index]);\n\t\t\t\tif (Array.isArray(parsed) && parsed.every((value) => typeof value === \"string\")) childArgs = parsed;\n\t\t\t} catch {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (arg === \"--agent-dir\" && index + 1 < argv.length) {\n\t\t\tagentDir = argv[++index];\n\t\t\tcontinue;\n\t\t}\n\t\thostArgs.push(arg);\n\t}\n\treturn socket === undefined ? undefined : { socket, hostArgs, childCommand, childArgs, agentDir };\n}\n\n/** Resolves the committed CLI entry this supervisor wraps (source tree or built dist). */\nexport function resolveCliMainPath(): string {\n\tconst modulePath = fileURLToPath(import.meta.url);\n\tconst extension = modulePath.endsWith(\".ts\") ? \".ts\" : \".js\";\n\treturn resolve(dirname(modulePath), \"..\", \"..\", `cli-main${extension}`);\n}\n\nexport async function runHostSupervisor(launch: SupervisorLaunch): Promise<void> {\n\tconst paths = createHostDaemonPaths(launch.agentDir ?? getAgentDir());\n\tconst policy = resolveHostPolicy(await readSettingsFile(paths.settingsFile), process.env);\n\tconst publicSocket = launch.socket;\n\tconst internal = await createInternalSocketPath();\n\tconst internalSocket = internal.socket;\n\tconst clientSockets = new Set<Socket>();\n\tconst busySessions = new Map<string, number>();\n\tlet observerHealthy = false;\n\tlet shuttingDown = false;\n\tlet shutdownPromise: Promise<never> | undefined;\n\n\tconst child = spawn(\n\t\tlaunch.childCommand ?? process.execPath,\n\t\tlaunch.childCommand\n\t\t\t? [...(launch.childArgs ?? []), \"--listen\", `unix://${internalSocket}`]\n\t\t\t: [\n\t\t\t\t\t...process.execArgv,\n\t\t\t\t\tresolveCliMainPath(),\n\t\t\t\t\t\"--mode\",\n\t\t\t\t\t\"rpc\",\n\t\t\t\t\t\"--multi-session\",\n\t\t\t\t\t\"--listen\",\n\t\t\t\t\t`unix://${internalSocket}`,\n\t\t\t\t\t...launch.hostArgs,\n\t\t\t\t],\n\t\t{\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\t[HOST_WATCH_FD_ENV]: String(CHILD_WATCH_FD),\n\t\t\t\t[HOST_WATCH_PPID_ENV]: String(process.pid),\n\t\t\t\t[HOST_SCRATCH_DIR_ENV]: internal.dir,\n\t\t\t},\n\t\t\t// Slot 3 is the lifetime pipe: \"pipe\" gives the child a read end it can\n\t\t\t// wait on and keeps the write end owned by this process alone.\n\t\t\tstdio: [\"ignore\", \"ignore\", \"inherit\", \"pipe\"],\n\t\t},\n\t);\n\t// Nothing is ever written; the pipe exists purely so its EOF is a reliable\n\t// death notification. Errors on it must not crash the supervisor.\n\tchild.stdio[CHILD_WATCH_FD]?.on(\"error\", () => {});\n\tchild.once(\"exit\", (code, signal) => {\n\t\tif (!shuttingDown) void shutdown(`rpc host process exited unexpectedly (${code ?? signal})`, 1);\n\t});\n\n\tconst server = createServer((client) => {\n\t\tif (shuttingDown) {\n\t\t\tclient.destroy();\n\t\t\treturn;\n\t\t}\n\t\tconst internal = createConnection(internalSocket);\n\t\tclientSockets.add(client);\n\t\tconst detach = (): void => {\n\t\t\tclientSockets.delete(client);\n\t\t\tinternal.destroy();\n\t\t\tclient.destroy();\n\t\t};\n\t\tclient.pipe(internal);\n\t\tinternal.pipe(client);\n\t\tclient.once(\"close\", detach);\n\t\tclient.once(\"error\", detach);\n\t\tinternal.once(\"close\", detach);\n\t\tinternal.once(\"error\", detach);\n\t});\n\tserver.once(\"error\", (cause) => {\n\t\tif (!shuttingDown) void shutdown(`public socket listener failed: ${errorMessage(cause)}`, 1);\n\t});\n\n\tconst decider = new IdleExitDecider(\n\t\tpolicy.coldStart === \"persistent\" ? Number.POSITIVE_INFINITY : policy.idleExitMs,\n\t);\n\tconst tickIntervalMs = Math.max(20, Math.min(1_000, policy.idleExitMs / 4));\n\tconst ticker = setInterval(() => {\n\t\tif (decider.update(currentActivity()) === \"exit\") void shutdown(\"idle\", 0);\n\t}, tickIntervalMs);\n\n\tfunction currentActivity(): HostActivity {\n\t\treturn {\n\t\t\tconnections: clientSockets.size,\n\t\t\tactiveTurns: observerHealthy ? countBusySessions() : 1,\n\t\t};\n\t}\n\n\tfunction countBusySessions(): number {\n\t\tlet busy = 0;\n\t\tfor (const count of busySessions.values()) if (count > 0) busy++;\n\t\treturn busy;\n\t}\n\n\tfunction observeHostEvent(line: string): void {\n\t\tlet event: unknown;\n\t\ttry {\n\t\t\tevent = JSON.parse(line);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tif (typeof event !== \"object\" || event === null) return;\n\t\tconst { type, sessionId } = event as { type?: unknown; sessionId?: unknown };\n\t\tif (typeof sessionId !== \"string\") return;\n\t\tif (type === \"agent_start\") busySessions.set(sessionId, (busySessions.get(sessionId) ?? 0) + 1);\n\t\telse if (type === \"agent_settled\")\n\t\t\tbusySessions.set(sessionId, Math.max(0, (busySessions.get(sessionId) ?? 1) - 1));\n\t}\n\n\tasync function shutdown(reason: string, exitCode: number): Promise<never> {\n\t\t// Single-flight: concurrent triggers (listener error, child exit, signals)\n\t\t// must not process.exit mid-cleanup. Late callers park on this promise while\n\t\t// the first shutdown finishes tearing down and exits.\n\t\tshutdownPromise ??= performShutdown(reason, exitCode);\n\t\treturn shutdownPromise;\n\t}\n\n\tasync function performShutdown(reason: string, exitCode: number): Promise<never> {\n\t\tif (shuttingDown) process.exit(exitCode);\n\t\tshuttingDown = true;\n\t\tclearInterval(ticker);\n\t\twriteStderrLine(`senpi rpc host supervisor: ${reason} shutdown`);\n\t\tfor (const client of clientSockets) client.destroy();\n\t\tawait closeServer(server);\n\t\t// Unlink the private directory BEFORE the child stop, which can take seconds:\n\t\t// an external SIGKILL landing during that wait (ensureHost escalates while\n\t\t// replacing a host) would otherwise leave the directory behind. The child\n\t\t// keeps serving through its already-open socket fd until it exits, and its\n\t\t// own watchdog cleanup makes the removal idempotent.\n\t\tawait rm(internal.dir, { recursive: true, force: true });\n\t\tawait stopChild(child);\n\t\tobserver?.destroy();\n\t\tawait rm(publicSocket, { force: true });\n\t\t// Mirror ensureHost's cleanupState: the pidfile and settings describe a\n\t\t// live host only; the stderr log stays for diagnostics.\n\t\tawait rm(paths.pidFile, { force: true });\n\t\tawait rm(paths.settingsFile, { force: true });\n\t\tprocess.exit(exitCode);\n\t}\n\n\tlet observer: Socket | undefined;\n\t// Registered before the startup handshake, not after it: the private internal\n\t// directory already exists at this point, so a SIGTERM arriving during host\n\t// startup must run the same cleanup instead of Node's default kill, which\n\t// would leave that directory behind.\n\tregisterSupervisorSignals(shutdown);\n\ttry {\n\t\tawait waitForListener(internalSocket, 30_000);\n\t\tobserver = createConnection(internalSocket);\n\t\tawait waitForConnect(observer, 5_000);\n\t\tobserverHealthy = true;\n\t\tattachJsonlLineReader(observer, observeHostEvent, { maxLineLength: MAX_RPC_LINE_CHARACTERS });\n\t\tobserver.once(\"close\", () => {\n\t\t\tobserverHealthy = false;\n\t\t});\n\t\tobserver.once(\"error\", () => {\n\t\t\tobserverHealthy = false;\n\t\t});\n\t\tawait prepareSocketPath(publicSocket);\n\t\tawait listen(server, publicSocket);\n\t} catch (cause) {\n\t\tawait shutdown(`startup failed: ${errorMessage(cause)}`, 1);\n\t}\n\twriteStderrLine(\n\t\t`senpi rpc host ready on unix://${publicSocket} (coldStart=${policy.coldStart}, idleExitMs=${\n\t\t\tpolicy.coldStart === \"persistent\" ? \"never\" : String(policy.idleExitMs)\n\t\t})`,\n\t);\n\tawait new Promise<never>(() => {});\n}\n\n/** External stop (ensureHost replacement, tests, QA) must clean up like idle exit. */\nfunction registerSupervisorSignals(shutdown: (reason: string, exitCode: number) => Promise<never>): void {\n\tfor (const signal of process.platform === \"win32\" ? ([\"SIGTERM\"] as const) : ([\"SIGTERM\", \"SIGHUP\"] as const)) {\n\t\tprocess.on(signal, () => {\n\t\t\tvoid shutdown(`signal:${signal}`, signal === \"SIGHUP\" ? 129 : 143);\n\t\t});\n\t}\n}\n\nasync function readSettingsFile(settingsFile: string): Promise<unknown> {\n\ttry {\n\t\treturn JSON.parse(await readFile(settingsFile, \"utf8\"));\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nasync function stopChild(child: ChildProcess): Promise<void> {\n\tif (child.exitCode !== null || child.signalCode !== null) return;\n\ttry {\n\t\tchild.kill(\"SIGTERM\");\n\t} catch {\n\t\treturn;\n\t}\n\tif (await waitForChildExit(child, CHILD_STOP_TIMEOUT_MS)) return;\n\ttry {\n\t\tchild.kill(\"SIGKILL\");\n\t} catch {\n\t\treturn;\n\t}\n\tawait waitForChildExit(child, 2_000);\n}\n\nfunction waitForChildExit(child: ChildProcess, timeoutMs: number): Promise<boolean> {\n\tif (child.exitCode !== null || child.signalCode !== null) return Promise.resolve(true);\n\treturn new Promise((resolve) => {\n\t\tconst timer = setTimeout(() => {\n\t\t\tchild.off(\"exit\", onExit);\n\t\t\tresolve(false);\n\t\t}, timeoutMs);\n\t\tconst onExit = (): void => {\n\t\t\tclearTimeout(timer);\n\t\t\tresolve(true);\n\t\t};\n\t\tchild.once(\"exit\", onExit);\n\t});\n}\n\nasync function waitForListener(socketPath: string, timeoutMs: number): Promise<void> {\n\tconst deadline = Date.now() + timeoutMs;\n\twhile (Date.now() <= deadline) {\n\t\tif (await canConnect(socketPath)) return;\n\t\tawait delay(50);\n\t}\n\tthrow new Error(`${socketPath}: host did not start listening within ${timeoutMs}ms`);\n}\n\nfunction canConnect(socketPath: string): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst socket = createConnection(socketPath);\n\t\tconst settle = (value: boolean): void => {\n\t\t\tsocket.destroy();\n\t\t\tresolve(value);\n\t\t};\n\t\tsocket.once(\"connect\", () => settle(true));\n\t\tsocket.once(\"error\", () => settle(false));\n\t});\n}\n\nfunction waitForConnect(socket: Socket, timeoutMs: number): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst timer = setTimeout(() => {\n\t\t\tcleanup();\n\t\t\treject(new Error(`observer connection to internal host timed out after ${timeoutMs}ms`));\n\t\t}, timeoutMs);\n\t\tconst onConnect = (): void => {\n\t\t\tcleanup();\n\t\t\tresolve();\n\t\t};\n\t\tconst onError = (cause: Error): void => {\n\t\t\tcleanup();\n\t\t\treject(cause);\n\t\t};\n\t\tconst cleanup = (): void => {\n\t\t\tclearTimeout(timer);\n\t\t\tsocket.off(\"connect\", onConnect);\n\t\t\tsocket.off(\"error\", onError);\n\t\t};\n\t\tsocket.once(\"connect\", onConnect);\n\t\tsocket.once(\"error\", onError);\n\t});\n}\n\nasync function prepareSocketPath(socketPath: string): Promise<void> {\n\tawait mkdir(dirname(socketPath), { recursive: true, mode: 0o700 });\n\ttry {\n\t\tawait access(socketPath);\n\t} catch (cause) {\n\t\tif (isNodeErrorCode(cause, \"ENOENT\")) return;\n\t\tthrow cause;\n\t}\n\tif (await canConnect(socketPath)) throw new Error(`${socketPath}: address already in use by a live server.`);\n\tawait unlink(socketPath);\n}\n\nfunction listen(server: Server, socketPath: string): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tserver.once(\"error\", reject);\n\t\tserver.listen(socketPath, async () => {\n\t\t\tserver.off(\"error\", reject);\n\t\t\tif (!socketPath.startsWith(\"\\0\")) await chmod(socketPath, 0o600);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction closeServer(server: Server): Promise<void> {\n\treturn new Promise((resolve) => {\n\t\tserver.close(() => resolve());\n\t});\n}\n\nfunction writeStderrLine(text: string): void {\n\t// A detached daemon exiting right after an async stderr.write to a file can\n\t// lose the output entirely; write synchronously so diagnostics always land.\n\ttry {\n\t\twriteSync(2, `${text}\\n`);\n\t} catch {\n\t\t/* fd 2 unavailable: nothing more we can do. */\n\t}\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolveDelay) => setTimeout(resolveDelay, ms));\n}\n\nfunction errorMessage(cause: unknown): string {\n\treturn cause instanceof Error ? cause.message : String(cause);\n}\n\nfunction isNodeErrorCode(cause: unknown, code: string): boolean {\n\treturn cause instanceof Error && \"code\" in cause && cause.code === code;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction asOptionalString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : typeof value === \"number\" ? String(value) : undefined;\n}\n\nfunction isEntryScript(): boolean {\n\tconst entry = process.argv[1];\n\tif (!entry) return false;\n\ttry {\n\t\treturn fileURLToPath(import.meta.url) === realpathSync(entry);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nif (isEntryScript()) {\n\tconst launch = parseSupervisorArgs(process.argv.slice(2));\n\tif (!launch) {\n\t\twriteStderrLine(\"usage: host-lifecycle.ts --socket <path> [host cli args...]\");\n\t\tprocess.exit(2);\n\t}\n\tvoid runHostSupervisor(launch);\n}\n"]}
@@ -0,0 +1,33 @@
1
+ /** Inherited fd whose EOF means "the supervisor died"; set by the supervisor only. */
2
+ export declare const HOST_WATCH_FD_ENV = "SENPI_RPC_HOST_WATCH_FD";
3
+ /** Supervisor-owned private directory the host removes on watchdog shutdown. */
4
+ export declare const HOST_SCRATCH_DIR_ENV = "SENPI_RPC_HOST_SCRATCH_DIR";
5
+ /** Fallback binding when no inherited fd is available: poll this pid. */
6
+ export declare const HOST_WATCH_PPID_ENV = "SENPI_RPC_HOST_WATCH_PPID";
7
+ /** Poll cadence for the ppid fallback. */
8
+ export declare const HOST_WATCH_PPID_INTERVAL_MS = 2000;
9
+ export interface HostWatchdogConfig {
10
+ readonly fd?: number;
11
+ readonly ppid?: number;
12
+ readonly scratchDir?: string;
13
+ }
14
+ /**
15
+ * Reads the watchdog configuration from the environment. Returns `undefined`
16
+ * when neither binding is requested, which is the case for every host launch
17
+ * that does not come from the lifecycle supervisor.
18
+ */
19
+ export declare function readHostWatchdogConfig(env?: Readonly<Record<string, string | undefined>>): HostWatchdogConfig | undefined;
20
+ /** Same configuration, resolved through the brand-aware env prefixes. */
21
+ export declare function readHostWatchdogConfigFromBrandEnv(): HostWatchdogConfig | undefined;
22
+ /**
23
+ * Arms the configured watchdog. `onSupervisorGone` receives the reason and is
24
+ * expected to perform the host's normal clean shutdown; the scratch directory
25
+ * (the supervisor's private socket directory, which no longer has an owner) is
26
+ * removed first so nothing is left behind even if shutdown then hangs.
27
+ *
28
+ * Takes ownership of `config.fd`: disarming (or firing) closes it, so callers
29
+ * must not close it themselves. Returns a disarm function; a no-op when no
30
+ * binding is configured.
31
+ */
32
+ export declare function armHostWatchdog(config: HostWatchdogConfig | undefined, onSupervisorGone: (reason: string) => void): () => void;
33
+ //# sourceMappingURL=host-watchdog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-watchdog.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-watchdog.ts"],"names":[],"mappings":"AAoBA,sFAAsF;AACtF,eAAO,MAAM,iBAAiB,4BAA4B,CAAC;AAC3D,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,+BAA+B,CAAC;AACjE,yEAAyE;AACzE,eAAO,MAAM,mBAAmB,8BAA8B,CAAC;AAC/D,0CAA0C;AAC1C,eAAO,MAAM,2BAA2B,OAAQ,CAAC;AAEjD,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAQD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACrC,GAAG,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAe,GAC7D,kBAAkB,GAAG,SAAS,CAMhC;AAED,yEAAyE;AACzE,wBAAgB,kCAAkC,IAAI,kBAAkB,GAAG,SAAS,CAMnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC9B,MAAM,EAAE,kBAAkB,GAAG,SAAS,EACtC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GACxC,MAAM,IAAI,CAaZ"}