@ash-cloud/ash-ai 0.1.8 → 0.1.10

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
@@ -4,10 +4,11 @@ var zod = require('zod');
4
4
  var child_process = require('child_process');
5
5
  var nanoid = require('nanoid');
6
6
  var fs = require('fs/promises');
7
- var path3 = require('path');
7
+ var path4 = require('path');
8
8
  var stream = require('stream');
9
+ var chokidar = require('chokidar');
9
10
  var crypto = require('crypto');
10
- var fs9 = require('fs');
11
+ var fs10 = require('fs');
11
12
  var hono = require('hono');
12
13
  var streaming = require('hono/streaming');
13
14
  var cors = require('hono/cors');
@@ -46,8 +47,9 @@ function _interopNamespace(e) {
46
47
  }
47
48
 
48
49
  var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
49
- var path3__namespace = /*#__PURE__*/_interopNamespace(path3);
50
- var fs9__namespace = /*#__PURE__*/_interopNamespace(fs9);
50
+ var path4__namespace = /*#__PURE__*/_interopNamespace(path4);
51
+ var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
52
+ var fs10__namespace = /*#__PURE__*/_interopNamespace(fs10);
51
53
  var os__namespace = /*#__PURE__*/_interopNamespace(os);
52
54
  var postgres__default = /*#__PURE__*/_interopDefault(postgres);
53
55
 
@@ -105,7 +107,7 @@ var init_normalized = __esm({
105
107
  "src/types/normalized.ts"() {
106
108
  }
107
109
  });
108
- exports.SessionStatus = void 0; exports.AgentStatus = void 0; exports.MessageRole = void 0; exports.messageContentSchema = void 0; exports.messageSchema = void 0; exports.sessionSchema = void 0; exports.attachmentSchema = void 0; exports.DEFAULT_SANDBOX_PROVIDER_CONFIG = void 0; exports.StreamEventType = void 0; exports.QueueItemStatus = void 0;
110
+ exports.SessionStatus = void 0; exports.AgentStatus = void 0; exports.MessageRole = void 0; exports.messageContentSchema = void 0; exports.messageSchema = void 0; exports.sessionSchema = void 0; exports.attachmentSchema = void 0; exports.DEFAULT_SANDBOX_PROVIDER_CONFIG = void 0; exports.StreamEventType = void 0; exports.QueueItemStatus = void 0; exports.EventCategory = void 0;
109
111
  var init_types = __esm({
110
112
  "src/types/index.ts"() {
111
113
  init_normalized();
@@ -202,6 +204,7 @@ var init_types = __esm({
202
204
  SESSION_END: "session_end",
203
205
  SESSION_STOPPED: "session_stopped",
204
206
  SANDBOX_LOG: "sandbox_log",
207
+ MCP_STATUS: "mcp_status",
205
208
  ERROR: "error"
206
209
  };
207
210
  exports.QueueItemStatus = {
@@ -211,6 +214,22 @@ var init_types = __esm({
211
214
  FAILED: "failed",
212
215
  CANCELLED: "cancelled"
213
216
  };
217
+ exports.EventCategory = {
218
+ LIFECYCLE: "lifecycle",
219
+ // session_start, session_end, turn_complete
220
+ CONTENT: "content",
221
+ // text_stream (aggregated), thinking_stream
222
+ TOOL: "tool",
223
+ // tool_use, tool_result
224
+ SYSTEM: "system",
225
+ // mcp_status, sandbox_log
226
+ ERROR: "error",
227
+ // error events
228
+ FILE: "file",
229
+ // file_push, file_pull, file_sync (file sync operations)
230
+ INPUT: "input"
231
+ // user_input (user prompts/messages)
232
+ };
214
233
  }
215
234
  });
216
235
 
@@ -630,7 +649,7 @@ var init_mcp = __esm({
630
649
  * @param path Path to the server script
631
650
  * @param runtime Runtime to use (node, python, etc.)
632
651
  */
633
- custom: (path14, runtime = "node", args) => {
652
+ custom: (path15, runtime = "node", args) => {
634
653
  const commands = {
635
654
  node: "node",
636
655
  python: "python3",
@@ -638,7 +657,7 @@ var init_mcp = __esm({
638
657
  };
639
658
  return {
640
659
  command: commands[runtime] ?? "node",
641
- args: runtime === "deno" ? ["run", "-A", path14, ...args ?? []] : [path14, ...args ?? []]
660
+ args: runtime === "deno" ? ["run", "-A", path15, ...args ?? []] : [path15, ...args ?? []]
642
661
  };
643
662
  }
644
663
  };
@@ -696,8 +715,8 @@ var init_mcp = __esm({
696
715
  /**
697
716
  * Add a custom stdio MCP server
698
717
  */
699
- withCustom(name, path14, runtime, args) {
700
- return this.add(name, exports.McpServers.custom(path14, runtime, args));
718
+ withCustom(name, path15, runtime, args) {
719
+ return this.add(name, exports.McpServers.custom(path15, runtime, args));
701
720
  }
702
721
  /**
703
722
  * Add an HTTP MCP server with typed authentication
@@ -3019,9 +3038,9 @@ var init_attachment = __esm({
3019
3038
  throw new Error(`MIME type ${mimeType} is not allowed`);
3020
3039
  }
3021
3040
  const id = nanoid.nanoid();
3022
- const ext = path3__namespace.extname(filename) || this.getExtensionForMimeType(mimeType);
3041
+ const ext = path4__namespace.extname(filename) || this.getExtensionForMimeType(mimeType);
3023
3042
  const storageName = `${id}${ext}`;
3024
- const storagePath = path3__namespace.join(this.config.basePath, storageName);
3043
+ const storagePath = path4__namespace.join(this.config.basePath, storageName);
3025
3044
  await fs__namespace.writeFile(storagePath, content);
3026
3045
  return {
3027
3046
  id,
@@ -3038,8 +3057,8 @@ var init_attachment = __esm({
3038
3057
  */
3039
3058
  async storeFromPath(messageId, sourcePath, mimeType) {
3040
3059
  const content = await fs__namespace.readFile(sourcePath);
3041
- const filename = path3__namespace.basename(sourcePath);
3042
- const detectedMimeType = mimeType ?? this.getMimeTypeForExtension(path3__namespace.extname(filename));
3060
+ const filename = path4__namespace.basename(sourcePath);
3061
+ const detectedMimeType = mimeType ?? this.getMimeTypeForExtension(path4__namespace.extname(filename));
3043
3062
  return this.storeFromBuffer(messageId, filename, content, detectedMimeType);
3044
3063
  }
3045
3064
  /**
@@ -3270,8 +3289,8 @@ async function loadConfig(configPath, options = {}) {
3270
3289
  return config;
3271
3290
  }
3272
3291
  async function loadConfigFile(filePath) {
3273
- const ext = path3__namespace.extname(filePath);
3274
- const fullPath = path3__namespace.isAbsolute(filePath) ? filePath : path3__namespace.resolve(process.cwd(), filePath);
3292
+ const ext = path4__namespace.extname(filePath);
3293
+ const fullPath = path4__namespace.isAbsolute(filePath) ? filePath : path4__namespace.resolve(process.cwd(), filePath);
3275
3294
  if (ext === ".json") {
3276
3295
  const content = await fs__namespace.readFile(fullPath, "utf-8");
3277
3296
  return JSON.parse(content);
@@ -3284,7 +3303,7 @@ async function loadConfigFile(filePath) {
3284
3303
  }
3285
3304
  async function findAndLoadConfig(directory) {
3286
3305
  for (const fileName of CONFIG_FILE_NAMES) {
3287
- const filePath = path3__namespace.join(directory, fileName);
3306
+ const filePath = path4__namespace.join(directory, fileName);
3288
3307
  try {
3289
3308
  await fs__namespace.access(filePath);
3290
3309
  return await loadConfigFile(filePath);
@@ -3622,8 +3641,8 @@ var init_modal = __esm({
3622
3641
  }
3623
3642
  async writeFile(_sandboxId, _path, _content) {
3624
3643
  }
3625
- async readFile(_sandboxId, path14) {
3626
- return `[Modal] File content from ${path14}`;
3644
+ async readFile(_sandboxId, path15) {
3645
+ return `[Modal] File content from ${path15}`;
3627
3646
  }
3628
3647
  /**
3629
3648
  * Modal-specific: Get GPU information
@@ -3770,12 +3789,12 @@ var init_e2b = __esm({
3770
3789
  throw new SandboxNotFoundError(this.name, sandboxId);
3771
3790
  }
3772
3791
  }
3773
- async readFile(sandboxId, path14) {
3792
+ async readFile(sandboxId, path15) {
3774
3793
  const sandbox = this.sandboxes.get(sandboxId);
3775
3794
  if (!sandbox) {
3776
3795
  throw new SandboxNotFoundError(this.name, sandboxId);
3777
3796
  }
3778
- return `[E2B] File content from ${path14}`;
3797
+ return `[E2B] File content from ${path15}`;
3779
3798
  }
3780
3799
  async listFiles(sandboxId, _path) {
3781
3800
  const sandbox = this.sandboxes.get(sandboxId);
@@ -3804,17 +3823,17 @@ var init_e2b = __esm({
3804
3823
  * E2B-specific: Upload a file from local filesystem
3805
3824
  */
3806
3825
  async uploadFile(sandboxId, localPath, remotePath) {
3807
- const fs14 = await import('fs/promises');
3808
- const content = await fs14.readFile(localPath);
3826
+ const fs15 = await import('fs/promises');
3827
+ const content = await fs15.readFile(localPath);
3809
3828
  await this.writeFile(sandboxId, remotePath, content);
3810
3829
  }
3811
3830
  /**
3812
3831
  * E2B-specific: Download a file to local filesystem
3813
3832
  */
3814
3833
  async downloadFile(sandboxId, remotePath, localPath) {
3815
- const fs14 = await import('fs/promises');
3834
+ const fs15 = await import('fs/promises');
3816
3835
  const content = await this.readFile(sandboxId, remotePath);
3817
- await fs14.writeFile(localPath, content);
3836
+ await fs15.writeFile(localPath, content);
3818
3837
  }
3819
3838
  };
3820
3839
  }
@@ -4068,7 +4087,7 @@ var init_vercel = __esm({
4068
4087
  }
4069
4088
  };
4070
4089
  }
4071
- async writeFile(sandboxId, path14, content) {
4090
+ async writeFile(sandboxId, path15, content) {
4072
4091
  const instance = this.sandboxes.get(sandboxId);
4073
4092
  if (!instance) {
4074
4093
  throw new SandboxNotFoundError(this.name, sandboxId);
@@ -4078,34 +4097,34 @@ var init_vercel = __esm({
4078
4097
  if (isBase64) {
4079
4098
  await this.executeCommand(
4080
4099
  sandboxId,
4081
- `echo "${contentStr}" | base64 -d > "${path14}"`
4100
+ `echo "${contentStr}" | base64 -d > "${path15}"`
4082
4101
  );
4083
4102
  } else {
4084
4103
  const escaped = contentStr.replace(/'/g, "'\\''");
4085
- await this.executeCommand(sandboxId, `cat > "${path14}" << 'VERCEL_EOF'
4104
+ await this.executeCommand(sandboxId, `cat > "${path15}" << 'VERCEL_EOF'
4086
4105
  ${escaped}
4087
4106
  VERCEL_EOF`);
4088
4107
  }
4089
4108
  }
4090
- async readFile(sandboxId, path14) {
4109
+ async readFile(sandboxId, path15) {
4091
4110
  const instance = this.sandboxes.get(sandboxId);
4092
4111
  if (!instance) {
4093
4112
  throw new SandboxNotFoundError(this.name, sandboxId);
4094
4113
  }
4095
- const result = await this.executeCommand(sandboxId, `cat "${path14}"`);
4114
+ const result = await this.executeCommand(sandboxId, `cat "${path15}"`);
4096
4115
  if (result.exitCode !== 0) {
4097
- throw new Error(`Failed to read file ${path14}: ${result.stderr}`);
4116
+ throw new Error(`Failed to read file ${path15}: ${result.stderr}`);
4098
4117
  }
4099
4118
  return result.stdout;
4100
4119
  }
4101
- async listFiles(sandboxId, path14) {
4120
+ async listFiles(sandboxId, path15) {
4102
4121
  const instance = this.sandboxes.get(sandboxId);
4103
4122
  if (!instance) {
4104
4123
  throw new SandboxNotFoundError(this.name, sandboxId);
4105
4124
  }
4106
4125
  const result = await this.executeCommand(
4107
4126
  sandboxId,
4108
- `ls -la "${path14}" 2>/dev/null || echo ""`
4127
+ `ls -la "${path15}" 2>/dev/null || echo ""`
4109
4128
  );
4110
4129
  if (result.exitCode !== 0 || !result.stdout.trim()) {
4111
4130
  return [];
@@ -4120,7 +4139,7 @@ VERCEL_EOF`);
4120
4139
  const [, type, , size, , name] = match;
4121
4140
  if (name && name !== "." && name !== ".." && size) {
4122
4141
  files.push({
4123
- path: `${path14}/${name}`.replace(/\/+/g, "/"),
4142
+ path: `${path15}/${name}`.replace(/\/+/g, "/"),
4124
4143
  name,
4125
4144
  size: parseInt(size, 10),
4126
4145
  isDirectory: type === "d"
@@ -4130,12 +4149,12 @@ VERCEL_EOF`);
4130
4149
  }
4131
4150
  return files;
4132
4151
  }
4133
- async deleteFile(sandboxId, path14) {
4152
+ async deleteFile(sandboxId, path15) {
4134
4153
  const instance = this.sandboxes.get(sandboxId);
4135
4154
  if (!instance) {
4136
4155
  throw new SandboxNotFoundError(this.name, sandboxId);
4137
4156
  }
4138
- await this.executeCommand(sandboxId, `rm -rf "${path14}"`);
4157
+ await this.executeCommand(sandboxId, `rm -rf "${path15}"`);
4139
4158
  }
4140
4159
  async getLogs(sandboxId, _options) {
4141
4160
  const instance = this.sandboxes.get(sandboxId);
@@ -5268,7 +5287,7 @@ function isSandboxRunning(sessionId) {
5268
5287
  const cached = sandboxCache.get(sessionId);
5269
5288
  return cached !== void 0 && !cached.isExpired;
5270
5289
  }
5271
- async function writeFileToSandbox(sessionId, path14, content) {
5290
+ async function writeFileToSandbox(sessionId, path15, content) {
5272
5291
  const cached = sandboxCache.get(sessionId);
5273
5292
  if (!cached) {
5274
5293
  return { success: false, error: "No active sandbox for session" };
@@ -5278,14 +5297,14 @@ async function writeFileToSandbox(sessionId, path14, content) {
5278
5297
  }
5279
5298
  try {
5280
5299
  const sandbox = cached.sandbox;
5281
- const dir = path14.substring(0, path14.lastIndexOf("/"));
5300
+ const dir = path15.substring(0, path15.lastIndexOf("/"));
5282
5301
  if (dir) {
5283
5302
  await sandbox.runCommand({ cmd: "mkdir", args: ["-p", dir] });
5284
5303
  }
5285
5304
  const base64Content = content.toString("base64");
5286
5305
  const result = await sandbox.runCommand({
5287
5306
  cmd: "bash",
5288
- args: ["-c", `echo '${base64Content}' | base64 -d > '${path14}'`]
5307
+ args: ["-c", `echo '${base64Content}' | base64 -d > '${path15}'`]
5289
5308
  });
5290
5309
  if (result.exitCode !== 0) {
5291
5310
  const stderr = await result.stderr();
@@ -5300,7 +5319,7 @@ async function writeFileToSandbox(sessionId, path14, content) {
5300
5319
  };
5301
5320
  }
5302
5321
  }
5303
- async function readFileFromSandbox(sessionId, path14) {
5322
+ async function readFileFromSandbox(sessionId, path15) {
5304
5323
  const cached = sandboxCache.get(sessionId);
5305
5324
  if (!cached) {
5306
5325
  return { success: false, error: "No active sandbox for session" };
@@ -5312,14 +5331,14 @@ async function readFileFromSandbox(sessionId, path14) {
5312
5331
  const sandbox = cached.sandbox;
5313
5332
  const checkResult = await sandbox.runCommand({
5314
5333
  cmd: "test",
5315
- args: ["-f", path14]
5334
+ args: ["-f", path15]
5316
5335
  });
5317
5336
  if (checkResult.exitCode !== 0) {
5318
5337
  return { success: false, error: "File not found" };
5319
5338
  }
5320
5339
  const result = await sandbox.runCommand({
5321
5340
  cmd: "base64",
5322
- args: [path14]
5341
+ args: [path15]
5323
5342
  });
5324
5343
  if (result.exitCode !== 0) {
5325
5344
  const stderr = await result.stderr();
@@ -5336,7 +5355,7 @@ async function readFileFromSandbox(sessionId, path14) {
5336
5355
  };
5337
5356
  }
5338
5357
  }
5339
- async function listFilesInSandbox(sessionId, path14) {
5358
+ async function listFilesInSandbox(sessionId, path15) {
5340
5359
  const cached = sandboxCache.get(sessionId);
5341
5360
  if (!cached) {
5342
5361
  return { success: false, error: "No active sandbox for session" };
@@ -5348,14 +5367,14 @@ async function listFilesInSandbox(sessionId, path14) {
5348
5367
  const sandbox = cached.sandbox;
5349
5368
  const checkResult = await sandbox.runCommand({
5350
5369
  cmd: "test",
5351
- args: ["-d", path14]
5370
+ args: ["-d", path15]
5352
5371
  });
5353
5372
  if (checkResult.exitCode !== 0) {
5354
5373
  return { success: true, files: [] };
5355
5374
  }
5356
5375
  const result = await sandbox.runCommand({
5357
5376
  cmd: "find",
5358
- args: [path14, "-type", "f", "-printf", "%P\\n"]
5377
+ args: [path15, "-type", "f", "-printf", "%P\\n"]
5359
5378
  });
5360
5379
  if (result.exitCode !== 0) {
5361
5380
  const stderr = await result.stderr();
@@ -5803,8 +5822,516 @@ var init_vercel_sandbox_executor = __esm({
5803
5822
  heartbeatListeners = /* @__PURE__ */ new Set();
5804
5823
  }
5805
5824
  });
5806
-
5807
- // src/runtime/sandbox-file-sync.ts
5825
+ function createFileWatcher(options) {
5826
+ return new exports.SandboxFileWatcher(options);
5827
+ }
5828
+ function getFileWatcherManager() {
5829
+ if (!globalWatcherManager) {
5830
+ globalWatcherManager = new exports.FileWatcherManager();
5831
+ }
5832
+ return globalWatcherManager;
5833
+ }
5834
+ function createFileWatcherManager() {
5835
+ return new exports.FileWatcherManager();
5836
+ }
5837
+ function createRemoteFileWatcher(options) {
5838
+ return new exports.RemoteSandboxFileWatcher(options);
5839
+ }
5840
+ function getRemoteFileWatcherManager() {
5841
+ if (!globalRemoteWatcherManager) {
5842
+ globalRemoteWatcherManager = new exports.RemoteFileWatcherManager();
5843
+ }
5844
+ return globalRemoteWatcherManager;
5845
+ }
5846
+ function createRemoteFileWatcherManager() {
5847
+ return new exports.RemoteFileWatcherManager();
5848
+ }
5849
+ exports.SandboxFileWatcher = void 0; exports.FileWatcherManager = void 0; var globalWatcherManager; exports.RemoteSandboxFileWatcher = void 0; exports.RemoteFileWatcherManager = void 0; var globalRemoteWatcherManager;
5850
+ var init_sandbox_file_watcher = __esm({
5851
+ "src/runtime/sandbox-file-watcher.ts"() {
5852
+ exports.SandboxFileWatcher = class {
5853
+ sessionId;
5854
+ watchPath;
5855
+ debounceMs;
5856
+ patterns;
5857
+ ignored;
5858
+ emitInitialEvents;
5859
+ followSymlinks;
5860
+ usePolling;
5861
+ pollInterval;
5862
+ watcher = null;
5863
+ pendingEvents = /* @__PURE__ */ new Map();
5864
+ subscribers = /* @__PURE__ */ new Set();
5865
+ isWatching = false;
5866
+ startedAt;
5867
+ watchedFileCount = 0;
5868
+ onError;
5869
+ onReady;
5870
+ constructor(options) {
5871
+ this.sessionId = options.sessionId;
5872
+ this.watchPath = options.watchPath;
5873
+ this.debounceMs = options.debounceMs ?? 300;
5874
+ this.patterns = options.patterns ?? ["**/*"];
5875
+ this.ignored = options.ignored ?? ["**/node_modules/**", "**/.git/**", "**/*.log"];
5876
+ this.emitInitialEvents = options.emitInitialEvents ?? false;
5877
+ this.followSymlinks = options.followSymlinks ?? false;
5878
+ this.usePolling = options.usePolling ?? false;
5879
+ this.pollInterval = options.pollInterval ?? 100;
5880
+ this.onError = options.onError;
5881
+ this.onReady = options.onReady;
5882
+ if (options.onFileChange) {
5883
+ this.subscribers.add(options.onFileChange);
5884
+ }
5885
+ }
5886
+ /**
5887
+ * Start watching the directory for changes
5888
+ */
5889
+ async start() {
5890
+ if (this.isWatching) {
5891
+ console.warn(`[FILE_WATCHER] Already watching ${this.watchPath}`);
5892
+ return;
5893
+ }
5894
+ try {
5895
+ const stat2 = await fs__namespace.stat(this.watchPath);
5896
+ if (!stat2.isDirectory()) {
5897
+ throw new Error(`Watch path is not a directory: ${this.watchPath}`);
5898
+ }
5899
+ } catch (error) {
5900
+ if (error.code === "ENOENT") {
5901
+ throw new Error(`Watch path does not exist: ${this.watchPath}`);
5902
+ }
5903
+ throw error;
5904
+ }
5905
+ const watchPaths = this.patterns.map(
5906
+ (pattern) => path4__namespace.join(this.watchPath, pattern)
5907
+ );
5908
+ this.watcher = chokidar__default.default.watch(watchPaths, {
5909
+ ignored: this.ignored,
5910
+ persistent: true,
5911
+ ignoreInitial: !this.emitInitialEvents,
5912
+ followSymlinks: this.followSymlinks,
5913
+ usePolling: this.usePolling,
5914
+ interval: this.pollInterval,
5915
+ awaitWriteFinish: {
5916
+ stabilityThreshold: this.debounceMs,
5917
+ pollInterval: 100
5918
+ }
5919
+ });
5920
+ this.watcher.on("add", (filePath) => this.handleFileEvent("add", filePath)).on("change", (filePath) => this.handleFileEvent("change", filePath)).on("unlink", (filePath) => this.handleFileEvent("unlink", filePath)).on("addDir", (dirPath) => this.handleFileEvent("addDir", dirPath)).on("unlinkDir", (dirPath) => this.handleFileEvent("unlinkDir", dirPath)).on("error", (error) => {
5921
+ console.error(`[FILE_WATCHER] Error watching ${this.watchPath}:`, error);
5922
+ if (this.onError) {
5923
+ this.onError(error);
5924
+ }
5925
+ }).on("ready", () => {
5926
+ console.log(`[FILE_WATCHER] Ready to watch ${this.watchPath}`);
5927
+ this.isWatching = true;
5928
+ this.startedAt = /* @__PURE__ */ new Date();
5929
+ if (this.onReady) {
5930
+ this.onReady();
5931
+ }
5932
+ });
5933
+ this.watcher.on("add", () => this.watchedFileCount++);
5934
+ this.watcher.on("unlink", () => this.watchedFileCount--);
5935
+ }
5936
+ /**
5937
+ * Stop watching and cleanup resources
5938
+ */
5939
+ async stop() {
5940
+ if (!this.isWatching || !this.watcher) {
5941
+ return;
5942
+ }
5943
+ for (const pending of this.pendingEvents.values()) {
5944
+ clearTimeout(pending.timer);
5945
+ }
5946
+ this.pendingEvents.clear();
5947
+ await this.watcher.close();
5948
+ this.watcher = null;
5949
+ this.isWatching = false;
5950
+ this.watchedFileCount = 0;
5951
+ console.log(`[FILE_WATCHER] Stopped watching ${this.watchPath}`);
5952
+ }
5953
+ /**
5954
+ * Subscribe to file change events
5955
+ * @returns Unsubscribe function
5956
+ */
5957
+ subscribe(callback) {
5958
+ this.subscribers.add(callback);
5959
+ return () => this.subscribers.delete(callback);
5960
+ }
5961
+ /**
5962
+ * Remove a subscriber
5963
+ */
5964
+ unsubscribe(callback) {
5965
+ this.subscribers.delete(callback);
5966
+ }
5967
+ /**
5968
+ * Get the current status of the watcher
5969
+ */
5970
+ getStatus() {
5971
+ return {
5972
+ sessionId: this.sessionId,
5973
+ watchPath: this.watchPath,
5974
+ isWatching: this.isWatching,
5975
+ watchedFileCount: this.watchedFileCount,
5976
+ pendingEventCount: this.pendingEvents.size,
5977
+ startedAt: this.startedAt
5978
+ };
5979
+ }
5980
+ /**
5981
+ * Check if the watcher is currently active
5982
+ */
5983
+ isActive() {
5984
+ return this.isWatching;
5985
+ }
5986
+ /**
5987
+ * Handle a file system event with debouncing
5988
+ */
5989
+ handleFileEvent(type, absolutePath) {
5990
+ const relativePath = path4__namespace.relative(this.watchPath, absolutePath);
5991
+ const existing = this.pendingEvents.get(absolutePath);
5992
+ if (existing) {
5993
+ clearTimeout(existing.timer);
5994
+ }
5995
+ const timer = setTimeout(async () => {
5996
+ this.pendingEvents.delete(absolutePath);
5997
+ await this.emitEvent(type, absolutePath, relativePath);
5998
+ }, this.debounceMs);
5999
+ this.pendingEvents.set(absolutePath, {
6000
+ type,
6001
+ absolutePath,
6002
+ relativePath,
6003
+ timer
6004
+ });
6005
+ }
6006
+ /**
6007
+ * Emit a file change event to all subscribers
6008
+ */
6009
+ async emitEvent(type, absolutePath, relativePath) {
6010
+ let fileSize;
6011
+ if (type === "add" || type === "change") {
6012
+ try {
6013
+ const stat2 = await fs__namespace.stat(absolutePath);
6014
+ fileSize = stat2.size;
6015
+ } catch {
6016
+ }
6017
+ }
6018
+ const event = {
6019
+ type,
6020
+ relativePath,
6021
+ absolutePath,
6022
+ sessionId: this.sessionId,
6023
+ fileSize,
6024
+ timestamp: /* @__PURE__ */ new Date()
6025
+ };
6026
+ for (const callback of this.subscribers) {
6027
+ try {
6028
+ await callback(event);
6029
+ } catch (error) {
6030
+ console.error("[FILE_WATCHER] Error in subscriber callback:", error);
6031
+ }
6032
+ }
6033
+ }
6034
+ };
6035
+ exports.FileWatcherManager = class {
6036
+ watchers = /* @__PURE__ */ new Map();
6037
+ /**
6038
+ * Start watching a session's sandbox directory
6039
+ */
6040
+ async startWatching(options) {
6041
+ const { sessionId } = options;
6042
+ const existing = this.watchers.get(sessionId);
6043
+ if (existing) {
6044
+ await existing.stop();
6045
+ }
6046
+ const watcher = new exports.SandboxFileWatcher(options);
6047
+ await watcher.start();
6048
+ this.watchers.set(sessionId, watcher);
6049
+ return watcher;
6050
+ }
6051
+ /**
6052
+ * Stop watching a session's sandbox directory
6053
+ */
6054
+ async stopWatching(sessionId) {
6055
+ const watcher = this.watchers.get(sessionId);
6056
+ if (watcher) {
6057
+ await watcher.stop();
6058
+ this.watchers.delete(sessionId);
6059
+ }
6060
+ }
6061
+ /**
6062
+ * Get a watcher for a session
6063
+ */
6064
+ getWatcher(sessionId) {
6065
+ return this.watchers.get(sessionId);
6066
+ }
6067
+ /**
6068
+ * Check if a session is being watched
6069
+ */
6070
+ isWatching(sessionId) {
6071
+ const watcher = this.watchers.get(sessionId);
6072
+ return watcher?.isActive() ?? false;
6073
+ }
6074
+ /**
6075
+ * Get status of all watchers
6076
+ */
6077
+ getAllStatuses() {
6078
+ return Array.from(this.watchers.values()).map((w) => w.getStatus());
6079
+ }
6080
+ /**
6081
+ * Stop all watchers and cleanup
6082
+ */
6083
+ async stopAll() {
6084
+ const promises = Array.from(this.watchers.values()).map((w) => w.stop());
6085
+ await Promise.all(promises);
6086
+ this.watchers.clear();
6087
+ }
6088
+ };
6089
+ globalWatcherManager = null;
6090
+ exports.RemoteSandboxFileWatcher = class {
6091
+ sessionId;
6092
+ sandboxOps;
6093
+ basePath;
6094
+ pollIntervalMs;
6095
+ ignored;
6096
+ pollTimer = null;
6097
+ previousFiles = /* @__PURE__ */ new Map();
6098
+ subscribers = /* @__PURE__ */ new Set();
6099
+ isWatching = false;
6100
+ startedAt;
6101
+ lastPollAt;
6102
+ pollCount = 0;
6103
+ onError;
6104
+ constructor(options) {
6105
+ this.sessionId = options.sessionId;
6106
+ this.sandboxOps = options.sandboxOps;
6107
+ this.basePath = options.basePath;
6108
+ this.pollIntervalMs = options.pollIntervalMs ?? 2e3;
6109
+ this.ignored = options.ignored ?? ["**/node_modules/**", "**/.git/**"];
6110
+ this.onError = options.onError;
6111
+ if (options.onFileChange) {
6112
+ this.subscribers.add(options.onFileChange);
6113
+ }
6114
+ }
6115
+ /**
6116
+ * Start polling for file changes
6117
+ */
6118
+ async start() {
6119
+ if (this.isWatching) {
6120
+ console.warn(`[REMOTE_WATCHER] Already watching session ${this.sessionId}`);
6121
+ return;
6122
+ }
6123
+ if (!this.sandboxOps.isSandboxRunning(this.sessionId)) {
6124
+ throw new Error(`Sandbox is not running for session ${this.sessionId}`);
6125
+ }
6126
+ await this.scan(true);
6127
+ this.pollTimer = setInterval(async () => {
6128
+ try {
6129
+ await this.scan(false);
6130
+ } catch (error) {
6131
+ console.error(`[REMOTE_WATCHER] Poll error for session ${this.sessionId}:`, error);
6132
+ if (this.onError && error instanceof Error) {
6133
+ this.onError(error);
6134
+ }
6135
+ }
6136
+ }, this.pollIntervalMs);
6137
+ this.isWatching = true;
6138
+ this.startedAt = /* @__PURE__ */ new Date();
6139
+ console.log(`[REMOTE_WATCHER] Started watching session ${this.sessionId} at ${this.basePath}`);
6140
+ }
6141
+ /**
6142
+ * Stop polling and cleanup
6143
+ */
6144
+ async stop() {
6145
+ if (!this.isWatching) {
6146
+ return;
6147
+ }
6148
+ if (this.pollTimer) {
6149
+ clearInterval(this.pollTimer);
6150
+ this.pollTimer = null;
6151
+ }
6152
+ this.isWatching = false;
6153
+ this.previousFiles.clear();
6154
+ console.log(`[REMOTE_WATCHER] Stopped watching session ${this.sessionId}`);
6155
+ }
6156
+ /**
6157
+ * Subscribe to file change events
6158
+ */
6159
+ subscribe(callback) {
6160
+ this.subscribers.add(callback);
6161
+ return () => this.subscribers.delete(callback);
6162
+ }
6163
+ /**
6164
+ * Remove a subscriber
6165
+ */
6166
+ unsubscribe(callback) {
6167
+ this.subscribers.delete(callback);
6168
+ }
6169
+ /**
6170
+ * Check if watcher is active
6171
+ */
6172
+ isActive() {
6173
+ return this.isWatching;
6174
+ }
6175
+ /**
6176
+ * Get watcher status
6177
+ */
6178
+ getStatus() {
6179
+ return {
6180
+ sessionId: this.sessionId,
6181
+ watchPath: this.basePath,
6182
+ isWatching: this.isWatching,
6183
+ watchedFileCount: this.previousFiles.size,
6184
+ pendingEventCount: 0,
6185
+ startedAt: this.startedAt,
6186
+ lastPollAt: this.lastPollAt,
6187
+ pollCount: this.pollCount
6188
+ };
6189
+ }
6190
+ /**
6191
+ * Force an immediate scan (useful for testing or manual refresh)
6192
+ */
6193
+ async forceScan() {
6194
+ await this.scan(false);
6195
+ }
6196
+ /**
6197
+ * Scan the sandbox for file changes
6198
+ */
6199
+ async scan(isInitial) {
6200
+ if (!this.sandboxOps.isSandboxRunning(this.sessionId)) {
6201
+ console.warn(`[REMOTE_WATCHER] Sandbox stopped for session ${this.sessionId}`);
6202
+ await this.stop();
6203
+ return;
6204
+ }
6205
+ const listResult = await this.sandboxOps.listFiles(this.sessionId, this.basePath);
6206
+ if (!listResult.success || !listResult.files) {
6207
+ throw new Error(listResult.error ?? "Failed to list files in sandbox");
6208
+ }
6209
+ const currentFiles = /* @__PURE__ */ new Map();
6210
+ for (const filePath of listResult.files) {
6211
+ if (this.shouldIgnore(filePath)) {
6212
+ continue;
6213
+ }
6214
+ currentFiles.set(filePath, { path: filePath });
6215
+ }
6216
+ this.lastPollAt = /* @__PURE__ */ new Date();
6217
+ this.pollCount++;
6218
+ if (isInitial) {
6219
+ this.previousFiles = currentFiles;
6220
+ return;
6221
+ }
6222
+ const changes = [];
6223
+ for (const [filePath, info] of currentFiles) {
6224
+ const previous = this.previousFiles.get(filePath);
6225
+ if (!previous) {
6226
+ changes.push({
6227
+ type: "add",
6228
+ relativePath: filePath,
6229
+ absolutePath: `${this.basePath}/${filePath}`,
6230
+ sessionId: this.sessionId,
6231
+ fileSize: info.size,
6232
+ timestamp: /* @__PURE__ */ new Date()
6233
+ });
6234
+ }
6235
+ }
6236
+ for (const [filePath] of this.previousFiles) {
6237
+ if (!currentFiles.has(filePath)) {
6238
+ changes.push({
6239
+ type: "unlink",
6240
+ relativePath: filePath,
6241
+ absolutePath: `${this.basePath}/${filePath}`,
6242
+ sessionId: this.sessionId,
6243
+ timestamp: /* @__PURE__ */ new Date()
6244
+ });
6245
+ }
6246
+ }
6247
+ this.previousFiles = currentFiles;
6248
+ for (const event of changes) {
6249
+ await this.emitEvent(event);
6250
+ }
6251
+ }
6252
+ /**
6253
+ * Check if a path should be ignored
6254
+ */
6255
+ shouldIgnore(filePath) {
6256
+ for (const pattern of this.ignored) {
6257
+ if (this.matchesGlob(filePath, pattern)) {
6258
+ return true;
6259
+ }
6260
+ }
6261
+ return false;
6262
+ }
6263
+ /**
6264
+ * Simple glob matching (supports ** and *)
6265
+ */
6266
+ matchesGlob(filePath, pattern) {
6267
+ const regexPattern = pattern.replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/{{GLOBSTAR}}/g, ".*").replace(/\//g, "\\/");
6268
+ const regex = new RegExp(`^${regexPattern}$`);
6269
+ return regex.test(filePath);
6270
+ }
6271
+ /**
6272
+ * Emit a file change event to all subscribers
6273
+ */
6274
+ async emitEvent(event) {
6275
+ for (const callback of this.subscribers) {
6276
+ try {
6277
+ await callback(event);
6278
+ } catch (error) {
6279
+ console.error("[REMOTE_WATCHER] Error in subscriber callback:", error);
6280
+ }
6281
+ }
6282
+ }
6283
+ };
6284
+ exports.RemoteFileWatcherManager = class {
6285
+ watchers = /* @__PURE__ */ new Map();
6286
+ /**
6287
+ * Start watching a session's sandbox
6288
+ */
6289
+ async startWatching(options) {
6290
+ const { sessionId } = options;
6291
+ const existing = this.watchers.get(sessionId);
6292
+ if (existing) {
6293
+ await existing.stop();
6294
+ }
6295
+ const watcher = new exports.RemoteSandboxFileWatcher(options);
6296
+ await watcher.start();
6297
+ this.watchers.set(sessionId, watcher);
6298
+ return watcher;
6299
+ }
6300
+ /**
6301
+ * Stop watching a session
6302
+ */
6303
+ async stopWatching(sessionId) {
6304
+ const watcher = this.watchers.get(sessionId);
6305
+ if (watcher) {
6306
+ await watcher.stop();
6307
+ this.watchers.delete(sessionId);
6308
+ }
6309
+ }
6310
+ /**
6311
+ * Get a watcher for a session
6312
+ */
6313
+ getWatcher(sessionId) {
6314
+ return this.watchers.get(sessionId);
6315
+ }
6316
+ /**
6317
+ * Check if a session is being watched
6318
+ */
6319
+ isWatching(sessionId) {
6320
+ const watcher = this.watchers.get(sessionId);
6321
+ return watcher?.isActive() ?? false;
6322
+ }
6323
+ /**
6324
+ * Stop all watchers
6325
+ */
6326
+ async stopAll() {
6327
+ const promises = Array.from(this.watchers.values()).map((w) => w.stop());
6328
+ await Promise.all(promises);
6329
+ this.watchers.clear();
6330
+ }
6331
+ };
6332
+ globalRemoteWatcherManager = null;
6333
+ }
6334
+ });
5808
6335
  function extractErrorMessage(error) {
5809
6336
  if (error === null || error === void 0) {
5810
6337
  return "Unknown error (null/undefined)";
@@ -5850,13 +6377,256 @@ function createSandboxFileSync(options) {
5850
6377
  exports.SandboxFileSync = void 0;
5851
6378
  var init_sandbox_file_sync = __esm({
5852
6379
  "src/runtime/sandbox-file-sync.ts"() {
6380
+ init_sandbox_file_watcher();
6381
+ init_types();
5853
6382
  exports.SandboxFileSync = class {
5854
6383
  fileStore;
5855
6384
  sandboxBasePath;
6385
+ defaultWatchOptions;
5856
6386
  sandboxOps = null;
6387
+ onFileEvent;
6388
+ eventStorage;
6389
+ webhookConfig;
6390
+ // Watcher management
6391
+ remoteWatchers = /* @__PURE__ */ new Map();
6392
+ localWatchers = /* @__PURE__ */ new Map();
6393
+ fileChangeSubscribers = /* @__PURE__ */ new Set();
6394
+ // Sequence number cache per session (for event storage)
6395
+ sequenceNumbers = /* @__PURE__ */ new Map();
5857
6396
  constructor(options) {
5858
6397
  this.fileStore = options.fileStore;
5859
6398
  this.sandboxBasePath = options.sandboxBasePath ?? ".claude/files";
6399
+ this.onFileEvent = options.onFileEvent;
6400
+ this.defaultWatchOptions = options.watchOptions ?? {};
6401
+ this.eventStorage = options.eventStorage;
6402
+ this.webhookConfig = options.webhook;
6403
+ }
6404
+ /**
6405
+ * Set the file event callback
6406
+ * This can be used to set or update the callback after construction
6407
+ */
6408
+ setFileEventCallback(callback) {
6409
+ this.onFileEvent = callback;
6410
+ }
6411
+ /**
6412
+ * Set the event storage for persisting file events to the timeline
6413
+ * This can be used to set or update the storage after construction
6414
+ */
6415
+ setEventStorage(storage) {
6416
+ this.eventStorage = storage;
6417
+ }
6418
+ /**
6419
+ * Set webhook configuration for external notifications
6420
+ * This can be used to set or update the webhook after construction
6421
+ */
6422
+ setWebhook(config) {
6423
+ this.webhookConfig = config;
6424
+ }
6425
+ /**
6426
+ * Remove webhook configuration
6427
+ */
6428
+ removeWebhook() {
6429
+ this.webhookConfig = void 0;
6430
+ }
6431
+ /**
6432
+ * Send a webhook notification
6433
+ * @param payload - The webhook payload to send
6434
+ */
6435
+ async sendWebhook(payload) {
6436
+ if (!this.webhookConfig) return;
6437
+ const config = this.webhookConfig;
6438
+ const eventType = payload.fileSyncEvent?.operation ?? "file_change";
6439
+ if (config.events && config.events.length > 0) {
6440
+ const shouldSend = config.events.some(
6441
+ (e) => e === eventType || e === "file_change" && payload.event === "file_change"
6442
+ );
6443
+ if (!shouldSend) return;
6444
+ }
6445
+ const body = JSON.stringify(payload);
6446
+ const timeoutMs = config.timeoutMs ?? 1e4;
6447
+ const retries = config.retries ?? 3;
6448
+ const isAsync = config.async !== false;
6449
+ const headers = {
6450
+ "Content-Type": "application/json",
6451
+ "User-Agent": "Ash-FileSync/1.0",
6452
+ ...config.headers
6453
+ };
6454
+ if (config.secret) {
6455
+ const signature = crypto.createHmac("sha256", config.secret).update(body).digest("hex");
6456
+ headers["X-Ash-Signature"] = `sha256=${signature}`;
6457
+ headers["X-Ash-Timestamp"] = payload.timestamp;
6458
+ }
6459
+ const sendWithRetry = async (attempt) => {
6460
+ try {
6461
+ const controller = new AbortController();
6462
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
6463
+ const response = await fetch(config.url, {
6464
+ method: "POST",
6465
+ headers,
6466
+ body,
6467
+ signal: controller.signal
6468
+ });
6469
+ clearTimeout(timeoutId);
6470
+ if (!response.ok) {
6471
+ throw new Error(`Webhook returned ${response.status}: ${response.statusText}`);
6472
+ }
6473
+ console.log(`[FILE_SYNC] Webhook sent successfully to ${config.url}`);
6474
+ } catch (error) {
6475
+ const errorMsg = error instanceof Error ? error.message : String(error);
6476
+ if (attempt < retries) {
6477
+ const delay = Math.pow(2, attempt) * 1e3;
6478
+ console.warn(`[FILE_SYNC] Webhook failed (attempt ${attempt + 1}/${retries}), retrying in ${delay}ms: ${errorMsg}`);
6479
+ await new Promise((resolve3) => setTimeout(resolve3, delay));
6480
+ return sendWithRetry(attempt + 1);
6481
+ }
6482
+ console.error(`[FILE_SYNC] Webhook failed after ${retries} attempts: ${errorMsg}`);
6483
+ throw error;
6484
+ }
6485
+ };
6486
+ if (isAsync) {
6487
+ sendWithRetry(0).catch((error) => {
6488
+ console.error("[FILE_SYNC] Async webhook failed:", error);
6489
+ });
6490
+ } else {
6491
+ await sendWithRetry(0);
6492
+ }
6493
+ }
6494
+ /**
6495
+ * Send a file sync event webhook
6496
+ */
6497
+ async sendFileSyncWebhook(sessionId, event) {
6498
+ const payload = {
6499
+ event: "file_sync",
6500
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6501
+ sessionId,
6502
+ metadata: this.webhookConfig?.metadata,
6503
+ fileSyncEvent: event
6504
+ };
6505
+ await this.sendWebhook(payload);
6506
+ }
6507
+ /**
6508
+ * Send a file change event webhook (from watcher)
6509
+ */
6510
+ async sendFileChangeWebhook(event) {
6511
+ const payload = {
6512
+ event: "file_change",
6513
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6514
+ sessionId: event.sessionId,
6515
+ metadata: this.webhookConfig?.metadata,
6516
+ fileChangeEvent: event
6517
+ };
6518
+ await this.sendWebhook(payload);
6519
+ }
6520
+ /**
6521
+ * Get the next sequence number for a session
6522
+ */
6523
+ async getNextSequenceNumber(sessionId) {
6524
+ const cached = this.sequenceNumbers.get(sessionId);
6525
+ if (cached !== void 0) {
6526
+ const next = cached + 1;
6527
+ this.sequenceNumbers.set(sessionId, next);
6528
+ return next;
6529
+ }
6530
+ if (this.eventStorage) {
6531
+ try {
6532
+ const next = await this.eventStorage.getNextEventSequence(sessionId);
6533
+ this.sequenceNumbers.set(sessionId, next);
6534
+ return next;
6535
+ } catch (error) {
6536
+ console.warn("[FILE_SYNC] Failed to get next sequence number:", error);
6537
+ }
6538
+ }
6539
+ this.sequenceNumbers.set(sessionId, 1);
6540
+ return 1;
6541
+ }
6542
+ /**
6543
+ * Compute a unified diff between two text contents
6544
+ */
6545
+ computeDiff(previousContent, newContent) {
6546
+ if (!previousContent && !newContent) return void 0;
6547
+ if (!previousContent || !newContent) return void 0;
6548
+ try {
6549
+ const MAX_DIFF_SIZE = 100 * 1024;
6550
+ if (previousContent.length > MAX_DIFF_SIZE || newContent.length > MAX_DIFF_SIZE) {
6551
+ return void 0;
6552
+ }
6553
+ const prevText = previousContent.toString("utf-8");
6554
+ const newText = newContent.toString("utf-8");
6555
+ const sampleSize = Math.min(8192, prevText.length, newText.length);
6556
+ if (prevText.slice(0, sampleSize).includes("\0") || newText.slice(0, sampleSize).includes("\0")) {
6557
+ return void 0;
6558
+ }
6559
+ const prevLines = prevText.split("\n");
6560
+ const newLines = newText.split("\n");
6561
+ const diffLines = [];
6562
+ diffLines.push("--- a/file");
6563
+ diffLines.push("+++ b/file");
6564
+ const removed = prevLines.filter((l) => !newLines.includes(l));
6565
+ const added = newLines.filter((l) => !prevLines.includes(l));
6566
+ if (removed.length === 0 && added.length === 0) {
6567
+ return void 0;
6568
+ }
6569
+ diffLines.push("@@ -1 +1 @@");
6570
+ for (const line of removed) {
6571
+ diffLines.push(`-${line}`);
6572
+ }
6573
+ for (const line of added) {
6574
+ diffLines.push(`+${line}`);
6575
+ }
6576
+ return diffLines.join("\n");
6577
+ } catch {
6578
+ return void 0;
6579
+ }
6580
+ }
6581
+ /**
6582
+ * Emit a file event if a callback is registered
6583
+ * Also persists to EventStorage if configured
6584
+ */
6585
+ async emitFileEvent(sessionId, event) {
6586
+ if (this.onFileEvent) {
6587
+ try {
6588
+ this.onFileEvent(event);
6589
+ } catch (error) {
6590
+ console.error("[FILE_SYNC] Error in file event callback:", error);
6591
+ }
6592
+ }
6593
+ if (this.webhookConfig) {
6594
+ this.sendFileSyncWebhook(sessionId, event);
6595
+ }
6596
+ if (this.eventStorage) {
6597
+ try {
6598
+ const now = /* @__PURE__ */ new Date();
6599
+ const sequenceNumber = await this.getNextSequenceNumber(sessionId);
6600
+ const diff = this.computeDiff(event.previousContent, event.newContent);
6601
+ const isTextFile = diff !== void 0 || event.newContent && !event.newContent.slice(0, 8192).includes(0);
6602
+ const eventData = {
6603
+ operation: event.operation,
6604
+ direction: event.direction,
6605
+ source: "internal",
6606
+ // File sync operations are internal
6607
+ filePath: event.filePath,
6608
+ fileSize: event.fileSize,
6609
+ success: event.success,
6610
+ error: event.error,
6611
+ diff,
6612
+ isTextFile,
6613
+ previousSize: event.previousContent?.length
6614
+ };
6615
+ const eventType = `file_${event.operation}`;
6616
+ const sessionEvent = {
6617
+ eventType,
6618
+ category: exports.EventCategory.FILE,
6619
+ startedAt: now,
6620
+ endedAt: now,
6621
+ durationMs: 0,
6622
+ eventData,
6623
+ sequenceNumber
6624
+ };
6625
+ await this.eventStorage.saveEvents(sessionId, [sessionEvent]);
6626
+ } catch (error) {
6627
+ console.error("[FILE_SYNC] Error saving file event to storage:", error);
6628
+ }
6629
+ }
5860
6630
  }
5861
6631
  /**
5862
6632
  * Set the sandbox operations implementation
@@ -5870,8 +6640,8 @@ var init_sandbox_file_sync = __esm({
5870
6640
  * @param path - The relative file path
5871
6641
  * @param targetPath - Optional override for the base path
5872
6642
  */
5873
- getSandboxPath(path14, targetPath) {
5874
- const normalizedPath = path14.replace(/^\/+/, "");
6643
+ getSandboxPath(path15, targetPath) {
6644
+ const normalizedPath = path15.replace(/^\/+/, "");
5875
6645
  const basePath = targetPath ?? this.sandboxBasePath;
5876
6646
  if (basePath === ".") {
5877
6647
  return normalizedPath;
@@ -5884,33 +6654,67 @@ var init_sandbox_file_sync = __esm({
5884
6654
  * @param path - File path (stored in S3 and used as relative path in sandbox)
5885
6655
  * @param content - File content
5886
6656
  * @param options - Push options (e.g., targetPath to override sandbox location)
6657
+ * @param previousContent - Optional previous content for diff computation
5887
6658
  */
5888
- async pushFile(sessionId, path14, content, options) {
6659
+ async pushFile(sessionId, path15, content, options, previousContent) {
5889
6660
  const result = {
5890
- path: path14,
6661
+ path: path15,
5891
6662
  s3Written: false,
5892
6663
  sandboxWritten: false
5893
6664
  };
5894
6665
  try {
5895
- await this.fileStore.writeFile(sessionId, path14, content);
6666
+ await this.fileStore.writeFile(sessionId, path15, content);
5896
6667
  result.s3Written = true;
6668
+ await this.emitFileEvent(sessionId, {
6669
+ operation: "push",
6670
+ direction: "to_s3",
6671
+ filePath: path15,
6672
+ fileSize: content.length,
6673
+ success: true,
6674
+ previousContent,
6675
+ newContent: content
6676
+ });
5897
6677
  } catch (error) {
5898
6678
  const errorMessage = extractErrorMessage(error);
5899
6679
  result.error = `S3 write failed: ${errorMessage}`;
5900
- console.error(`[FILE_SYNC] S3 write failed for session ${sessionId}, path ${path14}:`, error);
6680
+ console.error(`[FILE_SYNC] S3 write failed for session ${sessionId}, path ${path15}:`, error);
6681
+ await this.emitFileEvent(sessionId, {
6682
+ operation: "push",
6683
+ direction: "to_s3",
6684
+ filePath: path15,
6685
+ fileSize: content.length,
6686
+ success: false,
6687
+ error: errorMessage
6688
+ });
5901
6689
  return result;
5902
6690
  }
5903
6691
  if (this.sandboxOps?.isSandboxRunning(sessionId)) {
5904
6692
  try {
5905
- const sandboxPath = this.getSandboxPath(path14, options?.targetPath);
6693
+ const sandboxPath = this.getSandboxPath(path15, options?.targetPath);
5906
6694
  const writeResult = await this.sandboxOps.writeFile(sessionId, sandboxPath, content);
5907
6695
  result.sandboxWritten = writeResult.success;
6696
+ await this.emitFileEvent(sessionId, {
6697
+ operation: "push",
6698
+ direction: "to_sandbox",
6699
+ filePath: path15,
6700
+ fileSize: content.length,
6701
+ success: writeResult.success,
6702
+ error: writeResult.error
6703
+ });
5908
6704
  if (!writeResult.success && writeResult.error) {
5909
- console.warn(`[FILE_SYNC] Sandbox write failed for ${path14}: ${writeResult.error}`);
6705
+ console.warn(`[FILE_SYNC] Sandbox write failed for ${path15}: ${writeResult.error}`);
5910
6706
  }
5911
6707
  } catch (error) {
5912
6708
  const errorMessage = extractErrorMessage(error);
5913
- console.warn(`[FILE_SYNC] Sandbox write error for ${path14}: ${errorMessage}`);
6709
+ console.warn(`[FILE_SYNC] Sandbox write error for ${path15}: ${errorMessage}`);
6710
+ await this.emitFileEvent(sessionId, {
6711
+ operation: "push",
6712
+ direction: "to_sandbox",
6713
+ filePath: path15,
6714
+ fileSize: content.length,
6715
+ success: false,
6716
+ error: errorMessage
6717
+ });
5914
6718
  }
5915
6719
  } else {
5916
6720
  console.debug(`[FILE_SYNC] Sandbox not running for session ${sessionId}, skipping sandbox write`);
@@ -5935,9 +6739,9 @@ var init_sandbox_file_sync = __esm({
5935
6739
  * Pull a file from sandbox to S3
5936
6740
  * Reads from sandbox and writes to S3
5937
6741
  */
5938
- async pullFile(sessionId, path14) {
6742
+ async pullFile(sessionId, path15) {
5939
6743
  const result = {
5940
- path: path14,
6744
+ path: path15,
5941
6745
  content: null,
5942
6746
  s3Written: false
5943
6747
  };
@@ -5946,35 +6750,73 @@ var init_sandbox_file_sync = __esm({
5946
6750
  return result;
5947
6751
  }
5948
6752
  try {
5949
- const sandboxPath = this.getSandboxPath(path14);
6753
+ const sandboxPath = this.getSandboxPath(path15);
5950
6754
  const readResult = await this.sandboxOps.readFile(sessionId, sandboxPath);
5951
6755
  if (!readResult.success || !readResult.content) {
5952
6756
  result.error = readResult.error ?? "File not found in sandbox";
6757
+ await this.emitFileEvent(sessionId, {
6758
+ operation: "pull",
6759
+ direction: "from_sandbox",
6760
+ filePath: path15,
6761
+ success: false,
6762
+ error: result.error
6763
+ });
5953
6764
  return result;
5954
6765
  }
5955
6766
  result.content = readResult.content;
6767
+ await this.emitFileEvent(sessionId, {
6768
+ operation: "pull",
6769
+ direction: "from_sandbox",
6770
+ filePath: path15,
6771
+ fileSize: readResult.content.length,
6772
+ success: true,
6773
+ newContent: readResult.content
6774
+ });
5956
6775
  } catch (error) {
5957
6776
  const errorMessage = extractErrorMessage(error);
5958
6777
  result.error = `Sandbox read failed: ${errorMessage}`;
6778
+ await this.emitFileEvent(sessionId, {
6779
+ operation: "pull",
6780
+ direction: "from_sandbox",
6781
+ filePath: path15,
6782
+ success: false,
6783
+ error: errorMessage
6784
+ });
5959
6785
  return result;
5960
6786
  }
5961
6787
  try {
5962
- await this.fileStore.writeFile(sessionId, path14, result.content);
6788
+ await this.fileStore.writeFile(sessionId, path15, result.content);
5963
6789
  result.s3Written = true;
6790
+ await this.emitFileEvent(sessionId, {
6791
+ operation: "pull",
6792
+ direction: "to_s3",
6793
+ filePath: path15,
6794
+ fileSize: result.content.length,
6795
+ success: true,
6796
+ newContent: result.content
6797
+ });
5964
6798
  } catch (error) {
5965
6799
  const errorMessage = extractErrorMessage(error);
5966
6800
  result.error = `S3 write failed: ${errorMessage}`;
5967
- console.error(`[FILE_SYNC] S3 write failed in pullFile for session ${sessionId}, path ${path14}:`, error);
6801
+ console.error(`[FILE_SYNC] S3 write failed in pullFile for session ${sessionId}, path ${path15}:`, error);
6802
+ await this.emitFileEvent(sessionId, {
6803
+ operation: "pull",
6804
+ direction: "to_s3",
6805
+ filePath: path15,
6806
+ fileSize: result.content?.length,
6807
+ success: false,
6808
+ error: errorMessage
6809
+ });
5968
6810
  }
5969
6811
  return result;
5970
6812
  }
5971
6813
  /**
5972
6814
  * Read a file (tries sandbox first, falls back to S3)
5973
6815
  */
5974
- async readFile(sessionId, path14) {
6816
+ async readFile(sessionId, path15) {
5975
6817
  if (this.sandboxOps?.isSandboxRunning(sessionId)) {
5976
6818
  try {
5977
- const sandboxPath = this.getSandboxPath(path14);
6819
+ const sandboxPath = this.getSandboxPath(path15);
5978
6820
  const readResult = await this.sandboxOps.readFile(sessionId, sandboxPath);
5979
6821
  if (readResult.success && readResult.content) {
5980
6822
  return { content: readResult.content, source: "sandbox" };
@@ -5983,7 +6825,7 @@ var init_sandbox_file_sync = __esm({
5983
6825
  }
5984
6826
  }
5985
6827
  try {
5986
- const content = await this.fileStore.readFile(sessionId, path14);
6828
+ const content = await this.fileStore.readFile(sessionId, path15);
5987
6829
  if (content) {
5988
6830
  return { content, source: "s3" };
5989
6831
  }
@@ -6000,21 +6842,48 @@ var init_sandbox_file_sync = __esm({
6000
6842
  /**
6001
6843
  * Delete a file from both S3 and sandbox
6002
6844
  */
6003
- async deleteFile(sessionId, path14) {
6845
+ async deleteFile(sessionId, path15) {
6004
6846
  const result = { s3Deleted: false, sandboxDeleted: false };
6005
6847
  try {
6006
- await this.fileStore.deleteFile(sessionId, path14);
6848
+ await this.fileStore.deleteFile(sessionId, path15);
6007
6849
  result.s3Deleted = true;
6850
+ await this.emitFileEvent(sessionId, {
6851
+ operation: "delete",
6852
+ direction: "from_s3",
6853
+ filePath: path15,
6854
+ success: true
6855
+ });
6008
6856
  } catch (error) {
6009
6857
  const errorMessage = extractErrorMessage(error);
6010
- console.warn(`[FILE_SYNC] S3 delete failed for ${path14}: ${errorMessage}`);
6858
+ console.warn(`[FILE_SYNC] S3 delete failed for ${path15}: ${errorMessage}`);
6859
+ await this.emitFileEvent(sessionId, {
6860
+ operation: "delete",
6861
+ direction: "from_s3",
6862
+ filePath: path15,
6863
+ success: false,
6864
+ error: errorMessage
6865
+ });
6011
6866
  }
6012
6867
  if (this.sandboxOps?.isSandboxRunning(sessionId)) {
6013
6868
  try {
6014
- const sandboxPath = this.getSandboxPath(path14);
6869
+ const sandboxPath = this.getSandboxPath(path15);
6015
6870
  console.log(`[FILE_SYNC] Would delete ${sandboxPath} from sandbox`);
6016
6871
  result.sandboxDeleted = true;
6017
- } catch {
6872
+ await this.emitFileEvent(sessionId, {
6873
+ operation: "delete",
6874
+ direction: "from_sandbox",
6875
+ filePath: path15,
6876
+ success: true
6877
+ });
6878
+ } catch (error) {
6879
+ const errorMessage = extractErrorMessage(error);
6880
+ await this.emitFileEvent(sessionId, {
6881
+ operation: "delete",
6882
+ direction: "from_sandbox",
6883
+ filePath: path15,
6884
+ success: false,
6885
+ error: errorMessage
6886
+ });
6018
6887
  }
6019
6888
  }
6020
6889
  return result;
@@ -6035,14 +6904,36 @@ var init_sandbox_file_sync = __esm({
6035
6904
  const content = await this.fileStore.readFile(sessionId, file.path);
6036
6905
  if (!content) {
6037
6906
  result.errors.push({ path: file.path, error: "File not found in S3" });
6907
+ await this.emitFileEvent(sessionId, {
6908
+ operation: "sync_to_sandbox",
6909
+ direction: "from_s3",
6910
+ filePath: file.path,
6911
+ success: false,
6912
+ error: "File not found in S3"
6913
+ });
6038
6914
  continue;
6039
6915
  }
6040
6916
  const sandboxPath = this.getSandboxPath(file.path);
6041
6917
  const writeResult = await this.sandboxOps.writeFile(sessionId, sandboxPath, content);
6042
6918
  if (writeResult.success) {
6043
6919
  result.fileCount++;
6920
+ await this.emitFileEvent(sessionId, {
6921
+ operation: "sync_to_sandbox",
6922
+ direction: "to_sandbox",
6923
+ filePath: file.path,
6924
+ fileSize: content.length,
6925
+ success: true
6926
+ });
6044
6927
  } else {
6045
6928
  result.errors.push({ path: file.path, error: writeResult.error ?? "Unknown error" });
6929
+ await this.emitFileEvent(sessionId, {
6930
+ operation: "sync_to_sandbox",
6931
+ direction: "to_sandbox",
6932
+ filePath: file.path,
6933
+ fileSize: content.length,
6934
+ success: false,
6935
+ error: writeResult.error
6936
+ });
6046
6937
  }
6047
6938
  } catch (error) {
6048
6939
  const errorMessage = extractErrorMessage(error);
@@ -6050,6 +6941,13 @@ var init_sandbox_file_sync = __esm({
6050
6941
  path: file.path,
6051
6942
  error: errorMessage
6052
6943
  });
6944
+ await this.emitFileEvent(sessionId, {
6945
+ operation: "sync_to_sandbox",
6946
+ direction: "to_sandbox",
6947
+ filePath: file.path,
6948
+ success: false,
6949
+ error: errorMessage
6950
+ });
6053
6951
  }
6054
6952
  }
6055
6953
  console.log(`[FILE_SYNC] Synced ${result.fileCount} files to sandbox for session ${sessionId}`);
@@ -6082,8 +6980,23 @@ var init_sandbox_file_sync = __esm({
6082
6980
  const pullResult = await this.pullFile(sessionId, filePath);
6083
6981
  if (pullResult.s3Written) {
6084
6982
  result.fileCount++;
6983
+ await this.emitFileEvent(sessionId, {
6984
+ operation: "sync_from_sandbox",
6985
+ direction: "to_s3",
6986
+ filePath,
6987
+ fileSize: pullResult.content?.length,
6988
+ success: true,
6989
+ newContent: pullResult.content ?? void 0
6990
+ });
6085
6991
  } else if (pullResult.error) {
6086
6992
  result.errors.push({ path: filePath, error: pullResult.error });
6993
+ await this.emitFileEvent(sessionId, {
6994
+ operation: "sync_from_sandbox",
6995
+ direction: "to_s3",
6996
+ filePath,
6997
+ success: false,
6998
+ error: pullResult.error
6999
+ });
6087
7000
  }
6088
7001
  } catch (error) {
6089
7002
  const errorMessage = extractErrorMessage(error);
@@ -6091,6 +7004,13 @@ var init_sandbox_file_sync = __esm({
6091
7004
  path: filePath,
6092
7005
  error: errorMessage
6093
7006
  });
7007
+ await this.emitFileEvent(sessionId, {
7008
+ operation: "sync_from_sandbox",
7009
+ direction: "to_s3",
7010
+ filePath,
7011
+ success: false,
7012
+ error: errorMessage
7013
+ });
6094
7014
  }
6095
7015
  }
6096
7016
  console.log(`[FILE_SYNC] Synced ${result.fileCount} files from sandbox to S3 for session ${sessionId}`);
@@ -6099,16 +7019,526 @@ var init_sandbox_file_sync = __esm({
6099
7019
  /**
6100
7020
  * Get a signed URL for direct file download
6101
7021
  */
6102
- async getSignedUrl(sessionId, path14, expiresIn) {
6103
- return this.fileStore.getSignedUrl(sessionId, path14, expiresIn);
7022
+ async getSignedUrl(sessionId, path15, expiresIn) {
7023
+ return this.fileStore.getSignedUrl(sessionId, path15, expiresIn);
6104
7024
  }
6105
7025
  /**
6106
7026
  * Get a signed URL for direct file upload
6107
7027
  */
6108
- async getUploadUrl(sessionId, path14, expiresIn) {
6109
- return this.fileStore.getUploadUrl(sessionId, path14, expiresIn);
7028
+ async getUploadUrl(sessionId, path15, expiresIn) {
7029
+ return this.fileStore.getUploadUrl(sessionId, path15, expiresIn);
7030
+ }
7031
+ // ===========================================================================
7032
+ // File Watching API
7033
+ // ===========================================================================
7034
+ /**
7035
+ * Start watching a sandbox for file changes and auto-sync to S3.
7036
+ *
7037
+ * For remote sandboxes (Vercel, E2B, etc.), uses polling.
7038
+ * For local sandboxes, can optionally use native file watching via chokidar.
7039
+ *
7040
+ * @param sessionId - Session to watch
7041
+ * @param options - Watch options (overrides constructor defaults)
7042
+ */
7043
+ async startWatching(sessionId, options) {
7044
+ const opts = { ...this.defaultWatchOptions, ...options };
7045
+ await this.stopWatching(sessionId);
7046
+ const handleFileChange = async (event) => {
7047
+ console.log(`[FILE_SYNC] File change detected: ${event.type} ${event.relativePath}`);
7048
+ if (this.webhookConfig) {
7049
+ this.sendFileChangeWebhook(event);
7050
+ }
7051
+ for (const subscriber of this.fileChangeSubscribers) {
7052
+ try {
7053
+ await subscriber(event);
7054
+ } catch (error) {
7055
+ console.error("[FILE_SYNC] Error in file change subscriber:", error);
7056
+ }
7057
+ }
7058
+ if (event.type === "add" || event.type === "change") {
7059
+ try {
7060
+ const pullResult = await this.pullFile(sessionId, event.relativePath);
7061
+ if (pullResult.s3Written) {
7062
+ console.log(`[FILE_SYNC] Auto-synced ${event.relativePath} to S3`);
7063
+ } else if (pullResult.error) {
7064
+ console.warn(`[FILE_SYNC] Failed to auto-sync ${event.relativePath}: ${pullResult.error}`);
7065
+ }
7066
+ } catch (error) {
7067
+ console.error(`[FILE_SYNC] Error auto-syncing ${event.relativePath}:`, error);
7068
+ }
7069
+ } else if (event.type === "unlink") {
7070
+ try {
7071
+ await this.deleteFile(sessionId, event.relativePath);
7072
+ console.log(`[FILE_SYNC] Auto-deleted ${event.relativePath} from S3`);
7073
+ } catch (error) {
7074
+ console.error(`[FILE_SYNC] Error auto-deleting ${event.relativePath}:`, error);
7075
+ }
7076
+ }
7077
+ };
7078
+ if (opts.useLocalWatcher && opts.localPath) {
7079
+ const watcher = new exports.SandboxFileWatcher({
7080
+ sessionId,
7081
+ watchPath: opts.localPath,
7082
+ debounceMs: opts.debounceMs ?? 300,
7083
+ ignored: opts.ignored ?? ["**/node_modules/**", "**/.git/**"],
7084
+ onFileChange: handleFileChange,
7085
+ onError: (error) => {
7086
+ console.error(`[FILE_SYNC] Local watcher error for session ${sessionId}:`, error);
7087
+ }
7088
+ });
7089
+ await watcher.start();
7090
+ this.localWatchers.set(sessionId, watcher);
7091
+ console.log(`[FILE_SYNC] Started local file watching for session ${sessionId}`);
7092
+ } else {
7093
+ if (!this.sandboxOps) {
7094
+ throw new Error("Sandbox operations not configured. Call setSandboxOperations first.");
7095
+ }
7096
+ const watcher = new exports.RemoteSandboxFileWatcher({
7097
+ sessionId,
7098
+ sandboxOps: this.sandboxOps,
7099
+ basePath: this.sandboxBasePath,
7100
+ pollIntervalMs: opts.pollIntervalMs ?? 2e3,
7101
+ ignored: opts.ignored ?? ["**/node_modules/**", "**/.git/**"],
7102
+ onFileChange: handleFileChange,
7103
+ onError: (error) => {
7104
+ console.error(`[FILE_SYNC] Remote watcher error for session ${sessionId}:`, error);
7105
+ }
7106
+ });
7107
+ await watcher.start();
7108
+ this.remoteWatchers.set(sessionId, watcher);
7109
+ console.log(`[FILE_SYNC] Started remote file watching for session ${sessionId}`);
7110
+ }
7111
+ }
7112
+ /**
7113
+ * Stop watching a session's sandbox
7114
+ */
7115
+ async stopWatching(sessionId) {
7116
+ const remoteWatcher = this.remoteWatchers.get(sessionId);
7117
+ if (remoteWatcher) {
7118
+ await remoteWatcher.stop();
7119
+ this.remoteWatchers.delete(sessionId);
7120
+ }
7121
+ const localWatcher = this.localWatchers.get(sessionId);
7122
+ if (localWatcher) {
7123
+ await localWatcher.stop();
7124
+ this.localWatchers.delete(sessionId);
7125
+ }
7126
+ }
7127
+ /**
7128
+ * Check if a session is being watched
7129
+ */
7130
+ isWatching(sessionId) {
7131
+ return this.remoteWatchers.has(sessionId) || this.localWatchers.has(sessionId);
7132
+ }
7133
+ /**
7134
+ * Subscribe to file change events across all watched sessions.
7135
+ * This allows external applications to react to file changes.
7136
+ *
7137
+ * @param callback - Function to call when a file changes
7138
+ * @returns Unsubscribe function
7139
+ *
7140
+ * @example
7141
+ * ```typescript
7142
+ * const unsubscribe = fileSync.onFileChange((event) => {
7143
+ * console.log(`File ${event.relativePath} was ${event.type}d`);
7144
+ * // Update your application state here
7145
+ * });
7146
+ *
7147
+ * // Later, when done
7148
+ * unsubscribe();
7149
+ * ```
7150
+ */
7151
+ onFileChange(callback) {
7152
+ this.fileChangeSubscribers.add(callback);
7153
+ return () => this.fileChangeSubscribers.delete(callback);
7154
+ }
7155
+ /**
7156
+ * Remove a file change subscriber
7157
+ */
7158
+ offFileChange(callback) {
7159
+ this.fileChangeSubscribers.delete(callback);
7160
+ }
7161
+ /**
7162
+ * Stop all watchers and cleanup
7163
+ */
7164
+ async stopAllWatching() {
7165
+ const remotePromises = Array.from(this.remoteWatchers.values()).map((w) => w.stop());
7166
+ const localPromises = Array.from(this.localWatchers.values()).map((w) => w.stop());
7167
+ await Promise.all([...remotePromises, ...localPromises]);
7168
+ this.remoteWatchers.clear();
7169
+ this.localWatchers.clear();
7170
+ }
7171
+ /**
7172
+ * Get watching status for all sessions
7173
+ */
7174
+ getWatchingStatus() {
7175
+ const statuses = [];
7176
+ for (const [sessionId, watcher] of this.remoteWatchers) {
7177
+ statuses.push({ sessionId, type: "remote", isActive: watcher.isActive() });
7178
+ }
7179
+ for (const [sessionId, watcher] of this.localWatchers) {
7180
+ statuses.push({ sessionId, type: "local", isActive: watcher.isActive() });
7181
+ }
7182
+ return statuses;
7183
+ }
7184
+ };
7185
+ }
7186
+ });
7187
+
7188
+ // src/runtime/in-sandbox-watcher.ts
7189
+ function createInSandboxWatcher(options) {
7190
+ return new exports.InSandboxWatcher(options);
7191
+ }
7192
+ function getInSandboxWatcherManager() {
7193
+ if (!globalInSandboxManager) {
7194
+ globalInSandboxManager = new exports.InSandboxWatcherManager();
7195
+ }
7196
+ return globalInSandboxManager;
7197
+ }
7198
+ function createInSandboxWatcherManager() {
7199
+ return new exports.InSandboxWatcherManager();
7200
+ }
7201
+ var WATCHER_SCRIPT; exports.InSandboxWatcher = void 0; exports.InSandboxWatcherManager = void 0; var globalInSandboxManager;
7202
+ var init_in_sandbox_watcher = __esm({
7203
+ "src/runtime/in-sandbox-watcher.ts"() {
7204
+ init_vercel_sandbox_executor();
7205
+ WATCHER_SCRIPT = `
7206
+ const fs = require('fs');
7207
+ const path = require('path');
7208
+
7209
+ const watchPath = process.argv[2] || '.';
7210
+ const ignored = new Set(['node_modules', '.git', '.cache', '__pycache__']);
7211
+
7212
+ // Track all watchers for cleanup
7213
+ const watchers = new Map();
7214
+
7215
+ // Debounce map to coalesce rapid changes
7216
+ const pending = new Map();
7217
+ const DEBOUNCE_MS = 100;
7218
+
7219
+ function emit(type, filePath) {
7220
+ const event = {
7221
+ type,
7222
+ path: filePath,
7223
+ timestamp: Date.now()
7224
+ };
7225
+ console.log(JSON.stringify(event));
7226
+ }
7227
+
7228
+ function shouldIgnore(name) {
7229
+ return ignored.has(name) || name.startsWith('.');
7230
+ }
7231
+
7232
+ function watchDir(dir) {
7233
+ try {
7234
+ const watcher = fs.watch(dir, { persistent: true }, (eventType, filename) => {
7235
+ if (!filename || shouldIgnore(filename)) return;
7236
+
7237
+ const fullPath = path.join(dir, filename);
7238
+ const key = fullPath;
7239
+
7240
+ // Debounce
7241
+ if (pending.has(key)) {
7242
+ clearTimeout(pending.get(key));
7243
+ }
7244
+
7245
+ pending.set(key, setTimeout(() => {
7246
+ pending.delete(key);
7247
+
7248
+ fs.stat(fullPath, (err, stats) => {
7249
+ if (err) {
7250
+ if (err.code === 'ENOENT') {
7251
+ emit('unlink', fullPath);
7252
+ // Stop watching if it was a directory
7253
+ if (watchers.has(fullPath)) {
7254
+ watchers.get(fullPath).close();
7255
+ watchers.delete(fullPath);
7256
+ }
7257
+ }
7258
+ } else {
7259
+ const type = eventType === 'rename' ? 'add' : 'change';
7260
+ emit(type, fullPath);
7261
+
7262
+ // If it's a new directory, start watching it
7263
+ if (stats.isDirectory() && !watchers.has(fullPath)) {
7264
+ watchDir(fullPath);
7265
+ }
7266
+ }
7267
+ });
7268
+ }, DEBOUNCE_MS));
7269
+ });
7270
+
7271
+ watchers.set(dir, watcher);
7272
+
7273
+ // Watch subdirectories
7274
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
7275
+ for (const entry of entries) {
7276
+ if (entry.isDirectory() && !shouldIgnore(entry.name)) {
7277
+ watchDir(path.join(dir, entry.name));
7278
+ }
7279
+ }
7280
+ } catch (err) {
7281
+ // Directory may not exist or be inaccessible
7282
+ console.error(JSON.stringify({ error: err.message, dir }));
7283
+ }
7284
+ }
7285
+
7286
+ // Start watching
7287
+ watchDir(watchPath);
7288
+
7289
+ // Keep alive
7290
+ process.on('SIGTERM', () => {
7291
+ for (const watcher of watchers.values()) {
7292
+ watcher.close();
7293
+ }
7294
+ process.exit(0);
7295
+ });
7296
+
7297
+ // Heartbeat to indicate we're running
7298
+ setInterval(() => {
7299
+ console.log(JSON.stringify({ heartbeat: true, timestamp: Date.now() }));
7300
+ }, 5000);
7301
+
7302
+ console.log(JSON.stringify({ started: true, path: watchPath }));
7303
+ `;
7304
+ exports.InSandboxWatcher = class {
7305
+ sessionId;
7306
+ watchPath;
7307
+ outputPollIntervalMs;
7308
+ sandboxState = null;
7309
+ outputPollTimer = null;
7310
+ subscribers = /* @__PURE__ */ new Set();
7311
+ isRunning = false;
7312
+ startedAt;
7313
+ lastHeartbeat;
7314
+ lastOutputPosition = 0;
7315
+ onError;
7316
+ onReady;
7317
+ constructor(options) {
7318
+ this.sessionId = options.sessionId;
7319
+ this.watchPath = options.watchPath;
7320
+ this.outputPollIntervalMs = options.outputPollIntervalMs ?? 1e3;
7321
+ this.onError = options.onError;
7322
+ this.onReady = options.onReady;
7323
+ if (options.onFileChange) {
7324
+ this.subscribers.add(options.onFileChange);
7325
+ }
7326
+ }
7327
+ /**
7328
+ * Start the watcher process inside the sandbox
7329
+ */
7330
+ async start() {
7331
+ if (this.isRunning) {
7332
+ console.warn(`[IN_SANDBOX_WATCHER] Already running for session ${this.sessionId}`);
7333
+ return;
7334
+ }
7335
+ try {
7336
+ this.sandboxState = await getOrCreateSandbox({
7337
+ sessionId: this.sessionId,
7338
+ runtime: "node22",
7339
+ timeout: 600
7340
+ });
7341
+ const { sandbox } = this.sandboxState;
7342
+ const scriptPath = "/tmp/.file-watcher.js";
7343
+ const outputPath = "/tmp/.file-watcher-output.log";
7344
+ const writeScriptCmd = `cat > ${scriptPath} << 'WATCHER_EOF'
7345
+ ${WATCHER_SCRIPT}
7346
+ WATCHER_EOF`;
7347
+ await sandbox.runCommand({
7348
+ cmd: "sh",
7349
+ args: ["-c", writeScriptCmd]
7350
+ });
7351
+ const startCmd = `nohup node ${scriptPath} "${this.watchPath}" > ${outputPath} 2>&1 &`;
7352
+ await sandbox.runCommand({
7353
+ cmd: "sh",
7354
+ args: ["-c", startCmd]
7355
+ });
7356
+ console.log(`[IN_SANDBOX_WATCHER] Started watcher in sandbox for ${this.watchPath}`);
7357
+ this.isRunning = true;
7358
+ this.startedAt = /* @__PURE__ */ new Date();
7359
+ this.outputPollTimer = setInterval(async () => {
7360
+ try {
7361
+ await this.pollOutput(outputPath);
7362
+ } catch (error) {
7363
+ console.error("[IN_SANDBOX_WATCHER] Error polling output:", error);
7364
+ if (this.onError && error instanceof Error) {
7365
+ this.onError(error);
7366
+ }
7367
+ }
7368
+ }, this.outputPollIntervalMs);
7369
+ setTimeout(() => {
7370
+ this.pollOutput(outputPath).catch(console.error);
7371
+ }, 500);
7372
+ } catch (error) {
7373
+ console.error(`[IN_SANDBOX_WATCHER] Failed to start watcher:`, error);
7374
+ throw error;
7375
+ }
7376
+ }
7377
+ /**
7378
+ * Poll the output file for new events
7379
+ */
7380
+ async pollOutput(outputPath) {
7381
+ if (!this.sandboxState?.sandbox) return;
7382
+ try {
7383
+ const result = await this.sandboxState.sandbox.runCommand({
7384
+ cmd: "sh",
7385
+ args: ["-c", `tail -c +${this.lastOutputPosition + 1} ${outputPath} 2>/dev/null || true`]
7386
+ });
7387
+ const output = await result.stdout();
7388
+ if (output && output.trim()) {
7389
+ const sizeResult = await this.sandboxState.sandbox.runCommand({
7390
+ cmd: "sh",
7391
+ args: ["-c", `stat -c%s ${outputPath} 2>/dev/null || echo 0`]
7392
+ });
7393
+ const sizeStr = await sizeResult.stdout();
7394
+ this.lastOutputPosition = parseInt(sizeStr.trim(), 10) || 0;
7395
+ this.processOutput(output);
7396
+ }
7397
+ } catch {
7398
+ }
7399
+ }
7400
+ /**
7401
+ * Process output from the watcher script
7402
+ */
7403
+ processOutput(stdout) {
7404
+ const lines = stdout.split("\n").filter(Boolean);
7405
+ for (const line of lines) {
7406
+ try {
7407
+ const data = JSON.parse(line);
7408
+ if (data.started) {
7409
+ console.log(`[IN_SANDBOX_WATCHER] Watcher started for ${data.path}`);
7410
+ if (this.onReady) {
7411
+ this.onReady();
7412
+ }
7413
+ } else if (data.heartbeat) {
7414
+ this.lastHeartbeat = new Date(data.timestamp);
7415
+ } else if (data.error) {
7416
+ console.warn(`[IN_SANDBOX_WATCHER] Error in sandbox:`, data.error);
7417
+ } else if (data.type && data.path) {
7418
+ this.emitEvent({
7419
+ type: data.type,
7420
+ relativePath: data.path.replace(/^\.\//, ""),
7421
+ absolutePath: data.path,
7422
+ sessionId: this.sessionId,
7423
+ timestamp: new Date(data.timestamp)
7424
+ });
7425
+ }
7426
+ } catch {
7427
+ }
7428
+ }
7429
+ }
7430
+ /**
7431
+ * Stop the watcher process
7432
+ */
7433
+ async stop() {
7434
+ if (!this.isRunning) {
7435
+ return;
7436
+ }
7437
+ if (this.outputPollTimer) {
7438
+ clearInterval(this.outputPollTimer);
7439
+ this.outputPollTimer = null;
7440
+ }
7441
+ try {
7442
+ if (this.sandboxState?.sandbox) {
7443
+ await this.sandboxState.sandbox.runCommand({
7444
+ cmd: "sh",
7445
+ args: ["-c", "pkill -f file-watcher.js 2>/dev/null || true"]
7446
+ });
7447
+ await this.sandboxState.sandbox.runCommand({
7448
+ cmd: "sh",
7449
+ args: ["-c", "rm -f /tmp/.file-watcher.js /tmp/.file-watcher-output.log"]
7450
+ });
7451
+ }
7452
+ } catch {
7453
+ }
7454
+ this.isRunning = false;
7455
+ this.sandboxState = null;
7456
+ this.lastOutputPosition = 0;
7457
+ console.log(`[IN_SANDBOX_WATCHER] Stopped watcher for session ${this.sessionId}`);
7458
+ }
7459
+ /**
7460
+ * Subscribe to file change events
7461
+ */
7462
+ subscribe(callback) {
7463
+ this.subscribers.add(callback);
7464
+ return () => this.subscribers.delete(callback);
7465
+ }
7466
+ /**
7467
+ * Check if watcher is running
7468
+ */
7469
+ isActive() {
7470
+ return this.isRunning;
7471
+ }
7472
+ /**
7473
+ * Get watcher status
7474
+ */
7475
+ getStatus() {
7476
+ return {
7477
+ sessionId: this.sessionId,
7478
+ watchPath: this.watchPath,
7479
+ isRunning: this.isRunning,
7480
+ startedAt: this.startedAt,
7481
+ lastHeartbeat: this.lastHeartbeat
7482
+ };
7483
+ }
7484
+ /**
7485
+ * Emit event to all subscribers
7486
+ */
7487
+ async emitEvent(event) {
7488
+ for (const callback of this.subscribers) {
7489
+ try {
7490
+ await callback(event);
7491
+ } catch (error) {
7492
+ console.error("[IN_SANDBOX_WATCHER] Error in subscriber callback:", error);
7493
+ }
7494
+ }
7495
+ }
7496
+ };
7497
+ exports.InSandboxWatcherManager = class {
7498
+ watchers = /* @__PURE__ */ new Map();
7499
+ /**
7500
+ * Start watching a sandbox
7501
+ */
7502
+ async startWatching(options) {
7503
+ const { sessionId } = options;
7504
+ await this.stopWatching(sessionId);
7505
+ const watcher = new exports.InSandboxWatcher(options);
7506
+ await watcher.start();
7507
+ this.watchers.set(sessionId, watcher);
7508
+ return watcher;
7509
+ }
7510
+ /**
7511
+ * Stop watching a session
7512
+ */
7513
+ async stopWatching(sessionId) {
7514
+ const watcher = this.watchers.get(sessionId);
7515
+ if (watcher) {
7516
+ await watcher.stop();
7517
+ this.watchers.delete(sessionId);
7518
+ }
7519
+ }
7520
+ /**
7521
+ * Get watcher for a session
7522
+ */
7523
+ getWatcher(sessionId) {
7524
+ return this.watchers.get(sessionId);
7525
+ }
7526
+ /**
7527
+ * Check if watching
7528
+ */
7529
+ isWatching(sessionId) {
7530
+ return this.watchers.get(sessionId)?.isActive() ?? false;
7531
+ }
7532
+ /**
7533
+ * Stop all watchers
7534
+ */
7535
+ async stopAll() {
7536
+ const promises = Array.from(this.watchers.values()).map((w) => w.stop());
7537
+ await Promise.all(promises);
7538
+ this.watchers.clear();
6110
7539
  }
6111
7540
  };
7541
+ globalInSandboxManager = null;
6112
7542
  }
6113
7543
  });
6114
7544
 
@@ -6220,8 +7650,8 @@ function checkSecurityConfig(config) {
6220
7650
  }
6221
7651
  return { issues, warnings, recommendations };
6222
7652
  }
6223
- function isSensitivePath(path14) {
6224
- const normalized = path14.replace(/\\/g, "/").toLowerCase();
7653
+ function isSensitivePath(path15) {
7654
+ const normalized = path15.replace(/\\/g, "/").toLowerCase();
6225
7655
  for (const sensitive of exports.SENSITIVE_PATHS) {
6226
7656
  const pattern = sensitive.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/~/g, ".*");
6227
7657
  if (new RegExp(pattern).test(normalized)) {
@@ -6238,6 +7668,8 @@ var init_runtime = __esm({
6238
7668
  init_vercel_sandbox_executor();
6239
7669
  init_sandbox_file_sync();
6240
7670
  init_sandbox_pool();
7671
+ init_sandbox_file_watcher();
7672
+ init_in_sandbox_watcher();
6241
7673
  exports.RuntimePresets = {
6242
7674
  /**
6243
7675
  * Development mode - no isolation, for local testing
@@ -6348,8 +7780,8 @@ var init_runtime = __esm({
6348
7780
  this.config.pattern = pattern;
6349
7781
  return this;
6350
7782
  }
6351
- workingDirectory(path14) {
6352
- this.config.workingDirectory = path14;
7783
+ workingDirectory(path15) {
7784
+ this.config.workingDirectory = path15;
6353
7785
  return this;
6354
7786
  }
6355
7787
  sandbox(config) {
@@ -6587,7 +8019,7 @@ var init_local_provider = __esm({
6587
8019
  maxFileSize;
6588
8020
  constructor(source, options = {}) {
6589
8021
  this.source = source;
6590
- this.basePath = path3__namespace.resolve(source.localPath);
8022
+ this.basePath = path4__namespace.resolve(source.localPath);
6591
8023
  this.maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
6592
8024
  }
6593
8025
  /**
@@ -6595,8 +8027,8 @@ var init_local_provider = __esm({
6595
8027
  * Prevents path traversal attacks.
6596
8028
  */
6597
8029
  resolvePath(relativePath) {
6598
- const normalized = path3__namespace.normalize(relativePath);
6599
- const resolved = path3__namespace.resolve(this.basePath, normalized);
8030
+ const normalized = path4__namespace.normalize(relativePath);
8031
+ const resolved = path4__namespace.resolve(this.basePath, normalized);
6600
8032
  if (!resolved.startsWith(this.basePath)) {
6601
8033
  throw new Error(`Path traversal detected: ${relativePath}`);
6602
8034
  }
@@ -6605,15 +8037,15 @@ var init_local_provider = __esm({
6605
8037
  async listFiles(relativePath) {
6606
8038
  const dirPath = this.resolvePath(relativePath || ".");
6607
8039
  try {
6608
- const entries = await fs9.promises.readdir(dirPath, { withFileTypes: true });
8040
+ const entries = await fs10.promises.readdir(dirPath, { withFileTypes: true });
6609
8041
  const fileEntries = await Promise.all(
6610
8042
  entries.map(async (entry) => {
6611
- const entryPath = path3__namespace.join(relativePath || ".", entry.name);
6612
- const fullPath = path3__namespace.join(dirPath, entry.name);
8043
+ const entryPath = path4__namespace.join(relativePath || ".", entry.name);
8044
+ const fullPath = path4__namespace.join(dirPath, entry.name);
6613
8045
  let size;
6614
8046
  let modifiedAt;
6615
8047
  try {
6616
- const stats = await fs9.promises.stat(fullPath);
8048
+ const stats = await fs10.promises.stat(fullPath);
6617
8049
  size = entry.isFile() ? stats.size : void 0;
6618
8050
  modifiedAt = stats.mtime;
6619
8051
  } catch {
@@ -6647,7 +8079,7 @@ var init_local_provider = __esm({
6647
8079
  async readFile(relativePath) {
6648
8080
  const filePath = this.resolvePath(relativePath);
6649
8081
  try {
6650
- const stats = await fs9.promises.stat(filePath);
8082
+ const stats = await fs10.promises.stat(filePath);
6651
8083
  if (stats.isDirectory()) {
6652
8084
  throw new Error(`Cannot read directory as file: ${relativePath}`);
6653
8085
  }
@@ -6657,7 +8089,7 @@ var init_local_provider = __esm({
6657
8089
  );
6658
8090
  }
6659
8091
  try {
6660
- const content = await fs9.promises.readFile(filePath, "utf-8");
8092
+ const content = await fs10.promises.readFile(filePath, "utf-8");
6661
8093
  return {
6662
8094
  path: relativePath,
6663
8095
  content,
@@ -6665,7 +8097,7 @@ var init_local_provider = __esm({
6665
8097
  size: stats.size
6666
8098
  };
6667
8099
  } catch {
6668
- const buffer = await fs9.promises.readFile(filePath);
8100
+ const buffer = await fs10.promises.readFile(filePath);
6669
8101
  return {
6670
8102
  path: relativePath,
6671
8103
  content: buffer.toString("base64"),
@@ -6683,7 +8115,7 @@ var init_local_provider = __esm({
6683
8115
  async exists(relativePath) {
6684
8116
  try {
6685
8117
  const fullPath = this.resolvePath(relativePath);
6686
- const stats = await fs9.promises.stat(fullPath);
8118
+ const stats = await fs10.promises.stat(fullPath);
6687
8119
  return stats.isDirectory() ? "directory" : "file";
6688
8120
  } catch {
6689
8121
  return null;
@@ -6692,7 +8124,7 @@ var init_local_provider = __esm({
6692
8124
  async copyTo(sourcePath, targetPath, recursive = true) {
6693
8125
  const sourceFullPath = this.resolvePath(sourcePath);
6694
8126
  try {
6695
- const stats = await fs9.promises.stat(sourceFullPath);
8127
+ const stats = await fs10.promises.stat(sourceFullPath);
6696
8128
  if (stats.isDirectory()) {
6697
8129
  if (!recursive) {
6698
8130
  throw new Error(
@@ -6701,8 +8133,8 @@ var init_local_provider = __esm({
6701
8133
  }
6702
8134
  await this.copyDirectory(sourceFullPath, targetPath);
6703
8135
  } else {
6704
- await fs9.promises.mkdir(path3__namespace.dirname(targetPath), { recursive: true });
6705
- await fs9.promises.copyFile(sourceFullPath, targetPath);
8136
+ await fs10.promises.mkdir(path4__namespace.dirname(targetPath), { recursive: true });
8137
+ await fs10.promises.copyFile(sourceFullPath, targetPath);
6706
8138
  }
6707
8139
  } catch (error) {
6708
8140
  if (error.code === "ENOENT") {
@@ -6712,16 +8144,16 @@ var init_local_provider = __esm({
6712
8144
  }
6713
8145
  }
6714
8146
  async copyDirectory(sourceDir, targetDir) {
6715
- await fs9.promises.mkdir(targetDir, { recursive: true });
6716
- const entries = await fs9.promises.readdir(sourceDir, { withFileTypes: true });
8147
+ await fs10.promises.mkdir(targetDir, { recursive: true });
8148
+ const entries = await fs10.promises.readdir(sourceDir, { withFileTypes: true });
6717
8149
  await Promise.all(
6718
8150
  entries.map(async (entry) => {
6719
- const sourcePath = path3__namespace.join(sourceDir, entry.name);
6720
- const targetPath = path3__namespace.join(targetDir, entry.name);
8151
+ const sourcePath = path4__namespace.join(sourceDir, entry.name);
8152
+ const targetPath = path4__namespace.join(targetDir, entry.name);
6721
8153
  if (entry.isDirectory()) {
6722
8154
  await this.copyDirectory(sourcePath, targetPath);
6723
8155
  } else {
6724
- await fs9.promises.copyFile(sourcePath, targetPath);
8156
+ await fs10.promises.copyFile(sourcePath, targetPath);
6725
8157
  }
6726
8158
  })
6727
8159
  );
@@ -6916,11 +8348,11 @@ var init_github_provider = __esm({
6916
8348
  }
6917
8349
  if (type === "file") {
6918
8350
  const content = await this.readFile(sourcePath);
6919
- await fs9.promises.mkdir(path3__namespace.dirname(targetPath), { recursive: true });
8351
+ await fs10.promises.mkdir(path4__namespace.dirname(targetPath), { recursive: true });
6920
8352
  if (content.encoding === "base64") {
6921
- await fs9.promises.writeFile(targetPath, Buffer.from(content.content, "base64"));
8353
+ await fs10.promises.writeFile(targetPath, Buffer.from(content.content, "base64"));
6922
8354
  } else {
6923
- await fs9.promises.writeFile(targetPath, content.content, "utf-8");
8355
+ await fs10.promises.writeFile(targetPath, content.content, "utf-8");
6924
8356
  }
6925
8357
  } else {
6926
8358
  if (!recursive) {
@@ -6928,13 +8360,13 @@ var init_github_provider = __esm({
6928
8360
  `Cannot copy directory without recursive flag: ${sourcePath}`
6929
8361
  );
6930
8362
  }
6931
- await fs9.promises.mkdir(targetPath, { recursive: true });
8363
+ await fs10.promises.mkdir(targetPath, { recursive: true });
6932
8364
  const entries = await this.listFiles(sourcePath);
6933
8365
  await Promise.all(
6934
8366
  entries.map(
6935
8367
  (entry) => this.copyTo(
6936
8368
  entry.path,
6937
- path3__namespace.join(targetPath, entry.name),
8369
+ path4__namespace.join(targetPath, entry.name),
6938
8370
  recursive
6939
8371
  )
6940
8372
  )
@@ -7017,9 +8449,9 @@ var init_manager2 = __esm({
7017
8449
  if (enabledSkills.length === 0) {
7018
8450
  return "";
7019
8451
  }
7020
- const sessionDir = path3__namespace.join(this.tempDir, sessionId);
7021
- const skillsDir = path3__namespace.join(sessionDir, ".claude", "skills");
7022
- await fs9.promises.mkdir(skillsDir, { recursive: true });
8452
+ const sessionDir = path4__namespace.join(this.tempDir, sessionId);
8453
+ const skillsDir = path4__namespace.join(sessionDir, ".claude", "skills");
8454
+ await fs10.promises.mkdir(skillsDir, { recursive: true });
7023
8455
  await Promise.all(
7024
8456
  enabledSkills.map((skill) => this.materializeSkill(skill, skillsDir, token))
7025
8457
  );
@@ -7030,10 +8462,10 @@ var init_manager2 = __esm({
7030
8462
  */
7031
8463
  async materializeSkill(skill, skillsDir, token) {
7032
8464
  const provider = this.createFileProvider(skill.source, token);
7033
- const skillDir = path3__namespace.join(skillsDir, skill.name);
7034
- await fs9.promises.mkdir(skillDir, { recursive: true });
8465
+ const skillDir = path4__namespace.join(skillsDir, skill.name);
8466
+ await fs10.promises.mkdir(skillDir, { recursive: true });
7035
8467
  for (const includePath of skill.includePaths) {
7036
- const targetPath = path3__namespace.join(skillDir, path3__namespace.basename(includePath));
8468
+ const targetPath = path4__namespace.join(skillDir, path4__namespace.basename(includePath));
7037
8469
  try {
7038
8470
  await provider.copyTo(includePath, targetPath, true);
7039
8471
  } catch (error) {
@@ -7048,9 +8480,9 @@ var init_manager2 = __esm({
7048
8480
  * Clean up materialized skills for a session
7049
8481
  */
7050
8482
  async cleanupSession(sessionId) {
7051
- const sessionDir = path3__namespace.join(this.tempDir, sessionId);
8483
+ const sessionDir = path4__namespace.join(this.tempDir, sessionId);
7052
8484
  try {
7053
- await fs9.promises.rm(sessionDir, { recursive: true, force: true });
8485
+ await fs10.promises.rm(sessionDir, { recursive: true, force: true });
7054
8486
  } catch (error) {
7055
8487
  console.warn(`Failed to cleanup session directory: ${sessionDir}`, error);
7056
8488
  }
@@ -7061,16 +8493,16 @@ var init_manager2 = __esm({
7061
8493
  */
7062
8494
  async cleanupStale(maxAgeMs = 24 * 60 * 60 * 1e3) {
7063
8495
  try {
7064
- const entries = await fs9.promises.readdir(this.tempDir, { withFileTypes: true });
8496
+ const entries = await fs10.promises.readdir(this.tempDir, { withFileTypes: true });
7065
8497
  const now = Date.now();
7066
8498
  await Promise.all(
7067
8499
  entries.map(async (entry) => {
7068
8500
  if (!entry.isDirectory()) return;
7069
- const dirPath = path3__namespace.join(this.tempDir, entry.name);
8501
+ const dirPath = path4__namespace.join(this.tempDir, entry.name);
7070
8502
  try {
7071
- const stats = await fs9.promises.stat(dirPath);
8503
+ const stats = await fs10.promises.stat(dirPath);
7072
8504
  if (now - stats.mtimeMs > maxAgeMs) {
7073
- await fs9.promises.rm(dirPath, { recursive: true, force: true });
8505
+ await fs10.promises.rm(dirPath, { recursive: true, force: true });
7074
8506
  }
7075
8507
  } catch {
7076
8508
  }
@@ -9875,11 +11307,11 @@ var init_dist = __esm({
9875
11307
  return isZodType(schema, "ZodEffects") ? this.cleanParameter(schema._def.schema) : schema;
9876
11308
  }
9877
11309
  generatePath(route) {
9878
- const { method, path: path14, request, responses } = route, pathItemConfig = __rest(route, ["method", "path", "request", "responses"]);
11310
+ const { method, path: path15, request, responses } = route, pathItemConfig = __rest(route, ["method", "path", "request", "responses"]);
9879
11311
  const generatedResponses = mapValues(responses, (response) => {
9880
11312
  return this.getResponse(response);
9881
11313
  });
9882
- const parameters = enhanceMissingParametersError(() => this.getParameters(request), { route: `${method} ${path14}` });
11314
+ const parameters = enhanceMissingParametersError(() => this.getParameters(request), { route: `${method} ${path15}` });
9883
11315
  const requestBody = this.getRequestBody(request === null || request === void 0 ? void 0 : request.body);
9884
11316
  const routeDoc = {
9885
11317
  [method]: Object.assign(Object.assign(Object.assign(Object.assign({}, pathItemConfig), parameters.length > 0 ? {
@@ -10074,8 +11506,8 @@ var init_dist2 = __esm({
10074
11506
  });
10075
11507
  function addBasePathToDocument(document, basePath) {
10076
11508
  const updatedPaths = {};
10077
- Object.keys(document.paths).forEach((path14) => {
10078
- updatedPaths[url.mergePath(basePath.replaceAll(/:([^\/]+)/g, "{$1}"), path14)] = document.paths[path14];
11509
+ Object.keys(document.paths).forEach((path15) => {
11510
+ updatedPaths[url.mergePath(basePath.replaceAll(/:([^\/]+)/g, "{$1}"), path15)] = document.paths[path15];
10079
11511
  });
10080
11512
  return {
10081
11513
  ...document,
@@ -10219,8 +11651,8 @@ var init_dist3 = __esm({
10219
11651
  const document = generator.generateDocument(config);
10220
11652
  return this._basePath ? addBasePathToDocument(document, this._basePath) : document;
10221
11653
  };
10222
- doc = (path14, configure) => {
10223
- return this.get(path14, (c) => {
11654
+ doc = (path15, configure) => {
11655
+ return this.get(path15, (c) => {
10224
11656
  const config = typeof configure === "function" ? configure(c) : configure;
10225
11657
  try {
10226
11658
  const document = this.getOpenAPIDocument(config);
@@ -10230,8 +11662,8 @@ var init_dist3 = __esm({
10230
11662
  }
10231
11663
  });
10232
11664
  };
10233
- doc31 = (path14, configure) => {
10234
- return this.get(path14, (c) => {
11665
+ doc31 = (path15, configure) => {
11666
+ return this.get(path15, (c) => {
10235
11667
  const config = typeof configure === "function" ? configure(c) : configure;
10236
11668
  try {
10237
11669
  const document = this.getOpenAPI31Document(config);
@@ -10241,9 +11673,9 @@ var init_dist3 = __esm({
10241
11673
  }
10242
11674
  });
10243
11675
  };
10244
- route(path14, app) {
10245
- const pathForOpenAPI = path14.replaceAll(/:([^\/]+)/g, "{$1}");
10246
- super.route(path14, app);
11676
+ route(path15, app) {
11677
+ const pathForOpenAPI = path15.replaceAll(/:([^\/]+)/g, "{$1}");
11678
+ super.route(path15, app);
10247
11679
  if (!(app instanceof _OpenAPIHono)) {
10248
11680
  return this;
10249
11681
  }
@@ -10290,8 +11722,8 @@ var init_dist3 = __esm({
10290
11722
  });
10291
11723
  return this;
10292
11724
  }
10293
- basePath(path14) {
10294
- return new _OpenAPIHono({ ...super.basePath(path14), defaultHook: this.defaultHook });
11725
+ basePath(path15) {
11726
+ return new _OpenAPIHono({ ...super.basePath(path15), defaultHook: this.defaultHook });
10295
11727
  }
10296
11728
  };
10297
11729
  createRoute = (routeConfig) => {
@@ -12072,12 +13504,12 @@ function requestLogger(options = {}) {
12072
13504
  const requestId = generateId();
12073
13505
  const start = Date.now();
12074
13506
  const method = c.req.method;
12075
- const path14 = c.req.path;
13507
+ const path15 = c.req.path;
12076
13508
  c.set("requestId", requestId);
12077
13509
  logger3.debug("Request started", {
12078
13510
  requestId,
12079
13511
  method,
12080
- path: path14,
13512
+ path: path15,
12081
13513
  userAgent: c.req.header("user-agent")
12082
13514
  });
12083
13515
  await next();
@@ -12087,7 +13519,7 @@ function requestLogger(options = {}) {
12087
13519
  logFn("Request completed", {
12088
13520
  requestId,
12089
13521
  method,
12090
- path: path14,
13522
+ path: path15,
12091
13523
  status,
12092
13524
  duration: `${duration}ms`
12093
13525
  });
@@ -12572,8 +14004,8 @@ function shellQuote3(str) {
12572
14004
  async function loadWorkspaceState(workspaceId, sandbox, bundleStore, repoRoot) {
12573
14005
  const resolvedRoot = repoRoot ?? sandbox.defaultRepoRoot;
12574
14006
  const gitRepo = new exports.SandboxGitRepo(sandbox, resolvedRoot);
12575
- const tmpDir = fs9__namespace.mkdtempSync(path3__namespace.join(os__namespace.tmpdir(), "ash-workspace-"));
12576
- const localBundle = path3__namespace.join(tmpDir, `${workspaceId}.bundle`);
14007
+ const tmpDir = fs10__namespace.mkdtempSync(path4__namespace.join(os__namespace.tmpdir(), "ash-workspace-"));
14008
+ const localBundle = path4__namespace.join(tmpDir, `${workspaceId}.bundle`);
12577
14009
  try {
12578
14010
  const bundleExists = await Promise.resolve(
12579
14011
  bundleStore.downloadBundle(workspaceId, localBundle)
@@ -12609,7 +14041,7 @@ async function loadWorkspaceState(workspaceId, sandbox, bundleStore, repoRoot) {
12609
14041
  }
12610
14042
  } finally {
12611
14043
  try {
12612
- fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
14044
+ fs10__namespace.rmSync(tmpDir, { recursive: true, force: true });
12613
14045
  } catch {
12614
14046
  }
12615
14047
  }
@@ -12629,11 +14061,11 @@ async function saveWorkspaceState(workspaceId, sandbox, gitRepo, bundleStore, co
12629
14061
  }
12630
14062
  const remoteBundlePath = BUNDLE_PATH_TEMPLATE.replace("{workspaceId}", workspaceId);
12631
14063
  await gitRepo.createBundle(remoteBundlePath);
12632
- const tmpDir = fs9__namespace.mkdtempSync(path3__namespace.join(os__namespace.tmpdir(), "ash-workspace-"));
12633
- const localBundle = path3__namespace.join(tmpDir, `${workspaceId}.bundle`);
14064
+ const tmpDir = fs10__namespace.mkdtempSync(path4__namespace.join(os__namespace.tmpdir(), "ash-workspace-"));
14065
+ const localBundle = path4__namespace.join(tmpDir, `${workspaceId}.bundle`);
12634
14066
  try {
12635
14067
  await sandbox.downloadFile(remoteBundlePath, localBundle);
12636
- const bundleSize = fs9__namespace.statSync(localBundle).size;
14068
+ const bundleSize = fs10__namespace.statSync(localBundle).size;
12637
14069
  await bundleStore.uploadBundle(workspaceId, localBundle);
12638
14070
  return {
12639
14071
  committed,
@@ -12642,7 +14074,7 @@ async function saveWorkspaceState(workspaceId, sandbox, gitRepo, bundleStore, co
12642
14074
  };
12643
14075
  } finally {
12644
14076
  try {
12645
- fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
14077
+ fs10__namespace.rmSync(tmpDir, { recursive: true, force: true });
12646
14078
  } catch {
12647
14079
  }
12648
14080
  }
@@ -12655,7 +14087,7 @@ var init_persistence = __esm({
12655
14087
  }
12656
14088
  });
12657
14089
  async function cloneRepository(url, ref, timeout = 12e4) {
12658
- const tmpDir = fs9__namespace.mkdtempSync(path3__namespace.join(os__namespace.tmpdir(), "ash-github-skill-"));
14090
+ const tmpDir = fs10__namespace.mkdtempSync(path4__namespace.join(os__namespace.tmpdir(), "ash-github-skill-"));
12659
14091
  try {
12660
14092
  const cloneCmd = ["git", "clone", "--depth", "1"];
12661
14093
  if (ref) {
@@ -12670,7 +14102,7 @@ async function cloneRepository(url, ref, timeout = 12e4) {
12670
14102
  return tmpDir;
12671
14103
  } catch (error) {
12672
14104
  try {
12673
- fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
14105
+ fs10__namespace.rmSync(tmpDir, { recursive: true, force: true });
12674
14106
  } catch {
12675
14107
  }
12676
14108
  throw error;
@@ -12682,10 +14114,10 @@ function getRepoName(url) {
12682
14114
  }
12683
14115
  function countFiles(dir, exclude) {
12684
14116
  let count = 0;
12685
- const entries = fs9__namespace.readdirSync(dir, { withFileTypes: true });
14117
+ const entries = fs10__namespace.readdirSync(dir, { withFileTypes: true });
12686
14118
  for (const entry of entries) {
12687
14119
  if (exclude.has(entry.name)) continue;
12688
- const fullPath = path3__namespace.join(dir, entry.name);
14120
+ const fullPath = path4__namespace.join(dir, entry.name);
12689
14121
  if (entry.isDirectory()) {
12690
14122
  count += countFiles(fullPath, exclude);
12691
14123
  } else if (entry.isFile()) {
@@ -12697,15 +14129,15 @@ function countFiles(dir, exclude) {
12697
14129
  async function uploadDirectory(sandbox, localPath, remotePath, exclude) {
12698
14130
  let uploadCount = 0;
12699
14131
  await sandbox.runCommand(`mkdir -p '${remotePath}'`);
12700
- const entries = fs9__namespace.readdirSync(localPath, { withFileTypes: true });
14132
+ const entries = fs10__namespace.readdirSync(localPath, { withFileTypes: true });
12701
14133
  for (const entry of entries) {
12702
14134
  if (exclude.has(entry.name)) continue;
12703
- const srcPath = path3__namespace.join(localPath, entry.name);
14135
+ const srcPath = path4__namespace.join(localPath, entry.name);
12704
14136
  const destPath = `${remotePath}/${entry.name}`;
12705
14137
  if (entry.isDirectory()) {
12706
14138
  uploadCount += await uploadDirectory(sandbox, srcPath, destPath, exclude);
12707
14139
  } else if (entry.isFile()) {
12708
- const content = fs9__namespace.readFileSync(srcPath, "utf-8");
14140
+ const content = fs10__namespace.readFileSync(srcPath, "utf-8");
12709
14141
  await sandbox.writeFile(destPath, content);
12710
14142
  uploadCount++;
12711
14143
  }
@@ -12731,13 +14163,13 @@ async function loadGitHubSkill(sandbox, config, skillsBasePath) {
12731
14163
  );
12732
14164
  }
12733
14165
  try {
12734
- const sourcePath = subpath ? path3__namespace.join(clonePath, subpath) : clonePath;
12735
- if (!fs9__namespace.existsSync(sourcePath)) {
14166
+ const sourcePath = subpath ? path4__namespace.join(clonePath, subpath) : clonePath;
14167
+ if (!fs10__namespace.existsSync(sourcePath)) {
12736
14168
  throw new Error(
12737
14169
  `Path '${subpath}' not found in repository ${url}`
12738
14170
  );
12739
14171
  }
12740
- if (!fs9__namespace.statSync(sourcePath).isDirectory()) {
14172
+ if (!fs10__namespace.statSync(sourcePath).isDirectory()) {
12741
14173
  throw new Error(
12742
14174
  `Path '${subpath}' in ${url} is not a directory`
12743
14175
  );
@@ -12756,7 +14188,7 @@ async function loadGitHubSkill(sandbox, config, skillsBasePath) {
12756
14188
  };
12757
14189
  } finally {
12758
14190
  try {
12759
- fs9__namespace.rmSync(clonePath, { recursive: true, force: true });
14191
+ fs10__namespace.rmSync(clonePath, { recursive: true, force: true });
12760
14192
  } catch {
12761
14193
  }
12762
14194
  }
@@ -13449,26 +14881,26 @@ var init_workspace = __esm({
13449
14881
  const localPaths = normalizeToArray(skillsConfig.local);
13450
14882
  for (const localPath of localPaths) {
13451
14883
  try {
13452
- if (!fs9__namespace.existsSync(localPath)) {
14884
+ if (!fs10__namespace.existsSync(localPath)) {
13453
14885
  errors.push({
13454
14886
  source: localPath,
13455
14887
  error: new Error(`Path does not exist: ${localPath}`)
13456
14888
  });
13457
14889
  continue;
13458
14890
  }
13459
- const stat = fs9__namespace.statSync(localPath);
13460
- if (!stat.isDirectory()) {
14891
+ const stat2 = fs10__namespace.statSync(localPath);
14892
+ if (!stat2.isDirectory()) {
13461
14893
  errors.push({
13462
14894
  source: localPath,
13463
14895
  error: new Error(`Path is not a directory: ${localPath}`)
13464
14896
  });
13465
14897
  continue;
13466
14898
  }
13467
- const entries = fs9__namespace.readdirSync(localPath);
14899
+ const entries = fs10__namespace.readdirSync(localPath);
13468
14900
  for (const entry of entries) {
13469
14901
  if (FILTERED_ITEMS2.has(entry)) continue;
13470
- const entryPath = path3__namespace.join(localPath, entry);
13471
- const entryStat = fs9__namespace.statSync(entryPath);
14902
+ const entryPath = path4__namespace.join(localPath, entry);
14903
+ const entryStat = fs10__namespace.statSync(entryPath);
13472
14904
  if (entryStat.isDirectory()) {
13473
14905
  const remotePath = `${skillsBase}/${entry}`;
13474
14906
  await this.sandbox.uploadDirectory(entryPath, remotePath, {
@@ -13553,50 +14985,50 @@ var init_bundle_store = __esm({
13553
14985
  directory;
13554
14986
  constructor(options) {
13555
14987
  this.directory = options.directory;
13556
- if (!fs9__namespace.existsSync(this.directory)) {
13557
- fs9__namespace.mkdirSync(this.directory, { recursive: true });
14988
+ if (!fs10__namespace.existsSync(this.directory)) {
14989
+ fs10__namespace.mkdirSync(this.directory, { recursive: true });
13558
14990
  }
13559
14991
  }
13560
14992
  keyForWorkspace(workspaceId) {
13561
- return path3__namespace.join(this.directory, `${workspaceId}.bundle`);
14993
+ return path4__namespace.join(this.directory, `${workspaceId}.bundle`);
13562
14994
  }
13563
14995
  downloadBundle(workspaceId, destPath) {
13564
14996
  const bundlePath = this.keyForWorkspace(workspaceId);
13565
- if (!fs9__namespace.existsSync(bundlePath)) {
14997
+ if (!fs10__namespace.existsSync(bundlePath)) {
13566
14998
  return false;
13567
14999
  }
13568
- const destDir = path3__namespace.dirname(destPath);
13569
- if (!fs9__namespace.existsSync(destDir)) {
13570
- fs9__namespace.mkdirSync(destDir, { recursive: true });
15000
+ const destDir = path4__namespace.dirname(destPath);
15001
+ if (!fs10__namespace.existsSync(destDir)) {
15002
+ fs10__namespace.mkdirSync(destDir, { recursive: true });
13571
15003
  }
13572
- fs9__namespace.copyFileSync(bundlePath, destPath);
15004
+ fs10__namespace.copyFileSync(bundlePath, destPath);
13573
15005
  return true;
13574
15006
  }
13575
15007
  uploadBundle(workspaceId, srcPath) {
13576
- if (!fs9__namespace.existsSync(srcPath)) {
15008
+ if (!fs10__namespace.existsSync(srcPath)) {
13577
15009
  throw new Error(`Source bundle not found: ${srcPath}`);
13578
15010
  }
13579
15011
  const bundlePath = this.keyForWorkspace(workspaceId);
13580
- const bundleDir = path3__namespace.dirname(bundlePath);
13581
- if (!fs9__namespace.existsSync(bundleDir)) {
13582
- fs9__namespace.mkdirSync(bundleDir, { recursive: true });
15012
+ const bundleDir = path4__namespace.dirname(bundlePath);
15013
+ if (!fs10__namespace.existsSync(bundleDir)) {
15014
+ fs10__namespace.mkdirSync(bundleDir, { recursive: true });
13583
15015
  }
13584
- fs9__namespace.copyFileSync(srcPath, bundlePath);
15016
+ fs10__namespace.copyFileSync(srcPath, bundlePath);
13585
15017
  }
13586
15018
  exists(workspaceId) {
13587
- return fs9__namespace.existsSync(this.keyForWorkspace(workspaceId));
15019
+ return fs10__namespace.existsSync(this.keyForWorkspace(workspaceId));
13588
15020
  }
13589
15021
  deleteBundle(workspaceId) {
13590
15022
  const bundlePath = this.keyForWorkspace(workspaceId);
13591
- if (fs9__namespace.existsSync(bundlePath)) {
13592
- fs9__namespace.unlinkSync(bundlePath);
15023
+ if (fs10__namespace.existsSync(bundlePath)) {
15024
+ fs10__namespace.unlinkSync(bundlePath);
13593
15025
  }
13594
15026
  }
13595
15027
  listWorkspaces() {
13596
- if (!fs9__namespace.existsSync(this.directory)) {
15028
+ if (!fs10__namespace.existsSync(this.directory)) {
13597
15029
  return [];
13598
15030
  }
13599
- return fs9__namespace.readdirSync(this.directory).filter((file) => file.endsWith(".bundle")).map((file) => file.replace(".bundle", ""));
15031
+ return fs10__namespace.readdirSync(this.directory).filter((file) => file.endsWith(".bundle")).map((file) => file.replace(".bundle", ""));
13600
15032
  }
13601
15033
  };
13602
15034
  exports.MemoryBundleStore = class {
@@ -13609,18 +15041,18 @@ var init_bundle_store = __esm({
13609
15041
  if (!bundle) {
13610
15042
  return false;
13611
15043
  }
13612
- const destDir = path3__namespace.dirname(destPath);
13613
- if (!fs9__namespace.existsSync(destDir)) {
13614
- fs9__namespace.mkdirSync(destDir, { recursive: true });
15044
+ const destDir = path4__namespace.dirname(destPath);
15045
+ if (!fs10__namespace.existsSync(destDir)) {
15046
+ fs10__namespace.mkdirSync(destDir, { recursive: true });
13615
15047
  }
13616
- fs9__namespace.writeFileSync(destPath, bundle);
15048
+ fs10__namespace.writeFileSync(destPath, bundle);
13617
15049
  return true;
13618
15050
  }
13619
15051
  uploadBundle(workspaceId, srcPath) {
13620
- if (!fs9__namespace.existsSync(srcPath)) {
15052
+ if (!fs10__namespace.existsSync(srcPath)) {
13621
15053
  throw new Error(`Source bundle not found: ${srcPath}`);
13622
15054
  }
13623
- const content = fs9__namespace.readFileSync(srcPath);
15055
+ const content = fs10__namespace.readFileSync(srcPath);
13624
15056
  this.bundles.set(workspaceId, content);
13625
15057
  }
13626
15058
  exists(workspaceId) {
@@ -13711,21 +15143,21 @@ var init_supabase_store = __esm({
13711
15143
  if (!data) {
13712
15144
  return false;
13713
15145
  }
13714
- const dir = path3__namespace.dirname(destPath);
13715
- if (!fs9__namespace.existsSync(dir)) {
13716
- fs9__namespace.mkdirSync(dir, { recursive: true });
15146
+ const dir = path4__namespace.dirname(destPath);
15147
+ if (!fs10__namespace.existsSync(dir)) {
15148
+ fs10__namespace.mkdirSync(dir, { recursive: true });
13717
15149
  }
13718
15150
  const arrayBuffer = await data.arrayBuffer();
13719
- fs9__namespace.writeFileSync(destPath, Buffer.from(arrayBuffer));
15151
+ fs10__namespace.writeFileSync(destPath, Buffer.from(arrayBuffer));
13720
15152
  return true;
13721
15153
  }
13722
15154
  async uploadBundle(workspaceId, srcPath) {
13723
- if (!fs9__namespace.existsSync(srcPath)) {
15155
+ if (!fs10__namespace.existsSync(srcPath)) {
13724
15156
  throw new Error(`Source bundle not found: ${srcPath}`);
13725
15157
  }
13726
15158
  const client = await this.ensureClient();
13727
15159
  const key = this.keyForWorkspace(workspaceId);
13728
- const content = fs9__namespace.readFileSync(srcPath);
15160
+ const content = fs10__namespace.readFileSync(srcPath);
13729
15161
  const { error } = await client.storage.from(this.bucket).upload(key, content, {
13730
15162
  contentType: "application/octet-stream",
13731
15163
  upsert: true
@@ -13854,11 +15286,11 @@ var init_cloud_store = __esm({
13854
15286
  return false;
13855
15287
  }
13856
15288
  const bytes = await body.transformToByteArray();
13857
- const dir = path3__namespace.dirname(destPath);
13858
- if (!fs9__namespace.existsSync(dir)) {
13859
- fs9__namespace.mkdirSync(dir, { recursive: true });
15289
+ const dir = path4__namespace.dirname(destPath);
15290
+ if (!fs10__namespace.existsSync(dir)) {
15291
+ fs10__namespace.mkdirSync(dir, { recursive: true });
13860
15292
  }
13861
- fs9__namespace.writeFileSync(destPath, Buffer.from(bytes));
15293
+ fs10__namespace.writeFileSync(destPath, Buffer.from(bytes));
13862
15294
  return true;
13863
15295
  } catch (error) {
13864
15296
  const err = error;
@@ -13869,12 +15301,12 @@ var init_cloud_store = __esm({
13869
15301
  }
13870
15302
  }
13871
15303
  async uploadBundle(workspaceId, srcPath) {
13872
- if (!fs9__namespace.existsSync(srcPath)) {
15304
+ if (!fs10__namespace.existsSync(srcPath)) {
13873
15305
  throw new Error(`Source bundle not found: ${srcPath}`);
13874
15306
  }
13875
15307
  const { client, module } = await this.ensureClient();
13876
15308
  const key = this.keyForWorkspace(workspaceId);
13877
- const content = fs9__namespace.readFileSync(srcPath);
15309
+ const content = fs10__namespace.readFileSync(srcPath);
13878
15310
  await client.send(
13879
15311
  new module.PutObjectCommand({
13880
15312
  Bucket: this.bucket,
@@ -13976,9 +15408,9 @@ var init_cloud_store = __esm({
13976
15408
  if (!exists) {
13977
15409
  return false;
13978
15410
  }
13979
- const dir = path3__namespace.dirname(destPath);
13980
- if (!fs9__namespace.existsSync(dir)) {
13981
- fs9__namespace.mkdirSync(dir, { recursive: true });
15411
+ const dir = path4__namespace.dirname(destPath);
15412
+ if (!fs10__namespace.existsSync(dir)) {
15413
+ fs10__namespace.mkdirSync(dir, { recursive: true });
13982
15414
  }
13983
15415
  await file.download({ destination: destPath });
13984
15416
  return true;
@@ -13987,12 +15419,12 @@ var init_cloud_store = __esm({
13987
15419
  }
13988
15420
  }
13989
15421
  async uploadBundle(workspaceId, srcPath) {
13990
- if (!fs9__namespace.existsSync(srcPath)) {
15422
+ if (!fs10__namespace.existsSync(srcPath)) {
13991
15423
  throw new Error(`Source bundle not found: ${srcPath}`);
13992
15424
  }
13993
15425
  const storage = await this.ensureStorage();
13994
15426
  const key = this.keyForWorkspace(workspaceId);
13995
- const content = fs9__namespace.readFileSync(srcPath);
15427
+ const content = fs10__namespace.readFileSync(srcPath);
13996
15428
  const file = storage.bucket(this.bucket).file(key);
13997
15429
  await file.save(content);
13998
15430
  }
@@ -14091,25 +15523,25 @@ var init_file_store = __esm({
14091
15523
  /**
14092
15524
  * Generate the S3 key for a file
14093
15525
  */
14094
- keyForFile(sessionId, path14) {
14095
- const normalizedPath = path14.replace(/^\/+/, "");
15526
+ keyForFile(sessionId, path15) {
15527
+ const normalizedPath = path15.replace(/^\/+/, "");
14096
15528
  return `${this.prefix}${sessionId}/${normalizedPath}`;
14097
15529
  }
14098
- async writeFile(sessionId, path14, content) {
15530
+ async writeFile(sessionId, path15, content) {
14099
15531
  const { client, module } = await this.ensureClient();
14100
- const key = this.keyForFile(sessionId, path14);
15532
+ const key = this.keyForFile(sessionId, path15);
14101
15533
  await client.send(
14102
15534
  new module.PutObjectCommand({
14103
15535
  Bucket: this.bucket,
14104
15536
  Key: key,
14105
15537
  Body: content,
14106
- ContentType: this.getMimeType(path14)
15538
+ ContentType: this.getMimeType(path15)
14107
15539
  })
14108
15540
  );
14109
15541
  }
14110
- async readFile(sessionId, path14) {
15542
+ async readFile(sessionId, path15) {
14111
15543
  const { client, module } = await this.ensureClient();
14112
- const key = this.keyForFile(sessionId, path14);
15544
+ const key = this.keyForFile(sessionId, path15);
14113
15545
  try {
14114
15546
  const response = await client.send(
14115
15547
  new module.GetObjectCommand({
@@ -14131,9 +15563,9 @@ var init_file_store = __esm({
14131
15563
  throw error;
14132
15564
  }
14133
15565
  }
14134
- async exists(sessionId, path14) {
15566
+ async exists(sessionId, path15) {
14135
15567
  const { client, module } = await this.ensureClient();
14136
- const key = this.keyForFile(sessionId, path14);
15568
+ const key = this.keyForFile(sessionId, path15);
14137
15569
  try {
14138
15570
  await client.send(
14139
15571
  new module.HeadObjectCommand({
@@ -14150,9 +15582,9 @@ var init_file_store = __esm({
14150
15582
  throw error;
14151
15583
  }
14152
15584
  }
14153
- async deleteFile(sessionId, path14) {
15585
+ async deleteFile(sessionId, path15) {
14154
15586
  const { client, module } = await this.ensureClient();
14155
- const key = this.keyForFile(sessionId, path14);
15587
+ const key = this.keyForFile(sessionId, path15);
14156
15588
  await client.send(
14157
15589
  new module.DeleteObjectCommand({
14158
15590
  Bucket: this.bucket,
@@ -14207,30 +15639,30 @@ var init_file_store = __esm({
14207
15639
  );
14208
15640
  }
14209
15641
  }
14210
- async getSignedUrl(sessionId, path14, expiresIn = 3600) {
15642
+ async getSignedUrl(sessionId, path15, expiresIn = 3600) {
14211
15643
  const { client, module, presigner } = await this.ensureClient();
14212
- const key = this.keyForFile(sessionId, path14);
15644
+ const key = this.keyForFile(sessionId, path15);
14213
15645
  const command = new module.GetObjectCommand({
14214
15646
  Bucket: this.bucket,
14215
15647
  Key: key
14216
15648
  });
14217
15649
  return presigner.getSignedUrl(client, command, { expiresIn });
14218
15650
  }
14219
- async getUploadUrl(sessionId, path14, expiresIn = 3600) {
15651
+ async getUploadUrl(sessionId, path15, expiresIn = 3600) {
14220
15652
  const { client, module, presigner } = await this.ensureClient();
14221
- const key = this.keyForFile(sessionId, path14);
15653
+ const key = this.keyForFile(sessionId, path15);
14222
15654
  const command = new module.PutObjectCommand({
14223
15655
  Bucket: this.bucket,
14224
15656
  Key: key,
14225
- ContentType: this.getMimeType(path14)
15657
+ ContentType: this.getMimeType(path15)
14226
15658
  });
14227
15659
  return presigner.getSignedUrl(client, command, { expiresIn });
14228
15660
  }
14229
15661
  /**
14230
15662
  * Get MIME type based on file extension
14231
15663
  */
14232
- getMimeType(path14) {
14233
- const ext = path14.split(".").pop()?.toLowerCase();
15664
+ getMimeType(path15) {
15665
+ const ext = path15.split(".").pop()?.toLowerCase();
14234
15666
  const mimeTypes = {
14235
15667
  // Text
14236
15668
  txt: "text/plain",
@@ -14285,8 +15717,8 @@ var init_local_sandbox = __esm({
14285
15717
  this.defaultRepoRoot = config.defaultRepoRoot ?? `${this.workingDirectory}/repo`;
14286
15718
  this.env = { ...process.env, ...config.env };
14287
15719
  this.timeout = config.timeout ?? 3e4;
14288
- if (!fs9__namespace.existsSync(this.workingDirectory)) {
14289
- fs9__namespace.mkdirSync(this.workingDirectory, { recursive: true });
15720
+ if (!fs10__namespace.existsSync(this.workingDirectory)) {
15721
+ fs10__namespace.mkdirSync(this.workingDirectory, { recursive: true });
14290
15722
  }
14291
15723
  }
14292
15724
  runCommand(command) {
@@ -14315,51 +15747,51 @@ var init_local_sandbox = __esm({
14315
15747
  }
14316
15748
  }
14317
15749
  uploadFile(localPath, remotePath) {
14318
- const dir = path3__namespace.dirname(remotePath);
14319
- if (!fs9__namespace.existsSync(dir)) {
14320
- fs9__namespace.mkdirSync(dir, { recursive: true });
15750
+ const dir = path4__namespace.dirname(remotePath);
15751
+ if (!fs10__namespace.existsSync(dir)) {
15752
+ fs10__namespace.mkdirSync(dir, { recursive: true });
14321
15753
  }
14322
- fs9__namespace.copyFileSync(localPath, remotePath);
15754
+ fs10__namespace.copyFileSync(localPath, remotePath);
14323
15755
  }
14324
15756
  downloadFile(remotePath, localPath) {
14325
- const dir = path3__namespace.dirname(localPath);
14326
- if (!fs9__namespace.existsSync(dir)) {
14327
- fs9__namespace.mkdirSync(dir, { recursive: true });
15757
+ const dir = path4__namespace.dirname(localPath);
15758
+ if (!fs10__namespace.existsSync(dir)) {
15759
+ fs10__namespace.mkdirSync(dir, { recursive: true });
14328
15760
  }
14329
- fs9__namespace.copyFileSync(remotePath, localPath);
15761
+ fs10__namespace.copyFileSync(remotePath, localPath);
14330
15762
  }
14331
15763
  uploadDirectory(localPath, remotePath, options) {
14332
15764
  const exclude = options?.exclude ?? /* @__PURE__ */ new Set();
14333
- if (!fs9__namespace.existsSync(remotePath)) {
14334
- fs9__namespace.mkdirSync(remotePath, { recursive: true });
15765
+ if (!fs10__namespace.existsSync(remotePath)) {
15766
+ fs10__namespace.mkdirSync(remotePath, { recursive: true });
14335
15767
  }
14336
15768
  const copyRecursive = (src, dest) => {
14337
- const entries = fs9__namespace.readdirSync(src, { withFileTypes: true });
15769
+ const entries = fs10__namespace.readdirSync(src, { withFileTypes: true });
14338
15770
  for (const entry of entries) {
14339
15771
  if (exclude.has(entry.name)) continue;
14340
- const srcPath = path3__namespace.join(src, entry.name);
14341
- const destPath = path3__namespace.join(dest, entry.name);
15772
+ const srcPath = path4__namespace.join(src, entry.name);
15773
+ const destPath = path4__namespace.join(dest, entry.name);
14342
15774
  if (entry.isDirectory()) {
14343
- if (!fs9__namespace.existsSync(destPath)) {
14344
- fs9__namespace.mkdirSync(destPath, { recursive: true });
15775
+ if (!fs10__namespace.existsSync(destPath)) {
15776
+ fs10__namespace.mkdirSync(destPath, { recursive: true });
14345
15777
  }
14346
15778
  copyRecursive(srcPath, destPath);
14347
15779
  } else if (entry.isFile()) {
14348
- fs9__namespace.copyFileSync(srcPath, destPath);
15780
+ fs10__namespace.copyFileSync(srcPath, destPath);
14349
15781
  }
14350
15782
  }
14351
15783
  };
14352
15784
  copyRecursive(localPath, remotePath);
14353
15785
  }
14354
15786
  readFile(remotePath) {
14355
- return fs9__namespace.readFileSync(remotePath, "utf-8");
15787
+ return fs10__namespace.readFileSync(remotePath, "utf-8");
14356
15788
  }
14357
15789
  writeFile(remotePath, content) {
14358
- const dir = path3__namespace.dirname(remotePath);
14359
- if (!fs9__namespace.existsSync(dir)) {
14360
- fs9__namespace.mkdirSync(dir, { recursive: true });
15790
+ const dir = path4__namespace.dirname(remotePath);
15791
+ if (!fs10__namespace.existsSync(dir)) {
15792
+ fs10__namespace.mkdirSync(dir, { recursive: true });
14361
15793
  }
14362
- fs9__namespace.writeFileSync(remotePath, content, "utf-8");
15794
+ fs10__namespace.writeFileSync(remotePath, content, "utf-8");
14363
15795
  }
14364
15796
  close() {
14365
15797
  }
@@ -14367,8 +15799,8 @@ var init_local_sandbox = __esm({
14367
15799
  * Clean up the working directory
14368
15800
  */
14369
15801
  cleanup() {
14370
- if (fs9__namespace.existsSync(this.workingDirectory)) {
14371
- fs9__namespace.rmSync(this.workingDirectory, { recursive: true, force: true });
15802
+ if (fs10__namespace.existsSync(this.workingDirectory)) {
15803
+ fs10__namespace.rmSync(this.workingDirectory, { recursive: true, force: true });
14372
15804
  }
14373
15805
  }
14374
15806
  };
@@ -14478,33 +15910,33 @@ var init_provider_sandbox = __esm({
14478
15910
  }
14479
15911
  async uploadFile(localPath, remotePath) {
14480
15912
  await this.initialize();
14481
- const content = fs9__namespace.readFileSync(localPath);
15913
+ const content = fs10__namespace.readFileSync(localPath);
14482
15914
  await this.provider.writeFile(this.getSandboxId(), remotePath, content);
14483
15915
  }
14484
15916
  async downloadFile(remotePath, localPath) {
14485
15917
  await this.initialize();
14486
15918
  const content = await this.provider.readFile(this.getSandboxId(), remotePath);
14487
- const dir = path3__namespace.dirname(localPath);
14488
- if (!fs9__namespace.existsSync(dir)) {
14489
- fs9__namespace.mkdirSync(dir, { recursive: true });
15919
+ const dir = path4__namespace.dirname(localPath);
15920
+ if (!fs10__namespace.existsSync(dir)) {
15921
+ fs10__namespace.mkdirSync(dir, { recursive: true });
14490
15922
  }
14491
- fs9__namespace.writeFileSync(localPath, content);
15923
+ fs10__namespace.writeFileSync(localPath, content);
14492
15924
  }
14493
15925
  async uploadDirectory(localPath, remotePath, options) {
14494
15926
  await this.initialize();
14495
15927
  const exclude = options?.exclude ?? /* @__PURE__ */ new Set();
14496
15928
  await this.runCommand(`mkdir -p '${remotePath}'`);
14497
15929
  const uploadRecursive = async (src, dest) => {
14498
- const entries = fs9__namespace.readdirSync(src, { withFileTypes: true });
15930
+ const entries = fs10__namespace.readdirSync(src, { withFileTypes: true });
14499
15931
  for (const entry of entries) {
14500
15932
  if (exclude.has(entry.name)) continue;
14501
- const srcPath = path3__namespace.join(src, entry.name);
15933
+ const srcPath = path4__namespace.join(src, entry.name);
14502
15934
  const destPath = `${dest}/${entry.name}`;
14503
15935
  if (entry.isDirectory()) {
14504
15936
  await this.runCommand(`mkdir -p '${destPath}'`);
14505
15937
  await uploadRecursive(srcPath, destPath);
14506
15938
  } else if (entry.isFile()) {
14507
- const content = fs9__namespace.readFileSync(srcPath);
15939
+ const content = fs10__namespace.readFileSync(srcPath);
14508
15940
  await this.provider.writeFile(this.getSandboxId(), destPath, content);
14509
15941
  }
14510
15942
  }
@@ -14742,16 +16174,19 @@ __export(schema_exports, {
14742
16174
  configDeploymentTriggerEnum: () => configDeploymentTriggerEnum,
14743
16175
  configDeployments: () => configDeployments,
14744
16176
  configDeploymentsRelations: () => configDeploymentsRelations,
16177
+ eventCategoryEnum: () => eventCategoryEnum,
14745
16178
  messageRoleEnum: () => messageRoleEnum,
14746
16179
  messages: () => messages,
14747
16180
  messagesRelations: () => messagesRelations,
14748
16181
  queueItemStatusEnum: () => queueItemStatusEnum,
14749
16182
  queueItems: () => queueItems,
16183
+ sessionEvents: () => sessionEvents,
16184
+ sessionEventsRelations: () => sessionEventsRelations,
14750
16185
  sessionStatusEnum: () => sessionStatusEnum,
14751
16186
  sessions: () => sessions,
14752
16187
  sessionsRelations: () => sessionsRelations
14753
16188
  });
14754
- var sessionStatusEnum, messageRoleEnum, agentStatusEnum, agentBackendEnum, queueItemStatusEnum, configDeploymentStatusEnum, configDeploymentTriggerEnum, sessions, messages, attachments, agents, queueItems, configDeployments, sessionsRelations, messagesRelations, attachmentsRelations, configDeploymentsRelations, agentsRelations;
16189
+ var sessionStatusEnum, messageRoleEnum, agentStatusEnum, agentBackendEnum, queueItemStatusEnum, configDeploymentStatusEnum, configDeploymentTriggerEnum, eventCategoryEnum, sessions, messages, attachments, agents, queueItems, configDeployments, sessionEvents, sessionsRelations, sessionEventsRelations, messagesRelations, attachmentsRelations, configDeploymentsRelations, agentsRelations;
14755
16190
  var init_schema = __esm({
14756
16191
  "src/storage-postgres/schema.ts"() {
14757
16192
  sessionStatusEnum = pgCore.pgEnum("session_status", [
@@ -14793,6 +16228,22 @@ var init_schema = __esm({
14793
16228
  "initial",
14794
16229
  "rollback"
14795
16230
  ]);
16231
+ eventCategoryEnum = pgCore.pgEnum("event_category", [
16232
+ "lifecycle",
16233
+ // session_start, session_end, turn_complete
16234
+ "content",
16235
+ // text_stream (aggregated), thinking_stream
16236
+ "tool",
16237
+ // tool_use, tool_result
16238
+ "system",
16239
+ // mcp_status, sandbox_log
16240
+ "error",
16241
+ // error events
16242
+ "file",
16243
+ // file_push, file_pull, file_sync (file sync operations)
16244
+ "input"
16245
+ // user_input (user prompts/messages)
16246
+ ]);
14796
16247
  sessions = pgCore.pgTable(
14797
16248
  "sessions",
14798
16249
  {
@@ -14948,6 +16399,32 @@ var init_schema = __esm({
14948
16399
  pgCore.index("idx_config_deployments_created").on(table.agentId, table.createdAt)
14949
16400
  ]
14950
16401
  );
16402
+ sessionEvents = pgCore.pgTable(
16403
+ "session_events",
16404
+ {
16405
+ id: pgCore.uuid("id").defaultRandom().primaryKey(),
16406
+ sessionId: pgCore.uuid("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
16407
+ eventType: pgCore.text("event_type").notNull(),
16408
+ category: eventCategoryEnum("category").notNull(),
16409
+ startedAt: pgCore.timestamp("started_at", { withTimezone: true }).notNull(),
16410
+ endedAt: pgCore.timestamp("ended_at", { withTimezone: true }),
16411
+ durationMs: pgCore.integer("duration_ms"),
16412
+ eventData: pgCore.jsonb("event_data").default({}).$type(),
16413
+ // Tool-specific fields
16414
+ toolUseId: pgCore.text("tool_use_id"),
16415
+ toolName: pgCore.text("tool_name"),
16416
+ // Aggregation fields (for text_stream events)
16417
+ isAggregated: pgCore.boolean("is_aggregated").default(false),
16418
+ aggregatedCount: pgCore.integer("aggregated_count"),
16419
+ // Ordering
16420
+ sequenceNumber: pgCore.integer("sequence_number").notNull(),
16421
+ createdAt: pgCore.timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
16422
+ },
16423
+ (table) => [
16424
+ pgCore.index("idx_session_events_session").on(table.sessionId, table.sequenceNumber),
16425
+ pgCore.index("idx_session_events_category").on(table.sessionId, table.category)
16426
+ ]
16427
+ );
14951
16428
  sessionsRelations = drizzleOrm.relations(sessions, ({ one, many }) => ({
14952
16429
  parentSession: one(sessions, {
14953
16430
  fields: [sessions.parentSessionId],
@@ -14957,7 +16434,14 @@ var init_schema = __esm({
14957
16434
  childSessions: many(sessions, {
14958
16435
  relationName: "parentSession"
14959
16436
  }),
14960
- messages: many(messages)
16437
+ messages: many(messages),
16438
+ events: many(sessionEvents)
16439
+ }));
16440
+ sessionEventsRelations = drizzleOrm.relations(sessionEvents, ({ one }) => ({
16441
+ session: one(sessions, {
16442
+ fields: [sessionEvents.sessionId],
16443
+ references: [sessions.id]
16444
+ })
14961
16445
  }));
14962
16446
  messagesRelations = drizzleOrm.relations(messages, ({ one, many }) => ({
14963
16447
  session: one(sessions, {
@@ -16496,6 +17980,92 @@ var init_storage3 = __esm({
16496
17980
  executionWebhookSecret: row.execution_webhook_secret ?? void 0
16497
17981
  };
16498
17982
  }
17983
+ rowToSessionEvent(row) {
17984
+ return {
17985
+ id: row.id,
17986
+ sessionId: row.session_id,
17987
+ eventType: row.event_type,
17988
+ category: row.category,
17989
+ startedAt: new Date(row.started_at),
17990
+ endedAt: row.ended_at ? new Date(row.ended_at) : void 0,
17991
+ durationMs: row.duration_ms ?? void 0,
17992
+ eventData: row.event_data ?? void 0,
17993
+ toolUseId: row.tool_use_id ?? void 0,
17994
+ toolName: row.tool_name ?? void 0,
17995
+ isAggregated: row.is_aggregated ?? void 0,
17996
+ aggregatedCount: row.aggregated_count ?? void 0,
17997
+ sequenceNumber: row.sequence_number,
17998
+ createdAt: new Date(row.created_at)
17999
+ };
18000
+ }
18001
+ // ============================================================================
18002
+ // Events
18003
+ // ============================================================================
18004
+ async saveEvents(sessionId, events) {
18005
+ if (events.length === 0) return [];
18006
+ const { data, error } = await this.client.from("session_events").insert(
18007
+ events.map((event) => ({
18008
+ session_id: sessionId,
18009
+ event_type: event.eventType,
18010
+ category: event.category,
18011
+ started_at: event.startedAt.toISOString(),
18012
+ ended_at: event.endedAt?.toISOString() ?? null,
18013
+ duration_ms: event.durationMs ?? null,
18014
+ event_data: event.eventData ?? {},
18015
+ tool_use_id: event.toolUseId ?? null,
18016
+ tool_name: event.toolName ?? null,
18017
+ is_aggregated: event.isAggregated ?? false,
18018
+ aggregated_count: event.aggregatedCount ?? null,
18019
+ sequence_number: event.sequenceNumber
18020
+ }))
18021
+ ).select();
18022
+ if (error) {
18023
+ throw new Error(`Failed to save events: ${error.message}`);
18024
+ }
18025
+ return data.map((row) => this.rowToSessionEvent(row));
18026
+ }
18027
+ async getSessionEvents(sessionId, options = {}) {
18028
+ const limit = options.limit ?? 100;
18029
+ const offset = options.offset ?? 0;
18030
+ let query = this.client.from("session_events").select("*", { count: "exact" }).eq("session_id", sessionId);
18031
+ if (options.category) {
18032
+ query = query.eq("category", options.category);
18033
+ }
18034
+ if (options.eventType) {
18035
+ query = query.eq("event_type", options.eventType);
18036
+ }
18037
+ if (options.filePath) {
18038
+ query = query.or(
18039
+ `event_data->>filePath.eq.${options.filePath},and(category.eq.tool,tool_name.in.(Read,Write,Edit),event_data->input->>file_path.eq.${options.filePath})`
18040
+ );
18041
+ }
18042
+ const ascending = options.order !== "desc";
18043
+ query = query.order("sequence_number", { ascending }).range(offset, offset + limit - 1);
18044
+ const { data, error, count } = await query;
18045
+ if (error) {
18046
+ throw new Error(`Failed to get session events: ${error.message}`);
18047
+ }
18048
+ const total = count ?? 0;
18049
+ return {
18050
+ items: data.map((row) => this.rowToSessionEvent(row)),
18051
+ total,
18052
+ hasMore: offset + limit < total,
18053
+ nextCursor: offset + limit < total ? String(offset + limit) : void 0
18054
+ };
18055
+ }
18056
+ async deleteSessionEvents(sessionId) {
18057
+ const { error } = await this.client.from("session_events").delete().eq("session_id", sessionId);
18058
+ if (error) {
18059
+ throw new Error(`Failed to delete session events: ${error.message}`);
18060
+ }
18061
+ }
18062
+ async getNextEventSequence(sessionId) {
18063
+ const { data, error } = await this.client.from("session_events").select("sequence_number").eq("session_id", sessionId).order("sequence_number", { ascending: false }).limit(1).single();
18064
+ if (error && error.code !== "PGRST116") {
18065
+ throw new Error(`Failed to get next event sequence: ${error.message}`);
18066
+ }
18067
+ return data ? data.sequence_number + 1 : 1;
18068
+ }
16499
18069
  };
16500
18070
  }
16501
18071
  });
@@ -16518,8 +18088,8 @@ var init_client = __esm({
16518
18088
  /**
16519
18089
  * Make an authenticated request to the Ash Cloud API
16520
18090
  */
16521
- async request(method, path14, options) {
16522
- const url = new URL(`${this.baseUrl}${path14}`);
18091
+ async request(method, path15, options) {
18092
+ const url = new URL(`${this.baseUrl}${path15}`);
16523
18093
  if (options?.query) {
16524
18094
  for (const [key, value] of Object.entries(options.query)) {
16525
18095
  if (value !== void 0) {
@@ -16587,8 +18157,8 @@ var init_client = __esm({
16587
18157
  /**
16588
18158
  * Make a streaming request (for SSE endpoints)
16589
18159
  */
16590
- async *stream(method, path14, options) {
16591
- const url = `${this.baseUrl}${path14}`;
18160
+ async *stream(method, path15, options) {
18161
+ const url = `${this.baseUrl}${path15}`;
16592
18162
  const headers = {
16593
18163
  Authorization: `Bearer ${this.apiKey}`,
16594
18164
  "Content-Type": "application/json",
@@ -17370,11 +18940,15 @@ exports.createDocumentedServer = createOpenAPIServer;
17370
18940
  exports.createE2BSandbox = createE2BSandbox;
17371
18941
  exports.createEventHandler = createEventHandler;
17372
18942
  exports.createEventMiddlewareChain = createEventMiddlewareChain;
18943
+ exports.createFileWatcher = createFileWatcher;
18944
+ exports.createFileWatcherManager = createFileWatcherManager;
17373
18945
  exports.createGCSBundleStore = createGCSBundleStore;
17374
18946
  exports.createGeminiExecutor = createGeminiExecutor;
17375
18947
  exports.createGitHubFileProvider = createGitHubFileProvider;
17376
18948
  exports.createGitRepo = createGitRepo;
17377
18949
  exports.createHarnessServer = createHarnessServer;
18950
+ exports.createInSandboxWatcher = createInSandboxWatcher;
18951
+ exports.createInSandboxWatcherManager = createInSandboxWatcherManager;
17378
18952
  exports.createLocalBundleStore = createLocalBundleStore;
17379
18953
  exports.createLocalFileProvider = createLocalFileProvider;
17380
18954
  exports.createLocalSandbox = createLocalSandbox;
@@ -17392,6 +18966,8 @@ exports.createQueueProcessor = createQueueProcessor;
17392
18966
  exports.createQueueRouter = createQueueRouter;
17393
18967
  exports.createR2BundleStore = createR2BundleStore;
17394
18968
  exports.createR2FileStore = createR2FileStore;
18969
+ exports.createRemoteFileWatcher = createRemoteFileWatcher;
18970
+ exports.createRemoteFileWatcherManager = createRemoteFileWatcherManager;
17395
18971
  exports.createS3BundleStore = createS3BundleStore;
17396
18972
  exports.createS3FileStore = createS3FileStore;
17397
18973
  exports.createSandboxFileOperations = createSandboxFileOperations;
@@ -17430,8 +19006,11 @@ exports.getActionLabel = getActionLabel;
17430
19006
  exports.getAllHeartbeatStatuses = getAllHeartbeatStatuses;
17431
19007
  exports.getApiKeyEnvVar = getApiKeyEnvVar;
17432
19008
  exports.getDefaultModel = getDefaultModel;
19009
+ exports.getFileWatcherManager = getFileWatcherManager;
17433
19010
  exports.getHeartbeatStatus = getHeartbeatStatus;
19011
+ exports.getInSandboxWatcherManager = getInSandboxWatcherManager;
17434
19012
  exports.getOrCreateSandbox = getOrCreateSandbox;
19013
+ exports.getRemoteFileWatcherManager = getRemoteFileWatcherManager;
17435
19014
  exports.getSandboxCacheStats = getSandboxCacheStats;
17436
19015
  exports.getSandboxPool = getSandboxPool;
17437
19016
  exports.getWorkspaceManager = getWorkspaceManager;