@ash-cloud/ash-ai 0.1.16 → 0.1.18
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/index.cjs +1008 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +360 -12
- package/dist/index.d.ts +360 -12
- package/dist/index.js +1007 -42
- package/dist/index.js.map +1 -1
- package/dist/playground.js +689 -684
- package/dist/{schema-DSLyNeoS.d.cts → schema-CAoHu2Rx.d.cts} +159 -2
- package/dist/{schema-DSLyNeoS.d.ts → schema-CAoHu2Rx.d.ts} +159 -2
- package/dist/schema.cjs +36 -1
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +34 -2
- package/dist/schema.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var zod = require('zod');
|
|
4
4
|
var child_process = require('child_process');
|
|
5
|
+
var events = require('events');
|
|
5
6
|
var nanoid = require('nanoid');
|
|
6
7
|
var fs = require('fs/promises');
|
|
7
8
|
var path4 = require('path');
|
|
@@ -107,7 +108,7 @@ var init_normalized = __esm({
|
|
|
107
108
|
"src/types/normalized.ts"() {
|
|
108
109
|
}
|
|
109
110
|
});
|
|
110
|
-
exports.SessionStatus = void 0; exports.AgentStatus = void 0; exports.MessageRole = void 0; exports.messageContentSchema = void 0; exports.messageSchema = void 0; exports.sessionSchema = void 0; exports.attachmentSchema = void 0; exports.DEFAULT_SANDBOX_PROVIDER_CONFIG = void 0; exports.StreamEventType = void 0; exports.QueueItemStatus = void 0; exports.EventCategory = void 0;
|
|
111
|
+
exports.SessionStatus = void 0; exports.AgentStatus = void 0; exports.MessageRole = void 0; exports.messageContentSchema = void 0; exports.messageSchema = void 0; exports.sessionSchema = void 0; exports.attachmentSchema = void 0; exports.DEFAULT_SANDBOX_PROVIDER_CONFIG = void 0; exports.StreamEventType = void 0; exports.QueueItemStatus = void 0; exports.EventCategory = void 0; exports.EventSource = void 0;
|
|
111
112
|
var init_types = __esm({
|
|
112
113
|
"src/types/index.ts"() {
|
|
113
114
|
init_normalized();
|
|
@@ -232,6 +233,14 @@ var init_types = __esm({
|
|
|
232
233
|
WEBHOOK: "webhook"
|
|
233
234
|
// webhook_delivery, webhook_failure (outbound webhook events)
|
|
234
235
|
};
|
|
236
|
+
exports.EventSource = {
|
|
237
|
+
AGENT: "agent",
|
|
238
|
+
// Directly from the agent SDK
|
|
239
|
+
SYSTEM: "system",
|
|
240
|
+
// Generated by execution layer (lifecycle, user input)
|
|
241
|
+
DERIVED: "derived"
|
|
242
|
+
// Transformed/aggregated from agent events
|
|
243
|
+
};
|
|
235
244
|
}
|
|
236
245
|
});
|
|
237
246
|
|
|
@@ -384,6 +393,85 @@ var init_errors = __esm({
|
|
|
384
393
|
}
|
|
385
394
|
});
|
|
386
395
|
|
|
396
|
+
// src/relay/stream-event-writer.ts
|
|
397
|
+
exports.StreamEventWriter = void 0;
|
|
398
|
+
var init_stream_event_writer = __esm({
|
|
399
|
+
"src/relay/stream-event-writer.ts"() {
|
|
400
|
+
exports.StreamEventWriter = class {
|
|
401
|
+
storage;
|
|
402
|
+
sessionId;
|
|
403
|
+
emitter;
|
|
404
|
+
flushIntervalMs;
|
|
405
|
+
maxBatchChars;
|
|
406
|
+
// Text delta batching state
|
|
407
|
+
pendingDelta = "";
|
|
408
|
+
pendingDeltaCount = 0;
|
|
409
|
+
flushTimer = null;
|
|
410
|
+
closed = false;
|
|
411
|
+
constructor(options) {
|
|
412
|
+
this.storage = options.storage;
|
|
413
|
+
this.sessionId = options.sessionId;
|
|
414
|
+
this.emitter = options.emitter;
|
|
415
|
+
this.flushIntervalMs = options.flushIntervalMs ?? 100;
|
|
416
|
+
this.maxBatchChars = options.maxBatchChars ?? 500;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Write a stream event. Text deltas are batched; all other events are written immediately.
|
|
420
|
+
*/
|
|
421
|
+
async write(event) {
|
|
422
|
+
if (this.closed) return;
|
|
423
|
+
if (this.emitter) {
|
|
424
|
+
this.emitter.emit("event", event);
|
|
425
|
+
}
|
|
426
|
+
if (event.type === "text_delta") {
|
|
427
|
+
this.pendingDelta += event.delta;
|
|
428
|
+
this.pendingDeltaCount++;
|
|
429
|
+
if (!this.flushTimer) {
|
|
430
|
+
this.flushTimer = setTimeout(() => this.flush(), this.flushIntervalMs);
|
|
431
|
+
}
|
|
432
|
+
if (this.pendingDelta.length >= this.maxBatchChars) {
|
|
433
|
+
await this.flush();
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
await this.flush();
|
|
437
|
+
await this.writeToStorage([{ eventType: event.type, payload: event, batchCount: 1 }]);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Flush any pending text_delta events to storage as a single batched event.
|
|
442
|
+
*/
|
|
443
|
+
async flush() {
|
|
444
|
+
if (this.flushTimer) {
|
|
445
|
+
clearTimeout(this.flushTimer);
|
|
446
|
+
this.flushTimer = null;
|
|
447
|
+
}
|
|
448
|
+
if (this.pendingDelta.length === 0) return;
|
|
449
|
+
const batchedEvent = {
|
|
450
|
+
type: "text_delta",
|
|
451
|
+
delta: this.pendingDelta
|
|
452
|
+
};
|
|
453
|
+
const batchCount = this.pendingDeltaCount;
|
|
454
|
+
this.pendingDelta = "";
|
|
455
|
+
this.pendingDeltaCount = 0;
|
|
456
|
+
await this.writeToStorage([{ eventType: "text_delta", payload: batchedEvent, batchCount }]);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Close the writer, flushing any remaining events.
|
|
460
|
+
*/
|
|
461
|
+
async close() {
|
|
462
|
+
await this.flush();
|
|
463
|
+
this.closed = true;
|
|
464
|
+
if (this.emitter) {
|
|
465
|
+
this.emitter.emit("done");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async writeToStorage(events) {
|
|
469
|
+
return this.storage.appendEvents(this.sessionId, events);
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
387
475
|
// src/session/manager.ts
|
|
388
476
|
exports.SessionManager = void 0;
|
|
389
477
|
var init_manager = __esm({
|
|
@@ -1788,11 +1876,10 @@ var init_sandbox_logger = __esm({
|
|
|
1788
1876
|
};
|
|
1789
1877
|
}
|
|
1790
1878
|
});
|
|
1791
|
-
|
|
1792
|
-
// src/agent/harness.ts
|
|
1793
1879
|
exports.AgentHarness = void 0;
|
|
1794
1880
|
var init_harness = __esm({
|
|
1795
1881
|
"src/agent/harness.ts"() {
|
|
1882
|
+
init_stream_event_writer();
|
|
1796
1883
|
init_manager();
|
|
1797
1884
|
init_claude_sdk();
|
|
1798
1885
|
init_backend();
|
|
@@ -1809,6 +1896,8 @@ var init_harness = __esm({
|
|
|
1809
1896
|
sessionSkillDirs = /* @__PURE__ */ new Map();
|
|
1810
1897
|
/** Tracks active executions by session ID for stop/interrupt capability */
|
|
1811
1898
|
activeExecutions = /* @__PURE__ */ new Map();
|
|
1899
|
+
/** EventEmitters for same-process SSE relay fast-path, keyed by session ID */
|
|
1900
|
+
sessionEmitters = /* @__PURE__ */ new Map();
|
|
1812
1901
|
constructor(config) {
|
|
1813
1902
|
this.name = config.name;
|
|
1814
1903
|
this.config = config;
|
|
@@ -1956,6 +2045,20 @@ var init_harness = __esm({
|
|
|
1956
2045
|
getSessionManager() {
|
|
1957
2046
|
return this.sessionManager;
|
|
1958
2047
|
}
|
|
2048
|
+
/**
|
|
2049
|
+
* Get the stream event storage (if configured)
|
|
2050
|
+
*/
|
|
2051
|
+
getStreamEventStorage() {
|
|
2052
|
+
return this.config.streamEventStorage;
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Get the EventEmitter for a session's stream events.
|
|
2056
|
+
* Used by the relay endpoint for same-process fast-path.
|
|
2057
|
+
* Returns undefined if no active execution or no streamEventStorage configured.
|
|
2058
|
+
*/
|
|
2059
|
+
getSessionEmitter(sessionId) {
|
|
2060
|
+
return this.sessionEmitters.get(sessionId);
|
|
2061
|
+
}
|
|
1959
2062
|
async ensureInitialized() {
|
|
1960
2063
|
if (!this.initialized) {
|
|
1961
2064
|
await this.initialize();
|
|
@@ -1970,9 +2073,24 @@ var init_harness = __esm({
|
|
|
1970
2073
|
async *send(prompt, options = {}) {
|
|
1971
2074
|
const controller = new AbortController();
|
|
1972
2075
|
self.activeExecutions.set(session.id, controller);
|
|
2076
|
+
let writer;
|
|
2077
|
+
let emitter;
|
|
2078
|
+
if (self.config.streamEventStorage) {
|
|
2079
|
+
emitter = new events.EventEmitter();
|
|
2080
|
+
self.sessionEmitters.set(session.id, emitter);
|
|
2081
|
+
writer = new exports.StreamEventWriter({
|
|
2082
|
+
storage: self.config.streamEventStorage,
|
|
2083
|
+
sessionId: session.id,
|
|
2084
|
+
emitter
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
1973
2087
|
if (options.signal) {
|
|
1974
2088
|
options.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
1975
2089
|
}
|
|
2090
|
+
const writeEvent = writer ? (event) => {
|
|
2091
|
+
writer.write(event).catch(() => {
|
|
2092
|
+
});
|
|
2093
|
+
} : void 0;
|
|
1976
2094
|
let partialTextContent = "";
|
|
1977
2095
|
const sandboxLogs = [];
|
|
1978
2096
|
const logQueue = [];
|
|
@@ -1986,7 +2104,9 @@ var init_harness = __esm({
|
|
|
1986
2104
|
const yieldQueuedLogs = async function* () {
|
|
1987
2105
|
while (logQueue.length > 0) {
|
|
1988
2106
|
const entry = logQueue.shift();
|
|
1989
|
-
|
|
2107
|
+
const event = { type: "sandbox_log", entry };
|
|
2108
|
+
writeEvent?.(event);
|
|
2109
|
+
yield event;
|
|
1990
2110
|
}
|
|
1991
2111
|
};
|
|
1992
2112
|
logger3.info("execution", `Starting execution for session ${session.id}`);
|
|
@@ -2003,11 +2123,13 @@ var init_harness = __esm({
|
|
|
2003
2123
|
if (self.hooks.onMessage && savedUserMessage) {
|
|
2004
2124
|
await self.hooks.onMessage(savedUserMessage);
|
|
2005
2125
|
}
|
|
2006
|
-
|
|
2126
|
+
const sessionStartEvent = {
|
|
2007
2127
|
type: "session_start",
|
|
2008
2128
|
sessionId: session.id,
|
|
2009
2129
|
sdkSessionId: session.sdkSessionId ?? ""
|
|
2010
2130
|
};
|
|
2131
|
+
writeEvent?.(sessionStartEvent);
|
|
2132
|
+
yield sessionStartEvent;
|
|
2011
2133
|
yield* yieldQueuedLogs();
|
|
2012
2134
|
const assistantContent = [];
|
|
2013
2135
|
let wasAborted = false;
|
|
@@ -2028,22 +2150,26 @@ var init_harness = __esm({
|
|
|
2028
2150
|
}
|
|
2029
2151
|
if (event.type === "text_delta" && event.delta) {
|
|
2030
2152
|
partialTextContent += event.delta;
|
|
2031
|
-
|
|
2153
|
+
const textDeltaEvent = {
|
|
2032
2154
|
type: "text_delta",
|
|
2033
2155
|
delta: event.delta
|
|
2034
2156
|
};
|
|
2157
|
+
writeEvent?.(textDeltaEvent);
|
|
2158
|
+
yield textDeltaEvent;
|
|
2035
2159
|
} else if (event.type === "thinking_delta" && event.delta) {
|
|
2036
|
-
|
|
2160
|
+
const thinkingEvent = {
|
|
2037
2161
|
type: "thinking_delta",
|
|
2038
2162
|
delta: event.delta
|
|
2039
2163
|
};
|
|
2164
|
+
writeEvent?.(thinkingEvent);
|
|
2165
|
+
yield thinkingEvent;
|
|
2040
2166
|
} else if (event.type === "text") {
|
|
2041
2167
|
const textContent = {
|
|
2042
2168
|
type: "text",
|
|
2043
2169
|
text: event.text
|
|
2044
2170
|
};
|
|
2045
2171
|
assistantContent.push(textContent);
|
|
2046
|
-
|
|
2172
|
+
const messageEvent = {
|
|
2047
2173
|
type: "message",
|
|
2048
2174
|
message: {
|
|
2049
2175
|
id: "",
|
|
@@ -2053,6 +2179,8 @@ var init_harness = __esm({
|
|
|
2053
2179
|
createdAt: /* @__PURE__ */ new Date()
|
|
2054
2180
|
}
|
|
2055
2181
|
};
|
|
2182
|
+
writeEvent?.(messageEvent);
|
|
2183
|
+
yield messageEvent;
|
|
2056
2184
|
} else if (event.type === "tool_use") {
|
|
2057
2185
|
logger3.debug("execution", `Tool use: ${event.name}`, { toolId: event.id });
|
|
2058
2186
|
const toolContent = {
|
|
@@ -2062,12 +2190,14 @@ var init_harness = __esm({
|
|
|
2062
2190
|
input: event.input
|
|
2063
2191
|
};
|
|
2064
2192
|
assistantContent.push(toolContent);
|
|
2065
|
-
|
|
2193
|
+
const toolUseEvent = {
|
|
2066
2194
|
type: "tool_use",
|
|
2067
2195
|
id: event.id,
|
|
2068
2196
|
name: event.name,
|
|
2069
2197
|
input: event.input
|
|
2070
2198
|
};
|
|
2199
|
+
writeEvent?.(toolUseEvent);
|
|
2200
|
+
yield toolUseEvent;
|
|
2071
2201
|
yield* yieldQueuedLogs();
|
|
2072
2202
|
if (self.hooks.onToolUse) {
|
|
2073
2203
|
await self.hooks.onToolUse(event.name, event.input, null);
|
|
@@ -2084,12 +2214,14 @@ var init_harness = __esm({
|
|
|
2084
2214
|
isError: event.isError
|
|
2085
2215
|
};
|
|
2086
2216
|
assistantContent.push(resultContent);
|
|
2087
|
-
|
|
2217
|
+
const toolResultEvent = {
|
|
2088
2218
|
type: "tool_result",
|
|
2089
2219
|
toolUseId: event.toolUseId,
|
|
2090
2220
|
content: event.content,
|
|
2091
2221
|
isError: event.isError
|
|
2092
2222
|
};
|
|
2223
|
+
writeEvent?.(toolResultEvent);
|
|
2224
|
+
yield toolResultEvent;
|
|
2093
2225
|
}
|
|
2094
2226
|
}
|
|
2095
2227
|
if (wasAborted || controller.signal.aborted) {
|
|
@@ -2118,19 +2250,23 @@ var init_harness = __esm({
|
|
|
2118
2250
|
}
|
|
2119
2251
|
});
|
|
2120
2252
|
yield* yieldQueuedLogs();
|
|
2121
|
-
|
|
2253
|
+
const stoppedEvent = {
|
|
2122
2254
|
type: "session_stopped",
|
|
2123
2255
|
sessionId: session.id,
|
|
2124
2256
|
reason: "user_requested",
|
|
2125
2257
|
partialContent: partialTextContent || void 0
|
|
2126
2258
|
};
|
|
2259
|
+
writeEvent?.(stoppedEvent);
|
|
2260
|
+
yield stoppedEvent;
|
|
2127
2261
|
return;
|
|
2128
2262
|
}
|
|
2129
2263
|
logger3.info("execution", "Execution completed successfully");
|
|
2130
|
-
|
|
2264
|
+
const turnCompleteEvent = {
|
|
2131
2265
|
type: "turn_complete",
|
|
2132
2266
|
sessionId: session.id
|
|
2133
2267
|
};
|
|
2268
|
+
writeEvent?.(turnCompleteEvent);
|
|
2269
|
+
yield turnCompleteEvent;
|
|
2134
2270
|
if (assistantContent.length > 0) {
|
|
2135
2271
|
const savedAssistantMessages = await self.sessionManager.saveMessages(
|
|
2136
2272
|
session.id,
|
|
@@ -2154,11 +2290,13 @@ var init_harness = __esm({
|
|
|
2154
2290
|
}
|
|
2155
2291
|
});
|
|
2156
2292
|
yield* yieldQueuedLogs();
|
|
2157
|
-
|
|
2293
|
+
const sessionEndEvent = {
|
|
2158
2294
|
type: "session_end",
|
|
2159
2295
|
sessionId: session.id,
|
|
2160
2296
|
status: "completed"
|
|
2161
2297
|
};
|
|
2298
|
+
writeEvent?.(sessionEndEvent);
|
|
2299
|
+
yield sessionEndEvent;
|
|
2162
2300
|
if (self.hooks.onSessionEnd) {
|
|
2163
2301
|
const updatedSession = await self.sessionManager.getSession(session.id);
|
|
2164
2302
|
if (updatedSession) {
|
|
@@ -2189,12 +2327,14 @@ var init_harness = __esm({
|
|
|
2189
2327
|
}
|
|
2190
2328
|
});
|
|
2191
2329
|
yield* yieldQueuedLogs();
|
|
2192
|
-
|
|
2330
|
+
const catchStoppedEvent = {
|
|
2193
2331
|
type: "session_stopped",
|
|
2194
2332
|
sessionId: session.id,
|
|
2195
2333
|
reason: "user_requested",
|
|
2196
2334
|
partialContent: partialTextContent || void 0
|
|
2197
2335
|
};
|
|
2336
|
+
writeEvent?.(catchStoppedEvent);
|
|
2337
|
+
yield catchStoppedEvent;
|
|
2198
2338
|
} else {
|
|
2199
2339
|
logger3.error("execution", `Execution error: ${errorMessage}`);
|
|
2200
2340
|
await self.sessionManager.updateSession(session.id, {
|
|
@@ -2212,13 +2352,19 @@ var init_harness = __esm({
|
|
|
2212
2352
|
await self.hooks.onError(error, updatedSession);
|
|
2213
2353
|
}
|
|
2214
2354
|
}
|
|
2215
|
-
|
|
2355
|
+
const errorEvent = {
|
|
2216
2356
|
type: "error",
|
|
2217
2357
|
error: errorMessage
|
|
2218
2358
|
};
|
|
2359
|
+
writeEvent?.(errorEvent);
|
|
2360
|
+
yield errorEvent;
|
|
2219
2361
|
}
|
|
2220
2362
|
} finally {
|
|
2221
2363
|
self.activeExecutions.delete(session.id);
|
|
2364
|
+
if (writer) {
|
|
2365
|
+
await writer.close();
|
|
2366
|
+
}
|
|
2367
|
+
self.sessionEmitters.delete(session.id);
|
|
2222
2368
|
}
|
|
2223
2369
|
},
|
|
2224
2370
|
async getSession() {
|
|
@@ -2457,6 +2603,11 @@ var init_memory = __esm({
|
|
|
2457
2603
|
if (options.status) {
|
|
2458
2604
|
sessions2 = sessions2.filter((s) => s.status === options.status);
|
|
2459
2605
|
}
|
|
2606
|
+
if (options.resourceId) {
|
|
2607
|
+
sessions2 = sessions2.filter(
|
|
2608
|
+
(s) => s.metadata && s.metadata["resourceId"] === options.resourceId
|
|
2609
|
+
);
|
|
2610
|
+
}
|
|
2460
2611
|
const orderBy = options.orderBy ?? "createdAt";
|
|
2461
2612
|
const order = options.order ?? "desc";
|
|
2462
2613
|
sessions2.sort((a, b) => {
|
|
@@ -2722,12 +2873,88 @@ var init_queue_memory = __esm({
|
|
|
2722
2873
|
};
|
|
2723
2874
|
}
|
|
2724
2875
|
});
|
|
2876
|
+
exports.MemoryStreamEventStorage = void 0;
|
|
2877
|
+
var init_stream_event_memory = __esm({
|
|
2878
|
+
"src/storage/stream-event-memory.ts"() {
|
|
2879
|
+
exports.MemoryStreamEventStorage = class {
|
|
2880
|
+
events = /* @__PURE__ */ new Map();
|
|
2881
|
+
nextSequence = /* @__PURE__ */ new Map();
|
|
2882
|
+
async initialize() {
|
|
2883
|
+
}
|
|
2884
|
+
async close() {
|
|
2885
|
+
this.events.clear();
|
|
2886
|
+
this.nextSequence.clear();
|
|
2887
|
+
}
|
|
2888
|
+
async appendEvents(sessionId, events) {
|
|
2889
|
+
if (!this.events.has(sessionId)) {
|
|
2890
|
+
this.events.set(sessionId, []);
|
|
2891
|
+
}
|
|
2892
|
+
const sessionEvents2 = this.events.get(sessionId);
|
|
2893
|
+
let seq = this.nextSequence.get(sessionId) ?? 0;
|
|
2894
|
+
const stored = [];
|
|
2895
|
+
for (const event of events) {
|
|
2896
|
+
seq += 1;
|
|
2897
|
+
const storedEvent = {
|
|
2898
|
+
id: nanoid.nanoid(),
|
|
2899
|
+
sessionId,
|
|
2900
|
+
sequence: seq,
|
|
2901
|
+
eventType: event.eventType,
|
|
2902
|
+
payload: event.payload,
|
|
2903
|
+
batchCount: event.batchCount ?? 1,
|
|
2904
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
2905
|
+
};
|
|
2906
|
+
sessionEvents2.push(storedEvent);
|
|
2907
|
+
stored.push(storedEvent);
|
|
2908
|
+
}
|
|
2909
|
+
this.nextSequence.set(sessionId, seq);
|
|
2910
|
+
return stored;
|
|
2911
|
+
}
|
|
2912
|
+
async readEvents(sessionId, options) {
|
|
2913
|
+
const sessionEvents2 = this.events.get(sessionId) ?? [];
|
|
2914
|
+
let result = sessionEvents2;
|
|
2915
|
+
if (options?.afterSequence !== void 0) {
|
|
2916
|
+
result = result.filter((e) => e.sequence > options.afterSequence);
|
|
2917
|
+
}
|
|
2918
|
+
if (options?.limit !== void 0) {
|
|
2919
|
+
result = result.slice(0, options.limit);
|
|
2920
|
+
}
|
|
2921
|
+
return result;
|
|
2922
|
+
}
|
|
2923
|
+
async getLatestSequence(sessionId) {
|
|
2924
|
+
return this.nextSequence.get(sessionId) ?? 0;
|
|
2925
|
+
}
|
|
2926
|
+
async deleteSessionEvents(sessionId) {
|
|
2927
|
+
this.events.delete(sessionId);
|
|
2928
|
+
this.nextSequence.delete(sessionId);
|
|
2929
|
+
}
|
|
2930
|
+
async cleanupOldEvents(maxAgeMs) {
|
|
2931
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
2932
|
+
let deleted = 0;
|
|
2933
|
+
for (const [sessionId, sessionEvents2] of this.events) {
|
|
2934
|
+
const before = sessionEvents2.length;
|
|
2935
|
+
const remaining = sessionEvents2.filter(
|
|
2936
|
+
(e) => e.createdAt.getTime() >= cutoff
|
|
2937
|
+
);
|
|
2938
|
+
deleted += before - remaining.length;
|
|
2939
|
+
if (remaining.length === 0) {
|
|
2940
|
+
this.events.delete(sessionId);
|
|
2941
|
+
this.nextSequence.delete(sessionId);
|
|
2942
|
+
} else {
|
|
2943
|
+
this.events.set(sessionId, remaining);
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
return deleted;
|
|
2947
|
+
}
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
});
|
|
2725
2951
|
|
|
2726
2952
|
// src/storage/index.ts
|
|
2727
2953
|
var init_storage = __esm({
|
|
2728
2954
|
"src/storage/index.ts"() {
|
|
2729
2955
|
init_memory();
|
|
2730
2956
|
init_queue_memory();
|
|
2957
|
+
init_stream_event_memory();
|
|
2731
2958
|
}
|
|
2732
2959
|
});
|
|
2733
2960
|
|
|
@@ -4312,13 +4539,13 @@ function getSandboxPool() {
|
|
|
4312
4539
|
return globalPool;
|
|
4313
4540
|
}
|
|
4314
4541
|
function isPoolEnabled() {
|
|
4315
|
-
if (!process.env.VERCEL) {
|
|
4316
|
-
return false;
|
|
4317
|
-
}
|
|
4318
4542
|
if (process.env.SANDBOX_POOL_ENABLED === "false") {
|
|
4319
4543
|
return false;
|
|
4320
4544
|
}
|
|
4321
|
-
|
|
4545
|
+
if (process.env.SANDBOX_POOL_ENABLED === "true") {
|
|
4546
|
+
return true;
|
|
4547
|
+
}
|
|
4548
|
+
return !!process.env.VERCEL;
|
|
4322
4549
|
}
|
|
4323
4550
|
async function ensureSandboxPoolInitialized() {
|
|
4324
4551
|
if (globalPool && globalPool.isRunning()) {
|
|
@@ -4403,7 +4630,7 @@ node -e "require('@anthropic-ai/claude-agent-sdk'); console.log('[warmup] SDK ve
|
|
|
4403
4630
|
|
|
4404
4631
|
echo "[warmup] Warmup complete!"
|
|
4405
4632
|
`;
|
|
4406
|
-
exports.SandboxPool = class {
|
|
4633
|
+
exports.SandboxPool = class _SandboxPool {
|
|
4407
4634
|
config;
|
|
4408
4635
|
pool = /* @__PURE__ */ new Map();
|
|
4409
4636
|
warmingInProgress = /* @__PURE__ */ new Set();
|
|
@@ -4412,6 +4639,14 @@ echo "[warmup] Warmup complete!"
|
|
|
4412
4639
|
lastMaintenanceAt = null;
|
|
4413
4640
|
metricsCallback;
|
|
4414
4641
|
startPromise = null;
|
|
4642
|
+
/** Consecutive warmup failure count (reset on success) */
|
|
4643
|
+
consecutiveFailures = 0;
|
|
4644
|
+
/** Timestamp of last warmup attempt — used for backoff */
|
|
4645
|
+
lastWarmupAttemptAt = 0;
|
|
4646
|
+
/** Max consecutive failures before stopping attempts entirely until manual restart */
|
|
4647
|
+
static MAX_CONSECUTIVE_FAILURES = 10;
|
|
4648
|
+
/** Base backoff delay in ms (doubles each failure: 30s, 60s, 120s, 240s…) */
|
|
4649
|
+
static BACKOFF_BASE_MS = 3e4;
|
|
4415
4650
|
constructor(config = {}) {
|
|
4416
4651
|
this.config = {
|
|
4417
4652
|
minPoolSize: config.minPoolSize ?? parseInt(process.env.SANDBOX_POOL_MIN_SIZE ?? "2"),
|
|
@@ -4587,6 +4822,8 @@ echo "[warmup] Warmup complete!"
|
|
|
4587
4822
|
warming: this.warmingInProgress.size,
|
|
4588
4823
|
running: this.running,
|
|
4589
4824
|
lastMaintenanceAt: this.lastMaintenanceAt,
|
|
4825
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
4826
|
+
warmupSuspended: this.consecutiveFailures >= _SandboxPool.MAX_CONSECUTIVE_FAILURES,
|
|
4590
4827
|
config: {
|
|
4591
4828
|
minPoolSize: this.config.minPoolSize,
|
|
4592
4829
|
maxPoolSize: this.config.maxPoolSize,
|
|
@@ -4612,6 +4849,7 @@ echo "[warmup] Warmup complete!"
|
|
|
4612
4849
|
this.warmingInProgress.add(warmupId);
|
|
4613
4850
|
this.emitMetric("warmup_started", { warmupId });
|
|
4614
4851
|
const startTime = Date.now();
|
|
4852
|
+
this.lastWarmupAttemptAt = startTime;
|
|
4615
4853
|
const useTarball = !!this.config.baseTarballUrl;
|
|
4616
4854
|
try {
|
|
4617
4855
|
const { Sandbox } = await import('@vercel/sandbox');
|
|
@@ -4712,6 +4950,7 @@ echo "[warmup] Warmup complete!"
|
|
|
4712
4950
|
lastHeartbeat: now
|
|
4713
4951
|
};
|
|
4714
4952
|
console.log(`[POOL] Warmup completed for ${sandbox.sandboxId} (took ${warmupTime}ms)${useTarball ? " [tarball]" : ""}`);
|
|
4953
|
+
this.consecutiveFailures = 0;
|
|
4715
4954
|
this.emitMetric("warmup_completed", {
|
|
4716
4955
|
sandboxId: pooled.sandboxId,
|
|
4717
4956
|
warmupTimeMs: warmupTime,
|
|
@@ -4721,10 +4960,20 @@ echo "[warmup] Warmup complete!"
|
|
|
4721
4960
|
return pooled;
|
|
4722
4961
|
} catch (error) {
|
|
4723
4962
|
const warmupTime = Date.now() - startTime;
|
|
4963
|
+
this.consecutiveFailures++;
|
|
4964
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown";
|
|
4965
|
+
if (this.consecutiveFailures === 1) {
|
|
4966
|
+
console.error(`[POOL] Warmup failed: ${errorMessage}`);
|
|
4967
|
+
} else {
|
|
4968
|
+
console.error(
|
|
4969
|
+
`[POOL] Warmup failed (${this.consecutiveFailures} consecutive). Next attempt in ${Math.min(_SandboxPool.BACKOFF_BASE_MS * Math.pow(2, this.consecutiveFailures - 1), 3e5) / 1e3}s. Error: ${errorMessage}`
|
|
4970
|
+
);
|
|
4971
|
+
}
|
|
4724
4972
|
this.emitMetric("warmup_failed", {
|
|
4725
|
-
error:
|
|
4973
|
+
error: errorMessage,
|
|
4726
4974
|
warmupTimeMs: warmupTime,
|
|
4727
|
-
usedTarball: useTarball
|
|
4975
|
+
usedTarball: useTarball,
|
|
4976
|
+
consecutiveFailures: this.consecutiveFailures
|
|
4728
4977
|
});
|
|
4729
4978
|
throw error;
|
|
4730
4979
|
} finally {
|
|
@@ -4832,9 +5081,30 @@ echo "[warmup] Warmup complete!"
|
|
|
4832
5081
|
});
|
|
4833
5082
|
}
|
|
4834
5083
|
/**
|
|
4835
|
-
* Replenish pool if below min size
|
|
5084
|
+
* Replenish pool if below min size.
|
|
5085
|
+
* Applies exponential backoff when warmups keep failing to avoid
|
|
5086
|
+
* a tight failure loop that wastes resources and floods metrics.
|
|
4836
5087
|
*/
|
|
4837
5088
|
async replenishPool() {
|
|
5089
|
+
if (this.consecutiveFailures >= _SandboxPool.MAX_CONSECUTIVE_FAILURES) {
|
|
5090
|
+
if (Math.random() < 0.2) {
|
|
5091
|
+
console.warn(
|
|
5092
|
+
`[POOL] Warmup suspended after ${this.consecutiveFailures} consecutive failures. Check SANDBOX_BASE_TARBALL_URL or Vercel API status. Pool will retry on next acquire().`
|
|
5093
|
+
);
|
|
5094
|
+
}
|
|
5095
|
+
return;
|
|
5096
|
+
}
|
|
5097
|
+
if (this.consecutiveFailures > 0) {
|
|
5098
|
+
const backoffMs = Math.min(
|
|
5099
|
+
_SandboxPool.BACKOFF_BASE_MS * Math.pow(2, this.consecutiveFailures - 1),
|
|
5100
|
+
5 * 6e4
|
|
5101
|
+
// Cap at 5 minutes
|
|
5102
|
+
);
|
|
5103
|
+
const timeSinceLastAttempt = Date.now() - this.lastWarmupAttemptAt;
|
|
5104
|
+
if (timeSinceLastAttempt < backoffMs) {
|
|
5105
|
+
return;
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
4838
5108
|
const eligibleCount = this.getEligibleCount();
|
|
4839
5109
|
const warmingCount = this.warmingInProgress.size;
|
|
4840
5110
|
const totalProjected = eligibleCount + warmingCount;
|
|
@@ -5182,10 +5452,11 @@ async function getOrCreateSandbox(options) {
|
|
|
5182
5452
|
startupScriptRan: true,
|
|
5183
5453
|
// Assume ran for reconnected sandboxes
|
|
5184
5454
|
startupScriptHash: void 0,
|
|
5455
|
+
// Unknown — caller should not re-run based on hash mismatch alone
|
|
5185
5456
|
isNew: false,
|
|
5186
5457
|
configFileUrl: void 0,
|
|
5187
|
-
configInstalledAt:
|
|
5188
|
-
//
|
|
5458
|
+
configInstalledAt: now2
|
|
5459
|
+
// Assume config was installed — prevents unnecessary re-install on reconnection
|
|
5189
5460
|
};
|
|
5190
5461
|
} else {
|
|
5191
5462
|
console.log("[SANDBOX] Reconnected sandbox failed health check, will create new");
|
|
@@ -5571,7 +5842,8 @@ async function* executeInSandbox(prompt, apiKey, options) {
|
|
|
5571
5842
|
markSdkInstalled(sessionId);
|
|
5572
5843
|
}
|
|
5573
5844
|
const currentScriptHash = hashStartupScript(options.startupScript);
|
|
5574
|
-
const
|
|
5845
|
+
const scriptHashChanged = cachedScriptHash !== void 0 && currentScriptHash !== cachedScriptHash;
|
|
5846
|
+
const needsStartupScript = options.startupScript && (!startupScriptRan || scriptHashChanged);
|
|
5575
5847
|
if (needsStartupScript) {
|
|
5576
5848
|
console.log("[SANDBOX] Running startup script...");
|
|
5577
5849
|
const startupStartTime = Date.now();
|
|
@@ -5705,11 +5977,18 @@ const { query } = require('@anthropic-ai/claude-agent-sdk');
|
|
|
5705
5977
|
const prompt = ${JSON.stringify(prompt)};
|
|
5706
5978
|
const options = ${JSON.stringify(sdkOptions)};
|
|
5707
5979
|
|
|
5980
|
+
let queryCompleted = false;
|
|
5981
|
+
|
|
5708
5982
|
async function run() {
|
|
5709
5983
|
try {
|
|
5710
5984
|
for await (const message of query({ prompt, options })) {
|
|
5711
5985
|
console.log(JSON.stringify(message));
|
|
5712
5986
|
}
|
|
5987
|
+
queryCompleted = true;
|
|
5988
|
+
// Exit cleanly immediately after query completes.
|
|
5989
|
+
// MCP server cleanup (e.g. Playwright browser shutdown) can trigger
|
|
5990
|
+
// unhandled rejections after the query is done, causing spurious exit code 1.
|
|
5991
|
+
process.exit(0);
|
|
5713
5992
|
} catch (error) {
|
|
5714
5993
|
const errorInfo = {
|
|
5715
5994
|
type: 'error',
|
|
@@ -5725,6 +6004,11 @@ async function run() {
|
|
|
5725
6004
|
}
|
|
5726
6005
|
|
|
5727
6006
|
process.on('unhandledRejection', (reason) => {
|
|
6007
|
+
// Ignore rejections from MCP server cleanup after query has completed
|
|
6008
|
+
if (queryCompleted) {
|
|
6009
|
+
process.exit(0);
|
|
6010
|
+
return;
|
|
6011
|
+
}
|
|
5728
6012
|
const errorInfo = {
|
|
5729
6013
|
type: 'error',
|
|
5730
6014
|
error: reason instanceof Error ? reason.message : String(reason),
|
|
@@ -5735,6 +6019,11 @@ process.on('unhandledRejection', (reason) => {
|
|
|
5735
6019
|
});
|
|
5736
6020
|
|
|
5737
6021
|
process.on('uncaughtException', (error) => {
|
|
6022
|
+
// Ignore exceptions from MCP server cleanup after query has completed
|
|
6023
|
+
if (queryCompleted) {
|
|
6024
|
+
process.exit(0);
|
|
6025
|
+
return;
|
|
6026
|
+
}
|
|
5738
6027
|
const errorInfo = {
|
|
5739
6028
|
type: 'error',
|
|
5740
6029
|
error: error.message || 'Unknown error',
|
|
@@ -5921,6 +6210,85 @@ SCRIPT_EOF`]
|
|
|
5921
6210
|
}
|
|
5922
6211
|
}
|
|
5923
6212
|
}
|
|
6213
|
+
async function warmSandboxForSession(options) {
|
|
6214
|
+
const {
|
|
6215
|
+
sessionId,
|
|
6216
|
+
runtime = "node22",
|
|
6217
|
+
timeout = 600,
|
|
6218
|
+
vcpus = 4,
|
|
6219
|
+
startupScript,
|
|
6220
|
+
configFileUrl,
|
|
6221
|
+
envVars = {}
|
|
6222
|
+
} = options;
|
|
6223
|
+
console.log(`[WARM] Pre-warming sandbox for session: ${sessionId}`);
|
|
6224
|
+
const startTime = Date.now();
|
|
6225
|
+
const result = await getOrCreateSandbox({
|
|
6226
|
+
sessionId,
|
|
6227
|
+
runtime,
|
|
6228
|
+
timeout,
|
|
6229
|
+
vcpus
|
|
6230
|
+
});
|
|
6231
|
+
const { sandbox, sdkInstalled } = result;
|
|
6232
|
+
if (!sdkInstalled) {
|
|
6233
|
+
console.log("[WARM] Installing SDK...");
|
|
6234
|
+
await sandbox.runCommand({
|
|
6235
|
+
cmd: "bash",
|
|
6236
|
+
args: ["-c", "which jq || (sudo dnf install -y jq 2>/dev/null || sudo apt-get update && sudo apt-get install -y jq 2>/dev/null || sudo apk add jq 2>/dev/null) || true"]
|
|
6237
|
+
});
|
|
6238
|
+
await sandbox.runCommand({
|
|
6239
|
+
cmd: "bash",
|
|
6240
|
+
args: ["-c", "which ffmpeg || (sudo dnf install -y ffmpeg 2>/dev/null || sudo apt-get update && sudo apt-get install -y ffmpeg 2>/dev/null || sudo apk add ffmpeg 2>/dev/null) || true"]
|
|
6241
|
+
});
|
|
6242
|
+
const sdkResult = await sandbox.runCommand({
|
|
6243
|
+
cmd: "bash",
|
|
6244
|
+
args: ["-c", "npm init -y && npm install @anthropic-ai/claude-agent-sdk"],
|
|
6245
|
+
env: envVars
|
|
6246
|
+
});
|
|
6247
|
+
if (sdkResult.exitCode !== 0) {
|
|
6248
|
+
const stderr = await sdkResult.stderr();
|
|
6249
|
+
throw new Error(`Failed to install Claude Agent SDK during warmup: ${stderr}`);
|
|
6250
|
+
}
|
|
6251
|
+
markSdkInstalled(sessionId);
|
|
6252
|
+
}
|
|
6253
|
+
if (startupScript) {
|
|
6254
|
+
console.log("[WARM] Running startup script...");
|
|
6255
|
+
const scriptResult = await sandbox.runCommand({
|
|
6256
|
+
cmd: "bash",
|
|
6257
|
+
args: ["-c", startupScript],
|
|
6258
|
+
env: envVars
|
|
6259
|
+
});
|
|
6260
|
+
if (scriptResult.exitCode !== 0) {
|
|
6261
|
+
const stderr = await scriptResult.stderr();
|
|
6262
|
+
throw new Error(`Startup script failed during warmup: ${stderr}`);
|
|
6263
|
+
}
|
|
6264
|
+
markStartupScriptRan(sessionId, hashStartupScript(startupScript));
|
|
6265
|
+
}
|
|
6266
|
+
if (configFileUrl) {
|
|
6267
|
+
console.log("[WARM] Installing config from:", configFileUrl);
|
|
6268
|
+
const configScript = `
|
|
6269
|
+
set -e
|
|
6270
|
+
curl -sSL --fail "${configFileUrl}" -o /tmp/config.zip
|
|
6271
|
+
unzip -o /tmp/config.zip -d .
|
|
6272
|
+
rm -f /tmp/config.zip
|
|
6273
|
+
`;
|
|
6274
|
+
const configResult = await sandbox.runCommand({
|
|
6275
|
+
cmd: "bash",
|
|
6276
|
+
args: ["-c", configScript],
|
|
6277
|
+
env: envVars
|
|
6278
|
+
});
|
|
6279
|
+
if (configResult.exitCode !== 0) {
|
|
6280
|
+
const stderr = await configResult.stderr();
|
|
6281
|
+
throw new Error(`Config installation failed during warmup: ${stderr}`);
|
|
6282
|
+
}
|
|
6283
|
+
markConfigInstalled(sessionId, configFileUrl);
|
|
6284
|
+
}
|
|
6285
|
+
const duration = Date.now() - startTime;
|
|
6286
|
+
console.log(`[WARM] Sandbox pre-warmed for session ${sessionId} in ${duration}ms (sandbox: ${result.sandboxId})`);
|
|
6287
|
+
return {
|
|
6288
|
+
sandboxId: result.sandboxId,
|
|
6289
|
+
ready: true
|
|
6290
|
+
};
|
|
6291
|
+
}
|
|
5924
6292
|
var sandboxCache, DEFAULT_IDLE_TTL, CLEANUP_INTERVAL, HEARTBEAT_INTERVAL, cleanupIntervalId, heartbeatIntervalId, GLOBAL_HEARTBEAT_KEY, heartbeatListeners;
|
|
5925
6293
|
var init_vercel_sandbox_executor = __esm({
|
|
5926
6294
|
"src/runtime/vercel-sandbox-executor.ts"() {
|
|
@@ -6575,6 +6943,31 @@ WATCHER_EOF`;
|
|
|
6575
6943
|
globalInSandboxManager = null;
|
|
6576
6944
|
}
|
|
6577
6945
|
});
|
|
6946
|
+
function matchGlob(pattern, filePath) {
|
|
6947
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
6948
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
6949
|
+
let regexStr = "";
|
|
6950
|
+
let i = 0;
|
|
6951
|
+
while (i < normalizedPattern.length) {
|
|
6952
|
+
if (normalizedPattern[i] === "*" && normalizedPattern[i + 1] === "*") {
|
|
6953
|
+
if (normalizedPattern[i + 2] === "/") {
|
|
6954
|
+
regexStr += "(?:.*/)?";
|
|
6955
|
+
i += 3;
|
|
6956
|
+
} else {
|
|
6957
|
+
regexStr += ".*";
|
|
6958
|
+
i += 2;
|
|
6959
|
+
}
|
|
6960
|
+
} else if (normalizedPattern[i] === "*") {
|
|
6961
|
+
regexStr += "[^/]*";
|
|
6962
|
+
i += 1;
|
|
6963
|
+
} else {
|
|
6964
|
+
const ch = normalizedPattern[i];
|
|
6965
|
+
regexStr += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6966
|
+
i += 1;
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
return new RegExp(`^${regexStr}$`).test(normalizedPath);
|
|
6970
|
+
}
|
|
6578
6971
|
function extractErrorMessage(error) {
|
|
6579
6972
|
if (error === null || error === void 0) {
|
|
6580
6973
|
return "Unknown error (null/undefined)";
|
|
@@ -6617,12 +7010,18 @@ function extractErrorMessage(error) {
|
|
|
6617
7010
|
function createSandboxFileSync(options) {
|
|
6618
7011
|
return new exports.SandboxFileSync(options);
|
|
6619
7012
|
}
|
|
6620
|
-
exports.SandboxFileSync = void 0;
|
|
7013
|
+
var DEFAULT_WEBHOOK_IGNORE; exports.SandboxFileSync = void 0;
|
|
6621
7014
|
var init_sandbox_file_sync = __esm({
|
|
6622
7015
|
"src/runtime/sandbox-file-sync.ts"() {
|
|
6623
7016
|
init_sandbox_file_watcher();
|
|
6624
7017
|
init_in_sandbox_watcher();
|
|
6625
7018
|
init_types();
|
|
7019
|
+
DEFAULT_WEBHOOK_IGNORE = [
|
|
7020
|
+
"**/agent-runner.js",
|
|
7021
|
+
"**/agent-runner.cjs",
|
|
7022
|
+
"**/.file-watcher.js",
|
|
7023
|
+
"**/.file-watcher-output.log"
|
|
7024
|
+
];
|
|
6626
7025
|
exports.SandboxFileSync = class {
|
|
6627
7026
|
fileStore;
|
|
6628
7027
|
sandboxBasePath;
|
|
@@ -6637,6 +7036,10 @@ var init_sandbox_file_sync = __esm({
|
|
|
6637
7036
|
fileChangeSubscribers = /* @__PURE__ */ new Set();
|
|
6638
7037
|
// Sequence number cache per session (for event storage)
|
|
6639
7038
|
sequenceNumbers = /* @__PURE__ */ new Map();
|
|
7039
|
+
// Webhook batching state
|
|
7040
|
+
batchTimer = null;
|
|
7041
|
+
batchQueue = /* @__PURE__ */ new Map();
|
|
7042
|
+
// keyed by file path for dedup
|
|
6640
7043
|
constructor(options) {
|
|
6641
7044
|
this.fileStore = options.fileStore;
|
|
6642
7045
|
this.sandboxBasePath = options.sandboxBasePath ?? ".claude/files";
|
|
@@ -6671,6 +7074,72 @@ var init_sandbox_file_sync = __esm({
|
|
|
6671
7074
|
*/
|
|
6672
7075
|
removeWebhook() {
|
|
6673
7076
|
this.webhookConfig = void 0;
|
|
7077
|
+
if (this.batchTimer) {
|
|
7078
|
+
clearTimeout(this.batchTimer);
|
|
7079
|
+
this.batchTimer = null;
|
|
7080
|
+
}
|
|
7081
|
+
this.batchQueue.clear();
|
|
7082
|
+
}
|
|
7083
|
+
/**
|
|
7084
|
+
* Check if a file path should be excluded from webhook notifications.
|
|
7085
|
+
* Tests against DEFAULT_WEBHOOK_IGNORE patterns and any user-configured ignorePaths.
|
|
7086
|
+
*/
|
|
7087
|
+
shouldIgnorePath(filePath) {
|
|
7088
|
+
if (!filePath) return false;
|
|
7089
|
+
const userPatterns = this.webhookConfig?.ignorePaths ?? [];
|
|
7090
|
+
const allPatterns = [...DEFAULT_WEBHOOK_IGNORE, ...userPatterns];
|
|
7091
|
+
return allPatterns.some((pattern) => matchGlob(pattern, filePath));
|
|
7092
|
+
}
|
|
7093
|
+
/**
|
|
7094
|
+
* Route a webhook payload through batching (if configured) or send immediately.
|
|
7095
|
+
* When batchWindowMs is set, payloads are queued and deduplicated by file path.
|
|
7096
|
+
*/
|
|
7097
|
+
enqueueOrSendWebhook(payload, sessionId) {
|
|
7098
|
+
const batchWindowMs = this.webhookConfig?.batchWindowMs;
|
|
7099
|
+
if (!batchWindowMs || batchWindowMs <= 0) {
|
|
7100
|
+
this.sendWebhook(payload, sessionId);
|
|
7101
|
+
return;
|
|
7102
|
+
}
|
|
7103
|
+
const filePath = payload.fileSyncEvent?.canonicalPath ?? payload.fileChangeEvent?.relativePath ?? `_unknown_${Date.now()}`;
|
|
7104
|
+
this.batchQueue.set(filePath, payload);
|
|
7105
|
+
if (this.batchTimer) {
|
|
7106
|
+
clearTimeout(this.batchTimer);
|
|
7107
|
+
}
|
|
7108
|
+
this.batchTimer = setTimeout(() => {
|
|
7109
|
+
this.flushWebhookBatch(sessionId);
|
|
7110
|
+
}, batchWindowMs);
|
|
7111
|
+
}
|
|
7112
|
+
/**
|
|
7113
|
+
* Flush all queued webhook payloads as a single batch request.
|
|
7114
|
+
*/
|
|
7115
|
+
async flushWebhookBatch(sessionId) {
|
|
7116
|
+
this.batchTimer = null;
|
|
7117
|
+
if (this.batchQueue.size === 0) return;
|
|
7118
|
+
const payloads = Array.from(this.batchQueue.values());
|
|
7119
|
+
this.batchQueue.clear();
|
|
7120
|
+
const fileSyncEvents = [];
|
|
7121
|
+
const fileChangeEvents = [];
|
|
7122
|
+
let batchSessionId = sessionId ?? "";
|
|
7123
|
+
for (const p of payloads) {
|
|
7124
|
+
if (!batchSessionId) {
|
|
7125
|
+
batchSessionId = p.sessionId;
|
|
7126
|
+
}
|
|
7127
|
+
if (p.fileSyncEvent) {
|
|
7128
|
+
fileSyncEvents.push(p.fileSyncEvent);
|
|
7129
|
+
}
|
|
7130
|
+
if (p.fileChangeEvent) {
|
|
7131
|
+
fileChangeEvents.push(p.fileChangeEvent);
|
|
7132
|
+
}
|
|
7133
|
+
}
|
|
7134
|
+
const batchPayload = {
|
|
7135
|
+
event: "file_sync_batch",
|
|
7136
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7137
|
+
sessionId: batchSessionId,
|
|
7138
|
+
metadata: this.webhookConfig?.metadata,
|
|
7139
|
+
fileSyncEvents,
|
|
7140
|
+
fileChangeEvents
|
|
7141
|
+
};
|
|
7142
|
+
await this.sendWebhook(batchPayload, sessionId);
|
|
6674
7143
|
}
|
|
6675
7144
|
/**
|
|
6676
7145
|
* Extract hostname from URL for display (security - don't expose full URL)
|
|
@@ -6807,22 +7276,48 @@ var init_sandbox_file_sync = __esm({
|
|
|
6807
7276
|
}
|
|
6808
7277
|
}
|
|
6809
7278
|
/**
|
|
6810
|
-
* Send a file sync event webhook
|
|
7279
|
+
* Send a file sync event webhook.
|
|
7280
|
+
* Strips raw Buffer content (previousContent/newContent) and replaces with a
|
|
7281
|
+
* pre-signed S3 download URL when the file exists in storage.
|
|
6811
7282
|
*/
|
|
6812
7283
|
async sendFileSyncWebhook(sessionId, event) {
|
|
7284
|
+
if (this.shouldIgnorePath(event.canonicalPath)) {
|
|
7285
|
+
return;
|
|
7286
|
+
}
|
|
7287
|
+
let downloadUrl;
|
|
7288
|
+
if (event.success && event.canonicalPath && !event.operation.startsWith("deleted")) {
|
|
7289
|
+
try {
|
|
7290
|
+
downloadUrl = await this.fileStore.getSignedUrl(sessionId, event.canonicalPath);
|
|
7291
|
+
} catch {
|
|
7292
|
+
}
|
|
7293
|
+
}
|
|
7294
|
+
const webhookEvent = {
|
|
7295
|
+
operation: event.operation,
|
|
7296
|
+
source: event.source,
|
|
7297
|
+
canonicalPath: event.canonicalPath,
|
|
7298
|
+
basePath: event.basePath,
|
|
7299
|
+
sandboxPath: event.sandboxPath,
|
|
7300
|
+
fileSize: event.fileSize,
|
|
7301
|
+
success: event.success,
|
|
7302
|
+
error: event.error,
|
|
7303
|
+
downloadUrl
|
|
7304
|
+
};
|
|
6813
7305
|
const payload = {
|
|
6814
7306
|
event: "file_sync",
|
|
6815
7307
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6816
7308
|
sessionId,
|
|
6817
7309
|
metadata: this.webhookConfig?.metadata,
|
|
6818
|
-
fileSyncEvent:
|
|
7310
|
+
fileSyncEvent: webhookEvent
|
|
6819
7311
|
};
|
|
6820
|
-
|
|
7312
|
+
this.enqueueOrSendWebhook(payload, sessionId);
|
|
6821
7313
|
}
|
|
6822
7314
|
/**
|
|
6823
7315
|
* Send a file change event webhook (from watcher)
|
|
6824
7316
|
*/
|
|
6825
7317
|
async sendFileChangeWebhook(event) {
|
|
7318
|
+
if (this.shouldIgnorePath(event.relativePath)) {
|
|
7319
|
+
return;
|
|
7320
|
+
}
|
|
6826
7321
|
const payload = {
|
|
6827
7322
|
event: "file_change",
|
|
6828
7323
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6830,7 +7325,7 @@ var init_sandbox_file_sync = __esm({
|
|
|
6830
7325
|
metadata: this.webhookConfig?.metadata,
|
|
6831
7326
|
fileChangeEvent: event
|
|
6832
7327
|
};
|
|
6833
|
-
|
|
7328
|
+
this.enqueueOrSendWebhook(payload, event.sessionId);
|
|
6834
7329
|
}
|
|
6835
7330
|
/**
|
|
6836
7331
|
* Get the next sequence number for a session
|
|
@@ -7494,6 +7989,11 @@ var init_sandbox_file_sync = __esm({
|
|
|
7494
7989
|
await Promise.all([...inSandboxPromises, ...localPromises]);
|
|
7495
7990
|
this.inSandboxWatchers.clear();
|
|
7496
7991
|
this.localWatchers.clear();
|
|
7992
|
+
if (this.batchTimer) {
|
|
7993
|
+
clearTimeout(this.batchTimer);
|
|
7994
|
+
this.batchTimer = null;
|
|
7995
|
+
await this.flushWebhookBatch();
|
|
7996
|
+
}
|
|
7497
7997
|
}
|
|
7498
7998
|
/**
|
|
7499
7999
|
* Get watching status for all sessions
|
|
@@ -9103,6 +9603,135 @@ var init_utils = __esm({
|
|
|
9103
9603
|
init_sandbox_logger();
|
|
9104
9604
|
}
|
|
9105
9605
|
});
|
|
9606
|
+
|
|
9607
|
+
// src/relay/stream-event-relay.ts
|
|
9608
|
+
async function* streamEventRelay(options) {
|
|
9609
|
+
const {
|
|
9610
|
+
storage,
|
|
9611
|
+
sessionId,
|
|
9612
|
+
afterSequence = 0,
|
|
9613
|
+
emitter,
|
|
9614
|
+
pollIntervalMs = 100,
|
|
9615
|
+
heartbeatIntervalMs = 15e3,
|
|
9616
|
+
signal
|
|
9617
|
+
} = options;
|
|
9618
|
+
let lastSequence = afterSequence;
|
|
9619
|
+
let done = false;
|
|
9620
|
+
const catchUpEvents = await storage.readEvents(sessionId, {
|
|
9621
|
+
afterSequence: lastSequence,
|
|
9622
|
+
limit: 1e4
|
|
9623
|
+
});
|
|
9624
|
+
for (const stored of catchUpEvents) {
|
|
9625
|
+
if (signal?.aborted || done) return;
|
|
9626
|
+
lastSequence = stored.sequence;
|
|
9627
|
+
yield stored.payload;
|
|
9628
|
+
if (TERMINAL_EVENT_TYPES.has(stored.eventType)) {
|
|
9629
|
+
done = true;
|
|
9630
|
+
return;
|
|
9631
|
+
}
|
|
9632
|
+
}
|
|
9633
|
+
if (signal?.aborted) return;
|
|
9634
|
+
if (emitter) {
|
|
9635
|
+
yield* relayFromEmitter(emitter, signal, heartbeatIntervalMs);
|
|
9636
|
+
} else {
|
|
9637
|
+
yield* relayByPolling(storage, sessionId, lastSequence, pollIntervalMs, heartbeatIntervalMs, signal);
|
|
9638
|
+
}
|
|
9639
|
+
}
|
|
9640
|
+
async function* relayFromEmitter(emitter, signal, heartbeatIntervalMs = 15e3) {
|
|
9641
|
+
const queue = [];
|
|
9642
|
+
let resolve3 = null;
|
|
9643
|
+
const onEvent = (event) => {
|
|
9644
|
+
queue.push(event);
|
|
9645
|
+
if (resolve3) {
|
|
9646
|
+
resolve3();
|
|
9647
|
+
resolve3 = null;
|
|
9648
|
+
}
|
|
9649
|
+
};
|
|
9650
|
+
const onDone = () => {
|
|
9651
|
+
queue.push("done");
|
|
9652
|
+
if (resolve3) {
|
|
9653
|
+
resolve3();
|
|
9654
|
+
resolve3 = null;
|
|
9655
|
+
}
|
|
9656
|
+
};
|
|
9657
|
+
emitter.on("event", onEvent);
|
|
9658
|
+
emitter.on("done", onDone);
|
|
9659
|
+
const heartbeatTimer = setInterval(() => {
|
|
9660
|
+
if (resolve3) {
|
|
9661
|
+
resolve3();
|
|
9662
|
+
resolve3 = null;
|
|
9663
|
+
}
|
|
9664
|
+
}, heartbeatIntervalMs);
|
|
9665
|
+
try {
|
|
9666
|
+
while (!signal?.aborted) {
|
|
9667
|
+
while (queue.length > 0) {
|
|
9668
|
+
const item = queue.shift();
|
|
9669
|
+
if (item === "done") return;
|
|
9670
|
+
yield item;
|
|
9671
|
+
if (TERMINAL_EVENT_TYPES.has(item.type)) return;
|
|
9672
|
+
}
|
|
9673
|
+
await new Promise((r) => {
|
|
9674
|
+
resolve3 = r;
|
|
9675
|
+
if (signal) {
|
|
9676
|
+
signal.addEventListener("abort", () => {
|
|
9677
|
+
resolve3 = null;
|
|
9678
|
+
r();
|
|
9679
|
+
}, { once: true });
|
|
9680
|
+
}
|
|
9681
|
+
});
|
|
9682
|
+
}
|
|
9683
|
+
} finally {
|
|
9684
|
+
clearInterval(heartbeatTimer);
|
|
9685
|
+
emitter.off("event", onEvent);
|
|
9686
|
+
emitter.off("done", onDone);
|
|
9687
|
+
}
|
|
9688
|
+
}
|
|
9689
|
+
async function* relayByPolling(storage, sessionId, startSequence, pollIntervalMs, heartbeatIntervalMs, signal) {
|
|
9690
|
+
let lastSequence = startSequence;
|
|
9691
|
+
while (!signal?.aborted) {
|
|
9692
|
+
const events = await storage.readEvents(sessionId, {
|
|
9693
|
+
afterSequence: lastSequence,
|
|
9694
|
+
limit: 100
|
|
9695
|
+
});
|
|
9696
|
+
for (const stored of events) {
|
|
9697
|
+
if (signal?.aborted) return;
|
|
9698
|
+
lastSequence = stored.sequence;
|
|
9699
|
+
yield stored.payload;
|
|
9700
|
+
if (TERMINAL_EVENT_TYPES.has(stored.eventType)) {
|
|
9701
|
+
return;
|
|
9702
|
+
}
|
|
9703
|
+
}
|
|
9704
|
+
if (events.length === 0) {
|
|
9705
|
+
await new Promise((r) => {
|
|
9706
|
+
const timer = setTimeout(r, pollIntervalMs);
|
|
9707
|
+
if (signal) {
|
|
9708
|
+
signal.addEventListener("abort", () => {
|
|
9709
|
+
clearTimeout(timer);
|
|
9710
|
+
r();
|
|
9711
|
+
}, { once: true });
|
|
9712
|
+
}
|
|
9713
|
+
});
|
|
9714
|
+
}
|
|
9715
|
+
}
|
|
9716
|
+
}
|
|
9717
|
+
var TERMINAL_EVENT_TYPES;
|
|
9718
|
+
var init_stream_event_relay = __esm({
|
|
9719
|
+
"src/relay/stream-event-relay.ts"() {
|
|
9720
|
+
TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
9721
|
+
"session_end",
|
|
9722
|
+
"error",
|
|
9723
|
+
"session_stopped"
|
|
9724
|
+
]);
|
|
9725
|
+
}
|
|
9726
|
+
});
|
|
9727
|
+
|
|
9728
|
+
// src/relay/index.ts
|
|
9729
|
+
var init_relay = __esm({
|
|
9730
|
+
"src/relay/index.ts"() {
|
|
9731
|
+
init_stream_event_writer();
|
|
9732
|
+
init_stream_event_relay();
|
|
9733
|
+
}
|
|
9734
|
+
});
|
|
9106
9735
|
function broadcastToSession(sessionId, event) {
|
|
9107
9736
|
const subscribers = sessionSubscribers.get(sessionId);
|
|
9108
9737
|
if (subscribers && subscribers.size > 0) {
|
|
@@ -9235,6 +9864,8 @@ function createSessionsRouter(options) {
|
|
|
9235
9864
|
agentName: session.agentName,
|
|
9236
9865
|
startedAt: /* @__PURE__ */ new Date()
|
|
9237
9866
|
});
|
|
9867
|
+
const hasStreamEventStorage = !!agent.getStreamEventStorage();
|
|
9868
|
+
let sendSequenceCounter = 0;
|
|
9238
9869
|
return streaming.streamSSE(c, async (stream) => {
|
|
9239
9870
|
try {
|
|
9240
9871
|
for await (const event of activeSession.send(data.prompt, {
|
|
@@ -9246,7 +9877,9 @@ function createSessionsRouter(options) {
|
|
|
9246
9877
|
if (controller.signal.aborted) {
|
|
9247
9878
|
break;
|
|
9248
9879
|
}
|
|
9880
|
+
sendSequenceCounter++;
|
|
9249
9881
|
await stream.writeSSE({
|
|
9882
|
+
...hasStreamEventStorage ? { id: String(sendSequenceCounter) } : {},
|
|
9250
9883
|
event: event.type,
|
|
9251
9884
|
data: JSON.stringify(event)
|
|
9252
9885
|
});
|
|
@@ -9429,11 +10062,129 @@ function createSessionsRouter(options) {
|
|
|
9429
10062
|
}
|
|
9430
10063
|
});
|
|
9431
10064
|
});
|
|
10065
|
+
router.get("/:sessionId/stream-events", async (c) => {
|
|
10066
|
+
const sessionId = c.req.param("sessionId");
|
|
10067
|
+
const afterSequence = Math.max(0, parseInt(c.req.query("afterSequence") || "0", 10) || 0);
|
|
10068
|
+
const limit = Math.min(Math.max(1, parseInt(c.req.query("limit") || "100", 10) || 100), 1e3);
|
|
10069
|
+
const waitMs = Math.min(Math.max(0, parseInt(c.req.query("waitMs") || "0", 10) || 0), 3e4);
|
|
10070
|
+
const session = await sessionManager.getSession(sessionId);
|
|
10071
|
+
if (!session) {
|
|
10072
|
+
return c.json({ error: "Session not found" }, 404);
|
|
10073
|
+
}
|
|
10074
|
+
const agent = agents2.get(session.agentName);
|
|
10075
|
+
if (!agent) {
|
|
10076
|
+
return c.json({ error: `Agent not found: ${session.agentName}` }, 404);
|
|
10077
|
+
}
|
|
10078
|
+
const streamEventStorage = agent.getStreamEventStorage();
|
|
10079
|
+
if (!streamEventStorage) {
|
|
10080
|
+
return c.json({ error: "Stream event storage not configured" }, 501);
|
|
10081
|
+
}
|
|
10082
|
+
let events = await streamEventStorage.readEvents(sessionId, {
|
|
10083
|
+
afterSequence,
|
|
10084
|
+
limit
|
|
10085
|
+
});
|
|
10086
|
+
if (events.length === 0 && waitMs > 0) {
|
|
10087
|
+
const deadline = Date.now() + waitMs;
|
|
10088
|
+
const pollInterval = 200;
|
|
10089
|
+
while (Date.now() < deadline) {
|
|
10090
|
+
events = await streamEventStorage.readEvents(sessionId, {
|
|
10091
|
+
afterSequence,
|
|
10092
|
+
limit
|
|
10093
|
+
});
|
|
10094
|
+
if (events.length > 0) break;
|
|
10095
|
+
const currentSession = await sessionManager.getSession(sessionId);
|
|
10096
|
+
if (currentSession && ["completed", "error", "stopped"].includes(currentSession.status)) {
|
|
10097
|
+
break;
|
|
10098
|
+
}
|
|
10099
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
|
|
10100
|
+
}
|
|
10101
|
+
}
|
|
10102
|
+
const nextCursor = events.length > 0 ? Math.max(...events.map((e) => e.sequence)) : afterSequence;
|
|
10103
|
+
return c.json({
|
|
10104
|
+
events: events.map((e) => ({
|
|
10105
|
+
id: e.id,
|
|
10106
|
+
sequence: e.sequence,
|
|
10107
|
+
eventType: e.eventType,
|
|
10108
|
+
payload: e.payload,
|
|
10109
|
+
batchCount: e.batchCount,
|
|
10110
|
+
createdAt: e.createdAt.toISOString()
|
|
10111
|
+
})),
|
|
10112
|
+
nextCursor,
|
|
10113
|
+
hasMore: events.length === limit,
|
|
10114
|
+
sessionStatus: session.status
|
|
10115
|
+
});
|
|
10116
|
+
});
|
|
10117
|
+
router.get("/:sessionId/stream", async (c) => {
|
|
10118
|
+
const sessionId = c.req.param("sessionId");
|
|
10119
|
+
const afterParam = c.req.query("after");
|
|
10120
|
+
const afterSequence = afterParam ? parseInt(afterParam, 10) : 0;
|
|
10121
|
+
const session = await sessionManager.getSession(sessionId);
|
|
10122
|
+
if (!session) {
|
|
10123
|
+
return c.json({ error: "Session not found" }, 404);
|
|
10124
|
+
}
|
|
10125
|
+
const agent = agents2.get(session.agentName);
|
|
10126
|
+
if (!agent) {
|
|
10127
|
+
return c.json({ error: `Agent not found: ${session.agentName}` }, 404);
|
|
10128
|
+
}
|
|
10129
|
+
const streamEventStorage = agent.getStreamEventStorage();
|
|
10130
|
+
if (!streamEventStorage) {
|
|
10131
|
+
return c.json({ error: "Stream event storage not configured" }, 501);
|
|
10132
|
+
}
|
|
10133
|
+
return streaming.streamSSE(c, async (stream) => {
|
|
10134
|
+
const abortController = new AbortController();
|
|
10135
|
+
c.req.raw.signal.addEventListener("abort", () => {
|
|
10136
|
+
abortController.abort();
|
|
10137
|
+
});
|
|
10138
|
+
const heartbeatInterval = setInterval(async () => {
|
|
10139
|
+
try {
|
|
10140
|
+
await stream.writeSSE({
|
|
10141
|
+
event: "heartbeat",
|
|
10142
|
+
data: JSON.stringify({ type: "heartbeat", timestamp: Date.now() })
|
|
10143
|
+
});
|
|
10144
|
+
} catch {
|
|
10145
|
+
clearInterval(heartbeatInterval);
|
|
10146
|
+
}
|
|
10147
|
+
}, 15e3);
|
|
10148
|
+
let sequenceCounter = afterSequence;
|
|
10149
|
+
try {
|
|
10150
|
+
const sessionEmitter = agent.getSessionEmitter(sessionId);
|
|
10151
|
+
const relay = streamEventRelay({
|
|
10152
|
+
storage: streamEventStorage,
|
|
10153
|
+
sessionId,
|
|
10154
|
+
afterSequence,
|
|
10155
|
+
emitter: sessionEmitter,
|
|
10156
|
+
signal: abortController.signal
|
|
10157
|
+
});
|
|
10158
|
+
for await (const event of relay) {
|
|
10159
|
+
if (abortController.signal.aborted) break;
|
|
10160
|
+
sequenceCounter++;
|
|
10161
|
+
await stream.writeSSE({
|
|
10162
|
+
id: String(sequenceCounter),
|
|
10163
|
+
event: event.type,
|
|
10164
|
+
data: JSON.stringify(event)
|
|
10165
|
+
});
|
|
10166
|
+
}
|
|
10167
|
+
} catch (error) {
|
|
10168
|
+
if (!abortController.signal.aborted) {
|
|
10169
|
+
await stream.writeSSE({
|
|
10170
|
+
event: "error",
|
|
10171
|
+
data: JSON.stringify({
|
|
10172
|
+
type: "error",
|
|
10173
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
10174
|
+
})
|
|
10175
|
+
});
|
|
10176
|
+
}
|
|
10177
|
+
} finally {
|
|
10178
|
+
clearInterval(heartbeatInterval);
|
|
10179
|
+
}
|
|
10180
|
+
});
|
|
10181
|
+
});
|
|
9432
10182
|
return router;
|
|
9433
10183
|
}
|
|
9434
10184
|
var createSessionSchema, fileAttachmentSchema, sendMessageSchema, updateEnvSchema, listSessionsSchema, activeStreams, sessionSubscribers;
|
|
9435
10185
|
var init_sessions = __esm({
|
|
9436
10186
|
"src/server/routes/sessions.ts"() {
|
|
10187
|
+
init_stream_event_relay();
|
|
9437
10188
|
createSessionSchema = zod.z.object({
|
|
9438
10189
|
agentName: zod.z.string(),
|
|
9439
10190
|
metadata: zod.z.record(zod.z.unknown()).optional(),
|
|
@@ -10047,7 +10798,8 @@ function createHarnessServer(config) {
|
|
|
10047
10798
|
basePath = "/api",
|
|
10048
10799
|
middleware = [],
|
|
10049
10800
|
hooks = {},
|
|
10050
|
-
queue: queueConfig = {}
|
|
10801
|
+
queue: queueConfig = {},
|
|
10802
|
+
sandbox: sandboxConfig
|
|
10051
10803
|
} = config;
|
|
10052
10804
|
let agentsNeedReload = false;
|
|
10053
10805
|
const sessionManager = new exports.SessionManager(storage);
|
|
@@ -10198,7 +10950,7 @@ function createHarnessServer(config) {
|
|
|
10198
10950
|
return agentStorage.getActiveAgents();
|
|
10199
10951
|
},
|
|
10200
10952
|
/**
|
|
10201
|
-
* Initialize storage, agents,
|
|
10953
|
+
* Initialize storage, agents, start queue processor, and optionally init sandbox pool
|
|
10202
10954
|
*/
|
|
10203
10955
|
async initialize() {
|
|
10204
10956
|
await storage.initialize();
|
|
@@ -10212,12 +10964,20 @@ function createHarnessServer(config) {
|
|
|
10212
10964
|
await agent.initialize();
|
|
10213
10965
|
}
|
|
10214
10966
|
queueProcessor?.start();
|
|
10967
|
+
if (sandboxConfig?.pool && sandboxConfig.autoInitPool !== false) {
|
|
10968
|
+
try {
|
|
10969
|
+
await initializeSandboxPool(sandboxConfig.pool);
|
|
10970
|
+
} catch (error) {
|
|
10971
|
+
console.error("[SERVER] Failed to initialize sandbox pool:", error instanceof Error ? error.message : error);
|
|
10972
|
+
}
|
|
10973
|
+
}
|
|
10215
10974
|
},
|
|
10216
10975
|
/**
|
|
10217
|
-
* Close storage, agents,
|
|
10976
|
+
* Close storage, agents, stop queue processor, and shutdown sandbox pool
|
|
10218
10977
|
*/
|
|
10219
10978
|
async close() {
|
|
10220
10979
|
queueProcessor?.stop();
|
|
10980
|
+
await shutdownSandboxPool();
|
|
10221
10981
|
for (const agent of agents2.values()) {
|
|
10222
10982
|
await agent.close();
|
|
10223
10983
|
}
|
|
@@ -10239,6 +10999,7 @@ var init_server = __esm({
|
|
|
10239
10999
|
init_agents();
|
|
10240
11000
|
init_skills2();
|
|
10241
11001
|
init_queue2();
|
|
11002
|
+
init_sandbox_pool();
|
|
10242
11003
|
}
|
|
10243
11004
|
});
|
|
10244
11005
|
|
|
@@ -13160,7 +13921,8 @@ function createOpenAPIServer(config) {
|
|
|
13160
13921
|
basePath = "/api",
|
|
13161
13922
|
middleware = [],
|
|
13162
13923
|
hooks = {},
|
|
13163
|
-
docs = {}
|
|
13924
|
+
docs = {},
|
|
13925
|
+
sandbox: sandboxConfig
|
|
13164
13926
|
} = config;
|
|
13165
13927
|
const {
|
|
13166
13928
|
enabled: docsEnabled = true,
|
|
@@ -13335,7 +14097,7 @@ Authentication is handled by the hosting application. This API does not enforce
|
|
|
13335
14097
|
return agentStorage.getActiveAgents();
|
|
13336
14098
|
},
|
|
13337
14099
|
/**
|
|
13338
|
-
* Initialize storage and
|
|
14100
|
+
* Initialize storage, agents, and optionally init sandbox pool
|
|
13339
14101
|
*/
|
|
13340
14102
|
async initialize() {
|
|
13341
14103
|
await storage.initialize();
|
|
@@ -13345,11 +14107,19 @@ Authentication is handled by the hosting application. This API does not enforce
|
|
|
13345
14107
|
for (const agent of agents2.values()) {
|
|
13346
14108
|
await agent.initialize();
|
|
13347
14109
|
}
|
|
14110
|
+
if (sandboxConfig?.pool && sandboxConfig.autoInitPool !== false) {
|
|
14111
|
+
try {
|
|
14112
|
+
await initializeSandboxPool(sandboxConfig.pool);
|
|
14113
|
+
} catch (error) {
|
|
14114
|
+
console.error("[SERVER] Failed to initialize sandbox pool:", error instanceof Error ? error.message : error);
|
|
14115
|
+
}
|
|
14116
|
+
}
|
|
13348
14117
|
},
|
|
13349
14118
|
/**
|
|
13350
|
-
* Close storage and
|
|
14119
|
+
* Close storage, agents, and shutdown sandbox pool
|
|
13351
14120
|
*/
|
|
13352
14121
|
async close() {
|
|
14122
|
+
await shutdownSandboxPool();
|
|
13353
14123
|
for (const agent of agents2.values()) {
|
|
13354
14124
|
await agent.close();
|
|
13355
14125
|
}
|
|
@@ -13381,6 +14151,7 @@ var init_server2 = __esm({
|
|
|
13381
14151
|
init_sessions2();
|
|
13382
14152
|
init_agents2();
|
|
13383
14153
|
init_skills3();
|
|
14154
|
+
init_sandbox_pool();
|
|
13384
14155
|
}
|
|
13385
14156
|
});
|
|
13386
14157
|
|
|
@@ -16145,6 +16916,7 @@ __export(schema_exports, {
|
|
|
16145
16916
|
configDeployments: () => configDeployments,
|
|
16146
16917
|
configDeploymentsRelations: () => configDeploymentsRelations,
|
|
16147
16918
|
eventCategoryEnum: () => eventCategoryEnum,
|
|
16919
|
+
eventSourceEnum: () => eventSourceEnum,
|
|
16148
16920
|
messageRoleEnum: () => messageRoleEnum,
|
|
16149
16921
|
messages: () => messages,
|
|
16150
16922
|
messagesRelations: () => messagesRelations,
|
|
@@ -16154,9 +16926,11 @@ __export(schema_exports, {
|
|
|
16154
16926
|
sessionEventsRelations: () => sessionEventsRelations,
|
|
16155
16927
|
sessionStatusEnum: () => sessionStatusEnum,
|
|
16156
16928
|
sessions: () => sessions,
|
|
16157
|
-
sessionsRelations: () => sessionsRelations
|
|
16929
|
+
sessionsRelations: () => sessionsRelations,
|
|
16930
|
+
streamEvents: () => streamEvents,
|
|
16931
|
+
streamEventsRelations: () => streamEventsRelations
|
|
16158
16932
|
});
|
|
16159
|
-
var sessionStatusEnum, messageRoleEnum, agentStatusEnum, agentBackendEnum, queueItemStatusEnum, configDeploymentStatusEnum, configDeploymentTriggerEnum, eventCategoryEnum, sessions, messages, attachments, agents, queueItems, configDeployments, sessionEvents, sessionsRelations, sessionEventsRelations, messagesRelations, attachmentsRelations, configDeploymentsRelations, agentsRelations;
|
|
16933
|
+
var sessionStatusEnum, messageRoleEnum, agentStatusEnum, agentBackendEnum, queueItemStatusEnum, configDeploymentStatusEnum, configDeploymentTriggerEnum, eventSourceEnum, eventCategoryEnum, sessions, messages, attachments, agents, queueItems, configDeployments, sessionEvents, streamEvents, sessionsRelations, sessionEventsRelations, messagesRelations, attachmentsRelations, configDeploymentsRelations, streamEventsRelations, agentsRelations;
|
|
16160
16934
|
var init_schema = __esm({
|
|
16161
16935
|
"src/storage-postgres/schema.ts"() {
|
|
16162
16936
|
sessionStatusEnum = pgCore.pgEnum("session_status", [
|
|
@@ -16198,6 +16972,14 @@ var init_schema = __esm({
|
|
|
16198
16972
|
"initial",
|
|
16199
16973
|
"rollback"
|
|
16200
16974
|
]);
|
|
16975
|
+
eventSourceEnum = pgCore.pgEnum("event_source", [
|
|
16976
|
+
"agent",
|
|
16977
|
+
// Directly from the agent SDK
|
|
16978
|
+
"system",
|
|
16979
|
+
// Generated by execution layer (lifecycle, user input)
|
|
16980
|
+
"derived"
|
|
16981
|
+
// Transformed/aggregated from agent events
|
|
16982
|
+
]);
|
|
16201
16983
|
eventCategoryEnum = pgCore.pgEnum("event_category", [
|
|
16202
16984
|
"lifecycle",
|
|
16203
16985
|
// session_start, session_end, turn_complete
|
|
@@ -16388,6 +17170,8 @@ var init_schema = __esm({
|
|
|
16388
17170
|
// Aggregation fields (for text_stream events)
|
|
16389
17171
|
isAggregated: pgCore.boolean("is_aggregated").default(false),
|
|
16390
17172
|
aggregatedCount: pgCore.integer("aggregated_count"),
|
|
17173
|
+
// Event source tagging
|
|
17174
|
+
eventSource: eventSourceEnum("event_source").notNull().default("agent"),
|
|
16391
17175
|
// Ordering
|
|
16392
17176
|
sequenceNumber: pgCore.integer("sequence_number").notNull(),
|
|
16393
17177
|
createdAt: pgCore.timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
|
|
@@ -16397,6 +17181,21 @@ var init_schema = __esm({
|
|
|
16397
17181
|
pgCore.index("idx_session_events_category").on(table.sessionId, table.category)
|
|
16398
17182
|
]
|
|
16399
17183
|
);
|
|
17184
|
+
streamEvents = pgCore.pgTable(
|
|
17185
|
+
"stream_events",
|
|
17186
|
+
{
|
|
17187
|
+
id: pgCore.uuid("id").defaultRandom().primaryKey(),
|
|
17188
|
+
sessionId: pgCore.uuid("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
17189
|
+
sequence: pgCore.integer("sequence").notNull(),
|
|
17190
|
+
eventType: pgCore.text("event_type").notNull(),
|
|
17191
|
+
payload: pgCore.jsonb("payload").notNull().$type(),
|
|
17192
|
+
batchCount: pgCore.integer("batch_count").default(1).notNull(),
|
|
17193
|
+
createdAt: pgCore.timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
|
|
17194
|
+
},
|
|
17195
|
+
(table) => [
|
|
17196
|
+
pgCore.index("idx_stream_events_session_seq").on(table.sessionId, table.sequence)
|
|
17197
|
+
]
|
|
17198
|
+
);
|
|
16400
17199
|
sessionsRelations = drizzleOrm.relations(sessions, ({ one, many }) => ({
|
|
16401
17200
|
parentSession: one(sessions, {
|
|
16402
17201
|
fields: [sessions.parentSessionId],
|
|
@@ -16407,7 +17206,8 @@ var init_schema = __esm({
|
|
|
16407
17206
|
relationName: "parentSession"
|
|
16408
17207
|
}),
|
|
16409
17208
|
messages: many(messages),
|
|
16410
|
-
events: many(sessionEvents)
|
|
17209
|
+
events: many(sessionEvents),
|
|
17210
|
+
streamEvents: many(streamEvents)
|
|
16411
17211
|
}));
|
|
16412
17212
|
sessionEventsRelations = drizzleOrm.relations(sessionEvents, ({ one }) => ({
|
|
16413
17213
|
session: one(sessions, {
|
|
@@ -16434,6 +17234,12 @@ var init_schema = __esm({
|
|
|
16434
17234
|
references: [agents.id]
|
|
16435
17235
|
})
|
|
16436
17236
|
}));
|
|
17237
|
+
streamEventsRelations = drizzleOrm.relations(streamEvents, ({ one }) => ({
|
|
17238
|
+
session: one(sessions, {
|
|
17239
|
+
fields: [streamEvents.sessionId],
|
|
17240
|
+
references: [sessions.id]
|
|
17241
|
+
})
|
|
17242
|
+
}));
|
|
16437
17243
|
agentsRelations = drizzleOrm.relations(agents, ({ many }) => ({
|
|
16438
17244
|
configDeployments: many(configDeployments)
|
|
16439
17245
|
}));
|
|
@@ -16621,6 +17427,20 @@ var init_storage2 = __esm({
|
|
|
16621
17427
|
`);
|
|
16622
17428
|
await this.db.execute(drizzleOrm.sql`
|
|
16623
17429
|
ALTER TABLE agents ADD COLUMN IF NOT EXISTS backend agent_backend DEFAULT 'claude' NOT NULL;
|
|
17430
|
+
`);
|
|
17431
|
+
await this.db.execute(drizzleOrm.sql`
|
|
17432
|
+
CREATE TABLE IF NOT EXISTS stream_events (
|
|
17433
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
17434
|
+
session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
17435
|
+
sequence INTEGER NOT NULL,
|
|
17436
|
+
event_type TEXT NOT NULL,
|
|
17437
|
+
payload JSONB NOT NULL,
|
|
17438
|
+
batch_count INTEGER NOT NULL DEFAULT 1,
|
|
17439
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
17440
|
+
);
|
|
17441
|
+
`);
|
|
17442
|
+
await this.db.execute(drizzleOrm.sql`
|
|
17443
|
+
CREATE INDEX IF NOT EXISTS idx_stream_events_session_seq ON stream_events(session_id, sequence);
|
|
16624
17444
|
`);
|
|
16625
17445
|
}
|
|
16626
17446
|
/**
|
|
@@ -16813,6 +17633,20 @@ var init_storage2 = __esm({
|
|
|
16813
17633
|
`);
|
|
16814
17634
|
await this.db.execute(drizzleOrm.sql`
|
|
16815
17635
|
CREATE INDEX IF NOT EXISTS idx_queue_items_pending ON queue_items(agent_name, session_id, status, priority, created_at);
|
|
17636
|
+
`);
|
|
17637
|
+
await this.db.execute(drizzleOrm.sql`
|
|
17638
|
+
CREATE TABLE IF NOT EXISTS stream_events (
|
|
17639
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
17640
|
+
session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
17641
|
+
sequence INTEGER NOT NULL,
|
|
17642
|
+
event_type TEXT NOT NULL,
|
|
17643
|
+
payload JSONB NOT NULL,
|
|
17644
|
+
batch_count INTEGER NOT NULL DEFAULT 1,
|
|
17645
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
17646
|
+
);
|
|
17647
|
+
`);
|
|
17648
|
+
await this.db.execute(drizzleOrm.sql`
|
|
17649
|
+
CREATE INDEX IF NOT EXISTS idx_stream_events_session_seq ON stream_events(session_id, sequence);
|
|
16816
17650
|
`);
|
|
16817
17651
|
await this.db.execute(drizzleOrm.sql`
|
|
16818
17652
|
DO $$ BEGIN
|
|
@@ -16930,6 +17764,11 @@ var init_storage2 = __esm({
|
|
|
16930
17764
|
drizzleOrm.eq(sessions.status, options.status)
|
|
16931
17765
|
);
|
|
16932
17766
|
}
|
|
17767
|
+
if (options.resourceId) {
|
|
17768
|
+
conditions.push(
|
|
17769
|
+
drizzleOrm.sql`${sessions.metadata}->>'resourceId' = ${options.resourceId}`
|
|
17770
|
+
);
|
|
17771
|
+
}
|
|
16933
17772
|
const orderColumn = options.orderBy === "updatedAt" ? sessions.updatedAt : sessions.createdAt;
|
|
16934
17773
|
const orderFn = options.order === "asc" ? drizzleOrm.asc : drizzleOrm.desc;
|
|
16935
17774
|
const limit = options.limit ?? 50;
|
|
@@ -17203,6 +18042,56 @@ var init_storage2 = __esm({
|
|
|
17203
18042
|
return rows.map((row) => this.rowToAgent(row));
|
|
17204
18043
|
}
|
|
17205
18044
|
// ============================================================================
|
|
18045
|
+
// Stream Events (SSE Relay)
|
|
18046
|
+
// ============================================================================
|
|
18047
|
+
async appendEvents(sessionId, events) {
|
|
18048
|
+
if (events.length === 0) return [];
|
|
18049
|
+
const [maxSeqResult] = await this.db.select({ maxSeq: drizzleOrm.sql`COALESCE(MAX(${streamEvents.sequence}), 0)` }).from(streamEvents).where(drizzleOrm.eq(streamEvents.sessionId, sessionId));
|
|
18050
|
+
let nextSeq = (maxSeqResult?.maxSeq ?? 0) + 1;
|
|
18051
|
+
const rows = await this.db.insert(streamEvents).values(
|
|
18052
|
+
events.map((event) => ({
|
|
18053
|
+
sessionId,
|
|
18054
|
+
sequence: nextSeq++,
|
|
18055
|
+
eventType: event.eventType,
|
|
18056
|
+
payload: event.payload,
|
|
18057
|
+
batchCount: event.batchCount ?? 1
|
|
18058
|
+
}))
|
|
18059
|
+
).returning();
|
|
18060
|
+
return rows.map((row) => this.rowToStoredStreamEvent(row));
|
|
18061
|
+
}
|
|
18062
|
+
async readEvents(sessionId, options) {
|
|
18063
|
+
const conditions = [drizzleOrm.eq(streamEvents.sessionId, sessionId)];
|
|
18064
|
+
if (options?.afterSequence !== void 0) {
|
|
18065
|
+
conditions.push(drizzleOrm.sql`${streamEvents.sequence} > ${options.afterSequence}`);
|
|
18066
|
+
}
|
|
18067
|
+
const limit = options?.limit ?? 1e3;
|
|
18068
|
+
const rows = await this.db.select().from(streamEvents).where(drizzleOrm.and(...conditions)).orderBy(drizzleOrm.asc(streamEvents.sequence)).limit(limit);
|
|
18069
|
+
return rows.map((row) => this.rowToStoredStreamEvent(row));
|
|
18070
|
+
}
|
|
18071
|
+
async getLatestSequence(sessionId) {
|
|
18072
|
+
const [result] = await this.db.select({ maxSeq: drizzleOrm.sql`COALESCE(MAX(${streamEvents.sequence}), 0)` }).from(streamEvents).where(drizzleOrm.eq(streamEvents.sessionId, sessionId));
|
|
18073
|
+
return result?.maxSeq ?? 0;
|
|
18074
|
+
}
|
|
18075
|
+
async deleteSessionEvents(sessionId) {
|
|
18076
|
+
await this.db.delete(streamEvents).where(drizzleOrm.eq(streamEvents.sessionId, sessionId));
|
|
18077
|
+
}
|
|
18078
|
+
async cleanupOldEvents(maxAgeMs) {
|
|
18079
|
+
const cutoff = new Date(Date.now() - maxAgeMs);
|
|
18080
|
+
const result = await this.db.delete(streamEvents).where(drizzleOrm.sql`${streamEvents.createdAt} < ${cutoff.toISOString()}`);
|
|
18081
|
+
return result.rowCount ?? 0;
|
|
18082
|
+
}
|
|
18083
|
+
rowToStoredStreamEvent(row) {
|
|
18084
|
+
return {
|
|
18085
|
+
id: row.id,
|
|
18086
|
+
sessionId: row.sessionId,
|
|
18087
|
+
sequence: row.sequence,
|
|
18088
|
+
eventType: row.eventType,
|
|
18089
|
+
payload: row.payload,
|
|
18090
|
+
batchCount: row.batchCount,
|
|
18091
|
+
createdAt: row.createdAt
|
|
18092
|
+
};
|
|
18093
|
+
}
|
|
18094
|
+
// ============================================================================
|
|
17206
18095
|
// Database Access (for extensions)
|
|
17207
18096
|
// ============================================================================
|
|
17208
18097
|
/**
|
|
@@ -17601,6 +18490,9 @@ var init_storage3 = __esm({
|
|
|
17601
18490
|
if (options.status) {
|
|
17602
18491
|
query = query.eq("status", options.status);
|
|
17603
18492
|
}
|
|
18493
|
+
if (options.resourceId) {
|
|
18494
|
+
query = query.eq("metadata->>resourceId", options.resourceId);
|
|
18495
|
+
}
|
|
17604
18496
|
const orderColumn = options.orderBy === "updatedAt" ? "updated_at" : "created_at";
|
|
17605
18497
|
const ascending = options.order === "asc";
|
|
17606
18498
|
query = query.order(orderColumn, { ascending }).range(offset, offset + limit - 1);
|
|
@@ -17958,6 +18850,7 @@ var init_storage3 = __esm({
|
|
|
17958
18850
|
sessionId: row.session_id,
|
|
17959
18851
|
eventType: row.event_type,
|
|
17960
18852
|
category: row.category,
|
|
18853
|
+
eventSource: row.event_source,
|
|
17961
18854
|
startedAt: new Date(row.started_at),
|
|
17962
18855
|
endedAt: row.ended_at ? new Date(row.ended_at) : void 0,
|
|
17963
18856
|
durationMs: row.duration_ms ?? void 0,
|
|
@@ -17980,6 +18873,7 @@ var init_storage3 = __esm({
|
|
|
17980
18873
|
session_id: sessionId,
|
|
17981
18874
|
event_type: event.eventType,
|
|
17982
18875
|
category: event.category,
|
|
18876
|
+
event_source: event.eventSource || "agent",
|
|
17983
18877
|
started_at: event.startedAt.toISOString(),
|
|
17984
18878
|
ended_at: event.endedAt?.toISOString() ?? null,
|
|
17985
18879
|
duration_ms: event.durationMs ?? null,
|
|
@@ -18006,6 +18900,9 @@ var init_storage3 = __esm({
|
|
|
18006
18900
|
if (options.eventType) {
|
|
18007
18901
|
query = query.eq("event_type", options.eventType);
|
|
18008
18902
|
}
|
|
18903
|
+
if (options.eventSource) {
|
|
18904
|
+
query = query.eq("event_source", options.eventSource);
|
|
18905
|
+
}
|
|
18009
18906
|
if (options.filePath) {
|
|
18010
18907
|
query = query.or(
|
|
18011
18908
|
`event_data->>filePath.eq.${options.filePath},event_data->>canonicalPath.eq.${options.filePath},and(category.eq.tool,tool_name.in.(Read,Write,Edit),event_data->input->>file_path.eq.${options.filePath})`
|
|
@@ -18038,6 +18935,72 @@ var init_storage3 = __esm({
|
|
|
18038
18935
|
}
|
|
18039
18936
|
return data ? data.sequence_number + 1 : 1;
|
|
18040
18937
|
}
|
|
18938
|
+
// ============================================================================
|
|
18939
|
+
// Stream Events (SSE Relay)
|
|
18940
|
+
// ============================================================================
|
|
18941
|
+
async appendEvents(sessionId, events) {
|
|
18942
|
+
if (events.length === 0) return [];
|
|
18943
|
+
const { data: maxSeqData } = await this.client.from("stream_events").select("sequence").eq("session_id", sessionId).order("sequence", { ascending: false }).limit(1).single();
|
|
18944
|
+
let nextSeq = maxSeqData ? maxSeqData.sequence + 1 : 1;
|
|
18945
|
+
const { data, error } = await this.client.from("stream_events").insert(
|
|
18946
|
+
events.map((event) => ({
|
|
18947
|
+
session_id: sessionId,
|
|
18948
|
+
sequence: nextSeq++,
|
|
18949
|
+
event_type: event.eventType,
|
|
18950
|
+
payload: event.payload,
|
|
18951
|
+
batch_count: event.batchCount ?? 1
|
|
18952
|
+
}))
|
|
18953
|
+
).select();
|
|
18954
|
+
if (error) {
|
|
18955
|
+
throw new Error(`Failed to append stream events: ${error.message}`);
|
|
18956
|
+
}
|
|
18957
|
+
return data.map((row) => this.rowToStoredStreamEvent(row));
|
|
18958
|
+
}
|
|
18959
|
+
async readEvents(sessionId, options) {
|
|
18960
|
+
let query = this.client.from("stream_events").select("*").eq("session_id", sessionId);
|
|
18961
|
+
if (options?.afterSequence !== void 0) {
|
|
18962
|
+
query = query.gt("sequence", options.afterSequence);
|
|
18963
|
+
}
|
|
18964
|
+
const limit = options?.limit ?? 1e3;
|
|
18965
|
+
query = query.order("sequence", { ascending: true }).limit(limit);
|
|
18966
|
+
const { data, error } = await query;
|
|
18967
|
+
if (error) {
|
|
18968
|
+
throw new Error(`Failed to read stream events: ${error.message}`);
|
|
18969
|
+
}
|
|
18970
|
+
return data.map((row) => this.rowToStoredStreamEvent(row));
|
|
18971
|
+
}
|
|
18972
|
+
async getLatestSequence(sessionId) {
|
|
18973
|
+
const { data, error } = await this.client.from("stream_events").select("sequence").eq("session_id", sessionId).order("sequence", { ascending: false }).limit(1).single();
|
|
18974
|
+
if (error && error.code !== "PGRST116") {
|
|
18975
|
+
throw new Error(`Failed to get latest sequence: ${error.message}`);
|
|
18976
|
+
}
|
|
18977
|
+
return data ? data.sequence : 0;
|
|
18978
|
+
}
|
|
18979
|
+
async deleteStreamEvents(sessionId) {
|
|
18980
|
+
const { error } = await this.client.from("stream_events").delete().eq("session_id", sessionId);
|
|
18981
|
+
if (error) {
|
|
18982
|
+
throw new Error(`Failed to delete stream events: ${error.message}`);
|
|
18983
|
+
}
|
|
18984
|
+
}
|
|
18985
|
+
async cleanupOldEvents(maxAgeMs) {
|
|
18986
|
+
const cutoff = new Date(Date.now() - maxAgeMs).toISOString();
|
|
18987
|
+
const { data, error } = await this.client.from("stream_events").delete().lt("created_at", cutoff).select("id");
|
|
18988
|
+
if (error) {
|
|
18989
|
+
throw new Error(`Failed to cleanup old stream events: ${error.message}`);
|
|
18990
|
+
}
|
|
18991
|
+
return data?.length ?? 0;
|
|
18992
|
+
}
|
|
18993
|
+
rowToStoredStreamEvent(row) {
|
|
18994
|
+
return {
|
|
18995
|
+
id: row.id,
|
|
18996
|
+
sessionId: row.session_id,
|
|
18997
|
+
sequence: row.sequence,
|
|
18998
|
+
eventType: row.event_type,
|
|
18999
|
+
payload: row.payload,
|
|
19000
|
+
batchCount: row.batch_count,
|
|
19001
|
+
createdAt: new Date(row.created_at)
|
|
19002
|
+
};
|
|
19003
|
+
}
|
|
18041
19004
|
};
|
|
18042
19005
|
}
|
|
18043
19006
|
});
|
|
@@ -18276,7 +19239,8 @@ var init_storage4 = __esm({
|
|
|
18276
19239
|
limit: options?.limit,
|
|
18277
19240
|
offset: options?.offset,
|
|
18278
19241
|
agentName: options?.agentName,
|
|
18279
|
-
status: options?.status
|
|
19242
|
+
status: options?.status,
|
|
19243
|
+
resourceId: options?.resourceId
|
|
18280
19244
|
}
|
|
18281
19245
|
}
|
|
18282
19246
|
);
|
|
@@ -18870,6 +19834,7 @@ var init_src = __esm({
|
|
|
18870
19834
|
init_credentials();
|
|
18871
19835
|
init_skills();
|
|
18872
19836
|
init_utils();
|
|
19837
|
+
init_relay();
|
|
18873
19838
|
init_server();
|
|
18874
19839
|
init_server2();
|
|
18875
19840
|
init_schemas();
|
|
@@ -19046,7 +20011,9 @@ exports.shouldUseSandbox = shouldUseSandbox;
|
|
|
19046
20011
|
exports.shutdownSandboxPool = shutdownSandboxPool;
|
|
19047
20012
|
exports.sseMcpWithAuth = sseMcpWithAuth;
|
|
19048
20013
|
exports.startServer = startServer;
|
|
20014
|
+
exports.streamEventRelay = streamEventRelay;
|
|
19049
20015
|
exports.updateToolCallWithResult = updateToolCallWithResult;
|
|
20016
|
+
exports.warmSandboxForSession = warmSandboxForSession;
|
|
19050
20017
|
exports.writeFileToSandbox = writeFileToSandbox;
|
|
19051
20018
|
//# sourceMappingURL=index.cjs.map
|
|
19052
20019
|
//# sourceMappingURL=index.cjs.map
|