@ash-cloud/ash-ai 0.1.19 → 0.1.20

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.js CHANGED
@@ -885,6 +885,32 @@ var init_mcp = __esm({
885
885
  });
886
886
 
887
887
  // src/agent/claude-sdk.ts
888
+ function isStandardMcpServerConfig(config) {
889
+ if (!config || typeof config !== "object") return false;
890
+ const candidate = config;
891
+ if (typeof candidate.command === "string") return true;
892
+ if (typeof candidate.url === "string") return true;
893
+ if (candidate.type === "stdio" || candidate.type === "http" || candidate.type === "sse") return true;
894
+ return false;
895
+ }
896
+ function hasCustomMcpServers(mcpServers) {
897
+ if (!mcpServers) return false;
898
+ return Object.values(mcpServers).some((config) => !isStandardMcpServerConfig(config));
899
+ }
900
+ async function* toStreamingPrompt(prompt) {
901
+ yield {
902
+ type: "user",
903
+ message: {
904
+ role: "user",
905
+ content: prompt
906
+ }
907
+ };
908
+ }
909
+ function normalizePromptForSdk(prompt, mcpServers) {
910
+ if (typeof prompt !== "string") return prompt;
911
+ if (!hasCustomMcpServers(mcpServers)) return prompt;
912
+ return toStreamingPrompt(prompt);
913
+ }
888
914
  function convertClaudeMessage(claudeMessage, sessionId) {
889
915
  if (claudeMessage.type !== "assistant" || !claudeMessage.message) {
890
916
  return null;
@@ -928,16 +954,17 @@ function normalizeMcpServers(mcpServers) {
928
954
  if (!mcpServers) return mcpServers;
929
955
  return Object.fromEntries(
930
956
  Object.entries(mcpServers).map(([name, config]) => {
931
- if (!config.auth) {
957
+ const configWithAuth = config;
958
+ if (!configWithAuth.auth) {
932
959
  return [name, config];
933
960
  }
934
- const authHeaders = mcpAuthToHeaders(config.auth);
935
- const { auth: _auth, ...rest } = config;
961
+ const authHeaders = mcpAuthToHeaders(configWithAuth.auth);
962
+ const { auth: _auth, ...rest } = configWithAuth;
936
963
  return [name, {
937
964
  ...rest,
938
965
  headers: {
939
966
  ...authHeaders,
940
- ...config.headers
967
+ ...configWithAuth.headers
941
968
  // Explicit headers take precedence
942
969
  }
943
970
  }];
@@ -964,8 +991,22 @@ var init_claude_sdk = __esm({
964
991
  async *query(prompt, options = {}) {
965
992
  const model = options.model ?? this.defaultModel;
966
993
  if (await this.checkSdkAvailable()) {
967
- yield* this.executeRealQuery(prompt, { ...options, model });
994
+ const normalizedOptions = { ...options, model };
995
+ if (normalizedOptions.agents) {
996
+ const allowedTools = normalizedOptions.allowedTools ?? [];
997
+ if (!allowedTools.includes("Task")) {
998
+ normalizedOptions.allowedTools = [...allowedTools, "Task"];
999
+ }
1000
+ }
1001
+ yield* this.executeRealQuery(prompt, normalizedOptions);
968
1002
  } else {
1003
+ if (typeof prompt !== "string") {
1004
+ yield {
1005
+ type: "error",
1006
+ error: "Streaming prompts require the real Claude Agent SDK to be installed."
1007
+ };
1008
+ return;
1009
+ }
969
1010
  yield* this.executeSimulatedQuery(prompt, { ...options, model });
970
1011
  }
971
1012
  }
@@ -979,8 +1020,9 @@ var init_claude_sdk = __esm({
979
1020
  }
980
1021
  try {
981
1022
  const { query } = await import('@anthropic-ai/claude-agent-sdk');
1023
+ const normalizedMcpServers = normalizeMcpServers(options.mcpServers);
982
1024
  const queryOptions = {
983
- prompt,
1025
+ prompt: normalizePromptForSdk(prompt, options.mcpServers),
984
1026
  options: {
985
1027
  model: options.model,
986
1028
  allowedTools: options.allowedTools,
@@ -989,10 +1031,12 @@ var init_claude_sdk = __esm({
989
1031
  maxTurns: options.maxTurns,
990
1032
  resume: options.resume,
991
1033
  forkSession: options.forkSession,
992
- mcpServers: options.mcpServers,
1034
+ mcpServers: normalizedMcpServers,
993
1035
  agents: options.agents,
994
1036
  hooks: options.hooks,
1037
+ plugins: options.plugins,
995
1038
  settingSources: options.settingSources,
1039
+ outputFormat: options.outputFormat,
996
1040
  // Enable streaming partial messages for real-time text deltas
997
1041
  includePartialMessages: true,
998
1042
  // Environment variables for the SDK
@@ -1015,7 +1059,8 @@ var init_claude_sdk = __esm({
1015
1059
  if (message.type === "system" && message.subtype === "init") {
1016
1060
  yield {
1017
1061
  type: "session_init",
1018
- sessionId: message.session_id
1062
+ sessionId: message.session_id,
1063
+ slashCommands: message.slash_commands
1019
1064
  };
1020
1065
  continue;
1021
1066
  }
@@ -1066,12 +1111,17 @@ var init_claude_sdk = __esm({
1066
1111
  }
1067
1112
  }
1068
1113
  if (message.type === "result") {
1114
+ const usage = message.usage;
1115
+ const hasUsageTokens = !!usage && (usage.input_tokens !== void 0 || usage.output_tokens !== void 0);
1116
+ const totalTokens = hasUsageTokens ? (usage?.input_tokens ?? 0) + (usage?.output_tokens ?? 0) : message.tokens ? message.tokens.input + message.tokens.output : void 0;
1117
+ const totalCost = usage?.total_cost_usd ?? message.cost ?? message.total_cost_usd;
1069
1118
  yield {
1070
1119
  type: "complete",
1071
1120
  sessionId: message.session_id,
1072
1121
  result: message.result,
1073
- totalCost: message.cost,
1074
- totalTokens: message.tokens ? message.tokens.input + message.tokens.output : void 0
1122
+ structured_output: message.structured_output,
1123
+ totalCost,
1124
+ totalTokens
1075
1125
  };
1076
1126
  }
1077
1127
  }
@@ -1079,6 +1129,13 @@ var init_claude_sdk = __esm({
1079
1129
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
1080
1130
  if (errorMessage.includes("Cannot find module") || errorMessage.includes("MODULE_NOT_FOUND")) {
1081
1131
  console.warn("Claude Agent SDK not installed, using simulation mode");
1132
+ if (typeof prompt !== "string") {
1133
+ yield {
1134
+ type: "error",
1135
+ error: "Streaming prompts require the real Claude Agent SDK to be installed."
1136
+ };
1137
+ return;
1138
+ }
1082
1139
  yield* this.executeSimulatedQuery(prompt, options);
1083
1140
  } else {
1084
1141
  yield {
@@ -1256,7 +1313,11 @@ var init_claude_sdk = __esm({
1256
1313
  this.sessionId = msg.session_id;
1257
1314
  }
1258
1315
  if (msg.type === "system" && msg.subtype === "init") {
1259
- yield { type: "session_init", sessionId: msg.session_id };
1316
+ yield {
1317
+ type: "session_init",
1318
+ sessionId: msg.session_id,
1319
+ slashCommands: msg.slash_commands
1320
+ };
1260
1321
  } else if (msg.type === "assistant" && msg.message) {
1261
1322
  for (const block of msg.message.content) {
1262
1323
  if (block.type === "text") {
@@ -1684,6 +1745,13 @@ function createGeminiBackendExecutor(options) {
1684
1745
  defaultModel: options.model ?? DEFAULT_MODELS.gemini
1685
1746
  });
1686
1747
  return async function* (prompt, queryOptions) {
1748
+ if (typeof prompt !== "string") {
1749
+ yield {
1750
+ type: "error",
1751
+ error: "Gemini backend does not support streaming prompt inputs."
1752
+ };
1753
+ return;
1754
+ }
1687
1755
  const geminiOptions = mapClaudeOptionsToGemini(queryOptions);
1688
1756
  if (queryOptions.signal) {
1689
1757
  geminiOptions.signal = queryOptions.signal;
@@ -1848,6 +1916,11 @@ var init_sandbox_logger = __esm({
1848
1916
  };
1849
1917
  }
1850
1918
  });
1919
+ function getClaudeSdkOverrides(config) {
1920
+ const raw = config?.claudeSdkOptions;
1921
+ if (!raw || typeof raw !== "object") return void 0;
1922
+ return raw;
1923
+ }
1851
1924
  var AgentHarness;
1852
1925
  var init_harness = __esm({
1853
1926
  "src/agent/harness.ts"() {
@@ -2104,6 +2177,7 @@ var init_harness = __esm({
2104
2177
  yield sessionStartEvent;
2105
2178
  yield* yieldQueuedLogs();
2106
2179
  const assistantContent = [];
2180
+ let structuredOutput;
2107
2181
  let wasAborted = false;
2108
2182
  try {
2109
2183
  logger3.info("execution", "Starting Claude Agent SDK query");
@@ -2194,6 +2268,10 @@ var init_harness = __esm({
2194
2268
  };
2195
2269
  writeEvent?.(toolResultEvent);
2196
2270
  yield toolResultEvent;
2271
+ } else if (event.type === "complete") {
2272
+ if (event.structured_output !== void 0) {
2273
+ structuredOutput = event.structured_output;
2274
+ }
2197
2275
  }
2198
2276
  }
2199
2277
  if (wasAborted || controller.signal.aborted) {
@@ -2245,7 +2323,8 @@ var init_harness = __esm({
2245
2323
  [
2246
2324
  {
2247
2325
  role: "assistant",
2248
- content: assistantContent
2326
+ content: assistantContent,
2327
+ ...structuredOutput !== void 0 ? { metadata: { structured_output: structuredOutput } } : {}
2249
2328
  }
2250
2329
  ]
2251
2330
  );
@@ -2366,11 +2445,15 @@ var init_harness = __esm({
2366
2445
  async *executeAgentQuery(session, prompt, options, signal, _logger) {
2367
2446
  const sessionEnvVars = session.metadata?.envVars;
2368
2447
  const sessionStartupScript = session.metadata?.startupScript;
2448
+ const sdkOverrides = getClaudeSdkOverrides(this.config.config);
2369
2449
  const mergedEnvVars = {
2370
2450
  MAX_THINKING_TOKENS: "1024",
2371
2451
  ...this.config.envVars,
2372
2452
  ...sessionEnvVars
2373
2453
  };
2454
+ if (sdkOverrides?.enableFileCheckpointing) {
2455
+ mergedEnvVars.CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING = "1";
2456
+ }
2374
2457
  const hasEnvVars = Object.keys(mergedEnvVars).length > 0;
2375
2458
  const startupScript = sessionStartupScript ?? this.config.startupScript;
2376
2459
  const queryOptions = {
@@ -2379,7 +2462,9 @@ var init_harness = __esm({
2379
2462
  disallowedTools: this.config.disallowedTools,
2380
2463
  permissionMode: this.config.permissionMode,
2381
2464
  maxTurns: this.config.maxTurns,
2465
+ outputFormat: options.outputFormat,
2382
2466
  mcpServers: this.config.mcpServers,
2467
+ settingSources: this.config.settingSources ?? ["project"],
2383
2468
  // Pass the harness session ID for sandbox caching
2384
2469
  harnessSessionId: session.id,
2385
2470
  // Pass environment and startup configuration
@@ -2388,6 +2473,17 @@ var init_harness = __esm({
2388
2473
  // Pass config file URL for cloud-hosted .claude directory (downloaded in sandbox)
2389
2474
  ...this.config.configFileUrl && { configFileUrl: this.config.configFileUrl }
2390
2475
  };
2476
+ if (sdkOverrides) {
2477
+ if (sdkOverrides.hooks) queryOptions.hooks = sdkOverrides.hooks;
2478
+ if (typeof sdkOverrides.enableFileCheckpointing === "boolean") {
2479
+ queryOptions.enableFileCheckpointing = sdkOverrides.enableFileCheckpointing;
2480
+ if (sdkOverrides.enableFileCheckpointing && !sdkOverrides.extraArgs) {
2481
+ queryOptions.extraArgs = { "replay-user-messages": null };
2482
+ }
2483
+ }
2484
+ if (sdkOverrides.extraArgs) queryOptions.extraArgs = sdkOverrides.extraArgs;
2485
+ if (sdkOverrides.permissionMode) queryOptions.permissionMode = sdkOverrides.permissionMode;
2486
+ }
2391
2487
  const sessionApiKey = this.sessionApiKeys.get(session.id);
2392
2488
  if (sessionApiKey) {
2393
2489
  queryOptions.apiKey = sessionApiKey;
@@ -2423,7 +2519,9 @@ ${prompt}`;
2423
2519
  const skillsDir = this.sessionSkillDirs.get(session.id);
2424
2520
  if (skillsDir) {
2425
2521
  queryOptions.cwd = skillsDir;
2426
- queryOptions.settingSources = ["project"];
2522
+ if (queryOptions.settingSources === void 0) {
2523
+ queryOptions.settingSources = ["project"];
2524
+ }
2427
2525
  if (queryOptions.allowedTools && !queryOptions.allowedTools.includes("Skill")) {
2428
2526
  queryOptions.allowedTools = [...queryOptions.allowedTools, "Skill"];
2429
2527
  }
@@ -2452,6 +2550,7 @@ ${prompt}`;
2452
2550
  await this.sessionManager.updateSession(session.id, {
2453
2551
  sdkSessionId: event.sessionId
2454
2552
  });
2553
+ yield { type: "session_init", sessionId: event.sessionId, slashCommands: event.slashCommands };
2455
2554
  } else if (event.type === "text_delta" && event.delta) {
2456
2555
  yield { type: "text_delta", delta: event.delta };
2457
2556
  } else if (event.type === "thinking_delta" && event.delta) {
@@ -2472,6 +2571,11 @@ ${prompt}`;
2472
2571
  content: event.content,
2473
2572
  isError: event.isError
2474
2573
  };
2574
+ } else if (event.type === "complete") {
2575
+ yield {
2576
+ type: "complete",
2577
+ structured_output: event.structured_output
2578
+ };
2475
2579
  } else if (event.type === "error") {
2476
2580
  throw new Error(event.error ?? "Unknown error from Claude SDK");
2477
2581
  }
@@ -4613,6 +4717,12 @@ echo "[warmup] Warmup complete!"
4613
4717
  startPromise = null;
4614
4718
  /** Registered warmup specs by tag (e.g. agentId -> spec) */
4615
4719
  warmupSpecs = /* @__PURE__ */ new Map();
4720
+ /** Tags currently being warmed (prevents duplicate warming of same spec) */
4721
+ warmingTags = /* @__PURE__ */ new Set();
4722
+ /** Max warmup specs to keep (LRU eviction above this) */
4723
+ static MAX_SPECS = 10;
4724
+ /** Timeout for spec setup in ms (prevents hanging S3 pulls / install.sh) */
4725
+ static SPEC_SETUP_TIMEOUT_MS = 12e4;
4616
4726
  /** Consecutive warmup failure count (reset on success) */
4617
4727
  consecutiveFailures = 0;
4618
4728
  /** Timestamp of last warmup attempt — used for backoff */
@@ -4625,7 +4735,7 @@ echo "[warmup] Warmup complete!"
4625
4735
  this.config = {
4626
4736
  minPoolSize: config.minPoolSize ?? parseInt(process.env.SANDBOX_POOL_MIN_SIZE ?? "2"),
4627
4737
  maxPoolSize: config.maxPoolSize ?? parseInt(process.env.SANDBOX_POOL_MAX_SIZE ?? "5"),
4628
- sandboxTimeout: config.sandboxTimeout ?? parseInt(process.env.SANDBOX_TIMEOUT ?? "900"),
4738
+ sandboxTimeout: config.sandboxTimeout ?? parseInt(process.env.SANDBOX_TIMEOUT ?? "300"),
4629
4739
  expiryThresholdMs: config.expiryThresholdMs ?? parseInt(process.env.SANDBOX_EXPIRY_THRESHOLD_MS ?? "120000"),
4630
4740
  maintenanceIntervalMs: config.maintenanceIntervalMs ?? parseInt(process.env.SANDBOX_POOL_MAINTENANCE_MS ?? "30000"),
4631
4741
  runtime: config.runtime ?? "node22",
@@ -4831,18 +4941,28 @@ echo "[warmup] Warmup complete!"
4831
4941
  }
4832
4942
  /**
4833
4943
  * Register a warmup spec so the pool can pre-warm agent-specific sandboxes.
4834
- * If a spec with the same tag already exists, it is replaced.
4944
+ * If a spec with the same tag and configHash already exists, only updates priority (skip #7).
4945
+ * Evicts lowest-priority specs when exceeding MAX_SPECS (fix #2).
4835
4946
  * Triggers replenishment to warm a sandbox for this spec.
4836
4947
  */
4837
4948
  registerWarmupSpec(spec) {
4838
- const isNew = !this.warmupSpecs.has(spec.tag);
4949
+ const existing = this.warmupSpecs.get(spec.tag);
4950
+ if (existing && spec.configHash && existing.configHash === spec.configHash) {
4951
+ existing.priority = spec.priority;
4952
+ return;
4953
+ }
4954
+ const isNew = !existing;
4839
4955
  this.warmupSpecs.set(spec.tag, spec);
4840
- console.log(`[POOL] ${isNew ? "Registered" : "Updated"} warmup spec: ${spec.tag} (priority=${spec.priority})`);
4956
+ if (this.warmupSpecs.size > _SandboxPool.MAX_SPECS) {
4957
+ this.evictLowestPrioritySpecs();
4958
+ }
4959
+ console.log(`[POOL] ${isNew ? "Registered" : "Updated"} warmup spec: ${spec.tag} (priority=${spec.priority}, specs=${this.warmupSpecs.size})`);
4841
4960
  this.emitMetric("spec_registered", {
4842
4961
  tag: spec.tag,
4843
4962
  priority: spec.priority,
4844
4963
  isNew,
4845
- totalSpecs: this.warmupSpecs.size
4964
+ totalSpecs: this.warmupSpecs.size,
4965
+ configHash: spec.configHash
4846
4966
  });
4847
4967
  if (this.running) {
4848
4968
  this.triggerReplenishment();
@@ -4969,20 +5089,41 @@ echo "[warmup] Warmup complete!"
4969
5089
  }
4970
5090
  let warmupTag;
4971
5091
  let agentSetupComplete = false;
5092
+ let warmupInstallRan = false;
5093
+ let warmupStartupRan = false;
4972
5094
  if (spec) {
5095
+ this.warmingTags.add(spec.tag);
4973
5096
  console.log(`[POOL] Running spec setup for tag=${spec.tag} on sandbox ${sandbox.sandboxId}...`);
4974
5097
  this.emitMetric("spec_setup_started", { tag: spec.tag, sandboxId: sandbox.sandboxId });
4975
5098
  const specStartTime = Date.now();
4976
5099
  try {
4977
- await spec.setup(sandbox);
5100
+ const setupResult = await new Promise((resolve3, reject) => {
5101
+ const timer = setTimeout(
5102
+ () => reject(new Error(`Spec setup timed out after ${_SandboxPool.SPEC_SETUP_TIMEOUT_MS / 1e3}s`)),
5103
+ _SandboxPool.SPEC_SETUP_TIMEOUT_MS
5104
+ );
5105
+ spec.setup(sandbox).then((result) => {
5106
+ clearTimeout(timer);
5107
+ resolve3(result);
5108
+ }).catch((error) => {
5109
+ clearTimeout(timer);
5110
+ reject(error);
5111
+ });
5112
+ });
4978
5113
  warmupTag = spec.tag;
4979
5114
  agentSetupComplete = true;
5115
+ if (setupResult && typeof setupResult === "object") {
5116
+ warmupInstallRan = setupResult.installRan === true;
5117
+ warmupStartupRan = setupResult.startupRan === true;
5118
+ }
4980
5119
  const specDuration = Date.now() - specStartTime;
4981
- console.log(`[POOL] Spec setup completed for tag=${spec.tag} on sandbox ${sandbox.sandboxId} (${specDuration}ms)`);
5120
+ console.log(`[POOL] Spec setup completed for tag=${spec.tag} on sandbox ${sandbox.sandboxId} (${specDuration}ms, install=${warmupInstallRan}, startup=${warmupStartupRan})`);
4982
5121
  this.emitMetric("spec_setup_completed", {
4983
5122
  tag: spec.tag,
4984
5123
  sandboxId: sandbox.sandboxId,
4985
- durationMs: specDuration
5124
+ durationMs: specDuration,
5125
+ warmupInstallRan,
5126
+ warmupStartupRan
4986
5127
  });
4987
5128
  } catch (specError) {
4988
5129
  const specDuration = Date.now() - specStartTime;
@@ -4996,6 +5137,8 @@ echo "[warmup] Warmup complete!"
4996
5137
  durationMs: specDuration,
4997
5138
  error: specErrorMessage
4998
5139
  });
5140
+ } finally {
5141
+ this.warmingTags.delete(spec.tag);
4999
5142
  }
5000
5143
  }
5001
5144
  const warmupTime = Date.now() - startTime;
@@ -5009,7 +5152,9 @@ echo "[warmup] Warmup complete!"
5009
5152
  eligible: true,
5010
5153
  lastHeartbeat: now,
5011
5154
  warmupTag,
5012
- agentSetupComplete
5155
+ agentSetupComplete,
5156
+ warmupInstallRan,
5157
+ warmupStartupRan
5013
5158
  };
5014
5159
  const tagInfo = warmupTag ? ` [tag=${warmupTag}]` : "";
5015
5160
  console.log(`[POOL] Warmup completed for ${sandbox.sandboxId} (took ${warmupTime}ms)${useTarball ? " [tarball]" : ""}${tagInfo}`);
@@ -5211,15 +5356,20 @@ echo "[warmup] Warmup complete!"
5211
5356
  }
5212
5357
  /**
5213
5358
  * Decide which specs to apply to new sandboxes during replenishment.
5214
- * Strategy: cover uncovered specs first (highest priority), then fill remaining as generic.
5359
+ * Strategy:
5360
+ * - Always reserve at least 1 slot for generic (fix #3)
5361
+ * - Cover uncovered specs first (highest priority), skipping in-flight tags (fix #4)
5362
+ * - Fill remaining as generic
5215
5363
  * Returns an array of length `needed`, where each element is a spec or undefined (generic).
5216
5364
  */
5217
5365
  selectSpecsForReplenishment(needed) {
5218
- if (this.warmupSpecs.size === 0) {
5366
+ if (this.warmupSpecs.size === 0 || needed === 0) {
5219
5367
  return new Array(needed).fill(void 0);
5220
5368
  }
5369
+ const maxTaggedSlots = Math.max(0, needed - 1);
5221
5370
  const uncoveredSpecs = [];
5222
5371
  for (const spec of this.warmupSpecs.values()) {
5372
+ if (this.warmingTags.has(spec.tag)) continue;
5223
5373
  let hasCoverage = false;
5224
5374
  for (const pooled of this.pool.values()) {
5225
5375
  if (pooled.warmupTag === spec.tag && pooled.eligible && !pooled.assignedTo) {
@@ -5234,7 +5384,7 @@ echo "[warmup] Warmup complete!"
5234
5384
  uncoveredSpecs.sort((a, b) => b.priority - a.priority);
5235
5385
  const assignments = [];
5236
5386
  for (const spec of uncoveredSpecs) {
5237
- if (assignments.length >= needed) break;
5387
+ if (assignments.length >= maxTaggedSlots) break;
5238
5388
  assignments.push(spec);
5239
5389
  }
5240
5390
  while (assignments.length < needed) {
@@ -5242,6 +5392,27 @@ echo "[warmup] Warmup complete!"
5242
5392
  }
5243
5393
  return assignments;
5244
5394
  }
5395
+ /**
5396
+ * Evict lowest-priority specs when over MAX_SPECS capacity (fix #2).
5397
+ */
5398
+ evictLowestPrioritySpecs() {
5399
+ while (this.warmupSpecs.size > _SandboxPool.MAX_SPECS) {
5400
+ let lowestTag;
5401
+ let lowestPriority = Infinity;
5402
+ for (const [tag, spec] of this.warmupSpecs.entries()) {
5403
+ if (spec.priority < lowestPriority) {
5404
+ lowestPriority = spec.priority;
5405
+ lowestTag = tag;
5406
+ }
5407
+ }
5408
+ if (lowestTag) {
5409
+ this.warmupSpecs.delete(lowestTag);
5410
+ console.log(`[POOL] Evicted warmup spec: ${lowestTag} (priority=${lowestPriority}, specs=${this.warmupSpecs.size})`);
5411
+ } else {
5412
+ break;
5413
+ }
5414
+ }
5415
+ }
5245
5416
  /**
5246
5417
  * Destroy a sandbox and clean up
5247
5418
  */
@@ -5561,6 +5732,8 @@ async function getOrCreateSandbox(options) {
5561
5732
  const pooled = await pool.acquire(sessionId, preferTag);
5562
5733
  const tagInfo = pooled.warmupTag ? ` [tag=${pooled.warmupTag}, agentSetup=${pooled.agentSetupComplete}]` : "";
5563
5734
  console.log(`[SANDBOX] Acquired pre-warmed sandbox: ${pooled.sandboxId}${tagInfo}`);
5735
+ const installDone = pooled.warmupInstallRan === true;
5736
+ const startupDone = pooled.warmupStartupRan === true;
5564
5737
  const agentSetupDone = pooled.agentSetupComplete === true;
5565
5738
  const now2 = Date.now();
5566
5739
  const entry2 = {
@@ -5569,24 +5742,26 @@ async function getOrCreateSandbox(options) {
5569
5742
  createdAt: pooled.createdAt,
5570
5743
  lastUsedAt: now2,
5571
5744
  sdkInstalled: pooled.sdkInstalled,
5572
- startupScriptRan: agentSetupDone,
5573
- installScriptRan: agentSetupDone
5745
+ startupScriptRan: startupDone,
5746
+ installScriptRan: installDone
5574
5747
  };
5575
5748
  sandboxCache.set(sessionId, entry2);
5576
5749
  return {
5577
5750
  sandbox: pooled.sandbox,
5578
5751
  sandboxId: pooled.sandboxId,
5579
5752
  sdkInstalled: pooled.sdkInstalled,
5580
- startupScriptRan: agentSetupDone,
5753
+ startupScriptRan: startupDone,
5581
5754
  startupScriptHash: void 0,
5582
- installScriptRan: agentSetupDone,
5755
+ installScriptRan: installDone,
5583
5756
  installScriptHash: void 0,
5584
5757
  isNew: false,
5585
5758
  // Not new - came from pool
5586
5759
  configFileUrl: void 0,
5587
5760
  configInstalledAt: agentSetupDone ? now2 : void 0,
5588
5761
  warmupTag: pooled.warmupTag,
5589
- agentSetupComplete: pooled.agentSetupComplete
5762
+ agentSetupComplete: pooled.agentSetupComplete,
5763
+ warmupInstallRan: pooled.warmupInstallRan,
5764
+ warmupStartupRan: pooled.warmupStartupRan
5590
5765
  };
5591
5766
  } catch (error) {
5592
5767
  console.warn(
@@ -5931,6 +6106,13 @@ function createVercelSandboxExecutor(apiKey) {
5931
6106
  };
5932
6107
  }
5933
6108
  async function* executeInSandbox(prompt, apiKey, options) {
6109
+ if (typeof prompt !== "string") {
6110
+ yield {
6111
+ type: "error",
6112
+ error: "Vercel sandbox executor does not support streaming prompt inputs."
6113
+ };
6114
+ return;
6115
+ }
5934
6116
  const sessionId = options.harnessSessionId || `temp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
5935
6117
  try {
5936
6118
  const { sandbox, sdkInstalled, startupScriptRan, startupScriptHash: cachedScriptHash, configFileUrl: cachedConfigUrl } = await getOrCreateSandbox({
@@ -6072,9 +6254,13 @@ async function* executeInSandbox(prompt, apiKey, options) {
6072
6254
  permissionMode: options.permissionMode || "bypassPermissions",
6073
6255
  includePartialMessages: true
6074
6256
  };
6257
+ if (options.settingSources !== void 0) {
6258
+ sdkOptions.settingSources = options.settingSources;
6259
+ } else {
6260
+ sdkOptions.settingSources = ["project"];
6261
+ }
6075
6262
  const hasConfig = options.configFileUrl || cachedConfigUrl;
6076
6263
  if (hasConfig) {
6077
- sdkOptions.settingSources = ["project"];
6078
6264
  if (options.allowedTools && options.allowedTools.length > 0) {
6079
6265
  const allowedTools = [...options.allowedTools];
6080
6266
  if (!allowedTools.includes("Skill")) {
@@ -6110,12 +6296,47 @@ async function* executeInSandbox(prompt, apiKey, options) {
6110
6296
  if (options.resume) {
6111
6297
  sdkOptions.resume = options.resume;
6112
6298
  }
6299
+ try {
6300
+ const pluginFindResult = await sandbox.runCommand({
6301
+ cmd: "bash",
6302
+ args: [
6303
+ "-c",
6304
+ [
6305
+ 'for base in ".claude/plugins" "$HOME/.claude/plugins"; do',
6306
+ ' if [ -d "$base" ]; then',
6307
+ ' find "$base" -type f -path "*/.claude-plugin/plugin.json" -print',
6308
+ " fi",
6309
+ "done | sed 's#/.claude-plugin/plugin.json$##'"
6310
+ ].join("\n")
6311
+ ]
6312
+ });
6313
+ const rawPluginRoots = (await pluginFindResult.stdout()).trim();
6314
+ const pluginRoots = rawPluginRoots ? rawPluginRoots.split("\n").map((line) => line.trim()).filter(Boolean) : [];
6315
+ const uniquePluginRoots = Array.from(new Set(pluginRoots));
6316
+ if (uniquePluginRoots.length > 0) {
6317
+ sdkOptions.plugins = uniquePluginRoots.map((path15) => ({ type: "local", path: path15 }));
6318
+ console.log("[SANDBOX] Plugins detected:", uniquePluginRoots);
6319
+ }
6320
+ } catch (pluginError) {
6321
+ console.warn("[SANDBOX] Failed to detect plugins:", pluginError);
6322
+ }
6113
6323
  const agentScript = `
6114
6324
  const { query } = require('@anthropic-ai/claude-agent-sdk');
6325
+ const fs = require('fs');
6115
6326
 
6116
6327
  const prompt = ${JSON.stringify(prompt)};
6117
6328
  const options = ${JSON.stringify(sdkOptions)};
6118
6329
 
6330
+ // Enable subagents if .claude/agents exists (requires Task tool)
6331
+ if (fs.existsSync('.claude/agents')) {
6332
+ if (!Array.isArray(options.allowedTools)) {
6333
+ options.allowedTools = [];
6334
+ }
6335
+ if (!options.allowedTools.includes('Task')) {
6336
+ options.allowedTools.push('Task');
6337
+ }
6338
+ }
6339
+
6119
6340
  let queryCompleted = false;
6120
6341
 
6121
6342
  async function run() {
@@ -6212,7 +6433,11 @@ SCRIPT_EOF`]
6212
6433
  });
6213
6434
  }
6214
6435
  if (event.type === "system" && event.subtype === "init") {
6215
- events.push({ type: "session_init", sessionId: event.session_id || "" });
6436
+ events.push({
6437
+ type: "session_init",
6438
+ sessionId: event.session_id || "",
6439
+ slashCommands: event.slash_commands
6440
+ });
6216
6441
  } else if (event.type === "stream_event" && event.event) {
6217
6442
  const streamEvent = event.event;
6218
6443
  if (streamEvent.type === "content_block_delta") {
@@ -6247,12 +6472,16 @@ SCRIPT_EOF`]
6247
6472
  }
6248
6473
  }
6249
6474
  } else if (event.type === "result") {
6475
+ const usage = event.usage;
6476
+ const hasUsageTokens = !!usage && (usage.input_tokens !== void 0 || usage.output_tokens !== void 0);
6477
+ const totalTokens = hasUsageTokens ? (usage?.input_tokens ?? 0) + (usage?.output_tokens ?? 0) : event.total_tokens ?? (event.tokens ? event.tokens.input + event.tokens.output : void 0);
6478
+ const totalCost = usage?.total_cost_usd ?? event.total_cost_usd ?? event.cost ?? event.total_cost;
6250
6479
  events.push({
6251
6480
  type: "complete",
6252
6481
  sessionId: event.session_id,
6253
6482
  result: event.result,
6254
- totalCost: event.total_cost_usd,
6255
- totalTokens: event.total_tokens
6483
+ totalCost,
6484
+ totalTokens
6256
6485
  });
6257
6486
  }
6258
6487
  return events;
@@ -12831,6 +13060,7 @@ __export(schemas_exports, {
12831
13060
  RunAgentRequestSchema: () => RunAgentRequestSchema,
12832
13061
  SendMessageRequestSchema: () => SendMessageRequestSchema,
12833
13062
  SessionEndEventSchema: () => SessionEndEventSchema,
13063
+ SessionInitEventSchema: () => SessionInitEventSchema,
12834
13064
  SessionSchema: () => SessionSchema,
12835
13065
  SessionStartEventSchema: () => SessionStartEventSchema,
12836
13066
  SessionStatusSchema: () => SessionStatusSchema,
@@ -12850,7 +13080,7 @@ __export(schemas_exports, {
12850
13080
  TurnCompleteEventSchema: () => TurnCompleteEventSchema,
12851
13081
  UpdateAgentRequestSchema: () => UpdateAgentRequestSchema
12852
13082
  });
12853
- var ErrorResponseSchema, SuccessResponseSchema, PaginationQuerySchema, OrderQuerySchema, TextContentSchema, ToolUseContentSchema, ToolResultContentSchema, ImageContentSchema, FileContentSchema, MessageContentSchema, MessageSchema, PaginatedMessagesSchema, SessionStatusSchema, SessionSchema, PaginatedSessionsSchema, CreateSessionRequestSchema, SendMessageRequestSchema, ResumeSessionRequestSchema, ListSessionsQuerySchema, AgentStatusSchema, PermissionModeSchema, McpServerConfigSchema, StoredAgentSchema, SimpleAgentSchema, PaginatedAgentsSchema, CreateAgentRequestSchema, UpdateAgentRequestSchema, RunAgentRequestSchema, ListAgentsQuerySchema, GitHubSkillSourceSchema, LocalSkillSourceSchema, SkillSourceSchema, BrowseSkillsRequestSchema, ReadSkillFileRequestSchema, FileEntrySchema, BrowseSkillsResponseSchema, SkillFileContentSchema, ReadSkillFileResponseSchema, SessionStartEventSchema, TextDeltaEventSchema, ThinkingDeltaEventSchema, MessageEventSchema, ToolUseEventSchema, ToolResultEventSchema, SessionEndEventSchema, TurnCompleteEventSchema, StreamErrorEventSchema, HealthResponseSchema;
13083
+ var ErrorResponseSchema, SuccessResponseSchema, PaginationQuerySchema, OrderQuerySchema, TextContentSchema, ToolUseContentSchema, ToolResultContentSchema, ImageContentSchema, FileContentSchema, MessageContentSchema, MessageSchema, PaginatedMessagesSchema, SessionStatusSchema, SessionSchema, PaginatedSessionsSchema, CreateSessionRequestSchema, SendMessageRequestSchema, ResumeSessionRequestSchema, ListSessionsQuerySchema, AgentStatusSchema, PermissionModeSchema, McpServerConfigSchema, StoredAgentSchema, SimpleAgentSchema, PaginatedAgentsSchema, CreateAgentRequestSchema, UpdateAgentRequestSchema, RunAgentRequestSchema, ListAgentsQuerySchema, GitHubSkillSourceSchema, LocalSkillSourceSchema, SkillSourceSchema, BrowseSkillsRequestSchema, ReadSkillFileRequestSchema, FileEntrySchema, BrowseSkillsResponseSchema, SkillFileContentSchema, ReadSkillFileResponseSchema, SessionStartEventSchema, SessionInitEventSchema, TextDeltaEventSchema, ThinkingDeltaEventSchema, MessageEventSchema, ToolUseEventSchema, ToolResultEventSchema, SessionEndEventSchema, TurnCompleteEventSchema, StreamErrorEventSchema, HealthResponseSchema;
12854
13084
  var init_schemas = __esm({
12855
13085
  "src/server/openapi/schemas.ts"() {
12856
13086
  init_dist3();
@@ -13076,6 +13306,15 @@ var init_schemas = __esm({
13076
13306
  sessionId: z.string().openapi({ description: "Session ID" }),
13077
13307
  claudeSessionId: z.string().openapi({ description: "Claude SDK session ID" })
13078
13308
  }).openapi("SessionStartEvent");
13309
+ SessionInitEventSchema = z.object({
13310
+ type: z.literal("session_init"),
13311
+ sessionId: z.string().openapi({ description: "Claude SDK session ID" }),
13312
+ slashCommands: z.array(z.object({
13313
+ name: z.string().openapi({ description: "Slash command name" }),
13314
+ description: z.string().optional().openapi({ description: "Slash command description" }),
13315
+ prompt: z.string().optional().openapi({ description: "Slash command prompt" })
13316
+ })).optional().openapi({ description: "Slash commands advertised by the SDK" })
13317
+ }).openapi("SessionInitEvent");
13079
13318
  TextDeltaEventSchema = z.object({
13080
13319
  type: z.literal("text_delta"),
13081
13320
  delta: z.string().openapi({ description: "Text chunk", example: "Hello" })
@@ -13402,6 +13641,7 @@ var init_sessions2 = __esm({
13402
13641
  The stream emits the following event types:
13403
13642
 
13404
13643
  - \`session_start\` - Session started, includes sessionId and sdkSessionId
13644
+ - \`session_init\` - Claude SDK session initialized (includes slash commands)
13405
13645
  - \`text_delta\` - Text chunk being generated
13406
13646
  - \`thinking_delta\` - Thinking/reasoning text chunk
13407
13647
  - \`message\` - Complete message saved to storage
@@ -13850,6 +14090,7 @@ This is a convenience endpoint that combines session creation and message sendin
13850
14090
  The stream emits the following event types:
13851
14091
 
13852
14092
  - \`session_start\` - Session started
14093
+ - \`session_init\` - Claude SDK session initialized (includes slash commands)
13853
14094
  - \`text_delta\` - Text chunk being generated
13854
14095
  - \`thinking_delta\` - Thinking/reasoning text chunk
13855
14096
  - \`message\` - Complete message saved