@agentrix/cli 0.0.11 → 0.0.13

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.
@@ -6,8 +6,7 @@ var chalk = require('chalk');
6
6
  var shared = require('@agentrix/shared');
7
7
  var node_crypto = require('node:crypto');
8
8
  var axios = require('axios');
9
- var _package = require('./logger-BZKdzrRM.cjs');
10
- var fs$1 = require('node:fs');
9
+ var _package = require('./logger-DzYRcKN1.cjs');
11
10
  var node_readline = require('node:readline');
12
11
  var fs = require('fs');
13
12
  var path = require('path');
@@ -15,21 +14,24 @@ var os = require('node:os');
15
14
  var open = require('open');
16
15
  var socket_ioClient = require('socket.io-client');
17
16
  var node_events = require('node:events');
17
+ var fs$1 = require('node:fs');
18
18
  var path$1 = require('node:path');
19
19
  var child_process = require('child_process');
20
20
  var psList = require('ps-list');
21
21
  var spawn = require('cross-spawn');
22
+ var sandboxRuntime = require('@xmz-ai/sandbox-runtime');
23
+ var platform_js = require('@xmz-ai/sandbox-runtime/dist/utils/platform.js');
22
24
  var fastify = require('fastify');
23
25
  var zod = require('zod');
24
26
  var fastifyTypeProviderZod = require('fastify-type-provider-zod');
25
27
  var require$$7 = require('url');
28
+ var promises = require('node:stream/promises');
26
29
  var claudeAgentSdk = require('@anthropic-ai/claude-agent-sdk');
27
30
  var simpleGit = require('simple-git');
28
- var promises = require('node:fs/promises');
31
+ var promises$1 = require('node:fs/promises');
29
32
  var require$$1 = require('crypto');
30
33
  var codexSdk = require('@openai/codex-sdk');
31
34
  var v3 = require('zod/v3');
32
- var promises$1 = require('node:stream/promises');
33
35
 
34
36
  function _interopNamespaceDefault(e) {
35
37
  var n = Object.create(null);
@@ -64,22 +66,12 @@ async function daemonPost(path, body) {
64
66
  error: errorMessage
65
67
  };
66
68
  }
67
- try {
68
- process.kill(state.pid, 0);
69
- } catch (error) {
70
- const errorMessage = "Daemon is not running, file is stale";
71
- _package.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
72
- return {
73
- error: errorMessage
74
- };
75
- }
76
69
  try {
77
70
  const timeout = process.env.AGENTRIX_DAEMON_HTTP_TIMEOUT ? parseInt(process.env.AGENTRIX_DAEMON_HTTP_TIMEOUT) : 1e4;
78
- const response = await fetch(`http://127.0.0.1:${state.port}${path}`, {
71
+ const response = await fetch(`http://agentrix-local.xmz.ai:${state.port}${path}`, {
79
72
  method: "POST",
80
73
  headers: { "Content-Type": "application/json" },
81
74
  body: JSON.stringify(body || {}),
82
- // Mostly increased for stress test
83
75
  signal: AbortSignal.timeout(timeout)
84
76
  });
85
77
  if (!response.ok) {
@@ -225,11 +217,8 @@ async function handleAuthLogout() {
225
217
  console.log(chalk.gray("Stopped daemon"));
226
218
  } catch {
227
219
  }
228
- const paths = _package.machine.getStatePaths();
229
- if (fs$1.existsSync(paths.rootDir)) {
230
- fs$1.rmSync(paths.rootDir, { recursive: true, force: true });
231
- console.log(chalk.gray(`Removed agentrix home directory`));
232
- }
220
+ await _package.machine.clearCredentials();
221
+ console.log(chalk.gray(`Removed credentials`));
233
222
  console.log(chalk.green("\u2713 Successfully logged out"));
234
223
  } catch (error) {
235
224
  throw new Error(`Failed to logout: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -11885,7 +11874,7 @@ async function findAllAgentrixProcesses() {
11885
11874
  async function findRunawayAgentrixProcesses() {
11886
11875
  const allProcesses = await findAllAgentrixProcesses();
11887
11876
  return allProcesses.filter(
11888
- (p) => p.pid !== process.pid && (p.type === "daemon" || p.type === "worker")
11877
+ (p) => p.pid !== process.pid && (p.type === "daemon" || p.type === "worker" || p.type === "upgrade-daemon")
11889
11878
  ).map((p) => ({ pid: p.pid, command: p.command }));
11890
11879
  }
11891
11880
  async function killRunawayAgentrixProcesses() {
@@ -11987,6 +11976,27 @@ async function runDoctorCommand(filter) {
11987
11976
  } catch (error) {
11988
11977
  console.log(chalk.red("\u274C Error reading credentials"));
11989
11978
  }
11979
+ console.log(chalk.bold("\n\u{1F512} Sandbox Dependencies"));
11980
+ const platform = platform_js.getPlatform();
11981
+ if (sandboxRuntime.isSupportedPlatform(platform)) {
11982
+ console.log(`Platform: ${chalk.green(platform)} (supported)`);
11983
+ const depsOk = sandboxRuntime.checkSandboxDependencies();
11984
+ if (depsOk) {
11985
+ console.log(chalk.green("\u2713 All sandbox dependencies available"));
11986
+ } else {
11987
+ console.log(chalk.yellow("\u26A0\uFE0F Some sandbox dependencies missing"));
11988
+ if (platform === "linux") {
11989
+ console.log(chalk.gray(" Required: bubblewrap, socat"));
11990
+ console.log(chalk.gray(" Install: sudo apt install bubblewrap socat"));
11991
+ } else if (platform === "macos") {
11992
+ console.log(chalk.gray(" Required: ripgrep"));
11993
+ console.log(chalk.gray(" Install: brew install ripgrep"));
11994
+ }
11995
+ }
11996
+ } else {
11997
+ console.log(`Platform: ${chalk.yellow(platform)} (not supported)`);
11998
+ console.log(chalk.gray(" \u26A0\uFE0F Sandbox will be disabled"));
11999
+ }
11990
12000
  }
11991
12001
  console.log(chalk.bold("\n\u{1F916} Daemon Status"));
11992
12002
  try {
@@ -12211,9 +12221,11 @@ function createPromiseWithTimeout(options) {
12211
12221
  class TaskWorkerManager {
12212
12222
  pidToTrackedSession;
12213
12223
  pidToAwaiter;
12214
- constructor() {
12224
+ sandboxPool;
12225
+ constructor(sandboxPool) {
12215
12226
  this.pidToTrackedSession = /* @__PURE__ */ new Map();
12216
12227
  this.pidToAwaiter = /* @__PURE__ */ new Map();
12228
+ this.sandboxPool = sandboxPool || null;
12217
12229
  }
12218
12230
  getCurrentSessions() {
12219
12231
  return Array.from(this.pidToTrackedSession.values());
@@ -12254,9 +12266,15 @@ class TaskWorkerManager {
12254
12266
  this.pidToTrackedSession.set(workerProcess.pid, tracked);
12255
12267
  workerProcess.on("exit", (code, signal) => {
12256
12268
  this.pidToTrackedSession.delete(workerProcess.pid);
12269
+ if (this.sandboxPool) {
12270
+ this.sandboxPool.disposeWorkerSandbox(data.taskId);
12271
+ }
12257
12272
  });
12258
12273
  workerProcess.on("error", (error) => {
12259
12274
  this.pidToTrackedSession.delete(workerProcess.pid);
12275
+ if (this.sandboxPool) {
12276
+ this.sandboxPool.disposeWorkerSandbox(data.taskId);
12277
+ }
12260
12278
  });
12261
12279
  }
12262
12280
  async startWorker(options) {
@@ -12281,14 +12299,53 @@ class TaskWorkerManager {
12281
12299
  "--idle-timeout",
12282
12300
  "120"
12283
12301
  ];
12284
- const workerProcess = spawnAgentrixCLI(args, {
12285
- cwd,
12286
- detached: true,
12287
- stdio: ["ignore", "pipe", "pipe"],
12288
- env: {
12289
- ...process.env
12302
+ let workerProcess;
12303
+ if (this.sandboxPool?.isEnabled()) {
12304
+ try {
12305
+ const sandbox = await this.sandboxPool.createWorkerSandbox(
12306
+ options.taskId,
12307
+ options.userId,
12308
+ cwd
12309
+ );
12310
+ if (!sandbox) {
12311
+ throw new Error("Failed to create sandbox instance");
12312
+ }
12313
+ const { projectPath } = await Promise.resolve().then(function () { return require('./logger-DzYRcKN1.cjs'); }).then(function (n) { return n.machine$1; });
12314
+ const { join } = await import('path');
12315
+ const entrypoint = join(projectPath(), "dist", "index.mjs");
12316
+ const nodeArgs = ["--no-warnings", "--no-deprecation", entrypoint, ...args];
12317
+ const originalCommand = `"${process.execPath}" ${nodeArgs.map((a) => `"${a}"`).join(" ")}`;
12318
+ const sandboxedCommand = await this.sandboxPool.wrapWorkerCommand(
12319
+ options.taskId,
12320
+ originalCommand
12321
+ );
12322
+ _package.logger.debug(`[SESSION] Sandboxed command for task ${options.taskId}: ${sandboxedCommand}`);
12323
+ workerProcess = child_process.spawn(sandboxedCommand, {
12324
+ shell: true,
12325
+ cwd,
12326
+ detached: true,
12327
+ stdio: ["ignore", "pipe", "pipe"],
12328
+ env: {
12329
+ ...process.env
12330
+ // Environment variables controlled by SandboxManager
12331
+ }
12332
+ });
12333
+ _package.logger.info(`[SESSION] Worker started with sandbox, PID: ${workerProcess.pid}`);
12334
+ } catch (error) {
12335
+ _package.logger.error(`[SESSION] Failed to setup sandbox for task ${options.taskId}:`, error);
12336
+ ack.status = "failed";
12337
+ ack.message = `Sandbox setup failed: ${error instanceof Error ? error.message : String(error)}`;
12338
+ return ack;
12290
12339
  }
12291
- });
12340
+ } else {
12341
+ workerProcess = spawnAgentrixCLI(args, {
12342
+ cwd,
12343
+ detached: true,
12344
+ stdio: ["ignore", "pipe", "pipe"],
12345
+ env: { ...process.env }
12346
+ });
12347
+ _package.logger.info(`[SESSION] Worker started without sandbox, PID: ${workerProcess.pid}`);
12348
+ }
12292
12349
  if (process.env.DEBUG) {
12293
12350
  workerProcess.stdout?.on("data", (data) => {
12294
12351
  _package.logger.debug(`[Daemon] worker stdout: ${data.toString()}`);
@@ -12407,6 +12464,114 @@ function setupGracefulShutdown(options) {
12407
12464
  };
12408
12465
  }
12409
12466
 
12467
+ class SandboxPool {
12468
+ networkManager = null;
12469
+ workerSandboxes = /* @__PURE__ */ new Map();
12470
+ settings = null;
12471
+ platform;
12472
+ constructor() {
12473
+ this.platform = platform_js.getPlatform();
12474
+ }
12475
+ async initialize(settings) {
12476
+ this.settings = settings;
12477
+ if (!settings.enabled) {
12478
+ _package.logger.info("[SANDBOX] Sandbox disabled via settings");
12479
+ return false;
12480
+ }
12481
+ if (!sandboxRuntime.isSupportedPlatform(this.platform)) {
12482
+ _package.logger.warn("[SANDBOX] Platform not supported, sandbox disabled");
12483
+ return false;
12484
+ }
12485
+ try {
12486
+ const apiHost = new URL(_package.machine.serverUrl).hostname;
12487
+ const networkConfig = {
12488
+ allowedDomains: [
12489
+ apiHost,
12490
+ ...settings.network.allowedDomains
12491
+ ],
12492
+ deniedDomains: settings.network.deniedDomains,
12493
+ allowLocalBinding: false
12494
+ };
12495
+ this.networkManager = new sandboxRuntime.NetworkManager();
12496
+ await this.networkManager.initialize(networkConfig);
12497
+ _package.logger.info("[SANDBOX] Sandbox pool initialized successfully");
12498
+ return true;
12499
+ } catch (error) {
12500
+ _package.logger.error("[SANDBOX] Failed to initialize:", error);
12501
+ throw error;
12502
+ }
12503
+ }
12504
+ async createWorkerSandbox(taskId, userId, workingDirectory) {
12505
+ if (!this.networkManager || !this.settings?.enabled) {
12506
+ return null;
12507
+ }
12508
+ try {
12509
+ const taskDir = _package.machine.resolveTaskDir(userId, taskId);
12510
+ const logsDir = _package.machine.getStatePaths().logsDir;
12511
+ const baseFilesystem = this.settings.filesystem || {};
12512
+ const baseEnv = this.settings.env || {};
12513
+ const filesystemConfig = {
12514
+ ...baseFilesystem,
12515
+ allowWrite: [
12516
+ ...baseFilesystem.allowWrite || [],
12517
+ taskDir,
12518
+ workingDirectory,
12519
+ logsDir
12520
+ ]
12521
+ };
12522
+ if (this.platform === "linux" && baseFilesystem.allowRead) {
12523
+ filesystemConfig.allowRead = [
12524
+ ...baseFilesystem.allowRead
12525
+ ];
12526
+ }
12527
+ const instanceConfig = {
12528
+ filesystem: filesystemConfig,
12529
+ env: baseEnv
12530
+ };
12531
+ const sandbox = new sandboxRuntime.SandboxManager(this.networkManager, instanceConfig);
12532
+ this.workerSandboxes.set(taskId, sandbox);
12533
+ _package.logger.info(`[SANDBOX] Created sandbox for task ${taskId}`);
12534
+ return sandbox;
12535
+ } catch (error) {
12536
+ _package.logger.error(`[SANDBOX] Failed to create sandbox for task ${taskId}:`, error);
12537
+ return null;
12538
+ }
12539
+ }
12540
+ async wrapWorkerCommand(taskId, command) {
12541
+ const sandbox = this.workerSandboxes.get(taskId);
12542
+ if (!sandbox) {
12543
+ throw new Error(`No sandbox found for task ${taskId}`);
12544
+ }
12545
+ const wrapped = await sandbox.wrapWithSandbox(command);
12546
+ _package.logger.debug(`[SANDBOX] Wrapped command for task ${taskId}`);
12547
+ return wrapped;
12548
+ }
12549
+ disposeWorkerSandbox(taskId) {
12550
+ const sandbox = this.workerSandboxes.get(taskId);
12551
+ if (sandbox) {
12552
+ sandbox.dispose();
12553
+ this.workerSandboxes.delete(taskId);
12554
+ _package.logger.debug(`[SANDBOX] Disposed sandbox for task ${taskId}`);
12555
+ }
12556
+ }
12557
+ async shutdown() {
12558
+ _package.logger.info("[SANDBOX] Shutting down sandbox pool");
12559
+ for (const [taskId, sandbox] of this.workerSandboxes.entries()) {
12560
+ sandbox.dispose();
12561
+ _package.logger.debug(`[SANDBOX] Disposed sandbox for task ${taskId}`);
12562
+ }
12563
+ this.workerSandboxes.clear();
12564
+ if (this.networkManager) {
12565
+ await this.networkManager.shutdown();
12566
+ this.networkManager = null;
12567
+ _package.logger.info("[SANDBOX] Network manager shutdown complete");
12568
+ }
12569
+ }
12570
+ isEnabled() {
12571
+ return this.settings?.enabled === true;
12572
+ }
12573
+ }
12574
+
12410
12575
  async function startDaemon() {
12411
12576
  Object.assign(_package.logger, _package.createLogger({ type: "daemon" }));
12412
12577
  const { requestShutdown, shutdownPromise } = setupGracefulShutdown({
@@ -12435,7 +12600,9 @@ async function startDaemon() {
12435
12600
  }
12436
12601
  const credentials = await authAndSetupMachineIfNeeded();
12437
12602
  _package.logger.debug("[DAEMON RUN] Auth and machine setup complete");
12438
- const sessionManager = new TaskWorkerManager();
12603
+ const sandboxPool = new SandboxPool();
12604
+ await sandboxPool.initialize(_package.machine.getSandboxSettings());
12605
+ const sessionManager = new TaskWorkerManager(sandboxPool);
12439
12606
  const { port: controlPort, stop: stopControlServer } = await startDaemonControlServer({
12440
12607
  getChildren: () => sessionManager.getCurrentSessions(),
12441
12608
  stopSession: (id) => sessionManager.stopSession(id),
@@ -12483,6 +12650,7 @@ async function startDaemon() {
12483
12650
  const cleanupAndShutdown = async (source, errorMessage) => {
12484
12651
  await machineClient.disconnect();
12485
12652
  await stopControlServer();
12653
+ await sandboxPool.shutdown();
12486
12654
  await cleanupDaemonState();
12487
12655
  await stopCaffeinate();
12488
12656
  await _package.machine.releaseDaemonLock(daemonLockHandle);
@@ -12599,14 +12767,6 @@ function createWorkerEventHandlers(context) {
12599
12767
  }
12600
12768
  }
12601
12769
  }
12602
- },
12603
- "require-permission-response": async (data) => {
12604
- if (data.taskId !== context.taskId) {
12605
- return;
12606
- }
12607
- if (context.onPermissionResponse) {
12608
- await context.onPermissionResponse(data);
12609
- }
12610
12770
  }
12611
12771
  };
12612
12772
  }
@@ -12647,7 +12807,6 @@ class WorkerClient {
12647
12807
  cwd: normalizedCwd,
12648
12808
  stopTask: options.stopTask,
12649
12809
  onTaskMessage: options.onTaskMessage,
12650
- onPermissionResponse: options.onPermissionResponse,
12651
12810
  onGitPush: options.onGitPush,
12652
12811
  dataEncryptionKey: config.dataEncryptionKey
12653
12812
  };
@@ -12676,26 +12835,32 @@ class WorkerClient {
12676
12835
  }
12677
12836
  this.client.disconnect();
12678
12837
  }
12679
- sendTaskMessage(message) {
12680
- const cwdWithSlash = this.context.cwd;
12681
- const cwdWithoutSlash = cwdWithSlash.slice(0, -1);
12682
- let content = JSON.stringify(message);
12683
- content = content.replaceAll(cwdWithSlash, "");
12684
- content = content.replaceAll(cwdWithoutSlash, ".");
12685
- let assistantMessage = JSON.parse(content);
12838
+ sendTaskMessage(message, options) {
12839
+ const { replaceCwd = true } = options || {};
12840
+ let processedMessage = message;
12841
+ if (replaceCwd) {
12842
+ const cwdWithSlash = this.context.cwd;
12843
+ const cwdWithoutSlash = cwdWithSlash.slice(0, -1);
12844
+ let content = JSON.stringify(message);
12845
+ content = content.replaceAll(cwdWithSlash, "");
12846
+ content = content.replaceAll(cwdWithoutSlash, ".");
12847
+ processedMessage = JSON.parse(content);
12848
+ }
12686
12849
  let encryptedMessage;
12687
12850
  if (this.context.dataEncryptionKey) {
12688
- encryptedMessage = shared.encryptSdkMessage(assistantMessage, this.context.dataEncryptionKey);
12689
- assistantMessage = void 0;
12851
+ encryptedMessage = shared.encryptSdkMessage(processedMessage, this.context.dataEncryptionKey);
12852
+ processedMessage = void 0;
12690
12853
  }
12854
+ const eventId = shared.createEventId();
12691
12855
  const payload = {
12692
- eventId: shared.createEventId(),
12856
+ eventId,
12693
12857
  taskId: this.context.taskId,
12694
12858
  from: "worker",
12695
- message: assistantMessage,
12859
+ message: processedMessage,
12696
12860
  encryptedMessage
12697
12861
  };
12698
12862
  this.client.send("task-message", payload);
12863
+ return eventId;
12699
12864
  }
12700
12865
  sendWorkerInitializing() {
12701
12866
  const workerInitializingEvent = {
@@ -12706,12 +12871,13 @@ class WorkerClient {
12706
12871
  };
12707
12872
  this.client.send("worker-initializing", workerInitializingEvent);
12708
12873
  }
12709
- sendWorkerReady() {
12874
+ sendWorkerReady(duration) {
12710
12875
  const workerReadyEvent = {
12711
12876
  eventId: shared.createEventId(),
12712
12877
  taskId: this.context.taskId,
12713
12878
  machineId: this.context.machineId,
12714
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
12879
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12880
+ ...duration !== void 0 && { duration }
12715
12881
  };
12716
12882
  this.client.send("worker-ready", workerReadyEvent);
12717
12883
  }
@@ -12769,17 +12935,13 @@ ${errorMessage}`,
12769
12935
  };
12770
12936
  this.sendTaskMessage(systemMessage);
12771
12937
  }
12772
- sendRequirePermission(toolName, toolInput) {
12773
- const eventId = shared.createEventId();
12774
- const permissionRequest = {
12775
- eventId,
12776
- taskId: this.context.taskId,
12777
- toolName,
12778
- toolInput
12779
- };
12780
- this.client.send("require-permission", permissionRequest);
12781
- _package.logger.info(`[AGENT] Permission requested for tool: ${toolName}`);
12782
- return eventId;
12938
+ /**
12939
+ * Send ask-user message to request user input
12940
+ * @param questions - Array of questions (1-4)
12941
+ * @returns eventId for tracking the request
12942
+ */
12943
+ sendAskUser(questions) {
12944
+ return this.sendTaskMessage({ type: "ask_user", questions }, { replaceCwd: false });
12783
12945
  }
12784
12946
  sendUpdateTaskAgentSessionId(agentSessionId) {
12785
12947
  const updateSessionEvent = {
@@ -12834,7 +12996,108 @@ ${errorMessage}`,
12834
12996
  this.client.onEvent("cancel-task", handlers["cancel-task"]);
12835
12997
  this.client.onEvent("stop-task", handlers["stop-task"]);
12836
12998
  this.client.onEvent("task-message", handlers["task-message"]);
12837
- this.client.onEvent("require-permission-response", handlers["require-permission-response"]);
12999
+ }
13000
+ }
13001
+
13002
+ const MIME_TYPE_MAP = {
13003
+ // Images
13004
+ ".jpg": "image/jpeg",
13005
+ ".jpeg": "image/jpeg",
13006
+ ".png": "image/png",
13007
+ ".gif": "image/gif",
13008
+ ".webp": "image/webp",
13009
+ ".bmp": "image/bmp",
13010
+ ".svg": "image/svg+xml",
13011
+ ".ico": "image/x-icon",
13012
+ // Documents
13013
+ ".pdf": "application/pdf",
13014
+ ".doc": "application/msword",
13015
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
13016
+ ".xls": "application/vnd.ms-excel",
13017
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
13018
+ ".ppt": "application/vnd.ms-powerpoint",
13019
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
13020
+ // Text
13021
+ ".txt": "text/plain",
13022
+ ".md": "text/markdown",
13023
+ ".csv": "text/csv",
13024
+ ".json": "application/json",
13025
+ ".xml": "application/xml",
13026
+ ".html": "text/html",
13027
+ ".css": "text/css",
13028
+ ".js": "application/javascript",
13029
+ ".ts": "application/typescript",
13030
+ // Archives
13031
+ ".zip": "application/zip",
13032
+ ".tar": "application/x-tar",
13033
+ ".gz": "application/gzip",
13034
+ ".rar": "application/vnd.rar",
13035
+ // Other
13036
+ ".mp3": "audio/mpeg",
13037
+ ".mp4": "video/mp4",
13038
+ ".wav": "audio/wav",
13039
+ ".avi": "video/x-msvideo"
13040
+ };
13041
+ function detectMimeType(extension) {
13042
+ const normalized = extension.toLowerCase();
13043
+ return MIME_TYPE_MAP[normalized] || "application/octet-stream";
13044
+ }
13045
+ function extractExtension(url) {
13046
+ try {
13047
+ const urlObj = new URL(url);
13048
+ const pathExt = path$1.extname(urlObj.pathname);
13049
+ if (pathExt) {
13050
+ return pathExt;
13051
+ }
13052
+ const filenameParam = urlObj.searchParams.get("filename") || urlObj.searchParams.get("name") || urlObj.searchParams.get("file");
13053
+ if (filenameParam) {
13054
+ const paramExt = path$1.extname(filenameParam);
13055
+ if (paramExt) {
13056
+ return paramExt;
13057
+ }
13058
+ }
13059
+ return "";
13060
+ } catch {
13061
+ return "";
13062
+ }
13063
+ }
13064
+ async function downloadFile(url, targetDir, preserveFilename = false) {
13065
+ try {
13066
+ const extension = extractExtension(url) || "";
13067
+ let filename;
13068
+ if (preserveFilename) {
13069
+ try {
13070
+ const urlPath = new URL(url).pathname;
13071
+ const originalFilename = path$1.basename(urlPath);
13072
+ filename = originalFilename && path$1.extname(originalFilename) ? originalFilename : `${node_crypto.randomUUID()}${extension || ".dat"}`;
13073
+ } catch {
13074
+ filename = `${node_crypto.randomUUID()}${extension || ".dat"}`;
13075
+ }
13076
+ } else {
13077
+ filename = `${node_crypto.randomUUID()}${extension || ".dat"}`;
13078
+ }
13079
+ const filePath = path$1.join(targetDir, filename);
13080
+ const response = await fetch(url);
13081
+ if (!response.ok) {
13082
+ throw new Error(`Failed to download file: ${response.status} ${response.statusText}`);
13083
+ }
13084
+ if (!response.body) {
13085
+ throw new Error("Response body is null");
13086
+ }
13087
+ const contentType = response.headers.get("content-type");
13088
+ const mimeType = contentType?.split(";")[0].trim() || detectMimeType(extension);
13089
+ const nodeStream = response.body;
13090
+ const fileStream = fs$1.createWriteStream(filePath);
13091
+ await promises.pipeline(nodeStream, fileStream);
13092
+ return {
13093
+ filePath,
13094
+ mimeType,
13095
+ filename
13096
+ };
13097
+ } catch (error) {
13098
+ throw new Error(
13099
+ `Failed to download file from ${url}: ${error instanceof Error ? error.message : String(error)}`
13100
+ );
12838
13101
  }
12839
13102
  }
12840
13103
 
@@ -12935,6 +13198,12 @@ async function hasUncommittedChanges(dir) {
12935
13198
  const status = await git.status();
12936
13199
  return !status.isClean();
12937
13200
  }
13201
+ async function gitStash(dir, message) {
13202
+ const git = simpleGit(dir);
13203
+ {
13204
+ await git.stash(["push"]);
13205
+ }
13206
+ }
12938
13207
  async function getCurrentCommitHash(dir) {
12939
13208
  const git = simpleGit(dir);
12940
13209
  const log = await git.log({ maxCount: 1 });
@@ -13024,6 +13293,30 @@ async function executeHook(hooks, hookName, input, logger) {
13024
13293
  }
13025
13294
  }
13026
13295
 
13296
+ async function checkUncommittedChanges(workingDirectory) {
13297
+ const isRepo = await isGitRepository(workingDirectory);
13298
+ if (!isRepo) {
13299
+ return false;
13300
+ }
13301
+ return await hasUncommittedChanges(workingDirectory);
13302
+ }
13303
+ async function handleUncommittedChanges(workingDirectory, action) {
13304
+ switch (action) {
13305
+ case "Ignore":
13306
+ console.log("[GIT] User chose to ignore uncommitted changes");
13307
+ break;
13308
+ case "Commit":
13309
+ console.log("[GIT] Auto-committing uncommitted changes");
13310
+ await autoCommit(workingDirectory, "WIP: Auto-commit before task");
13311
+ break;
13312
+ case "Stash":
13313
+ console.log("[GIT] Stashing uncommitted changes");
13314
+ await gitStash(workingDirectory);
13315
+ break;
13316
+ case "Abort":
13317
+ throw new Error("Task aborted by user due to uncommitted changes");
13318
+ }
13319
+ }
13027
13320
  function createTaskBranchName(taskId) {
13028
13321
  return `agentrix/${taskId}`;
13029
13322
  }
@@ -13063,17 +13356,6 @@ async function setupLocalWorkspace(workingDirectory, taskId, hooks) {
13063
13356
  const isRepo = await isGitRepository(workingDirectory);
13064
13357
  const isEmpty = isDirectoryEmpty(workingDirectory);
13065
13358
  if (isRepo) {
13066
- const hasChanges = await hasUncommittedChanges(workingDirectory);
13067
- if (hasChanges) {
13068
- throw new Error(
13069
- `Directory ${workingDirectory} has uncommitted changes.
13070
-
13071
- Please commit or stash your changes before starting:
13072
- git add . && git commit -m "WIP"
13073
- or:
13074
- git stash`
13075
- );
13076
- }
13077
13359
  const hasCommits = await hasAnyCommits(workingDirectory);
13078
13360
  if (!hasCommits) {
13079
13361
  console.log("[GIT] Repository has no commits, creating initial commit");
@@ -13146,7 +13428,7 @@ async function generateAndSavePatch(workingDirectory, fromCommit, toCommit, data
13146
13428
  return void 0;
13147
13429
  }
13148
13430
  const patchPath = path$1.join(dataDir, "patch.diff");
13149
- await promises.writeFile(patchPath, patch);
13431
+ await promises$1.writeFile(patchPath, patch);
13150
13432
  return patchPath;
13151
13433
  }
13152
13434
  async function handleGitState(workingDirectory, userId, taskId, commitMessage) {
@@ -13201,6 +13483,29 @@ async function markCommitAsSent(userId, taskId, commitHash) {
13201
13483
  await _package.machine.writeLastSentCommitHash(userId, taskId, commitHash);
13202
13484
  }
13203
13485
 
13486
+ function getDefaultPRPrompt(params) {
13487
+ return `All changes have been pushed to branch "${params.branchName}".
13488
+
13489
+ Commit range: ${params.initialCommitHash}..${params.currentCommitHash}
13490
+
13491
+ Based on our conversation context, create a Pull Request:
13492
+ - Title: conventional commits format (feat/fix/docs/refactor/test/chore: description)
13493
+ - Description: what changed, why, and any important decisions
13494
+
13495
+ Use mcp__agentrix__create_pr tool to create the PR.`;
13496
+ }
13497
+ function applyTemplateVariables(template, params) {
13498
+ return template.replace(/\{\{initialCommitHash\}\}/g, params.initialCommitHash).replace(/\{\{currentCommitHash\}\}/g, params.currentCommitHash).replace(/\{\{branchName\}\}/g, params.branchName);
13499
+ }
13500
+ function buildPRPrompt(params, config) {
13501
+ const defaultPrompt = getDefaultPRPrompt(params);
13502
+ if (!config?.customTemplate) {
13503
+ return defaultPrompt;
13504
+ }
13505
+ const customPrompt = applyTemplateVariables(config.customTemplate, params);
13506
+ return config.mode === "replace" ? customPrompt : defaultPrompt + "\n\n" + customPrompt;
13507
+ }
13508
+
13204
13509
  function executeCommandStreaming(command, cwd, callbacks, timeoutMs = 6e4) {
13205
13510
  return new Promise((resolve) => {
13206
13511
  const toolUseId = `shell_${require$$1.randomUUID().replace(/-/g, "")}`;
@@ -13318,6 +13623,7 @@ class MessageCoordinator {
13318
13623
  currentMessageId = null;
13319
13624
  messageIdCounter = 0;
13320
13625
  isStopped = false;
13626
+ runStartTime = null;
13321
13627
  constructor(config) {
13322
13628
  this.config = config;
13323
13629
  }
@@ -13437,11 +13743,11 @@ class MessageCoordinator {
13437
13743
  async processBashCommand(envelope) {
13438
13744
  this.log("info", "COORDINATOR", `Processing bash command: ${envelope.content}`);
13439
13745
  await this.waitForState("idle");
13440
- this.workerState = "executing-command";
13746
+ this.setWorkerState("running");
13441
13747
  try {
13442
13748
  await this.config.handlers.onBashCommand(envelope.content, envelope.originalMessage);
13443
13749
  } finally {
13444
- this.workerState = "idle";
13750
+ this.setWorkerState("idle");
13445
13751
  }
13446
13752
  }
13447
13753
  /**
@@ -13450,11 +13756,11 @@ class MessageCoordinator {
13450
13756
  async processMergeRequest(envelope) {
13451
13757
  this.log("info", "COORDINATOR", "Processing merge-request command");
13452
13758
  await this.waitForState("idle");
13453
- this.workerState = "executing-command";
13759
+ this.setWorkerState("running");
13454
13760
  try {
13455
13761
  await this.config.handlers.onMergeRequest(envelope.originalMessage);
13456
13762
  } finally {
13457
- this.workerState = "idle";
13763
+ this.setWorkerState("idle");
13458
13764
  }
13459
13765
  }
13460
13766
  async waitForState(targetState) {
@@ -13474,11 +13780,24 @@ class MessageCoordinator {
13474
13780
  }
13475
13781
  /**
13476
13782
  * Set the worker state (called by worker when state changes)
13783
+ * Automatically sends WebSocket events and tracks execution duration
13477
13784
  */
13478
13785
  setWorkerState(state) {
13479
- if (this.workerState !== state) {
13480
- this.log("info", "COORDINATOR", `Worker state: ${this.workerState} \u2192 ${state}`);
13481
- this.workerState = state;
13786
+ if (this.workerState === state) return;
13787
+ const prevState = this.workerState;
13788
+ this.log("info", "COORDINATOR", `Worker state: ${prevState} \u2192 ${state}`);
13789
+ this.workerState = state;
13790
+ if (state === "running" && prevState === "idle") {
13791
+ this.runStartTime = Date.now();
13792
+ this.config.workClient.sendWorkRunning();
13793
+ }
13794
+ if (state === "idle" && prevState === "running") {
13795
+ let duration;
13796
+ if (this.runStartTime) {
13797
+ duration = Date.now() - this.runStartTime;
13798
+ this.runStartTime = null;
13799
+ }
13800
+ this.config.workClient.sendWorkerReady(duration);
13482
13801
  }
13483
13802
  }
13484
13803
  /**
@@ -13518,7 +13837,7 @@ class ClaudeWorker {
13518
13837
  messageQueue = [];
13519
13838
  messageResolverRef = { current: null };
13520
13839
  abortController = new AbortController();
13521
- permissionAwaiter = /* @__PURE__ */ new Map();
13840
+ askUserAwaiter = /* @__PURE__ */ new Map();
13522
13841
  filteredToolUseIds = /* @__PURE__ */ new Set();
13523
13842
  timerManager;
13524
13843
  context;
@@ -13528,6 +13847,11 @@ class ClaudeWorker {
13528
13847
  dataEncryptionKey = null;
13529
13848
  coordinator;
13530
13849
  loadedHooks;
13850
+ loadedAgentConfig;
13851
+ // Pending permission requests: toolName -> Promise<'allow' | 'deny'> (to dedupe concurrent requests)
13852
+ pendingPermissions = /* @__PURE__ */ new Map();
13853
+ // Granted permissions cache: toolName -> true (to avoid repeated asks for same tool)
13854
+ grantedPermissions = /* @__PURE__ */ new Set();
13531
13855
  async start() {
13532
13856
  try {
13533
13857
  await this.initialize();
@@ -13548,18 +13872,69 @@ class ClaudeWorker {
13548
13872
  if (this.timerManager) {
13549
13873
  this.timerManager.clearIdleTimer();
13550
13874
  }
13875
+ if (this.logger) {
13876
+ await new Promise((resolve) => {
13877
+ this.logger.on("finish", resolve);
13878
+ this.logger.end();
13879
+ });
13880
+ }
13551
13881
  process.exit(0);
13552
13882
  }
13553
13883
  }
13554
13884
  async initialize() {
13555
13885
  const taskId = this.options.input.taskId;
13556
13886
  const userId = this.options.input.userId;
13557
- let workingDirectory = process.cwd();
13558
- let initialCommitHash;
13559
- this.logger = await this.createLogger({ type: "worker", taskId });
13887
+ this.logger = this.createWorkerLogger({ type: "worker", taskId });
13560
13888
  if (this.options.input.dataEncryptionKey && this.options.secretKey) {
13561
13889
  this.dataEncryptionKey = shared.decryptWithEphemeralKey(shared.decodeBase64(this.options.input.dataEncryptionKey), this.options.secretKey);
13562
13890
  }
13891
+ if (this.options.input.encryptedMessage && this.dataEncryptionKey) {
13892
+ this.options.input.message = shared.decryptSdkMessage(this.options.input.encryptedMessage, this.dataEncryptionKey) || void 0;
13893
+ }
13894
+ let workingDirectory = this.options.input.cwd ? this.options.input.cwd.replace(/^~/, os.homedir()) : process.cwd();
13895
+ const idleTimeoutMs = Math.max(0, this.options.idleTimeoutSecond ?? 0) * 1e3;
13896
+ this.timerManager = this.createIdleTimerManager(idleTimeoutMs, taskId);
13897
+ const workerConfig = this.createWorkerClientConfig(userId, taskId, workingDirectory);
13898
+ const workClient = new WorkerClient(workerConfig.config, workerConfig.handlers);
13899
+ await workClient.connect();
13900
+ workClient.sendWorkerInitializing();
13901
+ this.context = {
13902
+ credentials: this.credentials,
13903
+ options: this.options,
13904
+ workClient,
13905
+ workingDirectory,
13906
+ initialCommitHash: "",
13907
+ // Will be set after setupWorkspace
13908
+ logger: this.logger
13909
+ };
13910
+ this.coordinator = new MessageCoordinator({
13911
+ workerType: "claude",
13912
+ workClient,
13913
+ handlers: {
13914
+ onNormalMessage: async (message) => {
13915
+ await this.enqueueMessage(message);
13916
+ },
13917
+ onBashCommand: async (command, _originalMessage) => {
13918
+ await this.executeBashCommand(command);
13919
+ },
13920
+ onMergeRequest: async (_originalMessage) => {
13921
+ await this.executeMergeRequest();
13922
+ }
13923
+ },
13924
+ logger: (level, category, message) => {
13925
+ const validLevel = level;
13926
+ this.log(validLevel, category, message);
13927
+ }
13928
+ });
13929
+ if (!this.options.input.gitUrl) {
13930
+ const hasChanges = await checkUncommittedChanges(workingDirectory);
13931
+ if (hasChanges) {
13932
+ this.log("info", "GIT", "Detected uncommitted changes, asking user for action");
13933
+ const action = await this.askUncommittedChangesAction();
13934
+ await handleUncommittedChanges(workingDirectory, action);
13935
+ }
13936
+ }
13937
+ let initialCommitHash;
13563
13938
  try {
13564
13939
  const hooks = await this.loadAgentHooks();
13565
13940
  const workspaceResult = await setupWorkspace({
@@ -13571,6 +13946,8 @@ class ClaudeWorker {
13571
13946
  }, hooks);
13572
13947
  workingDirectory = workspaceResult.workingDirectory;
13573
13948
  initialCommitHash = workspaceResult.initialCommitHash;
13949
+ this.context.workingDirectory = workingDirectory;
13950
+ this.context.initialCommitHash = initialCommitHash;
13574
13951
  await _package.machine.writeInitialCommitHash(userId, taskId, initialCommitHash);
13575
13952
  this.log("info", "GIT", `Initial commit: ${initialCommitHash}`);
13576
13953
  this.initialCommitHashForPR = initialCommitHash;
@@ -13578,20 +13955,10 @@ class ClaudeWorker {
13578
13955
  this.logGitStateResult(gitStateResult, "start");
13579
13956
  } catch (error) {
13580
13957
  this.log("error", "GIT", "Failed to setup workspace:", error);
13581
- const basicConfig = this.createBasicWorkerConfig(userId, taskId, workingDirectory);
13582
13958
  const errorMessage = error instanceof Error ? error.message : String(error);
13583
- await WorkerClient.sendErrorAndExit(
13584
- basicConfig,
13585
- `Failed to setup workspace: ${errorMessage}`
13586
- );
13959
+ await workClient.sendErrorMessageAndExit(`Failed to setup workspace: ${errorMessage}`);
13587
13960
  process.exit(1);
13588
13961
  }
13589
- const idleTimeoutMs = Math.max(0, this.options.idleTimeoutSecond ?? 0) * 1e3;
13590
- this.timerManager = this.createIdleTimerManager(idleTimeoutMs, taskId);
13591
- const workerConfig = this.createWorkerClientConfig(userId, taskId, workingDirectory);
13592
- const workClient = new WorkerClient(workerConfig.config, workerConfig.handlers);
13593
- await workClient.connect();
13594
- workClient.sendWorkerInitializing();
13595
13962
  try {
13596
13963
  const metadata = {
13597
13964
  cwd: workingDirectory,
@@ -13608,35 +13975,6 @@ class ClaudeWorker {
13608
13975
  } catch (error) {
13609
13976
  this.log("warn", "DAEMON", "Failed to report session:", error);
13610
13977
  }
13611
- if (this.options.input.encryptedMessage && this.dataEncryptionKey) {
13612
- this.options.input.message = shared.decryptSdkMessage(this.options.input.encryptedMessage, this.dataEncryptionKey) || void 0;
13613
- }
13614
- this.context = {
13615
- credentials: this.credentials,
13616
- options: this.options,
13617
- workClient,
13618
- workingDirectory,
13619
- initialCommitHash,
13620
- logger: this.logger
13621
- };
13622
- this.coordinator = new MessageCoordinator({
13623
- workerType: "claude",
13624
- handlers: {
13625
- onNormalMessage: async (message) => {
13626
- await this.enqueueMessage(message);
13627
- },
13628
- onBashCommand: async (command, _originalMessage) => {
13629
- await this.executeBashCommand(command);
13630
- },
13631
- onMergeRequest: async (_originalMessage) => {
13632
- await this.executeMergeRequest();
13633
- }
13634
- },
13635
- logger: (level, category, message) => {
13636
- const validLevel = level;
13637
- this.log(validLevel, category, message);
13638
- }
13639
- });
13640
13978
  }
13641
13979
  async handleEvent() {
13642
13980
  if (this.options.input.message) {
@@ -13654,15 +13992,30 @@ class ClaudeWorker {
13654
13992
  }
13655
13993
  const hasChanges = await hasUncommittedChanges(this.context.workingDirectory);
13656
13994
  if (hasChanges) {
13657
- await autoCommit(this.context.workingDirectory, "Update task changes");
13995
+ await autoCommit(this.context.workingDirectory, "Checkpoint for PR generation");
13658
13996
  this.log("info", "MERGE", "Auto-committed changes");
13659
13997
  }
13998
+ const currentHash = await getCurrentCommitHash(this.context.workingDirectory);
13999
+ const diffStats = await getDiffStats(this.context.workingDirectory, this.initialCommitHashForPR, currentHash);
14000
+ if (diffStats.files.length === 0) {
14001
+ const errorMessage = "No changes to create PR: no files changed since task started";
14002
+ this.log("error", "MERGE", errorMessage);
14003
+ this.context.workClient.sendSystemErrorMessage(errorMessage);
14004
+ return;
14005
+ }
14006
+ this.log("info", "MERGE", `Found ${diffStats.files.length} files changed`);
13660
14007
  const branchName = await getCurrentBranch(this.context.workingDirectory);
13661
14008
  this.log("info", "MERGE", `Pushing branch ${branchName} to remote`);
13662
14009
  await gitPush(this.context.workingDirectory, branchName, false);
13663
14010
  this.log("info", "MERGE", "Successfully pushed branch to remote");
14011
+ const prPrompt = buildPRPrompt(
14012
+ { initialCommitHash: this.initialCommitHashForPR, currentCommitHash: currentHash, branchName },
14013
+ this.loadedAgentConfig?.customPRPromptTemplate ? {
14014
+ customTemplate: this.loadedAgentConfig.customPRPromptTemplate,
14015
+ mode: this.loadedAgentConfig.prPromptMode
14016
+ } : void 0
14017
+ );
13664
14018
  this.inMergeRequest = true;
13665
- const prPrompt = await this.buildCreatePRPrompt();
13666
14019
  await this.enqueueMessage({
13667
14020
  type: "user",
13668
14021
  message: {
@@ -13691,53 +14044,8 @@ class ClaudeWorker {
13691
14044
  }
13692
14045
  );
13693
14046
  this.timerManager.startIdleTimer();
13694
- this.context.workClient.sendWorkerReady();
13695
14047
  this.log("info", "BASH", `Worker ready after command execution (exit code: ${exitCode})`);
13696
14048
  }
13697
- async buildCreatePRPrompt() {
13698
- if (!this.initialCommitHashForPR) {
13699
- return 'Forbidden create PR by yourself. Must use the mcp__agentrix__create_pr tool. All the changed has been pushed. Please analyze the changes and use the mcp__agentrix__create_pr tool to create a pull request with a title and description. Use conventional commits format for the title (e.g., "feat: add new feature").';
13700
- }
13701
- try {
13702
- const currentHash = await autoCommit(this.context.workingDirectory, "Checkpoint for PR generation");
13703
- const stats = await getDiffStats(
13704
- this.context.workingDirectory,
13705
- this.initialCommitHashForPR,
13706
- currentHash
13707
- );
13708
- const diff = await generateDiffPatch(
13709
- this.context.workingDirectory,
13710
- this.initialCommitHashForPR,
13711
- currentHash
13712
- );
13713
- const statsText = `Files changed: ${stats.files.length}, +${stats.totalInsertions}/-${stats.totalDeletions}
13714
-
13715
- Detailed changes:
13716
- ${stats.files.map((f) => ` ${f.path}: +${f.insertions}/-${f.deletions}`).join("\n")}`;
13717
- return `All the changed has been successfully pushed to the git branch. Please create a Pull Request by analyzing the changes below.
13718
-
13719
- Changes made:
13720
- ${statsText}
13721
-
13722
- Diff (first 5000 chars):
13723
- \`\`\`
13724
- ${diff.substring(0, 5e3)}
13725
- \`\`\`
13726
-
13727
- Requirements:
13728
- - Title: Use conventional commits format (feat/fix/docs/refactor/test/chore: description), maximum 50 characters
13729
- - Description: Provide a clear, detailed explanation of:
13730
- * What changed (the actual modifications made)
13731
- * Why these changes were necessary (the problem being solved)
13732
- * Any important technical decisions or trade-offs
13733
- * Impact on existing functionality
13734
-
13735
- Please must use the mcp__agentrix__create_pr tool to create the pull request with the generated title and description.Forbidden create PR by yourself.`;
13736
- } catch (error) {
13737
- this.log("warn", "GIT", "Failed to generate diff for PR prompt:", error);
13738
- return 'The code has been committed. Please use the mcp__agentrix__create_pr tool to create a pull request with a title and description. Use conventional commits format for the title (e.g., "feat: add new feature").';
13739
- }
13740
- }
13741
14049
  async runClaude() {
13742
14050
  this.log("info", "AGENT", `Starting Claude agent for task ${this.taskId}`);
13743
14051
  const agentSessionId = "agentSessionId" in this.options.input ? this.options.input.agentSessionId : void 0;
@@ -13748,12 +14056,12 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
13748
14056
  );
13749
14057
  const sdkMcpServer = this.createAgentrixMcpServer();
13750
14058
  const mcpServers = {
13751
- agentrix: sdkMcpServer,
13752
- ...agentConfig.customMcpServers
14059
+ agentrix: sdkMcpServer
13753
14060
  };
13754
14061
  const allowedTools = [
13755
14062
  "mcp__agentrix__change_task_title",
13756
14063
  "mcp__agentrix__create_pr",
14064
+ "mcp__agentrix__ask_user",
13757
14065
  ...agentConfig.customAllowedTools
13758
14066
  ];
13759
14067
  const messageConsumer = this.createMessageConsumer();
@@ -13773,6 +14081,7 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
13773
14081
  systemPrompt: finalSystemPrompt,
13774
14082
  mcpServers,
13775
14083
  allowedTools,
14084
+ plugins: agentConfig.customPlugins,
13776
14085
  abortController: this.abortController,
13777
14086
  env: this.buildEnvironmentOverrides(),
13778
14087
  maxTurns: agentConfig.customMaxTurns ?? this.options.input.maxTurns ?? 50,
@@ -13782,18 +14091,16 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
13782
14091
  }
13783
14092
  });
13784
14093
  if (this.messageQueue.length > 0) {
13785
- this.context.workClient.sendWorkRunning();
13786
- this.coordinator.setWorkerState("processing-sdk");
14094
+ this.coordinator.setWorkerState("running");
13787
14095
  } else {
13788
14096
  this.timerManager.startIdleTimer();
13789
- this.context.workClient.sendWorkerReady();
13790
14097
  }
13791
14098
  for await (const message of response) {
13792
14099
  this.timerManager.clearIdleTimer();
13793
14100
  this.context.logger.debug(`sdk message: ${JSON.stringify(message)}`);
13794
14101
  if (message.type === "system" && message.subtype === "init") {
13795
14102
  this.context.workClient.sendUpdateTaskAgentSessionId(message.session_id);
13796
- this.context.workClient.sendWorkRunning();
14103
+ this.coordinator.setWorkerState("running");
13797
14104
  continue;
13798
14105
  }
13799
14106
  const filteredMessage = this.filterMessages(message);
@@ -13803,15 +14110,14 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
13803
14110
  if (message.type === "result") {
13804
14111
  this.coordinator.setWorkerState("idle");
13805
14112
  this.timerManager.startIdleTimer();
13806
- this.context.workClient.sendWorkerReady();
13807
14113
  } else {
13808
- this.coordinator.setWorkerState("processing-sdk");
14114
+ this.coordinator.setWorkerState("running");
13809
14115
  }
13810
14116
  }
13811
14117
  this.log("info", "AGENT", `Claude agent finished for task ${this.taskId}`);
13812
14118
  }
13813
14119
  async enqueueMessage(message) {
13814
- const processedMessage = await this.processImageUrls(message);
14120
+ const processedMessage = await this.processAttachments(message);
13815
14121
  if (this.messageResolverRef.current) {
13816
14122
  const resolver = this.messageResolverRef.current;
13817
14123
  this.messageResolverRef.current = null;
@@ -13820,10 +14126,14 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
13820
14126
  this.messageQueue.push(processedMessage);
13821
14127
  }
13822
14128
  }
13823
- async processImageUrls(message) {
14129
+ async processAttachments(message) {
13824
14130
  if (!Array.isArray(message.message.content)) {
13825
14131
  return message;
13826
14132
  }
14133
+ const attachmentsDir = _package.machine.resolveAttachmentsDir(
14134
+ this.options.input.userId,
14135
+ this.taskId
14136
+ );
13827
14137
  const processedContent = await Promise.all(
13828
14138
  message.message.content.map(async (block) => {
13829
14139
  if (block.type === "image" && block.source?.type === "url" && block.source?.url) {
@@ -13859,6 +14169,24 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
13859
14169
  return block;
13860
14170
  }
13861
14171
  }
14172
+ if (block.type === "document" && block.source?.type === "url" && block.source?.url) {
14173
+ try {
14174
+ const url = block.source.url;
14175
+ this.log("info", "DOCUMENT", `Downloading document from: ${url}`);
14176
+ const { filePath, mimeType, filename } = await downloadFile(url, attachmentsDir, true);
14177
+ this.log("info", "DOCUMENT", `Document downloaded to: ${filePath}`);
14178
+ const title = block.title || filename;
14179
+ return {
14180
+ type: "text",
14181
+ text: `Document: ${filePath}
14182
+ Title: ${title}
14183
+ Type: ${mimeType}`
14184
+ };
14185
+ } catch (error) {
14186
+ this.log("error", "DOCUMENT", `Error processing document: ${error}`);
14187
+ return block;
14188
+ }
14189
+ }
13862
14190
  return block;
13863
14191
  })
13864
14192
  );
@@ -13943,33 +14271,117 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
13943
14271
  }
13944
14272
  createPermissionHandler() {
13945
14273
  return async (toolName, input) => {
13946
- const eventId = this.context.workClient.sendRequirePermission(toolName, input);
13947
- const timeoutMs = 3e4;
13948
- const permissionResponse = await new Promise(
13949
- (resolve, reject) => {
13950
- const timeout = setTimeout(() => {
13951
- this.permissionAwaiter.delete(eventId);
13952
- reject(new Error(`Permission request timeout for tool ${toolName}`));
13953
- }, timeoutMs);
13954
- this.permissionAwaiter.set(eventId, (response) => {
13955
- clearTimeout(timeout);
13956
- resolve(response);
13957
- });
14274
+ if (this.grantedPermissions.has(toolName)) {
14275
+ this.log("info", "PERMISSION", `Tool "${toolName}" already granted, skipping`);
14276
+ return { behavior: "allow", updatedInput: input };
14277
+ }
14278
+ const pending = this.pendingPermissions.get(toolName);
14279
+ if (pending) {
14280
+ this.log("info", "PERMISSION", `Tool "${toolName}" has pending request, waiting...`);
14281
+ const decision = await pending;
14282
+ if (decision === "allow") {
14283
+ return { behavior: "allow", updatedInput: input };
14284
+ } else {
14285
+ return { behavior: "deny", message: "Permission denied by user" };
13958
14286
  }
13959
- );
13960
- if (permissionResponse.behavior === "allow") {
13961
- return {
13962
- behavior: "allow",
13963
- updatedInput: input
13964
- };
13965
- } else {
13966
- return {
13967
- behavior: "deny",
13968
- message: permissionResponse.message || "Permission denied"
13969
- };
14287
+ }
14288
+ this.log("info", "PERMISSION", `Requesting permission for "${toolName}"`);
14289
+ let resolveDecision;
14290
+ const permissionPromise = new Promise((resolve) => {
14291
+ resolveDecision = resolve;
14292
+ });
14293
+ this.pendingPermissions.set(toolName, permissionPromise);
14294
+ try {
14295
+ const decision = await this.requestToolPermission(toolName);
14296
+ resolveDecision(decision);
14297
+ if (decision === "allow") {
14298
+ this.grantedPermissions.add(toolName);
14299
+ return { behavior: "allow", updatedInput: input };
14300
+ } else {
14301
+ return { behavior: "deny", message: "Permission denied by user" };
14302
+ }
14303
+ } catch (error) {
14304
+ resolveDecision("deny");
14305
+ return { behavior: "deny", message: "Permission request failed" };
14306
+ } finally {
14307
+ this.pendingPermissions.delete(toolName);
13970
14308
  }
13971
14309
  };
13972
14310
  }
14311
+ async requestToolPermission(toolName) {
14312
+ const questions = [{
14313
+ question: `Tool "${toolName}" is requesting permission to execute. Allow this operation?`,
14314
+ header: "Permission",
14315
+ multiSelect: false,
14316
+ options: [
14317
+ { label: "Allow", description: "Allow this tool to execute" },
14318
+ { label: "Deny", description: "Deny this tool execution" }
14319
+ ]
14320
+ }];
14321
+ try {
14322
+ const response = await this.askUser(questions);
14323
+ const answer = response.answers[0];
14324
+ return answer === "Allow" ? "allow" : "deny";
14325
+ } catch (error) {
14326
+ this.log("warn", "PERMISSION", `Permission request failed: ${error}`);
14327
+ return "deny";
14328
+ }
14329
+ }
14330
+ /**
14331
+ * Ask user questions and wait for response
14332
+ * Sends ask_user message via task-message and waits for ask_user_response
14333
+ * @param questions - Array of questions (1-4)
14334
+ * @returns Promise resolving to user's response
14335
+ */
14336
+ async askUser(questions) {
14337
+ const eventId = this.context.workClient.sendAskUser(questions);
14338
+ const timeoutMs = 3e5;
14339
+ return new Promise((resolve, reject) => {
14340
+ const timeout = setTimeout(() => {
14341
+ this.askUserAwaiter.delete(eventId);
14342
+ reject(new Error("Ask user request timed out"));
14343
+ }, timeoutMs);
14344
+ this.askUserAwaiter.set(eventId, (response) => {
14345
+ clearTimeout(timeout);
14346
+ resolve(response);
14347
+ });
14348
+ });
14349
+ }
14350
+ /**
14351
+ * Ask user how to handle uncommitted changes
14352
+ * @returns The action to take: ignore, commit, stash, or abort
14353
+ */
14354
+ async askUncommittedChangesAction() {
14355
+ const questions = [{
14356
+ question: "Uncommitted changes detected in the working directory. How would you like to proceed?",
14357
+ header: "Git Status",
14358
+ multiSelect: false,
14359
+ options: [
14360
+ { label: "Ignore", description: "Keep changes and continue with the task" },
14361
+ { label: "Commit", description: "Commit current changes before starting" },
14362
+ { label: "Stash", description: "Stash changes (git stash) before starting" },
14363
+ { label: "Abort", description: "Cancel the task, do nothing" }
14364
+ ]
14365
+ }];
14366
+ try {
14367
+ const response = await this.askUser(questions);
14368
+ const answer = response.answers[0];
14369
+ if (answer.startsWith("other:")) {
14370
+ this.log("info", "GIT", `User provided custom input: ${answer}, defaulting to Abort`);
14371
+ return "Abort";
14372
+ }
14373
+ const labelToAction = {
14374
+ "Ignore": "Ignore",
14375
+ "Commit": "Commit",
14376
+ "Stash": "Stash",
14377
+ "Abort": "Abort"
14378
+ };
14379
+ return labelToAction[answer] || "Abort";
14380
+ } catch (error) {
14381
+ this.log("warn", "GIT", `Failed to get user response for uncommitted changes: ${error}`);
14382
+ return "Abort";
14383
+ }
14384
+ }
13973
14385
  createAgentrixMcpServer() {
13974
14386
  return claudeAgentSdk.createSdkMcpServer({
13975
14387
  name: "agentrix",
@@ -14030,6 +14442,50 @@ URL: ${result.pullRequestUrl}`
14030
14442
  };
14031
14443
  }
14032
14444
  }
14445
+ ),
14446
+ claudeAgentSdk.tool(
14447
+ "ask_user",
14448
+ 'Ask the user questions when you need clarification or user input. Supports 1-4 questions with 2-4 options each. Use this when you need user decisions or additional information. An "Other" option with free text input is automatically added.',
14449
+ {
14450
+ questions: zod.z.array(zod.z.object({
14451
+ question: zod.z.string().describe("The complete question to ask the user"),
14452
+ header: zod.z.string().max(12).describe("Short label displayed as a chip/tag (max 12 chars)"),
14453
+ multiSelect: zod.z.boolean().describe("Set to true to allow multiple option selections"),
14454
+ options: zod.z.array(zod.z.object({
14455
+ label: zod.z.string().describe("Option label (1-5 words)"),
14456
+ description: zod.z.string().describe("Explanation of what this option means")
14457
+ })).min(2).max(4).describe("Available choices (2-4 options)")
14458
+ })).min(1).max(4).describe("Questions to ask (1-4 questions)")
14459
+ },
14460
+ async (args) => {
14461
+ try {
14462
+ const questionsWithOther = args.questions.map((q) => ({
14463
+ ...q,
14464
+ options: [...q.options, { label: "Other", description: "" }]
14465
+ }));
14466
+ const result = await this.askUser(questionsWithOther);
14467
+ const answerText = result.answers.map((answer) => {
14468
+ if (answer.startsWith("other:")) {
14469
+ return `Other: "${answer.slice(6)}"`;
14470
+ }
14471
+ return answer;
14472
+ }).join("\n");
14473
+ return {
14474
+ content: [{ type: "text", text: `User answers:
14475
+ ${answerText}` }]
14476
+ };
14477
+ } catch (error) {
14478
+ this.log("error", "ASK_USER", "Failed to get user response:", error);
14479
+ return {
14480
+ content: [
14481
+ {
14482
+ type: "text",
14483
+ text: `Failed to get user response: ${error instanceof Error ? error.message : "Unknown error"}`
14484
+ }
14485
+ ]
14486
+ };
14487
+ }
14488
+ }
14033
14489
  )
14034
14490
  ]
14035
14491
  });
@@ -14045,7 +14501,7 @@ URL: ${result.pullRequestUrl}`
14045
14501
  } catch (error) {
14046
14502
  this.log("warn", "GIT", "Failed to handle git state on worker stop:", error);
14047
14503
  }
14048
- this.context.workClient.sendWorkerReady();
14504
+ this.coordinator.setWorkerState("idle");
14049
14505
  }
14050
14506
  filterMessages(message) {
14051
14507
  const msg = message;
@@ -14086,38 +14542,45 @@ URL: ${result.pullRequestUrl}`
14086
14542
  return this.createDefaultAgentConfig();
14087
14543
  }
14088
14544
  const claudeConfig = agentConfig.claude;
14089
- const customMcpServers = Object.fromEntries(
14090
- Object.entries(claudeConfig.mcpServers).map(([name, server]) => [name, server.instance])
14091
- );
14092
- this.log("info", "AGENT", `Agent ${this.options.input.agentId} loaded successfully`);
14093
- return {
14545
+ const customPlugins = claudeConfig.plugins.map((path) => ({
14546
+ type: "local",
14547
+ path
14548
+ }));
14549
+ this.log("info", "AGENT", `Agent ${this.options.input.agentId} loaded successfully (${customPlugins.length} plugins)`);
14550
+ const config = {
14094
14551
  customSystemPrompt: claudeConfig.systemPrompt,
14095
- // Loaded string content
14096
14552
  customModel: claudeConfig.config.model,
14097
14553
  customFallbackModel: claudeConfig.config.fallbackModel,
14098
14554
  customMaxTurns: claudeConfig.config.maxTurns,
14099
14555
  customExtraArgs: claudeConfig.config.extraArgs,
14100
14556
  customPermissionMode: claudeConfig.config.settings?.permissionMode,
14101
- customMcpServers,
14102
14557
  customAllowedTools: claudeConfig.config.settings?.allowedTools || [],
14558
+ customPlugins,
14103
14559
  systemPromptMode: claudeConfig.config.systemPrompt?.mode ?? "append",
14104
- hooks: this.loadedHooks
14105
- // Use cached hooks
14560
+ hooks: this.loadedHooks,
14561
+ customPRPromptTemplate: claudeConfig.prPromptTemplate,
14562
+ prPromptMode: claudeConfig.config.pullRequestPrompt?.mode ?? "append"
14106
14563
  };
14564
+ this.loadedAgentConfig = config;
14565
+ return config;
14107
14566
  } catch (error) {
14108
14567
  this.log("error", "AGENT", `Failed to load agent: ${error instanceof Error ? error.message : String(error)}`);
14109
14568
  return this.createDefaultAgentConfig();
14110
14569
  }
14111
14570
  }
14112
14571
  createDefaultAgentConfig() {
14113
- return {
14572
+ const config = {
14114
14573
  customSystemPrompt: void 0,
14115
14574
  customModel: void 0,
14116
14575
  customMaxTurns: void 0,
14117
- customMcpServers: {},
14118
14576
  customAllowedTools: [],
14119
- systemPromptMode: "append"
14577
+ customPlugins: [],
14578
+ systemPromptMode: "append",
14579
+ customPRPromptTemplate: void 0,
14580
+ prPromptMode: "append"
14120
14581
  };
14582
+ this.loadedAgentConfig = config;
14583
+ return config;
14121
14584
  }
14122
14585
  buildEnvironmentOverrides() {
14123
14586
  return {
@@ -14225,18 +14688,20 @@ URL: ${result.pullRequestUrl}`
14225
14688
  this.timerManager.stopTask("event");
14226
14689
  },
14227
14690
  onTaskMessage: async (message) => {
14228
- if (message.type === "user") {
14691
+ if (shared.isAskUserResponseMessage(message)) {
14692
+ const [eventId, awaiter] = this.askUserAwaiter.entries().next().value || [];
14693
+ if (eventId && awaiter) {
14694
+ this.askUserAwaiter.delete(eventId);
14695
+ awaiter(message);
14696
+ }
14697
+ this.timerManager.clearIdleTimer();
14698
+ return;
14699
+ }
14700
+ if ("type" in message && message.type === "user") {
14229
14701
  const userMessage = message;
14230
14702
  await this.coordinator.enqueue(userMessage);
14231
14703
  this.timerManager.clearIdleTimer();
14232
14704
  }
14233
- },
14234
- onPermissionResponse: async (response) => {
14235
- const awaiter = this.permissionAwaiter.get(response.opCode);
14236
- if (awaiter) {
14237
- this.permissionAwaiter.delete(response.opCode);
14238
- awaiter(response);
14239
- }
14240
14705
  }
14241
14706
  }
14242
14707
  };
@@ -14275,9 +14740,8 @@ URL: ${result.pullRequestUrl}`
14275
14740
  this.log("info", "GIT", `Patch: ${gitStateResult.patchPath}`);
14276
14741
  }
14277
14742
  }
14278
- async createLogger(options) {
14279
- const { createLogger } = await Promise.resolve().then(function () { return require('./logger-BZKdzrRM.cjs'); }).then(function (n) { return n.logger$1; });
14280
- return createLogger(options);
14743
+ createWorkerLogger(options) {
14744
+ return _package.createLogger(options);
14281
14745
  }
14282
14746
  log(level, category, message, ...args) {
14283
14747
  if (this.logger) {
@@ -15900,28 +16364,6 @@ Example response format:
15900
16364
  CRITICAL: Respond with ONLY the JSON object, no additional text before or after. Now analyze the changes and provide your response:`;
15901
16365
  }
15902
16366
 
15903
- async function downloadImage(url, targetDir) {
15904
- try {
15905
- const urlPath = new URL(url).pathname;
15906
- const extension = path$1.extname(urlPath) || ".jpg";
15907
- const filename = `${node_crypto.randomUUID()}${extension}`;
15908
- const filePath = path$1.join(targetDir, filename);
15909
- const response = await fetch(url);
15910
- if (!response.ok) {
15911
- throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
15912
- }
15913
- if (!response.body) {
15914
- throw new Error("Response body is null");
15915
- }
15916
- const nodeStream = response.body;
15917
- const fileStream = fs$1.createWriteStream(filePath);
15918
- await promises$1.pipeline(nodeStream, fileStream);
15919
- return filePath;
15920
- } catch (error) {
15921
- throw new Error(`Failed to download image from ${url}: ${error instanceof Error ? error.message : String(error)}`);
15922
- }
15923
- }
15924
-
15925
16367
  class CodexWorker {
15926
16368
  constructor(credentials, options) {
15927
16369
  this.credentials = credentials;
@@ -16037,6 +16479,7 @@ class CodexWorker {
16037
16479
  };
16038
16480
  this.coordinator = new MessageCoordinator({
16039
16481
  workerType: "codex",
16482
+ workClient,
16040
16483
  handlers: {
16041
16484
  onNormalMessage: async (message) => {
16042
16485
  const input = await this.convertSDKMessageToCodexInput(message);
@@ -16109,7 +16552,6 @@ class CodexWorker {
16109
16552
  }
16110
16553
  );
16111
16554
  this.timerManager.startIdleTimer();
16112
- this.context.workClient.sendWorkerReady();
16113
16555
  this.log("info", "BASH", `Worker ready after command execution (exit code: ${exitCode})`);
16114
16556
  }
16115
16557
  async buildCreatePRPrompt() {
@@ -16166,8 +16608,7 @@ ${stats.files.map((f) => ` ${f.path}: +${f.insertions}/-${f.deletions}`).join("
16166
16608
  skipGitRepoCheck: true
16167
16609
  });
16168
16610
  }
16169
- this.context.workClient.sendWorkRunning();
16170
- this.coordinator.setWorkerState("processing-sdk");
16611
+ this.coordinator.setWorkerState("running");
16171
16612
  while (!this.isStopping) {
16172
16613
  this.log("debug", "AGENT", `Loop iteration: turnCount=${this.turnCount}, queueLength=${this.messageQueue.length}`);
16173
16614
  let userInput = null;
@@ -16179,7 +16620,6 @@ ${stats.files.map((f) => ` ${f.path}: +${f.insertions}/-${f.deletions}`).join("
16179
16620
  await this.reportGitState("idle");
16180
16621
  this.coordinator.setWorkerState("idle");
16181
16622
  this.timerManager.startIdleTimer();
16182
- this.context.workClient.sendWorkerReady();
16183
16623
  this.log("info", "AGENT", "Sent worker-ready, waiting for next message");
16184
16624
  userInput = await this.waitForNextMessage();
16185
16625
  }
@@ -16189,7 +16629,7 @@ ${stats.files.map((f) => ` ${f.path}: +${f.insertions}/-${f.deletions}`).join("
16189
16629
  }
16190
16630
  try {
16191
16631
  this.timerManager.clearIdleTimer();
16192
- this.coordinator.setWorkerState("processing-sdk");
16632
+ this.coordinator.setWorkerState("running");
16193
16633
  await this.processUserInput(userInput);
16194
16634
  this.turnCount++;
16195
16635
  this.log("debug", "AGENT", `Message processed, turnCount now ${this.turnCount}`);
@@ -16225,7 +16665,7 @@ ${stats.files.map((f) => ` ${f.path}: +${f.insertions}/-${f.deletions}`).join("
16225
16665
  const turnOptions = this.inMergeRequest ? { outputSchema: getPROutputSchema() } : {};
16226
16666
  this.log("debug", "AGENT", "Calling thread.runStreamed");
16227
16667
  const { events } = await this.thread.runStreamed(input, turnOptions);
16228
- this.context.workClient.sendWorkRunning();
16668
+ this.coordinator.setWorkerState("running");
16229
16669
  this.log("debug", "AGENT", "Starting to process events");
16230
16670
  for await (const event of events) {
16231
16671
  if (this.isStopping) {
@@ -16400,8 +16840,8 @@ URL: ${result.pullRequestUrl}`);
16400
16840
  }
16401
16841
  /**
16402
16842
  * Convert SDKUserMessage to Codex Input format
16403
- * Handles both text-only messages and messages with images
16404
- * Downloads images from URLs to local attachments directory
16843
+ * Handles both text-only messages and messages with images and documents
16844
+ * Downloads images and documents from URLs to local attachments directory
16405
16845
  */
16406
16846
  async convertSDKMessageToCodexInput(message) {
16407
16847
  const content = message.message.content;
@@ -16410,6 +16850,10 @@ URL: ${result.pullRequestUrl}`);
16410
16850
  }
16411
16851
  if (Array.isArray(content)) {
16412
16852
  const userInputs = [];
16853
+ const attachmentsDir = _package.machine.resolveAttachmentsDir(
16854
+ this.options.input.userId,
16855
+ this.taskId
16856
+ );
16413
16857
  for (const block of content) {
16414
16858
  if (block.type === "text" && block.text) {
16415
16859
  userInputs.push({
@@ -16419,19 +16863,30 @@ URL: ${result.pullRequestUrl}`);
16419
16863
  } else if (block.type === "image" && block.source && block.source.url) {
16420
16864
  const url = block.source.url;
16421
16865
  try {
16422
- const attachmentsDir = _package.machine.resolveAttachmentsDir(
16423
- this.options.input.userId,
16424
- this.taskId
16425
- );
16426
- const localPath = await downloadImage(url, attachmentsDir);
16427
- this.log("info", "IMAGE", `Downloaded image from ${url} to ${localPath}`);
16866
+ const { filePath } = await downloadFile(url, attachmentsDir, false);
16867
+ this.log("info", "IMAGE", `Downloaded image from ${url} to ${filePath}`);
16428
16868
  userInputs.push({
16429
16869
  type: "local_image",
16430
- path: localPath
16870
+ path: filePath
16431
16871
  });
16432
16872
  } catch (error) {
16433
16873
  this.log("error", "IMAGE", `Failed to download image from ${url}:`, error);
16434
16874
  }
16875
+ } else if (block.type === "document" && block.source && block.source.url) {
16876
+ const url = block.source.url;
16877
+ try {
16878
+ const { filePath, mimeType, filename } = await downloadFile(url, attachmentsDir, true);
16879
+ this.log("info", "DOCUMENT", `Downloaded document from ${url} to ${filePath}`);
16880
+ const title = block.title || filename;
16881
+ userInputs.push({
16882
+ type: "text",
16883
+ text: `Document: ${filePath}
16884
+ Title: ${title}
16885
+ Type: ${mimeType}`
16886
+ });
16887
+ } catch (error) {
16888
+ this.log("error", "DOCUMENT", `Failed to download document from ${url}:`, error);
16889
+ }
16435
16890
  }
16436
16891
  }
16437
16892
  if (userInputs.length === 1 && userInputs[0].type === "text") {
@@ -16501,7 +16956,7 @@ URL: ${result.pullRequestUrl}`);
16501
16956
  }
16502
16957
  async handleStopHook() {
16503
16958
  await this.reportGitState("stop");
16504
- this.context.workClient.sendWorkerReady();
16959
+ this.coordinator.setWorkerState("idle");
16505
16960
  }
16506
16961
  async loadAgentConfiguration() {
16507
16962
  if (!this.options.input.agentId || this.options.input.agentId === "default") {
@@ -16593,14 +17048,15 @@ URL: ${result.pullRequestUrl}`);
16593
17048
  this.timerManager.stopTask("event");
16594
17049
  },
16595
17050
  onTaskMessage: async (message) => {
16596
- if (message.type === "user") {
17051
+ if (shared.isAskUserResponseMessage(message)) {
17052
+ this.log("debug", "AGENT", "Received ask_user_response (not used by Codex)");
17053
+ return;
17054
+ }
17055
+ if ("type" in message && message.type === "user") {
16597
17056
  const userMessage = message;
16598
17057
  await this.coordinator.enqueue(userMessage);
16599
17058
  this.timerManager.clearIdleTimer();
16600
17059
  }
16601
- },
16602
- onPermissionResponse: async (response) => {
16603
- this.log("debug", "AGENT", `Permission response received: ${response.behavior}`);
16604
17060
  }
16605
17061
  }
16606
17062
  };
@@ -16955,7 +17411,7 @@ cli.command("upgrade", "Upgrade CLI to the latest version", {}, async (argv) =>
16955
17411
  }
16956
17412
  }
16957
17413
  try {
16958
- const { version } = await Promise.resolve().then(function () { return require('./logger-BZKdzrRM.cjs'); }).then(function (n) { return n._package; });
17414
+ const { version } = await Promise.resolve().then(function () { return require('./logger-DzYRcKN1.cjs'); }).then(function (n) { return n._package; });
16959
17415
  console.log(chalk.green(`
16960
17416
  \u2713 Now running version: ${version}`));
16961
17417
  } catch {
@@ -17137,7 +17593,7 @@ cli.command(
17137
17593
  },
17138
17594
  async (argv) => {
17139
17595
  try {
17140
- const { testCommand } = await Promise.resolve().then(function () { return require('./index-DuBvuZ3A.cjs'); });
17596
+ const { testCommand } = await Promise.resolve().then(function () { return require('./index-Cs99FGLe.cjs'); });
17141
17597
  await testCommand(argv);
17142
17598
  } catch (error) {
17143
17599
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Test mode failed");