@ash-cloud/ash-ai 0.1.18 → 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
@@ -4639,6 +4639,8 @@ echo "[warmup] Warmup complete!"
4639
4639
  lastMaintenanceAt = null;
4640
4640
  metricsCallback;
4641
4641
  startPromise = null;
4642
+ /** Registered warmup specs by tag (e.g. agentId -> spec) */
4643
+ warmupSpecs = /* @__PURE__ */ new Map();
4642
4644
  /** Consecutive warmup failure count (reset on success) */
4643
4645
  consecutiveFailures = 0;
4644
4646
  /** Timestamp of last warmup attempt — used for backoff */
@@ -4695,11 +4697,13 @@ echo "[warmup] Warmup complete!"
4695
4697
  if (this.maintenanceIntervalId.unref) {
4696
4698
  this.maintenanceIntervalId.unref();
4697
4699
  }
4700
+ const initialSpecs = this.selectSpecsForReplenishment(this.config.minPoolSize);
4698
4701
  console.log(`[POOL] Spawning ${this.config.minPoolSize} initial sandbox(es)...`);
4699
4702
  const warmupPromises = [];
4700
4703
  for (let i = 0; i < this.config.minPoolSize; i++) {
4704
+ const spec = initialSpecs[i];
4701
4705
  warmupPromises.push(
4702
- this.warmSandbox().then((sandbox) => {
4706
+ this.warmSandbox(spec).then((sandbox) => {
4703
4707
  this.pool.set(sandbox.sandboxId, sandbox);
4704
4708
  console.log(`[POOL] Initial sandbox ready: ${sandbox.sandboxId}`);
4705
4709
  }).catch((error) => {
@@ -4743,9 +4747,11 @@ echo "[warmup] Warmup complete!"
4743
4747
  }
4744
4748
  /**
4745
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.
4746
4752
  * If no eligible sandbox is available, creates one on-demand.
4747
4753
  */
4748
- async acquire(sessionId) {
4754
+ async acquire(sessionId, preferTag) {
4749
4755
  if (!this.running) {
4750
4756
  throw new Error("Sandbox pool is not running");
4751
4757
  }
@@ -4755,26 +4761,34 @@ echo "[warmup] Warmup complete!"
4755
4761
  return pooled;
4756
4762
  }
4757
4763
  }
4758
- const available = this.getAvailableSandbox();
4764
+ const available = this.getAvailableSandbox(preferTag);
4759
4765
  if (available) {
4760
4766
  available.assignedTo = sessionId;
4761
- 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}`);
4762
4769
  this.emitMetric("sandbox_assigned", {
4763
4770
  sandboxId: available.sandboxId,
4764
4771
  sessionId,
4765
- poolAvailable: this.getAvailableCount()
4772
+ poolAvailable: this.getAvailableCount(),
4773
+ warmupTag: available.warmupTag,
4774
+ agentSetupComplete: available.agentSetupComplete,
4775
+ preferTag
4766
4776
  });
4767
4777
  this.triggerReplenishment();
4768
4778
  return available;
4769
4779
  }
4770
- console.log(`[POOL] No available sandbox, creating on-demand for session ${sessionId}...`);
4771
- 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);
4772
4783
  sandbox.assignedTo = sessionId;
4773
4784
  this.pool.set(sandbox.sandboxId, sandbox);
4774
4785
  this.emitMetric("sandbox_assigned", {
4775
4786
  sandboxId: sandbox.sandboxId,
4776
4787
  sessionId,
4777
- onDemand: true
4788
+ onDemand: true,
4789
+ warmupTag: sandbox.warmupTag,
4790
+ agentSetupComplete: sandbox.agentSetupComplete,
4791
+ preferTag
4778
4792
  });
4779
4793
  return sandbox;
4780
4794
  }
@@ -4805,11 +4819,14 @@ echo "[warmup] Warmup complete!"
4805
4819
  let available = 0;
4806
4820
  let assigned = 0;
4807
4821
  let ineligible = 0;
4822
+ const availableByTag = {};
4808
4823
  for (const pooled of this.pool.values()) {
4809
4824
  if (pooled.assignedTo) {
4810
4825
  assigned++;
4811
4826
  } else if (pooled.eligible) {
4812
4827
  available++;
4828
+ const tagKey = pooled.warmupTag || "generic";
4829
+ availableByTag[tagKey] = (availableByTag[tagKey] || 0) + 1;
4813
4830
  } else {
4814
4831
  ineligible++;
4815
4832
  }
@@ -4824,6 +4841,8 @@ echo "[warmup] Warmup complete!"
4824
4841
  lastMaintenanceAt: this.lastMaintenanceAt,
4825
4842
  consecutiveFailures: this.consecutiveFailures,
4826
4843
  warmupSuspended: this.consecutiveFailures >= _SandboxPool.MAX_CONSECUTIVE_FAILURES,
4844
+ registeredSpecs: this.warmupSpecs.size,
4845
+ availableByTag,
4827
4846
  config: {
4828
4847
  minPoolSize: this.config.minPoolSize,
4829
4848
  maxPoolSize: this.config.maxPoolSize,
@@ -4838,13 +4857,51 @@ echo "[warmup] Warmup complete!"
4838
4857
  onMetrics(callback) {
4839
4858
  this.metricsCallback = callback;
4840
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
+ }
4841
4896
  // ===========================================================================
4842
4897
  // PRIVATE METHODS
4843
4898
  // ===========================================================================
4844
4899
  /**
4845
- * 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).
4846
4903
  */
4847
- async warmSandbox() {
4904
+ async warmSandbox(spec) {
4848
4905
  const warmupId = `warming-${Date.now()}-${Math.random().toString(36).slice(2)}`;
4849
4906
  this.warmingInProgress.add(warmupId);
4850
4907
  this.emitMetric("warmup_started", { warmupId });
@@ -4938,6 +4995,37 @@ echo "[warmup] Warmup complete!"
4938
4995
  throw new Error(`Warmup failed: ${stderr}`);
4939
4996
  }
4940
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
+ }
4941
5029
  const warmupTime = Date.now() - startTime;
4942
5030
  const now = Date.now();
4943
5031
  const pooled = {
@@ -4947,14 +5035,19 @@ echo "[warmup] Warmup complete!"
4947
5035
  expiresAt: now + this.config.sandboxTimeout * 1e3,
4948
5036
  sdkInstalled: true,
4949
5037
  eligible: true,
4950
- lastHeartbeat: now
5038
+ lastHeartbeat: now,
5039
+ warmupTag,
5040
+ agentSetupComplete
4951
5041
  };
4952
- 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}`);
4953
5044
  this.consecutiveFailures = 0;
4954
5045
  this.emitMetric("warmup_completed", {
4955
5046
  sandboxId: pooled.sandboxId,
4956
5047
  warmupTimeMs: warmupTime,
4957
- usedTarball: useTarball
5048
+ usedTarball: useTarball,
5049
+ warmupTag,
5050
+ agentSetupComplete
4958
5051
  });
4959
5052
  this.emitMetric("sandbox_created", { sandboxId: pooled.sandboxId });
4960
5053
  return pooled;
@@ -5035,18 +5128,29 @@ echo "[warmup] Warmup complete!"
5035
5128
  }
5036
5129
  }
5037
5130
  /**
5038
- * 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)
5039
5136
  */
5040
- getAvailableSandbox() {
5041
- let best = null;
5137
+ getAvailableSandbox(preferTag) {
5138
+ let bestTagged = null;
5139
+ let bestGeneric = null;
5042
5140
  for (const pooled of this.pool.values()) {
5043
5141
  if (!pooled.assignedTo && pooled.eligible) {
5044
- if (!best || pooled.expiresAt > best.expiresAt) {
5045
- 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
+ }
5046
5150
  }
5047
5151
  }
5048
5152
  }
5049
- return best;
5153
+ return bestTagged || bestGeneric;
5050
5154
  }
5051
5155
  /**
5052
5156
  * Get count of available sandboxes
@@ -5113,13 +5217,16 @@ echo "[warmup] Warmup complete!"
5113
5217
  this.config.maxPoolSize - this.pool.size - warmingCount
5114
5218
  );
5115
5219
  if (needed <= 0) return;
5220
+ const specAssignments = this.selectSpecsForReplenishment(needed);
5116
5221
  console.log(`[POOL] Spawning ${needed} sandbox(es) to maintain pool...`);
5117
5222
  const promises = [];
5118
5223
  for (let i = 0; i < needed; i++) {
5224
+ const spec = specAssignments[i];
5119
5225
  promises.push(
5120
- this.warmSandbox().then((sandbox) => {
5226
+ this.warmSandbox(spec).then((sandbox) => {
5121
5227
  this.pool.set(sandbox.sandboxId, sandbox);
5122
- 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}`);
5123
5230
  }).catch((error) => {
5124
5231
  console.error("[POOL] Failed to warm replenishment sandbox:", error);
5125
5232
  })
@@ -5130,6 +5237,39 @@ echo "[warmup] Warmup complete!"
5130
5237
  }
5131
5238
  await Promise.all(promises);
5132
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
+ }
5133
5273
  /**
5134
5274
  * Destroy a sandbox and clean up
5135
5275
  */
@@ -5361,7 +5501,7 @@ function removeExpiredSandbox(sessionId) {
5361
5501
  }
5362
5502
  }
5363
5503
  async function getOrCreateSandbox(options) {
5364
- const { sessionId, runtime = "node22", timeout = 300, vcpus = 4, existingSandboxId } = options;
5504
+ const { sessionId, runtime = "node22", timeout = 300, vcpus = 4, existingSandboxId, preferTag } = options;
5365
5505
  ensureCleanupRunning();
5366
5506
  ensureHeartbeatRunning();
5367
5507
  const { Sandbox } = await import('@vercel/sandbox');
@@ -5378,6 +5518,8 @@ async function getOrCreateSandbox(options) {
5378
5518
  sdkInstalled: cached.sdkInstalled,
5379
5519
  startupScriptRan: cached.startupScriptRan,
5380
5520
  startupScriptHash: cached.startupScriptHash,
5521
+ installScriptRan: cached.installScriptRan,
5522
+ installScriptHash: cached.installScriptHash,
5381
5523
  isNew: false,
5382
5524
  configFileUrl: cached.configFileUrl,
5383
5525
  configInstalledAt: cached.configInstalledAt
@@ -5386,41 +5528,6 @@ async function getOrCreateSandbox(options) {
5386
5528
  console.log("[SANDBOX] Cached sandbox has expired (HTTP 410 or similar), creating new sandbox");
5387
5529
  removeExpiredSandbox(sessionId);
5388
5530
  }
5389
- const pool = await ensureSandboxPoolInitialized();
5390
- if (pool && pool.isRunning()) {
5391
- try {
5392
- console.log("[SANDBOX] Attempting to acquire from pre-warmed pool...");
5393
- const pooled = await pool.acquire(sessionId);
5394
- console.log("[SANDBOX] Acquired pre-warmed sandbox:", pooled.sandboxId);
5395
- const now2 = Date.now();
5396
- const entry2 = {
5397
- sandbox: pooled.sandbox,
5398
- sessionId,
5399
- createdAt: pooled.createdAt,
5400
- lastUsedAt: now2,
5401
- sdkInstalled: pooled.sdkInstalled,
5402
- startupScriptRan: false
5403
- // User script hasn't run yet
5404
- };
5405
- sandboxCache.set(sessionId, entry2);
5406
- return {
5407
- sandbox: pooled.sandbox,
5408
- sandboxId: pooled.sandboxId,
5409
- sdkInstalled: pooled.sdkInstalled,
5410
- startupScriptRan: false,
5411
- startupScriptHash: void 0,
5412
- isNew: false,
5413
- // Not new - came from pool
5414
- configFileUrl: void 0,
5415
- configInstalledAt: void 0
5416
- };
5417
- } catch (error) {
5418
- console.warn(
5419
- "[SANDBOX] Failed to acquire from pool, falling back to on-demand creation:",
5420
- error instanceof Error ? error.message : "Unknown error"
5421
- );
5422
- }
5423
- }
5424
5531
  if (existingSandboxId) {
5425
5532
  console.log("[SANDBOX] Attempting to reconnect to existing sandbox:", existingSandboxId);
5426
5533
  try {
@@ -5441,7 +5548,9 @@ async function getOrCreateSandbox(options) {
5441
5548
  // We assume SDK is installed since this is an existing sandbox
5442
5549
  // The caller can verify and re-mark if needed
5443
5550
  sdkInstalled: true,
5444
- startupScriptRan: true
5551
+ startupScriptRan: true,
5552
+ installScriptRan: true
5553
+ // Assume ran for reconnected sandboxes
5445
5554
  };
5446
5555
  sandboxCache.set(sessionId, entry2);
5447
5556
  return {
@@ -5453,13 +5562,17 @@ async function getOrCreateSandbox(options) {
5453
5562
  // Assume ran for reconnected sandboxes
5454
5563
  startupScriptHash: void 0,
5455
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
5456
5569
  isNew: false,
5457
5570
  configFileUrl: void 0,
5458
5571
  configInstalledAt: now2
5459
5572
  // Assume config was installed — prevents unnecessary re-install on reconnection
5460
5573
  };
5461
5574
  } else {
5462
- 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");
5463
5576
  }
5464
5577
  } catch (error) {
5465
5578
  console.log(
@@ -5469,6 +5582,47 @@ async function getOrCreateSandbox(options) {
5469
5582
  );
5470
5583
  }
5471
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
+ }
5472
5626
  console.log("[SANDBOX] Creating new sandbox for session:", sessionId);
5473
5627
  const baseTarballUrl = process.env.SANDBOX_BASE_TARBALL_URL;
5474
5628
  const useTarball = !!baseTarballUrl;
@@ -5502,7 +5656,8 @@ async function getOrCreateSandbox(options) {
5502
5656
  lastUsedAt: now,
5503
5657
  // If we used tarball, SDK is pre-installed
5504
5658
  sdkInstalled: useTarball,
5505
- startupScriptRan: false
5659
+ startupScriptRan: false,
5660
+ installScriptRan: false
5506
5661
  };
5507
5662
  sandboxCache.set(sessionId, entry);
5508
5663
  return {
@@ -5512,6 +5667,8 @@ async function getOrCreateSandbox(options) {
5512
5667
  sdkInstalled: useTarball,
5513
5668
  startupScriptRan: false,
5514
5669
  startupScriptHash: void 0,
5670
+ installScriptRan: false,
5671
+ installScriptHash: void 0,
5515
5672
  isNew: true,
5516
5673
  configFileUrl: void 0,
5517
5674
  configInstalledAt: void 0
@@ -5551,6 +5708,14 @@ function markStartupScriptRan(sessionId, scriptHash) {
5551
5708
  cached.lastUsedAt = Date.now();
5552
5709
  }
5553
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
+ }
5554
5719
  function needsStartupScriptRerun(sessionId, newScript) {
5555
5720
  const cached = sandboxCache.get(sessionId);
5556
5721
  if (!cached) return true;
@@ -5618,6 +5783,8 @@ function getCachedSandbox(sessionId) {
5618
5783
  sdkInstalled: cached.sdkInstalled,
5619
5784
  startupScriptRan: cached.startupScriptRan,
5620
5785
  startupScriptHash: cached.startupScriptHash,
5786
+ installScriptRan: cached.installScriptRan,
5787
+ installScriptHash: cached.installScriptHash,
5621
5788
  isNew: false,
5622
5789
  configFileUrl: cached.configFileUrl,
5623
5790
  configInstalledAt: cached.configInstalledAt
@@ -19986,6 +20153,7 @@ exports.loadWorkspaceState = loadWorkspaceState;
19986
20153
  exports.mapClaudeOptionsToGemini = mapClaudeOptionsToGemini;
19987
20154
  exports.mapToolToActionType = mapToolToActionType;
19988
20155
  exports.markConfigInstalled = markConfigInstalled;
20156
+ exports.markInstallScriptRan = markInstallScriptRan;
19989
20157
  exports.markSdkInstalled = markSdkInstalled;
19990
20158
  exports.markStartupScriptRan = markStartupScriptRan;
19991
20159
  exports.mcpAuthToHeaders = mcpAuthToHeaders;