@mondaydotcomorg/atp-runtime 0.17.16 → 0.18.4-rc.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/dist/approval/index.js +2 -1
- package/dist/approval/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/index.d.ts +1 -1
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +1 -1
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/replay.d.ts +75 -0
- package/dist/llm/replay.d.ts.map +1 -1
- package/dist/llm/replay.js +187 -5
- package/dist/llm/replay.js.map +1 -1
- package/dist/metadata/generated.d.ts +4 -0
- package/dist/metadata/generated.d.ts.map +1 -1
- package/dist/metadata/generated.js +189 -189
- package/dist/metadata/generated.js.map +1 -1
- package/dist/metadata/index.d.ts +14 -1
- package/dist/metadata/index.d.ts.map +1 -1
- package/dist/metadata/index.js +45 -3
- package/dist/metadata/index.js.map +1 -1
- package/dist/metadata/types.d.ts +2 -1
- package/dist/metadata/types.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/approval/index.ts +2 -2
- package/src/index.ts +13 -0
- package/src/llm/index.ts +13 -0
- package/src/llm/replay.ts +239 -11
- package/src/metadata/generated.ts +288 -275
- package/src/metadata/index.ts +59 -3
- package/src/metadata/types.ts +3 -1
package/dist/metadata/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Common metadata interface for runtime APIs
|
|
3
3
|
* Each runtime module exports its metadata for the type generator
|
|
4
4
|
*/
|
|
5
|
+
import { RuntimeAPIName } from './generated';
|
|
5
6
|
export interface RuntimeAPIParam {
|
|
6
7
|
name: string;
|
|
7
8
|
type: string;
|
|
@@ -15,7 +16,7 @@ export interface RuntimeAPIMethod {
|
|
|
15
16
|
returns: string;
|
|
16
17
|
}
|
|
17
18
|
export interface RuntimeAPIMetadata {
|
|
18
|
-
name:
|
|
19
|
+
name: RuntimeAPIName;
|
|
19
20
|
description: string;
|
|
20
21
|
methods: RuntimeAPIMethod[];
|
|
21
22
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/metadata/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/metadata/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,cAAc,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC5B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mondaydotcomorg/atp-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.4-rc.0",
|
|
4
4
|
"description": "Runtime SDK injected into sandbox for Agent Tool Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
|
-
"
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/mondaycom/agent-tool-protocol",
|
|
24
|
+
"directory": "packages/runtime"
|
|
23
25
|
},
|
|
24
26
|
"scripts": {
|
|
25
27
|
"build": "npm run generate:metadata && tsc -p tsconfig.json",
|
|
@@ -32,6 +34,7 @@
|
|
|
32
34
|
"keywords": [
|
|
33
35
|
"agent",
|
|
34
36
|
"protocol",
|
|
37
|
+
"atp",
|
|
35
38
|
"runtime",
|
|
36
39
|
"sdk",
|
|
37
40
|
"ai",
|
package/src/approval/index.ts
CHANGED
|
@@ -44,7 +44,8 @@ class ApprovalAPI {
|
|
|
44
44
|
return cachedResult as ApprovalResponse;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
const shouldPause = shouldPauseForClient();
|
|
48
|
+
if (shouldPause) {
|
|
48
49
|
pauseForCallback(CallbackType.APPROVAL, ApprovalOperation.REQUEST, {
|
|
49
50
|
message,
|
|
50
51
|
context,
|
|
@@ -53,7 +54,6 @@ class ApprovalAPI {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
const handler = getApprovalHandler();
|
|
56
|
-
|
|
57
57
|
if (!handler) {
|
|
58
58
|
throw new Error(
|
|
59
59
|
'Approval handler not configured. Human approval is required but no handler is set.'
|
package/src/index.ts
CHANGED
|
@@ -13,10 +13,23 @@ export type { LogLevel, LoggerConfig, Logger } from './log/index.js';
|
|
|
13
13
|
export { GENERATED_METADATA } from './metadata/generated.js';
|
|
14
14
|
|
|
15
15
|
export {
|
|
16
|
+
initializeExecutionState,
|
|
16
17
|
setClientLLMCallback,
|
|
17
18
|
setPauseForClient,
|
|
19
|
+
shouldPauseForClient,
|
|
18
20
|
setReplayMode,
|
|
19
21
|
getCallSequenceNumber,
|
|
22
|
+
isReplayMode,
|
|
23
|
+
storeAPICallResult,
|
|
24
|
+
getAPICallResults,
|
|
25
|
+
clearAPICallResults,
|
|
26
|
+
setAPIResultCache,
|
|
27
|
+
getAPIResultFromCache,
|
|
28
|
+
storeAPIResultInCache,
|
|
29
|
+
cleanupExecutionState,
|
|
30
|
+
cleanupOldExecutionStates,
|
|
31
|
+
resetAllExecutionState,
|
|
32
|
+
getExecutionStateStats,
|
|
20
33
|
} from './llm/index.js';
|
|
21
34
|
export { initializeCache } from './cache/index.js';
|
|
22
35
|
export { initializeApproval } from './approval/index.js';
|
package/src/llm/index.ts
CHANGED
|
@@ -19,14 +19,27 @@ export type {
|
|
|
19
19
|
} from './types';
|
|
20
20
|
export { setClientLLMCallback, getClientLLMCallback } from './callback.js';
|
|
21
21
|
export {
|
|
22
|
+
initializeExecutionState,
|
|
22
23
|
setPauseForClient,
|
|
24
|
+
shouldPauseForClient,
|
|
23
25
|
setReplayMode,
|
|
24
26
|
getCallSequenceNumber,
|
|
25
27
|
nextSequenceNumber,
|
|
26
28
|
getCachedResult,
|
|
29
|
+
isReplayMode,
|
|
27
30
|
runInExecutionContext,
|
|
28
31
|
setCurrentExecutionId,
|
|
29
32
|
clearCurrentExecutionId,
|
|
33
|
+
storeAPICallResult,
|
|
34
|
+
getAPICallResults,
|
|
35
|
+
clearAPICallResults,
|
|
36
|
+
setAPIResultCache,
|
|
37
|
+
getAPIResultFromCache,
|
|
38
|
+
storeAPIResultInCache,
|
|
39
|
+
cleanupExecutionState,
|
|
40
|
+
cleanupOldExecutionStates,
|
|
41
|
+
resetAllExecutionState,
|
|
42
|
+
getExecutionStateStats,
|
|
30
43
|
} from './replay.js';
|
|
31
44
|
|
|
32
45
|
/**
|
package/src/llm/replay.ts
CHANGED
|
@@ -3,10 +3,22 @@ import { AsyncLocalStorage } from 'async_hooks';
|
|
|
3
3
|
/**
|
|
4
4
|
* Execution-scoped state
|
|
5
5
|
*/
|
|
6
|
+
interface APICallRecord {
|
|
7
|
+
type: string;
|
|
8
|
+
operation: string;
|
|
9
|
+
payload: unknown;
|
|
10
|
+
result: unknown;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
sequenceNumber: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
interface ExecutionState {
|
|
7
16
|
shouldPauseForClient: boolean;
|
|
8
17
|
replayResults: Map<number, unknown> | undefined;
|
|
9
18
|
callSequenceNumber: number;
|
|
19
|
+
apiCallResults: APICallRecord[];
|
|
20
|
+
apiResultCache: Map<string, unknown> | undefined;
|
|
21
|
+
createdAt: number;
|
|
10
22
|
}
|
|
11
23
|
|
|
12
24
|
/**
|
|
@@ -45,7 +57,8 @@ export function clearCurrentExecutionId(): void {
|
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
/**
|
|
48
|
-
* Gets the current execution state
|
|
60
|
+
* Gets the current execution state
|
|
61
|
+
* Note: State must be initialized before calling this. Use initializeExecutionState() first.
|
|
49
62
|
*/
|
|
50
63
|
function getCurrentState(): ExecutionState {
|
|
51
64
|
let executionId = currentExecutionId;
|
|
@@ -60,18 +73,65 @@ function getCurrentState(): ExecutionState {
|
|
|
60
73
|
);
|
|
61
74
|
}
|
|
62
75
|
|
|
76
|
+
console.log(`[STATE] getCurrentState: executionId=${executionId}, hasState=${executionStates.has(executionId)}, totalStates=${executionStates.size}, stateKeys=${Array.from(executionStates.keys())}`);
|
|
77
|
+
|
|
63
78
|
let state = executionStates.get(executionId);
|
|
64
79
|
if (!state) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
// State should have been initialized explicitly at execution start
|
|
81
|
+
// Create it now with a safe default to prevent crashes
|
|
82
|
+
console.warn('[STATE] State not initialized, creating with default. This should not happen.', { executionId });
|
|
83
|
+
state = {
|
|
84
|
+
shouldPauseForClient: false,
|
|
85
|
+
replayResults: undefined,
|
|
86
|
+
callSequenceNumber: 0,
|
|
87
|
+
apiCallResults: [],
|
|
88
|
+
apiResultCache: undefined,
|
|
89
|
+
createdAt: Date.now(),
|
|
90
|
+
};
|
|
91
|
+
executionStates.set(executionId, state);
|
|
92
|
+
} else {
|
|
93
|
+
console.log(`[STATE] Found existing state: shouldPause=${state.shouldPauseForClient}, hasReplay=${!!state.replayResults}, seqNum=${state.callSequenceNumber}`);
|
|
71
94
|
}
|
|
72
95
|
return state;
|
|
73
96
|
}
|
|
74
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Initialize execution state with correct values at execution start
|
|
100
|
+
* This must be called before any state access to ensure correct pause mode
|
|
101
|
+
*/
|
|
102
|
+
export function initializeExecutionState(shouldPause: boolean): void {
|
|
103
|
+
const executionId = currentExecutionId || executionContext.getStore();
|
|
104
|
+
if (!executionId) {
|
|
105
|
+
throw new Error('No execution context set. Executor must call setCurrentExecutionId() before initializeExecutionState().');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log(`[INIT] initializeExecutionState called: executionId=${executionId}, shouldPause=${shouldPause}, existingState=${executionStates.has(executionId)}`);
|
|
109
|
+
|
|
110
|
+
const existingState = executionStates.get(executionId);
|
|
111
|
+
if (existingState) {
|
|
112
|
+
existingState.shouldPauseForClient = shouldPause;
|
|
113
|
+
if (!existingState.apiCallResults) {
|
|
114
|
+
existingState.apiCallResults = [];
|
|
115
|
+
}
|
|
116
|
+
if (!existingState.apiResultCache) {
|
|
117
|
+
existingState.apiResultCache = undefined;
|
|
118
|
+
}
|
|
119
|
+
console.log(`[INIT] Preserving existing state: replaySize=${existingState.replayResults?.size || 0}, counter=${existingState.callSequenceNumber}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const state: ExecutionState = {
|
|
124
|
+
shouldPauseForClient: shouldPause,
|
|
125
|
+
replayResults: undefined,
|
|
126
|
+
callSequenceNumber: 0,
|
|
127
|
+
apiCallResults: [],
|
|
128
|
+
apiResultCache: undefined,
|
|
129
|
+
createdAt: Date.now(),
|
|
130
|
+
};
|
|
131
|
+
console.log(`[INIT] Creating new state: shouldPause=${shouldPause}`);
|
|
132
|
+
executionStates.set(executionId, state);
|
|
133
|
+
}
|
|
134
|
+
|
|
75
135
|
/**
|
|
76
136
|
* Runs a function within an execution context
|
|
77
137
|
* @param executionId - Unique ID for this execution
|
|
@@ -86,14 +146,24 @@ export function runInExecutionContext<T>(executionId: string, fn: () => T): T {
|
|
|
86
146
|
* @param pause - If true, throws PauseExecutionError instead of calling callback
|
|
87
147
|
*/
|
|
88
148
|
export function setPauseForClient(pause: boolean): void {
|
|
89
|
-
|
|
149
|
+
const executionId = currentExecutionId || executionContext.getStore();
|
|
150
|
+
if (!executionId) {
|
|
151
|
+
throw new Error('No execution context set. Executor must call setCurrentExecutionId() before setPauseForClient().');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const state = executionStates.get(executionId);
|
|
155
|
+
if (!state) {
|
|
156
|
+
throw new Error('Execution state not initialized. Call initializeExecutionState() first.');
|
|
157
|
+
}
|
|
158
|
+
state.shouldPauseForClient = pause;
|
|
90
159
|
}
|
|
91
160
|
|
|
92
161
|
/**
|
|
93
162
|
* Checks if should pause for client
|
|
94
163
|
*/
|
|
95
164
|
export function shouldPauseForClient(): boolean {
|
|
96
|
-
|
|
165
|
+
const state = getCurrentState();
|
|
166
|
+
return state.shouldPauseForClient;
|
|
97
167
|
}
|
|
98
168
|
|
|
99
169
|
/**
|
|
@@ -101,16 +171,36 @@ export function shouldPauseForClient(): boolean {
|
|
|
101
171
|
* @param results - Map of sequence number to result for replaying callbacks
|
|
102
172
|
*/
|
|
103
173
|
export function setReplayMode(results: Map<number, unknown> | undefined): void {
|
|
174
|
+
const executionId = currentExecutionId || executionContext.getStore();
|
|
175
|
+
console.log(`[REPLAY] setReplayMode called: executionId=${executionId}, replaySize=${results?.size || 0}, replayKeys=${results ? Array.from(results.keys()) : []}`);
|
|
104
176
|
const state = getCurrentState();
|
|
177
|
+
|
|
178
|
+
// Store replay results
|
|
179
|
+
const oldSize = state.replayResults?.size || 0;
|
|
105
180
|
state.replayResults = results;
|
|
181
|
+
|
|
182
|
+
// Always reset counter when setting replay mode
|
|
183
|
+
// - When entering replay mode with cached results: start from 0 to match first call
|
|
184
|
+
// - When clearing replay mode (results=undefined): reset to 0 for clean state
|
|
185
|
+
const oldCounter = state.callSequenceNumber;
|
|
106
186
|
state.callSequenceNumber = 0;
|
|
187
|
+
|
|
188
|
+
if (results && results.size > 0) {
|
|
189
|
+
console.log(`[REPLAY] Entering replay mode: ${oldCounter} -> 0 (have ${results.size} cached results)`);
|
|
190
|
+
} else {
|
|
191
|
+
console.log(`[REPLAY] Clearing replay mode: ${oldCounter} -> 0`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log(`[REPLAY] setReplayMode completed: oldSize=${oldSize}, newSize=${state.replayResults?.size || 0}, callSequenceNumber=${state.callSequenceNumber}`);
|
|
107
195
|
}
|
|
108
196
|
|
|
109
197
|
/**
|
|
110
198
|
* Gets current call sequence number
|
|
111
199
|
*/
|
|
112
200
|
export function getCallSequenceNumber(): number {
|
|
113
|
-
|
|
201
|
+
const state = getCurrentState();
|
|
202
|
+
console.log(`[GET_SEQ] getCallSequenceNumber called: returning ${state.callSequenceNumber}, hasReplay=${!!state.replayResults}, replaySize=${state.replayResults?.size || 0}`);
|
|
203
|
+
return state.callSequenceNumber;
|
|
114
204
|
}
|
|
115
205
|
|
|
116
206
|
/**
|
|
@@ -118,7 +208,10 @@ export function getCallSequenceNumber(): number {
|
|
|
118
208
|
*/
|
|
119
209
|
export function nextSequenceNumber(): number {
|
|
120
210
|
const state = getCurrentState();
|
|
121
|
-
|
|
211
|
+
const current = state.callSequenceNumber;
|
|
212
|
+
state.callSequenceNumber++;
|
|
213
|
+
console.log(`[SEQUENCE] nextSequenceNumber: returning ${current}, next will be ${state.callSequenceNumber}, isReplay=${state.replayResults !== undefined}, replaySize=${state.replayResults?.size || 0}`);
|
|
214
|
+
return current;
|
|
122
215
|
}
|
|
123
216
|
|
|
124
217
|
/**
|
|
@@ -126,10 +219,13 @@ export function nextSequenceNumber(): number {
|
|
|
126
219
|
*/
|
|
127
220
|
export function getCachedResult(sequenceNumber: number): unknown | undefined {
|
|
128
221
|
const state = getCurrentState();
|
|
222
|
+
console.log(`[CACHE] getCachedResult(${sequenceNumber}): hasReplayResults=${!!state.replayResults}, replayKeys=${state.replayResults ? Array.from(state.replayResults.keys()) : []}`);
|
|
129
223
|
if (state.replayResults && state.replayResults.has(sequenceNumber)) {
|
|
130
224
|
const result = state.replayResults.get(sequenceNumber);
|
|
225
|
+
console.log(`[CACHE] Found cached result for sequence ${sequenceNumber}:`, JSON.stringify(result));
|
|
131
226
|
return result;
|
|
132
227
|
}
|
|
228
|
+
console.log(`[CACHE] No cached result for sequence ${sequenceNumber}`);
|
|
133
229
|
return undefined;
|
|
134
230
|
}
|
|
135
231
|
|
|
@@ -139,3 +235,135 @@ export function getCachedResult(sequenceNumber: number): unknown | undefined {
|
|
|
139
235
|
export function isReplayMode(): boolean {
|
|
140
236
|
return getCurrentState().replayResults !== undefined;
|
|
141
237
|
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Store an API call result during execution
|
|
241
|
+
* This is used to track server-side API calls so they can be cached on resume
|
|
242
|
+
*/
|
|
243
|
+
export function storeAPICallResult(record: {
|
|
244
|
+
type: string;
|
|
245
|
+
operation: string;
|
|
246
|
+
payload: unknown;
|
|
247
|
+
result: unknown;
|
|
248
|
+
timestamp: number;
|
|
249
|
+
sequenceNumber: number;
|
|
250
|
+
}): void {
|
|
251
|
+
const state = getCurrentState();
|
|
252
|
+
state.apiCallResults.push(record);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Get all API call results tracked during this execution
|
|
257
|
+
* Used when building callback history on pause
|
|
258
|
+
*/
|
|
259
|
+
export function getAPICallResults(): APICallRecord[] {
|
|
260
|
+
const state = getCurrentState();
|
|
261
|
+
return state.apiCallResults;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Clear API call results (used when execution completes or fails)
|
|
266
|
+
*/
|
|
267
|
+
export function clearAPICallResults(): void {
|
|
268
|
+
const state = getCurrentState();
|
|
269
|
+
state.apiCallResults = [];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Set up API result cache for resume (operation-based, not sequence-based)
|
|
274
|
+
* This allows API calls to find their cached results even if execution order changes
|
|
275
|
+
*/
|
|
276
|
+
export function setAPIResultCache(cache: Map<string, unknown> | undefined): void {
|
|
277
|
+
const state = getCurrentState();
|
|
278
|
+
state.apiResultCache = cache;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get API result from cache by operation name
|
|
283
|
+
*/
|
|
284
|
+
export function getAPIResultFromCache(operation: string): unknown | undefined {
|
|
285
|
+
const state = getCurrentState();
|
|
286
|
+
return state.apiResultCache?.get(operation);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Store API result in cache by operation name (for initial execution)
|
|
291
|
+
*/
|
|
292
|
+
export function storeAPIResultInCache(operation: string, result: unknown): void {
|
|
293
|
+
const state = getCurrentState();
|
|
294
|
+
if (!state.apiResultCache) {
|
|
295
|
+
state.apiResultCache = new Map();
|
|
296
|
+
}
|
|
297
|
+
state.apiResultCache.set(operation, result);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Cleanup a specific execution's state
|
|
302
|
+
* This should be called when an execution completes, fails, or is no longer needed
|
|
303
|
+
*/
|
|
304
|
+
export function cleanupExecutionState(executionId: string): void {
|
|
305
|
+
executionStates.delete(executionId);
|
|
306
|
+
if (currentExecutionId === executionId) {
|
|
307
|
+
currentExecutionId = null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Cleanup old execution states to prevent memory leaks
|
|
313
|
+
* Removes states older than the specified max age (default: 1 hour)
|
|
314
|
+
*/
|
|
315
|
+
export function cleanupOldExecutionStates(maxAgeMs: number = 3600000): number {
|
|
316
|
+
const now = Date.now();
|
|
317
|
+
let cleaned = 0;
|
|
318
|
+
|
|
319
|
+
for (const [executionId, state] of executionStates.entries()) {
|
|
320
|
+
const age = now - state.createdAt;
|
|
321
|
+
if (age > maxAgeMs) {
|
|
322
|
+
executionStates.delete(executionId);
|
|
323
|
+
cleaned++;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return cleaned;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Reset ALL execution state - for testing purposes only
|
|
332
|
+
* WARNING: This will clear all execution states, breaking any in-flight executions
|
|
333
|
+
*/
|
|
334
|
+
export function resetAllExecutionState(): void {
|
|
335
|
+
executionStates.clear();
|
|
336
|
+
currentExecutionId = null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Get execution state statistics - for monitoring/debugging
|
|
341
|
+
*/
|
|
342
|
+
export function getExecutionStateStats(): {
|
|
343
|
+
totalStates: number;
|
|
344
|
+
oldestStateAge: number | null;
|
|
345
|
+
newestStateAge: number | null;
|
|
346
|
+
executionIds: string[];
|
|
347
|
+
} {
|
|
348
|
+
const now = Date.now();
|
|
349
|
+
const executionIds = Array.from(executionStates.keys());
|
|
350
|
+
let oldestAge: number | null = null;
|
|
351
|
+
let newestAge: number | null = null;
|
|
352
|
+
|
|
353
|
+
for (const state of executionStates.values()) {
|
|
354
|
+
const age = now - state.createdAt;
|
|
355
|
+
if (oldestAge === null || age > oldestAge) {
|
|
356
|
+
oldestAge = age;
|
|
357
|
+
}
|
|
358
|
+
if (newestAge === null || age < newestAge) {
|
|
359
|
+
newestAge = age;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
totalStates: executionStates.size,
|
|
365
|
+
oldestStateAge: oldestAge,
|
|
366
|
+
newestStateAge: newestAge,
|
|
367
|
+
executionIds,
|
|
368
|
+
};
|
|
369
|
+
}
|