@ash-cloud/ash-ai 0.1.17 → 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 CHANGED
@@ -108,7 +108,7 @@ var init_normalized = __esm({
108
108
  "src/types/normalized.ts"() {
109
109
  }
110
110
  });
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;
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;
112
112
  var init_types = __esm({
113
113
  "src/types/index.ts"() {
114
114
  init_normalized();
@@ -233,6 +233,14 @@ var init_types = __esm({
233
233
  WEBHOOK: "webhook"
234
234
  // webhook_delivery, webhook_failure (outbound webhook events)
235
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
+ };
236
244
  }
237
245
  });
238
246
 
@@ -2595,6 +2603,11 @@ var init_memory = __esm({
2595
2603
  if (options.status) {
2596
2604
  sessions2 = sessions2.filter((s) => s.status === options.status);
2597
2605
  }
2606
+ if (options.resourceId) {
2607
+ sessions2 = sessions2.filter(
2608
+ (s) => s.metadata && s.metadata["resourceId"] === options.resourceId
2609
+ );
2610
+ }
2598
2611
  const orderBy = options.orderBy ?? "createdAt";
2599
2612
  const order = options.order ?? "desc";
2600
2613
  sessions2.sort((a, b) => {
@@ -4526,13 +4539,13 @@ function getSandboxPool() {
4526
4539
  return globalPool;
4527
4540
  }
4528
4541
  function isPoolEnabled() {
4529
- if (!process.env.VERCEL) {
4530
- return false;
4531
- }
4532
4542
  if (process.env.SANDBOX_POOL_ENABLED === "false") {
4533
4543
  return false;
4534
4544
  }
4535
- return true;
4545
+ if (process.env.SANDBOX_POOL_ENABLED === "true") {
4546
+ return true;
4547
+ }
4548
+ return !!process.env.VERCEL;
4536
4549
  }
4537
4550
  async function ensureSandboxPoolInitialized() {
4538
4551
  if (globalPool && globalPool.isRunning()) {
@@ -4617,7 +4630,7 @@ node -e "require('@anthropic-ai/claude-agent-sdk'); console.log('[warmup] SDK ve
4617
4630
 
4618
4631
  echo "[warmup] Warmup complete!"
4619
4632
  `;
4620
- exports.SandboxPool = class {
4633
+ exports.SandboxPool = class _SandboxPool {
4621
4634
  config;
4622
4635
  pool = /* @__PURE__ */ new Map();
4623
4636
  warmingInProgress = /* @__PURE__ */ new Set();
@@ -4626,6 +4639,14 @@ echo "[warmup] Warmup complete!"
4626
4639
  lastMaintenanceAt = null;
4627
4640
  metricsCallback;
4628
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;
4629
4650
  constructor(config = {}) {
4630
4651
  this.config = {
4631
4652
  minPoolSize: config.minPoolSize ?? parseInt(process.env.SANDBOX_POOL_MIN_SIZE ?? "2"),
@@ -4801,6 +4822,8 @@ echo "[warmup] Warmup complete!"
4801
4822
  warming: this.warmingInProgress.size,
4802
4823
  running: this.running,
4803
4824
  lastMaintenanceAt: this.lastMaintenanceAt,
4825
+ consecutiveFailures: this.consecutiveFailures,
4826
+ warmupSuspended: this.consecutiveFailures >= _SandboxPool.MAX_CONSECUTIVE_FAILURES,
4804
4827
  config: {
4805
4828
  minPoolSize: this.config.minPoolSize,
4806
4829
  maxPoolSize: this.config.maxPoolSize,
@@ -4826,6 +4849,7 @@ echo "[warmup] Warmup complete!"
4826
4849
  this.warmingInProgress.add(warmupId);
4827
4850
  this.emitMetric("warmup_started", { warmupId });
4828
4851
  const startTime = Date.now();
4852
+ this.lastWarmupAttemptAt = startTime;
4829
4853
  const useTarball = !!this.config.baseTarballUrl;
4830
4854
  try {
4831
4855
  const { Sandbox } = await import('@vercel/sandbox');
@@ -4926,6 +4950,7 @@ echo "[warmup] Warmup complete!"
4926
4950
  lastHeartbeat: now
4927
4951
  };
4928
4952
  console.log(`[POOL] Warmup completed for ${sandbox.sandboxId} (took ${warmupTime}ms)${useTarball ? " [tarball]" : ""}`);
4953
+ this.consecutiveFailures = 0;
4929
4954
  this.emitMetric("warmup_completed", {
4930
4955
  sandboxId: pooled.sandboxId,
4931
4956
  warmupTimeMs: warmupTime,
@@ -4935,10 +4960,20 @@ echo "[warmup] Warmup complete!"
4935
4960
  return pooled;
4936
4961
  } catch (error) {
4937
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
+ }
4938
4972
  this.emitMetric("warmup_failed", {
4939
- error: error instanceof Error ? error.message : "Unknown",
4973
+ error: errorMessage,
4940
4974
  warmupTimeMs: warmupTime,
4941
- usedTarball: useTarball
4975
+ usedTarball: useTarball,
4976
+ consecutiveFailures: this.consecutiveFailures
4942
4977
  });
4943
4978
  throw error;
4944
4979
  } finally {
@@ -5046,9 +5081,30 @@ echo "[warmup] Warmup complete!"
5046
5081
  });
5047
5082
  }
5048
5083
  /**
5049
- * 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.
5050
5087
  */
5051
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
+ }
5052
5108
  const eligibleCount = this.getEligibleCount();
5053
5109
  const warmingCount = this.warmingInProgress.size;
5054
5110
  const totalProjected = eligibleCount + warmingCount;
@@ -6154,6 +6210,85 @@ SCRIPT_EOF`]
6154
6210
  }
6155
6211
  }
6156
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
+ }
6157
6292
  var sandboxCache, DEFAULT_IDLE_TTL, CLEANUP_INTERVAL, HEARTBEAT_INTERVAL, cleanupIntervalId, heartbeatIntervalId, GLOBAL_HEARTBEAT_KEY, heartbeatListeners;
6158
6293
  var init_vercel_sandbox_executor = __esm({
6159
6294
  "src/runtime/vercel-sandbox-executor.ts"() {
@@ -6808,6 +6943,31 @@ WATCHER_EOF`;
6808
6943
  globalInSandboxManager = null;
6809
6944
  }
6810
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
+ }
6811
6971
  function extractErrorMessage(error) {
6812
6972
  if (error === null || error === void 0) {
6813
6973
  return "Unknown error (null/undefined)";
@@ -6850,12 +7010,18 @@ function extractErrorMessage(error) {
6850
7010
  function createSandboxFileSync(options) {
6851
7011
  return new exports.SandboxFileSync(options);
6852
7012
  }
6853
- exports.SandboxFileSync = void 0;
7013
+ var DEFAULT_WEBHOOK_IGNORE; exports.SandboxFileSync = void 0;
6854
7014
  var init_sandbox_file_sync = __esm({
6855
7015
  "src/runtime/sandbox-file-sync.ts"() {
6856
7016
  init_sandbox_file_watcher();
6857
7017
  init_in_sandbox_watcher();
6858
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
+ ];
6859
7025
  exports.SandboxFileSync = class {
6860
7026
  fileStore;
6861
7027
  sandboxBasePath;
@@ -6870,6 +7036,10 @@ var init_sandbox_file_sync = __esm({
6870
7036
  fileChangeSubscribers = /* @__PURE__ */ new Set();
6871
7037
  // Sequence number cache per session (for event storage)
6872
7038
  sequenceNumbers = /* @__PURE__ */ new Map();
7039
+ // Webhook batching state
7040
+ batchTimer = null;
7041
+ batchQueue = /* @__PURE__ */ new Map();
7042
+ // keyed by file path for dedup
6873
7043
  constructor(options) {
6874
7044
  this.fileStore = options.fileStore;
6875
7045
  this.sandboxBasePath = options.sandboxBasePath ?? ".claude/files";
@@ -6904,6 +7074,72 @@ var init_sandbox_file_sync = __esm({
6904
7074
  */
6905
7075
  removeWebhook() {
6906
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);
6907
7143
  }
6908
7144
  /**
6909
7145
  * Extract hostname from URL for display (security - don't expose full URL)
@@ -7045,6 +7281,9 @@ var init_sandbox_file_sync = __esm({
7045
7281
  * pre-signed S3 download URL when the file exists in storage.
7046
7282
  */
7047
7283
  async sendFileSyncWebhook(sessionId, event) {
7284
+ if (this.shouldIgnorePath(event.canonicalPath)) {
7285
+ return;
7286
+ }
7048
7287
  let downloadUrl;
7049
7288
  if (event.success && event.canonicalPath && !event.operation.startsWith("deleted")) {
7050
7289
  try {
@@ -7070,12 +7309,15 @@ var init_sandbox_file_sync = __esm({
7070
7309
  metadata: this.webhookConfig?.metadata,
7071
7310
  fileSyncEvent: webhookEvent
7072
7311
  };
7073
- await this.sendWebhook(payload, sessionId);
7312
+ this.enqueueOrSendWebhook(payload, sessionId);
7074
7313
  }
7075
7314
  /**
7076
7315
  * Send a file change event webhook (from watcher)
7077
7316
  */
7078
7317
  async sendFileChangeWebhook(event) {
7318
+ if (this.shouldIgnorePath(event.relativePath)) {
7319
+ return;
7320
+ }
7079
7321
  const payload = {
7080
7322
  event: "file_change",
7081
7323
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -7083,7 +7325,7 @@ var init_sandbox_file_sync = __esm({
7083
7325
  metadata: this.webhookConfig?.metadata,
7084
7326
  fileChangeEvent: event
7085
7327
  };
7086
- await this.sendWebhook(payload, event.sessionId);
7328
+ this.enqueueOrSendWebhook(payload, event.sessionId);
7087
7329
  }
7088
7330
  /**
7089
7331
  * Get the next sequence number for a session
@@ -7747,6 +7989,11 @@ var init_sandbox_file_sync = __esm({
7747
7989
  await Promise.all([...inSandboxPromises, ...localPromises]);
7748
7990
  this.inSandboxWatchers.clear();
7749
7991
  this.localWatchers.clear();
7992
+ if (this.batchTimer) {
7993
+ clearTimeout(this.batchTimer);
7994
+ this.batchTimer = null;
7995
+ await this.flushWebhookBatch();
7996
+ }
7750
7997
  }
7751
7998
  /**
7752
7999
  * Get watching status for all sessions
@@ -9815,6 +10062,58 @@ function createSessionsRouter(options) {
9815
10062
  }
9816
10063
  });
9817
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
+ });
9818
10117
  router.get("/:sessionId/stream", async (c) => {
9819
10118
  const sessionId = c.req.param("sessionId");
9820
10119
  const afterParam = c.req.query("after");
@@ -10499,7 +10798,8 @@ function createHarnessServer(config) {
10499
10798
  basePath = "/api",
10500
10799
  middleware = [],
10501
10800
  hooks = {},
10502
- queue: queueConfig = {}
10801
+ queue: queueConfig = {},
10802
+ sandbox: sandboxConfig
10503
10803
  } = config;
10504
10804
  let agentsNeedReload = false;
10505
10805
  const sessionManager = new exports.SessionManager(storage);
@@ -10650,7 +10950,7 @@ function createHarnessServer(config) {
10650
10950
  return agentStorage.getActiveAgents();
10651
10951
  },
10652
10952
  /**
10653
- * Initialize storage, agents, and start queue processor
10953
+ * Initialize storage, agents, start queue processor, and optionally init sandbox pool
10654
10954
  */
10655
10955
  async initialize() {
10656
10956
  await storage.initialize();
@@ -10664,12 +10964,20 @@ function createHarnessServer(config) {
10664
10964
  await agent.initialize();
10665
10965
  }
10666
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
+ }
10667
10974
  },
10668
10975
  /**
10669
- * Close storage, agents, and stop queue processor
10976
+ * Close storage, agents, stop queue processor, and shutdown sandbox pool
10670
10977
  */
10671
10978
  async close() {
10672
10979
  queueProcessor?.stop();
10980
+ await shutdownSandboxPool();
10673
10981
  for (const agent of agents2.values()) {
10674
10982
  await agent.close();
10675
10983
  }
@@ -10691,6 +10999,7 @@ var init_server = __esm({
10691
10999
  init_agents();
10692
11000
  init_skills2();
10693
11001
  init_queue2();
11002
+ init_sandbox_pool();
10694
11003
  }
10695
11004
  });
10696
11005
 
@@ -13612,7 +13921,8 @@ function createOpenAPIServer(config) {
13612
13921
  basePath = "/api",
13613
13922
  middleware = [],
13614
13923
  hooks = {},
13615
- docs = {}
13924
+ docs = {},
13925
+ sandbox: sandboxConfig
13616
13926
  } = config;
13617
13927
  const {
13618
13928
  enabled: docsEnabled = true,
@@ -13787,7 +14097,7 @@ Authentication is handled by the hosting application. This API does not enforce
13787
14097
  return agentStorage.getActiveAgents();
13788
14098
  },
13789
14099
  /**
13790
- * Initialize storage and agents
14100
+ * Initialize storage, agents, and optionally init sandbox pool
13791
14101
  */
13792
14102
  async initialize() {
13793
14103
  await storage.initialize();
@@ -13797,11 +14107,19 @@ Authentication is handled by the hosting application. This API does not enforce
13797
14107
  for (const agent of agents2.values()) {
13798
14108
  await agent.initialize();
13799
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
+ }
13800
14117
  },
13801
14118
  /**
13802
- * Close storage and agents
14119
+ * Close storage, agents, and shutdown sandbox pool
13803
14120
  */
13804
14121
  async close() {
14122
+ await shutdownSandboxPool();
13805
14123
  for (const agent of agents2.values()) {
13806
14124
  await agent.close();
13807
14125
  }
@@ -13833,6 +14151,7 @@ var init_server2 = __esm({
13833
14151
  init_sessions2();
13834
14152
  init_agents2();
13835
14153
  init_skills3();
14154
+ init_sandbox_pool();
13836
14155
  }
13837
14156
  });
13838
14157
 
@@ -16597,6 +16916,7 @@ __export(schema_exports, {
16597
16916
  configDeployments: () => configDeployments,
16598
16917
  configDeploymentsRelations: () => configDeploymentsRelations,
16599
16918
  eventCategoryEnum: () => eventCategoryEnum,
16919
+ eventSourceEnum: () => eventSourceEnum,
16600
16920
  messageRoleEnum: () => messageRoleEnum,
16601
16921
  messages: () => messages,
16602
16922
  messagesRelations: () => messagesRelations,
@@ -16610,7 +16930,7 @@ __export(schema_exports, {
16610
16930
  streamEvents: () => streamEvents,
16611
16931
  streamEventsRelations: () => streamEventsRelations
16612
16932
  });
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;
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;
16614
16934
  var init_schema = __esm({
16615
16935
  "src/storage-postgres/schema.ts"() {
16616
16936
  sessionStatusEnum = pgCore.pgEnum("session_status", [
@@ -16652,6 +16972,14 @@ var init_schema = __esm({
16652
16972
  "initial",
16653
16973
  "rollback"
16654
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
+ ]);
16655
16983
  eventCategoryEnum = pgCore.pgEnum("event_category", [
16656
16984
  "lifecycle",
16657
16985
  // session_start, session_end, turn_complete
@@ -16842,6 +17170,8 @@ var init_schema = __esm({
16842
17170
  // Aggregation fields (for text_stream events)
16843
17171
  isAggregated: pgCore.boolean("is_aggregated").default(false),
16844
17172
  aggregatedCount: pgCore.integer("aggregated_count"),
17173
+ // Event source tagging
17174
+ eventSource: eventSourceEnum("event_source").notNull().default("agent"),
16845
17175
  // Ordering
16846
17176
  sequenceNumber: pgCore.integer("sequence_number").notNull(),
16847
17177
  createdAt: pgCore.timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
@@ -17434,6 +17764,11 @@ var init_storage2 = __esm({
17434
17764
  drizzleOrm.eq(sessions.status, options.status)
17435
17765
  );
17436
17766
  }
17767
+ if (options.resourceId) {
17768
+ conditions.push(
17769
+ drizzleOrm.sql`${sessions.metadata}->>'resourceId' = ${options.resourceId}`
17770
+ );
17771
+ }
17437
17772
  const orderColumn = options.orderBy === "updatedAt" ? sessions.updatedAt : sessions.createdAt;
17438
17773
  const orderFn = options.order === "asc" ? drizzleOrm.asc : drizzleOrm.desc;
17439
17774
  const limit = options.limit ?? 50;
@@ -18155,6 +18490,9 @@ var init_storage3 = __esm({
18155
18490
  if (options.status) {
18156
18491
  query = query.eq("status", options.status);
18157
18492
  }
18493
+ if (options.resourceId) {
18494
+ query = query.eq("metadata->>resourceId", options.resourceId);
18495
+ }
18158
18496
  const orderColumn = options.orderBy === "updatedAt" ? "updated_at" : "created_at";
18159
18497
  const ascending = options.order === "asc";
18160
18498
  query = query.order(orderColumn, { ascending }).range(offset, offset + limit - 1);
@@ -18512,6 +18850,7 @@ var init_storage3 = __esm({
18512
18850
  sessionId: row.session_id,
18513
18851
  eventType: row.event_type,
18514
18852
  category: row.category,
18853
+ eventSource: row.event_source,
18515
18854
  startedAt: new Date(row.started_at),
18516
18855
  endedAt: row.ended_at ? new Date(row.ended_at) : void 0,
18517
18856
  durationMs: row.duration_ms ?? void 0,
@@ -18534,6 +18873,7 @@ var init_storage3 = __esm({
18534
18873
  session_id: sessionId,
18535
18874
  event_type: event.eventType,
18536
18875
  category: event.category,
18876
+ event_source: event.eventSource || "agent",
18537
18877
  started_at: event.startedAt.toISOString(),
18538
18878
  ended_at: event.endedAt?.toISOString() ?? null,
18539
18879
  duration_ms: event.durationMs ?? null,
@@ -18560,6 +18900,9 @@ var init_storage3 = __esm({
18560
18900
  if (options.eventType) {
18561
18901
  query = query.eq("event_type", options.eventType);
18562
18902
  }
18903
+ if (options.eventSource) {
18904
+ query = query.eq("event_source", options.eventSource);
18905
+ }
18563
18906
  if (options.filePath) {
18564
18907
  query = query.or(
18565
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})`
@@ -18896,7 +19239,8 @@ var init_storage4 = __esm({
18896
19239
  limit: options?.limit,
18897
19240
  offset: options?.offset,
18898
19241
  agentName: options?.agentName,
18899
- status: options?.status
19242
+ status: options?.status,
19243
+ resourceId: options?.resourceId
18900
19244
  }
18901
19245
  }
18902
19246
  );
@@ -19669,6 +20013,7 @@ exports.sseMcpWithAuth = sseMcpWithAuth;
19669
20013
  exports.startServer = startServer;
19670
20014
  exports.streamEventRelay = streamEventRelay;
19671
20015
  exports.updateToolCallWithResult = updateToolCallWithResult;
20016
+ exports.warmSandboxForSession = warmSandboxForSession;
19672
20017
  exports.writeFileToSandbox = writeFileToSandbox;
19673
20018
  //# sourceMappingURL=index.cjs.map
19674
20019
  //# sourceMappingURL=index.cjs.map