pi-app-server 0.1.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/LICENSE +21 -0
- package/README.md +195 -0
- package/dist/command-classification.d.ts +59 -0
- package/dist/command-classification.d.ts.map +1 -0
- package/dist/command-classification.js +78 -0
- package/dist/command-classification.js.map +7 -0
- package/dist/command-execution-engine.d.ts +118 -0
- package/dist/command-execution-engine.d.ts.map +1 -0
- package/dist/command-execution-engine.js +259 -0
- package/dist/command-execution-engine.js.map +7 -0
- package/dist/command-replay-store.d.ts +241 -0
- package/dist/command-replay-store.d.ts.map +1 -0
- package/dist/command-replay-store.js +306 -0
- package/dist/command-replay-store.js.map +7 -0
- package/dist/command-router.d.ts +25 -0
- package/dist/command-router.d.ts.map +1 -0
- package/dist/command-router.js +353 -0
- package/dist/command-router.js.map +7 -0
- package/dist/extension-ui.d.ts +139 -0
- package/dist/extension-ui.d.ts.map +1 -0
- package/dist/extension-ui.js +189 -0
- package/dist/extension-ui.js.map +7 -0
- package/dist/resource-governor.d.ts +254 -0
- package/dist/resource-governor.d.ts.map +1 -0
- package/dist/resource-governor.js +603 -0
- package/dist/resource-governor.js.map +7 -0
- package/dist/server-command-handlers.d.ts +120 -0
- package/dist/server-command-handlers.d.ts.map +1 -0
- package/dist/server-command-handlers.js +234 -0
- package/dist/server-command-handlers.js.map +7 -0
- package/dist/server-ui-context.d.ts +22 -0
- package/dist/server-ui-context.d.ts.map +1 -0
- package/dist/server-ui-context.js +221 -0
- package/dist/server-ui-context.js.map +7 -0
- package/dist/server.d.ts +82 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +561 -0
- package/dist/server.js.map +7 -0
- package/dist/session-lock-manager.d.ts +100 -0
- package/dist/session-lock-manager.d.ts.map +1 -0
- package/dist/session-lock-manager.js +199 -0
- package/dist/session-lock-manager.js.map +7 -0
- package/dist/session-manager.d.ts +196 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +1010 -0
- package/dist/session-manager.js.map +7 -0
- package/dist/session-store.d.ts +190 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +446 -0
- package/dist/session-store.js.map +7 -0
- package/dist/session-version-store.d.ts +83 -0
- package/dist/session-version-store.d.ts.map +1 -0
- package/dist/session-version-store.js +117 -0
- package/dist/session-version-store.js.map +7 -0
- package/dist/type-guards.d.ts +59 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +40 -0
- package/dist/type-guards.js.map +7 -0
- package/dist/types.d.ts +621 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +7 -0
- package/dist/validation.d.ts +22 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +323 -0
- package/dist/validation.js.map +7 -0
- package/package.json +135 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { getCommandId, getCommandIdempotencyKey, getSessionId } from "./types.js";
|
|
2
|
+
const DEFAULT_IDEMPOTENCY_TTL_MS = 10 * 60 * 1e3;
|
|
3
|
+
const DEFAULT_MAX_COMMAND_OUTCOMES = 2e3;
|
|
4
|
+
const DEFAULT_MAX_IN_FLIGHT_COMMANDS = 1e4;
|
|
5
|
+
const SYNTHETIC_ID_PREFIX = "anon:";
|
|
6
|
+
class CommandReplayStore {
|
|
7
|
+
/** In-flight commands by command id (for dependency waits and duplicate-id replay). */
|
|
8
|
+
commandInFlightById = /* @__PURE__ */ new Map();
|
|
9
|
+
/** Insertion order for in-flight commands (for bounded eviction). */
|
|
10
|
+
inFlightOrder = [];
|
|
11
|
+
/** Completed command outcomes (for dependency checks and duplicate-id replay). */
|
|
12
|
+
commandOutcomes = /* @__PURE__ */ new Map();
|
|
13
|
+
/** Bounded insertion order to trim commandOutcomes memory. */
|
|
14
|
+
commandOutcomeOrder = [];
|
|
15
|
+
/** Idempotency replay cache. */
|
|
16
|
+
idempotencyCache = /* @__PURE__ */ new Map();
|
|
17
|
+
/** Sequence for synthetic command IDs when client omits id. */
|
|
18
|
+
syntheticCommandSequence = 0;
|
|
19
|
+
/** Process start time for unique synthetic IDs that don't collide after clear(). */
|
|
20
|
+
processStartTime = Date.now();
|
|
21
|
+
idempotencyTtlMs;
|
|
22
|
+
maxCommandOutcomes;
|
|
23
|
+
maxInFlightCommands;
|
|
24
|
+
inFlightRejections = 0;
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this.idempotencyTtlMs = typeof options.idempotencyTtlMs === "number" && options.idempotencyTtlMs > 0 ? options.idempotencyTtlMs : DEFAULT_IDEMPOTENCY_TTL_MS;
|
|
27
|
+
this.maxCommandOutcomes = typeof options.maxCommandOutcomes === "number" && options.maxCommandOutcomes > 0 ? options.maxCommandOutcomes : DEFAULT_MAX_COMMAND_OUTCOMES;
|
|
28
|
+
this.maxInFlightCommands = typeof options.maxInFlightCommands === "number" && options.maxInFlightCommands > 0 ? options.maxInFlightCommands : DEFAULT_MAX_IN_FLIGHT_COMMANDS;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get statistics about store state for monitoring.
|
|
32
|
+
*/
|
|
33
|
+
getStats() {
|
|
34
|
+
return {
|
|
35
|
+
inFlightCount: this.commandInFlightById.size,
|
|
36
|
+
outcomeCount: this.commandOutcomes.size,
|
|
37
|
+
idempotencyCacheSize: this.idempotencyCache.size,
|
|
38
|
+
maxInFlightCommands: this.maxInFlightCommands,
|
|
39
|
+
maxCommandOutcomes: this.maxCommandOutcomes,
|
|
40
|
+
inFlightRejections: this.inFlightRejections
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// ==========================================================================
|
|
44
|
+
// COMMAND ID GENERATION
|
|
45
|
+
// ==========================================================================
|
|
46
|
+
/**
|
|
47
|
+
* Get or create a deterministic command ID.
|
|
48
|
+
* Returns explicit ID if provided, otherwise generates a synthetic one.
|
|
49
|
+
*
|
|
50
|
+
* Synthetic IDs use process-start-time + sequence to guarantee uniqueness:
|
|
51
|
+
* 1. Process start time distinguishes IDs across server restarts
|
|
52
|
+
* 2. Sequence guarantees uniqueness within a process lifetime
|
|
53
|
+
* 3. Clear() resets sequence but start time stays same (collision-safe within run)
|
|
54
|
+
*
|
|
55
|
+
* NOTE: This method has a side effect (increments sequence) when generating.
|
|
56
|
+
*/
|
|
57
|
+
getOrCreateCommandId(command) {
|
|
58
|
+
const explicitId = getCommandId(command);
|
|
59
|
+
if (explicitId) return explicitId;
|
|
60
|
+
this.syntheticCommandSequence += 1;
|
|
61
|
+
return `${SYNTHETIC_ID_PREFIX}${this.processStartTime}:${this.syntheticCommandSequence}`;
|
|
62
|
+
}
|
|
63
|
+
// ==========================================================================
|
|
64
|
+
// FINGERPRINTING
|
|
65
|
+
// ==========================================================================
|
|
66
|
+
/**
|
|
67
|
+
* Compute a fingerprint for conflict detection.
|
|
68
|
+
* Excludes retry identity fields (id, idempotencyKey) since those
|
|
69
|
+
* don't affect semantic equivalence - only determine replay mechanics.
|
|
70
|
+
*/
|
|
71
|
+
getCommandFingerprint(command) {
|
|
72
|
+
const { id: _id, idempotencyKey: _key, ...rest } = command;
|
|
73
|
+
return JSON.stringify(rest);
|
|
74
|
+
}
|
|
75
|
+
// ==========================================================================
|
|
76
|
+
// IDEMPOTENCY KEY CACHE
|
|
77
|
+
// ==========================================================================
|
|
78
|
+
/**
|
|
79
|
+
* Build a cache key for idempotency lookup.
|
|
80
|
+
*/
|
|
81
|
+
buildIdempotencyCacheKey(command, key) {
|
|
82
|
+
const sessionId = getSessionId(command) ?? "_server_";
|
|
83
|
+
return `${sessionId}:${key}`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Remove expired entries from the idempotency cache.
|
|
87
|
+
*/
|
|
88
|
+
cleanupIdempotencyCache(now = Date.now()) {
|
|
89
|
+
for (const [key, entry] of this.idempotencyCache) {
|
|
90
|
+
if (entry.expiresAt <= now) {
|
|
91
|
+
this.idempotencyCache.delete(key);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Store a response in the idempotency cache.
|
|
97
|
+
*/
|
|
98
|
+
cacheIdempotencyResult(input) {
|
|
99
|
+
const cacheKey = this.buildIdempotencyCacheKey(input.command, input.idempotencyKey);
|
|
100
|
+
this.idempotencyCache.set(cacheKey, {
|
|
101
|
+
expiresAt: Date.now() + this.idempotencyTtlMs,
|
|
102
|
+
commandType: input.commandType,
|
|
103
|
+
fingerprint: input.fingerprint,
|
|
104
|
+
response: input.response
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// ==========================================================================
|
|
108
|
+
// COMMAND OUTCOMES
|
|
109
|
+
// ==========================================================================
|
|
110
|
+
/**
|
|
111
|
+
* Trim old command outcomes to bound memory.
|
|
112
|
+
*/
|
|
113
|
+
trimCommandOutcomes() {
|
|
114
|
+
while (this.commandOutcomeOrder.length > this.maxCommandOutcomes) {
|
|
115
|
+
const oldest = this.commandOutcomeOrder.shift();
|
|
116
|
+
if (!oldest) break;
|
|
117
|
+
this.commandOutcomes.delete(oldest);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Store a completed command outcome.
|
|
122
|
+
*/
|
|
123
|
+
storeCommandOutcome(outcome) {
|
|
124
|
+
const existed = this.commandOutcomes.has(outcome.commandId);
|
|
125
|
+
this.commandOutcomes.set(outcome.commandId, outcome);
|
|
126
|
+
if (!existed) {
|
|
127
|
+
this.commandOutcomeOrder.push(outcome.commandId);
|
|
128
|
+
this.trimCommandOutcomes();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get a completed command outcome by ID.
|
|
133
|
+
*/
|
|
134
|
+
getCommandOutcome(commandId) {
|
|
135
|
+
return this.commandOutcomes.get(commandId);
|
|
136
|
+
}
|
|
137
|
+
// ==========================================================================
|
|
138
|
+
// IN-FLIGHT TRACKING
|
|
139
|
+
// ==========================================================================
|
|
140
|
+
/**
|
|
141
|
+
* Check whether an in-flight command can be tracked for this command ID.
|
|
142
|
+
*
|
|
143
|
+
* Returns true if:
|
|
144
|
+
* - the command ID is already tracked (replacement/update path), OR
|
|
145
|
+
* - there is free capacity for a new tracked command.
|
|
146
|
+
*/
|
|
147
|
+
canRegisterInFlight(commandId) {
|
|
148
|
+
const existed = this.commandInFlightById.has(commandId);
|
|
149
|
+
return existed || this.inFlightOrder.length < this.maxInFlightCommands;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Register an in-flight command.
|
|
153
|
+
*
|
|
154
|
+
* ADR-0001: Rejects when limit exceeded instead of evicting.
|
|
155
|
+
* Eviction breaks dependency chains - if command A depends on command B,
|
|
156
|
+
* and B is evicted, A fails with "unknown dependency". Rejection preserves
|
|
157
|
+
* correctness at the cost of temporary unavailability under load.
|
|
158
|
+
*
|
|
159
|
+
* @returns true if registered, false if limit exceeded
|
|
160
|
+
*/
|
|
161
|
+
registerInFlight(commandId, record) {
|
|
162
|
+
const existed = this.commandInFlightById.has(commandId);
|
|
163
|
+
if (!existed && this.inFlightOrder.length >= this.maxInFlightCommands) {
|
|
164
|
+
this.inFlightRejections++;
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
this.commandInFlightById.set(commandId, record);
|
|
168
|
+
if (!existed) {
|
|
169
|
+
this.inFlightOrder.push(commandId);
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Unregister an in-flight command.
|
|
175
|
+
* Only removes if the record matches (prevents race conditions).
|
|
176
|
+
*/
|
|
177
|
+
unregisterInFlight(commandId, record) {
|
|
178
|
+
if (this.commandInFlightById.get(commandId) === record) {
|
|
179
|
+
this.commandInFlightById.delete(commandId);
|
|
180
|
+
const idx = this.inFlightOrder.indexOf(commandId);
|
|
181
|
+
if (idx !== -1) {
|
|
182
|
+
this.inFlightOrder.splice(idx, 1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get an in-flight command by ID.
|
|
188
|
+
*/
|
|
189
|
+
getInFlight(commandId) {
|
|
190
|
+
return this.commandInFlightById.get(commandId);
|
|
191
|
+
}
|
|
192
|
+
// ==========================================================================
|
|
193
|
+
// REPLAY CHECK (MAIN API)
|
|
194
|
+
// ==========================================================================
|
|
195
|
+
/**
|
|
196
|
+
* Create a conflict response for fingerprint mismatch.
|
|
197
|
+
*/
|
|
198
|
+
createConflictResponse(id, commandType, conflictType, value, originalType) {
|
|
199
|
+
return {
|
|
200
|
+
id,
|
|
201
|
+
type: "response",
|
|
202
|
+
command: commandType,
|
|
203
|
+
success: false,
|
|
204
|
+
error: `Conflicting ${conflictType} '${value}': previously used for '${originalType}', now used for '${commandType}'`
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Clone a cached response for a new request.
|
|
209
|
+
* Preserves or strips ID based on whether the new request has one.
|
|
210
|
+
*/
|
|
211
|
+
cloneResponseForRequest(response, requestId) {
|
|
212
|
+
if (requestId === void 0) {
|
|
213
|
+
const { id: _oldId, ...rest } = response;
|
|
214
|
+
return { ...rest };
|
|
215
|
+
}
|
|
216
|
+
return { ...response, id: requestId };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Check for replay opportunities or conflicts.
|
|
220
|
+
*
|
|
221
|
+
* Call this BEFORE executing a command. Returns:
|
|
222
|
+
* - "proceed": No replay possible, execute normally
|
|
223
|
+
* - "conflict": Fingerprint mismatch, return error response
|
|
224
|
+
* - "replay_cached": Found cached response, return it (with replayed: true)
|
|
225
|
+
* - "replay_inflight": Found in-flight command, await its promise
|
|
226
|
+
*/
|
|
227
|
+
checkReplay(command, fingerprint) {
|
|
228
|
+
const id = getCommandId(command);
|
|
229
|
+
const commandType = command.type;
|
|
230
|
+
const idempotencyKey = getCommandIdempotencyKey(command);
|
|
231
|
+
if (idempotencyKey) {
|
|
232
|
+
const cacheKey = this.buildIdempotencyCacheKey(command, idempotencyKey);
|
|
233
|
+
const cached = this.idempotencyCache.get(cacheKey);
|
|
234
|
+
if (cached) {
|
|
235
|
+
if (cached.fingerprint !== fingerprint) {
|
|
236
|
+
return {
|
|
237
|
+
kind: "conflict",
|
|
238
|
+
response: this.createConflictResponse(
|
|
239
|
+
id,
|
|
240
|
+
commandType,
|
|
241
|
+
"idempotencyKey",
|
|
242
|
+
idempotencyKey,
|
|
243
|
+
cached.commandType
|
|
244
|
+
)
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
kind: "replay_cached",
|
|
249
|
+
response: this.cloneResponseForRequest({ ...cached.response, replayed: true }, id)
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (id) {
|
|
254
|
+
const completed = this.commandOutcomes.get(id);
|
|
255
|
+
if (completed) {
|
|
256
|
+
if (completed.fingerprint !== fingerprint) {
|
|
257
|
+
return {
|
|
258
|
+
kind: "conflict",
|
|
259
|
+
response: this.createConflictResponse(id, commandType, "id", id, completed.commandType)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
kind: "replay_cached",
|
|
264
|
+
response: this.cloneResponseForRequest({ ...completed.response, replayed: true }, id)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const inFlight = this.commandInFlightById.get(id);
|
|
268
|
+
if (inFlight) {
|
|
269
|
+
if (inFlight.fingerprint !== fingerprint) {
|
|
270
|
+
return {
|
|
271
|
+
kind: "conflict",
|
|
272
|
+
response: this.createConflictResponse(id, commandType, "id", id, inFlight.commandType)
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
kind: "replay_inflight",
|
|
277
|
+
promise: inFlight.promise.then(
|
|
278
|
+
(response) => this.cloneResponseForRequest({ ...response, replayed: true }, id)
|
|
279
|
+
)
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return { kind: "proceed" };
|
|
284
|
+
}
|
|
285
|
+
// ==========================================================================
|
|
286
|
+
// LIFECYCLE
|
|
287
|
+
// ==========================================================================
|
|
288
|
+
/**
|
|
289
|
+
* Clear all state (used during disposal).
|
|
290
|
+
* Note: Does NOT reset syntheticCommandSequence to prevent ID collisions.
|
|
291
|
+
* Process start time ensures uniqueness even after clear.
|
|
292
|
+
*/
|
|
293
|
+
clear() {
|
|
294
|
+
this.commandInFlightById.clear();
|
|
295
|
+
this.inFlightOrder = [];
|
|
296
|
+
this.commandOutcomes.clear();
|
|
297
|
+
this.commandOutcomeOrder = [];
|
|
298
|
+
this.idempotencyCache.clear();
|
|
299
|
+
this.inFlightRejections = 0;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
export {
|
|
303
|
+
CommandReplayStore,
|
|
304
|
+
SYNTHETIC_ID_PREFIX
|
|
305
|
+
};
|
|
306
|
+
//# sourceMappingURL=command-replay-store.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/command-replay-store.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Command Replay Store - manages idempotency, duplicate detection, and outcome history.\n *\n * Responsibilities:\n * - Idempotency key replay (cached responses for retry safety)\n * - Command ID deduplication (completed outcomes + in-flight tracking)\n * - Fingerprint conflict detection (prevent same ID with different payload)\n * - Bounded outcome retention (LRU-style trimming)\n */\n\nimport type { RpcCommand, RpcResponse } from \"./types.js\";\nimport { getCommandId, getCommandIdempotencyKey, getSessionId } from \"./types.js\";\n\n/** How long idempotency results are replayable (10 minutes). */\nconst DEFAULT_IDEMPOTENCY_TTL_MS = 10 * 60 * 1000;\n\n/** Maximum number of retained command outcomes for dependency checks. */\nconst DEFAULT_MAX_COMMAND_OUTCOMES = 2000;\n\n/** Maximum number of in-flight commands to track (prevents unbounded memory). */\nconst DEFAULT_MAX_IN_FLIGHT_COMMANDS = 10000;\n\n/** Reserved prefix for server-generated command IDs. Client IDs matching this are rejected. */\nexport const SYNTHETIC_ID_PREFIX = \"anon:\";\n\n/**\n * Record of a completed command execution.\n * Used for dependency resolution and duplicate-id replay.\n */\nexport interface CommandOutcomeRecord {\n commandId: string;\n commandType: string;\n laneKey: string;\n fingerprint: string;\n success: boolean;\n error?: string;\n response: RpcResponse;\n sessionVersion?: number;\n finishedAt: number;\n}\n\n/**\n * Record of an in-flight command execution.\n * Used for dependency waits and duplicate-id replay.\n */\nexport interface InFlightCommandRecord {\n commandType: string;\n laneKey: string;\n fingerprint: string;\n promise: Promise<RpcResponse>;\n}\n\n/**\n * Input for caching an idempotency result.\n */\nexport interface IdempotencyCacheInput {\n command: RpcCommand;\n idempotencyKey: string;\n commandType: string;\n fingerprint: string;\n response: RpcResponse;\n}\nexport interface IdempotencyCacheEntry {\n expiresAt: number;\n commandType: string;\n fingerprint: string;\n response: RpcResponse;\n}\n\n/**\n * Result of checking for replay conflicts.\n *\n * This discriminated union enables type-safe handling of all replay scenarios:\n *\n * - `proceed`: No replay possible, execute the command normally\n * - `conflict`: Fingerprint mismatch - same ID/key but different payload\n * - `replay_cached`: Found cached response, return it immediately\n * - `replay_inflight`: Command with same ID is executing, await its promise\n *\n * @example\n * ```typescript\n * const result = store.checkReplay(command, fingerprint);\n * switch (result.kind) {\n * case \"proceed\": // execute normally\n * case \"conflict\": return result.response; // error response\n * case \"replay_cached\": return result.response; // cached response\n * case \"replay_inflight\": return await result.promise; // wait for in-flight\n * }\n * ```\n */\nexport type ReplayCheckResult =\n | { kind: \"proceed\" }\n | { kind: \"conflict\"; response: RpcResponse }\n | { kind: \"replay_cached\"; response: RpcResponse }\n | { kind: \"replay_inflight\"; promise: Promise<RpcResponse> };\n\n/**\n * Configuration options for the replay store.\n */\nexport interface ReplayStoreOptions {\n idempotencyTtlMs?: number;\n maxCommandOutcomes?: number;\n /** Maximum in-flight commands to track. Excess is rejected (ADR-0001: reject, don't evict). */\n maxInFlightCommands?: number;\n}\n\n/**\n * Statistics about store state for monitoring.\n */\nexport interface ReplayStoreStats {\n /** Number of in-flight commands being tracked */\n inFlightCount: number;\n /** Number of completed command outcomes stored */\n outcomeCount: number;\n /** Number of idempotency cache entries */\n idempotencyCacheSize: number;\n /** Maximum configured in-flight commands */\n maxInFlightCommands: number;\n /** Maximum configured command outcomes */\n maxCommandOutcomes: number;\n /** Count of in-flight rejections due to exceeding max (ADR-0001: reject, don't evict) */\n inFlightRejections: number;\n}\n\n/**\n * Command Replay Store - manages idempotency and duplicate detection.\n *\n * Extracted from PiSessionManager to isolate:\n * - Replay semantics (idempotency keys, command IDs)\n * - Fingerprint conflict detection\n * - Bounded outcome history\n */\nexport class CommandReplayStore {\n /** In-flight commands by command id (for dependency waits and duplicate-id replay). */\n private commandInFlightById = new Map<string, InFlightCommandRecord>();\n /** Insertion order for in-flight commands (for bounded eviction). */\n private inFlightOrder: string[] = [];\n\n /** Completed command outcomes (for dependency checks and duplicate-id replay). */\n private commandOutcomes = new Map<string, CommandOutcomeRecord>();\n\n /** Bounded insertion order to trim commandOutcomes memory. */\n private commandOutcomeOrder: string[] = [];\n\n /** Idempotency replay cache. */\n private idempotencyCache = new Map<string, IdempotencyCacheEntry>();\n\n /** Sequence for synthetic command IDs when client omits id. */\n private syntheticCommandSequence = 0;\n /** Process start time for unique synthetic IDs that don't collide after clear(). */\n private readonly processStartTime = Date.now();\n\n private readonly idempotencyTtlMs: number;\n private readonly maxCommandOutcomes: number;\n private readonly maxInFlightCommands: number;\n private inFlightRejections = 0;\n\n constructor(options: ReplayStoreOptions = {}) {\n this.idempotencyTtlMs =\n typeof options.idempotencyTtlMs === \"number\" && options.idempotencyTtlMs > 0\n ? options.idempotencyTtlMs\n : DEFAULT_IDEMPOTENCY_TTL_MS;\n this.maxCommandOutcomes =\n typeof options.maxCommandOutcomes === \"number\" && options.maxCommandOutcomes > 0\n ? options.maxCommandOutcomes\n : DEFAULT_MAX_COMMAND_OUTCOMES;\n this.maxInFlightCommands =\n typeof options.maxInFlightCommands === \"number\" && options.maxInFlightCommands > 0\n ? options.maxInFlightCommands\n : DEFAULT_MAX_IN_FLIGHT_COMMANDS;\n }\n\n /**\n * Get statistics about store state for monitoring.\n */\n getStats(): ReplayStoreStats {\n return {\n inFlightCount: this.commandInFlightById.size,\n outcomeCount: this.commandOutcomes.size,\n idempotencyCacheSize: this.idempotencyCache.size,\n maxInFlightCommands: this.maxInFlightCommands,\n maxCommandOutcomes: this.maxCommandOutcomes,\n inFlightRejections: this.inFlightRejections,\n };\n }\n\n // ==========================================================================\n // COMMAND ID GENERATION\n // ==========================================================================\n\n /**\n * Get or create a deterministic command ID.\n * Returns explicit ID if provided, otherwise generates a synthetic one.\n *\n * Synthetic IDs use process-start-time + sequence to guarantee uniqueness:\n * 1. Process start time distinguishes IDs across server restarts\n * 2. Sequence guarantees uniqueness within a process lifetime\n * 3. Clear() resets sequence but start time stays same (collision-safe within run)\n *\n * NOTE: This method has a side effect (increments sequence) when generating.\n */\n getOrCreateCommandId(command: RpcCommand): string {\n const explicitId = getCommandId(command);\n if (explicitId) return explicitId;\n\n this.syntheticCommandSequence += 1;\n return `${SYNTHETIC_ID_PREFIX}${this.processStartTime}:${this.syntheticCommandSequence}`;\n }\n\n // ==========================================================================\n // FINGERPRINTING\n // ==========================================================================\n\n /**\n * Compute a fingerprint for conflict detection.\n * Excludes retry identity fields (id, idempotencyKey) since those\n * don't affect semantic equivalence - only determine replay mechanics.\n */\n getCommandFingerprint(command: RpcCommand): string {\n const { id: _id, idempotencyKey: _key, ...rest } = command;\n return JSON.stringify(rest);\n }\n\n // ==========================================================================\n // IDEMPOTENCY KEY CACHE\n // ==========================================================================\n\n /**\n * Build a cache key for idempotency lookup.\n */\n private buildIdempotencyCacheKey(command: RpcCommand, key: string): string {\n const sessionId = getSessionId(command) ?? \"_server_\";\n return `${sessionId}:${key}`;\n }\n\n /**\n * Remove expired entries from the idempotency cache.\n */\n cleanupIdempotencyCache(now = Date.now()): void {\n for (const [key, entry] of this.idempotencyCache) {\n if (entry.expiresAt <= now) {\n this.idempotencyCache.delete(key);\n }\n }\n }\n\n /**\n * Store a response in the idempotency cache.\n */\n cacheIdempotencyResult(input: IdempotencyCacheInput): void {\n const cacheKey = this.buildIdempotencyCacheKey(input.command, input.idempotencyKey);\n this.idempotencyCache.set(cacheKey, {\n expiresAt: Date.now() + this.idempotencyTtlMs,\n commandType: input.commandType,\n fingerprint: input.fingerprint,\n response: input.response,\n });\n }\n\n // ==========================================================================\n // COMMAND OUTCOMES\n // ==========================================================================\n\n /**\n * Trim old command outcomes to bound memory.\n */\n private trimCommandOutcomes(): void {\n while (this.commandOutcomeOrder.length > this.maxCommandOutcomes) {\n const oldest = this.commandOutcomeOrder.shift();\n if (!oldest) break;\n this.commandOutcomes.delete(oldest);\n }\n }\n\n /**\n * Store a completed command outcome.\n */\n storeCommandOutcome(outcome: CommandOutcomeRecord): void {\n const existed = this.commandOutcomes.has(outcome.commandId);\n this.commandOutcomes.set(outcome.commandId, outcome);\n\n if (!existed) {\n this.commandOutcomeOrder.push(outcome.commandId);\n this.trimCommandOutcomes();\n }\n }\n\n /**\n * Get a completed command outcome by ID.\n */\n getCommandOutcome(commandId: string): CommandOutcomeRecord | undefined {\n return this.commandOutcomes.get(commandId);\n }\n\n // ==========================================================================\n // IN-FLIGHT TRACKING\n // ==========================================================================\n\n /**\n * Check whether an in-flight command can be tracked for this command ID.\n *\n * Returns true if:\n * - the command ID is already tracked (replacement/update path), OR\n * - there is free capacity for a new tracked command.\n */\n canRegisterInFlight(commandId: string): boolean {\n const existed = this.commandInFlightById.has(commandId);\n return existed || this.inFlightOrder.length < this.maxInFlightCommands;\n }\n\n /**\n * Register an in-flight command.\n *\n * ADR-0001: Rejects when limit exceeded instead of evicting.\n * Eviction breaks dependency chains - if command A depends on command B,\n * and B is evicted, A fails with \"unknown dependency\". Rejection preserves\n * correctness at the cost of temporary unavailability under load.\n *\n * @returns true if registered, false if limit exceeded\n */\n registerInFlight(commandId: string, record: InFlightCommandRecord): boolean {\n const existed = this.commandInFlightById.has(commandId);\n\n // Reject if at capacity and this is a new entry\n if (!existed && this.inFlightOrder.length >= this.maxInFlightCommands) {\n this.inFlightRejections++;\n return false;\n }\n\n this.commandInFlightById.set(commandId, record);\n\n if (!existed) {\n this.inFlightOrder.push(commandId);\n }\n return true;\n }\n\n /**\n * Unregister an in-flight command.\n * Only removes if the record matches (prevents race conditions).\n */\n unregisterInFlight(commandId: string, record: InFlightCommandRecord): void {\n if (this.commandInFlightById.get(commandId) === record) {\n this.commandInFlightById.delete(commandId);\n // Remove from order array\n const idx = this.inFlightOrder.indexOf(commandId);\n if (idx !== -1) {\n this.inFlightOrder.splice(idx, 1);\n }\n }\n }\n\n /**\n * Get an in-flight command by ID.\n */\n getInFlight(commandId: string): InFlightCommandRecord | undefined {\n return this.commandInFlightById.get(commandId);\n }\n\n // ==========================================================================\n // REPLAY CHECK (MAIN API)\n // ==========================================================================\n\n /**\n * Create a conflict response for fingerprint mismatch.\n */\n private createConflictResponse(\n id: string | undefined,\n commandType: string,\n conflictType: \"id\" | \"idempotencyKey\",\n value: string,\n originalType: string\n ): RpcResponse {\n return {\n id,\n type: \"response\",\n command: commandType,\n success: false,\n error: `Conflicting ${conflictType} '${value}': previously used for '${originalType}', now used for '${commandType}'`,\n };\n }\n\n /**\n * Clone a cached response for a new request.\n * Preserves or strips ID based on whether the new request has one.\n */\n private cloneResponseForRequest(\n response: RpcResponse,\n requestId: string | undefined\n ): RpcResponse {\n if (requestId === undefined) {\n const { id: _oldId, ...rest } = response;\n return { ...rest };\n }\n return { ...response, id: requestId };\n }\n\n /**\n * Check for replay opportunities or conflicts.\n *\n * Call this BEFORE executing a command. Returns:\n * - \"proceed\": No replay possible, execute normally\n * - \"conflict\": Fingerprint mismatch, return error response\n * - \"replay_cached\": Found cached response, return it (with replayed: true)\n * - \"replay_inflight\": Found in-flight command, await its promise\n */\n checkReplay(command: RpcCommand, fingerprint: string): ReplayCheckResult {\n const id = getCommandId(command);\n const commandType = command.type;\n const idempotencyKey = getCommandIdempotencyKey(command);\n\n // 1. Check idempotency key cache\n if (idempotencyKey) {\n const cacheKey = this.buildIdempotencyCacheKey(command, idempotencyKey);\n const cached = this.idempotencyCache.get(cacheKey);\n if (cached) {\n // Fingerprint conflict?\n if (cached.fingerprint !== fingerprint) {\n return {\n kind: \"conflict\",\n response: this.createConflictResponse(\n id,\n commandType,\n \"idempotencyKey\",\n idempotencyKey,\n cached.commandType\n ),\n };\n }\n\n // Replay cached response\n return {\n kind: \"replay_cached\",\n response: this.cloneResponseForRequest({ ...cached.response, replayed: true }, id),\n };\n }\n }\n\n // 2. Check for explicit command ID\n if (id) {\n // 2a. Check completed outcomes\n const completed = this.commandOutcomes.get(id);\n if (completed) {\n // Fingerprint conflict?\n if (completed.fingerprint !== fingerprint) {\n return {\n kind: \"conflict\",\n response: this.createConflictResponse(id, commandType, \"id\", id, completed.commandType),\n };\n }\n\n // Replay completed response\n return {\n kind: \"replay_cached\",\n response: this.cloneResponseForRequest({ ...completed.response, replayed: true }, id),\n };\n }\n\n // 2b. Check in-flight commands\n const inFlight = this.commandInFlightById.get(id);\n if (inFlight) {\n // Fingerprint conflict?\n if (inFlight.fingerprint !== fingerprint) {\n return {\n kind: \"conflict\",\n response: this.createConflictResponse(id, commandType, \"id\", id, inFlight.commandType),\n };\n }\n\n // Wait for in-flight to complete and replay\n return {\n kind: \"replay_inflight\",\n promise: inFlight.promise.then((response) =>\n this.cloneResponseForRequest({ ...response, replayed: true }, id)\n ),\n };\n }\n }\n\n // No replay possible, proceed with execution\n return { kind: \"proceed\" };\n }\n\n // ==========================================================================\n // LIFECYCLE\n // ==========================================================================\n\n /**\n * Clear all state (used during disposal).\n * Note: Does NOT reset syntheticCommandSequence to prevent ID collisions.\n * Process start time ensures uniqueness even after clear.\n */\n clear(): void {\n this.commandInFlightById.clear();\n this.inFlightOrder = [];\n this.commandOutcomes.clear();\n this.commandOutcomeOrder = [];\n this.idempotencyCache.clear();\n // Don't reset syntheticCommandSequence - processStartTime ensures uniqueness\n this.inFlightRejections = 0;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Router - extensible command dispatch via handler map.
|
|
3
|
+
*
|
|
4
|
+
* Each handler is a self-contained function that takes a session and command,
|
|
5
|
+
* executes against the session, and returns a response.
|
|
6
|
+
*
|
|
7
|
+
* This replaces the giant switch statement, enabling:
|
|
8
|
+
* - Easy addition of new commands
|
|
9
|
+
* - Isolated testing of handlers
|
|
10
|
+
* - Clear separation of concerns
|
|
11
|
+
*/
|
|
12
|
+
import { type AgentSession } from "@mariozechner/pi-coding-agent";
|
|
13
|
+
import type { RpcResponse, SessionInfo } from "./types.js";
|
|
14
|
+
export type CommandHandler = (session: AgentSession, command: any, getSessionInfo: (sessionId: string) => SessionInfo | undefined) => Promise<RpcResponse> | RpcResponse;
|
|
15
|
+
export declare const sessionCommandHandlers: Record<string, CommandHandler>;
|
|
16
|
+
/**
|
|
17
|
+
* Route a session command to the appropriate handler.
|
|
18
|
+
* Returns a response or undefined if no handler exists (unknown command).
|
|
19
|
+
*/
|
|
20
|
+
export declare function routeSessionCommand(session: AgentSession, command: any, getSessionInfo: (sessionId: string) => SessionInfo | undefined): Promise<RpcResponse> | RpcResponse | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Get list of supported session command types.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getSupportedSessionCommands(): string[];
|
|
25
|
+
//# sourceMappingURL=command-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-router.d.ts","sourceRoot":"","sources":["../src/command-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,+BAA+B,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM3D,MAAM,MAAM,cAAc,GAAG,CAC3B,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,GAAG,EACZ,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,KAC3D,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CAAC;AA+WxC,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAkCjE,CAAC;AAMF;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,GAAG,EACZ,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,GAC7D,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,GAAG,SAAS,CAMhD;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,EAAE,CAEtD"}
|