@code-yeongyu/senpi 2026.9.27-4 → 2026.9.28-2
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.
- package/CHANGELOG.md +50 -0
- package/dist/bundle/chunks/{app-server-command-3GVJZFFH.js → app-server-command-H6LFICZ2.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-4SUOOZZ4.js → azure-openai-responses-FCZNBRWE.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/{chunk-PSCNHY7D.js → chunk-3GMFUEU7.js} +1 -1
- package/dist/bundle/chunks/{chunk-HBG6SQU5.js → chunk-62XHQXCZ.js} +1 -1
- package/dist/bundle/chunks/{chunk-EJKCBRYK.js → chunk-6BWCPOCV.js} +4 -4
- package/dist/bundle/chunks/{chunk-SLL375O7.js → chunk-6VD3EBQE.js} +1 -1
- package/dist/bundle/chunks/chunk-7YUGCTEV.js +15 -0
- package/dist/bundle/chunks/{chunk-FWBSGMAE.js → chunk-A4UMPXQF.js} +1 -1
- package/dist/bundle/chunks/chunk-BBK6SSYT.js +2 -0
- package/dist/bundle/chunks/{chunk-C2425O2Q.js → chunk-CNQ4JB45.js} +1 -1
- package/dist/bundle/chunks/{chunk-27IFXX3B.js → chunk-DRN4FQCE.js} +1 -1
- package/dist/bundle/chunks/{chunk-22MGY5JQ.js → chunk-EHSZ7UCW.js} +1 -1
- package/dist/bundle/chunks/chunk-FHJYIU3U.js +5 -0
- package/dist/bundle/chunks/chunk-GJUCSTAG.js +19 -0
- package/dist/bundle/chunks/chunk-KOPTADA3.js +8 -0
- package/dist/bundle/chunks/{chunk-VMKAYXHL.js → chunk-LSW7QSLV.js} +31 -26
- package/dist/bundle/chunks/{chunk-2BMD37YV.js → chunk-LUPPSEQW.js} +1 -1
- package/dist/bundle/chunks/{chunk-NRGMT74E.js → chunk-QOVUQZO4.js} +1 -1
- package/dist/bundle/chunks/{chunk-IKXPBYSO.js → chunk-SICBVLRH.js} +1 -1
- package/dist/bundle/chunks/chunk-SN4ZHPMN.js +135 -0
- package/dist/bundle/chunks/{chunk-MUTD2NOH.js → chunk-TFA3YAJK.js} +1 -1
- package/dist/bundle/chunks/{chunk-4T4GFAGK.js → chunk-VFSXUT7K.js} +1 -1
- package/dist/bundle/chunks/{cli-main-66NFBKVH.js → cli-main-GYRTYPMG.js} +1 -1
- package/dist/bundle/chunks/{google-generative-ai-ZTGKZTGU.js → google-generative-ai-LFCBHG3Y.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-CCZ376YK.js → google-vertex-C474FXU7.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-NZ3I2UEN.js → help-fast-path-3U3YDEZ6.js} +1 -1
- package/dist/bundle/chunks/{host-command-RORPAC5X.js → host-command-6CJ7YM6W.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-UDMJGLOR.js → host-lifecycle-RZIMOFDM.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +6 -6
- package/dist/bundle/chunks/interactive-host-runtime-A3GEIFTU.js +4 -0
- package/dist/bundle/chunks/{interactive-mode-LOAADKLH.js → interactive-mode-LX665HTP.js} +1 -1
- package/dist/bundle/chunks/{list-tips-S5CANO5W.js → list-tips-S4U7TKER.js} +1 -1
- package/dist/bundle/chunks/multi-session-host-MFTUM6XQ.js +21 -0
- package/dist/bundle/chunks/{openai-codex-responses-YSGZZDD5.js → openai-codex-responses-2LDIFL6Z.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-KWHRWCBN.js → openai-completions-7KZLPKYP.js} +1 -1
- package/dist/bundle/chunks/{openai-images-6U5FCN5S.js → openai-images-I7DR7ERP.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-7PWMBGYX.js → openai-responses-LCHS3AF3.js} +1 -1
- package/dist/bundle/chunks/{openrouter-images-QIKWQZUD.js → openrouter-images-IPNDRK6C.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-5EEDA35I.js → package-manager-cli-6IAE5WG3.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-KZG4OVR7.js → rotation-stream-ZVJEGA2G.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-HOEA5NGQ.js → rpc-mode-XN3HNZMH.js} +1 -1
- package/dist/bundle/chunks/session-picker-FPKB4DAC.js +2 -0
- package/dist/bundle/chunks/session-worker.js +318 -309
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/host-command.d.ts +8 -1
- package/dist/cli/host-command.d.ts.map +1 -1
- package/dist/cli/host-command.js +66 -14
- package/dist/cli/host-command.js.map +1 -1
- package/dist/core/agent-session.d.ts +18 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +119 -3
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.d.ts +2 -2
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js +13 -5
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js.map +1 -1
- package/dist/core/extensions/builtin/tool-search/native-search.d.ts.map +1 -1
- package/dist/core/extensions/builtin/tool-search/native-search.js +13 -1
- package/dist/core/extensions/builtin/tool-search/native-search.js.map +1 -1
- package/dist/core/retry-fallback/candidates.d.ts +26 -0
- package/dist/core/retry-fallback/candidates.d.ts.map +1 -0
- package/dist/core/retry-fallback/candidates.js +41 -0
- package/dist/core/retry-fallback/candidates.js.map +1 -0
- package/dist/core/retry-fallback/circuit-probes.d.ts +30 -0
- package/dist/core/retry-fallback/circuit-probes.d.ts.map +1 -0
- package/dist/core/retry-fallback/circuit-probes.js +60 -0
- package/dist/core/retry-fallback/circuit-probes.js.map +1 -0
- package/dist/core/retry-fallback/circuit.d.ts +101 -0
- package/dist/core/retry-fallback/circuit.d.ts.map +1 -0
- package/dist/core/retry-fallback/circuit.js +175 -0
- package/dist/core/retry-fallback/circuit.js.map +1 -0
- package/dist/core/retry-fallback/controller-types.d.ts +58 -0
- package/dist/core/retry-fallback/controller-types.d.ts.map +1 -0
- package/dist/core/retry-fallback/controller-types.js +2 -0
- package/dist/core/retry-fallback/controller-types.js.map +1 -0
- package/dist/core/retry-fallback/controller.d.ts +21 -49
- package/dist/core/retry-fallback/controller.d.ts.map +1 -1
- package/dist/core/retry-fallback/controller.js +88 -42
- package/dist/core/retry-fallback/controller.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +25 -17
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-failure-report.d.ts +33 -0
- package/dist/core/session-failure-report.d.ts.map +1 -0
- package/dist/core/session-failure-report.js +62 -0
- package/dist/core/session-failure-report.js.map +1 -0
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +4 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +2 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/help-content.d.ts.map +1 -1
- package/dist/modes/interactive/help-content.js +3 -0
- package/dist/modes/interactive/help-content.js.map +1 -1
- package/dist/modes/interactive/interactive-host-attach.d.ts +10 -0
- package/dist/modes/interactive/interactive-host-attach.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-host-attach.js +14 -0
- package/dist/modes/interactive/interactive-host-attach.js.map +1 -0
- package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-host-runtime.js +3 -6
- package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +12 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +95 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/session-failure-info.d.ts +3 -0
- package/dist/modes/interactive/session-failure-info.d.ts.map +1 -0
- package/dist/modes/interactive/session-failure-info.js +21 -0
- package/dist/modes/interactive/session-failure-info.js.map +1 -0
- package/dist/modes/interactive/tips/catalog/model-tips.d.ts +7 -7
- package/dist/modes/interactive/tips/catalog/model-tips.d.ts.map +1 -1
- package/dist/modes/interactive/tips/catalog/model-tips.js +7 -1
- package/dist/modes/interactive/tips/catalog/model-tips.js.map +1 -1
- package/dist/modes/interactive/tips/catalog/session-tips.d.ts.map +1 -1
- package/dist/modes/interactive/tips/catalog/session-tips.js +1 -1
- package/dist/modes/interactive/tips/catalog/session-tips.js.map +1 -1
- package/dist/modes/rpc/host-attach-hold.d.ts +16 -0
- package/dist/modes/rpc/host-attach-hold.d.ts.map +1 -0
- package/dist/modes/rpc/host-attach-hold.js +9 -0
- package/dist/modes/rpc/host-attach-hold.js.map +1 -0
- package/dist/modes/rpc/host-cleanup-paths.d.ts +20 -0
- package/dist/modes/rpc/host-cleanup-paths.d.ts.map +1 -0
- package/dist/modes/rpc/host-cleanup-paths.js +13 -0
- package/dist/modes/rpc/host-cleanup-paths.js.map +1 -0
- package/dist/modes/rpc/host-client-occupancy.d.ts +24 -0
- package/dist/modes/rpc/host-client-occupancy.d.ts.map +1 -0
- package/dist/modes/rpc/host-client-occupancy.js +53 -0
- package/dist/modes/rpc/host-client-occupancy.js.map +1 -0
- package/dist/modes/rpc/host-daemon-paths.d.ts +61 -8
- package/dist/modes/rpc/host-daemon-paths.d.ts.map +1 -1
- package/dist/modes/rpc/host-daemon-paths.js +128 -11
- package/dist/modes/rpc/host-daemon-paths.js.map +1 -1
- package/dist/modes/rpc/host-daemon-registration.d.ts +11 -1
- package/dist/modes/rpc/host-daemon-registration.d.ts.map +1 -1
- package/dist/modes/rpc/host-daemon-registration.js +28 -15
- package/dist/modes/rpc/host-daemon-registration.js.map +1 -1
- package/dist/modes/rpc/host-endpoints.d.ts +12 -0
- package/dist/modes/rpc/host-endpoints.d.ts.map +1 -0
- package/dist/modes/rpc/host-endpoints.js +59 -0
- package/dist/modes/rpc/host-endpoints.js.map +1 -0
- package/dist/modes/rpc/host-ensure-lock.d.ts +11 -0
- package/dist/modes/rpc/host-ensure-lock.d.ts.map +1 -0
- package/dist/modes/rpc/host-ensure-lock.js +49 -0
- package/dist/modes/rpc/host-ensure-lock.js.map +1 -0
- package/dist/modes/rpc/host-ensure.d.ts +7 -0
- package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
- package/dist/modes/rpc/host-ensure.js +42 -65
- package/dist/modes/rpc/host-ensure.js.map +1 -1
- package/dist/modes/rpc/host-gc-evidence.d.ts +18 -0
- package/dist/modes/rpc/host-gc-evidence.d.ts.map +1 -0
- package/dist/modes/rpc/host-gc-evidence.js +128 -0
- package/dist/modes/rpc/host-gc-evidence.js.map +1 -0
- package/dist/modes/rpc/host-gc.d.ts +28 -0
- package/dist/modes/rpc/host-gc.d.ts.map +1 -0
- package/dist/modes/rpc/host-gc.js +110 -0
- package/dist/modes/rpc/host-gc.js.map +1 -0
- package/dist/modes/rpc/host-generations.d.ts +10 -2
- package/dist/modes/rpc/host-generations.d.ts.map +1 -1
- package/dist/modes/rpc/host-generations.js +12 -7
- package/dist/modes/rpc/host-generations.js.map +1 -1
- package/dist/modes/rpc/host-handoff.d.ts +6 -0
- package/dist/modes/rpc/host-handoff.d.ts.map +1 -1
- package/dist/modes/rpc/host-handoff.js +31 -4
- package/dist/modes/rpc/host-handoff.js.map +1 -1
- package/dist/modes/rpc/host-lifecycle-policy.d.ts +53 -0
- package/dist/modes/rpc/host-lifecycle-policy.d.ts.map +1 -0
- package/dist/modes/rpc/host-lifecycle-policy.js +64 -0
- package/dist/modes/rpc/host-lifecycle-policy.js.map +1 -0
- package/dist/modes/rpc/host-lifecycle.d.ts +1 -44
- package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
- package/dist/modes/rpc/host-lifecycle.js +23 -85
- package/dist/modes/rpc/host-lifecycle.js.map +1 -1
- package/dist/modes/rpc/host-observe-request.d.ts +17 -0
- package/dist/modes/rpc/host-observe-request.d.ts.map +1 -0
- package/dist/modes/rpc/host-observe-request.js +30 -0
- package/dist/modes/rpc/host-observe-request.js.map +1 -0
- package/dist/modes/rpc/host-probe.d.ts +12 -0
- package/dist/modes/rpc/host-probe.d.ts.map +1 -1
- package/dist/modes/rpc/host-probe.js +28 -1
- package/dist/modes/rpc/host-probe.js.map +1 -1
- package/dist/modes/rpc/host-protocol-info.d.ts +2 -0
- package/dist/modes/rpc/host-protocol-info.d.ts.map +1 -1
- package/dist/modes/rpc/host-protocol-info.js +1 -0
- package/dist/modes/rpc/host-protocol-info.js.map +1 -1
- package/dist/modes/rpc/host-readiness.d.ts +22 -0
- package/dist/modes/rpc/host-readiness.d.ts.map +1 -0
- package/dist/modes/rpc/host-readiness.js +44 -0
- package/dist/modes/rpc/host-readiness.js.map +1 -0
- package/dist/modes/rpc/host-runner.d.ts +13 -0
- package/dist/modes/rpc/host-runner.d.ts.map +1 -1
- package/dist/modes/rpc/host-runner.js +23 -4
- package/dist/modes/rpc/host-runner.js.map +1 -1
- package/dist/modes/rpc/host-status-all.d.ts +31 -0
- package/dist/modes/rpc/host-status-all.d.ts.map +1 -0
- package/dist/modes/rpc/host-status-all.js +78 -0
- package/dist/modes/rpc/host-status-all.js.map +1 -0
- package/dist/modes/rpc/host-status-rows.d.ts +35 -0
- package/dist/modes/rpc/host-status-rows.d.ts.map +1 -0
- package/dist/modes/rpc/host-status-rows.js +42 -0
- package/dist/modes/rpc/host-status-rows.js.map +1 -0
- package/dist/modes/rpc/host-status.d.ts +32 -1
- package/dist/modes/rpc/host-status.d.ts.map +1 -1
- package/dist/modes/rpc/host-status.js +33 -27
- package/dist/modes/rpc/host-status.js.map +1 -1
- package/dist/modes/rpc/host-successor.d.ts +3 -1
- package/dist/modes/rpc/host-successor.d.ts.map +1 -1
- package/dist/modes/rpc/host-successor.js +105 -42
- package/dist/modes/rpc/host-successor.js.map +1 -1
- package/dist/modes/rpc/host-watchdog.d.ts +6 -0
- package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
- package/dist/modes/rpc/host-watchdog.js +12 -13
- package/dist/modes/rpc/host-watchdog.js.map +1 -1
- package/dist/modes/rpc/loop-lag-watchdog.d.ts +16 -0
- package/dist/modes/rpc/loop-lag-watchdog.d.ts.map +1 -1
- package/dist/modes/rpc/loop-lag-watchdog.js +34 -2
- package/dist/modes/rpc/loop-lag-watchdog.js.map +1 -1
- package/dist/modes/rpc/multi-session-host.d.ts +2 -1
- package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
- package/dist/modes/rpc/multi-session-host.js +33 -6
- package/dist/modes/rpc/multi-session-host.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +2 -2
- package/dist/modes/rpc/rpc-mode.js +2 -2
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +16 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/rpc/session-command-router.d.ts +9 -1
- package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
- package/dist/modes/rpc/session-command-router.js +4 -1
- package/dist/modes/rpc/session-command-router.js.map +1 -1
- package/dist/modes/rpc/session-path-key.d.ts +3 -1
- package/dist/modes/rpc/session-path-key.d.ts.map +1 -1
- package/dist/modes/rpc/session-path-key.js +3 -1
- package/dist/modes/rpc/session-path-key.js.map +1 -1
- package/docs/rpc.md +186 -19
- package/docs/settings.md +25 -1
- package/docs/usage.md +5 -2
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +24 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js +36 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +14 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js +6 -4
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +3 -0
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +20 -0
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.d.ts +2 -0
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.d.ts +5 -0
- package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.js +7 -0
- package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
- package/dist/bundle/chunks/chunk-3DEXCYNN.js +0 -135
- package/dist/bundle/chunks/chunk-5X4KSPK5.js +0 -12
- package/dist/bundle/chunks/chunk-KMD5ZONU.js +0 -5
- package/dist/bundle/chunks/chunk-PEHZYGYA.js +0 -8
- package/dist/bundle/chunks/chunk-PRC5XIIH.js +0 -2
- package/dist/bundle/chunks/chunk-QKY33652.js +0 -18
- package/dist/bundle/chunks/interactive-host-runtime-TIFT2SKR.js +0 -4
- package/dist/bundle/chunks/multi-session-host-GRHKB7Q2.js +0 -21
- package/dist/bundle/chunks/session-picker-FTFCQYHH.js +0 -2
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ensure lock of one endpoint: the one lock every operation that changes WHO serves a socket takes
|
|
3
|
+
* first - `ensureHost` (start, reuse, upgrade), `handoffHost` (a forced handoff) and `host gc` (removal) -
|
|
4
|
+
* so none of them ever acts on a state another one is halfway through changing.
|
|
5
|
+
*
|
|
6
|
+
* It is keyed by the socket's transport address rather than by any agent directory, so two
|
|
7
|
+
* installations targeting one endpoint exclude each other, and on POSIX by the canonical spelling of
|
|
8
|
+
* that address, so two spellings of one socket exclude each other too.
|
|
9
|
+
*/
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { canonicalEndpointPath } from "./host-daemon-paths.js";
|
|
15
|
+
import { acquireOwnershipSafeLock } from "./ownership-safe-lock.js";
|
|
16
|
+
import { resolveSocketTransportAddress } from "./socket-transport.js";
|
|
17
|
+
/** Each SQLite busy wait stays short: it blocks the event loop, so a same-process holder could never finish. */
|
|
18
|
+
const LOCK_BUSY_WAIT_MS = 100;
|
|
19
|
+
/** The endpoint lock every ensure of `socket` serializes on, without its `.lock` suffix. */
|
|
20
|
+
export function hostEnsureLockTarget(socket) {
|
|
21
|
+
return join(tmpdir(), "senpi-rpc-host-locks", createSocketLockName(socket));
|
|
22
|
+
}
|
|
23
|
+
export async function acquireHostEnsureLock(socket, waitMs) {
|
|
24
|
+
const lockTarget = hostEnsureLockTarget(socket);
|
|
25
|
+
await mkdir(dirname(lockTarget), { recursive: true });
|
|
26
|
+
await writeFile(lockTarget, "", { flag: "a", mode: 0o600 });
|
|
27
|
+
return acquireOwnershipSafeLock(`${lockTarget}.lock`, hostEnsureLockOptions(waitMs));
|
|
28
|
+
}
|
|
29
|
+
export function hostEnsureLockOptions(waitMs) {
|
|
30
|
+
return {
|
|
31
|
+
retries: { retries: Math.ceil(waitMs / LOCK_BUSY_WAIT_MS), minTimeout: 20, maxTimeout: LOCK_BUSY_WAIT_MS },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function createSocketLockName(socket) {
|
|
35
|
+
return createHash("sha256").update(socketLockAddress(socket), "utf8").digest("hex").slice(0, 32);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* One address per PHYSICAL socket: the canonical endpoint path (`/tmp` vs `/private/tmp`, a symlinked
|
|
39
|
+
* agent directory), the identity the daemon directory is named by, so a gc holding one spelling's lock
|
|
40
|
+
* can never unlink the socket an ensure through the other spelling just bound. Named pipes and abstract
|
|
41
|
+
* sockets have no directory to resolve.
|
|
42
|
+
*/
|
|
43
|
+
function socketLockAddress(socket) {
|
|
44
|
+
if (process.platform === "win32" || socket.startsWith("\0")) {
|
|
45
|
+
return resolveSocketTransportAddress(socket, process.platform);
|
|
46
|
+
}
|
|
47
|
+
return canonicalEndpointPath(socket);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=host-ensure-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-ensure-lock.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-ensure-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAC;AAEtE,gHAAgH;AAChH,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,4FAA4F;AAC5F,MAAM,UAAU,oBAAoB,CAAC,MAAc;IAClD,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE,sBAAsB,EAAE,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,MAAc,EAAE,MAAc;IACzE,MAAM,UAAU,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,MAAM,SAAS,CAAC,UAAU,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5D,OAAO,wBAAwB,CAAC,GAAG,UAAU,OAAO,EAAE,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAc;IACnD,OAAO;QACN,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE,iBAAiB,EAAE;KACjG,CAAC;AACZ,CAAC;AAED,SAAS,oBAAoB,CAAC,MAAc;IAC3C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClG,CAAC;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,MAAc;IACxC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7D,OAAO,6BAA6B,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC","sourcesContent":["/**\n * The ensure lock of one endpoint: the one lock every operation that changes WHO serves a socket takes\n * first - `ensureHost` (start, reuse, upgrade), `handoffHost` (a forced handoff) and `host gc` (removal) -\n * so none of them ever acts on a state another one is halfway through changing.\n *\n * It is keyed by the socket's transport address rather than by any agent directory, so two\n * installations targeting one endpoint exclude each other, and on POSIX by the canonical spelling of\n * that address, so two spellings of one socket exclude each other too.\n */\nimport { createHash } from \"node:crypto\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { canonicalEndpointPath } from \"./host-daemon-paths.ts\";\nimport { acquireOwnershipSafeLock } from \"./ownership-safe-lock.ts\";\nimport { resolveSocketTransportAddress } from \"./socket-transport.ts\";\n\n/** Each SQLite busy wait stays short: it blocks the event loop, so a same-process holder could never finish. */\nconst LOCK_BUSY_WAIT_MS = 100;\n\n/** The endpoint lock every ensure of `socket` serializes on, without its `.lock` suffix. */\nexport function hostEnsureLockTarget(socket: string): string {\n\treturn join(tmpdir(), \"senpi-rpc-host-locks\", createSocketLockName(socket));\n}\n\nexport async function acquireHostEnsureLock(socket: string, waitMs: number): Promise<() => Promise<void>> {\n\tconst lockTarget = hostEnsureLockTarget(socket);\n\tawait mkdir(dirname(lockTarget), { recursive: true });\n\tawait writeFile(lockTarget, \"\", { flag: \"a\", mode: 0o600 });\n\treturn acquireOwnershipSafeLock(`${lockTarget}.lock`, hostEnsureLockOptions(waitMs));\n}\n\nexport function hostEnsureLockOptions(waitMs: number) {\n\treturn {\n\t\tretries: { retries: Math.ceil(waitMs / LOCK_BUSY_WAIT_MS), minTimeout: 20, maxTimeout: LOCK_BUSY_WAIT_MS },\n\t} as const;\n}\n\nfunction createSocketLockName(socket: string): string {\n\treturn createHash(\"sha256\").update(socketLockAddress(socket), \"utf8\").digest(\"hex\").slice(0, 32);\n}\n\n/**\n * One address per PHYSICAL socket: the canonical endpoint path (`/tmp` vs `/private/tmp`, a symlinked\n * agent directory), the identity the daemon directory is named by, so a gc holding one spelling's lock\n * can never unlink the socket an ensure through the other spelling just bound. Named pipes and abstract\n * sockets have no directory to resolve.\n */\nfunction socketLockAddress(socket: string): string {\n\tif (process.platform === \"win32\" || socket.startsWith(\"\\0\")) {\n\t\treturn resolveSocketTransportAddress(socket, process.platform);\n\t}\n\treturn canonicalEndpointPath(socket);\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type HostColdStart, type HostLifecyclePolicyInput } from "./host-lifecycle.ts";
|
|
2
2
|
export { createHostDaemonPaths, daemonDirectoryName, type HostDaemonPaths, HostDaemonStateError, type HostGenerationPaths, } from "./host-daemon-paths.ts";
|
|
3
|
+
export { hostEnsureLockTarget } from "./host-ensure-lock.ts";
|
|
3
4
|
export { defaultHostLaunch, PINNED_HOST_CLIENT_CAPABILITIES } from "./host-launch.ts";
|
|
4
5
|
export { type ProbeHostOptions, probeHost } from "./host-probe.ts";
|
|
5
6
|
export type { HostColdStart, HostLifecyclePolicyInput };
|
|
@@ -51,6 +52,12 @@ export interface EnsuredHost {
|
|
|
51
52
|
readonly pid: number;
|
|
52
53
|
readonly socket: string;
|
|
53
54
|
readonly reused: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Ends this ensure's attach hold (host-attach-hold.ts). Until then the host counts this client as
|
|
57
|
+
* attached, so its idle window cannot close before the client's own connection is up; release it
|
|
58
|
+
* once that connection is attached, or when the client no longer needs the host.
|
|
59
|
+
*/
|
|
60
|
+
readonly release: () => void;
|
|
54
61
|
}
|
|
55
62
|
export declare function ensureHost(options: EnsureHostOptions): Promise<EnsuredHost>;
|
|
56
63
|
//# sourceMappingURL=host-ensure.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-ensure.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-ensure.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"host-ensure.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-ensure.ts"],"names":[],"mappings":"AA2CA,OAAO,EAA6B,KAAK,aAAa,EAAE,KAAK,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAUnH,OAAO,EACN,qBAAqB,EACrB,mBAAmB,EACnB,KAAK,eAAe,EACpB,oBAAoB,EACpB,KAAK,mBAAmB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,+BAA+B,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACnE,YAAY,EAAE,aAAa,EAAE,wBAAwB,EAAE,CAAC;AAExD;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,mBAAmB,CAAC;AAE9D,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,sFAAsF;IACtF,QAAQ,CAAC,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAC3C,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,sFAAsF;IACtF,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IACvD,iGAAiG;IACjG,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,KAAK,CAAC,EAAE;QAChB,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;QACrC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,KAAK,CAAC,EAAE;YAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;SAAE,CAAC;QAChF,6FAA6F;QAC7F,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK;YAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;SAAE,CAAC;QAC9G,oFAAoF;QACpF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QACjD;;;;WAIG;QACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QAClD,4EAA4E;QAC5E,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;KAC7E,CAAC;CACF;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC7B;AAqBD,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAsBjF"}
|
|
@@ -1,54 +1,56 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { mkdir, open, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { getAgentDir } from "../../config.js";
|
|
7
7
|
import { engineBuildIdentity } from "../../core/engine-build-identity.js";
|
|
8
8
|
import { ProcessIdentityUnreadableError, processIsLive, processMatchesPidFile, readProcessStartTime, waitForStartTime, } from "../app-server/daemon/process.js";
|
|
9
|
-
import { createDaemonDirectories, createHostDaemonPaths } from "./host-daemon-paths.js";
|
|
9
|
+
import { createDaemonDirectories, createHostDaemonPaths, ensureEndpointIdentity, sameEndpoint, } from "./host-daemon-paths.js";
|
|
10
10
|
import { clearHostRegistration, legacyHostIsLive, readHostRegistration, writeHostRegistration, writtenByThisProcess, } from "./host-daemon-registration.js";
|
|
11
11
|
import { writeHostSettings } from "./host-daemon-state.js";
|
|
12
12
|
import { decideHostAction, HOST_PROTOCOL_VERSION, HostEnsureRefusedError, REQUIRED_HOST_CAPABILITIES, } from "./host-decision.js";
|
|
13
|
-
import {
|
|
13
|
+
import { hostEnsureLockOptions, hostEnsureLockTarget } from "./host-ensure-lock.js";
|
|
14
|
+
import { HANDOFF_LOCK_HOLD_MS, handoffHostLocked } from "./host-handoff.js";
|
|
14
15
|
import { defaultHostLaunch, PINNED_HOST_CLIENT_CAPABILITIES } from "./host-launch.js";
|
|
15
16
|
import { DEFAULT_HOST_IDLE_EXIT_MS } from "./host-lifecycle.js";
|
|
16
|
-
import {
|
|
17
|
+
import { holdProtocolInfo, probeSocketReachable } from "./host-probe.js";
|
|
17
18
|
import { isHostGenerationProcess } from "./host-process-role.js";
|
|
19
|
+
import { pollProtocolInfo } from "./host-readiness.js";
|
|
18
20
|
import { initialHostEnvironment } from "./host-spawn-environment.js";
|
|
19
21
|
import { acquireOwnershipSafeLock } from "./ownership-safe-lock.js";
|
|
20
22
|
import { hostLaunchProfile } from "./protocol-identity.js";
|
|
21
23
|
import { statSocketIdentity } from "./socket-ownership.js";
|
|
22
|
-
import { createSocketSecret,
|
|
24
|
+
import { createSocketSecret, socketSecretPath } from "./socket-transport.js";
|
|
23
25
|
export { createHostDaemonPaths, daemonDirectoryName, HostDaemonStateError, } from "./host-daemon-paths.js";
|
|
26
|
+
export { hostEnsureLockTarget } from "./host-ensure-lock.js";
|
|
24
27
|
export { defaultHostLaunch, PINNED_HOST_CLIENT_CAPABILITIES } from "./host-launch.js";
|
|
25
28
|
export { probeHost } from "./host-probe.js";
|
|
26
|
-
const SPAWNED_HOST_PROBE_TIMEOUT_MS = 10_000;
|
|
27
29
|
const EXISTING_HOST_PROBE_TIMEOUT_MS = 10_000;
|
|
28
30
|
const DEFAULT_READINESS_TIMEOUT_MS = 10_000;
|
|
29
31
|
const DEFAULT_STOP_TIMEOUT_MS = 10_000;
|
|
30
32
|
const SIGKILL_GRACE_MS = 2_000;
|
|
31
33
|
/**
|
|
32
34
|
* A lock waiter must outlast the longest critical section a holder can run:
|
|
33
|
-
* probing an existing host, stopping an incompatible one (SIGTERM wait
|
|
34
|
-
* SIGKILL grace)
|
|
35
|
+
* probing an existing host, then either stopping an incompatible one (SIGTERM wait
|
|
36
|
+
* plus the SIGKILL grace) and spawning the replacement and waiting for it to answer,
|
|
37
|
+
* or handing it off (an upgrade) - which is also as long as a forced handoff holds it.
|
|
35
38
|
* Each SQLite busy wait stays short because it blocks the event loop; this
|
|
36
39
|
* cumulative budget is what covers the whole section, with headroom for a slow
|
|
37
40
|
* runner. A waiter that gives up early surfaces as a raw "database is locked"
|
|
38
41
|
* failure on the second of two concurrent starts.
|
|
39
42
|
*/
|
|
40
|
-
const ENSURE_LOCK_WAIT_MS = EXISTING_HOST_PROBE_TIMEOUT_MS +
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
};
|
|
43
|
+
const ENSURE_LOCK_WAIT_MS = EXISTING_HOST_PROBE_TIMEOUT_MS +
|
|
44
|
+
Math.max(DEFAULT_STOP_TIMEOUT_MS + SIGKILL_GRACE_MS + DEFAULT_READINESS_TIMEOUT_MS, HANDOFF_LOCK_HOLD_MS) +
|
|
45
|
+
10_000;
|
|
46
|
+
const lockOptions = hostEnsureLockOptions(ENSURE_LOCK_WAIT_MS);
|
|
45
47
|
export async function ensureHost(options) {
|
|
46
48
|
const socket = normalizeSocketPath(options.socket);
|
|
47
49
|
const paths = createHostDaemonPaths({ socket, ...(options.agentDir ? { agentDir: options.agentDir } : {}) });
|
|
48
50
|
await createDaemonDirectories(paths);
|
|
49
51
|
// The public socket is the shared resource; agent directories are not a
|
|
50
52
|
// sufficient lock scope when two installations target the same endpoint.
|
|
51
|
-
const lockTarget =
|
|
53
|
+
const lockTarget = hostEnsureLockTarget(socket);
|
|
52
54
|
await mkdir(dirname(lockTarget), { recursive: true });
|
|
53
55
|
await writeFile(lockTarget, "", { flag: "a", mode: 0o600 });
|
|
54
56
|
// Opportunistic GC of other installs' leftovers stays OUTSIDE the endpoint lock.
|
|
@@ -67,21 +69,29 @@ export async function ensureHost(options) {
|
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
async function ensureHostLocked(paths, socket, options) {
|
|
72
|
+
// Under the lock, so a torn or foreign `endpoint.json` is repaired rather than left unaddressable.
|
|
73
|
+
await ensureEndpointIdentity(paths, socket, { repair: true });
|
|
70
74
|
const testOptions = options._test;
|
|
71
75
|
const registered = await readHostRegistration(paths);
|
|
72
76
|
// A record naming ANOTHER endpoint is not about this ensure's host. The per-socket directory
|
|
73
77
|
// makes that structural, and the field stays as the second guard for a directory that was
|
|
74
78
|
// somehow reused: a second socket must never read the first socket's daemon as its own.
|
|
75
79
|
const registeredHere = registersSocket(registered, socket);
|
|
76
|
-
|
|
80
|
+
// A reusable host is held from the connection that proved it compatible, never re-probed later.
|
|
81
|
+
const held = await holdProtocolInfo(socket, EXISTING_HOST_PROBE_TIMEOUT_MS);
|
|
82
|
+
const protocol = held?.info;
|
|
77
83
|
const startedByUs = registeredHere && (await writtenByThisProcess(registered?.writer));
|
|
78
84
|
const attachedPid = registeredHere ? (registered?.record.pid ?? 0) : 0;
|
|
79
85
|
const decision = decide(options, startedByUs, protocol);
|
|
86
|
+
if (decision.action === "reuse" && held) {
|
|
87
|
+
// A compatible socket is attachable even when another client surface
|
|
88
|
+
// started it. Only hosts we spawned are eligible for lifecycle management.
|
|
89
|
+
return { pid: attachedPid, socket, reused: true, release: held.hold.release };
|
|
90
|
+
}
|
|
91
|
+
held?.hold.release();
|
|
80
92
|
switch (decision.action) {
|
|
81
93
|
case "reuse":
|
|
82
|
-
|
|
83
|
-
// started it. Only hosts we spawned are eligible for lifecycle management.
|
|
84
|
-
return { pid: attachedPid, socket, reused: true };
|
|
94
|
+
throw new Error(`host at ${socket} was reused without answering its probe`);
|
|
85
95
|
case "refuse":
|
|
86
96
|
throw new HostEnsureRefusedError(socket, decision.reason, protocol);
|
|
87
97
|
case "handoff":
|
|
@@ -166,7 +176,7 @@ function decide(options, startedByUs, protocol) {
|
|
|
166
176
|
* host drains. A refused handoff ATTACHES - an upgrade that cannot happen must never become a stop.
|
|
167
177
|
*/
|
|
168
178
|
async function upgradeGeneration(paths, socket, options, attachedPid) {
|
|
169
|
-
const result = await
|
|
179
|
+
const result = await handoffHostLocked({
|
|
170
180
|
socket,
|
|
171
181
|
agentDir: options.agentDir ?? getAgentDir(),
|
|
172
182
|
hostArgs: options.hostArgs ?? [],
|
|
@@ -178,9 +188,16 @@ async function upgradeGeneration(paths, socket, options, attachedPid) {
|
|
|
178
188
|
},
|
|
179
189
|
});
|
|
180
190
|
if (result.action === "handoff")
|
|
181
|
-
return { pid: result.pid, socket, reused: false };
|
|
191
|
+
return { pid: result.pid, socket, reused: false, release: await holdEnsured(socket) };
|
|
182
192
|
await appendStderr(paths, `generation handoff refused: ${result.reason}${result.detail ? ` (${result.detail})` : ""}`);
|
|
183
|
-
return { pid: attachedPid, socket, reused: true };
|
|
193
|
+
return { pid: attachedPid, socket, reused: true, release: await holdEnsured(socket) };
|
|
194
|
+
}
|
|
195
|
+
/** The attach hold for a host another step already proved ready (a handoff successor, a refused handoff). */
|
|
196
|
+
async function holdEnsured(socket) {
|
|
197
|
+
const held = await holdProtocolInfo(socket, EXISTING_HOST_PROBE_TIMEOUT_MS);
|
|
198
|
+
if (!held)
|
|
199
|
+
throw new Error(`RPC socket host at ${socket} stopped answering before this ensure could hold it`);
|
|
200
|
+
return held.hold.release;
|
|
184
201
|
}
|
|
185
202
|
/** This build as a client: which protocol it speaks, what it needs from a host, and which build it is. */
|
|
186
203
|
function ensureClient(options, startedByUs) {
|
|
@@ -206,7 +223,7 @@ function hostChildArgv(hostArgs) {
|
|
|
206
223
|
function registersSocket(registered, socket) {
|
|
207
224
|
if (registered === undefined)
|
|
208
225
|
return false;
|
|
209
|
-
return registered.socket === undefined || registered.socket
|
|
226
|
+
return registered.socket === undefined || sameEndpoint(registered.socket, socket);
|
|
210
227
|
}
|
|
211
228
|
/**
|
|
212
229
|
* `generation` is 0 for a fresh endpoint; a start that leaves a stranded generation running beside
|
|
@@ -322,9 +339,9 @@ async function startHost(paths, socket, options, generation = 0) {
|
|
|
322
339
|
await stderr.close();
|
|
323
340
|
}
|
|
324
341
|
const readinessTimeoutMs = testOptions?.readinessTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS;
|
|
325
|
-
const result = await pollProtocolInfo(socket, readinessTimeoutMs, childExit);
|
|
326
|
-
if (
|
|
327
|
-
return { pid: pidFile.pid, socket, reused: false };
|
|
342
|
+
const result = await pollProtocolInfo(socket, readinessTimeoutMs, isCompatible, childExit);
|
|
343
|
+
if (result.ready)
|
|
344
|
+
return { pid: pidFile.pid, socket, reused: false, release: result.hold.release };
|
|
328
345
|
// Teardown runs for the diagnostic's sake, so it must never replace it: a stop
|
|
329
346
|
// failure here (unreadable identity, a host that outlives SIGKILL) would other-
|
|
330
347
|
// wise propagate instead of the readiness message and skip cleanupState below,
|
|
@@ -426,40 +443,6 @@ async function waitForGone(pidFile, timeoutMs, readStartTime = readProcessStartT
|
|
|
426
443
|
}
|
|
427
444
|
return (await resolvePidFileOwnership(pidFile, readStartTime)) === "gone";
|
|
428
445
|
}
|
|
429
|
-
async function pollProtocolInfo(socket, timeoutMs, childExit) {
|
|
430
|
-
const deadline = Date.now() + timeoutMs;
|
|
431
|
-
let lastProtocol;
|
|
432
|
-
while (Date.now() <= deadline) {
|
|
433
|
-
const probe = probeProtocolInfo(socket, Math.min(SPAWNED_HOST_PROBE_TIMEOUT_MS, Math.max(1, deadline - Date.now())));
|
|
434
|
-
const raced = childExit ? await Promise.race([probe, childExit]) : await probe;
|
|
435
|
-
if (isChildExit(raced)) {
|
|
436
|
-
// A supervisor exit can be triggered by the Windows identity watchdog
|
|
437
|
-
// while a named-pipe client is still composing its protocol reply. Do
|
|
438
|
-
// not terminate the host based solely on that exit until this probe has
|
|
439
|
-
// had a chance to deliver an answer. A host that never answers still
|
|
440
|
-
// resolves through probeProtocolInfo's bounded timeout/close handling.
|
|
441
|
-
const info = await probe;
|
|
442
|
-
if (info) {
|
|
443
|
-
lastProtocol = info;
|
|
444
|
-
if (isCompatible(info))
|
|
445
|
-
return { protocol: info };
|
|
446
|
-
}
|
|
447
|
-
else {
|
|
448
|
-
return { protocol: lastProtocol, exited: raced };
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
else if (raced) {
|
|
452
|
-
lastProtocol = raced;
|
|
453
|
-
if (isCompatible(raced))
|
|
454
|
-
return { protocol: raced };
|
|
455
|
-
}
|
|
456
|
-
await delay(50);
|
|
457
|
-
}
|
|
458
|
-
return { protocol: lastProtocol };
|
|
459
|
-
}
|
|
460
|
-
function isChildExit(value) {
|
|
461
|
-
return !!value && "code" in value && "signal" in value;
|
|
462
|
-
}
|
|
463
446
|
/**
|
|
464
447
|
* Compatibility, for the attach decision and the readiness gate alike: a host is compatible exactly
|
|
465
448
|
* when a client that is forbidden to upgrade would attach to it. Never a version-string comparison (I2).
|
|
@@ -505,12 +488,6 @@ async function appendStderr(paths, message) {
|
|
|
505
488
|
throw error;
|
|
506
489
|
}
|
|
507
490
|
}
|
|
508
|
-
function createSocketLockName(socket) {
|
|
509
|
-
return createHash("sha256")
|
|
510
|
-
.update(resolveSocketTransportAddress(socket, process.platform), "utf8")
|
|
511
|
-
.digest("hex")
|
|
512
|
-
.slice(0, 32);
|
|
513
|
-
}
|
|
514
491
|
function normalizeSocketPath(value) {
|
|
515
492
|
if (value.startsWith("unix://"))
|
|
516
493
|
return value.slice("unix://".length);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-ensure.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-ensure.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { mkdir, open, readdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { engineBuildIdentity } from \"../../core/engine-build-identity.ts\";\nimport {\n\ttype DaemonPidFile,\n\tProcessIdentityUnreadableError,\n\tprocessIsLive,\n\tprocessMatchesPidFile,\n\treadProcessStartTime,\n\twaitForStartTime,\n} from \"../app-server/daemon/process.ts\";\nimport { createDaemonDirectories, createHostDaemonPaths, type HostDaemonPaths } from \"./host-daemon-paths.ts\";\nimport {\n\tclearHostRegistration,\n\tlegacyHostIsLive,\n\ttype RegisteredHost,\n\treadHostRegistration,\n\twriteHostRegistration,\n\twrittenByThisProcess,\n} from \"./host-daemon-registration.ts\";\nimport { writeHostSettings } from \"./host-daemon-state.ts\";\nimport {\n\tdecideHostAction,\n\tHOST_PROTOCOL_VERSION,\n\ttype HostDecision,\n\ttype HostDecisionClient,\n\tHostEnsureRefusedError,\n\ttype HostProtocolInfo,\n\tREQUIRED_HOST_CAPABILITIES,\n} from \"./host-decision.ts\";\nimport { handoffHost } from \"./host-handoff.ts\";\nimport { defaultHostLaunch, PINNED_HOST_CLIENT_CAPABILITIES } from \"./host-launch.ts\";\nimport { DEFAULT_HOST_IDLE_EXIT_MS, type HostColdStart, type HostLifecyclePolicyInput } from \"./host-lifecycle.ts\";\nimport { probeProtocolInfo, probeSocketReachable } from \"./host-probe.ts\";\nimport { isHostGenerationProcess } from \"./host-process-role.ts\";\nimport { initialHostEnvironment } from \"./host-spawn-environment.ts\";\nimport { acquireOwnershipSafeLock } from \"./ownership-safe-lock.ts\";\nimport { hostLaunchProfile } from \"./protocol-identity.ts\";\nimport { statSocketIdentity } from \"./socket-ownership.ts\";\nimport { createSocketSecret, resolveSocketTransportAddress, socketSecretPath } from \"./socket-transport.ts\";\n\nexport {\n\tcreateHostDaemonPaths,\n\tdaemonDirectoryName,\n\ttype HostDaemonPaths,\n\tHostDaemonStateError,\n\ttype HostGenerationPaths,\n} from \"./host-daemon-paths.ts\";\nexport { defaultHostLaunch, PINNED_HOST_CLIENT_CAPABILITIES } from \"./host-launch.ts\";\nexport { type ProbeHostOptions, probeHost } from \"./host-probe.ts\";\nexport type { HostColdStart, HostLifecyclePolicyInput };\n\n/**\n * What an ensure may do to a host that is already running.\n *\n * `never` (the default) attaches or starts, and touches nothing that is already serving the\n * socket. `if-engine-differs` additionally allows a GENERATION HANDOFF when `decideHostAction`\n * finds this build strictly newer and its extension set a superset of the running host's - the\n * running host then drains instead of dying, so no session is ever ended by an upgrade.\n */\nexport type HostUpgradePolicy = \"never\" | \"if-engine-differs\";\n\nexport interface EnsureHostOptions {\n\treadonly socket: string;\n\treadonly agentDir?: string;\n\t/** Host lifecycle policy recorded in settings.json (env overrides win at runtime). */\n\treadonly policy?: HostLifecyclePolicyInput;\n\t/** Extra CLI arguments forwarded through the supervisor to the host process. */\n\treadonly hostArgs?: readonly string[];\n\t/** Environment for the spawned host; a `null` value removes an inherited variable. */\n\treadonly env?: Readonly<Record<string, string | null>>;\n\t/** Whether a newer build may take the socket over from the running host. Defaults to `never`. */\n\treadonly upgrade?: HostUpgradePolicy;\n\treadonly _test?: {\n\t\treadonly readinessTimeoutMs?: number;\n\t\treadonly stopTimeoutMs?: number;\n\t\treadonly spawn?: { readonly command: string; readonly args: readonly string[] };\n\t\t/** Builds the spawnable command from supervisor argv; tests point it at the source entry. */\n\t\treadonly launch?: (args: readonly string[]) => { readonly command: string; readonly args: readonly string[] };\n\t\t/** Runs after endpoint ownership is locked; deterministic concurrency-test gate. */\n\t\treadonly afterLockAcquired?: () => Promise<void>;\n\t\t/**\n\t\t * Runs after the child is spawned but before its pidfile is registered, so a\n\t\t * test can force the startup failure a loaded runner produces without having\n\t\t * to stall the real process-identity probe.\n\t\t */\n\t\treadonly beforePidFileWrite?: () => Promise<void>;\n\t\t/** Overrides the process-identity probe so a test can force its failure. */\n\t\treadonly readProcessStartTime?: (pid: number) => Promise<string | undefined>;\n\t};\n}\n\nexport interface EnsuredHost {\n\treadonly pid: number;\n\treadonly socket: string;\n\treadonly reused: boolean;\n}\n\nconst SPAWNED_HOST_PROBE_TIMEOUT_MS = 10_000;\nconst EXISTING_HOST_PROBE_TIMEOUT_MS = 10_000;\nconst DEFAULT_READINESS_TIMEOUT_MS = 10_000;\nconst DEFAULT_STOP_TIMEOUT_MS = 10_000;\nconst SIGKILL_GRACE_MS = 2_000;\n/**\n * A lock waiter must outlast the longest critical section a holder can run:\n * probing an existing host, stopping an incompatible one (SIGTERM wait plus the\n * SIGKILL grace), then spawning the replacement and waiting for it to answer.\n * Each SQLite busy wait stays short because it blocks the event loop; this\n * cumulative budget is what covers the whole section, with headroom for a slow\n * runner. A waiter that gives up early surfaces as a raw \"database is locked\"\n * failure on the second of two concurrent starts.\n */\nconst ENSURE_LOCK_WAIT_MS =\n\tEXISTING_HOST_PROBE_TIMEOUT_MS + DEFAULT_STOP_TIMEOUT_MS + SIGKILL_GRACE_MS + DEFAULT_READINESS_TIMEOUT_MS + 10_000;\nconst LOCK_BUSY_WAIT_MS = 100;\nconst lockOptions = {\n\tretries: { retries: ENSURE_LOCK_WAIT_MS / LOCK_BUSY_WAIT_MS, minTimeout: 20, maxTimeout: LOCK_BUSY_WAIT_MS },\n} as const;\nexport async function ensureHost(options: EnsureHostOptions): Promise<EnsuredHost> {\n\tconst socket = normalizeSocketPath(options.socket);\n\tconst paths = createHostDaemonPaths({ socket, ...(options.agentDir ? { agentDir: options.agentDir } : {}) });\n\tawait createDaemonDirectories(paths);\n\t// The public socket is the shared resource; agent directories are not a\n\t// sufficient lock scope when two installations target the same endpoint.\n\tconst lockTarget = join(tmpdir(), \"senpi-rpc-host-locks\", createSocketLockName(socket));\n\tawait mkdir(dirname(lockTarget), { recursive: true });\n\tawait writeFile(lockTarget, \"\", { flag: \"a\", mode: 0o600 });\n\t// Opportunistic GC of other installs' leftovers stays OUTSIDE the endpoint lock.\n\t// Its cost scales with the whole tmpdir and, on win32, adds a ~1s process probe per\n\t// candidate; inside the critical section that inflated the hold for every concurrent\n\t// ensureHost until a waiter exhausted its budget and surfaced a raw \"database is\n\t// locked\". Its own guards (60s age, dead owner pid) already make it safe unlocked.\n\tawait reapOrphanedInternalHostDirs();\n\tconst release = await acquireOwnershipSafeLock(`${lockTarget}.lock`, lockOptions);\n\ttry {\n\t\tawait options._test?.afterLockAcquired?.();\n\t\treturn await ensureHostLocked(paths, socket, options);\n\t} finally {\n\t\tawait release();\n\t}\n}\n\nasync function ensureHostLocked(\n\tpaths: HostDaemonPaths,\n\tsocket: string,\n\toptions: EnsureHostOptions,\n): Promise<EnsuredHost> {\n\tconst testOptions = options._test;\n\tconst registered = await readHostRegistration(paths);\n\t// A record naming ANOTHER endpoint is not about this ensure's host. The per-socket directory\n\t// makes that structural, and the field stays as the second guard for a directory that was\n\t// somehow reused: a second socket must never read the first socket's daemon as its own.\n\tconst registeredHere = registersSocket(registered, socket);\n\tconst protocol = await probeProtocolInfo(socket, EXISTING_HOST_PROBE_TIMEOUT_MS);\n\tconst startedByUs = registeredHere && (await writtenByThisProcess(registered?.writer));\n\tconst attachedPid = registeredHere ? (registered?.record.pid ?? 0) : 0;\n\tconst decision = decide(options, startedByUs, protocol);\n\tswitch (decision.action) {\n\t\tcase \"reuse\":\n\t\t\t// A compatible socket is attachable even when another client surface\n\t\t\t// started it. Only hosts we spawned are eligible for lifecycle management.\n\t\t\treturn { pid: attachedPid, socket, reused: true };\n\t\tcase \"refuse\":\n\t\t\tthrow new HostEnsureRefusedError(socket, decision.reason, protocol);\n\t\tcase \"handoff\":\n\t\t\treturn upgradeGeneration(paths, socket, options, attachedPid);\n\t\tcase \"start\":\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn assertNever(decision);\n\t}\n\tconst probe = testOptions?.readProcessStartTime ?? readProcessStartTime;\n\tconst pidMatches = registeredHere && registered ? await matchesPidFileOrUnknown(registered.record, probe) : false;\n\t// The generation this start leaves RUNNING beside the new one, when there is one.\n\tlet stranded: RegisteredHost | undefined;\n\tif (registered && pidMatches) {\n\t\tif (!startedByUs) {\n\t\t\t// I1: the socket is silent, but the process behind it is alive. Only the process that WROTE\n\t\t\t// this record may end it - anyone else refuses rather than signalling somebody else's host.\n\t\t\tif (await publicEndpointAccepts(socket)) throw new HostEnsureRefusedError(socket, \"foreign_writer\", protocol);\n\t\t\t// A foreign record whose public endpoint accepts NOTHING names a generation nobody can reach:\n\t\t\t// its entry was replaced (so it is already draining, #1893) or removed, or a dead listener\n\t\t\t// left the entry behind. Refusing here locked every client out until that process happened\n\t\t\t// to exit (#1936). Binding a fresh generation there signals nothing, so that is what happens -\n\t\t\t// the stranded one keeps its record.\n\t\t\tstranded = registered;\n\t\t} else {\n\t\t\t// Silent is not the same as gone. A host serving many sessions can miss a probe budget\n\t\t\t// while its event loop is busy; its socket still ACCEPTS the connection. Ending it then\n\t\t\t// would destroy every live session to replace a host that was never broken, so a\n\t\t\t// reachable socket is refused instead of signalled - the caller retries or falls back.\n\t\t\tif (await probeSocketReachable(socket, EXISTING_HOST_PROBE_TIMEOUT_MS)) {\n\t\t\t\tthrow new HostEnsureRefusedError(socket, \"host_busy\", protocol);\n\t\t\t}\n\t\t\tawait stopManagedHost(registered.record, testOptions?.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS, probe);\n\t\t}\n\t}\n\t// A host from before this layout registered itself in the FLAT directory. Its files are another\n\t// process's state: never read as ours, never signalled, never removed - and while it is alive,\n\t// this ensure refuses instead of binding a socket it may still be serving.\n\tif (await legacyHostIsLive(paths, probe)) throw new HostEnsureRefusedError(socket, \"legacy_host\", protocol);\n\tif (stranded !== undefined) return startHost(paths, socket, options, stranded.generation + 1);\n\tif (registeredHere) await clearHostRegistration(paths);\n\treturn startHost(paths, socket, options);\n}\n\n/**\n * Only the connect matters here, never an answer: the kernel completes it from the listen backlog\n * without the host's event loop, so a live owner under load still accepts within this budget.\n */\nconst FOREIGN_ENDPOINT_PROBE_TIMEOUT_MS = 2_000;\n\n/**\n * Whether SOMETHING still accepts connections at the public path - the one fact that says a\n * registered process may still own the endpoint. A missing entry and an entry nobody listens\n * behind (connection refused) both answer no; an accepted connection, however silent, answers yes.\n * A named pipe has no entry to lose and an abstract socket has no path, so both read as owned;\n * so does an entry this process cannot stat, because an owner that cannot be ruled out is one\n * this ensure must not bind over.\n */\nasync function publicEndpointAccepts(socket: string): Promise<boolean> {\n\tif (process.platform === \"win32\" || socket.startsWith(\"\\0\")) return true;\n\tconst entry = await statSocketIdentity(socket).then(\n\t\t(identity) => (identity === undefined ? \"absent\" : \"present\"),\n\t\t() => \"unknown\",\n\t);\n\tif (entry === \"absent\") return false;\n\tif (entry === \"unknown\") return true;\n\treturn probeSocketReachable(socket, FOREIGN_ENDPOINT_PROBE_TIMEOUT_MS);\n}\n\n/** `fallback` belongs to clients that can live without a host; an ensure must produce one or fail. */\nfunction decide(\n\toptions: EnsureHostOptions,\n\tstartedByUs: boolean,\n\tprotocol: HostProtocolInfo | undefined,\n): Exclude<HostDecision, { action: \"fallback\" }> {\n\tif (options.upgrade !== \"if-engine-differs\" || isHostGenerationProcess()) {\n\t\treturn decideHostAction(ensureClient(options, startedByUs), protocol, \"never\");\n\t}\n\tconst decision = decideHostAction(ensureClient(options, startedByUs), protocol, \"upgrade\");\n\treturn decision.action === \"fallback\" ? { action: \"reuse\", reason: \"compatible\", upgradeable: false } : decision;\n}\n\n/**\n * The upgrade, when the decision allows one: a new generation takes the socket and the running\n * host drains. A refused handoff ATTACHES - an upgrade that cannot happen must never become a stop.\n */\nasync function upgradeGeneration(\n\tpaths: HostDaemonPaths,\n\tsocket: string,\n\toptions: EnsureHostOptions,\n\tattachedPid: number,\n): Promise<EnsuredHost> {\n\tconst result = await handoffHost({\n\t\tsocket,\n\t\tagentDir: options.agentDir ?? getAgentDir(),\n\t\thostArgs: options.hostArgs ?? [],\n\t\t...(options.env ? { env: options.env } : {}),\n\t\t...(options.policy ? { policy: options.policy } : {}),\n\t\t_test: {\n\t\t\t...(options._test?.launch ? { launch: options._test.launch } : {}),\n\t\t\t...(options._test?.readinessTimeoutMs ? { readinessTimeoutMs: options._test.readinessTimeoutMs } : {}),\n\t\t},\n\t});\n\tif (result.action === \"handoff\") return { pid: result.pid, socket, reused: false };\n\tawait appendStderr(\n\t\tpaths,\n\t\t`generation handoff refused: ${result.reason}${result.detail ? ` (${result.detail})` : \"\"}`,\n\t);\n\treturn { pid: attachedPid, socket, reused: true };\n}\n\n/** This build as a client: which protocol it speaks, what it needs from a host, and which build it is. */\nfunction ensureClient(options: EnsureHostOptions, startedByUs: boolean): HostDecisionClient {\n\treturn {\n\t\tprotocolVersion: HOST_PROTOCOL_VERSION,\n\t\trequiredCapabilities: REQUIRED_HOST_CAPABILITIES,\n\t\tidentity: engineBuildIdentity(),\n\t\t// An ensure that may upgrade has to say what it would launch: the superset rule refuses to\n\t\t// hand off to a generation that would drop what the running host loads. An ensure that may\n\t\t// not upgrade declares nothing, and therefore can never be the newer candidate.\n\t\t...(options.upgrade === \"if-engine-differs\"\n\t\t\t? { launchProfile: hostLaunchProfile(hostChildArgv(options.hostArgs ?? []), process.cwd()) }\n\t\t\t: {}),\n\t\tstartedByUs,\n\t\tplatform: process.platform,\n\t};\n}\n\n/** The argv the supervisor gives its host child; the launch profile has to describe THAT host. */\nfunction hostChildArgv(hostArgs: readonly string[]): string[] {\n\treturn [\"--mode\", \"rpc\", \"--multi-session\", ...hostArgs];\n}\n\n/** Whether a registration is about this endpoint. A record written before the field existed is. */\nfunction registersSocket(registered: RegisteredHost | undefined, socket: string): boolean {\n\tif (registered === undefined) return false;\n\treturn registered.socket === undefined || registered.socket === socket;\n}\n\n/**\n * `generation` is 0 for a fresh endpoint; a start that leaves a stranded generation running beside\n * the new one numbers it after that generation, as a handoff would.\n */\nasync function startHost(\n\tpaths: HostDaemonPaths,\n\tsocket: string,\n\toptions: EnsureHostOptions,\n\tgeneration = 0,\n): Promise<EnsuredHost> {\n\tconst testOptions = options._test;\n\t// The generation this ensure is about to spawn, chosen HERE so its directory exists before the\n\t// host boots and the pointer can name it the moment the host is registered.\n\tconst instanceId = randomUUID();\n\t// The settings file must exist before the supervisor reads it at boot, so it\n\t// records the policy before the spawn instead of beside the pidfile.\n\tif (process.platform === \"win32\") await createSocketSecret(socketSecretPath(socket));\n\tawait writeHostSettings(paths, {\n\t\tsocket,\n\t\tcapabilities: PINNED_HOST_CLIENT_CAPABILITIES,\n\t\tcoldStart: options.policy?.coldStart ?? \"transient\",\n\t\tidleExitMs: options.policy?.idleExitMs ?? DEFAULT_HOST_IDLE_EXIT_MS,\n\t\tgeneration,\n\t\tinstanceId,\n\t});\n\t// A stranded generation is still writing its diagnostics here; only a fresh endpoint starts over.\n\tconst stderr = await open(paths.stderrLog, generation === 0 ? \"w\" : \"a\", 0o600);\n\tlet pidFile: DaemonPidFile | undefined;\n\tlet child: ReturnType<typeof spawn> | undefined;\n\tlet exitedEarly: ChildExit | undefined;\n\tlet childExit: Promise<ChildExit> | undefined;\n\ttry {\n\t\tconst supervisorArgs = [\"--socket\", socket, ...(options.hostArgs ?? [])];\n\t\tconst launch = testOptions?.spawn ?? testOptions?.launch?.(supervisorArgs) ?? defaultHostLaunch(supervisorArgs);\n\t\tchild = spawn(launch.command, [...launch.args], {\n\t\t\tdetached: true,\n\t\t\twindowsHide: true,\n\t\t\tenv: initialHostEnvironment({\n\t\t\t\tagentDir: options.agentDir,\n\t\t\t\tenv: options.env,\n\t\t\t\tpaths,\n\t\t\t\tinstanceId,\n\t\t\t\tgeneration,\n\t\t\t}),\n\t\t\tstdio: [\"ignore\", \"ignore\", stderr.fd],\n\t\t});\n\t\tchildExit = new Promise((resolveExit) => {\n\t\t\tchild!.once(\"exit\", (code, signal) => {\n\t\t\t\texitedEarly = { code, signal };\n\t\t\t\tresolveExit(exitedEarly);\n\t\t\t});\n\t\t});\n\t\tif (child.pid === undefined) throw new Error(\"failed to spawn RPC socket host\");\n\t\tconst probe = testOptions?.readProcessStartTime ?? readProcessStartTime;\n\t\tconst observedStartTime = await Promise.race([\n\t\t\twaitForStartTime(child.pid, 10_000, probe),\n\t\t\tchildExit.then(() => {\n\t\t\t\tthrow new Error(\"RPC socket host exited before its start time could be read\");\n\t\t\t}),\n\t\t]);\n\t\t// UNKNOWN identity on a live child: the probe was starved, not the host. Give the CIM table\n\t\t// one unhurried read (the per-attempt win32 default is 1s, which a loaded runner exceeds on\n\t\t// every attempt) before deciding.\n\t\tconst unhurriedProbe = testOptions?.readProcessStartTime\n\t\t\t? testOptions.readProcessStartTime\n\t\t\t: (pid: number) => readProcessStartTime(pid, process.platform, 15_000);\n\t\tconst processStartTime = observedStartTime ?? (await unhurriedProbe(child.pid).catch(() => undefined));\n\t\t// Still unreadable: the host is ours, alive, and about to prove itself on the socket, so it is\n\t\t// registered WITHOUT an ownership guard instead of being torn down for a starved probe. A\n\t\t// guard-less record never claims ownership and never authorizes a signal - every later caller\n\t\t// reads it as unknown - so the worst case is a fresh host next time, not a killed healthy one.\n\t\tpidFile = { pid: child.pid, processStartTime: processStartTime ?? null };\n\t\tawait testOptions?.beforePidFileWrite?.();\n\t\tawait writeHostRegistration(paths, {\n\t\t\trecord: pidFile,\n\t\t\tsocket,\n\t\t\tinstanceId,\n\t\t\tgeneration,\n\t\t\tlaunchProfileId: hostLaunchProfile(hostChildArgv(options.hostArgs ?? []), process.cwd()).profile_id,\n\t\t});\n\t\tchild.unref();\n\t} catch (error: unknown) {\n\t\t// Whether the child died on its own decides which diagnostic is true, and the\n\t\t// cleanup kill below records an `exitedEarly` indistinguishable from a real\n\t\t// self-exit. Latch it before killing, or the catch reports the SIGTERM it is\n\t\t// about to send and discards the actual startup failure.\n\t\tconst exitedBeforeCleanup = exitedEarly;\n\t\t// Keep the ChildProcess handle owned until registration succeeds. If startup\n\t\t// fails before the pidfile is written, terminate this exact child through\n\t\t// its still-attached handle rather than leaving an unmanaged daemon behind.\n\t\tif (!exitedBeforeCleanup && child && child.exitCode === null && child.signalCode === null) {\n\t\t\ttry {\n\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t} catch {}\n\t\t\tif (childExit) await Promise.race([childExit, delay(2_000)]);\n\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\ttry {\n\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t}\n\t\tif (!exitedBeforeCleanup) {\n\t\t\tawait clearHostRegistration(paths);\n\t\t\tthrow error;\n\t\t}\n\t\tconst diagnostic = await appendStderr(\n\t\t\tpaths,\n\t\t\t`RPC socket host exited with code ${exitedBeforeCleanup.code ?? \"null\"}${exitedBeforeCleanup.signal ? ` (${exitedBeforeCleanup.signal})` : \"\"} before answering get_protocol_info`,\n\t\t);\n\t\tawait clearHostRegistration(paths);\n\t\tthrow new Error(diagnostic);\n\t} finally {\n\t\tawait stderr.close();\n\t}\n\tconst readinessTimeoutMs = testOptions?.readinessTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS;\n\tconst result = await pollProtocolInfo(socket, readinessTimeoutMs, childExit);\n\tif (isCompatible(result.protocol)) return { pid: pidFile.pid, socket, reused: false };\n\t// Teardown runs for the diagnostic's sake, so it must never replace it: a stop\n\t// failure here (unreadable identity, a host that outlives SIGKILL) would other-\n\t// wise propagate instead of the readiness message and skip cleanupState below,\n\t// leaving the pidfile and socket behind. Record it and keep going.\n\t// This child is ours and its handle is still attached: stop it through the handle.\n\t// Validating ownership through the pidfile would re-run the identity probe whose\n\t// failure is the very thing a loaded runner produces here.\n\tconst stopFailure = await stopSpawnedChild(\n\t\tchild,\n\t\tchildExit,\n\t\ttestOptions?.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS,\n\t).then(\n\t\t() => undefined,\n\t\t(error: unknown) => (error instanceof Error ? error.message : String(error)),\n\t);\n\tconst message = result.protocol\n\t\t? `RPC socket host answered get_protocol_info with protocolVersion ${result.protocol.protocolVersion}, serverVersion ${result.protocol.serverVersion} and capabilities ${JSON.stringify(result.protocol.capabilities)}, but is incompatible with protocol version ${HOST_PROTOCOL_VERSION} and required capabilities ${JSON.stringify(REQUIRED_HOST_CAPABILITIES)}`\n\t\t: result.exited\n\t\t\t? `RPC socket host exited with code ${result.exited.code ?? \"null\"}${result.exited.signal ? ` (${result.exited.signal})` : \"\"} before answering get_protocol_info`\n\t\t\t: `spawned RPC socket host did not answer get_protocol_info within ${readinessTimeoutMs}ms`;\n\tconst diagnostic = await appendStderr(\n\t\tpaths,\n\t\tstopFailure === undefined ? message : `${message} (teardown also reported: ${stopFailure})`,\n\t);\n\tawait clearHostRegistration(paths);\n\t// The supervisor may have failed before binding, or another owner may have\n\t// appeared while readiness was being checked. Never unlink an endpoint we\n\t// cannot prove this start owned.\n\tthrow new Error(diagnostic);\n}\n\n/**\n * Ownership for the reuse decision. An identity we cannot read proves nothing: it can neither\n * claim the host nor authorize a kill, so it reads as \"not ours\" and the caller starts fresh\n * rather than failing the whole ensure on an observation gap.\n */\nasync function matchesPidFileOrUnknown(\n\tpidFile: DaemonPidFile,\n\tprobe: (pid: number) => Promise<string | undefined>,\n): Promise<boolean> {\n\ttry {\n\t\treturn await processMatchesPidFile(pidFile, probe);\n\t} catch (error: unknown) {\n\t\tif (error instanceof ProcessIdentityUnreadableError) return false;\n\t\tthrow error;\n\t}\n}\n\nasync function stopSpawnedChild(\n\tchild: ChildProcess,\n\tchildExit: Promise<ChildExit>,\n\ttermTimeoutMs: number,\n): Promise<void> {\n\tconst exited = () => child.exitCode !== null || child.signalCode !== null;\n\tif (exited()) return;\n\tconst signal = (name: NodeJS.Signals) => {\n\t\ttry {\n\t\t\tchild.kill(name);\n\t\t} catch (error: unknown) {\n\t\t\tif (!isNodeErrorCode(error, \"ESRCH\")) throw error;\n\t\t}\n\t};\n\tconst waitFor = (ms: number) => Promise.race([childExit.then(() => true), delay(ms).then(() => exited())]);\n\tsignal(\"SIGTERM\");\n\tif (await waitFor(termTimeoutMs)) return;\n\tsignal(\"SIGKILL\");\n\tif (!(await waitFor(SIGKILL_GRACE_MS))) {\n\t\tthrow new Error(`RPC socket host pid ${child.pid ?? \"?\"} remained alive after SIGKILL`);\n\t}\n}\n\nasync function stopManagedHost(\n\tpidFile: DaemonPidFile,\n\ttermTimeoutMs: number,\n\treadStartTime: (pid: number) => Promise<string | undefined> = readProcessStartTime,\n): Promise<void> {\n\tawait signalValidated(pidFile, \"SIGTERM\", readStartTime);\n\tif (await waitForGone(pidFile, termTimeoutMs, readStartTime)) return;\n\tawait signalValidated(pidFile, \"SIGKILL\", readStartTime);\n\tif (!(await waitForGone(pidFile, SIGKILL_GRACE_MS, readStartTime))) {\n\t\tthrow new Error(`RPC socket host pid ${pidFile.pid} remained alive after SIGKILL`);\n\t}\n}\n\ntype PidFileOwnership = \"owns\" | \"gone\" | \"unknown\";\n\n// One probe per call: the teardown loops below are themselves the retry, so the\n// budget inside processMatchesPidFile would only multiply their wall time. A probe\n// that fails against a LIVE pid is \"unknown\" — it proves nothing about ownership, so\n// signalling on it would be unsafe and treating it as \"gone\" would abandon a host that\n// may still be running. A failed probe against a dead pid is \"gone\".\nasync function resolvePidFileOwnership(\n\tpidFile: DaemonPidFile,\n\treadStartTime: (pid: number) => Promise<string | undefined>,\n): Promise<PidFileOwnership> {\n\ttry {\n\t\treturn (await processMatchesPidFile(pidFile, readStartTime, processIsLive, { attempts: 1 })) ? \"owns\" : \"gone\";\n\t} catch (error: unknown) {\n\t\tif (error instanceof ProcessIdentityUnreadableError) return \"unknown\";\n\t\tthrow error;\n\t}\n}\n\nasync function signalValidated(\n\tpidFile: DaemonPidFile,\n\tsignal: NodeJS.Signals,\n\treadStartTime: (pid: number) => Promise<string | undefined> = readProcessStartTime,\n): Promise<void> {\n\tif ((await resolvePidFileOwnership(pidFile, readStartTime)) !== \"owns\") return;\n\ttry {\n\t\tprocess.kill(pidFile.pid, signal);\n\t} catch (error: unknown) {\n\t\tif (!isNodeErrorCode(error, \"ESRCH\")) throw error;\n\t}\n}\n\nasync function waitForGone(\n\tpidFile: DaemonPidFile,\n\ttimeoutMs: number,\n\treadStartTime: (pid: number) => Promise<string | undefined> = readProcessStartTime,\n): Promise<boolean> {\n\tconst deadline = Date.now() + timeoutMs;\n\twhile (Date.now() <= deadline) {\n\t\tif ((await resolvePidFileOwnership(pidFile, readStartTime)) === \"gone\") return true;\n\t\tawait delay(50);\n\t}\n\treturn (await resolvePidFileOwnership(pidFile, readStartTime)) === \"gone\";\n}\n\ntype ChildExit = { readonly code: number | null; readonly signal: NodeJS.Signals | null };\n\ntype ProtocolPollResult = { readonly protocol?: HostProtocolInfo; readonly exited?: ChildExit };\n\nasync function pollProtocolInfo(\n\tsocket: string,\n\ttimeoutMs: number,\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 = probeProtocolInfo(\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 probeProtocolInfo's bounded timeout/close handling.\n\t\t\tconst info = await probe;\n\t\t\tif (info) {\n\t\t\t\tlastProtocol = info;\n\t\t\t\tif (isCompatible(info)) return { protocol: info };\n\t\t\t} else {\n\t\t\t\treturn { protocol: lastProtocol, exited: raced };\n\t\t\t}\n\t\t} else if (raced) {\n\t\t\tlastProtocol = raced;\n\t\t\tif (isCompatible(raced)) return { protocol: raced };\n\t\t}\n\t\tawait delay(50);\n\t}\n\treturn { protocol: lastProtocol };\n}\n\nfunction isChildExit(value: HostProtocolInfo | ChildExit | undefined): value is ChildExit {\n\treturn !!value && \"code\" in value && \"signal\" in value;\n}\n\n/**\n * Compatibility, for the attach decision and the readiness gate alike: a host is compatible exactly\n * when a client that is forbidden to upgrade would attach to it. Never a version-string comparison (I2).\n */\nfunction isCompatible(protocol: HostProtocolInfo | undefined): boolean {\n\treturn decideHostAction(ensureClient({ socket: \"\" }, false), protocol, \"never\").action === \"reuse\";\n}\n\nasync function reapOrphanedInternalHostDirs(): Promise<void> {\n\ttry {\n\t\tconst entries = await readdir(tmpdir(), { withFileTypes: true });\n\t\tawait Promise.all(\n\t\t\tentries\n\t\t\t\t.filter((entry) => entry.isDirectory() && entry.name.startsWith(\"senpi-rpc-host-internal-\"))\n\t\t\t\t.map(async (entry) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst owner = JSON.parse(await readFile(join(tmpdir(), entry.name, \".owner\"), \"utf8\")) as {\n\t\t\t\t\t\t\tpid?: unknown;\n\t\t\t\t\t\t\tprocessStartTime?: unknown;\n\t\t\t\t\t\t\tcreatedAt?: unknown;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttypeof owner.pid === \"number\" &&\n\t\t\t\t\t\t\ttypeof owner.processStartTime === \"string\" &&\n\t\t\t\t\t\t\ttypeof owner.createdAt === \"number\" &&\n\t\t\t\t\t\t\towner.processStartTime.length > 0 &&\n\t\t\t\t\t\t\towner.createdAt < Date.now() - 60_000 &&\n\t\t\t\t\t\t\t(await readdir(join(tmpdir(), entry.name))).length === 1 &&\n\t\t\t\t\t\t\t!(await processMatchesPidFile({ pid: owner.pid, processStartTime: owner.processStartTime }).catch(\n\t\t\t\t\t\t\t\t(error: unknown) => {\n\t\t\t\t\t\t\t\t\t// Unreadable but live: assume the owner is alive rather than steal its lock.\n\t\t\t\t\t\t\t\t\tif (error instanceof ProcessIdentityUnreadableError) return true;\n\t\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t))\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\tawait rm(join(tmpdir(), entry.name), { recursive: true, force: true });\n\t\t\t\t\t} catch {}\n\t\t\t\t}),\n\t\t);\n\t} catch {}\n}\n\nasync function appendStderr(paths: HostDaemonPaths, message: string): Promise<string> {\n\ttry {\n\t\tconst stderr = (await readFile(paths.stderrLog, \"utf8\")).trim();\n\t\treturn stderr ? `${message}\\n${stderr}` : message;\n\t} catch (error: unknown) {\n\t\tif (isNodeErrorCode(error, \"ENOENT\")) return message;\n\t\tthrow error;\n\t}\n}\n\nfunction createSocketLockName(socket: string): string {\n\treturn createHash(\"sha256\")\n\t\t.update(resolveSocketTransportAddress(socket, process.platform), \"utf8\")\n\t\t.digest(\"hex\")\n\t\t.slice(0, 32);\n}\n\nfunction normalizeSocketPath(value: string): string {\n\tif (value.startsWith(\"unix://\")) return value.slice(\"unix://\".length);\n\treturn value;\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolveDelay) => setTimeout(resolveDelay, ms));\n}\n\nfunction isNodeErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nfunction assertNever(value: never): never {\n\tthrow new Error(`unreachable host decision: ${JSON.stringify(value)}`);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"host-ensure.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-ensure.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { mkdir, open, readdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { engineBuildIdentity } from \"../../core/engine-build-identity.ts\";\nimport {\n\ttype DaemonPidFile,\n\tProcessIdentityUnreadableError,\n\tprocessIsLive,\n\tprocessMatchesPidFile,\n\treadProcessStartTime,\n\twaitForStartTime,\n} from \"../app-server/daemon/process.ts\";\nimport {\n\tcreateDaemonDirectories,\n\tcreateHostDaemonPaths,\n\tensureEndpointIdentity,\n\ttype HostDaemonPaths,\n\tsameEndpoint,\n} from \"./host-daemon-paths.ts\";\nimport {\n\tclearHostRegistration,\n\tlegacyHostIsLive,\n\ttype RegisteredHost,\n\treadHostRegistration,\n\twriteHostRegistration,\n\twrittenByThisProcess,\n} from \"./host-daemon-registration.ts\";\nimport { writeHostSettings } from \"./host-daemon-state.ts\";\nimport {\n\tdecideHostAction,\n\tHOST_PROTOCOL_VERSION,\n\ttype HostDecision,\n\ttype HostDecisionClient,\n\tHostEnsureRefusedError,\n\ttype HostProtocolInfo,\n\tREQUIRED_HOST_CAPABILITIES,\n} from \"./host-decision.ts\";\nimport { hostEnsureLockOptions, hostEnsureLockTarget } from \"./host-ensure-lock.ts\";\nimport { HANDOFF_LOCK_HOLD_MS, handoffHostLocked } from \"./host-handoff.ts\";\nimport { defaultHostLaunch, PINNED_HOST_CLIENT_CAPABILITIES } from \"./host-launch.ts\";\nimport { DEFAULT_HOST_IDLE_EXIT_MS, type HostColdStart, type HostLifecyclePolicyInput } from \"./host-lifecycle.ts\";\nimport { holdProtocolInfo, probeSocketReachable } from \"./host-probe.ts\";\nimport { isHostGenerationProcess } from \"./host-process-role.ts\";\nimport { type ChildExit, pollProtocolInfo } from \"./host-readiness.ts\";\nimport { initialHostEnvironment } from \"./host-spawn-environment.ts\";\nimport { acquireOwnershipSafeLock } from \"./ownership-safe-lock.ts\";\nimport { hostLaunchProfile } from \"./protocol-identity.ts\";\nimport { statSocketIdentity } from \"./socket-ownership.ts\";\nimport { createSocketSecret, socketSecretPath } from \"./socket-transport.ts\";\n\nexport {\n\tcreateHostDaemonPaths,\n\tdaemonDirectoryName,\n\ttype HostDaemonPaths,\n\tHostDaemonStateError,\n\ttype HostGenerationPaths,\n} from \"./host-daemon-paths.ts\";\nexport { hostEnsureLockTarget } from \"./host-ensure-lock.ts\";\nexport { defaultHostLaunch, PINNED_HOST_CLIENT_CAPABILITIES } from \"./host-launch.ts\";\nexport { type ProbeHostOptions, probeHost } from \"./host-probe.ts\";\nexport type { HostColdStart, HostLifecyclePolicyInput };\n\n/**\n * What an ensure may do to a host that is already running.\n *\n * `never` (the default) attaches or starts, and touches nothing that is already serving the\n * socket. `if-engine-differs` additionally allows a GENERATION HANDOFF when `decideHostAction`\n * finds this build strictly newer and its extension set a superset of the running host's - the\n * running host then drains instead of dying, so no session is ever ended by an upgrade.\n */\nexport type HostUpgradePolicy = \"never\" | \"if-engine-differs\";\n\nexport interface EnsureHostOptions {\n\treadonly socket: string;\n\treadonly agentDir?: string;\n\t/** Host lifecycle policy recorded in settings.json (env overrides win at runtime). */\n\treadonly policy?: HostLifecyclePolicyInput;\n\t/** Extra CLI arguments forwarded through the supervisor to the host process. */\n\treadonly hostArgs?: readonly string[];\n\t/** Environment for the spawned host; a `null` value removes an inherited variable. */\n\treadonly env?: Readonly<Record<string, string | null>>;\n\t/** Whether a newer build may take the socket over from the running host. Defaults to `never`. */\n\treadonly upgrade?: HostUpgradePolicy;\n\treadonly _test?: {\n\t\treadonly readinessTimeoutMs?: number;\n\t\treadonly stopTimeoutMs?: number;\n\t\treadonly spawn?: { readonly command: string; readonly args: readonly string[] };\n\t\t/** Builds the spawnable command from supervisor argv; tests point it at the source entry. */\n\t\treadonly launch?: (args: readonly string[]) => { readonly command: string; readonly args: readonly string[] };\n\t\t/** Runs after endpoint ownership is locked; deterministic concurrency-test gate. */\n\t\treadonly afterLockAcquired?: () => Promise<void>;\n\t\t/**\n\t\t * Runs after the child is spawned but before its pidfile is registered, so a\n\t\t * test can force the startup failure a loaded runner produces without having\n\t\t * to stall the real process-identity probe.\n\t\t */\n\t\treadonly beforePidFileWrite?: () => Promise<void>;\n\t\t/** Overrides the process-identity probe so a test can force its failure. */\n\t\treadonly readProcessStartTime?: (pid: number) => Promise<string | undefined>;\n\t};\n}\n\nexport interface EnsuredHost {\n\treadonly pid: number;\n\treadonly socket: string;\n\treadonly reused: boolean;\n\t/**\n\t * Ends this ensure's attach hold (host-attach-hold.ts). Until then the host counts this client as\n\t * attached, so its idle window cannot close before the client's own connection is up; release it\n\t * once that connection is attached, or when the client no longer needs the host.\n\t */\n\treadonly release: () => void;\n}\n\nconst EXISTING_HOST_PROBE_TIMEOUT_MS = 10_000;\nconst DEFAULT_READINESS_TIMEOUT_MS = 10_000;\nconst DEFAULT_STOP_TIMEOUT_MS = 10_000;\nconst SIGKILL_GRACE_MS = 2_000;\n/**\n * A lock waiter must outlast the longest critical section a holder can run:\n * probing an existing host, then either stopping an incompatible one (SIGTERM wait\n * plus the SIGKILL grace) and spawning the replacement and waiting for it to answer,\n * or handing it off (an upgrade) - which is also as long as a forced handoff holds it.\n * Each SQLite busy wait stays short because it blocks the event loop; this\n * cumulative budget is what covers the whole section, with headroom for a slow\n * runner. A waiter that gives up early surfaces as a raw \"database is locked\"\n * failure on the second of two concurrent starts.\n */\nconst ENSURE_LOCK_WAIT_MS =\n\tEXISTING_HOST_PROBE_TIMEOUT_MS +\n\tMath.max(DEFAULT_STOP_TIMEOUT_MS + SIGKILL_GRACE_MS + DEFAULT_READINESS_TIMEOUT_MS, HANDOFF_LOCK_HOLD_MS) +\n\t10_000;\nconst lockOptions = hostEnsureLockOptions(ENSURE_LOCK_WAIT_MS);\nexport async function ensureHost(options: EnsureHostOptions): Promise<EnsuredHost> {\n\tconst socket = normalizeSocketPath(options.socket);\n\tconst paths = createHostDaemonPaths({ socket, ...(options.agentDir ? { agentDir: options.agentDir } : {}) });\n\tawait createDaemonDirectories(paths);\n\t// The public socket is the shared resource; agent directories are not a\n\t// sufficient lock scope when two installations target the same endpoint.\n\tconst lockTarget = hostEnsureLockTarget(socket);\n\tawait mkdir(dirname(lockTarget), { recursive: true });\n\tawait writeFile(lockTarget, \"\", { flag: \"a\", mode: 0o600 });\n\t// Opportunistic GC of other installs' leftovers stays OUTSIDE the endpoint lock.\n\t// Its cost scales with the whole tmpdir and, on win32, adds a ~1s process probe per\n\t// candidate; inside the critical section that inflated the hold for every concurrent\n\t// ensureHost until a waiter exhausted its budget and surfaced a raw \"database is\n\t// locked\". Its own guards (60s age, dead owner pid) already make it safe unlocked.\n\tawait reapOrphanedInternalHostDirs();\n\tconst release = await acquireOwnershipSafeLock(`${lockTarget}.lock`, lockOptions);\n\ttry {\n\t\tawait options._test?.afterLockAcquired?.();\n\t\treturn await ensureHostLocked(paths, socket, options);\n\t} finally {\n\t\tawait release();\n\t}\n}\n\nasync function ensureHostLocked(\n\tpaths: HostDaemonPaths,\n\tsocket: string,\n\toptions: EnsureHostOptions,\n): Promise<EnsuredHost> {\n\t// Under the lock, so a torn or foreign `endpoint.json` is repaired rather than left unaddressable.\n\tawait ensureEndpointIdentity(paths, socket, { repair: true });\n\tconst testOptions = options._test;\n\tconst registered = await readHostRegistration(paths);\n\t// A record naming ANOTHER endpoint is not about this ensure's host. The per-socket directory\n\t// makes that structural, and the field stays as the second guard for a directory that was\n\t// somehow reused: a second socket must never read the first socket's daemon as its own.\n\tconst registeredHere = registersSocket(registered, socket);\n\t// A reusable host is held from the connection that proved it compatible, never re-probed later.\n\tconst held = await holdProtocolInfo(socket, EXISTING_HOST_PROBE_TIMEOUT_MS);\n\tconst protocol = held?.info;\n\tconst startedByUs = registeredHere && (await writtenByThisProcess(registered?.writer));\n\tconst attachedPid = registeredHere ? (registered?.record.pid ?? 0) : 0;\n\tconst decision = decide(options, startedByUs, protocol);\n\tif (decision.action === \"reuse\" && held) {\n\t\t// A compatible socket is attachable even when another client surface\n\t\t// started it. Only hosts we spawned are eligible for lifecycle management.\n\t\treturn { pid: attachedPid, socket, reused: true, release: held.hold.release };\n\t}\n\theld?.hold.release();\n\tswitch (decision.action) {\n\t\tcase \"reuse\":\n\t\t\tthrow new Error(`host at ${socket} was reused without answering its probe`);\n\t\tcase \"refuse\":\n\t\t\tthrow new HostEnsureRefusedError(socket, decision.reason, protocol);\n\t\tcase \"handoff\":\n\t\t\treturn upgradeGeneration(paths, socket, options, attachedPid);\n\t\tcase \"start\":\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn assertNever(decision);\n\t}\n\tconst probe = testOptions?.readProcessStartTime ?? readProcessStartTime;\n\tconst pidMatches = registeredHere && registered ? await matchesPidFileOrUnknown(registered.record, probe) : false;\n\t// The generation this start leaves RUNNING beside the new one, when there is one.\n\tlet stranded: RegisteredHost | undefined;\n\tif (registered && pidMatches) {\n\t\tif (!startedByUs) {\n\t\t\t// I1: the socket is silent, but the process behind it is alive. Only the process that WROTE\n\t\t\t// this record may end it - anyone else refuses rather than signalling somebody else's host.\n\t\t\tif (await publicEndpointAccepts(socket)) throw new HostEnsureRefusedError(socket, \"foreign_writer\", protocol);\n\t\t\t// A foreign record whose public endpoint accepts NOTHING names a generation nobody can reach:\n\t\t\t// its entry was replaced (so it is already draining, #1893) or removed, or a dead listener\n\t\t\t// left the entry behind. Refusing here locked every client out until that process happened\n\t\t\t// to exit (#1936). Binding a fresh generation there signals nothing, so that is what happens -\n\t\t\t// the stranded one keeps its record.\n\t\t\tstranded = registered;\n\t\t} else {\n\t\t\t// Silent is not the same as gone. A host serving many sessions can miss a probe budget\n\t\t\t// while its event loop is busy; its socket still ACCEPTS the connection. Ending it then\n\t\t\t// would destroy every live session to replace a host that was never broken, so a\n\t\t\t// reachable socket is refused instead of signalled - the caller retries or falls back.\n\t\t\tif (await probeSocketReachable(socket, EXISTING_HOST_PROBE_TIMEOUT_MS)) {\n\t\t\t\tthrow new HostEnsureRefusedError(socket, \"host_busy\", protocol);\n\t\t\t}\n\t\t\tawait stopManagedHost(registered.record, testOptions?.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS, probe);\n\t\t}\n\t}\n\t// A host from before this layout registered itself in the FLAT directory. Its files are another\n\t// process's state: never read as ours, never signalled, never removed - and while it is alive,\n\t// this ensure refuses instead of binding a socket it may still be serving.\n\tif (await legacyHostIsLive(paths, probe)) throw new HostEnsureRefusedError(socket, \"legacy_host\", protocol);\n\tif (stranded !== undefined) return startHost(paths, socket, options, stranded.generation + 1);\n\tif (registeredHere) await clearHostRegistration(paths);\n\treturn startHost(paths, socket, options);\n}\n\n/**\n * Only the connect matters here, never an answer: the kernel completes it from the listen backlog\n * without the host's event loop, so a live owner under load still accepts within this budget.\n */\nconst FOREIGN_ENDPOINT_PROBE_TIMEOUT_MS = 2_000;\n\n/**\n * Whether SOMETHING still accepts connections at the public path - the one fact that says a\n * registered process may still own the endpoint. A missing entry and an entry nobody listens\n * behind (connection refused) both answer no; an accepted connection, however silent, answers yes.\n * A named pipe has no entry to lose and an abstract socket has no path, so both read as owned;\n * so does an entry this process cannot stat, because an owner that cannot be ruled out is one\n * this ensure must not bind over.\n */\nasync function publicEndpointAccepts(socket: string): Promise<boolean> {\n\tif (process.platform === \"win32\" || socket.startsWith(\"\\0\")) return true;\n\tconst entry = await statSocketIdentity(socket).then(\n\t\t(identity) => (identity === undefined ? \"absent\" : \"present\"),\n\t\t() => \"unknown\",\n\t);\n\tif (entry === \"absent\") return false;\n\tif (entry === \"unknown\") return true;\n\treturn probeSocketReachable(socket, FOREIGN_ENDPOINT_PROBE_TIMEOUT_MS);\n}\n\n/** `fallback` belongs to clients that can live without a host; an ensure must produce one or fail. */\nfunction decide(\n\toptions: EnsureHostOptions,\n\tstartedByUs: boolean,\n\tprotocol: HostProtocolInfo | undefined,\n): Exclude<HostDecision, { action: \"fallback\" }> {\n\tif (options.upgrade !== \"if-engine-differs\" || isHostGenerationProcess()) {\n\t\treturn decideHostAction(ensureClient(options, startedByUs), protocol, \"never\");\n\t}\n\tconst decision = decideHostAction(ensureClient(options, startedByUs), protocol, \"upgrade\");\n\treturn decision.action === \"fallback\" ? { action: \"reuse\", reason: \"compatible\", upgradeable: false } : decision;\n}\n\n/**\n * The upgrade, when the decision allows one: a new generation takes the socket and the running\n * host drains. A refused handoff ATTACHES - an upgrade that cannot happen must never become a stop.\n */\nasync function upgradeGeneration(\n\tpaths: HostDaemonPaths,\n\tsocket: string,\n\toptions: EnsureHostOptions,\n\tattachedPid: number,\n): Promise<EnsuredHost> {\n\tconst result = await handoffHostLocked({\n\t\tsocket,\n\t\tagentDir: options.agentDir ?? getAgentDir(),\n\t\thostArgs: options.hostArgs ?? [],\n\t\t...(options.env ? { env: options.env } : {}),\n\t\t...(options.policy ? { policy: options.policy } : {}),\n\t\t_test: {\n\t\t\t...(options._test?.launch ? { launch: options._test.launch } : {}),\n\t\t\t...(options._test?.readinessTimeoutMs ? { readinessTimeoutMs: options._test.readinessTimeoutMs } : {}),\n\t\t},\n\t});\n\tif (result.action === \"handoff\")\n\t\treturn { pid: result.pid, socket, reused: false, release: await holdEnsured(socket) };\n\tawait appendStderr(\n\t\tpaths,\n\t\t`generation handoff refused: ${result.reason}${result.detail ? ` (${result.detail})` : \"\"}`,\n\t);\n\treturn { pid: attachedPid, socket, reused: true, release: await holdEnsured(socket) };\n}\n\n/** The attach hold for a host another step already proved ready (a handoff successor, a refused handoff). */\nasync function holdEnsured(socket: string): Promise<() => void> {\n\tconst held = await holdProtocolInfo(socket, EXISTING_HOST_PROBE_TIMEOUT_MS);\n\tif (!held) throw new Error(`RPC socket host at ${socket} stopped answering before this ensure could hold it`);\n\treturn held.hold.release;\n}\n\n/** This build as a client: which protocol it speaks, what it needs from a host, and which build it is. */\nfunction ensureClient(options: EnsureHostOptions, startedByUs: boolean): HostDecisionClient {\n\treturn {\n\t\tprotocolVersion: HOST_PROTOCOL_VERSION,\n\t\trequiredCapabilities: REQUIRED_HOST_CAPABILITIES,\n\t\tidentity: engineBuildIdentity(),\n\t\t// An ensure that may upgrade has to say what it would launch: the superset rule refuses to\n\t\t// hand off to a generation that would drop what the running host loads. An ensure that may\n\t\t// not upgrade declares nothing, and therefore can never be the newer candidate.\n\t\t...(options.upgrade === \"if-engine-differs\"\n\t\t\t? { launchProfile: hostLaunchProfile(hostChildArgv(options.hostArgs ?? []), process.cwd()) }\n\t\t\t: {}),\n\t\tstartedByUs,\n\t\tplatform: process.platform,\n\t};\n}\n\n/** The argv the supervisor gives its host child; the launch profile has to describe THAT host. */\nfunction hostChildArgv(hostArgs: readonly string[]): string[] {\n\treturn [\"--mode\", \"rpc\", \"--multi-session\", ...hostArgs];\n}\n\n/** Whether a registration is about this endpoint. A record written before the field existed is. */\nfunction registersSocket(registered: RegisteredHost | undefined, socket: string): boolean {\n\tif (registered === undefined) return false;\n\treturn registered.socket === undefined || sameEndpoint(registered.socket, socket);\n}\n\n/**\n * `generation` is 0 for a fresh endpoint; a start that leaves a stranded generation running beside\n * the new one numbers it after that generation, as a handoff would.\n */\nasync function startHost(\n\tpaths: HostDaemonPaths,\n\tsocket: string,\n\toptions: EnsureHostOptions,\n\tgeneration = 0,\n): Promise<EnsuredHost> {\n\tconst testOptions = options._test;\n\t// The generation this ensure is about to spawn, chosen HERE so its directory exists before the\n\t// host boots and the pointer can name it the moment the host is registered.\n\tconst instanceId = randomUUID();\n\t// The settings file must exist before the supervisor reads it at boot, so it\n\t// records the policy before the spawn instead of beside the pidfile.\n\tif (process.platform === \"win32\") await createSocketSecret(socketSecretPath(socket));\n\tawait writeHostSettings(paths, {\n\t\tsocket,\n\t\tcapabilities: PINNED_HOST_CLIENT_CAPABILITIES,\n\t\tcoldStart: options.policy?.coldStart ?? \"transient\",\n\t\tidleExitMs: options.policy?.idleExitMs ?? DEFAULT_HOST_IDLE_EXIT_MS,\n\t\tgeneration,\n\t\tinstanceId,\n\t});\n\t// A stranded generation is still writing its diagnostics here; only a fresh endpoint starts over.\n\tconst stderr = await open(paths.stderrLog, generation === 0 ? \"w\" : \"a\", 0o600);\n\tlet pidFile: DaemonPidFile | undefined;\n\tlet child: ReturnType<typeof spawn> | undefined;\n\tlet exitedEarly: ChildExit | undefined;\n\tlet childExit: Promise<ChildExit> | undefined;\n\ttry {\n\t\tconst supervisorArgs = [\"--socket\", socket, ...(options.hostArgs ?? [])];\n\t\tconst launch = testOptions?.spawn ?? testOptions?.launch?.(supervisorArgs) ?? defaultHostLaunch(supervisorArgs);\n\t\tchild = spawn(launch.command, [...launch.args], {\n\t\t\tdetached: true,\n\t\t\twindowsHide: true,\n\t\t\tenv: initialHostEnvironment({\n\t\t\t\tagentDir: options.agentDir,\n\t\t\t\tenv: options.env,\n\t\t\t\tpaths,\n\t\t\t\tinstanceId,\n\t\t\t\tgeneration,\n\t\t\t}),\n\t\t\tstdio: [\"ignore\", \"ignore\", stderr.fd],\n\t\t});\n\t\tchildExit = new Promise((resolveExit) => {\n\t\t\tchild!.once(\"exit\", (code, signal) => {\n\t\t\t\texitedEarly = { code, signal };\n\t\t\t\tresolveExit(exitedEarly);\n\t\t\t});\n\t\t});\n\t\tif (child.pid === undefined) throw new Error(\"failed to spawn RPC socket host\");\n\t\tconst probe = testOptions?.readProcessStartTime ?? readProcessStartTime;\n\t\tconst observedStartTime = await Promise.race([\n\t\t\twaitForStartTime(child.pid, 10_000, probe),\n\t\t\tchildExit.then(() => {\n\t\t\t\tthrow new Error(\"RPC socket host exited before its start time could be read\");\n\t\t\t}),\n\t\t]);\n\t\t// UNKNOWN identity on a live child: the probe was starved, not the host. Give the CIM table\n\t\t// one unhurried read (the per-attempt win32 default is 1s, which a loaded runner exceeds on\n\t\t// every attempt) before deciding.\n\t\tconst unhurriedProbe = testOptions?.readProcessStartTime\n\t\t\t? testOptions.readProcessStartTime\n\t\t\t: (pid: number) => readProcessStartTime(pid, process.platform, 15_000);\n\t\tconst processStartTime = observedStartTime ?? (await unhurriedProbe(child.pid).catch(() => undefined));\n\t\t// Still unreadable: the host is ours, alive, and about to prove itself on the socket, so it is\n\t\t// registered WITHOUT an ownership guard instead of being torn down for a starved probe. A\n\t\t// guard-less record never claims ownership and never authorizes a signal - every later caller\n\t\t// reads it as unknown - so the worst case is a fresh host next time, not a killed healthy one.\n\t\tpidFile = { pid: child.pid, processStartTime: processStartTime ?? null };\n\t\tawait testOptions?.beforePidFileWrite?.();\n\t\tawait writeHostRegistration(paths, {\n\t\t\trecord: pidFile,\n\t\t\tsocket,\n\t\t\tinstanceId,\n\t\t\tgeneration,\n\t\t\tlaunchProfileId: hostLaunchProfile(hostChildArgv(options.hostArgs ?? []), process.cwd()).profile_id,\n\t\t});\n\t\tchild.unref();\n\t} catch (error: unknown) {\n\t\t// Whether the child died on its own decides which diagnostic is true, and the\n\t\t// cleanup kill below records an `exitedEarly` indistinguishable from a real\n\t\t// self-exit. Latch it before killing, or the catch reports the SIGTERM it is\n\t\t// about to send and discards the actual startup failure.\n\t\tconst exitedBeforeCleanup = exitedEarly;\n\t\t// Keep the ChildProcess handle owned until registration succeeds. If startup\n\t\t// fails before the pidfile is written, terminate this exact child through\n\t\t// its still-attached handle rather than leaving an unmanaged daemon behind.\n\t\tif (!exitedBeforeCleanup && child && child.exitCode === null && child.signalCode === null) {\n\t\t\ttry {\n\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t} catch {}\n\t\t\tif (childExit) await Promise.race([childExit, delay(2_000)]);\n\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\ttry {\n\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t}\n\t\tif (!exitedBeforeCleanup) {\n\t\t\tawait clearHostRegistration(paths);\n\t\t\tthrow error;\n\t\t}\n\t\tconst diagnostic = await appendStderr(\n\t\t\tpaths,\n\t\t\t`RPC socket host exited with code ${exitedBeforeCleanup.code ?? \"null\"}${exitedBeforeCleanup.signal ? ` (${exitedBeforeCleanup.signal})` : \"\"} before answering get_protocol_info`,\n\t\t);\n\t\tawait clearHostRegistration(paths);\n\t\tthrow new Error(diagnostic);\n\t} finally {\n\t\tawait stderr.close();\n\t}\n\tconst readinessTimeoutMs = testOptions?.readinessTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS;\n\tconst result = await pollProtocolInfo(socket, readinessTimeoutMs, isCompatible, childExit);\n\tif (result.ready) return { pid: pidFile.pid, socket, reused: false, release: result.hold.release };\n\t// Teardown runs for the diagnostic's sake, so it must never replace it: a stop\n\t// failure here (unreadable identity, a host that outlives SIGKILL) would other-\n\t// wise propagate instead of the readiness message and skip cleanupState below,\n\t// leaving the pidfile and socket behind. Record it and keep going.\n\t// This child is ours and its handle is still attached: stop it through the handle.\n\t// Validating ownership through the pidfile would re-run the identity probe whose\n\t// failure is the very thing a loaded runner produces here.\n\tconst stopFailure = await stopSpawnedChild(\n\t\tchild,\n\t\tchildExit,\n\t\ttestOptions?.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS,\n\t).then(\n\t\t() => undefined,\n\t\t(error: unknown) => (error instanceof Error ? error.message : String(error)),\n\t);\n\tconst message = result.protocol\n\t\t? `RPC socket host answered get_protocol_info with protocolVersion ${result.protocol.protocolVersion}, serverVersion ${result.protocol.serverVersion} and capabilities ${JSON.stringify(result.protocol.capabilities)}, but is incompatible with protocol version ${HOST_PROTOCOL_VERSION} and required capabilities ${JSON.stringify(REQUIRED_HOST_CAPABILITIES)}`\n\t\t: result.exited\n\t\t\t? `RPC socket host exited with code ${result.exited.code ?? \"null\"}${result.exited.signal ? ` (${result.exited.signal})` : \"\"} before answering get_protocol_info`\n\t\t\t: `spawned RPC socket host did not answer get_protocol_info within ${readinessTimeoutMs}ms`;\n\tconst diagnostic = await appendStderr(\n\t\tpaths,\n\t\tstopFailure === undefined ? message : `${message} (teardown also reported: ${stopFailure})`,\n\t);\n\tawait clearHostRegistration(paths);\n\t// The supervisor may have failed before binding, or another owner may have\n\t// appeared while readiness was being checked. Never unlink an endpoint we\n\t// cannot prove this start owned.\n\tthrow new Error(diagnostic);\n}\n\n/**\n * Ownership for the reuse decision. An identity we cannot read proves nothing: it can neither\n * claim the host nor authorize a kill, so it reads as \"not ours\" and the caller starts fresh\n * rather than failing the whole ensure on an observation gap.\n */\nasync function matchesPidFileOrUnknown(\n\tpidFile: DaemonPidFile,\n\tprobe: (pid: number) => Promise<string | undefined>,\n): Promise<boolean> {\n\ttry {\n\t\treturn await processMatchesPidFile(pidFile, probe);\n\t} catch (error: unknown) {\n\t\tif (error instanceof ProcessIdentityUnreadableError) return false;\n\t\tthrow error;\n\t}\n}\n\nasync function stopSpawnedChild(\n\tchild: ChildProcess,\n\tchildExit: Promise<ChildExit>,\n\ttermTimeoutMs: number,\n): Promise<void> {\n\tconst exited = () => child.exitCode !== null || child.signalCode !== null;\n\tif (exited()) return;\n\tconst signal = (name: NodeJS.Signals) => {\n\t\ttry {\n\t\t\tchild.kill(name);\n\t\t} catch (error: unknown) {\n\t\t\tif (!isNodeErrorCode(error, \"ESRCH\")) throw error;\n\t\t}\n\t};\n\tconst waitFor = (ms: number) => Promise.race([childExit.then(() => true), delay(ms).then(() => exited())]);\n\tsignal(\"SIGTERM\");\n\tif (await waitFor(termTimeoutMs)) return;\n\tsignal(\"SIGKILL\");\n\tif (!(await waitFor(SIGKILL_GRACE_MS))) {\n\t\tthrow new Error(`RPC socket host pid ${child.pid ?? \"?\"} remained alive after SIGKILL`);\n\t}\n}\n\nasync function stopManagedHost(\n\tpidFile: DaemonPidFile,\n\ttermTimeoutMs: number,\n\treadStartTime: (pid: number) => Promise<string | undefined> = readProcessStartTime,\n): Promise<void> {\n\tawait signalValidated(pidFile, \"SIGTERM\", readStartTime);\n\tif (await waitForGone(pidFile, termTimeoutMs, readStartTime)) return;\n\tawait signalValidated(pidFile, \"SIGKILL\", readStartTime);\n\tif (!(await waitForGone(pidFile, SIGKILL_GRACE_MS, readStartTime))) {\n\t\tthrow new Error(`RPC socket host pid ${pidFile.pid} remained alive after SIGKILL`);\n\t}\n}\n\ntype PidFileOwnership = \"owns\" | \"gone\" | \"unknown\";\n\n// One probe per call: the teardown loops below are themselves the retry, so the\n// budget inside processMatchesPidFile would only multiply their wall time. A probe\n// that fails against a LIVE pid is \"unknown\" — it proves nothing about ownership, so\n// signalling on it would be unsafe and treating it as \"gone\" would abandon a host that\n// may still be running. A failed probe against a dead pid is \"gone\".\nasync function resolvePidFileOwnership(\n\tpidFile: DaemonPidFile,\n\treadStartTime: (pid: number) => Promise<string | undefined>,\n): Promise<PidFileOwnership> {\n\ttry {\n\t\treturn (await processMatchesPidFile(pidFile, readStartTime, processIsLive, { attempts: 1 })) ? \"owns\" : \"gone\";\n\t} catch (error: unknown) {\n\t\tif (error instanceof ProcessIdentityUnreadableError) return \"unknown\";\n\t\tthrow error;\n\t}\n}\n\nasync function signalValidated(\n\tpidFile: DaemonPidFile,\n\tsignal: NodeJS.Signals,\n\treadStartTime: (pid: number) => Promise<string | undefined> = readProcessStartTime,\n): Promise<void> {\n\tif ((await resolvePidFileOwnership(pidFile, readStartTime)) !== \"owns\") return;\n\ttry {\n\t\tprocess.kill(pidFile.pid, signal);\n\t} catch (error: unknown) {\n\t\tif (!isNodeErrorCode(error, \"ESRCH\")) throw error;\n\t}\n}\n\nasync function waitForGone(\n\tpidFile: DaemonPidFile,\n\ttimeoutMs: number,\n\treadStartTime: (pid: number) => Promise<string | undefined> = readProcessStartTime,\n): Promise<boolean> {\n\tconst deadline = Date.now() + timeoutMs;\n\twhile (Date.now() <= deadline) {\n\t\tif ((await resolvePidFileOwnership(pidFile, readStartTime)) === \"gone\") return true;\n\t\tawait delay(50);\n\t}\n\treturn (await resolvePidFileOwnership(pidFile, readStartTime)) === \"gone\";\n}\n\n/**\n * Compatibility, for the attach decision and the readiness gate alike: a host is compatible exactly\n * when a client that is forbidden to upgrade would attach to it. Never a version-string comparison (I2).\n */\nfunction isCompatible(protocol: HostProtocolInfo | undefined): boolean {\n\treturn decideHostAction(ensureClient({ socket: \"\" }, false), protocol, \"never\").action === \"reuse\";\n}\n\nasync function reapOrphanedInternalHostDirs(): Promise<void> {\n\ttry {\n\t\tconst entries = await readdir(tmpdir(), { withFileTypes: true });\n\t\tawait Promise.all(\n\t\t\tentries\n\t\t\t\t.filter((entry) => entry.isDirectory() && entry.name.startsWith(\"senpi-rpc-host-internal-\"))\n\t\t\t\t.map(async (entry) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst owner = JSON.parse(await readFile(join(tmpdir(), entry.name, \".owner\"), \"utf8\")) as {\n\t\t\t\t\t\t\tpid?: unknown;\n\t\t\t\t\t\t\tprocessStartTime?: unknown;\n\t\t\t\t\t\t\tcreatedAt?: unknown;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttypeof owner.pid === \"number\" &&\n\t\t\t\t\t\t\ttypeof owner.processStartTime === \"string\" &&\n\t\t\t\t\t\t\ttypeof owner.createdAt === \"number\" &&\n\t\t\t\t\t\t\towner.processStartTime.length > 0 &&\n\t\t\t\t\t\t\towner.createdAt < Date.now() - 60_000 &&\n\t\t\t\t\t\t\t(await readdir(join(tmpdir(), entry.name))).length === 1 &&\n\t\t\t\t\t\t\t!(await processMatchesPidFile({ pid: owner.pid, processStartTime: owner.processStartTime }).catch(\n\t\t\t\t\t\t\t\t(error: unknown) => {\n\t\t\t\t\t\t\t\t\t// Unreadable but live: assume the owner is alive rather than steal its lock.\n\t\t\t\t\t\t\t\t\tif (error instanceof ProcessIdentityUnreadableError) return true;\n\t\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t))\n\t\t\t\t\t\t)\n\t\t\t\t\t\t\tawait rm(join(tmpdir(), entry.name), { recursive: true, force: true });\n\t\t\t\t\t} catch {}\n\t\t\t\t}),\n\t\t);\n\t} catch {}\n}\n\nasync function appendStderr(paths: HostDaemonPaths, message: string): Promise<string> {\n\ttry {\n\t\tconst stderr = (await readFile(paths.stderrLog, \"utf8\")).trim();\n\t\treturn stderr ? `${message}\\n${stderr}` : message;\n\t} catch (error: unknown) {\n\t\tif (isNodeErrorCode(error, \"ENOENT\")) return message;\n\t\tthrow error;\n\t}\n}\n\nfunction normalizeSocketPath(value: string): string {\n\tif (value.startsWith(\"unix://\")) return value.slice(\"unix://\".length);\n\treturn value;\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolveDelay) => setTimeout(resolveDelay, ms));\n}\n\nfunction isNodeErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n\nfunction assertNever(value: never): never {\n\tthrow new Error(`unreachable host decision: ${JSON.stringify(value)}`);\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type HostDaemonDirectory } from "./host-daemon-paths.ts";
|
|
2
|
+
export type EndpointInUse = "live_generation" | "live_claim" | "reachable";
|
|
3
|
+
export type SocketSilence = "socket_refused" | "socket_absent";
|
|
4
|
+
/**
|
|
5
|
+
* The three-part test, in the order its answers are reported: (a) no generation pidfile names a live
|
|
6
|
+
* process - the pointer's generation included, since its record lives under `generations/` too - (b) no
|
|
7
|
+
* session-path claim has a live owner, (c) the public socket - and every `.next-*` successor bind beside
|
|
8
|
+
* it - refuses the connection or does not exist.
|
|
9
|
+
*/
|
|
10
|
+
export declare function endpointInUse(paths: HostDaemonDirectory, socket: string): Promise<{
|
|
11
|
+
readonly inUse: EndpointInUse;
|
|
12
|
+
} | {
|
|
13
|
+
readonly inUse: undefined;
|
|
14
|
+
readonly silence: SocketSilence;
|
|
15
|
+
}>;
|
|
16
|
+
/** Every entry beside `socket` that belongs to it: `<name>.next-*` successor binds and `<name>.shield-*`. */
|
|
17
|
+
export declare function socketSiblings(socket: string): Promise<readonly string[]>;
|
|
18
|
+
//# sourceMappingURL=host-gc-evidence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-gc-evidence.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-gc-evidence.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAKnF,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,YAAY,GAAG,WAAW,CAAC;AAE3E,MAAM,MAAM,aAAa,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAK/D;;;;;GAKG;AACH,wBAAsB,aAAa,CAClC,KAAK,EAAE,mBAAmB,EAC1B,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;CAAE,CAAC,CAa7G;AAED,6GAA6G;AAC7G,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAI/E"}
|