@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.
@@ -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: string;
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,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC5B"}
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.17.16",
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
- "url": "https://github.com/mondaycom/agent-tool-protocol"
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",
@@ -44,7 +44,8 @@ class ApprovalAPI {
44
44
  return cachedResult as ApprovalResponse;
45
45
  }
46
46
 
47
- if (shouldPauseForClient()) {
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, creating it if needed
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
- state = {
66
- shouldPauseForClient: false,
67
- replayResults: undefined,
68
- callSequenceNumber: 0,
69
- };
70
- executionStates.set(executionId, state);
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
- getCurrentState().shouldPauseForClient = pause;
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
- return getCurrentState().shouldPauseForClient;
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
- return getCurrentState().callSequenceNumber;
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
- return state.callSequenceNumber++;
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
+ }