@code-yeongyu/senpi 2026.10.19-2 → 2026.10.20-1
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 +18 -0
- package/dist/bundle/chunks/{anthropic-messages-VVRTT7O5.js → anthropic-messages-3QYMAEH2.js} +1 -1
- package/dist/bundle/chunks/{args-35XTBPC7.js → args-NHDT5JG3.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-BUSMTCQ5.js → azure-openai-responses-2DCY6QKO.js} +1 -1
- package/dist/bundle/chunks/chunk-2JGY3V4H.js +395 -0
- package/dist/bundle/chunks/{chunk-2RLZXLXS.js → chunk-2LQZJFKQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-2IK7QUJX.js → chunk-2SM4TR4H.js} +1 -1
- package/dist/bundle/chunks/{chunk-DXOSXUVI.js → chunk-3YKKXTJM.js} +1 -1
- package/dist/bundle/chunks/chunk-6L3H4UVN.js +2 -0
- package/dist/bundle/chunks/{chunk-46NW3UKO.js → chunk-7R4CJDWT.js} +1 -1
- package/dist/bundle/chunks/chunk-AA2L2RHH.js +2 -0
- package/dist/bundle/chunks/chunk-DKBJM22C.js +5 -0
- package/dist/bundle/chunks/{chunk-Z32CYNHC.js → chunk-F4YDU66P.js} +1 -1
- package/dist/bundle/chunks/chunk-FC2YWHEC.js +6 -0
- package/dist/bundle/chunks/{chunk-T2EE5G4S.js → chunk-FRFGJC3I.js} +1 -1
- package/dist/bundle/chunks/{chunk-57RKQONO.js → chunk-GBAS4FFM.js} +1 -1
- package/dist/bundle/chunks/{chunk-FDCYQYBP.js → chunk-JVLIKUBL.js} +1 -1
- package/dist/bundle/chunks/{chunk-EKDY4H2J.js → chunk-JXCHRJA7.js} +1 -1
- package/dist/bundle/chunks/{chunk-YID5W42N.js → chunk-KXTWMPCP.js} +1 -1
- package/dist/bundle/chunks/{chunk-ADNKYKU4.js → chunk-RYQ2FFF5.js} +1 -1
- package/dist/bundle/chunks/chunk-SOMFLLPD.js +3 -0
- package/dist/bundle/chunks/{chunk-4CJJ4SK6.js → chunk-SOWFP3I7.js} +1 -1
- package/dist/bundle/chunks/{chunk-GX7MTG25.js → chunk-TK6RKAB4.js} +4 -4
- package/dist/bundle/chunks/{chunk-NN44F5QF.js → chunk-V6RY5A6T.js} +1 -1
- package/dist/bundle/chunks/{chunk-77PVTNLJ.js → chunk-W557SQYL.js} +2 -2
- package/dist/bundle/chunks/{chunk-F4ZU4RBE.js → chunk-YIRQJCPA.js} +45 -46
- package/dist/bundle/chunks/cli-main-JCQTFUHO.js +3 -0
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-4QEQ7MGK.js +1 -1
- package/dist/bundle/chunks/{dispatch-R37L3FW5.js → dispatch-YPCDRQYI.js} +1 -1
- package/dist/bundle/chunks/{google-generative-ai-4UOGH7RT.js → google-generative-ai-3SKUSCIL.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-2A6KZ5TX.js → google-vertex-YHSIIE4G.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-Z5JGXNCS.js → help-fast-path-TUCMQOOF.js} +1 -1
- package/dist/bundle/chunks/{host-command-IP3QAZYI.js → host-command-WEOS6VD6.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-FRQK5GAC.js → host-lifecycle-ZDCO62Z4.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +6 -6
- package/dist/bundle/chunks/host-session-command-IGTNZ4F6.js +20 -0
- package/dist/bundle/chunks/interactive-mode-5KYT7LMW.js +2 -0
- package/dist/bundle/chunks/{multi-session-host-EMI54PTJ.js → multi-session-host-25XQFXJC.js} +5 -5
- package/dist/bundle/chunks/openai-codex-responses-4BIPACKV.js +10 -0
- package/dist/bundle/chunks/{openai-completions-AGTH2RRA.js → openai-completions-NHDMDAS7.js} +1 -1
- package/dist/bundle/chunks/{openai-images-5UGZHY27.js → openai-images-PNXQDBSP.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-KGXDGHPI.js → openai-responses-2UOPEJLP.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-TK44VHGK.js → package-manager-cli-CF53LR5X.js} +1 -1
- package/dist/bundle/chunks/{role-F6IEUF2N.js → role-OK7RKBGF.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-XX2NXYL3.js → rotation-stream-QRRJ7G5Y.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-LOODTK3J.js +2 -0
- package/dist/bundle/chunks/{schedule-command-DYHRFIX2.js → schedule-command-LKL6DLOT.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-MLANJVB2.js +3 -0
- package/dist/bundle/chunks/session-picker-RQDVWJRL.js +2 -0
- package/dist/bundle/chunks/session-worker.js +240 -240
- package/dist/bundle/chunks/session-write-fence-66EMOO4Y.js +2 -0
- package/dist/bundle/chunks/typesafe-system-one-W2ZTYF7X.js +1 -1
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +6 -6
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/agent-session.d.ts +0 -2
- package/dist/core/agent-session.js +4 -13
- package/dist/core/credential-pool/classify.d.ts +7 -0
- package/dist/core/credential-pool/classify.js +12 -4
- package/dist/core/credential-pool/failover.js +16 -1
- package/dist/core/credential-pool/rotation-stream.js +2 -0
- package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +8 -62
- package/dist/core/extensions/builtin/anthropic-subscription/context-window.d.ts +25 -0
- package/dist/core/extensions/builtin/anthropic-subscription/context-window.js +38 -0
- package/dist/core/extensions/builtin/anthropic-subscription/index.d.ts +2 -2
- package/dist/core/extensions/builtin/anthropic-subscription/index.js +4 -6
- package/dist/core/extensions/builtin/anthropic-subscription/long-context-retry.d.ts +18 -0
- package/dist/core/extensions/builtin/anthropic-subscription/long-context-retry.js +74 -0
- package/dist/core/extensions/builtin/anthropic-subscription/long-context.js +13 -5
- package/dist/core/extensions/builtin/anthropic-subscription/options.d.ts +0 -10
- package/dist/core/extensions/builtin/anthropic-subscription/options.js +8 -16
- package/dist/core/extensions/builtin/anthropic-subscription/session-registry-wiring.js +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-sync.js +3 -0
- package/dist/core/extensions/builtin/terminal/lease-file.d.ts +1 -0
- package/dist/core/extensions/builtin/terminal/lease-file.js +5 -3
- package/dist/core/foreign-session-holders.d.ts +2 -3
- package/dist/core/provider-failure.d.ts +2 -0
- package/dist/core/provider-failure.js +3 -0
- package/dist/core/reserved-keywords.js +1 -1
- package/dist/core/session-hold-errors.d.ts +2 -0
- package/dist/core/session-holder-records.d.ts +21 -0
- package/dist/core/session-holder-records.js +76 -0
- package/dist/core/session-holders.d.ts +3 -3
- package/dist/core/session-holders.js +4 -41
- package/dist/core/session-manager.d.ts +1 -0
- package/dist/core/session-manager.js +8 -1
- package/dist/core/session-write-fence.d.ts +18 -0
- package/dist/core/session-write-fence.js +71 -0
- package/dist/core/user-text-blocks.d.ts +11 -0
- package/dist/core/user-text-blocks.js +19 -1
- package/dist/modes/rpc/host-daemon-registration.js +2 -1
- package/dist/modes/rpc/host-generations.d.ts +6 -0
- package/dist/modes/rpc/host-generations.js +35 -4
- package/dist/modes/rpc/host-lifecycle-scratch.d.ts +9 -2
- package/dist/modes/rpc/host-lifecycle-scratch.js +26 -6
- package/dist/modes/rpc/host-lifecycle.js +1 -1
- package/dist/modes/rpc/host-reservations.d.ts +5 -0
- package/dist/modes/rpc/host-reservations.js +54 -12
- package/dist/modes/rpc/host-stalled-evidence.d.ts +27 -1
- package/dist/modes/rpc/host-stalled-evidence.js +52 -4
- package/dist/modes/rpc/multi-session-host.js +10 -1
- package/dist/modes/rpc/session-command-router.js +2 -0
- package/dist/modes/rpc/session-held.js +2 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-74V6LCWU.js +0 -2
- package/dist/bundle/chunks/chunk-DDQJSVIZ.js +0 -5
- package/dist/bundle/chunks/chunk-DY2EM44T.js +0 -395
- package/dist/bundle/chunks/chunk-HOXW5IHD.js +0 -6
- package/dist/bundle/chunks/chunk-UAQW2T7G.js +0 -2
- package/dist/bundle/chunks/cli-main-RXZVFXK7.js +0 -3
- package/dist/bundle/chunks/host-session-command-KGCQPMES.js +0 -20
- package/dist/bundle/chunks/interactive-mode-DQOPIZSI.js +0 -2
- package/dist/bundle/chunks/openai-codex-responses-I2NSK6AD.js +0 -10
- package/dist/bundle/chunks/rpc-mode-X6J3FC3N.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-XN4BAZTU.js +0 -3
- package/dist/bundle/chunks/session-picker-US5YPKC7.js +0 -2
- /package/dist/bundle/chunks/{chunk-KW6654UH.js → chunk-RPAHOJ6F.js} +0 -0
|
@@ -17,7 +17,7 @@ import { processIsLive } from "../../utils/process-identity.js";
|
|
|
17
17
|
import { generationPaths } from "./host-daemon-paths.js";
|
|
18
18
|
import { parseJson, readFileOrUndefined } from "./host-daemon-state.js";
|
|
19
19
|
import { readHostProcessMetrics } from "./host-process-metrics.js";
|
|
20
|
-
import {
|
|
20
|
+
import { claimOwnerIdentity, readSessionPathClaims } from "./host-reservations.js";
|
|
21
21
|
import { hostChildAlive } from "./host-stalled-evidence.js";
|
|
22
22
|
/**
|
|
23
23
|
* Drops every record of a generation that is no longer running: its directory, the pointer while it
|
|
@@ -34,21 +34,52 @@ export async function pruneDeadGenerations(paths) {
|
|
|
34
34
|
continue;
|
|
35
35
|
if (await hostChildAlive(generationPaths(paths, instanceId)))
|
|
36
36
|
continue;
|
|
37
|
-
await rm(generationPaths(paths, instanceId).dir, { recursive: true, force: true }).catch(() => undefined);
|
|
38
37
|
generations.push(instanceId);
|
|
39
38
|
}
|
|
39
|
+
// Claims end before the directories go: a prune cut short in between must not leave a claim whose generation
|
|
40
|
+
// (and heartbeat) no longer exists to explain it.
|
|
41
|
+
const claims = await endClaimsOfEndedGenerations(paths, new Set(generations));
|
|
42
|
+
for (const instanceId of generations)
|
|
43
|
+
await rm(generationPaths(paths, instanceId).dir, { recursive: true, force: true }).catch(() => undefined);
|
|
40
44
|
const pointerRemoved = pointedAt !== undefined && generations.includes(pointedAt);
|
|
41
45
|
// The pointer outliving its generation is what makes a client probe a socket nobody serves.
|
|
42
46
|
if (pointerRemoved)
|
|
43
47
|
await rm(paths.pointerFile, { force: true }).catch(() => undefined);
|
|
48
|
+
return { generations, claims, pointer: pointerRemoved };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Removes every claim whose owner is gone, and every claim of a generation in `ended` (being removed by the caller)
|
|
52
|
+
* whose owner identity cannot be proven. Returns the session paths released. Every path that removes a generation
|
|
53
|
+
* directory calls this first, so no generation leaves claims behind that its directory no longer explains.
|
|
54
|
+
*/
|
|
55
|
+
export async function endClaimsOfEndedGenerations(paths, ended) {
|
|
44
56
|
const claims = [];
|
|
45
57
|
for (const claim of await readSessionPathClaims(paths.reservationsDir)) {
|
|
46
|
-
if (await
|
|
58
|
+
if (!(await claimEnded(claim.owner, ended)))
|
|
47
59
|
continue;
|
|
48
60
|
await rm(claim.file, { force: true }).catch(() => undefined);
|
|
49
61
|
claims.push(claim.owner.sessionPath);
|
|
50
62
|
}
|
|
51
|
-
return
|
|
63
|
+
return claims;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A claim ends with its owner's pid, or with the generation this prune just removed when the owner's identity
|
|
67
|
+
* cannot be proven (desktop#3157: Windows hands the dead host child's pid to another process, and removing the
|
|
68
|
+
* generation also removed the heartbeat that would have released the claim). Such an owner might only be frozen,
|
|
69
|
+
* so its session file is fenced first; a fence that cannot be written keeps the claim.
|
|
70
|
+
*/
|
|
71
|
+
async function claimEnded(owner, ended) {
|
|
72
|
+
const identity = await claimOwnerIdentity(owner);
|
|
73
|
+
if (identity === "gone")
|
|
74
|
+
return true;
|
|
75
|
+
if (identity === "proven" || !ended.has(owner.instanceId))
|
|
76
|
+
return false;
|
|
77
|
+
// Documented lazy boundary (AGENTS.md: inline dynamic imports only at documented lazy boundaries): the lifecycle
|
|
78
|
+
// supervisor imports this module, and the fence module (whose writers read it synchronously) must stay out of its
|
|
79
|
+
// startup graph (test/supervisor-import-graph.test.ts).
|
|
80
|
+
return import("../../core/session-write-fence.js")
|
|
81
|
+
.then(({ advanceSessionFence }) => advanceSessionFence(owner.sessionPath))
|
|
82
|
+
.then(() => true, () => false);
|
|
52
83
|
}
|
|
53
84
|
function memoryOf(metrics) {
|
|
54
85
|
return { rss_mb: metrics.rss_mb, host_rss_mb: metrics.host_rss_mb };
|
|
@@ -20,7 +20,14 @@ export declare function createInternalSocketPath(baseDir?: string, platform?: No
|
|
|
20
20
|
dir?: string;
|
|
21
21
|
secretPath?: string;
|
|
22
22
|
}>;
|
|
23
|
-
/**
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Best-effort, 0600, by rename; released with the generation directory.
|
|
25
|
+
*
|
|
26
|
+
* The pid is recorded at once and its start time added when it can be read, the way the supervisor's own
|
|
27
|
+
* record is (`host-ensure-start.ts`): a bounded wait of 1 s probes, then one unhurried read. A single
|
|
28
|
+
* 1 s CIM probe fired right after spawn came back empty on every Windows generation (#3658). An identity
|
|
29
|
+
* read after the child exited is never recorded: its pid may already name an unrelated process.
|
|
30
|
+
*/
|
|
31
|
+
export declare function recordChildPid(file: string, pid: number, running: () => boolean): Promise<void>;
|
|
25
32
|
export declare function readSettingsFile(settingsFile: string): Promise<unknown>;
|
|
26
33
|
//# sourceMappingURL=host-lifecycle-scratch.d.ts.map
|
|
@@ -6,7 +6,7 @@ import { randomUUID } from "node:crypto";
|
|
|
6
6
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import { readProcessIdentity, readProcessStartTime } from "../../utils/process-identity.js";
|
|
9
|
+
import { readProcessIdentity, readProcessStartTime, waitForStartTime } from "../../utils/process-identity.js";
|
|
10
10
|
import { writeJsonAtomic } from "./host-state-json.js";
|
|
11
11
|
import { errorMessage, supervisorLog } from "./host-supervisor-log.js";
|
|
12
12
|
import { MAX_SOCKET_PATH_BYTES } from "./socket-ownership.js";
|
|
@@ -59,13 +59,33 @@ export async function createInternalSocketPath(baseDir = tmpdir(), platform = pr
|
|
|
59
59
|
}), { mode: 0o600 });
|
|
60
60
|
return { socket: join(dir, "host.sock"), dir, secretPath: join(dir, ".secret") };
|
|
61
61
|
}
|
|
62
|
-
/**
|
|
63
|
-
|
|
62
|
+
/** The bounded startup wait for the child's identity, then the one unhurried read (#3658). */
|
|
63
|
+
const CHILD_START_TIME_WAIT_MS = 10_000;
|
|
64
|
+
const CHILD_START_TIME_UNHURRIED_MS = 15_000;
|
|
65
|
+
async function readChildStartTime(pid, timeoutMs) {
|
|
66
|
+
const identity = await readProcessIdentity(pid, process.platform, timeoutMs, undefined, "UTC");
|
|
67
|
+
if (identity.kind === "error")
|
|
68
|
+
throw identity.error;
|
|
69
|
+
return identity.kind === "present" ? identity.identity : undefined;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Best-effort, 0600, by rename; released with the generation directory.
|
|
73
|
+
*
|
|
74
|
+
* The pid is recorded at once and its start time added when it can be read, the way the supervisor's own
|
|
75
|
+
* record is (`host-ensure-start.ts`): a bounded wait of 1 s probes, then one unhurried read. A single
|
|
76
|
+
* 1 s CIM probe fired right after spawn came back empty on every Windows generation (#3658). An identity
|
|
77
|
+
* read after the child exited is never recorded: its pid may already name an unrelated process.
|
|
78
|
+
*/
|
|
79
|
+
export async function recordChildPid(file, pid, running) {
|
|
64
80
|
try {
|
|
65
|
-
|
|
66
|
-
const processStartTime =
|
|
67
|
-
if (processStartTime ===
|
|
81
|
+
await writeJsonAtomic(file, { pid, processStartTime: null });
|
|
82
|
+
const processStartTime = (await waitForStartTime(pid, CHILD_START_TIME_WAIT_MS, (p) => readChildStartTime(p, 1_000)).catch(() => undefined)) ?? (await readChildStartTime(pid, CHILD_START_TIME_UNHURRIED_MS).catch(() => undefined));
|
|
83
|
+
if (processStartTime === undefined) {
|
|
68
84
|
supervisorLog(`host child pid ${pid} start time unreadable at recording`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!running())
|
|
88
|
+
return;
|
|
69
89
|
await writeJsonAtomic(file, { pid, processStartTime });
|
|
70
90
|
}
|
|
71
91
|
catch (cause) {
|
|
@@ -92,7 +92,7 @@ export async function runHostSupervisor(launch) {
|
|
|
92
92
|
child.stdio[HOST_CHILD_WATCH_FD]?.on("error", () => { });
|
|
93
93
|
// The CHILD's own identity, so a reader can tell "supervisor gone, child still running" apart.
|
|
94
94
|
if (child.pid !== undefined)
|
|
95
|
-
void recordChildPid(generation.childPidFile, child.pid);
|
|
95
|
+
void recordChildPid(generation.childPidFile, child.pid, () => child.exitCode === null && child.signalCode === null);
|
|
96
96
|
child.once("exit", (code, signal) => {
|
|
97
97
|
// Recorded BEFORE any shutdown it triggers: `shutdown` ends in `process.exit`. A stop the
|
|
98
98
|
// supervisor is performing itself is recorded too, as the engine stop it is.
|
|
@@ -56,4 +56,9 @@ export declare function readSessionPathClaims(dir: string): Promise<readonly Ses
|
|
|
56
56
|
* what separates "that host is still writing" from "the OS handed its pid to something else".
|
|
57
57
|
*/
|
|
58
58
|
export declare function claimOwnerIsLive(owner: SessionPathOwner): Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* `proven`: the pid runs and its start time matches the claim. `gone`: the pid is dead, or runs with a
|
|
61
|
+
* different start time. `unproven`: the pid runs but no start time was recorded or none can be read.
|
|
62
|
+
*/
|
|
63
|
+
export declare function claimOwnerIdentity(owner: SessionPathOwner): Promise<"proven" | "gone" | "unproven">;
|
|
59
64
|
//# sourceMappingURL=host-reservations.d.ts.map
|
|
@@ -23,8 +23,9 @@ import { createHash } from "node:crypto";
|
|
|
23
23
|
import { mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
import { processIsLive, processStartTimeMs, readProcessStartTime, sameProcessStartMs, } from "../../utils/process-identity.js";
|
|
26
|
-
import { createHostDaemonPaths, HOST_DAEMON_DIR_ENV, hostDaemonDirectoryPaths } from "./host-daemon-paths.js";
|
|
26
|
+
import { createHostDaemonPaths, generationPaths, HOST_DAEMON_DIR_ENV, hostDaemonDirectoryPaths, } from "./host-daemon-paths.js";
|
|
27
27
|
import { parseJson, readFileOrUndefined } from "./host-daemon-state.js";
|
|
28
|
+
import { generationHeartbeatStopped } from "./host-stalled-evidence.js";
|
|
28
29
|
import { logUnknownHostIdentity } from "./host-supervisor-log.js";
|
|
29
30
|
/** How long a client should wait before retrying a path another generation still holds. */
|
|
30
31
|
export const SESSION_PATH_RETRY_AFTER_MS = 2_000;
|
|
@@ -141,9 +142,22 @@ export function createSessionPathReservations(options) {
|
|
|
141
142
|
async claim(sessionPath, attached = true) {
|
|
142
143
|
const existing = await readOwner(reservationFile(dir, sessionPath));
|
|
143
144
|
if (existing && existing.pid !== pid) {
|
|
144
|
-
const
|
|
145
|
-
if (
|
|
146
|
-
return
|
|
145
|
+
const verdict = await standingOwner(existing, paths);
|
|
146
|
+
if (verdict.kind === "stands")
|
|
147
|
+
return verdict.owner;
|
|
148
|
+
const fenced = verdict.kind === "released" ||
|
|
149
|
+
// Documented lazy boundary (AGENTS.md: inline dynamic imports only at documented lazy boundaries): the
|
|
150
|
+
// lifecycle supervisor imports this module, and the fence module (whose writers read it synchronously)
|
|
151
|
+
// must stay out of the supervisor's startup graph (test/supervisor-import-graph.test.ts). Only a takeover
|
|
152
|
+
// of an unprovable claim reaches it.
|
|
153
|
+
(await import("../../core/session-write-fence.js")
|
|
154
|
+
.then(({ advanceSessionFence }) => advanceSessionFence(sessionPath))
|
|
155
|
+
.then(() => true, (cause) => {
|
|
156
|
+
report(`session file ${sessionPath} could not be fenced for a takeover (${errorMessage(cause)})`);
|
|
157
|
+
return false;
|
|
158
|
+
}));
|
|
159
|
+
if (!fenced)
|
|
160
|
+
return { ...existing, current: false };
|
|
147
161
|
}
|
|
148
162
|
try {
|
|
149
163
|
await publish(sessionPath, attached);
|
|
@@ -200,12 +214,31 @@ export async function readSessionPathClaims(dir) {
|
|
|
200
214
|
* writing the file. A superseded owner that published `attached: false` is draining (or should
|
|
201
215
|
* be), so its claim is released rather than obeyed. A claim without the flag predates it and is
|
|
202
216
|
* honored, so a running older generation is never reclaimed from.
|
|
217
|
+
*
|
|
218
|
+
* A superseded owner whose identity cannot be PROVEN - no start time was recorded, or none can be read
|
|
219
|
+
* now - needs one more piece of evidence before it may block an open (#3652). On Windows a terminated
|
|
220
|
+
* generation leaves its claim behind and the OS hands its pid to an unrelated process; without a
|
|
221
|
+
* readable start time that stranger would hold the session file for as long as it runs. So such a
|
|
222
|
+
* claim stands only while its generation still heartbeats: a heartbeat older than the stall-refusal
|
|
223
|
+
* window is the same "died without cleaning up" verdict `hostLoopStalled` already reaches. A live
|
|
224
|
+
* host whose probe is merely starved (#1609) keeps heartbeating and keeps its claim; a claim with no
|
|
225
|
+
* heartbeat at all (an unsupervised or older host) keeps the previous benefit of the doubt.
|
|
203
226
|
*/
|
|
204
|
-
async function standingOwner(owner,
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
227
|
+
async function standingOwner(owner, paths) {
|
|
228
|
+
const identity = await claimOwnerIdentity(owner);
|
|
229
|
+
if (identity === "gone")
|
|
230
|
+
return { kind: "released" };
|
|
231
|
+
const current = (await currentInstanceId(paths.pointerFile)) === owner.instanceId;
|
|
232
|
+
if (!current && identity === "unproven" && (await heartbeatStopped(paths, owner.instanceId)))
|
|
233
|
+
return { kind: "presumed_done" };
|
|
234
|
+
return current || owner.attached !== false ? { kind: "stands", owner: { ...owner, current } } : { kind: "released" };
|
|
235
|
+
}
|
|
236
|
+
/** Whether the owner's generation left a heartbeat that is older than a stalled host could explain. */
|
|
237
|
+
async function heartbeatStopped(paths, instanceId) {
|
|
238
|
+
// A claim file is untrusted input: an instance id that is not a plain directory name names no generation.
|
|
239
|
+
if (instanceId === "" || instanceId === "." || instanceId === ".." || /[\\/]/.test(instanceId))
|
|
240
|
+
return false;
|
|
241
|
+
return generationHeartbeatStopped(generationPaths(paths, instanceId));
|
|
209
242
|
}
|
|
210
243
|
/** The generation the pointer names, or nothing when no pointer describes this endpoint. */
|
|
211
244
|
async function currentInstanceId(pointerFile) {
|
|
@@ -240,16 +273,25 @@ async function readOwner(file) {
|
|
|
240
273
|
* what separates "that host is still writing" from "the OS handed its pid to something else".
|
|
241
274
|
*/
|
|
242
275
|
export async function claimOwnerIsLive(owner) {
|
|
276
|
+
return (await claimOwnerIdentity(owner)) !== "gone";
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* `proven`: the pid runs and its start time matches the claim. `gone`: the pid is dead, or runs with a
|
|
280
|
+
* different start time. `unproven`: the pid runs but no start time was recorded or none can be read.
|
|
281
|
+
*/
|
|
282
|
+
export async function claimOwnerIdentity(owner) {
|
|
243
283
|
if (!processIsLive(owner.pid))
|
|
244
|
-
return
|
|
284
|
+
return "gone";
|
|
245
285
|
const recorded = owner.processStartTime === null ? undefined : processStartTimeMs(owner.processStartTime);
|
|
246
286
|
if (recorded === undefined) {
|
|
247
287
|
logUnknownHostIdentity("session-path-claim", owner.pid);
|
|
248
|
-
return
|
|
288
|
+
return "unproven";
|
|
249
289
|
}
|
|
250
290
|
const current = await readProcessStartTime(owner.pid).catch(() => undefined);
|
|
251
291
|
const observed = current === undefined ? undefined : processStartTimeMs(current);
|
|
252
|
-
|
|
292
|
+
if (observed === undefined)
|
|
293
|
+
return "unproven";
|
|
294
|
+
return sameProcessStartMs(recorded, observed) ? "proven" : "gone";
|
|
253
295
|
}
|
|
254
296
|
function errorMessage(cause) {
|
|
255
297
|
return cause instanceof Error ? cause.message : String(cause);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HolderGeneration } from "../../core/session-holders.ts";
|
|
1
2
|
import { type HostGenerationPaths } from "./host-daemon-paths.ts";
|
|
2
3
|
/** How recent stall evidence keeps an unreachable generation from being replaced. */
|
|
3
4
|
export declare const STALL_REFUSAL_WINDOW_MS_ENV = "SENPI_RPC_STALL_REFUSAL_MS";
|
|
@@ -37,7 +38,32 @@ export declare function hostLoopStalled(generation: HostGenerationPaths, options
|
|
|
37
38
|
readonly errorMs: number;
|
|
38
39
|
readonly windowMs: number;
|
|
39
40
|
}): Promise<boolean>;
|
|
40
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Whether the generation left a heartbeat older than any stall could explain: the same "died without
|
|
43
|
+
* cleaning up" verdict `hostLoopStalled` reaches past the window. No heartbeat file is no evidence.
|
|
44
|
+
*/
|
|
45
|
+
export declare function generationHeartbeatStopped(generation: HostGenerationPaths, now?: number): Promise<boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the host generation a session-holder lease names has ENDED, for a holder whose pid is alive but
|
|
48
|
+
* whose start time cannot be compared (#3658). Two pieces of evidence count:
|
|
49
|
+
* - its heartbeat stopped (`generationHeartbeatStopped`, the rule claims and generations share);
|
|
50
|
+
* - the daemon directory the lease names is an endpoint's (`endpoint.json`, which every release leaves)
|
|
51
|
+
* and the generation's directory is gone: a daemon removes it only after its child was judged not
|
|
52
|
+
* alive, by the same heartbeat rule. A lease without a daemon directory is read against this host's
|
|
53
|
+
* own daemon, where only the heartbeat counts, since absence there cannot prove membership.
|
|
54
|
+
* A fresh heartbeat, no heartbeat file, or this host's own generation never ends: a live host whose
|
|
55
|
+
* probe is merely slow keeps its sessions (#1609).
|
|
56
|
+
*/
|
|
57
|
+
export declare function holderGenerationEnded(holder: HolderGeneration, env?: Readonly<Record<string, string | undefined>>): Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the generation's host CHILD is running, from its own pid record; `undefined` when unrecorded.
|
|
60
|
+
*
|
|
61
|
+
* A live pid whose start time cannot be compared (none recorded, or none readable now) is preserved -
|
|
62
|
+
* a starved probe on a live host must not read as dead (#1609) - unless the generation's heartbeat has
|
|
63
|
+
* stopped (#3658). On Windows a killed generation's pid is handed to an unrelated process, and without
|
|
64
|
+
* that second piece of evidence the stranger would keep the generation, and every ensure refusing
|
|
65
|
+
* `host_stalled`, alive for as long as it runs.
|
|
66
|
+
*/
|
|
41
67
|
export declare function hostChildAlive(generation: HostGenerationPaths): Promise<boolean | undefined>;
|
|
42
68
|
/**
|
|
43
69
|
* The supervisor's report that it is waiting out a stalled child before escalating; a caller with a
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
* endpoint directory during a handoff. A stalled host is still serving its sessions; nothing here
|
|
11
11
|
* signals anything.
|
|
12
12
|
*/
|
|
13
|
-
import { rm } from "node:fs/promises";
|
|
14
|
-
import { join } from "node:path";
|
|
13
|
+
import { rm, stat } from "node:fs/promises";
|
|
14
|
+
import { isAbsolute, join } from "node:path";
|
|
15
15
|
import { processIsLive, processStartTimeMs, readProcessIdentity, sameProcessStartMs, } from "../../utils/process-identity.js";
|
|
16
|
-
import { HOST_DAEMON_DIR_ENV } from "./host-daemon-paths.js";
|
|
16
|
+
import { generationPaths, HOST_DAEMON_DIR_ENV, hostDaemonDirectoryPaths, } from "./host-daemon-paths.js";
|
|
17
17
|
import { HOST_INSTANCE_ID_ENV } from "./host-identity-env.js";
|
|
18
18
|
import { parseIdleExitMs } from "./host-lifecycle-policy.js";
|
|
19
19
|
import { ageOf, readJsonObject, writeJsonAtomic } from "./host-state-json.js";
|
|
@@ -79,7 +79,51 @@ export async function hostLoopStalled(generation, options) {
|
|
|
79
79
|
const heartbeatAge = ageOf((await readJsonObject(generation.aliveFile))?.at, options.now);
|
|
80
80
|
return heartbeatAge !== undefined && heartbeatAge > options.errorMs && heartbeatAge < options.windowMs;
|
|
81
81
|
}
|
|
82
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Whether the generation left a heartbeat older than any stall could explain: the same "died without
|
|
84
|
+
* cleaning up" verdict `hostLoopStalled` reaches past the window. No heartbeat file is no evidence.
|
|
85
|
+
*/
|
|
86
|
+
export async function generationHeartbeatStopped(generation, now = Date.now()) {
|
|
87
|
+
const age = ageOf((await readJsonObject(generation.aliveFile))?.at, now);
|
|
88
|
+
return age !== undefined && age >= stallRefusalWindowMs();
|
|
89
|
+
}
|
|
90
|
+
const SAFE_INSTANCE_ID = /^[A-Za-z0-9_-]+$/;
|
|
91
|
+
async function exists(path) {
|
|
92
|
+
return stat(path).then(() => true, () => false);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Whether the host generation a session-holder lease names has ENDED, for a holder whose pid is alive but
|
|
96
|
+
* whose start time cannot be compared (#3658). Two pieces of evidence count:
|
|
97
|
+
* - its heartbeat stopped (`generationHeartbeatStopped`, the rule claims and generations share);
|
|
98
|
+
* - the daemon directory the lease names is an endpoint's (`endpoint.json`, which every release leaves)
|
|
99
|
+
* and the generation's directory is gone: a daemon removes it only after its child was judged not
|
|
100
|
+
* alive, by the same heartbeat rule. A lease without a daemon directory is read against this host's
|
|
101
|
+
* own daemon, where only the heartbeat counts, since absence there cannot prove membership.
|
|
102
|
+
* A fresh heartbeat, no heartbeat file, or this host's own generation never ends: a live host whose
|
|
103
|
+
* probe is merely slow keeps its sessions (#1609).
|
|
104
|
+
*/
|
|
105
|
+
export async function holderGenerationEnded(holder, env = process.env) {
|
|
106
|
+
const instance = holder.host_instance;
|
|
107
|
+
if (instance === undefined || !SAFE_INSTANCE_ID.test(instance) || instance === env[HOST_INSTANCE_ID_ENV])
|
|
108
|
+
return false;
|
|
109
|
+
const daemonDir = holder.host_daemon_dir ?? env[HOST_DAEMON_DIR_ENV];
|
|
110
|
+
if (daemonDir === undefined || !isAbsolute(daemonDir))
|
|
111
|
+
return false;
|
|
112
|
+
const directory = hostDaemonDirectoryPaths(daemonDir);
|
|
113
|
+
const generation = generationPaths(directory, instance);
|
|
114
|
+
if (await generationHeartbeatStopped(generation))
|
|
115
|
+
return true;
|
|
116
|
+
return (holder.host_daemon_dir !== undefined && (await exists(directory.endpointFile)) && !(await exists(generation.dir)));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Whether the generation's host CHILD is running, from its own pid record; `undefined` when unrecorded.
|
|
120
|
+
*
|
|
121
|
+
* A live pid whose start time cannot be compared (none recorded, or none readable now) is preserved -
|
|
122
|
+
* a starved probe on a live host must not read as dead (#1609) - unless the generation's heartbeat has
|
|
123
|
+
* stopped (#3658). On Windows a killed generation's pid is handed to an unrelated process, and without
|
|
124
|
+
* that second piece of evidence the stranger would keep the generation, and every ensure refusing
|
|
125
|
+
* `host_stalled`, alive for as long as it runs.
|
|
126
|
+
*/
|
|
83
127
|
export async function hostChildAlive(generation) {
|
|
84
128
|
const record = await readJsonObject(generation.childPidFile);
|
|
85
129
|
const pid = record?.pid;
|
|
@@ -94,6 +138,10 @@ export async function hostChildAlive(generation) {
|
|
|
94
138
|
const observedMs = observed.kind === "present" ? processStartTimeMs(observed.identity) : undefined;
|
|
95
139
|
if (recordedMs !== undefined && observedMs !== undefined)
|
|
96
140
|
return sameProcessStartMs(recordedMs, observedMs);
|
|
141
|
+
if (await generationHeartbeatStopped(generation)) {
|
|
142
|
+
supervisorLog(`host child pid ${pid} start time unreadable and its heartbeat stopped; releasing generation`);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
97
145
|
supervisorLog(`host child pid ${pid} start time unreadable; preserving generation`);
|
|
98
146
|
return true;
|
|
99
147
|
}
|
|
@@ -37,6 +37,13 @@ import { WorkerSessionRegistry } from "./worker-session-registry.js";
|
|
|
37
37
|
export { DEFAULT_HOST_EMPTY_EXIT_MS, DEFAULT_SESSION_IDLE_EVICTION_MS, RPC_HOST_EMPTY_EXIT_MS_ENV, RPC_SESSION_IDLE_EVICTION_MS_ENV, resolveHostIdlePolicy, } from "./host-idle-policy.js";
|
|
38
38
|
/** Win32 named-pipe close can leave libuv's server callback pending after handles are destroyed. */
|
|
39
39
|
const WINDOWS_SHUTDOWN_HARD_EXIT_MS = 2_000;
|
|
40
|
+
/**
|
|
41
|
+
* How long a win32 shutdown waits for its sessions to finish closing (#3652). A TerminateProcess'd
|
|
42
|
+
* supervisor runs no stop of its own, its watchdog removes the pointer at once, and until the
|
|
43
|
+
* connections detach this host's claims still read `attached: true` - so an unbounded dispose would
|
|
44
|
+
* hold every session file it served for as long as one close stays wedged.
|
|
45
|
+
*/
|
|
46
|
+
const WINDOWS_SHUTDOWN_DISPOSE_BOUND_MS = 10_000;
|
|
40
47
|
/**
|
|
41
48
|
* Socket agent events are delivered only to connections attached to their
|
|
42
49
|
* session, tagged with its routing sessionId. Content-free session lifecycle
|
|
@@ -354,7 +361,9 @@ async function runSocketHost(options, socketPath) {
|
|
|
354
361
|
try {
|
|
355
362
|
// Dispose while connections are still registered so `session_closed`
|
|
356
363
|
// `{ reason: "host_shutdown" }` reaches clients before the sockets die.
|
|
357
|
-
await
|
|
364
|
+
await (process.platform === "win32"
|
|
365
|
+
? Promise.race([router.dispose(), delay(WINDOWS_SHUTDOWN_DISPOSE_BOUND_MS)])
|
|
366
|
+
: router.dispose());
|
|
358
367
|
await writer.flush();
|
|
359
368
|
for (const connection of connections.values()) {
|
|
360
369
|
connection.detach();
|
|
@@ -5,6 +5,7 @@ import { buildRpcSessionState } from "./connection-handler.js";
|
|
|
5
5
|
import { answerEffectFromTranscript } from "./effect-commands.js";
|
|
6
6
|
import { latestSessionEffect } from "./effect-ledger.js";
|
|
7
7
|
import { multiSessionHostCapabilities } from "./host-capabilities.js";
|
|
8
|
+
import { HOST_DAEMON_DIR_ENV } from "./host-daemon-paths.js";
|
|
8
9
|
import { clearHandoverParkedSession, recordHandoverParkedSession, } from "./host-handover-parked.js";
|
|
9
10
|
import { parkedEntryFromProfile, parkedOpenCommand } from "./host-handover-parked-open.js";
|
|
10
11
|
import { answerWarm } from "./host-warm.js";
|
|
@@ -703,6 +704,7 @@ export class SessionCommandRouter {
|
|
|
703
704
|
sessionPath: command.sessionPath,
|
|
704
705
|
holderEndpoint: {
|
|
705
706
|
host_instance: instanceId,
|
|
707
|
+
...(process.env[HOST_DAEMON_DIR_ENV] ? { host_daemon_dir: process.env[HOST_DAEMON_DIR_ENV] } : {}),
|
|
706
708
|
...(this.defaults.hostContext?.host_socket === undefined
|
|
707
709
|
? {}
|
|
708
710
|
: { host_socket: this.defaults.hostContext.host_socket }),
|
|
@@ -2,6 +2,7 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { readProcessStartMs } from "../../core/extensions/builtin/terminal/process-start-probe.js";
|
|
3
3
|
import { foreignSessionHolders } from "../../core/foreign-session-holders.js";
|
|
4
4
|
import { processStartTimeMs } from "../../utils/process-identity.js";
|
|
5
|
+
import { holderGenerationEnded } from "./host-stalled-evidence.js";
|
|
5
6
|
import { RPC_ERROR_SESSION_HELD } from "./rpc-types.js";
|
|
6
7
|
import { RpcSessionRegistryError } from "./session-registry-types.js";
|
|
7
8
|
// Admission is conservative: a new command must pass the guard unless it is explicitly
|
|
@@ -88,6 +89,7 @@ export function sessionHeldCheck(resolveDaemonPids) {
|
|
|
88
89
|
const observedStarts = new Map();
|
|
89
90
|
return async (sessionFile, sessionId) => {
|
|
90
91
|
const holders = await foreignSessionHolders(sessionFile, sessionId, {
|
|
92
|
+
holderEnded: (generation) => holderGenerationEnded(generation),
|
|
91
93
|
readProcessStartMs: (pid) => {
|
|
92
94
|
let probe = pidStarts.get(pid);
|
|
93
95
|
if (!probe) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.20-1",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@earendil-works/chord": "0.99.1",
|
|
67
|
-
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.10.
|
|
68
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.
|
|
69
|
-
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.
|
|
67
|
+
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.10.20-1",
|
|
68
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.20-1",
|
|
69
|
+
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.20-1",
|
|
70
70
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
71
71
|
"chalk": "6.0.0",
|
|
72
72
|
"cross-spawn": "7.0.6",
|
|
@@ -85,8 +85,8 @@
|
|
|
85
85
|
"yaml": "2.9.1",
|
|
86
86
|
"@anthropic-ai/claude-agent-sdk": "0.3.296",
|
|
87
87
|
"@anthropic-ai/sdk": "0.127.0",
|
|
88
|
-
"@code-yeongyu/senpi-codemode": "2026.10.
|
|
89
|
-
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.
|
|
88
|
+
"@code-yeongyu/senpi-codemode": "2026.10.20-1",
|
|
89
|
+
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.20-1",
|
|
90
90
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
91
91
|
"@mozilla/readability": "0.6.0",
|
|
92
92
|
"linkedom": "0.18.13",
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{rateLimitModelFamily,usageLimitResetMs}from"./chunk-QZBH7PDO.js";import{signalSpawnedChild}from"./chunk-KLAX467B.js";import{normalizeProviderError}from"./chunk-TAV6QZFM.js";import{extract429RetryAfterMs}from"./chunk-PELNNWGO.js";import{getShellConfig}from"./chunk-EKDY4H2J.js";import{getOverflowPatterns}from"./chunk-2IK7QUJX.js";import{PROVIDER_NOT_CONFIGURED_PREFIX,USAGE_LIMIT_EXHAUSTION,isOAuthRefreshUnavailableError}from"./chunk-LAWS3FYQ.js";function rendezvousOrder(key,slots,hasher){return[...slots].map(slot=>({slot,score:hasher(`${key}\0${slot.name}`)})).sort((left,right)=>right.score>left.score?1:right.score<left.score?-1:0).map(({slot})=>slot)}function applyAccountOrder(order,ranked){if(order===void 0||order.length===0)return[...ranked];let byName=new Map(ranked.map(slot=>[slot.name,slot])),front=[];for(let name of order){let slot=byName.get(name);slot!==void 0&&!front.includes(slot)&&front.push(slot)}return[...front,...ranked.filter(slot=>!front.includes(slot))]}function withoutOrderedAccount(credential,name){if(credential.order===void 0||!credential.order.includes(name))return credential;let order=credential.order.filter(entry=>entry!==name),next={...credential};return order.length===0?delete next.order:next.order=order,next}var PRE_COMMIT_EVENT_TYPES=new Set(["start","text_start","thinking_start","toolcall_start"]);function isCommittedRotationOutput(event){return!PRE_COMMIT_EVENT_TYPES.has(event.type)}function isRotationStreamStart(event){return event.type==="start"}function rotationErrorFromEvent(event){if(event.type!=="error")return;let failure=new Error(event.error.errorMessage??"provider stream error"),status=event.error.providerDiagnostic?.httpStatus;return status===void 0?failure:Object.assign(failure,{status})}var DEFAULT_SLOT_BLOCK_MS=6e4,MAX_SLOT_BLOCK_MS=2880*60*1e3;function escapeRegExp(text){return text.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var ACCOUNT_USAGE_LIMIT_TEXT=new RegExp([...USAGE_LIMIT_EXHAUSTION.markers.map(escapeRegExp),String.raw`\b(?:hit|reached)\s+your\b[^.]*?\blimit\b`,String.raw`\busage\s+limit\s+reached\b`,String.raw`\b(?:Go|Free)UsageLimitError\b`,String.raw`\bblocking_limit\b`,String.raw`\brapid_refill_breaker\b`,String.raw`\bquota\s+exceeded\b`].join("|"),"i");function isAccountUsageLimitText(text){return ACCOUNT_USAGE_LIMIT_TEXT.test(text)}var COOLDOWN_BASE_MS=DEFAULT_SLOT_BLOCK_MS,COOLDOWN_CAP_MS=MAX_SLOT_BLOCK_MS,RETRY_SAME_MAX_ATTEMPTS=2;function rateLimitCooldown(failureCount,serverHintMs,policy={}){let cap=policy.capMs??COOLDOWN_CAP_MS,base=policy.baseMs??COOLDOWN_BASE_MS,backoff=Math.min(cap,base*2**Math.max(0,failureCount)),floored=serverHintMs===void 0?backoff:Math.max(backoff,serverHintMs);return{cooldownMs:Math.min(cap,floored),retryAfterWasCapped:floored>cap}}var INVALID_KEY_TEXT=/invalid[ _-]?(?:api[ _-]?)?key|authentication[_ ]?error|invalid x-api-key|unauthorized/i,ACCOUNT_SCOPED_403_TEXT=/account|credential|token|api[ _-]?key|organization|subscription/i,RATE_LIMIT_TEXT=/rate[ _-]?limit|too many requests|resource_exhausted/i,BILLING_TEXT=/billing|credits?[ _-]?(?:required|exhausted|balance)|insufficient[ _-]?(?:funds|quota|credit)|payment[ _-]?required|quota[ _-]?exhausted/i,OVERLOAD_TEXT=/overloaded/i,NETWORK_TEXT=/econnreset|econnrefused|etimedout|enotfound|socket hang up|fetch failed|network error|request timed out/i,WEBSOCKET_REQUEST_FAULT_TEXT=/websocket closed 100[89]\b/i,WEBSOCKET_TRANSPORT_FAULT_TEXT=/websocket (?:closed|error|connect timeout|liveness timeout)/i,FAIL_FAST_TEXT=/context[ _-]?(?:length|window)|maximum context|invalid[ _-]?model|model[ _-]?not[ _-]?found|malformed[ _-]?stream|premature[ _-]?(?:close|stream)/i,ABORT_TEXT=/\baborted?\b/i;function isAbort(error,message){return error instanceof Error&&error.name==="AbortError"?!0:ABORT_TEXT.test(message)}function isOverflowText(text){return getOverflowPatterns().some(pattern=>pattern.test(text))}function classifyCredentialFailure(error,context={}){if(isOAuthRefreshUnavailableError(error))return{kind:"retry_same",maxAttempts:RETRY_SAME_MAX_ATTEMPTS};let normalized=normalizeProviderError(error),text=normalized.messageCarriesBody?normalized.message:`${normalized.message} ${normalized.body??""}`,status=normalized.status,failureCount=context.failureCount??0;if(isAbort(error,text))return{kind:"fail_request"};if(text.includes(PROVIDER_NOT_CONFIGURED_PREFIX))return{kind:"failover",block:{reason:"auth_error"}};if(status===401||INVALID_KEY_TEXT.test(text))return{kind:"failover",block:{reason:"auth_error"}};if(status===403)return ACCOUNT_SCOPED_403_TEXT.test(text)?{kind:"failover",block:{reason:"auth_error"}}:{kind:"fail_request"};if(status===402||BILLING_TEXT.test(text))return{kind:"failover",block:{reason:"account_disabled"}};let usageLimit=isAccountUsageLimitText(text)&&!isOverflowText(text);if(status===429||RATE_LIMIT_TEXT.test(text)||usageLimit){let nowMs=context.nowMs??Date.now(),hint=extract429RetryAfterMs({status:status??429,bodyText:text},nowMs)??(usageLimit?normalized.retryAfterMs??usageLimitResetMs(text,nowMs):void 0),modelFamily=rateLimitModelFamily(text);return{kind:"failover",block:{reason:"rate_limit",...rateLimitCooldown(failureCount,hint,{baseMs:context.cooldownBaseMs,capMs:context.cooldownCapMs}),...modelFamily===void 0?{}:{modelFamily}}}}return isOverflowText(text)||status===400||status===404||FAIL_FAST_TEXT.test(text)?{kind:"fail_request"}:WEBSOCKET_REQUEST_FAULT_TEXT.test(text)?{kind:"fail_request"}:WEBSOCKET_TRANSPORT_FAULT_TEXT.test(text)?{kind:"retry_same",maxAttempts:RETRY_SAME_MAX_ATTEMPTS}:status===529||OVERLOAD_TEXT.test(text)||status!==void 0&&status>=500&&status<600?{kind:"retry_same",maxAttempts:RETRY_SAME_MAX_ATTEMPTS}:status===408||NETWORK_TEXT.test(text)?{kind:"retry_same",maxAttempts:RETRY_SAME_MAX_ATTEMPTS}:{kind:"fail_request"}}var CredentialFailoverError=class extends Error{constructor(action,original,options={}){let detail=original instanceof Error?original.message:String(original);super(detail,{cause:original}),this.name="CredentialFailoverError",this.action=action,this.original=original,this.retryAt=options.retryAt}};function isAvailable(slot,now){return!(slot.blockReason==="account_disabled"||slot.blockedUntil!==void 0&&slot.blockedUntil>now)}function soonestRetryAt(slots,now){let deadlines=slots.map(slot=>slot.blockedUntil).filter(value=>value!==void 0&&value>now);return deadlines.length===0?void 0:Math.min(...deadlines)}async function settle(stream){let iterator=stream[Symbol.asyncIterator]();try{await iterator.return?.(void 0)}catch{}}async function*runCredentialFailover(options){let now=options.now??Date.now,classify=options.classify??classifyCredentialFailure,attempted=new Set,retriesBySlot=new Map,lastError,lastOriginal,lastTerminalEvent,startAnnounced=!1;for(;;){let slots=await options.listSlots(),candidates=slots.filter(slot2=>!attempted.has(slot2.name)&&isAvailable(slot2,now()));if(candidates.length===0){if(lastTerminalEvent!==void 0){yield lastTerminalEvent;return}let retryAt=soonestRetryAt(slots,now());throw lastError!==void 0&&lastOriginal!==void 0?new CredentialFailoverError(lastError.action,lastOriginal,retryAt===void 0?{}:{retryAt}):new CredentialFailoverError({kind:"fail_request"},new Error("No credential slots available"),retryAt===void 0?{}:{retryAt})}let slot=options.select(candidates),committedOutput=!1,terminalEvent,attemptStream;try{attemptStream=await options.runAttempt(slot);for await(let event of attemptStream){let failure=options.errorFromEvent?.(event);if(failure!==void 0)throw terminalEvent=event,failure;if(options.isStreamStart?.(event)){if(startAnnounced)continue;startAnnounced=!0}committedOutput||=options.isCommittedOutput(event),yield event}await options.onSuccess?.(slot);return}catch(error){attemptStream&&await settle(attemptStream);let failureCount=(slot.failureCount??0)+(retriesBySlot.get(slot.name)??0),action=classify(error,{failureCount});if(lastOriginal=error,lastTerminalEvent=terminalEvent,action.kind==="retry_same"&&!committedOutput){let used=(retriesBySlot.get(slot.name)??0)+1;if(retriesBySlot.set(slot.name,used),used<action.maxAttempts)continue;if(terminalEvent!==void 0){yield terminalEvent;return}throw new CredentialFailoverError(action,error)}if(action.kind!=="failover"){if(terminalEvent!==void 0){yield terminalEvent;return}throw new CredentialFailoverError(action,error)}await options.persistBlock(slot,action.block),attempted.add(slot.name);let failure=new CredentialFailoverError(action,error);if(lastError=failure,await options.onRotate?.({slot,block:action.block,attempt:attempted.size,committedOutput}),committedOutput){if(terminalEvent!==void 0){yield terminalEvent;return}throw failure}}}}function sleep(ms,signal){return new Promise((resolve,reject)=>{if(signal?.aborted){reject(new Error("Aborted"));return}let timeout=setTimeout(resolve,ms);signal?.addEventListener("abort",()=>{clearTimeout(timeout),reject(new Error("Aborted"))})})}import{spawn}from"child_process";var COMMAND_EXECUTION_MAX_ATTEMPTS=3,COMMAND_EXECUTION_BACKOFF_MS=[250,1e3],COMMAND_TIMEOUT_MS=1e4,COMMAND_MAX_OUTPUT_BYTES=1024*1024;function runCommandProcess(file,args,options){return new Promise(resolveOutcome=>{let spawnOptions={shell:options.shell,windowsHide:!0,...options.env===void 0?{}:{env:{...process.env,...options.env}}},child=options.stdin===void 0?spawn(file,[...args],{...spawnOptions,stdio:["ignore","pipe","ignore"]}):spawn(file,[...args],{...spawnOptions,stdio:["pipe","pipe","ignore"]}),stdout="",settled=!1,finish=outcome=>{settled||(settled=!0,clearTimeout(timer),resolveOutcome(outcome))},timer=setTimeout(()=>{signalSpawnedChild(child,"SIGTERM"),child.unref(),finish({executed:!0,value:void 0})},COMMAND_TIMEOUT_MS);child.stdout.setEncoding("utf-8"),child.stdout.on("data",chunk=>{stdout+=chunk,!(stdout.length<=COMMAND_MAX_OUTPUT_BYTES)&&(signalSpawnedChild(child,"SIGTERM"),finish({executed:!0,value:void 0}))}),child.on("error",error=>finish({executed:error.code!=="ENOENT",value:void 0})),child.on("close",code=>{let value=stdout.trim();finish({executed:!0,value:code===0&&value?value:void 0})}),child.stdin?.end(options.stdin)})}async function executeWithConfiguredShell(command,env){try{let{shell,args,commandTransport}=getShellConfig(),commandFromStdin=commandTransport==="stdin";return await runCommandProcess(shell,commandFromStdin?args:[...args,command],{shell:!1,...commandFromStdin?{stdin:command}:{},...env===void 0?{}:{env}})}catch{return{executed:!1,value:void 0}}}async function executeCommandOnce(command,env){if(process.platform==="win32"){let configured=await executeWithConfiguredShell(command,env);if(configured.executed)return configured.value}return(await runCommandProcess(command,[],{shell:!0,...env===void 0?{}:{env}})).value}async function runConfigCommand(commandConfig,env){let command=commandConfig.slice(1);for(let attempt=0;attempt<COMMAND_EXECUTION_MAX_ATTEMPTS;attempt++){let value=await executeCommandOnce(command,env);if(value!==void 0)return value;let backoffMs=COMMAND_EXECUTION_BACKOFF_MS[attempt];backoffMs!==void 0&&await sleep(backoffMs)}}var commandResultCache=new Map,ENV_VAR_NAME_RE=/^[A-Za-z_][A-Za-z0-9_]*$/,ENV_VAR_NAME_PREFIX_RE=/^[A-Za-z_][A-Za-z0-9_]*/;function appendLiteral(parts,value){if(!value)return;let previousPart=parts[parts.length-1];if(previousPart?.type==="literal"){previousPart.value+=value;return}parts.push({type:"literal",value})}function parseConfigValueTemplate(config){let parts=[],index=0;for(;index<config.length;){let dollarIndex=config.indexOf("$",index);if(dollarIndex<0){appendLiteral(parts,config.slice(index));break}appendLiteral(parts,config.slice(index,dollarIndex));let nextChar=config[dollarIndex+1];if(nextChar==="$"||nextChar==="!"){appendLiteral(parts,nextChar),index=dollarIndex+2;continue}if(nextChar==="{"){let endIndex=config.indexOf("}",dollarIndex+2);if(endIndex<0){appendLiteral(parts,"$"),index=dollarIndex+1;continue}let name=config.slice(dollarIndex+2,endIndex);ENV_VAR_NAME_RE.test(name)?parts.push({type:"env",name}):appendLiteral(parts,config.slice(dollarIndex,endIndex+1)),index=endIndex+1;continue}let match=config.slice(dollarIndex+1).match(ENV_VAR_NAME_PREFIX_RE);if(match){parts.push({type:"env",name:match[0]}),index=dollarIndex+1+match[0].length;continue}appendLiteral(parts,"$"),index=dollarIndex+1}return parts}function parseConfigValueReference(config){return config.startsWith("!")?{type:"command",config}:{type:"template",parts:parseConfigValueTemplate(config)}}function resolveEnvConfigValue(name,env){return env?.[name]||process.env[name]||void 0}function getTemplateEnvVarNames(parts){let names=[];for(let part of parts)part.type!=="env"||names.includes(part.name)||names.push(part.name);return names}function resolveTemplate(parts,env){let resolved="";for(let part of parts){if(part.type==="literal"){resolved+=part.value;continue}let envValue=resolveEnvConfigValue(part.name,env);if(envValue===void 0)return;resolved+=envValue}return resolved}function getConfigValueEnvVarNames(config){let reference=parseConfigValueReference(config);return reference.type==="template"?getTemplateEnvVarNames(reference.parts):[]}function getMissingConfigValueEnvVarNames(config,env){return getConfigValueEnvVarNames(config).filter(name=>resolveEnvConfigValue(name,env)===void 0)}function isCommandConfigValue(config){return parseConfigValueReference(config).type==="command"}function isConfigValueConfigured(config,env){return getMissingConfigValueEnvVarNames(config,env).length===0}function resolveConfigValue(config,env){let reference=parseConfigValueReference(config);return reference.type==="command"?env===void 0?executeCommand(reference.config):runConfigCommand(reference.config,env):Promise.resolve(resolveTemplate(reference.parts,env))}function executeCommand(commandConfig){let cached=commandResultCache.get(commandConfig);if(cached)return cached;let result=runConfigCommand(commandConfig);return commandResultCache.set(commandConfig,result),result}function resolveConfigValueUncached(config,env){let reference=parseConfigValueReference(config);return reference.type==="command"?runConfigCommand(reference.config,env):Promise.resolve(resolveTemplate(reference.parts,env))}async function resolveConfigValueOrThrow(config,description,env){let resolvedValue=await resolveConfigValueUncached(config,env);if(resolvedValue!==void 0)return resolvedValue;let reference=parseConfigValueReference(config);if(reference.type==="command")throw new Error(`Failed to resolve ${description} from shell command: ${reference.config.slice(1)}`);if(reference.type==="template"){let missingEnvVars=getMissingConfigValueEnvVarNames(config,env);if(missingEnvVars.length===1)throw new Error(`Failed to resolve ${description} from environment variable: ${missingEnvVars[0]}`);if(missingEnvVars.length>1)throw new Error(`Failed to resolve ${description} from environment variables: ${missingEnvVars.join(", ")}`)}throw new Error(`Failed to resolve ${description}`)}async function resolveHeadersOrThrow(headers,description,env){if(!headers)return;let resolved={};for(let[key,value]of Object.entries(headers))resolved[key]=await resolveConfigValueOrThrow(value,`${description} header "${key}"`,env);return Object.keys(resolved).length>0?resolved:void 0}export{rendezvousOrder,applyAccountOrder,withoutOrderedAccount,sleep,getConfigValueEnvVarNames,isCommandConfigValue,isConfigValueConfigured,resolveConfigValue,resolveConfigValueOrThrow,resolveHeadersOrThrow,isCommittedRotationOutput,isRotationStreamStart,rotationErrorFromEvent,MAX_SLOT_BLOCK_MS,classifyCredentialFailure,CredentialFailoverError,runCredentialFailover};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{HOST_DAEMON_DIR_ENV,HOST_DAEMON_LAYOUT,ageOf,createGenerationDirectory,createHostDaemonPaths,generationPaths,hostDaemonDirectoryPaths,isRecord,parseJson,readFileOrUndefined,readJsonObject,sameEndpoint,writeJsonAtomic,writeStateFile}from"./chunk-KUZPBZUK.js";import{ProcessIdentityUnreadableError,parseDaemonPidFile,processIsLive,processMatchesPidFile,processStartTimeMs,readProcessIdentity,readProcessStartTime,sameProcessStartMs}from"./chunk-7ASLQ243.js";import{writeSync}from"node:fs";function writeStderrLine(text){try{writeSync(2,`${text}
|
|
3
|
-
`)}catch{}}function supervisorLog(message){writeStderrLine(`senpi rpc host supervisor: ${message}`)}var loggedUnknownIdentities=new Set;function logUnknownHostIdentity(record,pid){let key=`${record}:${pid}`;loggedUnknownIdentities.has(key)||(loggedUnknownIdentities.add(key),writeStderrLine(JSON.stringify({event:"legacy_host_identity_unknown",record,pid})))}function errorMessage(cause){return cause instanceof Error?cause.message:String(cause)}import{createHash}from"node:crypto";import{mkdir,readdir,readFile,rename,rm,writeFile}from"node:fs/promises";import{join}from"node:path";var SESSION_PATH_RETRY_AFTER_MS=2e3;function reservationFile(dir,sessionPath){return join(dir,`${createHash("sha256").update(sessionPath,"utf8").digest("hex").slice(0,16)}.json`)}function createEndpointReservations(host){let told=process.env[HOST_DAEMON_DIR_ENV],dir=told!==void 0&&told.trim()!==""?told:host.socket===void 0?void 0:createHostDaemonPaths({socket:host.socket,agentDir:host.agentDir}).dir;if(dir!==void 0)return createSessionPathReservations({daemonDir:dir,instanceId:host.instanceId,...host.publicSocket===void 0?{}:{publicSocket:host.publicSocket},...host.onFailure?{onFailure:host.onFailure}:{}})}function createSessionPathReservations(options){let paths=hostDaemonDirectoryPaths(options.daemonDir),dir=paths.reservationsDir,pid=options.pid??process.pid,startTime=readProcessStartTime(pid).then(value=>value??null,()=>null),held=new Set,updates=new Map,report=message=>options.onFailure?.(message),publish=async(sessionPath,attached,stillHeld)=>{let owner={instanceId:options.instanceId,...options.publicSocket===void 0?{}:{host_socket:options.publicSocket},pid,processStartTime:await startTime,sessionPath,attached},file=reservationFile(dir,sessionPath);await mkdir(dir,{recursive:!0,mode:448});let staging=`${file}.${pid}.tmp`;if(await writeFile(staging,`${JSON.stringify(owner)}
|
|
4
|
-
`,{mode:384}),stillHeld?.()===!1){await rm(staging,{force:!0});return}await rename(staging,file)};return{async holderPids(observedStarts){let candidates=(await readSessionPathClaims(dir)).map(({owner})=>owner),generations=await readdir(paths.generationsDir,{withFileTypes:!0}).catch(()=>[]);for(let generation of generations){if(!generation.isDirectory())continue;let record=parseJson(await readFileOrUndefined(join(paths.generationsDir,generation.name,"host-child.pid")));typeof record?.pid=="number"&&candidates.push({pid:record.pid,processStartTime:typeof record.processStartTime=="string"?record.processStartTime:null,instanceId:generation.name,sessionPath:""})}let byPid=new Map;for(let owner of candidates){if(owner.pid===process.pid)continue;let owners=byPid.get(owner.pid)??[];owners.push(owner),byPid.set(owner.pid,owners)}let live=await Promise.all([...byPid].map(async([pid2,owners])=>{if(!processIsLive(pid2))return;if(observedStarts){if(!observedStarts.has(pid2))return;let observed2=observedStarts.get(pid2);return owners.some(owner=>owner.processStartTime===null?!1:sameProcessStartMs(processStartTimeMs(owner.processStartTime),observed2))?pid2:void 0}let current=await readProcessStartTime(pid2,process.platform,1e3).catch(()=>{}),observed=current===void 0?void 0:processStartTimeMs(current);return observed!==void 0&&owners.some(owner=>owner.processStartTime!==null&&sameProcessStartMs(processStartTimeMs(owner.processStartTime),observed))?pid2:void 0}));return[...new Set(live.filter(value=>value!==void 0))]},async claim(sessionPath,attached=!0){let existing=await readOwner(reservationFile(dir,sessionPath));if(existing&&existing.pid!==pid){let standing=await standingOwner(existing,paths.pointerFile);if(standing)return standing}try{await publish(sessionPath,attached),held.add(sessionPath)}catch(cause){report(`session path reservation for ${sessionPath} could not be written (${errorMessage2(cause)})`)}},async release(sessionPath){held.delete(sessionPath)&&(await updates.get(sessionPath),updates.delete(sessionPath),await rm(reservationFile(dir,sessionPath),{force:!0}).catch(cause=>{report(`session path reservation for ${sessionPath} could not be removed (${errorMessage2(cause)})`)}))},setAttached(sessionPath,attached){if(!held.has(sessionPath))return;let update=(updates.get(sessionPath)??Promise.resolve()).then(()=>publish(sessionPath,attached,()=>held.has(sessionPath))).catch(cause=>{report(`session path reservation for ${sessionPath} could not be updated (${errorMessage2(cause)})`)});updates.set(sessionPath,update)}}}async function readSessionPathClaims(dir){let entries=await readdir(dir).catch(()=>[]),claims=[];for(let entry of entries){if(!entry.endsWith(".json"))continue;let file=join(dir,entry),owner=await readOwner(file);owner&&claims.push({file,owner})}return claims}async function standingOwner(owner,pointerFile){if(!await claimOwnerIsLive(owner))return;let current=await currentInstanceId(pointerFile)===owner.instanceId;return current||owner.attached!==!1?{...owner,current}:void 0}async function currentInstanceId(pointerFile){let pointer=parseJson(await readFileOrUndefined(pointerFile).catch(()=>{}));return typeof pointer?.instance_id=="string"?pointer.instance_id:void 0}async function readOwner(file){let parsed;try{parsed=JSON.parse(await readFile(file,"utf8"))}catch{return}if(typeof parsed!="object"||parsed===null)return;let{instanceId,pid,processStartTime,sessionPath,attached,host_socket}=parsed;if(!(typeof instanceId!="string"||typeof pid!="number"||typeof sessionPath!="string"))return{instanceId,pid,processStartTime:typeof processStartTime=="string"?processStartTime:null,sessionPath,...typeof host_socket=="string"?{host_socket}:{},...typeof attached=="boolean"?{attached}:{}}}async function claimOwnerIsLive(owner){if(!processIsLive(owner.pid))return!1;let recorded=owner.processStartTime===null?void 0:processStartTimeMs(owner.processStartTime);if(recorded===void 0)return logUnknownHostIdentity("session-path-claim",owner.pid),!0;let current=await readProcessStartTime(owner.pid).catch(()=>{}),observed=current===void 0?void 0:processStartTimeMs(current);return observed===void 0||sameProcessStartMs(recorded,observed)}function errorMessage2(cause){return cause instanceof Error?cause.message:String(cause)}var HOST_GENERATION_ENV="SENPI_RPC_HOST_GENERATION",HOST_INSTANCE_ID_ENV="SENPI_RPC_HOST_INSTANCE_ID";var HOST_COLD_START_ENV="SENPI_RPC_HOST_COLD_START",HOST_IDLE_EXIT_MS_ENV="SENPI_RPC_HOST_IDLE_EXIT_MS",DEFAULT_HOST_IDLE_EXIT_MS=9e5,HANDOFF_GRACE_MS_ENV="SENPI_RPC_HANDOFF_GRACE_MS",DEFAULT_HANDOFF_GRACE_MS=6e5;function parseColdStart(value){return value==="transient"||value==="persistent"?value:void 0}function parseIdleExitMs(value){if(value===void 0||!/^\d+$/.test(value.trim()))return;let parsed=Number(value.trim());return Number.isFinite(parsed)&&parsed>0?parsed:void 0}function resolveHostPolicy(settings,env){let record=isRecord2(settings)?settings:{},coldStart=parseColdStart(env[HOST_COLD_START_ENV])??parseColdStart(asOptionalString(record.coldStart))??"transient",idleExitMs=parseIdleExitMs(env[HOST_IDLE_EXIT_MS_ENV])??parseIdleExitMs(asOptionalString(record.idleExitMs))??9e5;return{coldStart,idleExitMs}}var IdleExitDecider=class{constructor(idleExitMs,now=Date.now){this.idleExitMs=idleExitMs,this.now=now}update(activity){return activity.connections>0||activity.activeTurns>0?(this.idleSince=void 0,"active"):this.idleExitMs===Number.POSITIVE_INFINITY?"idle":this.idleSince===void 0?(this.idleSince=this.now(),"idle"):this.now()-this.idleSince>=this.idleExitMs?"exit":"idle"}};function isRecord2(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function asOptionalString(value){return typeof value=="string"?value:typeof value=="number"?String(value):void 0}import{rename as rename2,rm as rm4}from"node:fs/promises";import{readdir as readdir3,rm as rm3}from"node:fs/promises";import{readdir as readdir2}from"node:fs/promises";import{readdirSync,readFileSync}from"node:fs";function supportedPlatform(platform){return platform==="darwin"||platform==="linux"?platform:void 0}async function loadProcessTableReader(platform=process.platform,options={}){let supported=supportedPlatform(platform);return supported===void 0||typeof globalThis.Bun>"u"?void 0:supported==="darwin"?await darwinReader(options):linuxReader}var KINFO_PROC_STRIDE=648,KINFO_PID_OFFSET=40,KINFO_PPID_OFFSET=560,KINFO_STAT_OFFSET=36,SZOMB=5,PROC_PIDTASKINFO=4,TASKINFO_RESIDENT_OFFSET=8,TABLE_START_BYTES=1<<20,TABLE_MAX_BYTES=32<<20;function parseKernelProcessTable(table,rowCount,residentKb){if(rowCount<=0||table.byteLength<rowCount*KINFO_PROC_STRIDE)return;let view=new DataView(table.buffer,table.byteOffset,table.byteLength),rows=[],selfSeen=!1;for(let index=0;index<rowCount;index++){let base=index*KINFO_PROC_STRIDE,pid=view.getUint32(base+KINFO_PID_OFFSET,!0);if(!Number.isInteger(pid)||pid<=0)continue;let ppid=view.getUint32(base+KINFO_PPID_OFFSET,!0),zombie=table[base+KINFO_STAT_OFFSET]===SZOMB;if(pid===process.pid){if(ppid!==process.ppid||zombie)return;selfSeen=!0}rows.push({pid,ppid,state:zombie?"Z":"U",rssKb:residentKb(pid)})}return selfSeen?rows:void 0}async function darwinReader(options){let{dlopen,FFIType,ptr}=await import("bun:ffi"),library=dlopen("libSystem.B.dylib",{sysctl:{args:[FFIType.ptr,FFIType.u32,FFIType.ptr,FFIType.ptr,FFIType.ptr,FFIType.i32],returns:FFIType.i32},proc_pidinfo:{args:[FFIType.i32,FFIType.i32,FFIType.u32,FFIType.ptr,FFIType.i32],returns:FFIType.i32}}),mib=new Int32Array([1,14,0]),none=new Uint8Array(1),requested=options.initialTableBytes,table=new Uint8Array(Number.isFinite(requested)&&requested!==void 0&&requested>0?Math.min(Math.floor(requested),TABLE_MAX_BYTES):TABLE_START_BYTES),length=new Uint32Array(1),taskInfo=new Uint8Array(256),taskView=new DataView(taskInfo.buffer),residentKb=pid=>(taskInfo.fill(0),library.symbols.proc_pidinfo(pid,PROC_PIDTASKINFO,0,ptr(taskInfo),taskInfo.byteLength)>0?Math.round(Number(taskView.getBigUint64(TASKINFO_RESIDENT_OFFSET,!0))/1024):0);return()=>{for(;;){if(length[0]=table.byteLength,library.symbols.sysctl(ptr(mib),mib.length,ptr(table),ptr(length),ptr(none),0)!==0){if(table.byteLength>=TABLE_MAX_BYTES)return;let reported=length[0]>table.byteLength?length[0]:0;table=new Uint8Array(Math.min(TABLE_MAX_BYTES,Math.max(table.byteLength*2,reported)));continue}return parseKernelProcessTable(table,Math.floor(length[0]/KINFO_PROC_STRIDE),residentKb)}}}function linuxReader(){let rows=[];for(let entry of readdirSync("/proc")){let pid=Number(entry);if(!Number.isInteger(pid)||pid<=0)continue;let stat2;try{stat2=readFileSync(`/proc/${pid}/stat`,"utf8")}catch{continue}let tail=stat2.slice(stat2.lastIndexOf(")")+2).split(" "),state=tail[0],ppid=Number(tail[1]);if(state===void 0||!Number.isInteger(ppid))continue;let rssPages=Number(tail[21]);rows.push({pid,ppid,state,rssKb:Number.isFinite(rssPages)?Math.round(rssPages*4):0})}return rows}var UNOBSERVED={rss_mb:null,host_rss_mb:null,host_footprint_mb:null,open_fds:null,zombies:null},readers=new Map,footprintReader;function loadFootprintReader(){return footprintReader??=import("./process-footprint-SOBYQLMN.js").then(module=>module.readProcessFootprint),footprintReader}function tableReader(platform){let cached=readers.get(platform);if(cached!==void 0)return cached;let loading=loadProcessTableReader(platform);return readers.set(platform,loading),loading}async function readHostProcessMetrics(pid,platform=process.platform){if(platform==="win32")return UNOBSERVED;let read=await tableReader(platform);if(read===void 0)return UNOBSERVED;let rows=read();if(rows===void 0)return UNOBSERVED;let tree=descendants(rows,pid);if(tree.length===0)return UNOBSERVED;let hostRows=tree.filter(row=>row.pid===pid||row.ppid===pid);return{rss_mb:residentMegabytes(tree),host_rss_mb:residentMegabytes(hostRows),host_footprint_mb:footprintMegabytes(hostRows,await loadFootprintReader()),open_fds:platform==="linux"?await openDescriptors(tree):null,zombies:tree.filter(row=>row.state.startsWith("Z")).length}}function residentMegabytes(rows){return Math.round(rows.reduce((total,row)=>total+row.rssKb,0)/KILOBYTES_PER_MEGABYTE)}function footprintMegabytes(rows,readProcessFootprint){let bytes=0;for(let row of rows){let reading=readProcessFootprint(row.pid);if(reading===void 0)return null;bytes+=reading.bytes}return Math.round(bytes/BYTES_PER_MEGABYTE)}var BYTES_PER_MEGABYTE=1024*1024;function descendants(rows,root){let tree=rows.filter(row=>row.pid===root);if(tree.length===0)return tree;for(let index=0;index<tree.length;index++){let parent=tree[index].pid;for(let row of rows)row.ppid===parent&&!tree.includes(row)&&tree.push(row)}return tree}var KILOBYTES_PER_MEGABYTE=1024;async function openDescriptors(tree){let total=0,counted=!1;for(let row of tree){let entries=await readdir2(`/proc/${row.pid}/fd`).catch(()=>{});entries!==void 0&&(counted=!0,total+=entries.length)}return counted?total:null}import{rm as rm2}from"node:fs/promises";import{join as join2}from"node:path";var STALL_REFUSAL_WINDOW_MS_ENV="SENPI_RPC_STALL_REFUSAL_MS",DEFAULT_STALL_REFUSAL_WINDOW_MS=12e4,STOP_STALL_EVIDENCE_MAX_AGE_MS=6e4,CHILD_STALLED_STOP_MAX_MS_ENV="SENPI_RPC_CHILD_STALLED_STOP_MAX_MS",DEFAULT_CHILD_STALLED_STOP_MAX_MS=6e4;function childStalledStopMaxMs(env=process.env){return parseIdleExitMs(env[CHILD_STALLED_STOP_MAX_MS_ENV])??DEFAULT_CHILD_STALLED_STOP_MAX_MS}function ownGenerationDir(env){let daemonDir=env[HOST_DAEMON_DIR_ENV],instanceId=env[HOST_INSTANCE_ID_ENV];if(!(!daemonDir||!instanceId))return join2(daemonDir,"generations",instanceId)}function stallRefusalWindowMs(env=process.env){return parseIdleExitMs(env[STALL_REFUSAL_WINDOW_MS_ENV])??DEFAULT_STALL_REFUSAL_WINDOW_MS}function writeStallEvidence(generationDir,evidence){return writeJsonAtomic(join2(generationDir,"host-stalled.json"),evidence)}function writeHeartbeat(generationDir,at){return writeJsonAtomic(join2(generationDir,"host-alive.json"),{at})}async function readStallEvidence(generation){let raw=await readJsonObject(generation.stalledFile);if(!(raw===void 0||typeof raw.at!="string"||typeof raw.driftMs!="number"))return{at:raw.at,driftMs:raw.driftMs,processCpuMs:typeof raw.processCpuMs=="number"?raw.processCpuMs:0,heapDeltaMb:typeof raw.heapDeltaMb=="number"?raw.heapDeltaMb:0,...typeof raw.sessionId=="string"?{sessionId:raw.sessionId}:{},...typeof raw.tool=="string"?{tool:raw.tool}:{}}}async function recentStall(generation,now,maxAgeMs){let evidence=await readStallEvidence(generation),age=ageOf(evidence?.at,now);return evidence!==void 0&&age!==void 0&&age<=maxAgeMs?evidence:void 0}async function hostLoopStalled(generation,options){if(await recentStall(generation,options.now,options.windowMs)!==void 0)return!0;let heartbeatAge=ageOf((await readJsonObject(generation.aliveFile))?.at,options.now);return heartbeatAge!==void 0&&heartbeatAge>options.errorMs&&heartbeatAge<options.windowMs}async function hostChildAlive(generation){let record=await readJsonObject(generation.childPidFile),pid=record?.pid;if(typeof pid!="number"||!Number.isInteger(pid)||pid<=0)return;if(!processIsLive(pid))return!1;let observed=await readProcessIdentity(pid,process.platform,1e3,processIsLive,"UTC");if(!processIsLive(pid))return!1;let recordedMs=typeof record?.processStartTime=="string"?processStartTimeMs(record.processStartTime):void 0,observedMs=observed.kind==="present"?processStartTimeMs(observed.identity):void 0;return recordedMs!==void 0&&observedMs!==void 0?sameProcessStartMs(recordedMs,observedMs):(supervisorLog(`host child pid ${pid} start time unreadable; preserving generation`),!0)}function writeStopProgress(generation,progress){return writeJsonAtomic(generation.stopProgressFile,progress)}async function clearStopProgress(generation){await rm2(generation.stopProgressFile,{force:!0}).catch(()=>{})}async function activeStopProgress(generation,now,freshMs){let raw=await readJsonObject(generation.stopProgressFile),age=ageOf(raw?.at,now),untilAt=typeof raw?.untilAt=="string"?Date.parse(raw.untilAt):Number.NaN;if(!(age===void 0||age>freshMs||Number.isNaN(untilAt)||untilAt<=now))return untilAt}async function pruneDeadGenerations(paths){let pointer=parseJson(await readFileOrUndefined(paths.pointerFile).catch(()=>{})),pointedAt=typeof pointer?.instance_id=="string"?pointer.instance_id:void 0,generations=[];for(let instanceId of await readdir3(paths.generationsDir).catch(()=>[])){let record=parseJson(await readFileOrUndefined(generationPaths(paths,instanceId).pidFile));typeof record?.pid!="number"||processIsLive(record.pid)||await hostChildAlive(generationPaths(paths,instanceId))||(await rm3(generationPaths(paths,instanceId).dir,{recursive:!0,force:!0}).catch(()=>{}),generations.push(instanceId))}let pointerRemoved=pointedAt!==void 0&&generations.includes(pointedAt);pointerRemoved&&await rm3(paths.pointerFile,{force:!0}).catch(()=>{});let claims=[];for(let claim of await readSessionPathClaims(paths.reservationsDir))await claimOwnerIsLive(claim.owner)||(await rm3(claim.file,{force:!0}).catch(()=>{}),claims.push(claim.owner.sessionPath));return{generations,claims,pointer:pointerRemoved}}function memoryOf(metrics){return{rss_mb:metrics.rss_mb,host_rss_mb:metrics.host_rss_mb}}async function readGenerationRows(paths,options={}){let pointer=parseJson(await readFileOrUndefined(paths.pointerFile).catch(()=>{})),currentId=typeof pointer?.instance_id=="string"?pointer.instance_id:void 0,claims=await readSessionPathClaims(paths.reservationsDir),rows=[];for(let instanceId of await readdir3(paths.generationsDir).catch(()=>[])){let record=parseJson(await readFileOrUndefined(generationPaths(paths,instanceId).pidFile));if(typeof record?.pid!="number")continue;let alive=processIsLive(record.pid)||await hostChildAlive(generationPaths(paths,instanceId))===!0;!alive&&options.includeDead!==!0||rows.push({instanceId,generation:typeof record.generation=="number"?record.generation:0,pid:record.pid,engineVersion:typeof record.engineVersion=="string"?record.engineVersion:null,...alive?memoryOf(await readHostProcessMetrics(record.pid)):{rss_mb:null,host_rss_mb:null},sessions:claims.filter(claim=>claim.owner.instanceId===instanceId).length,current:instanceId===currentId,alive})}return rows.sort((left,right)=>left.generation-right.generation)}async function readHostRegistration(paths){let pointer=parseJson(await readFileOrUndefined(paths.pointerFile));if(!(!pointer||typeof pointer.instance_id!="string"))return readGenerationRegistration(paths,pointer.instance_id)}async function readGenerationRegistration(paths,instanceId){let text=await readFileOrUndefined(generationPaths(paths,instanceId).pidFile),record=text===void 0?void 0:parseDaemonPidFile(text);if(record===void 0)return;let parsed=parseJson(text)??{},writer=parseWriter(parsed);return{record,...writer&&{writer},...typeof parsed.socket=="string"&&{socket:parsed.socket},instanceId,generation:typeof parsed.generation=="number"?parsed.generation:0}}async function writeHostRegistration(paths,registration,options={}){options.fresh!==!0&&await pruneDeadGenerations(paths);let{generation,writer}=await writeGenerationRecord(paths,registration);await writeStateFile(`${paths.pointerFile}.${process.pid}.tmp`,{layout:HOST_DAEMON_LAYOUT,instance_id:registration.instanceId,generation_dir:generation.relativeDir,writer}),await rename2(`${paths.pointerFile}.${process.pid}.tmp`,paths.pointerFile)}async function writeGenerationRecord(paths,registration){let generation=generationPaths(paths,registration.instanceId),writer={pid:process.pid,startTime:await thisProcessStartTime()},{build}=registration;return await createGenerationDirectory(generation),await writeStateFile(generation.pidFile,{...registration.record,instance_id:registration.instanceId,generation:registration.generation,...build&&{engineVersion:build.text,engineOrdinal:build.ordinal},launchProfileId:registration.launchProfileId,socket:registration.socket,writer}),{generation,writer}}async function clearHostRegistration(paths){let pointer=parseJson(await readFileOrUndefined(paths.pointerFile));if(typeof pointer?.instance_id=="string"){if(await hostChildAlive(generationPaths(paths,pointer.instance_id)))throw new Error(`refusing to clear generation ${pointer.instance_id} with a live host child`);await rm4(generationPaths(paths,pointer.instance_id).dir,{recursive:!0,force:!0})}await rm4(paths.settingsFile,{force:!0}),await rm4(paths.pointerFile,{force:!0})}async function releaseGeneration(paths,owner){let generation=generationPaths(paths,owner.instanceId);if(await hostChildAlive(generation))return;let record=parseDaemonPidFile(await readFileOrUndefined(generation.pidFile)??"");if(record!==void 0&&record.pid!==owner.pid)return;if(owner.superseded===!0){await rm4(generation.dir,{recursive:!0,force:!0});return}let ownsPointer=parseJson(await readFileOrUndefined(paths.pointerFile))?.instance_id===owner.instanceId;ownsPointer&&await rm4(paths.settingsFile,{force:!0}),await rm4(generation.dir,{recursive:!0,force:!0}),ownsPointer&&await rm4(paths.pointerFile,{force:!0})}async function provenOwner(registered,socket){let record=registered?.record;if(!record||registered?.socket!==void 0&&!sameEndpoint(registered.socket,socket))return;if(record.processStartTime===null){processIsLive(record.pid)&&logUnknownHostIdentity("host.pid",record.pid);return}let identity={pid:record.pid,processStartTime:record.processStartTime};return await processMatchesPidFile(identity,readProcessStartTime).catch(error=>(error instanceof ProcessIdentityUnreadableError&&logUnknownHostIdentity("host.pid",identity.pid),!1))?{...identity,instanceId:registered.instanceId}:void 0}async function writtenByThisProcess(writer,readStartTime){if(writer===void 0||writer.pid!==process.pid||writer.startTime===null)return!1;let startTime=readStartTime?await readStartTime(process.pid).then(value=>value??null,()=>null):await thisProcessStartTime();return sameProcessStartMs(processStartTimeMs(writer.startTime),startTime===null?void 0:processStartTimeMs(startTime))}var selfStartTime;function thisProcessStartTime(){return selfStartTime??=readProcessStartTime(process.pid).then(value=>value??null,()=>null),selfStartTime}function isGenerationId(instanceId){return/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(instanceId)&&!instanceId.includes("..")}function parseWriter(parsed){if(!isRecord(parsed.writer)||typeof parsed.writer.pid!="number")return;let{pid,startTime}=parsed.writer;return{pid,startTime:typeof startTime=="string"?startTime:null}}import{randomUUID}from"node:crypto";import{watch}from"node:fs";import{readFile as readFile2,rm as rm5}from"node:fs/promises";import{basename,dirname}from"node:path";var STOP_INTENT_MAX_AGE_MS=12e4,CHILD_STOP_TIMEOUT_MS=5e3,CHILD_KILL_EXIT_TIMEOUT_MS=3e4,SENDER_KINDS=new Set(["ensure","supervisor","stop","successor","handoff"]);async function writeStopIntent(generation,intent,log=message=>{process.stderr.write(`${message}
|
|
5
|
-
`)}){try{await writeJsonAtomic(generation.stopIntentFile,intent)}catch(cause){log(`could not record the stop intent for ${generation.dir}: ${cause instanceof Error?cause.message:cause}`)}}async function readStopIntent(generation,options={}){let raw=await readJsonObject(generation.stopIntentFile);if(raw===void 0)return;let intent=parseStopIntent(raw),age=ageOf(raw.at,options.now??Date.now());if(intent!==void 0&&age!==void 0&&age<=(options.maxAgeMs??STOP_INTENT_MAX_AGE_MS))return intent;await clearStopIntent(generation)}async function consumeStopIntent(generation,now=Date.now()){let intent=await readStopIntent(generation,{now});return intent!==void 0&&await clearStopIntent(generation),intent}async function clearStopIntent(generation){await rm5(generation.stopIntentFile,{force:!0}).catch(()=>{})}function layeredSupervisorIntent(outer,supervisor,stop){return outer===void 0?{sender:supervisor,signal:"SIGTERM",...stop}:{sender:outer.sender,chain:[...outer.chain??[outer.sender],supervisor],targetPid:stop.targetPid,reason:`${outer.reason} -> ${stop.reason}`,signal:"SIGTERM",at:stop.at}}function parseStopIntent(raw){let sender=parseSender(raw.sender),chain=Array.isArray(raw.chain)?raw.chain.map(parseSender):void 0;if(!(sender===void 0||chain?.some(entry=>entry===void 0))&&!(typeof raw.reason!="string"||typeof raw.at!="string"))return{sender,...chain?{chain:chain.filter(entry=>entry!==void 0)}:{},targetPid:typeof raw.targetPid=="number"?raw.targetPid:0,reason:raw.reason,signal:typeof raw.signal=="string"?raw.signal:"SIGTERM",at:raw.at}}function parseSender(value){if(typeof value!="object"||value===null)return;let pid=Reflect.get(value,"pid"),kind=Reflect.get(value,"kind"),generation=Reflect.get(value,"generation");if(!(typeof pid!="number"||typeof kind!="string"||!isSenderKind(kind)))return{pid,kind,...typeof generation=="string"?{generation}:{}}}function isSenderKind(kind){return SENDER_KINDS.has(kind)}var DRAIN_REQUEST_POLL_MS=1e3,DRAIN_REQUEST_WATCHED_POLL_MS=5e3;async function writeDrainRequest(file,request){let record={...request,at:new Date().toISOString(),nonce:randomUUID()};await writeJsonAtomic(file,record)}function watchDrainRequest(file,onRequest,options={}){let stopped=!1,timer,watcher,polling=!1,lastNonce,poll=()=>{stopped||polling||(polling=!0,readDrainRequest(file).then(request=>{stopped||request===void 0||request.nonce===lastNonce||(lastNonce=request.nonce,onRequest(request))},ignore).finally(()=>{polling=!1}))},schedule=intervalMs=>{timer!==void 0&&clearInterval(timer),timer=setInterval(poll,options.pollMs??intervalMs),timer.unref?.()},name=basename(file);try{watcher=watch(dirname(file),{persistent:!1},(_event,changed)=>{(changed===null||changed===name)&&poll()}),watcher.on("error",()=>{watcher?.close(),watcher=void 0,stopped||schedule(DRAIN_REQUEST_POLL_MS)})}catch{watcher=void 0}return schedule(watcher===void 0?DRAIN_REQUEST_POLL_MS:DRAIN_REQUEST_WATCHED_POLL_MS),poll(),()=>{stopped=!0,timer!==void 0&&clearInterval(timer),watcher?.close()}}async function readDrainRequest(file){let raw=await readJsonObject(file);if(raw===void 0)return;let sender=parseSender(raw.sender);if(sender!==void 0&&!(typeof raw.reason!="string"||typeof raw.at!="string"||typeof raw.nonce!="string"))return{sender,reason:raw.reason,at:raw.at,nonce:raw.nonce}}async function writeDrainWatch(file,watch2){await writeJsonAtomic(file,{...watch2,at:new Date().toISOString()})}async function readDrainWatch(file){let raw=await readFileOrUndefined2(file);if(raw!==void 0)try{let parsed=JSON.parse(raw),pid=typeof parsed=="object"&&parsed!==null?Reflect.get(parsed,"pid"):void 0;return typeof pid=="number"&&Number.isInteger(pid)&&pid>0?{pid}:void 0}catch{return}}function hostChildDrainRequestFile(env=process.env){let daemonDir=env[HOST_DAEMON_DIR_ENV],instanceId=env[HOST_INSTANCE_ID_ENV];if(!(!daemonDir||!instanceId))return generationPaths(hostDaemonDirectoryPaths(daemonDir),instanceId).childDrainRequestFile}async function readFileOrUndefined2(file){try{return await readFile2(file,"utf8")}catch{return}}function ignore(){}var OBSERVE_REQUEST_FIELD="observe",ORIGIN_HOST_INSTANCE_FIELD="origin_host_instance",OBSERVING_COMMANDS=new Set(["get_protocol_info","list_sessions","subscribe_sessions","unsubscribe_sessions","get_effect_status","get_pending_inputs","get_subscription_limits"]);function isObservingRequest(line){let request;try{request=JSON.parse(line)}catch{return!1}if(typeof request!="object"||request===null||Array.isArray(request))return!1;let record=request;return record.type==="warm"?!0:record[OBSERVE_REQUEST_FIELD]===!0&&OBSERVING_COMMANDS.has(record.type)}function originHostInstance(env=process.env){let instance=env[HOST_INSTANCE_ID_ENV]?.trim();return instance||void 0}function requestOriginHostInstance(line){let request;try{request=JSON.parse(line)}catch{return}if(typeof request!="object"||request===null||Array.isArray(request))return;let origin=Reflect.get(request,ORIGIN_HOST_INSTANCE_FIELD);return typeof origin=="string"&&origin.length>0?origin:void 0}import{stat}from"node:fs/promises";var LegacyLockArtifactError=class extends Error{constructor(lockPath){super(`Legacy lock directory is present at ${lockPath}`),this.code="ELEGACY_LOCK_ARTIFACT",this.name="LegacyLockArtifactError",this.lockPath=lockPath}},DEFAULT_RETRIES={retries:100,minTimeout:20,maxTimeout:100};async function openLockDatabase(lockPath){if(typeof globalThis.Bun<"u"){let{Database}=await import("bun:sqlite"),database2=new Database(lockPath,{create:!0});return{exec:sql=>database2.exec(sql),close:()=>database2.close(!1)}}let{DatabaseSync}=await import("node:sqlite"),database=new DatabaseSync(lockPath);return{exec:sql=>database.exec(sql),close:()=>database.close()}}async function acquireOwnershipSafeLock(lockPath,options={}){let retries=options.retries??DEFAULT_RETRIES,deadline=Date.now()+retries.retries*retries.maxTimeout;for(;;){await rejectLegacyDirectory(lockPath);let busyMs=Math.max(1,Math.min(retries.maxTimeout,deadline-Date.now())),database;try{database=await openLockDatabase(lockPath),database.exec(`PRAGMA busy_timeout = ${busyMs}; BEGIN EXCLUSIVE;`);let released=!1,held=database;return async()=>{if(!released){released=!0;try{held.exec("COMMIT;")}finally{held.close()}}}}catch(error){try{database?.close()}catch{}if(await rejectLegacyDirectory(lockPath),isBusy(error)&&Date.now()<deadline){await new Promise(resolve=>setTimeout(resolve,Math.min(retries.minTimeout,Math.max(1,deadline-Date.now()))));continue}throw error}}}async function rejectLegacyDirectory(lockPath){try{if((await stat(lockPath)).isDirectory())throw new LegacyLockArtifactError(lockPath)}catch(error){if(error instanceof LegacyLockArtifactError||error.code!=="ENOENT")throw error}}function isBusy(error){let message=error instanceof Error?error.message:String(error);return/busy|locked/i.test(message)||error?.code==="SQLITE_BUSY"}export{readHostProcessMetrics,writeStderrLine,supervisorLog,logUnknownHostIdentity,errorMessage,SESSION_PATH_RETRY_AFTER_MS,createEndpointReservations,readSessionPathClaims,claimOwnerIsLive,HOST_GENERATION_ENV,HOST_INSTANCE_ID_ENV,HOST_COLD_START_ENV,HOST_IDLE_EXIT_MS_ENV,DEFAULT_HOST_IDLE_EXIT_MS,HANDOFF_GRACE_MS_ENV,DEFAULT_HANDOFF_GRACE_MS,parseColdStart,parseIdleExitMs,resolveHostPolicy,IdleExitDecider,STOP_STALL_EVIDENCE_MAX_AGE_MS,CHILD_STALLED_STOP_MAX_MS_ENV,childStalledStopMaxMs,ownGenerationDir,stallRefusalWindowMs,writeStallEvidence,writeHeartbeat,recentStall,hostLoopStalled,hostChildAlive,writeStopProgress,clearStopProgress,activeStopProgress,pruneDeadGenerations,readGenerationRows,readHostRegistration,readGenerationRegistration,writeHostRegistration,writeGenerationRecord,clearHostRegistration,releaseGeneration,provenOwner,writtenByThisProcess,thisProcessStartTime,isGenerationId,OBSERVE_REQUEST_FIELD,ORIGIN_HOST_INSTANCE_FIELD,isObservingRequest,originHostInstance,requestOriginHostInstance,acquireOwnershipSafeLock,CHILD_STOP_TIMEOUT_MS,CHILD_KILL_EXIT_TIMEOUT_MS,writeStopIntent,readStopIntent,consumeStopIntent,layeredSupervisorIntent,writeDrainRequest,watchDrainRequest,writeDrainWatch,readDrainWatch,hostChildDrainRequestFile};
|