@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.js CHANGED
@@ -2,10 +2,11 @@ import { z, ZodType } from 'zod';
2
2
  import { spawn, execSync } from 'child_process';
3
3
  import { nanoid } from 'nanoid';
4
4
  import * as fs from 'fs/promises';
5
- import * as path3 from 'path';
5
+ import * as path4 from 'path';
6
6
  import { Writable } from 'stream';
7
- import { scryptSync, randomBytes, createCipheriv, createDecipheriv } from 'crypto';
8
- import * as fs9 from 'fs';
7
+ import chokidar from 'chokidar';
8
+ import { scryptSync, randomBytes, createCipheriv, createDecipheriv, createHmac } from 'crypto';
9
+ import * as fs10 from 'fs';
9
10
  import { promises } from 'fs';
10
11
  import { Hono } from 'hono';
11
12
  import { streamSSE } from 'hono/streaming';
@@ -18,7 +19,7 @@ import { mergePath } from 'hono/utils/url';
18
19
  import { HTTPException } from 'hono/http-exception';
19
20
  export { serve } from '@hono/node-server';
20
21
  import * as os from 'os';
21
- import { pgEnum, pgTable, timestamp, jsonb, integer, uuid, text, index } from 'drizzle-orm/pg-core';
22
+ import { pgEnum, pgTable, timestamp, jsonb, integer, uuid, text, index, boolean } from 'drizzle-orm/pg-core';
22
23
  import { eq, sql, and, asc, desc, isNull, or, lt, relations } from 'drizzle-orm';
23
24
  import { drizzle } from 'drizzle-orm/postgres-js';
24
25
  import postgres from 'postgres';
@@ -78,7 +79,7 @@ var init_normalized = __esm({
78
79
  "src/types/normalized.ts"() {
79
80
  }
80
81
  });
81
- var SessionStatus, AgentStatus, MessageRole, messageContentSchema, messageSchema, sessionSchema, attachmentSchema, DEFAULT_SANDBOX_PROVIDER_CONFIG, StreamEventType, QueueItemStatus;
82
+ var SessionStatus, AgentStatus, MessageRole, messageContentSchema, messageSchema, sessionSchema, attachmentSchema, DEFAULT_SANDBOX_PROVIDER_CONFIG, StreamEventType, QueueItemStatus, EventCategory;
82
83
  var init_types = __esm({
83
84
  "src/types/index.ts"() {
84
85
  init_normalized();
@@ -175,6 +176,7 @@ var init_types = __esm({
175
176
  SESSION_END: "session_end",
176
177
  SESSION_STOPPED: "session_stopped",
177
178
  SANDBOX_LOG: "sandbox_log",
179
+ MCP_STATUS: "mcp_status",
178
180
  ERROR: "error"
179
181
  };
180
182
  QueueItemStatus = {
@@ -184,6 +186,22 @@ var init_types = __esm({
184
186
  FAILED: "failed",
185
187
  CANCELLED: "cancelled"
186
188
  };
189
+ EventCategory = {
190
+ LIFECYCLE: "lifecycle",
191
+ // session_start, session_end, turn_complete
192
+ CONTENT: "content",
193
+ // text_stream (aggregated), thinking_stream
194
+ TOOL: "tool",
195
+ // tool_use, tool_result
196
+ SYSTEM: "system",
197
+ // mcp_status, sandbox_log
198
+ ERROR: "error",
199
+ // error events
200
+ FILE: "file",
201
+ // file_push, file_pull, file_sync (file sync operations)
202
+ INPUT: "input"
203
+ // user_input (user prompts/messages)
204
+ };
187
205
  }
188
206
  });
189
207
 
@@ -603,7 +621,7 @@ var init_mcp = __esm({
603
621
  * @param path Path to the server script
604
622
  * @param runtime Runtime to use (node, python, etc.)
605
623
  */
606
- custom: (path14, runtime = "node", args) => {
624
+ custom: (path15, runtime = "node", args) => {
607
625
  const commands = {
608
626
  node: "node",
609
627
  python: "python3",
@@ -611,7 +629,7 @@ var init_mcp = __esm({
611
629
  };
612
630
  return {
613
631
  command: commands[runtime] ?? "node",
614
- args: runtime === "deno" ? ["run", "-A", path14, ...args ?? []] : [path14, ...args ?? []]
632
+ args: runtime === "deno" ? ["run", "-A", path15, ...args ?? []] : [path15, ...args ?? []]
615
633
  };
616
634
  }
617
635
  };
@@ -669,8 +687,8 @@ var init_mcp = __esm({
669
687
  /**
670
688
  * Add a custom stdio MCP server
671
689
  */
672
- withCustom(name, path14, runtime, args) {
673
- return this.add(name, McpServers.custom(path14, runtime, args));
690
+ withCustom(name, path15, runtime, args) {
691
+ return this.add(name, McpServers.custom(path15, runtime, args));
674
692
  }
675
693
  /**
676
694
  * Add an HTTP MCP server with typed authentication
@@ -2992,9 +3010,9 @@ var init_attachment = __esm({
2992
3010
  throw new Error(`MIME type ${mimeType} is not allowed`);
2993
3011
  }
2994
3012
  const id = nanoid();
2995
- const ext = path3.extname(filename) || this.getExtensionForMimeType(mimeType);
3013
+ const ext = path4.extname(filename) || this.getExtensionForMimeType(mimeType);
2996
3014
  const storageName = `${id}${ext}`;
2997
- const storagePath = path3.join(this.config.basePath, storageName);
3015
+ const storagePath = path4.join(this.config.basePath, storageName);
2998
3016
  await fs.writeFile(storagePath, content);
2999
3017
  return {
3000
3018
  id,
@@ -3011,8 +3029,8 @@ var init_attachment = __esm({
3011
3029
  */
3012
3030
  async storeFromPath(messageId, sourcePath, mimeType) {
3013
3031
  const content = await fs.readFile(sourcePath);
3014
- const filename = path3.basename(sourcePath);
3015
- const detectedMimeType = mimeType ?? this.getMimeTypeForExtension(path3.extname(filename));
3032
+ const filename = path4.basename(sourcePath);
3033
+ const detectedMimeType = mimeType ?? this.getMimeTypeForExtension(path4.extname(filename));
3016
3034
  return this.storeFromBuffer(messageId, filename, content, detectedMimeType);
3017
3035
  }
3018
3036
  /**
@@ -3243,8 +3261,8 @@ async function loadConfig(configPath, options = {}) {
3243
3261
  return config;
3244
3262
  }
3245
3263
  async function loadConfigFile(filePath) {
3246
- const ext = path3.extname(filePath);
3247
- const fullPath = path3.isAbsolute(filePath) ? filePath : path3.resolve(process.cwd(), filePath);
3264
+ const ext = path4.extname(filePath);
3265
+ const fullPath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
3248
3266
  if (ext === ".json") {
3249
3267
  const content = await fs.readFile(fullPath, "utf-8");
3250
3268
  return JSON.parse(content);
@@ -3257,7 +3275,7 @@ async function loadConfigFile(filePath) {
3257
3275
  }
3258
3276
  async function findAndLoadConfig(directory) {
3259
3277
  for (const fileName of CONFIG_FILE_NAMES) {
3260
- const filePath = path3.join(directory, fileName);
3278
+ const filePath = path4.join(directory, fileName);
3261
3279
  try {
3262
3280
  await fs.access(filePath);
3263
3281
  return await loadConfigFile(filePath);
@@ -3595,8 +3613,8 @@ var init_modal = __esm({
3595
3613
  }
3596
3614
  async writeFile(_sandboxId, _path, _content) {
3597
3615
  }
3598
- async readFile(_sandboxId, path14) {
3599
- return `[Modal] File content from ${path14}`;
3616
+ async readFile(_sandboxId, path15) {
3617
+ return `[Modal] File content from ${path15}`;
3600
3618
  }
3601
3619
  /**
3602
3620
  * Modal-specific: Get GPU information
@@ -3743,12 +3761,12 @@ var init_e2b = __esm({
3743
3761
  throw new SandboxNotFoundError(this.name, sandboxId);
3744
3762
  }
3745
3763
  }
3746
- async readFile(sandboxId, path14) {
3764
+ async readFile(sandboxId, path15) {
3747
3765
  const sandbox = this.sandboxes.get(sandboxId);
3748
3766
  if (!sandbox) {
3749
3767
  throw new SandboxNotFoundError(this.name, sandboxId);
3750
3768
  }
3751
- return `[E2B] File content from ${path14}`;
3769
+ return `[E2B] File content from ${path15}`;
3752
3770
  }
3753
3771
  async listFiles(sandboxId, _path) {
3754
3772
  const sandbox = this.sandboxes.get(sandboxId);
@@ -3777,17 +3795,17 @@ var init_e2b = __esm({
3777
3795
  * E2B-specific: Upload a file from local filesystem
3778
3796
  */
3779
3797
  async uploadFile(sandboxId, localPath, remotePath) {
3780
- const fs14 = await import('fs/promises');
3781
- const content = await fs14.readFile(localPath);
3798
+ const fs15 = await import('fs/promises');
3799
+ const content = await fs15.readFile(localPath);
3782
3800
  await this.writeFile(sandboxId, remotePath, content);
3783
3801
  }
3784
3802
  /**
3785
3803
  * E2B-specific: Download a file to local filesystem
3786
3804
  */
3787
3805
  async downloadFile(sandboxId, remotePath, localPath) {
3788
- const fs14 = await import('fs/promises');
3806
+ const fs15 = await import('fs/promises');
3789
3807
  const content = await this.readFile(sandboxId, remotePath);
3790
- await fs14.writeFile(localPath, content);
3808
+ await fs15.writeFile(localPath, content);
3791
3809
  }
3792
3810
  };
3793
3811
  }
@@ -4041,7 +4059,7 @@ var init_vercel = __esm({
4041
4059
  }
4042
4060
  };
4043
4061
  }
4044
- async writeFile(sandboxId, path14, content) {
4062
+ async writeFile(sandboxId, path15, content) {
4045
4063
  const instance = this.sandboxes.get(sandboxId);
4046
4064
  if (!instance) {
4047
4065
  throw new SandboxNotFoundError(this.name, sandboxId);
@@ -4051,34 +4069,34 @@ var init_vercel = __esm({
4051
4069
  if (isBase64) {
4052
4070
  await this.executeCommand(
4053
4071
  sandboxId,
4054
- `echo "${contentStr}" | base64 -d > "${path14}"`
4072
+ `echo "${contentStr}" | base64 -d > "${path15}"`
4055
4073
  );
4056
4074
  } else {
4057
4075
  const escaped = contentStr.replace(/'/g, "'\\''");
4058
- await this.executeCommand(sandboxId, `cat > "${path14}" << 'VERCEL_EOF'
4076
+ await this.executeCommand(sandboxId, `cat > "${path15}" << 'VERCEL_EOF'
4059
4077
  ${escaped}
4060
4078
  VERCEL_EOF`);
4061
4079
  }
4062
4080
  }
4063
- async readFile(sandboxId, path14) {
4081
+ async readFile(sandboxId, path15) {
4064
4082
  const instance = this.sandboxes.get(sandboxId);
4065
4083
  if (!instance) {
4066
4084
  throw new SandboxNotFoundError(this.name, sandboxId);
4067
4085
  }
4068
- const result = await this.executeCommand(sandboxId, `cat "${path14}"`);
4086
+ const result = await this.executeCommand(sandboxId, `cat "${path15}"`);
4069
4087
  if (result.exitCode !== 0) {
4070
- throw new Error(`Failed to read file ${path14}: ${result.stderr}`);
4088
+ throw new Error(`Failed to read file ${path15}: ${result.stderr}`);
4071
4089
  }
4072
4090
  return result.stdout;
4073
4091
  }
4074
- async listFiles(sandboxId, path14) {
4092
+ async listFiles(sandboxId, path15) {
4075
4093
  const instance = this.sandboxes.get(sandboxId);
4076
4094
  if (!instance) {
4077
4095
  throw new SandboxNotFoundError(this.name, sandboxId);
4078
4096
  }
4079
4097
  const result = await this.executeCommand(
4080
4098
  sandboxId,
4081
- `ls -la "${path14}" 2>/dev/null || echo ""`
4099
+ `ls -la "${path15}" 2>/dev/null || echo ""`
4082
4100
  );
4083
4101
  if (result.exitCode !== 0 || !result.stdout.trim()) {
4084
4102
  return [];
@@ -4093,7 +4111,7 @@ VERCEL_EOF`);
4093
4111
  const [, type, , size, , name] = match;
4094
4112
  if (name && name !== "." && name !== ".." && size) {
4095
4113
  files.push({
4096
- path: `${path14}/${name}`.replace(/\/+/g, "/"),
4114
+ path: `${path15}/${name}`.replace(/\/+/g, "/"),
4097
4115
  name,
4098
4116
  size: parseInt(size, 10),
4099
4117
  isDirectory: type === "d"
@@ -4103,12 +4121,12 @@ VERCEL_EOF`);
4103
4121
  }
4104
4122
  return files;
4105
4123
  }
4106
- async deleteFile(sandboxId, path14) {
4124
+ async deleteFile(sandboxId, path15) {
4107
4125
  const instance = this.sandboxes.get(sandboxId);
4108
4126
  if (!instance) {
4109
4127
  throw new SandboxNotFoundError(this.name, sandboxId);
4110
4128
  }
4111
- await this.executeCommand(sandboxId, `rm -rf "${path14}"`);
4129
+ await this.executeCommand(sandboxId, `rm -rf "${path15}"`);
4112
4130
  }
4113
4131
  async getLogs(sandboxId, _options) {
4114
4132
  const instance = this.sandboxes.get(sandboxId);
@@ -5241,7 +5259,7 @@ function isSandboxRunning(sessionId) {
5241
5259
  const cached = sandboxCache.get(sessionId);
5242
5260
  return cached !== void 0 && !cached.isExpired;
5243
5261
  }
5244
- async function writeFileToSandbox(sessionId, path14, content) {
5262
+ async function writeFileToSandbox(sessionId, path15, content) {
5245
5263
  const cached = sandboxCache.get(sessionId);
5246
5264
  if (!cached) {
5247
5265
  return { success: false, error: "No active sandbox for session" };
@@ -5251,14 +5269,14 @@ async function writeFileToSandbox(sessionId, path14, content) {
5251
5269
  }
5252
5270
  try {
5253
5271
  const sandbox = cached.sandbox;
5254
- const dir = path14.substring(0, path14.lastIndexOf("/"));
5272
+ const dir = path15.substring(0, path15.lastIndexOf("/"));
5255
5273
  if (dir) {
5256
5274
  await sandbox.runCommand({ cmd: "mkdir", args: ["-p", dir] });
5257
5275
  }
5258
5276
  const base64Content = content.toString("base64");
5259
5277
  const result = await sandbox.runCommand({
5260
5278
  cmd: "bash",
5261
- args: ["-c", `echo '${base64Content}' | base64 -d > '${path14}'`]
5279
+ args: ["-c", `echo '${base64Content}' | base64 -d > '${path15}'`]
5262
5280
  });
5263
5281
  if (result.exitCode !== 0) {
5264
5282
  const stderr = await result.stderr();
@@ -5273,7 +5291,7 @@ async function writeFileToSandbox(sessionId, path14, content) {
5273
5291
  };
5274
5292
  }
5275
5293
  }
5276
- async function readFileFromSandbox(sessionId, path14) {
5294
+ async function readFileFromSandbox(sessionId, path15) {
5277
5295
  const cached = sandboxCache.get(sessionId);
5278
5296
  if (!cached) {
5279
5297
  return { success: false, error: "No active sandbox for session" };
@@ -5285,14 +5303,14 @@ async function readFileFromSandbox(sessionId, path14) {
5285
5303
  const sandbox = cached.sandbox;
5286
5304
  const checkResult = await sandbox.runCommand({
5287
5305
  cmd: "test",
5288
- args: ["-f", path14]
5306
+ args: ["-f", path15]
5289
5307
  });
5290
5308
  if (checkResult.exitCode !== 0) {
5291
5309
  return { success: false, error: "File not found" };
5292
5310
  }
5293
5311
  const result = await sandbox.runCommand({
5294
5312
  cmd: "base64",
5295
- args: [path14]
5313
+ args: [path15]
5296
5314
  });
5297
5315
  if (result.exitCode !== 0) {
5298
5316
  const stderr = await result.stderr();
@@ -5309,7 +5327,7 @@ async function readFileFromSandbox(sessionId, path14) {
5309
5327
  };
5310
5328
  }
5311
5329
  }
5312
- async function listFilesInSandbox(sessionId, path14) {
5330
+ async function listFilesInSandbox(sessionId, path15) {
5313
5331
  const cached = sandboxCache.get(sessionId);
5314
5332
  if (!cached) {
5315
5333
  return { success: false, error: "No active sandbox for session" };
@@ -5321,14 +5339,14 @@ async function listFilesInSandbox(sessionId, path14) {
5321
5339
  const sandbox = cached.sandbox;
5322
5340
  const checkResult = await sandbox.runCommand({
5323
5341
  cmd: "test",
5324
- args: ["-d", path14]
5342
+ args: ["-d", path15]
5325
5343
  });
5326
5344
  if (checkResult.exitCode !== 0) {
5327
5345
  return { success: true, files: [] };
5328
5346
  }
5329
5347
  const result = await sandbox.runCommand({
5330
5348
  cmd: "find",
5331
- args: [path14, "-type", "f", "-printf", "%P\\n"]
5349
+ args: [path15, "-type", "f", "-printf", "%P\\n"]
5332
5350
  });
5333
5351
  if (result.exitCode !== 0) {
5334
5352
  const stderr = await result.stderr();
@@ -5776,8 +5794,516 @@ var init_vercel_sandbox_executor = __esm({
5776
5794
  heartbeatListeners = /* @__PURE__ */ new Set();
5777
5795
  }
5778
5796
  });
5779
-
5780
- // src/runtime/sandbox-file-sync.ts
5797
+ function createFileWatcher(options) {
5798
+ return new SandboxFileWatcher(options);
5799
+ }
5800
+ function getFileWatcherManager() {
5801
+ if (!globalWatcherManager) {
5802
+ globalWatcherManager = new FileWatcherManager();
5803
+ }
5804
+ return globalWatcherManager;
5805
+ }
5806
+ function createFileWatcherManager() {
5807
+ return new FileWatcherManager();
5808
+ }
5809
+ function createRemoteFileWatcher(options) {
5810
+ return new RemoteSandboxFileWatcher(options);
5811
+ }
5812
+ function getRemoteFileWatcherManager() {
5813
+ if (!globalRemoteWatcherManager) {
5814
+ globalRemoteWatcherManager = new RemoteFileWatcherManager();
5815
+ }
5816
+ return globalRemoteWatcherManager;
5817
+ }
5818
+ function createRemoteFileWatcherManager() {
5819
+ return new RemoteFileWatcherManager();
5820
+ }
5821
+ var SandboxFileWatcher, FileWatcherManager, globalWatcherManager, RemoteSandboxFileWatcher, RemoteFileWatcherManager, globalRemoteWatcherManager;
5822
+ var init_sandbox_file_watcher = __esm({
5823
+ "src/runtime/sandbox-file-watcher.ts"() {
5824
+ SandboxFileWatcher = class {
5825
+ sessionId;
5826
+ watchPath;
5827
+ debounceMs;
5828
+ patterns;
5829
+ ignored;
5830
+ emitInitialEvents;
5831
+ followSymlinks;
5832
+ usePolling;
5833
+ pollInterval;
5834
+ watcher = null;
5835
+ pendingEvents = /* @__PURE__ */ new Map();
5836
+ subscribers = /* @__PURE__ */ new Set();
5837
+ isWatching = false;
5838
+ startedAt;
5839
+ watchedFileCount = 0;
5840
+ onError;
5841
+ onReady;
5842
+ constructor(options) {
5843
+ this.sessionId = options.sessionId;
5844
+ this.watchPath = options.watchPath;
5845
+ this.debounceMs = options.debounceMs ?? 300;
5846
+ this.patterns = options.patterns ?? ["**/*"];
5847
+ this.ignored = options.ignored ?? ["**/node_modules/**", "**/.git/**", "**/*.log"];
5848
+ this.emitInitialEvents = options.emitInitialEvents ?? false;
5849
+ this.followSymlinks = options.followSymlinks ?? false;
5850
+ this.usePolling = options.usePolling ?? false;
5851
+ this.pollInterval = options.pollInterval ?? 100;
5852
+ this.onError = options.onError;
5853
+ this.onReady = options.onReady;
5854
+ if (options.onFileChange) {
5855
+ this.subscribers.add(options.onFileChange);
5856
+ }
5857
+ }
5858
+ /**
5859
+ * Start watching the directory for changes
5860
+ */
5861
+ async start() {
5862
+ if (this.isWatching) {
5863
+ console.warn(`[FILE_WATCHER] Already watching ${this.watchPath}`);
5864
+ return;
5865
+ }
5866
+ try {
5867
+ const stat2 = await fs.stat(this.watchPath);
5868
+ if (!stat2.isDirectory()) {
5869
+ throw new Error(`Watch path is not a directory: ${this.watchPath}`);
5870
+ }
5871
+ } catch (error) {
5872
+ if (error.code === "ENOENT") {
5873
+ throw new Error(`Watch path does not exist: ${this.watchPath}`);
5874
+ }
5875
+ throw error;
5876
+ }
5877
+ const watchPaths = this.patterns.map(
5878
+ (pattern) => path4.join(this.watchPath, pattern)
5879
+ );
5880
+ this.watcher = chokidar.watch(watchPaths, {
5881
+ ignored: this.ignored,
5882
+ persistent: true,
5883
+ ignoreInitial: !this.emitInitialEvents,
5884
+ followSymlinks: this.followSymlinks,
5885
+ usePolling: this.usePolling,
5886
+ interval: this.pollInterval,
5887
+ awaitWriteFinish: {
5888
+ stabilityThreshold: this.debounceMs,
5889
+ pollInterval: 100
5890
+ }
5891
+ });
5892
+ 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) => {
5893
+ console.error(`[FILE_WATCHER] Error watching ${this.watchPath}:`, error);
5894
+ if (this.onError) {
5895
+ this.onError(error);
5896
+ }
5897
+ }).on("ready", () => {
5898
+ console.log(`[FILE_WATCHER] Ready to watch ${this.watchPath}`);
5899
+ this.isWatching = true;
5900
+ this.startedAt = /* @__PURE__ */ new Date();
5901
+ if (this.onReady) {
5902
+ this.onReady();
5903
+ }
5904
+ });
5905
+ this.watcher.on("add", () => this.watchedFileCount++);
5906
+ this.watcher.on("unlink", () => this.watchedFileCount--);
5907
+ }
5908
+ /**
5909
+ * Stop watching and cleanup resources
5910
+ */
5911
+ async stop() {
5912
+ if (!this.isWatching || !this.watcher) {
5913
+ return;
5914
+ }
5915
+ for (const pending of this.pendingEvents.values()) {
5916
+ clearTimeout(pending.timer);
5917
+ }
5918
+ this.pendingEvents.clear();
5919
+ await this.watcher.close();
5920
+ this.watcher = null;
5921
+ this.isWatching = false;
5922
+ this.watchedFileCount = 0;
5923
+ console.log(`[FILE_WATCHER] Stopped watching ${this.watchPath}`);
5924
+ }
5925
+ /**
5926
+ * Subscribe to file change events
5927
+ * @returns Unsubscribe function
5928
+ */
5929
+ subscribe(callback) {
5930
+ this.subscribers.add(callback);
5931
+ return () => this.subscribers.delete(callback);
5932
+ }
5933
+ /**
5934
+ * Remove a subscriber
5935
+ */
5936
+ unsubscribe(callback) {
5937
+ this.subscribers.delete(callback);
5938
+ }
5939
+ /**
5940
+ * Get the current status of the watcher
5941
+ */
5942
+ getStatus() {
5943
+ return {
5944
+ sessionId: this.sessionId,
5945
+ watchPath: this.watchPath,
5946
+ isWatching: this.isWatching,
5947
+ watchedFileCount: this.watchedFileCount,
5948
+ pendingEventCount: this.pendingEvents.size,
5949
+ startedAt: this.startedAt
5950
+ };
5951
+ }
5952
+ /**
5953
+ * Check if the watcher is currently active
5954
+ */
5955
+ isActive() {
5956
+ return this.isWatching;
5957
+ }
5958
+ /**
5959
+ * Handle a file system event with debouncing
5960
+ */
5961
+ handleFileEvent(type, absolutePath) {
5962
+ const relativePath = path4.relative(this.watchPath, absolutePath);
5963
+ const existing = this.pendingEvents.get(absolutePath);
5964
+ if (existing) {
5965
+ clearTimeout(existing.timer);
5966
+ }
5967
+ const timer = setTimeout(async () => {
5968
+ this.pendingEvents.delete(absolutePath);
5969
+ await this.emitEvent(type, absolutePath, relativePath);
5970
+ }, this.debounceMs);
5971
+ this.pendingEvents.set(absolutePath, {
5972
+ type,
5973
+ absolutePath,
5974
+ relativePath,
5975
+ timer
5976
+ });
5977
+ }
5978
+ /**
5979
+ * Emit a file change event to all subscribers
5980
+ */
5981
+ async emitEvent(type, absolutePath, relativePath) {
5982
+ let fileSize;
5983
+ if (type === "add" || type === "change") {
5984
+ try {
5985
+ const stat2 = await fs.stat(absolutePath);
5986
+ fileSize = stat2.size;
5987
+ } catch {
5988
+ }
5989
+ }
5990
+ const event = {
5991
+ type,
5992
+ relativePath,
5993
+ absolutePath,
5994
+ sessionId: this.sessionId,
5995
+ fileSize,
5996
+ timestamp: /* @__PURE__ */ new Date()
5997
+ };
5998
+ for (const callback of this.subscribers) {
5999
+ try {
6000
+ await callback(event);
6001
+ } catch (error) {
6002
+ console.error("[FILE_WATCHER] Error in subscriber callback:", error);
6003
+ }
6004
+ }
6005
+ }
6006
+ };
6007
+ FileWatcherManager = class {
6008
+ watchers = /* @__PURE__ */ new Map();
6009
+ /**
6010
+ * Start watching a session's sandbox directory
6011
+ */
6012
+ async startWatching(options) {
6013
+ const { sessionId } = options;
6014
+ const existing = this.watchers.get(sessionId);
6015
+ if (existing) {
6016
+ await existing.stop();
6017
+ }
6018
+ const watcher = new SandboxFileWatcher(options);
6019
+ await watcher.start();
6020
+ this.watchers.set(sessionId, watcher);
6021
+ return watcher;
6022
+ }
6023
+ /**
6024
+ * Stop watching a session's sandbox directory
6025
+ */
6026
+ async stopWatching(sessionId) {
6027
+ const watcher = this.watchers.get(sessionId);
6028
+ if (watcher) {
6029
+ await watcher.stop();
6030
+ this.watchers.delete(sessionId);
6031
+ }
6032
+ }
6033
+ /**
6034
+ * Get a watcher for a session
6035
+ */
6036
+ getWatcher(sessionId) {
6037
+ return this.watchers.get(sessionId);
6038
+ }
6039
+ /**
6040
+ * Check if a session is being watched
6041
+ */
6042
+ isWatching(sessionId) {
6043
+ const watcher = this.watchers.get(sessionId);
6044
+ return watcher?.isActive() ?? false;
6045
+ }
6046
+ /**
6047
+ * Get status of all watchers
6048
+ */
6049
+ getAllStatuses() {
6050
+ return Array.from(this.watchers.values()).map((w) => w.getStatus());
6051
+ }
6052
+ /**
6053
+ * Stop all watchers and cleanup
6054
+ */
6055
+ async stopAll() {
6056
+ const promises = Array.from(this.watchers.values()).map((w) => w.stop());
6057
+ await Promise.all(promises);
6058
+ this.watchers.clear();
6059
+ }
6060
+ };
6061
+ globalWatcherManager = null;
6062
+ RemoteSandboxFileWatcher = class {
6063
+ sessionId;
6064
+ sandboxOps;
6065
+ basePath;
6066
+ pollIntervalMs;
6067
+ ignored;
6068
+ pollTimer = null;
6069
+ previousFiles = /* @__PURE__ */ new Map();
6070
+ subscribers = /* @__PURE__ */ new Set();
6071
+ isWatching = false;
6072
+ startedAt;
6073
+ lastPollAt;
6074
+ pollCount = 0;
6075
+ onError;
6076
+ constructor(options) {
6077
+ this.sessionId = options.sessionId;
6078
+ this.sandboxOps = options.sandboxOps;
6079
+ this.basePath = options.basePath;
6080
+ this.pollIntervalMs = options.pollIntervalMs ?? 2e3;
6081
+ this.ignored = options.ignored ?? ["**/node_modules/**", "**/.git/**"];
6082
+ this.onError = options.onError;
6083
+ if (options.onFileChange) {
6084
+ this.subscribers.add(options.onFileChange);
6085
+ }
6086
+ }
6087
+ /**
6088
+ * Start polling for file changes
6089
+ */
6090
+ async start() {
6091
+ if (this.isWatching) {
6092
+ console.warn(`[REMOTE_WATCHER] Already watching session ${this.sessionId}`);
6093
+ return;
6094
+ }
6095
+ if (!this.sandboxOps.isSandboxRunning(this.sessionId)) {
6096
+ throw new Error(`Sandbox is not running for session ${this.sessionId}`);
6097
+ }
6098
+ await this.scan(true);
6099
+ this.pollTimer = setInterval(async () => {
6100
+ try {
6101
+ await this.scan(false);
6102
+ } catch (error) {
6103
+ console.error(`[REMOTE_WATCHER] Poll error for session ${this.sessionId}:`, error);
6104
+ if (this.onError && error instanceof Error) {
6105
+ this.onError(error);
6106
+ }
6107
+ }
6108
+ }, this.pollIntervalMs);
6109
+ this.isWatching = true;
6110
+ this.startedAt = /* @__PURE__ */ new Date();
6111
+ console.log(`[REMOTE_WATCHER] Started watching session ${this.sessionId} at ${this.basePath}`);
6112
+ }
6113
+ /**
6114
+ * Stop polling and cleanup
6115
+ */
6116
+ async stop() {
6117
+ if (!this.isWatching) {
6118
+ return;
6119
+ }
6120
+ if (this.pollTimer) {
6121
+ clearInterval(this.pollTimer);
6122
+ this.pollTimer = null;
6123
+ }
6124
+ this.isWatching = false;
6125
+ this.previousFiles.clear();
6126
+ console.log(`[REMOTE_WATCHER] Stopped watching session ${this.sessionId}`);
6127
+ }
6128
+ /**
6129
+ * Subscribe to file change events
6130
+ */
6131
+ subscribe(callback) {
6132
+ this.subscribers.add(callback);
6133
+ return () => this.subscribers.delete(callback);
6134
+ }
6135
+ /**
6136
+ * Remove a subscriber
6137
+ */
6138
+ unsubscribe(callback) {
6139
+ this.subscribers.delete(callback);
6140
+ }
6141
+ /**
6142
+ * Check if watcher is active
6143
+ */
6144
+ isActive() {
6145
+ return this.isWatching;
6146
+ }
6147
+ /**
6148
+ * Get watcher status
6149
+ */
6150
+ getStatus() {
6151
+ return {
6152
+ sessionId: this.sessionId,
6153
+ watchPath: this.basePath,
6154
+ isWatching: this.isWatching,
6155
+ watchedFileCount: this.previousFiles.size,
6156
+ pendingEventCount: 0,
6157
+ startedAt: this.startedAt,
6158
+ lastPollAt: this.lastPollAt,
6159
+ pollCount: this.pollCount
6160
+ };
6161
+ }
6162
+ /**
6163
+ * Force an immediate scan (useful for testing or manual refresh)
6164
+ */
6165
+ async forceScan() {
6166
+ await this.scan(false);
6167
+ }
6168
+ /**
6169
+ * Scan the sandbox for file changes
6170
+ */
6171
+ async scan(isInitial) {
6172
+ if (!this.sandboxOps.isSandboxRunning(this.sessionId)) {
6173
+ console.warn(`[REMOTE_WATCHER] Sandbox stopped for session ${this.sessionId}`);
6174
+ await this.stop();
6175
+ return;
6176
+ }
6177
+ const listResult = await this.sandboxOps.listFiles(this.sessionId, this.basePath);
6178
+ if (!listResult.success || !listResult.files) {
6179
+ throw new Error(listResult.error ?? "Failed to list files in sandbox");
6180
+ }
6181
+ const currentFiles = /* @__PURE__ */ new Map();
6182
+ for (const filePath of listResult.files) {
6183
+ if (this.shouldIgnore(filePath)) {
6184
+ continue;
6185
+ }
6186
+ currentFiles.set(filePath, { path: filePath });
6187
+ }
6188
+ this.lastPollAt = /* @__PURE__ */ new Date();
6189
+ this.pollCount++;
6190
+ if (isInitial) {
6191
+ this.previousFiles = currentFiles;
6192
+ return;
6193
+ }
6194
+ const changes = [];
6195
+ for (const [filePath, info] of currentFiles) {
6196
+ const previous = this.previousFiles.get(filePath);
6197
+ if (!previous) {
6198
+ changes.push({
6199
+ type: "add",
6200
+ relativePath: filePath,
6201
+ absolutePath: `${this.basePath}/${filePath}`,
6202
+ sessionId: this.sessionId,
6203
+ fileSize: info.size,
6204
+ timestamp: /* @__PURE__ */ new Date()
6205
+ });
6206
+ }
6207
+ }
6208
+ for (const [filePath] of this.previousFiles) {
6209
+ if (!currentFiles.has(filePath)) {
6210
+ changes.push({
6211
+ type: "unlink",
6212
+ relativePath: filePath,
6213
+ absolutePath: `${this.basePath}/${filePath}`,
6214
+ sessionId: this.sessionId,
6215
+ timestamp: /* @__PURE__ */ new Date()
6216
+ });
6217
+ }
6218
+ }
6219
+ this.previousFiles = currentFiles;
6220
+ for (const event of changes) {
6221
+ await this.emitEvent(event);
6222
+ }
6223
+ }
6224
+ /**
6225
+ * Check if a path should be ignored
6226
+ */
6227
+ shouldIgnore(filePath) {
6228
+ for (const pattern of this.ignored) {
6229
+ if (this.matchesGlob(filePath, pattern)) {
6230
+ return true;
6231
+ }
6232
+ }
6233
+ return false;
6234
+ }
6235
+ /**
6236
+ * Simple glob matching (supports ** and *)
6237
+ */
6238
+ matchesGlob(filePath, pattern) {
6239
+ const regexPattern = pattern.replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/{{GLOBSTAR}}/g, ".*").replace(/\//g, "\\/");
6240
+ const regex = new RegExp(`^${regexPattern}$`);
6241
+ return regex.test(filePath);
6242
+ }
6243
+ /**
6244
+ * Emit a file change event to all subscribers
6245
+ */
6246
+ async emitEvent(event) {
6247
+ for (const callback of this.subscribers) {
6248
+ try {
6249
+ await callback(event);
6250
+ } catch (error) {
6251
+ console.error("[REMOTE_WATCHER] Error in subscriber callback:", error);
6252
+ }
6253
+ }
6254
+ }
6255
+ };
6256
+ RemoteFileWatcherManager = class {
6257
+ watchers = /* @__PURE__ */ new Map();
6258
+ /**
6259
+ * Start watching a session's sandbox
6260
+ */
6261
+ async startWatching(options) {
6262
+ const { sessionId } = options;
6263
+ const existing = this.watchers.get(sessionId);
6264
+ if (existing) {
6265
+ await existing.stop();
6266
+ }
6267
+ const watcher = new RemoteSandboxFileWatcher(options);
6268
+ await watcher.start();
6269
+ this.watchers.set(sessionId, watcher);
6270
+ return watcher;
6271
+ }
6272
+ /**
6273
+ * Stop watching a session
6274
+ */
6275
+ async stopWatching(sessionId) {
6276
+ const watcher = this.watchers.get(sessionId);
6277
+ if (watcher) {
6278
+ await watcher.stop();
6279
+ this.watchers.delete(sessionId);
6280
+ }
6281
+ }
6282
+ /**
6283
+ * Get a watcher for a session
6284
+ */
6285
+ getWatcher(sessionId) {
6286
+ return this.watchers.get(sessionId);
6287
+ }
6288
+ /**
6289
+ * Check if a session is being watched
6290
+ */
6291
+ isWatching(sessionId) {
6292
+ const watcher = this.watchers.get(sessionId);
6293
+ return watcher?.isActive() ?? false;
6294
+ }
6295
+ /**
6296
+ * Stop all watchers
6297
+ */
6298
+ async stopAll() {
6299
+ const promises = Array.from(this.watchers.values()).map((w) => w.stop());
6300
+ await Promise.all(promises);
6301
+ this.watchers.clear();
6302
+ }
6303
+ };
6304
+ globalRemoteWatcherManager = null;
6305
+ }
6306
+ });
5781
6307
  function extractErrorMessage(error) {
5782
6308
  if (error === null || error === void 0) {
5783
6309
  return "Unknown error (null/undefined)";
@@ -5823,13 +6349,256 @@ function createSandboxFileSync(options) {
5823
6349
  var SandboxFileSync;
5824
6350
  var init_sandbox_file_sync = __esm({
5825
6351
  "src/runtime/sandbox-file-sync.ts"() {
6352
+ init_sandbox_file_watcher();
6353
+ init_types();
5826
6354
  SandboxFileSync = class {
5827
6355
  fileStore;
5828
6356
  sandboxBasePath;
6357
+ defaultWatchOptions;
5829
6358
  sandboxOps = null;
6359
+ onFileEvent;
6360
+ eventStorage;
6361
+ webhookConfig;
6362
+ // Watcher management
6363
+ remoteWatchers = /* @__PURE__ */ new Map();
6364
+ localWatchers = /* @__PURE__ */ new Map();
6365
+ fileChangeSubscribers = /* @__PURE__ */ new Set();
6366
+ // Sequence number cache per session (for event storage)
6367
+ sequenceNumbers = /* @__PURE__ */ new Map();
5830
6368
  constructor(options) {
5831
6369
  this.fileStore = options.fileStore;
5832
6370
  this.sandboxBasePath = options.sandboxBasePath ?? ".claude/files";
6371
+ this.onFileEvent = options.onFileEvent;
6372
+ this.defaultWatchOptions = options.watchOptions ?? {};
6373
+ this.eventStorage = options.eventStorage;
6374
+ this.webhookConfig = options.webhook;
6375
+ }
6376
+ /**
6377
+ * Set the file event callback
6378
+ * This can be used to set or update the callback after construction
6379
+ */
6380
+ setFileEventCallback(callback) {
6381
+ this.onFileEvent = callback;
6382
+ }
6383
+ /**
6384
+ * Set the event storage for persisting file events to the timeline
6385
+ * This can be used to set or update the storage after construction
6386
+ */
6387
+ setEventStorage(storage) {
6388
+ this.eventStorage = storage;
6389
+ }
6390
+ /**
6391
+ * Set webhook configuration for external notifications
6392
+ * This can be used to set or update the webhook after construction
6393
+ */
6394
+ setWebhook(config) {
6395
+ this.webhookConfig = config;
6396
+ }
6397
+ /**
6398
+ * Remove webhook configuration
6399
+ */
6400
+ removeWebhook() {
6401
+ this.webhookConfig = void 0;
6402
+ }
6403
+ /**
6404
+ * Send a webhook notification
6405
+ * @param payload - The webhook payload to send
6406
+ */
6407
+ async sendWebhook(payload) {
6408
+ if (!this.webhookConfig) return;
6409
+ const config = this.webhookConfig;
6410
+ const eventType = payload.fileSyncEvent?.operation ?? "file_change";
6411
+ if (config.events && config.events.length > 0) {
6412
+ const shouldSend = config.events.some(
6413
+ (e) => e === eventType || e === "file_change" && payload.event === "file_change"
6414
+ );
6415
+ if (!shouldSend) return;
6416
+ }
6417
+ const body = JSON.stringify(payload);
6418
+ const timeoutMs = config.timeoutMs ?? 1e4;
6419
+ const retries = config.retries ?? 3;
6420
+ const isAsync = config.async !== false;
6421
+ const headers = {
6422
+ "Content-Type": "application/json",
6423
+ "User-Agent": "Ash-FileSync/1.0",
6424
+ ...config.headers
6425
+ };
6426
+ if (config.secret) {
6427
+ const signature = createHmac("sha256", config.secret).update(body).digest("hex");
6428
+ headers["X-Ash-Signature"] = `sha256=${signature}`;
6429
+ headers["X-Ash-Timestamp"] = payload.timestamp;
6430
+ }
6431
+ const sendWithRetry = async (attempt) => {
6432
+ try {
6433
+ const controller = new AbortController();
6434
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
6435
+ const response = await fetch(config.url, {
6436
+ method: "POST",
6437
+ headers,
6438
+ body,
6439
+ signal: controller.signal
6440
+ });
6441
+ clearTimeout(timeoutId);
6442
+ if (!response.ok) {
6443
+ throw new Error(`Webhook returned ${response.status}: ${response.statusText}`);
6444
+ }
6445
+ console.log(`[FILE_SYNC] Webhook sent successfully to ${config.url}`);
6446
+ } catch (error) {
6447
+ const errorMsg = error instanceof Error ? error.message : String(error);
6448
+ if (attempt < retries) {
6449
+ const delay = Math.pow(2, attempt) * 1e3;
6450
+ console.warn(`[FILE_SYNC] Webhook failed (attempt ${attempt + 1}/${retries}), retrying in ${delay}ms: ${errorMsg}`);
6451
+ await new Promise((resolve3) => setTimeout(resolve3, delay));
6452
+ return sendWithRetry(attempt + 1);
6453
+ }
6454
+ console.error(`[FILE_SYNC] Webhook failed after ${retries} attempts: ${errorMsg}`);
6455
+ throw error;
6456
+ }
6457
+ };
6458
+ if (isAsync) {
6459
+ sendWithRetry(0).catch((error) => {
6460
+ console.error("[FILE_SYNC] Async webhook failed:", error);
6461
+ });
6462
+ } else {
6463
+ await sendWithRetry(0);
6464
+ }
6465
+ }
6466
+ /**
6467
+ * Send a file sync event webhook
6468
+ */
6469
+ async sendFileSyncWebhook(sessionId, event) {
6470
+ const payload = {
6471
+ event: "file_sync",
6472
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6473
+ sessionId,
6474
+ metadata: this.webhookConfig?.metadata,
6475
+ fileSyncEvent: event
6476
+ };
6477
+ await this.sendWebhook(payload);
6478
+ }
6479
+ /**
6480
+ * Send a file change event webhook (from watcher)
6481
+ */
6482
+ async sendFileChangeWebhook(event) {
6483
+ const payload = {
6484
+ event: "file_change",
6485
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6486
+ sessionId: event.sessionId,
6487
+ metadata: this.webhookConfig?.metadata,
6488
+ fileChangeEvent: event
6489
+ };
6490
+ await this.sendWebhook(payload);
6491
+ }
6492
+ /**
6493
+ * Get the next sequence number for a session
6494
+ */
6495
+ async getNextSequenceNumber(sessionId) {
6496
+ const cached = this.sequenceNumbers.get(sessionId);
6497
+ if (cached !== void 0) {
6498
+ const next = cached + 1;
6499
+ this.sequenceNumbers.set(sessionId, next);
6500
+ return next;
6501
+ }
6502
+ if (this.eventStorage) {
6503
+ try {
6504
+ const next = await this.eventStorage.getNextEventSequence(sessionId);
6505
+ this.sequenceNumbers.set(sessionId, next);
6506
+ return next;
6507
+ } catch (error) {
6508
+ console.warn("[FILE_SYNC] Failed to get next sequence number:", error);
6509
+ }
6510
+ }
6511
+ this.sequenceNumbers.set(sessionId, 1);
6512
+ return 1;
6513
+ }
6514
+ /**
6515
+ * Compute a unified diff between two text contents
6516
+ */
6517
+ computeDiff(previousContent, newContent) {
6518
+ if (!previousContent && !newContent) return void 0;
6519
+ if (!previousContent || !newContent) return void 0;
6520
+ try {
6521
+ const MAX_DIFF_SIZE = 100 * 1024;
6522
+ if (previousContent.length > MAX_DIFF_SIZE || newContent.length > MAX_DIFF_SIZE) {
6523
+ return void 0;
6524
+ }
6525
+ const prevText = previousContent.toString("utf-8");
6526
+ const newText = newContent.toString("utf-8");
6527
+ const sampleSize = Math.min(8192, prevText.length, newText.length);
6528
+ if (prevText.slice(0, sampleSize).includes("\0") || newText.slice(0, sampleSize).includes("\0")) {
6529
+ return void 0;
6530
+ }
6531
+ const prevLines = prevText.split("\n");
6532
+ const newLines = newText.split("\n");
6533
+ const diffLines = [];
6534
+ diffLines.push("--- a/file");
6535
+ diffLines.push("+++ b/file");
6536
+ const removed = prevLines.filter((l) => !newLines.includes(l));
6537
+ const added = newLines.filter((l) => !prevLines.includes(l));
6538
+ if (removed.length === 0 && added.length === 0) {
6539
+ return void 0;
6540
+ }
6541
+ diffLines.push("@@ -1 +1 @@");
6542
+ for (const line of removed) {
6543
+ diffLines.push(`-${line}`);
6544
+ }
6545
+ for (const line of added) {
6546
+ diffLines.push(`+${line}`);
6547
+ }
6548
+ return diffLines.join("\n");
6549
+ } catch {
6550
+ return void 0;
6551
+ }
6552
+ }
6553
+ /**
6554
+ * Emit a file event if a callback is registered
6555
+ * Also persists to EventStorage if configured
6556
+ */
6557
+ async emitFileEvent(sessionId, event) {
6558
+ if (this.onFileEvent) {
6559
+ try {
6560
+ this.onFileEvent(event);
6561
+ } catch (error) {
6562
+ console.error("[FILE_SYNC] Error in file event callback:", error);
6563
+ }
6564
+ }
6565
+ if (this.webhookConfig) {
6566
+ this.sendFileSyncWebhook(sessionId, event);
6567
+ }
6568
+ if (this.eventStorage) {
6569
+ try {
6570
+ const now = /* @__PURE__ */ new Date();
6571
+ const sequenceNumber = await this.getNextSequenceNumber(sessionId);
6572
+ const diff = this.computeDiff(event.previousContent, event.newContent);
6573
+ const isTextFile = diff !== void 0 || event.newContent && !event.newContent.slice(0, 8192).includes(0);
6574
+ const eventData = {
6575
+ operation: event.operation,
6576
+ direction: event.direction,
6577
+ source: "internal",
6578
+ // File sync operations are internal
6579
+ filePath: event.filePath,
6580
+ fileSize: event.fileSize,
6581
+ success: event.success,
6582
+ error: event.error,
6583
+ diff,
6584
+ isTextFile,
6585
+ previousSize: event.previousContent?.length
6586
+ };
6587
+ const eventType = `file_${event.operation}`;
6588
+ const sessionEvent = {
6589
+ eventType,
6590
+ category: EventCategory.FILE,
6591
+ startedAt: now,
6592
+ endedAt: now,
6593
+ durationMs: 0,
6594
+ eventData,
6595
+ sequenceNumber
6596
+ };
6597
+ await this.eventStorage.saveEvents(sessionId, [sessionEvent]);
6598
+ } catch (error) {
6599
+ console.error("[FILE_SYNC] Error saving file event to storage:", error);
6600
+ }
6601
+ }
5833
6602
  }
5834
6603
  /**
5835
6604
  * Set the sandbox operations implementation
@@ -5843,8 +6612,8 @@ var init_sandbox_file_sync = __esm({
5843
6612
  * @param path - The relative file path
5844
6613
  * @param targetPath - Optional override for the base path
5845
6614
  */
5846
- getSandboxPath(path14, targetPath) {
5847
- const normalizedPath = path14.replace(/^\/+/, "");
6615
+ getSandboxPath(path15, targetPath) {
6616
+ const normalizedPath = path15.replace(/^\/+/, "");
5848
6617
  const basePath = targetPath ?? this.sandboxBasePath;
5849
6618
  if (basePath === ".") {
5850
6619
  return normalizedPath;
@@ -5857,33 +6626,67 @@ var init_sandbox_file_sync = __esm({
5857
6626
  * @param path - File path (stored in S3 and used as relative path in sandbox)
5858
6627
  * @param content - File content
5859
6628
  * @param options - Push options (e.g., targetPath to override sandbox location)
6629
+ * @param previousContent - Optional previous content for diff computation
5860
6630
  */
5861
- async pushFile(sessionId, path14, content, options) {
6631
+ async pushFile(sessionId, path15, content, options, previousContent) {
5862
6632
  const result = {
5863
- path: path14,
6633
+ path: path15,
5864
6634
  s3Written: false,
5865
6635
  sandboxWritten: false
5866
6636
  };
5867
6637
  try {
5868
- await this.fileStore.writeFile(sessionId, path14, content);
6638
+ await this.fileStore.writeFile(sessionId, path15, content);
5869
6639
  result.s3Written = true;
6640
+ await this.emitFileEvent(sessionId, {
6641
+ operation: "push",
6642
+ direction: "to_s3",
6643
+ filePath: path15,
6644
+ fileSize: content.length,
6645
+ success: true,
6646
+ previousContent,
6647
+ newContent: content
6648
+ });
5870
6649
  } catch (error) {
5871
6650
  const errorMessage = extractErrorMessage(error);
5872
6651
  result.error = `S3 write failed: ${errorMessage}`;
5873
- console.error(`[FILE_SYNC] S3 write failed for session ${sessionId}, path ${path14}:`, error);
6652
+ console.error(`[FILE_SYNC] S3 write failed for session ${sessionId}, path ${path15}:`, error);
6653
+ await this.emitFileEvent(sessionId, {
6654
+ operation: "push",
6655
+ direction: "to_s3",
6656
+ filePath: path15,
6657
+ fileSize: content.length,
6658
+ success: false,
6659
+ error: errorMessage
6660
+ });
5874
6661
  return result;
5875
6662
  }
5876
6663
  if (this.sandboxOps?.isSandboxRunning(sessionId)) {
5877
6664
  try {
5878
- const sandboxPath = this.getSandboxPath(path14, options?.targetPath);
6665
+ const sandboxPath = this.getSandboxPath(path15, options?.targetPath);
5879
6666
  const writeResult = await this.sandboxOps.writeFile(sessionId, sandboxPath, content);
5880
6667
  result.sandboxWritten = writeResult.success;
6668
+ await this.emitFileEvent(sessionId, {
6669
+ operation: "push",
6670
+ direction: "to_sandbox",
6671
+ filePath: path15,
6672
+ fileSize: content.length,
6673
+ success: writeResult.success,
6674
+ error: writeResult.error
6675
+ });
5881
6676
  if (!writeResult.success && writeResult.error) {
5882
- console.warn(`[FILE_SYNC] Sandbox write failed for ${path14}: ${writeResult.error}`);
6677
+ console.warn(`[FILE_SYNC] Sandbox write failed for ${path15}: ${writeResult.error}`);
5883
6678
  }
5884
6679
  } catch (error) {
5885
6680
  const errorMessage = extractErrorMessage(error);
5886
- console.warn(`[FILE_SYNC] Sandbox write error for ${path14}: ${errorMessage}`);
6681
+ console.warn(`[FILE_SYNC] Sandbox write error for ${path15}: ${errorMessage}`);
6682
+ await this.emitFileEvent(sessionId, {
6683
+ operation: "push",
6684
+ direction: "to_sandbox",
6685
+ filePath: path15,
6686
+ fileSize: content.length,
6687
+ success: false,
6688
+ error: errorMessage
6689
+ });
5887
6690
  }
5888
6691
  } else {
5889
6692
  console.debug(`[FILE_SYNC] Sandbox not running for session ${sessionId}, skipping sandbox write`);
@@ -5908,9 +6711,9 @@ var init_sandbox_file_sync = __esm({
5908
6711
  * Pull a file from sandbox to S3
5909
6712
  * Reads from sandbox and writes to S3
5910
6713
  */
5911
- async pullFile(sessionId, path14) {
6714
+ async pullFile(sessionId, path15) {
5912
6715
  const result = {
5913
- path: path14,
6716
+ path: path15,
5914
6717
  content: null,
5915
6718
  s3Written: false
5916
6719
  };
@@ -5919,35 +6722,73 @@ var init_sandbox_file_sync = __esm({
5919
6722
  return result;
5920
6723
  }
5921
6724
  try {
5922
- const sandboxPath = this.getSandboxPath(path14);
6725
+ const sandboxPath = this.getSandboxPath(path15);
5923
6726
  const readResult = await this.sandboxOps.readFile(sessionId, sandboxPath);
5924
6727
  if (!readResult.success || !readResult.content) {
5925
6728
  result.error = readResult.error ?? "File not found in sandbox";
6729
+ await this.emitFileEvent(sessionId, {
6730
+ operation: "pull",
6731
+ direction: "from_sandbox",
6732
+ filePath: path15,
6733
+ success: false,
6734
+ error: result.error
6735
+ });
5926
6736
  return result;
5927
6737
  }
5928
6738
  result.content = readResult.content;
6739
+ await this.emitFileEvent(sessionId, {
6740
+ operation: "pull",
6741
+ direction: "from_sandbox",
6742
+ filePath: path15,
6743
+ fileSize: readResult.content.length,
6744
+ success: true,
6745
+ newContent: readResult.content
6746
+ });
5929
6747
  } catch (error) {
5930
6748
  const errorMessage = extractErrorMessage(error);
5931
6749
  result.error = `Sandbox read failed: ${errorMessage}`;
6750
+ await this.emitFileEvent(sessionId, {
6751
+ operation: "pull",
6752
+ direction: "from_sandbox",
6753
+ filePath: path15,
6754
+ success: false,
6755
+ error: errorMessage
6756
+ });
5932
6757
  return result;
5933
6758
  }
5934
6759
  try {
5935
- await this.fileStore.writeFile(sessionId, path14, result.content);
6760
+ await this.fileStore.writeFile(sessionId, path15, result.content);
5936
6761
  result.s3Written = true;
6762
+ await this.emitFileEvent(sessionId, {
6763
+ operation: "pull",
6764
+ direction: "to_s3",
6765
+ filePath: path15,
6766
+ fileSize: result.content.length,
6767
+ success: true,
6768
+ newContent: result.content
6769
+ });
5937
6770
  } catch (error) {
5938
6771
  const errorMessage = extractErrorMessage(error);
5939
6772
  result.error = `S3 write failed: ${errorMessage}`;
5940
- console.error(`[FILE_SYNC] S3 write failed in pullFile for session ${sessionId}, path ${path14}:`, error);
6773
+ console.error(`[FILE_SYNC] S3 write failed in pullFile for session ${sessionId}, path ${path15}:`, error);
6774
+ await this.emitFileEvent(sessionId, {
6775
+ operation: "pull",
6776
+ direction: "to_s3",
6777
+ filePath: path15,
6778
+ fileSize: result.content?.length,
6779
+ success: false,
6780
+ error: errorMessage
6781
+ });
5941
6782
  }
5942
6783
  return result;
5943
6784
  }
5944
6785
  /**
5945
6786
  * Read a file (tries sandbox first, falls back to S3)
5946
6787
  */
5947
- async readFile(sessionId, path14) {
6788
+ async readFile(sessionId, path15) {
5948
6789
  if (this.sandboxOps?.isSandboxRunning(sessionId)) {
5949
6790
  try {
5950
- const sandboxPath = this.getSandboxPath(path14);
6791
+ const sandboxPath = this.getSandboxPath(path15);
5951
6792
  const readResult = await this.sandboxOps.readFile(sessionId, sandboxPath);
5952
6793
  if (readResult.success && readResult.content) {
5953
6794
  return { content: readResult.content, source: "sandbox" };
@@ -5956,7 +6797,7 @@ var init_sandbox_file_sync = __esm({
5956
6797
  }
5957
6798
  }
5958
6799
  try {
5959
- const content = await this.fileStore.readFile(sessionId, path14);
6800
+ const content = await this.fileStore.readFile(sessionId, path15);
5960
6801
  if (content) {
5961
6802
  return { content, source: "s3" };
5962
6803
  }
@@ -5973,21 +6814,48 @@ var init_sandbox_file_sync = __esm({
5973
6814
  /**
5974
6815
  * Delete a file from both S3 and sandbox
5975
6816
  */
5976
- async deleteFile(sessionId, path14) {
6817
+ async deleteFile(sessionId, path15) {
5977
6818
  const result = { s3Deleted: false, sandboxDeleted: false };
5978
6819
  try {
5979
- await this.fileStore.deleteFile(sessionId, path14);
6820
+ await this.fileStore.deleteFile(sessionId, path15);
5980
6821
  result.s3Deleted = true;
6822
+ await this.emitFileEvent(sessionId, {
6823
+ operation: "delete",
6824
+ direction: "from_s3",
6825
+ filePath: path15,
6826
+ success: true
6827
+ });
5981
6828
  } catch (error) {
5982
6829
  const errorMessage = extractErrorMessage(error);
5983
- console.warn(`[FILE_SYNC] S3 delete failed for ${path14}: ${errorMessage}`);
6830
+ console.warn(`[FILE_SYNC] S3 delete failed for ${path15}: ${errorMessage}`);
6831
+ await this.emitFileEvent(sessionId, {
6832
+ operation: "delete",
6833
+ direction: "from_s3",
6834
+ filePath: path15,
6835
+ success: false,
6836
+ error: errorMessage
6837
+ });
5984
6838
  }
5985
6839
  if (this.sandboxOps?.isSandboxRunning(sessionId)) {
5986
6840
  try {
5987
- const sandboxPath = this.getSandboxPath(path14);
6841
+ const sandboxPath = this.getSandboxPath(path15);
5988
6842
  console.log(`[FILE_SYNC] Would delete ${sandboxPath} from sandbox`);
5989
6843
  result.sandboxDeleted = true;
5990
- } catch {
6844
+ await this.emitFileEvent(sessionId, {
6845
+ operation: "delete",
6846
+ direction: "from_sandbox",
6847
+ filePath: path15,
6848
+ success: true
6849
+ });
6850
+ } catch (error) {
6851
+ const errorMessage = extractErrorMessage(error);
6852
+ await this.emitFileEvent(sessionId, {
6853
+ operation: "delete",
6854
+ direction: "from_sandbox",
6855
+ filePath: path15,
6856
+ success: false,
6857
+ error: errorMessage
6858
+ });
5991
6859
  }
5992
6860
  }
5993
6861
  return result;
@@ -6008,14 +6876,36 @@ var init_sandbox_file_sync = __esm({
6008
6876
  const content = await this.fileStore.readFile(sessionId, file.path);
6009
6877
  if (!content) {
6010
6878
  result.errors.push({ path: file.path, error: "File not found in S3" });
6879
+ await this.emitFileEvent(sessionId, {
6880
+ operation: "sync_to_sandbox",
6881
+ direction: "from_s3",
6882
+ filePath: file.path,
6883
+ success: false,
6884
+ error: "File not found in S3"
6885
+ });
6011
6886
  continue;
6012
6887
  }
6013
6888
  const sandboxPath = this.getSandboxPath(file.path);
6014
6889
  const writeResult = await this.sandboxOps.writeFile(sessionId, sandboxPath, content);
6015
6890
  if (writeResult.success) {
6016
6891
  result.fileCount++;
6892
+ await this.emitFileEvent(sessionId, {
6893
+ operation: "sync_to_sandbox",
6894
+ direction: "to_sandbox",
6895
+ filePath: file.path,
6896
+ fileSize: content.length,
6897
+ success: true
6898
+ });
6017
6899
  } else {
6018
6900
  result.errors.push({ path: file.path, error: writeResult.error ?? "Unknown error" });
6901
+ await this.emitFileEvent(sessionId, {
6902
+ operation: "sync_to_sandbox",
6903
+ direction: "to_sandbox",
6904
+ filePath: file.path,
6905
+ fileSize: content.length,
6906
+ success: false,
6907
+ error: writeResult.error
6908
+ });
6019
6909
  }
6020
6910
  } catch (error) {
6021
6911
  const errorMessage = extractErrorMessage(error);
@@ -6023,6 +6913,13 @@ var init_sandbox_file_sync = __esm({
6023
6913
  path: file.path,
6024
6914
  error: errorMessage
6025
6915
  });
6916
+ await this.emitFileEvent(sessionId, {
6917
+ operation: "sync_to_sandbox",
6918
+ direction: "to_sandbox",
6919
+ filePath: file.path,
6920
+ success: false,
6921
+ error: errorMessage
6922
+ });
6026
6923
  }
6027
6924
  }
6028
6925
  console.log(`[FILE_SYNC] Synced ${result.fileCount} files to sandbox for session ${sessionId}`);
@@ -6055,8 +6952,23 @@ var init_sandbox_file_sync = __esm({
6055
6952
  const pullResult = await this.pullFile(sessionId, filePath);
6056
6953
  if (pullResult.s3Written) {
6057
6954
  result.fileCount++;
6955
+ await this.emitFileEvent(sessionId, {
6956
+ operation: "sync_from_sandbox",
6957
+ direction: "to_s3",
6958
+ filePath,
6959
+ fileSize: pullResult.content?.length,
6960
+ success: true,
6961
+ newContent: pullResult.content ?? void 0
6962
+ });
6058
6963
  } else if (pullResult.error) {
6059
6964
  result.errors.push({ path: filePath, error: pullResult.error });
6965
+ await this.emitFileEvent(sessionId, {
6966
+ operation: "sync_from_sandbox",
6967
+ direction: "to_s3",
6968
+ filePath,
6969
+ success: false,
6970
+ error: pullResult.error
6971
+ });
6060
6972
  }
6061
6973
  } catch (error) {
6062
6974
  const errorMessage = extractErrorMessage(error);
@@ -6064,6 +6976,13 @@ var init_sandbox_file_sync = __esm({
6064
6976
  path: filePath,
6065
6977
  error: errorMessage
6066
6978
  });
6979
+ await this.emitFileEvent(sessionId, {
6980
+ operation: "sync_from_sandbox",
6981
+ direction: "to_s3",
6982
+ filePath,
6983
+ success: false,
6984
+ error: errorMessage
6985
+ });
6067
6986
  }
6068
6987
  }
6069
6988
  console.log(`[FILE_SYNC] Synced ${result.fileCount} files from sandbox to S3 for session ${sessionId}`);
@@ -6072,16 +6991,526 @@ var init_sandbox_file_sync = __esm({
6072
6991
  /**
6073
6992
  * Get a signed URL for direct file download
6074
6993
  */
6075
- async getSignedUrl(sessionId, path14, expiresIn) {
6076
- return this.fileStore.getSignedUrl(sessionId, path14, expiresIn);
6994
+ async getSignedUrl(sessionId, path15, expiresIn) {
6995
+ return this.fileStore.getSignedUrl(sessionId, path15, expiresIn);
6077
6996
  }
6078
6997
  /**
6079
6998
  * Get a signed URL for direct file upload
6080
6999
  */
6081
- async getUploadUrl(sessionId, path14, expiresIn) {
6082
- return this.fileStore.getUploadUrl(sessionId, path14, expiresIn);
7000
+ async getUploadUrl(sessionId, path15, expiresIn) {
7001
+ return this.fileStore.getUploadUrl(sessionId, path15, expiresIn);
7002
+ }
7003
+ // ===========================================================================
7004
+ // File Watching API
7005
+ // ===========================================================================
7006
+ /**
7007
+ * Start watching a sandbox for file changes and auto-sync to S3.
7008
+ *
7009
+ * For remote sandboxes (Vercel, E2B, etc.), uses polling.
7010
+ * For local sandboxes, can optionally use native file watching via chokidar.
7011
+ *
7012
+ * @param sessionId - Session to watch
7013
+ * @param options - Watch options (overrides constructor defaults)
7014
+ */
7015
+ async startWatching(sessionId, options) {
7016
+ const opts = { ...this.defaultWatchOptions, ...options };
7017
+ await this.stopWatching(sessionId);
7018
+ const handleFileChange = async (event) => {
7019
+ console.log(`[FILE_SYNC] File change detected: ${event.type} ${event.relativePath}`);
7020
+ if (this.webhookConfig) {
7021
+ this.sendFileChangeWebhook(event);
7022
+ }
7023
+ for (const subscriber of this.fileChangeSubscribers) {
7024
+ try {
7025
+ await subscriber(event);
7026
+ } catch (error) {
7027
+ console.error("[FILE_SYNC] Error in file change subscriber:", error);
7028
+ }
7029
+ }
7030
+ if (event.type === "add" || event.type === "change") {
7031
+ try {
7032
+ const pullResult = await this.pullFile(sessionId, event.relativePath);
7033
+ if (pullResult.s3Written) {
7034
+ console.log(`[FILE_SYNC] Auto-synced ${event.relativePath} to S3`);
7035
+ } else if (pullResult.error) {
7036
+ console.warn(`[FILE_SYNC] Failed to auto-sync ${event.relativePath}: ${pullResult.error}`);
7037
+ }
7038
+ } catch (error) {
7039
+ console.error(`[FILE_SYNC] Error auto-syncing ${event.relativePath}:`, error);
7040
+ }
7041
+ } else if (event.type === "unlink") {
7042
+ try {
7043
+ await this.deleteFile(sessionId, event.relativePath);
7044
+ console.log(`[FILE_SYNC] Auto-deleted ${event.relativePath} from S3`);
7045
+ } catch (error) {
7046
+ console.error(`[FILE_SYNC] Error auto-deleting ${event.relativePath}:`, error);
7047
+ }
7048
+ }
7049
+ };
7050
+ if (opts.useLocalWatcher && opts.localPath) {
7051
+ const watcher = new SandboxFileWatcher({
7052
+ sessionId,
7053
+ watchPath: opts.localPath,
7054
+ debounceMs: opts.debounceMs ?? 300,
7055
+ ignored: opts.ignored ?? ["**/node_modules/**", "**/.git/**"],
7056
+ onFileChange: handleFileChange,
7057
+ onError: (error) => {
7058
+ console.error(`[FILE_SYNC] Local watcher error for session ${sessionId}:`, error);
7059
+ }
7060
+ });
7061
+ await watcher.start();
7062
+ this.localWatchers.set(sessionId, watcher);
7063
+ console.log(`[FILE_SYNC] Started local file watching for session ${sessionId}`);
7064
+ } else {
7065
+ if (!this.sandboxOps) {
7066
+ throw new Error("Sandbox operations not configured. Call setSandboxOperations first.");
7067
+ }
7068
+ const watcher = new RemoteSandboxFileWatcher({
7069
+ sessionId,
7070
+ sandboxOps: this.sandboxOps,
7071
+ basePath: this.sandboxBasePath,
7072
+ pollIntervalMs: opts.pollIntervalMs ?? 2e3,
7073
+ ignored: opts.ignored ?? ["**/node_modules/**", "**/.git/**"],
7074
+ onFileChange: handleFileChange,
7075
+ onError: (error) => {
7076
+ console.error(`[FILE_SYNC] Remote watcher error for session ${sessionId}:`, error);
7077
+ }
7078
+ });
7079
+ await watcher.start();
7080
+ this.remoteWatchers.set(sessionId, watcher);
7081
+ console.log(`[FILE_SYNC] Started remote file watching for session ${sessionId}`);
7082
+ }
7083
+ }
7084
+ /**
7085
+ * Stop watching a session's sandbox
7086
+ */
7087
+ async stopWatching(sessionId) {
7088
+ const remoteWatcher = this.remoteWatchers.get(sessionId);
7089
+ if (remoteWatcher) {
7090
+ await remoteWatcher.stop();
7091
+ this.remoteWatchers.delete(sessionId);
7092
+ }
7093
+ const localWatcher = this.localWatchers.get(sessionId);
7094
+ if (localWatcher) {
7095
+ await localWatcher.stop();
7096
+ this.localWatchers.delete(sessionId);
7097
+ }
7098
+ }
7099
+ /**
7100
+ * Check if a session is being watched
7101
+ */
7102
+ isWatching(sessionId) {
7103
+ return this.remoteWatchers.has(sessionId) || this.localWatchers.has(sessionId);
7104
+ }
7105
+ /**
7106
+ * Subscribe to file change events across all watched sessions.
7107
+ * This allows external applications to react to file changes.
7108
+ *
7109
+ * @param callback - Function to call when a file changes
7110
+ * @returns Unsubscribe function
7111
+ *
7112
+ * @example
7113
+ * ```typescript
7114
+ * const unsubscribe = fileSync.onFileChange((event) => {
7115
+ * console.log(`File ${event.relativePath} was ${event.type}d`);
7116
+ * // Update your application state here
7117
+ * });
7118
+ *
7119
+ * // Later, when done
7120
+ * unsubscribe();
7121
+ * ```
7122
+ */
7123
+ onFileChange(callback) {
7124
+ this.fileChangeSubscribers.add(callback);
7125
+ return () => this.fileChangeSubscribers.delete(callback);
7126
+ }
7127
+ /**
7128
+ * Remove a file change subscriber
7129
+ */
7130
+ offFileChange(callback) {
7131
+ this.fileChangeSubscribers.delete(callback);
7132
+ }
7133
+ /**
7134
+ * Stop all watchers and cleanup
7135
+ */
7136
+ async stopAllWatching() {
7137
+ const remotePromises = Array.from(this.remoteWatchers.values()).map((w) => w.stop());
7138
+ const localPromises = Array.from(this.localWatchers.values()).map((w) => w.stop());
7139
+ await Promise.all([...remotePromises, ...localPromises]);
7140
+ this.remoteWatchers.clear();
7141
+ this.localWatchers.clear();
7142
+ }
7143
+ /**
7144
+ * Get watching status for all sessions
7145
+ */
7146
+ getWatchingStatus() {
7147
+ const statuses = [];
7148
+ for (const [sessionId, watcher] of this.remoteWatchers) {
7149
+ statuses.push({ sessionId, type: "remote", isActive: watcher.isActive() });
7150
+ }
7151
+ for (const [sessionId, watcher] of this.localWatchers) {
7152
+ statuses.push({ sessionId, type: "local", isActive: watcher.isActive() });
7153
+ }
7154
+ return statuses;
7155
+ }
7156
+ };
7157
+ }
7158
+ });
7159
+
7160
+ // src/runtime/in-sandbox-watcher.ts
7161
+ function createInSandboxWatcher(options) {
7162
+ return new InSandboxWatcher(options);
7163
+ }
7164
+ function getInSandboxWatcherManager() {
7165
+ if (!globalInSandboxManager) {
7166
+ globalInSandboxManager = new InSandboxWatcherManager();
7167
+ }
7168
+ return globalInSandboxManager;
7169
+ }
7170
+ function createInSandboxWatcherManager() {
7171
+ return new InSandboxWatcherManager();
7172
+ }
7173
+ var WATCHER_SCRIPT, InSandboxWatcher, InSandboxWatcherManager, globalInSandboxManager;
7174
+ var init_in_sandbox_watcher = __esm({
7175
+ "src/runtime/in-sandbox-watcher.ts"() {
7176
+ init_vercel_sandbox_executor();
7177
+ WATCHER_SCRIPT = `
7178
+ const fs = require('fs');
7179
+ const path = require('path');
7180
+
7181
+ const watchPath = process.argv[2] || '.';
7182
+ const ignored = new Set(['node_modules', '.git', '.cache', '__pycache__']);
7183
+
7184
+ // Track all watchers for cleanup
7185
+ const watchers = new Map();
7186
+
7187
+ // Debounce map to coalesce rapid changes
7188
+ const pending = new Map();
7189
+ const DEBOUNCE_MS = 100;
7190
+
7191
+ function emit(type, filePath) {
7192
+ const event = {
7193
+ type,
7194
+ path: filePath,
7195
+ timestamp: Date.now()
7196
+ };
7197
+ console.log(JSON.stringify(event));
7198
+ }
7199
+
7200
+ function shouldIgnore(name) {
7201
+ return ignored.has(name) || name.startsWith('.');
7202
+ }
7203
+
7204
+ function watchDir(dir) {
7205
+ try {
7206
+ const watcher = fs.watch(dir, { persistent: true }, (eventType, filename) => {
7207
+ if (!filename || shouldIgnore(filename)) return;
7208
+
7209
+ const fullPath = path.join(dir, filename);
7210
+ const key = fullPath;
7211
+
7212
+ // Debounce
7213
+ if (pending.has(key)) {
7214
+ clearTimeout(pending.get(key));
7215
+ }
7216
+
7217
+ pending.set(key, setTimeout(() => {
7218
+ pending.delete(key);
7219
+
7220
+ fs.stat(fullPath, (err, stats) => {
7221
+ if (err) {
7222
+ if (err.code === 'ENOENT') {
7223
+ emit('unlink', fullPath);
7224
+ // Stop watching if it was a directory
7225
+ if (watchers.has(fullPath)) {
7226
+ watchers.get(fullPath).close();
7227
+ watchers.delete(fullPath);
7228
+ }
7229
+ }
7230
+ } else {
7231
+ const type = eventType === 'rename' ? 'add' : 'change';
7232
+ emit(type, fullPath);
7233
+
7234
+ // If it's a new directory, start watching it
7235
+ if (stats.isDirectory() && !watchers.has(fullPath)) {
7236
+ watchDir(fullPath);
7237
+ }
7238
+ }
7239
+ });
7240
+ }, DEBOUNCE_MS));
7241
+ });
7242
+
7243
+ watchers.set(dir, watcher);
7244
+
7245
+ // Watch subdirectories
7246
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
7247
+ for (const entry of entries) {
7248
+ if (entry.isDirectory() && !shouldIgnore(entry.name)) {
7249
+ watchDir(path.join(dir, entry.name));
7250
+ }
7251
+ }
7252
+ } catch (err) {
7253
+ // Directory may not exist or be inaccessible
7254
+ console.error(JSON.stringify({ error: err.message, dir }));
7255
+ }
7256
+ }
7257
+
7258
+ // Start watching
7259
+ watchDir(watchPath);
7260
+
7261
+ // Keep alive
7262
+ process.on('SIGTERM', () => {
7263
+ for (const watcher of watchers.values()) {
7264
+ watcher.close();
7265
+ }
7266
+ process.exit(0);
7267
+ });
7268
+
7269
+ // Heartbeat to indicate we're running
7270
+ setInterval(() => {
7271
+ console.log(JSON.stringify({ heartbeat: true, timestamp: Date.now() }));
7272
+ }, 5000);
7273
+
7274
+ console.log(JSON.stringify({ started: true, path: watchPath }));
7275
+ `;
7276
+ InSandboxWatcher = class {
7277
+ sessionId;
7278
+ watchPath;
7279
+ outputPollIntervalMs;
7280
+ sandboxState = null;
7281
+ outputPollTimer = null;
7282
+ subscribers = /* @__PURE__ */ new Set();
7283
+ isRunning = false;
7284
+ startedAt;
7285
+ lastHeartbeat;
7286
+ lastOutputPosition = 0;
7287
+ onError;
7288
+ onReady;
7289
+ constructor(options) {
7290
+ this.sessionId = options.sessionId;
7291
+ this.watchPath = options.watchPath;
7292
+ this.outputPollIntervalMs = options.outputPollIntervalMs ?? 1e3;
7293
+ this.onError = options.onError;
7294
+ this.onReady = options.onReady;
7295
+ if (options.onFileChange) {
7296
+ this.subscribers.add(options.onFileChange);
7297
+ }
7298
+ }
7299
+ /**
7300
+ * Start the watcher process inside the sandbox
7301
+ */
7302
+ async start() {
7303
+ if (this.isRunning) {
7304
+ console.warn(`[IN_SANDBOX_WATCHER] Already running for session ${this.sessionId}`);
7305
+ return;
7306
+ }
7307
+ try {
7308
+ this.sandboxState = await getOrCreateSandbox({
7309
+ sessionId: this.sessionId,
7310
+ runtime: "node22",
7311
+ timeout: 600
7312
+ });
7313
+ const { sandbox } = this.sandboxState;
7314
+ const scriptPath = "/tmp/.file-watcher.js";
7315
+ const outputPath = "/tmp/.file-watcher-output.log";
7316
+ const writeScriptCmd = `cat > ${scriptPath} << 'WATCHER_EOF'
7317
+ ${WATCHER_SCRIPT}
7318
+ WATCHER_EOF`;
7319
+ await sandbox.runCommand({
7320
+ cmd: "sh",
7321
+ args: ["-c", writeScriptCmd]
7322
+ });
7323
+ const startCmd = `nohup node ${scriptPath} "${this.watchPath}" > ${outputPath} 2>&1 &`;
7324
+ await sandbox.runCommand({
7325
+ cmd: "sh",
7326
+ args: ["-c", startCmd]
7327
+ });
7328
+ console.log(`[IN_SANDBOX_WATCHER] Started watcher in sandbox for ${this.watchPath}`);
7329
+ this.isRunning = true;
7330
+ this.startedAt = /* @__PURE__ */ new Date();
7331
+ this.outputPollTimer = setInterval(async () => {
7332
+ try {
7333
+ await this.pollOutput(outputPath);
7334
+ } catch (error) {
7335
+ console.error("[IN_SANDBOX_WATCHER] Error polling output:", error);
7336
+ if (this.onError && error instanceof Error) {
7337
+ this.onError(error);
7338
+ }
7339
+ }
7340
+ }, this.outputPollIntervalMs);
7341
+ setTimeout(() => {
7342
+ this.pollOutput(outputPath).catch(console.error);
7343
+ }, 500);
7344
+ } catch (error) {
7345
+ console.error(`[IN_SANDBOX_WATCHER] Failed to start watcher:`, error);
7346
+ throw error;
7347
+ }
7348
+ }
7349
+ /**
7350
+ * Poll the output file for new events
7351
+ */
7352
+ async pollOutput(outputPath) {
7353
+ if (!this.sandboxState?.sandbox) return;
7354
+ try {
7355
+ const result = await this.sandboxState.sandbox.runCommand({
7356
+ cmd: "sh",
7357
+ args: ["-c", `tail -c +${this.lastOutputPosition + 1} ${outputPath} 2>/dev/null || true`]
7358
+ });
7359
+ const output = await result.stdout();
7360
+ if (output && output.trim()) {
7361
+ const sizeResult = await this.sandboxState.sandbox.runCommand({
7362
+ cmd: "sh",
7363
+ args: ["-c", `stat -c%s ${outputPath} 2>/dev/null || echo 0`]
7364
+ });
7365
+ const sizeStr = await sizeResult.stdout();
7366
+ this.lastOutputPosition = parseInt(sizeStr.trim(), 10) || 0;
7367
+ this.processOutput(output);
7368
+ }
7369
+ } catch {
7370
+ }
7371
+ }
7372
+ /**
7373
+ * Process output from the watcher script
7374
+ */
7375
+ processOutput(stdout) {
7376
+ const lines = stdout.split("\n").filter(Boolean);
7377
+ for (const line of lines) {
7378
+ try {
7379
+ const data = JSON.parse(line);
7380
+ if (data.started) {
7381
+ console.log(`[IN_SANDBOX_WATCHER] Watcher started for ${data.path}`);
7382
+ if (this.onReady) {
7383
+ this.onReady();
7384
+ }
7385
+ } else if (data.heartbeat) {
7386
+ this.lastHeartbeat = new Date(data.timestamp);
7387
+ } else if (data.error) {
7388
+ console.warn(`[IN_SANDBOX_WATCHER] Error in sandbox:`, data.error);
7389
+ } else if (data.type && data.path) {
7390
+ this.emitEvent({
7391
+ type: data.type,
7392
+ relativePath: data.path.replace(/^\.\//, ""),
7393
+ absolutePath: data.path,
7394
+ sessionId: this.sessionId,
7395
+ timestamp: new Date(data.timestamp)
7396
+ });
7397
+ }
7398
+ } catch {
7399
+ }
7400
+ }
7401
+ }
7402
+ /**
7403
+ * Stop the watcher process
7404
+ */
7405
+ async stop() {
7406
+ if (!this.isRunning) {
7407
+ return;
7408
+ }
7409
+ if (this.outputPollTimer) {
7410
+ clearInterval(this.outputPollTimer);
7411
+ this.outputPollTimer = null;
7412
+ }
7413
+ try {
7414
+ if (this.sandboxState?.sandbox) {
7415
+ await this.sandboxState.sandbox.runCommand({
7416
+ cmd: "sh",
7417
+ args: ["-c", "pkill -f file-watcher.js 2>/dev/null || true"]
7418
+ });
7419
+ await this.sandboxState.sandbox.runCommand({
7420
+ cmd: "sh",
7421
+ args: ["-c", "rm -f /tmp/.file-watcher.js /tmp/.file-watcher-output.log"]
7422
+ });
7423
+ }
7424
+ } catch {
7425
+ }
7426
+ this.isRunning = false;
7427
+ this.sandboxState = null;
7428
+ this.lastOutputPosition = 0;
7429
+ console.log(`[IN_SANDBOX_WATCHER] Stopped watcher for session ${this.sessionId}`);
7430
+ }
7431
+ /**
7432
+ * Subscribe to file change events
7433
+ */
7434
+ subscribe(callback) {
7435
+ this.subscribers.add(callback);
7436
+ return () => this.subscribers.delete(callback);
7437
+ }
7438
+ /**
7439
+ * Check if watcher is running
7440
+ */
7441
+ isActive() {
7442
+ return this.isRunning;
7443
+ }
7444
+ /**
7445
+ * Get watcher status
7446
+ */
7447
+ getStatus() {
7448
+ return {
7449
+ sessionId: this.sessionId,
7450
+ watchPath: this.watchPath,
7451
+ isRunning: this.isRunning,
7452
+ startedAt: this.startedAt,
7453
+ lastHeartbeat: this.lastHeartbeat
7454
+ };
7455
+ }
7456
+ /**
7457
+ * Emit event to all subscribers
7458
+ */
7459
+ async emitEvent(event) {
7460
+ for (const callback of this.subscribers) {
7461
+ try {
7462
+ await callback(event);
7463
+ } catch (error) {
7464
+ console.error("[IN_SANDBOX_WATCHER] Error in subscriber callback:", error);
7465
+ }
7466
+ }
7467
+ }
7468
+ };
7469
+ InSandboxWatcherManager = class {
7470
+ watchers = /* @__PURE__ */ new Map();
7471
+ /**
7472
+ * Start watching a sandbox
7473
+ */
7474
+ async startWatching(options) {
7475
+ const { sessionId } = options;
7476
+ await this.stopWatching(sessionId);
7477
+ const watcher = new InSandboxWatcher(options);
7478
+ await watcher.start();
7479
+ this.watchers.set(sessionId, watcher);
7480
+ return watcher;
7481
+ }
7482
+ /**
7483
+ * Stop watching a session
7484
+ */
7485
+ async stopWatching(sessionId) {
7486
+ const watcher = this.watchers.get(sessionId);
7487
+ if (watcher) {
7488
+ await watcher.stop();
7489
+ this.watchers.delete(sessionId);
7490
+ }
7491
+ }
7492
+ /**
7493
+ * Get watcher for a session
7494
+ */
7495
+ getWatcher(sessionId) {
7496
+ return this.watchers.get(sessionId);
7497
+ }
7498
+ /**
7499
+ * Check if watching
7500
+ */
7501
+ isWatching(sessionId) {
7502
+ return this.watchers.get(sessionId)?.isActive() ?? false;
7503
+ }
7504
+ /**
7505
+ * Stop all watchers
7506
+ */
7507
+ async stopAll() {
7508
+ const promises = Array.from(this.watchers.values()).map((w) => w.stop());
7509
+ await Promise.all(promises);
7510
+ this.watchers.clear();
6083
7511
  }
6084
7512
  };
7513
+ globalInSandboxManager = null;
6085
7514
  }
6086
7515
  });
6087
7516
 
@@ -6193,8 +7622,8 @@ function checkSecurityConfig(config) {
6193
7622
  }
6194
7623
  return { issues, warnings, recommendations };
6195
7624
  }
6196
- function isSensitivePath(path14) {
6197
- const normalized = path14.replace(/\\/g, "/").toLowerCase();
7625
+ function isSensitivePath(path15) {
7626
+ const normalized = path15.replace(/\\/g, "/").toLowerCase();
6198
7627
  for (const sensitive of SENSITIVE_PATHS) {
6199
7628
  const pattern = sensitive.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/~/g, ".*");
6200
7629
  if (new RegExp(pattern).test(normalized)) {
@@ -6211,6 +7640,8 @@ var init_runtime = __esm({
6211
7640
  init_vercel_sandbox_executor();
6212
7641
  init_sandbox_file_sync();
6213
7642
  init_sandbox_pool();
7643
+ init_sandbox_file_watcher();
7644
+ init_in_sandbox_watcher();
6214
7645
  RuntimePresets = {
6215
7646
  /**
6216
7647
  * Development mode - no isolation, for local testing
@@ -6321,8 +7752,8 @@ var init_runtime = __esm({
6321
7752
  this.config.pattern = pattern;
6322
7753
  return this;
6323
7754
  }
6324
- workingDirectory(path14) {
6325
- this.config.workingDirectory = path14;
7755
+ workingDirectory(path15) {
7756
+ this.config.workingDirectory = path15;
6326
7757
  return this;
6327
7758
  }
6328
7759
  sandbox(config) {
@@ -6560,7 +7991,7 @@ var init_local_provider = __esm({
6560
7991
  maxFileSize;
6561
7992
  constructor(source, options = {}) {
6562
7993
  this.source = source;
6563
- this.basePath = path3.resolve(source.localPath);
7994
+ this.basePath = path4.resolve(source.localPath);
6564
7995
  this.maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
6565
7996
  }
6566
7997
  /**
@@ -6568,8 +7999,8 @@ var init_local_provider = __esm({
6568
7999
  * Prevents path traversal attacks.
6569
8000
  */
6570
8001
  resolvePath(relativePath) {
6571
- const normalized = path3.normalize(relativePath);
6572
- const resolved = path3.resolve(this.basePath, normalized);
8002
+ const normalized = path4.normalize(relativePath);
8003
+ const resolved = path4.resolve(this.basePath, normalized);
6573
8004
  if (!resolved.startsWith(this.basePath)) {
6574
8005
  throw new Error(`Path traversal detected: ${relativePath}`);
6575
8006
  }
@@ -6581,8 +8012,8 @@ var init_local_provider = __esm({
6581
8012
  const entries = await promises.readdir(dirPath, { withFileTypes: true });
6582
8013
  const fileEntries = await Promise.all(
6583
8014
  entries.map(async (entry) => {
6584
- const entryPath = path3.join(relativePath || ".", entry.name);
6585
- const fullPath = path3.join(dirPath, entry.name);
8015
+ const entryPath = path4.join(relativePath || ".", entry.name);
8016
+ const fullPath = path4.join(dirPath, entry.name);
6586
8017
  let size;
6587
8018
  let modifiedAt;
6588
8019
  try {
@@ -6674,7 +8105,7 @@ var init_local_provider = __esm({
6674
8105
  }
6675
8106
  await this.copyDirectory(sourceFullPath, targetPath);
6676
8107
  } else {
6677
- await promises.mkdir(path3.dirname(targetPath), { recursive: true });
8108
+ await promises.mkdir(path4.dirname(targetPath), { recursive: true });
6678
8109
  await promises.copyFile(sourceFullPath, targetPath);
6679
8110
  }
6680
8111
  } catch (error) {
@@ -6689,8 +8120,8 @@ var init_local_provider = __esm({
6689
8120
  const entries = await promises.readdir(sourceDir, { withFileTypes: true });
6690
8121
  await Promise.all(
6691
8122
  entries.map(async (entry) => {
6692
- const sourcePath = path3.join(sourceDir, entry.name);
6693
- const targetPath = path3.join(targetDir, entry.name);
8123
+ const sourcePath = path4.join(sourceDir, entry.name);
8124
+ const targetPath = path4.join(targetDir, entry.name);
6694
8125
  if (entry.isDirectory()) {
6695
8126
  await this.copyDirectory(sourcePath, targetPath);
6696
8127
  } else {
@@ -6889,7 +8320,7 @@ var init_github_provider = __esm({
6889
8320
  }
6890
8321
  if (type === "file") {
6891
8322
  const content = await this.readFile(sourcePath);
6892
- await promises.mkdir(path3.dirname(targetPath), { recursive: true });
8323
+ await promises.mkdir(path4.dirname(targetPath), { recursive: true });
6893
8324
  if (content.encoding === "base64") {
6894
8325
  await promises.writeFile(targetPath, Buffer.from(content.content, "base64"));
6895
8326
  } else {
@@ -6907,7 +8338,7 @@ var init_github_provider = __esm({
6907
8338
  entries.map(
6908
8339
  (entry) => this.copyTo(
6909
8340
  entry.path,
6910
- path3.join(targetPath, entry.name),
8341
+ path4.join(targetPath, entry.name),
6911
8342
  recursive
6912
8343
  )
6913
8344
  )
@@ -6990,8 +8421,8 @@ var init_manager2 = __esm({
6990
8421
  if (enabledSkills.length === 0) {
6991
8422
  return "";
6992
8423
  }
6993
- const sessionDir = path3.join(this.tempDir, sessionId);
6994
- const skillsDir = path3.join(sessionDir, ".claude", "skills");
8424
+ const sessionDir = path4.join(this.tempDir, sessionId);
8425
+ const skillsDir = path4.join(sessionDir, ".claude", "skills");
6995
8426
  await promises.mkdir(skillsDir, { recursive: true });
6996
8427
  await Promise.all(
6997
8428
  enabledSkills.map((skill) => this.materializeSkill(skill, skillsDir, token))
@@ -7003,10 +8434,10 @@ var init_manager2 = __esm({
7003
8434
  */
7004
8435
  async materializeSkill(skill, skillsDir, token) {
7005
8436
  const provider = this.createFileProvider(skill.source, token);
7006
- const skillDir = path3.join(skillsDir, skill.name);
8437
+ const skillDir = path4.join(skillsDir, skill.name);
7007
8438
  await promises.mkdir(skillDir, { recursive: true });
7008
8439
  for (const includePath of skill.includePaths) {
7009
- const targetPath = path3.join(skillDir, path3.basename(includePath));
8440
+ const targetPath = path4.join(skillDir, path4.basename(includePath));
7010
8441
  try {
7011
8442
  await provider.copyTo(includePath, targetPath, true);
7012
8443
  } catch (error) {
@@ -7021,7 +8452,7 @@ var init_manager2 = __esm({
7021
8452
  * Clean up materialized skills for a session
7022
8453
  */
7023
8454
  async cleanupSession(sessionId) {
7024
- const sessionDir = path3.join(this.tempDir, sessionId);
8455
+ const sessionDir = path4.join(this.tempDir, sessionId);
7025
8456
  try {
7026
8457
  await promises.rm(sessionDir, { recursive: true, force: true });
7027
8458
  } catch (error) {
@@ -7039,7 +8470,7 @@ var init_manager2 = __esm({
7039
8470
  await Promise.all(
7040
8471
  entries.map(async (entry) => {
7041
8472
  if (!entry.isDirectory()) return;
7042
- const dirPath = path3.join(this.tempDir, entry.name);
8473
+ const dirPath = path4.join(this.tempDir, entry.name);
7043
8474
  try {
7044
8475
  const stats = await promises.stat(dirPath);
7045
8476
  if (now - stats.mtimeMs > maxAgeMs) {
@@ -9848,11 +11279,11 @@ var init_dist = __esm({
9848
11279
  return isZodType(schema, "ZodEffects") ? this.cleanParameter(schema._def.schema) : schema;
9849
11280
  }
9850
11281
  generatePath(route) {
9851
- const { method, path: path14, request, responses } = route, pathItemConfig = __rest(route, ["method", "path", "request", "responses"]);
11282
+ const { method, path: path15, request, responses } = route, pathItemConfig = __rest(route, ["method", "path", "request", "responses"]);
9852
11283
  const generatedResponses = mapValues(responses, (response) => {
9853
11284
  return this.getResponse(response);
9854
11285
  });
9855
- const parameters = enhanceMissingParametersError(() => this.getParameters(request), { route: `${method} ${path14}` });
11286
+ const parameters = enhanceMissingParametersError(() => this.getParameters(request), { route: `${method} ${path15}` });
9856
11287
  const requestBody = this.getRequestBody(request === null || request === void 0 ? void 0 : request.body);
9857
11288
  const routeDoc = {
9858
11289
  [method]: Object.assign(Object.assign(Object.assign(Object.assign({}, pathItemConfig), parameters.length > 0 ? {
@@ -10047,8 +11478,8 @@ var init_dist2 = __esm({
10047
11478
  });
10048
11479
  function addBasePathToDocument(document, basePath) {
10049
11480
  const updatedPaths = {};
10050
- Object.keys(document.paths).forEach((path14) => {
10051
- updatedPaths[mergePath(basePath.replaceAll(/:([^\/]+)/g, "{$1}"), path14)] = document.paths[path14];
11481
+ Object.keys(document.paths).forEach((path15) => {
11482
+ updatedPaths[mergePath(basePath.replaceAll(/:([^\/]+)/g, "{$1}"), path15)] = document.paths[path15];
10052
11483
  });
10053
11484
  return {
10054
11485
  ...document,
@@ -10192,8 +11623,8 @@ var init_dist3 = __esm({
10192
11623
  const document = generator.generateDocument(config);
10193
11624
  return this._basePath ? addBasePathToDocument(document, this._basePath) : document;
10194
11625
  };
10195
- doc = (path14, configure) => {
10196
- return this.get(path14, (c) => {
11626
+ doc = (path15, configure) => {
11627
+ return this.get(path15, (c) => {
10197
11628
  const config = typeof configure === "function" ? configure(c) : configure;
10198
11629
  try {
10199
11630
  const document = this.getOpenAPIDocument(config);
@@ -10203,8 +11634,8 @@ var init_dist3 = __esm({
10203
11634
  }
10204
11635
  });
10205
11636
  };
10206
- doc31 = (path14, configure) => {
10207
- return this.get(path14, (c) => {
11637
+ doc31 = (path15, configure) => {
11638
+ return this.get(path15, (c) => {
10208
11639
  const config = typeof configure === "function" ? configure(c) : configure;
10209
11640
  try {
10210
11641
  const document = this.getOpenAPI31Document(config);
@@ -10214,9 +11645,9 @@ var init_dist3 = __esm({
10214
11645
  }
10215
11646
  });
10216
11647
  };
10217
- route(path14, app) {
10218
- const pathForOpenAPI = path14.replaceAll(/:([^\/]+)/g, "{$1}");
10219
- super.route(path14, app);
11648
+ route(path15, app) {
11649
+ const pathForOpenAPI = path15.replaceAll(/:([^\/]+)/g, "{$1}");
11650
+ super.route(path15, app);
10220
11651
  if (!(app instanceof _OpenAPIHono)) {
10221
11652
  return this;
10222
11653
  }
@@ -10263,8 +11694,8 @@ var init_dist3 = __esm({
10263
11694
  });
10264
11695
  return this;
10265
11696
  }
10266
- basePath(path14) {
10267
- return new _OpenAPIHono({ ...super.basePath(path14), defaultHook: this.defaultHook });
11697
+ basePath(path15) {
11698
+ return new _OpenAPIHono({ ...super.basePath(path15), defaultHook: this.defaultHook });
10268
11699
  }
10269
11700
  };
10270
11701
  createRoute = (routeConfig) => {
@@ -12045,12 +13476,12 @@ function requestLogger(options = {}) {
12045
13476
  const requestId = generateId();
12046
13477
  const start = Date.now();
12047
13478
  const method = c.req.method;
12048
- const path14 = c.req.path;
13479
+ const path15 = c.req.path;
12049
13480
  c.set("requestId", requestId);
12050
13481
  logger3.debug("Request started", {
12051
13482
  requestId,
12052
13483
  method,
12053
- path: path14,
13484
+ path: path15,
12054
13485
  userAgent: c.req.header("user-agent")
12055
13486
  });
12056
13487
  await next();
@@ -12060,7 +13491,7 @@ function requestLogger(options = {}) {
12060
13491
  logFn("Request completed", {
12061
13492
  requestId,
12062
13493
  method,
12063
- path: path14,
13494
+ path: path15,
12064
13495
  status,
12065
13496
  duration: `${duration}ms`
12066
13497
  });
@@ -12545,8 +13976,8 @@ function shellQuote3(str) {
12545
13976
  async function loadWorkspaceState(workspaceId, sandbox, bundleStore, repoRoot) {
12546
13977
  const resolvedRoot = repoRoot ?? sandbox.defaultRepoRoot;
12547
13978
  const gitRepo = new SandboxGitRepo(sandbox, resolvedRoot);
12548
- const tmpDir = fs9.mkdtempSync(path3.join(os.tmpdir(), "ash-workspace-"));
12549
- const localBundle = path3.join(tmpDir, `${workspaceId}.bundle`);
13979
+ const tmpDir = fs10.mkdtempSync(path4.join(os.tmpdir(), "ash-workspace-"));
13980
+ const localBundle = path4.join(tmpDir, `${workspaceId}.bundle`);
12550
13981
  try {
12551
13982
  const bundleExists = await Promise.resolve(
12552
13983
  bundleStore.downloadBundle(workspaceId, localBundle)
@@ -12582,7 +14013,7 @@ async function loadWorkspaceState(workspaceId, sandbox, bundleStore, repoRoot) {
12582
14013
  }
12583
14014
  } finally {
12584
14015
  try {
12585
- fs9.rmSync(tmpDir, { recursive: true, force: true });
14016
+ fs10.rmSync(tmpDir, { recursive: true, force: true });
12586
14017
  } catch {
12587
14018
  }
12588
14019
  }
@@ -12602,11 +14033,11 @@ async function saveWorkspaceState(workspaceId, sandbox, gitRepo, bundleStore, co
12602
14033
  }
12603
14034
  const remoteBundlePath = BUNDLE_PATH_TEMPLATE.replace("{workspaceId}", workspaceId);
12604
14035
  await gitRepo.createBundle(remoteBundlePath);
12605
- const tmpDir = fs9.mkdtempSync(path3.join(os.tmpdir(), "ash-workspace-"));
12606
- const localBundle = path3.join(tmpDir, `${workspaceId}.bundle`);
14036
+ const tmpDir = fs10.mkdtempSync(path4.join(os.tmpdir(), "ash-workspace-"));
14037
+ const localBundle = path4.join(tmpDir, `${workspaceId}.bundle`);
12607
14038
  try {
12608
14039
  await sandbox.downloadFile(remoteBundlePath, localBundle);
12609
- const bundleSize = fs9.statSync(localBundle).size;
14040
+ const bundleSize = fs10.statSync(localBundle).size;
12610
14041
  await bundleStore.uploadBundle(workspaceId, localBundle);
12611
14042
  return {
12612
14043
  committed,
@@ -12615,7 +14046,7 @@ async function saveWorkspaceState(workspaceId, sandbox, gitRepo, bundleStore, co
12615
14046
  };
12616
14047
  } finally {
12617
14048
  try {
12618
- fs9.rmSync(tmpDir, { recursive: true, force: true });
14049
+ fs10.rmSync(tmpDir, { recursive: true, force: true });
12619
14050
  } catch {
12620
14051
  }
12621
14052
  }
@@ -12628,7 +14059,7 @@ var init_persistence = __esm({
12628
14059
  }
12629
14060
  });
12630
14061
  async function cloneRepository(url, ref, timeout = 12e4) {
12631
- const tmpDir = fs9.mkdtempSync(path3.join(os.tmpdir(), "ash-github-skill-"));
14062
+ const tmpDir = fs10.mkdtempSync(path4.join(os.tmpdir(), "ash-github-skill-"));
12632
14063
  try {
12633
14064
  const cloneCmd = ["git", "clone", "--depth", "1"];
12634
14065
  if (ref) {
@@ -12643,7 +14074,7 @@ async function cloneRepository(url, ref, timeout = 12e4) {
12643
14074
  return tmpDir;
12644
14075
  } catch (error) {
12645
14076
  try {
12646
- fs9.rmSync(tmpDir, { recursive: true, force: true });
14077
+ fs10.rmSync(tmpDir, { recursive: true, force: true });
12647
14078
  } catch {
12648
14079
  }
12649
14080
  throw error;
@@ -12655,10 +14086,10 @@ function getRepoName(url) {
12655
14086
  }
12656
14087
  function countFiles(dir, exclude) {
12657
14088
  let count = 0;
12658
- const entries = fs9.readdirSync(dir, { withFileTypes: true });
14089
+ const entries = fs10.readdirSync(dir, { withFileTypes: true });
12659
14090
  for (const entry of entries) {
12660
14091
  if (exclude.has(entry.name)) continue;
12661
- const fullPath = path3.join(dir, entry.name);
14092
+ const fullPath = path4.join(dir, entry.name);
12662
14093
  if (entry.isDirectory()) {
12663
14094
  count += countFiles(fullPath, exclude);
12664
14095
  } else if (entry.isFile()) {
@@ -12670,15 +14101,15 @@ function countFiles(dir, exclude) {
12670
14101
  async function uploadDirectory(sandbox, localPath, remotePath, exclude) {
12671
14102
  let uploadCount = 0;
12672
14103
  await sandbox.runCommand(`mkdir -p '${remotePath}'`);
12673
- const entries = fs9.readdirSync(localPath, { withFileTypes: true });
14104
+ const entries = fs10.readdirSync(localPath, { withFileTypes: true });
12674
14105
  for (const entry of entries) {
12675
14106
  if (exclude.has(entry.name)) continue;
12676
- const srcPath = path3.join(localPath, entry.name);
14107
+ const srcPath = path4.join(localPath, entry.name);
12677
14108
  const destPath = `${remotePath}/${entry.name}`;
12678
14109
  if (entry.isDirectory()) {
12679
14110
  uploadCount += await uploadDirectory(sandbox, srcPath, destPath, exclude);
12680
14111
  } else if (entry.isFile()) {
12681
- const content = fs9.readFileSync(srcPath, "utf-8");
14112
+ const content = fs10.readFileSync(srcPath, "utf-8");
12682
14113
  await sandbox.writeFile(destPath, content);
12683
14114
  uploadCount++;
12684
14115
  }
@@ -12704,13 +14135,13 @@ async function loadGitHubSkill(sandbox, config, skillsBasePath) {
12704
14135
  );
12705
14136
  }
12706
14137
  try {
12707
- const sourcePath = subpath ? path3.join(clonePath, subpath) : clonePath;
12708
- if (!fs9.existsSync(sourcePath)) {
14138
+ const sourcePath = subpath ? path4.join(clonePath, subpath) : clonePath;
14139
+ if (!fs10.existsSync(sourcePath)) {
12709
14140
  throw new Error(
12710
14141
  `Path '${subpath}' not found in repository ${url}`
12711
14142
  );
12712
14143
  }
12713
- if (!fs9.statSync(sourcePath).isDirectory()) {
14144
+ if (!fs10.statSync(sourcePath).isDirectory()) {
12714
14145
  throw new Error(
12715
14146
  `Path '${subpath}' in ${url} is not a directory`
12716
14147
  );
@@ -12729,7 +14160,7 @@ async function loadGitHubSkill(sandbox, config, skillsBasePath) {
12729
14160
  };
12730
14161
  } finally {
12731
14162
  try {
12732
- fs9.rmSync(clonePath, { recursive: true, force: true });
14163
+ fs10.rmSync(clonePath, { recursive: true, force: true });
12733
14164
  } catch {
12734
14165
  }
12735
14166
  }
@@ -13422,26 +14853,26 @@ var init_workspace = __esm({
13422
14853
  const localPaths = normalizeToArray(skillsConfig.local);
13423
14854
  for (const localPath of localPaths) {
13424
14855
  try {
13425
- if (!fs9.existsSync(localPath)) {
14856
+ if (!fs10.existsSync(localPath)) {
13426
14857
  errors.push({
13427
14858
  source: localPath,
13428
14859
  error: new Error(`Path does not exist: ${localPath}`)
13429
14860
  });
13430
14861
  continue;
13431
14862
  }
13432
- const stat = fs9.statSync(localPath);
13433
- if (!stat.isDirectory()) {
14863
+ const stat2 = fs10.statSync(localPath);
14864
+ if (!stat2.isDirectory()) {
13434
14865
  errors.push({
13435
14866
  source: localPath,
13436
14867
  error: new Error(`Path is not a directory: ${localPath}`)
13437
14868
  });
13438
14869
  continue;
13439
14870
  }
13440
- const entries = fs9.readdirSync(localPath);
14871
+ const entries = fs10.readdirSync(localPath);
13441
14872
  for (const entry of entries) {
13442
14873
  if (FILTERED_ITEMS2.has(entry)) continue;
13443
- const entryPath = path3.join(localPath, entry);
13444
- const entryStat = fs9.statSync(entryPath);
14874
+ const entryPath = path4.join(localPath, entry);
14875
+ const entryStat = fs10.statSync(entryPath);
13445
14876
  if (entryStat.isDirectory()) {
13446
14877
  const remotePath = `${skillsBase}/${entry}`;
13447
14878
  await this.sandbox.uploadDirectory(entryPath, remotePath, {
@@ -13526,50 +14957,50 @@ var init_bundle_store = __esm({
13526
14957
  directory;
13527
14958
  constructor(options) {
13528
14959
  this.directory = options.directory;
13529
- if (!fs9.existsSync(this.directory)) {
13530
- fs9.mkdirSync(this.directory, { recursive: true });
14960
+ if (!fs10.existsSync(this.directory)) {
14961
+ fs10.mkdirSync(this.directory, { recursive: true });
13531
14962
  }
13532
14963
  }
13533
14964
  keyForWorkspace(workspaceId) {
13534
- return path3.join(this.directory, `${workspaceId}.bundle`);
14965
+ return path4.join(this.directory, `${workspaceId}.bundle`);
13535
14966
  }
13536
14967
  downloadBundle(workspaceId, destPath) {
13537
14968
  const bundlePath = this.keyForWorkspace(workspaceId);
13538
- if (!fs9.existsSync(bundlePath)) {
14969
+ if (!fs10.existsSync(bundlePath)) {
13539
14970
  return false;
13540
14971
  }
13541
- const destDir = path3.dirname(destPath);
13542
- if (!fs9.existsSync(destDir)) {
13543
- fs9.mkdirSync(destDir, { recursive: true });
14972
+ const destDir = path4.dirname(destPath);
14973
+ if (!fs10.existsSync(destDir)) {
14974
+ fs10.mkdirSync(destDir, { recursive: true });
13544
14975
  }
13545
- fs9.copyFileSync(bundlePath, destPath);
14976
+ fs10.copyFileSync(bundlePath, destPath);
13546
14977
  return true;
13547
14978
  }
13548
14979
  uploadBundle(workspaceId, srcPath) {
13549
- if (!fs9.existsSync(srcPath)) {
14980
+ if (!fs10.existsSync(srcPath)) {
13550
14981
  throw new Error(`Source bundle not found: ${srcPath}`);
13551
14982
  }
13552
14983
  const bundlePath = this.keyForWorkspace(workspaceId);
13553
- const bundleDir = path3.dirname(bundlePath);
13554
- if (!fs9.existsSync(bundleDir)) {
13555
- fs9.mkdirSync(bundleDir, { recursive: true });
14984
+ const bundleDir = path4.dirname(bundlePath);
14985
+ if (!fs10.existsSync(bundleDir)) {
14986
+ fs10.mkdirSync(bundleDir, { recursive: true });
13556
14987
  }
13557
- fs9.copyFileSync(srcPath, bundlePath);
14988
+ fs10.copyFileSync(srcPath, bundlePath);
13558
14989
  }
13559
14990
  exists(workspaceId) {
13560
- return fs9.existsSync(this.keyForWorkspace(workspaceId));
14991
+ return fs10.existsSync(this.keyForWorkspace(workspaceId));
13561
14992
  }
13562
14993
  deleteBundle(workspaceId) {
13563
14994
  const bundlePath = this.keyForWorkspace(workspaceId);
13564
- if (fs9.existsSync(bundlePath)) {
13565
- fs9.unlinkSync(bundlePath);
14995
+ if (fs10.existsSync(bundlePath)) {
14996
+ fs10.unlinkSync(bundlePath);
13566
14997
  }
13567
14998
  }
13568
14999
  listWorkspaces() {
13569
- if (!fs9.existsSync(this.directory)) {
15000
+ if (!fs10.existsSync(this.directory)) {
13570
15001
  return [];
13571
15002
  }
13572
- return fs9.readdirSync(this.directory).filter((file) => file.endsWith(".bundle")).map((file) => file.replace(".bundle", ""));
15003
+ return fs10.readdirSync(this.directory).filter((file) => file.endsWith(".bundle")).map((file) => file.replace(".bundle", ""));
13573
15004
  }
13574
15005
  };
13575
15006
  MemoryBundleStore = class {
@@ -13582,18 +15013,18 @@ var init_bundle_store = __esm({
13582
15013
  if (!bundle) {
13583
15014
  return false;
13584
15015
  }
13585
- const destDir = path3.dirname(destPath);
13586
- if (!fs9.existsSync(destDir)) {
13587
- fs9.mkdirSync(destDir, { recursive: true });
15016
+ const destDir = path4.dirname(destPath);
15017
+ if (!fs10.existsSync(destDir)) {
15018
+ fs10.mkdirSync(destDir, { recursive: true });
13588
15019
  }
13589
- fs9.writeFileSync(destPath, bundle);
15020
+ fs10.writeFileSync(destPath, bundle);
13590
15021
  return true;
13591
15022
  }
13592
15023
  uploadBundle(workspaceId, srcPath) {
13593
- if (!fs9.existsSync(srcPath)) {
15024
+ if (!fs10.existsSync(srcPath)) {
13594
15025
  throw new Error(`Source bundle not found: ${srcPath}`);
13595
15026
  }
13596
- const content = fs9.readFileSync(srcPath);
15027
+ const content = fs10.readFileSync(srcPath);
13597
15028
  this.bundles.set(workspaceId, content);
13598
15029
  }
13599
15030
  exists(workspaceId) {
@@ -13684,21 +15115,21 @@ var init_supabase_store = __esm({
13684
15115
  if (!data) {
13685
15116
  return false;
13686
15117
  }
13687
- const dir = path3.dirname(destPath);
13688
- if (!fs9.existsSync(dir)) {
13689
- fs9.mkdirSync(dir, { recursive: true });
15118
+ const dir = path4.dirname(destPath);
15119
+ if (!fs10.existsSync(dir)) {
15120
+ fs10.mkdirSync(dir, { recursive: true });
13690
15121
  }
13691
15122
  const arrayBuffer = await data.arrayBuffer();
13692
- fs9.writeFileSync(destPath, Buffer.from(arrayBuffer));
15123
+ fs10.writeFileSync(destPath, Buffer.from(arrayBuffer));
13693
15124
  return true;
13694
15125
  }
13695
15126
  async uploadBundle(workspaceId, srcPath) {
13696
- if (!fs9.existsSync(srcPath)) {
15127
+ if (!fs10.existsSync(srcPath)) {
13697
15128
  throw new Error(`Source bundle not found: ${srcPath}`);
13698
15129
  }
13699
15130
  const client = await this.ensureClient();
13700
15131
  const key = this.keyForWorkspace(workspaceId);
13701
- const content = fs9.readFileSync(srcPath);
15132
+ const content = fs10.readFileSync(srcPath);
13702
15133
  const { error } = await client.storage.from(this.bucket).upload(key, content, {
13703
15134
  contentType: "application/octet-stream",
13704
15135
  upsert: true
@@ -13827,11 +15258,11 @@ var init_cloud_store = __esm({
13827
15258
  return false;
13828
15259
  }
13829
15260
  const bytes = await body.transformToByteArray();
13830
- const dir = path3.dirname(destPath);
13831
- if (!fs9.existsSync(dir)) {
13832
- fs9.mkdirSync(dir, { recursive: true });
15261
+ const dir = path4.dirname(destPath);
15262
+ if (!fs10.existsSync(dir)) {
15263
+ fs10.mkdirSync(dir, { recursive: true });
13833
15264
  }
13834
- fs9.writeFileSync(destPath, Buffer.from(bytes));
15265
+ fs10.writeFileSync(destPath, Buffer.from(bytes));
13835
15266
  return true;
13836
15267
  } catch (error) {
13837
15268
  const err = error;
@@ -13842,12 +15273,12 @@ var init_cloud_store = __esm({
13842
15273
  }
13843
15274
  }
13844
15275
  async uploadBundle(workspaceId, srcPath) {
13845
- if (!fs9.existsSync(srcPath)) {
15276
+ if (!fs10.existsSync(srcPath)) {
13846
15277
  throw new Error(`Source bundle not found: ${srcPath}`);
13847
15278
  }
13848
15279
  const { client, module } = await this.ensureClient();
13849
15280
  const key = this.keyForWorkspace(workspaceId);
13850
- const content = fs9.readFileSync(srcPath);
15281
+ const content = fs10.readFileSync(srcPath);
13851
15282
  await client.send(
13852
15283
  new module.PutObjectCommand({
13853
15284
  Bucket: this.bucket,
@@ -13949,9 +15380,9 @@ var init_cloud_store = __esm({
13949
15380
  if (!exists) {
13950
15381
  return false;
13951
15382
  }
13952
- const dir = path3.dirname(destPath);
13953
- if (!fs9.existsSync(dir)) {
13954
- fs9.mkdirSync(dir, { recursive: true });
15383
+ const dir = path4.dirname(destPath);
15384
+ if (!fs10.existsSync(dir)) {
15385
+ fs10.mkdirSync(dir, { recursive: true });
13955
15386
  }
13956
15387
  await file.download({ destination: destPath });
13957
15388
  return true;
@@ -13960,12 +15391,12 @@ var init_cloud_store = __esm({
13960
15391
  }
13961
15392
  }
13962
15393
  async uploadBundle(workspaceId, srcPath) {
13963
- if (!fs9.existsSync(srcPath)) {
15394
+ if (!fs10.existsSync(srcPath)) {
13964
15395
  throw new Error(`Source bundle not found: ${srcPath}`);
13965
15396
  }
13966
15397
  const storage = await this.ensureStorage();
13967
15398
  const key = this.keyForWorkspace(workspaceId);
13968
- const content = fs9.readFileSync(srcPath);
15399
+ const content = fs10.readFileSync(srcPath);
13969
15400
  const file = storage.bucket(this.bucket).file(key);
13970
15401
  await file.save(content);
13971
15402
  }
@@ -14064,25 +15495,25 @@ var init_file_store = __esm({
14064
15495
  /**
14065
15496
  * Generate the S3 key for a file
14066
15497
  */
14067
- keyForFile(sessionId, path14) {
14068
- const normalizedPath = path14.replace(/^\/+/, "");
15498
+ keyForFile(sessionId, path15) {
15499
+ const normalizedPath = path15.replace(/^\/+/, "");
14069
15500
  return `${this.prefix}${sessionId}/${normalizedPath}`;
14070
15501
  }
14071
- async writeFile(sessionId, path14, content) {
15502
+ async writeFile(sessionId, path15, content) {
14072
15503
  const { client, module } = await this.ensureClient();
14073
- const key = this.keyForFile(sessionId, path14);
15504
+ const key = this.keyForFile(sessionId, path15);
14074
15505
  await client.send(
14075
15506
  new module.PutObjectCommand({
14076
15507
  Bucket: this.bucket,
14077
15508
  Key: key,
14078
15509
  Body: content,
14079
- ContentType: this.getMimeType(path14)
15510
+ ContentType: this.getMimeType(path15)
14080
15511
  })
14081
15512
  );
14082
15513
  }
14083
- async readFile(sessionId, path14) {
15514
+ async readFile(sessionId, path15) {
14084
15515
  const { client, module } = await this.ensureClient();
14085
- const key = this.keyForFile(sessionId, path14);
15516
+ const key = this.keyForFile(sessionId, path15);
14086
15517
  try {
14087
15518
  const response = await client.send(
14088
15519
  new module.GetObjectCommand({
@@ -14104,9 +15535,9 @@ var init_file_store = __esm({
14104
15535
  throw error;
14105
15536
  }
14106
15537
  }
14107
- async exists(sessionId, path14) {
15538
+ async exists(sessionId, path15) {
14108
15539
  const { client, module } = await this.ensureClient();
14109
- const key = this.keyForFile(sessionId, path14);
15540
+ const key = this.keyForFile(sessionId, path15);
14110
15541
  try {
14111
15542
  await client.send(
14112
15543
  new module.HeadObjectCommand({
@@ -14123,9 +15554,9 @@ var init_file_store = __esm({
14123
15554
  throw error;
14124
15555
  }
14125
15556
  }
14126
- async deleteFile(sessionId, path14) {
15557
+ async deleteFile(sessionId, path15) {
14127
15558
  const { client, module } = await this.ensureClient();
14128
- const key = this.keyForFile(sessionId, path14);
15559
+ const key = this.keyForFile(sessionId, path15);
14129
15560
  await client.send(
14130
15561
  new module.DeleteObjectCommand({
14131
15562
  Bucket: this.bucket,
@@ -14180,30 +15611,30 @@ var init_file_store = __esm({
14180
15611
  );
14181
15612
  }
14182
15613
  }
14183
- async getSignedUrl(sessionId, path14, expiresIn = 3600) {
15614
+ async getSignedUrl(sessionId, path15, expiresIn = 3600) {
14184
15615
  const { client, module, presigner } = await this.ensureClient();
14185
- const key = this.keyForFile(sessionId, path14);
15616
+ const key = this.keyForFile(sessionId, path15);
14186
15617
  const command = new module.GetObjectCommand({
14187
15618
  Bucket: this.bucket,
14188
15619
  Key: key
14189
15620
  });
14190
15621
  return presigner.getSignedUrl(client, command, { expiresIn });
14191
15622
  }
14192
- async getUploadUrl(sessionId, path14, expiresIn = 3600) {
15623
+ async getUploadUrl(sessionId, path15, expiresIn = 3600) {
14193
15624
  const { client, module, presigner } = await this.ensureClient();
14194
- const key = this.keyForFile(sessionId, path14);
15625
+ const key = this.keyForFile(sessionId, path15);
14195
15626
  const command = new module.PutObjectCommand({
14196
15627
  Bucket: this.bucket,
14197
15628
  Key: key,
14198
- ContentType: this.getMimeType(path14)
15629
+ ContentType: this.getMimeType(path15)
14199
15630
  });
14200
15631
  return presigner.getSignedUrl(client, command, { expiresIn });
14201
15632
  }
14202
15633
  /**
14203
15634
  * Get MIME type based on file extension
14204
15635
  */
14205
- getMimeType(path14) {
14206
- const ext = path14.split(".").pop()?.toLowerCase();
15636
+ getMimeType(path15) {
15637
+ const ext = path15.split(".").pop()?.toLowerCase();
14207
15638
  const mimeTypes = {
14208
15639
  // Text
14209
15640
  txt: "text/plain",
@@ -14258,8 +15689,8 @@ var init_local_sandbox = __esm({
14258
15689
  this.defaultRepoRoot = config.defaultRepoRoot ?? `${this.workingDirectory}/repo`;
14259
15690
  this.env = { ...process.env, ...config.env };
14260
15691
  this.timeout = config.timeout ?? 3e4;
14261
- if (!fs9.existsSync(this.workingDirectory)) {
14262
- fs9.mkdirSync(this.workingDirectory, { recursive: true });
15692
+ if (!fs10.existsSync(this.workingDirectory)) {
15693
+ fs10.mkdirSync(this.workingDirectory, { recursive: true });
14263
15694
  }
14264
15695
  }
14265
15696
  runCommand(command) {
@@ -14288,51 +15719,51 @@ var init_local_sandbox = __esm({
14288
15719
  }
14289
15720
  }
14290
15721
  uploadFile(localPath, remotePath) {
14291
- const dir = path3.dirname(remotePath);
14292
- if (!fs9.existsSync(dir)) {
14293
- fs9.mkdirSync(dir, { recursive: true });
15722
+ const dir = path4.dirname(remotePath);
15723
+ if (!fs10.existsSync(dir)) {
15724
+ fs10.mkdirSync(dir, { recursive: true });
14294
15725
  }
14295
- fs9.copyFileSync(localPath, remotePath);
15726
+ fs10.copyFileSync(localPath, remotePath);
14296
15727
  }
14297
15728
  downloadFile(remotePath, localPath) {
14298
- const dir = path3.dirname(localPath);
14299
- if (!fs9.existsSync(dir)) {
14300
- fs9.mkdirSync(dir, { recursive: true });
15729
+ const dir = path4.dirname(localPath);
15730
+ if (!fs10.existsSync(dir)) {
15731
+ fs10.mkdirSync(dir, { recursive: true });
14301
15732
  }
14302
- fs9.copyFileSync(remotePath, localPath);
15733
+ fs10.copyFileSync(remotePath, localPath);
14303
15734
  }
14304
15735
  uploadDirectory(localPath, remotePath, options) {
14305
15736
  const exclude = options?.exclude ?? /* @__PURE__ */ new Set();
14306
- if (!fs9.existsSync(remotePath)) {
14307
- fs9.mkdirSync(remotePath, { recursive: true });
15737
+ if (!fs10.existsSync(remotePath)) {
15738
+ fs10.mkdirSync(remotePath, { recursive: true });
14308
15739
  }
14309
15740
  const copyRecursive = (src, dest) => {
14310
- const entries = fs9.readdirSync(src, { withFileTypes: true });
15741
+ const entries = fs10.readdirSync(src, { withFileTypes: true });
14311
15742
  for (const entry of entries) {
14312
15743
  if (exclude.has(entry.name)) continue;
14313
- const srcPath = path3.join(src, entry.name);
14314
- const destPath = path3.join(dest, entry.name);
15744
+ const srcPath = path4.join(src, entry.name);
15745
+ const destPath = path4.join(dest, entry.name);
14315
15746
  if (entry.isDirectory()) {
14316
- if (!fs9.existsSync(destPath)) {
14317
- fs9.mkdirSync(destPath, { recursive: true });
15747
+ if (!fs10.existsSync(destPath)) {
15748
+ fs10.mkdirSync(destPath, { recursive: true });
14318
15749
  }
14319
15750
  copyRecursive(srcPath, destPath);
14320
15751
  } else if (entry.isFile()) {
14321
- fs9.copyFileSync(srcPath, destPath);
15752
+ fs10.copyFileSync(srcPath, destPath);
14322
15753
  }
14323
15754
  }
14324
15755
  };
14325
15756
  copyRecursive(localPath, remotePath);
14326
15757
  }
14327
15758
  readFile(remotePath) {
14328
- return fs9.readFileSync(remotePath, "utf-8");
15759
+ return fs10.readFileSync(remotePath, "utf-8");
14329
15760
  }
14330
15761
  writeFile(remotePath, content) {
14331
- const dir = path3.dirname(remotePath);
14332
- if (!fs9.existsSync(dir)) {
14333
- fs9.mkdirSync(dir, { recursive: true });
15762
+ const dir = path4.dirname(remotePath);
15763
+ if (!fs10.existsSync(dir)) {
15764
+ fs10.mkdirSync(dir, { recursive: true });
14334
15765
  }
14335
- fs9.writeFileSync(remotePath, content, "utf-8");
15766
+ fs10.writeFileSync(remotePath, content, "utf-8");
14336
15767
  }
14337
15768
  close() {
14338
15769
  }
@@ -14340,8 +15771,8 @@ var init_local_sandbox = __esm({
14340
15771
  * Clean up the working directory
14341
15772
  */
14342
15773
  cleanup() {
14343
- if (fs9.existsSync(this.workingDirectory)) {
14344
- fs9.rmSync(this.workingDirectory, { recursive: true, force: true });
15774
+ if (fs10.existsSync(this.workingDirectory)) {
15775
+ fs10.rmSync(this.workingDirectory, { recursive: true, force: true });
14345
15776
  }
14346
15777
  }
14347
15778
  };
@@ -14451,33 +15882,33 @@ var init_provider_sandbox = __esm({
14451
15882
  }
14452
15883
  async uploadFile(localPath, remotePath) {
14453
15884
  await this.initialize();
14454
- const content = fs9.readFileSync(localPath);
15885
+ const content = fs10.readFileSync(localPath);
14455
15886
  await this.provider.writeFile(this.getSandboxId(), remotePath, content);
14456
15887
  }
14457
15888
  async downloadFile(remotePath, localPath) {
14458
15889
  await this.initialize();
14459
15890
  const content = await this.provider.readFile(this.getSandboxId(), remotePath);
14460
- const dir = path3.dirname(localPath);
14461
- if (!fs9.existsSync(dir)) {
14462
- fs9.mkdirSync(dir, { recursive: true });
15891
+ const dir = path4.dirname(localPath);
15892
+ if (!fs10.existsSync(dir)) {
15893
+ fs10.mkdirSync(dir, { recursive: true });
14463
15894
  }
14464
- fs9.writeFileSync(localPath, content);
15895
+ fs10.writeFileSync(localPath, content);
14465
15896
  }
14466
15897
  async uploadDirectory(localPath, remotePath, options) {
14467
15898
  await this.initialize();
14468
15899
  const exclude = options?.exclude ?? /* @__PURE__ */ new Set();
14469
15900
  await this.runCommand(`mkdir -p '${remotePath}'`);
14470
15901
  const uploadRecursive = async (src, dest) => {
14471
- const entries = fs9.readdirSync(src, { withFileTypes: true });
15902
+ const entries = fs10.readdirSync(src, { withFileTypes: true });
14472
15903
  for (const entry of entries) {
14473
15904
  if (exclude.has(entry.name)) continue;
14474
- const srcPath = path3.join(src, entry.name);
15905
+ const srcPath = path4.join(src, entry.name);
14475
15906
  const destPath = `${dest}/${entry.name}`;
14476
15907
  if (entry.isDirectory()) {
14477
15908
  await this.runCommand(`mkdir -p '${destPath}'`);
14478
15909
  await uploadRecursive(srcPath, destPath);
14479
15910
  } else if (entry.isFile()) {
14480
- const content = fs9.readFileSync(srcPath);
15911
+ const content = fs10.readFileSync(srcPath);
14481
15912
  await this.provider.writeFile(this.getSandboxId(), destPath, content);
14482
15913
  }
14483
15914
  }
@@ -14715,16 +16146,19 @@ __export(schema_exports, {
14715
16146
  configDeploymentTriggerEnum: () => configDeploymentTriggerEnum,
14716
16147
  configDeployments: () => configDeployments,
14717
16148
  configDeploymentsRelations: () => configDeploymentsRelations,
16149
+ eventCategoryEnum: () => eventCategoryEnum,
14718
16150
  messageRoleEnum: () => messageRoleEnum,
14719
16151
  messages: () => messages,
14720
16152
  messagesRelations: () => messagesRelations,
14721
16153
  queueItemStatusEnum: () => queueItemStatusEnum,
14722
16154
  queueItems: () => queueItems,
16155
+ sessionEvents: () => sessionEvents,
16156
+ sessionEventsRelations: () => sessionEventsRelations,
14723
16157
  sessionStatusEnum: () => sessionStatusEnum,
14724
16158
  sessions: () => sessions,
14725
16159
  sessionsRelations: () => sessionsRelations
14726
16160
  });
14727
- var sessionStatusEnum, messageRoleEnum, agentStatusEnum, agentBackendEnum, queueItemStatusEnum, configDeploymentStatusEnum, configDeploymentTriggerEnum, sessions, messages, attachments, agents, queueItems, configDeployments, sessionsRelations, messagesRelations, attachmentsRelations, configDeploymentsRelations, agentsRelations;
16161
+ var sessionStatusEnum, messageRoleEnum, agentStatusEnum, agentBackendEnum, queueItemStatusEnum, configDeploymentStatusEnum, configDeploymentTriggerEnum, eventCategoryEnum, sessions, messages, attachments, agents, queueItems, configDeployments, sessionEvents, sessionsRelations, sessionEventsRelations, messagesRelations, attachmentsRelations, configDeploymentsRelations, agentsRelations;
14728
16162
  var init_schema = __esm({
14729
16163
  "src/storage-postgres/schema.ts"() {
14730
16164
  sessionStatusEnum = pgEnum("session_status", [
@@ -14766,6 +16200,22 @@ var init_schema = __esm({
14766
16200
  "initial",
14767
16201
  "rollback"
14768
16202
  ]);
16203
+ eventCategoryEnum = pgEnum("event_category", [
16204
+ "lifecycle",
16205
+ // session_start, session_end, turn_complete
16206
+ "content",
16207
+ // text_stream (aggregated), thinking_stream
16208
+ "tool",
16209
+ // tool_use, tool_result
16210
+ "system",
16211
+ // mcp_status, sandbox_log
16212
+ "error",
16213
+ // error events
16214
+ "file",
16215
+ // file_push, file_pull, file_sync (file sync operations)
16216
+ "input"
16217
+ // user_input (user prompts/messages)
16218
+ ]);
14769
16219
  sessions = pgTable(
14770
16220
  "sessions",
14771
16221
  {
@@ -14921,6 +16371,32 @@ var init_schema = __esm({
14921
16371
  index("idx_config_deployments_created").on(table.agentId, table.createdAt)
14922
16372
  ]
14923
16373
  );
16374
+ sessionEvents = pgTable(
16375
+ "session_events",
16376
+ {
16377
+ id: uuid("id").defaultRandom().primaryKey(),
16378
+ sessionId: uuid("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
16379
+ eventType: text("event_type").notNull(),
16380
+ category: eventCategoryEnum("category").notNull(),
16381
+ startedAt: timestamp("started_at", { withTimezone: true }).notNull(),
16382
+ endedAt: timestamp("ended_at", { withTimezone: true }),
16383
+ durationMs: integer("duration_ms"),
16384
+ eventData: jsonb("event_data").default({}).$type(),
16385
+ // Tool-specific fields
16386
+ toolUseId: text("tool_use_id"),
16387
+ toolName: text("tool_name"),
16388
+ // Aggregation fields (for text_stream events)
16389
+ isAggregated: boolean("is_aggregated").default(false),
16390
+ aggregatedCount: integer("aggregated_count"),
16391
+ // Ordering
16392
+ sequenceNumber: integer("sequence_number").notNull(),
16393
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
16394
+ },
16395
+ (table) => [
16396
+ index("idx_session_events_session").on(table.sessionId, table.sequenceNumber),
16397
+ index("idx_session_events_category").on(table.sessionId, table.category)
16398
+ ]
16399
+ );
14924
16400
  sessionsRelations = relations(sessions, ({ one, many }) => ({
14925
16401
  parentSession: one(sessions, {
14926
16402
  fields: [sessions.parentSessionId],
@@ -14930,7 +16406,14 @@ var init_schema = __esm({
14930
16406
  childSessions: many(sessions, {
14931
16407
  relationName: "parentSession"
14932
16408
  }),
14933
- messages: many(messages)
16409
+ messages: many(messages),
16410
+ events: many(sessionEvents)
16411
+ }));
16412
+ sessionEventsRelations = relations(sessionEvents, ({ one }) => ({
16413
+ session: one(sessions, {
16414
+ fields: [sessionEvents.sessionId],
16415
+ references: [sessions.id]
16416
+ })
14934
16417
  }));
14935
16418
  messagesRelations = relations(messages, ({ one, many }) => ({
14936
16419
  session: one(sessions, {
@@ -16469,6 +17952,92 @@ var init_storage3 = __esm({
16469
17952
  executionWebhookSecret: row.execution_webhook_secret ?? void 0
16470
17953
  };
16471
17954
  }
17955
+ rowToSessionEvent(row) {
17956
+ return {
17957
+ id: row.id,
17958
+ sessionId: row.session_id,
17959
+ eventType: row.event_type,
17960
+ category: row.category,
17961
+ startedAt: new Date(row.started_at),
17962
+ endedAt: row.ended_at ? new Date(row.ended_at) : void 0,
17963
+ durationMs: row.duration_ms ?? void 0,
17964
+ eventData: row.event_data ?? void 0,
17965
+ toolUseId: row.tool_use_id ?? void 0,
17966
+ toolName: row.tool_name ?? void 0,
17967
+ isAggregated: row.is_aggregated ?? void 0,
17968
+ aggregatedCount: row.aggregated_count ?? void 0,
17969
+ sequenceNumber: row.sequence_number,
17970
+ createdAt: new Date(row.created_at)
17971
+ };
17972
+ }
17973
+ // ============================================================================
17974
+ // Events
17975
+ // ============================================================================
17976
+ async saveEvents(sessionId, events) {
17977
+ if (events.length === 0) return [];
17978
+ const { data, error } = await this.client.from("session_events").insert(
17979
+ events.map((event) => ({
17980
+ session_id: sessionId,
17981
+ event_type: event.eventType,
17982
+ category: event.category,
17983
+ started_at: event.startedAt.toISOString(),
17984
+ ended_at: event.endedAt?.toISOString() ?? null,
17985
+ duration_ms: event.durationMs ?? null,
17986
+ event_data: event.eventData ?? {},
17987
+ tool_use_id: event.toolUseId ?? null,
17988
+ tool_name: event.toolName ?? null,
17989
+ is_aggregated: event.isAggregated ?? false,
17990
+ aggregated_count: event.aggregatedCount ?? null,
17991
+ sequence_number: event.sequenceNumber
17992
+ }))
17993
+ ).select();
17994
+ if (error) {
17995
+ throw new Error(`Failed to save events: ${error.message}`);
17996
+ }
17997
+ return data.map((row) => this.rowToSessionEvent(row));
17998
+ }
17999
+ async getSessionEvents(sessionId, options = {}) {
18000
+ const limit = options.limit ?? 100;
18001
+ const offset = options.offset ?? 0;
18002
+ let query = this.client.from("session_events").select("*", { count: "exact" }).eq("session_id", sessionId);
18003
+ if (options.category) {
18004
+ query = query.eq("category", options.category);
18005
+ }
18006
+ if (options.eventType) {
18007
+ query = query.eq("event_type", options.eventType);
18008
+ }
18009
+ if (options.filePath) {
18010
+ query = query.or(
18011
+ `event_data->>filePath.eq.${options.filePath},and(category.eq.tool,tool_name.in.(Read,Write,Edit),event_data->input->>file_path.eq.${options.filePath})`
18012
+ );
18013
+ }
18014
+ const ascending = options.order !== "desc";
18015
+ query = query.order("sequence_number", { ascending }).range(offset, offset + limit - 1);
18016
+ const { data, error, count } = await query;
18017
+ if (error) {
18018
+ throw new Error(`Failed to get session events: ${error.message}`);
18019
+ }
18020
+ const total = count ?? 0;
18021
+ return {
18022
+ items: data.map((row) => this.rowToSessionEvent(row)),
18023
+ total,
18024
+ hasMore: offset + limit < total,
18025
+ nextCursor: offset + limit < total ? String(offset + limit) : void 0
18026
+ };
18027
+ }
18028
+ async deleteSessionEvents(sessionId) {
18029
+ const { error } = await this.client.from("session_events").delete().eq("session_id", sessionId);
18030
+ if (error) {
18031
+ throw new Error(`Failed to delete session events: ${error.message}`);
18032
+ }
18033
+ }
18034
+ async getNextEventSequence(sessionId) {
18035
+ const { data, error } = await this.client.from("session_events").select("sequence_number").eq("session_id", sessionId).order("sequence_number", { ascending: false }).limit(1).single();
18036
+ if (error && error.code !== "PGRST116") {
18037
+ throw new Error(`Failed to get next event sequence: ${error.message}`);
18038
+ }
18039
+ return data ? data.sequence_number + 1 : 1;
18040
+ }
16472
18041
  };
16473
18042
  }
16474
18043
  });
@@ -16491,8 +18060,8 @@ var init_client = __esm({
16491
18060
  /**
16492
18061
  * Make an authenticated request to the Ash Cloud API
16493
18062
  */
16494
- async request(method, path14, options) {
16495
- const url = new URL(`${this.baseUrl}${path14}`);
18063
+ async request(method, path15, options) {
18064
+ const url = new URL(`${this.baseUrl}${path15}`);
16496
18065
  if (options?.query) {
16497
18066
  for (const [key, value] of Object.entries(options.query)) {
16498
18067
  if (value !== void 0) {
@@ -16560,8 +18129,8 @@ var init_client = __esm({
16560
18129
  /**
16561
18130
  * Make a streaming request (for SSE endpoints)
16562
18131
  */
16563
- async *stream(method, path14, options) {
16564
- const url = `${this.baseUrl}${path14}`;
18132
+ async *stream(method, path15, options) {
18133
+ const url = `${this.baseUrl}${path15}`;
16565
18134
  const headers = {
16566
18135
  Authorization: `Bearer ${this.apiKey}`,
16567
18136
  "Content-Type": "application/json",
@@ -17323,6 +18892,6 @@ var init_src = __esm({
17323
18892
  });
17324
18893
  init_src();
17325
18894
 
17326
- export { AVAILABLE_MODELS, AgentConfigSchema, AgentError, AgentHarness, AgentStatus, AshCloud, AshCloudApiError, AshCloudClient, AttachmentConfigSchema, AttachmentStorage, ClaudeSdkClient, CloudSandbox, CloudStorage, ConfigBuilder, ConfigError, CredentialManager, DEFAULT_MODELS, DEFAULT_SANDBOX_PROVIDER_CONFIG, GCSBundleStore, GeminiCliClient, GitHubFileProvider, HarnessConfigSchema, HarnessError, HarnessErrorCode, HarnessEventEmitter, LocalBundleStore, LocalFileProvider, LocalSandbox, McpConfigBuilder, McpPresets, McpServers, MemoryBundleStore, MemoryCredentialStorage, MemoryQueueStorage, MemoryRateLimitStore, MemoryStorage, MessageRole, NotFoundError, PostgresQueueStorage, PostgresStorage, ProviderSandbox, QueueItemStatus, QueueProcessor, RuntimeConfigBuilder, RuntimePresets, S3BundleStore, S3FileStore, SENSITIVE_PATHS, SandboxFileSync, SandboxGitRepo, SandboxLogger, SandboxPool, ServerConfigSchema, SessionError, SessionManager, SessionStatus, SkillCatalog, SkillManager, StorageConfigSchema, StorageError, StreamEventType, SupabaseBundleStore, SupabaseStorage, ToolCallProcessor, ToolError, ValidationError, Workspace, WorkspaceManager, attachmentSchema, attachmentToDataUrl, checkSecurityConfig, claudeClient, cleanupAllSandboxes, configureMcp, configureRuntime, convertClaudeMessage, createAgentsRouter, createAshCloud, createBackendExecutor, createCloudSandbox, createConfig, createCredentialManager, createOpenAPIServer as createDocumentedServer, createE2BSandbox, createEventHandler, createEventMiddlewareChain, createGCSBundleStore, createGeminiExecutor, createGitHubFileProvider, createGitRepo, createHarnessServer, createLocalBundleStore, createLocalFileProvider, createLocalSandbox, createLogger, createMemoryBundleStore, createMinioBundleStore, createMinioFileStore, createModalSandbox, createAgentsRouter2 as createOpenAPIAgentsRouter, createOpenAPIServer, createSessionsRouter2 as createOpenAPISessionsRouter, createSkillsRouter2 as createOpenAPISkillsRouter, createProviderSandbox, createQueueProcessor, createQueueRouter, createR2BundleStore, createR2FileStore, createS3BundleStore, createS3FileStore, createSandboxFileOperations, createSandboxFileSync, createSandboxLogger, createSandboxOptions, createSessionWorkspace, createSessionsRouter, createSkillCatalog, createSkillManager, createSupabaseBundleStore, createSupabaseBundleStoreFromEnv, createToolCall, createToolCallProcessor, createVercelSandbox, createVercelSandboxExecutor, createWorkspace, createWorkspaceHooks, createWorkspaceManager, dataUrlToBuffer, defineAgent, defineConfig, ensureSandboxPoolInitialized, env, envOptional, extractTextContent, extractTextFromMessage, fileEntrySchema, formatToolName, generateDockerCommand, generateMcpServerPackage, generateMcpServers, generateProxyEnv, generateToolSummary, getActionIcon, getActionLabel, getAllHeartbeatStatuses, getApiKeyEnvVar, getDefaultModel, getHeartbeatStatus, getOrCreateSandbox, getSandboxCacheStats, getSandboxPool, getWorkspaceManager, gitHubSkillSourceSchema, globalEventEmitter, hasErrorCode, hashStartupScript, httpMcpWithAuth, initializeSandboxPool, introspectMcpServer, invalidateSandbox, isCommandRunAction, isDocumentMimeType, isErrorEntry, isFileEditAction, isFileReadAction, isFileWriteAction, isGenericToolAction, isGlobAction, isHarnessError, isHttpMcpConfig, isImageMimeType, isMcpToolAction, isSandboxExpiredError, isSandboxRunning, isSearchAction, isSensitivePath, isStdioMcpConfig, isTodoWriteAction, isToolCallEntry, isValidModel, isWebFetchAction, isWebSearchAction, listFilesInSandbox, loadConfig, loadGitHubSkill, loadGitHubSkills, loadWorkspaceState, localSkillSourceSchema, log, mapClaudeOptionsToGemini, mapToolToActionType, markConfigInstalled, markSdkInstalled, markStartupScriptRan, mcpAuthToHeaders, messageContentSchema, messageSchema, needsStartupScriptRerun, normalizeGitHubConfigs, normalizeMcpServers, normalizeMessages, normalizeToolResult, onHeartbeat, schemas_exports as openApiSchemas, parseCommandResult, parseGitHubUrl, parseMcpToolName, processStreamEvents, rateLimit, rateLimiters, readFileFromSandbox, rekeySessionId, releaseSandbox, requestLogger, saveWorkspaceState, schema_exports as schema, sessionSchema, shouldUseSandbox, shutdownSandboxPool, skillConfigSchema, skillSourceSchema, sseMcpWithAuth, startServer, updateToolCallWithResult, writeFileToSandbox };
18895
+ export { AVAILABLE_MODELS, AgentConfigSchema, AgentError, AgentHarness, AgentStatus, AshCloud, AshCloudApiError, AshCloudClient, AttachmentConfigSchema, AttachmentStorage, ClaudeSdkClient, CloudSandbox, CloudStorage, ConfigBuilder, ConfigError, CredentialManager, DEFAULT_MODELS, DEFAULT_SANDBOX_PROVIDER_CONFIG, EventCategory, FileWatcherManager, GCSBundleStore, GeminiCliClient, GitHubFileProvider, HarnessConfigSchema, HarnessError, HarnessErrorCode, HarnessEventEmitter, InSandboxWatcher, InSandboxWatcherManager, LocalBundleStore, LocalFileProvider, LocalSandbox, McpConfigBuilder, McpPresets, McpServers, MemoryBundleStore, MemoryCredentialStorage, MemoryQueueStorage, MemoryRateLimitStore, MemoryStorage, MessageRole, NotFoundError, PostgresQueueStorage, PostgresStorage, ProviderSandbox, QueueItemStatus, QueueProcessor, RemoteFileWatcherManager, RemoteSandboxFileWatcher, RuntimeConfigBuilder, RuntimePresets, S3BundleStore, S3FileStore, SENSITIVE_PATHS, SandboxFileSync, SandboxFileWatcher, SandboxGitRepo, SandboxLogger, SandboxPool, ServerConfigSchema, SessionError, SessionManager, SessionStatus, SkillCatalog, SkillManager, StorageConfigSchema, StorageError, StreamEventType, SupabaseBundleStore, SupabaseStorage, ToolCallProcessor, ToolError, ValidationError, Workspace, WorkspaceManager, attachmentSchema, attachmentToDataUrl, checkSecurityConfig, claudeClient, cleanupAllSandboxes, configureMcp, configureRuntime, convertClaudeMessage, createAgentsRouter, createAshCloud, createBackendExecutor, createCloudSandbox, createConfig, createCredentialManager, createOpenAPIServer as createDocumentedServer, createE2BSandbox, createEventHandler, createEventMiddlewareChain, createFileWatcher, createFileWatcherManager, createGCSBundleStore, createGeminiExecutor, createGitHubFileProvider, createGitRepo, createHarnessServer, createInSandboxWatcher, createInSandboxWatcherManager, createLocalBundleStore, createLocalFileProvider, createLocalSandbox, createLogger, createMemoryBundleStore, createMinioBundleStore, createMinioFileStore, createModalSandbox, createAgentsRouter2 as createOpenAPIAgentsRouter, createOpenAPIServer, createSessionsRouter2 as createOpenAPISessionsRouter, createSkillsRouter2 as createOpenAPISkillsRouter, createProviderSandbox, createQueueProcessor, createQueueRouter, createR2BundleStore, createR2FileStore, createRemoteFileWatcher, createRemoteFileWatcherManager, createS3BundleStore, createS3FileStore, createSandboxFileOperations, createSandboxFileSync, createSandboxLogger, createSandboxOptions, createSessionWorkspace, createSessionsRouter, createSkillCatalog, createSkillManager, createSupabaseBundleStore, createSupabaseBundleStoreFromEnv, createToolCall, createToolCallProcessor, createVercelSandbox, createVercelSandboxExecutor, createWorkspace, createWorkspaceHooks, createWorkspaceManager, dataUrlToBuffer, defineAgent, defineConfig, ensureSandboxPoolInitialized, env, envOptional, extractTextContent, extractTextFromMessage, fileEntrySchema, formatToolName, generateDockerCommand, generateMcpServerPackage, generateMcpServers, generateProxyEnv, generateToolSummary, getActionIcon, getActionLabel, getAllHeartbeatStatuses, getApiKeyEnvVar, getDefaultModel, getFileWatcherManager, getHeartbeatStatus, getInSandboxWatcherManager, getOrCreateSandbox, getRemoteFileWatcherManager, getSandboxCacheStats, getSandboxPool, getWorkspaceManager, gitHubSkillSourceSchema, globalEventEmitter, hasErrorCode, hashStartupScript, httpMcpWithAuth, initializeSandboxPool, introspectMcpServer, invalidateSandbox, isCommandRunAction, isDocumentMimeType, isErrorEntry, isFileEditAction, isFileReadAction, isFileWriteAction, isGenericToolAction, isGlobAction, isHarnessError, isHttpMcpConfig, isImageMimeType, isMcpToolAction, isSandboxExpiredError, isSandboxRunning, isSearchAction, isSensitivePath, isStdioMcpConfig, isTodoWriteAction, isToolCallEntry, isValidModel, isWebFetchAction, isWebSearchAction, listFilesInSandbox, loadConfig, loadGitHubSkill, loadGitHubSkills, loadWorkspaceState, localSkillSourceSchema, log, mapClaudeOptionsToGemini, mapToolToActionType, markConfigInstalled, markSdkInstalled, markStartupScriptRan, mcpAuthToHeaders, messageContentSchema, messageSchema, needsStartupScriptRerun, normalizeGitHubConfigs, normalizeMcpServers, normalizeMessages, normalizeToolResult, onHeartbeat, schemas_exports as openApiSchemas, parseCommandResult, parseGitHubUrl, parseMcpToolName, processStreamEvents, rateLimit, rateLimiters, readFileFromSandbox, rekeySessionId, releaseSandbox, requestLogger, saveWorkspaceState, schema_exports as schema, sessionSchema, shouldUseSandbox, shutdownSandboxPool, skillConfigSchema, skillSourceSchema, sseMcpWithAuth, startServer, updateToolCallWithResult, writeFileToSandbox };
17327
18896
  //# sourceMappingURL=index.js.map
17328
18897
  //# sourceMappingURL=index.js.map