@code-yeongyu/senpi 2026.9.4-2 → 2026.9.4-3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/core/auth-storage.d.ts.map +1 -1
  3. package/dist/core/auth-storage.js +31 -41
  4. package/dist/core/auth-storage.js.map +1 -1
  5. package/dist/core/credential-pool/state-store.d.ts +1 -0
  6. package/dist/core/credential-pool/state-store.d.ts.map +1 -1
  7. package/dist/core/credential-pool/state-store.js +22 -2
  8. package/dist/core/credential-pool/state-store.js.map +1 -1
  9. package/dist/core/extensions/builtin/goal/store.d.ts.map +1 -1
  10. package/dist/core/extensions/builtin/goal/store.js +8 -20
  11. package/dist/core/extensions/builtin/goal/store.js.map +1 -1
  12. package/dist/core/extensions/builtin/hooks/trust-storage.d.ts.map +1 -1
  13. package/dist/core/extensions/builtin/hooks/trust-storage.js +3 -1
  14. package/dist/core/extensions/builtin/hooks/trust-storage.js.map +1 -1
  15. package/dist/core/extensions/builtin/imagegen/auth.d.ts.map +1 -1
  16. package/dist/core/extensions/builtin/imagegen/auth.js +10 -2
  17. package/dist/core/extensions/builtin/imagegen/auth.js.map +1 -1
  18. package/dist/core/extensions/builtin/loop/store.d.ts +8 -6
  19. package/dist/core/extensions/builtin/loop/store.d.ts.map +1 -1
  20. package/dist/core/extensions/builtin/loop/store.js +54 -88
  21. package/dist/core/extensions/builtin/loop/store.js.map +1 -1
  22. package/dist/core/extensions/builtin/monitor-state-event.d.ts +2 -0
  23. package/dist/core/extensions/builtin/monitor-state-event.d.ts.map +1 -1
  24. package/dist/core/extensions/builtin/monitor-state-event.js.map +1 -1
  25. package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
  26. package/dist/core/extensions/builtin/terminal/extension.js +181 -3
  27. package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
  28. package/dist/core/extensions/builtin/terminal/manager.d.ts +10 -0
  29. package/dist/core/extensions/builtin/terminal/manager.d.ts.map +1 -1
  30. package/dist/core/extensions/builtin/terminal/manager.js +21 -0
  31. package/dist/core/extensions/builtin/terminal/manager.js.map +1 -1
  32. package/dist/core/extensions/builtin/terminal/manifest-lease.d.ts +24 -0
  33. package/dist/core/extensions/builtin/terminal/manifest-lease.d.ts.map +1 -0
  34. package/dist/core/extensions/builtin/terminal/manifest-lease.js +105 -0
  35. package/dist/core/extensions/builtin/terminal/manifest-lease.js.map +1 -0
  36. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +13 -2
  37. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  38. package/dist/core/extensions/builtin/terminal/monitor-registry.js +24 -2
  39. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  40. package/dist/core/extensions/builtin/terminal/restore.d.ts +43 -0
  41. package/dist/core/extensions/builtin/terminal/restore.d.ts.map +1 -0
  42. package/dist/core/extensions/builtin/terminal/restore.js +149 -0
  43. package/dist/core/extensions/builtin/terminal/restore.js.map +1 -0
  44. package/dist/core/extensions/builtin/terminal/terminal-manifest.d.ts +110 -0
  45. package/dist/core/extensions/builtin/terminal/terminal-manifest.d.ts.map +1 -0
  46. package/dist/core/extensions/builtin/terminal/terminal-manifest.js +158 -0
  47. package/dist/core/extensions/builtin/terminal/terminal-manifest.js.map +1 -0
  48. package/dist/core/extensions/builtin/terminal/tools/bash-input.d.ts.map +1 -1
  49. package/dist/core/extensions/builtin/terminal/tools/bash-input.js +3 -2
  50. package/dist/core/extensions/builtin/terminal/tools/bash-input.js.map +1 -1
  51. package/dist/core/extensions/builtin/terminal/tools/bash-output.d.ts.map +1 -1
  52. package/dist/core/extensions/builtin/terminal/tools/bash-output.js +5 -4
  53. package/dist/core/extensions/builtin/terminal/tools/bash-output.js.map +1 -1
  54. package/dist/core/extensions/builtin/terminal/tools/bash-resize.d.ts.map +1 -1
  55. package/dist/core/extensions/builtin/terminal/tools/bash-resize.js +3 -2
  56. package/dist/core/extensions/builtin/terminal/tools/bash-resize.js.map +1 -1
  57. package/dist/core/extensions/builtin/terminal/tools/context.d.ts +8 -0
  58. package/dist/core/extensions/builtin/terminal/tools/context.d.ts.map +1 -1
  59. package/dist/core/extensions/builtin/terminal/tools/context.js +10 -0
  60. package/dist/core/extensions/builtin/terminal/tools/context.js.map +1 -1
  61. package/dist/core/extensions/builtin/terminal/tools/kill-bash.d.ts.map +1 -1
  62. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js +5 -4
  63. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js.map +1 -1
  64. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +4 -1
  65. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
  66. package/dist/core/extensions/builtin/terminal/tools/monitor.js +58 -14
  67. package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
  68. package/dist/core/extensions/builtin/tool-search/index.d.ts.map +1 -1
  69. package/dist/core/extensions/builtin/tool-search/index.js +1 -1
  70. package/dist/core/extensions/builtin/tool-search/index.js.map +1 -1
  71. package/dist/core/extensions/builtin/tool-search/native-search.d.ts +9 -4
  72. package/dist/core/extensions/builtin/tool-search/native-search.d.ts.map +1 -1
  73. package/dist/core/extensions/builtin/tool-search/native-search.js +11 -6
  74. package/dist/core/extensions/builtin/tool-search/native-search.js.map +1 -1
  75. package/dist/core/extensions/builtin/tool-search/native-support.d.ts +13 -0
  76. package/dist/core/extensions/builtin/tool-search/native-support.d.ts.map +1 -0
  77. package/dist/core/extensions/builtin/tool-search/native-support.js +45 -0
  78. package/dist/core/extensions/builtin/tool-search/native-support.js.map +1 -0
  79. package/dist/core/lockfile-policy.d.ts +11 -0
  80. package/dist/core/lockfile-policy.d.ts.map +1 -1
  81. package/dist/core/lockfile-policy.js +19 -0
  82. package/dist/core/lockfile-policy.js.map +1 -1
  83. package/dist/core/session-sidecar-store.d.ts +62 -0
  84. package/dist/core/session-sidecar-store.d.ts.map +1 -0
  85. package/dist/core/session-sidecar-store.js +149 -0
  86. package/dist/core/session-sidecar-store.js.map +1 -0
  87. package/dist/core/settings-manager.d.ts.map +1 -1
  88. package/dist/core/settings-manager.js +11 -15
  89. package/dist/core/settings-manager.js.map +1 -1
  90. package/dist/modes/app-server/daemon/process.d.ts +33 -3
  91. package/dist/modes/app-server/daemon/process.d.ts.map +1 -1
  92. package/dist/modes/app-server/daemon/process.js +57 -6
  93. package/dist/modes/app-server/daemon/process.js.map +1 -1
  94. package/dist/modes/app-server/daemon.js +9 -1
  95. package/dist/modes/app-server/daemon.js.map +1 -1
  96. package/dist/modes/rpc/host-ensure.d.ts +2 -0
  97. package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
  98. package/dist/modes/rpc/host-ensure.js +77 -18
  99. package/dist/modes/rpc/host-ensure.js.map +1 -1
  100. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +14 -0
  101. package/node_modules/@code-yeongyu/senpi-codemode/package.json +5 -5
  102. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +1 -1
  103. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  104. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +4 -4
  105. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  106. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  107. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  108. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  109. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +3 -0
  110. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  111. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  112. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  113. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  114. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  115. package/package.json +7 -7
@@ -12,11 +12,41 @@ export type ProcessIdentityResult = {
12
12
  readonly kind: "error";
13
13
  readonly error: unknown;
14
14
  };
15
- export declare function processMatchesPidFile(pidFile: DaemonPidFile, readStartTime?: (pid: number) => Promise<string | undefined>): Promise<boolean>;
16
- export declare function readProcessIdentity(pid: number, platform?: NodeJS.Platform, timeoutMs?: number): Promise<ProcessIdentityResult>;
15
+ /** Thrown when a live process refuses to yield an identity after the retry budget. */
16
+ export declare class ProcessIdentityUnreadableError extends Error {
17
+ readonly pid: number;
18
+ readonly attempts: number;
19
+ readonly cause: unknown;
20
+ constructor(pid: number, attempts: number, cause: unknown);
21
+ }
22
+ export interface IdentityProbeRetry {
23
+ /** Probe attempts against a live pid before giving up (default 5). */
24
+ readonly attempts?: number;
25
+ /** Pause between attempts in ms (default 200). */
26
+ readonly delayMs?: number;
27
+ }
28
+ /**
29
+ * True when the pidfile still describes the running process. A probe FAILURE is not
30
+ * an answer: on a pid that is no longer live it means "gone" (false); on a live pid it
31
+ * is an observation gap — the platform query was starved or exited non-zero — so the
32
+ * probe is retried within a bounded budget and only then surfaces as
33
+ * ProcessIdentityUnreadableError. It never leaks the raw probe error as a verdict.
34
+ */
35
+ export declare function processMatchesPidFile(pidFile: DaemonPidFile, readStartTime?: (pid: number) => Promise<string | undefined>, isLive?: (pid: number) => boolean, retry?: IdentityProbeRetry): Promise<boolean>;
36
+ export declare function readProcessIdentity(pid: number, platform?: NodeJS.Platform, timeoutMs?: number, isLive?: (pid: number) => boolean): Promise<ProcessIdentityResult>;
17
37
  export declare function stopValidatedPid(pidFile: DaemonPidFile, signal: NodeJS.Signals): Promise<void>;
18
38
  export declare function waitForGone(pidFile: DaemonPidFile, timeoutMs: number): Promise<boolean>;
19
39
  export declare function readProcessStartTime(pid: number, platform?: NodeJS.Platform, timeoutMs?: number): Promise<string | undefined>;
20
40
  export declare function processIsLive(pid: number): boolean;
21
- export declare function waitForStartTime(pid: number, timeoutMs: number, readStartTime?: (pid: number) => Promise<string | undefined>): Promise<string>;
41
+ /**
42
+ * Wait for a spawned pid's process identity.
43
+ *
44
+ * Returns `undefined` (UNKNOWN) when the budget is exhausted but the process is still alive:
45
+ * budget exhaustion is an OBSERVABILITY failure, not evidence the child failed to start. On a
46
+ * loaded Windows runner every `Get-CimInstance` probe can outlive `readProcessIdentity`'s 1s win32
47
+ * default, so all ~9 attempts inside a 10s budget time out and throw while the process runs
48
+ * normally (PR #1351/#1352 CI, runs 33839093178 / 33842155236). Only a pid that is really gone is
49
+ * a startup failure, so callers can distinguish "no identity yet" from "child died".
50
+ */
51
+ export declare function waitForStartTime(pid: number, timeoutMs: number, readStartTime?: (pid: number) => Promise<string | undefined>, isLive?: (pid: number) => boolean): Promise<string | undefined>;
22
52
  //# sourceMappingURL=process.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../../src/modes/app-server/daemon/process.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CAClC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAe1E;AAED,MAAM,MAAM,qBAAqB,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvD,wBAAsB,qBAAqB,CAC1C,OAAO,EAAE,aAAa,EACtB,aAAa,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAwB,GAChF,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,wBAAsB,mBAAmB,CACxC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,SAAS,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,qBAAqB,CAAC,CAiChC;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CASpG;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7F;AAED,wBAAsB,oBAAoB,CACzC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,SAAS,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAK7B;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CASlD;AAED,wBAAsB,gBAAgB,CACrC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,aAAa,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAwB,GAChF,OAAO,CAAC,MAAM,CAAC,CAejB"}
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../../src/modes/app-server/daemon/process.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CAClC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAe1E;AAED,MAAM,MAAM,qBAAqB,GAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvD,sFAAsF;AACtF,qBAAa,8BAA+B,SAAQ,KAAK;IACxD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,SAAkB,KAAK,EAAE,OAAO,CAAC;gBAErB,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CASzD;AAED,MAAM,WAAW,kBAAkB;IAClC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,kDAAkD;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAC1C,OAAO,EAAE,aAAa,EACtB,aAAa,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAwB,EAClF,MAAM,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAuB,EAChD,KAAK,GAAE,kBAAuB,GAC5B,OAAO,CAAC,OAAO,CAAC,CAelB;AAED,wBAAsB,mBAAmB,CACxC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,SAAS,CAAC,EAAE,MAAM,EAClB,MAAM,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAuB,GAC9C,OAAO,CAAC,qBAAqB,CAAC,CAwChC;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CASpG;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7F;AAED,wBAAsB,oBAAoB,CACzC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,SAAS,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAK7B;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CASlD;AAED;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACrC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,aAAa,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAwB,EAClF,MAAM,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAuB,GAC9C,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAgB7B"}
@@ -17,11 +17,43 @@ export function parseDaemonPidFile(text) {
17
17
  }
18
18
  return { pid: parsed.pid, processStartTime: parsed.processStartTime };
19
19
  }
20
- export async function processMatchesPidFile(pidFile, readStartTime = readProcessStartTime) {
21
- const current = await readStartTime(pidFile.pid);
22
- return current === pidFile.processStartTime;
20
+ /** Thrown when a live process refuses to yield an identity after the retry budget. */
21
+ export class ProcessIdentityUnreadableError extends Error {
22
+ constructor(pid, attempts, cause) {
23
+ super(`process identity for live pid ${pid} stayed unreadable after ${attempts} probe attempt(s): ${cause instanceof Error ? cause.message : String(cause)}`);
24
+ this.name = "ProcessIdentityUnreadableError";
25
+ this.pid = pid;
26
+ this.attempts = attempts;
27
+ this.cause = cause;
28
+ }
23
29
  }
24
- export async function readProcessIdentity(pid, platform = process.platform, timeoutMs) {
30
+ /**
31
+ * True when the pidfile still describes the running process. A probe FAILURE is not
32
+ * an answer: on a pid that is no longer live it means "gone" (false); on a live pid it
33
+ * is an observation gap — the platform query was starved or exited non-zero — so the
34
+ * probe is retried within a bounded budget and only then surfaces as
35
+ * ProcessIdentityUnreadableError. It never leaks the raw probe error as a verdict.
36
+ */
37
+ export async function processMatchesPidFile(pidFile, readStartTime = readProcessStartTime, isLive = processIsLive, retry = {}) {
38
+ const attempts = Math.max(1, retry.attempts ?? 5);
39
+ const delayMs = retry.delayMs ?? 200;
40
+ let lastError;
41
+ for (let attempt = 1; attempt <= attempts; attempt++) {
42
+ try {
43
+ const current = await readStartTime(pidFile.pid);
44
+ return current === pidFile.processStartTime;
45
+ }
46
+ catch (error) {
47
+ if (!isLive(pidFile.pid))
48
+ return false;
49
+ lastError = error;
50
+ if (attempt < attempts)
51
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
52
+ }
53
+ }
54
+ throw new ProcessIdentityUnreadableError(pidFile.pid, attempts, lastError);
55
+ }
56
+ export async function readProcessIdentity(pid, platform = process.platform, timeoutMs, isLive = processIsLive) {
25
57
  const command = platform === "win32"
26
58
  ? {
27
59
  executable: "powershell.exe",
@@ -38,7 +70,14 @@ export async function readProcessIdentity(pid, platform = process.platform, time
38
70
  execFile(command.executable, command.args, { windowsHide: true, ...(effectiveTimeoutMs === undefined ? {} : { timeout: effectiveTimeoutMs }) }, (error, stdout) => {
39
71
  if (error) {
40
72
  const code = "code" in error ? error.code : undefined;
41
- resolve(platform !== "win32" && code === 1 ? { kind: "absent" } : { kind: "error", error });
73
+ // ps exits 1 for an unknown pid; on every platform a query that fails against a
74
+ // pid that is no longer live has answered the question. Only a failure against a
75
+ // LIVE pid is an observation error the caller must treat as unknown.
76
+ if ((platform !== "win32" && code === 1) || !isLive(pid)) {
77
+ resolve({ kind: "absent" });
78
+ return;
79
+ }
80
+ resolve({ kind: "error", error });
42
81
  return;
43
82
  }
44
83
  const output = stdout.trim();
@@ -94,7 +133,17 @@ export function processIsLive(pid) {
94
133
  throw error;
95
134
  }
96
135
  }
97
- export async function waitForStartTime(pid, timeoutMs, readStartTime = readProcessStartTime) {
136
+ /**
137
+ * Wait for a spawned pid's process identity.
138
+ *
139
+ * Returns `undefined` (UNKNOWN) when the budget is exhausted but the process is still alive:
140
+ * budget exhaustion is an OBSERVABILITY failure, not evidence the child failed to start. On a
141
+ * loaded Windows runner every `Get-CimInstance` probe can outlive `readProcessIdentity`'s 1s win32
142
+ * default, so all ~9 attempts inside a 10s budget time out and throw while the process runs
143
+ * normally (PR #1351/#1352 CI, runs 33839093178 / 33842155236). Only a pid that is really gone is
144
+ * a startup failure, so callers can distinguish "no identity yet" from "child died".
145
+ */
146
+ export async function waitForStartTime(pid, timeoutMs, readStartTime = readProcessStartTime, isLive = processIsLive) {
98
147
  const deadline = Date.now() + timeoutMs;
99
148
  while (Date.now() <= deadline) {
100
149
  let startTime;
@@ -110,6 +159,8 @@ export async function waitForStartTime(pid, timeoutMs, readStartTime = readProce
110
159
  return startTime;
111
160
  await delay(20);
112
161
  }
162
+ if (isLive(pid))
163
+ return undefined;
113
164
  throw new Error(`spawned daemon pid ${pid} had no process start time`);
114
165
  }
115
166
  function delay(ms) {
@@ -1 +1 @@
1
- {"version":3,"file":"process.js","sourceRoot":"","sources":["../../../../src/modes/app-server/daemon/process.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"node:child_process\";\n\nexport interface DaemonPidFile {\n\treadonly pid: number;\n\treadonly processStartTime: string;\n}\n\nexport function parseDaemonPidFile(text: string): DaemonPidFile | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(text);\n\t} catch (error: unknown) {\n\t\tif (error instanceof SyntaxError) return undefined;\n\t\tthrow error;\n\t}\n\tif (!isRecord(parsed) || typeof parsed.pid !== \"number\" || typeof parsed.processStartTime !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (!Number.isInteger(parsed.pid) || parsed.pid <= 0 || parsed.processStartTime.trim() === \"\") {\n\t\treturn undefined;\n\t}\n\treturn { pid: parsed.pid, processStartTime: parsed.processStartTime };\n}\n\nexport type ProcessIdentityResult =\n\t| { readonly kind: \"present\"; readonly identity: string }\n\t| { readonly kind: \"absent\" }\n\t| { readonly kind: \"error\"; readonly error: unknown };\n\nexport async function processMatchesPidFile(\n\tpidFile: DaemonPidFile,\n\treadStartTime: (pid: number) => Promise<string | undefined> = readProcessStartTime,\n): Promise<boolean> {\n\tconst current = await readStartTime(pidFile.pid);\n\treturn current === pidFile.processStartTime;\n}\n\nexport async function readProcessIdentity(\n\tpid: number,\n\tplatform: NodeJS.Platform = process.platform,\n\ttimeoutMs?: number,\n): Promise<ProcessIdentityResult> {\n\tconst command =\n\t\tplatform === \"win32\"\n\t\t\t? {\n\t\t\t\t\texecutable: \"powershell.exe\",\n\t\t\t\t\targs: [\n\t\t\t\t\t\t\"-NoProfile\",\n\t\t\t\t\t\t\"-NonInteractive\",\n\t\t\t\t\t\t\"-Command\",\n\t\t\t\t\t\t`$process = Get-CimInstance Win32_Process -Filter \"ProcessId=${String(pid)}\" -ErrorAction Stop; if ($null -eq $process) { Write-Output '__SENPI_ABSENT__'; exit 0 }; $process.CreationDate.ToFileTimeUtc().ToString(\"D\", [Globalization.CultureInfo]::InvariantCulture)`,\n\t\t\t\t\t],\n\t\t\t\t}\n\t\t\t: { executable: \"ps\", args: [\"-o\", \"lstart=\", \"-p\", String(pid)] };\n\treturn new Promise((resolve) => {\n\t\tconst effectiveTimeoutMs = timeoutMs ?? (platform === \"win32\" ? 1_000 : undefined);\n\t\texecFile(\n\t\t\tcommand.executable,\n\t\t\tcommand.args,\n\t\t\t{ windowsHide: true, ...(effectiveTimeoutMs === undefined ? {} : { timeout: effectiveTimeoutMs }) },\n\t\t\t(error, stdout) => {\n\t\t\t\tif (error) {\n\t\t\t\t\tconst code = \"code\" in error ? error.code : undefined;\n\t\t\t\t\tresolve(platform !== \"win32\" && code === 1 ? { kind: \"absent\" } : { kind: \"error\", error });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst output = stdout.trim();\n\t\t\t\tif (output === \"__SENPI_ABSENT__\") return resolve({ kind: \"absent\" });\n\t\t\t\tif (!output || (platform === \"win32\" && !/^\\d+$/.test(output)))\n\t\t\t\t\treturn resolve({ kind: \"error\", error: new Error(\"invalid process identity output\") });\n\t\t\t\tresolve({ kind: \"present\", identity: output });\n\t\t\t},\n\t\t).once(\"error\", (error) => resolve({ kind: \"error\", error }));\n\t});\n}\n\nexport async function stopValidatedPid(pidFile: DaemonPidFile, signal: NodeJS.Signals): Promise<void> {\n\tif (!(await processMatchesPidFile(pidFile))) 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\tif (signal === \"SIGTERM\") await waitForGone(pidFile, 10_000);\n\tif (signal === \"SIGKILL\") await waitForGone(pidFile, 2_000);\n}\n\nexport async function waitForGone(pidFile: DaemonPidFile, timeoutMs: number): Promise<boolean> {\n\tconst deadline = Date.now() + timeoutMs;\n\twhile (Date.now() <= deadline) {\n\t\tif (!(await processMatchesPidFile(pidFile))) return true;\n\t\tawait delay(100);\n\t}\n\treturn !(await processMatchesPidFile(pidFile));\n}\n\nexport async function readProcessStartTime(\n\tpid: number,\n\tplatform: NodeJS.Platform = process.platform,\n\ttimeoutMs?: number,\n): Promise<string | undefined> {\n\treturn readProcessIdentity(pid, platform, timeoutMs).then((result) => {\n\t\tif (result.kind === \"error\") throw result.error;\n\t\treturn result.kind === \"present\" ? result.identity : undefined;\n\t});\n}\n\nexport function processIsLive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error: unknown) {\n\t\tif (isNodeErrorCode(error, \"ESRCH\")) return false;\n\t\tif (isNodeErrorCode(error, \"EPERM\")) return true;\n\t\tthrow error;\n\t}\n}\n\nexport async function waitForStartTime(\n\tpid: number,\n\ttimeoutMs: number,\n\treadStartTime: (pid: number) => Promise<string | undefined> = readProcessStartTime,\n): Promise<string> {\n\tconst deadline = Date.now() + timeoutMs;\n\twhile (Date.now() <= deadline) {\n\t\tlet startTime: string | undefined;\n\t\ttry {\n\t\t\tstartTime = await readStartTime(pid);\n\t\t} catch {\n\t\t\t// Process identity queries can fail transiently while a Windows process is\n\t\t\t// entering the CIM table. Keep the bounded startup wait alive so callers do\n\t\t\t// not mistake an observability failure for a child startup failure.\n\t\t}\n\t\tif (startTime) return startTime;\n\t\tawait delay(20);\n\t}\n\tthrow new Error(`spawned daemon pid ${pid} had no process start time`);\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolveDelay) => setTimeout(resolveDelay, ms));\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isNodeErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n"]}
1
+ {"version":3,"file":"process.js","sourceRoot":"","sources":["../../../../src/modes/app-server/daemon/process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAO9C,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC9C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACzB,IAAI,KAAK,YAAY,WAAW;YAAE,OAAO,SAAS,CAAC;QACnD,MAAM,KAAK,CAAC;IACb,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,gBAAgB,KAAK,QAAQ,EAAE,CAAC;QACxG,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/F,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,gBAAgB,EAAE,MAAM,CAAC,gBAAgB,EAAE,CAAC;AACvE,CAAC;AAOD,sFAAsF;AACtF,MAAM,OAAO,8BAA+B,SAAQ,KAAK;IAKxD,YAAY,GAAW,EAAE,QAAgB,EAAE,KAAc;QACxD,KAAK,CACJ,iCAAiC,GAAG,4BAA4B,QAAQ,sBAAsB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACtJ,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;QAC7C,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;CACD;AASD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,OAAsB,EACtB,gBAA8D,oBAAoB,EAClF,SAAmC,aAAa,EAChD,QAA4B,EAAE;IAE9B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,GAAG,CAAC;IACrC,IAAI,SAAkB,CAAC;IACvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC;QACtD,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACjD,OAAO,OAAO,KAAK,OAAO,CAAC,gBAAgB,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;gBAAE,OAAO,KAAK,CAAC;YACvC,SAAS,GAAG,KAAK,CAAC;YAClB,IAAI,OAAO,GAAG,QAAQ;gBAAE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAC5F,CAAC;IACF,CAAC;IACD,MAAM,IAAI,8BAA8B,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,GAAW,EACX,WAA4B,OAAO,CAAC,QAAQ,EAC5C,SAAkB,EAClB,SAAmC,aAAa;IAEhD,MAAM,OAAO,GACZ,QAAQ,KAAK,OAAO;QACnB,CAAC,CAAC;YACA,UAAU,EAAE,gBAAgB;YAC5B,IAAI,EAAE;gBACL,YAAY;gBACZ,iBAAiB;gBACjB,UAAU;gBACV,+DAA+D,MAAM,CAAC,GAAG,CAAC,8LAA8L;aACxQ;SACD;QACF,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;IACrE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,MAAM,kBAAkB,GAAG,SAAS,IAAI,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACnF,QAAQ,CACP,OAAO,CAAC,UAAU,EAClB,OAAO,CAAC,IAAI,EACZ,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,CAAC,kBAAkB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,EAAE,EACnG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YACjB,IAAI,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,GAAG,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;gBACtD,gFAAgF;gBAChF,iFAAiF;gBACjF,qEAAqE;gBACrE,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC1D,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;oBAC5B,OAAO;gBACR,CAAC;gBACD,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;gBAClC,OAAO;YACR,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAI,MAAM,KAAK,kBAAkB;gBAAE,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;YACtE,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC7D,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,iCAAiC,CAAC,EAAE,CAAC,CAAC;YACxF,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAChD,CAAC,CACD,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,OAAsB,EAAE,MAAsB;IACpF,IAAI,CAAC,CAAC,MAAM,qBAAqB,CAAC,OAAO,CAAC,CAAC;QAAE,OAAO;IACpD,IAAI,CAAC;QACJ,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACzB,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC;YAAE,MAAM,KAAK,CAAC;IACnD,CAAC;IACD,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7D,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAsB,EAAE,SAAiB;IAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC,CAAC,MAAM,qBAAqB,CAAC,OAAO,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACzD,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,CAAC,CAAC,MAAM,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACzC,GAAW,EACX,WAA4B,OAAO,CAAC,QAAQ,EAC5C,SAAkB;IAElB,OAAO,mBAAmB,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;QACpE,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO;YAAE,MAAM,MAAM,CAAC,KAAK,CAAC;QAChD,OAAO,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAW;IACxC,IAAI,CAAC;QACJ,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACzB,IAAI,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC;QAClD,IAAI,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QACjD,MAAM,KAAK,CAAC;IACb,CAAC;AACF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,GAAW,EACX,SAAiB,EACjB,gBAA8D,oBAAoB,EAClF,SAAmC,aAAa;IAEhD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,SAA6B,CAAC;QAClC,IAAI,CAAC;YACJ,SAAS,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACR,2EAA2E;YAC3E,4EAA4E;YAC5E,oEAAoE;QACrE,CAAC;QACD,IAAI,SAAS;YAAE,OAAO,SAAS,CAAC;QAChC,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,IAAI,MAAM,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAClC,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,4BAA4B,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACxB,OAAO,IAAI,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,UAAU,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,eAAe,CAAC,KAAc,EAAE,IAAY;IACpD,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;AACzE,CAAC","sourcesContent":["import { execFile } from \"node:child_process\";\n\nexport interface DaemonPidFile {\n\treadonly pid: number;\n\treadonly processStartTime: string;\n}\n\nexport function parseDaemonPidFile(text: string): DaemonPidFile | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(text);\n\t} catch (error: unknown) {\n\t\tif (error instanceof SyntaxError) return undefined;\n\t\tthrow error;\n\t}\n\tif (!isRecord(parsed) || typeof parsed.pid !== \"number\" || typeof parsed.processStartTime !== \"string\") {\n\t\treturn undefined;\n\t}\n\tif (!Number.isInteger(parsed.pid) || parsed.pid <= 0 || parsed.processStartTime.trim() === \"\") {\n\t\treturn undefined;\n\t}\n\treturn { pid: parsed.pid, processStartTime: parsed.processStartTime };\n}\n\nexport type ProcessIdentityResult =\n\t| { readonly kind: \"present\"; readonly identity: string }\n\t| { readonly kind: \"absent\" }\n\t| { readonly kind: \"error\"; readonly error: unknown };\n\n/** Thrown when a live process refuses to yield an identity after the retry budget. */\nexport class ProcessIdentityUnreadableError extends Error {\n\treadonly pid: number;\n\treadonly attempts: number;\n\toverride readonly cause: unknown;\n\n\tconstructor(pid: number, attempts: number, cause: unknown) {\n\t\tsuper(\n\t\t\t`process identity for live pid ${pid} stayed unreadable after ${attempts} probe attempt(s): ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t);\n\t\tthis.name = \"ProcessIdentityUnreadableError\";\n\t\tthis.pid = pid;\n\t\tthis.attempts = attempts;\n\t\tthis.cause = cause;\n\t}\n}\n\nexport interface IdentityProbeRetry {\n\t/** Probe attempts against a live pid before giving up (default 5). */\n\treadonly attempts?: number;\n\t/** Pause between attempts in ms (default 200). */\n\treadonly delayMs?: number;\n}\n\n/**\n * True when the pidfile still describes the running process. A probe FAILURE is not\n * an answer: on a pid that is no longer live it means \"gone\" (false); on a live pid it\n * is an observation gap — the platform query was starved or exited non-zero — so the\n * probe is retried within a bounded budget and only then surfaces as\n * ProcessIdentityUnreadableError. It never leaks the raw probe error as a verdict.\n */\nexport async function processMatchesPidFile(\n\tpidFile: DaemonPidFile,\n\treadStartTime: (pid: number) => Promise<string | undefined> = readProcessStartTime,\n\tisLive: (pid: number) => boolean = processIsLive,\n\tretry: IdentityProbeRetry = {},\n): Promise<boolean> {\n\tconst attempts = Math.max(1, retry.attempts ?? 5);\n\tconst delayMs = retry.delayMs ?? 200;\n\tlet lastError: unknown;\n\tfor (let attempt = 1; attempt <= attempts; attempt++) {\n\t\ttry {\n\t\t\tconst current = await readStartTime(pidFile.pid);\n\t\t\treturn current === pidFile.processStartTime;\n\t\t} catch (error: unknown) {\n\t\t\tif (!isLive(pidFile.pid)) return false;\n\t\t\tlastError = error;\n\t\t\tif (attempt < attempts) await new Promise<void>((resolve) => setTimeout(resolve, delayMs));\n\t\t}\n\t}\n\tthrow new ProcessIdentityUnreadableError(pidFile.pid, attempts, lastError);\n}\n\nexport async function readProcessIdentity(\n\tpid: number,\n\tplatform: NodeJS.Platform = process.platform,\n\ttimeoutMs?: number,\n\tisLive: (pid: number) => boolean = processIsLive,\n): Promise<ProcessIdentityResult> {\n\tconst command =\n\t\tplatform === \"win32\"\n\t\t\t? {\n\t\t\t\t\texecutable: \"powershell.exe\",\n\t\t\t\t\targs: [\n\t\t\t\t\t\t\"-NoProfile\",\n\t\t\t\t\t\t\"-NonInteractive\",\n\t\t\t\t\t\t\"-Command\",\n\t\t\t\t\t\t`$process = Get-CimInstance Win32_Process -Filter \"ProcessId=${String(pid)}\" -ErrorAction Stop; if ($null -eq $process) { Write-Output '__SENPI_ABSENT__'; exit 0 }; $process.CreationDate.ToFileTimeUtc().ToString(\"D\", [Globalization.CultureInfo]::InvariantCulture)`,\n\t\t\t\t\t],\n\t\t\t\t}\n\t\t\t: { executable: \"ps\", args: [\"-o\", \"lstart=\", \"-p\", String(pid)] };\n\treturn new Promise((resolve) => {\n\t\tconst effectiveTimeoutMs = timeoutMs ?? (platform === \"win32\" ? 1_000 : undefined);\n\t\texecFile(\n\t\t\tcommand.executable,\n\t\t\tcommand.args,\n\t\t\t{ windowsHide: true, ...(effectiveTimeoutMs === undefined ? {} : { timeout: effectiveTimeoutMs }) },\n\t\t\t(error, stdout) => {\n\t\t\t\tif (error) {\n\t\t\t\t\tconst code = \"code\" in error ? error.code : undefined;\n\t\t\t\t\t// ps exits 1 for an unknown pid; on every platform a query that fails against a\n\t\t\t\t\t// pid that is no longer live has answered the question. Only a failure against a\n\t\t\t\t\t// LIVE pid is an observation error the caller must treat as unknown.\n\t\t\t\t\tif ((platform !== \"win32\" && code === 1) || !isLive(pid)) {\n\t\t\t\t\t\tresolve({ kind: \"absent\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tresolve({ kind: \"error\", error });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst output = stdout.trim();\n\t\t\t\tif (output === \"__SENPI_ABSENT__\") return resolve({ kind: \"absent\" });\n\t\t\t\tif (!output || (platform === \"win32\" && !/^\\d+$/.test(output)))\n\t\t\t\t\treturn resolve({ kind: \"error\", error: new Error(\"invalid process identity output\") });\n\t\t\t\tresolve({ kind: \"present\", identity: output });\n\t\t\t},\n\t\t).once(\"error\", (error) => resolve({ kind: \"error\", error }));\n\t});\n}\n\nexport async function stopValidatedPid(pidFile: DaemonPidFile, signal: NodeJS.Signals): Promise<void> {\n\tif (!(await processMatchesPidFile(pidFile))) 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\tif (signal === \"SIGTERM\") await waitForGone(pidFile, 10_000);\n\tif (signal === \"SIGKILL\") await waitForGone(pidFile, 2_000);\n}\n\nexport async function waitForGone(pidFile: DaemonPidFile, timeoutMs: number): Promise<boolean> {\n\tconst deadline = Date.now() + timeoutMs;\n\twhile (Date.now() <= deadline) {\n\t\tif (!(await processMatchesPidFile(pidFile))) return true;\n\t\tawait delay(100);\n\t}\n\treturn !(await processMatchesPidFile(pidFile));\n}\n\nexport async function readProcessStartTime(\n\tpid: number,\n\tplatform: NodeJS.Platform = process.platform,\n\ttimeoutMs?: number,\n): Promise<string | undefined> {\n\treturn readProcessIdentity(pid, platform, timeoutMs).then((result) => {\n\t\tif (result.kind === \"error\") throw result.error;\n\t\treturn result.kind === \"present\" ? result.identity : undefined;\n\t});\n}\n\nexport function processIsLive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error: unknown) {\n\t\tif (isNodeErrorCode(error, \"ESRCH\")) return false;\n\t\tif (isNodeErrorCode(error, \"EPERM\")) return true;\n\t\tthrow error;\n\t}\n}\n\n/**\n * Wait for a spawned pid's process identity.\n *\n * Returns `undefined` (UNKNOWN) when the budget is exhausted but the process is still alive:\n * budget exhaustion is an OBSERVABILITY failure, not evidence the child failed to start. On a\n * loaded Windows runner every `Get-CimInstance` probe can outlive `readProcessIdentity`'s 1s win32\n * default, so all ~9 attempts inside a 10s budget time out and throw while the process runs\n * normally (PR #1351/#1352 CI, runs 33839093178 / 33842155236). Only a pid that is really gone is\n * a startup failure, so callers can distinguish \"no identity yet\" from \"child died\".\n */\nexport async function waitForStartTime(\n\tpid: number,\n\ttimeoutMs: number,\n\treadStartTime: (pid: number) => Promise<string | undefined> = readProcessStartTime,\n\tisLive: (pid: number) => boolean = processIsLive,\n): Promise<string | undefined> {\n\tconst deadline = Date.now() + timeoutMs;\n\twhile (Date.now() <= deadline) {\n\t\tlet startTime: string | undefined;\n\t\ttry {\n\t\t\tstartTime = await readStartTime(pid);\n\t\t} catch {\n\t\t\t// Process identity queries can fail transiently while a Windows process is\n\t\t\t// entering the CIM table. Keep the bounded startup wait alive so callers do\n\t\t\t// not mistake an observability failure for a child startup failure.\n\t\t}\n\t\tif (startTime) return startTime;\n\t\tawait delay(20);\n\t}\n\tif (isLive(pid)) return undefined;\n\tthrow new Error(`spawned daemon pid ${pid} had no process start time`);\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolveDelay) => setTimeout(resolveDelay, ms));\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isNodeErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n"]}
@@ -145,12 +145,20 @@ async function spawnDaemon(paths, listen) {
145
145
  throw new Error("failed to spawn daemon process");
146
146
  let startTime;
147
147
  try {
148
- startTime = await Promise.race([
148
+ const observed = await Promise.race([
149
149
  waitForStartTime(pid, 10_000),
150
150
  exited.then(() => {
151
151
  throw new Error(`spawned daemon ${pid} exited before its start time could be read`);
152
152
  }),
153
153
  ]);
154
+ // UNKNOWN identity on a live daemon means the probe was starved, not that startup failed.
155
+ // The per-attempt win32 probe budget is 1s, which a loaded runner exceeds every time, so
156
+ // take one unhurried read before treating an unreadable identity as a startup error.
157
+ const resolved = observed ?? (await readProcessStartTime(pid, process.platform, 15_000).catch(() => undefined));
158
+ if (resolved === undefined) {
159
+ throw new Error(`spawned daemon ${pid} started but its process identity stayed unreadable`);
160
+ }
161
+ startTime = resolved;
154
162
  }
155
163
  catch (error) {
156
164
  // Keep the handle owned until registration succeeds. This terminates the
@@ -1 +1 @@
1
- {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../../src/modes/app-server/daemon.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport { mkdir, open, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { acquireOwnershipSafeLock } from \"../rpc/ownership-safe-lock.ts\";\nimport { inspectAppServerListenOccupancy } from \"./daemon/occupancy.ts\";\nimport {\n\tcleanupState,\n\tpollProbe,\n\tprobeListen,\n\treadSettings,\n\trunningOutput,\n\trunningUnmanagedOutput,\n} from \"./daemon/probe.ts\";\nimport {\n\ttype DaemonPidFile,\n\tparseDaemonPidFile,\n\tprocessMatchesPidFile,\n\treadProcessStartTime,\n\tstopValidatedPid,\n\twaitForGone,\n\twaitForStartTime,\n} from \"./daemon/process.ts\";\nimport type { AppServerDaemonCommandOptions, AppServerListen } from \"./index.ts\";\n\nexport interface DaemonPaths {\n\treadonly dir: string;\n\treadonly pidFile: string;\n\treadonly lockFile: string;\n\treadonly settingsFile: string;\n\treadonly stderrLog: string;\n\treadonly tokenFile: string;\n}\n\ntype DaemonOutput = Readonly<Record<string, string | number | undefined>>;\n\ntype SpawnedDaemon = {\n\treadonly pid: number;\n\treadonly exited: Promise<DaemonExit>;\n};\n\ntype DaemonExit =\n\t| { readonly kind: \"error\"; readonly error: Error }\n\t| { readonly kind: \"exit\"; readonly code: number | null; readonly signal: NodeJS.Signals | null };\n\ntype DaemonReadiness =\n\t| { readonly kind: \"ready\"; readonly version: string }\n\t| { readonly kind: \"timed-out\" }\n\t| { readonly kind: \"exited\"; readonly exit: DaemonExit };\n\nconst lockOptions = { retries: { retries: 100, minTimeout: 20, maxTimeout: 100 } } as const;\n\nexport function createDaemonPaths(agentDir = getAgentDir()): DaemonPaths {\n\tconst dir = join(agentDir, \"app-server-daemon\");\n\treturn {\n\t\tdir,\n\t\tpidFile: join(dir, \"app-server.pid\"),\n\t\tlockFile: join(dir, \"daemon.lock\"),\n\t\tsettingsFile: join(dir, \"settings.json\"),\n\t\tstderrLog: join(dir, \"stderr.log\"),\n\t\ttokenFile: join(agentDir, \"app-server\", \"ws-token\"),\n\t};\n}\n\nexport async function withDaemonStateLock<T>(paths: DaemonPaths, task: () => Promise<T>): Promise<T> {\n\tawait mkdir(paths.dir, { recursive: true });\n\tconst release = await acquireOwnershipSafeLock(paths.lockFile, lockOptions);\n\ttry {\n\t\treturn await task();\n\t} finally {\n\t\tawait release();\n\t}\n}\n\nexport async function runAppServerDaemonCommand(options: AppServerDaemonCommandOptions): Promise<void> {\n\tconst paths = createDaemonPaths();\n\tconst output = await withDaemonStateLock(paths, async () => {\n\t\ttry {\n\t\t\treturn await runLockedDaemonCommand(options, paths);\n\t\t} catch (error: unknown) {\n\t\t\tprocess.exitCode = 1;\n\t\t\treturn { status: \"error\", message: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t});\n\tprocess.stdout.write(`${JSON.stringify(output)}\\n`);\n}\n\nasync function runLockedDaemonCommand(\n\toptions: AppServerDaemonCommandOptions,\n\tpaths: DaemonPaths,\n): Promise<DaemonOutput> {\n\tconst settings = await readSettings(paths);\n\tconst listen = options.verb === \"start\" ? options.listen : (settings?.listen ?? options.listen);\n\tswitch (options.verb) {\n\t\tcase \"start\":\n\t\t\treturn startDaemon(paths, listen);\n\t\tcase \"stop\":\n\t\t\treturn stopDaemon(paths, listen);\n\t\tcase \"status\":\n\t\t\treturn statusDaemon(paths, listen);\n\t\tcase \"restart\": {\n\t\t\tconst restartListen = settings?.listen ?? options.listen;\n\t\t\tawait stopDaemon(paths, restartListen);\n\t\t\treturn startDaemon(paths, restartListen);\n\t\t}\n\t}\n}\n\nasync function startDaemon(paths: DaemonPaths, listen: AppServerListen): Promise<DaemonOutput> {\n\tconst occupancy = await inspectAppServerListenOccupancy(paths, listen);\n\tif (occupancy.kind === \"app-server\") {\n\t\tconst pidFile = await readPidFile(paths);\n\t\tconst pid = pidFile && (await processMatchesPidFile(pidFile)) ? pidFile.pid : undefined;\n\t\tif (pid !== undefined) return runningOutput(\"already-running\", pid, listen, occupancy.version);\n\t\treturn { status: \"already-running\", listen: listen.url, version: occupancy.version };\n\t}\n\tconst pidFile = await readPidFile(paths);\n\tif (pidFile && (await processMatchesPidFile(pidFile))) {\n\t\tconst lateProbe = await pollProbe(paths, listen, 10_000);\n\t\tif (lateProbe) return runningOutput(\"already-running\", pidFile.pid, listen, lateProbe);\n\t\tthrow new Error(`managed daemon pid ${pidFile.pid} did not answer initialize`);\n\t}\n\tconst spawned = await spawnDaemon(paths, listen);\n\tconst readiness = await waitForDaemonReady(paths, listen, spawned.exited);\n\tif (readiness.kind === \"ready\") {\n\t\treturn { status: \"started\", pid: spawned.pid, listen: listen.url };\n\t}\n\tawait stopValidatedPid(\n\t\t{ pid: spawned.pid, processStartTime: (await readProcessStartTime(spawned.pid)) ?? \"\" },\n\t\t\"SIGTERM\",\n\t);\n\tconst message =\n\t\treadiness.kind === \"exited\"\n\t\t\t? `spawned daemon ${describeDaemonExit(readiness.exit)} before answering initialize`\n\t\t\t: \"spawned daemon did not answer initialize within 10s\";\n\tconst diagnostic = await appendDaemonStderr(paths, message);\n\tawait cleanupState(paths, listen);\n\tthrow new Error(diagnostic);\n}\n\nasync function stopDaemon(paths: DaemonPaths, listen: AppServerListen): Promise<DaemonOutput> {\n\tconst pidFile = await readPidFile(paths);\n\tif (!pidFile) {\n\t\tif (!(await probeListen(paths, listen, 2_000))) await cleanupState(paths, listen);\n\t\treturn { status: \"not-running\" };\n\t}\n\tif (!(await processMatchesPidFile(pidFile))) {\n\t\tawait cleanupState(paths, listen);\n\t\treturn { status: \"not-running\" };\n\t}\n\tawait stopValidatedPid(pidFile, \"SIGTERM\");\n\tif (await processMatchesPidFile(pidFile)) {\n\t\tawait stopValidatedPid(pidFile, \"SIGKILL\");\n\t}\n\tawait waitForGone(pidFile, 10_000);\n\tawait cleanupState(paths, listen);\n\treturn { status: \"stopped\" };\n}\n\nasync function statusDaemon(paths: DaemonPaths, listen: AppServerListen): Promise<DaemonOutput> {\n\tconst probe = await probeListen(paths, listen, 2_000);\n\tconst pidFile = await readPidFile(paths);\n\tconst pidMatches = pidFile ? await processMatchesPidFile(pidFile) : false;\n\tif (probe && pidFile && pidMatches) return runningOutput(\"running\", pidFile.pid, listen, probe);\n\tif (probe) return runningUnmanagedOutput(listen, probe);\n\tif (pidFile && !pidMatches) await cleanupState(paths, listen);\n\treturn { status: \"not-running\" };\n}\n\nasync function spawnDaemon(paths: DaemonPaths, listen: AppServerListen): Promise<SpawnedDaemon> {\n\tconst stderr = await open(paths.stderrLog, \"w\");\n\ttry {\n\t\tconst daemonExec = process.versions.bun\n\t\t\t? process.env.npm_node_execpath && !/[/\\\\]bun(?:$|[/\\\\])/.test(process.env.npm_node_execpath)\n\t\t\t\t? process.env.npm_node_execpath\n\t\t\t\t: \"/opt/homebrew/bin/node\"\n\t\t\t: process.execPath;\n\t\tconst child = spawn(\n\t\t\tdaemonExec,\n\t\t\t[\n\t\t\t\t...(process.versions.bun ? [] : process.execArgv),\n\t\t\t\tresolveCliMainPath(),\n\t\t\t\t\"app-server\",\n\t\t\t\t\"--listen\",\n\t\t\t\tlisten.url,\n\t\t\t],\n\t\t\t{\n\t\t\t\tdetached: true,\n\t\t\t\twindowsHide: true,\n\t\t\t\tenv: { ...process.env, SENPI_RUNTIME: \"node\" },\n\t\t\t\tstdio: [\"ignore\", \"ignore\", stderr.fd],\n\t\t\t},\n\t\t);\n\t\tconst exited = observeDaemonExit(child);\n\t\tconst pid = child.pid;\n\t\tif (pid === undefined) throw new Error(\"failed to spawn daemon process\");\n\t\tlet startTime: string;\n\t\ttry {\n\t\t\tstartTime = await Promise.race([\n\t\t\t\twaitForStartTime(pid, 10_000),\n\t\t\t\texited.then(() => {\n\t\t\t\t\tthrow new Error(`spawned daemon ${pid} exited before its start time could be read`);\n\t\t\t\t}),\n\t\t\t]);\n\t\t} catch (error: unknown) {\n\t\t\t// Keep the handle owned until registration succeeds. This terminates the\n\t\t\t// exact child even when start-time acquisition fails, without a raw PID.\n\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\ttry {\n\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t} catch {}\n\t\t\t\tawait Promise.race([exited, delay(2_000)]);\n\t\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait cleanupState(paths, listen);\n\t\t\tthrow error;\n\t\t}\n\t\ttry {\n\t\t\tawait writeFile(paths.pidFile, `${JSON.stringify({ pid, processStartTime: startTime })}\\n`, { mode: 0o600 });\n\t\t\tawait writeFile(paths.settingsFile, `${JSON.stringify({ listen })}\\n`, { mode: 0o600 });\n\t\t} catch (error: unknown) {\n\t\t\t// Registration is the ownership hand-off point. Until both files exist,\n\t\t\t// retain the exact ChildProcess handle and terminate it on any write\n\t\t\t// failure so a partial registration can never leave an unmanaged daemon.\n\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\ttry {\n\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t} catch {}\n\t\t\t\tawait Promise.race([exited, delay(2_000)]);\n\t\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t} catch (killError: unknown) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`failed to terminate daemon after registration failure: ${killError instanceof Error ? killError.message : String(killError)}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (!(await Promise.race([exited.then(() => true), delay(2_000).then(() => false)]))) {\n\t\t\t\t\t\tthrow new Error(`daemon ${pid} remained alive after SIGKILL during registration failure`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait cleanupState(paths, { ...listen, ...(listen.kind === \"unix\" ? { path: undefined } : {}) });\n\t\t\tthrow error;\n\t\t}\n\t\tchild.unref();\n\t\treturn { pid, exited };\n\t} finally {\n\t\tawait stderr.close();\n\t}\n}\n\nasync function waitForDaemonReady(\n\tpaths: DaemonPaths,\n\tlisten: AppServerListen,\n\texited: Promise<DaemonExit>,\n): Promise<DaemonReadiness> {\n\tconst controller = new AbortController();\n\ttry {\n\t\tconst probe = pollProbe(paths, listen, 10_000, controller.signal).then(\n\t\t\t(version): DaemonReadiness => (version ? { kind: \"ready\", version } : { kind: \"timed-out\" }),\n\t\t);\n\t\tconst childExit = exited.then((exit): DaemonReadiness => ({ kind: \"exited\", exit }));\n\t\treturn await Promise.race([probe, childExit]);\n\t} finally {\n\t\tcontroller.abort();\n\t}\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolveDelay) => setTimeout(resolveDelay, ms));\n}\n\nfunction observeDaemonExit(child: ChildProcess): Promise<DaemonExit> {\n\treturn new Promise((resolveExit) => {\n\t\tchild.once(\"error\", (error) => resolveExit({ kind: \"error\", error }));\n\t\tchild.once(\"exit\", (code, signal) => resolveExit({ kind: \"exit\", code, signal }));\n\t});\n}\n\nfunction describeDaemonExit(exit: DaemonExit): string {\n\tif (exit.kind === \"error\") return `failed to launch (${exit.error.message})`;\n\tif (exit.signal) return `was terminated by ${exit.signal}`;\n\treturn `exited with code ${exit.code ?? \"unknown\"}`;\n}\n\nasync function appendDaemonStderr(paths: DaemonPaths, 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\nasync function readPidFile(paths: DaemonPaths): Promise<DaemonPidFile | undefined> {\n\ttry {\n\t\treturn parseDaemonPidFile(await readFile(paths.pidFile, \"utf8\"));\n\t} catch (error: unknown) {\n\t\tif (isNodeErrorCode(error, \"ENOENT\")) return undefined;\n\t\tthrow error;\n\t}\n}\n\nfunction resolveCliMainPath(): string {\n\tconst modulePath = fileURLToPath(import.meta.url);\n\tconst extension = modulePath.endsWith(\".ts\") ? \".ts\" : \".js\";\n\treturn resolve(dirname(modulePath), \"..\", \"..\", `cli-main${extension}`);\n}\n\nfunction isNodeErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n"]}
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../../src/modes/app-server/daemon.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport { mkdir, open, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { acquireOwnershipSafeLock } from \"../rpc/ownership-safe-lock.ts\";\nimport { inspectAppServerListenOccupancy } from \"./daemon/occupancy.ts\";\nimport {\n\tcleanupState,\n\tpollProbe,\n\tprobeListen,\n\treadSettings,\n\trunningOutput,\n\trunningUnmanagedOutput,\n} from \"./daemon/probe.ts\";\nimport {\n\ttype DaemonPidFile,\n\tparseDaemonPidFile,\n\tprocessMatchesPidFile,\n\treadProcessStartTime,\n\tstopValidatedPid,\n\twaitForGone,\n\twaitForStartTime,\n} from \"./daemon/process.ts\";\nimport type { AppServerDaemonCommandOptions, AppServerListen } from \"./index.ts\";\n\nexport interface DaemonPaths {\n\treadonly dir: string;\n\treadonly pidFile: string;\n\treadonly lockFile: string;\n\treadonly settingsFile: string;\n\treadonly stderrLog: string;\n\treadonly tokenFile: string;\n}\n\ntype DaemonOutput = Readonly<Record<string, string | number | undefined>>;\n\ntype SpawnedDaemon = {\n\treadonly pid: number;\n\treadonly exited: Promise<DaemonExit>;\n};\n\ntype DaemonExit =\n\t| { readonly kind: \"error\"; readonly error: Error }\n\t| { readonly kind: \"exit\"; readonly code: number | null; readonly signal: NodeJS.Signals | null };\n\ntype DaemonReadiness =\n\t| { readonly kind: \"ready\"; readonly version: string }\n\t| { readonly kind: \"timed-out\" }\n\t| { readonly kind: \"exited\"; readonly exit: DaemonExit };\n\nconst lockOptions = { retries: { retries: 100, minTimeout: 20, maxTimeout: 100 } } as const;\n\nexport function createDaemonPaths(agentDir = getAgentDir()): DaemonPaths {\n\tconst dir = join(agentDir, \"app-server-daemon\");\n\treturn {\n\t\tdir,\n\t\tpidFile: join(dir, \"app-server.pid\"),\n\t\tlockFile: join(dir, \"daemon.lock\"),\n\t\tsettingsFile: join(dir, \"settings.json\"),\n\t\tstderrLog: join(dir, \"stderr.log\"),\n\t\ttokenFile: join(agentDir, \"app-server\", \"ws-token\"),\n\t};\n}\n\nexport async function withDaemonStateLock<T>(paths: DaemonPaths, task: () => Promise<T>): Promise<T> {\n\tawait mkdir(paths.dir, { recursive: true });\n\tconst release = await acquireOwnershipSafeLock(paths.lockFile, lockOptions);\n\ttry {\n\t\treturn await task();\n\t} finally {\n\t\tawait release();\n\t}\n}\n\nexport async function runAppServerDaemonCommand(options: AppServerDaemonCommandOptions): Promise<void> {\n\tconst paths = createDaemonPaths();\n\tconst output = await withDaemonStateLock(paths, async () => {\n\t\ttry {\n\t\t\treturn await runLockedDaemonCommand(options, paths);\n\t\t} catch (error: unknown) {\n\t\t\tprocess.exitCode = 1;\n\t\t\treturn { status: \"error\", message: error instanceof Error ? error.message : String(error) };\n\t\t}\n\t});\n\tprocess.stdout.write(`${JSON.stringify(output)}\\n`);\n}\n\nasync function runLockedDaemonCommand(\n\toptions: AppServerDaemonCommandOptions,\n\tpaths: DaemonPaths,\n): Promise<DaemonOutput> {\n\tconst settings = await readSettings(paths);\n\tconst listen = options.verb === \"start\" ? options.listen : (settings?.listen ?? options.listen);\n\tswitch (options.verb) {\n\t\tcase \"start\":\n\t\t\treturn startDaemon(paths, listen);\n\t\tcase \"stop\":\n\t\t\treturn stopDaemon(paths, listen);\n\t\tcase \"status\":\n\t\t\treturn statusDaemon(paths, listen);\n\t\tcase \"restart\": {\n\t\t\tconst restartListen = settings?.listen ?? options.listen;\n\t\t\tawait stopDaemon(paths, restartListen);\n\t\t\treturn startDaemon(paths, restartListen);\n\t\t}\n\t}\n}\n\nasync function startDaemon(paths: DaemonPaths, listen: AppServerListen): Promise<DaemonOutput> {\n\tconst occupancy = await inspectAppServerListenOccupancy(paths, listen);\n\tif (occupancy.kind === \"app-server\") {\n\t\tconst pidFile = await readPidFile(paths);\n\t\tconst pid = pidFile && (await processMatchesPidFile(pidFile)) ? pidFile.pid : undefined;\n\t\tif (pid !== undefined) return runningOutput(\"already-running\", pid, listen, occupancy.version);\n\t\treturn { status: \"already-running\", listen: listen.url, version: occupancy.version };\n\t}\n\tconst pidFile = await readPidFile(paths);\n\tif (pidFile && (await processMatchesPidFile(pidFile))) {\n\t\tconst lateProbe = await pollProbe(paths, listen, 10_000);\n\t\tif (lateProbe) return runningOutput(\"already-running\", pidFile.pid, listen, lateProbe);\n\t\tthrow new Error(`managed daemon pid ${pidFile.pid} did not answer initialize`);\n\t}\n\tconst spawned = await spawnDaemon(paths, listen);\n\tconst readiness = await waitForDaemonReady(paths, listen, spawned.exited);\n\tif (readiness.kind === \"ready\") {\n\t\treturn { status: \"started\", pid: spawned.pid, listen: listen.url };\n\t}\n\tawait stopValidatedPid(\n\t\t{ pid: spawned.pid, processStartTime: (await readProcessStartTime(spawned.pid)) ?? \"\" },\n\t\t\"SIGTERM\",\n\t);\n\tconst message =\n\t\treadiness.kind === \"exited\"\n\t\t\t? `spawned daemon ${describeDaemonExit(readiness.exit)} before answering initialize`\n\t\t\t: \"spawned daemon did not answer initialize within 10s\";\n\tconst diagnostic = await appendDaemonStderr(paths, message);\n\tawait cleanupState(paths, listen);\n\tthrow new Error(diagnostic);\n}\n\nasync function stopDaemon(paths: DaemonPaths, listen: AppServerListen): Promise<DaemonOutput> {\n\tconst pidFile = await readPidFile(paths);\n\tif (!pidFile) {\n\t\tif (!(await probeListen(paths, listen, 2_000))) await cleanupState(paths, listen);\n\t\treturn { status: \"not-running\" };\n\t}\n\tif (!(await processMatchesPidFile(pidFile))) {\n\t\tawait cleanupState(paths, listen);\n\t\treturn { status: \"not-running\" };\n\t}\n\tawait stopValidatedPid(pidFile, \"SIGTERM\");\n\tif (await processMatchesPidFile(pidFile)) {\n\t\tawait stopValidatedPid(pidFile, \"SIGKILL\");\n\t}\n\tawait waitForGone(pidFile, 10_000);\n\tawait cleanupState(paths, listen);\n\treturn { status: \"stopped\" };\n}\n\nasync function statusDaemon(paths: DaemonPaths, listen: AppServerListen): Promise<DaemonOutput> {\n\tconst probe = await probeListen(paths, listen, 2_000);\n\tconst pidFile = await readPidFile(paths);\n\tconst pidMatches = pidFile ? await processMatchesPidFile(pidFile) : false;\n\tif (probe && pidFile && pidMatches) return runningOutput(\"running\", pidFile.pid, listen, probe);\n\tif (probe) return runningUnmanagedOutput(listen, probe);\n\tif (pidFile && !pidMatches) await cleanupState(paths, listen);\n\treturn { status: \"not-running\" };\n}\n\nasync function spawnDaemon(paths: DaemonPaths, listen: AppServerListen): Promise<SpawnedDaemon> {\n\tconst stderr = await open(paths.stderrLog, \"w\");\n\ttry {\n\t\tconst daemonExec = process.versions.bun\n\t\t\t? process.env.npm_node_execpath && !/[/\\\\]bun(?:$|[/\\\\])/.test(process.env.npm_node_execpath)\n\t\t\t\t? process.env.npm_node_execpath\n\t\t\t\t: \"/opt/homebrew/bin/node\"\n\t\t\t: process.execPath;\n\t\tconst child = spawn(\n\t\t\tdaemonExec,\n\t\t\t[\n\t\t\t\t...(process.versions.bun ? [] : process.execArgv),\n\t\t\t\tresolveCliMainPath(),\n\t\t\t\t\"app-server\",\n\t\t\t\t\"--listen\",\n\t\t\t\tlisten.url,\n\t\t\t],\n\t\t\t{\n\t\t\t\tdetached: true,\n\t\t\t\twindowsHide: true,\n\t\t\t\tenv: { ...process.env, SENPI_RUNTIME: \"node\" },\n\t\t\t\tstdio: [\"ignore\", \"ignore\", stderr.fd],\n\t\t\t},\n\t\t);\n\t\tconst exited = observeDaemonExit(child);\n\t\tconst pid = child.pid;\n\t\tif (pid === undefined) throw new Error(\"failed to spawn daemon process\");\n\t\tlet startTime: string;\n\t\ttry {\n\t\t\tconst observed = await Promise.race([\n\t\t\t\twaitForStartTime(pid, 10_000),\n\t\t\t\texited.then(() => {\n\t\t\t\t\tthrow new Error(`spawned daemon ${pid} exited before its start time could be read`);\n\t\t\t\t}),\n\t\t\t]);\n\t\t\t// UNKNOWN identity on a live daemon means the probe was starved, not that startup failed.\n\t\t\t// The per-attempt win32 probe budget is 1s, which a loaded runner exceeds every time, so\n\t\t\t// take one unhurried read before treating an unreadable identity as a startup error.\n\t\t\tconst resolved =\n\t\t\t\tobserved ?? (await readProcessStartTime(pid, process.platform, 15_000).catch(() => undefined));\n\t\t\tif (resolved === undefined) {\n\t\t\t\tthrow new Error(`spawned daemon ${pid} started but its process identity stayed unreadable`);\n\t\t\t}\n\t\t\tstartTime = resolved;\n\t\t} catch (error: unknown) {\n\t\t\t// Keep the handle owned until registration succeeds. This terminates the\n\t\t\t// exact child even when start-time acquisition fails, without a raw PID.\n\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\ttry {\n\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t} catch {}\n\t\t\t\tawait Promise.race([exited, delay(2_000)]);\n\t\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait cleanupState(paths, listen);\n\t\t\tthrow error;\n\t\t}\n\t\ttry {\n\t\t\tawait writeFile(paths.pidFile, `${JSON.stringify({ pid, processStartTime: startTime })}\\n`, { mode: 0o600 });\n\t\t\tawait writeFile(paths.settingsFile, `${JSON.stringify({ listen })}\\n`, { mode: 0o600 });\n\t\t} catch (error: unknown) {\n\t\t\t// Registration is the ownership hand-off point. Until both files exist,\n\t\t\t// retain the exact ChildProcess handle and terminate it on any write\n\t\t\t// failure so a partial registration can never leave an unmanaged daemon.\n\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\ttry {\n\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t} catch {}\n\t\t\t\tawait Promise.race([exited, delay(2_000)]);\n\t\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t} catch (killError: unknown) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`failed to terminate daemon after registration failure: ${killError instanceof Error ? killError.message : String(killError)}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (!(await Promise.race([exited.then(() => true), delay(2_000).then(() => false)]))) {\n\t\t\t\t\t\tthrow new Error(`daemon ${pid} remained alive after SIGKILL during registration failure`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait cleanupState(paths, { ...listen, ...(listen.kind === \"unix\" ? { path: undefined } : {}) });\n\t\t\tthrow error;\n\t\t}\n\t\tchild.unref();\n\t\treturn { pid, exited };\n\t} finally {\n\t\tawait stderr.close();\n\t}\n}\n\nasync function waitForDaemonReady(\n\tpaths: DaemonPaths,\n\tlisten: AppServerListen,\n\texited: Promise<DaemonExit>,\n): Promise<DaemonReadiness> {\n\tconst controller = new AbortController();\n\ttry {\n\t\tconst probe = pollProbe(paths, listen, 10_000, controller.signal).then(\n\t\t\t(version): DaemonReadiness => (version ? { kind: \"ready\", version } : { kind: \"timed-out\" }),\n\t\t);\n\t\tconst childExit = exited.then((exit): DaemonReadiness => ({ kind: \"exited\", exit }));\n\t\treturn await Promise.race([probe, childExit]);\n\t} finally {\n\t\tcontroller.abort();\n\t}\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolveDelay) => setTimeout(resolveDelay, ms));\n}\n\nfunction observeDaemonExit(child: ChildProcess): Promise<DaemonExit> {\n\treturn new Promise((resolveExit) => {\n\t\tchild.once(\"error\", (error) => resolveExit({ kind: \"error\", error }));\n\t\tchild.once(\"exit\", (code, signal) => resolveExit({ kind: \"exit\", code, signal }));\n\t});\n}\n\nfunction describeDaemonExit(exit: DaemonExit): string {\n\tif (exit.kind === \"error\") return `failed to launch (${exit.error.message})`;\n\tif (exit.signal) return `was terminated by ${exit.signal}`;\n\treturn `exited with code ${exit.code ?? \"unknown\"}`;\n}\n\nasync function appendDaemonStderr(paths: DaemonPaths, 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\nasync function readPidFile(paths: DaemonPaths): Promise<DaemonPidFile | undefined> {\n\ttry {\n\t\treturn parseDaemonPidFile(await readFile(paths.pidFile, \"utf8\"));\n\t} catch (error: unknown) {\n\t\tif (isNodeErrorCode(error, \"ENOENT\")) return undefined;\n\t\tthrow error;\n\t}\n}\n\nfunction resolveCliMainPath(): string {\n\tconst modulePath = fileURLToPath(import.meta.url);\n\tconst extension = modulePath.endsWith(\".ts\") ? \".ts\" : \".js\";\n\treturn resolve(dirname(modulePath), \"..\", \"..\", `cli-main${extension}`);\n}\n\nfunction isNodeErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n"]}
@@ -31,6 +31,8 @@ export interface EnsureHostOptions {
31
31
  * to stall the real process-identity probe.
32
32
  */
33
33
  readonly beforePidFileWrite?: () => Promise<void>;
34
+ /** Overrides the process-identity probe so a test can force its failure. */
35
+ readonly readProcessStartTime?: (pid: number) => Promise<string | undefined>;
34
36
  };
35
37
  }
36
38
  export interface EnsuredHost {
@@ -1 +1 @@
1
- {"version":3,"file":"host-ensure.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-ensure.ts"],"names":[],"mappings":"AAmBA,OAAO,EAEN,KAAK,aAAa,EAClB,KAAK,wBAAwB,EAE7B,MAAM,qBAAqB,CAAC;AAU7B,YAAY,EAAE,aAAa,EAAE,wBAAwB,EAAE,CAAC;AAExD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAED,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,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,wFAAwF;QACxF,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAChD,2EAA2E;QAC3E,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACtC,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;KAClD,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;CACzB;AA4BD;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,qDAAwE,CAAC;AAErH,wBAAgB,qBAAqB,CAAC,QAAQ,SAAgB,GAAG,eAAe,CAS/E;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAsBjF;AAsUD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,GAAE,OAAqB,GAC7B;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CACf,CAWA"}
1
+ {"version":3,"file":"host-ensure.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-ensure.ts"],"names":[],"mappings":"AAsBA,OAAO,EAEN,KAAK,aAAa,EAClB,KAAK,wBAAwB,EAE7B,MAAM,qBAAqB,CAAC;AAU7B,YAAY,EAAE,aAAa,EAAE,wBAAwB,EAAE,CAAC;AAExD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAED,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,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,wFAAwF;QACxF,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAChD,2EAA2E;QAC3E,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACtC,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;CACzB;AA4BD;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,qDAAwE,CAAC;AAErH,wBAAgB,qBAAqB,CAAC,QAAQ,SAAgB,GAAG,eAAe,CAS/E;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAsBjF;AAgaD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,GAAE,OAAqB,GAC7B;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CACf,CAWA"}
@@ -6,7 +6,7 @@ import { tmpdir } from "node:os";
6
6
  import { dirname, join, resolve } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { ENV_AGENT_DIR, getAgentDir, isBunBinary, VERSION } from "../../config.js";
9
- import { parseDaemonPidFile, processMatchesPidFile, waitForStartTime, } from "../app-server/daemon/process.js";
9
+ import { ProcessIdentityUnreadableError, parseDaemonPidFile, processIsLive, processMatchesPidFile, readProcessStartTime, waitForStartTime, } from "../app-server/daemon/process.js";
10
10
  import { CUSTOM_UNSUPPORTED_CAPABILITY, EXTENSION_EVENTS_CAPABILITY, RPC_CLIENT_CAPABILITIES_ENV, } from "./custom-capability.js";
11
11
  import { DEFAULT_HOST_IDLE_EXIT_MS, INTERNAL_SUPERVISOR_FLAG, } from "./host-lifecycle.js";
12
12
  import { acquireOwnershipSafeLock } from "./ownership-safe-lock.js";
@@ -74,7 +74,8 @@ export async function ensureHost(options) {
74
74
  async function ensureHostLocked(paths, socket, agentDir, policy, testOptions) {
75
75
  const pidFile = await readPidFile(paths);
76
76
  const protocol = await probeProtocolInfo(socket, EXISTING_HOST_PROBE_TIMEOUT_MS);
77
- const pidMatches = pidFile ? await processMatchesPidFile(pidFile) : false;
77
+ const probe = testOptions?.readProcessStartTime ?? readProcessStartTime;
78
+ const pidMatches = pidFile ? await processMatchesPidFile(pidFile, probe) : false;
78
79
  if (isCompatible(protocol)) {
79
80
  // A compatible socket is attachable even when another client surface
80
81
  // started it. Only hosts we spawned are eligible for lifecycle management.
@@ -84,7 +85,7 @@ async function ensureHostLocked(paths, socket, agentDir, policy, testOptions) {
84
85
  throw new Error(`RPC socket ${socket} is owned by an unmanaged host`);
85
86
  }
86
87
  if (pidFile && pidMatches) {
87
- await stopManagedHost(pidFile, testOptions?.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS);
88
+ await stopManagedHost(pidFile, testOptions?.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS, probe);
88
89
  }
89
90
  await cleanupState(paths);
90
91
  return startHost(paths, socket, agentDir, policy, testOptions);
@@ -126,12 +127,21 @@ async function startHost(paths, socket, agentDir, policy, testOptions) {
126
127
  });
127
128
  if (child.pid === undefined)
128
129
  throw new Error("failed to spawn RPC socket host");
129
- const processStartTime = await Promise.race([
130
- waitForStartTime(child.pid, 10_000),
130
+ const probe = testOptions?.readProcessStartTime ?? readProcessStartTime;
131
+ const observedStartTime = await Promise.race([
132
+ waitForStartTime(child.pid, 10_000, probe),
131
133
  childExit.then(() => {
132
134
  throw new Error("RPC socket host exited before its start time could be read");
133
135
  }),
134
136
  ]);
137
+ // UNKNOWN identity on a live child: the probe was starved, not the host. Give the CIM table
138
+ // one unhurried read (the per-attempt win32 default is 1s, which a loaded runner exceeds on
139
+ // every attempt) before deciding. Without an identity the pidfile cannot carry an ownership
140
+ // guard, so a healthy host must still be kept rather than torn down for an unreadable probe.
141
+ const processStartTime = observedStartTime ?? (await readProcessStartTime(child.pid, process.platform, 15_000).catch(() => undefined));
142
+ if (processStartTime === undefined) {
143
+ throw new Error(`RPC socket host pid ${child.pid} started but its process identity stayed unreadable; refusing to register an unguarded pidfile`);
144
+ }
135
145
  pidFile = { pid: child.pid, processStartTime };
136
146
  await testOptions?.beforePidFileWrite?.();
137
147
  await writeFile(paths.pidFile, `${JSON.stringify(pidFile)}\n`, { mode: 0o600 });
@@ -175,30 +185,74 @@ async function startHost(paths, socket, agentDir, policy, testOptions) {
175
185
  const result = await pollProtocolInfo(socket, readinessTimeoutMs, childExit);
176
186
  if (isCompatible(result.protocol))
177
187
  return { pid: pidFile.pid, socket, reused: false };
178
- await stopManagedHost(pidFile, testOptions?.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS);
188
+ // Teardown runs for the diagnostic's sake, so it must never replace it: a stop
189
+ // failure here (unreadable identity, a host that outlives SIGKILL) would other-
190
+ // wise propagate instead of the readiness message and skip cleanupState below,
191
+ // leaving the pidfile and socket behind. Record it and keep going.
192
+ // This child is ours and its handle is still attached: stop it through the handle.
193
+ // Validating ownership through the pidfile would re-run the identity probe whose
194
+ // failure is the very thing a loaded runner produces here.
195
+ const stopFailure = await stopSpawnedChild(child, childExit, testOptions?.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS).then(() => undefined, (error) => (error instanceof Error ? error.message : String(error)));
179
196
  const message = result.protocol
180
197
  ? `RPC socket host answered get_protocol_info with serverVersion ${result.protocol.serverVersion} and capabilities ${JSON.stringify(result.protocol.capabilities)}, but is incompatible with serverVersion ${VERSION} and required capabilities ${JSON.stringify(REQUIRED_CAPABILITIES)}`
181
198
  : result.exited
182
199
  ? `RPC socket host exited with code ${result.exited.code ?? "null"}${result.exited.signal ? ` (${result.exited.signal})` : ""} before answering get_protocol_info`
183
200
  : `spawned RPC socket host did not answer get_protocol_info within ${readinessTimeoutMs}ms`;
184
- const diagnostic = await appendStderr(paths, message);
201
+ const diagnostic = await appendStderr(paths, stopFailure === undefined ? message : `${message} (teardown also reported: ${stopFailure})`);
185
202
  await cleanupState(paths);
186
203
  // The supervisor may have failed before binding, or another owner may have
187
204
  // appeared while readiness was being checked. Never unlink an endpoint we
188
205
  // cannot prove this start owned.
189
206
  throw new Error(diagnostic);
190
207
  }
191
- async function stopManagedHost(pidFile, termTimeoutMs) {
192
- await signalValidated(pidFile, "SIGTERM");
193
- if (await waitForGone(pidFile, termTimeoutMs))
208
+ async function stopSpawnedChild(child, childExit, termTimeoutMs) {
209
+ const exited = () => child.exitCode !== null || child.signalCode !== null;
210
+ if (exited())
211
+ return;
212
+ const signal = (name) => {
213
+ try {
214
+ child.kill(name);
215
+ }
216
+ catch (error) {
217
+ if (!isNodeErrorCode(error, "ESRCH"))
218
+ throw error;
219
+ }
220
+ };
221
+ const waitFor = (ms) => Promise.race([childExit.then(() => true), delay(ms).then(() => exited())]);
222
+ signal("SIGTERM");
223
+ if (await waitFor(termTimeoutMs))
194
224
  return;
195
- await signalValidated(pidFile, "SIGKILL");
196
- if (!(await waitForGone(pidFile, SIGKILL_GRACE_MS))) {
225
+ signal("SIGKILL");
226
+ if (!(await waitFor(SIGKILL_GRACE_MS))) {
227
+ throw new Error(`RPC socket host pid ${child.pid ?? "?"} remained alive after SIGKILL`);
228
+ }
229
+ }
230
+ async function stopManagedHost(pidFile, termTimeoutMs, readStartTime = readProcessStartTime) {
231
+ await signalValidated(pidFile, "SIGTERM", readStartTime);
232
+ if (await waitForGone(pidFile, termTimeoutMs, readStartTime))
233
+ return;
234
+ await signalValidated(pidFile, "SIGKILL", readStartTime);
235
+ if (!(await waitForGone(pidFile, SIGKILL_GRACE_MS, readStartTime))) {
197
236
  throw new Error(`RPC socket host pid ${pidFile.pid} remained alive after SIGKILL`);
198
237
  }
199
238
  }
200
- async function signalValidated(pidFile, signal) {
201
- if (!(await processMatchesPidFile(pidFile)))
239
+ // One probe per call: the teardown loops below are themselves the retry, so the
240
+ // budget inside processMatchesPidFile would only multiply their wall time. A probe
241
+ // that fails against a LIVE pid is "unknown" — it proves nothing about ownership, so
242
+ // signalling on it would be unsafe and treating it as "gone" would abandon a host that
243
+ // may still be running. A failed probe against a dead pid is "gone".
244
+ async function resolvePidFileOwnership(pidFile, readStartTime) {
245
+ try {
246
+ return (await processMatchesPidFile(pidFile, readStartTime, processIsLive, { attempts: 1 })) ? "owns" : "gone";
247
+ }
248
+ catch (error) {
249
+ if (error instanceof ProcessIdentityUnreadableError)
250
+ return "unknown";
251
+ throw error;
252
+ }
253
+ }
254
+ async function signalValidated(pidFile, signal, readStartTime = readProcessStartTime) {
255
+ if ((await resolvePidFileOwnership(pidFile, readStartTime)) !== "owns")
202
256
  return;
203
257
  try {
204
258
  process.kill(pidFile.pid, signal);
@@ -208,14 +262,14 @@ async function signalValidated(pidFile, signal) {
208
262
  throw error;
209
263
  }
210
264
  }
211
- async function waitForGone(pidFile, timeoutMs) {
265
+ async function waitForGone(pidFile, timeoutMs, readStartTime = readProcessStartTime) {
212
266
  const deadline = Date.now() + timeoutMs;
213
267
  while (Date.now() <= deadline) {
214
- if (!(await processMatchesPidFile(pidFile)))
268
+ if ((await resolvePidFileOwnership(pidFile, readStartTime)) === "gone")
215
269
  return true;
216
270
  await delay(50);
217
271
  }
218
- return !(await processMatchesPidFile(pidFile));
272
+ return (await resolvePidFileOwnership(pidFile, readStartTime)) === "gone";
219
273
  }
220
274
  async function pollProtocolInfo(socket, timeoutMs, childExit) {
221
275
  const deadline = Date.now() + timeoutMs;
@@ -336,7 +390,12 @@ async function reapOrphanedInternalHostDirs() {
336
390
  owner.processStartTime.length > 0 &&
337
391
  owner.createdAt < Date.now() - 60_000 &&
338
392
  (await readdir(join(tmpdir(), entry.name))).length === 1 &&
339
- !(await processMatchesPidFile({ pid: owner.pid, processStartTime: owner.processStartTime })))
393
+ !(await processMatchesPidFile({ pid: owner.pid, processStartTime: owner.processStartTime }).catch((error) => {
394
+ // Unreadable but live: assume the owner is alive rather than steal its lock.
395
+ if (error instanceof ProcessIdentityUnreadableError)
396
+ return true;
397
+ throw error;
398
+ })))
340
399
  await rm(join(tmpdir(), entry.name), { recursive: true, force: true });
341
400
  }
342
401
  catch { }