pilotswarm-sdk 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/src/protocol.js +7 -0
- package/dist/blob-store.d.ts +10 -1
- package/dist/blob-store.d.ts.map +1 -1
- package/dist/blob-store.js +275 -13
- package/dist/blob-store.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +15 -1
- package/dist/client.js.map +1 -1
- package/dist/cms-migrations.d.ts.map +1 -1
- package/dist/cms-migrations.js +164 -0
- package/dist/cms-migrations.js.map +1 -1
- package/dist/cms.d.ts +6 -0
- package/dist/cms.d.ts.map +1 -1
- package/dist/cms.js +3 -0
- package/dist/cms.js.map +1 -1
- package/dist/fault-injection.d.ts +2 -0
- package/dist/fault-injection.d.ts.map +1 -0
- package/dist/fault-injection.js +45 -0
- package/dist/fault-injection.js.map +1 -0
- package/dist/model-providers.js +1 -1
- package/dist/model-providers.js.map +1 -1
- package/dist/orchestration/agents.d.ts.map +1 -1
- package/dist/orchestration/agents.js +9 -1
- package/dist/orchestration/agents.js.map +1 -1
- package/dist/orchestration/index.d.ts +3 -3
- package/dist/orchestration/index.js +1 -1
- package/dist/orchestration/lifecycle.d.ts +9 -2
- package/dist/orchestration/lifecycle.d.ts.map +1 -1
- package/dist/orchestration/lifecycle.js +21 -55
- package/dist/orchestration/lifecycle.js.map +1 -1
- package/dist/orchestration/runtime.d.ts +1 -1
- package/dist/orchestration/state.d.ts +7 -3
- package/dist/orchestration/state.d.ts.map +1 -1
- package/dist/orchestration/state.js +13 -4
- package/dist/orchestration/state.js.map +1 -1
- package/dist/orchestration/turn.d.ts.map +1 -1
- package/dist/orchestration/turn.js +126 -66
- package/dist/orchestration/turn.js.map +1 -1
- package/dist/orchestration-registry.d.ts.map +1 -1
- package/dist/orchestration-registry.js +8 -2
- package/dist/orchestration-registry.js.map +1 -1
- package/dist/orchestration-version.d.ts +1 -1
- package/dist/orchestration-version.js +1 -1
- package/dist/orchestration.d.ts +2 -2
- package/dist/orchestration.js +4 -4
- package/dist/orchestration_1_0_46.d.ts +1 -1
- package/dist/orchestration_1_0_47.d.ts +1 -1
- package/dist/orchestration_1_0_48.d.ts +1 -1
- package/dist/orchestration_1_0_49.d.ts +1 -1
- package/dist/orchestration_1_0_50.d.ts +1 -1
- package/dist/orchestration_1_0_56/agents.d.ts +41 -0
- package/dist/orchestration_1_0_56/agents.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/agents.js +758 -0
- package/dist/orchestration_1_0_56/agents.js.map +1 -0
- package/dist/orchestration_1_0_56/index.d.ts +24 -0
- package/dist/orchestration_1_0_56/index.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/index.js +13 -0
- package/dist/orchestration_1_0_56/index.js.map +1 -0
- package/dist/orchestration_1_0_56/lifecycle.d.ts +41 -0
- package/dist/orchestration_1_0_56/lifecycle.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/lifecycle.js +580 -0
- package/dist/orchestration_1_0_56/lifecycle.js.map +1 -0
- package/dist/orchestration_1_0_56/queue.d.ts +7 -0
- package/dist/orchestration_1_0_56/queue.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/queue.js +644 -0
- package/dist/orchestration_1_0_56/queue.js.map +1 -0
- package/dist/orchestration_1_0_56/runtime.d.ts +29 -0
- package/dist/orchestration_1_0_56/runtime.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/runtime.js +194 -0
- package/dist/orchestration_1_0_56/runtime.js.map +1 -0
- package/dist/orchestration_1_0_56/state.d.ts +126 -0
- package/dist/orchestration_1_0_56/state.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/state.js +106 -0
- package/dist/orchestration_1_0_56/state.js.map +1 -0
- package/dist/orchestration_1_0_56/turn.d.ts +30 -0
- package/dist/orchestration_1_0_56/turn.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/turn.js +1075 -0
- package/dist/orchestration_1_0_56/turn.js.map +1 -0
- package/dist/orchestration_1_0_56/utils.d.ts +22 -0
- package/dist/orchestration_1_0_56/utils.d.ts.map +1 -0
- package/dist/orchestration_1_0_56/utils.js +226 -0
- package/dist/orchestration_1_0_56/utils.js.map +1 -0
- package/dist/orchestration_1_0_57/agents.d.ts +41 -0
- package/dist/orchestration_1_0_57/agents.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/agents.js +758 -0
- package/dist/orchestration_1_0_57/agents.js.map +1 -0
- package/dist/orchestration_1_0_57/index.d.ts +24 -0
- package/dist/orchestration_1_0_57/index.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/index.js +13 -0
- package/dist/orchestration_1_0_57/index.js.map +1 -0
- package/dist/orchestration_1_0_57/lifecycle.d.ts +49 -0
- package/dist/orchestration_1_0_57/lifecycle.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/lifecycle.js +583 -0
- package/dist/orchestration_1_0_57/lifecycle.js.map +1 -0
- package/dist/orchestration_1_0_57/queue.d.ts +7 -0
- package/dist/orchestration_1_0_57/queue.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/queue.js +644 -0
- package/dist/orchestration_1_0_57/queue.js.map +1 -0
- package/dist/orchestration_1_0_57/runtime.d.ts +29 -0
- package/dist/orchestration_1_0_57/runtime.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/runtime.js +198 -0
- package/dist/orchestration_1_0_57/runtime.js.map +1 -0
- package/dist/orchestration_1_0_57/state.d.ts +133 -0
- package/dist/orchestration_1_0_57/state.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/state.js +115 -0
- package/dist/orchestration_1_0_57/state.js.map +1 -0
- package/dist/orchestration_1_0_57/turn.d.ts +30 -0
- package/dist/orchestration_1_0_57/turn.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/turn.js +1110 -0
- package/dist/orchestration_1_0_57/turn.js.map +1 -0
- package/dist/orchestration_1_0_57/utils.d.ts +22 -0
- package/dist/orchestration_1_0_57/utils.d.ts.map +1 -0
- package/dist/orchestration_1_0_57/utils.js +226 -0
- package/dist/orchestration_1_0_57/utils.js.map +1 -0
- package/dist/orchestration_1_0_58/agents.d.ts +41 -0
- package/dist/orchestration_1_0_58/agents.d.ts.map +1 -0
- package/dist/orchestration_1_0_58/agents.js +758 -0
- package/dist/orchestration_1_0_58/agents.js.map +1 -0
- package/dist/orchestration_1_0_58/index.d.ts +24 -0
- package/dist/orchestration_1_0_58/index.d.ts.map +1 -0
- package/dist/orchestration_1_0_58/index.js +13 -0
- package/dist/orchestration_1_0_58/index.js.map +1 -0
- package/dist/orchestration_1_0_58/lifecycle.d.ts +48 -0
- package/dist/orchestration_1_0_58/lifecycle.d.ts.map +1 -0
- package/dist/orchestration_1_0_58/lifecycle.js +546 -0
- package/dist/orchestration_1_0_58/lifecycle.js.map +1 -0
- package/dist/orchestration_1_0_58/queue.d.ts +7 -0
- package/dist/orchestration_1_0_58/queue.d.ts.map +1 -0
- package/dist/orchestration_1_0_58/queue.js +644 -0
- package/dist/orchestration_1_0_58/queue.js.map +1 -0
- package/dist/orchestration_1_0_58/runtime.d.ts +29 -0
- package/dist/orchestration_1_0_58/runtime.d.ts.map +1 -0
- package/dist/orchestration_1_0_58/runtime.js +199 -0
- package/dist/orchestration_1_0_58/runtime.js.map +1 -0
- package/dist/orchestration_1_0_58/state.d.ts +130 -0
- package/dist/orchestration_1_0_58/state.d.ts.map +1 -0
- package/dist/orchestration_1_0_58/state.js +115 -0
- package/dist/orchestration_1_0_58/state.js.map +1 -0
- package/dist/orchestration_1_0_58/turn.d.ts +30 -0
- package/dist/orchestration_1_0_58/turn.d.ts.map +1 -0
- package/dist/orchestration_1_0_58/turn.js +1107 -0
- package/dist/orchestration_1_0_58/turn.js.map +1 -0
- package/dist/orchestration_1_0_58/utils.d.ts +22 -0
- package/dist/orchestration_1_0_58/utils.d.ts.map +1 -0
- package/dist/orchestration_1_0_58/utils.js +226 -0
- package/dist/orchestration_1_0_58/utils.js.map +1 -0
- package/dist/session-lifecycle.d.ts +114 -0
- package/dist/session-lifecycle.d.ts.map +1 -0
- package/dist/session-lifecycle.js +324 -0
- package/dist/session-lifecycle.js.map +1 -0
- package/dist/session-manager.d.ts +21 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +137 -1
- package/dist/session-manager.js.map +1 -1
- package/dist/session-proxy.d.ts +5 -1
- package/dist/session-proxy.d.ts.map +1 -1
- package/dist/session-proxy.js +1158 -954
- package/dist/session-proxy.js.map +1 -1
- package/dist/session-store.d.ts +48 -3
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +394 -28
- package/dist/session-store.js.map +1 -1
- package/dist/snapshot-protocol.d.ts +101 -0
- package/dist/snapshot-protocol.d.ts.map +1 -0
- package/dist/snapshot-protocol.js +120 -0
- package/dist/snapshot-protocol.js.map +1 -0
- package/dist/types.d.ts +24 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wait-affinity.d.ts +11 -0
- package/dist/wait-affinity.d.ts.map +1 -1
- package/dist/wait-affinity.js +6 -0
- package/dist/wait-affinity.js.map +1 -1
- package/dist/web/web-management-client.d.ts +28 -9
- package/dist/web/web-management-client.d.ts.map +1 -1
- package/dist/web/web-management-client.js +30 -15
- package/dist/web/web-management-client.js.map +1 -1
- package/dist/worker.d.ts +16 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +63 -6
- package/dist/worker.js.map +1 -1
- package/package.json +7 -4
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runTurn preamble/postamble for the session lifecycle protocol
|
|
3
|
+
* (docs/proposals/session-lifecycle-protocol.md §3.2–3.3).
|
|
4
|
+
*
|
|
5
|
+
* The whole lifecycle lives inside the single runTurn activity (principle
|
|
6
|
+
* P5 — no other session activity depends on landing where a previous one
|
|
7
|
+
* did). Reconcile is STORE-WINS (docs/proposals/snapshot-store-wins.md): one
|
|
8
|
+
* probe per turn is the only oracle — never the orchestration's expectation,
|
|
9
|
+
* which goes stale the moment a stopped/zombie turn advances the store the
|
|
10
|
+
* control plane discarded.
|
|
11
|
+
*
|
|
12
|
+
* preamble p1. one probe. Store bears this turn's own turnKey →
|
|
13
|
+
* already-committed recovery (restore, never replay).
|
|
14
|
+
* p2. store empty → trust a clean local dir (only truth), else fresh;
|
|
15
|
+
* legacy snapshot → trust clean local, else hydrate.
|
|
16
|
+
* p3. warm ONLY when a clean local dir's marker matches the store's
|
|
17
|
+
* (version AND content hash); every other state — store ahead
|
|
18
|
+
* (a discarded/foreign turn advanced it — NO fence, we adopt it),
|
|
19
|
+
* a same-version content swap, a torn/dirty dir, or no marker —
|
|
20
|
+
* hydrates the store. Store below the marker is hydrated too and
|
|
21
|
+
* flagged `regressed`.
|
|
22
|
+
* p4. (caller) write the turn sentinel just before the body
|
|
23
|
+
* postamble c1. write .ps-turn-commit.json {turnKey, result} into the dir
|
|
24
|
+
* c2. commit the tar (store.commitSnapshot) at base+1. alreadyCommitted
|
|
25
|
+
* (same turnKey) → restore the winner's snapshot + result. A
|
|
26
|
+
* SnapshotConflictError (store moved off base while we ran) is NOT
|
|
27
|
+
* fatal: the turn is left UNPUBLISHED (superseded), the sentinel
|
|
28
|
+
* stays dirty so the next preamble rehydrates the winner, and the
|
|
29
|
+
* caller emits `session.snapshot_unpublished`. A user-stopped
|
|
30
|
+
* result skips the commit entirely (also unpublished).
|
|
31
|
+
* c3. write the local version marker (published turns only)
|
|
32
|
+
* c4. clear the turn sentinel (published turns only)
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
import fs from "node:fs";
|
|
37
|
+
import path from "node:path";
|
|
38
|
+
import { faultPoint } from "./fault-injection.js";
|
|
39
|
+
import { SnapshotConflictError, clearTurnSentinel, readSnapshotMarker, readTurnCommitFile, readTurnSentinel, writeSnapshotMarker, writeTurnCommitFile, } from "./snapshot-protocol.js";
|
|
40
|
+
const COMMIT_TRANSIENT_RETRIES = 3;
|
|
41
|
+
// Tunable so fault-injection tests don't pay real backoff delays.
|
|
42
|
+
const COMMIT_RETRY_BASE_DELAY_MS = Number.parseInt(process.env.PILOTSWARM_COMMIT_RETRY_DELAY_MS ?? "", 10) || 1_000;
|
|
43
|
+
function sessionDirOf(ctx) {
|
|
44
|
+
return path.join(ctx.sessionStateDir, ctx.sessionId);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A marker alone is not proof the dir survived intact (a crashed recursive
|
|
48
|
+
* delete removes files in arbitrary order and can leave the marker while
|
|
49
|
+
* the session files are gone). Require the SDK's layout anchor before
|
|
50
|
+
* trusting local files without consulting the store.
|
|
51
|
+
*/
|
|
52
|
+
function hasUsableSessionLayout(sessionDir) {
|
|
53
|
+
return fs.existsSync(path.join(sessionDir, "workspace.yaml"));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Content-identity gate for the warm fast path. Only a mismatch between two
|
|
57
|
+
* PRESENT hashes forces a hydrate (a rule-breaking restore that overwrote the
|
|
58
|
+
* same version number with different content — the ETag CAS can't catch that,
|
|
59
|
+
* §5.1). When either side lacks a hash (a legacy marker/probe), fall back to
|
|
60
|
+
* version-only trust so an upgrade doesn't churn every warm worker into a
|
|
61
|
+
* spurious one-time hydrate.
|
|
62
|
+
*/
|
|
63
|
+
function contentMatches(markerHash, probeHash) {
|
|
64
|
+
if (markerHash && probeHash)
|
|
65
|
+
return markerHash === probeHash;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
function markerFromHydrate(sessionDir, hydrated) {
|
|
69
|
+
writeSnapshotMarker(sessionDir, {
|
|
70
|
+
version: hydrated.version,
|
|
71
|
+
...(hydrated.turnKey ? { turnKey: hydrated.turnKey } : {}),
|
|
72
|
+
...(hydrated.contentHash ? { contentHash: hydrated.contentHash } : {}),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/** Restore the committed snapshot and return the stored result (§3.2 r1–r3). */
|
|
76
|
+
async function recoverAlreadyCommitted(ctx) {
|
|
77
|
+
const sessionDir = sessionDirOf(ctx);
|
|
78
|
+
await ctx.dropWarmSession();
|
|
79
|
+
const hydrated = await ctx.store.hydrateSnapshot(ctx.sessionId);
|
|
80
|
+
markerFromHydrate(sessionDir, hydrated);
|
|
81
|
+
clearTurnSentinel(sessionDir);
|
|
82
|
+
const commitFile = readTurnCommitFile(sessionDir);
|
|
83
|
+
if (commitFile && commitFile.turnKey === ctx.turnKey) {
|
|
84
|
+
ctx.trace(`session=${ctx.sessionId} already-committed recovery: store at v${hydrated.version} ` +
|
|
85
|
+
`under this turnKey; returning stored result without re-running the turn`);
|
|
86
|
+
return { kind: "already-committed", version: hydrated.version, result: commitFile.result };
|
|
87
|
+
}
|
|
88
|
+
// Snapshot claims our turnKey but carries no readable committed result —
|
|
89
|
+
// degrade to a hydrated re-run (duplicate execution beats a wrong result).
|
|
90
|
+
ctx.trace(`session=${ctx.sessionId} store bears this turnKey (v${hydrated.version}) but the ` +
|
|
91
|
+
`turn-commit file is missing/unreadable; degrading to hydrated re-run`);
|
|
92
|
+
return { kind: "hydrated", baseVersion: hydrated.version, storeBehindExpected: false };
|
|
93
|
+
}
|
|
94
|
+
async function resolveFromStore(ctx, regressed) {
|
|
95
|
+
const sessionDir = sessionDirOf(ctx);
|
|
96
|
+
await ctx.dropWarmSession();
|
|
97
|
+
const hydrated = await ctx.store.hydrateSnapshot(ctx.sessionId);
|
|
98
|
+
markerFromHydrate(sessionDir, hydrated);
|
|
99
|
+
clearTurnSentinel(sessionDir);
|
|
100
|
+
const storeBehindExpected = hydrated.version < ctx.expectedVersion;
|
|
101
|
+
if (storeBehindExpected) {
|
|
102
|
+
ctx.trace(`session=${ctx.sessionId} store snapshot v${hydrated.version} is BEHIND the orchestration's ` +
|
|
103
|
+
`expected v${ctx.expectedVersion} (store restored from backup?); proceeding from stored state`);
|
|
104
|
+
}
|
|
105
|
+
if (regressed) {
|
|
106
|
+
ctx.trace(`session=${ctx.sessionId} store snapshot v${regressed.storeVersion} is BELOW the local marker ` +
|
|
107
|
+
`v${regressed.markerVersion} (store restored/lost data); store wins — hydrating v${hydrated.version}`);
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
kind: "hydrated",
|
|
111
|
+
baseVersion: hydrated.version,
|
|
112
|
+
storeBehindExpected,
|
|
113
|
+
...(regressed ? { regressed } : {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Preamble (store-wins). Runs under the per-session run-turn lock, before
|
|
118
|
+
* getOrCreate. ONE probe per turn is the reconcile oracle — never the
|
|
119
|
+
* orchestration's `expectedVersion`, which goes stale the moment a stopped or
|
|
120
|
+
* zombie turn advances the store the control plane discarded (the divergence
|
|
121
|
+
* this protocol removes). On return the local dir is in exactly one state:
|
|
122
|
+
* trusted at `baseVersion` (warm — local marker matches the store's
|
|
123
|
+
* version+hash), restored at the stored version (hydrated — the store wins),
|
|
124
|
+
* absent (fresh), or restored at this turn's committed version
|
|
125
|
+
* (already-committed — the caller returns the stored result without a body).
|
|
126
|
+
*/
|
|
127
|
+
export async function runTurnPreamble(ctx) {
|
|
128
|
+
const sessionDir = sessionDirOf(ctx);
|
|
129
|
+
const sentinel = readTurnSentinel(sessionDir);
|
|
130
|
+
const marker = readSnapshotMarker(sessionDir);
|
|
131
|
+
const localDirUsable = fs.existsSync(sessionDir) && hasUsableSessionLayout(sessionDir);
|
|
132
|
+
// Store-wins: one metadata probe is the ONLY oracle. `expectedVersion`
|
|
133
|
+
// rides in the input for frozen orchestration versions but is load-bearing
|
|
134
|
+
// for nothing here except the lossy-replay observability flag.
|
|
135
|
+
const probe = await ctx.store.probeSnapshot(ctx.sessionId);
|
|
136
|
+
if (sentinel) {
|
|
137
|
+
ctx.trace(`session=${ctx.sessionId} turn sentinel present (started ${sentinel.startedAt || "unknown"}); ` +
|
|
138
|
+
`local dir untrusted, resolving from store`);
|
|
139
|
+
}
|
|
140
|
+
// Idempotency (keeper): the store bears THIS turn's own key — a prior
|
|
141
|
+
// attempt of this very turn already committed (crash-after-commit, or a
|
|
142
|
+
// racing drain/lock-steal duplicate). Restore, never replay.
|
|
143
|
+
if (probe.exists && probe.turnKey && probe.turnKey === ctx.turnKey) {
|
|
144
|
+
return recoverAlreadyCommitted(ctx);
|
|
145
|
+
}
|
|
146
|
+
// Store holds nothing.
|
|
147
|
+
if (!probe.exists) {
|
|
148
|
+
// A clean, usable local dir is the best (only) data — trust it over a
|
|
149
|
+
// fresh replay; the fresh chain publishes from here.
|
|
150
|
+
if (!sentinel && localDirUsable) {
|
|
151
|
+
return { kind: "warm", baseVersion: marker?.version ?? 0 };
|
|
152
|
+
}
|
|
153
|
+
// Dirty or unusable local + empty store → fresh; lossy iff turns had
|
|
154
|
+
// been committed before (marker present, or a frozen orchestration
|
|
155
|
+
// still reports a nonzero expected version). W3 in the protocol doc.
|
|
156
|
+
await ctx.dropWarmSession();
|
|
157
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
158
|
+
return { kind: "fresh", baseVersion: 0, lossy: (marker?.version ?? ctx.expectedVersion) > 0 };
|
|
159
|
+
}
|
|
160
|
+
// Legacy snapshot (pre-protocol, no version metadata, counts as v0): a
|
|
161
|
+
// clean local dir is still trustworthy; otherwise hydrate what's stored.
|
|
162
|
+
if (probe.legacy) {
|
|
163
|
+
if (!sentinel && localDirUsable) {
|
|
164
|
+
return { kind: "warm", baseVersion: marker?.version ?? 0 };
|
|
165
|
+
}
|
|
166
|
+
return resolveFromStore(ctx);
|
|
167
|
+
}
|
|
168
|
+
// Regressed: the store sits BELOW this worker's own marker — a restore from
|
|
169
|
+
// an older backup, or store data loss. Anomalous (surfaced by the caller),
|
|
170
|
+
// but the store still wins: we hydrate what it holds.
|
|
171
|
+
const regressed = marker && probe.version < marker.version
|
|
172
|
+
? { markerVersion: marker.version, storeVersion: probe.version }
|
|
173
|
+
: undefined;
|
|
174
|
+
// Warm fast path: a clean local dir whose marker names EXACTLY the stored
|
|
175
|
+
// state — same version AND (when both carry a hash) same content. The
|
|
176
|
+
// layout anchor guards a marker orphaned by a torn delete; the hash guards
|
|
177
|
+
// a rule-breaking same-version restore (§5.1). Only branch that trusts
|
|
178
|
+
// local files without hydrating.
|
|
179
|
+
if (!sentinel && localDirUsable && marker
|
|
180
|
+
&& marker.version === probe.version
|
|
181
|
+
&& contentMatches(marker.contentHash, probe.contentHash)) {
|
|
182
|
+
return { kind: "warm", baseVersion: marker.version };
|
|
183
|
+
}
|
|
184
|
+
// Store wins for everything else: store AHEAD (a discarded/foreign turn
|
|
185
|
+
// advanced it — the incident; adopt it, no zombie fence), a same-version
|
|
186
|
+
// content swap, a torn/dirty dir, or no marker.
|
|
187
|
+
return resolveFromStore(ctx, regressed);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Last-resort recovery when getOrCreate fails on missing/lost local state
|
|
191
|
+
* DESPITE the preamble's resolution (e.g. the SDK refused to resume from
|
|
192
|
+
* intact-looking files): restore the committed snapshot if one exists.
|
|
193
|
+
* Returns the hydrated version, or null when the store holds nothing —
|
|
194
|
+
* only then may the caller fall back to the lossy fresh replay, whose
|
|
195
|
+
* store-deleting reset is a no-op against an empty store.
|
|
196
|
+
*/
|
|
197
|
+
export async function attemptStoreRecovery(ctx) {
|
|
198
|
+
const probe = await ctx.store.probeSnapshot(ctx.sessionId);
|
|
199
|
+
if (!probe.exists)
|
|
200
|
+
return null;
|
|
201
|
+
const sessionDir = sessionDirOf(ctx);
|
|
202
|
+
await ctx.dropWarmSession();
|
|
203
|
+
const hydrated = await ctx.store.hydrateSnapshot(ctx.sessionId);
|
|
204
|
+
markerFromHydrate(sessionDir, hydrated);
|
|
205
|
+
clearTurnSentinel(sessionDir);
|
|
206
|
+
return hydrated.version;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Postamble (c1–c4). Runs under the per-session run-turn lock after the
|
|
210
|
+
* body produced `result`. Transient store failures retry in place; a CAS
|
|
211
|
+
* conflict (foreign writer) always throws — the activity must fail loudly.
|
|
212
|
+
* An `alreadyCommitted` CAS outcome (a racing duplicate of this same turn
|
|
213
|
+
* won) triggers restore-not-replay: the winner's snapshot and result are
|
|
214
|
+
* adopted, discarding this attempt's divergent local state.
|
|
215
|
+
*/
|
|
216
|
+
export async function runTurnCommit(ctx, baseVersion, result) {
|
|
217
|
+
// Stop-turn divergence guard: a user-stopped turn (result type "stopped")
|
|
218
|
+
// is discarded by the orchestration (stop won the ctx.race →
|
|
219
|
+
// handleTurnStopped; this runTurn is the dropped race loser) and its
|
|
220
|
+
// turnKey is never re-run. Committing its snapshot would advance the
|
|
221
|
+
// stored version while the orchestration keeps state.snapshotVersion at the
|
|
222
|
+
// base — leaving the store one ahead so every later turn fails the CAS
|
|
223
|
+
// (zombie-duplicate fence: "expected N, found N+1"). Skip the commit and
|
|
224
|
+
// leave the .ps-turn-inprogress sentinel in place so the next preamble
|
|
225
|
+
// re-hydrates the base version and discards this partial turn. (Drain and
|
|
226
|
+
// lock-steal classify as "cancelled", not "stopped", and DO commit so their
|
|
227
|
+
// same-turnKey re-run adopts the already-committed snapshot.)
|
|
228
|
+
if (result?.type === "stopped") {
|
|
229
|
+
ctx.trace(`session=${ctx.sessionId} user-stopped turn: skipping snapshot commit at ` +
|
|
230
|
+
`base v${baseVersion}; sentinel left dirty for next-turn re-hydrate`);
|
|
231
|
+
return {
|
|
232
|
+
version: baseVersion,
|
|
233
|
+
contentHash: "",
|
|
234
|
+
alreadyCommitted: false,
|
|
235
|
+
published: false,
|
|
236
|
+
unpublishedReason: "stopped",
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const sessionDir = sessionDirOf(ctx);
|
|
240
|
+
writeTurnCommitFile(sessionDir, ctx.turnKey, result);
|
|
241
|
+
faultPoint("turn.commit.before-cas");
|
|
242
|
+
let lastError;
|
|
243
|
+
for (let attempt = 1; attempt <= COMMIT_TRANSIENT_RETRIES; attempt++) {
|
|
244
|
+
try {
|
|
245
|
+
const committed = await ctx.store.commitSnapshot(ctx.sessionId, {
|
|
246
|
+
baseVersion,
|
|
247
|
+
turnKey: ctx.turnKey,
|
|
248
|
+
});
|
|
249
|
+
faultPoint("turn.commit.after-cas");
|
|
250
|
+
if (committed.alreadyCommitted) {
|
|
251
|
+
// §3.2 restore-not-replay: a racing attempt of this same
|
|
252
|
+
// turn committed first. Its snapshot is the durable lineage;
|
|
253
|
+
// this attempt's local files diverge and must be replaced.
|
|
254
|
+
ctx.trace(`session=${ctx.sessionId} commit found this turn already committed at ` +
|
|
255
|
+
`v${committed.version} by a racing attempt; restoring the committed state`);
|
|
256
|
+
await ctx.dropWarmSession();
|
|
257
|
+
const hydrated = await ctx.store.hydrateSnapshot(ctx.sessionId);
|
|
258
|
+
markerFromHydrate(sessionDir, hydrated);
|
|
259
|
+
clearTurnSentinel(sessionDir);
|
|
260
|
+
const commitFile = readTurnCommitFile(sessionDir);
|
|
261
|
+
return {
|
|
262
|
+
version: committed.version,
|
|
263
|
+
contentHash: hydrated.contentHash ?? committed.contentHash,
|
|
264
|
+
...(hydrated.sizeBytes != null ? { sizeBytes: hydrated.sizeBytes } : {}),
|
|
265
|
+
...(hydrated.rawSizeBytes != null ? { rawSizeBytes: hydrated.rawSizeBytes } : {}),
|
|
266
|
+
alreadyCommitted: true,
|
|
267
|
+
published: true,
|
|
268
|
+
...(commitFile && commitFile.turnKey === ctx.turnKey
|
|
269
|
+
? { storedResult: commitFile.result }
|
|
270
|
+
: {}),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
writeSnapshotMarker(sessionDir, {
|
|
274
|
+
version: committed.version,
|
|
275
|
+
turnKey: ctx.turnKey,
|
|
276
|
+
contentHash: committed.contentHash,
|
|
277
|
+
});
|
|
278
|
+
faultPoint("turn.commit.after-marker");
|
|
279
|
+
clearTurnSentinel(sessionDir);
|
|
280
|
+
faultPoint("turn.commit.after-sentinel-clear");
|
|
281
|
+
return {
|
|
282
|
+
version: committed.version,
|
|
283
|
+
contentHash: committed.contentHash,
|
|
284
|
+
...(committed.sizeBytes != null ? { sizeBytes: committed.sizeBytes } : {}),
|
|
285
|
+
...(committed.rawSizeBytes != null ? { rawSizeBytes: committed.rawSizeBytes } : {}),
|
|
286
|
+
alreadyCommitted: false,
|
|
287
|
+
published: true,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
if (error instanceof SnapshotConflictError) {
|
|
292
|
+
// Store-wins: a turn the control plane discarded — or a foreign
|
|
293
|
+
// writer — advanced the store off our base while this turn ran.
|
|
294
|
+
// Do NOT brick (the old zombie-duplicate fence). Give up this
|
|
295
|
+
// publish: leave the sentinel dirty so the next preamble
|
|
296
|
+
// rehydrates the winner. The turn's result still returns to the
|
|
297
|
+
// orchestration; only its snapshot memory is superseded, and
|
|
298
|
+
// the caller emits `session.snapshot_unpublished{superseded}`.
|
|
299
|
+
ctx.trace(`session=${ctx.sessionId} commit superseded: store advanced off base v${baseVersion} ` +
|
|
300
|
+
`while this turn ran (${error.message}); leaving turn unpublished, sentinel dirty`);
|
|
301
|
+
return {
|
|
302
|
+
version: baseVersion,
|
|
303
|
+
contentHash: "",
|
|
304
|
+
alreadyCommitted: false,
|
|
305
|
+
published: false,
|
|
306
|
+
unpublishedReason: "superseded",
|
|
307
|
+
observedStoreVersion: error.storedVersion,
|
|
308
|
+
...(error.storedTurnKey ? { observedStoreTurnKey: error.storedTurnKey } : {}),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
lastError = error;
|
|
312
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
313
|
+
ctx.trace(`session=${ctx.sessionId} turn commit attempt ${attempt}/${COMMIT_TRANSIENT_RETRIES} ` +
|
|
314
|
+
`failed: ${message}`);
|
|
315
|
+
if (attempt < COMMIT_TRANSIENT_RETRIES) {
|
|
316
|
+
await new Promise((resolve) => setTimeout(resolve, COMMIT_RETRY_BASE_DELAY_MS * attempt));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
throw lastError instanceof Error
|
|
321
|
+
? lastError
|
|
322
|
+
: new Error(`Turn commit failed for ${ctx.sessionId}: ${String(lastError)}`);
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=session-lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-lifecycle.js","sourceRoot":"","sources":["../src/session-lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -123,6 +123,8 @@ export declare class SessionManager {
|
|
|
123
123
|
private _getLineageSessionIds;
|
|
124
124
|
/** Per-session critical sections; protects the SDK session handle and local session.db. */
|
|
125
125
|
private sessionLocks;
|
|
126
|
+
/** Last local activity per session — feeds the autonomous eviction clock. */
|
|
127
|
+
private sessionLastTouchedAt;
|
|
126
128
|
constructor(githubToken?: string | undefined, sessionStore?: SessionStateStore | null, workerDefaults?: WorkerDefaults, sessionStateDir?: string);
|
|
127
129
|
/** Store full config (with tools/hooks) for a session. Called by PilotSwarmClient. */
|
|
128
130
|
setConfig(sessionId: string, config: ManagedSessionConfig): void;
|
|
@@ -192,6 +194,16 @@ export declare class SessionManager {
|
|
|
192
194
|
withRunTurnLock<T>(sessionId: string, operation: string, fn: () => Promise<T>, options?: {
|
|
193
195
|
trace?: SessionTraceWriter;
|
|
194
196
|
}): Promise<T>;
|
|
197
|
+
/**
|
|
198
|
+
* Autonomous eviction sweep (lifecycle protocol §3.4): local session
|
|
199
|
+
* state is a cache. A session idle past `evictAfterMs` is reclaimed
|
|
200
|
+
* without telling anyone — sessions with a committed snapshot marker
|
|
201
|
+
* are simply destroyed + deleted (the store already holds their state;
|
|
202
|
+
* the next runTurn self-validates and hydrates); unmarked (legacy)
|
|
203
|
+
* sessions are dehydrated the old way so their only copy is preserved.
|
|
204
|
+
* Returns the number of sessions reclaimed.
|
|
205
|
+
*/
|
|
206
|
+
sweepIdleSessions(evictAfterMs: number): Promise<number>;
|
|
195
207
|
/**
|
|
196
208
|
* Stop-turn interrupt primitive: abort the warm session's in-flight turn.
|
|
197
209
|
*
|
|
@@ -223,6 +235,15 @@ export declare class SessionManager {
|
|
|
223
235
|
private _getOrCreateUnlocked;
|
|
224
236
|
/** Get a session by ID (null if not in memory on this node). */
|
|
225
237
|
get(sessionId: string): ManagedSession | null;
|
|
238
|
+
/** Root directory holding per-session state dirs. */
|
|
239
|
+
getSessionStateDir(): string;
|
|
240
|
+
/**
|
|
241
|
+
* Destroy the in-memory ManagedSession only — disk state untouched.
|
|
242
|
+
* Used by the lifecycle preamble before overwriting local files with a
|
|
243
|
+
* hydrated snapshot (a warm session bound to the old files must not
|
|
244
|
+
* survive the swap). Caller holds the per-session run-turn lock.
|
|
245
|
+
*/
|
|
246
|
+
dropWarmSession(sessionId: string): Promise<void>;
|
|
226
247
|
/**
|
|
227
248
|
* Dehydrate a session: snapshot to the session store, release in-memory state.
|
|
228
249
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAuE,KAAK,IAAI,EAAE,MAAM,qBAAqB,CAAC;AACpI,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAgC,KAAK,eAAe,EAAE,KAAK,oBAAoB,EAAE,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAC3I,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAIlE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/session-manager.js
CHANGED
|
@@ -9,6 +9,7 @@ import { buildKnowledgePromptBlocks, loadKnowledgeIndexFromFactStore, buildEnhan
|
|
|
9
9
|
import { composeStructuredSystemMessage, extractPromptContent, mergePromptSections } from "./prompt-layering.js";
|
|
10
10
|
import { buildPromptLayersEventPayload } from "./prompt-layers.js";
|
|
11
11
|
import { approvePermissionForSession } from "./permissions.js";
|
|
12
|
+
import { readSnapshotMarker, supportsVersionedSnapshots } from "./snapshot-protocol.js";
|
|
12
13
|
import fs from "node:fs";
|
|
13
14
|
import path from "node:path";
|
|
14
15
|
import os from "node:os";
|
|
@@ -148,6 +149,8 @@ export class SessionManager {
|
|
|
148
149
|
_getLineageSessionIds = null;
|
|
149
150
|
/** Per-session critical sections; protects the SDK session handle and local session.db. */
|
|
150
151
|
sessionLocks = new Map();
|
|
152
|
+
/** Last local activity per session — feeds the autonomous eviction clock. */
|
|
153
|
+
sessionLastTouchedAt = new Map();
|
|
151
154
|
constructor(githubToken, sessionStore, workerDefaults, sessionStateDir) {
|
|
152
155
|
this.githubToken = githubToken;
|
|
153
156
|
this.sessionStore = sessionStore ?? null;
|
|
@@ -433,7 +436,119 @@ export class SessionManager {
|
|
|
433
436
|
}
|
|
434
437
|
}
|
|
435
438
|
async withRunTurnLock(sessionId, operation, fn, options) {
|
|
436
|
-
|
|
439
|
+
this.sessionLastTouchedAt.set(sessionId, Date.now());
|
|
440
|
+
try {
|
|
441
|
+
return await this._withSessionLock(sessionId, operation, fn, options);
|
|
442
|
+
}
|
|
443
|
+
finally {
|
|
444
|
+
this.sessionLastTouchedAt.set(sessionId, Date.now());
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Autonomous eviction sweep (lifecycle protocol §3.4): local session
|
|
449
|
+
* state is a cache. A session idle past `evictAfterMs` is reclaimed
|
|
450
|
+
* without telling anyone — sessions with a committed snapshot marker
|
|
451
|
+
* are simply destroyed + deleted (the store already holds their state;
|
|
452
|
+
* the next runTurn self-validates and hydrates); unmarked (legacy)
|
|
453
|
+
* sessions are dehydrated the old way so their only copy is preserved.
|
|
454
|
+
* Returns the number of sessions reclaimed.
|
|
455
|
+
*/
|
|
456
|
+
async sweepIdleSessions(evictAfterMs) {
|
|
457
|
+
if (!(evictAfterMs > 0))
|
|
458
|
+
return 0;
|
|
459
|
+
const now = Date.now();
|
|
460
|
+
// Only sessions THIS manager has actually served since boot are
|
|
461
|
+
// eviction candidates. Stranger dirs on disk (leftovers from a
|
|
462
|
+
// previous container life, or another embedded worker sharing the
|
|
463
|
+
// sessionStateDir) must never be pushed to the store — a stale dir
|
|
464
|
+
// dehydrated over a newer snapshot silently rolls the session back.
|
|
465
|
+
const candidates = new Set([
|
|
466
|
+
...this.sessions.keys(),
|
|
467
|
+
...this.sessionLastTouchedAt.keys(),
|
|
468
|
+
]);
|
|
469
|
+
let reclaimed = 0;
|
|
470
|
+
for (const sessionId of candidates) {
|
|
471
|
+
if (this.sessionLocks.has(sessionId))
|
|
472
|
+
continue; // busy — never race a turn
|
|
473
|
+
const sessionDir = path.join(this.sessionStateDir, sessionId);
|
|
474
|
+
const lastTouched = this.sessionLastTouchedAt.get(sessionId);
|
|
475
|
+
if (lastTouched == null || now - lastTouched < evictAfterMs)
|
|
476
|
+
continue;
|
|
477
|
+
try {
|
|
478
|
+
await this._withSessionLock(sessionId, "eviction", async () => {
|
|
479
|
+
// Re-check under the lock — a turn may have landed.
|
|
480
|
+
const touched = this.sessionLastTouchedAt.get(sessionId);
|
|
481
|
+
if (touched != null && Date.now() - touched < evictAfterMs)
|
|
482
|
+
return;
|
|
483
|
+
const dirExists = fs.existsSync(sessionDir);
|
|
484
|
+
const committedMarker = dirExists ? readSnapshotMarker(sessionDir) : null;
|
|
485
|
+
const existing = this.sessions.get(sessionId);
|
|
486
|
+
if (existing) {
|
|
487
|
+
try {
|
|
488
|
+
await existing.destroy();
|
|
489
|
+
}
|
|
490
|
+
catch { }
|
|
491
|
+
this.sessions.delete(sessionId);
|
|
492
|
+
}
|
|
493
|
+
if (!dirExists) {
|
|
494
|
+
this.sessionLastTouchedAt.delete(sessionId);
|
|
495
|
+
reclaimed++;
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (committedMarker) {
|
|
499
|
+
// Committed snapshot in the store — pure local delete.
|
|
500
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
501
|
+
emitSessionManagerTrace(sessionId, `evicted (committed at v${committedMarker.version}); local cache reclaimed`);
|
|
502
|
+
}
|
|
503
|
+
else if (this.sessionStore) {
|
|
504
|
+
// Unmarked dir. If the store already holds a VERSIONED
|
|
505
|
+
// chain for this session, the local files are a stale
|
|
506
|
+
// cache at best — reclaim without writing (a legacy
|
|
507
|
+
// dehydrate would destroy the CAS metadata and could
|
|
508
|
+
// roll the session back).
|
|
509
|
+
const versioned = supportsVersionedSnapshots(this.sessionStore)
|
|
510
|
+
? await this.sessionStore.probeSnapshot(sessionId).catch(() => null)
|
|
511
|
+
: null;
|
|
512
|
+
if (versioned?.exists && !versioned.legacy) {
|
|
513
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
514
|
+
emitSessionManagerTrace(sessionId, `evicted (store versioned at v${versioned.version}); stale unmarked cache reclaimed`);
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
// Legacy session: its local files may be the only copy.
|
|
518
|
+
await this._dehydrateUnlocked(sessionId, "eviction");
|
|
519
|
+
emitSessionManagerTrace(sessionId, "evicted via legacy dehydrate (no committed marker)");
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
return; // no store — leave local state alone
|
|
524
|
+
}
|
|
525
|
+
this.sessionLastTouchedAt.delete(sessionId);
|
|
526
|
+
this.sessionClientKeys.delete(sessionId);
|
|
527
|
+
reclaimed++;
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
532
|
+
console.warn(`[SessionManager] eviction sweep skipped ${sessionId}: ${message}`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
// Reap crash-orphaned hydrate temp roots (finally blocks don't run
|
|
536
|
+
// on SIGKILL). They are dot-prefixed and never legitimate sessions.
|
|
537
|
+
try {
|
|
538
|
+
for (const entry of fs.readdirSync(this.sessionStateDir, { withFileTypes: true })) {
|
|
539
|
+
if (!entry.isDirectory() || !entry.name.startsWith(".ps-hydrate-"))
|
|
540
|
+
continue;
|
|
541
|
+
const orphan = path.join(this.sessionStateDir, entry.name);
|
|
542
|
+
try {
|
|
543
|
+
if (now - fs.statSync(orphan).mtimeMs > 3_600_000) {
|
|
544
|
+
fs.rmSync(orphan, { recursive: true, force: true });
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
catch { }
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
catch { }
|
|
551
|
+
return reclaimed;
|
|
437
552
|
}
|
|
438
553
|
/**
|
|
439
554
|
* Stop-turn interrupt primitive: abort the warm session's in-flight turn.
|
|
@@ -508,6 +623,7 @@ export class SessionManager {
|
|
|
508
623
|
return this._getOrCreateUnlocked(sessionId, serializableConfig, options);
|
|
509
624
|
}
|
|
510
625
|
async _getOrCreateUnlocked(sessionId, serializableConfig, options) {
|
|
626
|
+
this.sessionLastTouchedAt.set(sessionId, Date.now());
|
|
511
627
|
const turnIndex = options?.turnIndex;
|
|
512
628
|
const trace = options?.trace;
|
|
513
629
|
const inheritedToolNames = Array.from(new Set([
|
|
@@ -838,6 +954,26 @@ export class SessionManager {
|
|
|
838
954
|
get(sessionId) {
|
|
839
955
|
return this.sessions.get(sessionId) ?? null;
|
|
840
956
|
}
|
|
957
|
+
/** Root directory holding per-session state dirs. */
|
|
958
|
+
getSessionStateDir() {
|
|
959
|
+
return this.sessionStateDir;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Destroy the in-memory ManagedSession only — disk state untouched.
|
|
963
|
+
* Used by the lifecycle preamble before overwriting local files with a
|
|
964
|
+
* hydrated snapshot (a warm session bound to the old files must not
|
|
965
|
+
* survive the swap). Caller holds the per-session run-turn lock.
|
|
966
|
+
*/
|
|
967
|
+
async dropWarmSession(sessionId) {
|
|
968
|
+
const existing = this.sessions.get(sessionId);
|
|
969
|
+
if (!existing)
|
|
970
|
+
return;
|
|
971
|
+
try {
|
|
972
|
+
await existing.destroy();
|
|
973
|
+
}
|
|
974
|
+
catch { }
|
|
975
|
+
this.sessions.delete(sessionId);
|
|
976
|
+
}
|
|
841
977
|
/**
|
|
842
978
|
* Dehydrate a session: snapshot to the session store, release in-memory state.
|
|
843
979
|
*
|