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,189 @@
|
|
|
1
|
+
function isSelectResponse(r) {
|
|
2
|
+
return r.method === "select";
|
|
3
|
+
}
|
|
4
|
+
function isConfirmResponse(r) {
|
|
5
|
+
return r.method === "confirm";
|
|
6
|
+
}
|
|
7
|
+
function isInputResponse(r) {
|
|
8
|
+
return r.method === "input";
|
|
9
|
+
}
|
|
10
|
+
function isEditorResponse(r) {
|
|
11
|
+
return r.method === "editor";
|
|
12
|
+
}
|
|
13
|
+
const DEFAULT_MAX_PENDING_REQUESTS = 1e3;
|
|
14
|
+
class ExtensionUIManager {
|
|
15
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
16
|
+
defaultTimeoutMs;
|
|
17
|
+
maxPendingRequests;
|
|
18
|
+
rejectedCount = 0;
|
|
19
|
+
// Broadcast function - will be wired up in Phase 3
|
|
20
|
+
broadcast;
|
|
21
|
+
constructor(broadcast, defaultTimeoutMs = 6e4, maxPendingRequests = DEFAULT_MAX_PENDING_REQUESTS) {
|
|
22
|
+
this.broadcast = broadcast;
|
|
23
|
+
this.defaultTimeoutMs = defaultTimeoutMs;
|
|
24
|
+
this.maxPendingRequests = maxPendingRequests;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check if a new pending request would exceed the limit.
|
|
28
|
+
* Use this to check before calling createPendingRequest if you need to
|
|
29
|
+
* distinguish limit reached from other failure modes.
|
|
30
|
+
*/
|
|
31
|
+
wouldExceedLimit() {
|
|
32
|
+
return this.pendingRequests.size >= this.maxPendingRequests;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Handle a UI request from an extension.
|
|
36
|
+
* This is called by our ExtensionUIContext implementation.
|
|
37
|
+
* Returns the requestId and a promise that resolves when client responds.
|
|
38
|
+
*
|
|
39
|
+
* Returns null if the pending request limit has been reached.
|
|
40
|
+
*/
|
|
41
|
+
createPendingRequest(sessionId, method, requestData) {
|
|
42
|
+
if (this.pendingRequests.size >= this.maxPendingRequests) {
|
|
43
|
+
this.rejectedCount++;
|
|
44
|
+
console.error(
|
|
45
|
+
`[ExtensionUIManager] Pending request limit reached (${this.maxPendingRequests}), rejecting request for session ${sessionId}`
|
|
46
|
+
);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const requestId = this.generateRequestId(sessionId);
|
|
50
|
+
const timeoutMs = typeof requestData.timeout === "number" && Number.isFinite(requestData.timeout) && requestData.timeout > 0 ? requestData.timeout : this.defaultTimeoutMs;
|
|
51
|
+
const promise = new Promise((resolve, reject) => {
|
|
52
|
+
const timeout = setTimeout(() => {
|
|
53
|
+
const pending = this.pendingRequests.get(requestId);
|
|
54
|
+
if (!pending || pending.settled) return;
|
|
55
|
+
pending.settled = true;
|
|
56
|
+
this.pendingRequests.delete(requestId);
|
|
57
|
+
reject(new Error(`Extension UI request ${requestId} timed out after ${timeoutMs}ms`));
|
|
58
|
+
}, timeoutMs);
|
|
59
|
+
if (timeout.unref) {
|
|
60
|
+
timeout.unref();
|
|
61
|
+
}
|
|
62
|
+
this.pendingRequests.set(requestId, {
|
|
63
|
+
sessionId,
|
|
64
|
+
requestId,
|
|
65
|
+
method,
|
|
66
|
+
resolve,
|
|
67
|
+
reject,
|
|
68
|
+
timeout,
|
|
69
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
70
|
+
settled: false
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
return { requestId, promise };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Broadcast a UI request to clients.
|
|
77
|
+
*/
|
|
78
|
+
broadcastUIRequest(sessionId, requestId, method, data) {
|
|
79
|
+
this.broadcast(sessionId, {
|
|
80
|
+
type: "extension_ui_request",
|
|
81
|
+
requestId,
|
|
82
|
+
method,
|
|
83
|
+
...data
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Handle a response from a client to a pending UI request.
|
|
88
|
+
* Returns true if the response was handled, false if no pending request found.
|
|
89
|
+
*/
|
|
90
|
+
handleUIResponse(command) {
|
|
91
|
+
const pending = this.pendingRequests.get(command.requestId);
|
|
92
|
+
if (!pending) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: `No pending UI request with id ${command.requestId}`
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (pending.settled) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: `Request ${command.requestId} already settled`
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (pending.sessionId !== command.sessionId) {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
error: `Session ID mismatch for request ${command.requestId}`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
pending.settled = true;
|
|
111
|
+
clearTimeout(pending.timeout);
|
|
112
|
+
this.pendingRequests.delete(command.requestId);
|
|
113
|
+
pending.resolve(command.response);
|
|
114
|
+
return { success: true };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get all pending requests for a session (for debugging/cleanup).
|
|
118
|
+
*/
|
|
119
|
+
getPendingRequests(sessionId) {
|
|
120
|
+
const requests = [];
|
|
121
|
+
for (const pending of this.pendingRequests.values()) {
|
|
122
|
+
if (pending.sessionId === sessionId) {
|
|
123
|
+
requests.push(pending);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return requests;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Cancel all pending requests for a session (e.g., when session is deleted).
|
|
130
|
+
*/
|
|
131
|
+
cancelSessionRequests(sessionId) {
|
|
132
|
+
for (const [requestId, pending] of this.pendingRequests.entries()) {
|
|
133
|
+
if (pending.sessionId === sessionId && !pending.settled) {
|
|
134
|
+
pending.settled = true;
|
|
135
|
+
clearTimeout(pending.timeout);
|
|
136
|
+
pending.reject(new Error(`Session ${sessionId} was deleted`));
|
|
137
|
+
this.pendingRequests.delete(requestId);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Cancel a specific pending request (e.g., on abort signal).
|
|
143
|
+
*/
|
|
144
|
+
cancelRequest(requestId) {
|
|
145
|
+
const pending = this.pendingRequests.get(requestId);
|
|
146
|
+
if (pending && !pending.settled) {
|
|
147
|
+
pending.settled = true;
|
|
148
|
+
clearTimeout(pending.timeout);
|
|
149
|
+
pending.reject(new Error(`Request ${requestId} was cancelled`));
|
|
150
|
+
this.pendingRequests.delete(requestId);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get count of pending requests (for monitoring).
|
|
155
|
+
*/
|
|
156
|
+
getPendingCount() {
|
|
157
|
+
return this.pendingRequests.size;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get full statistics about the manager (for monitoring).
|
|
161
|
+
*/
|
|
162
|
+
getStats() {
|
|
163
|
+
return {
|
|
164
|
+
pendingCount: this.pendingRequests.size,
|
|
165
|
+
maxPendingRequests: this.maxPendingRequests,
|
|
166
|
+
rejectedCount: this.rejectedCount
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Reset rejected count (for testing).
|
|
171
|
+
*/
|
|
172
|
+
resetStats() {
|
|
173
|
+
this.rejectedCount = 0;
|
|
174
|
+
}
|
|
175
|
+
// ===========================================================================
|
|
176
|
+
// PRIVATE
|
|
177
|
+
// ===========================================================================
|
|
178
|
+
generateRequestId(sessionId) {
|
|
179
|
+
return `${sessionId}:${Date.now()}:${Math.random().toString(36).substring(2, 9)}`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export {
|
|
183
|
+
ExtensionUIManager,
|
|
184
|
+
isConfirmResponse,
|
|
185
|
+
isEditorResponse,
|
|
186
|
+
isInputResponse,
|
|
187
|
+
isSelectResponse
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=extension-ui.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/extension-ui.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Extension UI - tracks pending UI requests from extensions and routes responses.\n *\n * NOTE: Extension UI requests are NOT AgentSessionEvents. They come through\n * the ExtensionUIContext interface which must be provided when binding extensions.\n *\n * This module provides:\n * - Types for extension UI commands/responses\n * - ExtensionUIManager for tracking pending requests (used when we provide our own UIContext)\n *\n * Phase 3 will wire this up by providing a custom ExtensionUIContext to bindExtensions().\n */\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\nexport interface PendingUIRequest {\n sessionId: string;\n requestId: string;\n method: ExtensionUIMethod;\n resolve: (response: ExtensionUIResponseValue) => void;\n reject: (error: Error) => void;\n timeout: NodeJS.Timeout;\n createdAt: Date;\n /** Guard flag to prevent double-resolve on race between timeout and cancel */\n settled: boolean;\n}\n\nexport type ExtensionUIMethod =\n | \"select\"\n | \"confirm\"\n | \"input\"\n | \"editor\"\n | \"interview\"\n | \"notify\"\n | \"setStatus\"\n | \"setWidget\"\n | \"setTitle\";\n\nexport type ExtensionUIResponseValue =\n | { method: \"select\"; value: string }\n | { method: \"confirm\"; confirmed: boolean }\n | { method: \"input\"; value: string }\n | { method: \"editor\"; value: string }\n | { method: \"interview\"; responses: Record<string, any> }\n | { method: \"cancelled\" };\n\n// Type guards for response types\nexport function isSelectResponse(\n r: ExtensionUIResponseValue\n): r is { method: \"select\"; value: string } {\n return r.method === \"select\";\n}\n\nexport function isConfirmResponse(\n r: ExtensionUIResponseValue\n): r is { method: \"confirm\"; confirmed: boolean } {\n return r.method === \"confirm\";\n}\n\nexport function isInputResponse(\n r: ExtensionUIResponseValue\n): r is { method: \"input\"; value: string } {\n return r.method === \"input\";\n}\n\nexport function isEditorResponse(\n r: ExtensionUIResponseValue\n): r is { method: \"editor\"; value: string } {\n return r.method === \"editor\";\n}\n\n// Command from client to respond to UI request\nexport interface ExtensionUIResponseCommand {\n id?: string;\n sessionId: string;\n type: \"extension_ui_response\";\n requestId: string;\n response: ExtensionUIResponseValue;\n}\n\n// =============================================================================\n// EXTENSION UI MANAGER\n// =============================================================================\n\n/** Default maximum pending UI requests per manager. */\nconst DEFAULT_MAX_PENDING_REQUESTS = 1000;\n\n/**\n * Statistics about the extension UI manager.\n */\nexport interface ExtensionUIManagerStats {\n /** Current number of pending requests. */\n pendingCount: number;\n /** Maximum pending requests allowed. */\n maxPendingRequests: number;\n /** Total requests rejected due to limit. */\n rejectedCount: number;\n}\n\nexport class ExtensionUIManager {\n private pendingRequests = new Map<string, PendingUIRequest>();\n private defaultTimeoutMs: number;\n private maxPendingRequests: number;\n private rejectedCount = 0;\n // Broadcast function - will be wired up in Phase 3\n private broadcast: (sessionId: string, event: any) => void;\n\n constructor(\n broadcast: (sessionId: string, event: any) => void,\n defaultTimeoutMs: number = 60000,\n maxPendingRequests: number = DEFAULT_MAX_PENDING_REQUESTS\n ) {\n this.broadcast = broadcast;\n this.defaultTimeoutMs = defaultTimeoutMs;\n this.maxPendingRequests = maxPendingRequests;\n }\n\n /**\n * Check if a new pending request would exceed the limit.\n * Use this to check before calling createPendingRequest if you need to\n * distinguish limit reached from other failure modes.\n */\n wouldExceedLimit(): boolean {\n return this.pendingRequests.size >= this.maxPendingRequests;\n }\n\n /**\n * Handle a UI request from an extension.\n * This is called by our ExtensionUIContext implementation.\n * Returns the requestId and a promise that resolves when client responds.\n *\n * Returns null if the pending request limit has been reached.\n */\n createPendingRequest(\n sessionId: string,\n method: ExtensionUIMethod,\n requestData: Record<string, any>\n ): { requestId: string; promise: Promise<ExtensionUIResponseValue> } | null {\n // Check pending request limit to prevent memory exhaustion\n if (this.pendingRequests.size >= this.maxPendingRequests) {\n this.rejectedCount++;\n console.error(\n `[ExtensionUIManager] Pending request limit reached (${this.maxPendingRequests}), rejecting request for session ${sessionId}`\n );\n return null;\n }\n\n const requestId = this.generateRequestId(sessionId);\n const timeoutMs =\n typeof requestData.timeout === \"number\" &&\n Number.isFinite(requestData.timeout) &&\n requestData.timeout > 0\n ? requestData.timeout\n : this.defaultTimeoutMs;\n\n const promise = new Promise<ExtensionUIResponseValue>((resolve, reject) => {\n const timeout = setTimeout(() => {\n const pending = this.pendingRequests.get(requestId);\n if (!pending || pending.settled) return; // Guard against race\n pending.settled = true;\n this.pendingRequests.delete(requestId);\n reject(new Error(`Extension UI request ${requestId} timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n // Don't prevent process exit if waiting for UI response\n if (timeout.unref) {\n timeout.unref();\n }\n\n this.pendingRequests.set(requestId, {\n sessionId,\n requestId,\n method,\n resolve,\n reject,\n timeout,\n createdAt: new Date(),\n settled: false,\n });\n });\n\n return { requestId, promise };\n }\n\n /**\n * Broadcast a UI request to clients.\n */\n broadcastUIRequest(\n sessionId: string,\n requestId: string,\n method: ExtensionUIMethod,\n data: Record<string, any>\n ): void {\n this.broadcast(sessionId, {\n type: \"extension_ui_request\",\n requestId,\n method,\n ...data,\n });\n }\n\n /**\n * Handle a response from a client to a pending UI request.\n * Returns true if the response was handled, false if no pending request found.\n */\n handleUIResponse(command: ExtensionUIResponseCommand): { success: boolean; error?: string } {\n const pending = this.pendingRequests.get(command.requestId);\n\n if (!pending) {\n return {\n success: false,\n error: `No pending UI request with id ${command.requestId}`,\n };\n }\n\n // Guard against race with timeout/cancel\n if (pending.settled) {\n return {\n success: false,\n error: `Request ${command.requestId} already settled`,\n };\n }\n\n // Verify sessionId matches\n if (pending.sessionId !== command.sessionId) {\n return {\n success: false,\n error: `Session ID mismatch for request ${command.requestId}`,\n };\n }\n\n // Mark as settled, clear timeout and remove from pending\n pending.settled = true;\n clearTimeout(pending.timeout);\n this.pendingRequests.delete(command.requestId);\n\n // Resolve the promise\n pending.resolve(command.response);\n\n return { success: true };\n }\n\n /**\n * Get all pending requests for a session (for debugging/cleanup).\n */\n getPendingRequests(sessionId: string): PendingUIRequest[] {\n const requests: PendingUIRequest[] = [];\n for (const pending of this.pendingRequests.values()) {\n if (pending.sessionId === sessionId) {\n requests.push(pending);\n }\n }\n return requests;\n }\n\n /**\n * Cancel all pending requests for a session (e.g., when session is deleted).\n */\n cancelSessionRequests(sessionId: string): void {\n for (const [requestId, pending] of this.pendingRequests.entries()) {\n if (pending.sessionId === sessionId && !pending.settled) {\n pending.settled = true;\n clearTimeout(pending.timeout);\n pending.reject(new Error(`Session ${sessionId} was deleted`));\n this.pendingRequests.delete(requestId);\n }\n }\n }\n\n /**\n * Cancel a specific pending request (e.g., on abort signal).\n */\n cancelRequest(requestId: string): void {\n const pending = this.pendingRequests.get(requestId);\n if (pending && !pending.settled) {\n pending.settled = true;\n clearTimeout(pending.timeout);\n pending.reject(new Error(`Request ${requestId} was cancelled`));\n this.pendingRequests.delete(requestId);\n }\n }\n\n /**\n * Get count of pending requests (for monitoring).\n */\n getPendingCount(): number {\n return this.pendingRequests.size;\n }\n\n /**\n * Get full statistics about the manager (for monitoring).\n */\n getStats(): ExtensionUIManagerStats {\n return {\n pendingCount: this.pendingRequests.size,\n maxPendingRequests: this.maxPendingRequests,\n rejectedCount: this.rejectedCount,\n };\n }\n\n /**\n * Reset rejected count (for testing).\n */\n resetStats(): void {\n this.rejectedCount = 0;\n }\n\n // ===========================================================================\n // PRIVATE\n // ===========================================================================\n\n private generateRequestId(sessionId: string): string {\n return `${sessionId}:${Date.now()}:${Math.random().toString(36).substring(2, 9)}`;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResourceGovernor - Enforces all resource limits for pi-server.
|
|
3
|
+
*
|
|
4
|
+
* A single nexus point for:
|
|
5
|
+
* - Message size limits (prevents OOM)
|
|
6
|
+
* - Session limits (prevents resource exhaustion)
|
|
7
|
+
* - Rate limiting (prevents abuse)
|
|
8
|
+
* - Heartbeat tracking (enables zombie detection)
|
|
9
|
+
* - Connection limits (prevents DoS)
|
|
10
|
+
*
|
|
11
|
+
* Makes testing trivial (mock the governor).
|
|
12
|
+
*/
|
|
13
|
+
import type { MetricsEmitter } from "./metrics-emitter.js";
|
|
14
|
+
export interface ResourceGovernorConfig {
|
|
15
|
+
/** Maximum concurrent sessions (default: 100) */
|
|
16
|
+
maxSessions: number;
|
|
17
|
+
/** Maximum message size in bytes (default: 10MB) */
|
|
18
|
+
maxMessageSizeBytes: number;
|
|
19
|
+
/** Maximum commands per minute per session (default: 100) */
|
|
20
|
+
maxCommandsPerMinute: number;
|
|
21
|
+
/** Maximum commands per minute globally across all sessions (default: 1000) */
|
|
22
|
+
maxGlobalCommandsPerMinute: number;
|
|
23
|
+
/** Maximum concurrent WebSocket connections (default: 1000) */
|
|
24
|
+
maxConnections: number;
|
|
25
|
+
/** Heartbeat interval in ms for zombie detection (default: 30000) */
|
|
26
|
+
heartbeatIntervalMs: number;
|
|
27
|
+
/** Time without heartbeat before session is considered zombie (default: 5 min) */
|
|
28
|
+
zombieTimeoutMs: number;
|
|
29
|
+
/** Maximum extension_ui_response commands per minute per session (default: 60) */
|
|
30
|
+
maxExtensionUIResponsePerMinute: number;
|
|
31
|
+
/** Maximum session lifetime in ms (0 = unlimited, default: 24 hours) */
|
|
32
|
+
maxSessionLifetimeMs: number;
|
|
33
|
+
}
|
|
34
|
+
export declare const DEFAULT_CONFIG: ResourceGovernorConfig;
|
|
35
|
+
export interface GovernorMetrics {
|
|
36
|
+
sessionCount: number;
|
|
37
|
+
connectionCount: number;
|
|
38
|
+
totalCommandsExecuted: number;
|
|
39
|
+
commandsRejected: {
|
|
40
|
+
sessionLimit: number;
|
|
41
|
+
messageSize: number;
|
|
42
|
+
rateLimit: number;
|
|
43
|
+
globalRateLimit: number;
|
|
44
|
+
connectionLimit: number;
|
|
45
|
+
extensionUIResponseRateLimit: number;
|
|
46
|
+
};
|
|
47
|
+
zombieSessionsDetected: number;
|
|
48
|
+
zombieSessionsCleaned: number;
|
|
49
|
+
/** Count of double-unregister errors (session or connection unregistered twice) */
|
|
50
|
+
doubleUnregisterErrors: number;
|
|
51
|
+
rateLimitUsage: {
|
|
52
|
+
globalCount: number;
|
|
53
|
+
globalLimit: number;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface RejectionResult {
|
|
57
|
+
allowed: false;
|
|
58
|
+
reason: string;
|
|
59
|
+
}
|
|
60
|
+
export interface AllowResult {
|
|
61
|
+
allowed: true;
|
|
62
|
+
}
|
|
63
|
+
export type GovernorResult = AllowResult | RejectionResult;
|
|
64
|
+
/**
|
|
65
|
+
* ResourceGovernor enforces resource limits for the server.
|
|
66
|
+
*
|
|
67
|
+
* Memory management:
|
|
68
|
+
* - Rate limit timestamps are cleaned up periodically via startPeriodicCleanup()
|
|
69
|
+
* - Session-specific data is cleaned up when sessions are deleted
|
|
70
|
+
* - Call cleanupStaleData() after deleting sessions
|
|
71
|
+
*/
|
|
72
|
+
export declare class ResourceGovernor {
|
|
73
|
+
private config;
|
|
74
|
+
private metrics?;
|
|
75
|
+
private sessionCount;
|
|
76
|
+
private connectionCount;
|
|
77
|
+
private commandTimestamps;
|
|
78
|
+
private globalCommandTimestamps;
|
|
79
|
+
private extensionUIResponseTimestamps;
|
|
80
|
+
private lastHeartbeat;
|
|
81
|
+
private sessionCreatedAt;
|
|
82
|
+
private totalCommandsExecuted;
|
|
83
|
+
private commandsRejected;
|
|
84
|
+
private zombieSessionsDetected;
|
|
85
|
+
private zombieSessionsCleaned;
|
|
86
|
+
private doubleUnregisterErrors;
|
|
87
|
+
/** Generation counter for unique rate limit entry IDs */
|
|
88
|
+
private generationCounter;
|
|
89
|
+
/** Periodic cleanup timer for stale timestamps */
|
|
90
|
+
private cleanupTimer;
|
|
91
|
+
constructor(config?: ResourceGovernorConfig, metrics?: MetricsEmitter | undefined);
|
|
92
|
+
/**
|
|
93
|
+
* Set the metrics emitter for monitoring.
|
|
94
|
+
* Can be called after construction to wire up metrics.
|
|
95
|
+
*/
|
|
96
|
+
setMetrics(metrics: MetricsEmitter): void;
|
|
97
|
+
/**
|
|
98
|
+
* Increment generation counter and emit metric.
|
|
99
|
+
* Used for unique rate limit entry IDs and overflow monitoring.
|
|
100
|
+
*/
|
|
101
|
+
private nextGeneration;
|
|
102
|
+
getConfig(): Readonly<ResourceGovernorConfig>;
|
|
103
|
+
/**
|
|
104
|
+
* Validate a session ID.
|
|
105
|
+
* Returns null if valid, or an error message if invalid.
|
|
106
|
+
*/
|
|
107
|
+
validateSessionId(sessionId: string): string | null;
|
|
108
|
+
/**
|
|
109
|
+
* Validate a working directory path.
|
|
110
|
+
* Returns null if valid, or an error message if invalid.
|
|
111
|
+
*/
|
|
112
|
+
validateCwd(cwd: string): string | null;
|
|
113
|
+
/**
|
|
114
|
+
* Atomically check and reserve a session slot.
|
|
115
|
+
* Returns true if slot was reserved, false if limit reached.
|
|
116
|
+
*/
|
|
117
|
+
tryReserveSessionSlot(): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Release a reserved session slot (used if session creation fails after reservation).
|
|
120
|
+
* Tracks double-unregister as error metric instead of silently masking.
|
|
121
|
+
*/
|
|
122
|
+
releaseSessionSlot(): void;
|
|
123
|
+
/**
|
|
124
|
+
* Check if a new session can be created.
|
|
125
|
+
* @deprecated Use tryReserveSessionSlot for atomic operation. Will be removed in v2.0.0.
|
|
126
|
+
*/
|
|
127
|
+
canCreateSession(): GovernorResult;
|
|
128
|
+
/**
|
|
129
|
+
* Register a new session.
|
|
130
|
+
* @deprecated Use tryReserveSessionSlot for atomic operation. Will be removed in v2.0.0.
|
|
131
|
+
*/
|
|
132
|
+
registerSession(sessionId: string): void;
|
|
133
|
+
/**
|
|
134
|
+
* Unregister a session. Call AFTER session is deleted.
|
|
135
|
+
* Also cleans up rate limit timestamps for this session.
|
|
136
|
+
*/
|
|
137
|
+
unregisterSession(sessionId: string): void;
|
|
138
|
+
/**
|
|
139
|
+
* Get current session count.
|
|
140
|
+
*/
|
|
141
|
+
getSessionCount(): number;
|
|
142
|
+
/**
|
|
143
|
+
* Check if a new connection can be accepted.
|
|
144
|
+
*/
|
|
145
|
+
canAcceptConnection(): GovernorResult;
|
|
146
|
+
/**
|
|
147
|
+
* Register a new connection.
|
|
148
|
+
*/
|
|
149
|
+
registerConnection(): void;
|
|
150
|
+
/**
|
|
151
|
+
* Unregister a connection.
|
|
152
|
+
* Tracks double-unregister as error metric instead of silently masking.
|
|
153
|
+
*/
|
|
154
|
+
unregisterConnection(): void;
|
|
155
|
+
/**
|
|
156
|
+
* Get current connection count.
|
|
157
|
+
*/
|
|
158
|
+
getConnectionCount(): number;
|
|
159
|
+
/**
|
|
160
|
+
* Check if a message of the given size can be accepted.
|
|
161
|
+
* Rejects negative sizes, NaN, and sizes exceeding the limit.
|
|
162
|
+
*/
|
|
163
|
+
canAcceptMessage(sizeBytes: number): GovernorResult;
|
|
164
|
+
/**
|
|
165
|
+
* Check if a command can be executed for the given session.
|
|
166
|
+
* Implements sliding window rate limiting (both per-session and global).
|
|
167
|
+
*
|
|
168
|
+
* @returns The generation marker for refund, or rejection result
|
|
169
|
+
*/
|
|
170
|
+
canExecuteCommand(sessionId: string): GovernorResult & {
|
|
171
|
+
generation?: number;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Refund a previously counted command (e.g. command failed before execution).
|
|
175
|
+
* Uses generation marker to ensure correct entry is removed.
|
|
176
|
+
*/
|
|
177
|
+
refundCommand(sessionId: string, generation: number): void;
|
|
178
|
+
/**
|
|
179
|
+
* Get current rate limit usage for observability.
|
|
180
|
+
*/
|
|
181
|
+
getRateLimitUsage(sessionId: string): {
|
|
182
|
+
session: number;
|
|
183
|
+
global: number;
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Check if an extension_ui_response command can be executed for the given session.
|
|
187
|
+
* This is a separate, more restrictive rate limit to prevent abuse of UI responses.
|
|
188
|
+
*/
|
|
189
|
+
canExecuteExtensionUIResponse(sessionId: string): GovernorResult;
|
|
190
|
+
/**
|
|
191
|
+
* Record a heartbeat for a session.
|
|
192
|
+
* Also tracks session creation time for lifetime enforcement.
|
|
193
|
+
*/
|
|
194
|
+
recordHeartbeat(sessionId: string): void;
|
|
195
|
+
/**
|
|
196
|
+
* Get list of zombie session IDs.
|
|
197
|
+
*
|
|
198
|
+
* @param recordDetection Whether to increment zombie detection metrics.
|
|
199
|
+
*/
|
|
200
|
+
getZombieSessions(recordDetection?: boolean): string[];
|
|
201
|
+
/**
|
|
202
|
+
* Clean up zombie sessions. Returns IDs of cleaned sessions.
|
|
203
|
+
* Call this periodically or when you want to force cleanup.
|
|
204
|
+
*/
|
|
205
|
+
cleanupZombieSessions(): string[];
|
|
206
|
+
/**
|
|
207
|
+
* Get the last heartbeat time for a session.
|
|
208
|
+
*/
|
|
209
|
+
getLastHeartbeat(sessionId: string): number | undefined;
|
|
210
|
+
/**
|
|
211
|
+
* Get current metrics for observability.
|
|
212
|
+
*/
|
|
213
|
+
getMetrics(): GovernorMetrics;
|
|
214
|
+
/**
|
|
215
|
+
* Check if the server is healthy.
|
|
216
|
+
*/
|
|
217
|
+
isHealthy(): {
|
|
218
|
+
healthy: boolean;
|
|
219
|
+
issues: string[];
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Reset metrics (useful for testing).
|
|
223
|
+
*/
|
|
224
|
+
resetMetrics(): void;
|
|
225
|
+
/**
|
|
226
|
+
* Clean up stale rate limit data.
|
|
227
|
+
* Also cleans extensionUIResponseTimestamps.
|
|
228
|
+
*/
|
|
229
|
+
cleanupStaleTimestamps(): void;
|
|
230
|
+
/**
|
|
231
|
+
* Start periodic cleanup of stale timestamps.
|
|
232
|
+
* Call this during server startup to prevent memory leaks from inactive sessions.
|
|
233
|
+
* @param intervalMs Cleanup interval (default: 5 minutes)
|
|
234
|
+
*/
|
|
235
|
+
startPeriodicCleanup(intervalMs?: number): void;
|
|
236
|
+
/**
|
|
237
|
+
* Stop periodic cleanup.
|
|
238
|
+
*/
|
|
239
|
+
stopPeriodicCleanup(): void;
|
|
240
|
+
/**
|
|
241
|
+
* Clean up stale data for deleted sessions.
|
|
242
|
+
*/
|
|
243
|
+
cleanupStaleData(activeSessionIds: Set<string>): void;
|
|
244
|
+
/**
|
|
245
|
+
* Get list of expired session IDs (exceeded maxSessionLifetimeMs).
|
|
246
|
+
* Returns empty array if maxSessionLifetimeMs is 0 (unlimited).
|
|
247
|
+
*/
|
|
248
|
+
getExpiredSessions(): string[];
|
|
249
|
+
/**
|
|
250
|
+
* Get the creation time for a session.
|
|
251
|
+
*/
|
|
252
|
+
getSessionCreatedAt(sessionId: string): number | undefined;
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=resource-governor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-governor.d.ts","sourceRoot":"","sources":["../src/resource-governor.ts"],"names":[],"mappings":"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"}
|