@ash-cloud/ash-ai 0.1.17 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +365 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +151 -8
- package/dist/index.d.ts +151 -8
- package/dist/index.js +365 -21
- package/dist/index.js.map +1 -1
- package/dist/{schema-dr_7pxIB.d.cts → schema-CAoHu2Rx.d.cts} +21 -2
- package/dist/{schema-dr_7pxIB.d.ts → schema-CAoHu2Rx.d.ts} +21 -2
- package/dist/schema.cjs +11 -0
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +11 -1
- package/dist/schema.js.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
4545
|
+
if (process.env.SANDBOX_POOL_ENABLED === "true") {
|
|
4546
|
+
return true;
|
|
4547
|
+
}
|
|
4548
|
+
return !!process.env.VERCEL;
|
|
4536
4549
|
}
|
|
4537
4550
|
async function ensureSandboxPoolInitialized() {
|
|
4538
4551
|
if (globalPool && globalPool.isRunning()) {
|
|
@@ -4617,7 +4630,7 @@ node -e "require('@anthropic-ai/claude-agent-sdk'); console.log('[warmup] SDK ve
|
|
|
4617
4630
|
|
|
4618
4631
|
echo "[warmup] Warmup complete!"
|
|
4619
4632
|
`;
|
|
4620
|
-
exports.SandboxPool = class {
|
|
4633
|
+
exports.SandboxPool = class _SandboxPool {
|
|
4621
4634
|
config;
|
|
4622
4635
|
pool = /* @__PURE__ */ new Map();
|
|
4623
4636
|
warmingInProgress = /* @__PURE__ */ new Set();
|
|
@@ -4626,6 +4639,14 @@ echo "[warmup] Warmup complete!"
|
|
|
4626
4639
|
lastMaintenanceAt = null;
|
|
4627
4640
|
metricsCallback;
|
|
4628
4641
|
startPromise = null;
|
|
4642
|
+
/** Consecutive warmup failure count (reset on success) */
|
|
4643
|
+
consecutiveFailures = 0;
|
|
4644
|
+
/** Timestamp of last warmup attempt — used for backoff */
|
|
4645
|
+
lastWarmupAttemptAt = 0;
|
|
4646
|
+
/** Max consecutive failures before stopping attempts entirely until manual restart */
|
|
4647
|
+
static MAX_CONSECUTIVE_FAILURES = 10;
|
|
4648
|
+
/** Base backoff delay in ms (doubles each failure: 30s, 60s, 120s, 240s…) */
|
|
4649
|
+
static BACKOFF_BASE_MS = 3e4;
|
|
4629
4650
|
constructor(config = {}) {
|
|
4630
4651
|
this.config = {
|
|
4631
4652
|
minPoolSize: config.minPoolSize ?? parseInt(process.env.SANDBOX_POOL_MIN_SIZE ?? "2"),
|
|
@@ -4801,6 +4822,8 @@ echo "[warmup] Warmup complete!"
|
|
|
4801
4822
|
warming: this.warmingInProgress.size,
|
|
4802
4823
|
running: this.running,
|
|
4803
4824
|
lastMaintenanceAt: this.lastMaintenanceAt,
|
|
4825
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
4826
|
+
warmupSuspended: this.consecutiveFailures >= _SandboxPool.MAX_CONSECUTIVE_FAILURES,
|
|
4804
4827
|
config: {
|
|
4805
4828
|
minPoolSize: this.config.minPoolSize,
|
|
4806
4829
|
maxPoolSize: this.config.maxPoolSize,
|
|
@@ -4826,6 +4849,7 @@ echo "[warmup] Warmup complete!"
|
|
|
4826
4849
|
this.warmingInProgress.add(warmupId);
|
|
4827
4850
|
this.emitMetric("warmup_started", { warmupId });
|
|
4828
4851
|
const startTime = Date.now();
|
|
4852
|
+
this.lastWarmupAttemptAt = startTime;
|
|
4829
4853
|
const useTarball = !!this.config.baseTarballUrl;
|
|
4830
4854
|
try {
|
|
4831
4855
|
const { Sandbox } = await import('@vercel/sandbox');
|
|
@@ -4926,6 +4950,7 @@ echo "[warmup] Warmup complete!"
|
|
|
4926
4950
|
lastHeartbeat: now
|
|
4927
4951
|
};
|
|
4928
4952
|
console.log(`[POOL] Warmup completed for ${sandbox.sandboxId} (took ${warmupTime}ms)${useTarball ? " [tarball]" : ""}`);
|
|
4953
|
+
this.consecutiveFailures = 0;
|
|
4929
4954
|
this.emitMetric("warmup_completed", {
|
|
4930
4955
|
sandboxId: pooled.sandboxId,
|
|
4931
4956
|
warmupTimeMs: warmupTime,
|
|
@@ -4935,10 +4960,20 @@ echo "[warmup] Warmup complete!"
|
|
|
4935
4960
|
return pooled;
|
|
4936
4961
|
} catch (error) {
|
|
4937
4962
|
const warmupTime = Date.now() - startTime;
|
|
4963
|
+
this.consecutiveFailures++;
|
|
4964
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown";
|
|
4965
|
+
if (this.consecutiveFailures === 1) {
|
|
4966
|
+
console.error(`[POOL] Warmup failed: ${errorMessage}`);
|
|
4967
|
+
} else {
|
|
4968
|
+
console.error(
|
|
4969
|
+
`[POOL] Warmup failed (${this.consecutiveFailures} consecutive). Next attempt in ${Math.min(_SandboxPool.BACKOFF_BASE_MS * Math.pow(2, this.consecutiveFailures - 1), 3e5) / 1e3}s. Error: ${errorMessage}`
|
|
4970
|
+
);
|
|
4971
|
+
}
|
|
4938
4972
|
this.emitMetric("warmup_failed", {
|
|
4939
|
-
error:
|
|
4973
|
+
error: errorMessage,
|
|
4940
4974
|
warmupTimeMs: warmupTime,
|
|
4941
|
-
usedTarball: useTarball
|
|
4975
|
+
usedTarball: useTarball,
|
|
4976
|
+
consecutiveFailures: this.consecutiveFailures
|
|
4942
4977
|
});
|
|
4943
4978
|
throw error;
|
|
4944
4979
|
} finally {
|
|
@@ -5046,9 +5081,30 @@ echo "[warmup] Warmup complete!"
|
|
|
5046
5081
|
});
|
|
5047
5082
|
}
|
|
5048
5083
|
/**
|
|
5049
|
-
* Replenish pool if below min size
|
|
5084
|
+
* Replenish pool if below min size.
|
|
5085
|
+
* Applies exponential backoff when warmups keep failing to avoid
|
|
5086
|
+
* a tight failure loop that wastes resources and floods metrics.
|
|
5050
5087
|
*/
|
|
5051
5088
|
async replenishPool() {
|
|
5089
|
+
if (this.consecutiveFailures >= _SandboxPool.MAX_CONSECUTIVE_FAILURES) {
|
|
5090
|
+
if (Math.random() < 0.2) {
|
|
5091
|
+
console.warn(
|
|
5092
|
+
`[POOL] Warmup suspended after ${this.consecutiveFailures} consecutive failures. Check SANDBOX_BASE_TARBALL_URL or Vercel API status. Pool will retry on next acquire().`
|
|
5093
|
+
);
|
|
5094
|
+
}
|
|
5095
|
+
return;
|
|
5096
|
+
}
|
|
5097
|
+
if (this.consecutiveFailures > 0) {
|
|
5098
|
+
const backoffMs = Math.min(
|
|
5099
|
+
_SandboxPool.BACKOFF_BASE_MS * Math.pow(2, this.consecutiveFailures - 1),
|
|
5100
|
+
5 * 6e4
|
|
5101
|
+
// Cap at 5 minutes
|
|
5102
|
+
);
|
|
5103
|
+
const timeSinceLastAttempt = Date.now() - this.lastWarmupAttemptAt;
|
|
5104
|
+
if (timeSinceLastAttempt < backoffMs) {
|
|
5105
|
+
return;
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5052
5108
|
const eligibleCount = this.getEligibleCount();
|
|
5053
5109
|
const warmingCount = this.warmingInProgress.size;
|
|
5054
5110
|
const totalProjected = eligibleCount + warmingCount;
|
|
@@ -6154,6 +6210,85 @@ SCRIPT_EOF`]
|
|
|
6154
6210
|
}
|
|
6155
6211
|
}
|
|
6156
6212
|
}
|
|
6213
|
+
async function warmSandboxForSession(options) {
|
|
6214
|
+
const {
|
|
6215
|
+
sessionId,
|
|
6216
|
+
runtime = "node22",
|
|
6217
|
+
timeout = 600,
|
|
6218
|
+
vcpus = 4,
|
|
6219
|
+
startupScript,
|
|
6220
|
+
configFileUrl,
|
|
6221
|
+
envVars = {}
|
|
6222
|
+
} = options;
|
|
6223
|
+
console.log(`[WARM] Pre-warming sandbox for session: ${sessionId}`);
|
|
6224
|
+
const startTime = Date.now();
|
|
6225
|
+
const result = await getOrCreateSandbox({
|
|
6226
|
+
sessionId,
|
|
6227
|
+
runtime,
|
|
6228
|
+
timeout,
|
|
6229
|
+
vcpus
|
|
6230
|
+
});
|
|
6231
|
+
const { sandbox, sdkInstalled } = result;
|
|
6232
|
+
if (!sdkInstalled) {
|
|
6233
|
+
console.log("[WARM] Installing SDK...");
|
|
6234
|
+
await sandbox.runCommand({
|
|
6235
|
+
cmd: "bash",
|
|
6236
|
+
args: ["-c", "which jq || (sudo dnf install -y jq 2>/dev/null || sudo apt-get update && sudo apt-get install -y jq 2>/dev/null || sudo apk add jq 2>/dev/null) || true"]
|
|
6237
|
+
});
|
|
6238
|
+
await sandbox.runCommand({
|
|
6239
|
+
cmd: "bash",
|
|
6240
|
+
args: ["-c", "which ffmpeg || (sudo dnf install -y ffmpeg 2>/dev/null || sudo apt-get update && sudo apt-get install -y ffmpeg 2>/dev/null || sudo apk add ffmpeg 2>/dev/null) || true"]
|
|
6241
|
+
});
|
|
6242
|
+
const sdkResult = await sandbox.runCommand({
|
|
6243
|
+
cmd: "bash",
|
|
6244
|
+
args: ["-c", "npm init -y && npm install @anthropic-ai/claude-agent-sdk"],
|
|
6245
|
+
env: envVars
|
|
6246
|
+
});
|
|
6247
|
+
if (sdkResult.exitCode !== 0) {
|
|
6248
|
+
const stderr = await sdkResult.stderr();
|
|
6249
|
+
throw new Error(`Failed to install Claude Agent SDK during warmup: ${stderr}`);
|
|
6250
|
+
}
|
|
6251
|
+
markSdkInstalled(sessionId);
|
|
6252
|
+
}
|
|
6253
|
+
if (startupScript) {
|
|
6254
|
+
console.log("[WARM] Running startup script...");
|
|
6255
|
+
const scriptResult = await sandbox.runCommand({
|
|
6256
|
+
cmd: "bash",
|
|
6257
|
+
args: ["-c", startupScript],
|
|
6258
|
+
env: envVars
|
|
6259
|
+
});
|
|
6260
|
+
if (scriptResult.exitCode !== 0) {
|
|
6261
|
+
const stderr = await scriptResult.stderr();
|
|
6262
|
+
throw new Error(`Startup script failed during warmup: ${stderr}`);
|
|
6263
|
+
}
|
|
6264
|
+
markStartupScriptRan(sessionId, hashStartupScript(startupScript));
|
|
6265
|
+
}
|
|
6266
|
+
if (configFileUrl) {
|
|
6267
|
+
console.log("[WARM] Installing config from:", configFileUrl);
|
|
6268
|
+
const configScript = `
|
|
6269
|
+
set -e
|
|
6270
|
+
curl -sSL --fail "${configFileUrl}" -o /tmp/config.zip
|
|
6271
|
+
unzip -o /tmp/config.zip -d .
|
|
6272
|
+
rm -f /tmp/config.zip
|
|
6273
|
+
`;
|
|
6274
|
+
const configResult = await sandbox.runCommand({
|
|
6275
|
+
cmd: "bash",
|
|
6276
|
+
args: ["-c", configScript],
|
|
6277
|
+
env: envVars
|
|
6278
|
+
});
|
|
6279
|
+
if (configResult.exitCode !== 0) {
|
|
6280
|
+
const stderr = await configResult.stderr();
|
|
6281
|
+
throw new Error(`Config installation failed during warmup: ${stderr}`);
|
|
6282
|
+
}
|
|
6283
|
+
markConfigInstalled(sessionId, configFileUrl);
|
|
6284
|
+
}
|
|
6285
|
+
const duration = Date.now() - startTime;
|
|
6286
|
+
console.log(`[WARM] Sandbox pre-warmed for session ${sessionId} in ${duration}ms (sandbox: ${result.sandboxId})`);
|
|
6287
|
+
return {
|
|
6288
|
+
sandboxId: result.sandboxId,
|
|
6289
|
+
ready: true
|
|
6290
|
+
};
|
|
6291
|
+
}
|
|
6157
6292
|
var sandboxCache, DEFAULT_IDLE_TTL, CLEANUP_INTERVAL, HEARTBEAT_INTERVAL, cleanupIntervalId, heartbeatIntervalId, GLOBAL_HEARTBEAT_KEY, heartbeatListeners;
|
|
6158
6293
|
var init_vercel_sandbox_executor = __esm({
|
|
6159
6294
|
"src/runtime/vercel-sandbox-executor.ts"() {
|
|
@@ -6808,6 +6943,31 @@ WATCHER_EOF`;
|
|
|
6808
6943
|
globalInSandboxManager = null;
|
|
6809
6944
|
}
|
|
6810
6945
|
});
|
|
6946
|
+
function matchGlob(pattern, filePath) {
|
|
6947
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
6948
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
6949
|
+
let regexStr = "";
|
|
6950
|
+
let i = 0;
|
|
6951
|
+
while (i < normalizedPattern.length) {
|
|
6952
|
+
if (normalizedPattern[i] === "*" && normalizedPattern[i + 1] === "*") {
|
|
6953
|
+
if (normalizedPattern[i + 2] === "/") {
|
|
6954
|
+
regexStr += "(?:.*/)?";
|
|
6955
|
+
i += 3;
|
|
6956
|
+
} else {
|
|
6957
|
+
regexStr += ".*";
|
|
6958
|
+
i += 2;
|
|
6959
|
+
}
|
|
6960
|
+
} else if (normalizedPattern[i] === "*") {
|
|
6961
|
+
regexStr += "[^/]*";
|
|
6962
|
+
i += 1;
|
|
6963
|
+
} else {
|
|
6964
|
+
const ch = normalizedPattern[i];
|
|
6965
|
+
regexStr += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6966
|
+
i += 1;
|
|
6967
|
+
}
|
|
6968
|
+
}
|
|
6969
|
+
return new RegExp(`^${regexStr}$`).test(normalizedPath);
|
|
6970
|
+
}
|
|
6811
6971
|
function extractErrorMessage(error) {
|
|
6812
6972
|
if (error === null || error === void 0) {
|
|
6813
6973
|
return "Unknown error (null/undefined)";
|
|
@@ -6850,12 +7010,18 @@ function extractErrorMessage(error) {
|
|
|
6850
7010
|
function createSandboxFileSync(options) {
|
|
6851
7011
|
return new exports.SandboxFileSync(options);
|
|
6852
7012
|
}
|
|
6853
|
-
exports.SandboxFileSync = void 0;
|
|
7013
|
+
var DEFAULT_WEBHOOK_IGNORE; exports.SandboxFileSync = void 0;
|
|
6854
7014
|
var init_sandbox_file_sync = __esm({
|
|
6855
7015
|
"src/runtime/sandbox-file-sync.ts"() {
|
|
6856
7016
|
init_sandbox_file_watcher();
|
|
6857
7017
|
init_in_sandbox_watcher();
|
|
6858
7018
|
init_types();
|
|
7019
|
+
DEFAULT_WEBHOOK_IGNORE = [
|
|
7020
|
+
"**/agent-runner.js",
|
|
7021
|
+
"**/agent-runner.cjs",
|
|
7022
|
+
"**/.file-watcher.js",
|
|
7023
|
+
"**/.file-watcher-output.log"
|
|
7024
|
+
];
|
|
6859
7025
|
exports.SandboxFileSync = class {
|
|
6860
7026
|
fileStore;
|
|
6861
7027
|
sandboxBasePath;
|
|
@@ -6870,6 +7036,10 @@ var init_sandbox_file_sync = __esm({
|
|
|
6870
7036
|
fileChangeSubscribers = /* @__PURE__ */ new Set();
|
|
6871
7037
|
// Sequence number cache per session (for event storage)
|
|
6872
7038
|
sequenceNumbers = /* @__PURE__ */ new Map();
|
|
7039
|
+
// Webhook batching state
|
|
7040
|
+
batchTimer = null;
|
|
7041
|
+
batchQueue = /* @__PURE__ */ new Map();
|
|
7042
|
+
// keyed by file path for dedup
|
|
6873
7043
|
constructor(options) {
|
|
6874
7044
|
this.fileStore = options.fileStore;
|
|
6875
7045
|
this.sandboxBasePath = options.sandboxBasePath ?? ".claude/files";
|
|
@@ -6904,6 +7074,72 @@ var init_sandbox_file_sync = __esm({
|
|
|
6904
7074
|
*/
|
|
6905
7075
|
removeWebhook() {
|
|
6906
7076
|
this.webhookConfig = void 0;
|
|
7077
|
+
if (this.batchTimer) {
|
|
7078
|
+
clearTimeout(this.batchTimer);
|
|
7079
|
+
this.batchTimer = null;
|
|
7080
|
+
}
|
|
7081
|
+
this.batchQueue.clear();
|
|
7082
|
+
}
|
|
7083
|
+
/**
|
|
7084
|
+
* Check if a file path should be excluded from webhook notifications.
|
|
7085
|
+
* Tests against DEFAULT_WEBHOOK_IGNORE patterns and any user-configured ignorePaths.
|
|
7086
|
+
*/
|
|
7087
|
+
shouldIgnorePath(filePath) {
|
|
7088
|
+
if (!filePath) return false;
|
|
7089
|
+
const userPatterns = this.webhookConfig?.ignorePaths ?? [];
|
|
7090
|
+
const allPatterns = [...DEFAULT_WEBHOOK_IGNORE, ...userPatterns];
|
|
7091
|
+
return allPatterns.some((pattern) => matchGlob(pattern, filePath));
|
|
7092
|
+
}
|
|
7093
|
+
/**
|
|
7094
|
+
* Route a webhook payload through batching (if configured) or send immediately.
|
|
7095
|
+
* When batchWindowMs is set, payloads are queued and deduplicated by file path.
|
|
7096
|
+
*/
|
|
7097
|
+
enqueueOrSendWebhook(payload, sessionId) {
|
|
7098
|
+
const batchWindowMs = this.webhookConfig?.batchWindowMs;
|
|
7099
|
+
if (!batchWindowMs || batchWindowMs <= 0) {
|
|
7100
|
+
this.sendWebhook(payload, sessionId);
|
|
7101
|
+
return;
|
|
7102
|
+
}
|
|
7103
|
+
const filePath = payload.fileSyncEvent?.canonicalPath ?? payload.fileChangeEvent?.relativePath ?? `_unknown_${Date.now()}`;
|
|
7104
|
+
this.batchQueue.set(filePath, payload);
|
|
7105
|
+
if (this.batchTimer) {
|
|
7106
|
+
clearTimeout(this.batchTimer);
|
|
7107
|
+
}
|
|
7108
|
+
this.batchTimer = setTimeout(() => {
|
|
7109
|
+
this.flushWebhookBatch(sessionId);
|
|
7110
|
+
}, batchWindowMs);
|
|
7111
|
+
}
|
|
7112
|
+
/**
|
|
7113
|
+
* Flush all queued webhook payloads as a single batch request.
|
|
7114
|
+
*/
|
|
7115
|
+
async flushWebhookBatch(sessionId) {
|
|
7116
|
+
this.batchTimer = null;
|
|
7117
|
+
if (this.batchQueue.size === 0) return;
|
|
7118
|
+
const payloads = Array.from(this.batchQueue.values());
|
|
7119
|
+
this.batchQueue.clear();
|
|
7120
|
+
const fileSyncEvents = [];
|
|
7121
|
+
const fileChangeEvents = [];
|
|
7122
|
+
let batchSessionId = sessionId ?? "";
|
|
7123
|
+
for (const p of payloads) {
|
|
7124
|
+
if (!batchSessionId) {
|
|
7125
|
+
batchSessionId = p.sessionId;
|
|
7126
|
+
}
|
|
7127
|
+
if (p.fileSyncEvent) {
|
|
7128
|
+
fileSyncEvents.push(p.fileSyncEvent);
|
|
7129
|
+
}
|
|
7130
|
+
if (p.fileChangeEvent) {
|
|
7131
|
+
fileChangeEvents.push(p.fileChangeEvent);
|
|
7132
|
+
}
|
|
7133
|
+
}
|
|
7134
|
+
const batchPayload = {
|
|
7135
|
+
event: "file_sync_batch",
|
|
7136
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7137
|
+
sessionId: batchSessionId,
|
|
7138
|
+
metadata: this.webhookConfig?.metadata,
|
|
7139
|
+
fileSyncEvents,
|
|
7140
|
+
fileChangeEvents
|
|
7141
|
+
};
|
|
7142
|
+
await this.sendWebhook(batchPayload, sessionId);
|
|
6907
7143
|
}
|
|
6908
7144
|
/**
|
|
6909
7145
|
* Extract hostname from URL for display (security - don't expose full URL)
|
|
@@ -7045,6 +7281,9 @@ var init_sandbox_file_sync = __esm({
|
|
|
7045
7281
|
* pre-signed S3 download URL when the file exists in storage.
|
|
7046
7282
|
*/
|
|
7047
7283
|
async sendFileSyncWebhook(sessionId, event) {
|
|
7284
|
+
if (this.shouldIgnorePath(event.canonicalPath)) {
|
|
7285
|
+
return;
|
|
7286
|
+
}
|
|
7048
7287
|
let downloadUrl;
|
|
7049
7288
|
if (event.success && event.canonicalPath && !event.operation.startsWith("deleted")) {
|
|
7050
7289
|
try {
|
|
@@ -7070,12 +7309,15 @@ var init_sandbox_file_sync = __esm({
|
|
|
7070
7309
|
metadata: this.webhookConfig?.metadata,
|
|
7071
7310
|
fileSyncEvent: webhookEvent
|
|
7072
7311
|
};
|
|
7073
|
-
|
|
7312
|
+
this.enqueueOrSendWebhook(payload, sessionId);
|
|
7074
7313
|
}
|
|
7075
7314
|
/**
|
|
7076
7315
|
* Send a file change event webhook (from watcher)
|
|
7077
7316
|
*/
|
|
7078
7317
|
async sendFileChangeWebhook(event) {
|
|
7318
|
+
if (this.shouldIgnorePath(event.relativePath)) {
|
|
7319
|
+
return;
|
|
7320
|
+
}
|
|
7079
7321
|
const payload = {
|
|
7080
7322
|
event: "file_change",
|
|
7081
7323
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7083,7 +7325,7 @@ var init_sandbox_file_sync = __esm({
|
|
|
7083
7325
|
metadata: this.webhookConfig?.metadata,
|
|
7084
7326
|
fileChangeEvent: event
|
|
7085
7327
|
};
|
|
7086
|
-
|
|
7328
|
+
this.enqueueOrSendWebhook(payload, event.sessionId);
|
|
7087
7329
|
}
|
|
7088
7330
|
/**
|
|
7089
7331
|
* Get the next sequence number for a session
|
|
@@ -7747,6 +7989,11 @@ var init_sandbox_file_sync = __esm({
|
|
|
7747
7989
|
await Promise.all([...inSandboxPromises, ...localPromises]);
|
|
7748
7990
|
this.inSandboxWatchers.clear();
|
|
7749
7991
|
this.localWatchers.clear();
|
|
7992
|
+
if (this.batchTimer) {
|
|
7993
|
+
clearTimeout(this.batchTimer);
|
|
7994
|
+
this.batchTimer = null;
|
|
7995
|
+
await this.flushWebhookBatch();
|
|
7996
|
+
}
|
|
7750
7997
|
}
|
|
7751
7998
|
/**
|
|
7752
7999
|
* Get watching status for all sessions
|
|
@@ -9815,6 +10062,58 @@ function createSessionsRouter(options) {
|
|
|
9815
10062
|
}
|
|
9816
10063
|
});
|
|
9817
10064
|
});
|
|
10065
|
+
router.get("/:sessionId/stream-events", async (c) => {
|
|
10066
|
+
const sessionId = c.req.param("sessionId");
|
|
10067
|
+
const afterSequence = Math.max(0, parseInt(c.req.query("afterSequence") || "0", 10) || 0);
|
|
10068
|
+
const limit = Math.min(Math.max(1, parseInt(c.req.query("limit") || "100", 10) || 100), 1e3);
|
|
10069
|
+
const waitMs = Math.min(Math.max(0, parseInt(c.req.query("waitMs") || "0", 10) || 0), 3e4);
|
|
10070
|
+
const session = await sessionManager.getSession(sessionId);
|
|
10071
|
+
if (!session) {
|
|
10072
|
+
return c.json({ error: "Session not found" }, 404);
|
|
10073
|
+
}
|
|
10074
|
+
const agent = agents2.get(session.agentName);
|
|
10075
|
+
if (!agent) {
|
|
10076
|
+
return c.json({ error: `Agent not found: ${session.agentName}` }, 404);
|
|
10077
|
+
}
|
|
10078
|
+
const streamEventStorage = agent.getStreamEventStorage();
|
|
10079
|
+
if (!streamEventStorage) {
|
|
10080
|
+
return c.json({ error: "Stream event storage not configured" }, 501);
|
|
10081
|
+
}
|
|
10082
|
+
let events = await streamEventStorage.readEvents(sessionId, {
|
|
10083
|
+
afterSequence,
|
|
10084
|
+
limit
|
|
10085
|
+
});
|
|
10086
|
+
if (events.length === 0 && waitMs > 0) {
|
|
10087
|
+
const deadline = Date.now() + waitMs;
|
|
10088
|
+
const pollInterval = 200;
|
|
10089
|
+
while (Date.now() < deadline) {
|
|
10090
|
+
events = await streamEventStorage.readEvents(sessionId, {
|
|
10091
|
+
afterSequence,
|
|
10092
|
+
limit
|
|
10093
|
+
});
|
|
10094
|
+
if (events.length > 0) break;
|
|
10095
|
+
const currentSession = await sessionManager.getSession(sessionId);
|
|
10096
|
+
if (currentSession && ["completed", "error", "stopped"].includes(currentSession.status)) {
|
|
10097
|
+
break;
|
|
10098
|
+
}
|
|
10099
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
|
|
10100
|
+
}
|
|
10101
|
+
}
|
|
10102
|
+
const nextCursor = events.length > 0 ? Math.max(...events.map((e) => e.sequence)) : afterSequence;
|
|
10103
|
+
return c.json({
|
|
10104
|
+
events: events.map((e) => ({
|
|
10105
|
+
id: e.id,
|
|
10106
|
+
sequence: e.sequence,
|
|
10107
|
+
eventType: e.eventType,
|
|
10108
|
+
payload: e.payload,
|
|
10109
|
+
batchCount: e.batchCount,
|
|
10110
|
+
createdAt: e.createdAt.toISOString()
|
|
10111
|
+
})),
|
|
10112
|
+
nextCursor,
|
|
10113
|
+
hasMore: events.length === limit,
|
|
10114
|
+
sessionStatus: session.status
|
|
10115
|
+
});
|
|
10116
|
+
});
|
|
9818
10117
|
router.get("/:sessionId/stream", async (c) => {
|
|
9819
10118
|
const sessionId = c.req.param("sessionId");
|
|
9820
10119
|
const afterParam = c.req.query("after");
|
|
@@ -10499,7 +10798,8 @@ function createHarnessServer(config) {
|
|
|
10499
10798
|
basePath = "/api",
|
|
10500
10799
|
middleware = [],
|
|
10501
10800
|
hooks = {},
|
|
10502
|
-
queue: queueConfig = {}
|
|
10801
|
+
queue: queueConfig = {},
|
|
10802
|
+
sandbox: sandboxConfig
|
|
10503
10803
|
} = config;
|
|
10504
10804
|
let agentsNeedReload = false;
|
|
10505
10805
|
const sessionManager = new exports.SessionManager(storage);
|
|
@@ -10650,7 +10950,7 @@ function createHarnessServer(config) {
|
|
|
10650
10950
|
return agentStorage.getActiveAgents();
|
|
10651
10951
|
},
|
|
10652
10952
|
/**
|
|
10653
|
-
* Initialize storage, agents,
|
|
10953
|
+
* Initialize storage, agents, start queue processor, and optionally init sandbox pool
|
|
10654
10954
|
*/
|
|
10655
10955
|
async initialize() {
|
|
10656
10956
|
await storage.initialize();
|
|
@@ -10664,12 +10964,20 @@ function createHarnessServer(config) {
|
|
|
10664
10964
|
await agent.initialize();
|
|
10665
10965
|
}
|
|
10666
10966
|
queueProcessor?.start();
|
|
10967
|
+
if (sandboxConfig?.pool && sandboxConfig.autoInitPool !== false) {
|
|
10968
|
+
try {
|
|
10969
|
+
await initializeSandboxPool(sandboxConfig.pool);
|
|
10970
|
+
} catch (error) {
|
|
10971
|
+
console.error("[SERVER] Failed to initialize sandbox pool:", error instanceof Error ? error.message : error);
|
|
10972
|
+
}
|
|
10973
|
+
}
|
|
10667
10974
|
},
|
|
10668
10975
|
/**
|
|
10669
|
-
* Close storage, agents,
|
|
10976
|
+
* Close storage, agents, stop queue processor, and shutdown sandbox pool
|
|
10670
10977
|
*/
|
|
10671
10978
|
async close() {
|
|
10672
10979
|
queueProcessor?.stop();
|
|
10980
|
+
await shutdownSandboxPool();
|
|
10673
10981
|
for (const agent of agents2.values()) {
|
|
10674
10982
|
await agent.close();
|
|
10675
10983
|
}
|
|
@@ -10691,6 +10999,7 @@ var init_server = __esm({
|
|
|
10691
10999
|
init_agents();
|
|
10692
11000
|
init_skills2();
|
|
10693
11001
|
init_queue2();
|
|
11002
|
+
init_sandbox_pool();
|
|
10694
11003
|
}
|
|
10695
11004
|
});
|
|
10696
11005
|
|
|
@@ -13612,7 +13921,8 @@ function createOpenAPIServer(config) {
|
|
|
13612
13921
|
basePath = "/api",
|
|
13613
13922
|
middleware = [],
|
|
13614
13923
|
hooks = {},
|
|
13615
|
-
docs = {}
|
|
13924
|
+
docs = {},
|
|
13925
|
+
sandbox: sandboxConfig
|
|
13616
13926
|
} = config;
|
|
13617
13927
|
const {
|
|
13618
13928
|
enabled: docsEnabled = true,
|
|
@@ -13787,7 +14097,7 @@ Authentication is handled by the hosting application. This API does not enforce
|
|
|
13787
14097
|
return agentStorage.getActiveAgents();
|
|
13788
14098
|
},
|
|
13789
14099
|
/**
|
|
13790
|
-
* Initialize storage and
|
|
14100
|
+
* Initialize storage, agents, and optionally init sandbox pool
|
|
13791
14101
|
*/
|
|
13792
14102
|
async initialize() {
|
|
13793
14103
|
await storage.initialize();
|
|
@@ -13797,11 +14107,19 @@ Authentication is handled by the hosting application. This API does not enforce
|
|
|
13797
14107
|
for (const agent of agents2.values()) {
|
|
13798
14108
|
await agent.initialize();
|
|
13799
14109
|
}
|
|
14110
|
+
if (sandboxConfig?.pool && sandboxConfig.autoInitPool !== false) {
|
|
14111
|
+
try {
|
|
14112
|
+
await initializeSandboxPool(sandboxConfig.pool);
|
|
14113
|
+
} catch (error) {
|
|
14114
|
+
console.error("[SERVER] Failed to initialize sandbox pool:", error instanceof Error ? error.message : error);
|
|
14115
|
+
}
|
|
14116
|
+
}
|
|
13800
14117
|
},
|
|
13801
14118
|
/**
|
|
13802
|
-
* Close storage and
|
|
14119
|
+
* Close storage, agents, and shutdown sandbox pool
|
|
13803
14120
|
*/
|
|
13804
14121
|
async close() {
|
|
14122
|
+
await shutdownSandboxPool();
|
|
13805
14123
|
for (const agent of agents2.values()) {
|
|
13806
14124
|
await agent.close();
|
|
13807
14125
|
}
|
|
@@ -13833,6 +14151,7 @@ var init_server2 = __esm({
|
|
|
13833
14151
|
init_sessions2();
|
|
13834
14152
|
init_agents2();
|
|
13835
14153
|
init_skills3();
|
|
14154
|
+
init_sandbox_pool();
|
|
13836
14155
|
}
|
|
13837
14156
|
});
|
|
13838
14157
|
|
|
@@ -16597,6 +16916,7 @@ __export(schema_exports, {
|
|
|
16597
16916
|
configDeployments: () => configDeployments,
|
|
16598
16917
|
configDeploymentsRelations: () => configDeploymentsRelations,
|
|
16599
16918
|
eventCategoryEnum: () => eventCategoryEnum,
|
|
16919
|
+
eventSourceEnum: () => eventSourceEnum,
|
|
16600
16920
|
messageRoleEnum: () => messageRoleEnum,
|
|
16601
16921
|
messages: () => messages,
|
|
16602
16922
|
messagesRelations: () => messagesRelations,
|
|
@@ -16610,7 +16930,7 @@ __export(schema_exports, {
|
|
|
16610
16930
|
streamEvents: () => streamEvents,
|
|
16611
16931
|
streamEventsRelations: () => streamEventsRelations
|
|
16612
16932
|
});
|
|
16613
|
-
var sessionStatusEnum, messageRoleEnum, agentStatusEnum, agentBackendEnum, queueItemStatusEnum, configDeploymentStatusEnum, configDeploymentTriggerEnum, eventCategoryEnum, sessions, messages, attachments, agents, queueItems, configDeployments, sessionEvents, streamEvents, sessionsRelations, sessionEventsRelations, messagesRelations, attachmentsRelations, configDeploymentsRelations, streamEventsRelations, agentsRelations;
|
|
16933
|
+
var sessionStatusEnum, messageRoleEnum, agentStatusEnum, agentBackendEnum, queueItemStatusEnum, configDeploymentStatusEnum, configDeploymentTriggerEnum, eventSourceEnum, eventCategoryEnum, sessions, messages, attachments, agents, queueItems, configDeployments, sessionEvents, streamEvents, sessionsRelations, sessionEventsRelations, messagesRelations, attachmentsRelations, configDeploymentsRelations, streamEventsRelations, agentsRelations;
|
|
16614
16934
|
var init_schema = __esm({
|
|
16615
16935
|
"src/storage-postgres/schema.ts"() {
|
|
16616
16936
|
sessionStatusEnum = pgCore.pgEnum("session_status", [
|
|
@@ -16652,6 +16972,14 @@ var init_schema = __esm({
|
|
|
16652
16972
|
"initial",
|
|
16653
16973
|
"rollback"
|
|
16654
16974
|
]);
|
|
16975
|
+
eventSourceEnum = pgCore.pgEnum("event_source", [
|
|
16976
|
+
"agent",
|
|
16977
|
+
// Directly from the agent SDK
|
|
16978
|
+
"system",
|
|
16979
|
+
// Generated by execution layer (lifecycle, user input)
|
|
16980
|
+
"derived"
|
|
16981
|
+
// Transformed/aggregated from agent events
|
|
16982
|
+
]);
|
|
16655
16983
|
eventCategoryEnum = pgCore.pgEnum("event_category", [
|
|
16656
16984
|
"lifecycle",
|
|
16657
16985
|
// session_start, session_end, turn_complete
|
|
@@ -16842,6 +17170,8 @@ var init_schema = __esm({
|
|
|
16842
17170
|
// Aggregation fields (for text_stream events)
|
|
16843
17171
|
isAggregated: pgCore.boolean("is_aggregated").default(false),
|
|
16844
17172
|
aggregatedCount: pgCore.integer("aggregated_count"),
|
|
17173
|
+
// Event source tagging
|
|
17174
|
+
eventSource: eventSourceEnum("event_source").notNull().default("agent"),
|
|
16845
17175
|
// Ordering
|
|
16846
17176
|
sequenceNumber: pgCore.integer("sequence_number").notNull(),
|
|
16847
17177
|
createdAt: pgCore.timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
|
|
@@ -17434,6 +17764,11 @@ var init_storage2 = __esm({
|
|
|
17434
17764
|
drizzleOrm.eq(sessions.status, options.status)
|
|
17435
17765
|
);
|
|
17436
17766
|
}
|
|
17767
|
+
if (options.resourceId) {
|
|
17768
|
+
conditions.push(
|
|
17769
|
+
drizzleOrm.sql`${sessions.metadata}->>'resourceId' = ${options.resourceId}`
|
|
17770
|
+
);
|
|
17771
|
+
}
|
|
17437
17772
|
const orderColumn = options.orderBy === "updatedAt" ? sessions.updatedAt : sessions.createdAt;
|
|
17438
17773
|
const orderFn = options.order === "asc" ? drizzleOrm.asc : drizzleOrm.desc;
|
|
17439
17774
|
const limit = options.limit ?? 50;
|
|
@@ -18155,6 +18490,9 @@ var init_storage3 = __esm({
|
|
|
18155
18490
|
if (options.status) {
|
|
18156
18491
|
query = query.eq("status", options.status);
|
|
18157
18492
|
}
|
|
18493
|
+
if (options.resourceId) {
|
|
18494
|
+
query = query.eq("metadata->>resourceId", options.resourceId);
|
|
18495
|
+
}
|
|
18158
18496
|
const orderColumn = options.orderBy === "updatedAt" ? "updated_at" : "created_at";
|
|
18159
18497
|
const ascending = options.order === "asc";
|
|
18160
18498
|
query = query.order(orderColumn, { ascending }).range(offset, offset + limit - 1);
|
|
@@ -18512,6 +18850,7 @@ var init_storage3 = __esm({
|
|
|
18512
18850
|
sessionId: row.session_id,
|
|
18513
18851
|
eventType: row.event_type,
|
|
18514
18852
|
category: row.category,
|
|
18853
|
+
eventSource: row.event_source,
|
|
18515
18854
|
startedAt: new Date(row.started_at),
|
|
18516
18855
|
endedAt: row.ended_at ? new Date(row.ended_at) : void 0,
|
|
18517
18856
|
durationMs: row.duration_ms ?? void 0,
|
|
@@ -18534,6 +18873,7 @@ var init_storage3 = __esm({
|
|
|
18534
18873
|
session_id: sessionId,
|
|
18535
18874
|
event_type: event.eventType,
|
|
18536
18875
|
category: event.category,
|
|
18876
|
+
event_source: event.eventSource || "agent",
|
|
18537
18877
|
started_at: event.startedAt.toISOString(),
|
|
18538
18878
|
ended_at: event.endedAt?.toISOString() ?? null,
|
|
18539
18879
|
duration_ms: event.durationMs ?? null,
|
|
@@ -18560,6 +18900,9 @@ var init_storage3 = __esm({
|
|
|
18560
18900
|
if (options.eventType) {
|
|
18561
18901
|
query = query.eq("event_type", options.eventType);
|
|
18562
18902
|
}
|
|
18903
|
+
if (options.eventSource) {
|
|
18904
|
+
query = query.eq("event_source", options.eventSource);
|
|
18905
|
+
}
|
|
18563
18906
|
if (options.filePath) {
|
|
18564
18907
|
query = query.or(
|
|
18565
18908
|
`event_data->>filePath.eq.${options.filePath},event_data->>canonicalPath.eq.${options.filePath},and(category.eq.tool,tool_name.in.(Read,Write,Edit),event_data->input->>file_path.eq.${options.filePath})`
|
|
@@ -18896,7 +19239,8 @@ var init_storage4 = __esm({
|
|
|
18896
19239
|
limit: options?.limit,
|
|
18897
19240
|
offset: options?.offset,
|
|
18898
19241
|
agentName: options?.agentName,
|
|
18899
|
-
status: options?.status
|
|
19242
|
+
status: options?.status,
|
|
19243
|
+
resourceId: options?.resourceId
|
|
18900
19244
|
}
|
|
18901
19245
|
}
|
|
18902
19246
|
);
|
|
@@ -19669,6 +20013,7 @@ exports.sseMcpWithAuth = sseMcpWithAuth;
|
|
|
19669
20013
|
exports.startServer = startServer;
|
|
19670
20014
|
exports.streamEventRelay = streamEventRelay;
|
|
19671
20015
|
exports.updateToolCallWithResult = updateToolCallWithResult;
|
|
20016
|
+
exports.warmSandboxForSession = warmSandboxForSession;
|
|
19672
20017
|
exports.writeFileToSandbox = writeFileToSandbox;
|
|
19673
20018
|
//# sourceMappingURL=index.cjs.map
|
|
19674
20019
|
//# sourceMappingURL=index.cjs.map
|