pilotswarm-sdk 0.4.1 → 0.5.0
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 +3 -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/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 +98 -66
- package/dist/orchestration/turn.js.map +1 -1
- package/dist/orchestration-registry.d.ts.map +1 -1
- package/dist/orchestration-registry.js +6 -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/session-lifecycle.d.ts +80 -0
- package/dist/session-lifecycle.d.ts.map +1 -0
- package/dist/session-lifecycle.js +257 -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 +1113 -957
- 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,257 @@
|
|
|
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):
|
|
8
|
+
*
|
|
9
|
+
* preamble p1. turn sentinel present → local dir untrusted → resolve from store
|
|
10
|
+
* p2. marker == expected AND the dir has a usable layout → warm
|
|
11
|
+
* start (zero store I/O); else hydrate; store bearing this
|
|
12
|
+
* turn's own turnKey → already-committed recovery (restore,
|
|
13
|
+
* never replay); store AHEAD of expected under a foreign
|
|
14
|
+
* turnKey → loud failure (zombie-duplicate fence)
|
|
15
|
+
* p3. (caller) write the turn sentinel just before the body
|
|
16
|
+
* postamble c1. write .ps-turn-commit.json {turnKey, result} into the dir
|
|
17
|
+
* c2. CAS-commit the tar (store.commitSnapshot); alreadyCommitted
|
|
18
|
+
* → restore the winner's snapshot + result (§3.2 r1–r3)
|
|
19
|
+
* c3. write the local version marker
|
|
20
|
+
* c4. clear the turn sentinel
|
|
21
|
+
*
|
|
22
|
+
* The CAS base is the PREAMBLE-RESOLVED version. Rebasing is allowed only
|
|
23
|
+
* when the store is BEHIND the orchestration's expectation (store restored
|
|
24
|
+
* from an older backup — self-healing); a store AHEAD of the expectation
|
|
25
|
+
* under a foreign turnKey means this attempt is a stale zombie duplicate
|
|
26
|
+
* and must fail loudly instead of re-applying its turn.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
import fs from "node:fs";
|
|
31
|
+
import path from "node:path";
|
|
32
|
+
import { faultPoint } from "./fault-injection.js";
|
|
33
|
+
import { SnapshotConflictError, clearTurnSentinel, readSnapshotMarker, readTurnCommitFile, readTurnSentinel, writeSnapshotMarker, writeTurnCommitFile, } from "./snapshot-protocol.js";
|
|
34
|
+
const COMMIT_TRANSIENT_RETRIES = 3;
|
|
35
|
+
// Tunable so fault-injection tests don't pay real backoff delays.
|
|
36
|
+
const COMMIT_RETRY_BASE_DELAY_MS = Number.parseInt(process.env.PILOTSWARM_COMMIT_RETRY_DELAY_MS ?? "", 10) || 1_000;
|
|
37
|
+
function sessionDirOf(ctx) {
|
|
38
|
+
return path.join(ctx.sessionStateDir, ctx.sessionId);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A marker alone is not proof the dir survived intact (a crashed recursive
|
|
42
|
+
* delete removes files in arbitrary order and can leave the marker while
|
|
43
|
+
* the session files are gone). Require the SDK's layout anchor before
|
|
44
|
+
* trusting local files without consulting the store.
|
|
45
|
+
*/
|
|
46
|
+
function hasUsableSessionLayout(sessionDir) {
|
|
47
|
+
return fs.existsSync(path.join(sessionDir, "workspace.yaml"));
|
|
48
|
+
}
|
|
49
|
+
function markerFromHydrate(sessionDir, hydrated) {
|
|
50
|
+
writeSnapshotMarker(sessionDir, {
|
|
51
|
+
version: hydrated.version,
|
|
52
|
+
...(hydrated.turnKey ? { turnKey: hydrated.turnKey } : {}),
|
|
53
|
+
...(hydrated.contentHash ? { contentHash: hydrated.contentHash } : {}),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** Restore the committed snapshot and return the stored result (§3.2 r1–r3). */
|
|
57
|
+
async function recoverAlreadyCommitted(ctx) {
|
|
58
|
+
const sessionDir = sessionDirOf(ctx);
|
|
59
|
+
await ctx.dropWarmSession();
|
|
60
|
+
const hydrated = await ctx.store.hydrateSnapshot(ctx.sessionId);
|
|
61
|
+
markerFromHydrate(sessionDir, hydrated);
|
|
62
|
+
clearTurnSentinel(sessionDir);
|
|
63
|
+
const commitFile = readTurnCommitFile(sessionDir);
|
|
64
|
+
if (commitFile && commitFile.turnKey === ctx.turnKey) {
|
|
65
|
+
ctx.trace(`session=${ctx.sessionId} already-committed recovery: store at v${hydrated.version} ` +
|
|
66
|
+
`under this turnKey; returning stored result without re-running the turn`);
|
|
67
|
+
return { kind: "already-committed", version: hydrated.version, result: commitFile.result };
|
|
68
|
+
}
|
|
69
|
+
// Snapshot claims our turnKey but carries no readable committed result —
|
|
70
|
+
// degrade to a hydrated re-run (duplicate execution beats a wrong result).
|
|
71
|
+
ctx.trace(`session=${ctx.sessionId} store bears this turnKey (v${hydrated.version}) but the ` +
|
|
72
|
+
`turn-commit file is missing/unreadable; degrading to hydrated re-run`);
|
|
73
|
+
return { kind: "hydrated", baseVersion: hydrated.version, storeBehindExpected: false };
|
|
74
|
+
}
|
|
75
|
+
async function resolveFromStore(ctx) {
|
|
76
|
+
const sessionDir = sessionDirOf(ctx);
|
|
77
|
+
await ctx.dropWarmSession();
|
|
78
|
+
const hydrated = await ctx.store.hydrateSnapshot(ctx.sessionId);
|
|
79
|
+
markerFromHydrate(sessionDir, hydrated);
|
|
80
|
+
clearTurnSentinel(sessionDir);
|
|
81
|
+
const storeBehindExpected = hydrated.version < ctx.expectedVersion;
|
|
82
|
+
if (storeBehindExpected) {
|
|
83
|
+
ctx.trace(`session=${ctx.sessionId} store snapshot v${hydrated.version} is BEHIND the orchestration's ` +
|
|
84
|
+
`expected v${ctx.expectedVersion} (store restored from backup?); proceeding from stored state`);
|
|
85
|
+
}
|
|
86
|
+
return { kind: "hydrated", baseVersion: hydrated.version, storeBehindExpected };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Preamble (p1/p2). Runs under the per-session run-turn lock, before
|
|
90
|
+
* getOrCreate. On return the local dir is in exactly one of these states:
|
|
91
|
+
* trusted at `baseVersion` (warm/hydrated), absent (fresh), or restored at
|
|
92
|
+
* the committed version (already-committed — the caller returns the stored
|
|
93
|
+
* result without running the body).
|
|
94
|
+
*/
|
|
95
|
+
export async function runTurnPreamble(ctx) {
|
|
96
|
+
const sessionDir = sessionDirOf(ctx);
|
|
97
|
+
const sentinel = readTurnSentinel(sessionDir);
|
|
98
|
+
const marker = readSnapshotMarker(sessionDir);
|
|
99
|
+
const localDirExists = fs.existsSync(sessionDir);
|
|
100
|
+
const localDirUsable = localDirExists && hasUsableSessionLayout(sessionDir);
|
|
101
|
+
// p2 fast path: trusted local files at exactly the expected version.
|
|
102
|
+
// Requires the layout anchor — a marker orphaned by a torn delete must
|
|
103
|
+
// not be trusted (the store may hold a perfect copy).
|
|
104
|
+
if (!sentinel && marker && marker.version === ctx.expectedVersion && localDirUsable) {
|
|
105
|
+
return { kind: "warm", baseVersion: marker.version };
|
|
106
|
+
}
|
|
107
|
+
if (sentinel) {
|
|
108
|
+
ctx.trace(`session=${ctx.sessionId} turn sentinel present (started ${sentinel.startedAt || "unknown"}); ` +
|
|
109
|
+
`local dir untrusted, resolving from store`);
|
|
110
|
+
}
|
|
111
|
+
// Legacy warm continuity: the orchestration has never recorded a commit
|
|
112
|
+
// (expected 0) and the dir is clean but unmarked — typically a
|
|
113
|
+
// 1.0.56-era warm session that just migrated onto the new protocol.
|
|
114
|
+
// Trust it ONLY after confirming the store doesn't hold a versioned
|
|
115
|
+
// chain (a crash inside already-committed recovery of turn 1 leaves
|
|
116
|
+
// exactly this local shape while the store already has v1).
|
|
117
|
+
if (!sentinel && !marker && ctx.expectedVersion === 0 && localDirUsable) {
|
|
118
|
+
const probe0 = await ctx.store.probeSnapshot(ctx.sessionId);
|
|
119
|
+
if (!probe0.exists || probe0.legacy) {
|
|
120
|
+
return { kind: "warm", baseVersion: 0 };
|
|
121
|
+
}
|
|
122
|
+
if (probe0.turnKey && probe0.turnKey === ctx.turnKey) {
|
|
123
|
+
return recoverAlreadyCommitted(ctx);
|
|
124
|
+
}
|
|
125
|
+
ctx.trace(`session=${ctx.sessionId} unmarked local dir but the store holds versioned v${probe0.version}; ` +
|
|
126
|
+
`resolving from store`);
|
|
127
|
+
return resolveFromStore(ctx);
|
|
128
|
+
}
|
|
129
|
+
// Local is untrusted (dirty sentinel) or stale (marker mismatch/absent):
|
|
130
|
+
// resolve from the store.
|
|
131
|
+
const probe = await ctx.store.probeSnapshot(ctx.sessionId);
|
|
132
|
+
if (!probe.exists) {
|
|
133
|
+
// Nothing stored. A clean, usable dir is still the best data
|
|
134
|
+
// available — trust it over a fresh replay.
|
|
135
|
+
if (!sentinel && localDirUsable) {
|
|
136
|
+
ctx.trace(`session=${ctx.sessionId} store empty but clean local dir exists ` +
|
|
137
|
+
`(marker=${marker?.version ?? "none"} expected=${ctx.expectedVersion}); using local files`);
|
|
138
|
+
return { kind: "warm", baseVersion: marker?.version ?? 0 };
|
|
139
|
+
}
|
|
140
|
+
// Dirty or unusable local + empty store → fresh; lossy iff turns had
|
|
141
|
+
// been committed before (W3 in the protocol doc).
|
|
142
|
+
await ctx.dropWarmSession();
|
|
143
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
144
|
+
return { kind: "fresh", baseVersion: 0, lossy: ctx.expectedVersion > 0 };
|
|
145
|
+
}
|
|
146
|
+
// Already-committed recovery: the store bears this very turn's turnKey —
|
|
147
|
+
// the previous attempt crashed after its CAS landed (or a racing
|
|
148
|
+
// duplicate attempt already won). Restore, never replay.
|
|
149
|
+
if (probe.turnKey && probe.turnKey === ctx.turnKey) {
|
|
150
|
+
return recoverAlreadyCommitted(ctx);
|
|
151
|
+
}
|
|
152
|
+
// Zombie-duplicate fence: the store is AHEAD of what this attempt was
|
|
153
|
+
// scheduled against, under a DIFFERENT turn's key. A later turn already
|
|
154
|
+
// committed — re-running this one would silently double-apply it (and
|
|
155
|
+
// its rebased CAS would succeed). Fail loudly; duroxide's retry/poison
|
|
156
|
+
// machinery surfaces it instead of corrupting the chain.
|
|
157
|
+
if (ctx.expectedVersion > 0 && probe.version > ctx.expectedVersion) {
|
|
158
|
+
throw new SnapshotConflictError(ctx.sessionId, ctx.expectedVersion, probe.version, probe.turnKey);
|
|
159
|
+
}
|
|
160
|
+
// Normal resolve: hydrate exactly what the store holds (equal to
|
|
161
|
+
// expected after a migration/crash, or behind after a store restore).
|
|
162
|
+
return resolveFromStore(ctx);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Last-resort recovery when getOrCreate fails on missing/lost local state
|
|
166
|
+
* DESPITE the preamble's resolution (e.g. the SDK refused to resume from
|
|
167
|
+
* intact-looking files): restore the committed snapshot if one exists.
|
|
168
|
+
* Returns the hydrated version, or null when the store holds nothing —
|
|
169
|
+
* only then may the caller fall back to the lossy fresh replay, whose
|
|
170
|
+
* store-deleting reset is a no-op against an empty store.
|
|
171
|
+
*/
|
|
172
|
+
export async function attemptStoreRecovery(ctx) {
|
|
173
|
+
const probe = await ctx.store.probeSnapshot(ctx.sessionId);
|
|
174
|
+
if (!probe.exists)
|
|
175
|
+
return null;
|
|
176
|
+
const sessionDir = sessionDirOf(ctx);
|
|
177
|
+
await ctx.dropWarmSession();
|
|
178
|
+
const hydrated = await ctx.store.hydrateSnapshot(ctx.sessionId);
|
|
179
|
+
markerFromHydrate(sessionDir, hydrated);
|
|
180
|
+
clearTurnSentinel(sessionDir);
|
|
181
|
+
return hydrated.version;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Postamble (c1–c4). Runs under the per-session run-turn lock after the
|
|
185
|
+
* body produced `result`. Transient store failures retry in place; a CAS
|
|
186
|
+
* conflict (foreign writer) always throws — the activity must fail loudly.
|
|
187
|
+
* An `alreadyCommitted` CAS outcome (a racing duplicate of this same turn
|
|
188
|
+
* won) triggers restore-not-replay: the winner's snapshot and result are
|
|
189
|
+
* adopted, discarding this attempt's divergent local state.
|
|
190
|
+
*/
|
|
191
|
+
export async function runTurnCommit(ctx, baseVersion, result) {
|
|
192
|
+
const sessionDir = sessionDirOf(ctx);
|
|
193
|
+
writeTurnCommitFile(sessionDir, ctx.turnKey, result);
|
|
194
|
+
faultPoint("turn.commit.before-cas");
|
|
195
|
+
let lastError;
|
|
196
|
+
for (let attempt = 1; attempt <= COMMIT_TRANSIENT_RETRIES; attempt++) {
|
|
197
|
+
try {
|
|
198
|
+
const committed = await ctx.store.commitSnapshot(ctx.sessionId, {
|
|
199
|
+
baseVersion,
|
|
200
|
+
turnKey: ctx.turnKey,
|
|
201
|
+
});
|
|
202
|
+
faultPoint("turn.commit.after-cas");
|
|
203
|
+
if (committed.alreadyCommitted) {
|
|
204
|
+
// §3.2 restore-not-replay: a racing attempt of this same
|
|
205
|
+
// turn committed first. Its snapshot is the durable lineage;
|
|
206
|
+
// this attempt's local files diverge and must be replaced.
|
|
207
|
+
ctx.trace(`session=${ctx.sessionId} commit found this turn already committed at ` +
|
|
208
|
+
`v${committed.version} by a racing attempt; restoring the committed state`);
|
|
209
|
+
await ctx.dropWarmSession();
|
|
210
|
+
const hydrated = await ctx.store.hydrateSnapshot(ctx.sessionId);
|
|
211
|
+
markerFromHydrate(sessionDir, hydrated);
|
|
212
|
+
clearTurnSentinel(sessionDir);
|
|
213
|
+
const commitFile = readTurnCommitFile(sessionDir);
|
|
214
|
+
return {
|
|
215
|
+
version: committed.version,
|
|
216
|
+
contentHash: hydrated.contentHash ?? committed.contentHash,
|
|
217
|
+
...(hydrated.sizeBytes != null ? { sizeBytes: hydrated.sizeBytes } : {}),
|
|
218
|
+
...(hydrated.rawSizeBytes != null ? { rawSizeBytes: hydrated.rawSizeBytes } : {}),
|
|
219
|
+
alreadyCommitted: true,
|
|
220
|
+
...(commitFile && commitFile.turnKey === ctx.turnKey
|
|
221
|
+
? { storedResult: commitFile.result }
|
|
222
|
+
: {}),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
writeSnapshotMarker(sessionDir, {
|
|
226
|
+
version: committed.version,
|
|
227
|
+
turnKey: ctx.turnKey,
|
|
228
|
+
contentHash: committed.contentHash,
|
|
229
|
+
});
|
|
230
|
+
faultPoint("turn.commit.after-marker");
|
|
231
|
+
clearTurnSentinel(sessionDir);
|
|
232
|
+
faultPoint("turn.commit.after-sentinel-clear");
|
|
233
|
+
return {
|
|
234
|
+
version: committed.version,
|
|
235
|
+
contentHash: committed.contentHash,
|
|
236
|
+
...(committed.sizeBytes != null ? { sizeBytes: committed.sizeBytes } : {}),
|
|
237
|
+
...(committed.rawSizeBytes != null ? { rawSizeBytes: committed.rawSizeBytes } : {}),
|
|
238
|
+
alreadyCommitted: false,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
if (error instanceof SnapshotConflictError)
|
|
243
|
+
throw error;
|
|
244
|
+
lastError = error;
|
|
245
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
246
|
+
ctx.trace(`session=${ctx.sessionId} turn commit attempt ${attempt}/${COMMIT_TRANSIENT_RETRIES} ` +
|
|
247
|
+
`failed: ${message}`);
|
|
248
|
+
if (attempt < COMMIT_TRANSIENT_RETRIES) {
|
|
249
|
+
await new Promise((resolve) => setTimeout(resolve, COMMIT_RETRY_BASE_DELAY_MS * attempt));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
throw lastError instanceof Error
|
|
254
|
+
? lastError
|
|
255
|
+
: new Error(`Turn commit failed for ${ctx.sessionId}: ${String(lastError)}`);
|
|
256
|
+
}
|
|
257
|
+
//# 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
|
*
|