@ash-cloud/ash-ai 0.1.17 → 0.1.19

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,16 @@ echo "[warmup] Warmup complete!"
4626
4639
  lastMaintenanceAt = null;
4627
4640
  metricsCallback;
4628
4641
  startPromise = null;
4642
+ /** Registered warmup specs by tag (e.g. agentId -> spec) */
4643
+ warmupSpecs = /* @__PURE__ */ new Map();
4644
+ /** Consecutive warmup failure count (reset on success) */
4645
+ consecutiveFailures = 0;
4646
+ /** Timestamp of last warmup attempt — used for backoff */
4647
+ lastWarmupAttemptAt = 0;
4648
+ /** Max consecutive failures before stopping attempts entirely until manual restart */
4649
+ static MAX_CONSECUTIVE_FAILURES = 10;
4650
+ /** Base backoff delay in ms (doubles each failure: 30s, 60s, 120s, 240s…) */
4651
+ static BACKOFF_BASE_MS = 3e4;
4629
4652
  constructor(config = {}) {
4630
4653
  this.config = {
4631
4654
  minPoolSize: config.minPoolSize ?? parseInt(process.env.SANDBOX_POOL_MIN_SIZE ?? "2"),
@@ -4674,11 +4697,13 @@ echo "[warmup] Warmup complete!"
4674
4697
  if (this.maintenanceIntervalId.unref) {
4675
4698
  this.maintenanceIntervalId.unref();
4676
4699
  }
4700
+ const initialSpecs = this.selectSpecsForReplenishment(this.config.minPoolSize);
4677
4701
  console.log(`[POOL] Spawning ${this.config.minPoolSize} initial sandbox(es)...`);
4678
4702
  const warmupPromises = [];
4679
4703
  for (let i = 0; i < this.config.minPoolSize; i++) {
4704
+ const spec = initialSpecs[i];
4680
4705
  warmupPromises.push(
4681
- this.warmSandbox().then((sandbox) => {
4706
+ this.warmSandbox(spec).then((sandbox) => {
4682
4707
  this.pool.set(sandbox.sandboxId, sandbox);
4683
4708
  console.log(`[POOL] Initial sandbox ready: ${sandbox.sandboxId}`);
4684
4709
  }).catch((error) => {
@@ -4722,9 +4747,11 @@ echo "[warmup] Warmup complete!"
4722
4747
  }
4723
4748
  /**
4724
4749
  * Acquire a pre-warmed sandbox for a session.
4750
+ * If preferTag is provided, tries to find a sandbox warmed for that tag first.
4751
+ * Falls back to a generic (untagged) sandbox if no tag match is found.
4725
4752
  * If no eligible sandbox is available, creates one on-demand.
4726
4753
  */
4727
- async acquire(sessionId) {
4754
+ async acquire(sessionId, preferTag) {
4728
4755
  if (!this.running) {
4729
4756
  throw new Error("Sandbox pool is not running");
4730
4757
  }
@@ -4734,26 +4761,34 @@ echo "[warmup] Warmup complete!"
4734
4761
  return pooled;
4735
4762
  }
4736
4763
  }
4737
- const available = this.getAvailableSandbox();
4764
+ const available = this.getAvailableSandbox(preferTag);
4738
4765
  if (available) {
4739
4766
  available.assignedTo = sessionId;
4740
- console.log(`[POOL] Acquired sandbox ${available.sandboxId} for session ${sessionId}`);
4767
+ const tagInfo = available.warmupTag ? ` [tag=${available.warmupTag}, agentSetupComplete=${available.agentSetupComplete}]` : " [generic]";
4768
+ console.log(`[POOL] Acquired sandbox ${available.sandboxId} for session ${sessionId}${tagInfo}`);
4741
4769
  this.emitMetric("sandbox_assigned", {
4742
4770
  sandboxId: available.sandboxId,
4743
4771
  sessionId,
4744
- poolAvailable: this.getAvailableCount()
4772
+ poolAvailable: this.getAvailableCount(),
4773
+ warmupTag: available.warmupTag,
4774
+ agentSetupComplete: available.agentSetupComplete,
4775
+ preferTag
4745
4776
  });
4746
4777
  this.triggerReplenishment();
4747
4778
  return available;
4748
4779
  }
4749
- console.log(`[POOL] No available sandbox, creating on-demand for session ${sessionId}...`);
4750
- const sandbox = await this.warmSandbox();
4780
+ const spec = preferTag ? this.warmupSpecs.get(preferTag) : void 0;
4781
+ console.log(`[POOL] No available sandbox, creating on-demand for session ${sessionId}${spec ? ` [spec=${preferTag}]` : ""}...`);
4782
+ const sandbox = await this.warmSandbox(spec);
4751
4783
  sandbox.assignedTo = sessionId;
4752
4784
  this.pool.set(sandbox.sandboxId, sandbox);
4753
4785
  this.emitMetric("sandbox_assigned", {
4754
4786
  sandboxId: sandbox.sandboxId,
4755
4787
  sessionId,
4756
- onDemand: true
4788
+ onDemand: true,
4789
+ warmupTag: sandbox.warmupTag,
4790
+ agentSetupComplete: sandbox.agentSetupComplete,
4791
+ preferTag
4757
4792
  });
4758
4793
  return sandbox;
4759
4794
  }
@@ -4784,11 +4819,14 @@ echo "[warmup] Warmup complete!"
4784
4819
  let available = 0;
4785
4820
  let assigned = 0;
4786
4821
  let ineligible = 0;
4822
+ const availableByTag = {};
4787
4823
  for (const pooled of this.pool.values()) {
4788
4824
  if (pooled.assignedTo) {
4789
4825
  assigned++;
4790
4826
  } else if (pooled.eligible) {
4791
4827
  available++;
4828
+ const tagKey = pooled.warmupTag || "generic";
4829
+ availableByTag[tagKey] = (availableByTag[tagKey] || 0) + 1;
4792
4830
  } else {
4793
4831
  ineligible++;
4794
4832
  }
@@ -4801,6 +4839,10 @@ echo "[warmup] Warmup complete!"
4801
4839
  warming: this.warmingInProgress.size,
4802
4840
  running: this.running,
4803
4841
  lastMaintenanceAt: this.lastMaintenanceAt,
4842
+ consecutiveFailures: this.consecutiveFailures,
4843
+ warmupSuspended: this.consecutiveFailures >= _SandboxPool.MAX_CONSECUTIVE_FAILURES,
4844
+ registeredSpecs: this.warmupSpecs.size,
4845
+ availableByTag,
4804
4846
  config: {
4805
4847
  minPoolSize: this.config.minPoolSize,
4806
4848
  maxPoolSize: this.config.maxPoolSize,
@@ -4815,17 +4857,56 @@ echo "[warmup] Warmup complete!"
4815
4857
  onMetrics(callback) {
4816
4858
  this.metricsCallback = callback;
4817
4859
  }
4860
+ /**
4861
+ * Register a warmup spec so the pool can pre-warm agent-specific sandboxes.
4862
+ * If a spec with the same tag already exists, it is replaced.
4863
+ * Triggers replenishment to warm a sandbox for this spec.
4864
+ */
4865
+ registerWarmupSpec(spec) {
4866
+ const isNew = !this.warmupSpecs.has(spec.tag);
4867
+ this.warmupSpecs.set(spec.tag, spec);
4868
+ console.log(`[POOL] ${isNew ? "Registered" : "Updated"} warmup spec: ${spec.tag} (priority=${spec.priority})`);
4869
+ this.emitMetric("spec_registered", {
4870
+ tag: spec.tag,
4871
+ priority: spec.priority,
4872
+ isNew,
4873
+ totalSpecs: this.warmupSpecs.size
4874
+ });
4875
+ if (this.running) {
4876
+ this.triggerReplenishment();
4877
+ }
4878
+ }
4879
+ /**
4880
+ * Remove a warmup spec. Existing tagged sandboxes remain but won't be replaced.
4881
+ */
4882
+ unregisterWarmupSpec(tag) {
4883
+ this.warmupSpecs.delete(tag);
4884
+ console.log(`[POOL] Unregistered warmup spec: ${tag}`);
4885
+ }
4886
+ /**
4887
+ * Update the priority of a warmup spec (e.g. for MRU tracking).
4888
+ * Higher priority = more likely to get a warm sandbox during replenishment.
4889
+ */
4890
+ updateSpecPriority(tag, priority) {
4891
+ const spec = this.warmupSpecs.get(tag);
4892
+ if (spec) {
4893
+ spec.priority = priority;
4894
+ }
4895
+ }
4818
4896
  // ===========================================================================
4819
4897
  // PRIVATE METHODS
4820
4898
  // ===========================================================================
4821
4899
  /**
4822
- * Create and warm a new sandbox
4900
+ * Create and warm a new sandbox.
4901
+ * If a spec is provided, runs the spec's setup function after SDK installation.
4902
+ * On spec setup failure, the sandbox remains generic (graceful degradation).
4823
4903
  */
4824
- async warmSandbox() {
4904
+ async warmSandbox(spec) {
4825
4905
  const warmupId = `warming-${Date.now()}-${Math.random().toString(36).slice(2)}`;
4826
4906
  this.warmingInProgress.add(warmupId);
4827
4907
  this.emitMetric("warmup_started", { warmupId });
4828
4908
  const startTime = Date.now();
4909
+ this.lastWarmupAttemptAt = startTime;
4829
4910
  const useTarball = !!this.config.baseTarballUrl;
4830
4911
  try {
4831
4912
  const { Sandbox } = await import('@vercel/sandbox');
@@ -4914,6 +4995,37 @@ echo "[warmup] Warmup complete!"
4914
4995
  throw new Error(`Warmup failed: ${stderr}`);
4915
4996
  }
4916
4997
  }
4998
+ let warmupTag;
4999
+ let agentSetupComplete = false;
5000
+ if (spec) {
5001
+ console.log(`[POOL] Running spec setup for tag=${spec.tag} on sandbox ${sandbox.sandboxId}...`);
5002
+ this.emitMetric("spec_setup_started", { tag: spec.tag, sandboxId: sandbox.sandboxId });
5003
+ const specStartTime = Date.now();
5004
+ try {
5005
+ await spec.setup(sandbox);
5006
+ warmupTag = spec.tag;
5007
+ agentSetupComplete = true;
5008
+ const specDuration = Date.now() - specStartTime;
5009
+ console.log(`[POOL] Spec setup completed for tag=${spec.tag} on sandbox ${sandbox.sandboxId} (${specDuration}ms)`);
5010
+ this.emitMetric("spec_setup_completed", {
5011
+ tag: spec.tag,
5012
+ sandboxId: sandbox.sandboxId,
5013
+ durationMs: specDuration
5014
+ });
5015
+ } catch (specError) {
5016
+ const specDuration = Date.now() - specStartTime;
5017
+ const specErrorMessage = specError instanceof Error ? specError.message : "Unknown";
5018
+ console.warn(
5019
+ `[POOL] Spec setup failed for tag=${spec.tag} on sandbox ${sandbox.sandboxId} (${specDuration}ms): ${specErrorMessage}. Sandbox stays generic.`
5020
+ );
5021
+ this.emitMetric("spec_setup_failed", {
5022
+ tag: spec.tag,
5023
+ sandboxId: sandbox.sandboxId,
5024
+ durationMs: specDuration,
5025
+ error: specErrorMessage
5026
+ });
5027
+ }
5028
+ }
4917
5029
  const warmupTime = Date.now() - startTime;
4918
5030
  const now = Date.now();
4919
5031
  const pooled = {
@@ -4923,22 +5035,38 @@ echo "[warmup] Warmup complete!"
4923
5035
  expiresAt: now + this.config.sandboxTimeout * 1e3,
4924
5036
  sdkInstalled: true,
4925
5037
  eligible: true,
4926
- lastHeartbeat: now
5038
+ lastHeartbeat: now,
5039
+ warmupTag,
5040
+ agentSetupComplete
4927
5041
  };
4928
- console.log(`[POOL] Warmup completed for ${sandbox.sandboxId} (took ${warmupTime}ms)${useTarball ? " [tarball]" : ""}`);
5042
+ const tagInfo = warmupTag ? ` [tag=${warmupTag}]` : "";
5043
+ console.log(`[POOL] Warmup completed for ${sandbox.sandboxId} (took ${warmupTime}ms)${useTarball ? " [tarball]" : ""}${tagInfo}`);
5044
+ this.consecutiveFailures = 0;
4929
5045
  this.emitMetric("warmup_completed", {
4930
5046
  sandboxId: pooled.sandboxId,
4931
5047
  warmupTimeMs: warmupTime,
4932
- usedTarball: useTarball
5048
+ usedTarball: useTarball,
5049
+ warmupTag,
5050
+ agentSetupComplete
4933
5051
  });
4934
5052
  this.emitMetric("sandbox_created", { sandboxId: pooled.sandboxId });
4935
5053
  return pooled;
4936
5054
  } catch (error) {
4937
5055
  const warmupTime = Date.now() - startTime;
5056
+ this.consecutiveFailures++;
5057
+ const errorMessage = error instanceof Error ? error.message : "Unknown";
5058
+ if (this.consecutiveFailures === 1) {
5059
+ console.error(`[POOL] Warmup failed: ${errorMessage}`);
5060
+ } else {
5061
+ console.error(
5062
+ `[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}`
5063
+ );
5064
+ }
4938
5065
  this.emitMetric("warmup_failed", {
4939
- error: error instanceof Error ? error.message : "Unknown",
5066
+ error: errorMessage,
4940
5067
  warmupTimeMs: warmupTime,
4941
- usedTarball: useTarball
5068
+ usedTarball: useTarball,
5069
+ consecutiveFailures: this.consecutiveFailures
4942
5070
  });
4943
5071
  throw error;
4944
5072
  } finally {
@@ -5000,18 +5128,29 @@ echo "[warmup] Warmup complete!"
5000
5128
  }
5001
5129
  }
5002
5130
  /**
5003
- * Get an available eligible sandbox for assignment
5131
+ * Get an available eligible sandbox for assignment.
5132
+ * If preferTag is provided:
5133
+ * - First tries to find a sandbox tagged for that specific agent
5134
+ * - Falls back to a generic (untagged) sandbox
5135
+ * - Tagged sandboxes for OTHER agents are NOT used as fallback (reserved)
5004
5136
  */
5005
- getAvailableSandbox() {
5006
- let best = null;
5137
+ getAvailableSandbox(preferTag) {
5138
+ let bestTagged = null;
5139
+ let bestGeneric = null;
5007
5140
  for (const pooled of this.pool.values()) {
5008
5141
  if (!pooled.assignedTo && pooled.eligible) {
5009
- if (!best || pooled.expiresAt > best.expiresAt) {
5010
- best = pooled;
5142
+ if (preferTag && pooled.warmupTag === preferTag) {
5143
+ if (!bestTagged || pooled.expiresAt > bestTagged.expiresAt) {
5144
+ bestTagged = pooled;
5145
+ }
5146
+ } else if (!pooled.warmupTag) {
5147
+ if (!bestGeneric || pooled.expiresAt > bestGeneric.expiresAt) {
5148
+ bestGeneric = pooled;
5149
+ }
5011
5150
  }
5012
5151
  }
5013
5152
  }
5014
- return best;
5153
+ return bestTagged || bestGeneric;
5015
5154
  }
5016
5155
  /**
5017
5156
  * Get count of available sandboxes
@@ -5046,9 +5185,30 @@ echo "[warmup] Warmup complete!"
5046
5185
  });
5047
5186
  }
5048
5187
  /**
5049
- * Replenish pool if below min size
5188
+ * Replenish pool if below min size.
5189
+ * Applies exponential backoff when warmups keep failing to avoid
5190
+ * a tight failure loop that wastes resources and floods metrics.
5050
5191
  */
5051
5192
  async replenishPool() {
5193
+ if (this.consecutiveFailures >= _SandboxPool.MAX_CONSECUTIVE_FAILURES) {
5194
+ if (Math.random() < 0.2) {
5195
+ console.warn(
5196
+ `[POOL] Warmup suspended after ${this.consecutiveFailures} consecutive failures. Check SANDBOX_BASE_TARBALL_URL or Vercel API status. Pool will retry on next acquire().`
5197
+ );
5198
+ }
5199
+ return;
5200
+ }
5201
+ if (this.consecutiveFailures > 0) {
5202
+ const backoffMs = Math.min(
5203
+ _SandboxPool.BACKOFF_BASE_MS * Math.pow(2, this.consecutiveFailures - 1),
5204
+ 5 * 6e4
5205
+ // Cap at 5 minutes
5206
+ );
5207
+ const timeSinceLastAttempt = Date.now() - this.lastWarmupAttemptAt;
5208
+ if (timeSinceLastAttempt < backoffMs) {
5209
+ return;
5210
+ }
5211
+ }
5052
5212
  const eligibleCount = this.getEligibleCount();
5053
5213
  const warmingCount = this.warmingInProgress.size;
5054
5214
  const totalProjected = eligibleCount + warmingCount;
@@ -5057,13 +5217,16 @@ echo "[warmup] Warmup complete!"
5057
5217
  this.config.maxPoolSize - this.pool.size - warmingCount
5058
5218
  );
5059
5219
  if (needed <= 0) return;
5220
+ const specAssignments = this.selectSpecsForReplenishment(needed);
5060
5221
  console.log(`[POOL] Spawning ${needed} sandbox(es) to maintain pool...`);
5061
5222
  const promises = [];
5062
5223
  for (let i = 0; i < needed; i++) {
5224
+ const spec = specAssignments[i];
5063
5225
  promises.push(
5064
- this.warmSandbox().then((sandbox) => {
5226
+ this.warmSandbox(spec).then((sandbox) => {
5065
5227
  this.pool.set(sandbox.sandboxId, sandbox);
5066
- console.log(`[POOL] Replenishment sandbox ready: ${sandbox.sandboxId}`);
5228
+ const tagInfo = sandbox.warmupTag ? ` [tag=${sandbox.warmupTag}]` : "";
5229
+ console.log(`[POOL] Replenishment sandbox ready: ${sandbox.sandboxId}${tagInfo}`);
5067
5230
  }).catch((error) => {
5068
5231
  console.error("[POOL] Failed to warm replenishment sandbox:", error);
5069
5232
  })
@@ -5074,6 +5237,39 @@ echo "[warmup] Warmup complete!"
5074
5237
  }
5075
5238
  await Promise.all(promises);
5076
5239
  }
5240
+ /**
5241
+ * Decide which specs to apply to new sandboxes during replenishment.
5242
+ * Strategy: cover uncovered specs first (highest priority), then fill remaining as generic.
5243
+ * Returns an array of length `needed`, where each element is a spec or undefined (generic).
5244
+ */
5245
+ selectSpecsForReplenishment(needed) {
5246
+ if (this.warmupSpecs.size === 0) {
5247
+ return new Array(needed).fill(void 0);
5248
+ }
5249
+ const uncoveredSpecs = [];
5250
+ for (const spec of this.warmupSpecs.values()) {
5251
+ let hasCoverage = false;
5252
+ for (const pooled of this.pool.values()) {
5253
+ if (pooled.warmupTag === spec.tag && pooled.eligible && !pooled.assignedTo) {
5254
+ hasCoverage = true;
5255
+ break;
5256
+ }
5257
+ }
5258
+ if (!hasCoverage) {
5259
+ uncoveredSpecs.push(spec);
5260
+ }
5261
+ }
5262
+ uncoveredSpecs.sort((a, b) => b.priority - a.priority);
5263
+ const assignments = [];
5264
+ for (const spec of uncoveredSpecs) {
5265
+ if (assignments.length >= needed) break;
5266
+ assignments.push(spec);
5267
+ }
5268
+ while (assignments.length < needed) {
5269
+ assignments.push(void 0);
5270
+ }
5271
+ return assignments;
5272
+ }
5077
5273
  /**
5078
5274
  * Destroy a sandbox and clean up
5079
5275
  */
@@ -5305,7 +5501,7 @@ function removeExpiredSandbox(sessionId) {
5305
5501
  }
5306
5502
  }
5307
5503
  async function getOrCreateSandbox(options) {
5308
- const { sessionId, runtime = "node22", timeout = 300, vcpus = 4, existingSandboxId } = options;
5504
+ const { sessionId, runtime = "node22", timeout = 300, vcpus = 4, existingSandboxId, preferTag } = options;
5309
5505
  ensureCleanupRunning();
5310
5506
  ensureHeartbeatRunning();
5311
5507
  const { Sandbox } = await import('@vercel/sandbox');
@@ -5322,6 +5518,8 @@ async function getOrCreateSandbox(options) {
5322
5518
  sdkInstalled: cached.sdkInstalled,
5323
5519
  startupScriptRan: cached.startupScriptRan,
5324
5520
  startupScriptHash: cached.startupScriptHash,
5521
+ installScriptRan: cached.installScriptRan,
5522
+ installScriptHash: cached.installScriptHash,
5325
5523
  isNew: false,
5326
5524
  configFileUrl: cached.configFileUrl,
5327
5525
  configInstalledAt: cached.configInstalledAt
@@ -5330,41 +5528,6 @@ async function getOrCreateSandbox(options) {
5330
5528
  console.log("[SANDBOX] Cached sandbox has expired (HTTP 410 or similar), creating new sandbox");
5331
5529
  removeExpiredSandbox(sessionId);
5332
5530
  }
5333
- const pool = await ensureSandboxPoolInitialized();
5334
- if (pool && pool.isRunning()) {
5335
- try {
5336
- console.log("[SANDBOX] Attempting to acquire from pre-warmed pool...");
5337
- const pooled = await pool.acquire(sessionId);
5338
- console.log("[SANDBOX] Acquired pre-warmed sandbox:", pooled.sandboxId);
5339
- const now2 = Date.now();
5340
- const entry2 = {
5341
- sandbox: pooled.sandbox,
5342
- sessionId,
5343
- createdAt: pooled.createdAt,
5344
- lastUsedAt: now2,
5345
- sdkInstalled: pooled.sdkInstalled,
5346
- startupScriptRan: false
5347
- // User script hasn't run yet
5348
- };
5349
- sandboxCache.set(sessionId, entry2);
5350
- return {
5351
- sandbox: pooled.sandbox,
5352
- sandboxId: pooled.sandboxId,
5353
- sdkInstalled: pooled.sdkInstalled,
5354
- startupScriptRan: false,
5355
- startupScriptHash: void 0,
5356
- isNew: false,
5357
- // Not new - came from pool
5358
- configFileUrl: void 0,
5359
- configInstalledAt: void 0
5360
- };
5361
- } catch (error) {
5362
- console.warn(
5363
- "[SANDBOX] Failed to acquire from pool, falling back to on-demand creation:",
5364
- error instanceof Error ? error.message : "Unknown error"
5365
- );
5366
- }
5367
- }
5368
5531
  if (existingSandboxId) {
5369
5532
  console.log("[SANDBOX] Attempting to reconnect to existing sandbox:", existingSandboxId);
5370
5533
  try {
@@ -5385,7 +5548,9 @@ async function getOrCreateSandbox(options) {
5385
5548
  // We assume SDK is installed since this is an existing sandbox
5386
5549
  // The caller can verify and re-mark if needed
5387
5550
  sdkInstalled: true,
5388
- startupScriptRan: true
5551
+ startupScriptRan: true,
5552
+ installScriptRan: true
5553
+ // Assume ran for reconnected sandboxes
5389
5554
  };
5390
5555
  sandboxCache.set(sessionId, entry2);
5391
5556
  return {
@@ -5397,13 +5562,17 @@ async function getOrCreateSandbox(options) {
5397
5562
  // Assume ran for reconnected sandboxes
5398
5563
  startupScriptHash: void 0,
5399
5564
  // Unknown — caller should not re-run based on hash mismatch alone
5565
+ installScriptRan: true,
5566
+ // Assume ran for reconnected sandboxes
5567
+ installScriptHash: void 0,
5568
+ // Unknown — same logic as startup script
5400
5569
  isNew: false,
5401
5570
  configFileUrl: void 0,
5402
5571
  configInstalledAt: now2
5403
5572
  // Assume config was installed — prevents unnecessary re-install on reconnection
5404
5573
  };
5405
5574
  } else {
5406
- console.log("[SANDBOX] Reconnected sandbox failed health check, will create new");
5575
+ console.log("[SANDBOX] Reconnected sandbox failed health check, will try pool or create new");
5407
5576
  }
5408
5577
  } catch (error) {
5409
5578
  console.log(
@@ -5413,6 +5582,47 @@ async function getOrCreateSandbox(options) {
5413
5582
  );
5414
5583
  }
5415
5584
  }
5585
+ const pool = await ensureSandboxPoolInitialized();
5586
+ if (pool && pool.isRunning()) {
5587
+ try {
5588
+ console.log(`[SANDBOX] Attempting to acquire from pre-warmed pool...${preferTag ? ` [preferTag=${preferTag}]` : ""}`);
5589
+ const pooled = await pool.acquire(sessionId, preferTag);
5590
+ const tagInfo = pooled.warmupTag ? ` [tag=${pooled.warmupTag}, agentSetup=${pooled.agentSetupComplete}]` : "";
5591
+ console.log(`[SANDBOX] Acquired pre-warmed sandbox: ${pooled.sandboxId}${tagInfo}`);
5592
+ const agentSetupDone = pooled.agentSetupComplete === true;
5593
+ const now2 = Date.now();
5594
+ const entry2 = {
5595
+ sandbox: pooled.sandbox,
5596
+ sessionId,
5597
+ createdAt: pooled.createdAt,
5598
+ lastUsedAt: now2,
5599
+ sdkInstalled: pooled.sdkInstalled,
5600
+ startupScriptRan: agentSetupDone,
5601
+ installScriptRan: agentSetupDone
5602
+ };
5603
+ sandboxCache.set(sessionId, entry2);
5604
+ return {
5605
+ sandbox: pooled.sandbox,
5606
+ sandboxId: pooled.sandboxId,
5607
+ sdkInstalled: pooled.sdkInstalled,
5608
+ startupScriptRan: agentSetupDone,
5609
+ startupScriptHash: void 0,
5610
+ installScriptRan: agentSetupDone,
5611
+ installScriptHash: void 0,
5612
+ isNew: false,
5613
+ // Not new - came from pool
5614
+ configFileUrl: void 0,
5615
+ configInstalledAt: agentSetupDone ? now2 : void 0,
5616
+ warmupTag: pooled.warmupTag,
5617
+ agentSetupComplete: pooled.agentSetupComplete
5618
+ };
5619
+ } catch (error) {
5620
+ console.warn(
5621
+ "[SANDBOX] Failed to acquire from pool, falling back to on-demand creation:",
5622
+ error instanceof Error ? error.message : "Unknown error"
5623
+ );
5624
+ }
5625
+ }
5416
5626
  console.log("[SANDBOX] Creating new sandbox for session:", sessionId);
5417
5627
  const baseTarballUrl = process.env.SANDBOX_BASE_TARBALL_URL;
5418
5628
  const useTarball = !!baseTarballUrl;
@@ -5446,7 +5656,8 @@ async function getOrCreateSandbox(options) {
5446
5656
  lastUsedAt: now,
5447
5657
  // If we used tarball, SDK is pre-installed
5448
5658
  sdkInstalled: useTarball,
5449
- startupScriptRan: false
5659
+ startupScriptRan: false,
5660
+ installScriptRan: false
5450
5661
  };
5451
5662
  sandboxCache.set(sessionId, entry);
5452
5663
  return {
@@ -5456,6 +5667,8 @@ async function getOrCreateSandbox(options) {
5456
5667
  sdkInstalled: useTarball,
5457
5668
  startupScriptRan: false,
5458
5669
  startupScriptHash: void 0,
5670
+ installScriptRan: false,
5671
+ installScriptHash: void 0,
5459
5672
  isNew: true,
5460
5673
  configFileUrl: void 0,
5461
5674
  configInstalledAt: void 0
@@ -5495,6 +5708,14 @@ function markStartupScriptRan(sessionId, scriptHash) {
5495
5708
  cached.lastUsedAt = Date.now();
5496
5709
  }
5497
5710
  }
5711
+ function markInstallScriptRan(sessionId, scriptHash) {
5712
+ const cached = sandboxCache.get(sessionId);
5713
+ if (cached) {
5714
+ cached.installScriptRan = true;
5715
+ cached.installScriptHash = scriptHash;
5716
+ cached.lastUsedAt = Date.now();
5717
+ }
5718
+ }
5498
5719
  function needsStartupScriptRerun(sessionId, newScript) {
5499
5720
  const cached = sandboxCache.get(sessionId);
5500
5721
  if (!cached) return true;
@@ -5562,6 +5783,8 @@ function getCachedSandbox(sessionId) {
5562
5783
  sdkInstalled: cached.sdkInstalled,
5563
5784
  startupScriptRan: cached.startupScriptRan,
5564
5785
  startupScriptHash: cached.startupScriptHash,
5786
+ installScriptRan: cached.installScriptRan,
5787
+ installScriptHash: cached.installScriptHash,
5565
5788
  isNew: false,
5566
5789
  configFileUrl: cached.configFileUrl,
5567
5790
  configInstalledAt: cached.configInstalledAt
@@ -6154,6 +6377,85 @@ SCRIPT_EOF`]
6154
6377
  }
6155
6378
  }
6156
6379
  }
6380
+ async function warmSandboxForSession(options) {
6381
+ const {
6382
+ sessionId,
6383
+ runtime = "node22",
6384
+ timeout = 600,
6385
+ vcpus = 4,
6386
+ startupScript,
6387
+ configFileUrl,
6388
+ envVars = {}
6389
+ } = options;
6390
+ console.log(`[WARM] Pre-warming sandbox for session: ${sessionId}`);
6391
+ const startTime = Date.now();
6392
+ const result = await getOrCreateSandbox({
6393
+ sessionId,
6394
+ runtime,
6395
+ timeout,
6396
+ vcpus
6397
+ });
6398
+ const { sandbox, sdkInstalled } = result;
6399
+ if (!sdkInstalled) {
6400
+ console.log("[WARM] Installing SDK...");
6401
+ await sandbox.runCommand({
6402
+ cmd: "bash",
6403
+ 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"]
6404
+ });
6405
+ await sandbox.runCommand({
6406
+ cmd: "bash",
6407
+ 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"]
6408
+ });
6409
+ const sdkResult = await sandbox.runCommand({
6410
+ cmd: "bash",
6411
+ args: ["-c", "npm init -y && npm install @anthropic-ai/claude-agent-sdk"],
6412
+ env: envVars
6413
+ });
6414
+ if (sdkResult.exitCode !== 0) {
6415
+ const stderr = await sdkResult.stderr();
6416
+ throw new Error(`Failed to install Claude Agent SDK during warmup: ${stderr}`);
6417
+ }
6418
+ markSdkInstalled(sessionId);
6419
+ }
6420
+ if (startupScript) {
6421
+ console.log("[WARM] Running startup script...");
6422
+ const scriptResult = await sandbox.runCommand({
6423
+ cmd: "bash",
6424
+ args: ["-c", startupScript],
6425
+ env: envVars
6426
+ });
6427
+ if (scriptResult.exitCode !== 0) {
6428
+ const stderr = await scriptResult.stderr();
6429
+ throw new Error(`Startup script failed during warmup: ${stderr}`);
6430
+ }
6431
+ markStartupScriptRan(sessionId, hashStartupScript(startupScript));
6432
+ }
6433
+ if (configFileUrl) {
6434
+ console.log("[WARM] Installing config from:", configFileUrl);
6435
+ const configScript = `
6436
+ set -e
6437
+ curl -sSL --fail "${configFileUrl}" -o /tmp/config.zip
6438
+ unzip -o /tmp/config.zip -d .
6439
+ rm -f /tmp/config.zip
6440
+ `;
6441
+ const configResult = await sandbox.runCommand({
6442
+ cmd: "bash",
6443
+ args: ["-c", configScript],
6444
+ env: envVars
6445
+ });
6446
+ if (configResult.exitCode !== 0) {
6447
+ const stderr = await configResult.stderr();
6448
+ throw new Error(`Config installation failed during warmup: ${stderr}`);
6449
+ }
6450
+ markConfigInstalled(sessionId, configFileUrl);
6451
+ }
6452
+ const duration = Date.now() - startTime;
6453
+ console.log(`[WARM] Sandbox pre-warmed for session ${sessionId} in ${duration}ms (sandbox: ${result.sandboxId})`);
6454
+ return {
6455
+ sandboxId: result.sandboxId,
6456
+ ready: true
6457
+ };
6458
+ }
6157
6459
  var sandboxCache, DEFAULT_IDLE_TTL, CLEANUP_INTERVAL, HEARTBEAT_INTERVAL, cleanupIntervalId, heartbeatIntervalId, GLOBAL_HEARTBEAT_KEY, heartbeatListeners;
6158
6460
  var init_vercel_sandbox_executor = __esm({
6159
6461
  "src/runtime/vercel-sandbox-executor.ts"() {
@@ -6808,6 +7110,31 @@ WATCHER_EOF`;
6808
7110
  globalInSandboxManager = null;
6809
7111
  }
6810
7112
  });
7113
+ function matchGlob(pattern, filePath) {
7114
+ const normalizedPath = filePath.replace(/\\/g, "/");
7115
+ const normalizedPattern = pattern.replace(/\\/g, "/");
7116
+ let regexStr = "";
7117
+ let i = 0;
7118
+ while (i < normalizedPattern.length) {
7119
+ if (normalizedPattern[i] === "*" && normalizedPattern[i + 1] === "*") {
7120
+ if (normalizedPattern[i + 2] === "/") {
7121
+ regexStr += "(?:.*/)?";
7122
+ i += 3;
7123
+ } else {
7124
+ regexStr += ".*";
7125
+ i += 2;
7126
+ }
7127
+ } else if (normalizedPattern[i] === "*") {
7128
+ regexStr += "[^/]*";
7129
+ i += 1;
7130
+ } else {
7131
+ const ch = normalizedPattern[i];
7132
+ regexStr += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7133
+ i += 1;
7134
+ }
7135
+ }
7136
+ return new RegExp(`^${regexStr}$`).test(normalizedPath);
7137
+ }
6811
7138
  function extractErrorMessage(error) {
6812
7139
  if (error === null || error === void 0) {
6813
7140
  return "Unknown error (null/undefined)";
@@ -6850,12 +7177,18 @@ function extractErrorMessage(error) {
6850
7177
  function createSandboxFileSync(options) {
6851
7178
  return new exports.SandboxFileSync(options);
6852
7179
  }
6853
- exports.SandboxFileSync = void 0;
7180
+ var DEFAULT_WEBHOOK_IGNORE; exports.SandboxFileSync = void 0;
6854
7181
  var init_sandbox_file_sync = __esm({
6855
7182
  "src/runtime/sandbox-file-sync.ts"() {
6856
7183
  init_sandbox_file_watcher();
6857
7184
  init_in_sandbox_watcher();
6858
7185
  init_types();
7186
+ DEFAULT_WEBHOOK_IGNORE = [
7187
+ "**/agent-runner.js",
7188
+ "**/agent-runner.cjs",
7189
+ "**/.file-watcher.js",
7190
+ "**/.file-watcher-output.log"
7191
+ ];
6859
7192
  exports.SandboxFileSync = class {
6860
7193
  fileStore;
6861
7194
  sandboxBasePath;
@@ -6870,6 +7203,10 @@ var init_sandbox_file_sync = __esm({
6870
7203
  fileChangeSubscribers = /* @__PURE__ */ new Set();
6871
7204
  // Sequence number cache per session (for event storage)
6872
7205
  sequenceNumbers = /* @__PURE__ */ new Map();
7206
+ // Webhook batching state
7207
+ batchTimer = null;
7208
+ batchQueue = /* @__PURE__ */ new Map();
7209
+ // keyed by file path for dedup
6873
7210
  constructor(options) {
6874
7211
  this.fileStore = options.fileStore;
6875
7212
  this.sandboxBasePath = options.sandboxBasePath ?? ".claude/files";
@@ -6904,6 +7241,72 @@ var init_sandbox_file_sync = __esm({
6904
7241
  */
6905
7242
  removeWebhook() {
6906
7243
  this.webhookConfig = void 0;
7244
+ if (this.batchTimer) {
7245
+ clearTimeout(this.batchTimer);
7246
+ this.batchTimer = null;
7247
+ }
7248
+ this.batchQueue.clear();
7249
+ }
7250
+ /**
7251
+ * Check if a file path should be excluded from webhook notifications.
7252
+ * Tests against DEFAULT_WEBHOOK_IGNORE patterns and any user-configured ignorePaths.
7253
+ */
7254
+ shouldIgnorePath(filePath) {
7255
+ if (!filePath) return false;
7256
+ const userPatterns = this.webhookConfig?.ignorePaths ?? [];
7257
+ const allPatterns = [...DEFAULT_WEBHOOK_IGNORE, ...userPatterns];
7258
+ return allPatterns.some((pattern) => matchGlob(pattern, filePath));
7259
+ }
7260
+ /**
7261
+ * Route a webhook payload through batching (if configured) or send immediately.
7262
+ * When batchWindowMs is set, payloads are queued and deduplicated by file path.
7263
+ */
7264
+ enqueueOrSendWebhook(payload, sessionId) {
7265
+ const batchWindowMs = this.webhookConfig?.batchWindowMs;
7266
+ if (!batchWindowMs || batchWindowMs <= 0) {
7267
+ this.sendWebhook(payload, sessionId);
7268
+ return;
7269
+ }
7270
+ const filePath = payload.fileSyncEvent?.canonicalPath ?? payload.fileChangeEvent?.relativePath ?? `_unknown_${Date.now()}`;
7271
+ this.batchQueue.set(filePath, payload);
7272
+ if (this.batchTimer) {
7273
+ clearTimeout(this.batchTimer);
7274
+ }
7275
+ this.batchTimer = setTimeout(() => {
7276
+ this.flushWebhookBatch(sessionId);
7277
+ }, batchWindowMs);
7278
+ }
7279
+ /**
7280
+ * Flush all queued webhook payloads as a single batch request.
7281
+ */
7282
+ async flushWebhookBatch(sessionId) {
7283
+ this.batchTimer = null;
7284
+ if (this.batchQueue.size === 0) return;
7285
+ const payloads = Array.from(this.batchQueue.values());
7286
+ this.batchQueue.clear();
7287
+ const fileSyncEvents = [];
7288
+ const fileChangeEvents = [];
7289
+ let batchSessionId = sessionId ?? "";
7290
+ for (const p of payloads) {
7291
+ if (!batchSessionId) {
7292
+ batchSessionId = p.sessionId;
7293
+ }
7294
+ if (p.fileSyncEvent) {
7295
+ fileSyncEvents.push(p.fileSyncEvent);
7296
+ }
7297
+ if (p.fileChangeEvent) {
7298
+ fileChangeEvents.push(p.fileChangeEvent);
7299
+ }
7300
+ }
7301
+ const batchPayload = {
7302
+ event: "file_sync_batch",
7303
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7304
+ sessionId: batchSessionId,
7305
+ metadata: this.webhookConfig?.metadata,
7306
+ fileSyncEvents,
7307
+ fileChangeEvents
7308
+ };
7309
+ await this.sendWebhook(batchPayload, sessionId);
6907
7310
  }
6908
7311
  /**
6909
7312
  * Extract hostname from URL for display (security - don't expose full URL)
@@ -7045,6 +7448,9 @@ var init_sandbox_file_sync = __esm({
7045
7448
  * pre-signed S3 download URL when the file exists in storage.
7046
7449
  */
7047
7450
  async sendFileSyncWebhook(sessionId, event) {
7451
+ if (this.shouldIgnorePath(event.canonicalPath)) {
7452
+ return;
7453
+ }
7048
7454
  let downloadUrl;
7049
7455
  if (event.success && event.canonicalPath && !event.operation.startsWith("deleted")) {
7050
7456
  try {
@@ -7070,12 +7476,15 @@ var init_sandbox_file_sync = __esm({
7070
7476
  metadata: this.webhookConfig?.metadata,
7071
7477
  fileSyncEvent: webhookEvent
7072
7478
  };
7073
- await this.sendWebhook(payload, sessionId);
7479
+ this.enqueueOrSendWebhook(payload, sessionId);
7074
7480
  }
7075
7481
  /**
7076
7482
  * Send a file change event webhook (from watcher)
7077
7483
  */
7078
7484
  async sendFileChangeWebhook(event) {
7485
+ if (this.shouldIgnorePath(event.relativePath)) {
7486
+ return;
7487
+ }
7079
7488
  const payload = {
7080
7489
  event: "file_change",
7081
7490
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -7083,7 +7492,7 @@ var init_sandbox_file_sync = __esm({
7083
7492
  metadata: this.webhookConfig?.metadata,
7084
7493
  fileChangeEvent: event
7085
7494
  };
7086
- await this.sendWebhook(payload, event.sessionId);
7495
+ this.enqueueOrSendWebhook(payload, event.sessionId);
7087
7496
  }
7088
7497
  /**
7089
7498
  * Get the next sequence number for a session
@@ -7747,6 +8156,11 @@ var init_sandbox_file_sync = __esm({
7747
8156
  await Promise.all([...inSandboxPromises, ...localPromises]);
7748
8157
  this.inSandboxWatchers.clear();
7749
8158
  this.localWatchers.clear();
8159
+ if (this.batchTimer) {
8160
+ clearTimeout(this.batchTimer);
8161
+ this.batchTimer = null;
8162
+ await this.flushWebhookBatch();
8163
+ }
7750
8164
  }
7751
8165
  /**
7752
8166
  * Get watching status for all sessions
@@ -9815,6 +10229,58 @@ function createSessionsRouter(options) {
9815
10229
  }
9816
10230
  });
9817
10231
  });
10232
+ router.get("/:sessionId/stream-events", async (c) => {
10233
+ const sessionId = c.req.param("sessionId");
10234
+ const afterSequence = Math.max(0, parseInt(c.req.query("afterSequence") || "0", 10) || 0);
10235
+ const limit = Math.min(Math.max(1, parseInt(c.req.query("limit") || "100", 10) || 100), 1e3);
10236
+ const waitMs = Math.min(Math.max(0, parseInt(c.req.query("waitMs") || "0", 10) || 0), 3e4);
10237
+ const session = await sessionManager.getSession(sessionId);
10238
+ if (!session) {
10239
+ return c.json({ error: "Session not found" }, 404);
10240
+ }
10241
+ const agent = agents2.get(session.agentName);
10242
+ if (!agent) {
10243
+ return c.json({ error: `Agent not found: ${session.agentName}` }, 404);
10244
+ }
10245
+ const streamEventStorage = agent.getStreamEventStorage();
10246
+ if (!streamEventStorage) {
10247
+ return c.json({ error: "Stream event storage not configured" }, 501);
10248
+ }
10249
+ let events = await streamEventStorage.readEvents(sessionId, {
10250
+ afterSequence,
10251
+ limit
10252
+ });
10253
+ if (events.length === 0 && waitMs > 0) {
10254
+ const deadline = Date.now() + waitMs;
10255
+ const pollInterval = 200;
10256
+ while (Date.now() < deadline) {
10257
+ events = await streamEventStorage.readEvents(sessionId, {
10258
+ afterSequence,
10259
+ limit
10260
+ });
10261
+ if (events.length > 0) break;
10262
+ const currentSession = await sessionManager.getSession(sessionId);
10263
+ if (currentSession && ["completed", "error", "stopped"].includes(currentSession.status)) {
10264
+ break;
10265
+ }
10266
+ await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
10267
+ }
10268
+ }
10269
+ const nextCursor = events.length > 0 ? Math.max(...events.map((e) => e.sequence)) : afterSequence;
10270
+ return c.json({
10271
+ events: events.map((e) => ({
10272
+ id: e.id,
10273
+ sequence: e.sequence,
10274
+ eventType: e.eventType,
10275
+ payload: e.payload,
10276
+ batchCount: e.batchCount,
10277
+ createdAt: e.createdAt.toISOString()
10278
+ })),
10279
+ nextCursor,
10280
+ hasMore: events.length === limit,
10281
+ sessionStatus: session.status
10282
+ });
10283
+ });
9818
10284
  router.get("/:sessionId/stream", async (c) => {
9819
10285
  const sessionId = c.req.param("sessionId");
9820
10286
  const afterParam = c.req.query("after");
@@ -10499,7 +10965,8 @@ function createHarnessServer(config) {
10499
10965
  basePath = "/api",
10500
10966
  middleware = [],
10501
10967
  hooks = {},
10502
- queue: queueConfig = {}
10968
+ queue: queueConfig = {},
10969
+ sandbox: sandboxConfig
10503
10970
  } = config;
10504
10971
  let agentsNeedReload = false;
10505
10972
  const sessionManager = new exports.SessionManager(storage);
@@ -10650,7 +11117,7 @@ function createHarnessServer(config) {
10650
11117
  return agentStorage.getActiveAgents();
10651
11118
  },
10652
11119
  /**
10653
- * Initialize storage, agents, and start queue processor
11120
+ * Initialize storage, agents, start queue processor, and optionally init sandbox pool
10654
11121
  */
10655
11122
  async initialize() {
10656
11123
  await storage.initialize();
@@ -10664,12 +11131,20 @@ function createHarnessServer(config) {
10664
11131
  await agent.initialize();
10665
11132
  }
10666
11133
  queueProcessor?.start();
11134
+ if (sandboxConfig?.pool && sandboxConfig.autoInitPool !== false) {
11135
+ try {
11136
+ await initializeSandboxPool(sandboxConfig.pool);
11137
+ } catch (error) {
11138
+ console.error("[SERVER] Failed to initialize sandbox pool:", error instanceof Error ? error.message : error);
11139
+ }
11140
+ }
10667
11141
  },
10668
11142
  /**
10669
- * Close storage, agents, and stop queue processor
11143
+ * Close storage, agents, stop queue processor, and shutdown sandbox pool
10670
11144
  */
10671
11145
  async close() {
10672
11146
  queueProcessor?.stop();
11147
+ await shutdownSandboxPool();
10673
11148
  for (const agent of agents2.values()) {
10674
11149
  await agent.close();
10675
11150
  }
@@ -10691,6 +11166,7 @@ var init_server = __esm({
10691
11166
  init_agents();
10692
11167
  init_skills2();
10693
11168
  init_queue2();
11169
+ init_sandbox_pool();
10694
11170
  }
10695
11171
  });
10696
11172
 
@@ -13612,7 +14088,8 @@ function createOpenAPIServer(config) {
13612
14088
  basePath = "/api",
13613
14089
  middleware = [],
13614
14090
  hooks = {},
13615
- docs = {}
14091
+ docs = {},
14092
+ sandbox: sandboxConfig
13616
14093
  } = config;
13617
14094
  const {
13618
14095
  enabled: docsEnabled = true,
@@ -13787,7 +14264,7 @@ Authentication is handled by the hosting application. This API does not enforce
13787
14264
  return agentStorage.getActiveAgents();
13788
14265
  },
13789
14266
  /**
13790
- * Initialize storage and agents
14267
+ * Initialize storage, agents, and optionally init sandbox pool
13791
14268
  */
13792
14269
  async initialize() {
13793
14270
  await storage.initialize();
@@ -13797,11 +14274,19 @@ Authentication is handled by the hosting application. This API does not enforce
13797
14274
  for (const agent of agents2.values()) {
13798
14275
  await agent.initialize();
13799
14276
  }
14277
+ if (sandboxConfig?.pool && sandboxConfig.autoInitPool !== false) {
14278
+ try {
14279
+ await initializeSandboxPool(sandboxConfig.pool);
14280
+ } catch (error) {
14281
+ console.error("[SERVER] Failed to initialize sandbox pool:", error instanceof Error ? error.message : error);
14282
+ }
14283
+ }
13800
14284
  },
13801
14285
  /**
13802
- * Close storage and agents
14286
+ * Close storage, agents, and shutdown sandbox pool
13803
14287
  */
13804
14288
  async close() {
14289
+ await shutdownSandboxPool();
13805
14290
  for (const agent of agents2.values()) {
13806
14291
  await agent.close();
13807
14292
  }
@@ -13833,6 +14318,7 @@ var init_server2 = __esm({
13833
14318
  init_sessions2();
13834
14319
  init_agents2();
13835
14320
  init_skills3();
14321
+ init_sandbox_pool();
13836
14322
  }
13837
14323
  });
13838
14324
 
@@ -16597,6 +17083,7 @@ __export(schema_exports, {
16597
17083
  configDeployments: () => configDeployments,
16598
17084
  configDeploymentsRelations: () => configDeploymentsRelations,
16599
17085
  eventCategoryEnum: () => eventCategoryEnum,
17086
+ eventSourceEnum: () => eventSourceEnum,
16600
17087
  messageRoleEnum: () => messageRoleEnum,
16601
17088
  messages: () => messages,
16602
17089
  messagesRelations: () => messagesRelations,
@@ -16610,7 +17097,7 @@ __export(schema_exports, {
16610
17097
  streamEvents: () => streamEvents,
16611
17098
  streamEventsRelations: () => streamEventsRelations
16612
17099
  });
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;
17100
+ 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
17101
  var init_schema = __esm({
16615
17102
  "src/storage-postgres/schema.ts"() {
16616
17103
  sessionStatusEnum = pgCore.pgEnum("session_status", [
@@ -16652,6 +17139,14 @@ var init_schema = __esm({
16652
17139
  "initial",
16653
17140
  "rollback"
16654
17141
  ]);
17142
+ eventSourceEnum = pgCore.pgEnum("event_source", [
17143
+ "agent",
17144
+ // Directly from the agent SDK
17145
+ "system",
17146
+ // Generated by execution layer (lifecycle, user input)
17147
+ "derived"
17148
+ // Transformed/aggregated from agent events
17149
+ ]);
16655
17150
  eventCategoryEnum = pgCore.pgEnum("event_category", [
16656
17151
  "lifecycle",
16657
17152
  // session_start, session_end, turn_complete
@@ -16842,6 +17337,8 @@ var init_schema = __esm({
16842
17337
  // Aggregation fields (for text_stream events)
16843
17338
  isAggregated: pgCore.boolean("is_aggregated").default(false),
16844
17339
  aggregatedCount: pgCore.integer("aggregated_count"),
17340
+ // Event source tagging
17341
+ eventSource: eventSourceEnum("event_source").notNull().default("agent"),
16845
17342
  // Ordering
16846
17343
  sequenceNumber: pgCore.integer("sequence_number").notNull(),
16847
17344
  createdAt: pgCore.timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
@@ -17434,6 +17931,11 @@ var init_storage2 = __esm({
17434
17931
  drizzleOrm.eq(sessions.status, options.status)
17435
17932
  );
17436
17933
  }
17934
+ if (options.resourceId) {
17935
+ conditions.push(
17936
+ drizzleOrm.sql`${sessions.metadata}->>'resourceId' = ${options.resourceId}`
17937
+ );
17938
+ }
17437
17939
  const orderColumn = options.orderBy === "updatedAt" ? sessions.updatedAt : sessions.createdAt;
17438
17940
  const orderFn = options.order === "asc" ? drizzleOrm.asc : drizzleOrm.desc;
17439
17941
  const limit = options.limit ?? 50;
@@ -18155,6 +18657,9 @@ var init_storage3 = __esm({
18155
18657
  if (options.status) {
18156
18658
  query = query.eq("status", options.status);
18157
18659
  }
18660
+ if (options.resourceId) {
18661
+ query = query.eq("metadata->>resourceId", options.resourceId);
18662
+ }
18158
18663
  const orderColumn = options.orderBy === "updatedAt" ? "updated_at" : "created_at";
18159
18664
  const ascending = options.order === "asc";
18160
18665
  query = query.order(orderColumn, { ascending }).range(offset, offset + limit - 1);
@@ -18512,6 +19017,7 @@ var init_storage3 = __esm({
18512
19017
  sessionId: row.session_id,
18513
19018
  eventType: row.event_type,
18514
19019
  category: row.category,
19020
+ eventSource: row.event_source,
18515
19021
  startedAt: new Date(row.started_at),
18516
19022
  endedAt: row.ended_at ? new Date(row.ended_at) : void 0,
18517
19023
  durationMs: row.duration_ms ?? void 0,
@@ -18534,6 +19040,7 @@ var init_storage3 = __esm({
18534
19040
  session_id: sessionId,
18535
19041
  event_type: event.eventType,
18536
19042
  category: event.category,
19043
+ event_source: event.eventSource || "agent",
18537
19044
  started_at: event.startedAt.toISOString(),
18538
19045
  ended_at: event.endedAt?.toISOString() ?? null,
18539
19046
  duration_ms: event.durationMs ?? null,
@@ -18560,6 +19067,9 @@ var init_storage3 = __esm({
18560
19067
  if (options.eventType) {
18561
19068
  query = query.eq("event_type", options.eventType);
18562
19069
  }
19070
+ if (options.eventSource) {
19071
+ query = query.eq("event_source", options.eventSource);
19072
+ }
18563
19073
  if (options.filePath) {
18564
19074
  query = query.or(
18565
19075
  `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 +19406,8 @@ var init_storage4 = __esm({
18896
19406
  limit: options?.limit,
18897
19407
  offset: options?.offset,
18898
19408
  agentName: options?.agentName,
18899
- status: options?.status
19409
+ status: options?.status,
19410
+ resourceId: options?.resourceId
18900
19411
  }
18901
19412
  }
18902
19413
  );
@@ -19642,6 +20153,7 @@ exports.loadWorkspaceState = loadWorkspaceState;
19642
20153
  exports.mapClaudeOptionsToGemini = mapClaudeOptionsToGemini;
19643
20154
  exports.mapToolToActionType = mapToolToActionType;
19644
20155
  exports.markConfigInstalled = markConfigInstalled;
20156
+ exports.markInstallScriptRan = markInstallScriptRan;
19645
20157
  exports.markSdkInstalled = markSdkInstalled;
19646
20158
  exports.markStartupScriptRan = markStartupScriptRan;
19647
20159
  exports.mcpAuthToHeaders = mcpAuthToHeaders;
@@ -19669,6 +20181,7 @@ exports.sseMcpWithAuth = sseMcpWithAuth;
19669
20181
  exports.startServer = startServer;
19670
20182
  exports.streamEventRelay = streamEventRelay;
19671
20183
  exports.updateToolCallWithResult = updateToolCallWithResult;
20184
+ exports.warmSandboxForSession = warmSandboxForSession;
19672
20185
  exports.writeFileToSandbox = writeFileToSandbox;
19673
20186
  //# sourceMappingURL=index.cjs.map
19674
20187
  //# sourceMappingURL=index.cjs.map