@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 +228 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -5
- package/dist/index.d.ts +72 -5
- package/dist/index.js +228 -61
- package/dist/index.js.map +1 -1
- package/dist/playground/components/NormalizedMessageList.d.ts +7 -1
- package/dist/playground/components/NormalizedMessageList.d.ts.map +1 -1
- package/dist/playground.js +1241 -1181
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
4771
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
5045
|
-
|
|
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
|
|
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
|
-
|
|
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;
|