@code-yeongyu/senpi 2026.9.24-3 → 2026.9.25
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 +26 -0
- package/dist/bundle/chunks/{app-server-command-OTRHYRIB.js → app-server-command-RB6QUPRI.js} +1 -1
- package/dist/bundle/chunks/{chunk-UM43CBL4.js → chunk-JJ3NJZBD.js} +118 -111
- package/dist/bundle/chunks/chunk-SGYEOIOJ.js +8 -0
- package/dist/bundle/chunks/chunk-YS5YEX4S.js +2 -0
- package/dist/bundle/chunks/{cli-main-TX6I4X4F.js → cli-main-KO53KYDN.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-UI5Q6FGY.js → interactive-mode-ABDNLJSI.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-D6HMI3RQ.js → multi-session-host-JV3MNWXZ.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-3UWH3ZZS.js → package-manager-cli-ZYYAVCXU.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-LXIZHJSJ.js → rpc-mode-Q2OUJURB.js} +1 -1
- package/dist/bundle/chunks/runtime-session-XBT646O5.js +2 -0
- package/dist/bundle/chunks/session-worker.js +184 -177
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +1 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts +7 -0
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +23 -4
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/durable-command.d.ts +11 -16
- package/dist/core/extensions/builtin/terminal/durable-command.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/durable-command.js +97 -43
- package/dist/core/extensions/builtin/terminal/durable-command.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/durable-file.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/durable-file.js +11 -4
- package/dist/core/extensions/builtin/terminal/durable-file.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/extension-state.d.ts +26 -0
- package/dist/core/extensions/builtin/terminal/extension-state.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/extension-state.js +151 -0
- package/dist/core/extensions/builtin/terminal/extension-state.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/extension.js +32 -308
- package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/lease-file.d.ts +52 -0
- package/dist/core/extensions/builtin/terminal/lease-file.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/lease-file.js +205 -0
- package/dist/core/extensions/builtin/terminal/lease-file.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/lease-keeper.d.ts +33 -0
- package/dist/core/extensions/builtin/terminal/lease-keeper.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/lease-keeper.js +110 -0
- package/dist/core/extensions/builtin/terminal/lease-keeper.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/manager.d.ts +1 -0
- package/dist/core/extensions/builtin/terminal/manager.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/manager.js +6 -0
- package/dist/core/extensions/builtin/terminal/manager.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/manifest-lease.d.ts +48 -4
- package/dist/core/extensions/builtin/terminal/manifest-lease.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/manifest-lease.js +141 -59
- package/dist/core/extensions/builtin/terminal/manifest-lease.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +2 -0
- package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-registry.js +15 -10
- package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-state-dir.d.ts +11 -0
- package/dist/core/extensions/builtin/terminal/monitor-state-dir.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/monitor-state-dir.js +28 -0
- package/dist/core/extensions/builtin/terminal/monitor-state-dir.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/orphan-reaper.d.ts +41 -0
- package/dist/core/extensions/builtin/terminal/orphan-reaper.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/orphan-reaper.js +131 -0
- package/dist/core/extensions/builtin/terminal/orphan-reaper.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/process-identity.d.ts +21 -0
- package/dist/core/extensions/builtin/terminal/process-identity.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/process-identity.js +34 -0
- package/dist/core/extensions/builtin/terminal/process-identity.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/process-start-probe.d.ts +6 -0
- package/dist/core/extensions/builtin/terminal/process-start-probe.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/process-start-probe.js +60 -0
- package/dist/core/extensions/builtin/terminal/process-start-probe.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/prompt.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/prompt.js +8 -4
- package/dist/core/extensions/builtin/terminal/prompt.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/restore-digest.d.ts +54 -0
- package/dist/core/extensions/builtin/terminal/restore-digest.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/restore-digest.js +133 -0
- package/dist/core/extensions/builtin/terminal/restore-digest.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/restore-session.d.ts +56 -0
- package/dist/core/extensions/builtin/terminal/restore-session.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/restore-session.js +238 -0
- package/dist/core/extensions/builtin/terminal/restore-session.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/restore.d.ts +52 -19
- package/dist/core/extensions/builtin/terminal/restore.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/restore.js +63 -138
- package/dist/core/extensions/builtin/terminal/restore.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/runtime-session.d.ts +5 -0
- package/dist/core/extensions/builtin/terminal/runtime-session.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/runtime-session.js +17 -0
- package/dist/core/extensions/builtin/terminal/runtime-session.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/session-activity.d.ts +8 -0
- package/dist/core/extensions/builtin/terminal/session-activity.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/session-activity.js +41 -0
- package/dist/core/extensions/builtin/terminal/session-activity.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/shared.d.ts +14 -0
- package/dist/core/extensions/builtin/terminal/shared.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/shared.js +14 -0
- package/dist/core/extensions/builtin/terminal/shared.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/terminal-manifest-model.d.ts +8 -0
- package/dist/core/extensions/builtin/terminal/terminal-manifest-model.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/terminal-manifest-model.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.d.ts +15 -0
- package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.js +148 -0
- package/dist/core/extensions/builtin/terminal/terminal-manifest-parse.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/terminal-manifest.d.ts +15 -2
- package/dist/core/extensions/builtin/terminal/terminal-manifest.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/terminal-manifest.js +62 -11
- package/dist/core/extensions/builtin/terminal/terminal-manifest.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/terminal-state-gc.d.ts +17 -0
- package/dist/core/extensions/builtin/terminal/terminal-state-gc.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/terminal-state-gc.js +81 -0
- package/dist/core/extensions/builtin/terminal/terminal-state-gc.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/tools/context.d.ts +5 -0
- package/dist/core/extensions/builtin/terminal/tools/context.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/context.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/kill-bash.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/kill-bash.js +16 -1
- package/dist/core/extensions/builtin/terminal/tools/kill-bash.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor-manifest-binding.d.ts +27 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor-manifest-binding.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor-manifest-binding.js +75 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor-manifest-binding.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor-schema.d.ts +35 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor-schema.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor-schema.js +67 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor-schema.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +14 -35
- package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor.js +30 -110
- package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/first-turn.d.ts +7 -6
- package/dist/core/extensions/builtin/todotools/first-turn.d.ts.map +1 -1
- package/dist/core/extensions/builtin/todotools/first-turn.js +8 -8
- package/dist/core/extensions/builtin/todotools/first-turn.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/index.js +1 -1
- package/dist/core/extensions/builtin/todotools/index.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/todo-format.js +1 -1
- package/dist/core/extensions/builtin/todotools/todo-format.js.map +1 -1
- package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.d.ts.map +1 -1
- package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.js +4 -0
- package/dist/core/extensions/builtin/ttsr/detectors/repetitive-turns.js.map +1 -1
- package/dist/core/extensions/builtin/ttsr/prompts.js +1 -1
- package/dist/core/extensions/builtin/ttsr/prompts.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +1 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +6 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/docs/extensions.md +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-desktop-engine/README.md +31 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/handshake.d.ts +38 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/handshake.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/handshake.js +117 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/handshake.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/index.d.ts +3 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/index.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/index.js +3 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/index.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/locator.d.ts +42 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/locator.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/locator.js +79 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/dist/locator.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/native/check-prebuild-fresh.mjs +160 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/native/index.d.ts +20 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/native/index.js +23 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/native/prebuilds/darwin-arm64/senpi-desktop-engine +0 -0
- package/node_modules/@code-yeongyu/senpi-desktop-engine/package.json +53 -0
- package/node_modules/@code-yeongyu/senpi-desktop-prelude/README.md +20 -0
- package/node_modules/@code-yeongyu/senpi-desktop-prelude/dist/index.d.ts +2 -0
- package/node_modules/@code-yeongyu/senpi-desktop-prelude/dist/index.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-prelude/dist/index.js +2 -0
- package/node_modules/@code-yeongyu/senpi-desktop-prelude/dist/index.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-prelude/package.json +44 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/README.md +20 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/audit.d.ts +38 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/audit.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/audit.js +2 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/audit.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/call.d.ts +28 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/call.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/call.js +102 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/call.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/engine-schema.generated.d.ts +14 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/engine-schema.generated.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/engine-schema.generated.js +71 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/engine-schema.generated.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/index.d.ts +7 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/index.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/index.js +4 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/index.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/json-rpc.d.ts +66 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/json-rpc.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/json-rpc.js +8 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/json-rpc.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/session.d.ts +44 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/session.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/session.js +2 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/session.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/wire.d.ts +59 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/wire.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/wire.js +2 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/dist/wire.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-protocol/package.json +45 -0
- package/node_modules/@code-yeongyu/senpi-desktop-service/README.md +27 -0
- package/node_modules/@code-yeongyu/senpi-desktop-service/dist/index.d.ts +2 -0
- package/node_modules/@code-yeongyu/senpi-desktop-service/dist/index.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-service/dist/index.js +2 -0
- package/node_modules/@code-yeongyu/senpi-desktop-service/dist/index.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-service/package.json +44 -0
- package/node_modules/@code-yeongyu/senpi-desktop-tool/README.md +20 -0
- package/node_modules/@code-yeongyu/senpi-desktop-tool/dist/index.d.ts +2 -0
- package/node_modules/@code-yeongyu/senpi-desktop-tool/dist/index.d.ts.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-tool/dist/index.js +2 -0
- package/node_modules/@code-yeongyu/senpi-desktop-tool/dist/index.js.map +1 -0
- package/node_modules/@code-yeongyu/senpi-desktop-tool/package.json +44 -0
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cerebras.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.d.ts +2 -0
- package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.js +6 -0
- package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.js.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/session-bun.d.ts +1 -0
- package/node_modules/@earendil-works/pi-pty/dist/session-bun.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/session-bun.js +1 -0
- package/node_modules/@earendil-works/pi-pty/dist/session-bun.js.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/session-native.js +5 -0
- package/node_modules/@earendil-works/pi-pty/dist/session-native.js.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/session-raw-tail.d.ts +11 -0
- package/node_modules/@earendil-works/pi-pty/dist/session-raw-tail.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-pty/dist/session-raw-tail.js +24 -0
- package/node_modules/@earendil-works/pi-pty/dist/session-raw-tail.js.map +1 -0
- package/node_modules/@earendil-works/pi-pty/dist/session-types.d.ts +2 -0
- package/node_modules/@earendil-works/pi-pty/dist/session-types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/session-types.js.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/session.d.ts +4 -5
- package/node_modules/@earendil-works/pi-pty/dist/session.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/session.js +11 -16
- package/node_modules/@earendil-works/pi-pty/dist/session.js.map +1 -1
- package/node_modules/@earendil-works/pi-pty/native/prebuilds/darwin-arm64/senpi_pty.darwin-arm64.node +0 -0
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +22 -7
- package/dist/bundle/chunks/chunk-GFUZPFHN.js +0 -2
- package/dist/bundle/chunks/chunk-JBOV6VNK.js +0 -8
- package/dist/bundle/chunks/runtime-session-F3EXC44D.js +0 -2
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
2
3
|
import { join } from "node:path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { errorCode, publishExclusive, publishReplace, readLeaseText, reclaimInspected, unlinkIfPresent, } from "./lease-file.js";
|
|
5
|
+
import { ownProcessStartedAtMs, PROCESS_START_TOLERANCE_MS, processBootAtMs, sameProcessStart, } from "./process-identity.js";
|
|
6
|
+
import { readProcessStartMs as defaultReadProcessStartMs } from "./process-start-probe.js";
|
|
7
|
+
export const LEASE_RECORD_VERSION = 2;
|
|
8
|
+
const generationTokens = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Tokens held by generations of THIS process that are still running. A same-pid lease is a live
|
|
11
|
+
* holder only when its token is here (a sibling session generation, or a racing waiter that won);
|
|
12
|
+
* a token that is not here was minted by a generation that has shut down, even one whose
|
|
13
|
+
* shutdown never reached the release (the 10 s shutdown cap), so it is ours to re-enter.
|
|
14
|
+
*/
|
|
15
|
+
const liveTokens = new Set();
|
|
16
|
+
export function currentLeaseToken(encodedSessionId) {
|
|
17
|
+
return generationTokens.get(encodedSessionId);
|
|
18
|
+
}
|
|
19
|
+
/** Mark a generation's token dead the moment its shutdown starts, before any slow flush. */
|
|
20
|
+
export function retireLeaseToken(token) {
|
|
21
|
+
liveTokens.delete(token);
|
|
8
22
|
}
|
|
9
23
|
function probeAlive(pid, probe) {
|
|
10
24
|
try {
|
|
@@ -22,84 +36,152 @@ function probeAlive(pid, probe) {
|
|
|
22
36
|
throw error;
|
|
23
37
|
}
|
|
24
38
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
raw = await readFile(path, "utf8");
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
if (errorCode(error) === "ENOENT")
|
|
32
|
-
return "missing";
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
39
|
+
const isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
40
|
+
export function readLeaseRecord(raw) {
|
|
35
41
|
try {
|
|
36
42
|
const parsed = JSON.parse(raw);
|
|
37
43
|
if (typeof parsed !== "object" || parsed === null)
|
|
38
44
|
return "unparseable";
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
if (typeof pid !== "number" ||
|
|
42
|
-
!Number.isFinite(pid) ||
|
|
43
|
-
typeof startedAtMs !== "number" ||
|
|
44
|
-
!Number.isFinite(startedAtMs)) {
|
|
45
|
+
const candidate = parsed;
|
|
46
|
+
if (!isFiniteNumber(candidate.pid) || !isFiniteNumber(candidate.startedAtMs))
|
|
45
47
|
return "unparseable";
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
return {
|
|
49
|
+
pid: candidate.pid,
|
|
50
|
+
startedAtMs: candidate.startedAtMs,
|
|
51
|
+
...(isFiniteNumber(candidate.v) ? { v: candidate.v } : {}),
|
|
52
|
+
...(typeof candidate.token === "string" ? { token: candidate.token } : {}),
|
|
53
|
+
...(isFiniteNumber(candidate.bootAtMs) ? { bootAtMs: candidate.bootAtMs } : {}),
|
|
54
|
+
...(isFiniteNumber(candidate.processStartedAtMs) ? { processStartedAtMs: candidate.processStartedAtMs } : {}),
|
|
55
|
+
...(isFiniteNumber(candidate.acquiredAtMs) ? { acquiredAtMs: candidate.acquiredAtMs } : {}),
|
|
56
|
+
};
|
|
48
57
|
}
|
|
49
58
|
catch {
|
|
50
59
|
return "unparseable";
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
|
-
async function
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
await file.writeFile(JSON.stringify(record), "utf8");
|
|
57
|
-
}
|
|
58
|
-
finally {
|
|
59
|
-
await file.close();
|
|
60
|
-
}
|
|
62
|
+
async function readLeaseFile(path) {
|
|
63
|
+
const raw = await readLeaseText(path);
|
|
64
|
+
return raw === undefined ? "missing" : readLeaseRecord(raw);
|
|
61
65
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Decide what an existing lease is. A pid alone is never trusted: the holder is alive only when
|
|
68
|
+
* its pid answers AND its recorded start instant matches the process now wearing that pid
|
|
69
|
+
* (probed on this cold path only). A boot mismatch alone never reclaims an alive pid. Our own
|
|
70
|
+
* pid is "self" only when the file carries the token this process minted for the session (or a
|
|
71
|
+
* legacy record without one): a multi-session host holds many sessions under one pid, and a
|
|
72
|
+
* lease another live generation in this process owns is a live holder, not a re-entry.
|
|
73
|
+
*/
|
|
74
|
+
export async function classifyLease(existing, self, probes) {
|
|
75
|
+
if (existing.pid === self.pid) {
|
|
76
|
+
if (existing.token !== undefined && (probes.liveTokens ?? liveTokens).has(existing.token))
|
|
77
|
+
return "live-foreign";
|
|
78
|
+
return "self";
|
|
69
79
|
}
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
if (!probeAlive(existing.pid, probes.isProcessAlive))
|
|
81
|
+
return "dead";
|
|
82
|
+
let observedStart;
|
|
72
83
|
try {
|
|
73
|
-
await
|
|
74
|
-
return { acquired: true, path, pid: record.pid };
|
|
84
|
+
observedStart = await probes.readProcessStartMs(existing.pid);
|
|
75
85
|
}
|
|
76
|
-
catch
|
|
77
|
-
|
|
78
|
-
throw error;
|
|
79
|
-
const existing = await readLease(path);
|
|
80
|
-
const reclaimable = existing === "missing" || existing === "unparseable" || !probeAlive(existing.pid, isProcessAlive);
|
|
81
|
-
if (reclaimable && retry) {
|
|
82
|
-
await unlinkIfPresent(path);
|
|
83
|
-
return tryAcquire(path, record, isProcessAlive, false);
|
|
84
|
-
}
|
|
85
|
-
if (existing === "missing" || existing === "unparseable")
|
|
86
|
-
throw error;
|
|
87
|
-
return { acquired: false, holder: existing };
|
|
86
|
+
catch {
|
|
87
|
+
observedStart = undefined;
|
|
88
88
|
}
|
|
89
|
+
if (observedStart === undefined)
|
|
90
|
+
return "live-foreign";
|
|
91
|
+
if (existing.processStartedAtMs === undefined) {
|
|
92
|
+
// A v1 record's startedAtMs is when the lease was taken, so its holder started no later
|
|
93
|
+
// than that; a process that started afterwards on the same pid is a reuse.
|
|
94
|
+
return observedStart <= existing.startedAtMs + PROCESS_START_TOLERANCE_MS ? "live-foreign" : "reused";
|
|
95
|
+
}
|
|
96
|
+
return sameProcessStart(existing.processStartedAtMs, observedStart) ? "live-foreign" : "reused";
|
|
97
|
+
}
|
|
98
|
+
function holderOf(record) {
|
|
99
|
+
return {
|
|
100
|
+
pid: record.pid,
|
|
101
|
+
startedAtMs: record.startedAtMs,
|
|
102
|
+
...(record.bootAtMs !== undefined ? { bootAtMs: record.bootAtMs } : {}),
|
|
103
|
+
};
|
|
89
104
|
}
|
|
90
105
|
export async function acquireTerminalLease(options) {
|
|
91
|
-
const
|
|
92
|
-
const
|
|
106
|
+
const now = (options.now ?? Date.now)();
|
|
107
|
+
const self = options.self ?? {
|
|
108
|
+
pid: options.pid ?? process.pid,
|
|
109
|
+
bootAtMs: processBootAtMs(),
|
|
110
|
+
processStartedAtMs: ownProcessStartedAtMs(),
|
|
111
|
+
};
|
|
112
|
+
const pid = options.pid ?? self.pid;
|
|
93
113
|
const path = join(options.dir, `${options.encodedSessionId}.lease`);
|
|
94
114
|
await mkdir(options.dir, { recursive: true });
|
|
95
|
-
|
|
115
|
+
const token = randomUUID();
|
|
116
|
+
const record = {
|
|
117
|
+
v: LEASE_RECORD_VERSION,
|
|
118
|
+
token,
|
|
119
|
+
pid,
|
|
120
|
+
startedAtMs: now,
|
|
121
|
+
bootAtMs: self.bootAtMs,
|
|
122
|
+
processStartedAtMs: self.processStartedAtMs,
|
|
123
|
+
acquiredAtMs: now,
|
|
124
|
+
};
|
|
125
|
+
const acquired = () => {
|
|
126
|
+
generationTokens.set(options.encodedSessionId, token);
|
|
127
|
+
liveTokens.add(token);
|
|
128
|
+
return { acquired: true, path, pid, token };
|
|
129
|
+
};
|
|
130
|
+
const probes = {
|
|
131
|
+
isProcessAlive: options.isProcessAlive,
|
|
132
|
+
readProcessStartMs: options.readProcessStartMs ?? defaultReadProcessStartMs,
|
|
133
|
+
};
|
|
134
|
+
const content = JSON.stringify(record);
|
|
135
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
136
|
+
try {
|
|
137
|
+
await publishExclusive(path, content);
|
|
138
|
+
return acquired();
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (errorCode(error) !== "EEXIST")
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
const raw = await readLeaseText(path);
|
|
145
|
+
if (raw === undefined)
|
|
146
|
+
continue;
|
|
147
|
+
const existing = readLeaseRecord(raw);
|
|
148
|
+
const verdict = existing === "unparseable" ? "dead" : await classifyLease(existing, { ...self, pid }, probes);
|
|
149
|
+
if (verdict === "self") {
|
|
150
|
+
await publishReplace(path, content);
|
|
151
|
+
return acquired();
|
|
152
|
+
}
|
|
153
|
+
if (verdict === "live-foreign" && existing !== "unparseable")
|
|
154
|
+
return { acquired: false, holder: holderOf(existing) };
|
|
155
|
+
// Only the record judged stale is removed; a racer's fresh lease makes the next try lose cleanly.
|
|
156
|
+
const reclaim = await reclaimInspected(path, raw, probes);
|
|
157
|
+
// Someone is reclaiming right now: wait on that live process, never on the stale record.
|
|
158
|
+
if (reclaim.outcome === "busy" && reclaim.holder !== undefined) {
|
|
159
|
+
const { pid: holderPid, processStartedAtMs, bootAtMs } = reclaim.holder;
|
|
160
|
+
return { acquired: false, holder: { pid: holderPid, startedAtMs: processStartedAtMs, bootAtMs } };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const raw = await readLeaseText(path);
|
|
164
|
+
const last = raw === undefined ? "unparseable" : readLeaseRecord(raw);
|
|
165
|
+
if (last !== "unparseable" && (await classifyLease(last, { ...self, pid }, probes)) === "live-foreign") {
|
|
166
|
+
return { acquired: false, holder: holderOf(last) };
|
|
167
|
+
}
|
|
168
|
+
throw new Error(`terminal lease ${path} kept changing while it was being acquired`);
|
|
96
169
|
}
|
|
170
|
+
/** Release only the generation that acquired: a stale release from an earlier generation is a no-op. */
|
|
97
171
|
export async function releaseTerminalLease(handle) {
|
|
98
|
-
const existing = await
|
|
172
|
+
const existing = await readLeaseFile(handle.path);
|
|
99
173
|
if (existing === "missing" || existing === "unparseable")
|
|
100
174
|
return;
|
|
101
175
|
if (existing.pid !== handle.pid)
|
|
102
176
|
return;
|
|
177
|
+
if (handle.token !== undefined && existing.token !== undefined && existing.token !== handle.token)
|
|
178
|
+
return;
|
|
103
179
|
await unlinkIfPresent(handle.path);
|
|
180
|
+
if (existing.token !== undefined) {
|
|
181
|
+
liveTokens.delete(existing.token);
|
|
182
|
+
for (const [sessionId, token] of generationTokens)
|
|
183
|
+
if (token === existing.token)
|
|
184
|
+
generationTokens.delete(sessionId);
|
|
185
|
+
}
|
|
104
186
|
}
|
|
105
187
|
//# sourceMappingURL=manifest-lease.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-lease.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/manifest-lease.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, open, readFile, unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport type AcquireTerminalLeaseOptions = {\n\tdir: string;\n\tencodedSessionId: string;\n\tpid?: number;\n\tnow?: () => number;\n\tisProcessAlive?: (pid: number) => boolean;\n};\n\nexport type AcquireTerminalLeaseResult =\n\t| { acquired: true; path: string; pid: number }\n\t| { acquired: false; holder: { pid: number; startedAtMs: number } };\n\ntype LeaseRecord = { pid: number; startedAtMs: number };\n\nfunction errorCode(error: unknown): string | undefined {\n\tif (typeof error === \"object\" && error !== null && \"code\" in error && typeof error.code === \"string\") {\n\t\treturn error.code;\n\t}\n\treturn undefined;\n}\n\nfunction probeAlive(pid: number, probe?: (pid: number) => boolean): boolean {\n\ttry {\n\t\tif (probe) return probe(pid);\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\tconst code = errorCode(error);\n\t\tif (code === \"EPERM\") return true;\n\t\tif (code === \"ESRCH\") return false;\n\t\tthrow error;\n\t}\n}\n\nasync function readLease(path: string): Promise<LeaseRecord | \"missing\" | \"unparseable\"> {\n\tlet raw: string;\n\ttry {\n\t\traw = await readFile(path, \"utf8\");\n\t} catch (error) {\n\t\tif (errorCode(error) === \"ENOENT\") return \"missing\";\n\t\tthrow error;\n\t}\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(raw);\n\t\tif (typeof parsed !== \"object\" || parsed === null) return \"unparseable\";\n\t\tconst pid = (parsed as { pid?: unknown }).pid;\n\t\tconst startedAtMs = (parsed as { startedAtMs?: unknown }).startedAtMs;\n\t\tif (\n\t\t\ttypeof pid !== \"number\" ||\n\t\t\t!Number.isFinite(pid) ||\n\t\t\ttypeof startedAtMs !== \"number\" ||\n\t\t\t!Number.isFinite(startedAtMs)\n\t\t) {\n\t\t\treturn \"unparseable\";\n\t\t}\n\t\treturn { pid, startedAtMs };\n\t} catch {\n\t\treturn \"unparseable\";\n\t}\n}\n\nasync function exclusiveCreate(path: string, record: LeaseRecord): Promise<void> {\n\tconst file = await open(path, \"wx\");\n\ttry {\n\t\tawait file.writeFile(JSON.stringify(record), \"utf8\");\n\t} finally {\n\t\tawait file.close();\n\t}\n}\n\nasync function unlinkIfPresent(path: string): Promise<void> {\n\ttry {\n\t\tawait unlink(path);\n\t} catch (error) {\n\t\tif (errorCode(error) !== \"ENOENT\") throw error;\n\t}\n}\n\nasync function tryAcquire(\n\tpath: string,\n\trecord: LeaseRecord,\n\tisProcessAlive: ((pid: number) => boolean) | undefined,\n\tretry: boolean,\n): Promise<AcquireTerminalLeaseResult> {\n\ttry {\n\t\tawait exclusiveCreate(path, record);\n\t\treturn { acquired: true, path, pid: record.pid };\n\t} catch (error) {\n\t\tif (errorCode(error) !== \"EEXIST\") throw error;\n\t\tconst existing = await readLease(path);\n\t\tconst reclaimable =\n\t\t\texisting === \"missing\" || existing === \"unparseable\" || !probeAlive(existing.pid, isProcessAlive);\n\t\tif (reclaimable && retry) {\n\t\t\tawait unlinkIfPresent(path);\n\t\t\treturn tryAcquire(path, record, isProcessAlive, false);\n\t\t}\n\t\tif (existing === \"missing\" || existing === \"unparseable\") throw error;\n\t\treturn { acquired: false, holder: existing };\n\t}\n}\n\nexport async function acquireTerminalLease(options: AcquireTerminalLeaseOptions): Promise<AcquireTerminalLeaseResult> {\n\tconst pid = options.pid ?? process.pid;\n\tconst startedAtMs = (options.now ?? Date.now)();\n\tconst path = join(options.dir, `${options.encodedSessionId}.lease`);\n\tawait mkdir(options.dir, { recursive: true });\n\treturn tryAcquire(path, { pid, startedAtMs }, options.isProcessAlive, true);\n}\n\nexport async function releaseTerminalLease(handle: { path: string; pid: number }): Promise<void> {\n\tconst existing = await readLease(handle.path);\n\tif (existing === \"missing\" || existing === \"unparseable\") return;\n\tif (existing.pid !== handle.pid) return;\n\tawait unlinkIfPresent(handle.path);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"manifest-lease.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/manifest-lease.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport {\n\terrorCode,\n\tpublishExclusive,\n\tpublishReplace,\n\treadLeaseText,\n\treclaimInspected,\n\tunlinkIfPresent,\n} from \"./lease-file.ts\";\nimport {\n\townProcessStartedAtMs,\n\tPROCESS_START_TOLERANCE_MS,\n\tprocessBootAtMs,\n\tsameProcessStart,\n} from \"./process-identity.ts\";\nimport { readProcessStartMs as defaultReadProcessStartMs } from \"./process-start-probe.ts\";\n\nexport const LEASE_RECORD_VERSION = 2;\n\nexport interface LeaseSelfIdentity {\n\treadonly pid: number;\n\treadonly bootAtMs: number;\n\treadonly processStartedAtMs: number;\n}\n\n/**\n * `startedAtMs` duplicates `acquiredAtMs` on purpose: a senpi build that still runs the v1 lease\n * reader parses `{pid, startedAtMs}` and must not see this file as unparseable (which it reclaims).\n */\nexport interface LeaseRecord {\n\treadonly v?: number;\n\treadonly token?: string;\n\treadonly pid: number;\n\treadonly startedAtMs: number;\n\treadonly bootAtMs?: number;\n\treadonly processStartedAtMs?: number;\n\treadonly acquiredAtMs?: number;\n}\n\nexport interface LeaseHolder {\n\treadonly pid: number;\n\treadonly startedAtMs: number;\n\treadonly bootAtMs?: number;\n}\n\nexport type AcquireTerminalLeaseOptions = {\n\tdir: string;\n\tencodedSessionId: string;\n\tpid?: number;\n\tnow?: () => number;\n\tself?: LeaseSelfIdentity;\n\tisProcessAlive?: (pid: number) => boolean;\n\treadProcessStartMs?: (pid: number) => Promise<number | undefined>;\n};\n\nexport type AcquireTerminalLeaseResult =\n\t| { acquired: true; path: string; pid: number; token: string }\n\t| { acquired: false; holder: LeaseHolder };\n\nexport type LeaseClassification = \"self\" | \"dead\" | \"reused\" | \"live-foreign\";\n\nconst generationTokens = new Map<string, string>();\n/**\n * Tokens held by generations of THIS process that are still running. A same-pid lease is a live\n * holder only when its token is here (a sibling session generation, or a racing waiter that won);\n * a token that is not here was minted by a generation that has shut down, even one whose\n * shutdown never reached the release (the 10 s shutdown cap), so it is ours to re-enter.\n */\nconst liveTokens = new Set<string>();\n\nexport function currentLeaseToken(encodedSessionId: string): string | undefined {\n\treturn generationTokens.get(encodedSessionId);\n}\n\n/** Mark a generation's token dead the moment its shutdown starts, before any slow flush. */\nexport function retireLeaseToken(token: string): void {\n\tliveTokens.delete(token);\n}\n\nfunction probeAlive(pid: number, probe?: (pid: number) => boolean): boolean {\n\ttry {\n\t\tif (probe) return probe(pid);\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\tconst code = errorCode(error);\n\t\tif (code === \"EPERM\") return true;\n\t\tif (code === \"ESRCH\") return false;\n\t\tthrow error;\n\t}\n}\n\nconst isFiniteNumber = (value: unknown): value is number => typeof value === \"number\" && Number.isFinite(value);\n\nexport function readLeaseRecord(raw: string): LeaseRecord | \"unparseable\" {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(raw);\n\t\tif (typeof parsed !== \"object\" || parsed === null) return \"unparseable\";\n\t\tconst candidate = parsed as Record<string, unknown>;\n\t\tif (!isFiniteNumber(candidate.pid) || !isFiniteNumber(candidate.startedAtMs)) return \"unparseable\";\n\t\treturn {\n\t\t\tpid: candidate.pid,\n\t\t\tstartedAtMs: candidate.startedAtMs,\n\t\t\t...(isFiniteNumber(candidate.v) ? { v: candidate.v } : {}),\n\t\t\t...(typeof candidate.token === \"string\" ? { token: candidate.token } : {}),\n\t\t\t...(isFiniteNumber(candidate.bootAtMs) ? { bootAtMs: candidate.bootAtMs } : {}),\n\t\t\t...(isFiniteNumber(candidate.processStartedAtMs) ? { processStartedAtMs: candidate.processStartedAtMs } : {}),\n\t\t\t...(isFiniteNumber(candidate.acquiredAtMs) ? { acquiredAtMs: candidate.acquiredAtMs } : {}),\n\t\t};\n\t} catch {\n\t\treturn \"unparseable\";\n\t}\n}\n\nasync function readLeaseFile(path: string): Promise<LeaseRecord | \"missing\" | \"unparseable\"> {\n\tconst raw = await readLeaseText(path);\n\treturn raw === undefined ? \"missing\" : readLeaseRecord(raw);\n}\n\n/**\n * Decide what an existing lease is. A pid alone is never trusted: the holder is alive only when\n * its pid answers AND its recorded start instant matches the process now wearing that pid\n * (probed on this cold path only). A boot mismatch alone never reclaims an alive pid. Our own\n * pid is \"self\" only when the file carries the token this process minted for the session (or a\n * legacy record without one): a multi-session host holds many sessions under one pid, and a\n * lease another live generation in this process owns is a live holder, not a re-entry.\n */\nexport async function classifyLease(\n\texisting: LeaseRecord,\n\tself: LeaseSelfIdentity,\n\tprobes: {\n\t\treadonly isProcessAlive?: (pid: number) => boolean;\n\t\treadonly readProcessStartMs: (pid: number) => Promise<number | undefined>;\n\t\treadonly liveTokens?: ReadonlySet<string>;\n\t},\n): Promise<LeaseClassification> {\n\tif (existing.pid === self.pid) {\n\t\tif (existing.token !== undefined && (probes.liveTokens ?? liveTokens).has(existing.token)) return \"live-foreign\";\n\t\treturn \"self\";\n\t}\n\tif (!probeAlive(existing.pid, probes.isProcessAlive)) return \"dead\";\n\tlet observedStart: number | undefined;\n\ttry {\n\t\tobservedStart = await probes.readProcessStartMs(existing.pid);\n\t} catch {\n\t\tobservedStart = undefined;\n\t}\n\tif (observedStart === undefined) return \"live-foreign\";\n\tif (existing.processStartedAtMs === undefined) {\n\t\t// A v1 record's startedAtMs is when the lease was taken, so its holder started no later\n\t\t// than that; a process that started afterwards on the same pid is a reuse.\n\t\treturn observedStart <= existing.startedAtMs + PROCESS_START_TOLERANCE_MS ? \"live-foreign\" : \"reused\";\n\t}\n\treturn sameProcessStart(existing.processStartedAtMs, observedStart) ? \"live-foreign\" : \"reused\";\n}\n\nfunction holderOf(record: LeaseRecord): LeaseHolder {\n\treturn {\n\t\tpid: record.pid,\n\t\tstartedAtMs: record.startedAtMs,\n\t\t...(record.bootAtMs !== undefined ? { bootAtMs: record.bootAtMs } : {}),\n\t};\n}\n\nexport async function acquireTerminalLease(options: AcquireTerminalLeaseOptions): Promise<AcquireTerminalLeaseResult> {\n\tconst now = (options.now ?? Date.now)();\n\tconst self: LeaseSelfIdentity = options.self ?? {\n\t\tpid: options.pid ?? process.pid,\n\t\tbootAtMs: processBootAtMs(),\n\t\tprocessStartedAtMs: ownProcessStartedAtMs(),\n\t};\n\tconst pid = options.pid ?? self.pid;\n\tconst path = join(options.dir, `${options.encodedSessionId}.lease`);\n\tawait mkdir(options.dir, { recursive: true });\n\tconst token = randomUUID();\n\tconst record: LeaseRecord = {\n\t\tv: LEASE_RECORD_VERSION,\n\t\ttoken,\n\t\tpid,\n\t\tstartedAtMs: now,\n\t\tbootAtMs: self.bootAtMs,\n\t\tprocessStartedAtMs: self.processStartedAtMs,\n\t\tacquiredAtMs: now,\n\t};\n\tconst acquired = (): AcquireTerminalLeaseResult => {\n\t\tgenerationTokens.set(options.encodedSessionId, token);\n\t\tliveTokens.add(token);\n\t\treturn { acquired: true, path, pid, token };\n\t};\n\tconst probes = {\n\t\tisProcessAlive: options.isProcessAlive,\n\t\treadProcessStartMs: options.readProcessStartMs ?? defaultReadProcessStartMs,\n\t};\n\tconst content = JSON.stringify(record);\n\tfor (let attempt = 0; attempt < 3; attempt += 1) {\n\t\ttry {\n\t\t\tawait publishExclusive(path, content);\n\t\t\treturn acquired();\n\t\t} catch (error) {\n\t\t\tif (errorCode(error) !== \"EEXIST\") throw error;\n\t\t}\n\t\tconst raw = await readLeaseText(path);\n\t\tif (raw === undefined) continue;\n\t\tconst existing = readLeaseRecord(raw);\n\t\tconst verdict = existing === \"unparseable\" ? \"dead\" : await classifyLease(existing, { ...self, pid }, probes);\n\t\tif (verdict === \"self\") {\n\t\t\tawait publishReplace(path, content);\n\t\t\treturn acquired();\n\t\t}\n\t\tif (verdict === \"live-foreign\" && existing !== \"unparseable\")\n\t\t\treturn { acquired: false, holder: holderOf(existing) };\n\t\t// Only the record judged stale is removed; a racer's fresh lease makes the next try lose cleanly.\n\t\tconst reclaim = await reclaimInspected(path, raw, probes);\n\t\t// Someone is reclaiming right now: wait on that live process, never on the stale record.\n\t\tif (reclaim.outcome === \"busy\" && reclaim.holder !== undefined) {\n\t\t\tconst { pid: holderPid, processStartedAtMs, bootAtMs } = reclaim.holder;\n\t\t\treturn { acquired: false, holder: { pid: holderPid, startedAtMs: processStartedAtMs, bootAtMs } };\n\t\t}\n\t}\n\tconst raw = await readLeaseText(path);\n\tconst last = raw === undefined ? \"unparseable\" : readLeaseRecord(raw);\n\tif (last !== \"unparseable\" && (await classifyLease(last, { ...self, pid }, probes)) === \"live-foreign\") {\n\t\treturn { acquired: false, holder: holderOf(last) };\n\t}\n\tthrow new Error(`terminal lease ${path} kept changing while it was being acquired`);\n}\n\n/** Release only the generation that acquired: a stale release from an earlier generation is a no-op. */\nexport async function releaseTerminalLease(handle: { path: string; pid: number; token?: string }): Promise<void> {\n\tconst existing = await readLeaseFile(handle.path);\n\tif (existing === \"missing\" || existing === \"unparseable\") return;\n\tif (existing.pid !== handle.pid) return;\n\tif (handle.token !== undefined && existing.token !== undefined && existing.token !== handle.token) return;\n\tawait unlinkIfPresent(handle.path);\n\tif (existing.token !== undefined) {\n\t\tliveTokens.delete(existing.token);\n\t\tfor (const [sessionId, token] of generationTokens)\n\t\t\tif (token === existing.token) generationTokens.delete(sessionId);\n\t}\n}\n"]}
|
|
@@ -111,6 +111,8 @@ export declare class MonitorRegistry {
|
|
|
111
111
|
} | undefined;
|
|
112
112
|
/** Emit one restored-watch line through the SAME sink a live watch uses (coalescing, wake budget). */
|
|
113
113
|
emitFileLine(id: string, line: string): boolean;
|
|
114
|
+
/** Inject one line into a live command or file watch's event stream (e.g. a restore notice). */
|
|
115
|
+
emitLine(id: string, line: string): boolean;
|
|
114
116
|
stopFile(id: string): Promise<boolean>;
|
|
115
117
|
stopAllFiles(): Promise<number>;
|
|
116
118
|
register(options: RegisterMonitorOptions): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"monitor-registry.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/monitor-registry.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"monitor-registry.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/monitor-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -232,6 +232,7 @@ export class MonitorRegistry {
|
|
|
232
232
|
cleanupRegistration();
|
|
233
233
|
throw new Error("Cannot create file monitor: monitor registry is disposed.");
|
|
234
234
|
}
|
|
235
|
+
const persistent = options.persistent ?? options.expiresAt !== undefined;
|
|
235
236
|
const record = {
|
|
236
237
|
id,
|
|
237
238
|
monitorId: options.monitorId ?? allocateMonitorId(),
|
|
@@ -240,10 +241,8 @@ export class MonitorRegistry {
|
|
|
240
241
|
startedAtMs: Date.now(),
|
|
241
242
|
command: null,
|
|
242
243
|
filter: null,
|
|
243
|
-
persistent
|
|
244
|
-
deadlineMs: (options.
|
|
245
|
-
? null
|
|
246
|
-
: (options.deadlineMs ?? Date.now() + options.timeoutMs),
|
|
244
|
+
persistent,
|
|
245
|
+
deadlineMs: persistent ? null : (options.deadlineMs ?? Date.now() + options.timeoutMs),
|
|
247
246
|
fireCount: 0,
|
|
248
247
|
lastFiredAtMs: null,
|
|
249
248
|
expiresAt: options.expiresAt,
|
|
@@ -267,11 +266,13 @@ export class MonitorRegistry {
|
|
|
267
266
|
dirtyPasses: 0,
|
|
268
267
|
dirtyWindowStartedAt: 0,
|
|
269
268
|
checking: undefined,
|
|
270
|
-
deadline:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
this.#
|
|
274
|
-
|
|
269
|
+
deadline: persistent
|
|
270
|
+
? undefined
|
|
271
|
+
: setTimeout(() => {
|
|
272
|
+
const current = this.#files.get(id);
|
|
273
|
+
if (current)
|
|
274
|
+
this.#settleFile(current, "watcher timed_out");
|
|
275
|
+
}, options.timeoutMs),
|
|
275
276
|
};
|
|
276
277
|
this.#files.set(id, record);
|
|
277
278
|
if (this.#parked)
|
|
@@ -292,7 +293,11 @@ export class MonitorRegistry {
|
|
|
292
293
|
}
|
|
293
294
|
/** Emit one restored-watch line through the SAME sink a live watch uses (coalescing, wake budget). */
|
|
294
295
|
emitFileLine(id, line) {
|
|
295
|
-
|
|
296
|
+
return this.#files.has(id) && this.emitLine(id, line);
|
|
297
|
+
}
|
|
298
|
+
/** Inject one line into a live command or file watch's event stream (e.g. a restore notice). */
|
|
299
|
+
emitLine(id, line) {
|
|
300
|
+
const record = this.#records.get(id) ?? this.#files.get(id);
|
|
296
301
|
if (!record || record.settled)
|
|
297
302
|
return false;
|
|
298
303
|
this.#emitForRecord(record, { type: "line", id: record.id, description: record.description, line });
|