@ash-cloud/ash-ai 0.1.12 → 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 {
@@ -6021,6 +6074,7 @@ var init_sandbox_file_watcher = __esm({
6021
6074
  type,
6022
6075
  relativePath,
6023
6076
  absolutePath,
6077
+ basePath: this.watchPath,
6024
6078
  sessionId: this.sessionId,
6025
6079
  fileSize,
6026
6080
  timestamp: /* @__PURE__ */ new Date()
@@ -6089,212 +6143,327 @@ var init_sandbox_file_watcher = __esm({
6089
6143
  }
6090
6144
  };
6091
6145
  globalWatcherManager = null;
6092
- 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 {
6093
6266
  sessionId;
6094
- sandboxOps;
6095
- basePath;
6096
- pollIntervalMs;
6097
- ignored;
6098
- pollTimer = null;
6099
- previousFiles = /* @__PURE__ */ new Map();
6267
+ watchPath;
6268
+ outputPollIntervalMs;
6269
+ sandboxState = null;
6270
+ outputPollTimer = null;
6100
6271
  subscribers = /* @__PURE__ */ new Set();
6101
- isWatching = false;
6272
+ isRunning = false;
6102
6273
  startedAt;
6103
- lastPollAt;
6104
- pollCount = 0;
6274
+ lastHeartbeat;
6275
+ lastOutputPosition = 0;
6105
6276
  onError;
6277
+ onReady;
6106
6278
  constructor(options) {
6107
6279
  this.sessionId = options.sessionId;
6108
- this.sandboxOps = options.sandboxOps;
6109
- this.basePath = options.basePath;
6110
- this.pollIntervalMs = options.pollIntervalMs ?? 2e3;
6111
- this.ignored = options.ignored ?? ["**/node_modules/**", "**/.git/**"];
6280
+ this.watchPath = options.watchPath;
6281
+ this.outputPollIntervalMs = options.outputPollIntervalMs ?? 1e3;
6112
6282
  this.onError = options.onError;
6283
+ this.onReady = options.onReady;
6113
6284
  if (options.onFileChange) {
6114
6285
  this.subscribers.add(options.onFileChange);
6115
6286
  }
6116
6287
  }
6117
6288
  /**
6118
- * Start polling for file changes
6289
+ * Start the watcher process inside the sandbox
6119
6290
  */
6120
6291
  async start() {
6121
- if (this.isWatching) {
6122
- 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}`);
6123
6294
  return;
6124
6295
  }
6125
- if (!this.sandboxOps.isSandboxRunning(this.sessionId)) {
6126
- throw new Error(`Sandbox is not running for session ${this.sessionId}`);
6127
- }
6128
- await this.scan(true);
6129
- this.pollTimer = setInterval(async () => {
6130
- try {
6131
- await this.scan(false);
6132
- } catch (error) {
6133
- console.error(`[REMOTE_WATCHER] Poll error for session ${this.sessionId}:`, error);
6134
- if (this.onError && error instanceof Error) {
6135
- this.onError(error);
6136
- }
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.`);
6137
6300
  }
6138
- }, this.pollIntervalMs);
6139
- this.isWatching = true;
6140
- this.startedAt = /* @__PURE__ */ new Date();
6141
- console.log(`[REMOTE_WATCHER] Started watching session ${this.sessionId} at ${this.basePath}`);
6142
- }
6143
- /**
6144
- * Stop polling and cleanup
6145
- */
6146
- async stop() {
6147
- if (!this.isWatching) {
6148
- return;
6149
- }
6150
- if (this.pollTimer) {
6151
- clearInterval(this.pollTimer);
6152
- 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;
6153
6335
  }
6154
- this.isWatching = false;
6155
- this.previousFiles.clear();
6156
- console.log(`[REMOTE_WATCHER] Stopped watching session ${this.sessionId}`);
6157
- }
6158
- /**
6159
- * Subscribe to file change events
6160
- */
6161
- subscribe(callback) {
6162
- this.subscribers.add(callback);
6163
- return () => this.subscribers.delete(callback);
6164
6336
  }
6165
6337
  /**
6166
- * Remove a subscriber
6338
+ * Poll the output file for new events
6167
6339
  */
6168
- unsubscribe(callback) {
6169
- 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
+ }
6170
6359
  }
6171
6360
  /**
6172
- * Check if watcher is active
6361
+ * Process output from the watcher script
6173
6362
  */
6174
- isActive() {
6175
- return this.isWatching;
6176
- }
6177
- /**
6178
- * Get watcher status
6179
- */
6180
- getStatus() {
6181
- return {
6182
- sessionId: this.sessionId,
6183
- watchPath: this.basePath,
6184
- isWatching: this.isWatching,
6185
- watchedFileCount: this.previousFiles.size,
6186
- pendingEventCount: 0,
6187
- startedAt: this.startedAt,
6188
- lastPollAt: this.lastPollAt,
6189
- pollCount: this.pollCount
6190
- };
6191
- }
6192
- /**
6193
- * Force an immediate scan (useful for testing or manual refresh)
6194
- */
6195
- async forceScan() {
6196
- 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
+ }
6197
6390
  }
6198
6391
  /**
6199
- * Scan the sandbox for file changes
6392
+ * Stop the watcher process
6200
6393
  */
6201
- async scan(isInitial) {
6202
- if (!this.sandboxOps.isSandboxRunning(this.sessionId)) {
6203
- console.warn(`[REMOTE_WATCHER] Sandbox stopped for session ${this.sessionId}`);
6204
- await this.stop();
6394
+ async stop() {
6395
+ if (!this.isRunning) {
6205
6396
  return;
6206
6397
  }
6207
- const listResult = await this.sandboxOps.listFiles(this.sessionId, this.basePath);
6208
- if (!listResult.success || !listResult.files) {
6209
- throw new Error(listResult.error ?? "Failed to list files in sandbox");
6210
- }
6211
- const currentFiles = /* @__PURE__ */ new Map();
6212
- for (const filePath of listResult.files) {
6213
- if (this.shouldIgnore(filePath)) {
6214
- continue;
6215
- }
6216
- currentFiles.set(filePath, { path: filePath });
6217
- }
6218
- this.lastPollAt = /* @__PURE__ */ new Date();
6219
- this.pollCount++;
6220
- if (isInitial) {
6221
- this.previousFiles = currentFiles;
6222
- return;
6398
+ if (this.outputPollTimer) {
6399
+ clearInterval(this.outputPollTimer);
6400
+ this.outputPollTimer = null;
6223
6401
  }
6224
- const changes = [];
6225
- for (const [filePath, info] of currentFiles) {
6226
- const previous = this.previousFiles.get(filePath);
6227
- if (!previous) {
6228
- changes.push({
6229
- type: "add",
6230
- relativePath: filePath,
6231
- absolutePath: `${this.basePath}/${filePath}`,
6232
- sessionId: this.sessionId,
6233
- fileSize: info.size,
6234
- 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"]
6235
6407
  });
6236
- }
6237
- }
6238
- for (const [filePath] of this.previousFiles) {
6239
- if (!currentFiles.has(filePath)) {
6240
- changes.push({
6241
- type: "unlink",
6242
- relativePath: filePath,
6243
- absolutePath: `${this.basePath}/${filePath}`,
6244
- sessionId: this.sessionId,
6245
- 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"]
6246
6411
  });
6247
6412
  }
6413
+ } catch {
6248
6414
  }
6249
- this.previousFiles = currentFiles;
6250
- for (const event of changes) {
6251
- await this.emitEvent(event);
6252
- }
6415
+ this.isRunning = false;
6416
+ this.sandboxState = null;
6417
+ this.lastOutputPosition = 0;
6418
+ console.log(`[IN_SANDBOX_WATCHER] Stopped watcher for session ${this.sessionId}`);
6253
6419
  }
6254
6420
  /**
6255
- * Check if a path should be ignored
6421
+ * Subscribe to file change events
6256
6422
  */
6257
- shouldIgnore(filePath) {
6258
- for (const pattern of this.ignored) {
6259
- if (this.matchesGlob(filePath, pattern)) {
6260
- return true;
6261
- }
6262
- }
6263
- return false;
6423
+ subscribe(callback) {
6424
+ this.subscribers.add(callback);
6425
+ return () => this.subscribers.delete(callback);
6264
6426
  }
6265
6427
  /**
6266
- * Simple glob matching (supports ** and *)
6428
+ * Check if watcher is running
6267
6429
  */
6268
- matchesGlob(filePath, pattern) {
6269
- const regexPattern = pattern.replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/{{GLOBSTAR}}/g, ".*").replace(/\//g, "\\/");
6270
- const regex = new RegExp(`^${regexPattern}$`);
6271
- return regex.test(filePath);
6430
+ isActive() {
6431
+ return this.isRunning;
6272
6432
  }
6273
6433
  /**
6274
- * 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
6275
6447
  */
6276
6448
  async emitEvent(event) {
6277
6449
  for (const callback of this.subscribers) {
6278
6450
  try {
6279
6451
  await callback(event);
6280
6452
  } catch (error) {
6281
- console.error("[REMOTE_WATCHER] Error in subscriber callback:", error);
6453
+ console.error("[IN_SANDBOX_WATCHER] Error in subscriber callback:", error);
6282
6454
  }
6283
6455
  }
6284
6456
  }
6285
6457
  };
6286
- exports.RemoteFileWatcherManager = class {
6458
+ exports.InSandboxWatcherManager = class {
6287
6459
  watchers = /* @__PURE__ */ new Map();
6288
6460
  /**
6289
- * Start watching a session's sandbox
6461
+ * Start watching a sandbox
6290
6462
  */
6291
6463
  async startWatching(options) {
6292
6464
  const { sessionId } = options;
6293
- const existing = this.watchers.get(sessionId);
6294
- if (existing) {
6295
- await existing.stop();
6296
- }
6297
- const watcher = new exports.RemoteSandboxFileWatcher(options);
6465
+ await this.stopWatching(sessionId);
6466
+ const watcher = new exports.InSandboxWatcher(options);
6298
6467
  await watcher.start();
6299
6468
  this.watchers.set(sessionId, watcher);
6300
6469
  return watcher;
@@ -6310,17 +6479,16 @@ var init_sandbox_file_watcher = __esm({
6310
6479
  }
6311
6480
  }
6312
6481
  /**
6313
- * Get a watcher for a session
6482
+ * Get watcher for a session
6314
6483
  */
6315
6484
  getWatcher(sessionId) {
6316
6485
  return this.watchers.get(sessionId);
6317
6486
  }
6318
6487
  /**
6319
- * Check if a session is being watched
6488
+ * Check if watching
6320
6489
  */
6321
6490
  isWatching(sessionId) {
6322
- const watcher = this.watchers.get(sessionId);
6323
- return watcher?.isActive() ?? false;
6491
+ return this.watchers.get(sessionId)?.isActive() ?? false;
6324
6492
  }
6325
6493
  /**
6326
6494
  * Stop all watchers
@@ -6331,7 +6499,7 @@ var init_sandbox_file_watcher = __esm({
6331
6499
  this.watchers.clear();
6332
6500
  }
6333
6501
  };
6334
- globalRemoteWatcherManager = null;
6502
+ globalInSandboxManager = null;
6335
6503
  }
6336
6504
  });
6337
6505
  function extractErrorMessage(error) {
@@ -6380,6 +6548,7 @@ exports.SandboxFileSync = void 0;
6380
6548
  var init_sandbox_file_sync = __esm({
6381
6549
  "src/runtime/sandbox-file-sync.ts"() {
6382
6550
  init_sandbox_file_watcher();
6551
+ init_in_sandbox_watcher();
6383
6552
  init_types();
6384
6553
  exports.SandboxFileSync = class {
6385
6554
  fileStore;
@@ -6390,7 +6559,7 @@ var init_sandbox_file_sync = __esm({
6390
6559
  eventStorage;
6391
6560
  webhookConfig;
6392
6561
  // Watcher management
6393
- remoteWatchers = /* @__PURE__ */ new Map();
6562
+ inSandboxWatchers = /* @__PURE__ */ new Map();
6394
6563
  localWatchers = /* @__PURE__ */ new Map();
6395
6564
  fileChangeSubscribers = /* @__PURE__ */ new Set();
6396
6565
  // Sequence number cache per session (for event storage)
@@ -6545,7 +6714,7 @@ var init_sandbox_file_sync = __esm({
6545
6714
  }
6546
6715
  };
6547
6716
  const webhookType = payload.event === "file_sync" ? "file_sync" : "file_change";
6548
- const filePath = payload.fileSyncEvent?.filePath ?? payload.fileChangeEvent?.relativePath;
6717
+ const filePath = payload.fileSyncEvent?.canonicalPath ?? payload.fileChangeEvent?.relativePath;
6549
6718
  const operation = payload.fileSyncEvent?.operation ?? payload.fileChangeEvent?.type;
6550
6719
  if (isAsync) {
6551
6720
  sendWithRetry(0).then(async (result) => {
@@ -6675,7 +6844,9 @@ var init_sandbox_file_sync = __esm({
6675
6844
  const eventData = {
6676
6845
  operation: event.operation,
6677
6846
  source: event.source,
6678
- filePath: event.filePath,
6847
+ canonicalPath: event.canonicalPath,
6848
+ basePath: event.basePath,
6849
+ sandboxPath: event.sandboxPath,
6679
6850
  fileSize: event.fileSize,
6680
6851
  success: event.success,
6681
6852
  error: event.error,
@@ -6706,6 +6877,13 @@ var init_sandbox_file_sync = __esm({
6706
6877
  setSandboxOperations(ops) {
6707
6878
  this.sandboxOps = ops;
6708
6879
  }
6880
+ /**
6881
+ * Get the effective base path for sandbox operations
6882
+ * @param targetPath - Optional override for the base path
6883
+ */
6884
+ getBasePath(targetPath) {
6885
+ return targetPath ?? this.sandboxBasePath;
6886
+ }
6709
6887
  /**
6710
6888
  * Get the full sandbox path for a file
6711
6889
  * @param path - The relative file path
@@ -6713,12 +6891,30 @@ var init_sandbox_file_sync = __esm({
6713
6891
  */
6714
6892
  getSandboxPath(path15, targetPath) {
6715
6893
  const normalizedPath = path15.replace(/^\/+/, "");
6716
- const basePath = targetPath ?? this.sandboxBasePath;
6894
+ const basePath = this.getBasePath(targetPath);
6717
6895
  if (basePath === ".") {
6718
6896
  return normalizedPath;
6719
6897
  }
6720
6898
  return `${basePath}/${normalizedPath}`;
6721
6899
  }
6900
+ /**
6901
+ * Build all path fields for a FileSyncEvent
6902
+ * @param path - The canonical file path (used as S3 key)
6903
+ * @param targetPath - Optional override for the base path
6904
+ */
6905
+ buildPathFields(path15, targetPath) {
6906
+ const normalizedPath = path15.replace(/^\/+/, "");
6907
+ const basePath = this.getBasePath(targetPath);
6908
+ const sandboxPath = this.getSandboxPath(path15, targetPath);
6909
+ return {
6910
+ canonicalPath: normalizedPath,
6911
+ // The logical path (S3 key)
6912
+ basePath,
6913
+ // The prefix used in sandbox
6914
+ sandboxPath
6915
+ // Full computed path in sandbox
6916
+ };
6917
+ }
6722
6918
  /**
6723
6919
  * Push a file: writes to S3, then to sandbox if running
6724
6920
  * @param sessionId - Session ID
@@ -6735,13 +6931,14 @@ var init_sandbox_file_sync = __esm({
6735
6931
  };
6736
6932
  const source = options?.source ?? "client_api";
6737
6933
  const uploadToStorageOp = source === "client_api" ? "client_uploaded_to_ash_storage" : "agent_sandbox_saved_to_ash_storage";
6934
+ const pathFields = this.buildPathFields(path15, options?.targetPath);
6738
6935
  try {
6739
6936
  await this.fileStore.writeFile(sessionId, path15, content);
6740
6937
  result.s3Written = true;
6741
6938
  await this.emitFileEvent(sessionId, {
6742
6939
  operation: uploadToStorageOp,
6743
6940
  source,
6744
- filePath: path15,
6941
+ ...pathFields,
6745
6942
  fileSize: content.length,
6746
6943
  success: true,
6747
6944
  previousContent,
@@ -6754,7 +6951,7 @@ var init_sandbox_file_sync = __esm({
6754
6951
  await this.emitFileEvent(sessionId, {
6755
6952
  operation: uploadToStorageOp,
6756
6953
  source,
6757
- filePath: path15,
6954
+ ...pathFields,
6758
6955
  fileSize: content.length,
6759
6956
  success: false,
6760
6957
  error: errorMessage
@@ -6763,13 +6960,12 @@ var init_sandbox_file_sync = __esm({
6763
6960
  }
6764
6961
  if (this.sandboxOps?.isSandboxRunning(sessionId)) {
6765
6962
  try {
6766
- const sandboxPath = this.getSandboxPath(path15, options?.targetPath);
6767
- const writeResult = await this.sandboxOps.writeFile(sessionId, sandboxPath, content);
6963
+ const writeResult = await this.sandboxOps.writeFile(sessionId, pathFields.sandboxPath, content);
6768
6964
  result.sandboxWritten = writeResult.success;
6769
6965
  await this.emitFileEvent(sessionId, {
6770
6966
  operation: "ash_storage_synced_to_agent_sandbox",
6771
6967
  source,
6772
- filePath: path15,
6968
+ ...pathFields,
6773
6969
  fileSize: content.length,
6774
6970
  success: writeResult.success,
6775
6971
  error: writeResult.error
@@ -6783,7 +6979,7 @@ var init_sandbox_file_sync = __esm({
6783
6979
  await this.emitFileEvent(sessionId, {
6784
6980
  operation: "ash_storage_synced_to_agent_sandbox",
6785
6981
  source,
6786
- filePath: path15,
6982
+ ...pathFields,
6787
6983
  fileSize: content.length,
6788
6984
  success: false,
6789
6985
  error: errorMessage
@@ -6826,15 +7022,15 @@ var init_sandbox_file_sync = __esm({
6826
7022
  result.error = "Sandbox is not running";
6827
7023
  return result;
6828
7024
  }
7025
+ const pathFields = this.buildPathFields(path15, options?.targetPath);
6829
7026
  try {
6830
- const sandboxPath = this.getSandboxPath(path15, options?.targetPath);
6831
- const readResult = await this.sandboxOps.readFile(sessionId, sandboxPath);
7027
+ const readResult = await this.sandboxOps.readFile(sessionId, pathFields.sandboxPath);
6832
7028
  if (!readResult.success || !readResult.content) {
6833
7029
  result.error = readResult.error ?? "File not found in sandbox";
6834
7030
  await this.emitFileEvent(sessionId, {
6835
7031
  operation: "read_from_agent_sandbox",
6836
7032
  source: "ash_file_sync",
6837
- filePath: path15,
7033
+ ...pathFields,
6838
7034
  success: false,
6839
7035
  error: result.error
6840
7036
  });
@@ -6844,7 +7040,7 @@ var init_sandbox_file_sync = __esm({
6844
7040
  await this.emitFileEvent(sessionId, {
6845
7041
  operation: "read_from_agent_sandbox",
6846
7042
  source: "ash_file_sync",
6847
- filePath: path15,
7043
+ ...pathFields,
6848
7044
  fileSize: readResult.content.length,
6849
7045
  success: true,
6850
7046
  newContent: readResult.content
@@ -6855,7 +7051,7 @@ var init_sandbox_file_sync = __esm({
6855
7051
  await this.emitFileEvent(sessionId, {
6856
7052
  operation: "read_from_agent_sandbox",
6857
7053
  source: "ash_file_sync",
6858
- filePath: path15,
7054
+ ...pathFields,
6859
7055
  success: false,
6860
7056
  error: errorMessage
6861
7057
  });
@@ -6867,7 +7063,7 @@ var init_sandbox_file_sync = __esm({
6867
7063
  await this.emitFileEvent(sessionId, {
6868
7064
  operation: "agent_sandbox_saved_to_ash_storage",
6869
7065
  source: "ash_file_sync",
6870
- filePath: path15,
7066
+ ...pathFields,
6871
7067
  fileSize: result.content.length,
6872
7068
  success: true,
6873
7069
  newContent: result.content
@@ -6879,7 +7075,7 @@ var init_sandbox_file_sync = __esm({
6879
7075
  await this.emitFileEvent(sessionId, {
6880
7076
  operation: "agent_sandbox_saved_to_ash_storage",
6881
7077
  source: "ash_file_sync",
6882
- filePath: path15,
7078
+ ...pathFields,
6883
7079
  fileSize: result.content?.length,
6884
7080
  success: false,
6885
7081
  error: errorMessage
@@ -6921,13 +7117,14 @@ var init_sandbox_file_sync = __esm({
6921
7117
  */
6922
7118
  async deleteFile(sessionId, path15) {
6923
7119
  const result = { s3Deleted: false, sandboxDeleted: false };
7120
+ const pathFields = this.buildPathFields(path15);
6924
7121
  try {
6925
7122
  await this.fileStore.deleteFile(sessionId, path15);
6926
7123
  result.s3Deleted = true;
6927
7124
  await this.emitFileEvent(sessionId, {
6928
7125
  operation: "deleted_from_ash_storage",
6929
7126
  source: "ash_file_sync",
6930
- filePath: path15,
7127
+ ...pathFields,
6931
7128
  success: true
6932
7129
  });
6933
7130
  } catch (error) {
@@ -6936,20 +7133,19 @@ var init_sandbox_file_sync = __esm({
6936
7133
  await this.emitFileEvent(sessionId, {
6937
7134
  operation: "deleted_from_ash_storage",
6938
7135
  source: "ash_file_sync",
6939
- filePath: path15,
7136
+ ...pathFields,
6940
7137
  success: false,
6941
7138
  error: errorMessage
6942
7139
  });
6943
7140
  }
6944
7141
  if (this.sandboxOps?.isSandboxRunning(sessionId)) {
6945
7142
  try {
6946
- const sandboxPath = this.getSandboxPath(path15);
6947
- console.log(`[FILE_SYNC] Would delete ${sandboxPath} from sandbox`);
7143
+ console.log(`[FILE_SYNC] Would delete ${pathFields.sandboxPath} from sandbox`);
6948
7144
  result.sandboxDeleted = true;
6949
7145
  await this.emitFileEvent(sessionId, {
6950
7146
  operation: "deleted_from_agent_sandbox",
6951
7147
  source: "ash_file_sync",
6952
- filePath: path15,
7148
+ ...pathFields,
6953
7149
  success: true
6954
7150
  });
6955
7151
  } catch (error) {
@@ -6957,7 +7153,7 @@ var init_sandbox_file_sync = __esm({
6957
7153
  await this.emitFileEvent(sessionId, {
6958
7154
  operation: "deleted_from_agent_sandbox",
6959
7155
  source: "ash_file_sync",
6960
- filePath: path15,
7156
+ ...pathFields,
6961
7157
  success: false,
6962
7158
  error: errorMessage
6963
7159
  });
@@ -6977,6 +7173,7 @@ var init_sandbox_file_sync = __esm({
6977
7173
  }
6978
7174
  const files = await this.fileStore.listFiles(sessionId);
6979
7175
  for (const file of files) {
7176
+ const pathFields = this.buildPathFields(file.path);
6980
7177
  try {
6981
7178
  const content = await this.fileStore.readFile(sessionId, file.path);
6982
7179
  if (!content) {
@@ -6984,20 +7181,19 @@ var init_sandbox_file_sync = __esm({
6984
7181
  await this.emitFileEvent(sessionId, {
6985
7182
  operation: "ash_storage_synced_to_agent_sandbox",
6986
7183
  source: "ash_file_sync",
6987
- filePath: file.path,
7184
+ ...pathFields,
6988
7185
  success: false,
6989
7186
  error: "File not found in Ash storage"
6990
7187
  });
6991
7188
  continue;
6992
7189
  }
6993
- const sandboxPath = this.getSandboxPath(file.path);
6994
- const writeResult = await this.sandboxOps.writeFile(sessionId, sandboxPath, content);
7190
+ const writeResult = await this.sandboxOps.writeFile(sessionId, pathFields.sandboxPath, content);
6995
7191
  if (writeResult.success) {
6996
7192
  result.fileCount++;
6997
7193
  await this.emitFileEvent(sessionId, {
6998
7194
  operation: "ash_storage_synced_to_agent_sandbox",
6999
7195
  source: "ash_file_sync",
7000
- filePath: file.path,
7196
+ ...pathFields,
7001
7197
  fileSize: content.length,
7002
7198
  success: true
7003
7199
  });
@@ -7006,7 +7202,7 @@ var init_sandbox_file_sync = __esm({
7006
7202
  await this.emitFileEvent(sessionId, {
7007
7203
  operation: "ash_storage_synced_to_agent_sandbox",
7008
7204
  source: "ash_file_sync",
7009
- filePath: file.path,
7205
+ ...pathFields,
7010
7206
  fileSize: content.length,
7011
7207
  success: false,
7012
7208
  error: writeResult.error
@@ -7021,7 +7217,7 @@ var init_sandbox_file_sync = __esm({
7021
7217
  await this.emitFileEvent(sessionId, {
7022
7218
  operation: "ash_storage_synced_to_agent_sandbox",
7023
7219
  source: "ash_file_sync",
7024
- filePath: file.path,
7220
+ ...pathFields,
7025
7221
  success: false,
7026
7222
  error: errorMessage
7027
7223
  });
@@ -7052,24 +7248,25 @@ var init_sandbox_file_sync = __esm({
7052
7248
  return result;
7053
7249
  }
7054
7250
  }
7055
- for (const filePath of filesToSync) {
7251
+ for (const file of filesToSync) {
7252
+ const pathFields = this.buildPathFields(file);
7056
7253
  try {
7057
- const pullResult = await this.pullFile(sessionId, filePath);
7254
+ const pullResult = await this.pullFile(sessionId, file);
7058
7255
  if (pullResult.s3Written) {
7059
7256
  result.fileCount++;
7060
7257
  } else if (pullResult.error) {
7061
- result.errors.push({ path: filePath, error: pullResult.error });
7258
+ result.errors.push({ path: file, error: pullResult.error });
7062
7259
  }
7063
7260
  } catch (error) {
7064
7261
  const errorMessage = extractErrorMessage(error);
7065
7262
  result.errors.push({
7066
- path: filePath,
7263
+ path: file,
7067
7264
  error: errorMessage
7068
7265
  });
7069
7266
  await this.emitFileEvent(sessionId, {
7070
7267
  operation: "agent_sandbox_saved_to_ash_storage",
7071
7268
  source: "ash_file_sync",
7072
- filePath,
7269
+ ...pathFields,
7073
7270
  success: false,
7074
7271
  error: errorMessage
7075
7272
  });
@@ -7153,34 +7350,27 @@ var init_sandbox_file_sync = __esm({
7153
7350
  this.localWatchers.set(sessionId, watcher);
7154
7351
  console.log(`[FILE_SYNC] Started local file watching for session ${sessionId} at ${opts.localPath}`);
7155
7352
  } else {
7156
- if (!this.sandboxOps) {
7157
- throw new Error("Sandbox operations not configured. Call setSandboxOperations first.");
7158
- }
7159
- const watcher = new exports.RemoteSandboxFileWatcher({
7353
+ const watcher = new exports.InSandboxWatcher({
7160
7354
  sessionId,
7161
- sandboxOps: this.sandboxOps,
7162
- basePath: watchPath,
7163
- // Use watchPath instead of sandboxBasePath
7164
- pollIntervalMs: opts.pollIntervalMs ?? 2e3,
7165
- ignored: opts.ignored ?? ["**/node_modules/**", "**/.git/**"],
7355
+ watchPath,
7166
7356
  onFileChange: handleFileChange,
7167
7357
  onError: (error) => {
7168
- console.error(`[FILE_SYNC] Remote watcher error for session ${sessionId}:`, error);
7358
+ console.error(`[FILE_SYNC] In-sandbox watcher error for session ${sessionId}:`, error);
7169
7359
  }
7170
7360
  });
7171
7361
  await watcher.start();
7172
- this.remoteWatchers.set(sessionId, watcher);
7173
- 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}`);
7174
7364
  }
7175
7365
  }
7176
7366
  /**
7177
7367
  * Stop watching a session's sandbox
7178
7368
  */
7179
7369
  async stopWatching(sessionId) {
7180
- const remoteWatcher = this.remoteWatchers.get(sessionId);
7181
- if (remoteWatcher) {
7182
- await remoteWatcher.stop();
7183
- this.remoteWatchers.delete(sessionId);
7370
+ const inSandboxWatcher = this.inSandboxWatchers.get(sessionId);
7371
+ if (inSandboxWatcher) {
7372
+ await inSandboxWatcher.stop();
7373
+ this.inSandboxWatchers.delete(sessionId);
7184
7374
  }
7185
7375
  const localWatcher = this.localWatchers.get(sessionId);
7186
7376
  if (localWatcher) {
@@ -7192,7 +7382,7 @@ var init_sandbox_file_sync = __esm({
7192
7382
  * Check if a session is being watched
7193
7383
  */
7194
7384
  isWatching(sessionId) {
7195
- return this.remoteWatchers.has(sessionId) || this.localWatchers.has(sessionId);
7385
+ return this.inSandboxWatchers.has(sessionId) || this.localWatchers.has(sessionId);
7196
7386
  }
7197
7387
  /**
7198
7388
  * Subscribe to file change events across all watched sessions.
@@ -7226,10 +7416,10 @@ var init_sandbox_file_sync = __esm({
7226
7416
  * Stop all watchers and cleanup
7227
7417
  */
7228
7418
  async stopAllWatching() {
7229
- const remotePromises = Array.from(this.remoteWatchers.values()).map((w) => w.stop());
7419
+ const inSandboxPromises = Array.from(this.inSandboxWatchers.values()).map((w) => w.stop());
7230
7420
  const localPromises = Array.from(this.localWatchers.values()).map((w) => w.stop());
7231
- await Promise.all([...remotePromises, ...localPromises]);
7232
- this.remoteWatchers.clear();
7421
+ await Promise.all([...inSandboxPromises, ...localPromises]);
7422
+ this.inSandboxWatchers.clear();
7233
7423
  this.localWatchers.clear();
7234
7424
  }
7235
7425
  /**
@@ -7237,8 +7427,8 @@ var init_sandbox_file_sync = __esm({
7237
7427
  */
7238
7428
  getWatchingStatus() {
7239
7429
  const statuses = [];
7240
- for (const [sessionId, watcher] of this.remoteWatchers) {
7241
- 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() });
7242
7432
  }
7243
7433
  for (const [sessionId, watcher] of this.localWatchers) {
7244
7434
  statuses.push({ sessionId, type: "local", isActive: watcher.isActive() });
@@ -7249,363 +7439,6 @@ var init_sandbox_file_sync = __esm({
7249
7439
  }
7250
7440
  });
7251
7441
 
7252
- // src/runtime/in-sandbox-watcher.ts
7253
- function createInSandboxWatcher(options) {
7254
- return new exports.InSandboxWatcher(options);
7255
- }
7256
- function getInSandboxWatcherManager() {
7257
- if (!globalInSandboxManager) {
7258
- globalInSandboxManager = new exports.InSandboxWatcherManager();
7259
- }
7260
- return globalInSandboxManager;
7261
- }
7262
- function createInSandboxWatcherManager() {
7263
- return new exports.InSandboxWatcherManager();
7264
- }
7265
- var WATCHER_SCRIPT; exports.InSandboxWatcher = void 0; exports.InSandboxWatcherManager = void 0; var globalInSandboxManager;
7266
- var init_in_sandbox_watcher = __esm({
7267
- "src/runtime/in-sandbox-watcher.ts"() {
7268
- init_vercel_sandbox_executor();
7269
- WATCHER_SCRIPT = `
7270
- const fs = require('fs');
7271
- const path = require('path');
7272
-
7273
- const watchPath = process.argv[2] || '.';
7274
- const ignored = new Set(['node_modules', '.git', '.cache', '__pycache__']);
7275
-
7276
- // Track all watchers for cleanup
7277
- const watchers = new Map();
7278
-
7279
- // Debounce map to coalesce rapid changes
7280
- const pending = new Map();
7281
- const DEBOUNCE_MS = 100;
7282
-
7283
- function emit(type, filePath) {
7284
- const event = {
7285
- type,
7286
- path: filePath,
7287
- timestamp: Date.now()
7288
- };
7289
- console.log(JSON.stringify(event));
7290
- }
7291
-
7292
- function shouldIgnore(name) {
7293
- return ignored.has(name) || name.startsWith('.');
7294
- }
7295
-
7296
- function watchDir(dir) {
7297
- try {
7298
- const watcher = fs.watch(dir, { persistent: true }, (eventType, filename) => {
7299
- if (!filename || shouldIgnore(filename)) return;
7300
-
7301
- const fullPath = path.join(dir, filename);
7302
- const key = fullPath;
7303
-
7304
- // Debounce
7305
- if (pending.has(key)) {
7306
- clearTimeout(pending.get(key));
7307
- }
7308
-
7309
- pending.set(key, setTimeout(() => {
7310
- pending.delete(key);
7311
-
7312
- fs.stat(fullPath, (err, stats) => {
7313
- if (err) {
7314
- if (err.code === 'ENOENT') {
7315
- emit('unlink', fullPath);
7316
- // Stop watching if it was a directory
7317
- if (watchers.has(fullPath)) {
7318
- watchers.get(fullPath).close();
7319
- watchers.delete(fullPath);
7320
- }
7321
- }
7322
- } else {
7323
- const type = eventType === 'rename' ? 'add' : 'change';
7324
- emit(type, fullPath);
7325
-
7326
- // If it's a new directory, start watching it
7327
- if (stats.isDirectory() && !watchers.has(fullPath)) {
7328
- watchDir(fullPath);
7329
- }
7330
- }
7331
- });
7332
- }, DEBOUNCE_MS));
7333
- });
7334
-
7335
- watchers.set(dir, watcher);
7336
-
7337
- // Watch subdirectories
7338
- const entries = fs.readdirSync(dir, { withFileTypes: true });
7339
- for (const entry of entries) {
7340
- if (entry.isDirectory() && !shouldIgnore(entry.name)) {
7341
- watchDir(path.join(dir, entry.name));
7342
- }
7343
- }
7344
- } catch (err) {
7345
- // Directory may not exist or be inaccessible
7346
- console.error(JSON.stringify({ error: err.message, dir }));
7347
- }
7348
- }
7349
-
7350
- // Start watching
7351
- watchDir(watchPath);
7352
-
7353
- // Keep alive
7354
- process.on('SIGTERM', () => {
7355
- for (const watcher of watchers.values()) {
7356
- watcher.close();
7357
- }
7358
- process.exit(0);
7359
- });
7360
-
7361
- // Heartbeat to indicate we're running
7362
- setInterval(() => {
7363
- console.log(JSON.stringify({ heartbeat: true, timestamp: Date.now() }));
7364
- }, 5000);
7365
-
7366
- console.log(JSON.stringify({ started: true, path: watchPath }));
7367
- `;
7368
- exports.InSandboxWatcher = class {
7369
- sessionId;
7370
- watchPath;
7371
- outputPollIntervalMs;
7372
- sandboxState = null;
7373
- outputPollTimer = null;
7374
- subscribers = /* @__PURE__ */ new Set();
7375
- isRunning = false;
7376
- startedAt;
7377
- lastHeartbeat;
7378
- lastOutputPosition = 0;
7379
- onError;
7380
- onReady;
7381
- constructor(options) {
7382
- this.sessionId = options.sessionId;
7383
- this.watchPath = options.watchPath;
7384
- this.outputPollIntervalMs = options.outputPollIntervalMs ?? 1e3;
7385
- this.onError = options.onError;
7386
- this.onReady = options.onReady;
7387
- if (options.onFileChange) {
7388
- this.subscribers.add(options.onFileChange);
7389
- }
7390
- }
7391
- /**
7392
- * Start the watcher process inside the sandbox
7393
- */
7394
- async start() {
7395
- if (this.isRunning) {
7396
- console.warn(`[IN_SANDBOX_WATCHER] Already running for session ${this.sessionId}`);
7397
- return;
7398
- }
7399
- try {
7400
- this.sandboxState = await getOrCreateSandbox({
7401
- sessionId: this.sessionId,
7402
- runtime: "node22",
7403
- timeout: 600
7404
- });
7405
- const { sandbox } = this.sandboxState;
7406
- const scriptPath = "/tmp/.file-watcher.js";
7407
- const outputPath = "/tmp/.file-watcher-output.log";
7408
- const writeScriptCmd = `cat > ${scriptPath} << 'WATCHER_EOF'
7409
- ${WATCHER_SCRIPT}
7410
- WATCHER_EOF`;
7411
- await sandbox.runCommand({
7412
- cmd: "sh",
7413
- args: ["-c", writeScriptCmd]
7414
- });
7415
- const startCmd = `nohup node ${scriptPath} "${this.watchPath}" > ${outputPath} 2>&1 &`;
7416
- await sandbox.runCommand({
7417
- cmd: "sh",
7418
- args: ["-c", startCmd]
7419
- });
7420
- console.log(`[IN_SANDBOX_WATCHER] Started watcher in sandbox for ${this.watchPath}`);
7421
- this.isRunning = true;
7422
- this.startedAt = /* @__PURE__ */ new Date();
7423
- this.outputPollTimer = setInterval(async () => {
7424
- try {
7425
- await this.pollOutput(outputPath);
7426
- } catch (error) {
7427
- console.error("[IN_SANDBOX_WATCHER] Error polling output:", error);
7428
- if (this.onError && error instanceof Error) {
7429
- this.onError(error);
7430
- }
7431
- }
7432
- }, this.outputPollIntervalMs);
7433
- setTimeout(() => {
7434
- this.pollOutput(outputPath).catch(console.error);
7435
- }, 500);
7436
- } catch (error) {
7437
- console.error(`[IN_SANDBOX_WATCHER] Failed to start watcher:`, error);
7438
- throw error;
7439
- }
7440
- }
7441
- /**
7442
- * Poll the output file for new events
7443
- */
7444
- async pollOutput(outputPath) {
7445
- if (!this.sandboxState?.sandbox) return;
7446
- try {
7447
- const result = await this.sandboxState.sandbox.runCommand({
7448
- cmd: "sh",
7449
- args: ["-c", `tail -c +${this.lastOutputPosition + 1} ${outputPath} 2>/dev/null || true`]
7450
- });
7451
- const output = await result.stdout();
7452
- if (output && output.trim()) {
7453
- const sizeResult = await this.sandboxState.sandbox.runCommand({
7454
- cmd: "sh",
7455
- args: ["-c", `stat -c%s ${outputPath} 2>/dev/null || echo 0`]
7456
- });
7457
- const sizeStr = await sizeResult.stdout();
7458
- this.lastOutputPosition = parseInt(sizeStr.trim(), 10) || 0;
7459
- this.processOutput(output);
7460
- }
7461
- } catch {
7462
- }
7463
- }
7464
- /**
7465
- * Process output from the watcher script
7466
- */
7467
- processOutput(stdout) {
7468
- const lines = stdout.split("\n").filter(Boolean);
7469
- for (const line of lines) {
7470
- try {
7471
- const data = JSON.parse(line);
7472
- if (data.started) {
7473
- console.log(`[IN_SANDBOX_WATCHER] Watcher started for ${data.path}`);
7474
- if (this.onReady) {
7475
- this.onReady();
7476
- }
7477
- } else if (data.heartbeat) {
7478
- this.lastHeartbeat = new Date(data.timestamp);
7479
- } else if (data.error) {
7480
- console.warn(`[IN_SANDBOX_WATCHER] Error in sandbox:`, data.error);
7481
- } else if (data.type && data.path) {
7482
- this.emitEvent({
7483
- type: data.type,
7484
- relativePath: data.path.replace(/^\.\//, ""),
7485
- absolutePath: data.path,
7486
- sessionId: this.sessionId,
7487
- timestamp: new Date(data.timestamp)
7488
- });
7489
- }
7490
- } catch {
7491
- }
7492
- }
7493
- }
7494
- /**
7495
- * Stop the watcher process
7496
- */
7497
- async stop() {
7498
- if (!this.isRunning) {
7499
- return;
7500
- }
7501
- if (this.outputPollTimer) {
7502
- clearInterval(this.outputPollTimer);
7503
- this.outputPollTimer = null;
7504
- }
7505
- try {
7506
- if (this.sandboxState?.sandbox) {
7507
- await this.sandboxState.sandbox.runCommand({
7508
- cmd: "sh",
7509
- args: ["-c", "pkill -f file-watcher.js 2>/dev/null || true"]
7510
- });
7511
- await this.sandboxState.sandbox.runCommand({
7512
- cmd: "sh",
7513
- args: ["-c", "rm -f /tmp/.file-watcher.js /tmp/.file-watcher-output.log"]
7514
- });
7515
- }
7516
- } catch {
7517
- }
7518
- this.isRunning = false;
7519
- this.sandboxState = null;
7520
- this.lastOutputPosition = 0;
7521
- console.log(`[IN_SANDBOX_WATCHER] Stopped watcher for session ${this.sessionId}`);
7522
- }
7523
- /**
7524
- * Subscribe to file change events
7525
- */
7526
- subscribe(callback) {
7527
- this.subscribers.add(callback);
7528
- return () => this.subscribers.delete(callback);
7529
- }
7530
- /**
7531
- * Check if watcher is running
7532
- */
7533
- isActive() {
7534
- return this.isRunning;
7535
- }
7536
- /**
7537
- * Get watcher status
7538
- */
7539
- getStatus() {
7540
- return {
7541
- sessionId: this.sessionId,
7542
- watchPath: this.watchPath,
7543
- isRunning: this.isRunning,
7544
- startedAt: this.startedAt,
7545
- lastHeartbeat: this.lastHeartbeat
7546
- };
7547
- }
7548
- /**
7549
- * Emit event to all subscribers
7550
- */
7551
- async emitEvent(event) {
7552
- for (const callback of this.subscribers) {
7553
- try {
7554
- await callback(event);
7555
- } catch (error) {
7556
- console.error("[IN_SANDBOX_WATCHER] Error in subscriber callback:", error);
7557
- }
7558
- }
7559
- }
7560
- };
7561
- exports.InSandboxWatcherManager = class {
7562
- watchers = /* @__PURE__ */ new Map();
7563
- /**
7564
- * Start watching a sandbox
7565
- */
7566
- async startWatching(options) {
7567
- const { sessionId } = options;
7568
- await this.stopWatching(sessionId);
7569
- const watcher = new exports.InSandboxWatcher(options);
7570
- await watcher.start();
7571
- this.watchers.set(sessionId, watcher);
7572
- return watcher;
7573
- }
7574
- /**
7575
- * Stop watching a session
7576
- */
7577
- async stopWatching(sessionId) {
7578
- const watcher = this.watchers.get(sessionId);
7579
- if (watcher) {
7580
- await watcher.stop();
7581
- this.watchers.delete(sessionId);
7582
- }
7583
- }
7584
- /**
7585
- * Get watcher for a session
7586
- */
7587
- getWatcher(sessionId) {
7588
- return this.watchers.get(sessionId);
7589
- }
7590
- /**
7591
- * Check if watching
7592
- */
7593
- isWatching(sessionId) {
7594
- return this.watchers.get(sessionId)?.isActive() ?? false;
7595
- }
7596
- /**
7597
- * Stop all watchers
7598
- */
7599
- async stopAll() {
7600
- const promises = Array.from(this.watchers.values()).map((w) => w.stop());
7601
- await Promise.all(promises);
7602
- this.watchers.clear();
7603
- }
7604
- };
7605
- globalInSandboxManager = null;
7606
- }
7607
- });
7608
-
7609
7442
  // src/runtime/index.ts
7610
7443
  function generateDockerCommand(config) {
7611
7444
  const args = ["docker", "run"];
@@ -19032,8 +18865,6 @@ exports.createQueueProcessor = createQueueProcessor;
19032
18865
  exports.createQueueRouter = createQueueRouter;
19033
18866
  exports.createR2BundleStore = createR2BundleStore;
19034
18867
  exports.createR2FileStore = createR2FileStore;
19035
- exports.createRemoteFileWatcher = createRemoteFileWatcher;
19036
- exports.createRemoteFileWatcherManager = createRemoteFileWatcherManager;
19037
18868
  exports.createS3BundleStore = createS3BundleStore;
19038
18869
  exports.createS3FileStore = createS3FileStore;
19039
18870
  exports.createSandboxFileOperations = createSandboxFileOperations;
@@ -19059,6 +18890,7 @@ exports.defineConfig = defineConfig;
19059
18890
  exports.ensureSandboxPoolInitialized = ensureSandboxPoolInitialized;
19060
18891
  exports.env = env;
19061
18892
  exports.envOptional = envOptional;
18893
+ exports.executeCommandInSandbox = executeCommandInSandbox;
19062
18894
  exports.extractTextContent = extractTextContent;
19063
18895
  exports.extractTextFromMessage = extractTextFromMessage;
19064
18896
  exports.formatToolName = formatToolName;
@@ -19071,12 +18903,12 @@ exports.getActionIcon = getActionIcon;
19071
18903
  exports.getActionLabel = getActionLabel;
19072
18904
  exports.getAllHeartbeatStatuses = getAllHeartbeatStatuses;
19073
18905
  exports.getApiKeyEnvVar = getApiKeyEnvVar;
18906
+ exports.getCachedSandbox = getCachedSandbox;
19074
18907
  exports.getDefaultModel = getDefaultModel;
19075
18908
  exports.getFileWatcherManager = getFileWatcherManager;
19076
18909
  exports.getHeartbeatStatus = getHeartbeatStatus;
19077
18910
  exports.getInSandboxWatcherManager = getInSandboxWatcherManager;
19078
18911
  exports.getOrCreateSandbox = getOrCreateSandbox;
19079
- exports.getRemoteFileWatcherManager = getRemoteFileWatcherManager;
19080
18912
  exports.getSandboxCacheStats = getSandboxCacheStats;
19081
18913
  exports.getSandboxPool = getSandboxPool;
19082
18914
  exports.getWorkspaceManager = getWorkspaceManager;