@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.
- package/dist/{index-Dw2iFM1t.cjs → index-B_Hr2zCf.cjs} +731 -275
- package/dist/{index-DuBvuZ3A.cjs → index-Cs99FGLe.cjs} +9 -91
- package/dist/{index-zM9f-RKY.mjs → index-DNDJMHW8.mjs} +9 -91
- package/dist/{index-CvGINSkT.mjs → index-yYa7NL_3.mjs} +738 -282
- package/dist/index.cjs +7 -4
- package/dist/index.mjs +7 -4
- package/dist/lib.cjs +5 -1
- package/dist/lib.d.cts +111 -0
- package/dist/lib.d.mts +111 -0
- package/dist/lib.mjs +5 -1
- package/dist/{logger-7E71dnBD.mjs → logger-BzpMLIL-.mjs} +158 -24
- package/dist/{logger-BZKdzrRM.cjs → logger-DzYRcKN1.cjs} +140 -5
- package/dist/sandbox/node-proxy-boot.js +13 -0
- package/package.json +6 -4
|
@@ -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-
|
|
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://
|
|
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
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
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
|
|
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
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
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(
|
|
12689
|
-
|
|
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
|
|
12856
|
+
eventId,
|
|
12693
12857
|
taskId: this.context.taskId,
|
|
12694
12858
|
from: "worker",
|
|
12695
|
-
message:
|
|
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
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
|
|
12778
|
-
|
|
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
|
-
|
|
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.
|
|
13746
|
+
this.setWorkerState("running");
|
|
13441
13747
|
try {
|
|
13442
13748
|
await this.config.handlers.onBashCommand(envelope.content, envelope.originalMessage);
|
|
13443
13749
|
} finally {
|
|
13444
|
-
this.
|
|
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.
|
|
13759
|
+
this.setWorkerState("running");
|
|
13454
13760
|
try {
|
|
13455
13761
|
await this.config.handlers.onMergeRequest(envelope.originalMessage);
|
|
13456
13762
|
} finally {
|
|
13457
|
-
this.
|
|
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
|
|
13480
|
-
|
|
13481
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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, "
|
|
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.
|
|
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.
|
|
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("
|
|
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.
|
|
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
|
|
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
|
-
|
|
13947
|
-
|
|
13948
|
-
|
|
13949
|
-
|
|
13950
|
-
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
13955
|
-
|
|
13956
|
-
|
|
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
|
-
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13964
|
-
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
13968
|
-
|
|
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.
|
|
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
|
|
14090
|
-
|
|
14091
|
-
|
|
14092
|
-
|
|
14093
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14572
|
+
const config = {
|
|
14114
14573
|
customSystemPrompt: void 0,
|
|
14115
14574
|
customModel: void 0,
|
|
14116
14575
|
customMaxTurns: void 0,
|
|
14117
|
-
customMcpServers: {},
|
|
14118
14576
|
customAllowedTools: [],
|
|
14119
|
-
|
|
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
|
|
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
|
-
|
|
14279
|
-
|
|
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.
|
|
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("
|
|
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.
|
|
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
|
|
16423
|
-
|
|
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:
|
|
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.
|
|
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
|
|
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-
|
|
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-
|
|
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");
|