pentesting 0.21.7 → 0.21.9
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/main.js +1171 -975
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -13,11 +13,11 @@ import chalk from "chalk";
|
|
|
13
13
|
import gradient from "gradient-string";
|
|
14
14
|
|
|
15
15
|
// src/platform/tui/app.tsx
|
|
16
|
-
import { useState as
|
|
16
|
+
import { useState as useState4, useCallback as useCallback3, useEffect as useEffect4 } from "react";
|
|
17
17
|
import { Box as Box6, useInput, useApp } from "ink";
|
|
18
18
|
|
|
19
19
|
// src/platform/tui/hooks/useAgent.ts
|
|
20
|
-
import { useState, useEffect, useCallback, useRef } from "react";
|
|
20
|
+
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
21
21
|
|
|
22
22
|
// src/shared/constants/timing.ts
|
|
23
23
|
var TOOL_TIMEOUTS = {
|
|
@@ -43,6 +43,18 @@ var DISPLAY_LIMITS = {
|
|
|
43
43
|
TOOL_INPUT_TRUNCATED: 30,
|
|
44
44
|
/** Max characters to show in tool output preview */
|
|
45
45
|
TOOL_OUTPUT_PREVIEW: 200,
|
|
46
|
+
/** Max characters to show in tool output slice (events) */
|
|
47
|
+
TOOL_OUTPUT_SLICE: 80,
|
|
48
|
+
/** Max characters to show in tool error preview (events) */
|
|
49
|
+
TOOL_ERROR_SLICE: 120,
|
|
50
|
+
/** Max characters for delegate output preview */
|
|
51
|
+
DELEGATE_OUTPUT_SLICE: 60,
|
|
52
|
+
/** Max characters for command preview in logs */
|
|
53
|
+
COMMAND_PREVIEW: 100,
|
|
54
|
+
/** Max characters for output summary in agent loop */
|
|
55
|
+
OUTPUT_SUMMARY: 200,
|
|
56
|
+
/** Max characters for error preview in logs */
|
|
57
|
+
ERROR_PREVIEW: 100,
|
|
46
58
|
/** Max characters for status thought preview */
|
|
47
59
|
STATUS_THOUGHT_PREVIEW: 60,
|
|
48
60
|
/** Max characters after truncation for status */
|
|
@@ -66,7 +78,19 @@ var DISPLAY_LIMITS = {
|
|
|
66
78
|
/** Number of targets to preview in state summary */
|
|
67
79
|
TARGET_PREVIEW: 3,
|
|
68
80
|
/** Number of important findings to preview */
|
|
69
|
-
FINDING_PREVIEW: 5
|
|
81
|
+
FINDING_PREVIEW: 5,
|
|
82
|
+
/** Number of hashes to preview */
|
|
83
|
+
HASH_PREVIEW: 20,
|
|
84
|
+
/** Number of loot items to preview */
|
|
85
|
+
LOOT_PREVIEW: 30,
|
|
86
|
+
/** Number of recent process events to show */
|
|
87
|
+
RECENT_EVENT_COUNT: 5,
|
|
88
|
+
/** Max links to preview in browser output */
|
|
89
|
+
LINKS_PREVIEW: 20,
|
|
90
|
+
/** Max endpoints to sample */
|
|
91
|
+
ENDPOINT_SAMPLE: 5,
|
|
92
|
+
/** Purpose truncation length */
|
|
93
|
+
PURPOSE_TRUNCATE: 27
|
|
70
94
|
};
|
|
71
95
|
var AGENT_LIMITS = {
|
|
72
96
|
/** Maximum agent loop iterations — generous to allow complex tasks.
|
|
@@ -113,11 +137,27 @@ var INPUT_PROMPT_PATTERNS = [
|
|
|
113
137
|
/\(Y\/n\)/i
|
|
114
138
|
];
|
|
115
139
|
|
|
140
|
+
// src/shared/constants/exit-codes.ts
|
|
141
|
+
var EXIT_CODES = {
|
|
142
|
+
/** Successful execution */
|
|
143
|
+
SUCCESS: 0,
|
|
144
|
+
/** General error */
|
|
145
|
+
GENERAL_ERROR: 1,
|
|
146
|
+
/** Command not found */
|
|
147
|
+
COMMAND_NOT_FOUND: 127,
|
|
148
|
+
/** Process killed by SIGALRM (timeout) */
|
|
149
|
+
TIMEOUT: 124,
|
|
150
|
+
/** Process killed by SIGINT (Ctrl+C) */
|
|
151
|
+
SIGINT: 130,
|
|
152
|
+
/** Process killed by SIGTERM */
|
|
153
|
+
SIGTERM: 143
|
|
154
|
+
};
|
|
155
|
+
|
|
116
156
|
// src/shared/constants/agent.ts
|
|
117
157
|
var ID_LENGTH = AGENT_LIMITS.ID_LENGTH;
|
|
118
158
|
var ID_RADIX = AGENT_LIMITS.ID_RADIX;
|
|
119
159
|
var APP_NAME = "Pentest AI";
|
|
120
|
-
var APP_VERSION = "0.21.
|
|
160
|
+
var APP_VERSION = "0.21.9";
|
|
121
161
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
122
162
|
var LLM_ROLES = {
|
|
123
163
|
SYSTEM: "system",
|
|
@@ -146,7 +186,11 @@ var SENSITIVE_INPUT_TYPES = [
|
|
|
146
186
|
];
|
|
147
187
|
|
|
148
188
|
// src/shared/utils/id.ts
|
|
189
|
+
var ID_DEFAULT_RADIX = 36;
|
|
190
|
+
var ID_DEFAULT_LENGTH = 6;
|
|
149
191
|
var generateId = (radix = 36, length = 8) => Math.random().toString(radix).substring(2, 2 + length);
|
|
192
|
+
var generatePrefixedId = (prefix, radix = ID_DEFAULT_RADIX, randomLength = ID_DEFAULT_LENGTH) => `${prefix}_${Date.now()}_${Math.random().toString(radix).slice(2, 2 + randomLength)}`;
|
|
193
|
+
var generateTempFilename = (suffix = "", prefix = "pentest") => `${prefix}-${Date.now()}-${Math.random().toString(ID_DEFAULT_RADIX).slice(2, 2 + ID_DEFAULT_LENGTH)}${suffix}`;
|
|
150
194
|
|
|
151
195
|
// src/shared/constants/protocol.ts
|
|
152
196
|
var PHASES = {
|
|
@@ -410,15 +454,23 @@ var DEFAULTS = {
|
|
|
410
454
|
};
|
|
411
455
|
|
|
412
456
|
// src/engine/process-manager.ts
|
|
413
|
-
import { spawn as spawn2, execSync } from "child_process";
|
|
414
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
457
|
+
import { spawn as spawn2, execSync as execSync2 } from "child_process";
|
|
458
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync, writeFileSync as writeFileSync2, appendFileSync } from "fs";
|
|
415
459
|
|
|
416
460
|
// src/engine/tools-base.ts
|
|
417
461
|
import { spawn } from "child_process";
|
|
418
|
-
import { readFileSync, existsSync, writeFileSync
|
|
419
|
-
import { join } from "path";
|
|
462
|
+
import { readFileSync, existsSync as existsSync2, writeFileSync } from "fs";
|
|
463
|
+
import { join, dirname } from "path";
|
|
420
464
|
import { tmpdir } from "os";
|
|
421
465
|
|
|
466
|
+
// src/shared/utils/file-utils.ts
|
|
467
|
+
import { existsSync, mkdirSync } from "fs";
|
|
468
|
+
function ensureDirExists(dirPath) {
|
|
469
|
+
if (!existsSync(dirPath)) {
|
|
470
|
+
mkdirSync(dirPath, { recursive: true });
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
422
474
|
// src/shared/constants/system.ts
|
|
423
475
|
var PROCESS_ROLES = {
|
|
424
476
|
LISTENER: "listener",
|
|
@@ -469,6 +521,10 @@ var SYSTEM_LIMITS = {
|
|
|
469
521
|
MAX_STDOUT_SLICE: 3e3,
|
|
470
522
|
/** Maximum characters to show from stderr */
|
|
471
523
|
MAX_STDERR_SLICE: 500,
|
|
524
|
+
/** Maximum characters for error detail messages */
|
|
525
|
+
MAX_ERROR_DETAIL_SLICE: 200,
|
|
526
|
+
/** Maximum characters for input prompt previews */
|
|
527
|
+
MAX_PROMPT_PREVIEW: 50,
|
|
472
528
|
/** Maximum characters for input snippets in logs */
|
|
473
529
|
MAX_INPUT_SLICE: 100,
|
|
474
530
|
/** Maximum events to keep in process event log */
|
|
@@ -486,12 +542,6 @@ var SYSTEM_LIMITS = {
|
|
|
486
542
|
/** Port range for API services */
|
|
487
543
|
API_PORT_RANGE: { MIN: 3e3, MAX: 3500 }
|
|
488
544
|
};
|
|
489
|
-
var EXIT_CODES2 = {
|
|
490
|
-
/** SIGINT (Ctrl+C) - 128 + 2 */
|
|
491
|
-
SIGINT: 130,
|
|
492
|
-
/** SIGTERM - 128 + 15 */
|
|
493
|
-
SIGTERM: 143
|
|
494
|
-
};
|
|
495
545
|
var DETECTION_PATTERNS = {
|
|
496
546
|
LISTENER: /-(?:lvnp|nlvp|lp|p)\s+(\d+)/,
|
|
497
547
|
HTTP_SERVER: /(?:http\.server|SimpleHTTPServer)\s+(\d+)/,
|
|
@@ -1115,11 +1165,11 @@ async function installTool(toolName) {
|
|
|
1115
1165
|
globalEventEmitter?.({
|
|
1116
1166
|
type: COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED,
|
|
1117
1167
|
message: `Failed to install: ${toolName}`,
|
|
1118
|
-
detail: stderr.slice(0,
|
|
1168
|
+
detail: stderr.slice(0, SYSTEM_LIMITS.MAX_ERROR_DETAIL_SLICE)
|
|
1119
1169
|
});
|
|
1120
1170
|
resolve({
|
|
1121
1171
|
success: false,
|
|
1122
|
-
output: `Failed to install ${toolName}: ${stderr.slice(0,
|
|
1172
|
+
output: `Failed to install ${toolName}: ${stderr.slice(0, SYSTEM_LIMITS.MAX_ERROR_DETAIL_SLICE)}`
|
|
1123
1173
|
});
|
|
1124
1174
|
}
|
|
1125
1175
|
});
|
|
@@ -1169,7 +1219,7 @@ async function runCommand(command, args = [], options = {}) {
|
|
|
1169
1219
|
globalEventEmitter?.({
|
|
1170
1220
|
type: COMMAND_EVENT_TYPES.TOOL_RETRY,
|
|
1171
1221
|
message: `Retrying command after installing ${toolName}...`,
|
|
1172
|
-
detail: command.slice(0,
|
|
1222
|
+
detail: command.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)
|
|
1173
1223
|
});
|
|
1174
1224
|
}
|
|
1175
1225
|
return lastResult;
|
|
@@ -1179,7 +1229,7 @@ async function executeCommandOnce(command, args = [], options = {}) {
|
|
|
1179
1229
|
const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
|
|
1180
1230
|
globalEventEmitter?.({
|
|
1181
1231
|
type: COMMAND_EVENT_TYPES.COMMAND_START,
|
|
1182
|
-
message: `Executing: ${command.slice(0,
|
|
1232
|
+
message: `Executing: ${command.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)}${command.length > DISPLAY_LIMITS.COMMAND_PREVIEW ? "..." : ""}`
|
|
1183
1233
|
});
|
|
1184
1234
|
const child = spawn("sh", ["-c", command], {
|
|
1185
1235
|
timeout,
|
|
@@ -1197,7 +1247,7 @@ async function executeCommandOnce(command, args = [], options = {}) {
|
|
|
1197
1247
|
inputHandled = true;
|
|
1198
1248
|
globalEventEmitter?.({
|
|
1199
1249
|
type: COMMAND_EVENT_TYPES.INPUT_REQUIRED,
|
|
1200
|
-
message: `Input required: ${data.trim().slice(0,
|
|
1250
|
+
message: `Input required: ${data.trim().slice(0, SYSTEM_LIMITS.MAX_PROMPT_PREVIEW)}`,
|
|
1201
1251
|
detail: "Waiting for user input..."
|
|
1202
1252
|
});
|
|
1203
1253
|
const promptText = data.trim();
|
|
@@ -1237,7 +1287,7 @@ async function executeCommandOnce(command, args = [], options = {}) {
|
|
|
1237
1287
|
globalEventEmitter?.({
|
|
1238
1288
|
type: COMMAND_EVENT_TYPES.COMMAND_FAILED,
|
|
1239
1289
|
message: `Command failed (exit ${code})`,
|
|
1240
|
-
detail: stderr.slice(0,
|
|
1290
|
+
detail: stderr.slice(0, SYSTEM_LIMITS.MAX_INPUT_SLICE)
|
|
1241
1291
|
});
|
|
1242
1292
|
resolve({
|
|
1243
1293
|
success: false,
|
|
@@ -1262,7 +1312,7 @@ async function executeCommandOnce(command, args = [], options = {}) {
|
|
|
1262
1312
|
}
|
|
1263
1313
|
async function readFileContent(filePath) {
|
|
1264
1314
|
try {
|
|
1265
|
-
if (!
|
|
1315
|
+
if (!existsSync2(filePath)) {
|
|
1266
1316
|
return {
|
|
1267
1317
|
success: false,
|
|
1268
1318
|
output: "",
|
|
@@ -1285,10 +1335,8 @@ async function readFileContent(filePath) {
|
|
|
1285
1335
|
}
|
|
1286
1336
|
async function writeFileContent(filePath, content) {
|
|
1287
1337
|
try {
|
|
1288
|
-
const dir =
|
|
1289
|
-
|
|
1290
|
-
mkdirSync(dir, { recursive: true });
|
|
1291
|
-
}
|
|
1338
|
+
const dir = dirname(filePath);
|
|
1339
|
+
ensureDirExists(dir);
|
|
1292
1340
|
writeFileSync(filePath, content, "utf-8");
|
|
1293
1341
|
return {
|
|
1294
1342
|
success: true,
|
|
@@ -1304,20 +1352,11 @@ async function writeFileContent(filePath, content) {
|
|
|
1304
1352
|
}
|
|
1305
1353
|
}
|
|
1306
1354
|
function createTempFile(suffix = "") {
|
|
1307
|
-
return join(tmpdir(),
|
|
1355
|
+
return join(tmpdir(), generateTempFilename(suffix));
|
|
1308
1356
|
}
|
|
1309
1357
|
|
|
1310
|
-
// src/engine/process-
|
|
1311
|
-
|
|
1312
|
-
var cleanupDone = false;
|
|
1313
|
-
var processEventLog = [];
|
|
1314
|
-
function logEvent(processId, event, detail) {
|
|
1315
|
-
processEventLog.push({ timestamp: Date.now(), processId, event, detail });
|
|
1316
|
-
if (processEventLog.length > SYSTEM_LIMITS.MAX_EVENT_LOG) {
|
|
1317
|
-
processEventLog.splice(0, processEventLog.length - SYSTEM_LIMITS.MAX_EVENT_LOG);
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
function autoDetect(command) {
|
|
1358
|
+
// src/engine/process-detector.ts
|
|
1359
|
+
function detectProcessRole(command) {
|
|
1321
1360
|
const tags = [];
|
|
1322
1361
|
let port;
|
|
1323
1362
|
let role = PROCESS_ROLES.BACKGROUND;
|
|
@@ -1365,6 +1404,12 @@ function autoDetect(command) {
|
|
|
1365
1404
|
if (tags.length === 0) tags.push(PROCESS_ROLES.BACKGROUND);
|
|
1366
1405
|
return { tags, port, role, isInteractive };
|
|
1367
1406
|
}
|
|
1407
|
+
function detectConnection(stdout) {
|
|
1408
|
+
return DETECTION_PATTERNS.CONNECTION.some((p) => p.test(stdout));
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// src/engine/process-tree.ts
|
|
1412
|
+
import { execSync } from "child_process";
|
|
1368
1413
|
function discoverChildPids(parentPid) {
|
|
1369
1414
|
try {
|
|
1370
1415
|
const result2 = execSync(`pgrep -P ${parentPid} 2>/dev/null || true`, {
|
|
@@ -1395,15 +1440,74 @@ function isPidAlive(pid) {
|
|
|
1395
1440
|
return false;
|
|
1396
1441
|
}
|
|
1397
1442
|
}
|
|
1398
|
-
function
|
|
1399
|
-
|
|
1443
|
+
async function killProcessTree(pid, childPids, waitMs = SYSTEM_LIMITS.SHUTDOWN_WAIT_MS) {
|
|
1444
|
+
try {
|
|
1445
|
+
process.kill(-pid, "SIGTERM");
|
|
1446
|
+
} catch {
|
|
1447
|
+
try {
|
|
1448
|
+
process.kill(pid, "SIGTERM");
|
|
1449
|
+
} catch {
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
for (const childPid of childPids) {
|
|
1453
|
+
try {
|
|
1454
|
+
process.kill(childPid, "SIGTERM");
|
|
1455
|
+
} catch {
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
1459
|
+
if (isPidAlive(pid)) {
|
|
1460
|
+
try {
|
|
1461
|
+
process.kill(-pid, "SIGKILL");
|
|
1462
|
+
} catch {
|
|
1463
|
+
}
|
|
1464
|
+
try {
|
|
1465
|
+
process.kill(pid, "SIGKILL");
|
|
1466
|
+
} catch {
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
for (const childPid of childPids) {
|
|
1470
|
+
if (isPidAlive(childPid)) {
|
|
1471
|
+
try {
|
|
1472
|
+
process.kill(childPid, "SIGKILL");
|
|
1473
|
+
} catch {
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
function killProcessTreeSync(pid, childPids) {
|
|
1479
|
+
try {
|
|
1480
|
+
process.kill(-pid, "SIGKILL");
|
|
1481
|
+
} catch {
|
|
1482
|
+
}
|
|
1483
|
+
try {
|
|
1484
|
+
process.kill(pid, "SIGKILL");
|
|
1485
|
+
} catch {
|
|
1486
|
+
}
|
|
1487
|
+
for (const childPid of childPids) {
|
|
1488
|
+
try {
|
|
1489
|
+
process.kill(childPid, "SIGKILL");
|
|
1490
|
+
} catch {
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// src/engine/process-manager.ts
|
|
1496
|
+
var backgroundProcesses = /* @__PURE__ */ new Map();
|
|
1497
|
+
var cleanupDone = false;
|
|
1498
|
+
var processEventLog = [];
|
|
1499
|
+
function logEvent(processId, event, detail) {
|
|
1500
|
+
processEventLog.push({ timestamp: Date.now(), processId, event, detail });
|
|
1501
|
+
if (processEventLog.length > SYSTEM_LIMITS.MAX_EVENT_LOG) {
|
|
1502
|
+
processEventLog.splice(0, processEventLog.length - SYSTEM_LIMITS.MAX_EVENT_LOG);
|
|
1503
|
+
}
|
|
1400
1504
|
}
|
|
1401
1505
|
function startBackgroundProcess(command, options = {}) {
|
|
1402
|
-
const processId =
|
|
1506
|
+
const processId = generatePrefixedId("bg");
|
|
1403
1507
|
const stdoutFile = createTempFile(".stdout");
|
|
1404
1508
|
const stderrFile = createTempFile(".stderr");
|
|
1405
1509
|
const stdinFile = createTempFile(".stdin");
|
|
1406
|
-
const { tags, port, role, isInteractive } =
|
|
1510
|
+
const { tags, port, role, isInteractive } = detectProcessRole(command);
|
|
1407
1511
|
let wrappedCmd;
|
|
1408
1512
|
if (isInteractive) {
|
|
1409
1513
|
writeFileSync2(stdinFile, "", "utf-8");
|
|
@@ -1459,7 +1563,7 @@ async function sendToProcess(processId, input, waitMs = SYSTEM_LIMITS.DEFAULT_WA
|
|
|
1459
1563
|
if (proc.hasExited) return { success: false, output: `Process ${processId} has exited`, newOutput: "" };
|
|
1460
1564
|
let currentLen = 0;
|
|
1461
1565
|
try {
|
|
1462
|
-
if (
|
|
1566
|
+
if (existsSync3(proc.stdoutFile)) {
|
|
1463
1567
|
currentLen = readFileSync2(proc.stdoutFile, "utf-8").length;
|
|
1464
1568
|
}
|
|
1465
1569
|
} catch {
|
|
@@ -1473,7 +1577,7 @@ async function sendToProcess(processId, input, waitMs = SYSTEM_LIMITS.DEFAULT_WA
|
|
|
1473
1577
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
1474
1578
|
let fullStdout = "";
|
|
1475
1579
|
try {
|
|
1476
|
-
if (
|
|
1580
|
+
if (existsSync3(proc.stdoutFile)) {
|
|
1477
1581
|
fullStdout = readFileSync2(proc.stdoutFile, "utf-8");
|
|
1478
1582
|
}
|
|
1479
1583
|
} catch {
|
|
@@ -1506,7 +1610,7 @@ function isProcessRunning(processId) {
|
|
|
1506
1610
|
proc.childPids = discoverAllDescendants(proc.pid);
|
|
1507
1611
|
if (proc.role === "listener") {
|
|
1508
1612
|
try {
|
|
1509
|
-
if (
|
|
1613
|
+
if (existsSync3(proc.stdoutFile)) {
|
|
1510
1614
|
const stdout = readFileSync2(proc.stdoutFile, "utf-8");
|
|
1511
1615
|
if (detectConnection(stdout)) {
|
|
1512
1616
|
promoteToShell(processId, "Reverse shell connected (auto-detected)");
|
|
@@ -1525,7 +1629,7 @@ function getProcessOutput(processId) {
|
|
|
1525
1629
|
let stdout = "";
|
|
1526
1630
|
let stderr = "";
|
|
1527
1631
|
try {
|
|
1528
|
-
if (
|
|
1632
|
+
if (existsSync3(proc.stdoutFile)) {
|
|
1529
1633
|
const content = readFileSync2(proc.stdoutFile, "utf-8");
|
|
1530
1634
|
stdout = content.length > SYSTEM_LIMITS.MAX_STDOUT_SLICE ? `... [truncated ${content.length - SYSTEM_LIMITS.MAX_STDOUT_SLICE} chars] ...
|
|
1531
1635
|
` + content.slice(-SYSTEM_LIMITS.MAX_STDOUT_SLICE) : content;
|
|
@@ -1533,7 +1637,7 @@ function getProcessOutput(processId) {
|
|
|
1533
1637
|
} catch {
|
|
1534
1638
|
}
|
|
1535
1639
|
try {
|
|
1536
|
-
if (
|
|
1640
|
+
if (existsSync3(proc.stderrFile)) {
|
|
1537
1641
|
const content = readFileSync2(proc.stderrFile, "utf-8");
|
|
1538
1642
|
stderr = content.length > SYSTEM_LIMITS.MAX_STDERR_SLICE ? `... [truncated ${content.length - SYSTEM_LIMITS.MAX_STDERR_SLICE} chars] ...
|
|
1539
1643
|
` + content.slice(-SYSTEM_LIMITS.MAX_STDERR_SLICE) : content;
|
|
@@ -1554,46 +1658,14 @@ function getProcessOutput(processId) {
|
|
|
1554
1658
|
isInteractive: proc.isInteractive
|
|
1555
1659
|
};
|
|
1556
1660
|
}
|
|
1557
|
-
async function
|
|
1661
|
+
async function stopBackgroundProcess(processId) {
|
|
1558
1662
|
const proc = backgroundProcesses.get(processId);
|
|
1559
1663
|
if (!proc) return { success: false, output: `Process ${processId} not found` };
|
|
1560
1664
|
const wasActiveShell = proc.role === PROCESS_ROLES.ACTIVE_SHELL;
|
|
1561
1665
|
const finalOutput = getProcessOutput(processId);
|
|
1562
1666
|
if (!proc.hasExited) {
|
|
1563
1667
|
proc.childPids = discoverAllDescendants(proc.pid);
|
|
1564
|
-
|
|
1565
|
-
process.kill(-proc.pid, "SIGTERM");
|
|
1566
|
-
} catch {
|
|
1567
|
-
try {
|
|
1568
|
-
process.kill(proc.pid, "SIGTERM");
|
|
1569
|
-
} catch {
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
for (const childPid of proc.childPids) {
|
|
1573
|
-
try {
|
|
1574
|
-
process.kill(childPid, "SIGTERM");
|
|
1575
|
-
} catch {
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
await new Promise((r) => setTimeout(r, SYSTEM_LIMITS.SHUTDOWN_WAIT_MS));
|
|
1579
|
-
if (isPidAlive(proc.pid)) {
|
|
1580
|
-
try {
|
|
1581
|
-
process.kill(-proc.pid, "SIGKILL");
|
|
1582
|
-
} catch {
|
|
1583
|
-
}
|
|
1584
|
-
try {
|
|
1585
|
-
process.kill(proc.pid, "SIGKILL");
|
|
1586
|
-
} catch {
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
for (const childPid of proc.childPids) {
|
|
1590
|
-
if (isPidAlive(childPid)) {
|
|
1591
|
-
try {
|
|
1592
|
-
process.kill(childPid, "SIGKILL");
|
|
1593
|
-
} catch {
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1668
|
+
await killProcessTree(proc.pid, proc.childPids);
|
|
1597
1669
|
}
|
|
1598
1670
|
try {
|
|
1599
1671
|
unlinkSync(proc.stdoutFile);
|
|
@@ -1717,7 +1789,7 @@ async function cleanupAllProcesses() {
|
|
|
1717
1789
|
backgroundProcesses.clear();
|
|
1718
1790
|
for (const name of ORPHAN_PROCESS_NAMES) {
|
|
1719
1791
|
try {
|
|
1720
|
-
|
|
1792
|
+
execSync2(`pkill -f "${name}" 2>/dev/null || true`, { stdio: "ignore", timeout: SYSTEM_LIMITS.PROCESS_OP_TIMEOUT_MS });
|
|
1721
1793
|
} catch {
|
|
1722
1794
|
}
|
|
1723
1795
|
}
|
|
@@ -1740,10 +1812,10 @@ function getResourceSummary() {
|
|
|
1740
1812
|
const roleIcon = PROCESS_ICONS[p.role] || PROCESS_ICONS[PROCESS_ROLES.BACKGROUND];
|
|
1741
1813
|
let lastOutput = "";
|
|
1742
1814
|
try {
|
|
1743
|
-
if (p.stdoutFile &&
|
|
1815
|
+
if (p.stdoutFile && existsSync3(p.stdoutFile)) {
|
|
1744
1816
|
const content = readFileSync2(p.stdoutFile, "utf-8");
|
|
1745
|
-
const
|
|
1746
|
-
lastOutput =
|
|
1817
|
+
const outputLines = content.trim().split("\n");
|
|
1818
|
+
lastOutput = outputLines.slice(-3).join(" | ").replace(/\n/g, " ");
|
|
1747
1819
|
}
|
|
1748
1820
|
} catch {
|
|
1749
1821
|
}
|
|
@@ -1767,7 +1839,7 @@ ${STATUS_MARKERS.WARNING} SUSPECTED ZOMBIES (Orphaned Children):`);
|
|
|
1767
1839
|
for (const o of orphans) {
|
|
1768
1840
|
lines.push(` [${o.id}] Exited parent has alive children: ${o.childPids.join(", ")}`);
|
|
1769
1841
|
}
|
|
1770
|
-
lines.push(` \u2192 Recommendation: Run bg_process({ action: "stop", process_id: "ID" }) to clean up
|
|
1842
|
+
lines.push(` \u2192 Recommendation: Run bg_process({ action: "stop", process_id: "ID" }) to clean up.`);
|
|
1771
1843
|
}
|
|
1772
1844
|
if (ports.length > 0) {
|
|
1773
1845
|
lines.push(`
|
|
@@ -1799,20 +1871,7 @@ Ports In Use: ${ports.join(", ")}`);
|
|
|
1799
1871
|
function syncCleanupAllProcesses() {
|
|
1800
1872
|
for (const [, proc] of backgroundProcesses) {
|
|
1801
1873
|
if (!proc.hasExited) {
|
|
1802
|
-
|
|
1803
|
-
process.kill(-proc.pid, "SIGKILL");
|
|
1804
|
-
} catch {
|
|
1805
|
-
}
|
|
1806
|
-
try {
|
|
1807
|
-
process.kill(proc.pid, "SIGKILL");
|
|
1808
|
-
} catch {
|
|
1809
|
-
}
|
|
1810
|
-
for (const childPid of proc.childPids) {
|
|
1811
|
-
try {
|
|
1812
|
-
process.kill(childPid, "SIGKILL");
|
|
1813
|
-
} catch {
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1874
|
+
killProcessTreeSync(proc.pid, proc.childPids);
|
|
1816
1875
|
}
|
|
1817
1876
|
try {
|
|
1818
1877
|
unlinkSync(proc.stdoutFile);
|
|
@@ -1834,13 +1893,14 @@ process.on("exit", () => {
|
|
|
1834
1893
|
process.on("SIGINT", () => {
|
|
1835
1894
|
syncCleanupAllProcesses();
|
|
1836
1895
|
backgroundProcesses.clear();
|
|
1837
|
-
process.exit(
|
|
1896
|
+
process.exit(EXIT_CODES.SIGINT);
|
|
1838
1897
|
});
|
|
1839
1898
|
process.on("SIGTERM", () => {
|
|
1840
1899
|
syncCleanupAllProcesses();
|
|
1841
1900
|
backgroundProcesses.clear();
|
|
1842
|
-
process.exit(
|
|
1901
|
+
process.exit(EXIT_CODES.SIGTERM);
|
|
1843
1902
|
});
|
|
1903
|
+
var stopProcess = stopBackgroundProcess;
|
|
1844
1904
|
|
|
1845
1905
|
// src/engine/state-serializer.ts
|
|
1846
1906
|
var StateSerializer = class {
|
|
@@ -1952,11 +2012,11 @@ var SharedState = class {
|
|
|
1952
2012
|
for (const update of updates) {
|
|
1953
2013
|
const index = this.data.missionChecklist.findIndex((item) => item.id === update.id);
|
|
1954
2014
|
if (index !== -1) {
|
|
1955
|
-
if (update.
|
|
2015
|
+
if (update.shouldRemove) {
|
|
1956
2016
|
this.data.missionChecklist.splice(index, 1);
|
|
1957
2017
|
} else {
|
|
1958
2018
|
if (update.text !== void 0) this.data.missionChecklist[index].text = update.text;
|
|
1959
|
-
if (update.
|
|
2019
|
+
if (update.isCompleted !== void 0) this.data.missionChecklist[index].isCompleted = update.isCompleted;
|
|
1960
2020
|
}
|
|
1961
2021
|
}
|
|
1962
2022
|
}
|
|
@@ -2349,23 +2409,23 @@ function getApprovalLevel(toolCall) {
|
|
|
2349
2409
|
return APPROVAL_LEVELS.CONFIRM;
|
|
2350
2410
|
}
|
|
2351
2411
|
var ApprovalGate = class {
|
|
2352
|
-
constructor(
|
|
2353
|
-
this.
|
|
2412
|
+
constructor(shouldAutoApprove = false) {
|
|
2413
|
+
this.shouldAutoApprove = shouldAutoApprove;
|
|
2354
2414
|
}
|
|
2355
2415
|
/**
|
|
2356
2416
|
* Set auto-approve mode
|
|
2357
2417
|
*/
|
|
2358
2418
|
setAutoApprove(enabled) {
|
|
2359
|
-
this.
|
|
2419
|
+
this.shouldAutoApprove = enabled;
|
|
2360
2420
|
}
|
|
2361
2421
|
/**
|
|
2362
2422
|
* Get current auto-approve mode
|
|
2363
2423
|
*/
|
|
2364
2424
|
isAutoApprove() {
|
|
2365
|
-
return this.
|
|
2425
|
+
return this.shouldAutoApprove;
|
|
2366
2426
|
}
|
|
2367
2427
|
async request(toolCall) {
|
|
2368
|
-
if (this.
|
|
2428
|
+
if (this.shouldAutoApprove) return { isApproved: true, reason: "Auto-approve enabled" };
|
|
2369
2429
|
const level = getApprovalLevel(toolCall);
|
|
2370
2430
|
if (level === APPROVAL_LEVELS.AUTO) return { isApproved: true };
|
|
2371
2431
|
if (level === APPROVAL_LEVELS.BLOCK) return { isApproved: false, reason: "Policy blocked execution" };
|
|
@@ -2696,105 +2756,284 @@ All ports freed. All children killed.`
|
|
|
2696
2756
|
}
|
|
2697
2757
|
];
|
|
2698
2758
|
|
|
2699
|
-
// src/engine/tools-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
}
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
}
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
}
|
|
2733
|
-
|
|
2734
|
-
// src/shared/constants/search-api.const.ts
|
|
2735
|
-
var SEARCH_URL_PATTERN = {
|
|
2736
|
-
GLM: "bigmodel.cn",
|
|
2737
|
-
ZHIPU: "zhipuai",
|
|
2738
|
-
BRAVE: "brave.com",
|
|
2739
|
-
SERPER: "serper.dev"
|
|
2740
|
-
};
|
|
2741
|
-
var SEARCH_HEADER = {
|
|
2742
|
-
CONTENT_TYPE: "Content-Type",
|
|
2743
|
-
AUTHORIZATION: "Authorization",
|
|
2744
|
-
ACCEPT: "Accept",
|
|
2745
|
-
X_SUBSCRIPTION_TOKEN: "X-Subscription-Token",
|
|
2746
|
-
X_API_KEY: "X-API-KEY"
|
|
2747
|
-
};
|
|
2748
|
-
var SEARCH_LIMIT = {
|
|
2749
|
-
DEFAULT_RESULT_COUNT: 10,
|
|
2750
|
-
MAX_OUTPUT_LINES: 20,
|
|
2751
|
-
TIMEOUT_MS: 1e4
|
|
2752
|
-
};
|
|
2753
|
-
|
|
2754
|
-
// src/shared/utils/debug-logger.ts
|
|
2755
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
2756
|
-
import { join as join2 } from "path";
|
|
2757
|
-
var DebugLogger = class _DebugLogger {
|
|
2758
|
-
static instance;
|
|
2759
|
-
logPath;
|
|
2760
|
-
initialized = false;
|
|
2761
|
-
constructor(clearOnInit = false) {
|
|
2762
|
-
const debugDir = join2(process.cwd(), ".pentesting", "debug");
|
|
2763
|
-
try {
|
|
2764
|
-
if (!existsSync3(debugDir)) {
|
|
2765
|
-
mkdirSync2(debugDir, { recursive: true });
|
|
2766
|
-
}
|
|
2767
|
-
this.logPath = join2(debugDir, "debug.log");
|
|
2768
|
-
if (clearOnInit) {
|
|
2769
|
-
this.clear();
|
|
2759
|
+
// src/engine/tools/pentest-state-tools.ts
|
|
2760
|
+
var createStateTools = (state) => [
|
|
2761
|
+
{
|
|
2762
|
+
name: TOOL_NAMES.UPDATE_MISSION,
|
|
2763
|
+
description: `Update the mission summary and checklist.
|
|
2764
|
+
Use this for strategic planning and maintaining long-term context during complex engagements.
|
|
2765
|
+
The mission summary acts as your "long-term memory" \u2014 keep it updated with critical findings and overall progress.
|
|
2766
|
+
The checklist tracks granular steps (e.g., "[ ] Crack hashes", "[x] Recon 10.10.10.5").
|
|
2767
|
+
Mandatory when:
|
|
2768
|
+
- Big goals change
|
|
2769
|
+
- You encounter a major obstacle (firewall, port conflict)
|
|
2770
|
+
- You gain a significant new access point (reverse shell)
|
|
2771
|
+
- You need to summarize findings to prevent context overflow`,
|
|
2772
|
+
parameters: {
|
|
2773
|
+
summary: { type: "string", description: "Updated mission summary (concise overview)" },
|
|
2774
|
+
checklist_updates: {
|
|
2775
|
+
type: "array",
|
|
2776
|
+
items: {
|
|
2777
|
+
type: "object",
|
|
2778
|
+
properties: {
|
|
2779
|
+
id: { type: "string", description: "Item ID (required for update/remove)" },
|
|
2780
|
+
text: { type: "string", description: "Updated text" },
|
|
2781
|
+
completed: { type: "boolean", description: "Completion status" },
|
|
2782
|
+
remove: { type: "boolean", description: "Remove item if true" }
|
|
2783
|
+
},
|
|
2784
|
+
required: ["id"]
|
|
2785
|
+
},
|
|
2786
|
+
description: "Updates to existing checklist items"
|
|
2787
|
+
},
|
|
2788
|
+
add_items: {
|
|
2789
|
+
type: "array",
|
|
2790
|
+
items: { type: "string" },
|
|
2791
|
+
description: "New checklist items to add"
|
|
2770
2792
|
}
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2793
|
+
},
|
|
2794
|
+
execute: async (p) => {
|
|
2795
|
+
if (p.summary) state.setMissionSummary(p.summary);
|
|
2796
|
+
if (p.checklist_updates) state.updateMissionChecklist(p.checklist_updates);
|
|
2797
|
+
if (p.add_items) state.addMissionChecklistItems(p.add_items);
|
|
2798
|
+
return {
|
|
2799
|
+
success: true,
|
|
2800
|
+
output: `Mission updated.
|
|
2801
|
+
Summary: ${state.getMissionSummary()}
|
|
2802
|
+
Checklist Items: ${state.getMissionChecklist().length}`
|
|
2803
|
+
};
|
|
2782
2804
|
}
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2805
|
+
},
|
|
2806
|
+
{
|
|
2807
|
+
name: TOOL_NAMES.GET_STATE,
|
|
2808
|
+
description: "Get current engagement state summary",
|
|
2809
|
+
parameters: {},
|
|
2810
|
+
execute: async () => ({ success: true, output: state.toPrompt() })
|
|
2789
2811
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2812
|
+
];
|
|
2813
|
+
|
|
2814
|
+
// src/engine/tools/pentest-target-tools.ts
|
|
2815
|
+
var createTargetTools = (state) => [
|
|
2816
|
+
{
|
|
2817
|
+
name: TOOL_NAMES.ADD_TARGET,
|
|
2818
|
+
description: `Register a new target for the engagement.
|
|
2819
|
+
Use this when you discover new hosts during recon, pivoting, or post-exploitation.
|
|
2820
|
+
The target will be tracked in SharedState and available for all agents.`,
|
|
2821
|
+
parameters: {
|
|
2822
|
+
ip: { type: "string", description: "Target IP address" },
|
|
2823
|
+
hostname: { type: "string", description: "Target hostname (optional)" },
|
|
2824
|
+
ports: {
|
|
2825
|
+
type: "array",
|
|
2826
|
+
items: {
|
|
2827
|
+
type: "object",
|
|
2828
|
+
properties: {
|
|
2829
|
+
port: { type: "number", description: "Port number" },
|
|
2830
|
+
service: { type: "string", description: "Service name (e.g., http, ssh)" },
|
|
2831
|
+
version: { type: "string", description: "Service version" },
|
|
2832
|
+
state: { type: "string", description: "Port state (default: open)" }
|
|
2833
|
+
},
|
|
2834
|
+
required: ["port", "service"]
|
|
2835
|
+
},
|
|
2836
|
+
description: "Discovered ports (optional, can add later via recon)"
|
|
2837
|
+
},
|
|
2838
|
+
tags: {
|
|
2839
|
+
type: "array",
|
|
2840
|
+
items: { type: "string" },
|
|
2841
|
+
description: 'Tags for categorization (e.g., "internal", "dmz", "pivot")'
|
|
2842
|
+
}
|
|
2843
|
+
},
|
|
2844
|
+
required: ["ip"],
|
|
2845
|
+
execute: async (p) => {
|
|
2846
|
+
const ip = p.ip;
|
|
2847
|
+
const existing = state.getTarget(ip);
|
|
2848
|
+
if (existing) {
|
|
2849
|
+
const newPorts = p.ports || [];
|
|
2850
|
+
for (const np of newPorts) {
|
|
2851
|
+
const exists = existing.ports.some((ep) => ep.port === np.port);
|
|
2852
|
+
if (!exists) {
|
|
2853
|
+
existing.ports.push({
|
|
2854
|
+
port: np.port,
|
|
2855
|
+
service: np.service || "unknown",
|
|
2856
|
+
version: np.version,
|
|
2857
|
+
state: np.state || "open",
|
|
2858
|
+
notes: []
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
if (p.hostname) existing.hostname = p.hostname;
|
|
2863
|
+
if (p.tags) existing.tags = [.../* @__PURE__ */ new Set([...existing.tags, ...p.tags])];
|
|
2864
|
+
return { success: true, output: `Target ${ip} updated. Total ports: ${existing.ports.length}` };
|
|
2865
|
+
}
|
|
2866
|
+
const ports = (p.ports || []).map((port) => ({
|
|
2867
|
+
port: port.port,
|
|
2868
|
+
service: port.service || "unknown",
|
|
2869
|
+
version: port.version,
|
|
2870
|
+
state: port.state || "open",
|
|
2871
|
+
notes: []
|
|
2872
|
+
}));
|
|
2873
|
+
state.addTarget({
|
|
2874
|
+
ip,
|
|
2875
|
+
hostname: p.hostname,
|
|
2876
|
+
ports,
|
|
2877
|
+
tags: p.tags || [],
|
|
2878
|
+
firstSeen: Date.now()
|
|
2879
|
+
});
|
|
2880
|
+
return { success: true, output: `Target ${ip} added.${p.hostname ? ` Hostname: ${p.hostname}` : ""} Ports: ${ports.length}` };
|
|
2881
|
+
}
|
|
2882
|
+
},
|
|
2883
|
+
{
|
|
2884
|
+
name: TOOL_NAMES.ADD_LOOT,
|
|
2885
|
+
description: `Record captured loot (credentials, hashes, tokens, SSH keys, files, etc).
|
|
2886
|
+
Use this whenever you discover sensitive data during the engagement.
|
|
2887
|
+
Loot is tracked in SharedState and visible to all agents for credential reuse and lateral movement.
|
|
2888
|
+
Types: credential, hash, token, ssh_key, api_key, file, session, ticket, certificate`,
|
|
2889
|
+
parameters: {
|
|
2890
|
+
type: { type: "string", description: "Loot type: credential, hash, token, ssh_key, api_key, file, session, ticket, certificate" },
|
|
2891
|
+
host: { type: "string", description: "Source host where loot was found" },
|
|
2892
|
+
detail: { type: "string", description: 'Loot detail (e.g., "admin:Password123", "root:$6$...", "JWT admin token")' }
|
|
2893
|
+
},
|
|
2894
|
+
required: ["type", "host", "detail"],
|
|
2895
|
+
execute: async (p) => {
|
|
2896
|
+
const lootType = p.type;
|
|
2897
|
+
const crackableTypes = ["hash"];
|
|
2898
|
+
state.addLoot({
|
|
2899
|
+
type: lootType,
|
|
2900
|
+
host: p.host,
|
|
2901
|
+
detail: p.detail,
|
|
2902
|
+
obtainedAt: Date.now(),
|
|
2903
|
+
isCrackable: crackableTypes.includes(lootType),
|
|
2904
|
+
isCracked: false
|
|
2905
|
+
});
|
|
2906
|
+
return {
|
|
2907
|
+
success: true,
|
|
2908
|
+
output: `Loot recorded: [${lootType}] from ${p.host}
|
|
2909
|
+
Detail: ${p.detail}
|
|
2910
|
+
` + (crackableTypes.includes(lootType) ? `This is crackable. Consider: hash_crack({ hashes: "${p.detail.slice(0, 30)}..." })` : `Consider credential reuse / lateral movement with this loot.`)
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
},
|
|
2914
|
+
{
|
|
2915
|
+
name: TOOL_NAMES.ADD_FINDING,
|
|
2916
|
+
description: "Add a security finding",
|
|
2917
|
+
parameters: {
|
|
2918
|
+
title: { type: "string", description: "Finding title" },
|
|
2919
|
+
severity: { type: "string", description: "Severity" },
|
|
2920
|
+
affected: { type: "array", items: { type: "string" }, description: "Affected host:port" }
|
|
2921
|
+
},
|
|
2922
|
+
required: ["title", "severity"],
|
|
2923
|
+
execute: async (p) => {
|
|
2924
|
+
state.addFinding({
|
|
2925
|
+
id: generateId(ID_RADIX, ID_LENGTH),
|
|
2926
|
+
title: p.title,
|
|
2927
|
+
severity: p.severity,
|
|
2928
|
+
affected: p.affected || [],
|
|
2929
|
+
description: p.description || "",
|
|
2930
|
+
evidence: [],
|
|
2931
|
+
isVerified: false,
|
|
2932
|
+
remediation: "",
|
|
2933
|
+
foundAt: Date.now()
|
|
2934
|
+
});
|
|
2935
|
+
return { success: true, output: `Added: ${p.title}` };
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
];
|
|
2939
|
+
|
|
2940
|
+
// src/engine/tools-mid.ts
|
|
2941
|
+
import { execFileSync } from "child_process";
|
|
2942
|
+
|
|
2943
|
+
// src/shared/utils/config.ts
|
|
2944
|
+
import path from "path";
|
|
2945
|
+
import { fileURLToPath } from "url";
|
|
2946
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
2947
|
+
var __dirname = path.dirname(__filename);
|
|
2948
|
+
var ENV_KEYS = {
|
|
2949
|
+
API_KEY: "PENTEST_API_KEY",
|
|
2950
|
+
BASE_URL: "PENTEST_BASE_URL",
|
|
2951
|
+
MODEL: "PENTEST_MODEL",
|
|
2952
|
+
SEARCH_API_KEY: "SEARCH_API_KEY",
|
|
2953
|
+
SEARCH_API_URL: "SEARCH_API_URL"
|
|
2954
|
+
};
|
|
2955
|
+
var DEFAULT_SEARCH_API_URL = "https://api.search.brave.com/res/v1/web/search";
|
|
2956
|
+
function getApiKey() {
|
|
2957
|
+
return process.env[ENV_KEYS.API_KEY] || "";
|
|
2958
|
+
}
|
|
2959
|
+
function getBaseUrl() {
|
|
2960
|
+
return process.env[ENV_KEYS.BASE_URL] || void 0;
|
|
2961
|
+
}
|
|
2962
|
+
function getModel() {
|
|
2963
|
+
return process.env[ENV_KEYS.MODEL] || "";
|
|
2964
|
+
}
|
|
2965
|
+
function getSearchApiKey() {
|
|
2966
|
+
return process.env[ENV_KEYS.SEARCH_API_KEY];
|
|
2967
|
+
}
|
|
2968
|
+
function getSearchApiUrl() {
|
|
2969
|
+
return process.env[ENV_KEYS.SEARCH_API_URL] || DEFAULT_SEARCH_API_URL;
|
|
2970
|
+
}
|
|
2971
|
+
function isBrowserHeadless() {
|
|
2972
|
+
return true;
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
// src/shared/constants/search-api.const.ts
|
|
2976
|
+
var SEARCH_URL_PATTERN = {
|
|
2977
|
+
GLM: "bigmodel.cn",
|
|
2978
|
+
ZHIPU: "zhipuai",
|
|
2979
|
+
BRAVE: "brave.com",
|
|
2980
|
+
SERPER: "serper.dev"
|
|
2981
|
+
};
|
|
2982
|
+
var SEARCH_HEADER = {
|
|
2983
|
+
CONTENT_TYPE: "Content-Type",
|
|
2984
|
+
AUTHORIZATION: "Authorization",
|
|
2985
|
+
ACCEPT: "Accept",
|
|
2986
|
+
X_SUBSCRIPTION_TOKEN: "X-Subscription-Token",
|
|
2987
|
+
X_API_KEY: "X-API-KEY"
|
|
2988
|
+
};
|
|
2989
|
+
var SEARCH_LIMIT = {
|
|
2990
|
+
DEFAULT_RESULT_COUNT: 10,
|
|
2991
|
+
MAX_OUTPUT_LINES: 20,
|
|
2992
|
+
TIMEOUT_MS: 1e4
|
|
2993
|
+
};
|
|
2994
|
+
|
|
2995
|
+
// src/shared/utils/debug-logger.ts
|
|
2996
|
+
import { appendFileSync as appendFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2997
|
+
import { join as join2 } from "path";
|
|
2998
|
+
var DebugLogger = class _DebugLogger {
|
|
2999
|
+
static instance;
|
|
3000
|
+
logPath;
|
|
3001
|
+
initialized = false;
|
|
3002
|
+
constructor(clearOnInit = false) {
|
|
3003
|
+
const debugDir = join2(process.cwd(), ".pentesting", "debug");
|
|
3004
|
+
try {
|
|
3005
|
+
ensureDirExists(debugDir);
|
|
3006
|
+
this.logPath = join2(debugDir, "debug.log");
|
|
3007
|
+
if (clearOnInit) {
|
|
3008
|
+
this.clear();
|
|
3009
|
+
}
|
|
3010
|
+
this.initialized = true;
|
|
3011
|
+
this.log("general", "=== DEBUG LOGGER INITIALIZED ===");
|
|
3012
|
+
this.log("general", `Log file: ${this.logPath}`);
|
|
3013
|
+
} catch (e) {
|
|
3014
|
+
console.error("[DebugLogger] Failed to initialize:", e);
|
|
3015
|
+
this.logPath = "";
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
static getInstance(clearOnInit = false) {
|
|
3019
|
+
if (!_DebugLogger.instance) {
|
|
3020
|
+
_DebugLogger.instance = new _DebugLogger(clearOnInit);
|
|
3021
|
+
}
|
|
3022
|
+
return _DebugLogger.instance;
|
|
3023
|
+
}
|
|
3024
|
+
/** Reset the singleton instance (used by initDebugLogger) */
|
|
3025
|
+
static resetInstance(clearOnInit = false) {
|
|
3026
|
+
_DebugLogger.instance = new _DebugLogger(clearOnInit);
|
|
3027
|
+
return _DebugLogger.instance;
|
|
3028
|
+
}
|
|
3029
|
+
log(category, message, data) {
|
|
3030
|
+
if (!this.initialized || !this.logPath) return;
|
|
3031
|
+
try {
|
|
3032
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3033
|
+
let logLine = `[${timestamp}] [${category.toUpperCase()}] ${message}`;
|
|
3034
|
+
if (data !== void 0) {
|
|
3035
|
+
logLine += ` | ${JSON.stringify(data)}`;
|
|
3036
|
+
}
|
|
2798
3037
|
logLine += "\n";
|
|
2799
3038
|
appendFileSync2(this.logPath, logLine);
|
|
2800
3039
|
} catch (e) {
|
|
@@ -2832,10 +3071,7 @@ function debugLog(category, message, data) {
|
|
|
2832
3071
|
}
|
|
2833
3072
|
|
|
2834
3073
|
// src/engine/tools/web-browser.ts
|
|
2835
|
-
import {
|
|
2836
|
-
import { writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
2837
|
-
import { join as join3, dirname } from "path";
|
|
2838
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
3074
|
+
import { join as join5, tmpdir as tmpdir3 } from "path";
|
|
2839
3075
|
|
|
2840
3076
|
// src/shared/constants/browser/config.ts
|
|
2841
3077
|
var BROWSER_CONFIG = {
|
|
@@ -2883,11 +3119,14 @@ var PLAYWRIGHT_SCRIPT = {
|
|
|
2883
3119
|
SPAWN_TIMEOUT_BUFFER_MS: 1e4
|
|
2884
3120
|
};
|
|
2885
3121
|
|
|
2886
|
-
// src/engine/tools/web-browser.ts
|
|
3122
|
+
// src/engine/tools/web-browser-setup.ts
|
|
3123
|
+
import { spawn as spawn3 } from "child_process";
|
|
3124
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3125
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
2887
3126
|
function getPlaywrightPath() {
|
|
2888
3127
|
try {
|
|
2889
3128
|
const mainPath = __require.resolve("playwright");
|
|
2890
|
-
return
|
|
3129
|
+
return dirname2(mainPath);
|
|
2891
3130
|
} catch {
|
|
2892
3131
|
}
|
|
2893
3132
|
const dockerPath = "/app/node_modules/playwright";
|
|
@@ -2899,16 +3138,6 @@ function getPlaywrightPath() {
|
|
|
2899
3138
|
}
|
|
2900
3139
|
return join3(process.cwd(), "node_modules", "playwright");
|
|
2901
3140
|
}
|
|
2902
|
-
var DEFAULT_OPTIONS = {
|
|
2903
|
-
timeout: BROWSER_CONFIG.DEFAULT_TIMEOUT,
|
|
2904
|
-
userAgent: BROWSER_CONFIG.DEFAULT_USER_AGENT,
|
|
2905
|
-
viewport: BROWSER_CONFIG.VIEWPORT,
|
|
2906
|
-
waitAfterLoad: BROWSER_CONFIG.DEFAULT_WAIT_AFTER_LOAD,
|
|
2907
|
-
screenshot: false,
|
|
2908
|
-
extractContent: true,
|
|
2909
|
-
extractLinks: true,
|
|
2910
|
-
extractForms: true
|
|
2911
|
-
};
|
|
2912
3141
|
async function checkPlaywright() {
|
|
2913
3142
|
try {
|
|
2914
3143
|
const result2 = await new Promise((resolve) => {
|
|
@@ -2955,53 +3184,19 @@ async function installPlaywright() {
|
|
|
2955
3184
|
});
|
|
2956
3185
|
});
|
|
2957
3186
|
}
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
if (!installResult.success) {
|
|
2964
|
-
return {
|
|
2965
|
-
success: false,
|
|
2966
|
-
output: "",
|
|
2967
|
-
error: `Playwright not available and auto-install failed: ${installResult.output}`
|
|
2968
|
-
};
|
|
2969
|
-
}
|
|
2970
|
-
}
|
|
2971
|
-
const screenshotPath = opts.screenshot ? join3(join3(tmpdir2(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
|
|
2972
|
-
const script = buildBrowseScript(url, opts, screenshotPath);
|
|
2973
|
-
const result2 = await runPlaywrightScript(script, opts.timeout, "browse");
|
|
2974
|
-
if (!result2.success) {
|
|
2975
|
-
return {
|
|
2976
|
-
success: false,
|
|
2977
|
-
output: result2.output,
|
|
2978
|
-
error: result2.error
|
|
2979
|
-
};
|
|
2980
|
-
}
|
|
2981
|
-
if (result2.parsedData) {
|
|
2982
|
-
return {
|
|
2983
|
-
success: true,
|
|
2984
|
-
output: formatBrowserOutput(result2.parsedData, opts),
|
|
2985
|
-
screenshots: screenshotPath ? [screenshotPath] : void 0,
|
|
2986
|
-
extractedData: result2.parsedData
|
|
2987
|
-
};
|
|
2988
|
-
}
|
|
2989
|
-
return {
|
|
2990
|
-
success: true,
|
|
2991
|
-
output: result2.output || "Navigation completed",
|
|
2992
|
-
screenshots: screenshotPath ? [screenshotPath] : void 0
|
|
2993
|
-
};
|
|
2994
|
-
}
|
|
3187
|
+
|
|
3188
|
+
// src/engine/tools/web-browser-script.ts
|
|
3189
|
+
import { spawn as spawn4 } from "child_process";
|
|
3190
|
+
import { writeFileSync as writeFileSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
3191
|
+
import { join as join4, tmpdir as tmpdir2 } from "path";
|
|
2995
3192
|
function safeJsString(str) {
|
|
2996
3193
|
return JSON.stringify(str);
|
|
2997
3194
|
}
|
|
2998
3195
|
function runPlaywrightScript(script, timeout, scriptPrefix) {
|
|
2999
3196
|
return new Promise((resolve) => {
|
|
3000
|
-
const tempDir =
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
}
|
|
3004
|
-
const scriptPath = join3(tempDir, `${scriptPrefix}-${Date.now()}${PLAYWRIGHT_SCRIPT.EXTENSION}`);
|
|
3197
|
+
const tempDir = join4(tmpdir2(), BROWSER_PATHS.TEMP_DIR_NAME);
|
|
3198
|
+
ensureDirExists(tempDir);
|
|
3199
|
+
const scriptPath = join4(tempDir, `${scriptPrefix}-${Date.now()}${PLAYWRIGHT_SCRIPT.EXTENSION}`);
|
|
3005
3200
|
try {
|
|
3006
3201
|
writeFileSync4(scriptPath, script, "utf-8");
|
|
3007
3202
|
} catch (err) {
|
|
@@ -3014,10 +3209,10 @@ function runPlaywrightScript(script, timeout, scriptPrefix) {
|
|
|
3014
3209
|
}
|
|
3015
3210
|
const nodePathDirs = [
|
|
3016
3211
|
"/app/node_modules",
|
|
3017
|
-
|
|
3212
|
+
join4(process.cwd(), "node_modules"),
|
|
3018
3213
|
process.env.NODE_PATH || ""
|
|
3019
3214
|
].filter(Boolean).join(":");
|
|
3020
|
-
const child =
|
|
3215
|
+
const child = spawn4(PLAYWRIGHT_CMD.NODE, [scriptPath], {
|
|
3021
3216
|
timeout: timeout + PLAYWRIGHT_SCRIPT.SPAWN_TIMEOUT_BUFFER_MS,
|
|
3022
3217
|
env: { ...process.env, NODE_PATH: nodePathDirs }
|
|
3023
3218
|
});
|
|
@@ -3192,15 +3387,66 @@ function formatBrowserOutput(data, options) {
|
|
|
3192
3387
|
lines.push("");
|
|
3193
3388
|
});
|
|
3194
3389
|
}
|
|
3195
|
-
return lines.join("\n");
|
|
3390
|
+
return lines.join("\n");
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
// src/engine/tools/web-browser-types.ts
|
|
3394
|
+
var DEFAULT_BROWSER_OPTIONS = {
|
|
3395
|
+
timeout: BROWSER_CONFIG.DEFAULT_TIMEOUT,
|
|
3396
|
+
userAgent: BROWSER_CONFIG.DEFAULT_USER_AGENT,
|
|
3397
|
+
viewport: BROWSER_CONFIG.VIEWPORT,
|
|
3398
|
+
waitAfterLoad: BROWSER_CONFIG.DEFAULT_WAIT_AFTER_LOAD,
|
|
3399
|
+
screenshot: false,
|
|
3400
|
+
extractContent: true,
|
|
3401
|
+
extractLinks: true,
|
|
3402
|
+
extractForms: true
|
|
3403
|
+
};
|
|
3404
|
+
|
|
3405
|
+
// src/engine/tools/web-browser.ts
|
|
3406
|
+
async function browseUrl(url, options = {}) {
|
|
3407
|
+
const browserOptions = { ...DEFAULT_BROWSER_OPTIONS, ...options };
|
|
3408
|
+
const { installed, browserInstalled } = await checkPlaywright();
|
|
3409
|
+
if (!installed || !browserInstalled) {
|
|
3410
|
+
const installResult = await installPlaywright();
|
|
3411
|
+
if (!installResult.success) {
|
|
3412
|
+
return {
|
|
3413
|
+
success: false,
|
|
3414
|
+
output: "",
|
|
3415
|
+
error: `Playwright not available and auto-install failed: ${installResult.output}`
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
const screenshotPath = browserOptions.screenshot ? join5(join5(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
|
|
3420
|
+
const script = buildBrowseScript(url, browserOptions, screenshotPath);
|
|
3421
|
+
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "browse");
|
|
3422
|
+
if (!result2.success) {
|
|
3423
|
+
return {
|
|
3424
|
+
success: false,
|
|
3425
|
+
output: result2.output,
|
|
3426
|
+
error: result2.error
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
if (result2.parsedData) {
|
|
3430
|
+
return {
|
|
3431
|
+
success: true,
|
|
3432
|
+
output: formatBrowserOutput(result2.parsedData, browserOptions),
|
|
3433
|
+
screenshots: screenshotPath ? [screenshotPath] : void 0,
|
|
3434
|
+
extractedData: result2.parsedData
|
|
3435
|
+
};
|
|
3436
|
+
}
|
|
3437
|
+
return {
|
|
3438
|
+
success: true,
|
|
3439
|
+
output: result2.output || "Navigation completed",
|
|
3440
|
+
screenshots: screenshotPath ? [screenshotPath] : void 0
|
|
3441
|
+
};
|
|
3196
3442
|
}
|
|
3197
3443
|
async function fillAndSubmitForm(url, formData, options = {}) {
|
|
3198
|
-
const
|
|
3444
|
+
const browserOptions = { ...DEFAULT_BROWSER_OPTIONS, ...options };
|
|
3199
3445
|
const safeUrl = safeJsString(url);
|
|
3200
3446
|
const safeFormData = JSON.stringify(formData);
|
|
3201
3447
|
const playwrightPath = getPlaywrightPath();
|
|
3202
3448
|
const safePlaywrightPath = safeJsString(playwrightPath);
|
|
3203
|
-
const headlessMode =
|
|
3449
|
+
const headlessMode = process.env.HEADLESS !== "false";
|
|
3204
3450
|
const script = `
|
|
3205
3451
|
const { chromium } = require(${safePlaywrightPath});
|
|
3206
3452
|
|
|
@@ -3209,7 +3455,7 @@ const { chromium } = require(${safePlaywrightPath});
|
|
|
3209
3455
|
const page = await browser.newPage();
|
|
3210
3456
|
|
|
3211
3457
|
try {
|
|
3212
|
-
await page.goto(${safeUrl}, { waitUntil: 'networkidle', timeout: ${
|
|
3458
|
+
await page.goto(${safeUrl}, { waitUntil: 'networkidle', timeout: ${browserOptions.timeout} });
|
|
3213
3459
|
|
|
3214
3460
|
// Fill form fields
|
|
3215
3461
|
const formData = ${safeFormData};
|
|
@@ -3241,7 +3487,7 @@ const { chromium } = require(${safePlaywrightPath});
|
|
|
3241
3487
|
}
|
|
3242
3488
|
})();
|
|
3243
3489
|
`;
|
|
3244
|
-
const result2 = await runPlaywrightScript(script,
|
|
3490
|
+
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "form");
|
|
3245
3491
|
if (!result2.success) {
|
|
3246
3492
|
return {
|
|
3247
3493
|
success: false,
|
|
@@ -3764,6 +4010,260 @@ Use 'get_owasp_knowledge' with edition="2023" or similar to see specific details
|
|
|
3764
4010
|
`.trim();
|
|
3765
4011
|
}
|
|
3766
4012
|
|
|
4013
|
+
// src/engine/tools/pentest-intel-tools.ts
|
|
4014
|
+
var createIntelTools = (_state) => [
|
|
4015
|
+
{
|
|
4016
|
+
name: TOOL_NAMES.PARSE_NMAP,
|
|
4017
|
+
description: "Parse nmap XML output to structured JSON",
|
|
4018
|
+
parameters: { path: { type: "string", description: "Path to nmap XML" } },
|
|
4019
|
+
required: ["path"],
|
|
4020
|
+
execute: async (p) => parseNmap(p.path)
|
|
4021
|
+
},
|
|
4022
|
+
{
|
|
4023
|
+
name: TOOL_NAMES.SEARCH_CVE,
|
|
4024
|
+
description: "Search CVE and Exploit-DB for vulnerabilities",
|
|
4025
|
+
parameters: {
|
|
4026
|
+
service: { type: "string", description: "Service name" },
|
|
4027
|
+
version: { type: "string", description: "Version number" }
|
|
4028
|
+
},
|
|
4029
|
+
required: ["service"],
|
|
4030
|
+
execute: async (p) => searchCVE(p.service, p.version)
|
|
4031
|
+
},
|
|
4032
|
+
{
|
|
4033
|
+
name: TOOL_NAMES.WEB_SEARCH,
|
|
4034
|
+
description: `Search the web for information. Use this to:
|
|
4035
|
+
- Find CVE details and exploit code
|
|
4036
|
+
- Research security advisories
|
|
4037
|
+
- Look up OWASP vulnerabilities
|
|
4038
|
+
- Find documentation for tools and techniques
|
|
4039
|
+
- Search for latest security news and exploits
|
|
4040
|
+
- Research unknown services, parameters, or errors
|
|
4041
|
+
|
|
4042
|
+
IMPORTANT: When you encounter an error or unknown behavior, use this tool to research it.
|
|
4043
|
+
Returns search results with links and summaries.`,
|
|
4044
|
+
parameters: {
|
|
4045
|
+
query: { type: "string", description: "Search query" },
|
|
4046
|
+
use_browser: {
|
|
4047
|
+
type: "boolean",
|
|
4048
|
+
description: "Use headless browser for JavaScript-heavy pages (slower but more complete)"
|
|
4049
|
+
}
|
|
4050
|
+
},
|
|
4051
|
+
required: ["query"],
|
|
4052
|
+
execute: async (p) => {
|
|
4053
|
+
const query = p.query;
|
|
4054
|
+
const useBrowser = p.use_browser;
|
|
4055
|
+
if (useBrowser) {
|
|
4056
|
+
const result2 = await webSearchWithBrowser(query, "google");
|
|
4057
|
+
return {
|
|
4058
|
+
success: result2.success,
|
|
4059
|
+
output: result2.output,
|
|
4060
|
+
error: result2.error
|
|
4061
|
+
};
|
|
4062
|
+
}
|
|
4063
|
+
return webSearch(query);
|
|
4064
|
+
}
|
|
4065
|
+
},
|
|
4066
|
+
{
|
|
4067
|
+
name: TOOL_NAMES.BROWSE_URL,
|
|
4068
|
+
description: `Navigate to a URL using a headless browser (Playwright).
|
|
4069
|
+
Use this for:
|
|
4070
|
+
- Browsing JavaScript-heavy websites
|
|
4071
|
+
- Extracting links, forms, and content
|
|
4072
|
+
- Viewing security advisories
|
|
4073
|
+
- Accessing documentation pages
|
|
4074
|
+
- Analyzing web application structure
|
|
4075
|
+
- Discovering web attack surface (forms, inputs, API endpoints)
|
|
4076
|
+
|
|
4077
|
+
Can extract forms and inputs for security testing.`,
|
|
4078
|
+
parameters: {
|
|
4079
|
+
url: { type: "string", description: "URL to browse" },
|
|
4080
|
+
extract_forms: {
|
|
4081
|
+
type: "boolean",
|
|
4082
|
+
description: "Extract form information (inputs, actions, methods)"
|
|
4083
|
+
},
|
|
4084
|
+
extract_links: {
|
|
4085
|
+
type: "boolean",
|
|
4086
|
+
description: "Extract all links from the page"
|
|
4087
|
+
},
|
|
4088
|
+
screenshot: {
|
|
4089
|
+
type: "boolean",
|
|
4090
|
+
description: "Take a screenshot of the page"
|
|
4091
|
+
},
|
|
4092
|
+
extra_headers: {
|
|
4093
|
+
type: "object",
|
|
4094
|
+
description: 'Custom HTTP headers (e.g., {"X-Forwarded-For": "127.0.0.1"})',
|
|
4095
|
+
additionalProperties: { type: "string" }
|
|
4096
|
+
}
|
|
4097
|
+
},
|
|
4098
|
+
required: ["url"],
|
|
4099
|
+
execute: async (p) => {
|
|
4100
|
+
const result2 = await browseUrl(p.url, {
|
|
4101
|
+
extractForms: p.extract_forms,
|
|
4102
|
+
extractLinks: p.extract_links,
|
|
4103
|
+
screenshot: p.screenshot,
|
|
4104
|
+
extraHeaders: p.extra_headers
|
|
4105
|
+
});
|
|
4106
|
+
return {
|
|
4107
|
+
success: result2.success,
|
|
4108
|
+
output: result2.output,
|
|
4109
|
+
error: result2.error
|
|
4110
|
+
};
|
|
4111
|
+
}
|
|
4112
|
+
},
|
|
4113
|
+
{
|
|
4114
|
+
name: TOOL_NAMES.FILL_FORM,
|
|
4115
|
+
description: `Fill and submit a web form. Use this for:
|
|
4116
|
+
- Testing form-based authentication
|
|
4117
|
+
- Submitting search forms
|
|
4118
|
+
- Testing for form injection vulnerabilities
|
|
4119
|
+
- Automated form interaction`,
|
|
4120
|
+
parameters: {
|
|
4121
|
+
url: { type: "string", description: "URL containing the form" },
|
|
4122
|
+
fields: {
|
|
4123
|
+
type: "object",
|
|
4124
|
+
description: "Form field names and values to fill",
|
|
4125
|
+
additionalProperties: { type: "string" }
|
|
4126
|
+
}
|
|
4127
|
+
},
|
|
4128
|
+
required: ["url", "fields"],
|
|
4129
|
+
execute: async (p) => {
|
|
4130
|
+
const result2 = await fillAndSubmitForm(p.url, p.fields);
|
|
4131
|
+
return {
|
|
4132
|
+
success: result2.success,
|
|
4133
|
+
output: result2.output,
|
|
4134
|
+
error: result2.error
|
|
4135
|
+
};
|
|
4136
|
+
}
|
|
4137
|
+
},
|
|
4138
|
+
{
|
|
4139
|
+
name: TOOL_NAMES.GET_OWASP_KNOWLEDGE,
|
|
4140
|
+
description: `Get OWASP Top 10 vulnerability knowledge (2017, 2021, 2025 editions).
|
|
4141
|
+
Returns structured information about:
|
|
4142
|
+
- OWASP Top 10 category details (detection methods, test payloads, tools)
|
|
4143
|
+
- Common vulnerability patterns
|
|
4144
|
+
- Attack methodology per category
|
|
4145
|
+
- Recommended tools for each category
|
|
4146
|
+
|
|
4147
|
+
Available editions: "2025" (latest), "2021", "2017", "all"
|
|
4148
|
+
When attacking web services, ALWAYS start with get_web_attack_surface first, then use this.`,
|
|
4149
|
+
parameters: {
|
|
4150
|
+
edition: {
|
|
4151
|
+
type: "string",
|
|
4152
|
+
description: 'OWASP edition or year: "2017" through "2025", or "all"',
|
|
4153
|
+
enum: ["2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025", "all"]
|
|
4154
|
+
},
|
|
4155
|
+
category: {
|
|
4156
|
+
type: "string",
|
|
4157
|
+
description: 'Specific category (e.g., "A01", "API1", "LLM01", "K01").'
|
|
4158
|
+
}
|
|
4159
|
+
},
|
|
4160
|
+
execute: async (p) => {
|
|
4161
|
+
const edition = p.edition || "all";
|
|
4162
|
+
const category = p.category;
|
|
4163
|
+
if (category) {
|
|
4164
|
+
const results = [];
|
|
4165
|
+
for (const [year, data2] of Object.entries(OWASP_FULL_HISTORY)) {
|
|
4166
|
+
const catData = data2[category];
|
|
4167
|
+
if (catData) {
|
|
4168
|
+
results.push(`## OWASP ${year} ${category}
|
|
4169
|
+
${JSON.stringify(catData, null, 2)}`);
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
if (results.length > 0) {
|
|
4173
|
+
return {
|
|
4174
|
+
success: true,
|
|
4175
|
+
output: `# OWASP Category: ${category}
|
|
4176
|
+
|
|
4177
|
+
${results.join("\n\n")}`
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
return {
|
|
4181
|
+
success: false,
|
|
4182
|
+
output: `Category ${category} not found in any edition.`
|
|
4183
|
+
};
|
|
4184
|
+
}
|
|
4185
|
+
if (edition === "all") {
|
|
4186
|
+
return {
|
|
4187
|
+
success: true,
|
|
4188
|
+
output: getOWASPSummary()
|
|
4189
|
+
};
|
|
4190
|
+
}
|
|
4191
|
+
const data = OWASP_FULL_HISTORY[edition];
|
|
4192
|
+
if (!data) {
|
|
4193
|
+
return { success: false, output: `Year ${edition} not found in database. Reference 2017-2025.` };
|
|
4194
|
+
}
|
|
4195
|
+
return {
|
|
4196
|
+
success: true,
|
|
4197
|
+
output: getOWASPForYear(edition)
|
|
4198
|
+
};
|
|
4199
|
+
}
|
|
4200
|
+
},
|
|
4201
|
+
{
|
|
4202
|
+
name: TOOL_NAMES.GET_WEB_ATTACK_SURFACE,
|
|
4203
|
+
description: `Get the web attack surface discovery protocol.
|
|
4204
|
+
ALWAYS call this FIRST when the target is a web service (HTTP/HTTPS).
|
|
4205
|
+
Returns a step-by-step guide for:
|
|
4206
|
+
- Fingerprinting the web server and technology stack
|
|
4207
|
+
- Discovering directories, files, and API endpoints
|
|
4208
|
+
- Using Playwright headless browser for deep inspection
|
|
4209
|
+
- Systematic OWASP 2025 testing methodology
|
|
4210
|
+
- When and how to use web_search for unknown things`,
|
|
4211
|
+
parameters: {},
|
|
4212
|
+
execute: async () => ({
|
|
4213
|
+
success: true,
|
|
4214
|
+
output: getWebAttackSurface()
|
|
4215
|
+
})
|
|
4216
|
+
},
|
|
4217
|
+
{
|
|
4218
|
+
name: TOOL_NAMES.GET_CVE_INFO,
|
|
4219
|
+
description: `Get information about known CVEs. Use this to:
|
|
4220
|
+
- Look up vulnerability details
|
|
4221
|
+
- Check if a service version has known vulnerabilities
|
|
4222
|
+
- Get exploit information
|
|
4223
|
+
|
|
4224
|
+
Includes critical CVEs like Log4Shell, Spring4Shell, EternalBlue, XZ Backdoor, etc.
|
|
4225
|
+
If CVE not found locally, use web_search to find it online.`,
|
|
4226
|
+
parameters: {
|
|
4227
|
+
cve_id: {
|
|
4228
|
+
type: "string",
|
|
4229
|
+
description: 'CVE identifier (e.g., "CVE-2021-44228") or "list" to see all known CVEs'
|
|
4230
|
+
}
|
|
4231
|
+
},
|
|
4232
|
+
required: ["cve_id"],
|
|
4233
|
+
execute: async (p) => {
|
|
4234
|
+
const cveId = p.cve_id;
|
|
4235
|
+
if (cveId.toLowerCase() === "list") {
|
|
4236
|
+
const list = Object.entries(CVE_DATABASE).map(([id, info]) => `- ${id}: ${info.name} (${info.severity}) - ${info.affected}`).join("\n");
|
|
4237
|
+
return {
|
|
4238
|
+
success: true,
|
|
4239
|
+
output: `# Known CVEs in Database
|
|
4240
|
+
|
|
4241
|
+
${list}
|
|
4242
|
+
|
|
4243
|
+
Use the specific CVE ID to get more details.
|
|
4244
|
+
For CVEs not in this list, use web_search to find them.`
|
|
4245
|
+
};
|
|
4246
|
+
}
|
|
4247
|
+
const cve = CVE_DATABASE[cveId.toUpperCase()];
|
|
4248
|
+
if (cve) {
|
|
4249
|
+
return {
|
|
4250
|
+
success: true,
|
|
4251
|
+
output: `# ${cveId}
|
|
4252
|
+
|
|
4253
|
+
**Name:** ${cve.name}
|
|
4254
|
+
**Severity:** ${cve.severity}
|
|
4255
|
+
**Affected:** ${cve.affected}
|
|
4256
|
+
**Description:** ${cve.description}`
|
|
4257
|
+
};
|
|
4258
|
+
}
|
|
4259
|
+
return {
|
|
4260
|
+
success: true,
|
|
4261
|
+
output: `CVE ${cveId} not in local database. Use web_search({ query: "${cveId} exploit POC" }) to find more information online.`
|
|
4262
|
+
};
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
];
|
|
4266
|
+
|
|
3767
4267
|
// src/shared/utils/payload-mutator.ts
|
|
3768
4268
|
function urlEncode(s) {
|
|
3769
4269
|
return [...s].map((c) => {
|
|
@@ -4058,464 +4558,47 @@ function getContextRecommendations(context, variantCount) {
|
|
|
4058
4558
|
recs.push("Try: echo PAYLOAD_BASE64 | base64 -d | sh");
|
|
4059
4559
|
recs.push("Variable concatenation: a=c;b=at;$a$b /etc/passwd");
|
|
4060
4560
|
break;
|
|
4061
|
-
}
|
|
4062
|
-
recs.push("If all variants fail: web_search for latest bypass techniques for this specific filter type");
|
|
4063
|
-
return recs;
|
|
4064
|
-
}
|
|
4065
|
-
|
|
4066
|
-
// src/engine/tools/pentest.ts
|
|
4067
|
-
var createPentestTools = (state) => [
|
|
4068
|
-
{
|
|
4069
|
-
name: TOOL_NAMES.UPDATE_MISSION,
|
|
4070
|
-
description: `Update the mission summary and checklist.
|
|
4071
|
-
Use this for strategic planning and maintaining long-term context during complex engagements.
|
|
4072
|
-
The mission summary acts as your "long-term memory" \u2014 keep it updated with critical findings and overall progress.
|
|
4073
|
-
The checklist tracks granular steps (e.g., "[ ] Crack hashes", "[x] Recon 10.10.10.5").
|
|
4074
|
-
Mandatory when:
|
|
4075
|
-
- Big goals change
|
|
4076
|
-
- You encounter a major obstacle (firewall, port conflict)
|
|
4077
|
-
- You gain a significant new access point (reverse shell)
|
|
4078
|
-
- You need to summarize findings to prevent context overflow`,
|
|
4079
|
-
parameters: {
|
|
4080
|
-
summary: { type: "string", description: "Updated mission summary (concise overview)" },
|
|
4081
|
-
checklist_updates: {
|
|
4082
|
-
type: "array",
|
|
4083
|
-
items: {
|
|
4084
|
-
type: "object",
|
|
4085
|
-
properties: {
|
|
4086
|
-
id: { type: "string", description: "Item ID (required for update/remove)" },
|
|
4087
|
-
text: { type: "string", description: "Updated text" },
|
|
4088
|
-
completed: { type: "boolean", description: "Completion status" },
|
|
4089
|
-
remove: { type: "boolean", description: "Remove item if true" }
|
|
4090
|
-
},
|
|
4091
|
-
required: ["id"]
|
|
4092
|
-
},
|
|
4093
|
-
description: "Updates to existing checklist items"
|
|
4094
|
-
},
|
|
4095
|
-
add_items: {
|
|
4096
|
-
type: "array",
|
|
4097
|
-
items: { type: "string" },
|
|
4098
|
-
description: "New checklist items to add"
|
|
4099
|
-
}
|
|
4100
|
-
},
|
|
4101
|
-
execute: async (p) => {
|
|
4102
|
-
if (p.summary) state.setMissionSummary(p.summary);
|
|
4103
|
-
if (p.checklist_updates) state.updateMissionChecklist(p.checklist_updates);
|
|
4104
|
-
if (p.add_items) state.addMissionChecklistItems(p.add_items);
|
|
4105
|
-
return {
|
|
4106
|
-
success: true,
|
|
4107
|
-
output: `Mission updated.
|
|
4108
|
-
Summary: ${state.getMissionSummary()}
|
|
4109
|
-
Checklist Items: ${state.getMissionChecklist().length}`
|
|
4110
|
-
};
|
|
4111
|
-
}
|
|
4112
|
-
},
|
|
4113
|
-
{
|
|
4114
|
-
name: TOOL_NAMES.HASH_CRACK,
|
|
4115
|
-
description: `Crack password hashes using hashcat or john.
|
|
4116
|
-
Runs as a background process by default. Use bg_process status to check progress.
|
|
4117
|
-
Common wordlists are automatically searched in /usr/share/wordlists (rockyou.txt, etc).`,
|
|
4118
|
-
parameters: {
|
|
4119
|
-
hashes: { type: "string", description: "Hash(es) to crack (raw or file path)" },
|
|
4120
|
-
format: { type: "string", description: "Hash format (e.g., md5, sha1, nt, mscash2). Omit for auto-detect." },
|
|
4121
|
-
wordlist: { type: "string", description: "Wordlist name or path (default: rockyou)" },
|
|
4122
|
-
background: { type: "boolean", description: "Run in background. Default: true" }
|
|
4123
|
-
},
|
|
4124
|
-
required: ["hashes"],
|
|
4125
|
-
execute: async (p) => {
|
|
4126
|
-
const hashes = p.hashes;
|
|
4127
|
-
const format = p.format;
|
|
4128
|
-
const wordlist = p.wordlist || "rockyou";
|
|
4129
|
-
const background = p.background !== false;
|
|
4130
|
-
let wordlistPath = wordlist;
|
|
4131
|
-
if (wordlist === "rockyou") wordlistPath = WORDLISTS.ROCKYOU;
|
|
4132
|
-
const cmd = `hashcat -m ${format || HASHCAT_MODES.MD5} "${hashes}" "${wordlistPath}" --force`;
|
|
4133
|
-
if (background) {
|
|
4134
|
-
const proc = startBackgroundProcess(cmd, {
|
|
4135
|
-
description: `Cracking hashes: ${hashes.slice(0, 20)}...`,
|
|
4136
|
-
purpose: `Attempting to crack ${format || "unknown"} hashes using ${wordlist}`
|
|
4137
|
-
});
|
|
4138
|
-
return {
|
|
4139
|
-
success: true,
|
|
4140
|
-
output: `Hash cracking started in background (ID: ${proc.id}).
|
|
4141
|
-
Command: ${cmd}
|
|
4142
|
-
Check status with: bg_process({ action: "status", process_id: "${proc.id}" })`
|
|
4143
|
-
};
|
|
4144
|
-
} else {
|
|
4145
|
-
return runCommand(cmd);
|
|
4146
|
-
}
|
|
4147
|
-
}
|
|
4148
|
-
},
|
|
4149
|
-
{
|
|
4150
|
-
name: TOOL_NAMES.PARSE_NMAP,
|
|
4151
|
-
description: "Parse nmap XML output to structured JSON",
|
|
4152
|
-
parameters: { path: { type: "string", description: "Path to nmap XML" } },
|
|
4153
|
-
required: ["path"],
|
|
4154
|
-
execute: async (p) => parseNmap(p.path)
|
|
4155
|
-
},
|
|
4156
|
-
{
|
|
4157
|
-
name: TOOL_NAMES.SEARCH_CVE,
|
|
4158
|
-
description: "Search CVE and Exploit-DB for vulnerabilities",
|
|
4159
|
-
parameters: {
|
|
4160
|
-
service: { type: "string", description: "Service name" },
|
|
4161
|
-
version: { type: "string", description: "Version number" }
|
|
4162
|
-
},
|
|
4163
|
-
required: ["service"],
|
|
4164
|
-
execute: async (p) => searchCVE(p.service, p.version)
|
|
4165
|
-
},
|
|
4166
|
-
{
|
|
4167
|
-
name: TOOL_NAMES.WEB_SEARCH,
|
|
4168
|
-
description: `Search the web for information. Use this to:
|
|
4169
|
-
- Find CVE details and exploit code
|
|
4170
|
-
- Research security advisories
|
|
4171
|
-
- Look up OWASP vulnerabilities
|
|
4172
|
-
- Find documentation for tools and techniques
|
|
4173
|
-
- Search for latest security news and exploits
|
|
4174
|
-
- Research unknown services, parameters, or errors
|
|
4175
|
-
|
|
4176
|
-
IMPORTANT: When you encounter an error or unknown behavior, use this tool to research it.
|
|
4177
|
-
Returns search results with links and summaries.`,
|
|
4178
|
-
parameters: {
|
|
4179
|
-
query: { type: "string", description: "Search query" },
|
|
4180
|
-
use_browser: {
|
|
4181
|
-
type: "boolean",
|
|
4182
|
-
description: "Use headless browser for JavaScript-heavy pages (slower but more complete)"
|
|
4183
|
-
}
|
|
4184
|
-
},
|
|
4185
|
-
required: ["query"],
|
|
4186
|
-
execute: async (p) => {
|
|
4187
|
-
const query = p.query;
|
|
4188
|
-
const useBrowser = p.use_browser;
|
|
4189
|
-
if (useBrowser) {
|
|
4190
|
-
const result2 = await webSearchWithBrowser(query, "google");
|
|
4191
|
-
return {
|
|
4192
|
-
success: result2.success,
|
|
4193
|
-
output: result2.output,
|
|
4194
|
-
error: result2.error
|
|
4195
|
-
};
|
|
4196
|
-
}
|
|
4197
|
-
return webSearch(query);
|
|
4198
|
-
}
|
|
4199
|
-
},
|
|
4200
|
-
{
|
|
4201
|
-
name: TOOL_NAMES.BROWSE_URL,
|
|
4202
|
-
description: `Navigate to a URL using a headless browser (Playwright).
|
|
4203
|
-
Use this for:
|
|
4204
|
-
- Browsing JavaScript-heavy websites
|
|
4205
|
-
- Extracting links, forms, and content
|
|
4206
|
-
- Viewing security advisories
|
|
4207
|
-
- Accessing documentation pages
|
|
4208
|
-
- Analyzing web application structure
|
|
4209
|
-
- Discovering web attack surface (forms, inputs, API endpoints)
|
|
4210
|
-
|
|
4211
|
-
Can extract forms and inputs for security testing.`,
|
|
4212
|
-
parameters: {
|
|
4213
|
-
url: { type: "string", description: "URL to browse" },
|
|
4214
|
-
extract_forms: {
|
|
4215
|
-
type: "boolean",
|
|
4216
|
-
description: "Extract form information (inputs, actions, methods)"
|
|
4217
|
-
},
|
|
4218
|
-
extract_links: {
|
|
4219
|
-
type: "boolean",
|
|
4220
|
-
description: "Extract all links from the page"
|
|
4221
|
-
},
|
|
4222
|
-
screenshot: {
|
|
4223
|
-
type: "boolean",
|
|
4224
|
-
description: "Take a screenshot of the page"
|
|
4225
|
-
},
|
|
4226
|
-
extra_headers: {
|
|
4227
|
-
type: "object",
|
|
4228
|
-
description: 'Custom HTTP headers (e.g., {"X-Forwarded-For": "127.0.0.1"})',
|
|
4229
|
-
additionalProperties: { type: "string" }
|
|
4230
|
-
}
|
|
4231
|
-
},
|
|
4232
|
-
required: ["url"],
|
|
4233
|
-
execute: async (p) => {
|
|
4234
|
-
const result2 = await browseUrl(p.url, {
|
|
4235
|
-
extractForms: p.extract_forms,
|
|
4236
|
-
extractLinks: p.extract_links,
|
|
4237
|
-
screenshot: p.screenshot,
|
|
4238
|
-
extraHeaders: p.extra_headers
|
|
4239
|
-
});
|
|
4240
|
-
return {
|
|
4241
|
-
success: result2.success,
|
|
4242
|
-
output: result2.output,
|
|
4243
|
-
error: result2.error
|
|
4244
|
-
};
|
|
4245
|
-
}
|
|
4246
|
-
},
|
|
4247
|
-
{
|
|
4248
|
-
name: TOOL_NAMES.FILL_FORM,
|
|
4249
|
-
description: `Fill and submit a web form. Use this for:
|
|
4250
|
-
- Testing form-based authentication
|
|
4251
|
-
- Submitting search forms
|
|
4252
|
-
- Testing for form injection vulnerabilities
|
|
4253
|
-
- Automated form interaction`,
|
|
4254
|
-
parameters: {
|
|
4255
|
-
url: { type: "string", description: "URL containing the form" },
|
|
4256
|
-
fields: {
|
|
4257
|
-
type: "object",
|
|
4258
|
-
description: "Form field names and values to fill",
|
|
4259
|
-
additionalProperties: { type: "string" }
|
|
4260
|
-
}
|
|
4261
|
-
},
|
|
4262
|
-
required: ["url", "fields"],
|
|
4263
|
-
execute: async (p) => {
|
|
4264
|
-
const result2 = await fillAndSubmitForm(p.url, p.fields);
|
|
4265
|
-
return {
|
|
4266
|
-
success: result2.success,
|
|
4267
|
-
output: result2.output,
|
|
4268
|
-
error: result2.error
|
|
4269
|
-
};
|
|
4270
|
-
}
|
|
4271
|
-
},
|
|
4272
|
-
{
|
|
4273
|
-
name: TOOL_NAMES.GET_OWASP_KNOWLEDGE,
|
|
4274
|
-
description: `Get OWASP Top 10 vulnerability knowledge (2017, 2021, 2025 editions).
|
|
4275
|
-
Returns structured information about:
|
|
4276
|
-
- OWASP Top 10 category details (detection methods, test payloads, tools)
|
|
4277
|
-
- Common vulnerability patterns
|
|
4278
|
-
- Attack methodology per category
|
|
4279
|
-
- Recommended tools for each category
|
|
4280
|
-
|
|
4281
|
-
Available editions: "2025" (latest), "2021", "2017", "all"
|
|
4282
|
-
When attacking web services, ALWAYS start with get_web_attack_surface first, then use this.`,
|
|
4283
|
-
parameters: {
|
|
4284
|
-
edition: {
|
|
4285
|
-
type: "string",
|
|
4286
|
-
description: 'OWASP edition or year: "2017" through "2025", or "all"',
|
|
4287
|
-
enum: ["2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025", "all"]
|
|
4288
|
-
},
|
|
4289
|
-
category: {
|
|
4290
|
-
type: "string",
|
|
4291
|
-
description: 'Specific category (e.g., "A01", "API1", "LLM01", "K01").'
|
|
4292
|
-
}
|
|
4293
|
-
},
|
|
4294
|
-
execute: async (p) => {
|
|
4295
|
-
const edition = p.edition || "all";
|
|
4296
|
-
const category = p.category;
|
|
4297
|
-
if (category) {
|
|
4298
|
-
const results = [];
|
|
4299
|
-
for (const [year, data2] of Object.entries(OWASP_FULL_HISTORY)) {
|
|
4300
|
-
const catData = data2[category];
|
|
4301
|
-
if (catData) {
|
|
4302
|
-
results.push(`## OWASP ${year} ${category}
|
|
4303
|
-
${JSON.stringify(catData, null, 2)}`);
|
|
4304
|
-
}
|
|
4305
|
-
}
|
|
4306
|
-
if (results.length > 0) {
|
|
4307
|
-
return {
|
|
4308
|
-
success: true,
|
|
4309
|
-
output: `# OWASP Category: ${category}
|
|
4561
|
+
}
|
|
4562
|
+
recs.push("If all variants fail: web_search for latest bypass techniques for this specific filter type");
|
|
4563
|
+
return recs;
|
|
4564
|
+
}
|
|
4310
4565
|
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
}
|
|
4314
|
-
return {
|
|
4315
|
-
success: false,
|
|
4316
|
-
output: `Category ${category} not found in any edition.`
|
|
4317
|
-
};
|
|
4318
|
-
}
|
|
4319
|
-
if (edition === "all") {
|
|
4320
|
-
return {
|
|
4321
|
-
success: true,
|
|
4322
|
-
output: getOWASPSummary()
|
|
4323
|
-
};
|
|
4324
|
-
}
|
|
4325
|
-
const data = OWASP_FULL_HISTORY[edition];
|
|
4326
|
-
if (!data) {
|
|
4327
|
-
return { success: false, output: `Year ${edition} not found in database. Reference 2017-2025.` };
|
|
4328
|
-
}
|
|
4329
|
-
return {
|
|
4330
|
-
success: true,
|
|
4331
|
-
output: getOWASPForYear(edition)
|
|
4332
|
-
};
|
|
4333
|
-
}
|
|
4334
|
-
},
|
|
4335
|
-
{
|
|
4336
|
-
name: TOOL_NAMES.GET_WEB_ATTACK_SURFACE,
|
|
4337
|
-
description: `Get the web attack surface discovery protocol.
|
|
4338
|
-
ALWAYS call this FIRST when the target is a web service (HTTP/HTTPS).
|
|
4339
|
-
Returns a step-by-step guide for:
|
|
4340
|
-
- Fingerprinting the web server and technology stack
|
|
4341
|
-
- Discovering directories, files, and API endpoints
|
|
4342
|
-
- Using Playwright headless browser for deep inspection
|
|
4343
|
-
- Systematic OWASP 2025 testing methodology
|
|
4344
|
-
- When and how to use web_search for unknown things`,
|
|
4345
|
-
parameters: {},
|
|
4346
|
-
execute: async () => ({
|
|
4347
|
-
success: true,
|
|
4348
|
-
output: getWebAttackSurface()
|
|
4349
|
-
})
|
|
4350
|
-
},
|
|
4566
|
+
// src/engine/tools/pentest-attack-tools.ts
|
|
4567
|
+
var createAttackTools = (_state) => [
|
|
4351
4568
|
{
|
|
4352
|
-
name: TOOL_NAMES.
|
|
4353
|
-
description: `
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
- Get exploit information
|
|
4357
|
-
|
|
4358
|
-
Includes critical CVEs like Log4Shell, Spring4Shell, EternalBlue, XZ Backdoor, etc.
|
|
4359
|
-
If CVE not found locally, use web_search to find it online.`,
|
|
4569
|
+
name: TOOL_NAMES.HASH_CRACK,
|
|
4570
|
+
description: `Crack password hashes using hashcat or john.
|
|
4571
|
+
Runs as a background process by default. Use bg_process status to check progress.
|
|
4572
|
+
Common wordlists are automatically searched in /usr/share/wordlists (rockyou.txt, etc).`,
|
|
4360
4573
|
parameters: {
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
}
|
|
4574
|
+
hashes: { type: "string", description: "Hash(es) to crack (raw or file path)" },
|
|
4575
|
+
format: { type: "string", description: "Hash format (e.g., md5, sha1, nt, mscash2). Omit for auto-detect." },
|
|
4576
|
+
wordlist: { type: "string", description: "Wordlist name or path (default: rockyou)" },
|
|
4577
|
+
background: { type: "boolean", description: "Run in background. Default: true" }
|
|
4365
4578
|
},
|
|
4366
|
-
required: ["
|
|
4579
|
+
required: ["hashes"],
|
|
4367
4580
|
execute: async (p) => {
|
|
4368
|
-
const
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
};
|
|
4380
|
-
}
|
|
4381
|
-
const cve = CVE_DATABASE[cveId.toUpperCase()];
|
|
4382
|
-
if (cve) {
|
|
4581
|
+
const hashes = p.hashes;
|
|
4582
|
+
const format = p.format;
|
|
4583
|
+
const wordlist = p.wordlist || "rockyou";
|
|
4584
|
+
const background = p.background !== false;
|
|
4585
|
+
let wordlistPath = wordlist;
|
|
4586
|
+
if (wordlist === "rockyou") wordlistPath = WORDLISTS.ROCKYOU;
|
|
4587
|
+
const cmd = `hashcat -m ${format || HASHCAT_MODES.MD5} "${hashes}" "${wordlistPath}" --force`;
|
|
4588
|
+
if (background) {
|
|
4589
|
+
const proc = startBackgroundProcess(cmd, {
|
|
4590
|
+
description: `Cracking hashes: ${hashes.slice(0, 20)}...`,
|
|
4591
|
+
purpose: `Attempting to crack ${format || "unknown"} hashes using ${wordlist}`
|
|
4592
|
+
});
|
|
4383
4593
|
return {
|
|
4384
4594
|
success: true,
|
|
4385
|
-
output:
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
**Severity:** ${cve.severity}
|
|
4389
|
-
**Affected:** ${cve.affected}
|
|
4390
|
-
**Description:** ${cve.description}`
|
|
4595
|
+
output: `Hash cracking started in background (ID: ${proc.id}).
|
|
4596
|
+
Command: ${cmd}
|
|
4597
|
+
Check status with: bg_process({ action: "status", process_id: "${proc.id}" })`
|
|
4391
4598
|
};
|
|
4599
|
+
} else {
|
|
4600
|
+
return runCommand(cmd);
|
|
4392
4601
|
}
|
|
4393
|
-
return {
|
|
4394
|
-
success: true,
|
|
4395
|
-
output: `CVE ${cveId} not in local database. Use web_search({ query: "${cveId} exploit POC" }) to find more information online.`
|
|
4396
|
-
};
|
|
4397
|
-
}
|
|
4398
|
-
},
|
|
4399
|
-
{
|
|
4400
|
-
name: TOOL_NAMES.ADD_TARGET,
|
|
4401
|
-
description: `Register a new target for the engagement.
|
|
4402
|
-
Use this when you discover new hosts during recon, pivoting, or post-exploitation.
|
|
4403
|
-
The target will be tracked in SharedState and available for all agents.`,
|
|
4404
|
-
parameters: {
|
|
4405
|
-
ip: { type: "string", description: "Target IP address" },
|
|
4406
|
-
hostname: { type: "string", description: "Target hostname (optional)" },
|
|
4407
|
-
ports: {
|
|
4408
|
-
type: "array",
|
|
4409
|
-
items: {
|
|
4410
|
-
type: "object",
|
|
4411
|
-
properties: {
|
|
4412
|
-
port: { type: "number", description: "Port number" },
|
|
4413
|
-
service: { type: "string", description: "Service name (e.g., http, ssh)" },
|
|
4414
|
-
version: { type: "string", description: "Service version" },
|
|
4415
|
-
state: { type: "string", description: "Port state (default: open)" }
|
|
4416
|
-
},
|
|
4417
|
-
required: ["port", "service"]
|
|
4418
|
-
},
|
|
4419
|
-
description: "Discovered ports (optional, can add later via recon)"
|
|
4420
|
-
},
|
|
4421
|
-
tags: {
|
|
4422
|
-
type: "array",
|
|
4423
|
-
items: { type: "string" },
|
|
4424
|
-
description: 'Tags for categorization (e.g., "internal", "dmz", "pivot")'
|
|
4425
|
-
}
|
|
4426
|
-
},
|
|
4427
|
-
required: ["ip"],
|
|
4428
|
-
execute: async (p) => {
|
|
4429
|
-
const ip = p.ip;
|
|
4430
|
-
const existing = state.getTarget(ip);
|
|
4431
|
-
if (existing) {
|
|
4432
|
-
const newPorts = p.ports || [];
|
|
4433
|
-
for (const np of newPorts) {
|
|
4434
|
-
const exists = existing.ports.some((ep) => ep.port === np.port);
|
|
4435
|
-
if (!exists) {
|
|
4436
|
-
existing.ports.push({
|
|
4437
|
-
port: np.port,
|
|
4438
|
-
service: np.service || "unknown",
|
|
4439
|
-
version: np.version,
|
|
4440
|
-
state: np.state || "open",
|
|
4441
|
-
notes: []
|
|
4442
|
-
});
|
|
4443
|
-
}
|
|
4444
|
-
}
|
|
4445
|
-
if (p.hostname) existing.hostname = p.hostname;
|
|
4446
|
-
if (p.tags) existing.tags = [.../* @__PURE__ */ new Set([...existing.tags, ...p.tags])];
|
|
4447
|
-
return { success: true, output: `Target ${ip} updated. Total ports: ${existing.ports.length}` };
|
|
4448
|
-
}
|
|
4449
|
-
const ports = (p.ports || []).map((port) => ({
|
|
4450
|
-
port: port.port,
|
|
4451
|
-
service: port.service || "unknown",
|
|
4452
|
-
version: port.version,
|
|
4453
|
-
state: port.state || "open",
|
|
4454
|
-
notes: []
|
|
4455
|
-
}));
|
|
4456
|
-
state.addTarget({
|
|
4457
|
-
ip,
|
|
4458
|
-
hostname: p.hostname,
|
|
4459
|
-
ports,
|
|
4460
|
-
tags: p.tags || [],
|
|
4461
|
-
firstSeen: Date.now()
|
|
4462
|
-
});
|
|
4463
|
-
return { success: true, output: `Target ${ip} added.${p.hostname ? ` Hostname: ${p.hostname}` : ""} Ports: ${ports.length}` };
|
|
4464
|
-
}
|
|
4465
|
-
},
|
|
4466
|
-
{
|
|
4467
|
-
name: TOOL_NAMES.ADD_LOOT,
|
|
4468
|
-
description: `Record captured loot (credentials, hashes, tokens, SSH keys, files, etc).
|
|
4469
|
-
Use this whenever you discover sensitive data during the engagement.
|
|
4470
|
-
Loot is tracked in SharedState and visible to all agents for credential reuse and lateral movement.
|
|
4471
|
-
Types: credential, hash, token, ssh_key, api_key, file, session, ticket, certificate`,
|
|
4472
|
-
parameters: {
|
|
4473
|
-
type: { type: "string", description: "Loot type: credential, hash, token, ssh_key, api_key, file, session, ticket, certificate" },
|
|
4474
|
-
host: { type: "string", description: "Source host where loot was found" },
|
|
4475
|
-
detail: { type: "string", description: 'Loot detail (e.g., "admin:Password123", "root:$6$...", "JWT admin token")' }
|
|
4476
|
-
},
|
|
4477
|
-
required: ["type", "host", "detail"],
|
|
4478
|
-
execute: async (p) => {
|
|
4479
|
-
const lootType = p.type;
|
|
4480
|
-
const crackableTypes = ["hash"];
|
|
4481
|
-
state.addLoot({
|
|
4482
|
-
type: lootType,
|
|
4483
|
-
host: p.host,
|
|
4484
|
-
detail: p.detail,
|
|
4485
|
-
obtainedAt: Date.now(),
|
|
4486
|
-
isCrackable: crackableTypes.includes(lootType),
|
|
4487
|
-
isCracked: false
|
|
4488
|
-
});
|
|
4489
|
-
return {
|
|
4490
|
-
success: true,
|
|
4491
|
-
output: `Loot recorded: [${lootType}] from ${p.host}
|
|
4492
|
-
Detail: ${p.detail}
|
|
4493
|
-
` + (crackableTypes.includes(lootType) ? `This is crackable. Consider: hash_crack({ hashes: "${p.detail.slice(0, 30)}..." })` : `Consider credential reuse / lateral movement with this loot.`)
|
|
4494
|
-
};
|
|
4495
|
-
}
|
|
4496
|
-
},
|
|
4497
|
-
{
|
|
4498
|
-
name: TOOL_NAMES.ADD_FINDING,
|
|
4499
|
-
description: "Add a security finding",
|
|
4500
|
-
parameters: {
|
|
4501
|
-
title: { type: "string", description: "Finding title" },
|
|
4502
|
-
severity: { type: "string", description: "Severity" },
|
|
4503
|
-
affected: { type: "array", items: { type: "string" }, description: "Affected host:port" }
|
|
4504
|
-
},
|
|
4505
|
-
required: ["title", "severity"],
|
|
4506
|
-
execute: async (p) => {
|
|
4507
|
-
state.addFinding({
|
|
4508
|
-
id: generateId(ID_RADIX, ID_LENGTH),
|
|
4509
|
-
title: p.title,
|
|
4510
|
-
severity: p.severity,
|
|
4511
|
-
affected: p.affected || [],
|
|
4512
|
-
description: p.description || "",
|
|
4513
|
-
evidence: [],
|
|
4514
|
-
isVerified: false,
|
|
4515
|
-
remediation: "",
|
|
4516
|
-
foundAt: Date.now()
|
|
4517
|
-
});
|
|
4518
|
-
return { success: true, output: `Added: ${p.title}` };
|
|
4519
4602
|
}
|
|
4520
4603
|
},
|
|
4521
4604
|
{
|
|
@@ -4568,12 +4651,6 @@ ${variantList}
|
|
|
4568
4651
|
};
|
|
4569
4652
|
}
|
|
4570
4653
|
},
|
|
4571
|
-
{
|
|
4572
|
-
name: TOOL_NAMES.GET_STATE,
|
|
4573
|
-
description: "Get current engagement state summary",
|
|
4574
|
-
parameters: {},
|
|
4575
|
-
execute: async () => ({ success: true, output: state.toPrompt() })
|
|
4576
|
-
},
|
|
4577
4654
|
{
|
|
4578
4655
|
name: TOOL_NAMES.GET_WORDLISTS,
|
|
4579
4656
|
description: `Discover available wordlists on the system by scanning actual filesystem paths.
|
|
@@ -4607,7 +4684,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4607
4684
|
},
|
|
4608
4685
|
execute: async (p) => {
|
|
4609
4686
|
const { existsSync: existsSync6, statSync, readdirSync: readdirSync2 } = await import("fs");
|
|
4610
|
-
const { join:
|
|
4687
|
+
const { join: join9 } = await import("path");
|
|
4611
4688
|
const category = p.category || "";
|
|
4612
4689
|
const search = p.search || "";
|
|
4613
4690
|
const minSize = p.min_size || 0;
|
|
@@ -4626,7 +4703,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4626
4703
|
try {
|
|
4627
4704
|
const entries = readdirSync2(dirPath, { withFileTypes: true });
|
|
4628
4705
|
for (const entry of entries) {
|
|
4629
|
-
const fullPath =
|
|
4706
|
+
const fullPath = join9(dirPath, entry.name);
|
|
4630
4707
|
if (entry.isDirectory()) {
|
|
4631
4708
|
if (entry.name.startsWith(".")) continue;
|
|
4632
4709
|
if (entry.name === "doc") continue;
|
|
@@ -4712,6 +4789,14 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
4712
4789
|
}
|
|
4713
4790
|
];
|
|
4714
4791
|
|
|
4792
|
+
// src/engine/tools/pentest.ts
|
|
4793
|
+
var createPentestTools = (state) => [
|
|
4794
|
+
...createStateTools(state),
|
|
4795
|
+
...createTargetTools(state),
|
|
4796
|
+
...createIntelTools(state),
|
|
4797
|
+
...createAttackTools(state)
|
|
4798
|
+
];
|
|
4799
|
+
|
|
4715
4800
|
// src/engine/tools/agents.ts
|
|
4716
4801
|
var globalCredentialHandler = null;
|
|
4717
4802
|
function setCredentialHandler(handler) {
|
|
@@ -4727,7 +4812,7 @@ async function requestCredential(request) {
|
|
|
4727
4812
|
try {
|
|
4728
4813
|
const value = await globalCredentialHandler(request);
|
|
4729
4814
|
if (value === null) {
|
|
4730
|
-
if (request.
|
|
4815
|
+
if (request.isOptional) {
|
|
4731
4816
|
return {
|
|
4732
4817
|
success: true,
|
|
4733
4818
|
output: `User skipped: ${request.prompt}${request.default ? ` (using default)` : ""}`,
|
|
@@ -4779,7 +4864,7 @@ Always provide context about why you need this input.`,
|
|
|
4779
4864
|
type: "string",
|
|
4780
4865
|
description: 'Additional context (e.g., "for SSH connection to 192.168.1.1")'
|
|
4781
4866
|
},
|
|
4782
|
-
|
|
4867
|
+
isOptional: {
|
|
4783
4868
|
type: "boolean",
|
|
4784
4869
|
description: "Whether this input is optional"
|
|
4785
4870
|
},
|
|
@@ -4795,7 +4880,7 @@ Always provide context about why you need this input.`,
|
|
|
4795
4880
|
type: p.input_type || INPUT_TYPES.TEXT,
|
|
4796
4881
|
prompt: p.question,
|
|
4797
4882
|
context: p.context,
|
|
4798
|
-
|
|
4883
|
+
isOptional: p.isOptional,
|
|
4799
4884
|
options: p.options
|
|
4800
4885
|
};
|
|
4801
4886
|
const result2 = await requestCredential(request);
|
|
@@ -5742,81 +5827,81 @@ var ServiceParser = class {
|
|
|
5742
5827
|
};
|
|
5743
5828
|
|
|
5744
5829
|
// src/domains/registry.ts
|
|
5745
|
-
import { join as
|
|
5830
|
+
import { join as join6, dirname as dirname3 } from "path";
|
|
5746
5831
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5747
|
-
var __dirname2 =
|
|
5832
|
+
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
5748
5833
|
var DOMAINS = {
|
|
5749
5834
|
[SERVICE_CATEGORIES.NETWORK]: {
|
|
5750
5835
|
id: SERVICE_CATEGORIES.NETWORK,
|
|
5751
5836
|
name: "Network Infrastructure",
|
|
5752
5837
|
description: "Vulnerability scanning, port mapping, and network service exploitation.",
|
|
5753
|
-
promptPath:
|
|
5838
|
+
promptPath: join6(__dirname2, "network/prompt.md")
|
|
5754
5839
|
},
|
|
5755
5840
|
[SERVICE_CATEGORIES.WEB]: {
|
|
5756
5841
|
id: SERVICE_CATEGORIES.WEB,
|
|
5757
5842
|
name: "Web Application",
|
|
5758
5843
|
description: "Web app security testing, injection attacks, and auth bypass.",
|
|
5759
|
-
promptPath:
|
|
5844
|
+
promptPath: join6(__dirname2, "web/prompt.md")
|
|
5760
5845
|
},
|
|
5761
5846
|
[SERVICE_CATEGORIES.DATABASE]: {
|
|
5762
5847
|
id: SERVICE_CATEGORIES.DATABASE,
|
|
5763
5848
|
name: "Database Security",
|
|
5764
5849
|
description: "SQL injection, database enumeration, and data extraction.",
|
|
5765
|
-
promptPath:
|
|
5850
|
+
promptPath: join6(__dirname2, "database/prompt.md")
|
|
5766
5851
|
},
|
|
5767
5852
|
[SERVICE_CATEGORIES.AD]: {
|
|
5768
5853
|
id: SERVICE_CATEGORIES.AD,
|
|
5769
5854
|
name: "Active Directory",
|
|
5770
5855
|
description: "Kerberos, LDAP, and Windows domain privilege escalation.",
|
|
5771
|
-
promptPath:
|
|
5856
|
+
promptPath: join6(__dirname2, "ad/prompt.md")
|
|
5772
5857
|
},
|
|
5773
5858
|
[SERVICE_CATEGORIES.EMAIL]: {
|
|
5774
5859
|
id: SERVICE_CATEGORIES.EMAIL,
|
|
5775
5860
|
name: "Email Services",
|
|
5776
5861
|
description: "SMTP, IMAP, POP3 security and user enumeration.",
|
|
5777
|
-
promptPath:
|
|
5862
|
+
promptPath: join6(__dirname2, "email/prompt.md")
|
|
5778
5863
|
},
|
|
5779
5864
|
[SERVICE_CATEGORIES.REMOTE_ACCESS]: {
|
|
5780
5865
|
id: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
5781
5866
|
name: "Remote Access",
|
|
5782
5867
|
description: "SSH, RDP, VNC and other remote control protocols.",
|
|
5783
|
-
promptPath:
|
|
5868
|
+
promptPath: join6(__dirname2, "remote-access/prompt.md")
|
|
5784
5869
|
},
|
|
5785
5870
|
[SERVICE_CATEGORIES.FILE_SHARING]: {
|
|
5786
5871
|
id: SERVICE_CATEGORIES.FILE_SHARING,
|
|
5787
5872
|
name: "File Sharing",
|
|
5788
5873
|
description: "SMB, NFS, FTP and shared resource security.",
|
|
5789
|
-
promptPath:
|
|
5874
|
+
promptPath: join6(__dirname2, "file-sharing/prompt.md")
|
|
5790
5875
|
},
|
|
5791
5876
|
[SERVICE_CATEGORIES.CLOUD]: {
|
|
5792
5877
|
id: SERVICE_CATEGORIES.CLOUD,
|
|
5793
5878
|
name: "Cloud Infrastructure",
|
|
5794
5879
|
description: "AWS, Azure, and GCP security and misconfiguration.",
|
|
5795
|
-
promptPath:
|
|
5880
|
+
promptPath: join6(__dirname2, "cloud/prompt.md")
|
|
5796
5881
|
},
|
|
5797
5882
|
[SERVICE_CATEGORIES.CONTAINER]: {
|
|
5798
5883
|
id: SERVICE_CATEGORIES.CONTAINER,
|
|
5799
5884
|
name: "Container Systems",
|
|
5800
5885
|
description: "Docker and Kubernetes security testing.",
|
|
5801
|
-
promptPath:
|
|
5886
|
+
promptPath: join6(__dirname2, "container/prompt.md")
|
|
5802
5887
|
},
|
|
5803
5888
|
[SERVICE_CATEGORIES.API]: {
|
|
5804
5889
|
id: SERVICE_CATEGORIES.API,
|
|
5805
5890
|
name: "API Security",
|
|
5806
5891
|
description: "REST, GraphQL, and SOAP API security testing.",
|
|
5807
|
-
promptPath:
|
|
5892
|
+
promptPath: join6(__dirname2, "api/prompt.md")
|
|
5808
5893
|
},
|
|
5809
5894
|
[SERVICE_CATEGORIES.WIRELESS]: {
|
|
5810
5895
|
id: SERVICE_CATEGORIES.WIRELESS,
|
|
5811
5896
|
name: "Wireless Networks",
|
|
5812
5897
|
description: "WiFi and Bluetooth security testing.",
|
|
5813
|
-
promptPath:
|
|
5898
|
+
promptPath: join6(__dirname2, "wireless/prompt.md")
|
|
5814
5899
|
},
|
|
5815
5900
|
[SERVICE_CATEGORIES.ICS]: {
|
|
5816
5901
|
id: SERVICE_CATEGORIES.ICS,
|
|
5817
5902
|
name: "Industrial Systems",
|
|
5818
5903
|
description: "Critical infrastructure - Modbus, DNP3, ENIP.",
|
|
5819
|
-
promptPath:
|
|
5904
|
+
promptPath: join6(__dirname2, "ics/prompt.md")
|
|
5820
5905
|
}
|
|
5821
5906
|
};
|
|
5822
5907
|
|
|
@@ -6597,9 +6682,9 @@ function logLLM(message, data) {
|
|
|
6597
6682
|
|
|
6598
6683
|
// src/engine/orchestrator/orchestrator.ts
|
|
6599
6684
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6600
|
-
import { dirname as
|
|
6685
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
6601
6686
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
6602
|
-
var __dirname3 =
|
|
6687
|
+
var __dirname3 = dirname4(__filename2);
|
|
6603
6688
|
|
|
6604
6689
|
// src/agents/core-agent.ts
|
|
6605
6690
|
var CoreAgent = class _CoreAgent {
|
|
@@ -6869,16 +6954,44 @@ Please decide how to handle this error and continue.`;
|
|
|
6869
6954
|
emitReasoningEnd(phase) {
|
|
6870
6955
|
this.events.emit({ type: EVENT_TYPES.REASONING_END, timestamp: Date.now(), data: { phase } });
|
|
6871
6956
|
}
|
|
6872
|
-
emitComplete(output, iteration, toolsExecuted, durationMs, tokens) {
|
|
6957
|
+
emitComplete(output, iteration, toolsExecuted, durationMs, tokens) {
|
|
6958
|
+
this.events.emit({
|
|
6959
|
+
type: EVENT_TYPES.COMPLETE,
|
|
6960
|
+
timestamp: Date.now(),
|
|
6961
|
+
data: {
|
|
6962
|
+
finalOutput: output,
|
|
6963
|
+
iterations: iteration + 1,
|
|
6964
|
+
toolsExecuted,
|
|
6965
|
+
durationMs,
|
|
6966
|
+
tokens
|
|
6967
|
+
}
|
|
6968
|
+
});
|
|
6969
|
+
}
|
|
6970
|
+
/** Emit tool call event for TUI tracking */
|
|
6971
|
+
emitToolCall(toolName, input) {
|
|
6972
|
+
this.events.emit({
|
|
6973
|
+
type: EVENT_TYPES.TOOL_CALL,
|
|
6974
|
+
timestamp: Date.now(),
|
|
6975
|
+
data: {
|
|
6976
|
+
toolName,
|
|
6977
|
+
input,
|
|
6978
|
+
approvalLevel: APPROVAL_LEVELS.AUTO,
|
|
6979
|
+
needsApproval: false
|
|
6980
|
+
}
|
|
6981
|
+
});
|
|
6982
|
+
}
|
|
6983
|
+
/** Emit tool result event for TUI tracking */
|
|
6984
|
+
emitToolResult(toolName, success, output, error, duration) {
|
|
6873
6985
|
this.events.emit({
|
|
6874
|
-
type: EVENT_TYPES.
|
|
6986
|
+
type: EVENT_TYPES.TOOL_RESULT,
|
|
6875
6987
|
timestamp: Date.now(),
|
|
6876
6988
|
data: {
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6989
|
+
toolName,
|
|
6990
|
+
success,
|
|
6991
|
+
output,
|
|
6992
|
+
outputSummary: output.slice(0, 200),
|
|
6993
|
+
error,
|
|
6994
|
+
duration
|
|
6882
6995
|
}
|
|
6883
6996
|
});
|
|
6884
6997
|
}
|
|
@@ -6941,16 +7054,7 @@ Please decide how to handle this error and continue.`;
|
|
|
6941
7054
|
/** Execute tool calls in parallel via Promise.allSettled */
|
|
6942
7055
|
async executeToolCallsInParallel(toolCalls, progress) {
|
|
6943
7056
|
for (const call of toolCalls) {
|
|
6944
|
-
this.
|
|
6945
|
-
type: EVENT_TYPES.TOOL_CALL,
|
|
6946
|
-
timestamp: Date.now(),
|
|
6947
|
-
data: {
|
|
6948
|
-
toolName: call.name,
|
|
6949
|
-
input: call.input,
|
|
6950
|
-
approvalLevel: APPROVAL_LEVELS.AUTO,
|
|
6951
|
-
needsApproval: false
|
|
6952
|
-
}
|
|
6953
|
-
});
|
|
7057
|
+
this.emitToolCall(call.name, call.input);
|
|
6954
7058
|
}
|
|
6955
7059
|
const promises = toolCalls.map((call) => this.executeSingleTool(call, progress));
|
|
6956
7060
|
const settled = await Promise.allSettled(promises);
|
|
@@ -6964,16 +7068,7 @@ Please decide how to handle this error and continue.`;
|
|
|
6964
7068
|
async executeToolCallsSequentially(toolCalls, progress) {
|
|
6965
7069
|
const results = [];
|
|
6966
7070
|
for (const call of toolCalls) {
|
|
6967
|
-
this.
|
|
6968
|
-
type: EVENT_TYPES.TOOL_CALL,
|
|
6969
|
-
timestamp: Date.now(),
|
|
6970
|
-
data: {
|
|
6971
|
-
toolName: call.name,
|
|
6972
|
-
input: call.input,
|
|
6973
|
-
approvalLevel: APPROVAL_LEVELS.AUTO,
|
|
6974
|
-
needsApproval: false
|
|
6975
|
-
}
|
|
6976
|
-
});
|
|
7071
|
+
this.emitToolCall(call.name, call.input);
|
|
6977
7072
|
const result2 = await this.executeSingleTool(call, progress);
|
|
6978
7073
|
results.push(result2);
|
|
6979
7074
|
}
|
|
@@ -7013,35 +7108,13 @@ Please decide how to handle this error and continue.`;
|
|
|
7013
7108
|
progress.blockedCommandPatterns.clear();
|
|
7014
7109
|
}
|
|
7015
7110
|
}
|
|
7016
|
-
this.
|
|
7017
|
-
type: EVENT_TYPES.TOOL_RESULT,
|
|
7018
|
-
timestamp: Date.now(),
|
|
7019
|
-
data: {
|
|
7020
|
-
toolName: call.name,
|
|
7021
|
-
success: result2.success,
|
|
7022
|
-
output: outputText,
|
|
7023
|
-
outputSummary: outputText.slice(0, 200),
|
|
7024
|
-
error: result2.error,
|
|
7025
|
-
duration: Date.now() - toolStartTime
|
|
7026
|
-
}
|
|
7027
|
-
});
|
|
7111
|
+
this.emitToolResult(call.name, result2.success, outputText, result2.error, Date.now() - toolStartTime);
|
|
7028
7112
|
return { toolCallId: call.id, output: outputText, error: result2.error };
|
|
7029
7113
|
} catch (error) {
|
|
7030
7114
|
const errorMsg = String(error);
|
|
7031
7115
|
const enrichedError = this.enrichToolErrorContext({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
|
|
7032
7116
|
if (progress) progress.toolErrors++;
|
|
7033
|
-
this.
|
|
7034
|
-
type: EVENT_TYPES.TOOL_RESULT,
|
|
7035
|
-
timestamp: Date.now(),
|
|
7036
|
-
data: {
|
|
7037
|
-
toolName: call.name,
|
|
7038
|
-
success: false,
|
|
7039
|
-
output: enrichedError,
|
|
7040
|
-
outputSummary: enrichedError.slice(0, 200),
|
|
7041
|
-
error: errorMsg,
|
|
7042
|
-
duration: Date.now() - toolStartTime
|
|
7043
|
-
}
|
|
7044
|
-
});
|
|
7117
|
+
this.emitToolResult(call.name, false, enrichedError, errorMsg, Date.now() - toolStartTime);
|
|
7045
7118
|
return { toolCallId: call.id, output: enrichedError, error: errorMsg };
|
|
7046
7119
|
}
|
|
7047
7120
|
}
|
|
@@ -7141,7 +7214,7 @@ Please decide how to handle this error and continue.`;
|
|
|
7141
7214
|
|
|
7142
7215
|
// src/agents/prompt-builder.ts
|
|
7143
7216
|
import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
|
|
7144
|
-
import { join as
|
|
7217
|
+
import { join as join8, dirname as dirname5 } from "path";
|
|
7145
7218
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7146
7219
|
|
|
7147
7220
|
// src/shared/constants/prompts.ts
|
|
@@ -7195,9 +7268,9 @@ var INITIAL_TASKS = {
|
|
|
7195
7268
|
};
|
|
7196
7269
|
|
|
7197
7270
|
// src/agents/prompt-builder.ts
|
|
7198
|
-
var __dirname4 =
|
|
7199
|
-
var PROMPTS_DIR =
|
|
7200
|
-
var TECHNIQUES_DIR =
|
|
7271
|
+
var __dirname4 = dirname5(fileURLToPath4(import.meta.url));
|
|
7272
|
+
var PROMPTS_DIR = join8(__dirname4, "prompts");
|
|
7273
|
+
var TECHNIQUES_DIR = join8(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
|
|
7201
7274
|
var { AGENT_FILES } = PROMPT_PATHS;
|
|
7202
7275
|
var PHASE_PROMPT_MAP = {
|
|
7203
7276
|
// Direct mappings — phase has its own prompt file
|
|
@@ -7273,7 +7346,7 @@ var PromptBuilder = class {
|
|
|
7273
7346
|
* Load a prompt file from src/agents/prompts/
|
|
7274
7347
|
*/
|
|
7275
7348
|
loadPromptFile(filename) {
|
|
7276
|
-
const path2 =
|
|
7349
|
+
const path2 = join8(PROMPTS_DIR, filename);
|
|
7277
7350
|
return existsSync5(path2) ? readFileSync3(path2, PROMPT_CONFIG.ENCODING) : "";
|
|
7278
7351
|
}
|
|
7279
7352
|
/**
|
|
@@ -7322,7 +7395,7 @@ ${content}
|
|
|
7322
7395
|
if (relevantTechniques.length === 0) return "";
|
|
7323
7396
|
const fragments = [];
|
|
7324
7397
|
for (const technique of relevantTechniques) {
|
|
7325
|
-
const filePath =
|
|
7398
|
+
const filePath = join8(TECHNIQUES_DIR, `${technique}.md`);
|
|
7326
7399
|
try {
|
|
7327
7400
|
if (!existsSync5(filePath)) continue;
|
|
7328
7401
|
const content = readFileSync3(filePath, PROMPT_CONFIG.ENCODING);
|
|
@@ -7469,10 +7542,10 @@ var AgentFactory = class {
|
|
|
7469
7542
|
* Architecture: Single agent with all tools.
|
|
7470
7543
|
* No sub-agents, no spawn_sub, no nested loops.
|
|
7471
7544
|
*/
|
|
7472
|
-
static createMainAgent(
|
|
7545
|
+
static createMainAgent(shouldAutoApprove = false) {
|
|
7473
7546
|
const state = new SharedState();
|
|
7474
7547
|
const events = new AgentEventEmitter();
|
|
7475
|
-
const approvalGate = new ApprovalGate(
|
|
7548
|
+
const approvalGate = new ApprovalGate(shouldAutoApprove);
|
|
7476
7549
|
const scopeGuard = new ScopeGuard(state);
|
|
7477
7550
|
const toolRegistry = new CategorizedToolRegistry(
|
|
7478
7551
|
state,
|
|
@@ -7484,6 +7557,54 @@ var AgentFactory = class {
|
|
|
7484
7557
|
}
|
|
7485
7558
|
};
|
|
7486
7559
|
|
|
7560
|
+
// src/platform/tui/utils/format.ts
|
|
7561
|
+
var formatDuration = (ms) => {
|
|
7562
|
+
const totalSec = ms / 1e3;
|
|
7563
|
+
if (totalSec < 60) return `${totalSec.toFixed(1)}s`;
|
|
7564
|
+
const minutes = Math.floor(totalSec / 60);
|
|
7565
|
+
const seconds = Math.floor(totalSec % 60);
|
|
7566
|
+
return `${minutes}m ${seconds}s`;
|
|
7567
|
+
};
|
|
7568
|
+
var formatTokens = (count) => {
|
|
7569
|
+
if (count >= 1e6) return (count / 1e6).toFixed(1) + "M";
|
|
7570
|
+
if (count >= 1e3) return (count / 1e3).toFixed(1) + "K";
|
|
7571
|
+
return String(count);
|
|
7572
|
+
};
|
|
7573
|
+
var formatMeta = (ms, tokens) => {
|
|
7574
|
+
const parts = [];
|
|
7575
|
+
if (ms > 0) parts.push(formatDuration(ms));
|
|
7576
|
+
if (tokens > 0) parts.push(`\u2191 ${formatTokens(tokens)} tokens`);
|
|
7577
|
+
return parts.length > 0 ? `(${parts.join(" \xB7 ")})` : "";
|
|
7578
|
+
};
|
|
7579
|
+
var formatInlineStatus = () => {
|
|
7580
|
+
const zombieHunter2 = new ZombieHunter();
|
|
7581
|
+
const healthMonitor2 = new HealthMonitor();
|
|
7582
|
+
const processes = listBackgroundProcesses();
|
|
7583
|
+
const zombies = zombieHunter2.scan();
|
|
7584
|
+
const health = healthMonitor2.check();
|
|
7585
|
+
const statusData = {
|
|
7586
|
+
processes: processes.map((p) => ({
|
|
7587
|
+
id: p.id,
|
|
7588
|
+
role: p.role,
|
|
7589
|
+
description: p.description,
|
|
7590
|
+
purpose: p.purpose,
|
|
7591
|
+
running: p.isRunning,
|
|
7592
|
+
durationMs: p.durationMs,
|
|
7593
|
+
listeningPort: p.listeningPort,
|
|
7594
|
+
exitCode: p.exitCode
|
|
7595
|
+
})),
|
|
7596
|
+
zombies: zombies.map((z) => ({
|
|
7597
|
+
processId: z.processId,
|
|
7598
|
+
orphanedChildren: z.orphanedChildren
|
|
7599
|
+
})),
|
|
7600
|
+
health: health.overall
|
|
7601
|
+
};
|
|
7602
|
+
return JSON.stringify(statusData);
|
|
7603
|
+
};
|
|
7604
|
+
|
|
7605
|
+
// src/platform/tui/hooks/useAgentState.ts
|
|
7606
|
+
import { useState, useRef, useCallback } from "react";
|
|
7607
|
+
|
|
7487
7608
|
// src/shared/constants/thought.ts
|
|
7488
7609
|
var THOUGHT_TYPE = {
|
|
7489
7610
|
THINKING: "thinking",
|
|
@@ -7742,53 +7863,8 @@ var HELP_TEXT = `
|
|
|
7742
7863
|
/auto (toggle approval mode)
|
|
7743
7864
|
`;
|
|
7744
7865
|
|
|
7745
|
-
// src/platform/tui/
|
|
7746
|
-
var
|
|
7747
|
-
const totalSec = ms / 1e3;
|
|
7748
|
-
if (totalSec < 60) return `${totalSec.toFixed(1)}s`;
|
|
7749
|
-
const minutes = Math.floor(totalSec / 60);
|
|
7750
|
-
const seconds = Math.floor(totalSec % 60);
|
|
7751
|
-
return `${minutes}m ${seconds}s`;
|
|
7752
|
-
};
|
|
7753
|
-
var formatTokens = (count) => {
|
|
7754
|
-
if (count >= 1e6) return (count / 1e6).toFixed(1) + "M";
|
|
7755
|
-
if (count >= 1e3) return (count / 1e3).toFixed(1) + "K";
|
|
7756
|
-
return String(count);
|
|
7757
|
-
};
|
|
7758
|
-
var formatMeta = (ms, tokens) => {
|
|
7759
|
-
const parts = [];
|
|
7760
|
-
if (ms > 0) parts.push(formatDuration(ms));
|
|
7761
|
-
if (tokens > 0) parts.push(`\u2191 ${formatTokens(tokens)} tokens`);
|
|
7762
|
-
return parts.length > 0 ? `(${parts.join(" \xB7 ")})` : "";
|
|
7763
|
-
};
|
|
7764
|
-
var formatInlineStatus = () => {
|
|
7765
|
-
const zombieHunter2 = new ZombieHunter();
|
|
7766
|
-
const healthMonitor2 = new HealthMonitor();
|
|
7767
|
-
const processes = listBackgroundProcesses();
|
|
7768
|
-
const zombies = zombieHunter2.scan();
|
|
7769
|
-
const health = healthMonitor2.check();
|
|
7770
|
-
const statusData = {
|
|
7771
|
-
processes: processes.map((p) => ({
|
|
7772
|
-
id: p.id,
|
|
7773
|
-
role: p.role,
|
|
7774
|
-
description: p.description,
|
|
7775
|
-
purpose: p.purpose,
|
|
7776
|
-
running: p.isRunning,
|
|
7777
|
-
durationMs: p.durationMs,
|
|
7778
|
-
listeningPort: p.listeningPort,
|
|
7779
|
-
exitCode: p.exitCode
|
|
7780
|
-
})),
|
|
7781
|
-
zombies: zombies.map((z) => ({
|
|
7782
|
-
processId: z.processId,
|
|
7783
|
-
orphanedChildren: z.orphanedChildren
|
|
7784
|
-
})),
|
|
7785
|
-
health: health.overall
|
|
7786
|
-
};
|
|
7787
|
-
return JSON.stringify(statusData);
|
|
7788
|
-
};
|
|
7789
|
-
|
|
7790
|
-
// src/platform/tui/hooks/useAgent.ts
|
|
7791
|
-
var useAgent = (autoApprove, target) => {
|
|
7866
|
+
// src/platform/tui/hooks/useAgentState.ts
|
|
7867
|
+
var useAgentState = () => {
|
|
7792
7868
|
const [messages, setMessages] = useState([]);
|
|
7793
7869
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
7794
7870
|
const [currentStatus, setCurrentStatus] = useState("");
|
|
@@ -7816,14 +7892,6 @@ var useAgent = (autoApprove, target) => {
|
|
|
7816
7892
|
const retryCountRef = useRef(0);
|
|
7817
7893
|
const tokenAccumRef = useRef(0);
|
|
7818
7894
|
const lastStepTokensRef = useRef(0);
|
|
7819
|
-
const [agent] = useState(() => AgentFactory.createMainAgent(autoApprove));
|
|
7820
|
-
useEffect(() => {
|
|
7821
|
-
if (target) {
|
|
7822
|
-
agent.addTarget(target);
|
|
7823
|
-
agent.setScope([target]);
|
|
7824
|
-
}
|
|
7825
|
-
}, [agent, target]);
|
|
7826
|
-
const eventsRef = useRef(agent.getEventEmitter());
|
|
7827
7895
|
const addMessage = useCallback((type, content) => {
|
|
7828
7896
|
const id = Math.random().toString(36).substring(7);
|
|
7829
7897
|
setMessages((prev) => [...prev, { id, type, content, timestamp: /* @__PURE__ */ new Date() }]);
|
|
@@ -7847,56 +7915,63 @@ var useAgent = (autoApprove, target) => {
|
|
|
7847
7915
|
setElapsedTime(0);
|
|
7848
7916
|
}
|
|
7849
7917
|
}, []);
|
|
7850
|
-
const
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7918
|
+
const clearAllTimers = useCallback(() => {
|
|
7919
|
+
if (timerRef.current) clearInterval(timerRef.current);
|
|
7920
|
+
if (retryCountdownRef.current) clearInterval(retryCountdownRef.current);
|
|
7921
|
+
}, []);
|
|
7922
|
+
return {
|
|
7923
|
+
// State
|
|
7924
|
+
messages,
|
|
7925
|
+
setMessages,
|
|
7926
|
+
isProcessing,
|
|
7927
|
+
setIsProcessing,
|
|
7928
|
+
currentStatus,
|
|
7929
|
+
setCurrentStatus,
|
|
7930
|
+
elapsedTime,
|
|
7931
|
+
retryState,
|
|
7932
|
+
setRetryState,
|
|
7933
|
+
currentTokens,
|
|
7934
|
+
setCurrentTokens,
|
|
7935
|
+
inputRequest,
|
|
7936
|
+
setInputRequest,
|
|
7937
|
+
stats,
|
|
7938
|
+
setStats,
|
|
7939
|
+
// Refs
|
|
7940
|
+
lastResponseMetaRef,
|
|
7941
|
+
timerRef,
|
|
7942
|
+
retryCountdownRef,
|
|
7943
|
+
retryCountRef,
|
|
7944
|
+
tokenAccumRef,
|
|
7945
|
+
lastStepTokensRef,
|
|
7946
|
+
// Helpers
|
|
7947
|
+
addMessage,
|
|
7948
|
+
resetCumulativeCounters,
|
|
7949
|
+
manageTimer,
|
|
7950
|
+
clearAllTimers
|
|
7951
|
+
};
|
|
7952
|
+
};
|
|
7953
|
+
|
|
7954
|
+
// src/platform/tui/hooks/useAgentEvents.ts
|
|
7955
|
+
import { useEffect } from "react";
|
|
7956
|
+
var useAgentEvents = (agent, eventsRef, state) => {
|
|
7957
|
+
const {
|
|
7958
|
+
addMessage,
|
|
7959
|
+
setCurrentStatus,
|
|
7960
|
+
setRetryState,
|
|
7961
|
+
setCurrentTokens,
|
|
7962
|
+
setStats,
|
|
7963
|
+
lastResponseMetaRef,
|
|
7964
|
+
retryCountdownRef,
|
|
7965
|
+
retryCountRef,
|
|
7966
|
+
tokenAccumRef,
|
|
7967
|
+
lastStepTokensRef,
|
|
7968
|
+
clearAllTimers
|
|
7969
|
+
} = state;
|
|
7882
7970
|
useEffect(() => {
|
|
7883
7971
|
const events = eventsRef.current;
|
|
7884
7972
|
const onToolCall = (e) => {
|
|
7885
7973
|
setCurrentStatus(`Executing: ${e.data.toolName}`);
|
|
7886
|
-
|
|
7887
|
-
try {
|
|
7888
|
-
if (e.data.input && Object.keys(e.data.input).length > 0) {
|
|
7889
|
-
if (e.data.toolName === TOOL_NAMES.RUN_CMD && e.data.input.command) {
|
|
7890
|
-
inputStr = String(e.data.input.command);
|
|
7891
|
-
} else {
|
|
7892
|
-
const str = JSON.stringify(e.data.input);
|
|
7893
|
-
if (str !== "{}") {
|
|
7894
|
-
inputStr = str.length > TUI_DISPLAY_LIMITS.toolInputPreview ? str.substring(0, TUI_DISPLAY_LIMITS.toolInputTruncated) + "..." : str;
|
|
7895
|
-
}
|
|
7896
|
-
}
|
|
7897
|
-
}
|
|
7898
|
-
} catch {
|
|
7899
|
-
}
|
|
7974
|
+
const inputStr = formatToolInput(e.data.toolName, e.data.input);
|
|
7900
7975
|
addMessage("tool", inputStr ? `${e.data.toolName} ${inputStr}` : e.data.toolName);
|
|
7901
7976
|
};
|
|
7902
7977
|
const onToolResult = (e) => {
|
|
@@ -7912,55 +7987,42 @@ var useAgent = (autoApprove, target) => {
|
|
|
7912
7987
|
lastResponseMetaRef.current = { durationMs: e.data.durationMs, tokens: e.data.tokens };
|
|
7913
7988
|
};
|
|
7914
7989
|
const onRetry = (e) => {
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
remaining -= 1;
|
|
7924
|
-
if (remaining <= 0) {
|
|
7925
|
-
setRetryState((prev) => ({ ...prev, isRetrying: false, countdown: 0 }));
|
|
7926
|
-
if (retryCountdownRef.current) clearInterval(retryCountdownRef.current);
|
|
7927
|
-
} else {
|
|
7928
|
-
setRetryState((prev) => ({ ...prev, countdown: remaining }));
|
|
7929
|
-
}
|
|
7930
|
-
}, 1e3);
|
|
7990
|
+
handleRetry(e, addMessage, setRetryState, retryCountdownRef, retryCountRef);
|
|
7991
|
+
};
|
|
7992
|
+
const onThink = (e) => {
|
|
7993
|
+
const t = e.data.thought;
|
|
7994
|
+
setCurrentStatus(t.length > TUI_DISPLAY_LIMITS.statusThoughtPreview ? t.substring(0, TUI_DISPLAY_LIMITS.statusThoughtTruncated) + "..." : t);
|
|
7995
|
+
};
|
|
7996
|
+
const onError = (e) => {
|
|
7997
|
+
addMessage("error", e.data.message || "An error occurred");
|
|
7931
7998
|
};
|
|
7932
7999
|
const onUsageUpdate = (e) => {
|
|
7933
8000
|
const stepTokens = e.data.inputTokens + e.data.outputTokens;
|
|
7934
|
-
if (stepTokens < lastStepTokensRef.current)
|
|
8001
|
+
if (stepTokens < lastStepTokensRef.current) {
|
|
8002
|
+
tokenAccumRef.current += lastStepTokensRef.current;
|
|
8003
|
+
}
|
|
7935
8004
|
lastStepTokensRef.current = stepTokens;
|
|
7936
8005
|
setCurrentTokens(tokenAccumRef.current + stepTokens);
|
|
7937
8006
|
};
|
|
7938
8007
|
setInputHandler((p) => {
|
|
7939
|
-
return new Promise((resolve) =>
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
8008
|
+
return new Promise((resolve) => {
|
|
8009
|
+
const isPassword = /password|passphrase/i.test(p);
|
|
8010
|
+
const inputType = /sudo/i.test(p) ? "sudo_password" : isPassword ? "password" : "text";
|
|
8011
|
+
state.setInputRequest({
|
|
8012
|
+
isActive: true,
|
|
8013
|
+
prompt: p.trim(),
|
|
8014
|
+
isPassword,
|
|
8015
|
+
inputType,
|
|
8016
|
+
resolve
|
|
8017
|
+
});
|
|
8018
|
+
});
|
|
7946
8019
|
});
|
|
7947
8020
|
setCredentialHandler((request) => {
|
|
7948
8021
|
return new Promise((resolve) => {
|
|
7949
8022
|
const hiddenTypes = ["password", "sudo_password", "ssh_password", "passphrase", "api_key", "credential"];
|
|
7950
8023
|
const isPassword = hiddenTypes.includes(request.type);
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
displayPrompt = `${displayPrompt}
|
|
7954
|
-
Context: ${request.context}`;
|
|
7955
|
-
}
|
|
7956
|
-
if (request.optional) {
|
|
7957
|
-
displayPrompt += " (optional - press Enter to skip)";
|
|
7958
|
-
}
|
|
7959
|
-
if (request.options && request.options.length > 0) {
|
|
7960
|
-
displayPrompt += `
|
|
7961
|
-
Options: ${request.options.join(", ")}`;
|
|
7962
|
-
}
|
|
7963
|
-
setInputRequest({
|
|
8024
|
+
const displayPrompt = buildCredentialPrompt(request);
|
|
8025
|
+
state.setInputRequest({
|
|
7964
8026
|
isActive: true,
|
|
7965
8027
|
prompt: displayPrompt,
|
|
7966
8028
|
isPassword,
|
|
@@ -7973,35 +8035,25 @@ Options: ${request.options.join(", ")}`;
|
|
|
7973
8035
|
});
|
|
7974
8036
|
});
|
|
7975
8037
|
setCommandEventEmitter((event) => {
|
|
7976
|
-
const
|
|
7977
|
-
[COMMAND_EVENT_TYPES.TOOL_MISSING]: "\u26A0\uFE0F",
|
|
7978
|
-
[COMMAND_EVENT_TYPES.TOOL_INSTALL]: "\u{1F4E6}",
|
|
7979
|
-
[COMMAND_EVENT_TYPES.TOOL_INSTALLED]: "\u2705",
|
|
7980
|
-
[COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED]: "\u274C",
|
|
7981
|
-
[COMMAND_EVENT_TYPES.TOOL_RETRY]: "\u{1F504}",
|
|
7982
|
-
[COMMAND_EVENT_TYPES.COMMAND_START]: "\u25B6",
|
|
7983
|
-
[COMMAND_EVENT_TYPES.COMMAND_SUCCESS]: "\u2713",
|
|
7984
|
-
[COMMAND_EVENT_TYPES.COMMAND_FAILED]: "\u2717",
|
|
7985
|
-
[COMMAND_EVENT_TYPES.COMMAND_ERROR]: "\u274C",
|
|
7986
|
-
[COMMAND_EVENT_TYPES.INPUT_REQUIRED]: "\u{1F510}"
|
|
7987
|
-
};
|
|
7988
|
-
const icon = icons[event.type] || "\u2022";
|
|
8038
|
+
const icon = getCommandEventIcon(event.type);
|
|
7989
8039
|
const msg = event.detail ? `${icon} ${event.message}
|
|
7990
8040
|
${event.detail}` : `${icon} ${event.message}`;
|
|
7991
8041
|
addMessage("system", msg);
|
|
7992
8042
|
});
|
|
7993
8043
|
const updateStats = () => {
|
|
7994
8044
|
const s = agent.getState();
|
|
7995
|
-
setStats({
|
|
8045
|
+
setStats({
|
|
8046
|
+
phase: agent.getPhase(),
|
|
8047
|
+
targets: s.getTargets().size,
|
|
8048
|
+
findings: s.getFindings().length,
|
|
8049
|
+
todo: s.getTodo().length
|
|
8050
|
+
});
|
|
7996
8051
|
};
|
|
7997
8052
|
events.on(EVENT_TYPES.TOOL_CALL, onToolCall);
|
|
7998
8053
|
events.on(EVENT_TYPES.TOOL_RESULT, onToolResult);
|
|
7999
|
-
events.on(EVENT_TYPES.THINK,
|
|
8000
|
-
const t = e.data.thought;
|
|
8001
|
-
setCurrentStatus(t.length > TUI_DISPLAY_LIMITS.statusThoughtPreview ? t.substring(0, TUI_DISPLAY_LIMITS.statusThoughtTruncated) + "..." : t);
|
|
8002
|
-
});
|
|
8054
|
+
events.on(EVENT_TYPES.THINK, onThink);
|
|
8003
8055
|
events.on(EVENT_TYPES.COMPLETE, onComplete);
|
|
8004
|
-
events.on(EVENT_TYPES.ERROR,
|
|
8056
|
+
events.on(EVENT_TYPES.ERROR, onError);
|
|
8005
8057
|
events.on(EVENT_TYPES.RETRY, onRetry);
|
|
8006
8058
|
events.on(EVENT_TYPES.USAGE_UPDATE, onUsageUpdate);
|
|
8007
8059
|
events.on(EVENT_TYPES.STATE_CHANGE, updateStats);
|
|
@@ -8009,10 +8061,154 @@ Options: ${request.options.join(", ")}`;
|
|
|
8009
8061
|
updateStats();
|
|
8010
8062
|
return () => {
|
|
8011
8063
|
events.removeAllListeners();
|
|
8012
|
-
|
|
8013
|
-
if (retryCountdownRef.current) clearInterval(retryCountdownRef.current);
|
|
8064
|
+
clearAllTimers();
|
|
8014
8065
|
};
|
|
8015
|
-
}, [
|
|
8066
|
+
}, [
|
|
8067
|
+
agent,
|
|
8068
|
+
addMessage,
|
|
8069
|
+
setCurrentStatus,
|
|
8070
|
+
setRetryState,
|
|
8071
|
+
setCurrentTokens,
|
|
8072
|
+
setStats,
|
|
8073
|
+
lastResponseMetaRef,
|
|
8074
|
+
retryCountdownRef,
|
|
8075
|
+
retryCountRef,
|
|
8076
|
+
tokenAccumRef,
|
|
8077
|
+
lastStepTokensRef,
|
|
8078
|
+
clearAllTimers,
|
|
8079
|
+
state
|
|
8080
|
+
]);
|
|
8081
|
+
};
|
|
8082
|
+
function formatToolInput(toolName, input) {
|
|
8083
|
+
if (!input || Object.keys(input).length === 0) return "";
|
|
8084
|
+
try {
|
|
8085
|
+
if (toolName === TOOL_NAMES.RUN_CMD && input.command) {
|
|
8086
|
+
return String(input.command);
|
|
8087
|
+
}
|
|
8088
|
+
const str = JSON.stringify(input);
|
|
8089
|
+
if (str === "{}") return "";
|
|
8090
|
+
return str.length > TUI_DISPLAY_LIMITS.toolInputPreview ? str.substring(0, TUI_DISPLAY_LIMITS.toolInputTruncated) + "..." : str;
|
|
8091
|
+
} catch {
|
|
8092
|
+
return "";
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
function handleRetry(e, addMessage, setRetryState, retryCountdownRef, retryCountRef) {
|
|
8096
|
+
const delaySec = Math.ceil(e.data.delayMs / 1e3);
|
|
8097
|
+
retryCountRef.current += 1;
|
|
8098
|
+
const retryNum = retryCountRef.current;
|
|
8099
|
+
addMessage("system", `\u27F3 Retry #${retryNum} \xB7 ${e.data.error} \xB7 waiting ${delaySec}s...`);
|
|
8100
|
+
setRetryState({
|
|
8101
|
+
isRetrying: true,
|
|
8102
|
+
attempt: retryNum,
|
|
8103
|
+
maxRetries: e.data.maxRetries,
|
|
8104
|
+
delayMs: e.data.delayMs,
|
|
8105
|
+
error: e.data.error,
|
|
8106
|
+
countdown: delaySec
|
|
8107
|
+
});
|
|
8108
|
+
if (retryCountdownRef.current) clearInterval(retryCountdownRef.current);
|
|
8109
|
+
let remaining = delaySec;
|
|
8110
|
+
retryCountdownRef.current = setInterval(() => {
|
|
8111
|
+
remaining -= 1;
|
|
8112
|
+
if (remaining <= 0) {
|
|
8113
|
+
setRetryState((prev) => ({ ...prev, isRetrying: false, countdown: 0 }));
|
|
8114
|
+
if (retryCountdownRef.current) clearInterval(retryCountdownRef.current);
|
|
8115
|
+
} else {
|
|
8116
|
+
setRetryState((prev) => ({ ...prev, countdown: remaining }));
|
|
8117
|
+
}
|
|
8118
|
+
}, 1e3);
|
|
8119
|
+
}
|
|
8120
|
+
function buildCredentialPrompt(request) {
|
|
8121
|
+
let displayPrompt = request.prompt || "Enter input";
|
|
8122
|
+
if (request.context) {
|
|
8123
|
+
displayPrompt = `${displayPrompt}
|
|
8124
|
+
Context: ${request.context}`;
|
|
8125
|
+
}
|
|
8126
|
+
if (request.optional) {
|
|
8127
|
+
displayPrompt += " (optional - press Enter to skip)";
|
|
8128
|
+
}
|
|
8129
|
+
if (request.options && request.options.length > 0) {
|
|
8130
|
+
displayPrompt += `
|
|
8131
|
+
Options: ${request.options.join(", ")}`;
|
|
8132
|
+
}
|
|
8133
|
+
return displayPrompt;
|
|
8134
|
+
}
|
|
8135
|
+
function getCommandEventIcon(eventType) {
|
|
8136
|
+
const icons = {
|
|
8137
|
+
[COMMAND_EVENT_TYPES.TOOL_MISSING]: "\u26A0\uFE0F",
|
|
8138
|
+
[COMMAND_EVENT_TYPES.TOOL_INSTALL]: "\u{1F4E6}",
|
|
8139
|
+
[COMMAND_EVENT_TYPES.TOOL_INSTALLED]: "\u2705",
|
|
8140
|
+
[COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED]: "\u274C",
|
|
8141
|
+
[COMMAND_EVENT_TYPES.TOOL_RETRY]: "\u{1F504}",
|
|
8142
|
+
[COMMAND_EVENT_TYPES.COMMAND_START]: "\u25B6",
|
|
8143
|
+
[COMMAND_EVENT_TYPES.COMMAND_SUCCESS]: "\u2713",
|
|
8144
|
+
[COMMAND_EVENT_TYPES.COMMAND_FAILED]: "\u2717",
|
|
8145
|
+
[COMMAND_EVENT_TYPES.COMMAND_ERROR]: "\u274C",
|
|
8146
|
+
[COMMAND_EVENT_TYPES.INPUT_REQUIRED]: "\u{1F510}"
|
|
8147
|
+
};
|
|
8148
|
+
return icons[eventType] || "\u2022";
|
|
8149
|
+
}
|
|
8150
|
+
|
|
8151
|
+
// src/platform/tui/hooks/useAgent.ts
|
|
8152
|
+
var useAgent = (shouldAutoApprove, target) => {
|
|
8153
|
+
const [agent] = useState2(() => AgentFactory.createMainAgent(shouldAutoApprove));
|
|
8154
|
+
const eventsRef = useRef2(agent.getEventEmitter());
|
|
8155
|
+
const state = useAgentState();
|
|
8156
|
+
const {
|
|
8157
|
+
messages,
|
|
8158
|
+
setMessages,
|
|
8159
|
+
isProcessing,
|
|
8160
|
+
setIsProcessing,
|
|
8161
|
+
currentStatus,
|
|
8162
|
+
elapsedTime,
|
|
8163
|
+
retryState,
|
|
8164
|
+
currentTokens,
|
|
8165
|
+
inputRequest,
|
|
8166
|
+
setInputRequest,
|
|
8167
|
+
stats,
|
|
8168
|
+
lastResponseMetaRef,
|
|
8169
|
+
addMessage,
|
|
8170
|
+
manageTimer,
|
|
8171
|
+
resetCumulativeCounters
|
|
8172
|
+
} = state;
|
|
8173
|
+
useEffect2(() => {
|
|
8174
|
+
if (target) {
|
|
8175
|
+
agent.addTarget(target);
|
|
8176
|
+
agent.setScope([target]);
|
|
8177
|
+
}
|
|
8178
|
+
}, [agent, target]);
|
|
8179
|
+
useAgentEvents(agent, eventsRef, state);
|
|
8180
|
+
const executeTask = useCallback2(async (task) => {
|
|
8181
|
+
setIsProcessing(true);
|
|
8182
|
+
manageTimer("start");
|
|
8183
|
+
state.setCurrentStatus("Thinking");
|
|
8184
|
+
resetCumulativeCounters();
|
|
8185
|
+
try {
|
|
8186
|
+
const response = await agent.execute(task);
|
|
8187
|
+
const meta = lastResponseMetaRef.current;
|
|
8188
|
+
const suffix = meta ? ` ${formatMeta(meta.durationMs || 0, (meta.tokens?.input || 0) + (meta.tokens?.output || 0))}` : "";
|
|
8189
|
+
addMessage("ai", response + suffix);
|
|
8190
|
+
} catch (e) {
|
|
8191
|
+
addMessage("error", e instanceof Error ? e.message : String(e));
|
|
8192
|
+
} finally {
|
|
8193
|
+
manageTimer("stop");
|
|
8194
|
+
setIsProcessing(false);
|
|
8195
|
+
state.setCurrentStatus("");
|
|
8196
|
+
}
|
|
8197
|
+
}, [agent, addMessage, manageTimer, resetCumulativeCounters, setIsProcessing, lastResponseMetaRef, state]);
|
|
8198
|
+
const abort = useCallback2(() => {
|
|
8199
|
+
agent.abort();
|
|
8200
|
+
setIsProcessing(false);
|
|
8201
|
+
manageTimer("stop");
|
|
8202
|
+
state.setCurrentStatus("");
|
|
8203
|
+
addMessage("system", "Interrupted");
|
|
8204
|
+
}, [agent, addMessage, manageTimer, setIsProcessing, state]);
|
|
8205
|
+
const cancelInputRequest = useCallback2(() => {
|
|
8206
|
+
if (inputRequest.isActive && inputRequest.resolve) {
|
|
8207
|
+
inputRequest.resolve(null);
|
|
8208
|
+
setInputRequest({ isActive: false, prompt: "", isPassword: false, resolve: null });
|
|
8209
|
+
addMessage("system", "Input cancelled");
|
|
8210
|
+
}
|
|
8211
|
+
}, [inputRequest, setInputRequest, addMessage]);
|
|
8016
8212
|
return {
|
|
8017
8213
|
agent,
|
|
8018
8214
|
messages,
|
|
@@ -8222,14 +8418,14 @@ var MessageList = ({ messages }) => {
|
|
|
8222
8418
|
import { Box as Box3, Text as Text4 } from "ink";
|
|
8223
8419
|
|
|
8224
8420
|
// src/platform/tui/components/MusicSpinner.tsx
|
|
8225
|
-
import { useState as
|
|
8421
|
+
import { useState as useState3, useEffect as useEffect3 } from "react";
|
|
8226
8422
|
import { Text as Text3 } from "ink";
|
|
8227
8423
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
8228
8424
|
var FRAMES = ["\u2669", "\u266A", "\u266B", "\u266C", "\u266B", "\u266A"];
|
|
8229
8425
|
var INTERVAL = 150;
|
|
8230
8426
|
var MusicSpinner = ({ color }) => {
|
|
8231
|
-
const [index, setIndex] =
|
|
8232
|
-
|
|
8427
|
+
const [index, setIndex] = useState3(0);
|
|
8428
|
+
useEffect3(() => {
|
|
8233
8429
|
const timer = setInterval(() => {
|
|
8234
8430
|
setIndex((i) => (i + 1) % FRAMES.length);
|
|
8235
8431
|
}, INTERVAL);
|
|
@@ -8388,9 +8584,9 @@ var footer_default = Footer;
|
|
|
8388
8584
|
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
8389
8585
|
var App = ({ autoApprove = false, target }) => {
|
|
8390
8586
|
const { exit } = useApp();
|
|
8391
|
-
const [input, setInput] =
|
|
8392
|
-
const [secretInput, setSecretInput] =
|
|
8393
|
-
const [autoApproveMode, setAutoApproveMode] =
|
|
8587
|
+
const [input, setInput] = useState4("");
|
|
8588
|
+
const [secretInput, setSecretInput] = useState4("");
|
|
8589
|
+
const [autoApproveMode, setAutoApproveMode] = useState4(autoApprove);
|
|
8394
8590
|
const {
|
|
8395
8591
|
agent,
|
|
8396
8592
|
messages,
|
|
@@ -8408,14 +8604,14 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
8408
8604
|
cancelInputRequest,
|
|
8409
8605
|
addMessage
|
|
8410
8606
|
} = useAgent(autoApproveMode, target);
|
|
8411
|
-
const handleExit =
|
|
8607
|
+
const handleExit = useCallback3(() => {
|
|
8412
8608
|
if (inputRequest.isActive && inputRequest.resolve) inputRequest.resolve(null);
|
|
8413
8609
|
cleanupAllProcesses().catch(() => {
|
|
8414
8610
|
});
|
|
8415
8611
|
exit();
|
|
8416
8612
|
setTimeout(() => process.exit(0), DISPLAY_LIMITS.EXIT_DELAY);
|
|
8417
8613
|
}, [exit, inputRequest, cancelInputRequest]);
|
|
8418
|
-
const handleCommand =
|
|
8614
|
+
const handleCommand = useCallback3(async (cmd, args) => {
|
|
8419
8615
|
switch (cmd) {
|
|
8420
8616
|
case UI_COMMANDS.HELP:
|
|
8421
8617
|
case UI_COMMANDS.HELP_SHORT:
|
|
@@ -8497,7 +8693,7 @@ ${procData.stdout || "(no output)"}
|
|
|
8497
8693
|
addMessage("error", `Unknown command: /${cmd}`);
|
|
8498
8694
|
}
|
|
8499
8695
|
}, [agent, addMessage, executeTask, setMessages, handleExit, autoApproveMode]);
|
|
8500
|
-
const handleSubmit =
|
|
8696
|
+
const handleSubmit = useCallback3(async (value) => {
|
|
8501
8697
|
const trimmed = value.trim();
|
|
8502
8698
|
if (!trimmed) return;
|
|
8503
8699
|
setInput("");
|
|
@@ -8509,7 +8705,7 @@ ${procData.stdout || "(no output)"}
|
|
|
8509
8705
|
await executeTask(trimmed);
|
|
8510
8706
|
}
|
|
8511
8707
|
}, [addMessage, executeTask, handleCommand]);
|
|
8512
|
-
const handleSecretSubmit =
|
|
8708
|
+
const handleSecretSubmit = useCallback3((value) => {
|
|
8513
8709
|
if (!inputRequest.isActive || !inputRequest.resolve) return;
|
|
8514
8710
|
const displayText = inputRequest.isPassword ? "\u2022".repeat(value.length) : value;
|
|
8515
8711
|
const promptLabel = inputRequest.prompt || "Input";
|
|
@@ -8525,7 +8721,7 @@ ${procData.stdout || "(no output)"}
|
|
|
8525
8721
|
}
|
|
8526
8722
|
if (key.ctrl && ch === "c") handleExit();
|
|
8527
8723
|
});
|
|
8528
|
-
|
|
8724
|
+
useEffect4(() => {
|
|
8529
8725
|
const onSignal = () => handleExit();
|
|
8530
8726
|
process.on("SIGINT", onSignal);
|
|
8531
8727
|
process.on("SIGTERM", onSignal);
|