@ash-cloud/ash-ai 0.1.15 → 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 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
- yield { type: "sandbox_log", entry };
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
- yield {
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
- yield {
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
- yield {
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
- yield {
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
- yield {
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
- yield {
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
- yield {
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
- yield {
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
- yield {
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
- yield {
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
- yield {
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
 
@@ -4233,6 +4447,50 @@ VERCEL_EOF`);
4233
4447
  }
4234
4448
  });
4235
4449
 
4450
+ // src/runtime/providers/firecracker/types.ts
4451
+ var init_types3 = __esm({
4452
+ "src/runtime/providers/firecracker/types.ts"() {
4453
+ }
4454
+ });
4455
+
4456
+ // src/runtime/providers/firecracker/image-manager.ts
4457
+ var init_image_manager = __esm({
4458
+ "src/runtime/providers/firecracker/image-manager.ts"() {
4459
+ }
4460
+ });
4461
+
4462
+ // src/runtime/providers/firecracker/network-manager.ts
4463
+ var init_network_manager = __esm({
4464
+ "src/runtime/providers/firecracker/network-manager.ts"() {
4465
+ }
4466
+ });
4467
+
4468
+ // src/runtime/providers/firecracker/ssh-executor.ts
4469
+ var init_ssh_executor = __esm({
4470
+ "src/runtime/providers/firecracker/ssh-executor.ts"() {
4471
+ }
4472
+ });
4473
+
4474
+ // src/runtime/providers/firecracker/vm-manager.ts
4475
+ var init_vm_manager = __esm({
4476
+ "src/runtime/providers/firecracker/vm-manager.ts"() {
4477
+ init_types3();
4478
+ }
4479
+ });
4480
+
4481
+ // src/runtime/providers/firecracker.ts
4482
+ var init_firecracker = __esm({
4483
+ "src/runtime/providers/firecracker.ts"() {
4484
+ init_types2();
4485
+ init_types3();
4486
+ init_image_manager();
4487
+ init_network_manager();
4488
+ init_ssh_executor();
4489
+ init_vm_manager();
4490
+ init_types3();
4491
+ }
4492
+ });
4493
+
4236
4494
  // src/runtime/providers/index.ts
4237
4495
  var init_providers = __esm({
4238
4496
  "src/runtime/providers/index.ts"() {
@@ -4244,6 +4502,7 @@ var init_providers = __esm({
4244
4502
  init_fly();
4245
4503
  init_daytona();
4246
4504
  init_vercel();
4505
+ init_firecracker();
4247
4506
  init_local();
4248
4507
  init_modal();
4249
4508
  init_e2b();
@@ -4251,6 +4510,7 @@ var init_providers = __esm({
4251
4510
  init_fly();
4252
4511
  init_daytona();
4253
4512
  init_vercel();
4513
+ init_firecracker();
4254
4514
  }
4255
4515
  });
4256
4516
 
@@ -5136,10 +5396,11 @@ async function getOrCreateSandbox(options) {
5136
5396
  startupScriptRan: true,
5137
5397
  // Assume ran for reconnected sandboxes
5138
5398
  startupScriptHash: void 0,
5399
+ // Unknown — caller should not re-run based on hash mismatch alone
5139
5400
  isNew: false,
5140
5401
  configFileUrl: void 0,
5141
- configInstalledAt: void 0
5142
- // We don't know when config was installed on reconnected sandboxes
5402
+ configInstalledAt: now2
5403
+ // Assume config was installed — prevents unnecessary re-install on reconnection
5143
5404
  };
5144
5405
  } else {
5145
5406
  console.log("[SANDBOX] Reconnected sandbox failed health check, will create new");
@@ -5525,7 +5786,8 @@ async function* executeInSandbox(prompt, apiKey, options) {
5525
5786
  markSdkInstalled(sessionId);
5526
5787
  }
5527
5788
  const currentScriptHash = hashStartupScript(options.startupScript);
5528
- const needsStartupScript = options.startupScript && (!startupScriptRan || currentScriptHash !== cachedScriptHash);
5789
+ const scriptHashChanged = cachedScriptHash !== void 0 && currentScriptHash !== cachedScriptHash;
5790
+ const needsStartupScript = options.startupScript && (!startupScriptRan || scriptHashChanged);
5529
5791
  if (needsStartupScript) {
5530
5792
  console.log("[SANDBOX] Running startup script...");
5531
5793
  const startupStartTime = Date.now();
@@ -5659,11 +5921,18 @@ const { query } = require('@anthropic-ai/claude-agent-sdk');
5659
5921
  const prompt = ${JSON.stringify(prompt)};
5660
5922
  const options = ${JSON.stringify(sdkOptions)};
5661
5923
 
5924
+ let queryCompleted = false;
5925
+
5662
5926
  async function run() {
5663
5927
  try {
5664
5928
  for await (const message of query({ prompt, options })) {
5665
5929
  console.log(JSON.stringify(message));
5666
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);
5667
5936
  } catch (error) {
5668
5937
  const errorInfo = {
5669
5938
  type: 'error',
@@ -5679,6 +5948,11 @@ async function run() {
5679
5948
  }
5680
5949
 
5681
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
+ }
5682
5956
  const errorInfo = {
5683
5957
  type: 'error',
5684
5958
  error: reason instanceof Error ? reason.message : String(reason),
@@ -5689,6 +5963,11 @@ process.on('unhandledRejection', (reason) => {
5689
5963
  });
5690
5964
 
5691
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
+ }
5692
5971
  const errorInfo = {
5693
5972
  type: 'error',
5694
5973
  error: error.message || 'Unknown error',
@@ -5927,7 +6206,18 @@ var init_sandbox_file_watcher = __esm({
5927
6206
  this.watchPath = options.watchPath;
5928
6207
  this.debounceMs = options.debounceMs ?? 300;
5929
6208
  this.patterns = options.patterns ?? ["**/*"];
5930
- this.ignored = options.ignored ?? ["**/node_modules/**", "**/.git/**", "**/*.log"];
6209
+ this.ignored = options.ignored ?? [
6210
+ "**/node_modules/**",
6211
+ "**/.git/**",
6212
+ "**/*.log",
6213
+ // Temporary files from atomic saves and editors
6214
+ "**/*.tmp",
6215
+ "**/*.tmp.*",
6216
+ "**/*~",
6217
+ "**/*.swp",
6218
+ "**/*.swo",
6219
+ "**/.DS_Store"
6220
+ ];
5931
6221
  this.emitInitialEvents = options.emitInitialEvents ?? false;
5932
6222
  this.followSymlinks = options.followSymlinks ?? false;
5933
6223
  this.usePolling = options.usePolling ?? false;
@@ -6168,7 +6458,7 @@ const fs = require('fs');
6168
6458
  const path = require('path');
6169
6459
 
6170
6460
  const watchPath = process.argv[2] || '.';
6171
- const ignored = new Set(['node_modules', '.git', '.cache', '__pycache__']);
6461
+ const ignoredDirs = new Set(['node_modules', '.git', '.cache', '__pycache__']);
6172
6462
 
6173
6463
  // Track all watchers for cleanup
6174
6464
  const watchers = new Map();
@@ -6187,7 +6477,23 @@ function emit(type, filePath) {
6187
6477
  }
6188
6478
 
6189
6479
  function shouldIgnore(name) {
6190
- return ignored.has(name) || name.startsWith('.');
6480
+ // Ignore hidden files/dirs (starting with .)
6481
+ if (name.startsWith('.')) return true;
6482
+
6483
+ // Ignore known directories
6484
+ if (ignoredDirs.has(name)) return true;
6485
+
6486
+ // Ignore temporary files from atomic saves and editors:
6487
+ // - filename.tmp (ends with .tmp)
6488
+ // - filename.tmp.123456789 (has .tmp. in middle - atomic write pattern)
6489
+ // - filename~ (editor backup files)
6490
+ // - filename.swp / filename.swo (Vim swap files)
6491
+ if (name.endsWith('.tmp')) return true;
6492
+ if (name.includes('.tmp.')) return true;
6493
+ if (name.endsWith('~')) return true;
6494
+ if (name.endsWith('.swp') || name.endsWith('.swo')) return true;
6495
+
6496
+ return false;
6191
6497
  }
6192
6498
 
6193
6499
  function watchDir(dir) {
@@ -6734,15 +7040,35 @@ var init_sandbox_file_sync = __esm({
6734
7040
  }
6735
7041
  }
6736
7042
  /**
6737
- * 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.
6738
7046
  */
6739
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
+ };
6740
7066
  const payload = {
6741
7067
  event: "file_sync",
6742
7068
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6743
7069
  sessionId,
6744
7070
  metadata: this.webhookConfig?.metadata,
6745
- fileSyncEvent: event
7071
+ fileSyncEvent: webhookEvent
6746
7072
  };
6747
7073
  await this.sendWebhook(payload, sessionId);
6748
7074
  }
@@ -7871,7 +8197,7 @@ var init_credentials = __esm({
7871
8197
  }
7872
8198
  });
7873
8199
  exports.gitHubSkillSourceSchema = void 0; exports.localSkillSourceSchema = void 0; exports.skillSourceSchema = void 0; exports.skillConfigSchema = void 0; exports.fileEntrySchema = void 0;
7874
- var init_types3 = __esm({
8200
+ var init_types4 = __esm({
7875
8201
  "src/skills/types.ts"() {
7876
8202
  exports.gitHubSkillSourceSchema = zod.z.object({
7877
8203
  type: zod.z.literal("github"),
@@ -8415,7 +8741,7 @@ var init_manager2 = __esm({
8415
8741
  // src/skills/index.ts
8416
8742
  var init_skills = __esm({
8417
8743
  "src/skills/index.ts"() {
8418
- init_types3();
8744
+ init_types4();
8419
8745
  init_local_provider();
8420
8746
  init_github_provider();
8421
8747
  init_manager2();
@@ -9030,6 +9356,135 @@ var init_utils = __esm({
9030
9356
  init_sandbox_logger();
9031
9357
  }
9032
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
+ });
9033
9488
  function broadcastToSession(sessionId, event) {
9034
9489
  const subscribers = sessionSubscribers.get(sessionId);
9035
9490
  if (subscribers && subscribers.size > 0) {
@@ -9162,6 +9617,8 @@ function createSessionsRouter(options) {
9162
9617
  agentName: session.agentName,
9163
9618
  startedAt: /* @__PURE__ */ new Date()
9164
9619
  });
9620
+ const hasStreamEventStorage = !!agent.getStreamEventStorage();
9621
+ let sendSequenceCounter = 0;
9165
9622
  return streaming.streamSSE(c, async (stream) => {
9166
9623
  try {
9167
9624
  for await (const event of activeSession.send(data.prompt, {
@@ -9173,7 +9630,9 @@ function createSessionsRouter(options) {
9173
9630
  if (controller.signal.aborted) {
9174
9631
  break;
9175
9632
  }
9633
+ sendSequenceCounter++;
9176
9634
  await stream.writeSSE({
9635
+ ...hasStreamEventStorage ? { id: String(sendSequenceCounter) } : {},
9177
9636
  event: event.type,
9178
9637
  data: JSON.stringify(event)
9179
9638
  });
@@ -9356,11 +9815,77 @@ function createSessionsRouter(options) {
9356
9815
  }
9357
9816
  });
9358
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
+ });
9359
9883
  return router;
9360
9884
  }
9361
9885
  var createSessionSchema, fileAttachmentSchema, sendMessageSchema, updateEnvSchema, listSessionsSchema, activeStreams, sessionSubscribers;
9362
9886
  var init_sessions = __esm({
9363
9887
  "src/server/routes/sessions.ts"() {
9888
+ init_stream_event_relay();
9364
9889
  createSessionSchema = zod.z.object({
9365
9890
  agentName: zod.z.string(),
9366
9891
  metadata: zod.z.record(zod.z.unknown()).optional(),
@@ -16081,9 +16606,11 @@ __export(schema_exports, {
16081
16606
  sessionEventsRelations: () => sessionEventsRelations,
16082
16607
  sessionStatusEnum: () => sessionStatusEnum,
16083
16608
  sessions: () => sessions,
16084
- sessionsRelations: () => sessionsRelations
16609
+ sessionsRelations: () => sessionsRelations,
16610
+ streamEvents: () => streamEvents,
16611
+ streamEventsRelations: () => streamEventsRelations
16085
16612
  });
16086
- 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;
16087
16614
  var init_schema = __esm({
16088
16615
  "src/storage-postgres/schema.ts"() {
16089
16616
  sessionStatusEnum = pgCore.pgEnum("session_status", [
@@ -16324,6 +16851,21 @@ var init_schema = __esm({
16324
16851
  pgCore.index("idx_session_events_category").on(table.sessionId, table.category)
16325
16852
  ]
16326
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
+ );
16327
16869
  sessionsRelations = drizzleOrm.relations(sessions, ({ one, many }) => ({
16328
16870
  parentSession: one(sessions, {
16329
16871
  fields: [sessions.parentSessionId],
@@ -16334,7 +16876,8 @@ var init_schema = __esm({
16334
16876
  relationName: "parentSession"
16335
16877
  }),
16336
16878
  messages: many(messages),
16337
- events: many(sessionEvents)
16879
+ events: many(sessionEvents),
16880
+ streamEvents: many(streamEvents)
16338
16881
  }));
16339
16882
  sessionEventsRelations = drizzleOrm.relations(sessionEvents, ({ one }) => ({
16340
16883
  session: one(sessions, {
@@ -16361,6 +16904,12 @@ var init_schema = __esm({
16361
16904
  references: [agents.id]
16362
16905
  })
16363
16906
  }));
16907
+ streamEventsRelations = drizzleOrm.relations(streamEvents, ({ one }) => ({
16908
+ session: one(sessions, {
16909
+ fields: [streamEvents.sessionId],
16910
+ references: [sessions.id]
16911
+ })
16912
+ }));
16364
16913
  agentsRelations = drizzleOrm.relations(agents, ({ many }) => ({
16365
16914
  configDeployments: many(configDeployments)
16366
16915
  }));
@@ -16548,6 +17097,20 @@ var init_storage2 = __esm({
16548
17097
  `);
16549
17098
  await this.db.execute(drizzleOrm.sql`
16550
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);
16551
17114
  `);
16552
17115
  }
16553
17116
  /**
@@ -16740,6 +17303,20 @@ var init_storage2 = __esm({
16740
17303
  `);
16741
17304
  await this.db.execute(drizzleOrm.sql`
16742
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);
16743
17320
  `);
16744
17321
  await this.db.execute(drizzleOrm.sql`
16745
17322
  DO $$ BEGIN
@@ -17130,6 +17707,56 @@ var init_storage2 = __esm({
17130
17707
  return rows.map((row) => this.rowToAgent(row));
17131
17708
  }
17132
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
+ // ============================================================================
17133
17760
  // Database Access (for extensions)
17134
17761
  // ============================================================================
17135
17762
  /**
@@ -17935,7 +18562,7 @@ var init_storage3 = __esm({
17935
18562
  }
17936
18563
  if (options.filePath) {
17937
18564
  query = query.or(
17938
- `event_data->>filePath.eq.${options.filePath},and(category.eq.tool,tool_name.in.(Read,Write,Edit),event_data->input->>file_path.eq.${options.filePath})`
18565
+ `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})`
17939
18566
  );
17940
18567
  }
17941
18568
  const ascending = options.order !== "desc";
@@ -17965,6 +18592,72 @@ var init_storage3 = __esm({
17965
18592
  }
17966
18593
  return data ? data.sequence_number + 1 : 1;
17967
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
+ }
17968
18661
  };
17969
18662
  }
17970
18663
  });
@@ -18797,6 +19490,7 @@ var init_src = __esm({
18797
19490
  init_credentials();
18798
19491
  init_skills();
18799
19492
  init_utils();
19493
+ init_relay();
18800
19494
  init_server();
18801
19495
  init_server2();
18802
19496
  init_schemas();
@@ -18973,6 +19667,7 @@ exports.shouldUseSandbox = shouldUseSandbox;
18973
19667
  exports.shutdownSandboxPool = shutdownSandboxPool;
18974
19668
  exports.sseMcpWithAuth = sseMcpWithAuth;
18975
19669
  exports.startServer = startServer;
19670
+ exports.streamEventRelay = streamEventRelay;
18976
19671
  exports.updateToolCallWithResult = updateToolCallWithResult;
18977
19672
  exports.writeFileToSandbox = writeFileToSandbox;
18978
19673
  //# sourceMappingURL=index.cjs.map