@ash-cloud/ash-ai 0.1.13 → 0.1.15

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
@@ -5289,6 +5289,23 @@ function isSandboxRunning(sessionId) {
5289
5289
  const cached = sandboxCache.get(sessionId);
5290
5290
  return cached !== void 0 && !cached.isExpired;
5291
5291
  }
5292
+ function getCachedSandbox(sessionId) {
5293
+ const cached = sandboxCache.get(sessionId);
5294
+ if (!cached || cached.isExpired) {
5295
+ return null;
5296
+ }
5297
+ cached.lastUsedAt = Date.now();
5298
+ return {
5299
+ sandbox: cached.sandbox,
5300
+ sandboxId: cached.sandbox.sandboxId,
5301
+ sdkInstalled: cached.sdkInstalled,
5302
+ startupScriptRan: cached.startupScriptRan,
5303
+ startupScriptHash: cached.startupScriptHash,
5304
+ isNew: false,
5305
+ configFileUrl: cached.configFileUrl,
5306
+ configInstalledAt: cached.configInstalledAt
5307
+ };
5308
+ }
5292
5309
  async function writeFileToSandbox(sessionId, path15, content) {
5293
5310
  const cached = sandboxCache.get(sessionId);
5294
5311
  if (!cached) {
@@ -5357,6 +5374,54 @@ async function readFileFromSandbox(sessionId, path15) {
5357
5374
  };
5358
5375
  }
5359
5376
  }
5377
+ async function executeCommandInSandbox(sessionId, command, options) {
5378
+ const cached = sandboxCache.get(sessionId);
5379
+ if (!cached) {
5380
+ return { success: false, error: "No active sandbox for session" };
5381
+ }
5382
+ if (cached.isExpired) {
5383
+ return { success: false, error: "Sandbox has expired" };
5384
+ }
5385
+ const startTime = Date.now();
5386
+ try {
5387
+ const sandbox = cached.sandbox;
5388
+ let fullCommand = command;
5389
+ if (options?.cwd) {
5390
+ fullCommand = `cd ${JSON.stringify(options.cwd)} && ${command}`;
5391
+ }
5392
+ const result = await sandbox.runCommand({
5393
+ cmd: "bash",
5394
+ args: ["-c", fullCommand],
5395
+ env: options?.env
5396
+ });
5397
+ const stdout = await result.stdout();
5398
+ const stderr = await result.stderr();
5399
+ const durationMs = Date.now() - startTime;
5400
+ cached.lastUsedAt = Date.now();
5401
+ return {
5402
+ success: result.exitCode === 0,
5403
+ exitCode: result.exitCode,
5404
+ stdout,
5405
+ stderr,
5406
+ durationMs
5407
+ };
5408
+ } catch (error) {
5409
+ const durationMs = Date.now() - startTime;
5410
+ if (isSandboxExpiredError(error)) {
5411
+ cached.isExpired = true;
5412
+ return {
5413
+ success: false,
5414
+ error: "Sandbox has expired",
5415
+ durationMs
5416
+ };
5417
+ }
5418
+ return {
5419
+ success: false,
5420
+ error: error instanceof Error ? error.message : "Unknown error",
5421
+ durationMs
5422
+ };
5423
+ }
5424
+ }
5360
5425
  async function listFilesInSandbox(sessionId, path15) {
5361
5426
  const cached = sandboxCache.get(sessionId);
5362
5427
  if (!cached) {
@@ -5836,19 +5901,7 @@ function getFileWatcherManager() {
5836
5901
  function createFileWatcherManager() {
5837
5902
  return new exports.FileWatcherManager();
5838
5903
  }
5839
- function createRemoteFileWatcher(options) {
5840
- return new exports.RemoteSandboxFileWatcher(options);
5841
- }
5842
- function getRemoteFileWatcherManager() {
5843
- if (!globalRemoteWatcherManager) {
5844
- globalRemoteWatcherManager = new exports.RemoteFileWatcherManager();
5845
- }
5846
- return globalRemoteWatcherManager;
5847
- }
5848
- function createRemoteFileWatcherManager() {
5849
- return new exports.RemoteFileWatcherManager();
5850
- }
5851
- exports.SandboxFileWatcher = void 0; exports.FileWatcherManager = void 0; var globalWatcherManager; exports.RemoteSandboxFileWatcher = void 0; exports.RemoteFileWatcherManager = void 0; var globalRemoteWatcherManager;
5904
+ exports.SandboxFileWatcher = void 0; exports.FileWatcherManager = void 0; var globalWatcherManager;
5852
5905
  var init_sandbox_file_watcher = __esm({
5853
5906
  "src/runtime/sandbox-file-watcher.ts"() {
5854
5907
  exports.SandboxFileWatcher = class {
@@ -6090,214 +6143,327 @@ var init_sandbox_file_watcher = __esm({
6090
6143
  }
6091
6144
  };
6092
6145
  globalWatcherManager = null;
6093
- exports.RemoteSandboxFileWatcher = class {
6146
+ }
6147
+ });
6148
+
6149
+ // src/runtime/in-sandbox-watcher.ts
6150
+ function createInSandboxWatcher(options) {
6151
+ return new exports.InSandboxWatcher(options);
6152
+ }
6153
+ function getInSandboxWatcherManager() {
6154
+ if (!globalInSandboxManager) {
6155
+ globalInSandboxManager = new exports.InSandboxWatcherManager();
6156
+ }
6157
+ return globalInSandboxManager;
6158
+ }
6159
+ function createInSandboxWatcherManager() {
6160
+ return new exports.InSandboxWatcherManager();
6161
+ }
6162
+ var WATCHER_SCRIPT; exports.InSandboxWatcher = void 0; exports.InSandboxWatcherManager = void 0; var globalInSandboxManager;
6163
+ var init_in_sandbox_watcher = __esm({
6164
+ "src/runtime/in-sandbox-watcher.ts"() {
6165
+ init_vercel_sandbox_executor();
6166
+ WATCHER_SCRIPT = `
6167
+ const fs = require('fs');
6168
+ const path = require('path');
6169
+
6170
+ const watchPath = process.argv[2] || '.';
6171
+ const ignored = new Set(['node_modules', '.git', '.cache', '__pycache__']);
6172
+
6173
+ // Track all watchers for cleanup
6174
+ const watchers = new Map();
6175
+
6176
+ // Debounce map to coalesce rapid changes
6177
+ const pending = new Map();
6178
+ const DEBOUNCE_MS = 100;
6179
+
6180
+ function emit(type, filePath) {
6181
+ const event = {
6182
+ type,
6183
+ path: filePath,
6184
+ timestamp: Date.now()
6185
+ };
6186
+ console.log(JSON.stringify(event));
6187
+ }
6188
+
6189
+ function shouldIgnore(name) {
6190
+ return ignored.has(name) || name.startsWith('.');
6191
+ }
6192
+
6193
+ function watchDir(dir) {
6194
+ try {
6195
+ const watcher = fs.watch(dir, { persistent: true }, (eventType, filename) => {
6196
+ if (!filename || shouldIgnore(filename)) return;
6197
+
6198
+ const fullPath = path.join(dir, filename);
6199
+ const key = fullPath;
6200
+
6201
+ // Debounce
6202
+ if (pending.has(key)) {
6203
+ clearTimeout(pending.get(key));
6204
+ }
6205
+
6206
+ pending.set(key, setTimeout(() => {
6207
+ pending.delete(key);
6208
+
6209
+ fs.stat(fullPath, (err, stats) => {
6210
+ if (err) {
6211
+ if (err.code === 'ENOENT') {
6212
+ emit('unlink', fullPath);
6213
+ // Stop watching if it was a directory
6214
+ if (watchers.has(fullPath)) {
6215
+ watchers.get(fullPath).close();
6216
+ watchers.delete(fullPath);
6217
+ }
6218
+ }
6219
+ } else {
6220
+ const type = eventType === 'rename' ? 'add' : 'change';
6221
+ emit(type, fullPath);
6222
+
6223
+ // If it's a new directory, start watching it
6224
+ if (stats.isDirectory() && !watchers.has(fullPath)) {
6225
+ watchDir(fullPath);
6226
+ }
6227
+ }
6228
+ });
6229
+ }, DEBOUNCE_MS));
6230
+ });
6231
+
6232
+ watchers.set(dir, watcher);
6233
+
6234
+ // Watch subdirectories
6235
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
6236
+ for (const entry of entries) {
6237
+ if (entry.isDirectory() && !shouldIgnore(entry.name)) {
6238
+ watchDir(path.join(dir, entry.name));
6239
+ }
6240
+ }
6241
+ } catch (err) {
6242
+ // Directory may not exist or be inaccessible
6243
+ console.error(JSON.stringify({ error: err.message, dir }));
6244
+ }
6245
+ }
6246
+
6247
+ // Start watching
6248
+ watchDir(watchPath);
6249
+
6250
+ // Keep alive
6251
+ process.on('SIGTERM', () => {
6252
+ for (const watcher of watchers.values()) {
6253
+ watcher.close();
6254
+ }
6255
+ process.exit(0);
6256
+ });
6257
+
6258
+ // Heartbeat to indicate we're running
6259
+ setInterval(() => {
6260
+ console.log(JSON.stringify({ heartbeat: true, timestamp: Date.now() }));
6261
+ }, 5000);
6262
+
6263
+ console.log(JSON.stringify({ started: true, path: watchPath }));
6264
+ `;
6265
+ exports.InSandboxWatcher = class {
6094
6266
  sessionId;
6095
- sandboxOps;
6096
- basePath;
6097
- pollIntervalMs;
6098
- ignored;
6099
- pollTimer = null;
6100
- previousFiles = /* @__PURE__ */ new Map();
6267
+ watchPath;
6268
+ outputPollIntervalMs;
6269
+ sandboxState = null;
6270
+ outputPollTimer = null;
6101
6271
  subscribers = /* @__PURE__ */ new Set();
6102
- isWatching = false;
6272
+ isRunning = false;
6103
6273
  startedAt;
6104
- lastPollAt;
6105
- pollCount = 0;
6274
+ lastHeartbeat;
6275
+ lastOutputPosition = 0;
6106
6276
  onError;
6277
+ onReady;
6107
6278
  constructor(options) {
6108
6279
  this.sessionId = options.sessionId;
6109
- this.sandboxOps = options.sandboxOps;
6110
- this.basePath = options.basePath;
6111
- this.pollIntervalMs = options.pollIntervalMs ?? 2e3;
6112
- this.ignored = options.ignored ?? ["**/node_modules/**", "**/.git/**"];
6280
+ this.watchPath = options.watchPath;
6281
+ this.outputPollIntervalMs = options.outputPollIntervalMs ?? 1e3;
6113
6282
  this.onError = options.onError;
6283
+ this.onReady = options.onReady;
6114
6284
  if (options.onFileChange) {
6115
6285
  this.subscribers.add(options.onFileChange);
6116
6286
  }
6117
6287
  }
6118
6288
  /**
6119
- * Start polling for file changes
6289
+ * Start the watcher process inside the sandbox
6120
6290
  */
6121
6291
  async start() {
6122
- if (this.isWatching) {
6123
- console.warn(`[REMOTE_WATCHER] Already watching session ${this.sessionId}`);
6292
+ if (this.isRunning) {
6293
+ console.warn(`[IN_SANDBOX_WATCHER] Already running for session ${this.sessionId}`);
6124
6294
  return;
6125
6295
  }
6126
- if (!this.sandboxOps.isSandboxRunning(this.sessionId)) {
6127
- throw new Error(`Sandbox is not running for session ${this.sessionId}`);
6128
- }
6129
- await this.scan(true);
6130
- this.pollTimer = setInterval(async () => {
6131
- try {
6132
- await this.scan(false);
6133
- } catch (error) {
6134
- console.error(`[REMOTE_WATCHER] Poll error for session ${this.sessionId}:`, error);
6135
- if (this.onError && error instanceof Error) {
6136
- this.onError(error);
6137
- }
6296
+ try {
6297
+ this.sandboxState = getCachedSandbox(this.sessionId);
6298
+ if (!this.sandboxState) {
6299
+ throw new Error(`No active sandbox found for session ${this.sessionId}. Sandbox must be created before starting file watcher.`);
6138
6300
  }
6139
- }, this.pollIntervalMs);
6140
- this.isWatching = true;
6141
- this.startedAt = /* @__PURE__ */ new Date();
6142
- console.log(`[REMOTE_WATCHER] Started watching session ${this.sessionId} at ${this.basePath}`);
6143
- }
6144
- /**
6145
- * Stop polling and cleanup
6146
- */
6147
- async stop() {
6148
- if (!this.isWatching) {
6149
- return;
6150
- }
6151
- if (this.pollTimer) {
6152
- clearInterval(this.pollTimer);
6153
- this.pollTimer = null;
6301
+ const { sandbox } = this.sandboxState;
6302
+ const scriptPath = "/tmp/.file-watcher.js";
6303
+ const outputPath = "/tmp/.file-watcher-output.log";
6304
+ const writeScriptCmd = `cat > ${scriptPath} << 'WATCHER_EOF'
6305
+ ${WATCHER_SCRIPT}
6306
+ WATCHER_EOF`;
6307
+ await sandbox.runCommand({
6308
+ cmd: "sh",
6309
+ args: ["-c", writeScriptCmd]
6310
+ });
6311
+ const startCmd = `nohup node ${scriptPath} "${this.watchPath}" > ${outputPath} 2>&1 &`;
6312
+ await sandbox.runCommand({
6313
+ cmd: "sh",
6314
+ args: ["-c", startCmd]
6315
+ });
6316
+ console.log(`[IN_SANDBOX_WATCHER] Started watcher in sandbox for ${this.watchPath}`);
6317
+ this.isRunning = true;
6318
+ this.startedAt = /* @__PURE__ */ new Date();
6319
+ this.outputPollTimer = setInterval(async () => {
6320
+ try {
6321
+ await this.pollOutput(outputPath);
6322
+ } catch (error) {
6323
+ console.error("[IN_SANDBOX_WATCHER] Error polling output:", error);
6324
+ if (this.onError && error instanceof Error) {
6325
+ this.onError(error);
6326
+ }
6327
+ }
6328
+ }, this.outputPollIntervalMs);
6329
+ setTimeout(() => {
6330
+ this.pollOutput(outputPath).catch(console.error);
6331
+ }, 500);
6332
+ } catch (error) {
6333
+ console.error(`[IN_SANDBOX_WATCHER] Failed to start watcher:`, error);
6334
+ throw error;
6154
6335
  }
6155
- this.isWatching = false;
6156
- this.previousFiles.clear();
6157
- console.log(`[REMOTE_WATCHER] Stopped watching session ${this.sessionId}`);
6158
- }
6159
- /**
6160
- * Subscribe to file change events
6161
- */
6162
- subscribe(callback) {
6163
- this.subscribers.add(callback);
6164
- return () => this.subscribers.delete(callback);
6165
6336
  }
6166
6337
  /**
6167
- * Remove a subscriber
6338
+ * Poll the output file for new events
6168
6339
  */
6169
- unsubscribe(callback) {
6170
- this.subscribers.delete(callback);
6340
+ async pollOutput(outputPath) {
6341
+ if (!this.sandboxState?.sandbox) return;
6342
+ try {
6343
+ const result = await this.sandboxState.sandbox.runCommand({
6344
+ cmd: "sh",
6345
+ args: ["-c", `tail -c +${this.lastOutputPosition + 1} ${outputPath} 2>/dev/null || true`]
6346
+ });
6347
+ const output = await result.stdout();
6348
+ if (output && output.trim()) {
6349
+ const sizeResult = await this.sandboxState.sandbox.runCommand({
6350
+ cmd: "sh",
6351
+ args: ["-c", `stat -c%s ${outputPath} 2>/dev/null || echo 0`]
6352
+ });
6353
+ const sizeStr = await sizeResult.stdout();
6354
+ this.lastOutputPosition = parseInt(sizeStr.trim(), 10) || 0;
6355
+ this.processOutput(output);
6356
+ }
6357
+ } catch {
6358
+ }
6171
6359
  }
6172
6360
  /**
6173
- * Check if watcher is active
6361
+ * Process output from the watcher script
6174
6362
  */
6175
- isActive() {
6176
- return this.isWatching;
6177
- }
6178
- /**
6179
- * Get watcher status
6180
- */
6181
- getStatus() {
6182
- return {
6183
- sessionId: this.sessionId,
6184
- watchPath: this.basePath,
6185
- isWatching: this.isWatching,
6186
- watchedFileCount: this.previousFiles.size,
6187
- pendingEventCount: 0,
6188
- startedAt: this.startedAt,
6189
- lastPollAt: this.lastPollAt,
6190
- pollCount: this.pollCount
6191
- };
6192
- }
6193
- /**
6194
- * Force an immediate scan (useful for testing or manual refresh)
6195
- */
6196
- async forceScan() {
6197
- await this.scan(false);
6363
+ processOutput(stdout) {
6364
+ const lines = stdout.split("\n").filter(Boolean);
6365
+ for (const line of lines) {
6366
+ try {
6367
+ const data = JSON.parse(line);
6368
+ if (data.started) {
6369
+ console.log(`[IN_SANDBOX_WATCHER] Watcher started for ${data.path}`);
6370
+ if (this.onReady) {
6371
+ this.onReady();
6372
+ }
6373
+ } else if (data.heartbeat) {
6374
+ this.lastHeartbeat = new Date(data.timestamp);
6375
+ } else if (data.error) {
6376
+ console.warn(`[IN_SANDBOX_WATCHER] Error in sandbox:`, data.error);
6377
+ } else if (data.type && data.path) {
6378
+ this.emitEvent({
6379
+ type: data.type,
6380
+ relativePath: data.path.replace(/^\.\//, ""),
6381
+ absolutePath: data.path,
6382
+ basePath: this.watchPath,
6383
+ sessionId: this.sessionId,
6384
+ timestamp: new Date(data.timestamp)
6385
+ });
6386
+ }
6387
+ } catch {
6388
+ }
6389
+ }
6198
6390
  }
6199
6391
  /**
6200
- * Scan the sandbox for file changes
6392
+ * Stop the watcher process
6201
6393
  */
6202
- async scan(isInitial) {
6203
- if (!this.sandboxOps.isSandboxRunning(this.sessionId)) {
6204
- console.warn(`[REMOTE_WATCHER] Sandbox stopped for session ${this.sessionId}`);
6205
- await this.stop();
6394
+ async stop() {
6395
+ if (!this.isRunning) {
6206
6396
  return;
6207
6397
  }
6208
- const listResult = await this.sandboxOps.listFiles(this.sessionId, this.basePath);
6209
- if (!listResult.success || !listResult.files) {
6210
- throw new Error(listResult.error ?? "Failed to list files in sandbox");
6211
- }
6212
- const currentFiles = /* @__PURE__ */ new Map();
6213
- for (const filePath of listResult.files) {
6214
- if (this.shouldIgnore(filePath)) {
6215
- continue;
6216
- }
6217
- currentFiles.set(filePath, { path: filePath });
6218
- }
6219
- this.lastPollAt = /* @__PURE__ */ new Date();
6220
- this.pollCount++;
6221
- if (isInitial) {
6222
- this.previousFiles = currentFiles;
6223
- return;
6398
+ if (this.outputPollTimer) {
6399
+ clearInterval(this.outputPollTimer);
6400
+ this.outputPollTimer = null;
6224
6401
  }
6225
- const changes = [];
6226
- for (const [filePath, info] of currentFiles) {
6227
- const previous = this.previousFiles.get(filePath);
6228
- if (!previous) {
6229
- changes.push({
6230
- type: "add",
6231
- relativePath: filePath,
6232
- absolutePath: `${this.basePath}/${filePath}`,
6233
- basePath: this.basePath,
6234
- sessionId: this.sessionId,
6235
- fileSize: info.size,
6236
- timestamp: /* @__PURE__ */ new Date()
6402
+ try {
6403
+ if (this.sandboxState?.sandbox) {
6404
+ await this.sandboxState.sandbox.runCommand({
6405
+ cmd: "sh",
6406
+ args: ["-c", "pkill -f file-watcher.js 2>/dev/null || true"]
6237
6407
  });
6238
- }
6239
- }
6240
- for (const [filePath] of this.previousFiles) {
6241
- if (!currentFiles.has(filePath)) {
6242
- changes.push({
6243
- type: "unlink",
6244
- relativePath: filePath,
6245
- absolutePath: `${this.basePath}/${filePath}`,
6246
- basePath: this.basePath,
6247
- sessionId: this.sessionId,
6248
- timestamp: /* @__PURE__ */ new Date()
6408
+ await this.sandboxState.sandbox.runCommand({
6409
+ cmd: "sh",
6410
+ args: ["-c", "rm -f /tmp/.file-watcher.js /tmp/.file-watcher-output.log"]
6249
6411
  });
6250
6412
  }
6413
+ } catch {
6251
6414
  }
6252
- this.previousFiles = currentFiles;
6253
- for (const event of changes) {
6254
- await this.emitEvent(event);
6255
- }
6415
+ this.isRunning = false;
6416
+ this.sandboxState = null;
6417
+ this.lastOutputPosition = 0;
6418
+ console.log(`[IN_SANDBOX_WATCHER] Stopped watcher for session ${this.sessionId}`);
6256
6419
  }
6257
6420
  /**
6258
- * Check if a path should be ignored
6421
+ * Subscribe to file change events
6259
6422
  */
6260
- shouldIgnore(filePath) {
6261
- for (const pattern of this.ignored) {
6262
- if (this.matchesGlob(filePath, pattern)) {
6263
- return true;
6264
- }
6265
- }
6266
- return false;
6423
+ subscribe(callback) {
6424
+ this.subscribers.add(callback);
6425
+ return () => this.subscribers.delete(callback);
6267
6426
  }
6268
6427
  /**
6269
- * Simple glob matching (supports ** and *)
6428
+ * Check if watcher is running
6270
6429
  */
6271
- matchesGlob(filePath, pattern) {
6272
- const regexPattern = pattern.replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/{{GLOBSTAR}}/g, ".*").replace(/\//g, "\\/");
6273
- const regex = new RegExp(`^${regexPattern}$`);
6274
- return regex.test(filePath);
6430
+ isActive() {
6431
+ return this.isRunning;
6275
6432
  }
6276
6433
  /**
6277
- * Emit a file change event to all subscribers
6434
+ * Get watcher status
6435
+ */
6436
+ getStatus() {
6437
+ return {
6438
+ sessionId: this.sessionId,
6439
+ watchPath: this.watchPath,
6440
+ isRunning: this.isRunning,
6441
+ startedAt: this.startedAt,
6442
+ lastHeartbeat: this.lastHeartbeat
6443
+ };
6444
+ }
6445
+ /**
6446
+ * Emit event to all subscribers
6278
6447
  */
6279
6448
  async emitEvent(event) {
6280
6449
  for (const callback of this.subscribers) {
6281
6450
  try {
6282
6451
  await callback(event);
6283
6452
  } catch (error) {
6284
- console.error("[REMOTE_WATCHER] Error in subscriber callback:", error);
6453
+ console.error("[IN_SANDBOX_WATCHER] Error in subscriber callback:", error);
6285
6454
  }
6286
6455
  }
6287
6456
  }
6288
6457
  };
6289
- exports.RemoteFileWatcherManager = class {
6458
+ exports.InSandboxWatcherManager = class {
6290
6459
  watchers = /* @__PURE__ */ new Map();
6291
6460
  /**
6292
- * Start watching a session's sandbox
6461
+ * Start watching a sandbox
6293
6462
  */
6294
6463
  async startWatching(options) {
6295
6464
  const { sessionId } = options;
6296
- const existing = this.watchers.get(sessionId);
6297
- if (existing) {
6298
- await existing.stop();
6299
- }
6300
- const watcher = new exports.RemoteSandboxFileWatcher(options);
6465
+ await this.stopWatching(sessionId);
6466
+ const watcher = new exports.InSandboxWatcher(options);
6301
6467
  await watcher.start();
6302
6468
  this.watchers.set(sessionId, watcher);
6303
6469
  return watcher;
@@ -6313,17 +6479,16 @@ var init_sandbox_file_watcher = __esm({
6313
6479
  }
6314
6480
  }
6315
6481
  /**
6316
- * Get a watcher for a session
6482
+ * Get watcher for a session
6317
6483
  */
6318
6484
  getWatcher(sessionId) {
6319
6485
  return this.watchers.get(sessionId);
6320
6486
  }
6321
6487
  /**
6322
- * Check if a session is being watched
6488
+ * Check if watching
6323
6489
  */
6324
6490
  isWatching(sessionId) {
6325
- const watcher = this.watchers.get(sessionId);
6326
- return watcher?.isActive() ?? false;
6491
+ return this.watchers.get(sessionId)?.isActive() ?? false;
6327
6492
  }
6328
6493
  /**
6329
6494
  * Stop all watchers
@@ -6334,7 +6499,7 @@ var init_sandbox_file_watcher = __esm({
6334
6499
  this.watchers.clear();
6335
6500
  }
6336
6501
  };
6337
- globalRemoteWatcherManager = null;
6502
+ globalInSandboxManager = null;
6338
6503
  }
6339
6504
  });
6340
6505
  function extractErrorMessage(error) {
@@ -6383,6 +6548,7 @@ exports.SandboxFileSync = void 0;
6383
6548
  var init_sandbox_file_sync = __esm({
6384
6549
  "src/runtime/sandbox-file-sync.ts"() {
6385
6550
  init_sandbox_file_watcher();
6551
+ init_in_sandbox_watcher();
6386
6552
  init_types();
6387
6553
  exports.SandboxFileSync = class {
6388
6554
  fileStore;
@@ -6393,7 +6559,7 @@ var init_sandbox_file_sync = __esm({
6393
6559
  eventStorage;
6394
6560
  webhookConfig;
6395
6561
  // Watcher management
6396
- remoteWatchers = /* @__PURE__ */ new Map();
6562
+ inSandboxWatchers = /* @__PURE__ */ new Map();
6397
6563
  localWatchers = /* @__PURE__ */ new Map();
6398
6564
  fileChangeSubscribers = /* @__PURE__ */ new Set();
6399
6565
  // Sequence number cache per session (for event storage)
@@ -7184,34 +7350,27 @@ var init_sandbox_file_sync = __esm({
7184
7350
  this.localWatchers.set(sessionId, watcher);
7185
7351
  console.log(`[FILE_SYNC] Started local file watching for session ${sessionId} at ${opts.localPath}`);
7186
7352
  } else {
7187
- if (!this.sandboxOps) {
7188
- throw new Error("Sandbox operations not configured. Call setSandboxOperations first.");
7189
- }
7190
- const watcher = new exports.RemoteSandboxFileWatcher({
7353
+ const watcher = new exports.InSandboxWatcher({
7191
7354
  sessionId,
7192
- sandboxOps: this.sandboxOps,
7193
- basePath: watchPath,
7194
- // Use watchPath instead of sandboxBasePath
7195
- pollIntervalMs: opts.pollIntervalMs ?? 2e3,
7196
- ignored: opts.ignored ?? ["**/node_modules/**", "**/.git/**"],
7355
+ watchPath,
7197
7356
  onFileChange: handleFileChange,
7198
7357
  onError: (error) => {
7199
- console.error(`[FILE_SYNC] Remote watcher error for session ${sessionId}:`, error);
7358
+ console.error(`[FILE_SYNC] In-sandbox watcher error for session ${sessionId}:`, error);
7200
7359
  }
7201
7360
  });
7202
7361
  await watcher.start();
7203
- this.remoteWatchers.set(sessionId, watcher);
7204
- console.log(`[FILE_SYNC] Started remote file watching for session ${sessionId} at path: ${watchPath}`);
7362
+ this.inSandboxWatchers.set(sessionId, watcher);
7363
+ console.log(`[FILE_SYNC] Started in-sandbox file watching for session ${sessionId} at path: ${watchPath}`);
7205
7364
  }
7206
7365
  }
7207
7366
  /**
7208
7367
  * Stop watching a session's sandbox
7209
7368
  */
7210
7369
  async stopWatching(sessionId) {
7211
- const remoteWatcher = this.remoteWatchers.get(sessionId);
7212
- if (remoteWatcher) {
7213
- await remoteWatcher.stop();
7214
- this.remoteWatchers.delete(sessionId);
7370
+ const inSandboxWatcher = this.inSandboxWatchers.get(sessionId);
7371
+ if (inSandboxWatcher) {
7372
+ await inSandboxWatcher.stop();
7373
+ this.inSandboxWatchers.delete(sessionId);
7215
7374
  }
7216
7375
  const localWatcher = this.localWatchers.get(sessionId);
7217
7376
  if (localWatcher) {
@@ -7223,7 +7382,7 @@ var init_sandbox_file_sync = __esm({
7223
7382
  * Check if a session is being watched
7224
7383
  */
7225
7384
  isWatching(sessionId) {
7226
- return this.remoteWatchers.has(sessionId) || this.localWatchers.has(sessionId);
7385
+ return this.inSandboxWatchers.has(sessionId) || this.localWatchers.has(sessionId);
7227
7386
  }
7228
7387
  /**
7229
7388
  * Subscribe to file change events across all watched sessions.
@@ -7257,10 +7416,10 @@ var init_sandbox_file_sync = __esm({
7257
7416
  * Stop all watchers and cleanup
7258
7417
  */
7259
7418
  async stopAllWatching() {
7260
- const remotePromises = Array.from(this.remoteWatchers.values()).map((w) => w.stop());
7419
+ const inSandboxPromises = Array.from(this.inSandboxWatchers.values()).map((w) => w.stop());
7261
7420
  const localPromises = Array.from(this.localWatchers.values()).map((w) => w.stop());
7262
- await Promise.all([...remotePromises, ...localPromises]);
7263
- this.remoteWatchers.clear();
7421
+ await Promise.all([...inSandboxPromises, ...localPromises]);
7422
+ this.inSandboxWatchers.clear();
7264
7423
  this.localWatchers.clear();
7265
7424
  }
7266
7425
  /**
@@ -7268,8 +7427,8 @@ var init_sandbox_file_sync = __esm({
7268
7427
  */
7269
7428
  getWatchingStatus() {
7270
7429
  const statuses = [];
7271
- for (const [sessionId, watcher] of this.remoteWatchers) {
7272
- statuses.push({ sessionId, type: "remote", isActive: watcher.isActive() });
7430
+ for (const [sessionId, watcher] of this.inSandboxWatchers) {
7431
+ statuses.push({ sessionId, type: "in-sandbox", isActive: watcher.isActive() });
7273
7432
  }
7274
7433
  for (const [sessionId, watcher] of this.localWatchers) {
7275
7434
  statuses.push({ sessionId, type: "local", isActive: watcher.isActive() });
@@ -7280,364 +7439,6 @@ var init_sandbox_file_sync = __esm({
7280
7439
  }
7281
7440
  });
7282
7441
 
7283
- // src/runtime/in-sandbox-watcher.ts
7284
- function createInSandboxWatcher(options) {
7285
- return new exports.InSandboxWatcher(options);
7286
- }
7287
- function getInSandboxWatcherManager() {
7288
- if (!globalInSandboxManager) {
7289
- globalInSandboxManager = new exports.InSandboxWatcherManager();
7290
- }
7291
- return globalInSandboxManager;
7292
- }
7293
- function createInSandboxWatcherManager() {
7294
- return new exports.InSandboxWatcherManager();
7295
- }
7296
- var WATCHER_SCRIPT; exports.InSandboxWatcher = void 0; exports.InSandboxWatcherManager = void 0; var globalInSandboxManager;
7297
- var init_in_sandbox_watcher = __esm({
7298
- "src/runtime/in-sandbox-watcher.ts"() {
7299
- init_vercel_sandbox_executor();
7300
- WATCHER_SCRIPT = `
7301
- const fs = require('fs');
7302
- const path = require('path');
7303
-
7304
- const watchPath = process.argv[2] || '.';
7305
- const ignored = new Set(['node_modules', '.git', '.cache', '__pycache__']);
7306
-
7307
- // Track all watchers for cleanup
7308
- const watchers = new Map();
7309
-
7310
- // Debounce map to coalesce rapid changes
7311
- const pending = new Map();
7312
- const DEBOUNCE_MS = 100;
7313
-
7314
- function emit(type, filePath) {
7315
- const event = {
7316
- type,
7317
- path: filePath,
7318
- timestamp: Date.now()
7319
- };
7320
- console.log(JSON.stringify(event));
7321
- }
7322
-
7323
- function shouldIgnore(name) {
7324
- return ignored.has(name) || name.startsWith('.');
7325
- }
7326
-
7327
- function watchDir(dir) {
7328
- try {
7329
- const watcher = fs.watch(dir, { persistent: true }, (eventType, filename) => {
7330
- if (!filename || shouldIgnore(filename)) return;
7331
-
7332
- const fullPath = path.join(dir, filename);
7333
- const key = fullPath;
7334
-
7335
- // Debounce
7336
- if (pending.has(key)) {
7337
- clearTimeout(pending.get(key));
7338
- }
7339
-
7340
- pending.set(key, setTimeout(() => {
7341
- pending.delete(key);
7342
-
7343
- fs.stat(fullPath, (err, stats) => {
7344
- if (err) {
7345
- if (err.code === 'ENOENT') {
7346
- emit('unlink', fullPath);
7347
- // Stop watching if it was a directory
7348
- if (watchers.has(fullPath)) {
7349
- watchers.get(fullPath).close();
7350
- watchers.delete(fullPath);
7351
- }
7352
- }
7353
- } else {
7354
- const type = eventType === 'rename' ? 'add' : 'change';
7355
- emit(type, fullPath);
7356
-
7357
- // If it's a new directory, start watching it
7358
- if (stats.isDirectory() && !watchers.has(fullPath)) {
7359
- watchDir(fullPath);
7360
- }
7361
- }
7362
- });
7363
- }, DEBOUNCE_MS));
7364
- });
7365
-
7366
- watchers.set(dir, watcher);
7367
-
7368
- // Watch subdirectories
7369
- const entries = fs.readdirSync(dir, { withFileTypes: true });
7370
- for (const entry of entries) {
7371
- if (entry.isDirectory() && !shouldIgnore(entry.name)) {
7372
- watchDir(path.join(dir, entry.name));
7373
- }
7374
- }
7375
- } catch (err) {
7376
- // Directory may not exist or be inaccessible
7377
- console.error(JSON.stringify({ error: err.message, dir }));
7378
- }
7379
- }
7380
-
7381
- // Start watching
7382
- watchDir(watchPath);
7383
-
7384
- // Keep alive
7385
- process.on('SIGTERM', () => {
7386
- for (const watcher of watchers.values()) {
7387
- watcher.close();
7388
- }
7389
- process.exit(0);
7390
- });
7391
-
7392
- // Heartbeat to indicate we're running
7393
- setInterval(() => {
7394
- console.log(JSON.stringify({ heartbeat: true, timestamp: Date.now() }));
7395
- }, 5000);
7396
-
7397
- console.log(JSON.stringify({ started: true, path: watchPath }));
7398
- `;
7399
- exports.InSandboxWatcher = class {
7400
- sessionId;
7401
- watchPath;
7402
- outputPollIntervalMs;
7403
- sandboxState = null;
7404
- outputPollTimer = null;
7405
- subscribers = /* @__PURE__ */ new Set();
7406
- isRunning = false;
7407
- startedAt;
7408
- lastHeartbeat;
7409
- lastOutputPosition = 0;
7410
- onError;
7411
- onReady;
7412
- constructor(options) {
7413
- this.sessionId = options.sessionId;
7414
- this.watchPath = options.watchPath;
7415
- this.outputPollIntervalMs = options.outputPollIntervalMs ?? 1e3;
7416
- this.onError = options.onError;
7417
- this.onReady = options.onReady;
7418
- if (options.onFileChange) {
7419
- this.subscribers.add(options.onFileChange);
7420
- }
7421
- }
7422
- /**
7423
- * Start the watcher process inside the sandbox
7424
- */
7425
- async start() {
7426
- if (this.isRunning) {
7427
- console.warn(`[IN_SANDBOX_WATCHER] Already running for session ${this.sessionId}`);
7428
- return;
7429
- }
7430
- try {
7431
- this.sandboxState = await getOrCreateSandbox({
7432
- sessionId: this.sessionId,
7433
- runtime: "node22",
7434
- timeout: 600
7435
- });
7436
- const { sandbox } = this.sandboxState;
7437
- const scriptPath = "/tmp/.file-watcher.js";
7438
- const outputPath = "/tmp/.file-watcher-output.log";
7439
- const writeScriptCmd = `cat > ${scriptPath} << 'WATCHER_EOF'
7440
- ${WATCHER_SCRIPT}
7441
- WATCHER_EOF`;
7442
- await sandbox.runCommand({
7443
- cmd: "sh",
7444
- args: ["-c", writeScriptCmd]
7445
- });
7446
- const startCmd = `nohup node ${scriptPath} "${this.watchPath}" > ${outputPath} 2>&1 &`;
7447
- await sandbox.runCommand({
7448
- cmd: "sh",
7449
- args: ["-c", startCmd]
7450
- });
7451
- console.log(`[IN_SANDBOX_WATCHER] Started watcher in sandbox for ${this.watchPath}`);
7452
- this.isRunning = true;
7453
- this.startedAt = /* @__PURE__ */ new Date();
7454
- this.outputPollTimer = setInterval(async () => {
7455
- try {
7456
- await this.pollOutput(outputPath);
7457
- } catch (error) {
7458
- console.error("[IN_SANDBOX_WATCHER] Error polling output:", error);
7459
- if (this.onError && error instanceof Error) {
7460
- this.onError(error);
7461
- }
7462
- }
7463
- }, this.outputPollIntervalMs);
7464
- setTimeout(() => {
7465
- this.pollOutput(outputPath).catch(console.error);
7466
- }, 500);
7467
- } catch (error) {
7468
- console.error(`[IN_SANDBOX_WATCHER] Failed to start watcher:`, error);
7469
- throw error;
7470
- }
7471
- }
7472
- /**
7473
- * Poll the output file for new events
7474
- */
7475
- async pollOutput(outputPath) {
7476
- if (!this.sandboxState?.sandbox) return;
7477
- try {
7478
- const result = await this.sandboxState.sandbox.runCommand({
7479
- cmd: "sh",
7480
- args: ["-c", `tail -c +${this.lastOutputPosition + 1} ${outputPath} 2>/dev/null || true`]
7481
- });
7482
- const output = await result.stdout();
7483
- if (output && output.trim()) {
7484
- const sizeResult = await this.sandboxState.sandbox.runCommand({
7485
- cmd: "sh",
7486
- args: ["-c", `stat -c%s ${outputPath} 2>/dev/null || echo 0`]
7487
- });
7488
- const sizeStr = await sizeResult.stdout();
7489
- this.lastOutputPosition = parseInt(sizeStr.trim(), 10) || 0;
7490
- this.processOutput(output);
7491
- }
7492
- } catch {
7493
- }
7494
- }
7495
- /**
7496
- * Process output from the watcher script
7497
- */
7498
- processOutput(stdout) {
7499
- const lines = stdout.split("\n").filter(Boolean);
7500
- for (const line of lines) {
7501
- try {
7502
- const data = JSON.parse(line);
7503
- if (data.started) {
7504
- console.log(`[IN_SANDBOX_WATCHER] Watcher started for ${data.path}`);
7505
- if (this.onReady) {
7506
- this.onReady();
7507
- }
7508
- } else if (data.heartbeat) {
7509
- this.lastHeartbeat = new Date(data.timestamp);
7510
- } else if (data.error) {
7511
- console.warn(`[IN_SANDBOX_WATCHER] Error in sandbox:`, data.error);
7512
- } else if (data.type && data.path) {
7513
- this.emitEvent({
7514
- type: data.type,
7515
- relativePath: data.path.replace(/^\.\//, ""),
7516
- absolutePath: data.path,
7517
- basePath: this.watchPath,
7518
- sessionId: this.sessionId,
7519
- timestamp: new Date(data.timestamp)
7520
- });
7521
- }
7522
- } catch {
7523
- }
7524
- }
7525
- }
7526
- /**
7527
- * Stop the watcher process
7528
- */
7529
- async stop() {
7530
- if (!this.isRunning) {
7531
- return;
7532
- }
7533
- if (this.outputPollTimer) {
7534
- clearInterval(this.outputPollTimer);
7535
- this.outputPollTimer = null;
7536
- }
7537
- try {
7538
- if (this.sandboxState?.sandbox) {
7539
- await this.sandboxState.sandbox.runCommand({
7540
- cmd: "sh",
7541
- args: ["-c", "pkill -f file-watcher.js 2>/dev/null || true"]
7542
- });
7543
- await this.sandboxState.sandbox.runCommand({
7544
- cmd: "sh",
7545
- args: ["-c", "rm -f /tmp/.file-watcher.js /tmp/.file-watcher-output.log"]
7546
- });
7547
- }
7548
- } catch {
7549
- }
7550
- this.isRunning = false;
7551
- this.sandboxState = null;
7552
- this.lastOutputPosition = 0;
7553
- console.log(`[IN_SANDBOX_WATCHER] Stopped watcher for session ${this.sessionId}`);
7554
- }
7555
- /**
7556
- * Subscribe to file change events
7557
- */
7558
- subscribe(callback) {
7559
- this.subscribers.add(callback);
7560
- return () => this.subscribers.delete(callback);
7561
- }
7562
- /**
7563
- * Check if watcher is running
7564
- */
7565
- isActive() {
7566
- return this.isRunning;
7567
- }
7568
- /**
7569
- * Get watcher status
7570
- */
7571
- getStatus() {
7572
- return {
7573
- sessionId: this.sessionId,
7574
- watchPath: this.watchPath,
7575
- isRunning: this.isRunning,
7576
- startedAt: this.startedAt,
7577
- lastHeartbeat: this.lastHeartbeat
7578
- };
7579
- }
7580
- /**
7581
- * Emit event to all subscribers
7582
- */
7583
- async emitEvent(event) {
7584
- for (const callback of this.subscribers) {
7585
- try {
7586
- await callback(event);
7587
- } catch (error) {
7588
- console.error("[IN_SANDBOX_WATCHER] Error in subscriber callback:", error);
7589
- }
7590
- }
7591
- }
7592
- };
7593
- exports.InSandboxWatcherManager = class {
7594
- watchers = /* @__PURE__ */ new Map();
7595
- /**
7596
- * Start watching a sandbox
7597
- */
7598
- async startWatching(options) {
7599
- const { sessionId } = options;
7600
- await this.stopWatching(sessionId);
7601
- const watcher = new exports.InSandboxWatcher(options);
7602
- await watcher.start();
7603
- this.watchers.set(sessionId, watcher);
7604
- return watcher;
7605
- }
7606
- /**
7607
- * Stop watching a session
7608
- */
7609
- async stopWatching(sessionId) {
7610
- const watcher = this.watchers.get(sessionId);
7611
- if (watcher) {
7612
- await watcher.stop();
7613
- this.watchers.delete(sessionId);
7614
- }
7615
- }
7616
- /**
7617
- * Get watcher for a session
7618
- */
7619
- getWatcher(sessionId) {
7620
- return this.watchers.get(sessionId);
7621
- }
7622
- /**
7623
- * Check if watching
7624
- */
7625
- isWatching(sessionId) {
7626
- return this.watchers.get(sessionId)?.isActive() ?? false;
7627
- }
7628
- /**
7629
- * Stop all watchers
7630
- */
7631
- async stopAll() {
7632
- const promises = Array.from(this.watchers.values()).map((w) => w.stop());
7633
- await Promise.all(promises);
7634
- this.watchers.clear();
7635
- }
7636
- };
7637
- globalInSandboxManager = null;
7638
- }
7639
- });
7640
-
7641
7442
  // src/runtime/index.ts
7642
7443
  function generateDockerCommand(config) {
7643
7444
  const args = ["docker", "run"];
@@ -19064,8 +18865,6 @@ exports.createQueueProcessor = createQueueProcessor;
19064
18865
  exports.createQueueRouter = createQueueRouter;
19065
18866
  exports.createR2BundleStore = createR2BundleStore;
19066
18867
  exports.createR2FileStore = createR2FileStore;
19067
- exports.createRemoteFileWatcher = createRemoteFileWatcher;
19068
- exports.createRemoteFileWatcherManager = createRemoteFileWatcherManager;
19069
18868
  exports.createS3BundleStore = createS3BundleStore;
19070
18869
  exports.createS3FileStore = createS3FileStore;
19071
18870
  exports.createSandboxFileOperations = createSandboxFileOperations;
@@ -19091,6 +18890,7 @@ exports.defineConfig = defineConfig;
19091
18890
  exports.ensureSandboxPoolInitialized = ensureSandboxPoolInitialized;
19092
18891
  exports.env = env;
19093
18892
  exports.envOptional = envOptional;
18893
+ exports.executeCommandInSandbox = executeCommandInSandbox;
19094
18894
  exports.extractTextContent = extractTextContent;
19095
18895
  exports.extractTextFromMessage = extractTextFromMessage;
19096
18896
  exports.formatToolName = formatToolName;
@@ -19103,12 +18903,12 @@ exports.getActionIcon = getActionIcon;
19103
18903
  exports.getActionLabel = getActionLabel;
19104
18904
  exports.getAllHeartbeatStatuses = getAllHeartbeatStatuses;
19105
18905
  exports.getApiKeyEnvVar = getApiKeyEnvVar;
18906
+ exports.getCachedSandbox = getCachedSandbox;
19106
18907
  exports.getDefaultModel = getDefaultModel;
19107
18908
  exports.getFileWatcherManager = getFileWatcherManager;
19108
18909
  exports.getHeartbeatStatus = getHeartbeatStatus;
19109
18910
  exports.getInSandboxWatcherManager = getInSandboxWatcherManager;
19110
18911
  exports.getOrCreateSandbox = getOrCreateSandbox;
19111
- exports.getRemoteFileWatcherManager = getRemoteFileWatcherManager;
19112
18912
  exports.getSandboxCacheStats = getSandboxCacheStats;
19113
18913
  exports.getSandboxPool = getSandboxPool;
19114
18914
  exports.getWorkspaceManager = getWorkspaceManager;