@ash-cloud/ash-ai 0.1.16 → 0.1.17
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 +644 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +211 -6
- package/dist/index.d.ts +211 -6
- package/dist/index.js +644 -23
- package/dist/index.js.map +1 -1
- package/dist/playground.js +689 -684
- package/dist/{schema-DSLyNeoS.d.cts → schema-dr_7pxIB.d.cts} +140 -2
- package/dist/{schema-DSLyNeoS.d.ts → schema-dr_7pxIB.d.ts} +140 -2
- package/dist/schema.cjs +25 -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 +24 -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');
|
|
@@ -384,6 +385,85 @@ var init_errors = __esm({
|
|
|
384
385
|
}
|
|
385
386
|
});
|
|
386
387
|
|
|
388
|
+
// src/relay/stream-event-writer.ts
|
|
389
|
+
exports.StreamEventWriter = void 0;
|
|
390
|
+
var init_stream_event_writer = __esm({
|
|
391
|
+
"src/relay/stream-event-writer.ts"() {
|
|
392
|
+
exports.StreamEventWriter = class {
|
|
393
|
+
storage;
|
|
394
|
+
sessionId;
|
|
395
|
+
emitter;
|
|
396
|
+
flushIntervalMs;
|
|
397
|
+
maxBatchChars;
|
|
398
|
+
// Text delta batching state
|
|
399
|
+
pendingDelta = "";
|
|
400
|
+
pendingDeltaCount = 0;
|
|
401
|
+
flushTimer = null;
|
|
402
|
+
closed = false;
|
|
403
|
+
constructor(options) {
|
|
404
|
+
this.storage = options.storage;
|
|
405
|
+
this.sessionId = options.sessionId;
|
|
406
|
+
this.emitter = options.emitter;
|
|
407
|
+
this.flushIntervalMs = options.flushIntervalMs ?? 100;
|
|
408
|
+
this.maxBatchChars = options.maxBatchChars ?? 500;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Write a stream event. Text deltas are batched; all other events are written immediately.
|
|
412
|
+
*/
|
|
413
|
+
async write(event) {
|
|
414
|
+
if (this.closed) return;
|
|
415
|
+
if (this.emitter) {
|
|
416
|
+
this.emitter.emit("event", event);
|
|
417
|
+
}
|
|
418
|
+
if (event.type === "text_delta") {
|
|
419
|
+
this.pendingDelta += event.delta;
|
|
420
|
+
this.pendingDeltaCount++;
|
|
421
|
+
if (!this.flushTimer) {
|
|
422
|
+
this.flushTimer = setTimeout(() => this.flush(), this.flushIntervalMs);
|
|
423
|
+
}
|
|
424
|
+
if (this.pendingDelta.length >= this.maxBatchChars) {
|
|
425
|
+
await this.flush();
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
await this.flush();
|
|
429
|
+
await this.writeToStorage([{ eventType: event.type, payload: event, batchCount: 1 }]);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Flush any pending text_delta events to storage as a single batched event.
|
|
434
|
+
*/
|
|
435
|
+
async flush() {
|
|
436
|
+
if (this.flushTimer) {
|
|
437
|
+
clearTimeout(this.flushTimer);
|
|
438
|
+
this.flushTimer = null;
|
|
439
|
+
}
|
|
440
|
+
if (this.pendingDelta.length === 0) return;
|
|
441
|
+
const batchedEvent = {
|
|
442
|
+
type: "text_delta",
|
|
443
|
+
delta: this.pendingDelta
|
|
444
|
+
};
|
|
445
|
+
const batchCount = this.pendingDeltaCount;
|
|
446
|
+
this.pendingDelta = "";
|
|
447
|
+
this.pendingDeltaCount = 0;
|
|
448
|
+
await this.writeToStorage([{ eventType: "text_delta", payload: batchedEvent, batchCount }]);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Close the writer, flushing any remaining events.
|
|
452
|
+
*/
|
|
453
|
+
async close() {
|
|
454
|
+
await this.flush();
|
|
455
|
+
this.closed = true;
|
|
456
|
+
if (this.emitter) {
|
|
457
|
+
this.emitter.emit("done");
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
async writeToStorage(events) {
|
|
461
|
+
return this.storage.appendEvents(this.sessionId, events);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
387
467
|
// src/session/manager.ts
|
|
388
468
|
exports.SessionManager = void 0;
|
|
389
469
|
var init_manager = __esm({
|
|
@@ -1788,11 +1868,10 @@ var init_sandbox_logger = __esm({
|
|
|
1788
1868
|
};
|
|
1789
1869
|
}
|
|
1790
1870
|
});
|
|
1791
|
-
|
|
1792
|
-
// src/agent/harness.ts
|
|
1793
1871
|
exports.AgentHarness = void 0;
|
|
1794
1872
|
var init_harness = __esm({
|
|
1795
1873
|
"src/agent/harness.ts"() {
|
|
1874
|
+
init_stream_event_writer();
|
|
1796
1875
|
init_manager();
|
|
1797
1876
|
init_claude_sdk();
|
|
1798
1877
|
init_backend();
|
|
@@ -1809,6 +1888,8 @@ var init_harness = __esm({
|
|
|
1809
1888
|
sessionSkillDirs = /* @__PURE__ */ new Map();
|
|
1810
1889
|
/** Tracks active executions by session ID for stop/interrupt capability */
|
|
1811
1890
|
activeExecutions = /* @__PURE__ */ new Map();
|
|
1891
|
+
/** EventEmitters for same-process SSE relay fast-path, keyed by session ID */
|
|
1892
|
+
sessionEmitters = /* @__PURE__ */ new Map();
|
|
1812
1893
|
constructor(config) {
|
|
1813
1894
|
this.name = config.name;
|
|
1814
1895
|
this.config = config;
|
|
@@ -1956,6 +2037,20 @@ var init_harness = __esm({
|
|
|
1956
2037
|
getSessionManager() {
|
|
1957
2038
|
return this.sessionManager;
|
|
1958
2039
|
}
|
|
2040
|
+
/**
|
|
2041
|
+
* Get the stream event storage (if configured)
|
|
2042
|
+
*/
|
|
2043
|
+
getStreamEventStorage() {
|
|
2044
|
+
return this.config.streamEventStorage;
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Get the EventEmitter for a session's stream events.
|
|
2048
|
+
* Used by the relay endpoint for same-process fast-path.
|
|
2049
|
+
* Returns undefined if no active execution or no streamEventStorage configured.
|
|
2050
|
+
*/
|
|
2051
|
+
getSessionEmitter(sessionId) {
|
|
2052
|
+
return this.sessionEmitters.get(sessionId);
|
|
2053
|
+
}
|
|
1959
2054
|
async ensureInitialized() {
|
|
1960
2055
|
if (!this.initialized) {
|
|
1961
2056
|
await this.initialize();
|
|
@@ -1970,9 +2065,24 @@ var init_harness = __esm({
|
|
|
1970
2065
|
async *send(prompt, options = {}) {
|
|
1971
2066
|
const controller = new AbortController();
|
|
1972
2067
|
self.activeExecutions.set(session.id, controller);
|
|
2068
|
+
let writer;
|
|
2069
|
+
let emitter;
|
|
2070
|
+
if (self.config.streamEventStorage) {
|
|
2071
|
+
emitter = new events.EventEmitter();
|
|
2072
|
+
self.sessionEmitters.set(session.id, emitter);
|
|
2073
|
+
writer = new exports.StreamEventWriter({
|
|
2074
|
+
storage: self.config.streamEventStorage,
|
|
2075
|
+
sessionId: session.id,
|
|
2076
|
+
emitter
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
1973
2079
|
if (options.signal) {
|
|
1974
2080
|
options.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
1975
2081
|
}
|
|
2082
|
+
const writeEvent = writer ? (event) => {
|
|
2083
|
+
writer.write(event).catch(() => {
|
|
2084
|
+
});
|
|
2085
|
+
} : void 0;
|
|
1976
2086
|
let partialTextContent = "";
|
|
1977
2087
|
const sandboxLogs = [];
|
|
1978
2088
|
const logQueue = [];
|
|
@@ -1986,7 +2096,9 @@ var init_harness = __esm({
|
|
|
1986
2096
|
const yieldQueuedLogs = async function* () {
|
|
1987
2097
|
while (logQueue.length > 0) {
|
|
1988
2098
|
const entry = logQueue.shift();
|
|
1989
|
-
|
|
2099
|
+
const event = { type: "sandbox_log", entry };
|
|
2100
|
+
writeEvent?.(event);
|
|
2101
|
+
yield event;
|
|
1990
2102
|
}
|
|
1991
2103
|
};
|
|
1992
2104
|
logger3.info("execution", `Starting execution for session ${session.id}`);
|
|
@@ -2003,11 +2115,13 @@ var init_harness = __esm({
|
|
|
2003
2115
|
if (self.hooks.onMessage && savedUserMessage) {
|
|
2004
2116
|
await self.hooks.onMessage(savedUserMessage);
|
|
2005
2117
|
}
|
|
2006
|
-
|
|
2118
|
+
const sessionStartEvent = {
|
|
2007
2119
|
type: "session_start",
|
|
2008
2120
|
sessionId: session.id,
|
|
2009
2121
|
sdkSessionId: session.sdkSessionId ?? ""
|
|
2010
2122
|
};
|
|
2123
|
+
writeEvent?.(sessionStartEvent);
|
|
2124
|
+
yield sessionStartEvent;
|
|
2011
2125
|
yield* yieldQueuedLogs();
|
|
2012
2126
|
const assistantContent = [];
|
|
2013
2127
|
let wasAborted = false;
|
|
@@ -2028,22 +2142,26 @@ var init_harness = __esm({
|
|
|
2028
2142
|
}
|
|
2029
2143
|
if (event.type === "text_delta" && event.delta) {
|
|
2030
2144
|
partialTextContent += event.delta;
|
|
2031
|
-
|
|
2145
|
+
const textDeltaEvent = {
|
|
2032
2146
|
type: "text_delta",
|
|
2033
2147
|
delta: event.delta
|
|
2034
2148
|
};
|
|
2149
|
+
writeEvent?.(textDeltaEvent);
|
|
2150
|
+
yield textDeltaEvent;
|
|
2035
2151
|
} else if (event.type === "thinking_delta" && event.delta) {
|
|
2036
|
-
|
|
2152
|
+
const thinkingEvent = {
|
|
2037
2153
|
type: "thinking_delta",
|
|
2038
2154
|
delta: event.delta
|
|
2039
2155
|
};
|
|
2156
|
+
writeEvent?.(thinkingEvent);
|
|
2157
|
+
yield thinkingEvent;
|
|
2040
2158
|
} else if (event.type === "text") {
|
|
2041
2159
|
const textContent = {
|
|
2042
2160
|
type: "text",
|
|
2043
2161
|
text: event.text
|
|
2044
2162
|
};
|
|
2045
2163
|
assistantContent.push(textContent);
|
|
2046
|
-
|
|
2164
|
+
const messageEvent = {
|
|
2047
2165
|
type: "message",
|
|
2048
2166
|
message: {
|
|
2049
2167
|
id: "",
|
|
@@ -2053,6 +2171,8 @@ var init_harness = __esm({
|
|
|
2053
2171
|
createdAt: /* @__PURE__ */ new Date()
|
|
2054
2172
|
}
|
|
2055
2173
|
};
|
|
2174
|
+
writeEvent?.(messageEvent);
|
|
2175
|
+
yield messageEvent;
|
|
2056
2176
|
} else if (event.type === "tool_use") {
|
|
2057
2177
|
logger3.debug("execution", `Tool use: ${event.name}`, { toolId: event.id });
|
|
2058
2178
|
const toolContent = {
|
|
@@ -2062,12 +2182,14 @@ var init_harness = __esm({
|
|
|
2062
2182
|
input: event.input
|
|
2063
2183
|
};
|
|
2064
2184
|
assistantContent.push(toolContent);
|
|
2065
|
-
|
|
2185
|
+
const toolUseEvent = {
|
|
2066
2186
|
type: "tool_use",
|
|
2067
2187
|
id: event.id,
|
|
2068
2188
|
name: event.name,
|
|
2069
2189
|
input: event.input
|
|
2070
2190
|
};
|
|
2191
|
+
writeEvent?.(toolUseEvent);
|
|
2192
|
+
yield toolUseEvent;
|
|
2071
2193
|
yield* yieldQueuedLogs();
|
|
2072
2194
|
if (self.hooks.onToolUse) {
|
|
2073
2195
|
await self.hooks.onToolUse(event.name, event.input, null);
|
|
@@ -2084,12 +2206,14 @@ var init_harness = __esm({
|
|
|
2084
2206
|
isError: event.isError
|
|
2085
2207
|
};
|
|
2086
2208
|
assistantContent.push(resultContent);
|
|
2087
|
-
|
|
2209
|
+
const toolResultEvent = {
|
|
2088
2210
|
type: "tool_result",
|
|
2089
2211
|
toolUseId: event.toolUseId,
|
|
2090
2212
|
content: event.content,
|
|
2091
2213
|
isError: event.isError
|
|
2092
2214
|
};
|
|
2215
|
+
writeEvent?.(toolResultEvent);
|
|
2216
|
+
yield toolResultEvent;
|
|
2093
2217
|
}
|
|
2094
2218
|
}
|
|
2095
2219
|
if (wasAborted || controller.signal.aborted) {
|
|
@@ -2118,19 +2242,23 @@ var init_harness = __esm({
|
|
|
2118
2242
|
}
|
|
2119
2243
|
});
|
|
2120
2244
|
yield* yieldQueuedLogs();
|
|
2121
|
-
|
|
2245
|
+
const stoppedEvent = {
|
|
2122
2246
|
type: "session_stopped",
|
|
2123
2247
|
sessionId: session.id,
|
|
2124
2248
|
reason: "user_requested",
|
|
2125
2249
|
partialContent: partialTextContent || void 0
|
|
2126
2250
|
};
|
|
2251
|
+
writeEvent?.(stoppedEvent);
|
|
2252
|
+
yield stoppedEvent;
|
|
2127
2253
|
return;
|
|
2128
2254
|
}
|
|
2129
2255
|
logger3.info("execution", "Execution completed successfully");
|
|
2130
|
-
|
|
2256
|
+
const turnCompleteEvent = {
|
|
2131
2257
|
type: "turn_complete",
|
|
2132
2258
|
sessionId: session.id
|
|
2133
2259
|
};
|
|
2260
|
+
writeEvent?.(turnCompleteEvent);
|
|
2261
|
+
yield turnCompleteEvent;
|
|
2134
2262
|
if (assistantContent.length > 0) {
|
|
2135
2263
|
const savedAssistantMessages = await self.sessionManager.saveMessages(
|
|
2136
2264
|
session.id,
|
|
@@ -2154,11 +2282,13 @@ var init_harness = __esm({
|
|
|
2154
2282
|
}
|
|
2155
2283
|
});
|
|
2156
2284
|
yield* yieldQueuedLogs();
|
|
2157
|
-
|
|
2285
|
+
const sessionEndEvent = {
|
|
2158
2286
|
type: "session_end",
|
|
2159
2287
|
sessionId: session.id,
|
|
2160
2288
|
status: "completed"
|
|
2161
2289
|
};
|
|
2290
|
+
writeEvent?.(sessionEndEvent);
|
|
2291
|
+
yield sessionEndEvent;
|
|
2162
2292
|
if (self.hooks.onSessionEnd) {
|
|
2163
2293
|
const updatedSession = await self.sessionManager.getSession(session.id);
|
|
2164
2294
|
if (updatedSession) {
|
|
@@ -2189,12 +2319,14 @@ var init_harness = __esm({
|
|
|
2189
2319
|
}
|
|
2190
2320
|
});
|
|
2191
2321
|
yield* yieldQueuedLogs();
|
|
2192
|
-
|
|
2322
|
+
const catchStoppedEvent = {
|
|
2193
2323
|
type: "session_stopped",
|
|
2194
2324
|
sessionId: session.id,
|
|
2195
2325
|
reason: "user_requested",
|
|
2196
2326
|
partialContent: partialTextContent || void 0
|
|
2197
2327
|
};
|
|
2328
|
+
writeEvent?.(catchStoppedEvent);
|
|
2329
|
+
yield catchStoppedEvent;
|
|
2198
2330
|
} else {
|
|
2199
2331
|
logger3.error("execution", `Execution error: ${errorMessage}`);
|
|
2200
2332
|
await self.sessionManager.updateSession(session.id, {
|
|
@@ -2212,13 +2344,19 @@ var init_harness = __esm({
|
|
|
2212
2344
|
await self.hooks.onError(error, updatedSession);
|
|
2213
2345
|
}
|
|
2214
2346
|
}
|
|
2215
|
-
|
|
2347
|
+
const errorEvent = {
|
|
2216
2348
|
type: "error",
|
|
2217
2349
|
error: errorMessage
|
|
2218
2350
|
};
|
|
2351
|
+
writeEvent?.(errorEvent);
|
|
2352
|
+
yield errorEvent;
|
|
2219
2353
|
}
|
|
2220
2354
|
} finally {
|
|
2221
2355
|
self.activeExecutions.delete(session.id);
|
|
2356
|
+
if (writer) {
|
|
2357
|
+
await writer.close();
|
|
2358
|
+
}
|
|
2359
|
+
self.sessionEmitters.delete(session.id);
|
|
2222
2360
|
}
|
|
2223
2361
|
},
|
|
2224
2362
|
async getSession() {
|
|
@@ -2722,12 +2860,88 @@ var init_queue_memory = __esm({
|
|
|
2722
2860
|
};
|
|
2723
2861
|
}
|
|
2724
2862
|
});
|
|
2863
|
+
exports.MemoryStreamEventStorage = void 0;
|
|
2864
|
+
var init_stream_event_memory = __esm({
|
|
2865
|
+
"src/storage/stream-event-memory.ts"() {
|
|
2866
|
+
exports.MemoryStreamEventStorage = class {
|
|
2867
|
+
events = /* @__PURE__ */ new Map();
|
|
2868
|
+
nextSequence = /* @__PURE__ */ new Map();
|
|
2869
|
+
async initialize() {
|
|
2870
|
+
}
|
|
2871
|
+
async close() {
|
|
2872
|
+
this.events.clear();
|
|
2873
|
+
this.nextSequence.clear();
|
|
2874
|
+
}
|
|
2875
|
+
async appendEvents(sessionId, events) {
|
|
2876
|
+
if (!this.events.has(sessionId)) {
|
|
2877
|
+
this.events.set(sessionId, []);
|
|
2878
|
+
}
|
|
2879
|
+
const sessionEvents2 = this.events.get(sessionId);
|
|
2880
|
+
let seq = this.nextSequence.get(sessionId) ?? 0;
|
|
2881
|
+
const stored = [];
|
|
2882
|
+
for (const event of events) {
|
|
2883
|
+
seq += 1;
|
|
2884
|
+
const storedEvent = {
|
|
2885
|
+
id: nanoid.nanoid(),
|
|
2886
|
+
sessionId,
|
|
2887
|
+
sequence: seq,
|
|
2888
|
+
eventType: event.eventType,
|
|
2889
|
+
payload: event.payload,
|
|
2890
|
+
batchCount: event.batchCount ?? 1,
|
|
2891
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
2892
|
+
};
|
|
2893
|
+
sessionEvents2.push(storedEvent);
|
|
2894
|
+
stored.push(storedEvent);
|
|
2895
|
+
}
|
|
2896
|
+
this.nextSequence.set(sessionId, seq);
|
|
2897
|
+
return stored;
|
|
2898
|
+
}
|
|
2899
|
+
async readEvents(sessionId, options) {
|
|
2900
|
+
const sessionEvents2 = this.events.get(sessionId) ?? [];
|
|
2901
|
+
let result = sessionEvents2;
|
|
2902
|
+
if (options?.afterSequence !== void 0) {
|
|
2903
|
+
result = result.filter((e) => e.sequence > options.afterSequence);
|
|
2904
|
+
}
|
|
2905
|
+
if (options?.limit !== void 0) {
|
|
2906
|
+
result = result.slice(0, options.limit);
|
|
2907
|
+
}
|
|
2908
|
+
return result;
|
|
2909
|
+
}
|
|
2910
|
+
async getLatestSequence(sessionId) {
|
|
2911
|
+
return this.nextSequence.get(sessionId) ?? 0;
|
|
2912
|
+
}
|
|
2913
|
+
async deleteSessionEvents(sessionId) {
|
|
2914
|
+
this.events.delete(sessionId);
|
|
2915
|
+
this.nextSequence.delete(sessionId);
|
|
2916
|
+
}
|
|
2917
|
+
async cleanupOldEvents(maxAgeMs) {
|
|
2918
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
2919
|
+
let deleted = 0;
|
|
2920
|
+
for (const [sessionId, sessionEvents2] of this.events) {
|
|
2921
|
+
const before = sessionEvents2.length;
|
|
2922
|
+
const remaining = sessionEvents2.filter(
|
|
2923
|
+
(e) => e.createdAt.getTime() >= cutoff
|
|
2924
|
+
);
|
|
2925
|
+
deleted += before - remaining.length;
|
|
2926
|
+
if (remaining.length === 0) {
|
|
2927
|
+
this.events.delete(sessionId);
|
|
2928
|
+
this.nextSequence.delete(sessionId);
|
|
2929
|
+
} else {
|
|
2930
|
+
this.events.set(sessionId, remaining);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
return deleted;
|
|
2934
|
+
}
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2937
|
+
});
|
|
2725
2938
|
|
|
2726
2939
|
// src/storage/index.ts
|
|
2727
2940
|
var init_storage = __esm({
|
|
2728
2941
|
"src/storage/index.ts"() {
|
|
2729
2942
|
init_memory();
|
|
2730
2943
|
init_queue_memory();
|
|
2944
|
+
init_stream_event_memory();
|
|
2731
2945
|
}
|
|
2732
2946
|
});
|
|
2733
2947
|
|
|
@@ -5182,10 +5396,11 @@ async function getOrCreateSandbox(options) {
|
|
|
5182
5396
|
startupScriptRan: true,
|
|
5183
5397
|
// Assume ran for reconnected sandboxes
|
|
5184
5398
|
startupScriptHash: void 0,
|
|
5399
|
+
// Unknown — caller should not re-run based on hash mismatch alone
|
|
5185
5400
|
isNew: false,
|
|
5186
5401
|
configFileUrl: void 0,
|
|
5187
|
-
configInstalledAt:
|
|
5188
|
-
//
|
|
5402
|
+
configInstalledAt: now2
|
|
5403
|
+
// Assume config was installed — prevents unnecessary re-install on reconnection
|
|
5189
5404
|
};
|
|
5190
5405
|
} else {
|
|
5191
5406
|
console.log("[SANDBOX] Reconnected sandbox failed health check, will create new");
|
|
@@ -5571,7 +5786,8 @@ async function* executeInSandbox(prompt, apiKey, options) {
|
|
|
5571
5786
|
markSdkInstalled(sessionId);
|
|
5572
5787
|
}
|
|
5573
5788
|
const currentScriptHash = hashStartupScript(options.startupScript);
|
|
5574
|
-
const
|
|
5789
|
+
const scriptHashChanged = cachedScriptHash !== void 0 && currentScriptHash !== cachedScriptHash;
|
|
5790
|
+
const needsStartupScript = options.startupScript && (!startupScriptRan || scriptHashChanged);
|
|
5575
5791
|
if (needsStartupScript) {
|
|
5576
5792
|
console.log("[SANDBOX] Running startup script...");
|
|
5577
5793
|
const startupStartTime = Date.now();
|
|
@@ -5705,11 +5921,18 @@ const { query } = require('@anthropic-ai/claude-agent-sdk');
|
|
|
5705
5921
|
const prompt = ${JSON.stringify(prompt)};
|
|
5706
5922
|
const options = ${JSON.stringify(sdkOptions)};
|
|
5707
5923
|
|
|
5924
|
+
let queryCompleted = false;
|
|
5925
|
+
|
|
5708
5926
|
async function run() {
|
|
5709
5927
|
try {
|
|
5710
5928
|
for await (const message of query({ prompt, options })) {
|
|
5711
5929
|
console.log(JSON.stringify(message));
|
|
5712
5930
|
}
|
|
5931
|
+
queryCompleted = true;
|
|
5932
|
+
// Exit cleanly immediately after query completes.
|
|
5933
|
+
// MCP server cleanup (e.g. Playwright browser shutdown) can trigger
|
|
5934
|
+
// unhandled rejections after the query is done, causing spurious exit code 1.
|
|
5935
|
+
process.exit(0);
|
|
5713
5936
|
} catch (error) {
|
|
5714
5937
|
const errorInfo = {
|
|
5715
5938
|
type: 'error',
|
|
@@ -5725,6 +5948,11 @@ async function run() {
|
|
|
5725
5948
|
}
|
|
5726
5949
|
|
|
5727
5950
|
process.on('unhandledRejection', (reason) => {
|
|
5951
|
+
// Ignore rejections from MCP server cleanup after query has completed
|
|
5952
|
+
if (queryCompleted) {
|
|
5953
|
+
process.exit(0);
|
|
5954
|
+
return;
|
|
5955
|
+
}
|
|
5728
5956
|
const errorInfo = {
|
|
5729
5957
|
type: 'error',
|
|
5730
5958
|
error: reason instanceof Error ? reason.message : String(reason),
|
|
@@ -5735,6 +5963,11 @@ process.on('unhandledRejection', (reason) => {
|
|
|
5735
5963
|
});
|
|
5736
5964
|
|
|
5737
5965
|
process.on('uncaughtException', (error) => {
|
|
5966
|
+
// Ignore exceptions from MCP server cleanup after query has completed
|
|
5967
|
+
if (queryCompleted) {
|
|
5968
|
+
process.exit(0);
|
|
5969
|
+
return;
|
|
5970
|
+
}
|
|
5738
5971
|
const errorInfo = {
|
|
5739
5972
|
type: 'error',
|
|
5740
5973
|
error: error.message || 'Unknown error',
|
|
@@ -6807,15 +7040,35 @@ var init_sandbox_file_sync = __esm({
|
|
|
6807
7040
|
}
|
|
6808
7041
|
}
|
|
6809
7042
|
/**
|
|
6810
|
-
* Send a file sync event webhook
|
|
7043
|
+
* Send a file sync event webhook.
|
|
7044
|
+
* Strips raw Buffer content (previousContent/newContent) and replaces with a
|
|
7045
|
+
* pre-signed S3 download URL when the file exists in storage.
|
|
6811
7046
|
*/
|
|
6812
7047
|
async sendFileSyncWebhook(sessionId, event) {
|
|
7048
|
+
let downloadUrl;
|
|
7049
|
+
if (event.success && event.canonicalPath && !event.operation.startsWith("deleted")) {
|
|
7050
|
+
try {
|
|
7051
|
+
downloadUrl = await this.fileStore.getSignedUrl(sessionId, event.canonicalPath);
|
|
7052
|
+
} catch {
|
|
7053
|
+
}
|
|
7054
|
+
}
|
|
7055
|
+
const webhookEvent = {
|
|
7056
|
+
operation: event.operation,
|
|
7057
|
+
source: event.source,
|
|
7058
|
+
canonicalPath: event.canonicalPath,
|
|
7059
|
+
basePath: event.basePath,
|
|
7060
|
+
sandboxPath: event.sandboxPath,
|
|
7061
|
+
fileSize: event.fileSize,
|
|
7062
|
+
success: event.success,
|
|
7063
|
+
error: event.error,
|
|
7064
|
+
downloadUrl
|
|
7065
|
+
};
|
|
6813
7066
|
const payload = {
|
|
6814
7067
|
event: "file_sync",
|
|
6815
7068
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6816
7069
|
sessionId,
|
|
6817
7070
|
metadata: this.webhookConfig?.metadata,
|
|
6818
|
-
fileSyncEvent:
|
|
7071
|
+
fileSyncEvent: webhookEvent
|
|
6819
7072
|
};
|
|
6820
7073
|
await this.sendWebhook(payload, sessionId);
|
|
6821
7074
|
}
|
|
@@ -9103,6 +9356,135 @@ var init_utils = __esm({
|
|
|
9103
9356
|
init_sandbox_logger();
|
|
9104
9357
|
}
|
|
9105
9358
|
});
|
|
9359
|
+
|
|
9360
|
+
// src/relay/stream-event-relay.ts
|
|
9361
|
+
async function* streamEventRelay(options) {
|
|
9362
|
+
const {
|
|
9363
|
+
storage,
|
|
9364
|
+
sessionId,
|
|
9365
|
+
afterSequence = 0,
|
|
9366
|
+
emitter,
|
|
9367
|
+
pollIntervalMs = 100,
|
|
9368
|
+
heartbeatIntervalMs = 15e3,
|
|
9369
|
+
signal
|
|
9370
|
+
} = options;
|
|
9371
|
+
let lastSequence = afterSequence;
|
|
9372
|
+
let done = false;
|
|
9373
|
+
const catchUpEvents = await storage.readEvents(sessionId, {
|
|
9374
|
+
afterSequence: lastSequence,
|
|
9375
|
+
limit: 1e4
|
|
9376
|
+
});
|
|
9377
|
+
for (const stored of catchUpEvents) {
|
|
9378
|
+
if (signal?.aborted || done) return;
|
|
9379
|
+
lastSequence = stored.sequence;
|
|
9380
|
+
yield stored.payload;
|
|
9381
|
+
if (TERMINAL_EVENT_TYPES.has(stored.eventType)) {
|
|
9382
|
+
done = true;
|
|
9383
|
+
return;
|
|
9384
|
+
}
|
|
9385
|
+
}
|
|
9386
|
+
if (signal?.aborted) return;
|
|
9387
|
+
if (emitter) {
|
|
9388
|
+
yield* relayFromEmitter(emitter, signal, heartbeatIntervalMs);
|
|
9389
|
+
} else {
|
|
9390
|
+
yield* relayByPolling(storage, sessionId, lastSequence, pollIntervalMs, heartbeatIntervalMs, signal);
|
|
9391
|
+
}
|
|
9392
|
+
}
|
|
9393
|
+
async function* relayFromEmitter(emitter, signal, heartbeatIntervalMs = 15e3) {
|
|
9394
|
+
const queue = [];
|
|
9395
|
+
let resolve3 = null;
|
|
9396
|
+
const onEvent = (event) => {
|
|
9397
|
+
queue.push(event);
|
|
9398
|
+
if (resolve3) {
|
|
9399
|
+
resolve3();
|
|
9400
|
+
resolve3 = null;
|
|
9401
|
+
}
|
|
9402
|
+
};
|
|
9403
|
+
const onDone = () => {
|
|
9404
|
+
queue.push("done");
|
|
9405
|
+
if (resolve3) {
|
|
9406
|
+
resolve3();
|
|
9407
|
+
resolve3 = null;
|
|
9408
|
+
}
|
|
9409
|
+
};
|
|
9410
|
+
emitter.on("event", onEvent);
|
|
9411
|
+
emitter.on("done", onDone);
|
|
9412
|
+
const heartbeatTimer = setInterval(() => {
|
|
9413
|
+
if (resolve3) {
|
|
9414
|
+
resolve3();
|
|
9415
|
+
resolve3 = null;
|
|
9416
|
+
}
|
|
9417
|
+
}, heartbeatIntervalMs);
|
|
9418
|
+
try {
|
|
9419
|
+
while (!signal?.aborted) {
|
|
9420
|
+
while (queue.length > 0) {
|
|
9421
|
+
const item = queue.shift();
|
|
9422
|
+
if (item === "done") return;
|
|
9423
|
+
yield item;
|
|
9424
|
+
if (TERMINAL_EVENT_TYPES.has(item.type)) return;
|
|
9425
|
+
}
|
|
9426
|
+
await new Promise((r) => {
|
|
9427
|
+
resolve3 = r;
|
|
9428
|
+
if (signal) {
|
|
9429
|
+
signal.addEventListener("abort", () => {
|
|
9430
|
+
resolve3 = null;
|
|
9431
|
+
r();
|
|
9432
|
+
}, { once: true });
|
|
9433
|
+
}
|
|
9434
|
+
});
|
|
9435
|
+
}
|
|
9436
|
+
} finally {
|
|
9437
|
+
clearInterval(heartbeatTimer);
|
|
9438
|
+
emitter.off("event", onEvent);
|
|
9439
|
+
emitter.off("done", onDone);
|
|
9440
|
+
}
|
|
9441
|
+
}
|
|
9442
|
+
async function* relayByPolling(storage, sessionId, startSequence, pollIntervalMs, heartbeatIntervalMs, signal) {
|
|
9443
|
+
let lastSequence = startSequence;
|
|
9444
|
+
while (!signal?.aborted) {
|
|
9445
|
+
const events = await storage.readEvents(sessionId, {
|
|
9446
|
+
afterSequence: lastSequence,
|
|
9447
|
+
limit: 100
|
|
9448
|
+
});
|
|
9449
|
+
for (const stored of events) {
|
|
9450
|
+
if (signal?.aborted) return;
|
|
9451
|
+
lastSequence = stored.sequence;
|
|
9452
|
+
yield stored.payload;
|
|
9453
|
+
if (TERMINAL_EVENT_TYPES.has(stored.eventType)) {
|
|
9454
|
+
return;
|
|
9455
|
+
}
|
|
9456
|
+
}
|
|
9457
|
+
if (events.length === 0) {
|
|
9458
|
+
await new Promise((r) => {
|
|
9459
|
+
const timer = setTimeout(r, pollIntervalMs);
|
|
9460
|
+
if (signal) {
|
|
9461
|
+
signal.addEventListener("abort", () => {
|
|
9462
|
+
clearTimeout(timer);
|
|
9463
|
+
r();
|
|
9464
|
+
}, { once: true });
|
|
9465
|
+
}
|
|
9466
|
+
});
|
|
9467
|
+
}
|
|
9468
|
+
}
|
|
9469
|
+
}
|
|
9470
|
+
var TERMINAL_EVENT_TYPES;
|
|
9471
|
+
var init_stream_event_relay = __esm({
|
|
9472
|
+
"src/relay/stream-event-relay.ts"() {
|
|
9473
|
+
TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
9474
|
+
"session_end",
|
|
9475
|
+
"error",
|
|
9476
|
+
"session_stopped"
|
|
9477
|
+
]);
|
|
9478
|
+
}
|
|
9479
|
+
});
|
|
9480
|
+
|
|
9481
|
+
// src/relay/index.ts
|
|
9482
|
+
var init_relay = __esm({
|
|
9483
|
+
"src/relay/index.ts"() {
|
|
9484
|
+
init_stream_event_writer();
|
|
9485
|
+
init_stream_event_relay();
|
|
9486
|
+
}
|
|
9487
|
+
});
|
|
9106
9488
|
function broadcastToSession(sessionId, event) {
|
|
9107
9489
|
const subscribers = sessionSubscribers.get(sessionId);
|
|
9108
9490
|
if (subscribers && subscribers.size > 0) {
|
|
@@ -9235,6 +9617,8 @@ function createSessionsRouter(options) {
|
|
|
9235
9617
|
agentName: session.agentName,
|
|
9236
9618
|
startedAt: /* @__PURE__ */ new Date()
|
|
9237
9619
|
});
|
|
9620
|
+
const hasStreamEventStorage = !!agent.getStreamEventStorage();
|
|
9621
|
+
let sendSequenceCounter = 0;
|
|
9238
9622
|
return streaming.streamSSE(c, async (stream) => {
|
|
9239
9623
|
try {
|
|
9240
9624
|
for await (const event of activeSession.send(data.prompt, {
|
|
@@ -9246,7 +9630,9 @@ function createSessionsRouter(options) {
|
|
|
9246
9630
|
if (controller.signal.aborted) {
|
|
9247
9631
|
break;
|
|
9248
9632
|
}
|
|
9633
|
+
sendSequenceCounter++;
|
|
9249
9634
|
await stream.writeSSE({
|
|
9635
|
+
...hasStreamEventStorage ? { id: String(sendSequenceCounter) } : {},
|
|
9250
9636
|
event: event.type,
|
|
9251
9637
|
data: JSON.stringify(event)
|
|
9252
9638
|
});
|
|
@@ -9429,11 +9815,77 @@ function createSessionsRouter(options) {
|
|
|
9429
9815
|
}
|
|
9430
9816
|
});
|
|
9431
9817
|
});
|
|
9818
|
+
router.get("/:sessionId/stream", async (c) => {
|
|
9819
|
+
const sessionId = c.req.param("sessionId");
|
|
9820
|
+
const afterParam = c.req.query("after");
|
|
9821
|
+
const afterSequence = afterParam ? parseInt(afterParam, 10) : 0;
|
|
9822
|
+
const session = await sessionManager.getSession(sessionId);
|
|
9823
|
+
if (!session) {
|
|
9824
|
+
return c.json({ error: "Session not found" }, 404);
|
|
9825
|
+
}
|
|
9826
|
+
const agent = agents2.get(session.agentName);
|
|
9827
|
+
if (!agent) {
|
|
9828
|
+
return c.json({ error: `Agent not found: ${session.agentName}` }, 404);
|
|
9829
|
+
}
|
|
9830
|
+
const streamEventStorage = agent.getStreamEventStorage();
|
|
9831
|
+
if (!streamEventStorage) {
|
|
9832
|
+
return c.json({ error: "Stream event storage not configured" }, 501);
|
|
9833
|
+
}
|
|
9834
|
+
return streaming.streamSSE(c, async (stream) => {
|
|
9835
|
+
const abortController = new AbortController();
|
|
9836
|
+
c.req.raw.signal.addEventListener("abort", () => {
|
|
9837
|
+
abortController.abort();
|
|
9838
|
+
});
|
|
9839
|
+
const heartbeatInterval = setInterval(async () => {
|
|
9840
|
+
try {
|
|
9841
|
+
await stream.writeSSE({
|
|
9842
|
+
event: "heartbeat",
|
|
9843
|
+
data: JSON.stringify({ type: "heartbeat", timestamp: Date.now() })
|
|
9844
|
+
});
|
|
9845
|
+
} catch {
|
|
9846
|
+
clearInterval(heartbeatInterval);
|
|
9847
|
+
}
|
|
9848
|
+
}, 15e3);
|
|
9849
|
+
let sequenceCounter = afterSequence;
|
|
9850
|
+
try {
|
|
9851
|
+
const sessionEmitter = agent.getSessionEmitter(sessionId);
|
|
9852
|
+
const relay = streamEventRelay({
|
|
9853
|
+
storage: streamEventStorage,
|
|
9854
|
+
sessionId,
|
|
9855
|
+
afterSequence,
|
|
9856
|
+
emitter: sessionEmitter,
|
|
9857
|
+
signal: abortController.signal
|
|
9858
|
+
});
|
|
9859
|
+
for await (const event of relay) {
|
|
9860
|
+
if (abortController.signal.aborted) break;
|
|
9861
|
+
sequenceCounter++;
|
|
9862
|
+
await stream.writeSSE({
|
|
9863
|
+
id: String(sequenceCounter),
|
|
9864
|
+
event: event.type,
|
|
9865
|
+
data: JSON.stringify(event)
|
|
9866
|
+
});
|
|
9867
|
+
}
|
|
9868
|
+
} catch (error) {
|
|
9869
|
+
if (!abortController.signal.aborted) {
|
|
9870
|
+
await stream.writeSSE({
|
|
9871
|
+
event: "error",
|
|
9872
|
+
data: JSON.stringify({
|
|
9873
|
+
type: "error",
|
|
9874
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
9875
|
+
})
|
|
9876
|
+
});
|
|
9877
|
+
}
|
|
9878
|
+
} finally {
|
|
9879
|
+
clearInterval(heartbeatInterval);
|
|
9880
|
+
}
|
|
9881
|
+
});
|
|
9882
|
+
});
|
|
9432
9883
|
return router;
|
|
9433
9884
|
}
|
|
9434
9885
|
var createSessionSchema, fileAttachmentSchema, sendMessageSchema, updateEnvSchema, listSessionsSchema, activeStreams, sessionSubscribers;
|
|
9435
9886
|
var init_sessions = __esm({
|
|
9436
9887
|
"src/server/routes/sessions.ts"() {
|
|
9888
|
+
init_stream_event_relay();
|
|
9437
9889
|
createSessionSchema = zod.z.object({
|
|
9438
9890
|
agentName: zod.z.string(),
|
|
9439
9891
|
metadata: zod.z.record(zod.z.unknown()).optional(),
|
|
@@ -16154,9 +16606,11 @@ __export(schema_exports, {
|
|
|
16154
16606
|
sessionEventsRelations: () => sessionEventsRelations,
|
|
16155
16607
|
sessionStatusEnum: () => sessionStatusEnum,
|
|
16156
16608
|
sessions: () => sessions,
|
|
16157
|
-
sessionsRelations: () => sessionsRelations
|
|
16609
|
+
sessionsRelations: () => sessionsRelations,
|
|
16610
|
+
streamEvents: () => streamEvents,
|
|
16611
|
+
streamEventsRelations: () => streamEventsRelations
|
|
16158
16612
|
});
|
|
16159
|
-
var sessionStatusEnum, messageRoleEnum, agentStatusEnum, agentBackendEnum, queueItemStatusEnum, configDeploymentStatusEnum, configDeploymentTriggerEnum, eventCategoryEnum, sessions, messages, attachments, agents, queueItems, configDeployments, sessionEvents, sessionsRelations, sessionEventsRelations, messagesRelations, attachmentsRelations, configDeploymentsRelations, agentsRelations;
|
|
16613
|
+
var sessionStatusEnum, messageRoleEnum, agentStatusEnum, agentBackendEnum, queueItemStatusEnum, configDeploymentStatusEnum, configDeploymentTriggerEnum, eventCategoryEnum, sessions, messages, attachments, agents, queueItems, configDeployments, sessionEvents, streamEvents, sessionsRelations, sessionEventsRelations, messagesRelations, attachmentsRelations, configDeploymentsRelations, streamEventsRelations, agentsRelations;
|
|
16160
16614
|
var init_schema = __esm({
|
|
16161
16615
|
"src/storage-postgres/schema.ts"() {
|
|
16162
16616
|
sessionStatusEnum = pgCore.pgEnum("session_status", [
|
|
@@ -16397,6 +16851,21 @@ var init_schema = __esm({
|
|
|
16397
16851
|
pgCore.index("idx_session_events_category").on(table.sessionId, table.category)
|
|
16398
16852
|
]
|
|
16399
16853
|
);
|
|
16854
|
+
streamEvents = pgCore.pgTable(
|
|
16855
|
+
"stream_events",
|
|
16856
|
+
{
|
|
16857
|
+
id: pgCore.uuid("id").defaultRandom().primaryKey(),
|
|
16858
|
+
sessionId: pgCore.uuid("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
16859
|
+
sequence: pgCore.integer("sequence").notNull(),
|
|
16860
|
+
eventType: pgCore.text("event_type").notNull(),
|
|
16861
|
+
payload: pgCore.jsonb("payload").notNull().$type(),
|
|
16862
|
+
batchCount: pgCore.integer("batch_count").default(1).notNull(),
|
|
16863
|
+
createdAt: pgCore.timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
|
|
16864
|
+
},
|
|
16865
|
+
(table) => [
|
|
16866
|
+
pgCore.index("idx_stream_events_session_seq").on(table.sessionId, table.sequence)
|
|
16867
|
+
]
|
|
16868
|
+
);
|
|
16400
16869
|
sessionsRelations = drizzleOrm.relations(sessions, ({ one, many }) => ({
|
|
16401
16870
|
parentSession: one(sessions, {
|
|
16402
16871
|
fields: [sessions.parentSessionId],
|
|
@@ -16407,7 +16876,8 @@ var init_schema = __esm({
|
|
|
16407
16876
|
relationName: "parentSession"
|
|
16408
16877
|
}),
|
|
16409
16878
|
messages: many(messages),
|
|
16410
|
-
events: many(sessionEvents)
|
|
16879
|
+
events: many(sessionEvents),
|
|
16880
|
+
streamEvents: many(streamEvents)
|
|
16411
16881
|
}));
|
|
16412
16882
|
sessionEventsRelations = drizzleOrm.relations(sessionEvents, ({ one }) => ({
|
|
16413
16883
|
session: one(sessions, {
|
|
@@ -16434,6 +16904,12 @@ var init_schema = __esm({
|
|
|
16434
16904
|
references: [agents.id]
|
|
16435
16905
|
})
|
|
16436
16906
|
}));
|
|
16907
|
+
streamEventsRelations = drizzleOrm.relations(streamEvents, ({ one }) => ({
|
|
16908
|
+
session: one(sessions, {
|
|
16909
|
+
fields: [streamEvents.sessionId],
|
|
16910
|
+
references: [sessions.id]
|
|
16911
|
+
})
|
|
16912
|
+
}));
|
|
16437
16913
|
agentsRelations = drizzleOrm.relations(agents, ({ many }) => ({
|
|
16438
16914
|
configDeployments: many(configDeployments)
|
|
16439
16915
|
}));
|
|
@@ -16621,6 +17097,20 @@ var init_storage2 = __esm({
|
|
|
16621
17097
|
`);
|
|
16622
17098
|
await this.db.execute(drizzleOrm.sql`
|
|
16623
17099
|
ALTER TABLE agents ADD COLUMN IF NOT EXISTS backend agent_backend DEFAULT 'claude' NOT NULL;
|
|
17100
|
+
`);
|
|
17101
|
+
await this.db.execute(drizzleOrm.sql`
|
|
17102
|
+
CREATE TABLE IF NOT EXISTS stream_events (
|
|
17103
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
17104
|
+
session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
17105
|
+
sequence INTEGER NOT NULL,
|
|
17106
|
+
event_type TEXT NOT NULL,
|
|
17107
|
+
payload JSONB NOT NULL,
|
|
17108
|
+
batch_count INTEGER NOT NULL DEFAULT 1,
|
|
17109
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
17110
|
+
);
|
|
17111
|
+
`);
|
|
17112
|
+
await this.db.execute(drizzleOrm.sql`
|
|
17113
|
+
CREATE INDEX IF NOT EXISTS idx_stream_events_session_seq ON stream_events(session_id, sequence);
|
|
16624
17114
|
`);
|
|
16625
17115
|
}
|
|
16626
17116
|
/**
|
|
@@ -16813,6 +17303,20 @@ var init_storage2 = __esm({
|
|
|
16813
17303
|
`);
|
|
16814
17304
|
await this.db.execute(drizzleOrm.sql`
|
|
16815
17305
|
CREATE INDEX IF NOT EXISTS idx_queue_items_pending ON queue_items(agent_name, session_id, status, priority, created_at);
|
|
17306
|
+
`);
|
|
17307
|
+
await this.db.execute(drizzleOrm.sql`
|
|
17308
|
+
CREATE TABLE IF NOT EXISTS stream_events (
|
|
17309
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
17310
|
+
session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
17311
|
+
sequence INTEGER NOT NULL,
|
|
17312
|
+
event_type TEXT NOT NULL,
|
|
17313
|
+
payload JSONB NOT NULL,
|
|
17314
|
+
batch_count INTEGER NOT NULL DEFAULT 1,
|
|
17315
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
17316
|
+
);
|
|
17317
|
+
`);
|
|
17318
|
+
await this.db.execute(drizzleOrm.sql`
|
|
17319
|
+
CREATE INDEX IF NOT EXISTS idx_stream_events_session_seq ON stream_events(session_id, sequence);
|
|
16816
17320
|
`);
|
|
16817
17321
|
await this.db.execute(drizzleOrm.sql`
|
|
16818
17322
|
DO $$ BEGIN
|
|
@@ -17203,6 +17707,56 @@ var init_storage2 = __esm({
|
|
|
17203
17707
|
return rows.map((row) => this.rowToAgent(row));
|
|
17204
17708
|
}
|
|
17205
17709
|
// ============================================================================
|
|
17710
|
+
// Stream Events (SSE Relay)
|
|
17711
|
+
// ============================================================================
|
|
17712
|
+
async appendEvents(sessionId, events) {
|
|
17713
|
+
if (events.length === 0) return [];
|
|
17714
|
+
const [maxSeqResult] = await this.db.select({ maxSeq: drizzleOrm.sql`COALESCE(MAX(${streamEvents.sequence}), 0)` }).from(streamEvents).where(drizzleOrm.eq(streamEvents.sessionId, sessionId));
|
|
17715
|
+
let nextSeq = (maxSeqResult?.maxSeq ?? 0) + 1;
|
|
17716
|
+
const rows = await this.db.insert(streamEvents).values(
|
|
17717
|
+
events.map((event) => ({
|
|
17718
|
+
sessionId,
|
|
17719
|
+
sequence: nextSeq++,
|
|
17720
|
+
eventType: event.eventType,
|
|
17721
|
+
payload: event.payload,
|
|
17722
|
+
batchCount: event.batchCount ?? 1
|
|
17723
|
+
}))
|
|
17724
|
+
).returning();
|
|
17725
|
+
return rows.map((row) => this.rowToStoredStreamEvent(row));
|
|
17726
|
+
}
|
|
17727
|
+
async readEvents(sessionId, options) {
|
|
17728
|
+
const conditions = [drizzleOrm.eq(streamEvents.sessionId, sessionId)];
|
|
17729
|
+
if (options?.afterSequence !== void 0) {
|
|
17730
|
+
conditions.push(drizzleOrm.sql`${streamEvents.sequence} > ${options.afterSequence}`);
|
|
17731
|
+
}
|
|
17732
|
+
const limit = options?.limit ?? 1e3;
|
|
17733
|
+
const rows = await this.db.select().from(streamEvents).where(drizzleOrm.and(...conditions)).orderBy(drizzleOrm.asc(streamEvents.sequence)).limit(limit);
|
|
17734
|
+
return rows.map((row) => this.rowToStoredStreamEvent(row));
|
|
17735
|
+
}
|
|
17736
|
+
async getLatestSequence(sessionId) {
|
|
17737
|
+
const [result] = await this.db.select({ maxSeq: drizzleOrm.sql`COALESCE(MAX(${streamEvents.sequence}), 0)` }).from(streamEvents).where(drizzleOrm.eq(streamEvents.sessionId, sessionId));
|
|
17738
|
+
return result?.maxSeq ?? 0;
|
|
17739
|
+
}
|
|
17740
|
+
async deleteSessionEvents(sessionId) {
|
|
17741
|
+
await this.db.delete(streamEvents).where(drizzleOrm.eq(streamEvents.sessionId, sessionId));
|
|
17742
|
+
}
|
|
17743
|
+
async cleanupOldEvents(maxAgeMs) {
|
|
17744
|
+
const cutoff = new Date(Date.now() - maxAgeMs);
|
|
17745
|
+
const result = await this.db.delete(streamEvents).where(drizzleOrm.sql`${streamEvents.createdAt} < ${cutoff.toISOString()}`);
|
|
17746
|
+
return result.rowCount ?? 0;
|
|
17747
|
+
}
|
|
17748
|
+
rowToStoredStreamEvent(row) {
|
|
17749
|
+
return {
|
|
17750
|
+
id: row.id,
|
|
17751
|
+
sessionId: row.sessionId,
|
|
17752
|
+
sequence: row.sequence,
|
|
17753
|
+
eventType: row.eventType,
|
|
17754
|
+
payload: row.payload,
|
|
17755
|
+
batchCount: row.batchCount,
|
|
17756
|
+
createdAt: row.createdAt
|
|
17757
|
+
};
|
|
17758
|
+
}
|
|
17759
|
+
// ============================================================================
|
|
17206
17760
|
// Database Access (for extensions)
|
|
17207
17761
|
// ============================================================================
|
|
17208
17762
|
/**
|
|
@@ -18038,6 +18592,72 @@ var init_storage3 = __esm({
|
|
|
18038
18592
|
}
|
|
18039
18593
|
return data ? data.sequence_number + 1 : 1;
|
|
18040
18594
|
}
|
|
18595
|
+
// ============================================================================
|
|
18596
|
+
// Stream Events (SSE Relay)
|
|
18597
|
+
// ============================================================================
|
|
18598
|
+
async appendEvents(sessionId, events) {
|
|
18599
|
+
if (events.length === 0) return [];
|
|
18600
|
+
const { data: maxSeqData } = await this.client.from("stream_events").select("sequence").eq("session_id", sessionId).order("sequence", { ascending: false }).limit(1).single();
|
|
18601
|
+
let nextSeq = maxSeqData ? maxSeqData.sequence + 1 : 1;
|
|
18602
|
+
const { data, error } = await this.client.from("stream_events").insert(
|
|
18603
|
+
events.map((event) => ({
|
|
18604
|
+
session_id: sessionId,
|
|
18605
|
+
sequence: nextSeq++,
|
|
18606
|
+
event_type: event.eventType,
|
|
18607
|
+
payload: event.payload,
|
|
18608
|
+
batch_count: event.batchCount ?? 1
|
|
18609
|
+
}))
|
|
18610
|
+
).select();
|
|
18611
|
+
if (error) {
|
|
18612
|
+
throw new Error(`Failed to append stream events: ${error.message}`);
|
|
18613
|
+
}
|
|
18614
|
+
return data.map((row) => this.rowToStoredStreamEvent(row));
|
|
18615
|
+
}
|
|
18616
|
+
async readEvents(sessionId, options) {
|
|
18617
|
+
let query = this.client.from("stream_events").select("*").eq("session_id", sessionId);
|
|
18618
|
+
if (options?.afterSequence !== void 0) {
|
|
18619
|
+
query = query.gt("sequence", options.afterSequence);
|
|
18620
|
+
}
|
|
18621
|
+
const limit = options?.limit ?? 1e3;
|
|
18622
|
+
query = query.order("sequence", { ascending: true }).limit(limit);
|
|
18623
|
+
const { data, error } = await query;
|
|
18624
|
+
if (error) {
|
|
18625
|
+
throw new Error(`Failed to read stream events: ${error.message}`);
|
|
18626
|
+
}
|
|
18627
|
+
return data.map((row) => this.rowToStoredStreamEvent(row));
|
|
18628
|
+
}
|
|
18629
|
+
async getLatestSequence(sessionId) {
|
|
18630
|
+
const { data, error } = await this.client.from("stream_events").select("sequence").eq("session_id", sessionId).order("sequence", { ascending: false }).limit(1).single();
|
|
18631
|
+
if (error && error.code !== "PGRST116") {
|
|
18632
|
+
throw new Error(`Failed to get latest sequence: ${error.message}`);
|
|
18633
|
+
}
|
|
18634
|
+
return data ? data.sequence : 0;
|
|
18635
|
+
}
|
|
18636
|
+
async deleteStreamEvents(sessionId) {
|
|
18637
|
+
const { error } = await this.client.from("stream_events").delete().eq("session_id", sessionId);
|
|
18638
|
+
if (error) {
|
|
18639
|
+
throw new Error(`Failed to delete stream events: ${error.message}`);
|
|
18640
|
+
}
|
|
18641
|
+
}
|
|
18642
|
+
async cleanupOldEvents(maxAgeMs) {
|
|
18643
|
+
const cutoff = new Date(Date.now() - maxAgeMs).toISOString();
|
|
18644
|
+
const { data, error } = await this.client.from("stream_events").delete().lt("created_at", cutoff).select("id");
|
|
18645
|
+
if (error) {
|
|
18646
|
+
throw new Error(`Failed to cleanup old stream events: ${error.message}`);
|
|
18647
|
+
}
|
|
18648
|
+
return data?.length ?? 0;
|
|
18649
|
+
}
|
|
18650
|
+
rowToStoredStreamEvent(row) {
|
|
18651
|
+
return {
|
|
18652
|
+
id: row.id,
|
|
18653
|
+
sessionId: row.session_id,
|
|
18654
|
+
sequence: row.sequence,
|
|
18655
|
+
eventType: row.event_type,
|
|
18656
|
+
payload: row.payload,
|
|
18657
|
+
batchCount: row.batch_count,
|
|
18658
|
+
createdAt: new Date(row.created_at)
|
|
18659
|
+
};
|
|
18660
|
+
}
|
|
18041
18661
|
};
|
|
18042
18662
|
}
|
|
18043
18663
|
});
|
|
@@ -18870,6 +19490,7 @@ var init_src = __esm({
|
|
|
18870
19490
|
init_credentials();
|
|
18871
19491
|
init_skills();
|
|
18872
19492
|
init_utils();
|
|
19493
|
+
init_relay();
|
|
18873
19494
|
init_server();
|
|
18874
19495
|
init_server2();
|
|
18875
19496
|
init_schemas();
|
|
@@ -19046,6 +19667,7 @@ exports.shouldUseSandbox = shouldUseSandbox;
|
|
|
19046
19667
|
exports.shutdownSandboxPool = shutdownSandboxPool;
|
|
19047
19668
|
exports.sseMcpWithAuth = sseMcpWithAuth;
|
|
19048
19669
|
exports.startServer = startServer;
|
|
19670
|
+
exports.streamEventRelay = streamEventRelay;
|
|
19049
19671
|
exports.updateToolCallWithResult = updateToolCallWithResult;
|
|
19050
19672
|
exports.writeFileToSandbox = writeFileToSandbox;
|
|
19051
19673
|
//# sourceMappingURL=index.cjs.map
|