@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
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import yargs from 'yargs';
|
|
2
2
|
import { hideBin } from 'yargs/helpers';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import { encodeBase64, createKeyPairWithUit8Array, encryptMachineEncryptionKey, generateAESKey, decodeBase64, decryptWithEphemeralKey, createEventId, encryptFileContent, machineAuth, encryptSdkMessage, decryptSdkMessage, loadAgentConfig, getAgentContext, workerAuth } from '@agentrix/shared';
|
|
5
|
-
import { randomBytes, randomUUID
|
|
4
|
+
import { encodeBase64, createKeyPairWithUit8Array, encryptMachineEncryptionKey, generateAESKey, decodeBase64, decryptWithEphemeralKey, createEventId, encryptFileContent, machineAuth, encryptSdkMessage, decryptSdkMessage, loadAgentConfig, getAgentContext, workerAuth, isAskUserResponseMessage } from '@agentrix/shared';
|
|
5
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
6
6
|
import axios from 'axios';
|
|
7
|
-
import { m as machine, l as logger, p as projectPath, a as packageJson, c as createLogger, g as getLogPath, b as logger$1 } from './logger-
|
|
8
|
-
import * as fs from 'node:fs';
|
|
9
|
-
import { existsSync, rmSync, readdirSync, mkdirSync, createWriteStream } from 'node:fs';
|
|
7
|
+
import { m as machine, l as logger, p as projectPath, a as packageJson, c as createLogger, g as getLogPath, b as logger$1 } from './logger-BzpMLIL-.mjs';
|
|
10
8
|
import { createInterface } from 'node:readline';
|
|
11
9
|
import fs$1, { readFileSync, existsSync as existsSync$1 } from 'fs';
|
|
12
10
|
import path$1, { join } from 'path';
|
|
@@ -14,22 +12,26 @@ import os, { homedir } from 'node:os';
|
|
|
14
12
|
import open from 'open';
|
|
15
13
|
import { io } from 'socket.io-client';
|
|
16
14
|
import { EventEmitter } from 'node:events';
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import { existsSync, createWriteStream, readdirSync, mkdirSync } from 'node:fs';
|
|
17
17
|
import * as path from 'node:path';
|
|
18
|
-
import { join as join$1,
|
|
18
|
+
import { join as join$1, basename, extname, dirname } from 'node:path';
|
|
19
19
|
import { spawn, execSync } from 'child_process';
|
|
20
20
|
import psList from 'ps-list';
|
|
21
21
|
import spawn$1 from 'cross-spawn';
|
|
22
|
+
import { isSupportedPlatform, checkSandboxDependencies, NetworkManager, SandboxManager } from '@xmz-ai/sandbox-runtime';
|
|
23
|
+
import { getPlatform } from '@xmz-ai/sandbox-runtime/dist/utils/platform.js';
|
|
22
24
|
import fastify from 'fastify';
|
|
23
25
|
import { z } from 'zod';
|
|
24
26
|
import { validatorCompiler, serializerCompiler } from 'fastify-type-provider-zod';
|
|
25
27
|
import { pathToFileURL } from 'url';
|
|
28
|
+
import { pipeline } from 'node:stream/promises';
|
|
26
29
|
import { AbortError, query, createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
|
|
27
30
|
import simpleGit, { CheckRepoActions } from 'simple-git';
|
|
28
31
|
import { writeFile } from 'node:fs/promises';
|
|
29
|
-
import { randomUUID } from 'crypto';
|
|
32
|
+
import { randomUUID as randomUUID$1 } from 'crypto';
|
|
30
33
|
import { Codex } from '@openai/codex-sdk';
|
|
31
34
|
import { ZodFirstPartyTypeKind } from 'zod/v3';
|
|
32
|
-
import { pipeline } from 'node:stream/promises';
|
|
33
35
|
|
|
34
36
|
async function delay(ms) {
|
|
35
37
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -44,22 +46,12 @@ async function daemonPost(path, body) {
|
|
|
44
46
|
error: errorMessage
|
|
45
47
|
};
|
|
46
48
|
}
|
|
47
|
-
try {
|
|
48
|
-
process.kill(state.pid, 0);
|
|
49
|
-
} catch (error) {
|
|
50
|
-
const errorMessage = "Daemon is not running, file is stale";
|
|
51
|
-
logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
|
|
52
|
-
return {
|
|
53
|
-
error: errorMessage
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
49
|
try {
|
|
57
50
|
const timeout = process.env.AGENTRIX_DAEMON_HTTP_TIMEOUT ? parseInt(process.env.AGENTRIX_DAEMON_HTTP_TIMEOUT) : 1e4;
|
|
58
|
-
const response = await fetch(`http://
|
|
51
|
+
const response = await fetch(`http://agentrix-local.xmz.ai:${state.port}${path}`, {
|
|
59
52
|
method: "POST",
|
|
60
53
|
headers: { "Content-Type": "application/json" },
|
|
61
54
|
body: JSON.stringify(body || {}),
|
|
62
|
-
// Mostly increased for stress test
|
|
63
55
|
signal: AbortSignal.timeout(timeout)
|
|
64
56
|
});
|
|
65
57
|
if (!response.ok) {
|
|
@@ -205,11 +197,8 @@ async function handleAuthLogout() {
|
|
|
205
197
|
console.log(chalk.gray("Stopped daemon"));
|
|
206
198
|
} catch {
|
|
207
199
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
rmSync(paths.rootDir, { recursive: true, force: true });
|
|
211
|
-
console.log(chalk.gray(`Removed agentrix home directory`));
|
|
212
|
-
}
|
|
200
|
+
await machine.clearCredentials();
|
|
201
|
+
console.log(chalk.gray(`Removed credentials`));
|
|
213
202
|
console.log(chalk.green("\u2713 Successfully logged out"));
|
|
214
203
|
} catch (error) {
|
|
215
204
|
throw new Error(`Failed to logout: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -11865,7 +11854,7 @@ async function findAllAgentrixProcesses() {
|
|
|
11865
11854
|
async function findRunawayAgentrixProcesses() {
|
|
11866
11855
|
const allProcesses = await findAllAgentrixProcesses();
|
|
11867
11856
|
return allProcesses.filter(
|
|
11868
|
-
(p) => p.pid !== process.pid && (p.type === "daemon" || p.type === "worker")
|
|
11857
|
+
(p) => p.pid !== process.pid && (p.type === "daemon" || p.type === "worker" || p.type === "upgrade-daemon")
|
|
11869
11858
|
).map((p) => ({ pid: p.pid, command: p.command }));
|
|
11870
11859
|
}
|
|
11871
11860
|
async function killRunawayAgentrixProcesses() {
|
|
@@ -11967,6 +11956,27 @@ async function runDoctorCommand(filter) {
|
|
|
11967
11956
|
} catch (error) {
|
|
11968
11957
|
console.log(chalk.red("\u274C Error reading credentials"));
|
|
11969
11958
|
}
|
|
11959
|
+
console.log(chalk.bold("\n\u{1F512} Sandbox Dependencies"));
|
|
11960
|
+
const platform = getPlatform();
|
|
11961
|
+
if (isSupportedPlatform(platform)) {
|
|
11962
|
+
console.log(`Platform: ${chalk.green(platform)} (supported)`);
|
|
11963
|
+
const depsOk = checkSandboxDependencies();
|
|
11964
|
+
if (depsOk) {
|
|
11965
|
+
console.log(chalk.green("\u2713 All sandbox dependencies available"));
|
|
11966
|
+
} else {
|
|
11967
|
+
console.log(chalk.yellow("\u26A0\uFE0F Some sandbox dependencies missing"));
|
|
11968
|
+
if (platform === "linux") {
|
|
11969
|
+
console.log(chalk.gray(" Required: bubblewrap, socat"));
|
|
11970
|
+
console.log(chalk.gray(" Install: sudo apt install bubblewrap socat"));
|
|
11971
|
+
} else if (platform === "macos") {
|
|
11972
|
+
console.log(chalk.gray(" Required: ripgrep"));
|
|
11973
|
+
console.log(chalk.gray(" Install: brew install ripgrep"));
|
|
11974
|
+
}
|
|
11975
|
+
}
|
|
11976
|
+
} else {
|
|
11977
|
+
console.log(`Platform: ${chalk.yellow(platform)} (not supported)`);
|
|
11978
|
+
console.log(chalk.gray(" \u26A0\uFE0F Sandbox will be disabled"));
|
|
11979
|
+
}
|
|
11970
11980
|
}
|
|
11971
11981
|
console.log(chalk.bold("\n\u{1F916} Daemon Status"));
|
|
11972
11982
|
try {
|
|
@@ -12191,9 +12201,11 @@ function createPromiseWithTimeout(options) {
|
|
|
12191
12201
|
class TaskWorkerManager {
|
|
12192
12202
|
pidToTrackedSession;
|
|
12193
12203
|
pidToAwaiter;
|
|
12194
|
-
|
|
12204
|
+
sandboxPool;
|
|
12205
|
+
constructor(sandboxPool) {
|
|
12195
12206
|
this.pidToTrackedSession = /* @__PURE__ */ new Map();
|
|
12196
12207
|
this.pidToAwaiter = /* @__PURE__ */ new Map();
|
|
12208
|
+
this.sandboxPool = sandboxPool || null;
|
|
12197
12209
|
}
|
|
12198
12210
|
getCurrentSessions() {
|
|
12199
12211
|
return Array.from(this.pidToTrackedSession.values());
|
|
@@ -12234,9 +12246,15 @@ class TaskWorkerManager {
|
|
|
12234
12246
|
this.pidToTrackedSession.set(workerProcess.pid, tracked);
|
|
12235
12247
|
workerProcess.on("exit", (code, signal) => {
|
|
12236
12248
|
this.pidToTrackedSession.delete(workerProcess.pid);
|
|
12249
|
+
if (this.sandboxPool) {
|
|
12250
|
+
this.sandboxPool.disposeWorkerSandbox(data.taskId);
|
|
12251
|
+
}
|
|
12237
12252
|
});
|
|
12238
12253
|
workerProcess.on("error", (error) => {
|
|
12239
12254
|
this.pidToTrackedSession.delete(workerProcess.pid);
|
|
12255
|
+
if (this.sandboxPool) {
|
|
12256
|
+
this.sandboxPool.disposeWorkerSandbox(data.taskId);
|
|
12257
|
+
}
|
|
12240
12258
|
});
|
|
12241
12259
|
}
|
|
12242
12260
|
async startWorker(options) {
|
|
@@ -12261,14 +12279,53 @@ class TaskWorkerManager {
|
|
|
12261
12279
|
"--idle-timeout",
|
|
12262
12280
|
"120"
|
|
12263
12281
|
];
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12282
|
+
let workerProcess;
|
|
12283
|
+
if (this.sandboxPool?.isEnabled()) {
|
|
12284
|
+
try {
|
|
12285
|
+
const sandbox = await this.sandboxPool.createWorkerSandbox(
|
|
12286
|
+
options.taskId,
|
|
12287
|
+
options.userId,
|
|
12288
|
+
cwd
|
|
12289
|
+
);
|
|
12290
|
+
if (!sandbox) {
|
|
12291
|
+
throw new Error("Failed to create sandbox instance");
|
|
12292
|
+
}
|
|
12293
|
+
const { projectPath } = await import('./logger-BzpMLIL-.mjs').then(function (n) { return n.d; });
|
|
12294
|
+
const { join } = await import('path');
|
|
12295
|
+
const entrypoint = join(projectPath(), "dist", "index.mjs");
|
|
12296
|
+
const nodeArgs = ["--no-warnings", "--no-deprecation", entrypoint, ...args];
|
|
12297
|
+
const originalCommand = `"${process.execPath}" ${nodeArgs.map((a) => `"${a}"`).join(" ")}`;
|
|
12298
|
+
const sandboxedCommand = await this.sandboxPool.wrapWorkerCommand(
|
|
12299
|
+
options.taskId,
|
|
12300
|
+
originalCommand
|
|
12301
|
+
);
|
|
12302
|
+
logger.debug(`[SESSION] Sandboxed command for task ${options.taskId}: ${sandboxedCommand}`);
|
|
12303
|
+
workerProcess = spawn(sandboxedCommand, {
|
|
12304
|
+
shell: true,
|
|
12305
|
+
cwd,
|
|
12306
|
+
detached: true,
|
|
12307
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
12308
|
+
env: {
|
|
12309
|
+
...process.env
|
|
12310
|
+
// Environment variables controlled by SandboxManager
|
|
12311
|
+
}
|
|
12312
|
+
});
|
|
12313
|
+
logger.info(`[SESSION] Worker started with sandbox, PID: ${workerProcess.pid}`);
|
|
12314
|
+
} catch (error) {
|
|
12315
|
+
logger.error(`[SESSION] Failed to setup sandbox for task ${options.taskId}:`, error);
|
|
12316
|
+
ack.status = "failed";
|
|
12317
|
+
ack.message = `Sandbox setup failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
12318
|
+
return ack;
|
|
12270
12319
|
}
|
|
12271
|
-
}
|
|
12320
|
+
} else {
|
|
12321
|
+
workerProcess = spawnAgentrixCLI(args, {
|
|
12322
|
+
cwd,
|
|
12323
|
+
detached: true,
|
|
12324
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
12325
|
+
env: { ...process.env }
|
|
12326
|
+
});
|
|
12327
|
+
logger.info(`[SESSION] Worker started without sandbox, PID: ${workerProcess.pid}`);
|
|
12328
|
+
}
|
|
12272
12329
|
if (process.env.DEBUG) {
|
|
12273
12330
|
workerProcess.stdout?.on("data", (data) => {
|
|
12274
12331
|
logger.debug(`[Daemon] worker stdout: ${data.toString()}`);
|
|
@@ -12387,6 +12444,114 @@ function setupGracefulShutdown(options) {
|
|
|
12387
12444
|
};
|
|
12388
12445
|
}
|
|
12389
12446
|
|
|
12447
|
+
class SandboxPool {
|
|
12448
|
+
networkManager = null;
|
|
12449
|
+
workerSandboxes = /* @__PURE__ */ new Map();
|
|
12450
|
+
settings = null;
|
|
12451
|
+
platform;
|
|
12452
|
+
constructor() {
|
|
12453
|
+
this.platform = getPlatform();
|
|
12454
|
+
}
|
|
12455
|
+
async initialize(settings) {
|
|
12456
|
+
this.settings = settings;
|
|
12457
|
+
if (!settings.enabled) {
|
|
12458
|
+
logger.info("[SANDBOX] Sandbox disabled via settings");
|
|
12459
|
+
return false;
|
|
12460
|
+
}
|
|
12461
|
+
if (!isSupportedPlatform(this.platform)) {
|
|
12462
|
+
logger.warn("[SANDBOX] Platform not supported, sandbox disabled");
|
|
12463
|
+
return false;
|
|
12464
|
+
}
|
|
12465
|
+
try {
|
|
12466
|
+
const apiHost = new URL(machine.serverUrl).hostname;
|
|
12467
|
+
const networkConfig = {
|
|
12468
|
+
allowedDomains: [
|
|
12469
|
+
apiHost,
|
|
12470
|
+
...settings.network.allowedDomains
|
|
12471
|
+
],
|
|
12472
|
+
deniedDomains: settings.network.deniedDomains,
|
|
12473
|
+
allowLocalBinding: false
|
|
12474
|
+
};
|
|
12475
|
+
this.networkManager = new NetworkManager();
|
|
12476
|
+
await this.networkManager.initialize(networkConfig);
|
|
12477
|
+
logger.info("[SANDBOX] Sandbox pool initialized successfully");
|
|
12478
|
+
return true;
|
|
12479
|
+
} catch (error) {
|
|
12480
|
+
logger.error("[SANDBOX] Failed to initialize:", error);
|
|
12481
|
+
throw error;
|
|
12482
|
+
}
|
|
12483
|
+
}
|
|
12484
|
+
async createWorkerSandbox(taskId, userId, workingDirectory) {
|
|
12485
|
+
if (!this.networkManager || !this.settings?.enabled) {
|
|
12486
|
+
return null;
|
|
12487
|
+
}
|
|
12488
|
+
try {
|
|
12489
|
+
const taskDir = machine.resolveTaskDir(userId, taskId);
|
|
12490
|
+
const logsDir = machine.getStatePaths().logsDir;
|
|
12491
|
+
const baseFilesystem = this.settings.filesystem || {};
|
|
12492
|
+
const baseEnv = this.settings.env || {};
|
|
12493
|
+
const filesystemConfig = {
|
|
12494
|
+
...baseFilesystem,
|
|
12495
|
+
allowWrite: [
|
|
12496
|
+
...baseFilesystem.allowWrite || [],
|
|
12497
|
+
taskDir,
|
|
12498
|
+
workingDirectory,
|
|
12499
|
+
logsDir
|
|
12500
|
+
]
|
|
12501
|
+
};
|
|
12502
|
+
if (this.platform === "linux" && baseFilesystem.allowRead) {
|
|
12503
|
+
filesystemConfig.allowRead = [
|
|
12504
|
+
...baseFilesystem.allowRead
|
|
12505
|
+
];
|
|
12506
|
+
}
|
|
12507
|
+
const instanceConfig = {
|
|
12508
|
+
filesystem: filesystemConfig,
|
|
12509
|
+
env: baseEnv
|
|
12510
|
+
};
|
|
12511
|
+
const sandbox = new SandboxManager(this.networkManager, instanceConfig);
|
|
12512
|
+
this.workerSandboxes.set(taskId, sandbox);
|
|
12513
|
+
logger.info(`[SANDBOX] Created sandbox for task ${taskId}`);
|
|
12514
|
+
return sandbox;
|
|
12515
|
+
} catch (error) {
|
|
12516
|
+
logger.error(`[SANDBOX] Failed to create sandbox for task ${taskId}:`, error);
|
|
12517
|
+
return null;
|
|
12518
|
+
}
|
|
12519
|
+
}
|
|
12520
|
+
async wrapWorkerCommand(taskId, command) {
|
|
12521
|
+
const sandbox = this.workerSandboxes.get(taskId);
|
|
12522
|
+
if (!sandbox) {
|
|
12523
|
+
throw new Error(`No sandbox found for task ${taskId}`);
|
|
12524
|
+
}
|
|
12525
|
+
const wrapped = await sandbox.wrapWithSandbox(command);
|
|
12526
|
+
logger.debug(`[SANDBOX] Wrapped command for task ${taskId}`);
|
|
12527
|
+
return wrapped;
|
|
12528
|
+
}
|
|
12529
|
+
disposeWorkerSandbox(taskId) {
|
|
12530
|
+
const sandbox = this.workerSandboxes.get(taskId);
|
|
12531
|
+
if (sandbox) {
|
|
12532
|
+
sandbox.dispose();
|
|
12533
|
+
this.workerSandboxes.delete(taskId);
|
|
12534
|
+
logger.debug(`[SANDBOX] Disposed sandbox for task ${taskId}`);
|
|
12535
|
+
}
|
|
12536
|
+
}
|
|
12537
|
+
async shutdown() {
|
|
12538
|
+
logger.info("[SANDBOX] Shutting down sandbox pool");
|
|
12539
|
+
for (const [taskId, sandbox] of this.workerSandboxes.entries()) {
|
|
12540
|
+
sandbox.dispose();
|
|
12541
|
+
logger.debug(`[SANDBOX] Disposed sandbox for task ${taskId}`);
|
|
12542
|
+
}
|
|
12543
|
+
this.workerSandboxes.clear();
|
|
12544
|
+
if (this.networkManager) {
|
|
12545
|
+
await this.networkManager.shutdown();
|
|
12546
|
+
this.networkManager = null;
|
|
12547
|
+
logger.info("[SANDBOX] Network manager shutdown complete");
|
|
12548
|
+
}
|
|
12549
|
+
}
|
|
12550
|
+
isEnabled() {
|
|
12551
|
+
return this.settings?.enabled === true;
|
|
12552
|
+
}
|
|
12553
|
+
}
|
|
12554
|
+
|
|
12390
12555
|
async function startDaemon() {
|
|
12391
12556
|
Object.assign(logger, createLogger({ type: "daemon" }));
|
|
12392
12557
|
const { requestShutdown, shutdownPromise } = setupGracefulShutdown({
|
|
@@ -12415,7 +12580,9 @@ async function startDaemon() {
|
|
|
12415
12580
|
}
|
|
12416
12581
|
const credentials = await authAndSetupMachineIfNeeded();
|
|
12417
12582
|
logger.debug("[DAEMON RUN] Auth and machine setup complete");
|
|
12418
|
-
const
|
|
12583
|
+
const sandboxPool = new SandboxPool();
|
|
12584
|
+
await sandboxPool.initialize(machine.getSandboxSettings());
|
|
12585
|
+
const sessionManager = new TaskWorkerManager(sandboxPool);
|
|
12419
12586
|
const { port: controlPort, stop: stopControlServer } = await startDaemonControlServer({
|
|
12420
12587
|
getChildren: () => sessionManager.getCurrentSessions(),
|
|
12421
12588
|
stopSession: (id) => sessionManager.stopSession(id),
|
|
@@ -12463,6 +12630,7 @@ async function startDaemon() {
|
|
|
12463
12630
|
const cleanupAndShutdown = async (source, errorMessage) => {
|
|
12464
12631
|
await machineClient.disconnect();
|
|
12465
12632
|
await stopControlServer();
|
|
12633
|
+
await sandboxPool.shutdown();
|
|
12466
12634
|
await cleanupDaemonState();
|
|
12467
12635
|
await stopCaffeinate();
|
|
12468
12636
|
await machine.releaseDaemonLock(daemonLockHandle);
|
|
@@ -12579,14 +12747,6 @@ function createWorkerEventHandlers(context) {
|
|
|
12579
12747
|
}
|
|
12580
12748
|
}
|
|
12581
12749
|
}
|
|
12582
|
-
},
|
|
12583
|
-
"require-permission-response": async (data) => {
|
|
12584
|
-
if (data.taskId !== context.taskId) {
|
|
12585
|
-
return;
|
|
12586
|
-
}
|
|
12587
|
-
if (context.onPermissionResponse) {
|
|
12588
|
-
await context.onPermissionResponse(data);
|
|
12589
|
-
}
|
|
12590
12750
|
}
|
|
12591
12751
|
};
|
|
12592
12752
|
}
|
|
@@ -12627,7 +12787,6 @@ class WorkerClient {
|
|
|
12627
12787
|
cwd: normalizedCwd,
|
|
12628
12788
|
stopTask: options.stopTask,
|
|
12629
12789
|
onTaskMessage: options.onTaskMessage,
|
|
12630
|
-
onPermissionResponse: options.onPermissionResponse,
|
|
12631
12790
|
onGitPush: options.onGitPush,
|
|
12632
12791
|
dataEncryptionKey: config.dataEncryptionKey
|
|
12633
12792
|
};
|
|
@@ -12656,26 +12815,32 @@ class WorkerClient {
|
|
|
12656
12815
|
}
|
|
12657
12816
|
this.client.disconnect();
|
|
12658
12817
|
}
|
|
12659
|
-
sendTaskMessage(message) {
|
|
12660
|
-
const
|
|
12661
|
-
|
|
12662
|
-
|
|
12663
|
-
|
|
12664
|
-
|
|
12665
|
-
|
|
12818
|
+
sendTaskMessage(message, options) {
|
|
12819
|
+
const { replaceCwd = true } = options || {};
|
|
12820
|
+
let processedMessage = message;
|
|
12821
|
+
if (replaceCwd) {
|
|
12822
|
+
const cwdWithSlash = this.context.cwd;
|
|
12823
|
+
const cwdWithoutSlash = cwdWithSlash.slice(0, -1);
|
|
12824
|
+
let content = JSON.stringify(message);
|
|
12825
|
+
content = content.replaceAll(cwdWithSlash, "");
|
|
12826
|
+
content = content.replaceAll(cwdWithoutSlash, ".");
|
|
12827
|
+
processedMessage = JSON.parse(content);
|
|
12828
|
+
}
|
|
12666
12829
|
let encryptedMessage;
|
|
12667
12830
|
if (this.context.dataEncryptionKey) {
|
|
12668
|
-
encryptedMessage = encryptSdkMessage(
|
|
12669
|
-
|
|
12831
|
+
encryptedMessage = encryptSdkMessage(processedMessage, this.context.dataEncryptionKey);
|
|
12832
|
+
processedMessage = void 0;
|
|
12670
12833
|
}
|
|
12834
|
+
const eventId = createEventId();
|
|
12671
12835
|
const payload = {
|
|
12672
|
-
eventId
|
|
12836
|
+
eventId,
|
|
12673
12837
|
taskId: this.context.taskId,
|
|
12674
12838
|
from: "worker",
|
|
12675
|
-
message:
|
|
12839
|
+
message: processedMessage,
|
|
12676
12840
|
encryptedMessage
|
|
12677
12841
|
};
|
|
12678
12842
|
this.client.send("task-message", payload);
|
|
12843
|
+
return eventId;
|
|
12679
12844
|
}
|
|
12680
12845
|
sendWorkerInitializing() {
|
|
12681
12846
|
const workerInitializingEvent = {
|
|
@@ -12686,12 +12851,13 @@ class WorkerClient {
|
|
|
12686
12851
|
};
|
|
12687
12852
|
this.client.send("worker-initializing", workerInitializingEvent);
|
|
12688
12853
|
}
|
|
12689
|
-
sendWorkerReady() {
|
|
12854
|
+
sendWorkerReady(duration) {
|
|
12690
12855
|
const workerReadyEvent = {
|
|
12691
12856
|
eventId: createEventId(),
|
|
12692
12857
|
taskId: this.context.taskId,
|
|
12693
12858
|
machineId: this.context.machineId,
|
|
12694
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
12859
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12860
|
+
...duration !== void 0 && { duration }
|
|
12695
12861
|
};
|
|
12696
12862
|
this.client.send("worker-ready", workerReadyEvent);
|
|
12697
12863
|
}
|
|
@@ -12749,17 +12915,13 @@ ${errorMessage}`,
|
|
|
12749
12915
|
};
|
|
12750
12916
|
this.sendTaskMessage(systemMessage);
|
|
12751
12917
|
}
|
|
12752
|
-
|
|
12753
|
-
|
|
12754
|
-
|
|
12755
|
-
|
|
12756
|
-
|
|
12757
|
-
|
|
12758
|
-
|
|
12759
|
-
};
|
|
12760
|
-
this.client.send("require-permission", permissionRequest);
|
|
12761
|
-
logger.info(`[AGENT] Permission requested for tool: ${toolName}`);
|
|
12762
|
-
return eventId;
|
|
12918
|
+
/**
|
|
12919
|
+
* Send ask-user message to request user input
|
|
12920
|
+
* @param questions - Array of questions (1-4)
|
|
12921
|
+
* @returns eventId for tracking the request
|
|
12922
|
+
*/
|
|
12923
|
+
sendAskUser(questions) {
|
|
12924
|
+
return this.sendTaskMessage({ type: "ask_user", questions }, { replaceCwd: false });
|
|
12763
12925
|
}
|
|
12764
12926
|
sendUpdateTaskAgentSessionId(agentSessionId) {
|
|
12765
12927
|
const updateSessionEvent = {
|
|
@@ -12814,7 +12976,108 @@ ${errorMessage}`,
|
|
|
12814
12976
|
this.client.onEvent("cancel-task", handlers["cancel-task"]);
|
|
12815
12977
|
this.client.onEvent("stop-task", handlers["stop-task"]);
|
|
12816
12978
|
this.client.onEvent("task-message", handlers["task-message"]);
|
|
12817
|
-
|
|
12979
|
+
}
|
|
12980
|
+
}
|
|
12981
|
+
|
|
12982
|
+
const MIME_TYPE_MAP = {
|
|
12983
|
+
// Images
|
|
12984
|
+
".jpg": "image/jpeg",
|
|
12985
|
+
".jpeg": "image/jpeg",
|
|
12986
|
+
".png": "image/png",
|
|
12987
|
+
".gif": "image/gif",
|
|
12988
|
+
".webp": "image/webp",
|
|
12989
|
+
".bmp": "image/bmp",
|
|
12990
|
+
".svg": "image/svg+xml",
|
|
12991
|
+
".ico": "image/x-icon",
|
|
12992
|
+
// Documents
|
|
12993
|
+
".pdf": "application/pdf",
|
|
12994
|
+
".doc": "application/msword",
|
|
12995
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
12996
|
+
".xls": "application/vnd.ms-excel",
|
|
12997
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
12998
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
12999
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
13000
|
+
// Text
|
|
13001
|
+
".txt": "text/plain",
|
|
13002
|
+
".md": "text/markdown",
|
|
13003
|
+
".csv": "text/csv",
|
|
13004
|
+
".json": "application/json",
|
|
13005
|
+
".xml": "application/xml",
|
|
13006
|
+
".html": "text/html",
|
|
13007
|
+
".css": "text/css",
|
|
13008
|
+
".js": "application/javascript",
|
|
13009
|
+
".ts": "application/typescript",
|
|
13010
|
+
// Archives
|
|
13011
|
+
".zip": "application/zip",
|
|
13012
|
+
".tar": "application/x-tar",
|
|
13013
|
+
".gz": "application/gzip",
|
|
13014
|
+
".rar": "application/vnd.rar",
|
|
13015
|
+
// Other
|
|
13016
|
+
".mp3": "audio/mpeg",
|
|
13017
|
+
".mp4": "video/mp4",
|
|
13018
|
+
".wav": "audio/wav",
|
|
13019
|
+
".avi": "video/x-msvideo"
|
|
13020
|
+
};
|
|
13021
|
+
function detectMimeType(extension) {
|
|
13022
|
+
const normalized = extension.toLowerCase();
|
|
13023
|
+
return MIME_TYPE_MAP[normalized] || "application/octet-stream";
|
|
13024
|
+
}
|
|
13025
|
+
function extractExtension(url) {
|
|
13026
|
+
try {
|
|
13027
|
+
const urlObj = new URL(url);
|
|
13028
|
+
const pathExt = extname(urlObj.pathname);
|
|
13029
|
+
if (pathExt) {
|
|
13030
|
+
return pathExt;
|
|
13031
|
+
}
|
|
13032
|
+
const filenameParam = urlObj.searchParams.get("filename") || urlObj.searchParams.get("name") || urlObj.searchParams.get("file");
|
|
13033
|
+
if (filenameParam) {
|
|
13034
|
+
const paramExt = extname(filenameParam);
|
|
13035
|
+
if (paramExt) {
|
|
13036
|
+
return paramExt;
|
|
13037
|
+
}
|
|
13038
|
+
}
|
|
13039
|
+
return "";
|
|
13040
|
+
} catch {
|
|
13041
|
+
return "";
|
|
13042
|
+
}
|
|
13043
|
+
}
|
|
13044
|
+
async function downloadFile(url, targetDir, preserveFilename = false) {
|
|
13045
|
+
try {
|
|
13046
|
+
const extension = extractExtension(url) || "";
|
|
13047
|
+
let filename;
|
|
13048
|
+
if (preserveFilename) {
|
|
13049
|
+
try {
|
|
13050
|
+
const urlPath = new URL(url).pathname;
|
|
13051
|
+
const originalFilename = basename(urlPath);
|
|
13052
|
+
filename = originalFilename && extname(originalFilename) ? originalFilename : `${randomUUID()}${extension || ".dat"}`;
|
|
13053
|
+
} catch {
|
|
13054
|
+
filename = `${randomUUID()}${extension || ".dat"}`;
|
|
13055
|
+
}
|
|
13056
|
+
} else {
|
|
13057
|
+
filename = `${randomUUID()}${extension || ".dat"}`;
|
|
13058
|
+
}
|
|
13059
|
+
const filePath = join$1(targetDir, filename);
|
|
13060
|
+
const response = await fetch(url);
|
|
13061
|
+
if (!response.ok) {
|
|
13062
|
+
throw new Error(`Failed to download file: ${response.status} ${response.statusText}`);
|
|
13063
|
+
}
|
|
13064
|
+
if (!response.body) {
|
|
13065
|
+
throw new Error("Response body is null");
|
|
13066
|
+
}
|
|
13067
|
+
const contentType = response.headers.get("content-type");
|
|
13068
|
+
const mimeType = contentType?.split(";")[0].trim() || detectMimeType(extension);
|
|
13069
|
+
const nodeStream = response.body;
|
|
13070
|
+
const fileStream = createWriteStream(filePath);
|
|
13071
|
+
await pipeline(nodeStream, fileStream);
|
|
13072
|
+
return {
|
|
13073
|
+
filePath,
|
|
13074
|
+
mimeType,
|
|
13075
|
+
filename
|
|
13076
|
+
};
|
|
13077
|
+
} catch (error) {
|
|
13078
|
+
throw new Error(
|
|
13079
|
+
`Failed to download file from ${url}: ${error instanceof Error ? error.message : String(error)}`
|
|
13080
|
+
);
|
|
12818
13081
|
}
|
|
12819
13082
|
}
|
|
12820
13083
|
|
|
@@ -12915,6 +13178,12 @@ async function hasUncommittedChanges(dir) {
|
|
|
12915
13178
|
const status = await git.status();
|
|
12916
13179
|
return !status.isClean();
|
|
12917
13180
|
}
|
|
13181
|
+
async function gitStash(dir, message) {
|
|
13182
|
+
const git = simpleGit(dir);
|
|
13183
|
+
{
|
|
13184
|
+
await git.stash(["push"]);
|
|
13185
|
+
}
|
|
13186
|
+
}
|
|
12918
13187
|
async function getCurrentCommitHash(dir) {
|
|
12919
13188
|
const git = simpleGit(dir);
|
|
12920
13189
|
const log = await git.log({ maxCount: 1 });
|
|
@@ -13004,6 +13273,30 @@ async function executeHook(hooks, hookName, input, logger) {
|
|
|
13004
13273
|
}
|
|
13005
13274
|
}
|
|
13006
13275
|
|
|
13276
|
+
async function checkUncommittedChanges(workingDirectory) {
|
|
13277
|
+
const isRepo = await isGitRepository(workingDirectory);
|
|
13278
|
+
if (!isRepo) {
|
|
13279
|
+
return false;
|
|
13280
|
+
}
|
|
13281
|
+
return await hasUncommittedChanges(workingDirectory);
|
|
13282
|
+
}
|
|
13283
|
+
async function handleUncommittedChanges(workingDirectory, action) {
|
|
13284
|
+
switch (action) {
|
|
13285
|
+
case "Ignore":
|
|
13286
|
+
console.log("[GIT] User chose to ignore uncommitted changes");
|
|
13287
|
+
break;
|
|
13288
|
+
case "Commit":
|
|
13289
|
+
console.log("[GIT] Auto-committing uncommitted changes");
|
|
13290
|
+
await autoCommit(workingDirectory, "WIP: Auto-commit before task");
|
|
13291
|
+
break;
|
|
13292
|
+
case "Stash":
|
|
13293
|
+
console.log("[GIT] Stashing uncommitted changes");
|
|
13294
|
+
await gitStash(workingDirectory);
|
|
13295
|
+
break;
|
|
13296
|
+
case "Abort":
|
|
13297
|
+
throw new Error("Task aborted by user due to uncommitted changes");
|
|
13298
|
+
}
|
|
13299
|
+
}
|
|
13007
13300
|
function createTaskBranchName(taskId) {
|
|
13008
13301
|
return `agentrix/${taskId}`;
|
|
13009
13302
|
}
|
|
@@ -13043,17 +13336,6 @@ async function setupLocalWorkspace(workingDirectory, taskId, hooks) {
|
|
|
13043
13336
|
const isRepo = await isGitRepository(workingDirectory);
|
|
13044
13337
|
const isEmpty = isDirectoryEmpty(workingDirectory);
|
|
13045
13338
|
if (isRepo) {
|
|
13046
|
-
const hasChanges = await hasUncommittedChanges(workingDirectory);
|
|
13047
|
-
if (hasChanges) {
|
|
13048
|
-
throw new Error(
|
|
13049
|
-
`Directory ${workingDirectory} has uncommitted changes.
|
|
13050
|
-
|
|
13051
|
-
Please commit or stash your changes before starting:
|
|
13052
|
-
git add . && git commit -m "WIP"
|
|
13053
|
-
or:
|
|
13054
|
-
git stash`
|
|
13055
|
-
);
|
|
13056
|
-
}
|
|
13057
13339
|
const hasCommits = await hasAnyCommits(workingDirectory);
|
|
13058
13340
|
if (!hasCommits) {
|
|
13059
13341
|
console.log("[GIT] Repository has no commits, creating initial commit");
|
|
@@ -13181,9 +13463,32 @@ async function markCommitAsSent(userId, taskId, commitHash) {
|
|
|
13181
13463
|
await machine.writeLastSentCommitHash(userId, taskId, commitHash);
|
|
13182
13464
|
}
|
|
13183
13465
|
|
|
13466
|
+
function getDefaultPRPrompt(params) {
|
|
13467
|
+
return `All changes have been pushed to branch "${params.branchName}".
|
|
13468
|
+
|
|
13469
|
+
Commit range: ${params.initialCommitHash}..${params.currentCommitHash}
|
|
13470
|
+
|
|
13471
|
+
Based on our conversation context, create a Pull Request:
|
|
13472
|
+
- Title: conventional commits format (feat/fix/docs/refactor/test/chore: description)
|
|
13473
|
+
- Description: what changed, why, and any important decisions
|
|
13474
|
+
|
|
13475
|
+
Use mcp__agentrix__create_pr tool to create the PR.`;
|
|
13476
|
+
}
|
|
13477
|
+
function applyTemplateVariables(template, params) {
|
|
13478
|
+
return template.replace(/\{\{initialCommitHash\}\}/g, params.initialCommitHash).replace(/\{\{currentCommitHash\}\}/g, params.currentCommitHash).replace(/\{\{branchName\}\}/g, params.branchName);
|
|
13479
|
+
}
|
|
13480
|
+
function buildPRPrompt(params, config) {
|
|
13481
|
+
const defaultPrompt = getDefaultPRPrompt(params);
|
|
13482
|
+
if (!config?.customTemplate) {
|
|
13483
|
+
return defaultPrompt;
|
|
13484
|
+
}
|
|
13485
|
+
const customPrompt = applyTemplateVariables(config.customTemplate, params);
|
|
13486
|
+
return config.mode === "replace" ? customPrompt : defaultPrompt + "\n\n" + customPrompt;
|
|
13487
|
+
}
|
|
13488
|
+
|
|
13184
13489
|
function executeCommandStreaming(command, cwd, callbacks, timeoutMs = 6e4) {
|
|
13185
13490
|
return new Promise((resolve) => {
|
|
13186
|
-
const toolUseId = `shell_${randomUUID().replace(/-/g, "")}`;
|
|
13491
|
+
const toolUseId = `shell_${randomUUID$1().replace(/-/g, "")}`;
|
|
13187
13492
|
callbacks.onOutput({
|
|
13188
13493
|
type: "assistant",
|
|
13189
13494
|
message: {
|
|
@@ -13298,6 +13603,7 @@ class MessageCoordinator {
|
|
|
13298
13603
|
currentMessageId = null;
|
|
13299
13604
|
messageIdCounter = 0;
|
|
13300
13605
|
isStopped = false;
|
|
13606
|
+
runStartTime = null;
|
|
13301
13607
|
constructor(config) {
|
|
13302
13608
|
this.config = config;
|
|
13303
13609
|
}
|
|
@@ -13417,11 +13723,11 @@ class MessageCoordinator {
|
|
|
13417
13723
|
async processBashCommand(envelope) {
|
|
13418
13724
|
this.log("info", "COORDINATOR", `Processing bash command: ${envelope.content}`);
|
|
13419
13725
|
await this.waitForState("idle");
|
|
13420
|
-
this.
|
|
13726
|
+
this.setWorkerState("running");
|
|
13421
13727
|
try {
|
|
13422
13728
|
await this.config.handlers.onBashCommand(envelope.content, envelope.originalMessage);
|
|
13423
13729
|
} finally {
|
|
13424
|
-
this.
|
|
13730
|
+
this.setWorkerState("idle");
|
|
13425
13731
|
}
|
|
13426
13732
|
}
|
|
13427
13733
|
/**
|
|
@@ -13430,11 +13736,11 @@ class MessageCoordinator {
|
|
|
13430
13736
|
async processMergeRequest(envelope) {
|
|
13431
13737
|
this.log("info", "COORDINATOR", "Processing merge-request command");
|
|
13432
13738
|
await this.waitForState("idle");
|
|
13433
|
-
this.
|
|
13739
|
+
this.setWorkerState("running");
|
|
13434
13740
|
try {
|
|
13435
13741
|
await this.config.handlers.onMergeRequest(envelope.originalMessage);
|
|
13436
13742
|
} finally {
|
|
13437
|
-
this.
|
|
13743
|
+
this.setWorkerState("idle");
|
|
13438
13744
|
}
|
|
13439
13745
|
}
|
|
13440
13746
|
async waitForState(targetState) {
|
|
@@ -13454,11 +13760,24 @@ class MessageCoordinator {
|
|
|
13454
13760
|
}
|
|
13455
13761
|
/**
|
|
13456
13762
|
* Set the worker state (called by worker when state changes)
|
|
13763
|
+
* Automatically sends WebSocket events and tracks execution duration
|
|
13457
13764
|
*/
|
|
13458
13765
|
setWorkerState(state) {
|
|
13459
|
-
if (this.workerState
|
|
13460
|
-
|
|
13461
|
-
|
|
13766
|
+
if (this.workerState === state) return;
|
|
13767
|
+
const prevState = this.workerState;
|
|
13768
|
+
this.log("info", "COORDINATOR", `Worker state: ${prevState} \u2192 ${state}`);
|
|
13769
|
+
this.workerState = state;
|
|
13770
|
+
if (state === "running" && prevState === "idle") {
|
|
13771
|
+
this.runStartTime = Date.now();
|
|
13772
|
+
this.config.workClient.sendWorkRunning();
|
|
13773
|
+
}
|
|
13774
|
+
if (state === "idle" && prevState === "running") {
|
|
13775
|
+
let duration;
|
|
13776
|
+
if (this.runStartTime) {
|
|
13777
|
+
duration = Date.now() - this.runStartTime;
|
|
13778
|
+
this.runStartTime = null;
|
|
13779
|
+
}
|
|
13780
|
+
this.config.workClient.sendWorkerReady(duration);
|
|
13462
13781
|
}
|
|
13463
13782
|
}
|
|
13464
13783
|
/**
|
|
@@ -13498,7 +13817,7 @@ class ClaudeWorker {
|
|
|
13498
13817
|
messageQueue = [];
|
|
13499
13818
|
messageResolverRef = { current: null };
|
|
13500
13819
|
abortController = new AbortController();
|
|
13501
|
-
|
|
13820
|
+
askUserAwaiter = /* @__PURE__ */ new Map();
|
|
13502
13821
|
filteredToolUseIds = /* @__PURE__ */ new Set();
|
|
13503
13822
|
timerManager;
|
|
13504
13823
|
context;
|
|
@@ -13508,6 +13827,11 @@ class ClaudeWorker {
|
|
|
13508
13827
|
dataEncryptionKey = null;
|
|
13509
13828
|
coordinator;
|
|
13510
13829
|
loadedHooks;
|
|
13830
|
+
loadedAgentConfig;
|
|
13831
|
+
// Pending permission requests: toolName -> Promise<'allow' | 'deny'> (to dedupe concurrent requests)
|
|
13832
|
+
pendingPermissions = /* @__PURE__ */ new Map();
|
|
13833
|
+
// Granted permissions cache: toolName -> true (to avoid repeated asks for same tool)
|
|
13834
|
+
grantedPermissions = /* @__PURE__ */ new Set();
|
|
13511
13835
|
async start() {
|
|
13512
13836
|
try {
|
|
13513
13837
|
await this.initialize();
|
|
@@ -13528,18 +13852,69 @@ class ClaudeWorker {
|
|
|
13528
13852
|
if (this.timerManager) {
|
|
13529
13853
|
this.timerManager.clearIdleTimer();
|
|
13530
13854
|
}
|
|
13855
|
+
if (this.logger) {
|
|
13856
|
+
await new Promise((resolve) => {
|
|
13857
|
+
this.logger.on("finish", resolve);
|
|
13858
|
+
this.logger.end();
|
|
13859
|
+
});
|
|
13860
|
+
}
|
|
13531
13861
|
process.exit(0);
|
|
13532
13862
|
}
|
|
13533
13863
|
}
|
|
13534
13864
|
async initialize() {
|
|
13535
13865
|
const taskId = this.options.input.taskId;
|
|
13536
13866
|
const userId = this.options.input.userId;
|
|
13537
|
-
|
|
13538
|
-
let initialCommitHash;
|
|
13539
|
-
this.logger = await this.createLogger({ type: "worker", taskId });
|
|
13867
|
+
this.logger = this.createWorkerLogger({ type: "worker", taskId });
|
|
13540
13868
|
if (this.options.input.dataEncryptionKey && this.options.secretKey) {
|
|
13541
13869
|
this.dataEncryptionKey = decryptWithEphemeralKey(decodeBase64(this.options.input.dataEncryptionKey), this.options.secretKey);
|
|
13542
13870
|
}
|
|
13871
|
+
if (this.options.input.encryptedMessage && this.dataEncryptionKey) {
|
|
13872
|
+
this.options.input.message = decryptSdkMessage(this.options.input.encryptedMessage, this.dataEncryptionKey) || void 0;
|
|
13873
|
+
}
|
|
13874
|
+
let workingDirectory = this.options.input.cwd ? this.options.input.cwd.replace(/^~/, homedir()) : process.cwd();
|
|
13875
|
+
const idleTimeoutMs = Math.max(0, this.options.idleTimeoutSecond ?? 0) * 1e3;
|
|
13876
|
+
this.timerManager = this.createIdleTimerManager(idleTimeoutMs, taskId);
|
|
13877
|
+
const workerConfig = this.createWorkerClientConfig(userId, taskId, workingDirectory);
|
|
13878
|
+
const workClient = new WorkerClient(workerConfig.config, workerConfig.handlers);
|
|
13879
|
+
await workClient.connect();
|
|
13880
|
+
workClient.sendWorkerInitializing();
|
|
13881
|
+
this.context = {
|
|
13882
|
+
credentials: this.credentials,
|
|
13883
|
+
options: this.options,
|
|
13884
|
+
workClient,
|
|
13885
|
+
workingDirectory,
|
|
13886
|
+
initialCommitHash: "",
|
|
13887
|
+
// Will be set after setupWorkspace
|
|
13888
|
+
logger: this.logger
|
|
13889
|
+
};
|
|
13890
|
+
this.coordinator = new MessageCoordinator({
|
|
13891
|
+
workerType: "claude",
|
|
13892
|
+
workClient,
|
|
13893
|
+
handlers: {
|
|
13894
|
+
onNormalMessage: async (message) => {
|
|
13895
|
+
await this.enqueueMessage(message);
|
|
13896
|
+
},
|
|
13897
|
+
onBashCommand: async (command, _originalMessage) => {
|
|
13898
|
+
await this.executeBashCommand(command);
|
|
13899
|
+
},
|
|
13900
|
+
onMergeRequest: async (_originalMessage) => {
|
|
13901
|
+
await this.executeMergeRequest();
|
|
13902
|
+
}
|
|
13903
|
+
},
|
|
13904
|
+
logger: (level, category, message) => {
|
|
13905
|
+
const validLevel = level;
|
|
13906
|
+
this.log(validLevel, category, message);
|
|
13907
|
+
}
|
|
13908
|
+
});
|
|
13909
|
+
if (!this.options.input.gitUrl) {
|
|
13910
|
+
const hasChanges = await checkUncommittedChanges(workingDirectory);
|
|
13911
|
+
if (hasChanges) {
|
|
13912
|
+
this.log("info", "GIT", "Detected uncommitted changes, asking user for action");
|
|
13913
|
+
const action = await this.askUncommittedChangesAction();
|
|
13914
|
+
await handleUncommittedChanges(workingDirectory, action);
|
|
13915
|
+
}
|
|
13916
|
+
}
|
|
13917
|
+
let initialCommitHash;
|
|
13543
13918
|
try {
|
|
13544
13919
|
const hooks = await this.loadAgentHooks();
|
|
13545
13920
|
const workspaceResult = await setupWorkspace({
|
|
@@ -13551,6 +13926,8 @@ class ClaudeWorker {
|
|
|
13551
13926
|
}, hooks);
|
|
13552
13927
|
workingDirectory = workspaceResult.workingDirectory;
|
|
13553
13928
|
initialCommitHash = workspaceResult.initialCommitHash;
|
|
13929
|
+
this.context.workingDirectory = workingDirectory;
|
|
13930
|
+
this.context.initialCommitHash = initialCommitHash;
|
|
13554
13931
|
await machine.writeInitialCommitHash(userId, taskId, initialCommitHash);
|
|
13555
13932
|
this.log("info", "GIT", `Initial commit: ${initialCommitHash}`);
|
|
13556
13933
|
this.initialCommitHashForPR = initialCommitHash;
|
|
@@ -13558,20 +13935,10 @@ class ClaudeWorker {
|
|
|
13558
13935
|
this.logGitStateResult(gitStateResult, "start");
|
|
13559
13936
|
} catch (error) {
|
|
13560
13937
|
this.log("error", "GIT", "Failed to setup workspace:", error);
|
|
13561
|
-
const basicConfig = this.createBasicWorkerConfig(userId, taskId, workingDirectory);
|
|
13562
13938
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
13563
|
-
await
|
|
13564
|
-
basicConfig,
|
|
13565
|
-
`Failed to setup workspace: ${errorMessage}`
|
|
13566
|
-
);
|
|
13939
|
+
await workClient.sendErrorMessageAndExit(`Failed to setup workspace: ${errorMessage}`);
|
|
13567
13940
|
process.exit(1);
|
|
13568
13941
|
}
|
|
13569
|
-
const idleTimeoutMs = Math.max(0, this.options.idleTimeoutSecond ?? 0) * 1e3;
|
|
13570
|
-
this.timerManager = this.createIdleTimerManager(idleTimeoutMs, taskId);
|
|
13571
|
-
const workerConfig = this.createWorkerClientConfig(userId, taskId, workingDirectory);
|
|
13572
|
-
const workClient = new WorkerClient(workerConfig.config, workerConfig.handlers);
|
|
13573
|
-
await workClient.connect();
|
|
13574
|
-
workClient.sendWorkerInitializing();
|
|
13575
13942
|
try {
|
|
13576
13943
|
const metadata = {
|
|
13577
13944
|
cwd: workingDirectory,
|
|
@@ -13588,35 +13955,6 @@ class ClaudeWorker {
|
|
|
13588
13955
|
} catch (error) {
|
|
13589
13956
|
this.log("warn", "DAEMON", "Failed to report session:", error);
|
|
13590
13957
|
}
|
|
13591
|
-
if (this.options.input.encryptedMessage && this.dataEncryptionKey) {
|
|
13592
|
-
this.options.input.message = decryptSdkMessage(this.options.input.encryptedMessage, this.dataEncryptionKey) || void 0;
|
|
13593
|
-
}
|
|
13594
|
-
this.context = {
|
|
13595
|
-
credentials: this.credentials,
|
|
13596
|
-
options: this.options,
|
|
13597
|
-
workClient,
|
|
13598
|
-
workingDirectory,
|
|
13599
|
-
initialCommitHash,
|
|
13600
|
-
logger: this.logger
|
|
13601
|
-
};
|
|
13602
|
-
this.coordinator = new MessageCoordinator({
|
|
13603
|
-
workerType: "claude",
|
|
13604
|
-
handlers: {
|
|
13605
|
-
onNormalMessage: async (message) => {
|
|
13606
|
-
await this.enqueueMessage(message);
|
|
13607
|
-
},
|
|
13608
|
-
onBashCommand: async (command, _originalMessage) => {
|
|
13609
|
-
await this.executeBashCommand(command);
|
|
13610
|
-
},
|
|
13611
|
-
onMergeRequest: async (_originalMessage) => {
|
|
13612
|
-
await this.executeMergeRequest();
|
|
13613
|
-
}
|
|
13614
|
-
},
|
|
13615
|
-
logger: (level, category, message) => {
|
|
13616
|
-
const validLevel = level;
|
|
13617
|
-
this.log(validLevel, category, message);
|
|
13618
|
-
}
|
|
13619
|
-
});
|
|
13620
13958
|
}
|
|
13621
13959
|
async handleEvent() {
|
|
13622
13960
|
if (this.options.input.message) {
|
|
@@ -13634,15 +13972,30 @@ class ClaudeWorker {
|
|
|
13634
13972
|
}
|
|
13635
13973
|
const hasChanges = await hasUncommittedChanges(this.context.workingDirectory);
|
|
13636
13974
|
if (hasChanges) {
|
|
13637
|
-
await autoCommit(this.context.workingDirectory, "
|
|
13975
|
+
await autoCommit(this.context.workingDirectory, "Checkpoint for PR generation");
|
|
13638
13976
|
this.log("info", "MERGE", "Auto-committed changes");
|
|
13639
13977
|
}
|
|
13978
|
+
const currentHash = await getCurrentCommitHash(this.context.workingDirectory);
|
|
13979
|
+
const diffStats = await getDiffStats(this.context.workingDirectory, this.initialCommitHashForPR, currentHash);
|
|
13980
|
+
if (diffStats.files.length === 0) {
|
|
13981
|
+
const errorMessage = "No changes to create PR: no files changed since task started";
|
|
13982
|
+
this.log("error", "MERGE", errorMessage);
|
|
13983
|
+
this.context.workClient.sendSystemErrorMessage(errorMessage);
|
|
13984
|
+
return;
|
|
13985
|
+
}
|
|
13986
|
+
this.log("info", "MERGE", `Found ${diffStats.files.length} files changed`);
|
|
13640
13987
|
const branchName = await getCurrentBranch(this.context.workingDirectory);
|
|
13641
13988
|
this.log("info", "MERGE", `Pushing branch ${branchName} to remote`);
|
|
13642
13989
|
await gitPush(this.context.workingDirectory, branchName, false);
|
|
13643
13990
|
this.log("info", "MERGE", "Successfully pushed branch to remote");
|
|
13991
|
+
const prPrompt = buildPRPrompt(
|
|
13992
|
+
{ initialCommitHash: this.initialCommitHashForPR, currentCommitHash: currentHash, branchName },
|
|
13993
|
+
this.loadedAgentConfig?.customPRPromptTemplate ? {
|
|
13994
|
+
customTemplate: this.loadedAgentConfig.customPRPromptTemplate,
|
|
13995
|
+
mode: this.loadedAgentConfig.prPromptMode
|
|
13996
|
+
} : void 0
|
|
13997
|
+
);
|
|
13644
13998
|
this.inMergeRequest = true;
|
|
13645
|
-
const prPrompt = await this.buildCreatePRPrompt();
|
|
13646
13999
|
await this.enqueueMessage({
|
|
13647
14000
|
type: "user",
|
|
13648
14001
|
message: {
|
|
@@ -13671,53 +14024,8 @@ class ClaudeWorker {
|
|
|
13671
14024
|
}
|
|
13672
14025
|
);
|
|
13673
14026
|
this.timerManager.startIdleTimer();
|
|
13674
|
-
this.context.workClient.sendWorkerReady();
|
|
13675
14027
|
this.log("info", "BASH", `Worker ready after command execution (exit code: ${exitCode})`);
|
|
13676
14028
|
}
|
|
13677
|
-
async buildCreatePRPrompt() {
|
|
13678
|
-
if (!this.initialCommitHashForPR) {
|
|
13679
|
-
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").';
|
|
13680
|
-
}
|
|
13681
|
-
try {
|
|
13682
|
-
const currentHash = await autoCommit(this.context.workingDirectory, "Checkpoint for PR generation");
|
|
13683
|
-
const stats = await getDiffStats(
|
|
13684
|
-
this.context.workingDirectory,
|
|
13685
|
-
this.initialCommitHashForPR,
|
|
13686
|
-
currentHash
|
|
13687
|
-
);
|
|
13688
|
-
const diff = await generateDiffPatch(
|
|
13689
|
-
this.context.workingDirectory,
|
|
13690
|
-
this.initialCommitHashForPR,
|
|
13691
|
-
currentHash
|
|
13692
|
-
);
|
|
13693
|
-
const statsText = `Files changed: ${stats.files.length}, +${stats.totalInsertions}/-${stats.totalDeletions}
|
|
13694
|
-
|
|
13695
|
-
Detailed changes:
|
|
13696
|
-
${stats.files.map((f) => ` ${f.path}: +${f.insertions}/-${f.deletions}`).join("\n")}`;
|
|
13697
|
-
return `All the changed has been successfully pushed to the git branch. Please create a Pull Request by analyzing the changes below.
|
|
13698
|
-
|
|
13699
|
-
Changes made:
|
|
13700
|
-
${statsText}
|
|
13701
|
-
|
|
13702
|
-
Diff (first 5000 chars):
|
|
13703
|
-
\`\`\`
|
|
13704
|
-
${diff.substring(0, 5e3)}
|
|
13705
|
-
\`\`\`
|
|
13706
|
-
|
|
13707
|
-
Requirements:
|
|
13708
|
-
- Title: Use conventional commits format (feat/fix/docs/refactor/test/chore: description), maximum 50 characters
|
|
13709
|
-
- Description: Provide a clear, detailed explanation of:
|
|
13710
|
-
* What changed (the actual modifications made)
|
|
13711
|
-
* Why these changes were necessary (the problem being solved)
|
|
13712
|
-
* Any important technical decisions or trade-offs
|
|
13713
|
-
* Impact on existing functionality
|
|
13714
|
-
|
|
13715
|
-
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.`;
|
|
13716
|
-
} catch (error) {
|
|
13717
|
-
this.log("warn", "GIT", "Failed to generate diff for PR prompt:", error);
|
|
13718
|
-
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").';
|
|
13719
|
-
}
|
|
13720
|
-
}
|
|
13721
14029
|
async runClaude() {
|
|
13722
14030
|
this.log("info", "AGENT", `Starting Claude agent for task ${this.taskId}`);
|
|
13723
14031
|
const agentSessionId = "agentSessionId" in this.options.input ? this.options.input.agentSessionId : void 0;
|
|
@@ -13728,12 +14036,12 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
|
|
|
13728
14036
|
);
|
|
13729
14037
|
const sdkMcpServer = this.createAgentrixMcpServer();
|
|
13730
14038
|
const mcpServers = {
|
|
13731
|
-
agentrix: sdkMcpServer
|
|
13732
|
-
...agentConfig.customMcpServers
|
|
14039
|
+
agentrix: sdkMcpServer
|
|
13733
14040
|
};
|
|
13734
14041
|
const allowedTools = [
|
|
13735
14042
|
"mcp__agentrix__change_task_title",
|
|
13736
14043
|
"mcp__agentrix__create_pr",
|
|
14044
|
+
"mcp__agentrix__ask_user",
|
|
13737
14045
|
...agentConfig.customAllowedTools
|
|
13738
14046
|
];
|
|
13739
14047
|
const messageConsumer = this.createMessageConsumer();
|
|
@@ -13753,6 +14061,7 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
|
|
|
13753
14061
|
systemPrompt: finalSystemPrompt,
|
|
13754
14062
|
mcpServers,
|
|
13755
14063
|
allowedTools,
|
|
14064
|
+
plugins: agentConfig.customPlugins,
|
|
13756
14065
|
abortController: this.abortController,
|
|
13757
14066
|
env: this.buildEnvironmentOverrides(),
|
|
13758
14067
|
maxTurns: agentConfig.customMaxTurns ?? this.options.input.maxTurns ?? 50,
|
|
@@ -13762,18 +14071,16 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
|
|
|
13762
14071
|
}
|
|
13763
14072
|
});
|
|
13764
14073
|
if (this.messageQueue.length > 0) {
|
|
13765
|
-
this.
|
|
13766
|
-
this.coordinator.setWorkerState("processing-sdk");
|
|
14074
|
+
this.coordinator.setWorkerState("running");
|
|
13767
14075
|
} else {
|
|
13768
14076
|
this.timerManager.startIdleTimer();
|
|
13769
|
-
this.context.workClient.sendWorkerReady();
|
|
13770
14077
|
}
|
|
13771
14078
|
for await (const message of response) {
|
|
13772
14079
|
this.timerManager.clearIdleTimer();
|
|
13773
14080
|
this.context.logger.debug(`sdk message: ${JSON.stringify(message)}`);
|
|
13774
14081
|
if (message.type === "system" && message.subtype === "init") {
|
|
13775
14082
|
this.context.workClient.sendUpdateTaskAgentSessionId(message.session_id);
|
|
13776
|
-
this.
|
|
14083
|
+
this.coordinator.setWorkerState("running");
|
|
13777
14084
|
continue;
|
|
13778
14085
|
}
|
|
13779
14086
|
const filteredMessage = this.filterMessages(message);
|
|
@@ -13783,15 +14090,14 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
|
|
|
13783
14090
|
if (message.type === "result") {
|
|
13784
14091
|
this.coordinator.setWorkerState("idle");
|
|
13785
14092
|
this.timerManager.startIdleTimer();
|
|
13786
|
-
this.context.workClient.sendWorkerReady();
|
|
13787
14093
|
} else {
|
|
13788
|
-
this.coordinator.setWorkerState("
|
|
14094
|
+
this.coordinator.setWorkerState("running");
|
|
13789
14095
|
}
|
|
13790
14096
|
}
|
|
13791
14097
|
this.log("info", "AGENT", `Claude agent finished for task ${this.taskId}`);
|
|
13792
14098
|
}
|
|
13793
14099
|
async enqueueMessage(message) {
|
|
13794
|
-
const processedMessage = await this.
|
|
14100
|
+
const processedMessage = await this.processAttachments(message);
|
|
13795
14101
|
if (this.messageResolverRef.current) {
|
|
13796
14102
|
const resolver = this.messageResolverRef.current;
|
|
13797
14103
|
this.messageResolverRef.current = null;
|
|
@@ -13800,10 +14106,14 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
|
|
|
13800
14106
|
this.messageQueue.push(processedMessage);
|
|
13801
14107
|
}
|
|
13802
14108
|
}
|
|
13803
|
-
async
|
|
14109
|
+
async processAttachments(message) {
|
|
13804
14110
|
if (!Array.isArray(message.message.content)) {
|
|
13805
14111
|
return message;
|
|
13806
14112
|
}
|
|
14113
|
+
const attachmentsDir = machine.resolveAttachmentsDir(
|
|
14114
|
+
this.options.input.userId,
|
|
14115
|
+
this.taskId
|
|
14116
|
+
);
|
|
13807
14117
|
const processedContent = await Promise.all(
|
|
13808
14118
|
message.message.content.map(async (block) => {
|
|
13809
14119
|
if (block.type === "image" && block.source?.type === "url" && block.source?.url) {
|
|
@@ -13839,6 +14149,24 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
|
|
|
13839
14149
|
return block;
|
|
13840
14150
|
}
|
|
13841
14151
|
}
|
|
14152
|
+
if (block.type === "document" && block.source?.type === "url" && block.source?.url) {
|
|
14153
|
+
try {
|
|
14154
|
+
const url = block.source.url;
|
|
14155
|
+
this.log("info", "DOCUMENT", `Downloading document from: ${url}`);
|
|
14156
|
+
const { filePath, mimeType, filename } = await downloadFile(url, attachmentsDir, true);
|
|
14157
|
+
this.log("info", "DOCUMENT", `Document downloaded to: ${filePath}`);
|
|
14158
|
+
const title = block.title || filename;
|
|
14159
|
+
return {
|
|
14160
|
+
type: "text",
|
|
14161
|
+
text: `Document: ${filePath}
|
|
14162
|
+
Title: ${title}
|
|
14163
|
+
Type: ${mimeType}`
|
|
14164
|
+
};
|
|
14165
|
+
} catch (error) {
|
|
14166
|
+
this.log("error", "DOCUMENT", `Error processing document: ${error}`);
|
|
14167
|
+
return block;
|
|
14168
|
+
}
|
|
14169
|
+
}
|
|
13842
14170
|
return block;
|
|
13843
14171
|
})
|
|
13844
14172
|
);
|
|
@@ -13923,33 +14251,117 @@ Please must use the mcp__agentrix__create_pr tool to create the pull request wit
|
|
|
13923
14251
|
}
|
|
13924
14252
|
createPermissionHandler() {
|
|
13925
14253
|
return async (toolName, input) => {
|
|
13926
|
-
|
|
13927
|
-
|
|
13928
|
-
|
|
13929
|
-
|
|
13930
|
-
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
}
|
|
14254
|
+
if (this.grantedPermissions.has(toolName)) {
|
|
14255
|
+
this.log("info", "PERMISSION", `Tool "${toolName}" already granted, skipping`);
|
|
14256
|
+
return { behavior: "allow", updatedInput: input };
|
|
14257
|
+
}
|
|
14258
|
+
const pending = this.pendingPermissions.get(toolName);
|
|
14259
|
+
if (pending) {
|
|
14260
|
+
this.log("info", "PERMISSION", `Tool "${toolName}" has pending request, waiting...`);
|
|
14261
|
+
const decision = await pending;
|
|
14262
|
+
if (decision === "allow") {
|
|
14263
|
+
return { behavior: "allow", updatedInput: input };
|
|
14264
|
+
} else {
|
|
14265
|
+
return { behavior: "deny", message: "Permission denied by user" };
|
|
13938
14266
|
}
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
|
|
13944
|
-
|
|
13945
|
-
|
|
13946
|
-
|
|
13947
|
-
|
|
13948
|
-
|
|
13949
|
-
|
|
14267
|
+
}
|
|
14268
|
+
this.log("info", "PERMISSION", `Requesting permission for "${toolName}"`);
|
|
14269
|
+
let resolveDecision;
|
|
14270
|
+
const permissionPromise = new Promise((resolve) => {
|
|
14271
|
+
resolveDecision = resolve;
|
|
14272
|
+
});
|
|
14273
|
+
this.pendingPermissions.set(toolName, permissionPromise);
|
|
14274
|
+
try {
|
|
14275
|
+
const decision = await this.requestToolPermission(toolName);
|
|
14276
|
+
resolveDecision(decision);
|
|
14277
|
+
if (decision === "allow") {
|
|
14278
|
+
this.grantedPermissions.add(toolName);
|
|
14279
|
+
return { behavior: "allow", updatedInput: input };
|
|
14280
|
+
} else {
|
|
14281
|
+
return { behavior: "deny", message: "Permission denied by user" };
|
|
14282
|
+
}
|
|
14283
|
+
} catch (error) {
|
|
14284
|
+
resolveDecision("deny");
|
|
14285
|
+
return { behavior: "deny", message: "Permission request failed" };
|
|
14286
|
+
} finally {
|
|
14287
|
+
this.pendingPermissions.delete(toolName);
|
|
13950
14288
|
}
|
|
13951
14289
|
};
|
|
13952
14290
|
}
|
|
14291
|
+
async requestToolPermission(toolName) {
|
|
14292
|
+
const questions = [{
|
|
14293
|
+
question: `Tool "${toolName}" is requesting permission to execute. Allow this operation?`,
|
|
14294
|
+
header: "Permission",
|
|
14295
|
+
multiSelect: false,
|
|
14296
|
+
options: [
|
|
14297
|
+
{ label: "Allow", description: "Allow this tool to execute" },
|
|
14298
|
+
{ label: "Deny", description: "Deny this tool execution" }
|
|
14299
|
+
]
|
|
14300
|
+
}];
|
|
14301
|
+
try {
|
|
14302
|
+
const response = await this.askUser(questions);
|
|
14303
|
+
const answer = response.answers[0];
|
|
14304
|
+
return answer === "Allow" ? "allow" : "deny";
|
|
14305
|
+
} catch (error) {
|
|
14306
|
+
this.log("warn", "PERMISSION", `Permission request failed: ${error}`);
|
|
14307
|
+
return "deny";
|
|
14308
|
+
}
|
|
14309
|
+
}
|
|
14310
|
+
/**
|
|
14311
|
+
* Ask user questions and wait for response
|
|
14312
|
+
* Sends ask_user message via task-message and waits for ask_user_response
|
|
14313
|
+
* @param questions - Array of questions (1-4)
|
|
14314
|
+
* @returns Promise resolving to user's response
|
|
14315
|
+
*/
|
|
14316
|
+
async askUser(questions) {
|
|
14317
|
+
const eventId = this.context.workClient.sendAskUser(questions);
|
|
14318
|
+
const timeoutMs = 3e5;
|
|
14319
|
+
return new Promise((resolve, reject) => {
|
|
14320
|
+
const timeout = setTimeout(() => {
|
|
14321
|
+
this.askUserAwaiter.delete(eventId);
|
|
14322
|
+
reject(new Error("Ask user request timed out"));
|
|
14323
|
+
}, timeoutMs);
|
|
14324
|
+
this.askUserAwaiter.set(eventId, (response) => {
|
|
14325
|
+
clearTimeout(timeout);
|
|
14326
|
+
resolve(response);
|
|
14327
|
+
});
|
|
14328
|
+
});
|
|
14329
|
+
}
|
|
14330
|
+
/**
|
|
14331
|
+
* Ask user how to handle uncommitted changes
|
|
14332
|
+
* @returns The action to take: ignore, commit, stash, or abort
|
|
14333
|
+
*/
|
|
14334
|
+
async askUncommittedChangesAction() {
|
|
14335
|
+
const questions = [{
|
|
14336
|
+
question: "Uncommitted changes detected in the working directory. How would you like to proceed?",
|
|
14337
|
+
header: "Git Status",
|
|
14338
|
+
multiSelect: false,
|
|
14339
|
+
options: [
|
|
14340
|
+
{ label: "Ignore", description: "Keep changes and continue with the task" },
|
|
14341
|
+
{ label: "Commit", description: "Commit current changes before starting" },
|
|
14342
|
+
{ label: "Stash", description: "Stash changes (git stash) before starting" },
|
|
14343
|
+
{ label: "Abort", description: "Cancel the task, do nothing" }
|
|
14344
|
+
]
|
|
14345
|
+
}];
|
|
14346
|
+
try {
|
|
14347
|
+
const response = await this.askUser(questions);
|
|
14348
|
+
const answer = response.answers[0];
|
|
14349
|
+
if (answer.startsWith("other:")) {
|
|
14350
|
+
this.log("info", "GIT", `User provided custom input: ${answer}, defaulting to Abort`);
|
|
14351
|
+
return "Abort";
|
|
14352
|
+
}
|
|
14353
|
+
const labelToAction = {
|
|
14354
|
+
"Ignore": "Ignore",
|
|
14355
|
+
"Commit": "Commit",
|
|
14356
|
+
"Stash": "Stash",
|
|
14357
|
+
"Abort": "Abort"
|
|
14358
|
+
};
|
|
14359
|
+
return labelToAction[answer] || "Abort";
|
|
14360
|
+
} catch (error) {
|
|
14361
|
+
this.log("warn", "GIT", `Failed to get user response for uncommitted changes: ${error}`);
|
|
14362
|
+
return "Abort";
|
|
14363
|
+
}
|
|
14364
|
+
}
|
|
13953
14365
|
createAgentrixMcpServer() {
|
|
13954
14366
|
return createSdkMcpServer({
|
|
13955
14367
|
name: "agentrix",
|
|
@@ -14010,6 +14422,50 @@ URL: ${result.pullRequestUrl}`
|
|
|
14010
14422
|
};
|
|
14011
14423
|
}
|
|
14012
14424
|
}
|
|
14425
|
+
),
|
|
14426
|
+
tool(
|
|
14427
|
+
"ask_user",
|
|
14428
|
+
'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.',
|
|
14429
|
+
{
|
|
14430
|
+
questions: z.array(z.object({
|
|
14431
|
+
question: z.string().describe("The complete question to ask the user"),
|
|
14432
|
+
header: z.string().max(12).describe("Short label displayed as a chip/tag (max 12 chars)"),
|
|
14433
|
+
multiSelect: z.boolean().describe("Set to true to allow multiple option selections"),
|
|
14434
|
+
options: z.array(z.object({
|
|
14435
|
+
label: z.string().describe("Option label (1-5 words)"),
|
|
14436
|
+
description: z.string().describe("Explanation of what this option means")
|
|
14437
|
+
})).min(2).max(4).describe("Available choices (2-4 options)")
|
|
14438
|
+
})).min(1).max(4).describe("Questions to ask (1-4 questions)")
|
|
14439
|
+
},
|
|
14440
|
+
async (args) => {
|
|
14441
|
+
try {
|
|
14442
|
+
const questionsWithOther = args.questions.map((q) => ({
|
|
14443
|
+
...q,
|
|
14444
|
+
options: [...q.options, { label: "Other", description: "" }]
|
|
14445
|
+
}));
|
|
14446
|
+
const result = await this.askUser(questionsWithOther);
|
|
14447
|
+
const answerText = result.answers.map((answer) => {
|
|
14448
|
+
if (answer.startsWith("other:")) {
|
|
14449
|
+
return `Other: "${answer.slice(6)}"`;
|
|
14450
|
+
}
|
|
14451
|
+
return answer;
|
|
14452
|
+
}).join("\n");
|
|
14453
|
+
return {
|
|
14454
|
+
content: [{ type: "text", text: `User answers:
|
|
14455
|
+
${answerText}` }]
|
|
14456
|
+
};
|
|
14457
|
+
} catch (error) {
|
|
14458
|
+
this.log("error", "ASK_USER", "Failed to get user response:", error);
|
|
14459
|
+
return {
|
|
14460
|
+
content: [
|
|
14461
|
+
{
|
|
14462
|
+
type: "text",
|
|
14463
|
+
text: `Failed to get user response: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
14464
|
+
}
|
|
14465
|
+
]
|
|
14466
|
+
};
|
|
14467
|
+
}
|
|
14468
|
+
}
|
|
14013
14469
|
)
|
|
14014
14470
|
]
|
|
14015
14471
|
});
|
|
@@ -14025,7 +14481,7 @@ URL: ${result.pullRequestUrl}`
|
|
|
14025
14481
|
} catch (error) {
|
|
14026
14482
|
this.log("warn", "GIT", "Failed to handle git state on worker stop:", error);
|
|
14027
14483
|
}
|
|
14028
|
-
this.
|
|
14484
|
+
this.coordinator.setWorkerState("idle");
|
|
14029
14485
|
}
|
|
14030
14486
|
filterMessages(message) {
|
|
14031
14487
|
const msg = message;
|
|
@@ -14066,38 +14522,45 @@ URL: ${result.pullRequestUrl}`
|
|
|
14066
14522
|
return this.createDefaultAgentConfig();
|
|
14067
14523
|
}
|
|
14068
14524
|
const claudeConfig = agentConfig.claude;
|
|
14069
|
-
const
|
|
14070
|
-
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14525
|
+
const customPlugins = claudeConfig.plugins.map((path) => ({
|
|
14526
|
+
type: "local",
|
|
14527
|
+
path
|
|
14528
|
+
}));
|
|
14529
|
+
this.log("info", "AGENT", `Agent ${this.options.input.agentId} loaded successfully (${customPlugins.length} plugins)`);
|
|
14530
|
+
const config = {
|
|
14074
14531
|
customSystemPrompt: claudeConfig.systemPrompt,
|
|
14075
|
-
// Loaded string content
|
|
14076
14532
|
customModel: claudeConfig.config.model,
|
|
14077
14533
|
customFallbackModel: claudeConfig.config.fallbackModel,
|
|
14078
14534
|
customMaxTurns: claudeConfig.config.maxTurns,
|
|
14079
14535
|
customExtraArgs: claudeConfig.config.extraArgs,
|
|
14080
14536
|
customPermissionMode: claudeConfig.config.settings?.permissionMode,
|
|
14081
|
-
customMcpServers,
|
|
14082
14537
|
customAllowedTools: claudeConfig.config.settings?.allowedTools || [],
|
|
14538
|
+
customPlugins,
|
|
14083
14539
|
systemPromptMode: claudeConfig.config.systemPrompt?.mode ?? "append",
|
|
14084
|
-
hooks: this.loadedHooks
|
|
14085
|
-
|
|
14540
|
+
hooks: this.loadedHooks,
|
|
14541
|
+
customPRPromptTemplate: claudeConfig.prPromptTemplate,
|
|
14542
|
+
prPromptMode: claudeConfig.config.pullRequestPrompt?.mode ?? "append"
|
|
14086
14543
|
};
|
|
14544
|
+
this.loadedAgentConfig = config;
|
|
14545
|
+
return config;
|
|
14087
14546
|
} catch (error) {
|
|
14088
14547
|
this.log("error", "AGENT", `Failed to load agent: ${error instanceof Error ? error.message : String(error)}`);
|
|
14089
14548
|
return this.createDefaultAgentConfig();
|
|
14090
14549
|
}
|
|
14091
14550
|
}
|
|
14092
14551
|
createDefaultAgentConfig() {
|
|
14093
|
-
|
|
14552
|
+
const config = {
|
|
14094
14553
|
customSystemPrompt: void 0,
|
|
14095
14554
|
customModel: void 0,
|
|
14096
14555
|
customMaxTurns: void 0,
|
|
14097
|
-
customMcpServers: {},
|
|
14098
14556
|
customAllowedTools: [],
|
|
14099
|
-
|
|
14557
|
+
customPlugins: [],
|
|
14558
|
+
systemPromptMode: "append",
|
|
14559
|
+
customPRPromptTemplate: void 0,
|
|
14560
|
+
prPromptMode: "append"
|
|
14100
14561
|
};
|
|
14562
|
+
this.loadedAgentConfig = config;
|
|
14563
|
+
return config;
|
|
14101
14564
|
}
|
|
14102
14565
|
buildEnvironmentOverrides() {
|
|
14103
14566
|
return {
|
|
@@ -14205,18 +14668,20 @@ URL: ${result.pullRequestUrl}`
|
|
|
14205
14668
|
this.timerManager.stopTask("event");
|
|
14206
14669
|
},
|
|
14207
14670
|
onTaskMessage: async (message) => {
|
|
14208
|
-
if (message
|
|
14671
|
+
if (isAskUserResponseMessage(message)) {
|
|
14672
|
+
const [eventId, awaiter] = this.askUserAwaiter.entries().next().value || [];
|
|
14673
|
+
if (eventId && awaiter) {
|
|
14674
|
+
this.askUserAwaiter.delete(eventId);
|
|
14675
|
+
awaiter(message);
|
|
14676
|
+
}
|
|
14677
|
+
this.timerManager.clearIdleTimer();
|
|
14678
|
+
return;
|
|
14679
|
+
}
|
|
14680
|
+
if ("type" in message && message.type === "user") {
|
|
14209
14681
|
const userMessage = message;
|
|
14210
14682
|
await this.coordinator.enqueue(userMessage);
|
|
14211
14683
|
this.timerManager.clearIdleTimer();
|
|
14212
14684
|
}
|
|
14213
|
-
},
|
|
14214
|
-
onPermissionResponse: async (response) => {
|
|
14215
|
-
const awaiter = this.permissionAwaiter.get(response.opCode);
|
|
14216
|
-
if (awaiter) {
|
|
14217
|
-
this.permissionAwaiter.delete(response.opCode);
|
|
14218
|
-
awaiter(response);
|
|
14219
|
-
}
|
|
14220
14685
|
}
|
|
14221
14686
|
}
|
|
14222
14687
|
};
|
|
@@ -14255,8 +14720,7 @@ URL: ${result.pullRequestUrl}`
|
|
|
14255
14720
|
this.log("info", "GIT", `Patch: ${gitStateResult.patchPath}`);
|
|
14256
14721
|
}
|
|
14257
14722
|
}
|
|
14258
|
-
|
|
14259
|
-
const { createLogger } = await import('./logger-7E71dnBD.mjs').then(function (n) { return n.b; });
|
|
14723
|
+
createWorkerLogger(options) {
|
|
14260
14724
|
return createLogger(options);
|
|
14261
14725
|
}
|
|
14262
14726
|
log(level, category, message, ...args) {
|
|
@@ -14277,7 +14741,7 @@ async function runClaude(credentials, options) {
|
|
|
14277
14741
|
var require$$0 = /*@__PURE__*/getAugmentedNamespace(logger$1);
|
|
14278
14742
|
|
|
14279
14743
|
function generateAndMapToolId(itemId, idMapper) {
|
|
14280
|
-
const uniqueId = randomUUID
|
|
14744
|
+
const uniqueId = randomUUID();
|
|
14281
14745
|
idMapper.set(itemId, uniqueId);
|
|
14282
14746
|
return uniqueId;
|
|
14283
14747
|
}
|
|
@@ -14360,7 +14824,7 @@ function convertAgentMessage(item) {
|
|
|
14360
14824
|
},
|
|
14361
14825
|
parent_tool_use_id: null,
|
|
14362
14826
|
session_id: "",
|
|
14363
|
-
uuid: randomUUID
|
|
14827
|
+
uuid: randomUUID().toString()
|
|
14364
14828
|
};
|
|
14365
14829
|
}
|
|
14366
14830
|
function convertCommandExecutionStarted(item, idMapper) {
|
|
@@ -15880,28 +16344,6 @@ Example response format:
|
|
|
15880
16344
|
CRITICAL: Respond with ONLY the JSON object, no additional text before or after. Now analyze the changes and provide your response:`;
|
|
15881
16345
|
}
|
|
15882
16346
|
|
|
15883
|
-
async function downloadImage(url, targetDir) {
|
|
15884
|
-
try {
|
|
15885
|
-
const urlPath = new URL(url).pathname;
|
|
15886
|
-
const extension = extname(urlPath) || ".jpg";
|
|
15887
|
-
const filename = `${randomUUID$1()}${extension}`;
|
|
15888
|
-
const filePath = join$1(targetDir, filename);
|
|
15889
|
-
const response = await fetch(url);
|
|
15890
|
-
if (!response.ok) {
|
|
15891
|
-
throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
|
|
15892
|
-
}
|
|
15893
|
-
if (!response.body) {
|
|
15894
|
-
throw new Error("Response body is null");
|
|
15895
|
-
}
|
|
15896
|
-
const nodeStream = response.body;
|
|
15897
|
-
const fileStream = createWriteStream(filePath);
|
|
15898
|
-
await pipeline(nodeStream, fileStream);
|
|
15899
|
-
return filePath;
|
|
15900
|
-
} catch (error) {
|
|
15901
|
-
throw new Error(`Failed to download image from ${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
15902
|
-
}
|
|
15903
|
-
}
|
|
15904
|
-
|
|
15905
16347
|
class CodexWorker {
|
|
15906
16348
|
constructor(credentials, options) {
|
|
15907
16349
|
this.credentials = credentials;
|
|
@@ -16017,6 +16459,7 @@ class CodexWorker {
|
|
|
16017
16459
|
};
|
|
16018
16460
|
this.coordinator = new MessageCoordinator({
|
|
16019
16461
|
workerType: "codex",
|
|
16462
|
+
workClient,
|
|
16020
16463
|
handlers: {
|
|
16021
16464
|
onNormalMessage: async (message) => {
|
|
16022
16465
|
const input = await this.convertSDKMessageToCodexInput(message);
|
|
@@ -16089,7 +16532,6 @@ class CodexWorker {
|
|
|
16089
16532
|
}
|
|
16090
16533
|
);
|
|
16091
16534
|
this.timerManager.startIdleTimer();
|
|
16092
|
-
this.context.workClient.sendWorkerReady();
|
|
16093
16535
|
this.log("info", "BASH", `Worker ready after command execution (exit code: ${exitCode})`);
|
|
16094
16536
|
}
|
|
16095
16537
|
async buildCreatePRPrompt() {
|
|
@@ -16146,8 +16588,7 @@ ${stats.files.map((f) => ` ${f.path}: +${f.insertions}/-${f.deletions}`).join("
|
|
|
16146
16588
|
skipGitRepoCheck: true
|
|
16147
16589
|
});
|
|
16148
16590
|
}
|
|
16149
|
-
this.
|
|
16150
|
-
this.coordinator.setWorkerState("processing-sdk");
|
|
16591
|
+
this.coordinator.setWorkerState("running");
|
|
16151
16592
|
while (!this.isStopping) {
|
|
16152
16593
|
this.log("debug", "AGENT", `Loop iteration: turnCount=${this.turnCount}, queueLength=${this.messageQueue.length}`);
|
|
16153
16594
|
let userInput = null;
|
|
@@ -16159,7 +16600,6 @@ ${stats.files.map((f) => ` ${f.path}: +${f.insertions}/-${f.deletions}`).join("
|
|
|
16159
16600
|
await this.reportGitState("idle");
|
|
16160
16601
|
this.coordinator.setWorkerState("idle");
|
|
16161
16602
|
this.timerManager.startIdleTimer();
|
|
16162
|
-
this.context.workClient.sendWorkerReady();
|
|
16163
16603
|
this.log("info", "AGENT", "Sent worker-ready, waiting for next message");
|
|
16164
16604
|
userInput = await this.waitForNextMessage();
|
|
16165
16605
|
}
|
|
@@ -16169,7 +16609,7 @@ ${stats.files.map((f) => ` ${f.path}: +${f.insertions}/-${f.deletions}`).join("
|
|
|
16169
16609
|
}
|
|
16170
16610
|
try {
|
|
16171
16611
|
this.timerManager.clearIdleTimer();
|
|
16172
|
-
this.coordinator.setWorkerState("
|
|
16612
|
+
this.coordinator.setWorkerState("running");
|
|
16173
16613
|
await this.processUserInput(userInput);
|
|
16174
16614
|
this.turnCount++;
|
|
16175
16615
|
this.log("debug", "AGENT", `Message processed, turnCount now ${this.turnCount}`);
|
|
@@ -16205,7 +16645,7 @@ ${stats.files.map((f) => ` ${f.path}: +${f.insertions}/-${f.deletions}`).join("
|
|
|
16205
16645
|
const turnOptions = this.inMergeRequest ? { outputSchema: getPROutputSchema() } : {};
|
|
16206
16646
|
this.log("debug", "AGENT", "Calling thread.runStreamed");
|
|
16207
16647
|
const { events } = await this.thread.runStreamed(input, turnOptions);
|
|
16208
|
-
this.
|
|
16648
|
+
this.coordinator.setWorkerState("running");
|
|
16209
16649
|
this.log("debug", "AGENT", "Starting to process events");
|
|
16210
16650
|
for await (const event of events) {
|
|
16211
16651
|
if (this.isStopping) {
|
|
@@ -16309,7 +16749,7 @@ Please try again or create the PR manually.`
|
|
|
16309
16749
|
const message = {
|
|
16310
16750
|
type: "assistant",
|
|
16311
16751
|
message: {
|
|
16312
|
-
id: randomUUID
|
|
16752
|
+
id: randomUUID().toString(),
|
|
16313
16753
|
type: "message",
|
|
16314
16754
|
container: null,
|
|
16315
16755
|
role: "assistant",
|
|
@@ -16326,7 +16766,7 @@ Please try again or create the PR manually.`
|
|
|
16326
16766
|
},
|
|
16327
16767
|
parent_tool_use_id: null,
|
|
16328
16768
|
session_id: "",
|
|
16329
|
-
uuid: randomUUID
|
|
16769
|
+
uuid: randomUUID().toString()
|
|
16330
16770
|
};
|
|
16331
16771
|
this.context.workClient.sendTaskMessage(message);
|
|
16332
16772
|
}
|
|
@@ -16380,8 +16820,8 @@ URL: ${result.pullRequestUrl}`);
|
|
|
16380
16820
|
}
|
|
16381
16821
|
/**
|
|
16382
16822
|
* Convert SDKUserMessage to Codex Input format
|
|
16383
|
-
* Handles both text-only messages and messages with images
|
|
16384
|
-
* Downloads images from URLs to local attachments directory
|
|
16823
|
+
* Handles both text-only messages and messages with images and documents
|
|
16824
|
+
* Downloads images and documents from URLs to local attachments directory
|
|
16385
16825
|
*/
|
|
16386
16826
|
async convertSDKMessageToCodexInput(message) {
|
|
16387
16827
|
const content = message.message.content;
|
|
@@ -16390,6 +16830,10 @@ URL: ${result.pullRequestUrl}`);
|
|
|
16390
16830
|
}
|
|
16391
16831
|
if (Array.isArray(content)) {
|
|
16392
16832
|
const userInputs = [];
|
|
16833
|
+
const attachmentsDir = machine.resolveAttachmentsDir(
|
|
16834
|
+
this.options.input.userId,
|
|
16835
|
+
this.taskId
|
|
16836
|
+
);
|
|
16393
16837
|
for (const block of content) {
|
|
16394
16838
|
if (block.type === "text" && block.text) {
|
|
16395
16839
|
userInputs.push({
|
|
@@ -16399,19 +16843,30 @@ URL: ${result.pullRequestUrl}`);
|
|
|
16399
16843
|
} else if (block.type === "image" && block.source && block.source.url) {
|
|
16400
16844
|
const url = block.source.url;
|
|
16401
16845
|
try {
|
|
16402
|
-
const
|
|
16403
|
-
|
|
16404
|
-
this.taskId
|
|
16405
|
-
);
|
|
16406
|
-
const localPath = await downloadImage(url, attachmentsDir);
|
|
16407
|
-
this.log("info", "IMAGE", `Downloaded image from ${url} to ${localPath}`);
|
|
16846
|
+
const { filePath } = await downloadFile(url, attachmentsDir, false);
|
|
16847
|
+
this.log("info", "IMAGE", `Downloaded image from ${url} to ${filePath}`);
|
|
16408
16848
|
userInputs.push({
|
|
16409
16849
|
type: "local_image",
|
|
16410
|
-
path:
|
|
16850
|
+
path: filePath
|
|
16411
16851
|
});
|
|
16412
16852
|
} catch (error) {
|
|
16413
16853
|
this.log("error", "IMAGE", `Failed to download image from ${url}:`, error);
|
|
16414
16854
|
}
|
|
16855
|
+
} else if (block.type === "document" && block.source && block.source.url) {
|
|
16856
|
+
const url = block.source.url;
|
|
16857
|
+
try {
|
|
16858
|
+
const { filePath, mimeType, filename } = await downloadFile(url, attachmentsDir, true);
|
|
16859
|
+
this.log("info", "DOCUMENT", `Downloaded document from ${url} to ${filePath}`);
|
|
16860
|
+
const title = block.title || filename;
|
|
16861
|
+
userInputs.push({
|
|
16862
|
+
type: "text",
|
|
16863
|
+
text: `Document: ${filePath}
|
|
16864
|
+
Title: ${title}
|
|
16865
|
+
Type: ${mimeType}`
|
|
16866
|
+
});
|
|
16867
|
+
} catch (error) {
|
|
16868
|
+
this.log("error", "DOCUMENT", `Failed to download document from ${url}:`, error);
|
|
16869
|
+
}
|
|
16415
16870
|
}
|
|
16416
16871
|
}
|
|
16417
16872
|
if (userInputs.length === 1 && userInputs[0].type === "text") {
|
|
@@ -16481,7 +16936,7 @@ URL: ${result.pullRequestUrl}`);
|
|
|
16481
16936
|
}
|
|
16482
16937
|
async handleStopHook() {
|
|
16483
16938
|
await this.reportGitState("stop");
|
|
16484
|
-
this.
|
|
16939
|
+
this.coordinator.setWorkerState("idle");
|
|
16485
16940
|
}
|
|
16486
16941
|
async loadAgentConfiguration() {
|
|
16487
16942
|
if (!this.options.input.agentId || this.options.input.agentId === "default") {
|
|
@@ -16573,14 +17028,15 @@ URL: ${result.pullRequestUrl}`);
|
|
|
16573
17028
|
this.timerManager.stopTask("event");
|
|
16574
17029
|
},
|
|
16575
17030
|
onTaskMessage: async (message) => {
|
|
16576
|
-
if (message
|
|
17031
|
+
if (isAskUserResponseMessage(message)) {
|
|
17032
|
+
this.log("debug", "AGENT", "Received ask_user_response (not used by Codex)");
|
|
17033
|
+
return;
|
|
17034
|
+
}
|
|
17035
|
+
if ("type" in message && message.type === "user") {
|
|
16577
17036
|
const userMessage = message;
|
|
16578
17037
|
await this.coordinator.enqueue(userMessage);
|
|
16579
17038
|
this.timerManager.clearIdleTimer();
|
|
16580
17039
|
}
|
|
16581
|
-
},
|
|
16582
|
-
onPermissionResponse: async (response) => {
|
|
16583
|
-
this.log("debug", "AGENT", `Permission response received: ${response.behavior}`);
|
|
16584
17040
|
}
|
|
16585
17041
|
}
|
|
16586
17042
|
};
|
|
@@ -16935,7 +17391,7 @@ cli.command("upgrade", "Upgrade CLI to the latest version", {}, async (argv) =>
|
|
|
16935
17391
|
}
|
|
16936
17392
|
}
|
|
16937
17393
|
try {
|
|
16938
|
-
const { version } = await import('./logger-
|
|
17394
|
+
const { version } = await import('./logger-BzpMLIL-.mjs').then(function (n) { return n._; });
|
|
16939
17395
|
console.log(chalk.green(`
|
|
16940
17396
|
\u2713 Now running version: ${version}`));
|
|
16941
17397
|
} catch {
|
|
@@ -17117,7 +17573,7 @@ cli.command(
|
|
|
17117
17573
|
},
|
|
17118
17574
|
async (argv) => {
|
|
17119
17575
|
try {
|
|
17120
|
-
const { testCommand } = await import('./index-
|
|
17576
|
+
const { testCommand } = await import('./index-DNDJMHW8.mjs');
|
|
17121
17577
|
await testCommand(argv);
|
|
17122
17578
|
} catch (error) {
|
|
17123
17579
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Test mode failed");
|